Skip to content

fix(mcp): a projectPath naming a file resolves through its directory (#2982) - #2996

Closed
maxmilian wants to merge 1 commit into
colbymchenry:mainfrom
maxmilian:fix/2982-projectpath-file
Closed

maxmilian wants to merge 1 commit into
colbymchenry:mainfrom
maxmilian:fix/2982-projectpath-file

Conversation

@maxmilian

Copy link
Copy Markdown
Contributor

Fixes #2982.

A projectPath naming an existing file inside an indexed project (an easy agent mistake: passing the file it is looking at) was refused with isError "Path is not a directory", while a path that doesn't exist walked up to .codegraph/ fine.

execute() now treats an existing file as its directory, right after the projectPath length check. It rewrites args.projectPath there rather than inside getCodeGraph, so the project gate, the worker dispatch, the #155 worktree notice and getCodeGraph all see the same directory. Doing it only in getCodeGraph would have dropped the worktree notice: worktreeMismatchFor runs git rev-parse with the raw path as cwd, which fails with ENOTDIR on a file. A path that can't be stat'd is left alone and walks up as before.

The sensitive-folder refusal still applies: the directory goes through validateProjectPath, so /etc/hosts and ~/.ssh/id_rsa are refused the same as /etc and ~/.ssh. One behaviour change to note: a symlink inside a project that points at a file in a sensitive folder (say <project>/ssh-link -> ~/.ssh/known_hosts) is now answered from <project>'s own index, because the directory is taken from the link's location. Nothing under ~/.ssh is opened or read, and this matches how a symlinked directory already walks up by its own location.

Tests:

All three fail on main (the worktree case also fails if the rewrite is done only in getCodeGraph).

npm run build and npm test pass except 3 tests in bundle-launcher.test.ts and cli-sync.test.ts that fail the same way on unmodified main in my environment (macOS, Node 26).

🤖 Generated with Claude Code

https://claude.ai/code/session_01VNM2N9oxn6XgiLmuX6PZp1

colbymchenry pushed a commit that referenced this pull request Oct 30, 2026
An agent that passed the file it was looking at as `projectPath` got
`isError: true` ("Path is not a directory"), while a path that doesn't
exist at all walked up to the project's `.codegraph/` fine. An error teaches
an agent to stop calling codegraph, and this mistake is recoverable.

`execute()` now takes an existing file to stand for its directory, right
after the length check, and rewrites `args.projectPath` there, so the
project gate, the worker dispatch, the worktree notice and getCodeGraph all
see that directory. A path that can't be stat'd walks up as before.

Path safety: the directory goes through the same checks as any
projectPath, the sensitive-folder refusal on the path itself and on the
root its up-walk lands on, so a file reaches exactly what its directory
reaches and never more. `/etc/hosts` is refused as `/etc` is, a file in
`~/.ssh` as `~/.ssh` is, and `win.ini` in the Windows folder as that folder
is. A symlink inside a project that points at a file in a sensitive folder
is answered from the link's own folder, as a symlinked folder already is;
nothing in the sensitive folder is opened.

Validation on main de0a4eb (Windows 11):
- Red/green: the PR's 2 tests (a file in an indexed project, and a file in
  a linked worktree, which keeps its worktree notice) and 1 of ours fail
  with main's tools.ts and pass with the fix. Ours asks for a file and for
  its directory in 4 cases, an indexed project, `~/.ssh`, a folder in no
  project, and the Windows folder's `win.ini`, and requires the same
  answer for both, refusals included.
- A real `serve --mcp` session over stdio with a private HOME: on main,
  codegraph_search and codegraph_status for `<project>/src/a.ts` both
  returned isError "Path is not a directory"; with the fix both answer from
  the project, and `win.ini` is refused as a sensitive system directory.

Original PR #2996 by @maxmilian.
Fixes #2982

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

Copy link
Copy Markdown
Owner

Thanks for this, @maxmilian, and for flagging the symlink case yourself! It landed in #3075 with you as the author, and your change is unchanged. We added a test that asks for a file and for its folder, across an indexed project, a sensitive folder, a folder with no index and C:\Windows\win.ini, and requires the same answer for both. That pins down that a file never reaches more than its folder. We also confirmed it in a real serve --mcp session.

