Commit Graph
84 Commits
Author SHA1 Message Date
ce983a08fe fix(cli): node <symbol> -f <file> includes the source body (#1314)
The CLI's bare-symbol branch passes includeCode=true to the
codegraph_node handler, but the symbol-pinned-to-file branch didn't —
so exactly when a user disambiguated an overloaded name to one file
(the point of -f), they got Location + trail with no code (#1284).

Fixes #1284

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 15:30:17 -05:00
d6efd437b3 fix(cli): honor NO_COLOR/--no-color and go plain when stdout is piped (#1306)
List commands (status, query, callers, callees, impact, files) embedded
ANSI color codes even when stdout was a pipe, and NO_COLOR had no effect.
One switch now decides color for all codegraph-authored output:
--no-color > --color > NO_COLOR > FORCE_COLOR > stdout TTY > CI.

Piped init/index/sync also stop emitting shimmer animation frames
(\r + erase-line rewrites) and print one plain line per phase instead;
a TTY with NO_COLOR keeps the animation but drops the color codes.
The detection mirrors picocolors' so @clack frames and our own lines
agree within a run.

Fixes #1281

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 14:39:26 -05:00
5736e24bb6 perf(index): faster fresh indexing + parallel reference resolution, byte-identical graphs (#1305)
* perf(index): ~34% faster fresh indexing, byte-identical graphs

Profiling a fresh init on a medium TS repo (excalidraw, 657 files) showed
the main thread as the critical path: per-row SQLite statement calls,
repeated import-resolution walks, and per-row FTS trigger firings, with
the parse workers ~75% idle behind it. This lands the semantics-preserving
tranche of fixes:

- Multi-row batched INSERTs (nodes/edges/unresolved refs/name segments)
  behind cached per-batch-size prepared statements; row order preserved,
  so rowid-based resolution determinism (#1015) is unchanged.
- storeFileBundle: one transaction per file instead of four; nested
  transaction() calls now flatten (BEGIN-in-BEGIN previously threw, so no
  caller depended on nested rollback).
- Dedicated store-writer thread for the fresh-DB bulk path (bundles
  applied in file order on a single writer connection; main thread does
  no DB work during the parse loop). Kill switch: CODEGRAPH_NO_STORE_WORKER=1.
- Bulk FTS mode: drop the nodes_fts sync triggers during the bulk load,
  rebuild once at the end; crash inside the window self-heals on the
  next open.
- Per-context memos for resolveImportPath/findExportedSymbol + a per-file
  exported-symbol index, invalidated exactly where clearCaches() already
  resets the resolver's own caches.
- Fast-init on completely fresh DBs (journal in memory, no fsync until
  the index completes; interrupted init re-runs from scratch). Kill
  switch: CODEGRAPH_NO_FAST_INIT=1.
- MaybeYield returns undefined on the not-due path so per-ref yield
  checks stop paying a promise + microtask hop each.
- Parse pool prewarm for bulk indexing; compile-cache enabled at CLI and
  worker entry points.

Excalidraw fresh init: 5.11s -> 3.36s median (n=5, warm cache, M-series).
Graph dumps byte-identical across init, re-index, and sync paths; full
suite green (2403 passed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* perf(resolution): parallel reference resolution with canonical admission

Fan resolution batches across a pool of read-only worker threads, each
hosting a full ReferenceResolver over its own SQLite connection; results
are admitted on the main thread in chunk order, so edge insertion order,
row cleanup, failure parking, and deferred post-pass queues are exactly
the sequence the single-threaded loop produces. Per-ref inputs match the
baseline because the sequential path already resolves each batch against
the state committed BEFORE that batch.

Validated byte-identical on excalidraw (pool forced on) and apache/dubbo
(4,048 Java files): dubbo full index 39s -> 19s (2.05x) with identical
graph dumps (91,495 nodes / 223,953 edges).

The pool only engages when total pending refs clear a threshold (default
150k, CODEGRAPH_PARALLEL_RESOLVE_MIN to tune, CODEGRAPH_NO_PARALLEL_RESOLVE=1
to disable): measured on a ~58k-ref repo the workers' boot CPU contends
with resolution on the same cores and makes indexing slower, so small
repos keep the sequential path. When fast-init left the DB in
memory-journal mode, WAL is restored before resolution only when the pool
will run (readers + rollback-journal writers don't mix).

Also: sqlite adapter readOnly open support.

TreeCursor spine rewrite of the body walker was built, measured neutral
on real repos and equal in a 20k-child microbench (web-tree-sitter's
namedChild(i) is not quadratic in this binding), and rejected — per-node
JS<->WASM marshaling is the floor, which a traversal swap cannot remove.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 14:21:15 -05:00
a66683d3eb feat(installer): offer CodeGraph Pro beta signup after install and upgrade (#1297)
One-time, strictly opt-in prompt at the end of codegraph install and
codegraph upgrade to join the CodeGraph Pro beta waitlist (same list as
the getcodegraph.com homepage form). Nothing is sent unless the user
answers yes AND enters an email; either answer is recorded machine-wide
so no later install or upgrade re-asks, and --yes / non-interactive / CI
runs never see the prompt.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 19:11:06 -05:00
40aa092f5b fix(uninstall): remove the CLI binaries too, not just agent configs (#1254)
* fix(uninstall): remove the CLI binaries too, not just agent configs (#1071)

`codegraph uninstall` swept agent configurations and stopped — every
installed binary stayed behind, so `codegraph` still ran afterward. Three
disconnected paths each removed a fraction of an installation (uninstall:
configs; install.sh --uninstall: the bundle; npm preuninstall: configs +
npm's own package), and none cleared a shadowed second install — the
uninstall edition of the #1071 PATH shadow.

The uninstall now PLANS every install present on the machine — the bundle
layout(s) (running binary's own, the platform default, a custom
CODEGRAPH_INSTALL_DIR), the npm global package (found by asking
`npm root -g`, so nvm/fnm/volta prefixes resolve correctly), and the
bin-dir launcher link (only when it verifiably points into a detected
install) — confirms with the user, then removes them all. `--yes` skips
the prompt; the new `--keep-cli` flag keeps the old configs-only behavior.

Safety rules: a source checkout is reported, never deleted; a
project-local npm install is left to the project; on unix the default
install dir doubles as the machine state dir, so only the install
artifacts (versions/, current) are removed there — telemetry choice and
daemon records survive. Windows can't delete a running exe but can rename
it (the in-place upgrade's trick): a locked node.exe is renamed aside and
surfaced as a one-file leftover instead of failing the removal, and npm
is routed through cmd.exe (a direct .cmd spawn EINVALs on modern Node).

Planner/executor are split with injected side effects (the upgrade
orchestrator's convention) and unit-tested across the shadow case,
state-dir preservation, custom dirs, foreign-shim protection, and the
locked-exe dance; validated end-to-end on macOS against a fake HOME.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(uninstall): key path math on the target platform, not the host

Real-Windows validation caught it: the planner/executor used the host
path module, so win32 fixtures were meaningless on a POSIX host and
POSIX fixtures failed on the Windows VM. Same convention as
detectInstallMethod now — path.win32/path.posix chosen by the injected
platform.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(upgrade): route npm through cmd.exe on Windows — a direct npm.cmd spawn EINVALs on modern Node

Found while validating the uninstall change on the Windows VM: upgradeNpm
spawned npm.cmd without a shell, which every current Node rejects with
EINVAL (the CVE-2024-27980 hardening) — so `codegraph upgrade` on a
Windows npm install failed before doing anything. Verified live on the VM:
spawnSync('npm.cmd') → EINVAL; cmd.exe /d /s /c npm → works.

npmInvocation moves into the upgrade orchestrator (remove-binary imports
it from there — same direction as its existing imports, no cycle), and the
win32 test now pins the WORKING invocation instead of the broken one.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 17:37:20 -05:00
386bff0f84 fix(upgrade): refresh installer-written agent surfaces after a binary upgrade (#1238) (#1239)
* fix(upgrade): refresh installer-written agent surfaces after a binary upgrade

codegraph upgrade swapped the binary but never revisited what earlier
installs wrote into CLAUDE.md / AGENTS.md / GEMINI.md and the agent
configs, so sections written by a pre-1.0 installer kept teaching agents
a multi-tool surface (including tools that no longer exist) months of
releases later. The install path already self-heals everything it owns,
but nothing ever called it on upgrade.

- codegraph install --refresh: non-interactive sweep that re-runs
  install() for already-configured targets only — never a first
  install; permissions and prompt-hook choices are preserved.
- codegraph upgrade spawns it via the freshly-installed binary after a
  successful swap (the still-running old process would only rewrite its
  own stale template). Gated on PATH resolution and the
  CODEGRAPH_NO_INSTALL_REFRESH=1 kill-switch; never fatal to the
  upgrade.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(installer): clarify refresh change reporting

---------

Co-authored-by: xuing <np2v9bvbbs@privaterelay.appleid.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Colby McHenry <me@colbymchenry.com>
2026-07-10 10:58:55 -05:00
63eb488ed4 fix(upgrade): stop npm installs self-shadowing on upgrade; verify the resolved version after every upgrade (#1238, #1071) (#1245)
Two fixes to make `codegraph upgrade` trustworthy in the terminal it ran in:

1. detectInstallMethod checked the bundle layout before the node_modules
   path check, but the npm thin-installer's per-platform package IS a
   complete bundle inside node_modules — so every npm install misdetected
   as a standalone bundle, and upgrade curled install.sh into ~/.codegraph:
   a second install that never wins the PATH race against npm's shim,
   leaving `codegraph -v` permanently on the old version. Path-based
   checks (_npx, node_modules) now win over layout sniffing, so npm
   installs upgrade through npm again, in place.

2. After a successful swap, runUpgrade now probes the PATH-resolved
   `codegraph --version` and reports the real outcome: a green
   confirmation that this terminal already serves the new version, a
   loud shadow warning naming the fix (`which -a codegraph`) on
   mismatch, or the old soft new-terminal hint only when the probe is
   inconclusive. Replaces the unconditional "open a new terminal if the
   version looks unchanged" hedge. Skipped for npm-local installs, whose
   binary PATH never serves.

Companion to #1239: the misdetection also broke its post-upgrade
`install --refresh` for npm users (the spawn resolved the stale shim).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 10:52:22 -05:00
edb9f2f14c fix(watchdog): don't kill a healthy index on degraded storage — require heartbeat silence AND no disk progress (#1231) (#1244)
The liveness watchdog judged the main thread by heartbeat silence alone,
which cannot distinguish a true wedge (the #850 infinite loop it exists to
kill) from one long synchronous SQLite statement on severely degraded
storage — so it SIGKILLed valid, in-progress indexes (observed on a
150-IOPS throttled rig, and latent on real HDDs at scale).

The CLI index/init paths now hand the watchdog the project's DB + WAL
paths. On a silent timeout the watchdog child stats them first: if they
advanced during the silence, the block is a slow store making forward
progress — defer and keep watching; if not, kill at the base timeout
exactly as before. Deferral is bounded by a hard cap (10× the timeout) of
continuous silence so a wedge coinciding with unrelated file activity, or
I/O hung beyond any legitimate statement, still dies. The daemon path is
unchanged (no progress paths — pure heartbeat).

Validated with real spawned processes (defer-on-progress, kill-on-static,
hard-cap kill) and on the throttled rig: a 150-IOPS index under a 10s
watchdog window — 6× tighter than production, with store stalls measured
at 10-20s — completes cleanly where the old watchdog killed it, while
true-wedge kill latency is unchanged.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 08:25:23 -05:00
e65a39746c fix(init): surface and offer to opt in gitignored child repos on an empty index (#1156) (#1208)
A Git super-repo whose `.gitignore` excludes its child repositories indexed
~nothing at the parent: CodeGraph respects `.gitignore` by default (#970,
#1065), so the excluded children were skipped and `codegraph init` printed
"Done" with 0 nodes — even though `init` inside each child worked fine. The
empty index was silent and unexplained.

`init`/`index` now detect the gitignored child repos they skipped when an
index comes up empty of symbols, name them, and — in an interactive terminal
— offer to index them (writing an `includeIgnored` entry to codegraph.json and
re-indexing on the spot); non-interactive runs print the exact codegraph.json
snippet to add. Gated on nodesCreated === 0, so a project that deliberately
keeps gitignored reference clones out of a working index is never nagged.

- extraction: findUnindexedIgnoredRepos — the inverse of discoverEmbeddedRepoRoots
  (bounded, skips default-ignored dirs, respects existing includeIgnored)
- project-config: addIncludeIgnoredPatterns — create/merge codegraph.json,
  idempotent, refuses to clobber malformed JSON
- cli: wire the detect-name-offer flow into both `init` and `index`
- tests: +13 covering detection, config writing, and the no-nag gate

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 13:01:38 -05:00
c9f8c0ebaf fix(mcp): reap the server when its launcher is killed during startup (#1185) (#1199)
An MCP host that kills the launcher chain within the server's first ~100ms
while keeping the stdio pipes open (config probe, cancelled request, startup
timeout; Rust hosts that kill a child without dropping its stdio handles) left
the server orphaned: it booted already reparented to init, so the PPID
watchdog's "ppid changed" baseline was captured as 1 and could never fire, and
stdin never EOF'd. The process lingered — idle, ~30MB — until the host itself
exited, accumulating one per abandoned launch (the pile-up reported in #1185).
Reproduced on released 1.2.0/macOS: SIGKILL the launcher at +50ms → permanent
orphan; at +150ms the old late baseline had already run and reaped it.

Three-part fix:
- Capture process.ppid at the earliest line of the CLI entry (early-ppid.ts)
  and use it as every watchdog baseline, shrinking the blind window to the few
  ms before our first JS runs.
- Thread the real host pid down the bundled path: the npm shim and the
  standalone sh launcher set CODEGRAPH_HOST_PPID (an outer launcher's value
  wins), so the watchdog polls the host directly. Previously only the
  --liftoff-only relaunch set it, leaving the entire npm/standalone install
  base with hostPpid=null.
- Never-initialized backstop (startup-handshake.ts): a serve --mcp that
  receives no MCP traffic for CODEGRAPH_STARTUP_HANDSHAKE_TIMEOUT_MS (default
  15min, 0 disables) shuts down — the catch-all for a kill landing in the
  residual pre-JS window. Disarmed on the first byte, so a quiet-but-live
  session is never touched.

Also scrub CODEGRAPH_HOST_PPID from the detached daemon's env — it has no host,
and a stale pid must not leak into anything it spawns.

Validated end-to-end on the built bundle: the +50ms early-kill orphan is now
reaped while the host still holds the pipes open, and all six normal
lifecycle paths (clean close, SIGTERM/SIGKILL child, host exit/SIGKILL,
fd-holding adversarial host) stay clean. New coverage in
startup-handshake.test.ts, mcp-startup-orphan.test.ts, and npm-shim.test.ts.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 08:45:12 -05:00
4c15f84aa4 fix(resolution): sweep orphaned unresolved refs so an interrupted index heals on sync (#1187) (#1191)
An indexing run killed mid-"Resolving refs" (crash, Ctrl-C, the #1122
watchdog kill) left the refs it never reached parked in unresolved_refs.
The git-scoped sync fast path only re-resolves changed files' refs, so
those files' call edges were missing permanently — a too-small blast
radius clustering by package/module (the #1187 field report: 3 of 10
caller files for a Spring @Resource-injected method) — until a full
re-index.

- sync() now sweeps leftover unresolved refs with the batched resolver
  after its scoped pass, including on no-change syncs, so a bare
  `codegraph sync` recovers a wedged index (and heals pre-fix indexes
  on the first post-upgrade sync)
- the scoped pass deletes unresolvable rows too (parity with the
  batched path), making "rows at rest" a sound orphan signal
- drop the batched loop's early break that abandoned all later batches
  when one batch was all-unresolvable (its rows WERE consumed — that
  early stop could orphan the rest of the table at init)
- surface the state: `codegraph status` warns, `status --json` gains
  index.pendingRefs, and MCP codegraph_status tells agents the blast
  radius is incomplete until the next sync

Verified end-to-end on a 2,414-file synthetic Spring repo: SIGKILL
mid-resolution reproduces the reporter's exact 3-of-10-callers state;
a bare sync now heals it to 10/10 with the edge count converging to
the clean-init total; a healthy-index sync stays a no-op.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 14:20:19 -05:00
99152212a9 feat(extraction): add ArkTS language support with ArkUI dispatch bridges (#396, #512, #890 via #648) (#1186)
Adds ArkTS (.ets, HarmonyOS/OpenHarmony) as a first-class language:
full TypeScript-grade extraction via the harmony-contrib tree-sitter
grammar (MIT, vendored byte-identical from the tree-sitter-arkts 0.2.0
npm tarball), plus the ArkUI constructs that make HarmonyOS apps
traceable:

- @Component/@ComponentV2 structs with decorators from both grammar
  positions; members extract as class members with qualified names.
- build() component trees: child instantiation edges via
  arkui_component_expression, no synthesizer needed.
- Attribute chains emitted dot-prefixed and resolved ONLY against
  @Extend/@Styles/@AnimatableExtend/@Builder helpers (unique-or-drop) —
  bare-name fallthrough produced 36,840 wrong edges (17% of calls) on
  the OpenHarmony samples monorepo. All four grammar chain shapes
  handled, including the detached-chain forms.
- .onClick(this.handler) method-reference bindings.
- ohpm workspace modules: bare imports follow oh-package.json5 file:
  deps (ambiguous names dropped), honoring each module's main entry —
  which also lets .ts consumers resolve .ets modules.
- ArkUI dynamic-dispatch bridges, all provenance:'heuristic' with
  wiring-site metadata: assignment-gated state->build() re-render
  (V1 @State family + V2 @Local/@Provider/@Consumer),
  @ohos.events.emitter emit->subscriber pairing on static event keys
  (numeric ids same-file, named constants same-module, fan-out capped),
  and router.pushUrl literal urls -> the target page's @Entry struct.
- $r/$rawfile resource intrinsics treated as built-ins; arkts joins the
  web language family, value-reference edges, re-export chase, and the
  other TS-applicable gates.

Also ships a language-agnostic index-completeness guard: indexAll
stamps index_state (indexing -> complete/partial/failed), reconciles
discovered vs accounted files (a loaded run silently dropped 37 files),
and codegraph status surfaces truncated/partial indexes in human and
--json output.

Validated on HarmoneyOpenEye (82 files), CoolMallArkTS (528, modular
ohpm + ArkUI V2), and openharmony/applications_app_samples (11,693
files, 202,890 nodes stable across re-index, attribute false-positive
audit 36,840 -> 588 residual all-plausible). Supersedes PRs #656 and
#988 with credit — both informed this implementation.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 09:07:15 -05:00
35611b92bb fix(prompt-hook): close the segment-vocab integrity gaps (#1141, #1142, #1144, #1145, #1146) (#1150)
Five hardening fixes to the #1136 MEDIUM (graph-derived) tier:

- #1141: updateNode() now writes the segment vocabulary like insertNode()
  does — framework post-extract renames (NestJS route prefixing) left the
  new name permanently unsearchable (the old rows orphaned, the backfill
  gated on an EMPTY vocab, so even a full re-index re-created the drift).
- #1142: new CodeGraph.healSegmentVocabIfEmpty() — the hook opens the
  graph without sync, so a database migrated from pre-vocab schema kept
  the MEDIUM tier dormant until some unrelated sync ran. The hook heals
  on first use (one SELECT when populated; lock-aware, defers to a
  running sync) and records noop-vocab-empty when it can't.
- #1144: a name whose only nodes are file/import kind is skipped instead
  of falling back to surfacing an import statement as a matched symbol;
  import specifiers no longer enter the vocab at all (shared
  isSegmentableKind gate across insertNode/updateNode/rebuild page query)
  since they can never be surfaced and only inflate rarity statistics.
- #1145: plural variant folding is keyed on English plural spelling —
  bare-s plurals no longer mint a bogus -es sibling (services→servic),
  unambiguous sibilant-es plurals no longer mint a bogus -s sibling
  (classes→classe), trailing -ss singulars no longer strip (class→clas);
  genuinely ambiguous endings (caches/databases) still emit both keys.
- #1146: getSegmentCoOccurrence folds variants to their original word
  inside the SQL (CASE mapping + COUNT(DISTINCT word)) so a plural pair
  of ONE word can't tie with a genuine two-word match and crowd it past
  the pre-fold ORDER BY/LIMIT; the JS re-check stays as the honesty layer.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 17:23:54 -05:00
be55b93d02 fix(prompt-hook): record high-tier gate telemetry only when context was actually injected (#1143) (#1149)
gate('high-keyword'/'high-token') sat outside the injection guard, so an
errored or empty codegraph_explore still counted as a HIGH-tier success.
The gate telemetry is the measured recall/precision funnel that decides
whether the tiered gate design survives — a delivery failure must degrade
it toward noop-*, not inflate the high tiers. Failures now record
noop-explore-keyword / noop-explore-token. Doc enum updated (including
the noop-vocab-empty outcome the #1142 fix adds next).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 17:13:02 -05:00
e699ee9686 feat(prompt-hook): graph-derived gate tier + confidence-tiered injection + gate telemetry (#1136)
The keyword gate (#1126) can never know a repo's domain nouns. This adds
the graph-derived tier the design discussion converged on: symbol names
are split into prose segments at index time (name_segment_vocab, riding
the insertNode write path), and the hook verifies a prompt's plain words
against them — "the state machine des commandes" → OrderStateMachine, in
any language whose technical nouns are Latin script.

Confidence now decides HOW MUCH to inject, not just whether:
- HIGH (keyword, or index-verified code token): full explore injection,
  unchanged — the validated adoption lever.
- MEDIUM (segment matches only): a ~500-byte pointer naming the matching
  symbols; the AGENT writes the explore query. Never runs explore, so a
  fuzzy match can't inject 16KB of wrong-feature context.
- Silent otherwise, as before.

Precision is derived from the repo's own naming statistics plus measured
FP fixes: co-occurrence (≥2 words on one name) always qualifies; a single
word must be ≥5 chars, cluster across 2–25 names (singletons are prose
coincidence: "deploy to production" → matchesNonProductionDir), match a
multi-segment name, and not be an English function/filler word (the one
place a word list is honest: identifiers are English, so only English
prose collides). Every candidate is re-verified against nodes before
being surfaced — vocab rows are proposals, deletions leave orphans by
design, a full index rebuilds from scratch, and sync heals pre-upgrade
databases (batched + yielding; emptiness captured at sync ENTRY so the
sync's own writes can't mask the backfill).

Schema v7 migration is DDL-only (instant; none of the #1067 row-churn
hazards). Gate outcomes roll up as anonymous usage counters
(prompt-hook-gate-<outcome>, names only, never content) through the
existing telemetry pipeline — recall becomes measurable, and the counters
are the agreed kill-criterion data for ever revisiting a local classifier.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 14:35:38 -05:00
317e7f4d3d fix(prompt-hook): make the structural-question gate multilingual (#1126) (#1134)
* fix(prompt-hook): fire the structural gate for Latin-script, Cyrillic, and JA/KO prompts (#1126)

The prompt-hook's keyword gate only knew English and simplified-Chinese
keywords, so a structural question in French (or Spanish, German, Italian,
Portuguese, Russian, Japanese, Korean, traditional Chinese) silently
no-op'd unless it happened to contain an identifier-shaped code token —
the #994 symptom, resurfaced for every other language.

Root causes fixed:
- JS \b is ASCII-only: a keyword whose first/last char is accented or
  non-Latin (où, qué, Cyrillic, kana) can never match \bkeyword\b —
  the same mechanism behind #994. Keyword matching now uses Unicode
  lookaround boundaries ((?<![\p{L}\p{N}_]) … (?![\p{L}\p{N}_])).
- Bare-stem English entries never matched their own derived forms
  (\barchitect\b can't match "architecture", \bdepend\b can't match
  "dependencies"). Stems are now matched as word prefixes (leading
  boundary only), which also lets one shared stem cover the Romance/
  Germanic spellings that coincide.
- The "CJK" set was simplified-Chinese-only: Japanese (呼び出し, 仕組み,
  実装 — and 追跡 ≠ 追踪), Korean, and traditional-Chinese terms are now
  in the unsegmented substring set.

Code-token extraction and the graph-verification path are unchanged;
non-structural prose stays a zero-cost no-op in every language.

Fixes #1126

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(prompt-hook): extend the gate to tier-2 languages (VI/TR/ID/PL/UA/NL/CS/RO/HU/EL/Nordics/FI/HI/AR/FA/HE/TH)

The first pass covered the 10 largest languages; this closes the rest of
the major-developer-population set (~29 total). Notable per-language
mechanics the curation had to respect:

- Agglutinative languages (Turkish, Finnish, Hungarian) need stems, not
  exact words — suffixes attach to everything (akışı, riippuu, működik).
- Indonesian me-/di-/ber- prefixes block leading-boundary stems, so
  affixed forms are listed explicitly (memanggil, dipanggil, berfungsi).
- Arabic/Farsi/Hebrew are spaced but proclitics attach to the word
  (وكيف = and-how), so they join the substring class with Thai.
- Ukrainian і/и spellings diverge from Russian (архітектур ≠ архитектур).
- Excluded terms that collide with English or code words: NL "pad",
  SV "var", CS "tok", Catalan "com" (matches every .com domain) — with
  regression tests pinning the exclusions.

Vietnamese was the sharpest gap: spaced Latin with heavy diacritics —
exactly the ASCII-\b failure class #1126 reports.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 14:34:17 -05:00
ed39233f1a fix(index): yield during resolution so the liveness watchdog can't kill a valid large index (#1091) (#1105)
The #850 liveness watchdog SIGKILLs a process whose main-thread event loop
stalls past its window (60s default). It was extended to `index`/`init` in
#999, but reference resolution and callback-edge synthesis run synchronously
on that same thread — so on a large repo a legitimate, in-progress index gets
killed, and users had to disable the watchdog entirely (CODEGRAPH_NO_WATCHDOG=1).

Make the long synchronous spans yield cooperatively so the heartbeat keeps
firing during real work, while a genuinely wedged span (which never reaches a
yield) still trips the watchdog:

- synthesizeCallbackEdges yields between its whole-graph passes, and the heavy
  scanners (closure-collection, event-emitter, JSX-child, object-registry,
  field-channel) yield within their loops;
- batched resolution sub-chunks each batch with yields;
- the deferred chained-call and this-member post-passes yield per ref.

Behaviour-preserving — only timing changes; node/edge counts are identical.

Validated end-to-end with the real watchdog armed at the default 60s: the
released build is SIGKILLed partway through indexing the Swift compiler (27k
files, ~1.1M edges) and the TypeScript compiler, while the fixed build indexes
both to completion.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 12:13:42 -05:00
9684b3b5a5 fix(index): rebuild a poisoned/oversized index by recreating the DB, not row-DELETE (#1067) (#1073)
Follow-up to #1065/#1066. Those stopped a *new* index from scanning an
ignored gitlink corpus, but a project that had already built the multi-GB
graph before upgrading still couldn't recover: `codegraph index` printed
only "Indexing project" and was then SIGKILLed (137) by the #850 watchdog
~60s later, before scanning even started.

Root cause is not the scanner. `index` cleared the old graph with a
synchronous `DELETE FROM nodes/edges/files`. `nodes` carries an FTS5
`AFTER DELETE` trigger, so deleting ~1.6M rows fires ~1.6M FTS
delete-markers — O(rows), and it grows the WAL further before it can
finish. A deterministic probe puts the DELETE-clear at 20.4s on 1.5M
synthetic nodes (WAL 1.16->2.14GB); at the report's denser ~2.6KB/node WAL
that crosses the 60s main-thread watchdog. `open()` was never the wedge.

A full re-index is documented as "same result as a fresh init", so make it
one: discard the database files and re-initialize, instead of opening the
old DB and DELETE-ing every row.

- db: add removeDatabaseFiles(dbPath) — unlinks codegraph.db + its
  -wal/-shm sidecars (O(1) regardless of size; sidecars best-effort).
- index: add CodeGraph.recreate(projectRoot) — discards the files and
  returns a fresh, empty instance. Never opens or migrates the poisoned
  DB. POSIX unlinks an open file fine (a live daemon heals via
  reopenIfReplaced, #925); a Windows file lock becomes an actionable
  "stop the daemon / remove .codegraph" error.
- cli: `codegraph index` now calls recreate() instead of open()+clear();
  both clear() calls dropped. The public clear() API is unchanged.

This also reclaims the disk the bloated db/-wal were holding.

Validated: deterministic probe (DELETE O(rows) vs recreate O(1)); an
end-to-end run through the built binary recovering a real 800K-node /
419MB poisoned DB in 0.3s with no wedge and the correct small graph; new
unit + CLI regression tests; existing #874 index tests still green.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 23:15:41 -05:00
4b58a6d2d0 fix(cli): stop rendering raw FTS score as nonsensical percentages in query (#1045) (#1052)
`codegraph query` printed `(score * 100)%` next to each hit, but `score`
is an unbounded BM25/FTS relevance magnitude (relative-ranking only), so
it rendered as values like "12042%" that made the output look broken.

Results already arrive in rank order, so drop the score from the
human-readable output entirely — matching the MCP search tool, which
shows no score. The raw `score` stays in `--json` for programmatic
sorting/thresholding. Also corrects the SearchResult.score doc comment,
which wrongly claimed a 0-1 range. Adds an end-to-end regression test.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 22:01:47 -05:00
0d331b9017 fix(cli): make node symbol positional optional so node -f <file> works (#1044) (#1051)
`codegraph node` was defined with a required `<name>` positional, so
commander.js rejected `codegraph node -f <file>` with "missing required
argument 'name'" before the action ran — making file-read mode (the CLI
face of the codegraph_node MCP tool's file mode) unreachable. The action
body already handled an absent name.

Make `name` optional (`[name]`), validate that a symbol or a file is
supplied (friendly usage hint instead of a cryptic commander error when
neither is), and guard the name-based arg branches so they never run on
undefined. Adds an end-to-end regression test across all four paths.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 21:54:26 -05:00
45d3293c6a fix(resolution): stop "Resolving refs" wedge on theme-vendoring repos; add exclude config + index watchdogs (#999) (#1009)
Three fixes for a repo that commits a large JS/TS theme/SDK (Metronic under
static/, ~1,600 tracked files):

1. A SECOND "Resolving refs" quadratic that #915 didn't cover. #915 capped
   import-name collisions; this caps method-name collisions (init/update/render
   re-declared on every widget), which flow through matchMethodCall Strategy 3
   and findBestMatch instead. New AMBIGUOUS_NAME_CEILING (default 500, env
   CODEGRAPH_AMBIGUOUS_NAME_CEILING): above it the fuzzy strategies decline
   rather than score K candidates — no proximity score can pick the one true
   target among thousands anyway. Resolving drops from O(K^2) to linear in refs
   (e.g. 900-file synthetic: 28.7s -> 3.4s), edge counts unchanged, and the cap
   never fires on normal repos (max real method-collision ~40).

2. A new `exclude` array in codegraph.json keeps git-TRACKED paths out of the
   index, which .gitignore can't do (enumeration is `git ls-files`). Mirrors the
   existing includeIgnored plumbing across the git, sync, and non-git-walk
   paths.

3. `index`/`init` now install the #850 liveness + #277 ppid watchdogs (which
   were serve-only), so a wedged or orphaned indexer self-terminates instead of
   pinning a core. The --liftoff-only relaunch's spawnSync can't forward
   signals, so killing the parent shim used to orphan the worker.

Tests: ubiquitous-name ceiling, exclude (incl. tracked-file exclusion on git +
non-git), orphan self-termination (POSIX), and ppid-parser units. Shared the
ppid parsers out of mcp/index.ts into mcp/ppid-watchdog.ts.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 20:25:47 -05:00
b45f309a1b fix(prompt-hook): fire front-load hook for non-English prompts (#994) (#1004)
The UserPromptSubmit hook's structural-prompt gate was English-only, so a
structural question written in Chinese — or any non-Latin script — silently
injected nothing: JS `\b` is ASCII-only and never matches between Han
characters, so the keyword regex couldn't fire (and couldn't be extended in
place). To the user the hook looked unwired, with no error to explain why.

Make the gate language-aware, split into tested helpers in directory.ts:
- hasStructuralKeyword: English (\b-guarded) + CJK structural keywords.
- extractCodeTokens: identifier-shaped tokens (camelCase / snake_case /
  name() / a.b) in any language — verified against the index via
  getNodesByName before firing, so a tech brand like `JavaScript` that looks
  like a symbol but isn't one here doesn't inject ~16KB of spurious context.
- isStructuralPrompt: the cheap candidate gate (keyword OR code-token).

Adds 21 unit tests for the gate (previously untested) covering the reporter's
verification table plus the false-positive guards.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 18:39:34 -05:00
85a8f32fd9 fix(mcp): serve tools without a root index + make the front-load hook monorepo-aware (#964) (#966)
The MCP server gated tool availability on whether the server root had a
.codegraph/ index, so in a monorepo where only sub-projects are indexed the
agent saw zero tools — and couldn't reach an indexed sub-project even by
projectPath. A session started before `codegraph init` also never surfaced the
tools afterward. The Claude front-load hook had the mirror gap: it only walked
UP for an index, so it stayed silent at a monorepo root.

MCP server:
- Always expose the tool surface; when the root isn't indexed, send a
  per-project instructions variant (pass projectPath) instead of the
  "inactive" note. Safety comes from response SHAPE (success-shaped guidance,
  never isError), not from hiding tools.
- Reword the no-default-project guidance to be per-project, not per-session,
  and sharpen the projectPath schema description.

Front-load hook (UserPromptSubmit):
- Scan DOWN (bounded depth, workspace-root-gated) for indexed sub-projects and
  shape the injection by topology: front-load the one the prompt names, nudge
  about the rest, or list them when ambiguous.

Verified: full suite (1703 passed); a live two-package monorepo run confirms the
hook front-loads the correct sub-project with no cross-package leakage. The
front-load's net speed effect is the existing multi-file-vs-single-file
tradeoff, unchanged by this work.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 12:57:47 -05:00
Colby McHenryandClaude Opus 4.8 bd4814d8c1 feat(installer): stop auto-indexing on install + ship opt-in front-load prompt hook
`codegraph install` no longer indexes the current directory — it wires up agents
only, and building a project's graph is always the explicit `codegraph init` /
`index`. Removes the global-vs-local inconsistency (a local install silently
indexed, a global one didn't) and the docs/behavior mismatch (#826). README
updated to match; the stale `init --index` note (indexing is default now) fixed.

Adds an opt-in Claude Code front-load hook: a `UserPromptSubmit` hook that runs
the new hidden `codegraph prompt-hook`, which injects codegraph_explore context
for structural ("how / where / trace / impact") prompts so the agent answers
from the graph instead of grepping to rebuild it. Prompted at install
(default-yes; Claude-only — the only agent with prompt hooks), removed on
uninstall, and `codegraph upgrade` self-heals it onto an already-configured
global Claude install. Strictly additive + degradable: non-structural prompts,
un-indexed projects, and any failure are silent no-ops. Disable without
uninstalling via CODEGRAPH_NO_PROMPT_HOOK=1.

7 new installer-targets contract tests (write / idempotent / opt-out round-trip /
sibling-preserved / uninstall / legacy-independent). Full suite green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 12:36:41 -05:00
Colby McHenry e5897d0334 feat: remove reasoning offload / CodeGraph AI managed reasoning feature
Strips the bring-your-own-model reasoning offload and managed CodeGraph AI
integration (login/logout/usage commands, offload config/credentials/reasoner
modules, and the synthesizeOffload call in codegraph_explore). The eval findings
showed raw source output outperformed the synthesized path on accuracy, so
codegraph_explore reverts to returning verbatim retrieved source exclusively.

CHANGELOG and README sections for reasoning offload are removed; test comments
and DEFAULT_MCP_TOOLS description are updated to drop offload references.
2026-06-20 13:23:16 -05:00
Colby McHenry 6d5cb6b25c feat(reasoning): add CODEGRAPH_OFFLOAD_DISABLE kill-switch and per-call usage log
`CODEGRAPH_OFFLOAD_DISABLE=1` immediately disables the offload for the current
process without touching the persisted config or stored login — useful for A/B
arms or sessions where raw source is preferred.

`CODEGRAPH_OFFLOAD_USAGE_LOG=` appends one JSONL entry per call with token
counts, charged credits, and derived cost (`creditsCharged / 100_000`) so a
harness can attribute CodeGraph AI spend to a single run independently of the
server's cumulative totals. Both features are best-effort and never disrupt the
degradable offload path.

Also fixes the `login` credit display to check `unlimited` before the numeric
balance, so comped/internal accounts don't incorrectly show "0 remaining".
2026-06-18 21:10:10 -05:00
Colby McHenry c9e207a0f2 feat(cli): add codegraph usage command to show AI balance and recent usage
Adds a `usage` subcommand that pings `/v1/usage` with the stored token and
displays balance, plan, 30-day explore/token counts, and allowance reset date.

Degrades quietly in all non-happy-path states — signed out, BYO endpoint, or
unreachable server — so managed reasoning remaining optional doesn't change.

Also extends `OffloadUsage` with the fields the endpoint already returns
(`unlimited`, `banned`, `tokensLast30`, `callsLast30`, `creditsLast30`) that
were previously untyped.
2026-06-18 01:22:58 -05:00
Colby McHenry 193722de45 feat(cli): replace offload subcommands with browser device-authorization login / logout
The old `offload` command family required users to paste a token manually (`offload login --token `) and exposed bring-your-own-endpoint plumbing (`set-endpoint`, `status`, `disable`) as top-level CLI surface. This replaces it with a standard OAuth device flow (RFC 8628 shape) against the CodeGraph dashboard.

`codegraph login` calls `/api/cli/device/start`, opens the browser to the returned URL, polls `/api/cli/device/token` until the user approves, then stores the minted token and enables managed reasoning. `codegraph logout` clears it. BYO-endpoint configuration moves entirely to env vars (`CODEGRAPH_OFFLOAD_URL` / `CODEGRAPH_OFFLOAD_KEY` / `CODEGRAPH_OFFLOAD_MODEL`), keeping the CLI surface minimal.
2026-06-18 00:15:40 -05:00
Colby McHenryandClaude Opus 4.8 da5c6c2f79 feat(offload): managed tier (CodeGraph AI) — metered reasoning via org token [WIP]
Adds the managed offload mode: point codegraph_explore at the CodeGraph AI metered
gateway (https://ai.getcodegraph.com) with an org token instead of a BYO provider key.
Same synthesis client, pointed at codegraph-ai-proxy (a metered OpenAI-compatible gateway).

- credentials.ts — org token in ~/.codegraph/credentials.json (0600); unlike a BYO
  provider key it's a revocable org-scoped auth token (gh/npm-login style), kept out
  of config.json
- config.ts — managed branch in resolveOffload: default gateway URL + public model id
  (openai/gpt-oss-120b) + login token as bearer; managed requires a token to be enabled
- reasoner.ts — fetchUsage() reads the credit balance from /v1/usage
- bin/codegraph.ts — `codegraph offload login --token <t>` / `logout`; status shows the
  managed tier + live balance

Proven GREEN end-to-end against a local wrangler-dev of the proxy: org token validated,
credits prechecked, real Cerebras synthesis returned, and credits metered + charged
(250,000 → 248,473). Graceful degrade on upstream failure; balance via /v1/usage.
Phase 3 (codegraph login device flow) replaces the manual --token.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 15:06:42 -05:00
Colby McHenryandClaude Opus 4.8 db4c9f3641 feat(offload): reasoning offload for codegraph_explore (bring-your-own endpoint)
codegraph_explore can now hand the source it retrieved to a reasoning model you
point at — any OpenAI-compatible endpoint (Cerebras, OpenAI, a local vLLM/Ollama)
with your own key — and return that model's tight, cited answer instead of the
raw source dump. The agent's main context gets the answer in far fewer tokens, at
the cost of one network round-trip.

Off by default. Configure with `codegraph offload set-endpoint <url> --model <m>
--key-env <ENV>` (or the CODEGRAPH_OFFLOAD_* env vars); status/disable manage it.
The API key is never written to disk — the config stores the NAME of an env var
and the key is read from it at call time. Strictly degradable: any failure
(no endpoint, network, timeout, empty answer) returns null and the call falls
back to the local source, so the offload can never surface an error to the agent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 14:18:22 -05:00
b49147eab0 fix(cli): make codegraph index a full rebuild so it stops reporting 0 nodes (#874) (#894)
`codegraph index` ran extraction against the already-populated DB without
clearing it first. On an unchanged tree every file's content hash still
matched, so the orchestrator skipped re-inserting all of them and the run
reported its delta (after - before = 0) as "0 nodes, 0 edges" — which read as
if `index` had wiped the graph. `init` only ever differed because it runs on a
freshly created, empty DB.

Clear the existing graph before re-indexing so `index` rebuilds from scratch
and reports the same complete result as a fresh `init`. `--force` keeps its
role as the home-dir/root-path override; `sync` stays the incremental path.

Adds an end-to-end regression test driving the built binary (init -> index),
asserting the graph stays populated and the summary is never "0 nodes, 0 edges".

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 00:45:03 -05:00
64ff7597d0 fix(cli): stop serve --mcp from confusing humans — hide it + explain on a TTY (#867)
`codegraph serve --mcp` is the stdio MCP server an AI agent launches for itself
(the installer wires it into every agent's MCP config), not a command a human
runs. Run by hand in a terminal it just hung waiting for JSON-RPC, looking
broken.

- Hide `serve` from `--help` (commander `{ hidden: true }`); it stays fully
  invocable, so agents are unaffected.
- When stdin is an interactive TTY (a person — never the agent's pipe or the
  detached daemon), print what it is and point to `codegraph status` /
  `codegraph daemon`, then exit instead of hanging.
- README: drop `serve --mcp` from the CLI Reference and stop the troubleshooting
  section from telling users to run it; keep the accurate "your agent launches
  it" note.

Verified: agent path intact (22 MCP handshake/daemon tests pass), `serve` absent
from --help, and the TTY path prints the message and exits cleanly.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 15:18:55 -05:00
f7441f2124 fix(resolution,cli): cross-file static method calls + affected path normalization (#825) (#865)
Cross-file `ClassName.staticMethod()` calls resolved to the class, not the
method: the import resolver matched the receiver `Foo` to the named class
import but dropped the `.bar` member, and createEdges then mis-promoted the
`calls` edge to `instantiates`. So callers/impact for the static method came
back empty. Descend from the resolved class into its `Container::member` so the
call links to the method; fall back to the class when no such member exists
(non-`::` languages and genuine class references are unaffected).

Also normalize `codegraph affected` inputs to the project-relative,
forward-slash form the index stores, so `./src/x.ts`, an absolute path, and a
Windows back-slash path all match (previously silently returned 0).

Validated on luxon (24 files): node/edge totals identical (no explosion), 69
mis-promoted `instantiates` edges become `calls`, and real static factories
(DateTime.fromISO, etc.) resolve their callers. Full suite: 1534 passed.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 14:48:52 -05:00
070ce4da2b feat(cli): codegraph version command + complete CLI Reference (#864)
* feat(cli): codegraph version command + complete CLI Reference

Add a `codegraph version` subcommand plus the `-v` and `-version`
spellings (commander already wires up `--version`/`-V`), so the version
is easy to reach however a user guesses at it. The `-v`/`-version` forms
are intercepted before commander parses — its version short flag is the
capital `-V`, and its parser rejects a multi-character single-dash flag.
A trailing `-v` on a subcommand still means `--verbose`.

Document the previously-missing commands in the README CLI Reference:
`daemon`/`daemons`, `unlock`, `telemetry`, `version`, and `help`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(changelog): reference #864 on the version-command entry

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 14:14:32 -05:00
Colby MchenryandGitHub ff288ac711 feat(cli): one interactive codegraph daemon command, replaces stop/list (#863)
Collapses the unreleased daemon controls into a single interactive command.
`codegraph daemon` (alias `daemons`) opens an arrow-key picker (current project's
daemon first, pre-selected), enter stops it, or pick "Stop all"; non-TTY prints a
plain list. Removes stop/list/ps; reuses the unchanged daemon-registry machinery;
the pick->stop loop is in daemon-manager.ts behind an injectable select (unit
tested). Validated live on macOS/Linux (real clack picker driven via pty) and
Windows (real runDaemonPicker + stopDaemonAt against a real daemon). Closes #845
follow-up.
2026-06-13 13:53:38 -05:00
Colby MchenryandGitHub 0f825649a1 feat(cli): codegraph stop / list to manage background daemons (#861)
Adds first-class daemon control (the #845 pain point: no clean way to stop a
runaway daemon). `codegraph stop [path]` stops the current/given project's
daemon (SIGTERM -> SIGKILL fallback, sweeps artifacts); `stop --all` stops every
daemon; `list`/`ps` shows running daemons (--json for scripts).

Discovery via a small self-healing registry: each daemon records its root under
~/.codegraph/daemons/ on start, removes it on graceful shutdown; readers prune
dead pids. Cross-platform by construction (files + process.kill). Validated live
on macOS, Linux (docker), and Windows (VM): registry unit 6/6 and real-daemon
stop/list 6/6 on each.
2026-06-13 12:59:41 -05:00
Colby MchenryandGitHub 2472508549 fix(installer,cli): refuse to index $HOME / filesystem root (#860)
Running the installer or `codegraph init`/`index` from $HOME auto-indexed the
entire home tree (installer indexes process.cwd() with no guard), producing a
multi-GB ~/.codegraph/codegraph.db; the install dir sharing the ~/.codegraph
name then made every home subdir resolve its root to $HOME. On pre-1.0 macOS the
per-file watcher over that tree exhausted kern.maxfiles and crashed the machine
(#845; the fd blowup was fixed in 1.0.0, this fixes the root cause).

Add unsafeIndexRootReason() and refuse the home dir, a parent of home, and
filesystem roots at the installer auto-index, `init`, and `index`. Overridable
with --force. Closes #845.
2026-06-13 12:35:18 -05:00
Colby MchenryandGitHub 3476ac9a27 fix(mcp): exit on uncaught exception instead of orphaning/spinning at 100% CPU (#855)
The process-wide uncaughtException handler logged the error and kept running. For
the detached `serve --mcp` daemon that turned any escaped fault into an
unrecoverable orphan: nothing respawns it, and when logging the raw Error hit a
V8 source-position loop while lazily formatting `.stack`, the main thread wedged
at 100% CPU so even the PPID watchdog / idle-timer could no longer fire. Same
failure mode as #799, which only fixed the stdin-'error' trigger.

Restore Node's default fatal semantics: render a bounded, hang-proof line (name +
message only — never read `.stack`) then exit non-zero, so a fresh daemon starts
on the next connection. Extracted to src/bin/fatal-handler.ts with injectable
seams; unit-tested incl. the never-touch-stack invariant.

Closes #850.
2026-06-13 09:48:44 -05:00
848fde9f59 feat(telemetry): anonymous usage telemetry — documented schema, opt-out, public ingest worker (#834)
Adds anonymous usage statistics (commands/tools used, languages indexed,
connecting agents) with a strict, auditable allowlist. Never code, paths,
file/symbol names, queries, or IPs.

- src/telemetry/: zero-dep client — consent resolution (DO_NOT_TRACK >
  CODEGRAPH_TELEMETRY > stored choice > default-on), random machine UUID,
  in-memory counters → capped JSONL buffer → completed-day rollups; sync
  exit-append (survives process.exit) + opportunistic bounded sends; the
  first-run notice gates the first SEND, never local buffering, so the
  installer's consent toggle always precedes it. Off is off: no recording,
  no socket, buffered data deleted.
- codegraph telemetry status|on|off; per-command counting via preAction hook.
- MCP: tool counting after the reply is on the wire (session + proxy
  in-process fallback), agent attribution from initialize clientInfo,
  unref'd daemon flush interval. Zero hot-path cost, zero stdout.
- Installer: visible default-on consent toggle (asked once, never re-asked),
  install/index/uninstall lifecycle events.
- telemetry-worker/: public Cloudflare Worker behind telemetry.getcodegraph.com
  — allowlist validation, IP stripping, per-machine rate limit, forwards to
  PostHog as anonymous events. Ships nowhere with the npm package.
- TELEMETRY.md (field-by-field contract) + README section + design doc.
- 20 unit tests; suite-wide CODEGRAPH_TELEMETRY=0 guard so tests never
  pollute real telemetry. Full suite: 1448 passing.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 10:37:19 -05:00
01717854f5 fix(cli): codegraph node accepts Windows backslash paths in file mode (#822)
The file-vs-symbol heuristic only matched '/' — `codegraph node
src\auth\session.ts` on Windows fell through to symbol mode and found
nothing. Both separators now route to file mode, normalized to forward
slashes (the form the index stores). Symbols never contain either
separator in any indexed language.

Validated: macOS smoke (explore/node symbol/node file/unindexed
refusal) + Linux Docker (same smoke + full suite, 1428 passed).
Windows VM validation queued — the Parallels guest is currently
unreachable (control commands are Pro-gated; needs a manual start).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 00:30:56 -05:00
adcb862f8e fix(cli): explore/node not-indexed error stops agents from running init themselves (#821)
The message said "run 'codegraph init' first" — an instruction-shaped
error that invites an agent hitting it (e.g. a subagent following the
global instructions block into an unindexed repo) to index the project
uninvited: minutes of CPU and a surprise .codegraph/ the user never
asked for. Every other layer already encodes indexing-is-the-user's-
decision (the MCP NotIndexedError guidance, the inactive instructions,
the conditional block); the CLI now matches: continue with your usual
tools, do not run init yourself, the project owner can enable it.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 00:24:08 -05:00
8170d181f2 feat(cli+installer): codegraph explore/node CLI + instructions-file block — subagent & non-MCP reach (#704) (#819)
Task-tool subagents never see the MCP initialize instructions and hold
the MCP tools only as deferred names they rarely think to load — so
delegated work bypassed codegraph almost entirely (measured ~1 of 9
forced-delegation runs touched it; the rest did 30-50 grep/read calls).
Two additions close the gap:

- CLI: `codegraph explore` and `codegraph node` call the same ToolHandler
  as the MCP tools and print identical output — the graph for any agent
  with a shell (subagents, Gemini CLI, raw Codex, humans).
- Installer: each agent target (claude/codex/gemini/opencode) writes a
  short marker-fenced CodeGraph section into its instructions file —
  the one channel subagents DO receive — naming both surfaces. Upsert
  self-heals the stale pre-#529 long block; uninstall strips it; re-runs
  are byte-equal unchanged. (#529's duplication argument bounded the
  size: four lines, commands only.)

A/B (excalidraw, sonnet/high, forced Explore-agent delegation): without
the block, subagent codegraph usage ~1/9 runs; with it, 4/4 — subagents
ToolSearch-load the MCP tools and run explore 5-7x, best runs with ZERO
Read/grep (80-95s vs 150-197s baseline). The block's mechanism: the
parent relays the note into the task prompt, making the deferred tool
names salient.

Contract tests updated to the new expectations (write + self-heal
replace the #529 strip-only behavior); README install/guidance sections
refreshed (they also still described the pre-#817/#818 tool surface).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 21:50:41 -05:00
a56d9e6941 feat(directory): CODEGRAPH_DIR env var to override the index dir name (#636) (#741)
Two environments that share one working tree — most concretely Windows
and WSL — can't safely share a single `.codegraph/`: the daemon lockfile
records a platform-specific pid + socket (named pipe vs Unix socket), and
SQLite locking across the WSL2/Windows filesystem boundary is unreliable,
so two daemons over one index risks corruption.

Add a `CODEGRAPH_DIR` env var (default `.codegraph`) that overrides the
per-project data directory name, so each environment keeps its own index
in the same tree (e.g. `CODEGRAPH_DIR=.codegraph-win` on Windows). The
name is resolved live and validated (rejects separators / `..` / absolute,
falling back to the default with a one-time stderr warning). Indexing and
file-watching now skip ANY `.codegraph-*` sibling so neither side trips
over the other's data.

Routes the previously-hardcoded `.codegraph` literals (db path, lockfile,
error log, watcher ignore, file-scan skip, installer) through the
resolver. No extraction-version bump — index content is unchanged.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 19:31:50 -04:00
4e5cf2de56 feat(cli): add codegraph upgrade self-update + stale-index re-index hint (#710)
`codegraph upgrade [version]` detects how the CLI was installed — the standalone
install.sh/install.ps1 bundle, npm-global, npx, or a source checkout — and
updates in place: re-running the canonical install.sh on macOS/Linux, an
in-place rename-and-extract swap on Windows (a running node.exe can't be
deleted, only renamed, so the detached-helper approach is avoided), and
npm/npx/source-specific guidance otherwise. Flags: `--check` (report only),
`--force`, and a positional version to pin.

Each full index is now stamped with the engine's EXTRACTION_VERSION in
project_metadata; `codegraph status` (and `--json`) flags an index built by an
older engine and recommends re-indexing, and `upgrade` prints the same reminder.
Gated on EXTRACTION_VERSION so it never nags on extraction-neutral releases.

Validated end-to-end on macOS (real bundle upgrade), Linux (Docker, real
curl|sh) and Windows (Parallels VM, real in-place swap). 32 new unit tests.

Closes #679

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 13:38:38 -04:00
7b62356f53 feat(cli): add version, indexPath, lastIndexed to status --json (#329)
Adds `version`, `indexPath`, and an ISO `lastIndexed` to `codegraph status --json`, plus a `CodeGraph.getLastIndexedAt()` library method. `agentCount` dropped (no clear consumer). Reworked from contributor PRs #333 and #480.

Co-Authored-By: Javier Gómez <199902626+12122J@users.noreply.github.com>
Co-Authored-By: Ran <8403607+eddieran@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 17:50:33 -05:00
Colby MchenryandGitHub 68eaf0dbd8 feat(mcp): codegraph_explore as the sole primary tool + store coverage + overload disambiguation (#647)
## Summary

Completes the explore-overhaul arc: `codegraph_explore` becomes the single primary tool an agent reaches for, and its coverage + output shape are tuned so flow/architecture questions resolve with near-zero Read/Grep.

### What changed
- **explore is the sole primary tool** — removed `codegraph_context` (the fuzzy-input Read-trigger) and `codegraph_trace` (under-picked by agents); explore already surfaces the call flow among the symbols you name. A plain natural-language question now works as the query.
- **Store/handler coverage** — functions defined inside object literals (Zustand `create((set, get) => ({ … }))`, Redux/Pinia/MobX, exported handler/route maps) are indexed as real symbols, including calls through `useStore.getState().fn()` and destructured `const { fn } = useStore.getState()`. A general AST rule, not a per-lib hack.
- **Overload disambiguation** — explore leads with the *right* definition when a method name is overloaded across types (a PascalCase type token in the query biases to that type's own def); `codegraph_node` returns *every* overload's body in one call, with an optional `file`/`line` selector to pin one.
- **Method-atomic render** — explore never returns half a method; at the size budget it drops whole methods/files (and lists what it dropped) instead of truncating a body mid-method.
- **Native-read-shaped output** — per-call output is capped to ~24K with a 25K hard ceiling and concentrated into ~150–250-line flow windows, mirroring how the agent natively reads; repo size scales the *call* budget, not the per-call size (a larger response just gets externalized to a file the host Reads back).
- **Blast radius** folded into explore (dependents + covering tests, locations only).

### Benchmark (refreshed on this build)
Re-validated the 7-repo A/B on 2026-06-02 (Opus 4.8, effort=high, median of 4). WITH arm re-measured on this build, WITHOUT reused:

**~16% cheaper · 47% fewer tokens · 22% faster · 58% fewer tool calls** — 0 file reads on 6 of 7 repos (Gin ~1).

The arc trades larger, cache-heavy explore responses for guaranteed near-zero reads, so cost/token margins soften vs the prior build (Excalidraw and Tokio land at cost break-even) while time and tool-calls stay clear wins everywhere — consistent with the project's stated optimization target (latency + tool-calls, not token cost).

### Validation
- Full suite green: **1112 passed, 2 skipped**.
- 28/28 plain WITH runs across the 7 README repos completed clean; reads median 0 on 6/7.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-06-02 10:15:27 -05:00
7a75c82dd9 feat(cli): codegraph init builds the initial index by default (#483) (#546)
`codegraph init` now runs the initial index automatically. The -i/--index
flag is kept but is now a no-op, accepted for backward compatibility so
existing muscle memory and scripts don't break. Addresses #483, where a user
asked why -i wasn't implicit.

README and site/ docs are intentionally NOT updated in this commit — they
describe the currently-released 0.9.7 behavior (where -i is still required).
Update them at the 0.9.8 release so users on 0.9.7 aren't misled.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 23:25:07 -05:00
a9c9e76d8c fix(installer): stop duplicating agent instructions; MCP server is the single source of truth (#529) (#538)
The installer wrote a `## CodeGraph` usage block into each agent's
instructions file (CLAUDE.md / AGENTS.md / GEMINI.md / .cursor/rules /
Kiro steering) that duplicated, almost verbatim, the guidance the MCP
server already emits in its `initialize` response — so agents that
surface MCP instructions (Claude Code) read the same playbook twice
every turn.

All 6 instruction-writing targets (claude, cursor, codex, opencode,
gemini, kiro) now stop writing the block. install self-heals by
stripping a block a previous version wrote (uninstall already did), so
the next `codegraph install`/`uninstall` cleans up existing installs;
upgrading the package alone does not (the leftover block is harmless).
server-instructions.ts is now the single source of truth — the two
steers unique to the old template ("trust codegraph, don't re-verify
with grep" and the not-initialized -> `init -i` hint) are ported there.

Removes the now-dead INSTRUCTIONS_TEMPLATE / CLAUDE_MD_TEMPLATE,
claude-md-template.ts, writeClaudeMd / hasClaudeMdSection, and the
Cursor-only wireProjectSurfaces bootstrap. The install log learned a
"Removed" verb. Tests rewritten to the new contract + self-heal
coverage (140/140 installer tests pass).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 15:13:23 -05:00
71935e37c2 feat(mcp): multi-module Go trace-quality + small-repo retrieval tuning (#494)
* feat(go): generated-file down-rank + gRPC stub-impl bridge + trace-failure inlining

Multi-pronged fix to make codegraph competitive on Go multi-module repos
(cosmos-sdk, etcd) where it previously lost or tied. Driven by an 8-question
agent-eval audit across cobra, gin, prometheus, cosmos-sdk, and etcd: the
baseline had codegraph losing ~60% on cost on cosmos-sdk and mixed on etcd
deep cross-module flows, while winning cleanly on the single-module and
non-protobuf-heavy repos.

Diagnostics ruled OUT `go.work` parsing as the gap (prometheus crushes
without it). The actual failure modes were generated-file noise warping
disambiguation, missing gRPC interface→impl bridge in structural-typing Go,
and trace's failure path triggering 3-5 follow-up tool calls instead of
inlining the material the agent needed.

Changes:

- New `src/extraction/generated-detection.ts` — path-pattern classifier
  for `.pb.go`, `.pulsar.go`, `_grpc.pb.go`, `_mock.go`, `_mocks.go`,
  `mock_*.go`, `.generated.[jt]sx?`, `_pb2(_grpc)?.py`, `.pb.{cc,h}`,
  `.g.dart`, `.freezed.dart`. Applied as a stable sort tiebreaker in
  `findSymbol`, `findAllSymbols`, `codegraph_search` (MCP + CLI),
  `codegraph_explore` file ranking, and context formatter Entry Points /
  Related Symbols / Code blocks. Cosmos's `msgServer.Send` now ranks #3
  instead of #9 on a `Send` search.

- New `goGrpcStubImplEdges` synthesizer in `callback-synthesizer.ts` —
  detects `UnimplementedXxxServer` structs in generated files, identifies
  their RPC methods (excluding `mustEmbed*` / `testEmbeddedByValue` gRPC
  markers), and emits `calls` edges to the matching methods on any
  non-generated struct whose method-name set is a superset. Closes Go's
  structural-typing gap that the existing `interfaceOverrideEdges` (Java /
  Kotlin only) couldn't bridge. 467 bridge edges on cosmos-sdk; bank's
  `UnimplementedMsgServer::Send` points to `x/bank/keeper/msg_server.go`
  only, not to `msgClient` siblings or mock files.

- Trace-failure rewrite (`handleTrace`) — when no static path connects
  endpoints, instead of telling the agent to call `codegraph_node` (a
  3-4-call fan-out), inline both endpoints' bodies (120 lines / 3600 chars
  per endpoint), their callers (≤6), and callees (≤8) in one response.

- Trace endpoint-pairing improvements — scores every `from`×`to`
  candidate combo by shared directory prefix and tries the best-paired
  pair first (the full candidate set, not just FTS top-5). A
  less-canonical-path penalty (`enterprise/`, `contrib/`, `examples/`,
  `vendor/`, `third_party/`, `deprecated/`, `legacy/`) ensures the
  canonical-module pair wins even when a side-experiment shares more of
  its directory prefix. Find-path probe budget capped at 20 pairs.

- Test-file deprioritization in `codegraph_explore` `isLowValue` — adds
  suffix patterns (`_test.go`, `_spec.rb`, `.test.ts`, `.spec.tsx`,
  `Test.java`, `Spec.kt`) alongside the existing directory-style patterns.
  Otherwise etcd's `watchable_store_test.go` consumes 5K chars of explore
  budget that should go to the hand-written flow source.

Tests:

- New `__tests__/generated-detection.test.ts` (4 unit tests) pins the
  suffix patterns.
- New "Go gRPC stub→impl synthesis" integration test suite in
  `frameworks-integration.test.ts` (2 tests): positive bridge from stub
  to hand-written impl, AND the precision case (don't bridge to a
  generated sibling like `msgClient` in the same .pb.go).
- Full suite: 1076/1076 pass.

Empirical (post-fix, n=2 average per question):

| Repo / Q                | WITH       | WITHOUT     | Reads (W/WO) | Time (W/WO)
|-------------------------|------------|-------------|--------------|------------
| cobra (parse cmds)      | $0.27      | $0.27       | 0 / 4        | 39s / 60s
| prometheus (scrape→TSDB)| $0.63      | $0.70       | 0 / 6        | 106s/143s
| cosmos-sdk Q1 (MsgSend) | $0.41      | $0.26       | 1 / 2        | 67s / 64s
| cosmos-sdk Q2 (Delegate)| $0.47      | $0.46       | 0 / 5        | 50s / 73s
| cosmos-sdk Q3 (gov tally)| $0.34     | $0.31       | 1.5 / 3      | 54s / 76s
| etcd Q1 (Put→raft)      | $0.65      | $0.78       | 0 / 4        | 98s / 129s
| etcd Q2 (watch)         | $0.36      | $0.50       | 0 / 4+       | 58s / 89s

Codegraph wins on reads + time on every question. Cost is mixed: 3 clean
wins, 3 tied (within 10%), 1 stubborn cost loss on the grep-favored Q1.
Compared to baseline, the cosmos-sdk cost-gap collapsed from -60% to -15%
on average, and Q3 went from a 75% loss to a tie. Raw run artifacts in
`/tmp/cg-finalv2-*/` and `/tmp/cg-final-*/`.

Memory written at `project_go_multi_module_audit.md` for the methodology
+ before/after numbers.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(mcp): auto-inline trace in codegraph_context for flow queries

When a codegraph_context task contains a flow keyword ("trace", "from",
"reach", "flow", "propagat", "how does", "how do") AND at least two
distinct PascalCase / camelCase identifiers, internally invoke trace
between the first two extracted symbols and splice the trace body into
the context response. Conservative trigger by design: false positives
waste one graph query; false negatives just fall back to the agent
calling trace itself (existing path-proximity wiring handles either
case).

Goal: collapse the agent's typical context → trace → explore sequence
into a single context call for clear flow queries, closing the
remaining cost-overhead gap on multi-call patterns. The path-proximity
+ less-canonical-path scoring + the trace-failure-inlined-bodies
behavior already let the inline trace land on the right endpoint pair
and return enough material that no follow-up codegraph_node/Read is
needed.

Doesn't fire on:
- cobra's "How does cobra parse commands and flags?" (no PascalCase
  symbols) — verified in regression run, no behavior change ($0.260
  WITH vs $0.257 WITHOUT, basically tied)
- queries where the agent doesn't call codegraph_context at all
  (cosmos Q1 in the audit went search → trace → node → trace → node)

Tests: 1076/1076 still pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(mcp): trace failure inlines TO file siblings to displace node fan-out

The cosmos-Q1 audit revealed a static-resolution gap: msgServer.Send's
*real* next hop is `k.Keeper.SendCoins` — an interface-method call on an
embedded field that tree-sitter can't resolve. The static getCallees list
for msgServer.Send is all utility/error functions (StringToBytes, Wrapf,
…). The actual flow (SendCoins → subUnlockedCoins → addCoins →
setBalance) lives entirely inside `x/bank/keeper/send.go`, which is also
where the TO endpoint (setBalance) lives.

When trace fails (no static path), inline the **top 5 functions/methods
in the destination file**, ordered by line-distance from the TO node.
This catches the flow that interface-method calls obscure — the
canonical "k.<Iface>.<Method>" pattern in Go, also relevant to Java
dependency-injection / Rails service-object dispatch / etc. where
interface dispatch hides the real call.

Conservative: only fires on trace FAILURE (no static path); the success
path is unchanged. Per-body cap (40 lines / 1200 chars), top 5 siblings.
Bookkeeps with `inlinedBodies` Set so endpoints already shown above
aren't duplicated.

Result: cosmos-Q1 — historically the most stubborn cost loss (-2.2× to
-39% across the audit) — flipped to a clean WIN: $0.257 WITH vs $0.449
WITHOUT (-43%), 34s vs 79s, 0 Reads vs 2 Reads + 5 Greps, 5 codegraph
calls vs 12. Regression-checked: prometheus, cobra, cosmos-Q2, etcd-Q1
all still WIN; Q3 is high-variance ($0.30-$0.45 range historically) and
fell within that on this run.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: extend coverage to all supported languages, not just Go

PR review feedback: the audit was Go-driven, so the patterns I added
were Go-flavored. Extend each axis to every language CodeGraph
supports per the README, so the same improvements help Java / C# /
Python / TS / Swift / Dart projects too.

**generated-detection.ts** — Added patterns for:
- TS/JS: `.gen.[jt]sx?`, `.pb.[jt]s`, `_pb.[jt]s`, `_grpc_pb.[jt]s`
  (ts-proto, gRPC-web, Apollo / GraphQL codegen, Hasura).
- Python: `_pb2.pyi` (mypy stubs from protobuf).
- C#: `.g.cs` (T4 / Razor codegen), `Grpc.cs` (protoc-gen-csharp).
- Java: `OuterClass.java` (protoc-gen-java), `Grpc.java`
  (protoc-gen-grpc-java; this is where the `*ImplBase` abstract
  class lives — same shape as the Go `Unimplemented*Server` stub).
- Swift: `.pb.swift` (protoc-gen-swift).
- Dart: `.pb.dart`, `.pbgrpc.dart`, `.chopper.dart`.
- Rust: `.generated.rs`.

**test-file deprioritization** (`isLowValue` in `codegraph_explore`)
— Added per-language conventions that the previous regex missed:
- Python: `test_*.py` (pytest discovery) and `*_test.py`.
- Ruby: `*_test.rb` (minitest) — `*_spec.rb` already covered.
- C#: `*Tests.cs`, `*Test.cs`, `*Spec.cs`.
- Swift: `*Tests.swift` (XCTest).
- Dart: `*_test.dart`.

**IFACE_OVERRIDE_LANGS** in `callback-synthesizer.ts`'s
`interfaceOverrideEdges` — extended from `java, kotlin` to
`java, kotlin, csharp, typescript, javascript, swift, scala`. Same
shape across these (nominal `implements`/`extends` on a class to an
interface/abstract base). Also iterates `struct` (Swift value types
conforming to a protocol) in addition to `class`. The existing
matchesSymbol-style logic and `getOutgoingEdges(..., ['implements',
'extends'])` work unchanged.

**CLAUDE.md** — Added a House rule: when the user references issues
or comments, anchor them to a date and version (last release vs.
last main commit vs. current branch tip) BEFORE concluding a fix is
incomplete. Issue #388 comments from May 25-27 were responding to
the released v0.9.5 / merged-PR-469 state — not to this branch's
in-flight work. The new rule walks through the disambiguation:
`grep -m1 '^## \[' CHANGELOG.md` for release version, `git log
--first-parent main -1` for main tip.

Tests: 1076/1076 still pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(mcp): tiny-repo tool gating + shorter tool descriptions

Two cumulative changes targeting the small-repo cost gap surfaced by
the cross-language audit:

1. **Tool descriptions trimmed** (~2.1KB total saved across 10 tools).
   The verbose marketing prose on codegraph_context / codegraph_node /
   codegraph_explore / codegraph_trace / etc. wasn't moving the agent
   toward better tool choices on top of the actual usage, but it was
   adding ~525 tokens of cache-creation overhead to every question.
   The trimmed descriptions keep the operational hints (e.g. "Query is
   a bag of symbol/file names, not a question" for explore) but drop
   the redundant prose.

2. **Dynamic tiny-repo tool gating** in `ToolHandler.getTools()`. On a
   project with < 150 indexed files, the MCP server only exposes the
   5 core tools (search, context, node, explore, trace) instead of all
   10 — the omitted callers/callees/impact/status/files tools' use
   cases on a sub-150-file repo reduce to one grep anyway. The MCP
   tool-defs overhead is the #1 source of cost loss on tiny repos
   (~$0.10-0.15 fixed cache-creation per question); cutting 5 tools
   drops that by ~50%.

   Effect on ky (~25 files, the worst pre-fix offender):
     - Before: $0.59 WITH vs $0.42 WITHOUT (+42% loss, n=1)
     - After:  $0.32 WITH vs $0.44 WITHOUT (-26%, **flipped to WIN**)

   Effect on cobra/sinatra/slim (50-80 files): still cost-loss, but
   the gating doesn't regress them — same call-count, same reads.
   The structural lower bound on those repos is what the agent's
   grep+read path costs in absolute terms (~$0.20-0.30).

   Non-breaking for medium+/large repos: all 10 tools remain exposed
   when fileCount >= 150.

Tests: 1076/1076 still pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(mcp): combined tiny-tier — smaller explore + tool gating (cobra/ky flip to WIN)

Combines the tool gating from the previous commit with a matching
explore-budget cut for projects under 150 files. The two together close
the cost gap that neither closes alone:

- Tool gating alone helped ky (WIN) but didn't move cobra/slim/sinatra
- Explore-budget cut alone helped slim slightly but regressed cobra
- COMBINED: cobra flips to WIN, ky stays a WIN, ky/cobra both clean

`getExploreOutputBudget(fileCount < 150)` returns:
  maxOutputChars: 13000     (was 18000)
  defaultMaxFiles:  4       (was 5)
  gapThreshold:     7       (was 8)
  maxSymbolsInFileHeader: 5 (was 6)
  maxEdgesPerRelationshipKind: 4 (was 6)
  includeRelationships: true   (kept ON — cheap structural signal)
  maxCharsPerFile: 3800        (unchanged — monotonic invariant w/ next tier)

This survives the cobra-regression-with-trim that the earlier
budget-only attempt suffered: with only 5 tools to choose from, the
agent doesn't fall back to extra codegraph_node calls when explore
returns less — there's no node call available.

Results on the four worst small-repo losses (combined intervention):

| Repo   | Files | WITH (combo)| WITHOUT     | Verdict (pre → post)     |
|--------|-------|-------------|-------------|--------------------------|
| cobra  | ~50   | $0.25       | $0.31       | loss → **WIN** (-19%)    |
| ky     | ~25   | $0.39       | $0.39       | -42% → tied              |
| slim   | ~80   | $0.31       | $0.24       | LOSS 31% → still LOSS    |
| sinatra| ~60   | $0.30       | $0.23       | LOSS 18% → still LOSS    |

sinatra/slim remain a cost-loss because their WITHOUT path is
structurally cheap (~$0.20 — fewer than 4 cheap grep+read calls).
Codegraph can't beat that absolute floor with any meaningful response.
Both still WIN on time + reads + tool-call count.

Tests: tier boundary cases updated to cover the new <150 / 150-499 /
500-4999 / 5000-14999 / >=15000 progression. Off-by-one guard updated
to include the new 149↔150 boundary. All 1076 tests pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(context): trim maxNodes default to 8 on tiny repos

On a <150-file project the entire repo is grep-able in one turn, so the
20-node default `codegraph_context` was paying for a graph subset that
exceeds the agent's actual question. Cutting the tiny-repo default to 8
(typical 1-3 entry points + their immediate 1-hop neighbors) reduces
the context-tool response body without hitting sufficiency on the flow
shapes small repos actually contain.

Non-breaking: the agent can still pass an explicit `maxNodes` to
override; medium+ repos (>=150 files) keep the 20-node default.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(mcp): pin the empirical 5-tool gating floor for tiny repos

n=2 audit on cobra/ky/sinatra ruled out cutting below 5 tools (search +
context + node + explore + trace) on the tiny-repo tier. The smaller
3-tool gate (search + context + trace) saved ~$0.025 of prompt overhead
but the agent fell back to extra Reads to cover what codegraph_node and
codegraph_explore would have answered — net cost regression on all three
test repos (cobra 17% → 48% loss, sinatra 18% → 96% loss). Documented
inline so future tuners don't re-try this dead-end.

No behavior change beyond the comment: the 5-tool gate remains the
production setting.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(mcp): pin empirical lower bound on tool gating after n=2 micro test

Tested the hypothesis that exposing FEWER tools on micro repos (<50
files) would close the cost gap. Results:

- 1-tool gate (codegraph_search only):
  - ky:    +44% (worse than 5-tool +30%)
  - express: +107% (catastrophic — was -43% WIN with all 10)
  - cobra: +126% (way worse than 5-tool +17%)

The single-tool gate forces the agent to read everything because it
can't navigate the call graph. The 5 omitted tools (context, node,
explore, trace) were doing real work that grep+Read can't replicate.

Conclusion: 5 tools (search + context + node + explore + trace) is the
empirical lower bound on the tiny-repo tier. Cutting below regresses
EVERY tested repo. The remaining ~$0.04-0.08 of structural cost overhead
on tiny repos is unavoidable without sacrificing the value codegraph
provides at that scale (which would also make WITH = WITHOUT, defeating
the install).

Comment documents the dead-ends so future tuners don't relitigate.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(mcp): iter3/iter4 — raise tool-gate to 500, sufficiency steering in context, hard-exclude low-value files

Three layered changes targeting the sinatra/slim/small-repo cost gap
that iter2's body-shrink failed to close (smaller bodies just pushed
the agent to Read instead):

1. **Tool-gate threshold 150 → 500** (`TINY_REPO_FILE_THRESHOLD`).
   Sinatra (~159 files) and slim (~200 files) have the same structural
   problem as cobra (

* feat(context): iter7 — core-directory boost to surface dominant-file siblings in search ranking

On projects with a single file holding the dense majority of internal
call edges (e.g. sinatra's `lib/sinatra/base.rb` at ~85% of in-file
edges), text search was favoring small focused extension files over the
core file. A small focused file like `multi_route.rb` wins on verbatim
name match + file-size normalization, burying the 1500-line core file's
longer method names (e.g. `route!` vs `route`).

Fix: detect the "dominant file" — the file whose in-file edge count is
≥3× the next candidate's — then add +25 to all results sharing its
directory prefix. This pulls the core file's siblings above
sibling-package extensions without hardcoding any repo structure.

`getDominantFile()` excludes test/spec files and generated files
(e.g. etcd's `rpc.pb.go` has 4× the in-file edges of `server.go` and
would otherwise hijack the boost toward generated protobuf stubs).
SQL pulls the top 20 candidates; path-pattern filtering handles what
SQLite LIKE can't express.

* feat(mcp): iter10+iter12 — routing manifest inline + probe-sweep harness

On small projects (<500 files) with a routing-shaped query, build a
URL→handler manifest directly from the graph (each `route` node joins to
its handler via `references`/`calls` edges) and inline the top handler
file's source. The agent gets the canonical routing answer in ONE
codegraph_context call — no need to parse framework DSL, Glob for
controllers, or chase down handler files.

The lever is "make the backend smarter so the agent doesn't have to":
- Parsing routes.rb / routes/api.php / urls.py DSL is the agent's job
  in the WITHOUT arm. Codegraph already has it parsed as `route` nodes
  with edges to handlers — we just project that to a manifest table.
- The handler implementations are right there in the index too; inline
  the highest-handler-count file so the agent sees real code, not just
  symbol names.

Results on the realworld template repos that were losing badly:
  rails-rw  +89% LOSS → -15% WIN  (agent often answers with 0-1 tool calls)
  laravel-rw  +29% LOSS → +12% (tight gap)
  gin-rw    +30% LOSS → +23% (still loss but smaller)
  flask-mb  +64% LOSS → +25% (smaller gap)

The residual losses are mostly the agent's defensive read behavior on
super-cheap-WITHOUT repos (express-rw still does 4 Reads even with a
19-row manifest + service file inlined). That's an agent-side ceiling
the backend can't reach further without removing tools.

Also lands `scripts/agent-eval/probe-sweep.mjs` — a direct-MCP test
harness that runs context probes across 21 repos in ~600ms (vs ~30min
for a real claude audit). Enables rapid iteration on backend changes:
edit tools.ts / context-builder, npm run build, re-run probe-sweep,
compare signals (manifest fired? handler file inlined? response size?)
before paying for a claude run.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(mcp): first tool call awaits catch-up sync (no stale rows for deleted files)

`MCPEngine.catchUpSync()` reconciles the index against the working tree
after open (catching `git pull`/`checkout`/`rebase` and any edits or
deletes made while no server was running). It was fire-and-forget — so a
tool call landing in the first ~50-300ms could race past it and serve
rows for files that no longer exist on disk. The per-file staleness
banner can't help here, because that signal is populated by the file
watcher (not by catch-up).

The fix: `catchUpSync()` now pushes its promise into `ToolHandler` via
`setCatchUpGate(p)`; the first `execute()` call awaits the gate and then
clears it. Subsequent calls pay nothing. Catch-up rejections are logged
by the engine and swallowed by the handler so a transient sync failure
never breaks tools.

Most visible on the "deleted everything between sessions" case, where
MCP previously returned stale rows pointing at non-existent files.
Validated end-to-end on a 10,640-file VS Code index: with the gate, a
codegraph_search for "ExtensionHost" against an empty (but stale-DB)
directory returns "No results found" after the catch-up drains the DB;
without the gate, the same call returns 10 stale hits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(changelog): cover small-repo retrieval tuning + auto-trace + iface-override expansion

Add entries for work that landed on this branch but wasn't yet in
[Unreleased]: tiny-repo tool gating + sufficiency steering + budget
tier, auto-inline trace in codegraph_context, routing manifest inline,
core-directory ranking boost, JVM-only interfaceOverrideEdges extended
to C#/TS/JS/Swift/Scala, and the shorter tool descriptions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 12:38:03 -05:00
4a4a37d135 feat(mcp): detect borrowed git worktree index and surface on read tools (#312)
When a worktree is nested inside the main checkout (e.g. agent tools that place
worktrees under .claude/worktrees/<name>/), the nearest-.codegraph walk resolves
UP to the main checkout's index and queries silently return that tree's code —
usually a different branch. Symbols changed only in the worktree are invisible,
and nothing tells the user (#155).

Two layers:

- **Detection** (src/sync/worktree.ts): detectWorktreeIndexMismatch() compares
  the caller's git working-tree root vs the resolved index root via
  'git rev-parse --show-toplevel'. Best-effort; no git / not a repo / monorepo
  subdir / plain-ancestor index → no warning.
- **Surface**: codegraph status (CLI + MCP) embeds a verbose multi-line warning;
  every MCP read tool (search/context/trace/callers/callees/impact/explore/node/
  files) prefixes a compact one-line notice naming the borrowed index and the
  fix (codegraph init -i in the worktree). Detection is cached per session per
  start path, so it costs at most a single pair of 'git rev-parse' spawns per
  project no matter how many tool calls — respects the wall-clock-latency
  invariant.

Real-git tests (no mocking) cover both layers. Validated on macOS / Linux
(Docker) / Windows (Parallels VM); 11/11 worktree tests green on all three.

Closes #155

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 20:57:20 -05:00