81e1f4a92fdbd9413ba73cf72c8a5408102a7897
63
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
493d4210f1 |
Merge branch 'main' into feat/copilot-installer-targets
# Conflicts: # CHANGELOG.md |
||
|
|
0682137a42 |
fix(installer): write the Claude prompt hook as codegraph.cmd on Windows (#1466) (#1489)
The standalone bundle's bin dir exposes only codegraph.cmd, and Claude Code executes UserPromptSubmit hooks through Git Bash, which applies no PATHEXT — so the bare `codegraph prompt-hook` the installer wrote was "command not found" (exit 127) on every prompt. Write the platform-correct spelling, recognize both spellings on uninstall/opt-out, and self-heal an installer-written entry from the other platform in place on install/upgrade re-runs (npx/hand-edited variants stay untouched). Reproduced and validated on the Windows VM: bare form exits 127 under Git Bash on a standalone-only PATH, codegraph.cmd exits 0; full installer suite (165 tests, including the new migration coverage) green on Windows + macOS. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
9769d6be0f |
fix(installer): copilot-vscode global entry drops ${workspaceFolder} — VS Code toasts an error in every folderless window
A user-level mcp.json entry using ${workspaceFolder} makes VS Code
refuse to start the server in ANY window without a folder open (loose
files, welcome tab), toasting "Variable workspaceFolder can not be
resolved" — recurring error-noise, hit live during validation.
The pin was never needed for VS Code: unlike Cursor, VS Code documents
stdio-server cwd as the workspace folder, and the codegraph server
resolves its project via roots/list with a cwd fallback. Global entries
are now variable-free (`serve --mcp`); local installs keep the absolute
--path. This supersedes the "open a folder" install note from the
previous commit, which is removed again.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
73313213e1 |
fix(installer): warn that copilot-vscode global installs need an open folder
VS Code refuses to start a user-level MCP server whose entry uses
${workspaceFolder} in a window with no folder open, surfacing only a
cryptic "Variable workspaceFolder can not be resolved" toast (hit live
during validation). Global installs now note this up front.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
234dfe60cd |
fix(installer): copilot-cli detection false-positived on VS Code's ~/.copilot/ide locks
The VS Code Copilot Chat extension writes MCP socket-handoff lock files
into ~/.copilot/ide/ on launch, so `existsSync(~/.copilot)` reported the
Copilot CLI as installed on any machine that merely has the VS Code
extension (caught live on the maintainer's Mac). Detection now counts
the dir as a CLI footprint only when it holds something besides `ide`.
Also: uninstalling a from-scratch install now deletes mcp-config.json
instead of leaving a `{}` husk that would keep detect() reporting the
CLI as installed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
490791c07a |
feat(installer): GitHub Copilot targets — VS Code, Copilot CLI, JetBrains
Adds three new installer targets so `codegraph install` can wire the
MCP server into GitHub Copilot surfaces:
- copilot-vscode: .vscode/mcp.json (local) or the VS Code User-dir
mcp.json (global), JSONC-surgical edits, `--path` pinned via
${workspaceFolder} for global installs
- copilot-cli: ~/.copilot/mcp-config.json
- copilot-jetbrains: github-copilot config dir (XDG / %LOCALAPPDATA%)
Detection, install, uninstall, and --print-config are covered for all
three in installer-targets.test.ts, including platform-specific path
resolution.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
572d22bfbe |
fix(installer): Codex TOML block finder preserves trailing array-of-tables siblings (#1351) (#1370)
The Codex installer's `findNextTableHeader` skipped `[[array-of-tables]]` headers instead of treating them as a block boundary, so any `[[...]]` block after `[mcp_servers.codegraph]` in ~/.codex/config.toml was silently deleted on install/upgrade/uninstall. Now treats both `[...]` and `[[...]]` as boundaries, with a small line lexer so header-shaped text inside multiline strings/arrays isn't mistaken for a boundary. Adds round-trip regression coverage (install → reinstall → uninstall) + CHANGELOG entry. Fixes #1351. Supersedes #624. Thanks @KtzeAbyss. |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
2f70eb3d32 |
fix(sync,installer): time-bound the git/npm subprocess calls that had no timeout (#1139) (#1148)
extraction/index.ts bounds every git call it makes; worktree.ts, git-hooks.ts, and the installer's npm install -g did not, so a stuck subprocess blocked the caller indefinitely. Worst case was the daemon: gitWorktreeRoot/gitCommonDir run (memoized) on the main event loop while serving MCP clients, where an unbounded git hang would trip the 60s liveness watchdog and SIGKILL a healthy daemon. git calls get 5s, the interactive npm install 120s. Regression tests assert the option through a mocked child_process plus a per-file call-site sweep. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
f82a662ddb | feat(mcp): pare default tool surface to codegraph_explore alone + redux-thunk synthesizer | ||
|
|
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. |
||
|
|
13b2575dfe |
fix(installer): opencode global config goes to ~/.config/opencode on every platform (#535) (#836)
* fix(installer): opencode global config goes to ~/.config/opencode on every platform (#535) opencode resolves its config dir with xdg-basedir (XDG_CONFIG_HOME ?? ~/.config) unconditionally — it never reads %APPDATA%; that layout belonged to the discontinued Go fork. Writing there on Windows meant opencode never saw the MCP entry. - globalConfigDir(): drop the win32 APPDATA branch; XDG resolution everywhere - install/uninstall (global): sweep a stale codegraph entry + AGENTS.md block out of the legacy %APPDATA%/opencode location (siblings/comments untouched) - detect(global): a legacy-only dir still counts as installed so the sweep is reachable - tests are env-gated, not platform-gated, so the whole matrix runs on any OS; the suite previously pointed APPDATA and XDG_CONFIG_HOME at the same dir, which is exactly how the divergence stayed invisible Supersedes the prefer-if-exists approach of #670 (greenfield installs -- before opencode's first run -- would still have fallen back to APPDATA). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(installer): match legacy sweep paths by dir prefix, not 'AppData' substring On Windows os.tmpdir() lives under AppData\Local\Temp, so every harness path contains 'AppData' and the substring assertions false-positive. Caught on the real Windows VM. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
e2f1fe4b2a |
fix(installer): instructions block is scope-neutral — conditional on .codegraph/ existing (#820)
A global install writes the block into user-scope files (~/.claude/CLAUDE.md, ~/.codex/AGENTS.md) that apply to EVERY repo the user opens — the unconditional "This repository is indexed" claim was false in unindexed ones and would send subagents into failing codegraph calls, the exact noise the unindexed-session policy (#817) eliminates. Now: "In repositories indexed by CodeGraph (a .codegraph/ directory exists) …" plus an explicit skip-entirely line for the no-index case. Residual: the delegation A/B validated the assertive project-scoped wording; the conditional form keeps the same active ingredients (the codegraph name + both command surfaces in the relay-able slot) — fold a re-check into the next delegation A/B run. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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)
|
||
|
|
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>
|
||
|
|
cea78ceb1b |
fix(windows): suppress console popup on child_process calls (#498)
On Windows, v0.9.5's detached shared daemon (#411) has no inherited console, so any console-subsystem child it spawns gets a fresh visible console window unless the spawn passes `windowsHide: true`. The fix adds the flag to all ten `spawnSync` / `execFileSync` / `execSync` call sites across extraction, sync, installer, and the WASM-flags relaunch. macOS/Linux ignore the option, so this is a no-op elsewhere. Fixes #485, #510, #530. Co-authored work: - #498 (csw-chen) — full sweep across extraction, sync, installer, and wasm-runtime. **This is the change being merged.** - #505 (yushengruohui) — independently identified and fixed the 7 git execFileSync sites. Superseded by #498's broader sweep; same diagnosis. - #521 (JirA44) — independently identified and fixed the WASM-runtime spawnSync re-exec. Superseded by #498's broader sweep; same diagnosis. Validated on Windows 11 ARM64 (Parallels): a detached parent's 15 git spawns produce 15 visible black flash-windows without the fix and 0 with it. |
||
|
|
110e24fea7 |
fix(installer): tell Kiro IDE users to enable MCP in Settings (#475)
PR #473 emitted only "Restart Kiro for MCP changes to take effect." That note is incomplete for Kiro IDE users: the IDE ships with MCP support disabled by default, so a freshly-written ~/.kiro/settings/mcp.json is ignored until the user opens Settings, searches "MCP", and flips the "Kiro Agent: Configure MCP" dropdown to "Enabled". The agent then reports "No MCP powers installed" and falls back to grep/Read — which looks like an installer wiring bug but isn't. Kiro CLI doesn't gate on this flag — it reads the same file without any toggle — so the second note calls out which audience needs the extra step. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
6558b585ed |
feat(installer): add Kiro CLI/IDE target (#385) (#473)
`codegraph install` now detects and configures Kiro alongside the existing seven agents. Writes `mcpServers.codegraph` to `~/.kiro/settings/mcp.json` (global) or `./.kiro/settings/mcp.json` (local), plus a dedicated `~/.kiro/steering/codegraph.md` / `./.kiro/steering/codegraph.md` instruction file — Kiro's steering system loads every `*.md` file in `steering/` as agent context, so a dedicated file is the natural surface (no marker-based merging needed). Sibling MCP servers in `mcp.json` and unrelated steering files (`product.md`, `tech.md`, etc.) are preserved across install and uninstall. Validated end-to-end on macOS, Linux (Docker node:22-bookworm arm64), and Windows 11 (Parallels VM, Node 24): full installer-targets suite passes (132 tests) on all three platforms, and live install / idempotent re-run / uninstall round-trip works as expected. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
e76cc547b0 |
fix(hermes): preserve YAML list-at-same-indent style on install (#456) (#461)
Hermes Agent writes ~/.hermes/config.yaml with PyYAML's default block
style, which puts list items at the SAME indent as the parent key:
platform_toolsets:
cli:
- hermes-cli # indent 2, same as `cli:`
- browser
The previous line-based YAML patcher used `^ \S` to find the end of
the `cli:` block, which mistook that first ` - hermes-cli` line for
the next sibling key, truncated the block, and spliced
` - mcp-codegraph` at indent 4 BEFORE the existing items. The
result was unparseable YAML: every subsequent item (`- browser`,
`- clarify`, …) and every sibling platform (`telegram:`, `discord:`)
appeared at the `platform_toolsets:` level. Hermes silently fell back
to the default config, dropping every user override.
The new `listChildBlock` helper recognizes ` - ` as a list-item
continuation (not a sibling key), finds the real end of the block at
the next sibling mapping key, and detects the existing item indent so
the new entry matches it. Two regression tests cover the PyYAML-default
style; the existing 4-space-nested test still passes.
End-to-end verified against a real `hermes-agent` install on the exact
bug-triggering config: `hermes mcp list` shows codegraph as enabled,
`hermes tools --summary` lists both `mcp-codegraph` and `codegraph` in
the CLI toolset, and `hermes mcp test codegraph` connects in 264ms and
discovers all 10 codegraph tools. Re-running `codegraph install`
reports `Unchanged` and the file still has exactly one entry. Closes #456.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
180ba785ce |
feat(installer): add Gemini CLI + Antigravity IDE targets (#399) (#458)
`codegraph install` now detects and configures two more agents:
- Gemini CLI / Antigravity CLI — `~/.gemini/settings.json` (or
`./.gemini/settings.json`) + `~/.gemini/GEMINI.md` (or project-root
`./GEMINI.md`). Preserves pre-existing top-level settings like
`security.auth` and sibling MCP servers.
- Antigravity IDE — writes to Antigravity's unified MCP config at
`~/.gemini/config/mcp_config.json` (post-migration, detected via
the `.migrated` marker Antigravity drops). Falls back to the
legacy `~/.gemini/antigravity/mcp_config.json` on pre-migration
builds; install migrates a stale legacy entry, uninstall sweeps
both. Antigravity-managed sibling fields (e.g. the `disabled` flag
added when users disable a server through the UI) survive re-install.
Two Antigravity-specific quirks the target handles:
1. Entries with `type: "stdio"` are silently rejected by
Antigravity's MCP scanner; we omit the field for this target.
2. macOS GUI apps launched from Dock/Finder get a stripped PATH
that excludes nvm — a bare `codegraph` command name fails to
spawn even when `which codegraph` works in the user's shell.
The target resolves `codegraph` to its absolute path at install
time on macOS. Linux + Windows are unaffected.
End-to-end validated:
- macOS: real Gemini CLI v0.43 via tmux — `/mcp` shows codegraph with
all 10 tools, `codegraph_status` executes and returns real index
state. Real Antigravity IDE shows codegraph under Customizations
after restart.
- Linux (Docker node:22-bookworm) + Windows (Parallels Win11): 116
installer tests pass; CLI install + uninstall round-trip verified.
Test coverage: the new targets inherit the existing parameterized
contract (idempotent install, sibling preservation, install/uninstall
round-trip). Plus 14 target-specific tests covering migration-marker
detection, legacy→unified entry migration, `disabled` flag
preservation, the `type` field omission, gemini+antigravity
coexistence in the same `~/.gemini/`, and macOS-only path resolution.
Full suite: 972 passing.
Closes #399.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
b48170e69f |
feat(mcp): per-file staleness banner + tunable watcher debounce (#403) (#428)
Two coupled changes addressing the issue's underlying ask — "how does the
agent know when the index lags" — without resorting to a static wait.
Per-file staleness banner
-------------------------
FileWatcher now tracks per-path `pendingFiles` (path, firstSeenMs,
lastSeenMs, indexing) — events since the last successful sync, cleared
only after a sync whose `syncStartedMs >= lastSeenMs` commits. Chokidar
initial-scan events are gated behind a `ready` flag (with `waitUntilReady()`
exposed so tests can deterministically wait through it) so a fresh startup
doesn't falsely flag every existing file as pending.
ToolHandler now wraps every code-returning response (search, context,
callers, callees, impact, trace, explore, node, files) with
`withStalenessNotice`: intersects "files referenced in the response" with
`getPendingFiles()` and emits a hybrid signal —
* banner at the top for files referenced AND pending (with edit age +
indexing/pending-sync state, telling the agent to Read those specific
files directly; the rest of the response stays fresh and codegraph
stays authoritative for it),
* compact footer for pending files elsewhere in the project not
referenced above (capped at 5).
Cost is one boolean check + N substring matches when pending; zero
allocation when idle. `codegraph_status` surfaces the same data as a
first-class `### Pending sync:` section so the agent can ask "is the index
caught up?" in one call.
Cross-project quirk: when an agent passes `projectPath` matching the
default session's project, the staleness wrapper switches from the cached
cross-project CodeGraph (no watcher) to the default one (with watcher) so
the signal still fires. Same fix applied to `handleStatus`.
CODEGRAPH_WATCH_DEBOUNCE_MS
---------------------------
MCP `serve --mcp` now reads `CODEGRAPH_WATCH_DEBOUNCE_MS` and forwards it
to `cg.watch({ debounceMs })`. Clamped to [100ms, 60s]; out-of-range or
non-numeric values fall back to the FileWatcher default (2000ms). Active
value is logged to stderr on watcher startup so it's discoverable. The
docs in `server-instructions.ts`, `installer/instructions-template.ts`,
and `.cursor/rules/codegraph.mdc` no longer claim "~500ms"; they now
describe the banner mechanism instead — since per-file staleness replaces
the "wait N ms" guidance entirely, the docs become accurate at any
debounce value.
Validation
----------
* 847 unit/integration tests pass (added 15 new ones — pending-file
tracking, banner/footer routing, status section, env-var parsing).
* Direct MCP probe through a real `codegraph serve --mcp` process: edit a
file, query within the debounce window, banner fires naming the
edited file with edit-age.
* Real Claude TUI session via `scripts/agent-eval/itrun.sh` with
`CODEGRAPH_WATCH_DEBOUNCE_MS=10000`: agent edits `math.ts`, calls
`codegraph_explore`, reads the banner, **and discloses it unprompted in
its final reply**: "note: symbol index is mid-sync for the new `divide`,
but the source it returned is verbatim from disk."
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
025ebc88d6 |
Release 0.9.4: framework-aware routing + dynamic-dispatch coverage + retrieval improvements (#365)
* feat(resolution): close dynamic-dispatch coverage holes (callback synthesis + django ORM)
Static tree-sitter extraction misses calls whose target is computed or indirect,
so flows through callbacks, observers, and descriptors were absent from the graph.
- callback-synthesizer.ts: whole-graph pass after base resolution. Detects
registrar/dispatcher channels (field-backed observers + string-keyed
EventEmitters), correlates registration sites, and synthesizes
dispatcher->callback `calls` edges (provenance:'heuristic'). Records the
registration site (registeredAt) in edge metadata. Precision guards: named
handlers only, registrar-name match, event fan-out cap.
- frameworks/python.ts + resolution/{index,types}.ts: claimsReference hook +
django ORM resolver (_iterable_class -> ModelIterable.__iter__).
- extraction/tree-sitter.ts: extract named nested functions so inline named
handlers become linkable nodes.
trace(mutateElement, triggerRender) and trace(_fetch_all, execute_sql) now
connect; node count stable (no explosion).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(mcp): self-sufficient flow output + fix explore budget regression
- Surface synthesized-edge evidence in trace, the node trail, and context call
paths: a dynamic-dispatch hop now shows "callback via onUpdate @App.tsx:3148"
with the registration site inline (and trace inlines each hop's call-site
source line) -- the exact glue agents previously Read/Grep'd to reconstruct.
- Fix non-monotonic explore output budget: the 500-5000 file tier capped
maxCharsPerFile at 2500, BELOW the <500 tier's 3800, so on god-file projects
(excalidraw's 415 KB App.tsx) one explore returned <1% of the file and forced
a Read. Raised to 6500/file, 28000 total.
- Stop explore from inviting Read: truncation/trim notes said "use Read for
more"; they now steer to another codegraph_explore and treat returned source
as already Read.
Measured on excalidraw: best-case flow answer went from 5 reads / 131s to
0 reads / 73s with ~3-4 codegraph calls.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(agent-eval): coverage probes, block-read hook, and design docs
Dev-only validation harness for the dynamic-dispatch coverage work:
- probe-{trace,node,context,explore}.mjs: drive MCP tools against a built index
without a full agent run.
- block-read-hook.sh + hook-settings.json: PreToolUse experiment that denies
source Reads to measure codegraph sufficiency (forced Read-0).
- docs/design/: callback-edge-synthesis + dynamic-dispatch-coverage playbook.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(resolution): bridge React boundaries — re-render + JSX child synthesis
Closes the two dynamic-dispatch hops that broke "state mutation -> on-screen
render" flows in React apps. Both are call-invisible (React-internal) but the
code between them is fully call-connected, so one synthesized edge each makes the
whole flow trace end-to-end.
- reactRenderEdges: setState(...) re-runs the component's render(). For each
class with a render method, link sibling methods calling this.setState ->
render. The setState gate keeps it to React class components.
- reactJsxChildEdges: a component that returns <Child .../> mounts Child. Link
parent -> each capitalized JSX child, resolved to a component/function/class
node (the resolution gate drops TS generics like Array<Foo>). File-oriented,
capped per parent.
- Surface both in synthEdgeNote (trace + node trail) and context call-paths.
Validated on excalidraw: trace(mutateElement, renderStaticScene) now connects in
6 hops across callback -> react-render -> jsx-child; 1 + 46 + 280 synthesized
edges, node count stable (no explosion). Partial coverage is worse than none:
react-render alone raised agent reads (revealed a hop it then drilled); adding
the jsx hop closed the flow and dropped reads to 0-1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(claude): retrieval performance contract + coverage validation methodology
Add a "Retrieval performance & dynamic-dispatch coverage" section so future
changes/PRs don't silently regress agent retrieval:
- the explore call+output budget table by repo size, with the monotonic-per-file
invariant (the bug that started this: <5000 tier's 2500 < <500 tier's 3800).
- the "partial coverage is worse than none" principle.
- the required validation methodology (small/medium/large x >=3 prompts per
language x framework; deterministic probes + agent A/B; pass bar).
- the Excalidraw worked example (before/after numbers) as the template to
replicate for every language/framework.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(claude): use full n=4 measured range in Excalidraw worked example
Best run 0 Read/3 cg/76s; typical ~1 Read/~4 cg; occasional over-drill outlier.
Report the range, not a single run — run-to-run variance is large.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(mcp): steer flow questions to codegraph_trace first (tightens variance)
codegraph_trace was absent from every steering intent map — all three guidance
files routed "how does X reach Y" to context+explore, never to the trace tool.
So agents used trace only by chance; when one didn't, it floundered
reconstructing the path with search+callers (an 18-call run vs ~6 for trace-users).
Add codegraph_trace to the intent map + a "flow" common chain (trace from->to
FIRST = the whole path in one call, then ONE explore for bodies) across all three
synced files (server-instructions, instructions-template, .cursor rule).
Validated on excalidraw (hard "to the screen" Q, n=4 before/after):
- call count 3-10 -> 3-4 (over-drill outlier gone)
- duration 64-112s -> 51-74s
- trace adoption 3/4 -> 4/4; search+callers path-reconstruction -> 0
- fully-clean runs (0 Read, 0 Grep) 0/4 -> 2/4; best 3 cg / 0 / 0 / 51s
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(resolution): Vue SFC template coverage (events + kebab components)
The .vue extractor only parses <script>, so template usage is invisible —
handlers and kebab child components used only in <template> have no edge. Add a
vueTemplateEdges channel (scoped to the <template> block of .vue files):
- event bindings: @click="onClick" / v-on:submit="save" -> handler method/function
(skips inline arrows and $emit; resolves same-file first to avoid cross-app
mis-match in monorepos).
- kebab child components: <el-button> -> ElButton (PascalCase children like
<VPNav/> are already caught by the JSX channel via the SFC component node).
Surface vue-handler in synthEdgeNote (trace/node trail) + context call-paths.
Validated on vue repos (reindex, no node explosion):
- vue-handler edges: vitepress 15, vben 404, element-plus 603 — all precise
(code-login @submit -> handleLogin, register @submit -> handleSubmit, ...).
- callers(handleLogin) now includes the login component (was 0); each monorepo
app's login resolves to its own same-file handler.
- composition: PascalCase + kebab work; element-plus's el-/filename naming
(el-button -> button.vue) is a known library-prefix limitation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(playbook): record Vue validation in coverage matrix + limits
Vue / Nuxt row → ✅ template events + composition (vitepress S / vben M /
element-plus L); 🔬 reactive→render (vue-core Proxy runtime, deferred).
§7: Vue results + the two real limits — composable-destructure handlers
(@click="closeSidebar" from useSidebarControl, a data-flow frontier) and
prefix-convention kebab (el-button→button.vue). Agent reads dropped in every
size; strongest where handlers are local functions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(resolution): resolve Vue composable-destructure template handlers
@click="closeSidebar" where `const { close: closeSidebar } = useSidebarControl()`
previously didn't resolve — the handler is a destructured composable return, not a
local fn node. Now: parse the SFC's `use*()` destructures into alias→{composable,
key}, and for an unresolved template handler follow alias → composable → the
returned member (`close`) defined in the composable's file. Precise-only: no
fallback to the composable itself (the component already has a static useX() call
edge), so we add an edge only when the specific returned fn is found.
Validated: vitepress Layout @click→close / @open-menu→open (in composables/
sidebar.ts); sidebar-flow agent run dropped 6→0 reads (best case). element-plus's
fallback-only matches correctly drop to 0; node counts stable; direct handlers
(vben handleLogin) unaffected.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(playbook): composable-destructure handlers now resolved (Vue)
@click="closeSidebar" → composable returned fn; vitepress sidebar 6→0 reads.
Remaining Vue limits: prefix-convention kebab + reactive→render frontier.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(extraction): extract function-valued properties of exported-const objects
`export const actions = { default: async () => {...} }` (SvelteKit form actions,
and general JS handler/route/reducer maps) left the arrow functions unextracted —
the walker skips object-literal functions (deliberately, to avoid inline-object
noise like `ctx.set({...})`). So an action's body (and its calls) was invisible.
Now: for an EXPORTED const whose initializer is an object literal, extract each
function-valued property (arrow / function expression) as a function named by its
key and walk its body. extractFunction gains a nameOverride so ONLY this explicit
path names pair-arrows — inline-object arrows reached by the general walker still
fall through to the <anonymous> skip, so no noise returns. JS/TS-gated.
Validated: fixtures extract the actions + walk bodies (default→helper, default→
api.post resolve); SvelteKit detection doesn't break it. Blast radius tiny:
excalidraw +1 node, Python (django) +0, Vue repos +0, realworld +11 (the actions).
Known residual: a `$lib`-alias namespace-member call (`api.post`) from an extracted
action node doesn't resolve even though the same alias resolves for `load` — a
deeper resolver interaction, separate from this extraction change. Local/relative
calls from actions connect fine.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(playbook): record Svelte validation (already well-covered) + actions fix
Svelte/SvelteKit row → already strong (template calls/composition/namespace/load);
+ exported-const object-of-functions extraction. Lesson: measure before assuming
a hole — modern Svelte barely uses on:click={fn}; Svelte needed far less than Vue.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(resolution): connect Express inline arrow route handlers to their services
The Express resolver created route nodes but linked handlers via a single regex
whose `[^)]+` broke on inline arrows — so `router.post('/x', async (req,res) =>
{...})` (the dominant modern pattern) connected to NOTHING, and the anonymous
handler's body (the actual request→service flow) was lost. The whole inline-handler
API was unreachable: e.g. realworld's `POST /users/login` route → 0 edges.
Now: match the route head, span the full call with a string-aware balanced-paren
scan, and for an inline arrow handler extract its body's calls (string-aware brace
scan) and attribute them to the route node as `calls` edges. A RESERVED denylist
drops res/req/builtin methods (json, next, status, ...) to keep only business calls.
Named-handler routes keep the existing reference behavior.
Validated: realworld POST /users/login → login (auth.service); 19 precise
route→service edges (was 0) — POST /articles→createArticle, .../favorite→
favoriteArticle, etc., no json/next noise. ghost +65 inline-handler edges. No node
explosion (ghost 40767, parse 3394 unchanged). Framework-scoped: zero blast radius
off Express.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(playbook): record Express validation (inline-handler fix)
Express/Koa row → resolver already handled named handlers; the real hole was
inline arrow route handlers (router.post('/x', async (req,res)=>{...})) — fixed:
route→service body calls (realworld 19 / ghost 65 edges, no explosion). Agent A/B
muddied by repo size (realworld tiny) / complexity (ghost layered API). Lesson
inverse of Svelte: Express's dominant pattern WAS the uncovered one.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(playbook): record NestJS validation (already well-covered)
NestJS row → resolver handles @decorator routes; DI controller→service
(this.svc.method) resolves correctly at scale (immich: addUsersToAlbum→addUsers,
etc.). Agent A/B: codegraph eliminated Grep (0 vs 3). No dynamic-dispatch hole.
Surfaced a general hygiene gap (not NestJS): committed dist/ build output gets
indexed (no default build-dir ignore) — narrow (real apps gitignore dist/),
deferred as a core-indexer follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(resolution): Rails RESTful resources routing → controller#action
The rails resolver only saw explicit `get '/x' => 'c#a'` routes, so apps using
the dominant `resources :articles` / `resource :user` RESTful routing had ZERO
route nodes (realworld + spree: 0 routes despite full routes.rb files). The whole
request→controller flow was disconnected.
Fix (frameworks/ruby.ts):
- extract: expand `resources`/`resource` into their REST actions (only/except
filters; pluralize the singular `resource :user` → users_controller), emit a
precise `controller#action` ref per action. Explicit routes now also reference
`controller#action` instead of a bare ambiguous `action`.
- resolve: new `controller#action` pattern → the action method in
<ctrl>_controller.rb (file convention + controller-class fallback).
- claimsReference: claim `controller#action` refs so resolveOne's pre-filter
doesn't drop them before resolve() runs (same hook the django ORM work needed —
these refs name no declared symbol).
Validated: realworld 0→16, forem 0→635 precise route→action edges (GET /articles→
index, resource :user→users#show, etc.), pluralization correct, no node explosion
(route nodes proportional to resources). Agent A/B (forem, large): with codegraph
1-4 reads / 0 grep / 47-53s vs without 4-5 reads / 2-3 grep / 66-85s. Framework-
scoped (zero blast radius off Rails). Residuals: Rails Engine routing (spree
mounts an engine), ActiveRecord dynamic finders (metaprogramming frontier).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(resolution): Spring bare + class-prefixed route mappings → controller method
The Spring resolver required a string path in the mapping regex, so BARE method
mappings (`@PostMapping` with the path on the class-level `@RequestMapping`) were
missed — the dominant multi-method-controller pattern. realworld's two-action
ArticleFavoriteApi only linked one method; halo had 28 routes for 2444 files.
Fix (frameworks/java.ts):
- Treat class-level `@RequestMapping` as a PREFIX (not a bogus route) and join it
onto each method's path.
- Match verb-specific mappings (@GetMapping/@PostMapping/...) BARE or with a path.
- Also handle method-level `@RequestMapping(value=..., method=RequestMethod.X)`
(older style) — restored after an initial cut dropped it (mall regressed 292→1;
caught by the regression check).
Validated: realworld 13→19, mall 246 (all precise, class prefix joined:
GET /subject/listAll→listAll, POST /articles/{slug}/favorite→favoriteArticle +
DELETE→unfavoriteArticle), no node explosion. DI controller→service resolves
(article→findBySlug, updateArticle→canWriteArticle). Agent A/B (mall cart flow):
with codegraph 0 reads/0 grep vs without 2/2. Residuals: halo's complex custom
patterns (9/29 resolve); Spring Data JPA derived queries (metaprogramming frontier).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(playbook): record Spring validation (bare-mapping routing fix)
Spring row → bare @GetMapping/@PostMapping + class @RequestMapping prefix join →
route→method (realworld 13→19, mall →246); DI controller→service resolves. A
first cut regressed mall 292→1 (dropped @RequestMapping-on-method), caught by the
route-count regression check. Residuals: halo custom patterns, JPA derived queries.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(resolution): Django DRF router.register → ViewSet
Django's ORM (_iterable_class, prior work) and URL routing (path/url/as_view→view)
were already covered. The remaining hole: DRF `router.register(r'articles',
ArticleViewSet)` — the core CRUD endpoints — wasn't extracted (only path()/url()),
so a DRF API's main resources connected to nothing (realworld's ArticleViewSet:
0 callers).
Fix (frameworks/python.ts): match `.register(r'prefix', XViewSet)` → route→ViewSet
class. The STRING first arg distinguishes DRF router.register from
`admin.site.register(Model, Admin)` (model class first arg); View/ViewSet suffix
keeps it to viewsets. The ViewSet class resolves via the existing View/ViewSet
pattern.
Validated: realworld VIEWSET /articles → ArticleViewSet (was 0). Narrow in corpus
(realworld 1 router; wagtail=path, saleor=GraphQL) but real for DRF-router APIs.
Agent A/B (wagtail Page flow, medium): with codegraph 4-7 reads / 1-4 grep / 58-81s
vs without 7-9 reads / 6 grep / 82-86s. No regression (wagtail/saleor route counts
unchanged — purely additive). Residuals: signals, DRF inherited viewset actions,
GraphQL resolvers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(resolution): Laravel route → precise Controller@method (not bare action)
extractLaravelHandler discarded the controller: `Route::get([UserController::class,
'index'])` and `'UserController@index'` both emitted a BARE `index` ref. With the
route in routes/api.php (not the controller file), name-matching mis-resolved every
common action to the WRONG controller — realworld's GET user → ArticleController.index
(should be UserController), GET articles/feed → ArticleController (should be
FeedController), etc. The routes existed but pointed at the wrong handler.
Fix (frameworks/laravel.ts): emit precise `Controller@method` (array + string
syntax, namespace-stripped) and `claimsReference` it so resolveOne's pre-filter
doesn't drop it before Pattern-4 resolveControllerMethod runs (the recurring hook,
also needed by django ORM + Rails routing).
Validated: realworld all routes now resolve to the correct controller; bookstack
267/332 precise (GET pages → PageApiController.list, array syntax). No node
explosion. Agent A/B (bookstack page-view, large): with codegraph 2-3 reads / 1-2
grep / 51-60s vs without 4-6 / 3-5 / 60-74s. Residuals: firefly's fluent
->uses()/['uses'=>...] handler format (3/568 resolve), Eloquent dynamic finders.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(resolution): Gin/chi routes on group vars (any receiver, not just r/router)
The route regex matched only `(router|r|mux|app|e).METHOD(...)`, but real Gin/chi
apps route on GROUP variables — `v1.GET`, `PublicGroup.GET`, `userRouter.POST` —
so group-routed apps connected almost nothing: gin-vue-admin had 4 routes for 625
files. Broaden the receiver to ANY identifier; the verb + string-path + handler-arg
gates keep it route-specific (e.g. `http.Get(url)` has no handler arg, so it's
excluded).
Validated: gin-vue-admin 4→259 routes, 257 resolve precisely (POST createInfo→
CreateInfo, GET getInfoList→GetInfoList); realworld stable 24→25 (no regression);
no garbage (257/259 resolve, not false positives), node count proportional. gitness
(chi, custom handlers) is a residual (26/321). Inline `func(c *gin.Context){...}`
handlers still lose their body (anonymous, like Express was) — separate residual.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(playbook): record Gin validation (group-var routing fix)
Gin/chi row → routes on ANY group var (v1.GET/PublicGroup.GET), not just r/router
(gin-vue-admin 4→259 routes). Agent A/B: 0 reads/0 grep/26-30s vs 3/3/52-53s —
cleanest backend win yet. Residuals: inline func handlers, gitness chi custom.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(resolution): ASP.NET feature-folder detection + bare attribute routes
Two holes left ASP.NET apps disconnected:
1. detect() only fired on a /Controllers/ dir, root Program.cs/Startup.cs, or a
.csproj (which often isn't in the indexed source set). Feature-folder apps
(realworld: Features/*/FooController.cs, subdir Program.cs) were never detected
→ 0 routes despite a full set of controllers. Broaden: scan Controller/Program/
Startup .cs source for ASP.NET signatures ([ApiController]/[Route]/[Http*],
ControllerBase, MapControllers, WebApplication, Microsoft.AspNetCore).
2. The attribute regex required a string path, so BARE [HttpGet] (route on the
class [Route("[controller]")]) was missed — eShopOnWeb was 24 bare / 2 string.
Match bare-or-with-path + join the class [Route] prefix (like the Spring fix).
No claimsReference needed: ASP.NET attribute routes are co-located IN the controller
with the action, so the bare method-name ref resolves same-file.
Validated: realworld 0→19 routes (all precise: GET /articles→Get, POST /articles→
Create, class prefix joined), eShopOnWeb 9→33. Route→action correct + co-located.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(playbook): record ASP.NET validation (detection + bare-attribute fix)
ASP.NET Core row → feature-folder detection (realworld 0→19, was undetected) +
bare [HttpGet] / class [Route] prefix (eShopOnWeb 9→33, jellyfin 362→399). No
claimsReference needed (routes co-located in controller). Agent A/B (eShop): 1-2
reads/0 grep vs 6-7/1-6. Residual: EF Core LINQ.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(resolution): Flask/FastAPI route holes + Python builtin-name handler guard
Three fixes that connect the request→route→handler flow for Flask and
FastAPI. Validated S/L: fastapi-realworld 12→20, flask-microblog 6→27,
Netflix dispatch 290/290 (100%), redash decorator routes 6/6; canonical
flows trace end-to-end (login→get_user_by_email, create_user→from_dict).
- Flask: the route regex required `def` immediately after `@x.route(...)`,
so an intervening decorator (@login_required, @cache.cached) or stacked
@x.route lines (one view bound to several URLs) dropped the route.
Switch to the findHandler scan (match the decorator, then find the next
def) like FastAPI — skips intervening decorators.
- FastAPI: the path regex `[^'"]+` rejected the empty path `@router.get("")`
(router/prefix-root routes, frequently multi-line). Allow empty path +
guard the route name against a trailing space.
- Python builtin-name guard (src/resolution/index.ts): a handler named
after a Python builtin method (index/get/update/count…) was filtered by
isBuiltInOrExternal and lost its route→handler edge. Mirror the
dotted-method branch's knownNames guard onto the bare branch — a bare
name a declared symbol owns is a real target, not a builtin call.
+2 legit edges on realworld, 0 change on the django control (precision held).
Tests: new Flask (intervening/stacked decorator) and FastAPI (empty-path,
multi-line) extractor cases + a Flask end-to-end integration test (a view
named `index` behind @login_required). Also corrects 6 pre-existing stale
Laravel/Rails route-ref assertions surfaced by the suite — they expected
the old bare action name, but the resolvers now emit precise
controller@action / controller#action (from earlier precision commits).
Full suite green (781 passed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(playbook): record Flask/FastAPI validation (decorator + builtin-name fixes)
Matrix row Python/Flask+FastAPI 🔬→✅ and a §7 note: Flask intervening/
stacked decorators, FastAPI empty-path routes, the Python builtin-name
handler guard, S/L numbers, the login-auth A/B (0–1 read/0 grep with vs
3 read/2 grep without), and residuals (Flask-RESTful class-based
add_resource; redash JS file-route false-positives).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(resolution): Drupal route-handler resolution (claimsReference, single-colon controllers, contrib detection)
The *.routing.yml extractor and _controller/_form resolver existed but two
gaps left most routes unlinked. Validated S/M/L: admin_toolbar 0→14 (14/14),
webform 144/208, drupal-core 536→731/836 (87%); canonical flow traverses
(getAnnouncements ← /admin/announcements_feed); node count unchanged.
- claimsReference: Drupal handler refs are FQCNs (\Drupal\…\Class::method),
bare form classes (\…\SettingsForm), or single-colon controller-services
(\…\Controller:method). Only the ::method shape survived resolveOne's
pre-filter (its member is a known method name); the bare-FQCN forms and
single-colon controllers were dropped before resolve() ran. Claim FQCN /
Class:method / hook_* refs (same pattern as Rails controller#action).
- Single-colon controller match: broaden the controller regex from :: to
:{1,2} and tighten the _form branch to !name.includes(':').
- Detection: detect() only checked composer `require` for a drupal/* dep, but
a contrib module often has an empty require and is identified only by
"name":"drupal/<m>" + "type":"drupal-module" (admin_toolbar → 0 routes).
Broaden to composer name/type + a *.info.yml fallback.
Remaining unresolved is the entity-annotation handler frontier
(_entity_form: type.op) and OOP #[Hook] attributes (Drupal 11 moved ~all
procedural hooks to attribute methods — out of scope here). Tests: contrib
detection, *.info.yml fallback, claimsReference, single-colon controller.
Full suite green (787 passed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(playbook): record Drupal validation (claimsReference + contrib detection)
Add the PHP/Drupal matrix row (✅) and a §7 note: the claimsReference
pre-filter fix for FQCN/single-colon handlers, broadened contrib detection,
S/M/L numbers (admin_toolbar 0→14, webform 144/208, core 536→731), the
route→controller A/B (0 read/1 grep with vs 1 read/2 grep+glob without), and
the frontier residuals (entity-annotation handlers, OOP #[Hook] attributes).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(resolution): Axum chained methods + namespaced handlers
The Axum route extractor used a flat regex that captured only the first
method(handler) of a .route() call and only a bare \w+ handler, so two
dominant Axum idioms broke:
- method chains: .route("/user", get(get_current_user).put(update_user))
emitted no node for the .put arm — half the API was missing.
- namespaced handlers: get(listing::feed_articles) captured `listing`
(the module), so the route resolved to nothing.
Rewrite with a balanced-paren scan of each .route(...) call, a route node
per chained method, and last-::-segment handler names. realworld-axum
12→19 routes, 19/19 resolved (every chained PUT/DELETE/POST now present,
feed_articles resolves). Rocket needed nothing (550/556, 99%, attribute
macros); crates.io confirms namespaced axum handlers resolve.
Residual frontier: actix runtime routing web::get().to(handler) (the
dominant actix style, unextracted; attribute macros 35/51). Fix is
Axum-scoped — the attribute/actix/Rocket path is untouched. Tests: chained
methods + multi-line namespaced handler. Full suite green (789 passed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(playbook): record Rust/Axum validation (chained methods + namespaced handlers)
Update the Rust matrix row 🔬→✅ and add a §7 note: the Axum chained-method
+ namespaced-handler fix (realworld-axum 12→19, 19/19), Rocket already 99%,
crates.io (utoipa routes! macro frontier + SvelteKit frontend routes), the
update-user A/B, and the actix runtime-routing frontier.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(resolution): Vapor grouped/RouteCollection routing (was 0 routes on real apps)
The Vapor extractor only matched (app|router|routes).METHOD("path", use:
handler), but real Vapor apps route on a grouped builder inside
RouteCollection.boot(routes:): `let todos = routes.grouped("todos");
todos.get(use: index)` — any var receiver, no path arg (the path is the
group prefix). Every real app tested extracted 0 routes (template,
SteamPress, SwiftPackageIndex-Server, penny-bot, Feather).
Rewrite the extractor:
- any receiver (\w+), not just app/router/routes;
- optional path segments that may be non-string (User.parameter, :id, a
path constant) — the `use:` keyword discriminates a route from
Environment.get("X") / req.parameters.get("X");
- a group-prefix map from `let X = Y.grouped("a")` and
`Y.group("a") { X in }` so a grouped/nested route gets its full path
(todo.delete(use: delete) -> DELETE /todos/:todoID).
Result: vapor-template 0→3 (3/3, nested path exact), SteamPress 0→27
(27/27), SwiftPackageIndex-Server 0→14 (14/14 handler resolution).
Canonical flow traverses (createPostHandler <- GET /createPost ->
createPostView). Route names now carry a leading slash (GET /users),
consistent with the other frameworks.
Frontier: typed-route enums (SPI's SiteURL.x.pathComponents — handler
resolves, path label only) and closure handlers (app.get("x"){ } —
anonymous). Tests: grouped RouteCollection, self.handler + non-string
segments, use:-discriminator. Full suite green (792 passed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(playbook): record Vapor validation (grouped RouteCollection routing)
Update the Swift/Vapor matrix row ⬜→✅ and add a §7 note: the extractor was
dead on real apps (0 routes everywhere); rewrote for any receiver, optional
non-string paths, .grouped/.group{} prefix tracking, and the use:
discriminator. S/M/L all 100% handler resolution (template 0→3, SteamPress
0→27, SPI 0→14), the create-post A/B (0 read/0 grep with vs 1–4 read
without), and frontiers (typed-route enums, closure handlers).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(resolution): React Router <Route> JSX route extraction
react.ts extracted components/hooks and Next.js file routes but returned
references: [], so React Router <Route> declarations produced no route
nodes or route→component edges. Add <Route> JSX extraction: scan a window
after each <Route (so the nested > in element={<Comp/>} doesn't truncate
the match), pull path="…" + component={C} (v5) or element={<C/>} (v6) in
any attribute order, emit a route node + component reference (resolved by
the existing PascalCase resolveComponent). The <Routes> container is
excluded via the \b boundary.
react-realworld 0→10 routes, 10/10 resolved (/login→Login,
/editor/:slug→Editor, /@:username→Profile). No regression on excalidraw
(9,290 nodes, 46 react-render synth edges intact, 0 false routes). Tests:
v5 component=, v6 element=, <Routes>-container guard. Suite green (794).
Frontier: object data-router createBrowserRouter([{path,element}]) (modern
v6) is object-based not JSX — not covered.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(playbook): record React Router routing (the React row's routing half)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(resolution): actix-web builder-API routing (web::resource / .to(handler))
Actix's attribute macros were covered, but the dominant actix style is the
builder API — web::resource("/path").route(web::get().to(handler)),
web::resource("/").to(handler) (all methods), and App .route("/path",
web::get().to(handler)). The handler is in .to(handler), not get(handler),
so the Axum .route scan extracted nothing — actix-examples had 80
web::resource calls all unlinked.
Add an actix block: scan each web::resource("/path") (bounding its method
chain at the next resource) for web::METHOD().to(h) pairs, fall back to a
direct .to(h) (method ANY), plus the App-level .route("/x",
web::METHOD().to(h)) form. actix-examples 51→128 routes, 35→112 resolved
(GET /user/{name}→with_param, POST /user→add_user). No regression on Axum
(realworld-axum still 19/19). Tests: resource+route, resource direct .to,
App-level route. Suite green (797).
Frontier: web::scope("/api") prefixes not prepended; anonymous .to(|req|…)
closures have no named target.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(playbook): record actix builder-API routing validation
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(extraction): Flutter setState→build synthesis + Dart method body ranges
Two changes that connect Flutter's reactive dispatch:
- Dart method ranges (foundational): Dart models a method body as a SIBLING
of the method_signature node, so every Dart method node had endLine ==
startLine (signature only) — body-level analysis (callees, context slices,
the synthesizer's body scan) saw only `void f() {`. Extend endLine to the
resolved body in the shared createNode, guarded to only ever extend
(child-body grammars are a no-op; controls excalidraw 9,290 / django 302
unchanged).
- Flutter setState→build synthesizer channel (the Dart analog of react-render):
for each Dart class with a `build` method, link sibling methods whose body
calls setState( → build. setState re-runs build (Flutter-internal, no static
edge), so "tap → handler → setState → rebuilt UI" dead-ended at setState.
counter initState→build, books build→BookDetail/BookForm. Widget composition
needs no synthesis — Dart widgets are explicit constructor calls, already
static (compass_app build→ErrorIndicator/HomeButton). Tests: Dart method
spans its body; Flutter handler→build synthesis end-to-end. Suite green (798).
Frontier: MVVM Command/ChangeNotifier dispatch (no setState) + Navigator.push
route-as-widget navigation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(playbook): record Dart/Flutter validation (setState→build + method ranges)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(resolution): Spring Boot Kotlin routing (.kt + fun handlers)
Kotlin had zero framework coverage — no resolver listed kotlin, and the
Spring resolver was languages:['java'] with a .java-only extract gate and a
Java-syntax handler regex (public X name()). Spring Boot Kotlin apps (same
@GetMapping/@RestController annotations, .kt files) extracted 0 routes.
Extend the Spring resolver: languages ['java','kotlin'], accept .kt, and add
a Kotlin `fun name(` alternative to the handler-method regex (Kotlin has no
access modifier; the return type follows the name). Also allow Kotlin class
modifiers (open/data/sealed) in the class @RequestMapping-prefix detection,
and tag route/ref language per file.
spring-petclinic-kotlin 0→18 routes, 18/18 resolved; class @RequestMapping
prefixes join, stacked annotations skipped, DI controller→repo resolves
(showOwner ← GET /owners/{ownerId} → OwnerRepository.findById). Java Spring
unchanged (realworld 19/19 — the Kotlin fun and Java public-X alternatives
are disjoint per language). Jetpack Compose composition already works
(@Composable→child are plain function calls). Tests: Kotlin @GetMapping+fun,
class-prefix + stacked annotation. Suite green (800).
Frontier: Ktor inline-lambda routing, Compose recomposition, coroutines/Flow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(playbook): record Kotlin validation (Spring Boot Kotlin + Compose)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(playbook): record Lua/Luau validation (module dispatch already covered)
Measure-first: Neovim/Roblox dispatch is module-heavy (require + cross-file
mod.fn calls), already resolved by general import+name resolution
(telescope.nvim 220 imports + 335 cross-file calls; traces end-to-end). The
matrix's assumed "callback synthesizer" hole isn't real — event-callback
registration (keymap/autocmd/:Connect) is predominantly inline anonymous
closures (corpus ~12 inline vs ~2 named), too rare to synthesize. A/B: 0
read/0 grep with codegraph vs 1 read without. No code change; validated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(resolution): Play Framework conf/routes → controller routing (Scala/Java)
Play declares routes in an extensionless conf/routes file (GET /computers
controllers.Application.list(p: Int ?= 0)) the file walk never indexed
(isSourceFile requires an extension), so Play apps had 0 route nodes.
- grammars.ts: add isPlayRoutesFile (conf/routes + *.routes), opt it into
isSourceFile, and map it to the no-grammar (yaml-style) path in
detectLanguage so the framework resolver extracts it. Narrow match — only
ADDS Play routes files, never affects other indexing.
- play.ts: a Play resolver — detect (build.sbt/conf), extract (parse each
METHOD /path Controller.action(args) line, drop package + args), resolve
(Controller.action → the action method in that controller class),
claimsReference for the dotted Controller.action handler.
computer-database 0→8 routes, 7/8 resolved (the 1 unresolved is
controllers.Assets.versioned — Play's framework controller, external);
starter 0→4 (3/4). Flow connects request→route→controller→DAO. No-regression
(excalidraw 9,290 / suite unchanged). Tests: routes parse + `->` include
skipped, conf/routes file detection.
Frontier: SIRD programmatic routers (-> include + case GET(p"/x")) + Akka
actor message→handler.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(playbook): record Scala/Play validation (conf/routes → controller)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(extraction): C++ inheritance (base_class_clause) + virtual-override synthesis
C/C++ direct dispatch already resolves well (redis 29k / leveldb 1.4k
cross-file calls). Two changes close the C++ virtual-dispatch gap:
- extractInheritance handled base_clause (PHP) but not C++'s
base_class_clause, so C++ `extends` edges were missing/partial. Add the
C++ branch (emit an extends ref per base type, skipping access
specifiers) — leveldb extends 219→298.
- cpp-override synthesizer channel (the C++ analog of react-render): for
each extends edge, link each base method → the subclass override of the
same name, so trace/callees from a virtual/interface method reach the
implementation. Gated to C++, capped per class. leveldb 12 precise edges
(Iterator::Next/Seek/Prev → MergingIterator), 0 on C (redis) and TS
(excalidraw). Test: base virtual → subclass override bridge.
Frontier: C callback structs (cmd->proc() → 422-way fan-out, too noisy)
and C++ pure-virtual base methods (declarations aren't nodes, so those
overrides can't bridge). Suite green (804).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(playbook): record C/C++ validation (inheritance fix + override synthesis)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(resolution): React Router object data-router + Next.js route precision
- Object data-router (v6.4+): createBrowserRouter([{ path, element: <Comp/> }])
/ { path, Component: Comp } — extract route + component (gated to files using
the data-router API; requires a component so a stray `path:` field isn't a route).
- Next.js precision: filePathToRoute treated config files (next.config.mjs,
vite.config.ts) and a `nextjs-pages/` dir (substring of "pages/") as routes.
Require a real page extension (.tsx/.ts/.jsx/.js), exclude *.config.* and
_app/_document, and match pages/ + app/ as path SEGMENTS. bulletproof-react
4 bogus config "routes" → 0.
Frontier: lazy data-router routes (path: paths.x.path + lazy: () => import())
use variable paths + lazily-imported modules — no literal path/named component.
Tests: object-router literal form, config/nextjs-pages exclusion. Suite 806.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(resolution): Flask-RESTful add_resource + tuple methods + broader detection
Three Flask gaps closed (redash Flask-RESTful 6→77 py routes; flask-realworld 0→19):
- Flask-RESTful: api.add_resource(ResourceClass, '/path') (+ redash's
add_org_resource) now extracts a route per path referencing the Resource
class, whose get/post verb methods resolve as the handlers.
- Tuple methods: @x.route('/p', methods=('POST',)) — the method regex only
accepted a list [...]; now accepts a tuple (...) too, so POST/DELETE routes
aren't mislabeled GET.
- Detection: detect() only checked root app.py for the literal Flask(__name__);
broadened to requirements/pyproject/Pipfile/setup.py + any entrypoint file
(root or subdir, e.g. conduit/app.py) that imports flask and instantiates
Flask(...). flask-realworld (subdir app-factory) 0→19; django not falsely
detected.
Tests: tuple methods, add_resource. Suite green (808).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(playbook): record frontier pass; test(go): gorilla/mux subrouter coverage
Frontier triage after the main sweep — tractable partials closed (React object
data-router, Next.js false-positive fix, Flask-RESTful add_resource, Flask
tuple methods + detection, gorilla/mux confirmed), and the genuinely
hard/low-precision ones (C callback fan-out, metaprogramming finders, reactive
runtimes, Akka, anonymous closures, lazy data-router, C++ pure-virtual) left
documented with rationale. Adds a gorilla/mux subrouter-var HandleFunc test
(confirms the any-receiver handling already covers it).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(benchmarks): A/B with/without codegraph across every language (S/M/L)
37-cell matrix (every flow-relevant language × small/medium/large indexed
repos): a headless agent answers one canonical flow question per repo, with the
codegraph MCP vs without any MCP. Fresh re-index per cell so the with-arm
reflects current resolvers.
Result: 75% fewer file reads with codegraph (40 vs 158 across cells), ~70%
fewer greps, never more reads in any cell. Biggest wins on medium/large
backends (excalidraw 0R vs 9R, spring-halo 0R vs 9R+8 Bash, jellyfin 4R vs 13R+
21 Bash + a spawned sub-agent); tie zone on tiny repos where the flow fits in
1-2 files.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(mcp): self-sufficient codegraph_trace + CODEGRAPH_MCP_TOOLS allowlist
codegraph_trace now returns a complete flow dossier in one call: each hop with its full body inlined (not just the call-site line), plus the destination's own outgoing calls — the last mile agents otherwise explore/Read to get. Validated by A/B (arm I, 6 repos x 2): >= baseline on reads/turns/cost with no wall-clock regression, because one richer trace call displaces the explore+node+Read follow-ups. Sufficiency, not steering: complete context is what stops further investigation.
Also adds CODEGRAPH_MCP_TOOLS, an optional comma-separated allowlist that trims the exposed MCP tool surface (inert when unset); used to run the tool-ablation experiment cleanly, and useful for constraining an agent to a minimal surface.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(benchmarks): call-sequence + tool-ablation analysis; agent-eval arms harness
Records why codegraph read savings (-75%) under-convert to wall-clock (-16%): the bottleneck is round-trips + the synthesis turn, not reads. Ablation (arms A-I) shows explore is 68% of payload but load-bearing, trace is path-scoped but under-adopted, instruction/description steering cannot match an append-prompt's salience (and regresses), and the shippable win is making the trace output sufficient (arm I). Adds harness: seq-matrix, run-arms/arms-*, parse-arms.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(mcp): line-number codegraph_node + codegraph_trace source output
node's code block and trace's inlined hop/destination bodies now carry cat -n line numbers (reusing numberSourceLines, matching codegraph_explore and Read), so the agent can cite or edit exact lines without re-Reading the file just to get them. Consistency across the code-returning tools + edit-workflow sufficiency.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(resolution): Java/Kotlin interface & abstract dispatch synthesis
A call through an injected interface (Spring @Autowired svc.list()) or an abstract base dead-ended at the interface method — no static edge to the implementation — so request->service->impl flows broke at the DI boundary. Adds interfaceOverrideEdges: for each class implementing an interface (or extending an abstract base), synthesize interface/base-method -> same-name override 'calls' edges (JVM-gated, capped per class, overload-aware), with an 'interface-impl' trace label. trace + callees now follow the flow into the implementation.
Validated on spring-mall: 310 synth edges, node count unchanged (edges only); trace(PmsProductController.getList, PmsProductServiceImpl.list) connects in 3 hops (controller -> service interface -> impl) where it previously dead-ended at the interface.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(playbook): record Java/Kotlin interface-DI synthesizer (probe-validated; agent A/B adoption-gated)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(mcp): codegraph_explore surfaces the execution flow from its named symbols
Agents call explore far more than trace and pass a bag of symbol names that spans the flow they're after. explore now resolves those names and surfaces the longest call path AMONG them — riding synthesized dynamic-dispatch edges (callback/react-render/jsx/interface-impl) — leading the output with it, so a flow question answered via explore gets the trace-quality path without switching tools.
Precision: ambiguous tokens disambiguated by CO-NAMING (keep candidates whose qualifiedName SEGMENT matches another named token, so 'list' resolves to PmsProductServiceImpl::list not OmsOrderService::list); BFS anchored at named symbols on both ends with <=1 consecutive unnamed bridge (crosses a missing intermediate, never wanders a god-function's fan-out). Validated by probe: spring-mall getList->service-interface->impl (3 hops); excalidraw mutateElement->triggerUpdate->[callback]->triggerRender->[react-render]->render->[jsx]->StaticCanvas (full re-render chain). No flow section on fuzzy queries (safe). Suite green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(mcp): explore-flow resolves qualified Class.method query tokens
The agent often passes fully-qualified names to explore (PostEndpoint.publishPost, PmsProductServiceImpl.list) — its most precise input. The tokenizer's file-extension strip mangled Class.method into Class (treating .method as an extension), then the identifier filter dropped anything with a dot, throwing the method away. Now strips only REAL file extensions and keeps qualified tokens, which findAllSymbols resolves exactly; disambiguates ambiguous SIMPLE names by whether their container class is also named (segment match). Validated: 'PmsProductController.getList PmsProductServiceImpl.list' now surfaces getList->interface->impl. (spring-halo's publish flow stays absent — it's reactive/reconciler dispatch with no static edges, a coverage frontier, not an explore-flow gap.)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(claude): record the 'adapt the tool to the agent' retrieval principle
The lever that decides whether a retrieval change lands: make a tool the agent already calls do more with the input it already gives; changes that need the agent to behave differently (different tool, query, examples) hit codegraph's low-salience channels and don't land. Captures the validated evidence (sufficiency + explore-flow pass; steering + new-tools + context-fuzzy-flow fail) and points coverage as the remaining lever.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: correct 'cost stays flat' → neutral-to-lower (excalidraw with/without A/B)
Fresh with-vs-without A/B on excalidraw (current build, n=3): 3x faster (49s vs 145s), 15x fewer tool calls, ~0 vs 23 reads, and -40% cost ($0.41 vs $0.68). Cost is neutral-to-lower, not flat — compact codegraph answers cache across turns while the without-arm's read/grep thrash is fresh, poorly-cacheable input. Recorded in call-sequence-analysis.md; corrected the CLAUDE.md optimization-target note (still: don't optimize for cost; target wall-clock + tool-call count).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(benchmarks): current-build A/B on all 7 README repos + fix token-measurement bug
Re-ran the README benchmark on the current build (7 repos reindexed, median of 4): avg 35% cost / 57% tokens / 46% time / 71% tool calls saved — reproduces the published README (35/59/49/70), no regression. Adds bench-readme.sh + parse-bench-readme.mjs harness.
Fixes a token-measurement bug: result.usage is last-turn-only in current Claude Code; must sum per-turn assistant usage for cumulative tokens. Corrects the earlier excalidraw note (its '-34% tokens' was off this bug; real ~90%) and the cost MECHANISM (volume/fewer-turns, not cache-ability — the without-arm's huge token volume is mostly cheap cache-reads, so token savings 57% > cost savings 35%). Cost/time were always correct.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: finalize 0.9.4 — consolidate CHANGELOG + re-validate README benchmark
Folds the framework sweep + retrieval work into [0.9.4] (2026-05-24). README benchmark table refreshed with current-build medians (avg 35% cost / 57% tokens / 46% time / 71% tool calls) + a v0.9.4 re-validation note.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(readme): add codegraph_trace to the MCP Tools table
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
bf73f4d05c |
feat(installer): add codegraph uninstall command (#313) (#318)
Adds a cross-channel uninstall that removes CodeGraph from every agent it's configured on (Claude Code, Cursor, Codex CLI, opencode, Hermes). Prompts global-vs-local up front (no flags required) and reports which providers it actually hit; --location / --target / --yes supported for non-interactive use. Removes only what install wrote; leaves the .codegraph/ index to `uninit`. Also fixes Cursor uninstall leaving an orphaned .cursor/rules/codegraph.mdc (its description: CodeGraph frontmatter lingered); the dedicated rules file is now deleted outright while user content outside our markers is preserved. Validated end-to-end on macOS and Docker Linux (global + local sweeps clean). Adds 8 tests; full suite 730 passing. Bumps to 0.9.3 with CHANGELOG entry. Resolves #313. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
95dace987e |
feat(installer): add Hermes Agent target (#274)
Adds Hermes Agent (Nous Research) as a CodeGraph installer target. Writes mcp_servers.codegraph and ensures platform_toolsets.cli includes mcp-codegraph in $HERMES_HOME/config.yaml, with full installer contract-test coverage. |
||
|
|
b8aec39abd |
fix(installer): strip stale auto-sync hooks on install and uninstall (#278)
Pre-0.8 installers wrote `codegraph mark-dirty` / `sync-if-dirty` hooks
to Claude Code's settings.json. Both subcommands were removed from the
CLI, so the Stop hook fails every turn ("unknown command
'sync-if-dirty'"). The cleanup that once removed them was lost when the
installer moved to the per-target architecture.
Add cleanupLegacyHooks(), wired into both install (upgrades self-heal)
and uninstall (so the npm preuninstall step fully reverses a legacy
install). Surgical at the command level: only codegraph's own hook
entries are dropped, so unrelated hooks sharing a matcher group or event
(e.g. GitKraken's `gk ai hook run`) survive, and a settings.json with no
legacy hooks is left byte-for-byte untouched.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
f5bbc26c60 |
perf(mcp): answer-directly steering — ~35% cheaper, ~70% fewer tool calls (#224)
* perf(mcp): steer agents to answer directly instead of delegating to subagents CodeGraph beats native grep/read on cost only when the agent queries it directly. When the agent delegates to file-reading sub-agents, those sub-agents read files regardless of the index, so CodeGraph becomes net overhead on top of the reads. The install templates even told agents to "spawn a subagent for explore-class questions" — the expensive path. Changes: - server-instructions + both install templates: add an "Answer directly — don't delegate exploration" directive; reposition codegraph_explore as the efficient one-call multi-symbol tool (was: "spawn a subagent for it"). - codegraph_explore: hard-cap output to its adaptive budget (it overran, ~30k vs a 28k cap) and tighten the medium tier (28k->13k). - codegraph_node: return a member outline for container kinds instead of the full class body. Rigorous N>=4-per-arm warm-block benchmark (median total_cost_usd): excalidraw (~600 files): WITH $0.54 vs native $1.02 (-47%) vscode (~10k files): WITH $0.41 vs native $0.72 (-42%) ky (~25 files): WITH $0.46 vs native $0.44 (wash) Answers were equal-or-better (correct, file:line-cited) with ~6x fewer tool calls; the directive drove the direct path on 14/14 codegraph runs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(readme): rebuild benchmark with real-world repos + cost/token/time/tool savings Replace the "Claude Code (Python+Rust/Java)" rows — which benchmarked the Claude Code CLI repo, not real codebases in those languages — with real open-source projects per language: Django (Python), Tokio (Rust), OkHttp (Java), Gin (Go), plus Alamofire (Swift) and the existing TypeScript repos (VS Code, Excalidraw). The table now reports all four savings the change targets — cost, tokens, time, tool calls — as the median of 4 runs per arm (Claude Opus 4.7, headless claude -p, with vs empty MCP config). Averages across the 7 repos: 35% cheaper, 59% fewer tokens, 49% faster, 70% fewer tool calls. Adds a methodology note and raw WITH->WITHOUT medians. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
cf7db7cb98 |
fix(mcp): skip fs.watch on WSL2 /mnt drives that hang MCP startup (#199) (#210)
Recursive fs.watch on a WSL2 /mnt NTFS/9p mount walks the directory tree with every readdir/stat crossing the Windows boundary, stalling the event loop long enough to blow past opencode's 30s MCP handshake timeout so the tools never appear. This is the file-watcher half of the #172 fix, which moved the DB/WASM open off the handshake but left the watcher on the critical path. - Add watchDisabledReason() policy: CODEGRAPH_NO_WATCH (off) > CODEGRAPH_FORCE_WATCH (force on) > WSL2 + /mnt auto-detect (off). FileWatcher.start() and the MCP server both honor it; the server now logs why watching is off and how to refresh. - Add `codegraph serve --mcp --no-watch`. - When watching is off, init/install offer git sync hooks (post-commit, post-merge, post-checkout) that run `codegraph sync` in the background, or fall back to manual sync; either way the user is told the index stays frozen until re-synced. uninit removes the hooks. - Tests: watch-policy + git-hooks (idempotency, user-content preservation, core.hooksPath). Root-cause analysis and workaround by @mengfanbo123. Closes #199 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
79b9601aae |
fix(installer): write Claude project-local MCP config to .mcp.json (#207) (#209)
Project-local installs wrote the MCP server to ./.claude.json, which Claude Code never reads — project-scoped servers must live in .mcp.json. The codegraph tools silently never loaded until users renamed the file by hand. Local installs now write ./.mcp.json and migrate any stale ./.claude.json entry on install and uninstall (siblings preserved). Global installs (~/.claude.json, user scope) were already correct. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
e176062c56 |
fix(cli): ASCII glyph fallback for Windows console mojibake (#168) (#178)
The shimmer progress renderer writes from a worker thread via `fs.writeSync(1, ...)` to keep the animation smooth while the main thread is busy in SQLite. That path bypasses Node's TTY-aware UTF-8->codepage conversion on Windows, so glyphs like `|`/`<>`/`-` were emitted as raw UTF-8 bytes and reinterpreted by the console's OEM codepage (CP437, CP936, ...), producing strings like `鋍?[0m 鉒?[0m Scanning files 鈥?N found`. Add `src/ui/glyphs.ts` with `supportsUnicode()` detection plus matched Unicode + ASCII glyph sets, and route all CLI/shimmer output through `getGlyphs()`. Defaults: ASCII on Windows and on Linux kernel consoles (`TERM=linux`), Unicode everywhere else. `CODEGRAPH_UNICODE=1` and `CODEGRAPH_ASCII=1` are escape hatches. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
58c1414ce5 |
fix(installer): opencode .jsonc + AGENTS.md (0.7.8) (#163)
* release: 0.7.7 (multi-agent installer — Cursor, Codex, opencode) * fix(installer): opencode .jsonc + AGENTS.md (0.7.8) v0.7.7 wrote ~/.config/opencode/opencode.json, but opencode reads opencode.jsonc by default — so the codegraph MCP entry never appeared in any opencode session. Also installs AGENTS.md so opencode's model reaches for codegraph_* tools instead of native Grep. - Prefer existing .jsonc, fall back to .json, default new installs to .jsonc. - Surgical edits via jsonc-parser preserve user comments and formatting across install / re-install / uninstall round-trips. - Install AGENTS.md (global ~/.config/opencode/AGENTS.md, local ./AGENTS.md) with the shared INSTRUCTIONS_TEMPLATE — same marker-delimited approach Codex uses. - +9 opencode-specific tests covering filename precedence, comment preservation, AGENTS.md install + sibling-content preservation, uninstall reverses both files. 575/575 tests pass. Hand-verified end-to-end: opencode session calls codegraph_node + codegraph_callers for a structural query, zero Grep calls. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: overhaul CLAUDE.md and add scripts/release.sh + Cursor rules file Replaces the old Claude-only CLAUDE.md with a comprehensive guide covering the full project architecture, multi-agent installer, test conventions, NodeKind/EdgeKind reference, and release workflow. Key additions: - Documents the layered pipeline, all module paths, and the multi-target installer (targets/, registry.ts, AgentTarget interface). - Adds the Cursor `--path` quirk and the "update all three surfaces" rule when changing MCP tool guidance. - Documents `npm run eval`, `test:eval`, and the full set of build/test commands including single-file patterns. - `scripts/release.sh` — idempotent bash script that tags the current commit, pushes the tag, and creates a GitHub Release whose notes are extracted from the matching `## [X.Y.Z]` block in CHANGELOG.md. Safe to re-run after partial failure. - `.cursor/rules/codegraph.mdc` — Cursor-specific agent instructions (tool decision table, rules of thumb, index-lag warning) written by the installer and kept in sync with server-instructions.ts and instructions-template.ts. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
a447e1d430 |
feat(installer): multi-target — Claude Code, Cursor, Codex CLI, opencode (#162)
* feat(installer): multi-target — Claude Code, Cursor, Codex CLI, opencode Closes the Claude-locked installer behind issue #137. The runtime MCP server was already agent-agnostic (stdio); only the installer was locked. After this refactor, `codegraph install` can write per-agent MCP config + instructions for any combination of supported agents. ## What ships Four agent targets, each implementing the new `AgentTarget` interface: - **Claude Code** — `~/.claude.json`, `~/.claude/settings.json`, `~/.claude/CLAUDE.md` (or local equivalents). Behavior preserved from the original installer; existing installs upgrade in place. - **Cursor** — `~/.cursor/mcp.json` (g) or `./.cursor/mcp.json` (l) + project-local `./.cursor/rules/codegraph.mdc`. - **Codex CLI** — `~/.codex/config.toml` with `[mcp_servers.codegraph]` + `~/.codex/AGENTS.md`. Global only. Hand-rolled TOML serializer scoped to the table we own — siblings + array-of-tables preserved. - **opencode** — `~/.config/opencode/opencode.json` (XDG) or `./opencode.json`. Adding a 5th agent is a new file in `src/installer/targets/` plus one entry in `registry.ts`. ## CLI changes ``` codegraph install # interactive multi-select codegraph install --yes # auto-detect, install global codegraph install --target=cursor,claude --yes # explicit list codegraph install --target=auto --location=local # detected, project-local codegraph install --target=none # skip agent writes entirely codegraph install --print-config codex # dump snippet, no writes ``` ## Backwards compat Every export from the old `config-writer.ts` (`writeMcpConfig`, `writePermissions`, `writeClaudeMd`, `hasMcpConfig`, `hasPermissions`, `hasClaudeMdSection`) is preserved as a `@deprecated` shim that delegates to per-file helpers in `targets/claude.ts`. Existing Claude users see byte-identical on-disk layout — `detect()` reports `alreadyConfigured: true`, re-running is a no-op. ## Tests +47 new tests in `__tests__/installer-targets.test.ts`: - Parameterized contract test across all 4 targets × supported locations (install → unchanged on re-run, sibling preservation, uninstall reverses install, printConfig writes nothing). - Codex partial-state recovery, locked-block contract for the codegraph table, full TOML serializer suite. - Registry: getTarget, resolveTargetFlag (auto/all/none/csv). `__tests__/installer.test.ts` relaxed one assertion: the new code returns `unchanged` for byte-identical re-runs instead of `updated`; the surrounding-custom-content contract is unchanged. ## Uninstall behavior change `bin/uninstall.ts` now loops `ALL_TARGETS.uninstall('global')` on `npm uninstall -g`. A user who manually configured `~/.codex/config.toml` with our block will have only that block removed on package uninstall — we only touch the dotted-key table we own. Based on andreinknv/codegraph@c5165e4. Issue #137. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(scripts): add local-install.sh for hands-on branch testing Builds the current branch and `npm link`s it as the global `codegraph` binary. `--undo` unlinks and reinstalls the published version. Mirrors the style of scripts/release.sh. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(installer): move agent picker to the first prompt Reorders runInstallerWithOptions so the multi-select for agents (Claude / Cursor / Codex / opencode) is step 1 — before the global-npm-install confirm and before the location prompt. Bare `npx @colbymchenry/codegraph` now opens with "Which agents should CodeGraph configure?", which is the answer most users want first. Side effects of the reorder: - Early exit if zero targets selected — skips global-install and location prompts entirely, exits with "nothing to do." - Multiselect labels drop the per-location "will skip" hint (location isn't known yet) and replace it with a static "global only" badge for targets like Codex that have no project-local config concept. - If every selected target is global-only, the location prompt is skipped and global is forced (no point asking). - Detection probes the user-provided location if known via flag, else 'global' as the most common default — labels are a hint about what's installed locally, not load-bearing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(installer): disambiguate "global" wording in install prompts Two prompts both said "global" but meant different things — users read them as duplicates. Renamed for clarity: - Step 2 (npm install -g): "Install codegraph globally?" → "Install the codegraph CLI on your PATH? (Required so agents can launch the MCP server)". Spinner messages match. - Step 3 (config location): "Where would you like to install?" with "Global"/"Local" → "Apply agent configs to all your projects, or just this one?" with "All projects" (~/.claude, ~/.cursor, etc.) / "Just this project" (./.claude, ./.cursor, etc.). - All-global-only fallback: "Using global install" → "Writing user-wide configs (selected agents have no project-local config)." Underlying `Location` values ('global' / 'local') unchanged; only the UI strings shift, so no test or flag breakage. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(installer/cursor): inject --path so workspace-aware queries work Cursor launches MCP-server subprocesses with cwd != workspace root, AND does not pass rootUri or workspaceFolders in the MCP initialize call. The codegraph MCP server's process.cwd() fallback misses the workspace's .codegraph/ and reports "not initialized" on every tool call. Codex and Claude don't have this issue (Codex launches with cwd=workspace, Claude passes rootUri). Fix: inject `--path` into the args we write for Cursor. - local install (./.cursor/mcp.json): hardcode the absolute project path — known at install time. - global install (~/.cursor/mcp.json): use `${workspaceFolder}` so Cursor expands it per-workspace. One global config now drives every project the user opens, without per-project re-install. No test breakage — the parameterized contract tests check idempotency / sibling preservation, not the exact args content. File-header comment documents the rationale so the next person doesn't strip the arg as boilerplate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(init): auto-wire project-local agent surfaces Closes the global-Cursor UX gap: `~/.cursor/mcp.json` registers the MCP server, but Cursor's agent only learns to *prefer* codegraph over native grep when it sees `.cursor/rules/codegraph.mdc` — a project-local file that global install can't write. Previously the user had to re-run `codegraph install --target=cursor --location=local` for every new project. Now `codegraph init` does it automatically. ## What changed - New optional `AgentTarget.wireProjectSurfaces()` returning a WriteResult of project-local files to drop. Most targets omit it (their global config is complete). Cursor implements it to write the rules file. - New `wireProjectSurfacesForGlobalAgents()` orchestrator in installer/index.ts — iterates ALL_TARGETS, detects which are configured globally, calls their wireProjectSurfaces, returns what was written. - `codegraph init` calls the orchestrator in both branches: - Fresh init: write surfaces after CodeGraph.init succeeds. - Already-initialized re-init: write surfaces too, so re-running `init` is the documented recovery path for a project missing its rules file. ## Steady-state UX 1. Once, ever: `codegraph install` (writes global agent configs) 2. Per project: `codegraph init -i` (builds the index + auto-wires project-local agent surfaces — currently Cursor's rules file) No new tests — wireProjectSurfaces delegates to writeRulesEntry, which is already covered by the parameterized contract tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(installer): agent-agnostic instructions template The old template was inherited from the Claude-only era and prescribed "ALWAYS spawn an Explore agent" — a Claude Code-specific concept (subagents via the Task tool). When Cursor's agent read this it had no Explore agent to spawn, got confused, and fell back to native grep/read even for structural queries the codegraph MCP tools answer in one call. This rewrite: - Frames each tool by the question it answers (search vs callers vs impact vs context vs explore vs node vs files vs status). - Tells the agent explicitly to TRUST codegraph results and not re-verify them with grep — the over-grep-after-codegraph behavior was the main symptom we saw on Cursor. - Reframes "spawn Explore agent" as an OPTIONAL pattern for harnesses that support parallel subagents — Claude Code still gets the hint, Cursor / Codex / opencode just skip it. - Trims the "if not initialized" section to one prescriptive line. Same marker delimiters (`<!-- CODEGRAPH_START/END -->`) so existing installs upgrade in place via the marker-based section swap. No test changes needed — the parameterized contract tests check marker placement + sibling preservation, not the literal body. Effective surfaces: ~/.claude/CLAUDE.md (Claude), .cursor/rules/ codegraph.mdc (Cursor, project-local), ~/.codex/AGENTS.md (Codex). Users get the new copy by re-running `codegraph install` for global writes, or `codegraph init` for Cursor's project rules. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(readme): reflect multi-agent support at the top + accurate flow - Tagline now reads "Supercharge Claude Code, Cursor & Codex" instead of Claude-only — multi-agent support is what the PR is about, the README should say so above the fold. - New badge row (Claude Code / Cursor / Codex CLI / opencode) in the same shields.io style as the OS row. - Install-flow bullets reordered to match the actual prompt order (agent picker first, then PATH install, then location). - `codegraph init -i` step now mentions that init wires up project-local agent surfaces (Cursor rules file etc.) so global install works in every project without a re-run. - Agent-agnostic phrasing in the closing line ("your agent" not "Claude Code"). Headline-level brand decision left intentionally in this PR — the existing Claude-only positioning predates multi-agent support. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: andreinknv <andrei.nknv@outlook.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
32f9cd460e |
docs: Clean up README formatting and remove deprecated CLI hook commands
Removes crystal ball emoji and bullet formatting inconsistencies from README headers. Eliminates mark-dirty and sync-if-dirty CLI commands and related hook configuration code, simplifying the codebase after transitioning to file watcher-based auto-sync. |
||
|
|
77432fe4ea |
feat: Add dynamic codegraph_explore call budgets based on project size
Replaces fixed 6-call limit with adaptive budgets that scale from 2 calls for small projects ( |
||
|
|
8e8759ff13 |
feat: Add Swift Compiler benchmark and increase codegraph_explore call limit to 6
Updates benchmark results with the largest tested codebase (25,874 files, 272,898 nodes) demonstrating CodeGraph's scalability. Increases the recommended call limit from 3 to 6 to accommodate more complex cross-cutting queries while maintaining efficiency gains over traditional file-reading approaches. |
||
|
|
64d844c938 |
feat: Move parsing to worker threads for smooth progress animation
Offloads tree-sitter parsing to a dedicated worker thread, keeping the main thread unblocked so shimmer progress animations render smoothly during indexing. Refactors shimmer progress renderer into separate worker for consistent 50ms animation updates. Falls back to in-process parsing when worker compilation unavailable (e.g., tests). |
||
|
|
ed35d65f4c |
feat: Replace figlet with @clack/prompts for polished CLI experience
Replaces ASCII art banner and basic readline prompts with @clack/prompts for a modern interactive CLI. Adds animated shimmer progress bars with spinner glyphs during indexing operations. Improves installer UX with structured prompts, better error handling, and cleaner output formatting throughout all CLI commands. |
||
|
|
3a44d5c4d1 |
fix: Improve CLI progress display and prevent tree-sitter WASM memory crashes
Replaces fixed-width padding with terminal escape sequences for proper progress line clearing across different terminal widths. Adds periodic parser reset every 5000 parses per language to prevent WASM heap fragmentation that causes "memory access out of bounds" crashes in large repositories. Includes filename truncation to fit available terminal width. |
||
|
|
e4908e1270 |
feat: Add database schema v3 with optimized node lookups and improved error handling
Adds expression index on lower(name) for memory-efficient case-insensitive searches, replacing in-memory caches that caused OOM on large codebases. Includes batched reference resolution, enhanced error reporting with detailed breakdown by error type, and improved CLI progress display for scanning phases. |
||
|
|
4d65d60ded |
feat: Update Claude instructions to discourage direct codegraph tool usage in main session
Changes guidance to recommend spawning Explore agents for exploration questions instead of using codegraph_explore/codegraph_context directly in main session to avoid filling up context with large amounts of source code. Adds completeness signal to codegraph_explore output so agents know not to re-read files that already have source code included. |
||
|
|
b927492bc0 |
feat: Add codegraph_explore tool for comprehensive single-call code exploration
Introduces a new MCP tool that performs deep code exploration in a single call, returning comprehensive context with full source code sections grouped by file and relationship mapping. Designed to replace multiple codegraph_node + file read operations for thorough understanding of code topics. Updates documentation to position explore as the primary tool for deep exploration questions. |
||
|
|
4af51f565b |
feat: Extract type references from annotations and improve symbol query matching
Adds type annotation parsing to create references edges for parameter types, return types, and variable type annotations in TypeScript and other typed languages. Expands symbol extraction from queries to capture lowercase identifiers and filters out more common English words. Removes obsolete search utility tests. |
||
|
|
8b5622d346 |
Remove Sentry telemetry system from CodeGraph
Eliminates anonymous error reporting functionality that was collecting stack traces and error context via Sentry. Removes all telemetry-related code, configuration options, and documentation references. |
||
|
|
5a185eb736 |
fix: Add telemetry opt-out via installer prompt and env var
Sentry error reporting can now be disabled by: 1. Declining during the interactive installer (sets CODEGRAPH_TELEMETRY=off in the MCP server config env) 2. Setting CODEGRAPH_TELEMETRY=off in your shell environment README updated with a Telemetry section documenting what is collected and how to opt out. Closes #68 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
12b0414900 |
fix: Prompt before global npm install during installer
The installer previously ran `npm install -g` silently without user consent. Now it asks for confirmation first, explains why the global install is needed (hooks & MCP server), and gracefully skips if declined. README updated to document this step. Closes #69 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |