Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/skills/codegraph-build-validation/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Under Bun the suite runs green via `__tests__/bun-homedir.setup.ts` (an `os.home

`copy-assets` (called from `build`) copies `src/db/schema.sql` into `dist/`. **Any new SQL asset must be copied or it won't ship.**

The native kernel (`codegraph-kernel/`, Rust) is the **only parser**: every grammar is compiled into it (crates.io pins plus the vendored C under `codegraph-kernel/grammars/`, provenance in `grammars/PROVENANCE.md`). There is no wasm fallback. A source checkout needs a prebuild at `codegraph-kernel/prebuilds/<platform>-<arch>/codegraph-kernel.node`; `npm test` builds it through `scripts/ensure-kernel.mjs` when it is missing or was built from other kernel sources (needs a Rust toolchain), release bundles ship it, and a platform without a prebuild is unsupported (the CLI says so at startup). Languages with a bespoke walker are extracted in Rust; the rest are parsed by the kernel and walked by the generic TypeScript extractor over the serialized tree (`src/extraction/parse-tree.ts`). The whole-graph golden dumps (`__tests__/kernel-golden-dumps.test.ts`) are the regression gate for any extraction change; see `docs/design/kernel-only-extraction-plan.md`.
The native kernel (`codegraph-kernel/`, Rust) is the **only parser**: every grammar is compiled into it (crates.io pins plus the vendored C under `codegraph-kernel/grammars/`, provenance in `grammars/PROVENANCE.md`). There is no wasm fallback. A source checkout needs a prebuild at `codegraph-kernel/prebuilds/<platform>-<arch>/codegraph-kernel.node`; `npm test` builds it through `scripts/ensure-kernel.mjs` when it is missing or was built from other kernel sources (needs Rust 1.89 or newer), release bundles ship it, and a platform without a prebuild is unsupported (the CLI says so at startup). Languages with a bespoke walker are extracted in Rust; the rest are parsed by the kernel and walked by the generic TypeScript extractor over the serialized tree (`src/extraction/parse-tree.ts`). The whole-graph golden dumps (`__tests__/kernel-golden-dumps.test.ts`) are the regression gate for any extraction change; see `docs/design/kernel-only-extraction-plan.md`.

One other build step writes into `dist/` and is subject to the same rule: `build:ui` builds the
browser viewer into `dist/viewer/` (never `dist/ui/` — that's the terminal ui).
Expand Down
57 changes: 57 additions & 0 deletions .github/workflows/daemon-lifecycle.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Daemon installation lifecycle
on:
pull_request:
branches: [fork/consolidated]
paths:
- 'src/mcp/**'
- 'src/extraction/**'
- 'src/codegraph.ts'
- 'codegraph-kernel/**'
- '__tests__/daemon-*.test.ts'
- '__tests__/mcp-*.test.ts'
- '__tests__/writer-lock.test.ts'
- '__tests__/writer-handover-ordering.test.ts'
- '__tests__/fixtures/writer-lock-ordering.cjs'
- '__tests__/query-pool.test.ts'
- '__tests__/status-index-health.test.ts'
- '__tests__/fixtures/older-daemon.cjs'
- '.github/workflows/daemon-lifecycle.yml'
permissions:
contents: read
jobs:
lifecycle:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 25
env:
CARGO_BUILD_JOBS: 2
CARGO_PROFILE_RELEASE_DEBUG: 0
steps:
- name: Check out the tested commit
shell: bash
run: |
git init .
git config core.autocrlf false
git remote add origin "https://github.com/$GITHUB_REPOSITORY.git"
git fetch --no-tags --depth=1 origin "$GITHUB_SHA"
git checkout --detach FETCH_HEAD
- name: Use the runner's Node 24 installation
shell: pwsh
run: |
$versions = Get-ChildItem "$env:RUNNER_TOOL_CACHE/node" -Directory
$version = $versions | Where-Object Name -like '24.*' | Sort-Object { [version]$_.Name } -Descending | Select-Object -First 1
if (!$version) { throw 'Node 24 is missing from the runner tool cache' }
$arch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString().ToLowerInvariant()
$nodeDir = Join-Path $version.FullName $arch
if (!$IsWindows) { $nodeDir = Join-Path $nodeDir 'bin' }
$nodeExe = if ($IsWindows) { 'node.exe' } else { 'node' }
if (!(Test-Path (Join-Path $nodeDir $nodeExe))) { throw 'Node 24 executable is missing' }
Add-Content $env:GITHUB_PATH $nodeDir
- run: node -e "if (process.versions.node.split('.')[0] !== '24') process.exit(1)"
- run: npm ci --ignore-scripts
- run: npm run build:kernel
- run: npm run build
- run: npx vitest run __tests__/daemon-install-check.test.ts __tests__/daemon-older-version.test.ts __tests__/cli-version.test.ts __tests__/daemon-registry.test.ts __tests__/daemon-pid-reuse.test.ts __tests__/daemon-lock-refresh.test.ts __tests__/mcp-daemon.test.ts __tests__/daemon-client-liveness.test.ts __tests__/daemon-bind-failure.test.ts __tests__/daemon-socket-fallback.test.ts __tests__/daemon-rendezvous-case.test.ts __tests__/writer-lock.test.ts __tests__/writer-handover-ordering.test.ts __tests__/mcp-writer-lock.test.ts __tests__/mcp-projectpath-lifecycle.test.ts __tests__/query-pool.test.ts __tests__/status-index-health.test.ts --maxWorkers=2 --minWorkers=1
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

### Breaking Changes

- Source builds require Rust 1.89 or newer for native writer coordination.

- **The native engine is now the only parser; the older WebAssembly parser is gone.** Every supported language is built into the native engine, so there is no fallback for a platform without a native build. Release bundles cover macOS (Intel and Apple silicon), Linux (x64 and arm64, glibc) and Windows (x64 and arm64); other platforms need a from-source build with a Rust toolchain, and CodeGraph now says so at startup instead of failing later. Installs are about 40% smaller and the MCP server starts one process lighter. `CODEGRAPH_KERNEL_LANGS` and `CODEGRAPH_NO_RELAUNCH` no longer do anything; `CODEGRAPH_KERNEL=0` now only switches the per-language native walkers off for debugging (every file is still parsed natively).
- **Node.js 25 and newer are no longer refused.** The block existed for a V8 bug in compiling the old parser's grammars. Those versions are untested rather than unsupported.
- **Node.js 22.13 or Bun 1.4.0 is now the minimum.** Both are the first releases where the built-in `node:sqlite` loads without a flag; older ones failed at the first database open. CodeGraph now says so at startup (`CODEGRAPH_ALLOW_UNSAFE_NODE=1` skips the check). `package.json` declares both in `engines`.
Expand Down Expand Up @@ -87,6 +89,8 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

### Fixes

- Writer handovers, stale-lock cleanup, readiness updates and releases now share an OS file lock, preventing a competing launcher from overwriting a newer writer. The OS releases coordination locks on process exit. Contended retirement retries release while the owner stays alive; legacy daemons without coordinated handover stay running until their old sessions are restarted.

- Python member calls no longer keep a receiver's original type after it is reassigned on a later line.

- Source builds now rebuild the native kernel after edits to new, untracked source files instead of using an older prebuild.
Expand Down Expand Up @@ -286,6 +290,18 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- Local JavaScript and TypeScript calls stay connected through linked packages and imports configured by a nested `baseUrl` (#1715).

- Reading indexed files on Windows no longer produces false edit warnings when NTFS last-access updates are enabled. Thanks @JJordan0C. (#1451, #1472)

- In VB.NET, every member of a `Structure` is now indexed, including its fields, properties, methods, constructors and nested enums. Before, only the first member was, so the rest could not be found and their callers looked empty.

- In VB.NET and C#, what a property's `Get` and `Set` code calls, creates and reads now belongs to that property, as do C#'s `get => …` accessors and `=> …` property bodies. Before, it was dropped, so a method used only from a property looked unused.

- In VB.NET, a field or property initializer like `= Compute()` or `As New List(Of Order)` now links what it calls and creates, and so do a `Custom Event`'s `AddHandler`, `RemoveHandler` and `RaiseEvent` blocks. Re-index VB.NET and C# projects after upgrading.

- Upgrading CodeGraph while an agent session is open no longer leaves the old version's background server in charge of your project: the first session started from the new install stops it and starts a current one in its place, even while sessions opened before the upgrade are still running. That old server could no longer load the language parsers the upgrade removed, so it saved every file it re-indexed with no symbols, while new sessions could only read the index beside it without keeping it up to date. A background server from a newer install is never stopped, and sessions opened before the upgrade keep the old version until you restart them. Thanks @lipchey for the report. (#2335)

- A file is no longer saved with no symbols when its language parser can't be loaded, which is what happened to every file a background server re-indexed after an upgrade removed its install: the file keeps what it had and is indexed again once the parser loads, and files an earlier version emptied this way are re-indexed by the next sync. A background server also exits on its own once its install is upgraded or removed, so the next session starts one from the current install. Thanks @lipchey for the report. (#2335)

- `codegraph status` no longer says the index is up to date while indexed files are missing their symbols: it now names files the parser couldn't read and files stored without their symbols (which `codegraph sync` repairs), `status --json` counts both, and `codegraph files --json` lists each file's recorded errors. Thanks @lipchey for the report. (#2336)
#### MCP / indexing

- Full indexes of large projects no longer sometimes run two to three times slower than usual. A timing clash between two background database maintenance steps could switch reference resolution from several threads to one partway through.
Expand Down
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@ Follow [@getcodegraph](https://x.com/getcodegraph) on X for updates.

## About this fork

This is **bompus/codegraph**, a fork of [colbymchenry/codegraph](https://github.com/colbymchenry/codegraph). Its default branch, `fork/consolidated`, contains upstream `main` through [`6560052a`](https://github.com/colbymchenry/codegraph/commit/6560052a6f856855d3f71eee838fd66ccfa4285d) (v1.6.2, merged 2026-10-03) plus the fork's own work, and it takes upstream changes as they land. Changes that suit upstream are also offered there as pull requests.
This is **bompus/codegraph**, a fork of [colbymchenry/codegraph](https://github.com/colbymchenry/codegraph). Its default branch, `fork/consolidated`, contains upstream `main` through [`dea076fd`](https://github.com/colbymchenry/codegraph/commit/dea076fd1e9fa3e236e93fa915bb0feebe73579d) (after v1.6.2, merged 2026-10-05) plus the fork's own work, and it takes upstream changes as they land. Changes that suit upstream are also offered there as pull requests.

The fork publishes no releases. The install scripts, npm package, badges and `codegraph upgrade` further down this page install **upstream's** releases. To run the fork, build it from source (below).

### Install the fork from source

You need Node.js 22.13 or newer (or Bun 1.4.0 or newer), git, and a [Rust toolchain](https://rustup.rs/) for the native kernel.
You need Node.js 22.13 or newer (or Bun 1.4.0 or newer), git, and a [Rust toolchain](https://rustup.rs/) 1.89 or newer for the native kernel.

```bash
git clone https://github.com/bompus/codegraph.git
Expand All @@ -74,7 +74,7 @@ Then run `codegraph init` in each project, as in [Get Started](#get-started). In

### What the fork adds

Compared with upstream `main` at `6560052` (v1.6.2). Each item here and in the dispatch and framework lists below was checked against upstream's tree at that commit.
Compared with upstream `main` at `dea076fd` (after v1.6.2). Each item here and in the dispatch and framework lists below was checked against upstream's tree at that commit.

| Feature | Upstream | Fork | What it does |
|---|:-:|:-:|---|
Expand Down Expand Up @@ -106,6 +106,12 @@ Compared with upstream `main` at `6560052` (v1.6.2). Each item here and in the d

The other languages are the same in both, listed under [Supported Languages](#supported-languages).

C# property accessors and expression-bodied properties contribute calls and references owned by the property. VB.NET member bodies and field initializers also contribute their calls and references.

`codegraph status` reports files that need re-indexing and files with recorded parse errors. `status --json` includes `index.filesNeedingReindex` and `index.filesWithParseErrors`; `files --json` includes each file's extraction errors. A transient parser failure preserves the previous graph and retries on the next sync.

The MCP launcher can replace a daemon from an older release when its hello confirms coordinated writer handover. Legacy daemons stay running while new sessions serve reads without auto-sync; stop the old MCP sessions and daemon, then reconnect with the current install. A daemon exits when its installation is deleted or its package version changes. Different managed builds of the same release retain the fork's version-identity checks.

Dispatch and framework coverage the fork adds, by kind:

**Dispatch links**
Expand Down Expand Up @@ -1102,7 +1108,7 @@ Framework routing is validated the same way, on a canonical app per framework: E

**MCP server not connecting** — Your agent starts the server itself, so you don't launch it by hand. Make sure the project is initialized and indexed (`codegraph status`) and that the path in your MCP config is correct. If it still won't connect, re-run `codegraph install` to rewrite the config.

**Two `codegraph serve --mcp` on one project fight over the index / auto-sync stops** — CodeGraph allows one live MCP *writer* per project (the shared background daemon, or a single direct-mode process). Extra clients should proxy to that daemon. If you set `CODEGRAPH_NO_DAEMON=1`, run only one `serve --mcp` for that project; a second instance exits with a clear writer-lock error (see `writer.pid` under `.codegraph/`). Prefer leaving the daemon enabled so multiple MCP hosts share one watcher.
**Two `codegraph serve --mcp` on one project fight over the index / auto-sync stops** — CodeGraph allows one live MCP *writer* per project (the shared background daemon, or a single direct-mode process). Extra clients should proxy to that daemon. If you set `CODEGRAPH_NO_DAEMON=1`, run only one `serve --mcp` for that project; a second instance exits with a clear writer-lock error (see `writer.pid` under `.codegraph/`; its persistent `writer.pid.mutation.lock` coordinates ownership changes and needs no stale-lock deletion). Prefer leaving the daemon enabled so multiple MCP hosts share one watcher.

**MCP tool calls fail with `Transport closed` while `codegraph status`/`sync` are healthy** — almost always WSL2 with the project on a Windows drive (a `/mnt/c` or `/mnt/d` path), where the local socket CodeGraph uses to share one background server across sessions is unreliable. CodeGraph now falls back to serving the session in-process instead of dropping the connection, and keeps trying to reach the shared server in the background: first after 5 seconds, backing off to every 5 minutes. `CODEGRAPH_DAEMON_RETRY_MS` and `CODEGRAPH_DAEMON_RETRY_MAX_MS` set those two delays, and `CODEGRAPH_DAEMON_RETRY_MS=0` stops retrying. If you still hit it, set `CODEGRAPH_NO_DAEMON=1` in your MCP server's environment to skip the shared server entirely (each session runs in its own process). Moving the project onto the Linux-native filesystem (e.g. under `~/` instead of `/mnt/`) restores the shared server.

Expand Down
91 changes: 91 additions & 0 deletions __tests__/csharp-property-accessors.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* C# property accessor bodies belong to the property. `get { … }`,
* `set { … }`, `get => …` and an expression-bodied `=> …` property used to
* be skipped by both extractors, so the calls, instantiations and static
* reads written there were lost and a getter's callee looked unused.
*
* Runs against the native walker and the generic walker over native trees,
* which must agree.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { CodeGraph } from '../src';
import type { Node } from '../src/types';

const FILES: Record<string, string> = {
'Helper.cs': `public static class Helper {
public static int Compute(int n) => n;
public static void Store(int n) {}
public static int Arrow() => 1;
public static int GetA() => 1;
public static void SetA(int v) {}
}
public class Widget {}
public static class Defaults { public const string Name = "x"; }
`,
'Box.cs': `public class Box {
private int _x;
public int Value {
get { return Helper.Compute(_x); }
set { Helper.Store(value); _x = value; }
}
public int Arrow => Helper.Arrow();
public int Both { get => Helper.GetA(); set => Helper.SetA(value); }
public Widget Made { get { return new Widget(); } }
public string Label { get { return Defaults.Name; } }
private void Handle(int v) { }
public System.Action<int> Handler { get { return Pick(Handle); } }
}
`,
};

describe('C# property accessor bodies', () => {
let root = '';
let cg: CodeGraph | undefined;
let kernel: string | undefined;

beforeEach(() => {
root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-cs-accessors-'));
for (const [rel, content] of Object.entries(FILES)) fs.writeFileSync(path.join(root, rel), content);
kernel = process.env.CODEGRAPH_KERNEL;
});

afterEach(() => {
cg?.close();
cg = undefined;
fs.rmSync(root, { recursive: true, force: true });
if (kernel === undefined) delete process.env.CODEGRAPH_KERNEL;
else process.env.CODEGRAPH_KERNEL = kernel;
});

it.each(['default', 'generic'])('are walked as the property (%s)', async (backend) => {
if (backend === 'generic') process.env.CODEGRAPH_KERNEL = '0';
else delete process.env.CODEGRAPH_KERNEL;
cg = await CodeGraph.init(root, { index: true });
const graph = cg;
const member = (qualifiedName: string): Node => {
const node = graph.getNodesInFile('Box.cs').find((n) => n.qualifiedName === qualifiedName);
expect(node, qualifiedName).toBeDefined();
return node!;
};
const targets = (node: Node, kind: string): string[] =>
graph
.getOutgoingEdgesFrom([node.id])
.filter((e) => e.kind === kind)
.map((e) => graph.getNode(e.target)!.qualifiedName)
.sort();

expect(targets(member('Box::Value'), 'calls')).toEqual(['Helper::Compute', 'Helper::Store']);
expect(targets(member('Box::Arrow'), 'calls')).toEqual(['Helper::Arrow']);
expect(targets(member('Box::Both'), 'calls')).toEqual(['Helper::GetA', 'Helper::SetA']);
expect(targets(member('Box::Made'), 'instantiates')).toEqual(['Widget']);
expect(targets(member('Box::Label'), 'references')).toContain('Defaults');
// A method passed as a value inside an accessor is the property's
// reference, captured once — not the class's as well.
expect(targets(member('Box::Handler'), 'references')).toContain('Box::Handle');
expect(targets(member('Box'), 'references')).not.toContain('Box::Handle');
expect(targets(member('Box'), 'calls')).toEqual([]);
});
});
Loading
Loading