bompus added a commit to bompus/codegraph that referenced this pull request Oct 30, 2026
…598)

* fix(explore): Chinese, Japanese and Korean names reach explore, its Flow and context (colbymchenry#3031)

The index stores a name in any script as one full-text token, and
`codegraph query 로그인` found it, but every tokenizer between a question
and the index knew only ASCII: extractSearchTerms split on [^a-zA-Z0-9]
and kept 3+ characters, context's extractSymbolsFromQuery matched ASCII
patterns only, and explore's exploreQueryTokens and the Flow's flowTokens
required a leading [A-Za-z_$] and 3+ characters. So `explore 寻路服务`
answered "No relevant code found", `context 로그인` printed only its
header and `explore 入口 末端` had no Flow, while their ASCII twins worked.

Shared helpers in search/query-utils now read words in any script whole:
letters, digits and combining marks, cut where Han, kana or Hangul meets
another script (NavMeshAgent类型 is NavMeshAgent and 类型, never
agent类型), at least 2 characters in Han, kana or Hangul and 3 elsewhere,
so Russian на and из stay out. extractSearchTerms and the explore and
Flow tokens use them. Context adds names with a non-ASCII letter as whole
runs, and a mixed name like get用户信息 as written, with no n-grams, so
a partial Chinese word still never becomes a search of its own (PR colbymchenry#2005).
The ASCII-only patterns see a word with accented or other non-ASCII
letters blanked out, so état is no longer read as tat. A Han, kana or
Hangul token counts as a deliberately named identifier, as such a name
has no case or underscore to show it, so a query of such names is
neither damped as prose nor flagged low-confidence.

Validation: new cjk-query-tokens tests (12) cover CJK, mixed, Cyrillic
and accented words, reference copies of the old tokenizers over an ASCII
corpus, and explore and context end to end on Korean, Chinese and
Japanese fixtures; 9 fail on main's source. Explore and context A/B with
214 ASCII queries (gin 76, Alamofire 79, express 59; 49 lead with a
Flow): all byte-identical. Of 12 non-ASCII queries, 6 changed on gin and
4 on express, as intended: état, Señal and naïve are read whole, and
handleHTTPRequest处理 now names handleHTTPRequest. Tokenizing a
10,000-character query takes at most 1.8 ms, against 0.1-0.3 ms before.

Reimplements PR #516 by @andy-sg and PR colbymchenry#1262 by @yimingll.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit c12a7b6)

* fix(sync): a long-running session picks up a framework added while it runs (colbymchenry#2941)

The MCP server's watcher holds one CodeGraph for the whole session, and
its framework detection was cached from the first time it ran: the
orchestrator reset it only in indexAll, and CodeGraph.sync never
re-initialized the resolver. A framework that arrived mid-session, like
Express in a new package.json, was missing from every file synced after
it, so a new route file got no route nodes until `codegraph index`. The
resolver's declared-dependencies cache was also keyed by the indexed file
COUNT (PR colbymchenry#2519): swapping one app for another of the same size, or
editing a manifest, kept the old dependency set, even through
reinitializeResolver().

Detection now records what it read: indexed source files, which a sync's
change lists cover, and every other path it read or probed, with a size
and mtime signature, including the per-app manifests the extractor gating
reads. Before a sync extracts files, it detects again only when files
were added or removed since, a source file detection read changed, or one
of those paths changed on disk. A scoped sync takes the file list from
the index plus the files it adds, so it never walks the project (the
first sync in a process used to run a full scan for its first detection).
What detection read is kept between detections and dropped by the same
change lists and signatures, so detecting again reads only what changed.
When the detected set differs from the resolver's, the sync
re-initializes the resolver. Its initialize() now clears its caches
before detecting, and clearCaches() forgets the declared-dependencies
cache with the other per-context memos.

Files indexed before a framework change are not re-extracted. A framework
like Express applies to every file of its language, and re-reading them
all inside a watcher's sync would cost what a full index does. Instead a
full index records the frameworks it used, a sync records how detection
moved since, and `codegraph status` (text, and --json with
reindexRecommended and frameworksChangedSinceIndex) recommends
`codegraph index` while they differ. Status only reads that record.

Validation:
- sync-framework-detection.test.ts (new, 8 cases): Express arriving with a
  new route file, by full and by scoped sync; a .svelte file added with no
  manifest change; an edited source file that detection read; a
  manifest-only edit picked up by the next sync, with the status record,
  cleared on revert and by a full re-index; a fresh process's scoped
  syncs detecting once, from the index, with no scan; the equal-size app
  swap; and the dependency cache forgetting on request. 7 of 8 fail on
  main's source (the 8th needs the new export). Ten mutations, one per
  mechanism, each fail a test.
- The triage probes: main detects [] after Express arrives and routes.js
  gets no route; the fix detects express and extracts GET /hello, by full
  and by scoped sync. For the swap, main's reinitializeResolver() gives
  [react] and the fix [express].
- Cost on excalidraw (737 files, scoped syncs in one long-lived
  instance): the detection step takes 8 ms on a sync where nothing it
  read changed and 8-13 ms when it detects again, against syncs of about
  260 ms (add) to 2 s (modify). The first sync in a process detects once
  (about 300 ms) and no longer scans the project.

Reported in PRs colbymchenry#2516 and colbymchenry#2519 by @nk33-dev. This reimplements both
fixes independently.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 31ec908)

* test(sync): adapt the framework-detection tests to the fork's skip-table detection

The fork detects with the skip table (detectFrameworksWithSkips) and has an
http-routing detector that reads every JS/TS source file, so the once-only
detection test counts the sync's skip-table detections and edits a Python
file after the first sync. A new test covers a file the skip table let
detection pass over: editing it must detect again (the SynthSkips onSkipped
hook records it as a detection input).

* fix(status): recommend a re-index when package.json alone adds or drops a framework

Since PR colbymchenry#2941, a sync records the frameworks that came or went since the
last full index, and `codegraph status` recommends a re-index. But a sync
detects frameworks again only when it extracts a file, so adding `express`
or `@sveltejs/kit` to package.json with no source file edited recorded
nothing: status said the index was up to date while an unchanged Express
app's routes, or the `goto('/login')` in an unchanged +layout.svelte,
stayed unlinked until a full index.

A full index now also stamps the resolver's framework set, and
getFrameworkChangesSinceIndex() adds the difference between that stamp and
the set the resolver detects now (it detects on every open) to what syncs
recorded. So a fresh `codegraph status` sees a manifest-only change whether
or not a sync ran. An index built before this has no stamp, so that half
starts at its next full index. A long-running server's resolver keeps its
old set until it extracts a file, as before.

Validation on main 6177692 (Windows 11):
- Red/green: 3 of the 4 new tests fail with main's src/index.ts; the 4th,
  an unchanged project staying silent, passes on both. All 4 pass with the
  fix, as do the 8 sync-framework-detection tests.
- The CLI before and after, on a SvelteKit fixture indexed without
  @sveltejs/kit, then given it in package.json, then synced: main's status
  says "Index is up to date", and --json has reindexRecommended false and
  frameworksChangedSinceIndex null. With the fix, status adds "Frameworks
  changed since the last full index (added sveltekit-router)", and --json
  has true and {added: [sveltekit-router]}. Dropping the dependency gives
  {removed: [sveltekit-router]}, adding express to an Express app gives
  {added: [express]}, an unchanged project gives null on both arms, and a
  full `codegraph index` clears the advice.
- No false advice: a fresh index and then `status --json` on 18 real repos
  reports no change on any. They include express, flask, gin, vuejs/core,
  excalidraw, vuejs/docs, starlight, codegraph's own source and vscode's
  base and editor; Express, Flask, Next.js, React, Vue, Vue Router, Astro,
  Svelte, Spring, Go and UIKit are detected among them.

Original PR colbymchenry#3005 by @inth3shadows.
Fixes colbymchenry#3007

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 5526e33)

* fix(mcp): a projectPath naming a file resolves through its directory

An agent that passed the file it was looking at as `projectPath` got
`isError: true` ("Path is not a directory"), while a path that doesn't
exist at all walked up to the project's `.codegraph/` fine. An error teaches
an agent to stop calling codegraph, and this mistake is recoverable.

`execute()` now takes an existing file to stand for its directory, right
after the length check, and rewrites `args.projectPath` there, so the
project gate, the worker dispatch, the worktree notice and getCodeGraph all
see that directory. A path that can't be stat'd walks up as before.

Path safety: the directory goes through the same checks as any
projectPath, the sensitive-folder refusal on the path itself and on the
root its up-walk lands on, so a file reaches exactly what its directory
reaches and never more. `/etc/hosts` is refused as `/etc` is, a file in
`~/.ssh` as `~/.ssh` is, and `win.ini` in the Windows folder as that folder
is. A symlink inside a project that points at a file in a sensitive folder
is answered from the link's own folder, as a symlinked folder already is;
nothing in the sensitive folder is opened.

Validation on main de0a4eb (Windows 11):
- Red/green: the PR's 2 tests (a file in an indexed project, and a file in
  a linked worktree, which keeps its worktree notice) and 1 of ours fail
  with main's tools.ts and pass with the fix. Ours asks for a file and for
  its directory in 4 cases, an indexed project, `~/.ssh`, a folder in no
  project, and the Windows folder's `win.ini`, and requires the same
  answer for both, refusals included.
- A real `serve --mcp` session over stdio with a private HOME: on main,
  codegraph_search and codegraph_status for `<project>/src/a.ts` both
  returned isError "Path is not a directory"; with the fix both answer from
  the project, and `win.ini` is refused as a sensitive system directory.

Original PR colbymchenry#2996 by @maxmilian.
Fixes colbymchenry#2982

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 1c40e34)

