reference

Every command, every config key.
One page, nothing hidden.

ctx-optimize is configured in exactly three places: the committed repo config (.ctxoptimize/config.json), a machine-global config (~/ctxoptimize/config.json), and one environment variable. Everything else is a command.

install

npm, Go, or a plain binary.

# npm — thin launcher + prebuilt platform binary (no postinstall, no download step)
npm install -g @muthuishere/ctx-optimize

# Go
go install github.com/muthuishere/ctx-optimize/cmd/ctx-optimize@latest

# plain binaries (macOS / Linux / Windows) — GitHub Releases
# https://github.com/muthuishere/ctx-optimize/releases

# then give your agents the skill + prompt hook (Claude Code, Codex, Copilot, Devin)
ctx-optimize install --skills

# later — one command updates the binary AND every installed surface
ctx-optimize update            # npm installs update via npm; standalone binaries from
                               # GitHub Releases, sha256-verified against checksums.txt,
                               # swapped atomically — then skills + hooks refresh
ctx-optimize update --check    # report only, touch nothing

install detects which agent CLIs are on the machine and reports per platform. Narrow with --claude --codex --copilot --devin, or with --skills / --hooks to install only one mechanism. Remove everything with plain uninstall — no flags needed; stores and committed repo pointers stay. update's network call happens only when you run it — the binary never checks for updates in the background.

quickstart

One verb. Bare repo, monorepo, fresh clone, CI — up decides.

# the whole getting-started story — run it whenever, it's idempotent
ctx-optimize up            # no config → bootstraps it (monorepos via scan; curate
                           #   .ctxoptimize/config.json after) and gathers
                           # config declares a remote.pull + no local store → pulls the
                           #   team's prebuilt graph (falls back to gathering, loudly)
                           # no remote → gathers · stale vs git HEAD → fast re-gather
                           # fresh → no-op

# author-side, when you want control instead of up's defaults
ctx-optimize init          # scaffolds .ctxoptimize/ (commit it): config, adapter + transport
                           #   samples, remote.example.md, agent pointer block
ctx-optimize scan                  # monorepo, READ-ONLY: lists every project + the config it would write
ctx-optimize init --scan --yes     # writes the FULL module list to config.json — yours to edit after
ctx-optimize add .                 # gather explicitly (parallel fan-out per module + a navigator)

# then ask
ctx-optimize query "where is auth"
ctx-optimize card HandleCheckout   # signature + doc + callers + callees, no file read
ctx-optimize change-plan PaymentService   # about to EDIT: callers + blast radius + which tests to run, one call
ctx-optimize affected PaymentService --depth 2
new in v0.4 · breaking

The remote is your script. Onboarding is one verb.

