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

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 with uninstall --skills.

quickstart

One repo, two commands. A monorepo, three.

# single project
ctx-optimize init          # scaffolds .ctxoptimize/ (commit it) + writes the agent pointer block
ctx-optimize add .         # gathers code + docs + configs into ~/ctxoptimize/<repo>/

# monorepo
ctx-optimize scan                  # READ-ONLY: lists every project found + the exact config it would write
ctx-optimize init --scan --yes     # writes the FULL module list to config.json — yours to edit after
ctx-optimize add .                 # parallel fan-out: one store 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 affected PaymentService --depth 2
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. init now reads the committed remote: on a fresh clone it fetches the prebuilt graph itself rather than rebuilding from source (--force to re-index), and the agent-instruction block it 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
initScaffold .ctxoptimize/ (config + adapters template), 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.
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.
add [<path>] [--force] [--jobs N]Gather code + markdown + property/manifest files + every adapter. 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.
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.
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 read-only dashboard on 127.0.0.1. Zero external requests.

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 init <url> [--local]Write the remote to the repo config (committable). --local keeps it in this machine's store only.
remote push|pullIncremental sync with the configured remote. No URL argument — the config file is the single source of truth. Scope follows cwd.
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 / uninstall --skillsAgent skill + prompt hooks per platform.
repo config · committed

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

Scaffolded by init, owned by you, committed so the whole team's agents inherit it. Every field:

{
  "name": "my-service",                // store key override (default: repo basename)
  "remote": {                           // or a plain string URL
    "type": "s3",
    "url": "s3://team-bucket/ctx",
    "credentials": {
      "access_key_id": "${TEAM_KEY_ID}",       // ${VAR} resolves from env at sync time —
      "secret_access_key": "${TEAM_SECRET}",   // values are never written or printed
      "region": "auto",
      "endpoint": "${R2_ENDPOINT}"
    }
  },
  "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.
remoteWhere remote push/pull syncs. A plain string ("s3://…", "file:///…") or the object form with credentials. ${VAR} placeholders resolve from the environment at sync time; unset variables fail loudly and resolved values are never written anywhere.
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 are referenced by name (${VAR}). The binary resolves them only at the moment a sync call needs them; nothing resolved is ever stored, 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. That's the whole surface.

variablemeaning
CTX_OPTIMIZE_STOREStore root override (default ~/ctxoptimize). Also how tests stay hermetic.
${VAR} in remote credentialsAny variable your repo config references by name — resolved at sync time only, never persisted.
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.

# once, committed — the whole team inherits it
ctx-optimize remote init s3://team-bucket/ctx
git add .ctxoptimize && git commit -m "share the knowledge store"

# teammate, fresh clone: `init` sees the committed remote and
# FETCHES the prebuilt graph itself — no rebuild from source
ctx-optimize init            # auto-pulls; or `remote pull` directly

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

Two schemes: s3:// (AWS, R2, MinIO — any S3-compatible endpoint; SigV4 in the stdlib, no SDK) and file:// (shared drives). Sync is incremental by content hash and scope-aware: push from a monorepo root syncs the whole store tree, from a module dir only that module's prefix. Vocabulary: sync = keep the graph matching the code (add ., fresh); share = remote push/pull.

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.

{
  "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 the remote you configured. 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.