* fix(callers): list every call site of a caller, and a function that calls itself (colbymchenry#3077)

getCallers keeps one row per caller (issue colbymchenry#1086) with the caller's first
edge only, and seeds its reported set with the symbol itself, so
codegraph callers showed a caller that calls the symbol on two lines
once, at the line its own declaration starts on, and left a recursive
call out, though the index stores every one of those edges and each is
a line to edit when the symbol changes. The CLI also merged edges on
source, target and kind, which dropped a second call site from --json.

A new GraphTraverser.getCallSites (CodeGraph.getCallSites) reads the
direct callers or callees with every edge: one row per neighbour in the
order getCallers / getCallees list them, every site kept, and the
symbol's own row, flagged recursive, when it calls itself. getCallers
and getCallees are unchanged, so ranking, explore's Flow and blast
radius, impact and the viewer read exactly as before; a test pins that
every non-recursive row of getCallSites equals getCallers' row and first
edge. src/graph/call-sites.ts holds the one wording of a site list
(9, 10, +N more) the surfaces share.

- codegraph callers prints a caller at its call-site lines (up to 10,
  then +N more), a recursive row as "(calls itself)", and --json gives
  each row lines and recursive, with every site in edges. Callees keep
  the callee's declaration line, where a call leads.
- codegraph_callers / codegraph_callees read the same rows (10 sites per
  caller), and codegraph_node's trail shows callers at their sites, 3 at
  most, and the symbol once among its callers when it calls itself.

Validation: new callers-call-sites.test.ts (7 tests) fails 7 of 7 with
main's sources and passes with the fix; 11 neighbouring callers, trail
and lookup test files (217 tests) pass. Before/after on one index per
repo (expressjs/express 0dee2c52, excalidraw d3a9c2a1, gin-gonic/gin
0f09c3a9): trails changed for 108/253, 3630/4180 and 557/1395 symbols,
nearly all callers moving from the declaration line to the call sites;
recursive rows newly shown 2/20/7. Budget: trail median 115->117,
354->360, 185->187 chars, largest growth +131/+212/+91 chars, and the
busiest caller rows (67, 300 and 100 sites) stay one capped entry.
codegraph_callers on each repo's 15 busiest callees: largest answer
1025->1358, 1882->2127, 1790->1805 chars.

Not in this change: the issue's own recursion, child.walk() on a
for...of element over children: TreeNode[], is not linked at all on
main (the receiver's element type isn't inferred), so it needs a
resolution fix of its own; this.walk() and a plain recursive call are.

Reimplements PR colbymchenry#2994 by @johnhldiver-beep.

Fixes colbymchenry#2909

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit fa03fa6)

* fix(explore): Flow reaches a named symbol a shorter route gets to first (colbymchenry#3079)

The named walk behind codegraph_explore's Flow (and the viewer's
/api/flow) kept one route per node and replaced it only with a better
route of the same length. In entry -> mid1 -> deep -> leaf beside
entry -> mid2 -> leaf, leaf was reached first through the unnamed mid2,
so the route through the named deep was turned away for being longer,
and the query "entry deep leaf" got a Flow that dropped deep or leaf.
On django, SQLCompiler.as_sql pre_sql_setup get_select ran as_sql ->
get_qualify_sql -> get_select and left out pre_sql_setup (issue colbymchenry#2970).

A node now keeps every route to it that no other route to it beats: one
that got there no later, through as many named symbols, ending on no
more unnamed hops. That is the same-depth rule of PR colbymchenry#2453 widened, so a
longer route through more named symbols survives, while a longer route
through fewer is still turned away and can't win the deepest-sink
choice; that choice and the longest-chain ranking across seeds are
unchanged. A route that beats one waiting at its own depth takes its
place in the queue, so nodes are visited in the order they were
reached, as before. A route never passes a node twice: with several
routes per node, a cycle among named symbols (ping -> pong -> ping)
would count them again and out-rank the flow it repeats. Each node's
callees are read once per search, across seeds.

The explore-exact-target fixture's Flow now runs through every symbol
its query names, as django's does. Its compiler.py stays a per-symbol
view only while the named and spine bodies outgrow the file's share,
which django's do (18,390 chars against 8,895, measured on django/db at
11af25c0) and the miniature's no longer did once the oversized unnamed
bridge left the spine, so its get_select grows to django's proportions;
no explore render rule changes.

Validation: new flow-named-longer-route.test.ts (issue repro through
resolveNamedSymbolFlow and codegraph_explore, the bridge budget, no
cycles) and the updated explore-exact-target fixture: 3 assertions fail
with main's walk, all 11 pass with the fix; 64 other Flow tests pass.
Explore A/B, both arms on one index per repo (expressjs/express
0dee2c52, gin-gonic/gin 0f09c3a9, django/db of django 11af25c0,
excalidraw d3a9c2a1), 290 queries: 152 graph-derived colbymchenry#2970-shape
queries all changed their lead chain, each to one more named symbol
(37/37, 55/55, 60/60); of 125 random named-chain controls 11 changed
and of 32 hand-written flow queries 1 (django's), each gaining a named
symbol; no query lost one, no chain repeats a node, and every query
whose Flow is unchanged returns a byte-identical answer. Largest answer
24,997 -> 24,998 chars (cap 25,000). getCallees reads per query set
drop 35-50% (gin 657 -> 410, django 1207 -> 752, excalidraw
1407 -> 951); explore time unchanged within noise.

Reimplements PR colbymchenry#3003 by @johnhldiver-beep.

Fixes colbymchenry#2970

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit a3e9e76)

* fix(docs): agent and command lists match the code; worktree warning drops init -i (colbymchenry#3067)

The warning for a git worktree answered from another checkout's index
still said to run "codegraph init -i", a flag that has done nothing
since init started indexing by default. Its one-line form is prepended
to every read-tool result and codegraph_status embeds the long form, so
both told the agent to build the index, which the server instructions
rule out ("Indexing is the user's decision ... don't run it yourself").
The CLI's codegraph status now says: Run "codegraph init" in this
worktree. What an agent reads (the read-tool notice, which also reaches
it through the prompt hook and the explore/node CLI, and
codegraph_status) says the user can run "codegraph init" there.

Docs that no longer matched the code:
- AGENTS.md listed 4 of the 11 installer targets, called a new one "a
  5th agent", said the contract suite covers "all 4" targets, and listed
  11 of the 24 subcommands. It now lists every target and subcommand,
  and names the hidden ones (serve --mcp, prompt-hook, and ui/web unless
  CODEGRAPH_UI=1).
- README's Supported Agents section said no instructions file is
  written, but Claude Code, Codex CLI, Gemini CLI and opencode get the
  short CodeGraph section. Its Codex entry now says the Codex IDE
  extension reads the same config: $CODEX_HOME/config.toml (default
  ~/.codex/config.toml) for a global install, and .codex/config.toml,
  used only in a trusted project, for a local one.
- README's WSL2 "Transport closed" entry named only the in-process
  fallback. The daemon also moves its socket to the temp directory when
  the project's filesystem can't host one.
- init -i and -iv leftovers in internal docs, scripts, the add-lang
  skill and the repo's own Cursor rule, which now also leaves indexing
  to the user. Two stay on purpose: scripts/agent-eval/audit.sh can
  audit a published version older than 0.9.8, where init built the
  index only with -i (now commented there), and
  docs/benchmarks/codegraph-ab-matrix.md records a 0.9.4 run.

Validation:
- worktree-detection.test.ts: on main 6177692's worktree.ts and
  tools.ts, 4 of its 20 tests fail (the CLI wording, the agent wording,
  the read-tool notice and codegraph_status). With only the worktree.ts
  fix, the codegraph_status test still fails. All 20 pass here, and so
  do mcp-nested-repo-projectpath.test.ts's 13.
- Full suite on this patch over 6177692 (CODEGRAPH_KERNEL_EXPECT=1,
  4 workers): 815 files (809 passed, 6 skipped), 9608 tests passed,
  0 failed, 86 skipped.
- Rebased unchanged onto f67eafc. Its three new commits share only
  CHANGELOG.md with this PR, in other bullets. On the rebased tree these
  pass (81 tests): worktree-detection, mcp-nested-repo-projectpath, and
  the new commits' default-export-declaration,
  rust-path-attribute-modules, rust-path-super-glob,
  search-special-characters and strip-comments.

Reimplements #549 by @ashishpatel26, colbymchenry#651 by @GaoSSR, colbymchenry#885 and colbymchenry#886 by @Dashsoap, and colbymchenry#1083 by @zengwenliang416.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit cb7fb61)

* fix(site): agent lists include GitHub Copilot, and Codex notes its IDE extension (colbymchenry#3066)

The site's agent lists stopped at Kiro, though the installer configures
GitHub Copilot in VS Code, the Copilot CLI and JetBrains IDEs, and the
README lists it everywhere. Copilot is now in every list: Integrations,
Installation (the detected agents and the restart line), Introduction,
Quickstart, Indexing and the landing page.

The Integrations page's Codex CLI entry now says the Codex IDE
extension reads the same config: $CODEX_HOME/config.toml (default
~/.codex/config.toml) for a global install, and .codex/config.toml,
which Codex uses only in a trusted project, for a local one, as
src/installer/targets/codex.ts writes them.

Installation said "Cursor and Kiro get the MCP config only", which,
with more agents listed, read as if every other agent gets an
instructions-file section. It now names the four that do (Claude Code,
Codex CLI, Gemini CLI and opencode); the others get the MCP config
only.

Validation: site npm ci and astro build pass (21 pages); the built
pages carry the new entries.

Reimplements the site half of #549 by @ashishpatel26.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 910e9bd)

* fix(add-lang): the extraction check finds codegraph's npm shim on Windows, and takes CODEGRAPH_BIN (colbymchenry#3047)

scripts/add-lang/verify-extraction.mjs ran execFileSync('codegraph', ...).
On Windows, npm installs codegraph as a .cmd shim (so do npm link and
local-install.sh), and Node neither finds a .cmd by bare name nor runs
one without a shell. The check always died with "spawnSync codegraph
ENOENT" (exit 2), and /add-lang's bench.sh read that as broken
extraction and skipped its A/B.

The script now asks, in order:
- CODEGRAPH_BIN, when set. A .js, .mjs or .cjs path runs with the
  current node; anything else runs directly (a .cmd or .bat through
  cmd.exe).
- On Windows, the codegraph Windows itself would pick: the first PATH
  folder holding codegraph under one of PATHEXT's runnable extensions.
  A .cmd or .bat runs through cmd.exe (shell: true) as one command
  string. Each token is quoted for the C runtime and caret-escaped for
  cmd, so spaces, &, parentheses, a %NAME% and a trailing backslash in
  the repo path arrive intact. An .exe or .com needs no shell.
- Otherwise codegraph by bare name, exactly as before. On macOS and
  Linux without CODEGRAPH_BIN, the call and the output are unchanged.

bench.sh takes CODEGRAPH_BIN too. It makes a path absolute (indexing
runs from inside the repo), exports it so verify-extraction.mjs reads
the index with the same build, and runs its own --version and init calls
with it. Without it, bench.sh behaves exactly as before. The A/B's MCP
server stays run-all.sh's own CG_BIN.

Validation (Windows 11, Node 22.22.0):
- New __tests__/add-lang-verify-extraction.test.ts covers CODEGRAPH_BIN
  with a .js stand-in (all platforms), a .cmd shim on PATH with an
  awkward repo path (Windows only), and the bare-name call (macOS and
  Linux only). With main's script both tests that run here fail ("could
  not read codegraph status"); with the fix, 2 pass and 1 is skipped.
- The fixture from PR 1246's review (a codegraph.cmd on PATH): main
  exits 2 with ENOENT; the fix reads the status.
- 13 scenarios with npm's own generated shims, and a real index in a
  folder named "lua repo & co (x86) 100%": main fails every one that
  needs the shim or CODEGRAPH_BIN, and the fix passes them with the
  exact argv reaching codegraph. With no codegraph anywhere, both exit 2
  the same way.
- bench.sh with the A/B stubbed: with npm link's shims on PATH, main's
  copy skips the A/B (verify exit 2) and the fix reaches it (PASS).
  CODEGRAPH_BIN as an absolute or a relative path reaches it too, and a
  missing one stops with a clear message.
- With process.platform reported as linux or darwin, main's script and
  the new one make the same execFileSync call and print the same output.

Raised by PR 1246 (@bslatner).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 1941caf)

* chore: keep the kernel's vendored grammars out of GitHub's language stats (colbymchenry#3045)

GitHub reports the repository as 79% C and 18% TypeScript. Nearly all of
that C is the third-party tree-sitter grammars the native kernel compiles
from source (codegraph-kernel/grammars/, 29 files, about 78 MB).

A new .gitattributes marks every file under codegraph-kernel/grammars/
linguist-vendored, so Linguist leaves them out of the language
statistics, and marks the five generated parser.c files
linguist-generated, so pull requests show those collapsed. The
hand-written scanner.c files stay expanded in diffs on purpose: that is
where C gets patched or added (PR 2173, and the open PR 2866), and every
contributor PR is security-reviewed, so such a change must stay visible.
The patterns use /** because an attributes pattern that names a
directory does not reach the files inside it.

Validation: git check-attr over all 1,722 tracked files reports
linguist-vendored set on exactly the 29 files under
codegraph-kernel/grammars/ and linguist-generated set on exactly the
five parser.c files; scanner.c, the headers and the LICENSE files are
vendored only, and everything else, src/index.ts included, has neither.
In a scratch repository the bare directory pattern leaves parser.c
unspecified. Git does not act on linguist attributes and codegraph never
reads .gitattributes, so builds and tests are unaffected.

Raised by PR 2513 (@build8421) and PR 1668 (@kidonng).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 407ec4f)

* docs(changelog): drop duplicate and unported-fix entries the union merge added

---------

Co-authored-by: Colby Mchenry <me@colbymchenry.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Eric Minish <eric.minish@gmail.com>
Co-authored-by: Max Hsu <maxmilian@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

mcp: a projectPath naming a file inside a project returns isError ("Path is not a directory")

2 participants