Introduce Expo Router integration: a new framework resolver, route-based screen nodes, and navigates edges, plus a /api/screens endpoint and a Screens UI view. Adds branch-guard-driven labeling of edges, resolution logic, and tests to cover extraction, resolution, and end-to-end flow. This enables CodeGraph UI to surface screens and transitions from Expo Router apps.
Save trail on the trail bar writes the walk to .codegraph/ui/trails/ as one
JSON file, listed on the empty screen and on Entry points above the derived
suggestions, reopened at the symbol you left with the whole path restored.
A hop is stored by qualified name, kind and file — never by node id, which
contains a start line and so changes the first time anybody edits above the
symbol. Every hop is re-resolved against the current index on the way out and
each row says what became of it: still here, moved to another file, now
ambiguous, or gone. A hole is never stitched over: the row opens the longest
run of CONSECUTIVE resolved hops and says which ones those are, because the
trail is a path and a skipped hop would draw a call that does not exist.
This is the first write the viewer makes, and the boundary moved with it:
POST/DELETE answer under /api/ only, must carry X-CodeGraph-UI and
application/json (neither of which a cross-origin form can produce without a
preflight this server answers none of), and --read-only refuses both while
still listing what is there. The blanket "read-only" claim is retired from the
banner, the README, the CLI help and the docs site in favour of the narrower
true one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"Copy image" and "Download SVG" on the Flow strip's header and in the Map's
side panel. The image is the distribution loop: a flow pasted into a review, a
map pasted into a README, read by somebody with no viewer open.
The exporter serialises the LAYOUT OBJECT rather than scraping the DOM — no
html-to-image, no foreignObject, no new dependency. buildFlowLayout and
buildMapLayout already compute every rectangle, port and curve before a
component renders, so the image and the screen come from one piece of
arithmetic and cannot drift apart, and the whole exporter is a pure function a
test runs with no browser. Output is presentation-only SVG (rect, line, path,
polygon, text, tspan, clipPath) — no script, no external reference, no data:
URL — which is what GitHub's sanitiser accepts in a README.
Light theme is forced whatever the viewer is set to: a dark strip on GitHub's
white comment background reads as a mistake, not a preference. 24px of paper
around the drawing, a caption naming the path or the root at the bottom left,
a CodeGraph mark at the bottom right.
Fonts travel as family stacks, not bytes (spec). An SVG loaded as an image may
not fetch a webfont, so a raster falls back to the platform's own monospace —
every fallback in the stack advances at ~0.6em like IBM Plex Mono, so the code
grid survives and only the letterforms change. Text is truncated
arithmetically with an ellipsis and clipped as well, so a wider fallback
cannot spill a source line out of a card.
`scale` multiplies only the root width/height while the viewBox stays in CSS
pixels, so the raster draws an image whose intrinsic size is already 2x
instead of upscaling a 1x bitmap. The clipboard write uses the ClipboardItem
promise form (Safari discards the gesture across an await) and falls back to
downloading the PNG, saying which happened rather than claiming a copy it did
not make.
Measured on this repo: execute -> rowToFileRecord (8 hops) exports 3690x253
CSS px, 491 kB PNG at 2x / 38 kB SVG; the 16-module map reproduces the canvas
exactly — 16 boxes, 52 links, 9 layer rules, both band labels, and with
src/index.ts selected 15 links and 4 dimmed boxes.
`#/entry` answers "where does anything start" at full length, and turns any row
that names a symbol into a flow.
Server. `/api/entrypoints` gains `frameworks` (from `getDetectedFrameworks`), a
`tests` list, a `routes` limit of its own, and a cache keyed on the index build
— nothing here is read from disk, so unlike `/api/source` a cached answer cannot
be stale about drift. `routes.items` is now a `WireList` like every other list on
the payload.
Routes carry where the URL is REGISTERED as well as where it is served:
`getRoutingManifest` selects the route node's id, file and line, and
`buildRoutes` splits the verb off the name against a fixed list (never "the
first word", which would take the head off a file-routed `/blog/[slug]`). All
four payroll-go routes register in one router file and three are served from
another — group by the handler file and one router becomes two groups plus an
orphan.
`isTestFile` is split into `isTestPath` (test filename and directory
conventions) + the non-production catch-all, byte-identical at every existing
call site. The Tests list uses the narrow half: an example, a benchmark or a
fixture is off-target for ranking but is not a test, and a heading that says
"Tests" must not quietly count them. Tests rank by REACH — distinct other files
touched — because Go, Rust and Java put test work inside functions where a
module-level-calls ranking sees nothing. Two read-only engine queries make that
affordable: `getFileReachCounts` (the mirror of `getFileDependentCounts`, driven
from `nodes` by path so the cost follows the files asked about rather than the
edge table) and `getFileNodes`.
Viewer. `ui/src/lib/entry-model.ts` folds the four lists into file groups —
pure, and `panel.rows` stays exactly the sections it draws. `EntryView` +
`EntrySection` render them with the caller rail's `.filegroup` / `.row` shapes
rather than a second visual language for the same idea. A row that names a
callable symbol carries a `Flow ›` chip; the other end is typed or picked with
`→ here` on another row. File and test rows carry none: `/api/flow` searches by
name, and a file has none the path finder can look up.
A project with fewer than three resolvable routes gets no Routes heading at all,
not an empty one. Typing into the search box now also returns matching entry
points under their own heading below the symbol matches, so a URL comes back
with its handler attached; rows already in the results are dropped.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A flow that does not reach what it was asked about now ends in a cap instead
of in silence: the dispatch form that ended it, the line, the static key when
the source spells one out, the candidate runtime targets as clickable rows,
and the name-only matches under 0.6 the search refused to follow. A flow that
does reach its destination never shows one.
The verdict is lifted out of `ToolHandler` into
`src/graph/dynamic-boundary-report.ts` and both callers render it —
`codegraph_explore`'s prose and `/api/flow`'s `WireFlowBoundary` — the same
move `named-symbol-flow.ts` made for the path finder, and for the same reason:
a reader holding the strip and the MCP answer must not be told two different
things. The explore prose is unchanged, byte for byte.
When nothing connects at all and a dispatch site explains why, the strip is
that site: one card opened at the line where the static path ends, plus the
cap. When nothing explains it, no stopping point is invented.
`GET /api/events` is a server-sent-event stream the viewer holds open for the
life of the page. Two signals, two things the browser could not know:
changed source files touched on disk, before any sync — the drift banner
index the graph moved, naming what the sync re-indexed — the live refresh
The server WATCHES and never syncs: the project tree through the engine's own
FileWatcher with a notify-only syncFn, the index through one non-recursive
fs.watch on the data directory settled at 400 ms. Both start with the first
subscriber and stop with the last, so a viewer nobody has open costs no watch
descriptors. Nothing polls, on either side.
Drift is now parity with codegraph_node (#1474) rather than an absence.
`/api/source?ondrift=current` serves a drifted file's CURRENT bytes flagged
`showing: 'current'`, and the three screens that can say so switch off
everything anchored to the old line numbering — gutter ports, call-site links,
call arcs, the callee rail's anchoring — while keeping the source. The banner is
paper-2 with a hairline rule, never amber: amber belongs to the untested badge.
Also fixes a stale read this exposed. A long-lived reader holds an LRU of nodes
by id that only its own writes invalidate, so `/api/node/<id>` kept answering
with a symbol another process's sync had deleted while `/api/search` beside it
said it was gone. GraphSession now drops the read caches when the database (or
its WAL) has been written, and the Symbol view follows a symbol whose id changed
because an edit above it moved its start line, carrying the trail across.
Measured on a live viewer: banner 360 ms after a save, toast 440 ms after
`codegraph sync` returns, 0 requests in 4 idle seconds, and the client gives up
reconnecting after ~90 s with "Not live" rather than hammering a dead port.
The File view gains a Source tab: the file itself, top to bottom, with the
Symbol view's line grid, gutter ports and call-site links, a line-anchored
callee rail, and — in the left margin — an arc for every call that stays inside
the file, drawn from the calling line to the callee's definition line.
The arcs are the point. Source order is already a layout, chosen by whoever
wrote the file, so a file's internal call structure can be drawn with no
algorithm placing anything. Crabviz's idea, in the one place it is legible.
Everything is arithmetic, not measurement. The Symbol view queries the laid-out
DOM to place a callee row beside its line; a 6 820-line file cannot afford that.
Here a line is exactly 20px at `10 + (n - 1) x 20`, so ~90 line elements exist at
a time and the arcs, ports, rail rows and connectors are all functions of a line
number. `src/mcp/tools.ts` scrolls at a 16.6ms median frame.
- `GET /api/filecode/<path>` — outline, one call group per (caller, callee) PAIR
with its call-site lines, unresolved references, and the file's length. The
source is NOT in it: it pages through `/api/source` 800 lines at a time with a
discarded 150-line lead-in, so a page starting inside a block comment does not
render prose as code, and so the ports and arcs are complete from the first
frame while the text fills in behind them.
- `intraFileCalls` is counted over the groups actually returned, so the header
and the picture under it cannot disagree once a cap bites.
- Above 40 arcs the diagram narrows to the symbol under the pointer (or the one
the scroll position is inside) and the header states the total. Accent is for
the pointer only, never for the filter.
- Sticky outline rail at >= 1400px, following the reader down the file.
- `QueryBuilder.getUnresolvedReferencesInFile` — one indexed lookup instead of
one per symbol; `buildOutlineEntries` lifted out of `/api/file` so both
readings of a file draw the same rows.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ask "how does execute reach getFile" in the search box and the viewer draws the
call path between them, left to right, opening every card at the exact line that
makes the next call. Dynamic-dispatch hops are dashed and name the site they
were wired at; "Read as flow" turns a trail walked by hand into the same strip.
The path finder is NOT new. `codegraph_explore` already leads its answers with
the longest call chain among the symbols an agent named, and a viewer that drew
a different path would get the two quoted against each other in a review. So the
search moved out of `ToolHandler` into `src/graph/named-symbol-flow.ts` and both
callers ride it — same tokens, same overload rules, same synthesized edges. What
stayed behind in `tools.ts` is the prose.
A pinned from/to question is the same search with two options changed, because
both ends being named is the evidence explore's one-unnamed-bridge cap stands in
for: it bridges freely, keeps twelve candidates per endpoint instead of six
(the CLI's own `main` sorts seventh of ten), and searches from both ends at once
— identical paths to the one-way walk on twelve measured pairs, 3-6x faster.
`/api/flow` is deliberately the one endpoint with no cache: its cards carry
source read from disk, and a drift verdict changes without the index changing.
Verified on this repo (`execute` to `rowToFileRecord`, 8 hops; `main` to
`resolveOne`, 7) and on a fresh excalidraw index, where `mutateElement` to
`renderStaticScene` crosses callback, react-render and jsx-child hops and lists
exactly the hops `codegraph_explore` prints.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A new user can now reach the viewer from the README alone: a "Read your
graph in the browser" section with a screenshot re-shot from the real
build, a step 5 in Get Started, a CLI Reference row, and the same
content as a docs-site guide.
- README: new section (what the three columns are, the options, the
privacy posture), Contents entry, Get Started step 5, CLI row.
Screenshot at assets/codegraph-ui-symbol-view.png, version-tagged ?v=1.
- CHANGELOG: an [Unreleased] New Features entry in the user-facing voice.
- codegraph help ui: mentions the `web` alias, says what the screen shows,
and states that nothing is sent anywhere.
- TELEMETRY.md: the viewer has no telemetry of its own and makes no
outbound connections; the only thing recorded is the command name in
the daily rollup, which every off-switch already suppresses.
- site/: guides/viewer.md + sidebar entry, a `ui` section in the CLI
reference, and a link from Next Steps.
The 1.6.0 notes said the write-ahead log is "capped", which reads as a
limit on how much can be indexed. It bounds only the log's resting size
(64 MB default, CODEGRAPH_WAL_HEAL_MB) and folds a killed session's
leftover back into the index; a large repository's log still grows in
proportion to its index while it is built. Say so in both entries, and
document the two knobs in the README's troubleshooting section.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MC52FSFLtKtDCLqT81tZYG
Fixes#1578.
## What was wrong
Bootstrapping CodeGraph in a fresh environment — the issue's case is a throwaway container per AI session — took two commands, `codegraph install --yes` and then `codegraph init`, and the second one could still stop on a prompt (the gitignored-child-repos offer, the watch-fallback offer on WSL/`/mnt`). There was no way to wire agents and build the project's index in one non-interactive line.
The installer's "never index implicitly" rule is deliberate (a surprise index of `$HOME` is exactly what `init` refuses), so the gap is an explicit opt-in, not a change in default behavior.
## What this does
- **`codegraph install -i, --init`** — after wiring the agents, runs the `init` flow in the current directory. It also runs when nothing was wired (`--target none`, no agents detected), since the installer returns normally in that case. Every `init` guard applies: a home directory / filesystem root / parent of home is **refused with exit code 1** (no implied `--force`), and an already-initialized project just reports that and exits 0. `--print-config` and `--refresh` return before the install, so `--init` is a no-op with them.
- **`codegraph init -y, --yes`** — non-interactive: the ignored-repos offer prints its one-line `includeIgnored` opt-in snippet instead of prompting (the existing non-TTY behavior), and the watch-fallback offer takes its `yes` default. `install --init` passes `--yes` through, so `codegraph install --yes --init` is a fully unattended bootstrap.
- The `init` action body becomes `runInit()`, shared by both commands. The plain `init` path is behavior-identical (same refusal, already-initialized notice, supervised index, telemetry, offers, outro).
- The post-install "Next: index a project" note gains one line mentioning `--init`; README gets the flag row and a `--yes --init` example.
On the reporter's other observation — `install --yes` skipping the "install the CLI on your PATH" step: that's by design for scripted use (it assumes the CLI is already present), and the `bunx @colbymchenry/codegraph serve --mcp` MCP entry they found is the self-contained alternative. Not changed here.
## Tests
`__tests__/cli-install-init.test.ts` — end-to-end against the built binary with stdin closed (a blocking prompt would fail), always `--target none` so the suite never touches an agent config on the host:
- `install --yes --target none --init` → exit 0, installer reports nothing to wire, `Initialized in <tmp>`, `.codegraph/codegraph.db` exists;
- the same on an already-initialized project → `Already initialized`, exit 0;
- the same at the filesystem root → exit 1, `Refusing to initialize`, nothing written;
- `init --yes` with stdin closed → exit 0, index built;
- `init --help` lists `-y, --yes`, `install --help` lists `-i, --init`.
`npx vitest run __tests__/installer-targets.test.ts __tests__/upgrade.test.ts` → 283 passed, 3 skipped. Full `npm test` → see the checks on this PR / below.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01LxZj6W6Y1SHXwvpT3uwJpK
* feat(config): add codegraph.json "deprioritize" for ranking-only path down-weighting
matchesNonProductionDir hardcodes example/sample/fixture/benchmark/demo,
so a peripheral tree only the project knows about — optional-skills/,
scripts/ — gets no de-prioritization. When helpers there carry generic
symbol names, an exact name match hands them a large bonus and they crowd
out the product code that answers the query (#982).
deprioritize is the RANKING counterpart to exclude: those paths stay
indexed and findable, they just stop outranking first-party code. It is
deliberately distinct from the corpus-frequency discount, which keys on a
name being common and is near-inert on #982's own repro where only two
symbols are named usage.
The -15 path penalty alone is not enough, and measuring showed why: on
that repro a usage() helper sits at 74.8 against 51.2 for the top product
symbol, so -15 lands at 59.8 and still leads. The path penalty is additive
and the name bonus it must counter is additive and larger. A de-prioritized
path is saying its symbol NAMES are not the answer, so the exact-name bonus
is damped to 0.25x there as well — damped, not zeroed, so the tree still
ranks when it genuinely is what you asked for.
Refs #982
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKXAJMrVrdHS5Uco6ABtky
* fix(config): read deprioritize lazily and apply it in explore too
Review of the first cut found two real defects.
The matcher was built once in wireLayers(), which runs only from the
constructor and from reopenIfReplaced(). The MCP server keeps one
CodeGraph per project root alive for its whole lifetime, so editing
codegraph.json appeared to do nothing until the process restarted --
exclude and include do not behave that way. The predicate now reads
loadDeprioritizePatterns() per call (mtime-cached, one stat) and memoizes
the compiled matcher on the pattern array's identity. A regression test
writes the config after opening the project and fails on the old code.
Explore passed no matcher to scorePathRelevance at either of its two call
sites, so the setting only half-applied -- and #982's reproduction rows
B, C and D are all codegraph explore, which made this the surface the
issue actually reports on. Both sites now pass it.
Explore's hard early-continue filters and its non-production budget cap
are deliberately NOT joined: those REMOVE content, and deprioritize is a
ranking lever by definition. README narrowed accordingly -- it previously
claimed this extends the built-in list, which overstated it.
Also from review: scorePathRelevance takes a boolean rather than a
predicate (the caller already evaluated it, and it was being invoked
twice per result), the predicate body is exception-guarded so a bad path
can never take a search down, the misplaced const moved out from between
imports, two vacuous test assertions tightened, and tests added for the
single-penalty invariant, the deliberate isTestQuery asymmetry, and a
query that genuinely targets the de-prioritized tree.
Refs #982
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKXAJMrVrdHS5Uco6ABtky
* fix(search): derive the deprioritize name-bonus damping instead of picking it (#982)
The 0.25 scale was a guess. On a 62k-node django index it measurably breaks
the "discount, don't erase" rule the lever is built on: exact-name queries for
symbols that live only in the de-prioritized tree (child, parent, method) fall
behind mere prefix matches (children, all_parents, method_decorator).
The prefix arm of nameMatchBonus tops out below 40, and a de-prioritized node
also takes the -15 path penalty, so 80 * SCALE - 15 > 40 is the bound that
keeps a damped exact match ahead of a prefix match at any corpus shape. 0.75
clears it; crowd-out removal is nearly identical to 0.5 (39 vs 40 of 88
peripheral top-10 slots cleared on django), so the deeper discount bought
almost nothing and cost the invariant.
Two tests pin the bound, including one that fails at the old 0.25.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Re-measured on the CLI-blocked harness at the README's own stated methodology
(claude-opus-4-8, single question, median of 4, same 7 repos), with tokens
summed per turn per 04c0f8e.
What moved, and what did not:
tool calls 89% -> 88% fewer holds
file reads 0 on all seven holds
tokens 69% -> 62% fewer holds
time 20% -> 53% faster was badly understated
cost 60% -> 44% cheaper genuinely lower
Time was the most wrong figure in the old table, in our favour: every repo is
faster with the graph, including the two that previously carried a footnote
excusing them for being slower. That footnote is gone. Cost is the one real
downgrade, and it varies with how much discovery a question demands rather
than with repo size -- 57-78% where the file-reading arm needed 28-43 tool
calls, near-even on Gin at 7. Said plainly instead of averaged away.
Methodology now records that the CLI is blocked in BOTH arms and why: on an
unblocked harness the control reached codegraph through Bash in 26 of 28 runs,
so it was not a control. All 28 attempted it here; all 28 were blocked.
The context-footprint note from a7db24d is unaffected -- occupancy is measured
off the timeline, not the token field that was wrong.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The benchmark table measures throughput -- tokens processed, tools called,
cost to reach one answer. It has never measured what is still resident in the
window afterward, and on that axis codegraph costs more: ~80% more retrieval
context left behind than a file-reading agent, on all seven repos.
That is the axis issue #1500 reported, and it is structural rather than a
defect -- one dense payload that answers the question and stays, versus
grep-and-read churn that evicts. Worth stating plainly next to the cost note
rather than leaving a user to discover it in a long session.
The Opus 4.8 single-question figures in the table are deliberately untouched:
the occupancy campaign ran sonnet / 3-turn, a different regime, and nothing
measured there licenses restating them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
- Hero: larger theme-aware standalone Rust logo (new assets/rust-logo{,-dark}.svg
— gear only, no tile card; <picture> swaps by GitHub theme), tagline text
trimmed to 'Kernel powered by Rust'
- 'Built for speed' section: removed the floated language-tile logo (its baked-in
paper card rendered as an odd box on dark theme and pushed the text)
- Removed the '1.0 Released!' banner and the Star History section (+ its
Contents entry)
- package.json → 1.5.0 for the Rust-engine release
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
README Benchmark Results re-run 2026-07-21 on the current build (Rust
kernel + this cycle's resolution overhaul), Claude Opus 4.8, 7 repos,
median of 4 runs/arm: 89% fewer tool calls, 60% cheaper, 69% fewer
tokens, 20% faster on average, file reads 0-vs-1..24 on ALL seven repos.
Per-repo floor effects reported honestly (excalidraw/alamofire wall,
okhttp cost wash). Cost note updated to match the measured data.
Changelog [Unreleased] headline now co-leads with near-instant sync.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Two changes to the watcher path (the always-on daemon every agent
session uses), which previously paid a flat 2s debounce plus a full-tree
scan-diff on every save even though the OS events name the exact files:
1. Adaptive debounce: a pending set of ≤2 files fires after a 300ms
quiet window; ≥3 keeps the full configured window so agent
multi-file bursts coalesce exactly as before. Re-arming preserves
trailing-edge semantics; a user-set CODEGRAPH_WATCH_DEBOUNCE_MS
remains the authoritative upper bound (quick window never exceeds
it, floor 100ms).
2. Scoped sync: watcher-triggered syncs pass their pending paths, and
the reconciler stats exactly those — per-path logic identical to the
full walk (stat pre-filter, hash confirm, the #1240
removal/resurrection flow) — skipping the O(repo) scan and
tracked-load. Strict fallbacks keep the full scan-diff as ground
truth: directory removals (#1285 — the events can't name the
children), empty pending sets (retry paths), and >500-file storms
(branch checkouts, which also self-heal anything event coalescing
dropped). filesChecked counts examined PATHS so a deletion-only
scoped sync can't mimic the #449 lock-unavailable signature.
Measured (warm in-process, the daemon path): dubbo one-file sync work
512→335ms, Swift compiler (27k files) 884→385ms — save-to-fresh-graph
≈0.6-0.7s end-to-end including the quick debounce, from ~2.5-6s
perceived before. Gates: scoped-vs-full dumps byte-identical on dubbo
AND the Swift compiler; watcher suite 30/30 (3 new: scoped pass-through,
dir-removal fallback, quick-fire timing); sync suite 34/34 (4 new
scoped-parity cases incl. delete-resurrection and the lock signature);
full suite 2,696 ×2 with CODEGRAPH_KERNEL_EXPECT=1.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
All seven published packages (@colbymchenry/codegraph + six platform
bundles) now have this repo's release.yml configured as their trusted
publisher on npmjs.com, so publishes authenticate via the workflow's
OIDC identity instead of a long-lived NPM_TOKEN. The runner upgrades to
npm 11 (trusted publishing needs >= 11.5; Node 22 bundles npm 10) and
setup-node no longer writes a token-referencing .npmrc.
README gains a 'Verified releases' section + badges: how npm provenance
and the GitHub Release attestations work and the commands to verify them
(npm audit signatures / gh attestation verify).
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* 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>
Adds an `include` list to the root `codegraph.json` that forces gitignored first-party source (second-VCS / SVN / Perforce dual-tracked repos) into the index — discovered directly off disk on the full index, incremental sync, and file-watching, on both git and non-git projects. Gitignore-style patterns, root-relative; explicit `exclude` still wins and built-in skips (node_modules, dist, .git) are never re-included. Complements `exclude` and `includeIgnored`.
Closes#1163.
Thanks @luoyxy for the contribution.
Adds ArkTS (.ets, HarmonyOS/OpenHarmony) as a first-class language:
full TypeScript-grade extraction via the harmony-contrib tree-sitter
grammar (MIT, vendored byte-identical from the tree-sitter-arkts 0.2.0
npm tarball), plus the ArkUI constructs that make HarmonyOS apps
traceable:
- @Component/@ComponentV2 structs with decorators from both grammar
positions; members extract as class members with qualified names.
- build() component trees: child instantiation edges via
arkui_component_expression, no synthesizer needed.
- Attribute chains emitted dot-prefixed and resolved ONLY against
@Extend/@Styles/@AnimatableExtend/@Builder helpers (unique-or-drop) —
bare-name fallthrough produced 36,840 wrong edges (17% of calls) on
the OpenHarmony samples monorepo. All four grammar chain shapes
handled, including the detached-chain forms.
- .onClick(this.handler) method-reference bindings.
- ohpm workspace modules: bare imports follow oh-package.json5 file:
deps (ambiguous names dropped), honoring each module's main entry —
which also lets .ts consumers resolve .ets modules.
- ArkUI dynamic-dispatch bridges, all provenance:'heuristic' with
wiring-site metadata: assignment-gated state->build() re-render
(V1 @State family + V2 @Local/@Provider/@Consumer),
@ohos.events.emitter emit->subscriber pairing on static event keys
(numeric ids same-file, named constants same-module, fan-out capped),
and router.pushUrl literal urls -> the target page's @Entry struct.
- $r/$rawfile resource intrinsics treated as built-ins; arkts joins the
web language family, value-reference edges, re-export chase, and the
other TS-applicable gates.
Also ships a language-agnostic index-completeness guard: indexAll
stamps index_state (indexing -> complete/partial/failed), reconciles
discovered vs accounted files (a loaded run silently dropped 37 files),
and codegraph status surfaces truncated/partial indexes in human and
--json output.
Validated on HarmoneyOpenEye (82 files), CoolMallArkTS (528, modular
ohpm + ArkUI V2), and openharmony/applications_app_samples (11,693
files, 202,890 nodes stable across re-index, attribute false-positive
audit 36,840 -> 588 residual all-plausible). Supersedes PRs #656 and
#988 with credit — both informed this implementation.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Follow-ups noted in #1173:
- cloudposse/atmos remote-state: module.M.outputs.X emits a scoped
module.M:remote-output.X candidate; the resolver bridges it to the
target COMPONENT's own output when every gate holds — the module
source is the stack-config remote-state module, the component name is
static (a literal, or component = var.X whose variable declares a
literal default in the same directory), and exactly one directory in
the repo matches the component name and declares that output. Dynamic
(each.value) or ambiguous wiring stays unlinked. On
cloudposse/terraform-aws-components: 254 remote-state bridge edges,
every one re-derived from a matching source declaration (789/789
cross-directory output edges explained: 528 local-module + 254
remote-state + 7 checker-artifact false alarms under deprecated/);
coverage 66.4% -> 69.1%.
- provider aliases: provider "aws" { alias = "east" } is addressed as
provider.aws.east so aliased and default configurations stop
colliding; provider = aws.east on a resource/data block (and the
values of a module's providers map) reference the selected
configuration, resolved same-directory first then up the module tree
— the one construct Terraform genuinely inherits from parents. The
selection is no longer misread as a resource reference (aws.east).
- moved/import/removed blocks reference the resource addresses they
name (anchored to the file node — no phantom symbols), so a
refactor's paper trail joins the graph; check-assert conditions
contribute their references while check-scoped data blocks keep
indexing as before. Scoped module candidates are suppressed there:
module.a.aws_x.b names a resource inside a module instance, not an
output. +91 edges on cloud-foundation-fabric's moved-heavy stages.
Also fixes a latent test bug from #1173: cg.getNodeById is not public
API (cg.getNode is) — it only passed because the asserted edge list was
empty.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(extraction): add Terraform and OpenTofu language support
Index .tf, .tfvars, and .tofu files via the tree-sitter-terraform dialect
of HCL (vendored from @tree-sitter-grammars/tree-sitter-hcl, Apache-2.0).
Symbols extracted:
- resource / data → class (qualified "type.name" / "data.type.name")
- module → module (qualified "module.name")
- variable → variable (qualified "var.name")
- output → variable (qualified "output.name")
- provider → namespace
- locals → constant per attribute (qualified "local.key")
References resolved cross-file:
- var.X, local.X, module.M[.out], data.T.N[.attr], <type>.<name>[.attr]
- built-ins skipped: each.*, count.*, self.*, path.*, terraform.workspace
The Terraform framework resolver disambiguates same-named candidates
across modules by preferring the one in the same directory as the
reference site, then by closest common-ancestor path, falling back to
the generic name matcher only when neither applies.
Validated on two Terraform monorepos (277 and 470 .tf files): indexing
runs in 1.3s and 2.4s respectively, query latency stays under 200ms,
and cross-module references resolve to the correct module 100% of the
time on inspected samples.
18 new extraction tests; full suite 1146/1148 green (2 pre-existing
flaky skips, 0 regressions).
* feat(terraform): bridge the module boundary and enforce directory scoping
Builds on #706. The module declaration was a dead end: module.M.out
resolved to the declaration and stopped, module inputs never reached the
child module's variables, and impact could not cross the boundary — on
real multi-module repos that breaks the core blast-radius question
("what breaks upstream if I change this module's variable/output").
- module blocks now wire across the boundary through :-scoped refs only
the Terraform resolver understands: module.M:var.<input> → the child's
variable node, module.M:output.<o> → the child's output node (emitted
alongside the module.M declaration ref), and module.M:file → the local
source directory's entry file (imports). Registry/git sources emit no
file ref and resolve nothing — an out-of-repo module stays a visible
boundary instead of a guess.
- .tfvars top-level assignments reference the variable they set, walking
up to the nearest ancestor directory (envs/prod.tfvars → root vars).
- Resolution now enforces Terraform's real scoping: same-directory only
(no cross-module fallback by common path prefix, no single-candidate
anywhere-in-tree binding), and terraform refs never fall through to
the generic name matcher — var.X can never legally bind outside its
module directory, so the fallback could only add wrong edges.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(terraform): README language table + changelog entry + agent-eval corpus
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Javier Rodríguez Fernández <jfernandez@freepik.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
CUDA rides the C++ grammar via the Metal (#1121) dialect pattern:
blankCudaConstructs (offset-preserving) blanks execution-space specifiers
(__global__ family), __launch_bounds__(...), and <<<grid, block>>> launch
configs — which otherwise lex as shift operators and destroy the
host→kernel call edge entirely. Gated by .cu/.cuh extension OR by content
(looksLikeCudaSource), because much real CUDA lives in .h/.hpp headers:
cutlass launches most kernels from headers and flash-attention's launch
templates are .h. Safe by construction — no CUDA marker is valid C++
anywhere, and the launch blank is bounded + brace-balance-checked so a
stray <<< (committed merge-conflict markers) can never blank real code.
All real-world launch styles connect: plain, templated
(k<T, 256><<<...>>>), function-pointer (auto kernel = &fn<...>; with
branch reassignments each linked), dim3{...} brace-init configs, and
kernels defined through name-in-first-argument macros
(DEFINE_FLASH_FORWARD_KERNEL style — gtest TEST_F / PYBIND11_MODULE
shapes deliberately excluded by the two-lone-identifiers rule).
Two general C++ resolution wins the flow validation forced out:
- namespace blocks now prefix contained symbols' qualifiedNames
(prefix-only — no namespace nodes, avoiding #1093-style crowd-out), so
ns::fn(...) calls resolve; previously every namespace-qualified C++
call was a permanently dead edge. cutlass: +30,864 edges (~10%), node
count byte-identical.
- templated callees (fn<T, 256>(args)) strip template args at extraction
(mirroring #1043 for base classes), so they match their definitions.
Validated on llm.c (165 host→kernel launch edges, was 0),
flash-attention (run_flash_fwd → flash_fwd_kernel → compute_attn traces
in one codegraph_explore call), and NVIDIA CUTLASS; fmt as the plain-C++
control (unchanged). A/B n=2/arm: Read/Grep displacement decisive on all
three repos (flash-attention Reads 29,13 → 5,2).
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
escripts (.escript) index like any module — the ELP grammar has a
first-class shebang node, so no source transform is needed; main/1 and its
helpers get full function/call extraction.
OTP application resource files (<app>.app.src and compiled <app>.app) join
the graph as Erlang terms the grammar parses natively. They route by full
suffix (their last-dot extension, .src, is far too generic for the
extension map). The application tuple yields structure: {mod, {Mod, _}}
links the app to its callback module — the app's entry point — and
{applications, [...]} / {included_applications, [...]} connect umbrella
sibling apps, resolving through the OTP app-name == module-name convention;
kernel/stdlib and other out-of-repo apps stay unresolved.
App-file refs resolve only ever to MODULES: validation on emqx caught the
ssl OTP-app dependency resolving to a test helper FUNCTION named ssl (the
same defect class as the earlier -behaviour gate), so the matchReference
module-only gate now covers every ref an .app/.app.src file emits.
Validated on emqx: 2 app.src + 6 escripts indexed, entry-module and
umbrella-dependency edges all namespace-targeted post-gate, escript
functions extracted; a stray legacy/module.src stays unknown.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Vendored WhatsApp/tree-sitter-erlang 0.19 (the ELP grammar, ABI 14) with an
Erlang-shaped extractor: multi-clause/multi-arity functions merged into one
symbol, -spec signatures, records with fields, -type/-opaque aliases, -define
macros, -include/-include_lib file edges, and -export-driven visibility.
Modules wrap in a namespace so remote mod:fn(...) calls resolve through the
existing qualified-name matcher as mod::fn with zero resolver changes.
-behaviour declarations link to the behaviour module — gated to namespace
targets only (bare-name fallthrough linked -behaviour(supervisor) to an
unrelated macro constant on emqx). OTP indirection with static targets is
followed: spawn/apply/proc_lib/timer/rpc MFA-argument callees, and
gen_server:call/cast(?MODULE | ?SERVER) to the module's own
handle_call/handle_cast. Var-module dispatch and message sends stay
deliberately unlinked. codegraph_explore also normalizes Erlang-native query
spelling (mod:fn/3, init/2) so named symbols resolve as typed.
Benchmarked on cowboy (189 files), ejabberd (414), emqx (2,447): extraction
PASS on all three; with-codegraph arms reached 2/2/0 file Reads vs 10/5+/19
without, fastest on the largest repo.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Vendored patched govindbanura/tree-sitter-vbnet grammar (MIT, ~20-fix patch
+ new external scanner for XML literals and multi-line LINQ continuation;
provenance + rebuild instructions in docs/grammars/tree-sitter-vbnet.md),
vbnet extractor with VB-specific call/index disambiguation, Inherits/
Implements heritage, As New instantiation, events, Declare P/Invoke, and
MustOverride abstract members.
Parse health on five real repos: PolicyPlus 100%, CompactGUI 100%,
staxrip 95.2%, SCrawler 87.2%, PCL 87.5% (upstream grammar: 3-18%).
Retrieval A/B (sonnet): 26-43% faster with 0-5 file reads vs 7-20 without.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Programs, sections/paragraphs (reconstructed extents over the grammar's
flat header stream), PERFORM/THRU/GO TO/CALL call edges, COPY copybook
imports incl. standalone .cpy fragments, DATA DIVISION records/fields/
88-levels with write-site impact references, and CICS flows: EXEC
LINK/XCTL program targets (literal + same-file VALUE deref), EXEC SQL
INCLUDE, and pseudo-conversational RETURN/START TRANSID hops resolved
to the owning program via a CICS framework resolver. Fixed and free
source format (free format via a scanner wide-mode sentinel).
Grammar: vendored wasm built from a patched yutaro-sakamoto/
tree-sitter-cobol (EXEC blocks as an external-scanner token, copybook
fragment entry point, single-quote continuation, COPY REPLACING
pseudo-text, NOT=, CALL GIVING, ENTRY, FREE, bitwise ops, abbreviated
relations, COBOL-2002 usages, and more). Patch + provenance + upstream
PR draft in docs/grammars/. Parse health: AWS CardDemo 43/44 native
(upstream: 9/31), 44/44 through preParse; copybooks 28/29; CobolCraft
free-format 17/17 (upstream: 0); NIST COBOL85 unchanged at 373/382.
Copybook members resolve to files like C includes (basename index,
name-matcher short-circuit so compiler-supplied members stay honestly
unresolved): CardDemo imports 5 -> 285. Impact proof: ACCT-CURR-BAL
(CVACT01Y copybook) surfaces its 4 writer programs cross-file.
Also: run-all.sh now neutralizes the ambient prompt-hook in both A/B
arms (CODEGRAPH_NO_PROMPT_HOOK=1); COBOL corpus entries for agent-eval.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
.metal was absent from EXTENSION_MAP, so Metal Shading Language files were
silently skipped. MSL ≈ C++14, and the C++ grammar extracts its functions,
structs, type aliases, and call edges at parity with plain C++ — except MSL's
post-declarator [[attribute]] annotations, which misparse struct fields into
spurious extends refs from the struct to the field's own type (a wrong
inheritance edge whenever the repo typedefs float3/float4x4 itself, common in
shared ShaderTypes.h). blankMetalAttributes blanks them pre-parse,
offset-preserving, following the blankCppExportMacros pattern (#1061), gated
to .metal files only — in regular C++ the attribute position is legal syntax
the grammar parses natively. The preParse hook gains an optional filePath
param to support the gate.
Validated on llama.cpp's ggml-metal.metal (10.7k lines: 130 kernels vs 113
`kernel void` ground-truth lines, rope_yarn resolves its 4 kernel callers)
and SDL's shaders (PQtoLinear ← GetOutputColor), 0 bogus extends edges.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
#1009 added `exclude` (keep git-tracked dirs out of the index) but didn't
document it. Add an "Excluding a tracked directory" section to the site config
page (parallel to includeIgnored) and a brief note + example to the README,
covering the committed-theme/SDK case .gitignore can't handle.
The client-facing MCP proxy could exit with "Transport closed" when its
connection to the shared daemon hit a socket 'error' with no listener
attached — common on WSL2 /mnt (DrvFs), where AF_UNIX is flaky. The global
fatal handler turned that uncaughtException into process.exit(1), which the
MCP client saw as a bare transport close even though the index was healthy.
proxy.ts now keeps an 'error' listener on the daemon socket for its whole
life (and skips a socket destroyed in the connect window), so a stray error
degrades to the existing in-process fallback instead of crashing. daemon.ts
releases the lockfile it acquired when it fails to bind, so the next launch
doesn't spin on a stale lock (the duplicate serve --mcp pileup).
No default behavior change for anyone; WSL /mnt users who still hit trouble
can set CODEGRAPH_NO_DAEMON=1 to skip the shared daemon entirely. Validated
on macOS (unit + live serve probe) and Linux (Docker, --init): 64/64 across
the daemon/socket/lifecycle suites, incl. real AF_UNIX.
Closes#974
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The MCP server gated tool availability on whether the server root had a
.codegraph/ index, so in a monorepo where only sub-projects are indexed the
agent saw zero tools — and couldn't reach an indexed sub-project even by
projectPath. A session started before `codegraph init` also never surfaced the
tools afterward. The Claude front-load hook had the mirror gap: it only walked
UP for an index, so it stayed silent at a monorepo root.
MCP server:
- Always expose the tool surface; when the root isn't indexed, send a
per-project instructions variant (pass projectPath) instead of the
"inactive" note. Safety comes from response SHAPE (success-shaped guidance,
never isError), not from hiding tools.
- Reword the no-default-project guidance to be per-project, not per-session,
and sharpen the projectPath schema description.
Front-load hook (UserPromptSubmit):
- Scan DOWN (bounded depth, workspace-root-gated) for indexed sub-projects and
shape the injection by topology: front-load the one the prompt names, nudge
about the rest, or list them when ambiguous.
Verified: full suite (1703 passed); a live two-package monorepo run confirms the
hook front-loads the correct sub-project with no cross-package leakage. The
front-load's net speed effect is the existing multi-file-vs-single-file
tradeoff, unchanged by this work.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The extension → language table was hardcoded, so a codebase using a
non-standard extension for a supported language (e.g. `.dota_lua` for Lua)
had those files silently skipped — no way to opt them in short of patching
the source.
Add an opt-in, project-scoped `codegraph.json` at the repo root:
{ "extensions": { ".dota_lua": "lua", ".tpl": "php" } }
Mappings merge on top of the built-in defaults and take precedence (so a
built-in can be re-pointed, e.g. `.h` → `cpp`). Absent or malformed config
is the zero-config default — byte-identical to prior behavior; an invalid
target language or unparseable file is warned-and-skipped, never fatal.
Implementation:
- New `src/project-config.ts` — `loadExtensionOverrides(rootDir)`, validated
against `isLanguageSupported`, mtime-cached per root.
- `detectLanguage` / `isSourceFile` gain an optional `overrides` arg
(omitting it is the existing behavior).
- Overrides threaded per-operation through every extraction call site
(scan/walk gates, git change-detection, grammar selection, extraction,
the file watcher), resolved from the project root — no process-global
state, so the multi-project daemon stays isolated. The parse worker
receives the resolved language in its message.
Tests: 13 new cases (unit, loader validation/normalization/caching, and a
full-index integration proving a custom-extension file is extracted while
the zero-config path indexes nothing). Worker path smoke-tested via the
built CLI.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Benchmark table reordered to lead with tool calls, time, and file reads (the universal wins); cost and tokens moved right with a note that savings are scale-dependent, not a headline claim
- README/introduction/quickstart/installation messaging updated to "surgical context · fewer tool calls · faster answers" framing, dropping the "16% cheaper" headline
- Node engine floor raised from 18 to 20 in CLAUDE.md, package.json description updated
- `codegraph init` now creates and indexes in one step; the `-i` flag is retired (still accepted as a no-op)
- CLI reference expanded with new commands: `explore`, `node`, `unlock`, `daemon`, `telemetry`, `upgrade`, `version`, `help`
- MCP server docs clarified: single `codegraph_explore` tool exposed by default, others unlisted but re-enableable via `CODEGRAPH_MCP_TOOLS`
- Language support adds Objective-C, Astro, and R; framework routes adds Play, Vue Router/Nuxt, and Astro
- API reference documents lower-level exports and embedding requirements (Node 22.5+ for `node:sqlite`)
- Troubleshooting adds WSL/Windows dual-checkout guidance
- How-it-works updated: SQLite backend is now Node's built-in `node:sqlite` in WAL mode, not better-sqlite3/WASM
`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>
Strips the bring-your-own-model reasoning offload and managed CodeGraph AI
integration (login/logout/usage commands, offload config/credentials/reasoner
modules, and the synthesizeOffload call in codegraph_explore). The eval findings
showed raw source output outperformed the synthesized path on accuracy, so
codegraph_explore reverts to returning verbatim retrieved source exclusively.
CHANGELOG and README sections for reasoning offload are removed; test comments
and DEFAULT_MCP_TOOLS description are updated to drop offload references.
codegraph_explore can now hand the source it retrieved to a reasoning model you
point at — any OpenAI-compatible endpoint (Cerebras, OpenAI, a local vLLM/Ollama)
with your own key — and return that model's tight, cited answer instead of the
raw source dump. The agent's main context gets the answer in far fewer tokens, at
the cost of one network round-trip.
Off by default. Configure with `codegraph offload set-endpoint <url> --model <m>
--key-env <ENV>` (or the CODEGRAPH_OFFLOAD_* env vars); status/disable manage it.
The API key is never written to disk — the config stores the NAME of an env var
and the key is read from it at call time. Strictly degradable: any failure
(no endpoint, network, timeout, empty answer) returns null and the call falls
back to the local source, so the offload can never surface an error to the agent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`codegraph serve --mcp` is the stdio MCP server an AI agent launches for itself
(the installer wires it into every agent's MCP config), not a command a human
runs. Run by hand in a terminal it just hung waiting for JSON-RPC, looking
broken.
- Hide `serve` from `--help` (commander `{ hidden: true }`); it stays fully
invocable, so agents are unaffected.
- When stdin is an interactive TTY (a person — never the agent's pipe or the
detached daemon), print what it is and point to `codegraph status` /
`codegraph daemon`, then exit instead of hanging.
- README: drop `serve --mcp` from the CLI Reference and stop the troubleshooting
section from telling users to run it; keep the accurate "your agent launches
it" note.
Verified: agent path intact (22 MCP handshake/daemon tests pass), `serve` absent
from --help, and the TTY path prints the message and exits cleanly.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(cli): codegraph version command + complete CLI Reference
Add a `codegraph version` subcommand plus the `-v` and `-version`
spellings (commander already wires up `--version`/`-V`), so the version
is easy to reach however a user guesses at it. The `-v`/`-version` forms
are intercepted before commander parses — its version short flag is the
capital `-V`, and its parser rejects a multi-character single-dash flag.
A trailing `-v` on a subcommand still means `--verbose`.
Document the previously-missing commands in the README CLI Reference:
`daemon`/`daemons`, `unlock`, `telemetry`, `version`, and `help`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(changelog): reference #864 on the version-command entry
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>