Distill — compact command output
Run noisy commands through repowise distill for an errors-first, fully reversible rendering — 60–90% fewer tokens on tests, builds, git, and searches, with zero error-line loss.
Most of what an AI coding agent reads from a shell command is noise: 300 lines of passing tests around 4 failures, full commit bodies for "what changed recently". Distill compresses that output before the agent reads it — errors-first, structure preserved, and every omission reversible. It's a capability built on the intelligence layers (symbol bounds, graph centrality, hotspots decide what to keep), not a layer of its own.
Guarantees, enforced by the engine:
- Every error/failure-classified line in the raw output survives.
- Raw output is stored before any marker renders —
repowise expandalways round-trips it. - Any filter problem falls back to the raw output, unchanged.
- Output is only distilled when it actually gets smaller (net-positive guard) — small outputs pass through untouched.
- Exit codes are preserved, so it's a drop-in wrapper.
Measured on a public OSS repo (paired runs, one per command):
| Command | Raw → distilled tokens | Saved |
|---|---|---|
pytest -q (11 failures) | 3,374 → 1,317 | 61% — all 11 failure lines preserved |
git log -50 | 3,064 → 331 | 89% |
git diff (30 commits) | 62,833 → 8,635 | 86% |
repowise distill <command>
Run a command and print the compact rendering. Nine content filters
cover test runs (pytest, jest, vitest, cargo, go), builds (npm, tsc,
cargo, go), linters (eslint/biome, ruff/flake8/mypy, clippy,
golangci-lint), git status/log/diff, grep/rg floods, file
listings, and generic logs.
repowise distill pytest -x
repowise distill git status
repowise distill npm run lintThe lint filter keeps every error verbatim and groups warnings by rule
id with counts and file:line anchors — the shape that compresses best
on the output agents run most.
Dropped content is stored in .repowise/omissions/ and referenced by an
inline marker:
[repowise#a1b2c3d4e5f6: 230 lines omitted (~6.1k tokens); restore: repowise expand a1b2c3d4e5f6]repowise expand REF
Restore the original output behind a marker. Accepts a bare 12-hex ref or a pasted whole marker.
repowise expand a1b2c3d4e5f6
repowise expand a1b2c3d4e5f6 -q "FAILED" # only the matching linesProp
Type
The omission store is durable across sessions (an agent resuming work tomorrow can still expand yesterday's markers), pruned by TTL + size cap (7 days / 50 MB by default, configurable).
The command-rewrite hook (Claude Code + Codex)
Opt-in. A PreToolUse hook rewrites noisy agent commands —
pytest -x → repowise distill pytest -x — pending your approval:
the default posture is ask, so the modified command is shown before it
runs.
repowise hook rewrite install # or answer Yes at the `repowise init` prompt
repowise hook rewrite status
repowise hook rewrite uninstall # removes only the repowise entriesThe hook covers both Claude Code shell tools — Bash and, on Windows,
PowerShell — and never rewrites pipes, redirections, compound commands,
watch modes, PowerShell-native constructs (Verb-Noun cmdlets, &
invocations, aliases like ls that don't mean their unix namesakes), or
anything in a repo that hasn't opted into repowise. Declining the init
prompt gates the repo off in config, so a hook installed globally from
another repo stays inert.
repowise init also adds an "Output Distillation" section to the
managed CLAUDE.md, so any agent that runs shell commands prefers
repowise distill <cmd> voluntarily — hook or no hook.
The allowlist trap. A rewrite changes the command string, so a
Claude Code permission rule you already had — say Bash(git diff:*) —
no longer matches the rewritten repowise distill git diff …, and the
prompt comes back for commands you'd already allowed. repowise hook rewrite install offers to seed Bash(repowise distill:*) /
PowerShell(repowise distill:*) allow rules (or pass --allow-rule /
--no-allow-rule non-interactively). The rule only stops
double-asking — repowise distill runs the wrapped command unchanged
and never widens what it can do.
Codex
When ~/.codex exists, install also covers the Codex CLI, with two
caveats its hook protocol imposes:
- Codex applies a PreToolUse command rewrite only from version
0.137; on older builds the hook entry is skipped (a rewrite response
would error on every shell call).
repowise hook rewrite statusreports what your build can do. - Codex has no ask-with-mutation — a rewrite can only be
auto-allowed, never shown for approval. So under Codex, rewrites fire
only for command families you set to
permission: allow;askfamilies always pass through unchanged.
The hook entry lands in ~/.codex/hooks.json (run /hooks inside Codex
to review and trust it). Independently of any hook, install maintains
a marker-managed "Output Distillation" section in the repo's AGENTS.md
that works on every Codex version; uninstall removes it and restores
your file byte-for-byte.
repowise saved [PATH]
Report tokens (and estimated dollars) saved by repowise distill —
direct invocations and hook rewrites.
repowise saved # per-filter rollup + totals + est. dollars
repowise saved --by day
repowise saved --by source # cli vs hook-bash vs hook-powershell vs hook-codex
repowise saved --since 2026-06-01
repowise saved --missed # savings raw commands left on the tableProp
Type
Missed savings
--missed is the adoption feedback loop: it scans your local Claude
Code transcripts for shell commands in this repo that were not
routed through repowise distill, classifies each with the same router
the engine uses, and estimates the foregone savings using each filter's
conservative fixture floor — the estimate undersells on purpose. Plain
repowise saved appends a one-line summary when there's anything to
report.
The scan is read-only, best-effort, and entirely local: commands are
read from your own transcript directory (~/.claude/projects/…);
nothing leaves the machine.
The dashboard mirrors all of this: the Costs page's Cache & savings tab shows a Distill savings card with the same rollup plus the missed-savings secondary stat.
The savings ledger covers the distill command/hook path only. MCP
response truncation is not counted — those responses were always
budget-capped, so nothing was "saved" relative to before. (Truncated MCP
content is still recoverable: see
get_symbol omission refs.)
repowise corrections [PATH]
The same transcript reader, pointed at a different waste: commands the
agent got wrong and then fixed. The scan finds consecutive runs of the
same base command where the first failed and a later variant succeeded,
classifies the fumble — wrong tool (bare python vs the venv
interpreter), wrong path, unknown flag, missing argument — and reports
the recurring rules.
repowise corrections # report-only (default window: 30 days)
repowise corrections --days 60
repowise corrections --write # maintain the managed guidance blockProp
Type
Classification is precision-first: apart from the structural wrong-tool case, a rule only forms when the error text actually names the dropped flag or path — a red-green dev loop re-running tests with different selections never becomes a "correction". Wrong-path rules consult the symbol index when available and note where the corrected target actually lives.
--write maintains a short "Known command corrections" block —
most-frequent rules first, capped at 10 lines — between managed markers
in the repo's CLAUDE.md (and AGENTS.md when one exists), so the next
agent session is told up front. Re-running refreshes the block in place;
content outside the markers is never touched. The same privacy contract
as the missed scan applies: read-only, entirely local.
Configuration
The distill: block in .repowise/config.yaml:
distill:
enabled: true # master switch for this repo
commands:
enabled: true # the command path (CLI + hook rewrites)
permission: ask # ask | allow | off — rewrite-hook posture
families: # per-filter overrides
test_output: allow # auto-allow rewrites for test runs
git_diff: deny # never rewrite git diff here
disabled_filters: [] # filters to skip entirely, e.g. [logs]
omission_store:
ttl_days: 7
max_mb: 50repowise doctor validates the block, reports the omission store's size
against its cap, and shows whether the rewrite hook is installed.
Skeletons — the read-side counterpart
For structure-level questions about a large indexed file, ask MCP for a skeleton instead of reading the whole file: every signature, the imports, and the bodies of only the most central symbols — typically ~15% of the full file's tokens, sliced from persisted symbol bounds with zero query-time parsing.
get_context(["src/big_module.py"], include=["skeleton"])After a large Read, the PostToolUse hook nudges the agent with the
skeleton's cost (once per file per session), warns when a re-read
follows an edit, and digests grep floods by graph centrality. See
get_context.