Same contract as ever — deterministic binary, no LLM, no DB — and the network surface is now spelled exactly: only when you ask (update, grammar build's one-time zig fetch, and whatever your own remote scripts do).

Scripted remotes — the binary ships no transport (breaking)

remote push / remote pull now run the commands you declare in the committed .ctxoptimize/config.json — any shell line (js, py, sh, inline). The built-in file:// + s3:// transports and remote init are gone; a legacy URL-shaped config still loads but is inert, and push/pull print the migration pointer. Your command receives the store context in env — CTX_STORE_DIR, CTX_STORE_KEY, CTX_SCOPE_PREFIX, CTX_DIRECTION — and a non-zero exit fails the verb. init scaffolds a complete zero-dependency git lane as inert samples (push.js.sample / pull.js.sample) plus remote.example.md with git / s3 / custom recipes. Details in Remotes & sharing.

up — the one onboarding verb, bare repo included

Bare repo, fresh clone, teammate machine, CI: ctx-optimize up makes the store exist and be current, whatever it takes. No config at all → bootstraps it (monorepos via scan; curate .ctxoptimize/config.json after) and gathers; empty store + declared remote.pull → run it (a failed pull falls back to a local gather, loudly); no remote → gather; stale vs git HEAD → fast re-gather (adapter scripts skipped); fresh → no-op. Idempotent — run it whenever. init stays the author-control door (review module lists, pick pointer targets, transport samples); on a pull-declaring clone it just redirects to up. CI gate: ctx-optimize up && ctx-optimize fresh.

Fast lane / slow lane — sync and adapters run

Adapter scripts can be arbitrarily slow (DB dumps, doc converters), so they get their own lanes. sync re-gathers the repo you're in — code, docs, manifests, git — and skips adapters (safe: replacement is producer-scoped, adapter nodes stay put). adapters list shows them; adapters run [name] re-runs all or one on demand when the external system changed. add --no-adapters is the fast lane spelled long.

update — the whole tool, one command

Updates the binary (npm installs via npm; standalone binaries from GitHub Releases, sha256-verified against the release's checksums.txt, swapped atomically — dev builds left alone) and then refreshes skills, hooks and the global rule from the binary that is now current. update --check reports without touching anything. User-invoked network only — never a background check.

change-plan — the composed "I'm about to change X" answer

One bounded call returns the signature, callers, blast radius, which tests to run (affected filtered to test declarations), historical co-changes, and a confidence footer separating extracted from inferred evidence. Measured on this repo: 229 tokens in 1 call vs 2,270 across the query+card+affected chain it replaces — ~90% fewer answer tokens. Alias: plan.

new in v0.3

Routes, dependencies, subsystems, co-change — and a real dashboard.

Every one of these is deterministic pure-Go extraction over the ASTs and git history we already hold — no model, no network, same contract.

v0.3.5 — scattered modules, one-step clones, a viewer that never white-screens

Multi-path modules. A module can be a set of folders, not one subtree: {"name": "Billing", "paths": ["src/Billing", "tests/Billing.Tests"]}. The .NET / Gradle case — source in src/, tests in tests/ — gathers into one store and extracts in a single pass, so test→source calls resolve across the split instead of breaking at a folder boundary. One-step clones. A fresh clone fetches the team's prebuilt graph instead of rebuilding from source — since v0.4 the verb for this is ctx-optimize up (init on a pull-declaring clone just redirects to it). The agent-instruction block init writes is gated — if the binary isn't installed, agents ignore it and read code normally. A crash-proof viewer. One malformed graph node used to blank the whole dashboard; now a bad node is sanitized or dropped alone and every healthy node still renders, behind an error boundary — covered by unit tests.

Framework routes — the edge grep can't produce

Routing declarations become route nodes linked by handles edges to their handlers, so affected <handler> surfaces the URL that binds it and query "GET /users" finds the endpoint. Core recognizers: FastAPI, Flask, Express, NestJS, Angular, React Router, Vue Router (from source) and OpenAPI, Drupal *.routing.yml, Kubernetes Ingress (from YAML). Edges are INFERRED with a synthesized_by channel tag — honest provenance. Anything call-shaped that isn't core is a route pack (see below).

The manifest lane — dependencies + K8s as graph

Build manifests become a dependency graph: package.json, pom.xml, *.csproj/*.sln (ProjectReference = the .NET module graph), go.mod, build.gradle. A dependency is a version-free node (dep:maven/org.apache.kafka:kafka-clients) with the version on the edge — so the same library declared by a Maven module and a Gradle module federates to one node, and affected dep:npm/express lists every module that uses it. Kubernetes manifests become topology: resource nodes (k8s://ns/kind/name), Service→Deployment selects (label match), Ingress→Service, ConfigMap mounts, image refs. Secret resources: node only, data never read; Helm templates skipped.

Community-detected subsystems

The wiki now opens with a Subsystems table — deterministic community detection clusters the graph into architecture neighborhoods ("this repo is 6 subsystems"), each with its top hubs and dominant directories. Regenerated on every add, byte-stable.

Git-history co-change edges

From git log alone (zero parsing): co_changed_with edges between files that historically change together, with support counts. Answers what no AST can — "what usually breaks with this?" — and the strongest signals are often code↔doc couplings a parser could never see.

Pack doctrine — extend routes and manifests like grammars

Core stays embedded; everything else is a drop-in JSON pack, discovered at add time from .ctxoptimize/routes/ (or manifests/) in the repo and ~/ctxoptimize/routes/ on the machine — repo wins on collision, malformed packs fail loudly.

# route pack: teach it your team's own registerRoute(path, handler) convention
ctx-optimize routes add myframework            # scaffold .ctxoptimize/routes/myframework.json
ctx-optimize routes add https://github.com/org/route-packs
ctx-optimize routes list                       # core channels + discovered packs

# manifest packs work the same way (declarative path selectors over json/xml/yaml)
ctx-optimize manifests add internal-deps
ctx-optimize manifests list

First-class dashboard

ctx-optimize serve is now a full React app (embedded, zero external requests): Repos (freshness, counts), Onboard (scan → confirm → add with live progress), Query, Viewer (force-directed graph, budgeted, click-to-expand neighborhoods), Settings (every config key + pack across all four axes, editable), Changes (the audit feed). All mutations run through the CLI's own validated doors, loopback-only even if --host is widened, and every change is written to an append-only audit.ndjson — read it with ctx-optimize log.

cli reference

The whole verb set.

Global flags on every command: --path DIR (which module/repo, default cwd) and --store DIR (store root, default $CTX_OPTIMIZE_STORE or ~/ctxoptimize). Most read verbs take --json.

Build & refresh

commandwhat it does
initThe author-control door: scaffold .ctxoptimize/ (config, adapter template, transport samples push.js.sample/pull.js.sample, remote.example.md), prepare the store, write the agent pointer block into the repo's instruction files (see global config). Re-running never clobbers your config or duplicates the block; on a pull-declaring clone it redirects to up. Don't need control? up bootstraps for you.
init --scan [--yes] [--depth N] [--modules "globs"]Multi-module generator: scan, confirm, write the full found list into modules[]. Generated once — the list is yours to edit after.
scan [--depth N] [--json]Read-only module discovery. Prints every project found and the exact config init --scan would write. Changes nothing.
upThe front door. Makes the store exist and be current, whatever the state needs: no config → bootstraps it (monorepos via scan; curate after) and gathers; empty store + declared remote.pull → pull (a failed pull gathers locally and says so); no remote → gather; stale vs git HEAD → fast re-gather (adapters skipped); fresh → no-op. Idempotent.
add [<path>] [--force] [--jobs N] [--no-adapters]Gather code + markdown + property/manifest files + every adapter (--no-adapters skips the scripts). Incremental: re-add prunes stale nodes; a >50% shrink is refused unless --force (a gutted gather is more likely a broken run than a huge refactor). At a multi-module root it fans out one worker per module and refreshes the navigator. Honors .gitignore via git itself.
add --json -|FILEThe universal door: upsert a validated batch of nodes+edges from any adapter/tool. Fail-closed — an invalid batch is rejected whole.
syncThe fast lane: re-gather the repo you're in (code, docs, manifests, git; prunes deleted, refreshes wiki + navigator) but skip adapter scripts. Safe — replacement is producer-scoped, adapter nodes stay put; the report says how many were skipped.
adapters list | run [name]The slow lane, on demand: list the dropped adapter scripts, or re-run all (or one by name) when the external system changed — schema migrated, topics moved. Running one adapter never disturbs the code graph.
wikiRegenerate the markdown wiki (every add already does).

Ask

commandwhat it does
query|ask "terms" [--budget N] [--modules all|a,b] [--root]Ranked, cited hits (id, kind, file:line, signature, neighbors). Scope follows cwd: in a module dir it answers from that module and escalates repo-wide on zero hits; at the root it federates across all modules. --budget caps output tokens (default 2000).
card "X"Symbol card: signature, doc, location, callers, callees — cite without opening the file. Accepts id, exact label, or fuzzy name; strips invented qualifiers (ns::Class::MethodMethod); a total miss suggests the nearest labels.
change-plan "X" [--json]The composed "I'm about to change X" answer, one bounded call: signature + callers + blast radius + which tests to run + co-change history, with a confidence footer. ~90% fewer answer tokens than the query+card+affected chain (measured). Alias: plan.
explain "X"Plain-language node + neighborhood.
affected "X" [--depth N] [--relation R]Reverse impact: what breaks if X changes.
path "A" "B"Shortest path between two nodes.
hubs [--top N]Most-connected nodes — where to start reading.
statusStore facts + freshness vs git HEAD + tokens-saved tally.
freshOne-line verdict; exit 0 fresh / 1 stale / 2 unknown. The agent/hook gate before trusting an answer.
serve|dashboard [--port 4747]Local dashboard on 127.0.0.1 — zero external requests; mutations are loopback-only and audited.

Manage

commandwhat it does
config [<key> [<value>]] [--project]Get/set settings, git-style two levels: machine-global by default, --project writes the committable repo config. Project overrides global — see settings.
remote push|pullRun the push/pull commands you declared in .ctxoptimize/config.json — the binary ships no transport of its own. Your command gets CTX_STORE_DIR / CTX_STORE_KEY / CTX_SCOPE_PREFIX / CTX_DIRECTION in env; a non-zero exit fails the verb. No URL argument — the config file is the single source of truth. Scope follows cwd. (remote init was removed in v0.4.)
merge <module>... --into NCombine module stores into one merged view. Always opt-in, never automatic.
export [--format json|dot|graphml|csv|obsidian|all] [--out F|DIR]Dump the graph for other tools.
languages add|list|removeGrammar packs — see languages.
save-result / reflectThe learning loop — see below.
install / uninstallAgent skill + prompt hooks per platform. Plain uninstall removes everything install wrote; stores and committed repo pointers stay.
update [--check]Self-update the binary (npm via npm; standalone via GitHub Releases, sha256-verified) then refresh skills + hooks + the global rule. --check reports only. User-invoked network only.
repo config · committed

.ctxoptimize/config.json — the only thing we put in your repo.

Scaffolded by init — alongside an inert example.js.sample adapter, the git-lane transport samples push.js.sample / pull.js.sample, and remote.example.md — owned by you, committed so the whole team's agents inherit it. Every field:

{
  "name": "my-service",                // store key override (default: repo basename)
  "remote": {                           // YOUR transport — any shell line (js, py, sh, inline)
    "push": "node .ctxoptimize/push.js", // run by `remote push`; gets CTX_STORE_DIR,
    "pull": "node .ctxoptimize/pull.js"  // CTX_STORE_KEY, CTX_SCOPE_PREFIX, CTX_DIRECTION in env
  },
  "adapters": [                         // optional explicit list; scripts dropped in
    {"name": "kafka", "run": "adapters/kafka.js"}   // adapters/ are auto-discovered anyway
  ],
  "modules": [                          // PRESENT ⇒ this is a multi-module ROOT
    {"path": "services/api"},           // single-path: one dir, mirrored store
    {"path": "services/worker", "name": "worker"},
    {"name": "Billing", "paths": ["src/Billing", "tests/Billing.Tests"]}  // multi-path: scattered folders → ONE store
  ],
  "scan": {                             // tunes scan / init --scan
    "depth": 5,                         // max depth below root (default 5)
    "markers": ["BUILD.bazel"],         // extra marker filenames
    "include": ["tools/gen"],           // globs force-added as modules
    "exclude": ["experiments/*"]        // globs pruned from the walk
  }
}
fieldmeaning
nameOverrides the store folder under ~/ctxoptimize/. Use when two repos share a basename or you want a custom key.
remoteThe push/pull commands — plain shell lines the binary runs as-is (cwd = repo root, one env contract: CTX_STORE_DIR, CTX_STORE_KEY, CTX_SCOPE_PREFIX, CTX_DIRECTION). The binary ships no transport of its own. A legacy v0.3 URL-shaped value still loads but is inert — push/pull print the migration pointer.
adaptersOptional explicit adapter list. Any .js/.mjs/.py/.sh dropped into .ctxoptimize/adapters/ is discovered automatically on add and must print one batch JSON to stdout. init seeds an inert example.js.sample.
modulesThe generated, owned module list of a monorepo root. Written by init --scan, hand-editable, globs allowed. Present ⇒ add fans out and queries resolve scope against it. Two shapes: {"path": "…"} for a single directory, or {"name": "X", "paths": ["…", "…"]} to gather several scattered folders into ONE store — the .NET / Gradle case where a module's source and tests live in separate top-level dirs (src/Billing + tests/Billing.Tests). Multi-path modules extract in a single pass, so test→source calls resolve across the folder split.
module_of(child configs only) Marks an opt-in config inside a module dir; the value is the root store key. Written automatically when you run init inside a declared module — never a shadow store.
scanGenerator tuning: depth, extra markers, include/exclude globs. Built-in markers: go.mod, go.work, package.json, pom.xml, build.gradle(.kts), settings.gradle(.kts), Cargo.toml, pyproject.toml, setup.py.
instructions · skills · hooksPer-project overrides of the machine-global settings — set with config <key> <value> --project, committed so the whole team inherits them. Empty = inherit global.
Secrets rule. Credentials live in the environment and stay env-var names in config and scripts alike — the shell expands them only at the moment your remote command runs; values are never written, printed, or logged. Files whose names smell like secret stores (secret, credential, password, .env*) are refused by the extractors, and gathers honor your .gitignore via git's own semantics.
settings · global + per project

Two levels, git-style: machine-global, and --project overrides it.

Managed by ctx-optimize config. Global lives in ~/ctxoptimize/config.json (never committed); --project writes the same keys into the repo's .ctxoptimize/config.json (committable — a team pins a repo's behavior). Project beats global. Keys are flat artifact nouns; values name who gets the artifact.

ctx-optimize config                              # list effective values + which level set them
ctx-optimize config instructions                 # get one key (effective)
ctx-optimize config instructions CLAUDE          # set globally — scriptable (CI, npm setup, dotfiles)
ctx-optimize config instructions AGENTS --project # pin for THIS repo — commit .ctxoptimize/
keyvaluescontrols
instructionsCLAUDE · AGENTS · ALL (default) · NONEWhich instruction files init writes the pointer block into: CLAUDE.md, AGENTS.md, both, or don't touch the repo at all. The block is marker-fenced — your surrounding content is never modified, and re-init never duplicates it.
skillsCLAUDE · AGENTS · ALL (default)Which skill directories install --skills writes: ~/.claude/skills (Claude Code, Copilot), ~/.agents/skills (the cross-CLI SKILL.md standard), or both.
hooksCLAUDE · AGENTS · ALL (default) · NONEWhich platforms' prompt-hook files install writes: the Claude hook (~/.claude/settings.json), the AGENTS-family hooks (codex ~/.codex/hooks.json, copilot), both, or none. Devin never needs a hook file — it reads the Claude hook and AGENTS.md natively, and the install report says which lane covers it.

Typos are refused with the valid options — a bad value never silently falls back to writing files. BOTH is accepted as an alias for ALL.

environment

One variable in. Four handed to your remote scripts.

variablemeaning
CTX_OPTIMIZE_STOREStore root override (default ~/ctxoptimize). Also how tests stay hermetic.
CTX_STORE_DIR · CTX_STORE_KEY · CTX_SCOPE_PREFIX · CTX_DIRECTIONSet by the binary in the environment of your declared remote push/pull command: the local store tree (pull pre-creates it), the store key, the module scope, and push/pull — so one script can serve both directions.
$VAR in your scriptsYour own secrets stay env-var names — the shell expands them when your command runs; nothing is ever persisted or printed.
multi-module

One store per module, a navigator on top — never a giant merged graph.

Stores mirror your repo layout under ~/ctxoptimize/<root>/<module-path>/. Nested modules (a Maven archetype inside a module) get their own store and are never double-extracted. A module can also be a set of scattered folders{"name": "Billing", "paths": ["src/Billing", "tests/Billing.Tests"]} — gathered into one name-keyed store so source and tests that live in separate top-level dirs (the .NET / Gradle convention) share a graph and resolve calls across the split.

Scope follows where you ask

you ask fromwhat answers
a module directoryThat module's store. Zero hits escalate repo-wide automatically — a miss is never a dead end.
the repo rootFederation across all module stores (--modules a,b narrows, --root forces the root residual only).
anywhere else inside the repoThe nearest config wins — resolution walks upward exactly like git finds .git.

The navigator (navigator.md + modules.json in the root store) is the map: every module, node/edge counts, top hubs, README summary, wiki links. It refreshes on every root add. Code that lives outside any declared module lands in the root residual graph, so nothing in the repo is invisible. Stores on disk that your config no longer declares are inert — federation is config-driven — and add tells you they're safe to delete. merge stays an explicit verb.

remotes & sharing

Push the graph, not the gather time. The remote is your script.

The binary never moves bytes to a host it chose. remote push / remote pull run the commands you declare in the committed config — any shell line (js, py, sh, or inline). Same trust model as adapters and npm scripts.

# .ctxoptimize/config.json — committed, the whole team inherits it
{
  "remote": {
    "push": "node .ctxoptimize/push.js",
    "pull": "node .ctxoptimize/pull.js"
  }
}

# arm the scaffolded zero-dependency GIT LANE (a private repo hosts every store —
# artifacts are sorted ndjson, so git diffs and merges them cleanly)
gh repo create your-org/ctx-stores --private        # once per team
mv .ctxoptimize/push.js.sample .ctxoptimize/push.js
mv .ctxoptimize/pull.js.sample .ctxoptimize/pull.js
# set STORE_REPO_URL in both, add the "remote" block to config.json, commit
ctx-optimize remote push

# teammate, fresh clone — ONE command, done
ctx-optimize up              # runs the declared pull; falls back to a local gather, loudly

# after you gather new work
ctx-optimize add . && ctx-optimize remote push

Your command gets the store context in env — CTX_STORE_DIR (pull pre-creates it), CTX_STORE_KEY, CTX_SCOPE_PREFIX (module scope: push from a monorepo root shares the whole tree, from a module dir only that module's prefix), CTX_DIRECTION — and a non-zero exit fails the verb. S3/R2/MinIO is a small aws-CLI script over the same contract; GCS, Artifactory, rsync-over-ssh, anything — write the script that copies CTX_STORE_DIR to and from your host and declare it. Recipes live in the scaffolded .ctxoptimize/remote.example.md. Secrets stay env-var names, expanded by the shell at run time — never in config or scripts, never printed. Vocabulary: sync = keep the graph matching the code (up, sync, add ., fresh); share = remote push/pull.

Upgrading from v0.3: remote init and the built-in file:///s3:// transports are gone. A legacy URL-shaped config still loads but is inert — push/pull print the migration pointer.
adapters

Anything becomes nodes: databases, topics, logs, documents.

The binary never grows drivers. An adapter is any script in .ctxoptimize/adapters/ (.js/.mjs → node, .py → python3, .sh → sh) that prints one batch to stdout. It runs on every add; an invalid batch is rejected whole. Adapters can be arbitrarily slow (DB dumps, doc converters), so they get their own lanes: sync re-gathers the repo and skips them, adapters run [name] re-runs all or one on demand, add --no-adapters is the fast lane spelled long.

{
  "producer": "kafka",
  "nodes": [
    {"id": "kafka://orders", "label": "orders", "kind": "topic",
     "file_type": "messaging", "source": "kafka://orders", "location": "L1"}
  ],
  "edges": [
    {"source": "kafka://orders", "target": "svc://billing",
     "relation": "consumed_by", "confidence": "EXTRACTED"}
  ]
}

Replacement is producer-scoped: each adapter owns its slice of the graph; re-runs prune what it no longer emits, and other producers' nodes are untouched. confidence is honest provenance: EXTRACTED = parsed fact, INFERRED = name-matched. Cross-batch edges are the point — code ↔ docs ↔ schema link by node id. Read secrets from the environment inside your script; never hardcode values.

languages

12 embedded, everything else is a drop-in pack.

Embedded (tree-sitter compiled to WASM, zero setup): Go, Python, JavaScript, TypeScript/TSX, Java, C, C++, C#, Rust, Zig, SQL. Any other language is a grammar pack — <name>.wasm + <name>.json mapping — in ~/ctxoptimize/grammars/ (machine) or .ctxoptimize/grammars/ (repo, committable; kotlin/swift/dart ship in the repo's grammars/). Packs are discovered at add-time and override embedded grammars for their extensions; a malformed pack fails loudly.

ctx-optimize languages list            # embedded + installed packs + names addable by name
ctx-optimize languages add kotlin      # known name — builds the pack in pure Go (zig auto-fetched once, sha256-verified)
ctx-optimize languages add https://github.com/tree-sitter/tree-sitter-ruby
ctx-optimize languages remove kotlin
store layout

Plain files. Sorted output. Diffable forever.

~/ctxoptimize/
├── config.json                # machine-global settings (instructions, skills)
├── grammars/                  # machine-wide grammar packs
└── <repo-name>/               # one store per repo ("name" in repo config overrides)
    ├── graph/nodes.ndjson     # the graph — sorted, atomic-rename writes
    ├── graph/edges.ndjson
    ├── manifest.json          # content hashes → incremental add
    ├── source.json            # git HEAD at gather time → the fresh gate
    ├── wiki/index.md          # deterministic wiki, regenerated on every add
    ├── reflections/LESSONS.md # the learning loop's output
    ├── navigator.md           # (multi-module roots) the module map
    ├── modules.json
    └── services/api/          # (multi-module) one nested store per module, mirroring the repo
learning loop

The store remembers how its answers worked out.

# after an answer proves useful (or doesn't) — the agent records it
ctx-optimize save-result --question "where is auth" --answer "internal/auth" \
  --type query --nodes "auth.go::login" --outcome useful      # or dead_end | corrected

# at session start — aggregate with recency decay into reflections/LESSONS.md
ctx-optimize reflect

Deterministic, no model anywhere: the agent is the judge, the binary only tallies — preferred nodes (corroborated, recency-weighted), dead ends to avoid, verbatim corrections. status shows the running tokens-saved tally.

the contract

What the binary will never do.

No LLM calls. No database drivers. No embeddings. No MCP server. No network — except when you ask: update (npm / GitHub Releases, sha256-verified), languages add's one-time zig fetch, and whatever your own remote scripts do. No telemetry. The host agent is the only intelligence; the binary is deterministic and its artifacts are plain, sorted, git-diffable files. Full reasoning: VISION.md and the standing counter-weight CRITIQUE.md.