@bglocation/tune-context
v2.5.0
Published
Context-efficiency configurator for Claude Code — token-saving doctrine packaged as skills, plus a tune-context configurator that scaffolds a lean CLAUDE.md, doctrine skills, subagents, settings, hooks and MCP wiring.
Maintainers
Readme
tune-context
Context-efficiency configurator for Claude Code — the doctrine (token-saving
working methods) packaged as skills, plus a tune-context configurator that
scaffolds a cost-optimal, high-quality setup (lean CLAUDE.md + skills + subagents
- settings + hooks + MCP).
Self-contained — zero dependency on any host repo. Ships two ways: a Claude
Code plugin and an npm CLI (tune-context init), both under
@bglocation. Incubated inside the code-search-mcp
project, now extracted to its own repository.
tune-context/
├── .claude-plugin/plugin.json # Claude Code plugin manifest
├── skills/ # token-efficiency, caveman, cdd, phase-workflow, tune-context (+ templates/)
├── agents/ # explore-cheap, reviewer (model right-sizing)
├── hooks/ # PreCompact state-doc, SessionStart re-inject, adoption log, context reminder + hooks.json
├── bin/ + cli/ # `tune-context init` — deterministic detect → generate → verify
├── eval/ # adoption-report.mjs (the rag-usage analog), prompt-metrics.mjs, pricing.mjs, arm.mjs, analyze.mjs, verdict.mjs, report.mjs
└── scripts/ # verify-pack, smoke-init, sync-tune-context (dev/maintenance)Quick start
Node ≥ 18, no build step. From the repo you want to configure:
# Configure this repo — deterministic, no Claude session needed
npx -p @bglocation/tune-context tune-context initThe
-p <pkg> tune-contextform is deliberate: the package is scoped (@bglocation/…) and its bin (tune-context) doesn't match the scope, so a barenpx @bglocation/tune-contextcan fail to resolve the command.-pinstalls the package and names the bin explicitly.
That single command detects the stack + MCP servers, writes a lean CLAUDE.md,
syncs the doctrine skills/agents/hooks into ~/.claude/, merges hooks and
permissions, and prints a verification report. It's idempotent — safe to re-run.
Safe to re-run means exactly this: the managed block in CLAUDE.md holds only
what detection produced and is rewritten every time; everything below the closing
marker is yours and is never touched, including the ## Architecture and
## Conventions that bite sections the tool seeds there once. Write your own
notes below the marker, not inside the block.
After upgrading the package, re-run init. npm i -g updates the package,
not the hooks an earlier run copied into ~/.claude/hooks/ — settings.json
points at those copies, so a release that changes hooks goes live only after
another init. To check without changing anything:
tune-context verify # read-only: is what's deployed still what ships?Install it globally if you'll use it across repos:
npm install -g @bglocation/tune-context
tune-context init # in any repoThe command above also installs the /tune-context skill into
~/.claude/skills/, so you can type it in a Claude session straight away — it does
the same thing plus wiki wiring and richer MCP detection. Installing the plugin
instead gets you the skill without the npm install, and updates through
/plugin marketplace update:
/plugin marketplace add https://gitlab.com/bglocation/tune-context.git
/plugin install tune-context@bglocation
/reload-pluginsThen run /tune-context in the repo you want to configure. (The .git suffix
matters — see Install & run.)
The section below breaks down all three entry points and when to use each.
Install & run
Three entry points, same doctrine. Pick whichever fits how you work:
1. /tune-context skill (recommended). Type /tune-context in a Claude Code
session, in the repo you want to configure. It detects the stack, MCP servers and
existing config, then generates a lean project CLAUDE.md, wires the doctrine
skills, and merges hooks + permissions — idempotently, never clobbering
hand-written content. This is the full-fidelity path (it also handles wiki wiring
and semantic-search detection).
Either channel gives you the skill. The plugin installs it, and so does
tune-context init — the CLI copies skills/tune-context/ into
~/.claude/skills/ along with the doctrine skills. So a plain npm i -g gives you
both entry points, and so does the plugin (it puts bin/tune-context on PATH
while enabled). Claude never reaches for it on its own: the skill is marked
disable-model-invocation, so it runs only when you type it.
2. CLI tune-context init. The deterministic subset of the skill — same
managed-block rules, same never-clobber guarantee, no model in the loop. Good for
CI or scripted setup:
npx -p @bglocation/tune-context tune-context init # runs in the current repo
npx -p @bglocation/tune-context tune-context init --cwd . # explicit target dir
# from a checkout (dev):
node bin/tune-context.mjs init --cwd /path/to/your/repoIt prints what it detected, what it synced/merged, and a verification report; a second run is a no-op.
Two flags are worth knowing:
--cwd <dir>— configure another repo instead of the current directory.--force-claude-md— destructive, opt-in, and normally unnecessary. If aCLAUDE.mdstill has hand-written content inside the managed block (the layout used before 2.2.0),initrefuses to regenerate it and says so — "hand-written content sits inside the managed block — regenerating would erase it". The fix is to move that prose below the closing marker, where the tool never reaches. This flag regenerates anyway and discards it; use it only when there is nothing in there worth keeping.
/tune-context vs tune-context init
They are two different kinds of thing. The skill is a procedure Claude follows, so it can look at your repo and decide; the CLI is code that runs, so it does exactly what it was written to do, every time. Same doctrine, same managed-block rules, same never-clobber guarantee.
| | /tune-context (skill) | tune-context init (CLI) |
|---|---|---|
| Where you run it | Claude Code chat | terminal, incl. CI |
| Needs a Claude session | yes | no — plain Node ≥ 18 |
| Costs tokens | yes | no |
| Two runs, same repo | judgment may vary | byte-identical |
| Project CLAUDE.md, doctrine skills, hooks, permissions | ✅ | ✅ |
| Merge .mcp.json — add a semantic-search server if none was detected | ✅ | ❌ reads it, never writes it |
| Wiki lever — RAG segment vs its own MCP vs a plain pointer | ✅ | ❌ not detected, not wired |
Both write the same block into the same files, so mixing them is safe: script the CLI where you want repeatability, and run the skill when you want the two judgment steps it alone can do.
3. Plugin install. Load the plugin so the skills, agents and hooks are available
in every session. The best UX is the marketplace — install once, get updates
with /plugin marketplace update:
/plugin marketplace add https://gitlab.com/bglocation/tune-context.git
/plugin install tune-context@bglocationThe .git suffix is required: without it Claude Code treats the URL as a
direct link to a hosted marketplace.json instead of cloning the repo, and the
add fails with Invalid marketplace schema. Same commands work non-interactively
as claude plugin marketplace add … / claude plugin install … — useful where
the /plugin panel isn't available (e.g. the VS Code extension).
Or point Claude at a directory directly (no marketplace):
claude --plugin-dir "$(npm root -g)/@bglocation/tune-context" # global npm install
claude --plugin-dir /path/to/tune-context # or a local checkoutThe plugin manifest (.claude-plugin/plugin.json) and hook wiring
(hooks/hooks.json, via ${CLAUDE_PLUGIN_ROOT}) register the four hook events
automatically; bin/tune-context is added to PATH while the plugin is enabled.
The marketplace catalog is .claude-plugin/marketplace.json.
CLAUDE_CONFIG_DIR
By default the CLI reads and writes Claude config under ~/.claude (and
~/.claude.json). If you set Claude Code's CLAUDE_CONFIG_DIR, tune-context
init honors it consistently — everything user-level moves under the override:
| What | Default | With CLAUDE_CONFIG_DIR=/x |
|---|---|---|
| doctrine skills / agents / hook scripts | ~/.claude/… | /x/… |
| settings.json (hooks + gating) | ~/.claude/settings.json | /x/settings.json |
| MCP-server detection (.claude.json) | ~/.claude.json | /x/.claude.json |
| registered hook command paths | $HOME/.claude/hooks/… | /x/hooks/… |
| hook state (PreCompact snapshot, adoption log) | ~/.claude/tune-context/… | /x/tune-context/… |
Without the override the generated settings.json keeps the portable
$HOME/.claude/hooks/… form (expanded when the hook fires, so it travels between
machines). Under the override it's baked to the literal /x/hooks/… — where the
scripts actually land. Useful for shared accounts, containers, and test isolation.
Keeping context lean while you work
Setup is the cheap half. The recurring half is shedding context you no longer need — and that stays a manual, deliberate act, because only you know when a piece of work is actually finished.
Every turn re-sends the whole conversation as input. A session that has been running for hours pays for its early exploration on every later request, long after that detail stopped being useful. So:
| When | Do | Why |
|---|---|---|
| A work thread closes (feature done, bug fixed) | /compact | Keeps the conclusions, drops the raw tool output that led to them |
| Switching to unrelated work | /clear | Nothing from the old task is worth carrying — /compact would still keep a summary of it |
| Long session, no natural break yet | /compact anyway | Waiting for auto-compact means paying full price right up to the limit |
tune-context softens the cost of compacting rather than replacing it: its
PreCompact hook snapshots your branch, dirty files and recent commits, and the
SessionStart(compact) hook injects that back afterwards — so the mechanical
state survives even though the transcript doesn't. Judgment calls and open
threads do not survive; write those down before compacting if they matter.
The reminder is automatic
Claude cannot see its own context usage — nothing in a turn tells the model how
many tokens it is re-sending, and Claude Code has no timer or context-threshold
hook event. So context-reminder.mjs (registered under UserPromptSubmit)
supplies the missing measurement: it reads the tail of the session transcript,
sums the tokens actually re-sent, and once that passes 120,000 it injects a
short note suggesting /compact.
- The note goes to Claude, not to your screen. Hooks can only reach you
through the model — a
systemMessageon this event was measured and does not render — so what you actually see is Claude bringing it up in its reply. - The model picks the moment — at first. The hook can measure size, but only Claude can see whether you're mid-refactor, so the first note tells it to stay quiet mid-task and raise it at the next natural break. That deference runs out: see the ladder below. A session that never reaches a "natural break" is exactly the one paying the most for the wait.
- Three notes per crossing at most — 120,000 / 180,000 / 240,000. A note that lands mid-task can be correctly absorbed and then forgotten. Measured on one session: it fired at 166k, Claude stayed silent, and nothing spoke again until 346k. Each rung halves that blind spot, and each one is louder than the last: at 120k Claude is told to wait for a natural break, at 180k to raise it at the next pause anyway, at 240k to say it this turn whatever it is doing. There is deliberately no fourth — the reminder costs tokens on the prompt it appears in.
- Raise it for a large context window:
export TUNE_CONTEXT_REMIND_TOKENS=400000. 120k is ~60% of a standard 200k window; the transcript doesn't record the window size, so this can't be auto-scaled. The whole ladder scales with the override — the rungs are always 1×, 1.5× and 2× the threshold (400k/600k/800k here). - Turn it off: delete the
UserPromptSubmitentry fromsettings.json.
Depth lives in the phase-workflow and token-efficiency skills (both installed
by tune-context init), not here.
Measuring adoption (does the doctrine actually get used?)
hooks/adoption-log.mjs logs one JSONL line per event to
~/.claude/tune-context/adoption.jsonl by default (under CLAUDE_CONFIG_DIR, the
override's tune-context/adoption.jsonl — see above), and eval/adoption-report.mjs
turns that into a report — code search (semantic RAG vs the Grep tool, with
shell-plumbing greps kept out of the RAG share), doctrine skills, subagents, and
re-read churn — closing with an earned verdict: withheld below a PO-frozen floor of
distinct sessions, and never a false ✗ for a lever the log can't see (method-level
doctrine leaves no tool call at all). It's registered under two Claude Code hook events —
PostToolUse (every tool call) and SubagentStart (subagent spawns, which aren't
regular tools).
"RAG installed but ignored" vs "no RAG here" is the one gap the log alone cannot
close, and it happens to be the finding the report most wants — so it asks the config,
per project, and only as far as the config can honestly answer. A
~ RAG likely available, not reached for line appears when that project's .mcp.json
declares a semantic-search server, no settings file rejects it, and the file is older
than every session logged there. Everything else stays · unknown: config written
during or after those sessions says nothing about them, and user-scope servers
(~/.claude.json) can't be dated at all, since Claude Code rewrites that file
continuously. It is never a ✗ — config names servers, not their tools, so the line
claims the lever was probably within reach, not that it existed. It also answers to
the same frozen session floor as the rest of the verdict, applied per project:
"available and never reached for" is a claim about absence, and one logged session
cannot establish absence.
Using both the plugin and tune-context init (CLI) on the same repo registers
the hook twice — once per channel — so every event logs twice. This is harmless
(same script, same log, just doubled counts) and the report detects it: past a
measured share of near-duplicate pairs it withholds the re-read-churn verdict
instead of printing a number the doubling would have faked.
Turn it on
Install the hook. Either run
/tune-contextin your repo (it copies the script to~/.claude/hooks/and merges the hook entries intosettings.jsonfor you), or do it by hand:- copy
hooks/adoption-log.mjsandhooks/config-dir.mjs(it imports the latter for the log path) →~/.claude/hooks/(needs Node ≥ 18 onPATH); - merge this into your
settings.json—~/.claude/settings.jsonfor every project, or a repo's.claude/settings.jsonfor just that one (if the file doesn't exist yet, this can be the whole thing):
Using{ "hooks": { "PostToolUse": [ { "matcher": "*", "hooks": [ { "type": "command", "command": "node \"$HOME/.claude/hooks/adoption-log.mjs\"" } ] } ], "SubagentStart": [ { "matcher": "*", "hooks": [ { "type": "command", "command": "node \"$HOME/.claude/hooks/adoption-log.mjs\"" } ] } ] } }CLAUDE_CONFIG_DIR? Replace$HOME/.claude/hookswith the literal override path instead —$HOMEis not expanded there (seeCLAUDE_CONFIG_DIRabove).
- copy
No reload needed. Claude Code watches the settings files, so the new hooks go live within a few seconds of saving — you do not need to reload VSCode, restart the extension, or start a new session. Only if nothing shows up after a few seconds, restart the session to force a reload. Check what's active with the
/hookscommand.Just work. Every tool call and subagent run now appends a line to
~/.claude/tune-context/adoption.jsonlby default (created on first write; underCLAUDE_CONFIG_DIRit's the override'stune-context/adoption.jsonlinstead).Read the report any time, from this checkout (or the installed package):
node eval/adoption-report.mjs # default log location node eval/adoption-report.mjs --log <path>
Stop / opt out: delete the PostToolUse + SubagentStart entries from
settings.json (watcher picks that up too). The log stays local under
~/.claude/tune-context/ (or the CLAUDE_CONFIG_DIR override) and records only
tool names, file paths, and skill/agent names — never full Bash command strings.
Honest scope: this is a behavioral proxy, not a bill. The hook log carries
no tokens, so dollars come from prompt-metrics below (or from OTEL, which is
Anthropic's own figure); and caveman is an output style with no tool call, so it
can't be counted this way. Doctrine applied as method — narrow reads,
contracts-first edits, delegation — is only partly visible too: no Skill call,
but its trace shows up in the report's edit:read ratio, so the verdict never
reads zero Skill calls as "unused."
Per-prompt metrics from transcripts (eval/prompt-metrics.mjs)
The adoption report reads our hook log. prompt-metrics reads the transcripts
Claude Code writes itself (<config dir>/projects/<slug>/<session>.jsonl) and
emits one JSONL row per prompt — time, tokens split by cache class, model,
effort, tools, delegations, and cost in dollars (eval/pricing.mjs):
node eval/prompt-metrics.mjs # every transcript
node eval/prompt-metrics.mjs --transcript <path.jsonl> # one session
node eval/prompt-metrics.mjs --dir <dir> > prompts.jsonl # JSONL on stdoutRows go to stdout, a run summary to stderr — so a pipe stays clean. Two things that source makes possible and a hook cannot: it works for past sessions, and it works when tune-context is switched off (a disabled hook records nothing, and that is exactly the arm a comparison needs).
Honest scope, printed on every run:
list_cost_usdis API list price, not your bill. A Pro/Max subscription charges a flat fee and consumes plan limits; nothing here is billed per token. The figure answers "what would this work have cost through the API" — which is the right yardstick for comparing two arms, and the wrong number to reconcile against a statement. The field is named so the qualifier travels with the data.- Repeated entries are skipped, and that changed every figure. A transcript
writes some entries twice — 11 296 of 36 463 local ones (31%) repeat a
uuidalready written in the same file, 6 739 of them assistant turns with ausageblock, the copies identical apart from a cosmeticslug. Summing both charged 45% of all cache-read tokens twice: run totals from before 2026-08-02 are inflated ~1.97×. The skip is scoped to one file (nouuidis shared between files) and the count is printed on every run. - Cache-aware, because a naive token sum is off by ~10×. Cache reads cost
0.1× and writes 1.25×/2×; the local run carries 4.18 billion cache-read
tokens against 521 k plain input ones — 99.9% of the figure is cache and
output. The total never appears without its per-class split, and a model the
table doesn't know yields
nullplus anUNPRICEDcount, never a zero. - Prices are data, dated, and expire.
eval/pricing.mjsholds one table taken from the published pricing page with its retrieval date, storing base input/output only and deriving the three cache columns from the documented 1.25×/2×/0.1× multipliers — a test asserts the derivation reproduces every published row. Service tier (batch −50%), fast mode, andinference_geo(us +10%) are applied; the claude-sonnet-5 introductory price ends in the table on 2026-08-31. Rows are priced at the prompt's own timestamp, so re-running the report later yields the same figure. - Subagent cost is measured from below, not captured. Subagent turns never
reach the parent transcript (zero sidechain entries over 34 local ones), but
the result of an
Agentcall carries its ownusageandresolvedModel— so each delegation is priced with the same table and rides on the row asdelegations[], deduplicated byagentIdbecause the entry is written twice. That figure is a lower bound (cache_readgrows linearly with the subagent's tool calls, which looks like its last turn rather than a sum) and is never added to the row's own cost.agent_spawnsis still counted next to it: the spawns that left no priced result are cost there is no figure for at all. - Queue wait is not work.
queued_s(submission → first assistant entry) is reported apart fromwork_s, because a prompt sent while the agent is busy waits in a queue and that is the user's time.max_gap_sexposes the same thing inside a turn — a tool call blocked on a permission prompt lands inwork_sotherwise. - Model and effort change mid-session (measured), so both ride on every row, with a per-model token split when a single prompt used more than one.
- Entries that can't be attributed to a prompt are counted and reported,
never silently dropped. Same for the two ways the figure can be too low:
usage.iterationsreporting more than theusagewe price from (3 turns locally), and web searches, which are billed per request ($10/1000) rather than per token and so sit outside the token total. Web fetch is free.
The other arm (tune-context experiment)
A cost figure with nothing to compare it against is a number, not a measurement.
This switch turns the doctrine off so the same work can be run twice —
tc={on,off} × rag={on,off} — and records which arm was in force when:
tune-context experiment status # what is set, and what the config actually shows
tune-context experiment set --tc off --rag off --note "pair 1"
tune-context experiment restore # byte for byte, or a refusaltc offis the full arm (PO decision, EPIC-009): hook registrations insettings.json, the managed block in the projectCLAUDE.md, the doctrine skills, the agents. Four interventions in one variable on purpose — at small n the biggest effect is the easiest to detect; splitting them is the next experiment.rag offmeans this project's.mcp.json, and nothing else. The user scope (~/.claude.json) is rewritten by Claude Code while it runs, so the switch never touches it —statussays so out loud instead of letting you assume RAG is off globally. Checkclaude mcp listbefore a measured run.- Reversibility comes before the change. Every touched path is fingerprinted
and copied aside before it is modified, so a
setthat dies halfway is still undoable.restorecompares each path against whatsetleft there and refuses if anything changed in between, naming the file and the difference (first differing line for a text file, changed/added/removed files for a directory, and where the saved copy is) — the working session between the two commands is exactly when you might add a permission or write below theCLAUDE.mdmarker, and silently erasing that is worse than refusing. - The ledger is the source of truth
(
<config dir>/tune-context/experiment.jsonl, append-only). An analyzer assigns a session to an arm by timestamp; detecting the arm from a transcript is a cross-check, never the verdict. Everysetappends — including one that changes nothing, because a run whose ledger is silent at its start cannot be assigned to an arm at all. - The arm belongs to the session.
settings.jsonand MCP servers are read at startup, so it applies from the next Claude Code start; flipping it mid-session produces a mixed session, which the analysis throws away. tune-context verifyreads the arm and reports "off" rather than a broken install — and stays RED if the arm did not fully land.initrefuses to run while an arm is set: it would re-install what the arm parked and leaverestorewith nothing it recognises.
The measurement protocol that uses this — paired runs on exp/<task>/<arm>
branches, frozen prompt script, verdict gates — is
COST_EVALUATION_PROCEDURE.md.
Per-arm aggregate (eval/analyze.mjs)
Rows are data, not an answer. analyze turns a stream of them into one
aggregate per arm — and stops one step short of ruling which arm won, because
the thresholds that would do the ruling are frozen by the PO, not by the model:
node eval/prompt-metrics.mjs > rows.jsonl
node eval/analyze.mjs --rows rows.jsonl > aggregate.json # summary on stderr- Two populations, never one number. The paired measurement (branch
exp/<task>/<arm>, everything else held constant) and ordinary daily work (attributed by the ledger, nothing held constant) come out as two separate objects with different shapes. There is no total above them and no list of pools to fold over — adding them has to be written out by hand and meant. - Discards are the output too. Every row lands either in an arm or in a
named counter, and
rows_in = rows_used + rows_rejectedis asserted by a test. A session whose rows disagree about the arm is dropped whole —settings.jsonand MCP servers load at startup, so a session that switched arms was never in one — and a branch that mimicsexp/<task>/<arm>without matching it (exp/task-089,exp/) goes to a counter, never to the nearest plausible arm. - Median and spread, not the mean alone. One 44-hour turn or one prompt that read a 200 k-token file moves a mean of six and leaves the median untouched; the gap between the two is itself the finding, so both are reported with p25/p75, IQR and the extremes.
- An unpriced row leaves the cost, not the sample. It stays in
n_prompts, sits outside the total, and the pool sayscost_complete: falsewith the model that caused it named. pass_orderper pair. The second implementation of a task is cheaper for a reason that has nothing to do with the arm, and it pushes the same way as the effect being looked for. The per-pair, per-arm cost is carried so the two can be separated arithmetically (procedure §11b).
The report (eval/report.mjs)
One HTML file — data embedded as JSON, charts as inline SVG, no network and no
build step, so it opens from file:// on a machine with nothing installed:
node eval/analyze.mjs --rows rows.jsonl > aggregate.json
node eval/report.mjs --aggregate aggregate.json --out report.html
node eval/report.mjs --aggregate aggregate.json --rejected-runs journal.json --out report.html--rejected-runs carries what the run journal recorded as §7 invalidations
([{ session, task, arm, prompts, reason }]). Omitting it is not the same as
passing []: the first says nobody asked the journal, and the verdict is held
for exactly that reason — a rejection share whose denominator is unknown is a
quietly dropped sample wearing a passing gate.
- A withheld verdict is the page, not a footnote. When a gate holds, the arm-comparison chart is not drawn at all — not greyed out, not captioned. Two bars side by side assert "this is a comparison" more loudly than any caption can withdraw it. The per-pair costs stay in a table: those are what a repeat run is planned from.
- The order effect is printed the same size as the arm effect. A page that shows one large and the other in small print has already made the claim §11b refuses.
- Tokens never appear without their five classes, the two pools are never added, and the honesty counters — rejected rows and why, sessions dropped for switching arm mid-flight, the delegation cost that is a floor and not a total — are a section of their own rather than a footer.
Getting real cost (OTEL) — outside tune-context
tune-context does not set this up for you (deliberate — see TASK-062: it needs
infra this tool stays zero-infra without). prompt-metrics above computes cost
from a public price list, which is an independent calculation, not Anthropic's
invoice — OTEL is the way to check it against the real one:
export CLAUDE_CODE_ENABLE_TELEMETRY=1 # off by default
export OTEL_METRICS_EXPORTER=prometheus # NOT console — console is CLI-only and
# conflicts with the SDK/message stream
# a VSCode-extension session may use
export OTEL_METRIC_EXPORT_INTERVAL=1000 # default 60000ms; short sessions can
# exit before ever exportingStart a new Claude Code session with those set (must be inherited at launch — e.g. exported in the shell profile before opening VSCode, not just typed into an in-editor terminal), work for a bit, then:
curl -s http://localhost:9464/metrics | grep claude_codeLook for claude_code.cost.usage (USD, carries session.id) and
claude_code.token.usage (token counts). No collector needed just to see the
numbers — curl against the local Prometheus endpoint is enough; wire a real
Prometheus/Grafana at that endpoint only if you want history instead of a
point-in-time read. Traces (OTEL_TRACES_EXPORTER=otlp) need an extra
CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1 flag and aren't needed just for cost.
Design principles
- Minimal always-on, depth on-demand: thin CLAUDE.md points at skills; skill bodies load only when invoked (progressive disclosure).
@importis not a cost lever — it is inlined in full. Reduce via skills.- Right-size the model per role (subagent
model:). - Caveman targets output (uncached, ~4–5× input); skills/subagents target the window.
Local dogfooding
node scripts/sync-tune-context.mjs mirrors skills/, agents/ and hooks/
into ~/.claude/ (hooks get chmod 755). Source of truth stays here. Skills take
effect on the next Claude Code session; hooks are hot-reloaded by the settings
file watcher (once their settings.json entries exist).
Related — the @bglocation toolbelt
@bglocation/code-search-mcp— semantic code index (RAG) + MCP server. The strongest lever tune-context detects and wires: search by meaning instead ofgrep, read only the returned spans. tune-context runs fine without it, but recommends it when no semantic-search MCP is present.- bglocation.dev — the rest of the toolbelt.
