Skip to content

Repository files navigation

Refract

CI License: MIT Node Runtime dependencies Code style: Prettier

A Claude Code plugin that refines your prompts as you submit them.

Why Refract over other prompt-improver hooks: every failure fails open (your prompt always proceeds untouched, exit 0, no exceptions), zero runtime dependencies, no API key required (it rides Claude Code's own login), every decision is one auditable line in a local log, and injected refinements carry explicit precedence framing — your original wording always wins. Refinements are grounded in what is actually true: the session transcript, your profile, and the repository's live git state. A UserPromptSubmit hook decides — deterministically, in-process — whether a prompt is worth refining. Trivial turns (acknowledgments, option picks, slash commands, code pastes, bare paths/URLs) skip instantly with zero latency and zero API cost. Substantive but underspecified prompts get a structured expansion injected alongside your original wording; genuinely ambiguous ones get suggested clarifying questions instead.

Your prompt is never removed or altered. Injected text carries explicit precedence framing: your original wording wins wherever the two conflict. Every decision is one line in a local audit log, and every failure fails open — on any error, timeout, or missing credential the prompt proceeds untouched and the hook exits 0.

Status

  • Works: triage, both engines (CLI and direct API), expansion and clarify injection, the audit log, /refract:refine, /refract:profile, /refract:stats, the eval harness, per-project .refract.md. 69 offline tests cover all of it.
  • Rough: the CLI engine pays one to three seconds of claude process startup per engaged prompt; Windows setups using nvm/fnm often hide node from non-interactive shells (see Requirements).
  • Planned: an opt-in rewrite mode, blocked on Claude Code supporting updatedPrompt in UserPromptSubmit hooks (anthropics/claude-code#27365).

Requirements

  • Node.js 18 or newer on the PATH seen by non-interactive shells. Claude Code launches the hook as a bare node command. Version managers such as nvm and fnm on Windows commonly make node available only in interactive shells, leaving it off the PATH non-interactive processes see — this is the most common installation failure. Verify with node --version in a plain cmd.exe window (not your configured terminal profile).
  • No API key required. By default Refract reuses the authentication Claude Code already has.

Install

/plugin marketplace add TevBenji/refract
/plugin install refract@refract

Or clone this repo and add the local directory as a marketplace. No build step is needed — hooks/refract.mjs is a committed, zero-dependency bundle.

Authentication: your existing Claude Code login, no key needed

Refract makes one small model call per engaged prompt. The engine key in config.json picks how:

  • "auto" (default) — no configuration at all: Refract shells out to claude -p (the CLI's print mode), which runs on whatever authentication Claude Code itself is using — subscription OAuth login, an API key, Amazon Bedrock, or Google Vertex. Subscription users see the call counted against their plan usage like any other turn. If you have deliberately set REFRACT_API_KEY, auto switches to the direct API engine below.
  • "cli" — always use claude -p, even when a key is set.
  • "api" — call https://api.anthropic.com/v1/messages directly with an API key. This shaves the CLI's process-startup latency (roughly one to three seconds per engaged call) and bills the key instead of your plan.

Direct API mode: REFRACT_API_KEY (preferred) or ANTHROPIC_API_KEY

In api mode Refract reads REFRACT_API_KEY first; when that is absent or empty it falls back to ANTHROPIC_API_KEY. Prefer the dedicated variable: Claude Code reads ANTHROPIC_API_KEY for its own billing path, so a machine-wide export you intended for Refract can be silently consumed by Claude Code itself. REFRACT_API_KEY keeps the two separate.

Windows (PowerShell, persistent for your user):

[Environment]::SetEnvironmentVariable('REFRACT_API_KEY', 'sk-ant-...', 'User')

or cmd.exe:

setx REFRACT_API_KEY sk-ant-...

Restart Claude Code afterwards so the new environment is picked up. If engine is "api" and neither variable is set, every prompt passes through untouched and the audit log records no_api_key. In the default auto mode that situation cannot arise — with no key present, the CLI engine is used.

What leaves your machine

Only on engaged prompts, and only to your configured Claude provider — https://api.anthropic.com in api mode and for CLI users authenticated with Anthropic; your own Bedrock or Vertex endpoint when Claude Code is configured for those:

  • the prompt text
  • a capped excerpt of the recent session transcript (default at most 12,000 characters)
  • the contents of your profile.md (plus .refract.md when the project has one)
  • the working directory path
  • the repository state: current branch and git status --short output, capped at 1,500 characters (empty outside a git repo)

Nothing else is sent, and no other endpoint is ever contacted. Triage-skipped prompts make no network request at all. Keep secrets out of profile.md.

Data and log location

%CLAUDE_PLUGIN_DATA% when Claude Code sets it, otherwise ~/.claude/refract/ (on Windows: C:\Users\<you>\.claude\refract\):

File Purpose
profile.md Your stack, conventions, and definition of done — seeded with a neutral template on first run; edit it
config.json Settings — seeded with defaults on first run
log.jsonl One JSON line per prompt submission: verdict, triage rule, latency, token counts, warnings
log.1.jsonl Single rotated generation, produced when log.jsonl exceeds logMaxMB

This directory survives plugin updates; nothing is ever written inside the plugin's install directory.

Bypass and force prefixes

Prefix any prompt with raw: (configurable via bypassPrefix in config.json) to skip refinement for that one prompt:

raw: just tell me a joke

Prefix with refine: (configurable via forcePrefix) to force refinement even on prompts triage would normally skip — long pastes, symbol-dense text, mostly-fenced content:

refine: <a 10,000-character stack trace> — what should I actually be asking here?

Matching is case-insensitive and both prefixes are left in your prompt text.

Setting the environment variable REFRACT_SKIP=1 disables the hook entirely for that process — Refract sets it on its own inner CLI session as a recursion guard, and you can set it yourself to switch the hook off for a whole Claude Code session.

Project profile (.refract.md)

Drop a .refract.md file in a project's root and Refract appends it (capped at 4 KB) to your profile for prompts submitted from that directory. Commit it to share per-repo conventions — stack, style, definition of done — with everyone using the plugin. Your global profile.md still applies everywhere.

Cost

An engaged call runs roughly 4,000–5,000 input tokens at the default contextCharBudget of 12,000 characters (meta prompt + context excerpt + profile + prompt), plus up to 1,000 output tokens, on the configured model. In the default CLI engine this counts against your Claude plan usage (or your configured provider); in api mode it bills the key. The input_tokens and output_tokens fields in log.jsonl are the meter — read a few hundred real records before tuning contextCharBudget, maxTranscriptTurns, or perMessageCharCap, rather than guessing.

Latency: the CLI engine pays claude process startup (roughly one to three seconds) on top of the model call; the api engine is a single HTTPS round trip. Both are bounded by selfTimeoutMs and fail open to an untouched prompt on expiry.

To hard-cap spend, set maxEngagedPerDay in config.json: once that many engaged calls have run in the current UTC day, further prompts pass through untouched and the log records budget_exceeded. The default 0 means unlimited.

Model pinning

The default model is the claude-haiku-4-5 alias, which follows Anthropic's latest Haiku snapshot and picks up improvements without action. For behavior that stays fixed until you move it, set the dated snapshot in config.json:

{ "model": "claude-haiku-4-5-20251001" }

Tuning the meta prompt: the eval harness

Refract's audit log doubles as a regression set for the meta prompt. Set logFullPrompt: true in config.json to store full prompt text (locally only), collect some real usage, then:

node scripts/eval.mjs                     # replay logged prompts, compare verdicts to the log
node scripts/eval.mjs --meta variant.txt  # try a candidate meta prompt against the same prompts

Each replay makes one model call per prompt on your configured engine, prints the verdict distribution, and flags every prompt whose verdict changed. Never ship a meta-prompt change that flips verdicts you liked. Without logFullPrompt the harness falls back to the 200-character prompt_head (marked [head] in the output), which is a weaker signal.

Roadmap

UserPromptSubmit hooks currently cannot replace prompt text, only add context alongside it — which is why Refract's precedence-framed injection design exists. If anthropics/claude-code#27365 (updatedPrompt support) lands, an opt-in rewrite mode becomes possible.

Resume behavior

Injected expansions become part of the session transcript, so claude --resume replays previously injected context along with the rest of the session. This is harmless — the precedence framing still applies — but expect to see past expansions again in resumed sessions.

Commands

  • /refract:refine <draft prompt> — asks the main Claude model (not the hook) to produce a structured spec — objective, scope, constraints, definition of done, open questions — for your review before anything runs.
  • /refract:profile [instruction] — show your stored profile; with no argument it reviews it and suggests improvements grounded in the current project, with an instruction it edits the file and writes it back.
  • /refract:stats — summarize the local audit log: skip/engage totals, verdict breakdown, latency, token spend, and the last few decisions.

Slash commands are triage-skipped, so none of these cost anything at the hook layer.

Configuration reference (config.json)

Key Default Meaning
enabled true false disables the plugin entirely (instant pass-through)
engine "auto" auto = claude -p on your existing login, or direct API when REFRACT_API_KEY is set; cli / api force one
notify "refined" Visible status line per decision: refined = only when something was injected, all = every prompt (including skips), off = never
model "claude-haiku-4-5" Rewriter model
maxTranscriptTurns 10 Max user/assistant messages of session context
perMessageCharCap 1200 Per-message truncation (assistant: tail, user: head)
contextCharBudget 12000 Total context excerpt cap
selfTimeoutMs 10000 Self-imposed rewriter timeout; clamped to 10,000 max
maxRewriteWords 250 Rewrite length cap (sentence-boundary truncation)
bypassPrefix "raw:" Per-prompt bypass prefix
forcePrefix "refine:" Per-prompt force-refinement prefix (overrides triage skips)
maxEngagedPerDay 0 Hard cap on engaged calls per UTC day; 0 = unlimited
logFullPrompt false Store the full prompt text (not just a 200-char head) in each audit record, locally, for the eval harness
logMaxMB 20 Log rotation threshold

Keys absent from config.json take these defaults.

Development

npm install
npm test        # builds hooks/refract.mjs, then runs the offline checks (node --test)
npm run typecheck

All checks are offline (injected fetch, temporary stores under the OS temp dir) and use the built-in node:test runner. Dev dependencies only; the shipped bundle has zero runtime dependencies.

Manual integration checks, not part of the offline suite:

  • launch the hook from cmd.exe, PowerShell, and bash with a small JSON payload on stdin and confirm exit code 0;
  • triage-skip end-to-end wall time under 500 ms (Node process startup dominates; Windows real-time antivirus can consume 150–300 ms of that alone);
  • engaged wall time under selfTimeoutMs plus startup overhead;
  • kill the process mid-flight and confirm no log.jsonl line was written and the prompt proceeded unmodified.

About

Claude Code plugin that refines your prompts as you submit them — deterministic triage, fail-open by contract, zero runtime dependencies.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages