mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-09-19 07:34:57 +08:00
codegraph-ai
539 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ac52fd76c0 |
Self-contained distribution: bundle Node + node:sqlite, drop better-sqlite3/wasm (closes #238) (#282)
* fix(db): eliminate concurrent-read "database is locked"; add node:sqlite backend (#238) WAL + busy_timeout were already enabled, so the issue's suggested fix was a no-op. The real causes, addressed here: - busy_timeout is now set first (before journal_mode) and lowered 120s -> 5s, so open-time pragmas wait out a lock instead of hanging for two minutes. - getCodeGraph no longer opens a second connection to the default project when a tool passes its own projectPath (the in-process lock amplifier). - The wasm fallback (no WAL) gets a bounded read-retry on SQLITE_BUSY. - New: node:sqlite backend, preferred over wasm, so installs whose native better-sqlite3 build fails land on a real-WAL backend instead of no-WAL wasm. - codegraph status / codegraph_status now report the effective journal mode, so a lock report is triageable (wal vs delete). - CLI hard-blocks Node < 20 to actually enforce the engines floor. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(db)!: node:sqlite is the sole backend; drop better-sqlite3 + wasm Now that distribution will bundle a Node 24 runtime, node:sqlite (real SQLite with WAL + FTS5) is always available. Collapse the three-backend adapter to node:sqlite only and remove the machinery the other two needed: - Remove better-sqlite3 (optionalDependency) and node-sqlite3-wasm (dependency). - Remove WasmDatabaseAdapter, the named->positional param translation, the SQLITE_BUSY read-retry, the wasm fallback banner, the backend env override, and the native/node-sqlite/wasm selection chain. - createDatabase now opens node:sqlite directly, with a clear error pointing at the bundled release / Node 22.5+ when the module is absent. - NodeSqliteAdapter.close() is idempotent and pragma() supports { simple }, to match the better-sqlite3 behavior callers relied on. - status (CLI + MCP) reports the single node:sqlite backend; journal-mode diagnostics and the getCodeGraph single-connection fix are retained. - Tests repointed off better-sqlite3 onto node:sqlite. Net -1044 lines. Running from source now requires Node 22.5+. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(dist): self-contained bundle prototype (vendored Node + install channels) Phase 3 of the node:sqlite migration: ship a vendored Node runtime so CodeGraph runs with no system Node and no native build (node:sqlite is built in). - scripts/build-bundle.sh: build a per-platform archive (official Node + dist + prod deps + launcher). Same recipe per platform; pins Node v24.16.0. - install.sh: curl|sh installer (no Node required) — detects os/arch, pulls the archive from Releases, symlinks onto PATH; re-run to upgrade, --uninstall to remove. The VPS/SSH path. - scripts/npm-shim.js: thin launcher for the npm channel — resolves the per-platform optionalDependency bundle and execs it, so `npm i -g` keeps working and the real work runs on the bundled Node regardless of the user's. - BUNDLING.md: distribution design + release-pipeline TODO (CI matrix, platform packages, code signing, brew, retiring the Node-version gate). Validated end-to-end: darwin-arm64 and linux-x64 bundles both run init + index + status (Backend: node:sqlite, Journal: wal) + FTS query with NO system Node — linux-x64 verified in a clean ubuntu:24.04 amd64 container. Release archives are gitignored; CI will produce and upload them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(dist): add Windows PowerShell installer (install.ps1) The `irm … | iex` one-liner for Windows, mirroring install.sh: detect arch, pull the matching bundle from Releases, extract to %LOCALAPPDATA%\codegraph, add it to user PATH. Re-run to upgrade. (Windows bundle production in build-bundle.sh is still TODO.) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(dist): release workflow + npm packaging; README/CHANGELOG for bundled distro - .github/workflows/release.yml: manually-triggered (workflow_dispatch) release matrix. Builds a self-contained bundle per platform on its own runner (darwin-arm64/x64, linux-x64/arm64), publishes a GitHub Release with all archives, and publishes the npm thin-installer (shim + per-platform packages). Windows targets are TODO (build-bundle.sh is unix-only). - scripts/pack-npm.sh: assemble the npm packages from built bundles — per-platform packages tagged os/cpu + the main shim package with them as optionalDependencies (esbuild pattern). Proven locally: npm-install the tarballs, run via the shim, resolves the bundle and runs on the bundled Node 24 (node:sqlite / WAL). - README: install section now leads with the no-Node one-liners (curl|sh, irm|iex) then npm/npx; "bundled · none required" badge. - CHANGELOG: standout headline for the self-contained release, plus Added/Changed/ Removed for the install channels, node:sqlite backend, and dropped deps. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(dist): Windows bundles + single-trigger release workflow - build-bundle.sh: add win32-x64 / win32-arm64 targets — download Node's Windows zip, bundle node.exe + a .cmd launcher, output a .zip. Verified structurally (PE32+ node.exe, CRLF .cmd, portable node_modules). Since there are no native addons, any target builds on any OS, so the whole matrix builds on one runner. - pack-npm.sh: handle .zip bundles and win32 packages (os: win32, node.exe). - release.yml: simplified to your spec — manual trigger reads the version from package.json, builds all platform bundles, creates the GitHub Release with notes pulled from CHANGELOG.md, and publishes the npm shim + platform packages. - BUNDLING.md: Windows + build-anywhere notes; release pipeline documented. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
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>
|
||
|
|
4329a52bec |
feat: add Lua and Luau language support (#273)
Adds Lua (.lua) and Luau (.luau) extraction — functions, methods with receivers, type aliases (Luau), require imports (incl. Roblox instance-path), and call edges. Vendors the ABI-15 Lua and ABI-14 Luau tree-sitter grammars. Addresses #232. |
||
|
|
2fc0df7108 |
chore: remove leftover debug scripts and stale docs
Delete dead dev scratch (debug_python_ast*.js, test_python_inheritance.js), the obsolete tree-sitter-dart native patch (Dart loads via WASM now and the package is no longer a dependency), and orphaned docs superseded by the current code and the agent-eval skill (IMPLEMENTATION_PLAN.md, DELPHI-SUPPORT.md, run-interactive-test.md). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
c3f1e273d4 | release: 0.8.0 v0.8.0 | ||
|
|
d2664e87b0 | Readme updated and detect test files in kotlin and swift | ||
|
|
948b287536 |
feat(frameworks): add NestJS support (#220) (#225)
Detect NestJS projects and emit `route` nodes (each linked by a `references` edge to its handler method) across all four transport layers: - HTTP controllers: @Controller prefix joined with @Get/@Post/@Put/@Patch/@Delete/@Head/@Options/@All - GraphQL resolvers: @Query/@Mutation/@Subscription - Microservices: @MessagePattern/@EventPattern - WebSocket gateways: @SubscribeMessage (prefixed with gateway namespace) Detected from any @nestjs/* dependency in package.json (falls back to scanning *.controller.ts/*.resolver.ts/*.gateway.ts). Handles class+method path joining with empty @Controller()/@Get(), a string-aware balanced-paren arg reader so GraphQL type thunks (@Query(() => [User])) aren't truncated, stacked decorators (@UseGuards) when locating the handler, and disambiguates the @Query() GraphQL method decorator from the REST @Query() param decorator (GraphQL only counts inside @Resolver classes). Also resolves injected *Service/*Controller refs to their classes by Nest file-naming convention. Adds 18 framework tests; updates the README framework table and CHANGELOG. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
5c6e5d5c67 | Update README | ||
|
|
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> |
||
|
|
a47355780b |
fix(sync): stop reporting git-untracked files as pending after sync (#206) (#218)
Both git fast-paths in ExtractionOrchestrator (sync and getChangedFiles) classified every untracked (`??`) file as "added" without checking the index. Indexing a file doesn't make git track it, so the file stayed `??` and was re-reported as pending and re-indexed on every run: `codegraph status` listed it under Pending Changes forever and each `sync` re-added it, even though its symbols were already queryable. Merge the modified + added handling into a single hash-compared loop so untracked files get the same treatment as tracked ones: "added" only if missing from the index, "modified" if contents changed, skipped otherwise. The non-git fallback path already did this and is unchanged. Closes #206. Reported by @15290391025. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
07c093cc3f |
fix(extraction): index nested non-submodule git repos (#193) (#217)
`codegraph init -i` from a git super-repo containing independent nested git repositories (not submodules) reported "No files found to index": git ls-files reports an embedded repo only as an opaque `subdir/` entry and never lists its files. Detect embedded repos via that trailing-slash signal and recurse `git ls-files` into each, indexing tracked + untracked source and honoring each repo's own .gitignore. Reported by @timxx. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
9b06b0edde |
fix(db): require better-sqlite3 ^12.4.1 so Node 24 gets the native backend (#203) (#216)
better-sqlite3 ^11.0.0 (latest 11.10.0) ships no prebuilt binary for Node 24's ABI (node-v137) and predates Node 24, so every Node 24 install silently fell back to the 5-10x-slower WASM backend. Bump to ^12.4.1 — the first 12.x with the Node 24 prebuild — and raise the engines floor to Node 20 (Node 18 is EOL and dropped from better-sqlite3 12.x prebuilds). Verified on macOS Node 24.15.0 (ABI 137): prebuilt binary used with no compiler (installs even with CC/CXX sabotaged), `codegraph init -i` shows no WASM banner, and `codegraph status` reports Backend: native. 639/639 tests pass on Node 22. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
b3d3ddbd93 |
refactor(eval): rename /audit skill to /agent-eval
Renames the `.claude/skills/audit/` directory and all internal references to `agent-eval`, aligning the skill name with the `/agent-eval` command it invokes. |
||
|
|
1cd162a66d |
fix(mcp): auto-detect project via roots/list when no rootUri (#196) (#214)
MCP tools failed with "CodeGraph not initialized" when a client launched the server outside the project and sent no rootUri/workspaceFolders — the server fell back to its own cwd, missed the project's .codegraph/, and returned a misleading "run codegraph init" error on every call. The only workaround was passing projectPath by hand to each tool. When no explicit path is given, the server now asks the client for its workspace root via the standard MCP roots/list request (gated on the client advertising the roots capability) before falling back to cwd. This required teaching the stdio transport to send server->client requests and match their responses by id (previously responses were dropped as invalid). When a project still can't be resolved, the error now names the directory it searched and tells the user to pass projectPath or add --path to the MCP config, instead of pointing at a re-init they don't need. Reported-by: @zhangyu1197 Closes #196 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> |
||
|
|
7fe64b32be |
feat(eval): add agent-eval harness and /audit + /publish Claude skills
Replaces the old interactive publish.js script with two Claude skills and a full agent-evaluation harness: - `.claude/skills/audit/` — `/audit` skill drives `scripts/agent-eval/audit.sh` to benchmark retrieval quality (with vs. without codegraph) on a chosen real-world repo from the new `corpus.json` (17 repos across 14 languages). - `.claude/skills/publish/` — `/publish` skill orchestrates the full release workflow (preflight → changelog → confirmation gate → bump/build → npm publish → GitHub release), replacing `publish.js`. - `scripts/agent-eval/` — headless (`run-agent.sh`, `run-all.sh`) and interactive tmux (`itrun.sh`) harnesses with stream-json parsers (`parse-run.mjs`, `parse-session.mjs`) that report tool calls, token usage, and a VERDICT line summarising codegraph_explore vs. Read/Grep counts. - `run-interactive-test.md` — documents the two harnesses, idle-detection approach, and what "good" agent behavior looks like after explore-first guidance. |
||
|
|
1cbca5a51e | docs: add Star History chart to README | ||
|
|
2c1a314b84 |
feat(mcp): line numbers in explore output + per-file cluster fixes (#188)
* feat(mcp): line numbers in explore output + per-file cluster fixes Follow-up to #185. Three changes to codegraph_explore: 1. Source sections now carry cat -n style line-number prefixes (<num>\t<code>), so the agent can cite file:line straight from the payload instead of re-Reading the file just to recover a line number. Isolated A/B: the no-line-numbers arm spent 2 Reads + a grep to find a line number the line-numbered arm cited with zero follow-up calls. Payload cost ~3-5%. Toggle off with CODEGRAPH_EXPLORE_LINENUMS=0. 2. Per-file cluster selection now ranks clusters containing a query entry point ahead of dense declaration blocks. Density-only ranking buried the relevant methods (perform/didCreateURLRequest/task in Alamofire's Session.swift) under the top-of-file class header + property list. 3. Whole-file "envelope" nodes (a class/struct/etc. spanning >50% of the file) are excluded from clustering. The Session class spans ~1,400 lines; keeping it collapsed every method into one giant cluster that tail-trimmed down to just the class header, hiding the methods. Net vs the 0.7.10 baseline, line numbers on: Alamofire -60%, Excalidraw -32%, VS Code -12% per explore call. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(mcp): language-neutral omission markers in explore output The gap separator and the two tail-trim markers used C-style `//` comments, which aren't comments in Python, Ruby, etc. Switch to plain `... (gap) ...` / `... (trimmed) ...` so they read correctly inside any language's fenced source block. With line numbers on, the line-number jump already corroborates a gap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(mcp): language-neutral truncation marker in codegraph_context Sibling to the explore marker fix: codegraph_context's code-block truncation used a C-style `// ... truncated ...`. Switch to `... (truncated) ...` so it reads correctly in any language's fenced source block. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(release): bump version to 0.7.11 --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
93e53e7c69 |
feat(mcp): size-adaptive output budget for codegraph_explore (#185) (#187)
Output is now scaled to indexed file count. Small projects (<500 files) cap at ~18KB and skip the "Additional relevant files" / completeness / explore-budget reminders that earn their keep on larger codebases; medium (<5,000) caps at ~28KB; large (<15,000) keeps the historical ~35KB; very large goes up to ~38KB. A per-file char cap also prevents a single file with many adjacent symbols from collapsing into one whole-file dump (the pathological Alamofire `Session.swift` case reported in #185), and a per-file symbol- list cap stops the `#### path — sym(kind), ...` header from leaking multi-KB lists when many adjacent symbols cluster together. Measured against the README's benchmark repos: Alamofire (~100 files) ~62% smaller per call, Excalidraw (~600 files) ~35%, VS Code (~10k files) ~14%. Agent-trust floor preserved — Relationships, scored cluster selection, and structured-source output are all retained. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
4bb95639ca |
chore(release): refine release-notes extractor (#181)
Three fixes prompted by retroactively unwrapping the 0.7.6 / 0.7.7 / 0.7.9 release notes: - Add `--stdin` mode so the extractor can clean up an existing release body (via `gh release view ... --json body --jq '.body'`) without needing a matching CHANGELOG.md entry. The 0.7.9 release didn't have one — its body had been hand-rolled from the 0.7.8 entry on publish. - Stop treating `+` as a bullet marker. CommonMark allows it, but our CHANGELOG uses literal `+` inline (`MCP config + instructions`) and the script was misreading those as nested bullets. Keep `-`, `*`, and `N.` only. - Preserve fenced code blocks verbatim. The 0.7.6 entry has a triple- backtick ```bash block; the previous pass was joining its lines into one, producing unreadable code. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
483ec9171c |
chore(release): unwrap CHANGELOG paragraphs for GitHub Release notes (#180)
GitHub renders release-note Markdown with GFM hard breaks, so every `\n` becomes `<br>`. The CHANGELOG is hard-wrapped at ~75 chars for readable diffs, which renders as awkward visible line breaks on the release page (see https://github.com/colbymchenry/codegraph/releases/tag/v0.7.10). Add `scripts/extract-release-notes.mjs` to extract a version block and join indented continuation lines into a single line per bullet. Nested list items, headings, and link references are preserved. `scripts/release.sh` now uses this helper instead of the inline awk extractor — repo-level CHANGELOG.md viewing is unaffected because CommonMark there treats newlines as spaces. Also fix the 0.7.10 entry: "Two underlying fixes" -> "Three", "Rust file-/level" broken hyphen, and move the closes/credit line above the nested list so it doesn't strand as a top-level paragraph. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
fb8fb0ea8b | release: 0.7.10 (Windows mojibake fix, module-qualified symbol lookups, MCP handshake) v0.7.10 | ||
|
|
83f36dc170 |
fix(mcp): resolve module-qualified symbol lookups (#173) (#179)
`codegraph_callees stage_apply::run` (and `_node`, `_impact`, ...) returned "not found" against a repo with 7-9 sibling Rust modules, each exporting `pub async fn run`. Two underlying issues: 1. The FTS5 query builder stripped `:` as a special char without splitting on `::`, so `stage_apply::run` collapsed to the literal `stage_applyrun` which matches nothing. Treat `::` as whitespace before the strip step so both halves become FTS tokens. 2. `matchesSymbol` only understood `Parent.child` qualifiers and relied on `qualifiedName` carrying the module path. Rust file- level functions don't have their module name in `qualifiedName` (it's encoded in the file path instead), so even dot-style lookups failed. Accept `::`, `.`, `/` as separators; multi-level forms compose; Rust `crate::`/`super::`/`self::` prefixes get stripped before path matching. Fall back to file-path containment when the qualified-name suffix doesn't match — `stage_apply::run` matches a `run` in any file whose path has a `stage_apply` segment. Also tightens the no-match branch: qualified lookups no longer fall through to a fuzzy text match. `stage_apply::nonexistent_fn` returns `null` instead of silently resolving to an unrelated `rollback` in the same file. 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> |
||
|
|
36c8dbc404 |
fix(mcp): don't block initialize handshake on heavy init (#172) (#177)
The MCP `initialize` handler was awaiting `tryInitializeDefault` — which opens the SQLite DB and runs `await initGrammars()` (tree-sitter WASM bootstrap) — before sending the JSON-RPC response. On slow filesystems (Docker Desktop VirtioFS on macOS, WSL2) this could exceed Claude Code's ~30s handshake timeout, leaving the codegraph child process alive and unresponsive with no tools visible in the client. Send the response first; defer the open to a tracked background promise. The lazy retry path used by `tools/list` and `tools/call` now awaits that promise instead of racing it with `openSync`, so we never double-open the SQLite file. Adds a subprocess-based regression test that asserts the JSON-RPC response arrives on stdout before `startWatching()` logs to stderr. This ordering check catches the regression on any filesystem, not just slow ones where the timing matters in practice. Reported by @sashanclrp; isolated by @sgrimm's wire capture. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
9b6a917d32 | Merge remote-tracking branch 'origin/main' | ||
|
|
662bb1ece8 | release: 0.7.9 | ||
|
|
c811237db8 | Update README.md | ||
|
|
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>v0.7.8 v0.7.9 |
||
|
|
7d87126ee8 | release: 0.7.7 (multi-agent installer — Cursor, Codex, opencode) v0.7.7 | ||
|
|
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> |
||
|
|
7e617d819b |
release: 0.7.6 (fix permission denied on global install)
The 0.7.5 tarball shipped `dist/bin/codegraph.js` without the executable bit set, causing `zsh: permission denied: codegraph` after a fresh global install. The build script now `chmod +x`'s the binary before packing. Also adds CHANGELOG.md and documents the release workflow in CLAUDE.md.v0.7.6 |
||
|
|
6ac2066a7a |
test+feat: add cargo workspace crate resolution for rust resolver (#151)
* test+feat: add cargo workspace crate resolution for rust resolver Agent-Logs-Url: https://github.com/miketheman/codegraph/sessions/0101633b-8b63-4951-a6ca-03efe7fafe0b Co-authored-by: miketheman <529516+miketheman@users.noreply.github.com> * perf: cache cargo workspace map during rust resolution Agent-Logs-Url: https://github.com/miketheman/codegraph/sessions/0101633b-8b63-4951-a6ca-03efe7fafe0b Co-authored-by: miketheman <529516+miketheman@users.noreply.github.com> * feat(rust): expand cargo workspace member globs and trust workspace hits - Parse glob entries in `[workspace].members` (e.g. `crates/*`, `helix-*`) via picomatch against a new optional `ResolutionContext.listDirectories` so workspaces that don't enumerate every member are covered. Implementation walks the static-prefix subtree with a depth cap and skips `target`, `node_modules`, `.git`, etc. - Bump Pattern 4's confidence to 0.95 when the workspace map produces a hit. The cargo manifest gives an unambiguous crate-name -> crate-root mapping, so workspace-driven module resolution should beat name-matcher's self-file matches (otherwise every file with `use foo::...` self-resolves at 0.7 and the cross-crate edge never materializes). - Validated against astral-sh/uv (`members = ["crates/*"]`, 67 crates, 567 .rs files): 1,969 cross-crate `imports` edges reaching 60 distinct member lib.rs files, up from 0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Colby McHenry <me@colbymchenry.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
1cbd5a8123 |
fix(extraction): recurse into git submodules when listing files (#150)
`git ls-files -co --exclude-standard` only sees the submodule pointer in the main repo's index, so projects using submodules indexed 0 files. Now the tracked list runs with `-c --recurse-submodules` so submodule contents are included; untracked files are gathered with a separate `-o --exclude-standard` call (the two flags can't be combined — git only supports --recurse-submodules with --cached/--stage). Fixes #147. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
b47c9562ec |
fix(cli): hard-exit on Node 25.x instead of soft warning + crash (#149)
The Node 25.x V8 turboshaft WASM JIT Zone allocator bug (https://github.com/colbymchenry/codegraph/issues/81) reliably crashes CodeGraph mid-indexing with `Fatal process out of memory: Zone` when tree-sitter grammars get JIT-compiled. We already had: - `engines: "node": ">=18.0.0 <25.0.0"` in package.json - Lazy grammar loading (#61) - A startup `console.warn` when Node 25+ is detected But the recurring duplicates (#54, #81, #140, plus comments from multiple unique users) show those defenses aren't enough: - npm `engines` is a soft warning by default, so `npm install -g` doesn't block. - The startup `console.warn` is a single yellow line that scrolls off-screen before the OOM 30 seconds later, so users connect the crash to "CodeGraph is broken" rather than "I'm on the wrong Node version" and file a fresh issue. This patch turns the soft warning into a hard exit. On Node 25+ we print a bordered banner that names the V8 root cause, embeds the detected version, gives Node 22 LTS install commands (nvm + Homebrew), and links to #81 — then exit(1) BEFORE any tree-sitter import triggers WASM JIT. The previous behaviour is preserved behind `CODEGRAPH_ALLOW_UNSAFE_NODE=1` for anyone who patched V8 themselves or wants to test a future Node 25 fix. The banner builder is extracted to `src/bin/node-version-check.ts` so the test can import it without triggering CLI bootstrap. Five unit tests pin the version interpolation, root-cause explanation, recovery commands (nvm + brew), override env var, and #81 link — these are load-bearing and shouldn't get edited away silently. Suite: 509 → 514, all passing. Verified both paths manually by flipping the threshold to 22 in dist and running on Node 22.20.0: without the env var the CLI prints the banner and exits 1; with `CODEGRAPH_ALLOW_UNSAFE_NODE=1` it prints the banner and continues. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
55daeffe13 |
fix(db): surface SQLite backend in status + actionable WASM-fallback banner (#148)
Closes the visibility gap behind issues #138 (WASM-on-macOS) and #139 (MCP "database is locked"). `better-sqlite3` is in optionalDependencies, so when the native build fails npm install still succeeds and the runtime silently falls back to node-sqlite3-wasm — 5-10x slower and without WAL, so writers block readers (which is what makes the MCP server appear to "lock the DB" in #139). The only existing signal was a one-line `console.warn` to stderr that MCP transports typically swallow. This patch does NOT change install behavior — better-sqlite3 stays in optionalDependencies so cross-platform installs keep working. It just makes the substitution observable + recoverable. ## Visibility (4 surfaces) - CLI `codegraph status`: new `Backend:` line under Index Statistics. `native` rendered green; `wasm` rendered yellow with an inline `npm rebuild better-sqlite3` nudge. Also exposed in `--json` as `backend: 'native' | 'wasm'`. - MCP `codegraph_status`: new `**Backend:**` line. Native form reads `native (better-sqlite3)`; wasm form prepends a warning glyph and includes the full fix recipe. - Stderr banner on fallback (`buildWasmFallbackBanner`): replaces the bare one-line `console.warn` with a multi-line bordered banner covering macOS + Linux fix steps and optionally appending the native load error. - README troubleshooting: new "Indexing is slow / MCP database is locked / WASM fallback active" entry that walks users to the `Backend:` line and the fix. ## Per-instance backend tracking `createDatabase` previously set a module-level `activeBackend` global. MCP can open multiple project DBs in one process via the `getCodeGraph()` cache, so the global would race / overwrite. Refactor: `createDatabase` now returns `{db, backend}`, `DatabaseConnection` carries `private backend` and exposes `getBackend()`, and `CodeGraph.getBackend()` is the public surface. The CLI and MCP both call `cg.getBackend()`. ## What this does NOT fix The root cause of users landing on WASM is environment-specific (Mac without Xcode CLT, Node version mismatch, etc.) and not fixable in code without changing the optionalDependencies design. The README entry tells users what to run; `Backend: native` after rebuild is the confirmation signal. ## Tests New `__tests__/sqlite-backend.test.ts` (6 tests) pins the banner recipe content (so future edits can't strip the recovery commands), the `WASM_FALLBACK_FIX_RECIPE` constant, and per-instance `DatabaseConnection.getBackend()` / `CodeGraph.getBackend()` reporting. Suite: 503 → 509, all passing. Credit to @andreinknv whose analysis on #138 (and patches on his fork at 6d0e7a2 + 69f7001) framed the visibility approach. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
af7abd50cb |
docs(readme): move Initialize Projects above the gif + add Scala to languages (#146)
- Reorder Get Started so the per-project init code block sits between the npx install and the GIF — visually contiguous code blocks read better than code → image → code. - Add Scala (`.scala`, `.sc`) to the Supported Languages table now that #91 has landed. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
8506936f86 |
90 scala support (#91)
Co-authored-by: Colby McHenry <me@colbymchenry.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
00b298966f |
docs(readme): add Initialize Projects snippet to Get Started (#145)
The top-level Get Started section showed the install command but not the per-project init step. Adding the same `cd your-project / codegraph init -i` block that lives in Quick Start so users see the full happy path before scrolling. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
181b180881 |
docs(readme): add Vue to the Supported Languages table (#144)
Followup to #66 — Vue support shipped but the README languages table was never updated. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
74327814ee |
feat: wire up framework route extraction (#89)
* docs: add framework extract wiring plan
* feat(resolution): replace extractNodes with extract() returning nodes and references
* feat(resolution): add getApplicableFrameworks helper for per-language dispatch
* feat(django): emit route nodes and route->view references in extract()
* feat(flask,fastapi): emit route nodes and route->handler references
* feat(express): emit route nodes and route->handler references
* feat(laravel): emit route nodes and route->handler references
* feat(rails): emit route nodes and route->handler references
* feat(spring): emit route nodes and route->handler references
* feat(go): emit route nodes and route->handler references
* feat(rust): emit route nodes and route->handler references
* feat(aspnet): emit route nodes and route->handler references
* feat(swift,vapor): emit route nodes and route->handler references
* chore(react,svelte): migrate resolvers to extract() interface
* feat(extraction): run framework extractors after tree-sitter parse
* docs: document framework route extraction
* feat(strip-comments): add per-language comment stripper for framework extractors
Replaces comment characters and string-literal contents with spaces (not
removal) so source offsets stay valid for downstream regex match index ->
line number conversion. Handles Python triple-quoted docstrings, Ruby
=begin/=end, Rust nested block comments, and the standard //, #, /* */
forms across the supported languages.
This is consumed by framework extract() methods in a follow-up commit so
that commented-out / docstring routing examples don't surface as phantom
route nodes in the graph.
* feat(frameworks): strip comments before regex extraction (prevents phantom routes)
Pipes the per-language stripCommentsForRegex helper into every framework
extract() that scans raw source: django/flask/fastapi (python.ts),
express, laravel, rails, spring, go, rust, aspnet, vapor, plus
swiftui/uikit struct extraction in swift.ts.
Without this, examples like:
# path('/admin/', AdminPanel.as_view())
""" path('/users/', UserListView.as_view()) """
urlpatterns = [path('/real/', RealView.as_view())]
produced 3 phantom route nodes. Now only the real one is extracted.
Each framework gets a regression test in __tests__/frameworks.test.ts
asserting that line-, block-, docstring- and (where relevant)
heredoc-style commented-out routes do not surface as nodes.
---------
Co-authored-by: Colby McHenry <me@colbymchenry.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
5ab81746e8 |
feat: add Vue support (#66)
Co-authored-by: Colby McHenry <me@colbymchenry.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
804ab671d4 |
feat(mcp): emit server-level instructions in initialize response (#143)
Adds a universal tool-selection playbook surfaced by MCP clients (Claude Code, Cursor, opencode, LangChain, OpenAI Agent SDK) in the agent's system prompt automatically. Without this, agents have to infer tool composition from individual tool descriptions and tend to walk callers manually instead of reaching for codegraph_impact, etc. Scoped tight: only the 9 tools that exist on main today (search/context/callers/callees/impact/node/explore/files/status), no "(when present)" references to unmerged tools, no per-language guidance. ~40 lines of useful guidance. Salvaged from #121, which bundled the instructions with #117's MCP tool-registry refactor and referenced many tools that don't exist on main. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
a460b856c2 |
perf(db): drop redundant idx_edges_source / idx_edges_target (#142)
Both narrow indexes are fully covered by the existing (source, kind) and (target, kind) composites via SQLite's left-prefix scan, so they're dead weight on every write. Empirical measurements (from the spike script in PR #122 on a 50K-node / 250K-edge synthetic DB): - DB size: 34.7 MB → 27.0 MB (-22.2%) - Bulk insert (250K edges): 590ms → 431ms (1.37× faster) - source/target lookup latency: no regression Adds migration v4 to drop both on existing databases; fresh-DB schema no longer creates them. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
153fd1e974 |
fix(gitignore): anchor "coverage/" rule to repo root (#127)
The unanchored "coverage/" rule (intended to ignore the test-output directory at repo root) silently matches any "coverage/" directory in the tree. This bit a real PR: src/coverage/ was added but never made it into the commit because git add silently dropped the files. The PR shipped with the test importing a module that didn't exist. Anchor the rule to "/coverage/" so it only ignores root-level test output, allowing src/coverage/, packages/*/coverage/, etc. to be committed normally. |
||
|
|
5e5d8d9447 |
fix(cli): surface lock-acquisition errors and silence Emscripten Aborted() spam (#128)
* fix(cli): surface lock-acquisition errors and silence Emscripten Aborted() spam Two unrelated cosmetic but actively misleading bugs that surface when the indexer is under load. 1) printIndexResult fell through to "No files found to index" whenever the IndexResult had filesIndexed=0 AND filesErrored=0. The lock-acquisition path returns success:false with a generic "Could not acquire file lock" entry in result.errors[] (severity 'error'), but filesErrored counts only file-level parse failures, so the user saw "No files found to index" — actively wrong. Add a top-of-function check for the !success && !hasErrors case that surfaces the first severity:'error' message instead. 2) parse-worker.ts let Emscripten's stderr "Aborted()" lines (plus their "Build with -sASSERTIONS for more info" follow-ups) leak to the parent's terminal whenever a WASM tree-sitter parser crashed on a pathological file. Even after the JS layer caught and recovered, the user saw dozens of `Aborted()` lines spammed to stderr. Install a stderr filter at worker startup that drops only those specific Emscripten internal lines; everything we log ourselves passes through unchanged. Verified live against ollama/ollama@v0.22.0: - second concurrent `codegraph index` now shows "Could not acquire file lock - another process may be indexing" instead of "No files found to index" - WASM-crash-prone re-index produced 0 Aborted() lines (down from 68+). * fix(cli): null-safe error surfacing + clearer stderr-filter contract docs Two reviewer findings on PR #128: - printIndexResult: when result.success is false but result.errors contains no severity:'error' entry (degenerate case but possible if the result shape ever drifts), the find() returned undefined and the previous if-guard fell through to the misleading 'No files found to index' branch. Now always surfaces a clear failure message via clack.log.error, defaulting to 'Indexing failed — no further details available' when no specific error is in the errors list. - parse-worker stderr filter: callback handling was already correct but the comment didn't document it; expand the comment to spell out the Writable-stream-contract obligation, the per-call match semantics (split-chunk caveat), and the substring-exactness trade-off so future readers understand the deliberate trade-offs. |
||
|
|
4f6c51d381 |
fix(extraction): drop duplicate export-var nodes and honour maxFileSize in bulk path (#129)
Two correctness bugs in the core extraction pipeline, surfaced by an
adversarial stress corpus (5k synthetic export-const declarations
plus a deliberate 8MB single-line file):
1) Every `export const X = ...` produced TWO nodes for the same
symbol — one kind:'variable' from extractExportedVariables, plus
one kind:'constant' from extractVariable (called when the walker
descended into the export_statement child). Stress test showed
100% duplication across 5,003 export-const declarations. The
dedicated extractVariable dispatch is the correct one — it picks
kind from isConst, captures the initializer signature, and walks
type annotations; the export-statement helper was redundant
because the language extractors' isExported predicate already
walks parent chains. Remove the export_statement branch from the
dispatch (children are descended into normally) and drop the
private helper.
2) The bulk indexAll path read each file's stats but never compared
stats.size against config.maxFileSize. Vendored generated files
(multi-MB headers, minified bundles, etc.) were indexed regardless
of the user's size cap. The single-file extractFile path enforced
it; only the bulk path was missing the check. Mirror the
single-file behaviour: emit a 'size_exceeded' warning, count the
file as skipped, advance progress, and continue.
On the stress workspace (5,005 synthetic files; 50,000 fns in one
3MB file; 8MB single-line file; 5,000 export-const declarations):
before: 65,014 nodes (100% var/const duplication, every >1MB file
indexed despite maxFileSize=1MB)
after: 10,008 nodes (0 duplicates, large files correctly skipped
with size_exceeded warnings)
Tests calibrated to the duplicate behavior were updated to look for
kind:'constant' on `export const`, which is the correct kind. Full
suite: 380 passed (was 374 passed, 6 failed before this fix).
|
||
|
|
d151c0f922 |
feat(resolution): tsconfig path aliases + re-export chain following (#130)
* feat(resolution): tsconfig path aliases + re-export chain following
Two related correctness improvements that unlock accurate import
resolution on modern JS/TS codebases.
1) tsconfig/jsconfig path aliases.
The resolver previously had a hard-coded list of common aliases
(@/, ~/, src/, app/) and ignored any project-defined paths from
tsconfig.json compilerOptions.paths — which means every import
through @components/Foo, @lib/utils, etc. on Vite/Next/Nuxt/Nest
projects silently failed to resolve. Adds src/resolution/path-
aliases.ts that reads tsconfig.json (and falls back to jsconfig.json),
honours baseUrl, supports the * wildcard, and respects the priority
order of multiple replacement targets per alias. JSONC tolerant
(strips comments + trailing commas, common in the wild). The new
ResolutionContext.getProjectAliases() lazily loads + caches the
result; resolveAliasedImport consults it before the legacy fallback
list.
Verified live on a synthetic project with @utils/* and @lib custom
aliases: both resolved to the correct files and produced edges,
unresolved_refs empty.
2) Re-export chain following.
`import { Foo } from './barrel'` where barrel.ts only re-exports
(`export { Foo } from './real'` or `export * from './real'`) used
to fail because the resolver only looked for declarations IN the
resolved file — it never followed the export chain to the actual
definition. Adds extractReExports() (named + wildcard + as-rename
forms), a per-file getReExports() context method, and a recursive
findExportedSymbol() helper with depth cap (8) and visited-set
cycle protection. resolveViaImport now uses it whenever the symbol
isn't directly declared in the imported file.
Verified live on a synthetic 3-hop chain (main → all.ts wildcard →
index.ts named → auth.ts declaration): signIn resolved correctly,
unresolved_refs empty.
Full test suite: 380 passed, 0 failed.
* fix(resolution): address reviewer findings — isExternalImport bypass, JSONC strings, comment stripping, optional context method
Five fixes from independent semantic review:
- isExternalImport now consults context.getProjectAliases() before
the bare-specifier heuristic. Without this, custom prefixes like
'@components/*' from tsconfig.paths were classified as npm and
resolveAliasedImport never even ran. Adds a context parameter
(optional, for backward compat with mock contexts).
- stripJsonc rewritten as a string-aware state machine. The previous
regex-only version corrupted any URL embedded in a JSON string
value ('https://cdn.example.com' lost everything after '//').
- extractReExports now strips JS line+block comments from content
before applying the regex, so a commented-out 'export { x } from
...' no longer creates a phantom re-export edge. New
stripJsComments helper preserves string literals (single, double,
template) so '//' inside a string stays intact.
- ResolutionContext.getProjectAliases() made optional so existing
mock contexts in __tests__/resolution.test.ts (which TypeScript
doesn't type-check because tsconfig excludes __tests__) don't
throw at runtime when resolveAliasedImport hits them. Caller
uses ?.
- Two new integration tests in __tests__/resolution.test.ts:
* Path-alias resolution with name-collision: two pickMe() in
different dirs, only the @utils-aliased one should be the
call target. Asserts via getCallers on each candidate node.
* No-tsconfig fallback: relative import still produces the call
edge.
Full test suite: 832 passed (was 380; the increase is from the
biomarkers + LLM hooks that ship via parent branches).
* fix(resolution): allow re-export rename chains past the pre-filter
The fast pre-filter in resolveOne() bails when no symbol with the
reference name exists project-wide, which is incompatible with the
new chain-following code: a renamed re-export (`import { login }
from './barrel'` where the barrel does `export { signIn as login }
from './auth'`) intentionally calls a name that has no project-wide
declaration. The chain finds the renamed upstream symbol — but only
if resolution is allowed to run.
Add an import-mapping escape so the pre-filter only bails when the
ref also doesn't match any local import. Adds two tests covering the
3-hop wildcard chain and the named-rename branch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Colby McHenry <me@colbymchenry.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
56f6b3b485 |
feat(search): field-qualified queries (kind:/lang:/path:/name:) + fuzzy typo fallback (#131)
* feat(search): field-qualified queries (kind:/lang:/path:/name:) + fuzzy typo fallback
Two UX improvements that turn a free-text search into something a
real user can drive precisely.
1) Field-qualified queries.
A new query parser (src/search/query-parser.ts) splits the raw query
into structured filters and a free-text remainder:
kind:function name:auth path:src/api authenticate
becomes
{ kinds: ['function'], nameFilters: ['auth'],
pathFilters: ['src/api'], text: 'authenticate' }
Filters compose with the SearchOptions arg (intersection). Unknown
prefixes pass through as plain text so `query "TODO:"` keeps working.
Quoted values (`path:"my dir"`) handle whitespace. When the user
specifies only filters with no text, the search uses a filter-only
candidate scan instead of bailing out.
Recognised today:
kind: any NodeKind value
lang: any Language value (alias: language:)
path: case-insensitive substring of file_path
name: case-insensitive substring of node.name
2) Fuzzy fallback.
When BOTH FTS and LIKE return nothing AND the text is at least 3
chars, the resolver scans the distinct-name set with a bounded
Damerau-Levenshtein-style edit distance (≤2 for ≥5 chars, ≤1 for
4-char queries, off for shorter). Bounded edit-distance early-exits
once the row min exceeds maxDist, so this stays O(distinct-names *
avg-name-length) with a very low constant.
Verified live against ollama/ollama@v0.22.0:
query "kind:function auth" → only function-kind hits
query "lang:go path:server route" → Go files under server/
query "getUssr" (typo) → finds getUser, SetUser
query "confg" (typo) → finds Config
Full test suite: 380 passed.
* fix(search): address reviewer findings — tokenizer mid-token quotes, fuzzy fan-out cap, larger filter-only over-fetch, unit tests
Five fixes from independent review:
- parseQuery tokenizer: quotes that appear MID-token (path:"my dir/
file") were not being recognised — only quotes at the start of a
token were treated as quoted spans. The fixture path:"my dir"
parsed as ['path:"my', 'dir"'] instead of ['path:"my dir"'].
Tokeniser is now a single state machine that scans into a token
until whitespace OR a quote, and recognises quotes anywhere within
the token (skips to the matching close quote).
- searchNodesFuzzy: cap the per-name follow-up SQL queries at
Math.max(limit*2, 50) AFTER edit-distance filtering. Without
this, a project with many similar names (getUser1, getUser2...)
could fan out far beyond limit queries before the inner-loop
break kicks in.
- searchAllByFilters (filter-only no-text path): bumped over-fetch
multiplier from 2× to 5× so a selective post-filter (e.g.
path:src/very/specific/file.ts) doesn't return fewer than limit
results despite the DB having matches.
- 23 new unit tests in __tests__/search-query-parser.test.ts:
parseQuery covers known-field filter, lang/language alias,
multiple kind: ORs, quoted spans (incl. mid-token), URL
passthrough, empty-value passthrough, unknown prefix passthrough,
unknown value passthrough, all-filters-no-text, empty input,
20k-char input. boundedEditDistance covers identity, single
insertion/deletion/substitution, length-difference shortcut,
empty inputs, case-sensitivity, early-exit correctness.
Full test suite: 853 passed (up from 830).
* refactor(search): derive parser kind/lang sets from types.ts as const
Convert NodeKind and Language to runtime-iterable as const arrays
(NODE_KINDS, LANGUAGES) so the query parser imports the canonical
list instead of duplicating it. Also fix the path: JSDoc to say
substring (matches the .includes() impl).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Colby McHenry <me@colbymchenry.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|