Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
92e32bb
fix(node): every language and folder of an overloaded name shows in c…
colbymchenry Oct 30, 2026
fbb831c
fix(go): a package-level var or const named in uppercase is exported …
colbymchenry Oct 30, 2026
3a75d09
chore(deps): take the security fixes for devalue and postcss, drop an…
colbymchenry Oct 30, 2026
ab1db34
fix(deps): read .gitignore rules the way git does, with ignore 7.0.14…
colbymchenry Oct 30, 2026
dfd4052
fix(lock): only one process takes over a stale write lock (#3056)
colbymchenry Oct 30, 2026
25b86cb
fix(db): processes opening an index that needs a migration all open i…
colbymchenry Oct 30, 2026
e131f6e
fix(mcp): a session whose host stops reading its output shuts down cl…
colbymchenry Oct 30, 2026
b3da2c1
fix(astro,svelte): a self-closing <script /> no longer swallows the n…
maxmilian Oct 30, 2026
ac780c2
fix(search): a pasted call like leaf(entry) finds both names instead …
colbymchenry Oct 30, 2026
7ce04df
fix(mcp): a tools/call whose arguments aren't an object gets invalid …
colbymchenry Oct 30, 2026
5753521
fix(liquid): a variable named with a hyphen is indexed under its whol…
colbymchenry Oct 30, 2026
b10c5cc
docs: add a contributing guide and a documentation index (#3081)
colbymchenry Oct 30, 2026
f700428
test: adapt upstream's migration-concurrency and stdout-failure tests…
bompus Oct 30, 2026
583b1f3
fix: create the lock's private file exclusively; document the kernel …
bompus Oct 30, 2026
37dad55
docs: state the Node and Bun minimums the CLI enforces
bompus Oct 30, 2026
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
14 changes: 14 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

72 changes: 72 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Contributing to CodeGraph

Thanks for helping out. Small, focused changes are the easiest to review and land.

**Reporting a bug?** Open an [issue](https://github.com/colbymchenry/codegraph/issues) with your CodeGraph version (`codegraph --version`), Node.js version, operating system, the steps to reproduce it, what you expected, and what happened instead. A small public repository or snippet that shows the problem helps most.

**Asking for a feature?** Describe the use case and why what's there today doesn't cover it.

## Build from source

You need Git, npm, and Node.js 22.13 or later (or Bun 1.4 or later). Running from source uses Node's built-in `node:sqlite`; the published installers bundle their own Node, so this only matters here.

```bash
# Fork the repository on GitHub, then:
git clone https://github.com/<your-username>/codegraph.git
cd codegraph
git remote add upstream https://github.com/colbymchenry/codegraph.git

npm ci
npm run build
```

A globally installed `codegraph` doesn't run your changes, so run your build directly:

```bash
node dist/bin/codegraph.js --help # the build as it stands
npm run cli -- status # rebuild first, then run a command
npm run dev # recompile TypeScript on every save
```

`npm run build` covers the CLI, the SQL schema, the parser grammars and the browser viewer. Two builds are separate:

- `npm run build:kernel` builds the native extraction kernel, which this fork requires: the CLI refuses to start without it and the test setup builds it when missing. Install a Rust toolchain (1.89 or newer) and run it after `npm ci`.
- `npm run build:lib` builds the viewer's component library.

## Test your change

```bash
npx vitest run __tests__/extraction.test.ts # one file
npx vitest run __tests__/extraction.test.ts -t "TypeScript" # tests matching a name
npm run test:watch # rerun as you edit
npm test # everything
```

Run the focused tests while you work, then `npm test` before opening a pull request. Tests that run the compiled CLI use `dist/`, and the test setup rebuilds it first when it's stale.

- **Tests live in `__tests__/`** and use real files and real SQLite databases, with no database mocking. Create temporary folders with `fs.mkdtempSync` and remove them in `afterEach`.
- **A bug fix needs a test that fails without the fix.**
- **Behavior that differs by platform** gets a gated test, such as `it.runIf(process.platform === 'win32')(...)`, and needs checking on the platforms it affects. See [cross-platform validation](AGENTS.md#cross-platform-validation).
- **New language or framework support** needs validating on real repositories. Start with the `/add-lang` skill in [`.claude/skills/add-lang/SKILL.md`](.claude/skills/add-lang/SKILL.md), then follow the [validation methodology](docs/AGENTS.md#validation-methodology-required-for-every-new-languageframework) and the [coverage playbook](docs/design/dynamic-dispatch-coverage-playbook.md). Before extending a router or web framework, read [framework coverage](docs/design/framework-coverage.md).
- **A documentation-only change** doesn't need the test suite. Check its examples and links instead.

## Prepare a pull request

- Branch from the latest `main`, and keep to one concern. Unrelated formatting, CI or feature work belongs in its own pull request.
- Name commits `type(scope): what now works`, such as `fix(go): a constant in a const ( … ) group keeps its own doc comment`. Types in use are `feat`, `fix`, `docs`, `test`, `perf`, `chore` and `ci`.
- In the description, explain the problem, the resulting behavior, and what you ran, including any failures and any platform you couldn't test.
- Keep existing CLI commands and the default MCP tools working, unless the pull request exists to change them and says why.
- Add a line to `CHANGELOG.md` under `## [Unreleased]`, in `### New Features` or `### Fixes`, for anything a user would notice. Write one plain sentence about what changed for them, without internal file names or benchmark numbers. Changes to the `codegraph ui` viewer, which isn't released yet, go in [`docs/viewer-launch-changelog.md`](docs/viewer-launch-changelog.md) instead.
- Don't bump the version, and change `package-lock.json` only when you add or remove a dependency.
- Use public or made-up examples. Keep local paths, private code and machine-specific settings out of fixtures.

## What happens next

A maintainer reads every change closely before anything lands, and may ask for changes. Keep your branch up to date by rebasing on `main`.

A maintainer may also land your contribution as their own commit, rewritten where needed, instead of merging your branch. You're credited in the changelog either way.

## Find your way around

- [Documentation index](docs/README.md): guides, design notes and benchmarks.
- [AGENTS.md](AGENTS.md): the architecture and the development rules, which [CLAUDE.md](CLAUDE.md) imports for Claude Code.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ The [benchmark](#benchmark-results) and [speed](#built-for-speed--the-rust-kerne
- [Supported Languages](#supported-languages)
- [Measured cross-file coverage](#measured-cross-file-coverage)
- [Troubleshooting](#troubleshooting)
- [Contributing](#contributing)
- [License](#license)

## Get Started
Expand Down Expand Up @@ -1192,6 +1193,10 @@ Framework routing is validated the same way, on a canonical app per framework: E

**Very large repositories (hundreds of thousands of files), or a large `.codegraph/codegraph.db-wal` file** — The `-wal` file is SQLite's write-ahead log: writes waiting to be folded into `codegraph.db`. While a big index is being built, CodeGraph lets it grow in proportion to the index (soft threshold = the larger of 256 MB and a quarter of the index size, up to 2 GB) before folding it back, because folding too often is what made large indexes slow on ordinary disks. At rest it is trimmed to 64 MB, and a leftover from a killed session is folded and trimmed the next time the project opens — the index itself has no size limit. Two environment variables tune this: `CODEGRAPH_WAL_VALVE_MB` (the soft threshold during indexing) and `CODEGRAPH_WAL_HEAL_MB` (the resting size and the trim threshold). `CODEGRAPH_WAL_VALVE_DEBUG=1` prints every decision to stderr.

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) to build from source, test a change and prepare a pull request. The [documentation index](docs/README.md) links to the project's guides and design notes.

## License

MIT
Expand Down
91 changes: 87 additions & 4 deletions __tests__/extraction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5797,6 +5797,12 @@ import 'package:flutter/material.dart';
['{% liquid\n capture bare\n echo "Hi"\n endcapture\n%}', 'bare', 2, 2],
['{% liquid\r\n capture crlf # a note\r\n endcapture\r\n%}', 'crlf', 2, 2],
["{%- liquid\n capture 'quoted_line'\n endcapture\n-%}", 'quoted_line', 2, 2],
// A name with `-` in it is one name; the `-` of `-%}` trims whitespace.
['{% capture title-text %}Hi{% endcapture %}', 'title-text', 1, 0],
['{% capture title-text-%}Hi{% endcapture %}', 'title-text', 1, 0],
['{%- capture title-text -%}Hi{%- endcapture -%}', 'title-text', 1, 0],
["{% capture 'hero-alt' %}Hi{% endcapture %}", 'hero-alt', 1, 0],
['{% liquid\n capture hero-alt\n endcapture\n%}', 'hero-alt', 2, 2],
] as const)('reads a capture declaration from %j', (code, name, line, column) => {
const result = extractFromSource('sections/card.liquid', code);
const variables = result.nodes.filter((n) => n.kind === 'variable');
Expand All @@ -5820,10 +5826,27 @@ import 'package:flutter/material.dart';
expect(result.nodes.filter((n) => n.kind === 'variable').map((n) => n.name)).toEqual(['text']);
});

it('never cuts a capture name short', () => {
// Liquid allows `-` and `.` in a name, which the assign rule doesn't read
// either; such a name gets no node rather than a wrong one.
const code = ['{% capture ghost-name %}x{% endcapture %}', '{% capture ghost.name %}x{% endcapture %}'].join('\n');
it('reads an assign name with `-` in it whole', () => {
const code = [
"{% assign title-text = 'Sale' %}",
'{%- assign page-title=page.title -%}',
'{% liquid',
' assign hero-image = section.settings.image',
'%}',
].join('\n');
const variables = extractFromSource('sections/card.liquid', code).nodes.filter((n) => n.kind === 'variable');
expect(variables.map((n) => [n.name, n.startLine])).toEqual([['title-text', 1], ['page-title', 2], ['hero-image', 4]]);
});

it('never cuts a name short', () => {
// Liquid allows `.` in a name too, which neither rule reads; such a name
// gets no node rather than a wrong one.
const code = [
'{% capture ghost.name %}x{% endcapture %}',
'{% capture ghost-name.x %}x{% endcapture %}',
'{% assign ghost.name = 1 %}',
'{% assign ghost-name.x = 1 %}',
].join('\n');
expect(extractFromSource('sections/card.liquid', code).nodes.filter((n) => n.kind === 'variable')).toEqual([]);
});

Expand Down Expand Up @@ -10538,6 +10561,66 @@ const value = 42;
);
expect(containEdges.length).toBeGreaterThan(0);
});

it('should not let a self-closing <script /> swallow the next script (#2989)', () => {
const code = `---
import { formatDate } from '../utils/format';
const inlineScript = 'window.example = true';
---
<script is:inline set:html={inlineScript} />
<time>{formatDate(new Date())}</time>
<script>
const ready = true;
console.log(ready);
</script>
`;
const result = extractFromSource('Page.astro', code);

expect(result.errors).toEqual([]);
const ready = result.nodes.find((n) => n.name === 'ready');
expect(ready).toBeDefined();
expect(ready?.startLine).toBe(8);
// The markup between the two scripts is still scanned.
const call = result.unresolvedReferences.find(
(ref) => ref.referenceKind === 'calls' && ref.referenceName === 'formatDate' && ref.line === 6
);
expect(call).toBeDefined();
});

it('should not let a self-closing <style /> hide the markup after it (#2989)', () => {
const code = `<style is:global />
<p>{formatDate(date)}</p>
<style>.a { color: red; }</style>
`;
const result = extractFromSource('Styled.astro', code);

const call = result.unresolvedReferences.find(
(ref) => ref.referenceKind === 'calls' && ref.referenceName === 'formatDate' && ref.line === 2
);
expect(call).toBeDefined();
});

it("still reads a call in a self-closing <script />'s attributes (#2989)", () => {
// A self-closing tag has no body for the template scan to skip:
// `set:html={…}` runs when the page renders, like any attribute expression.
const code = `---
import { serializeJsonLd } from '../lib/jsonld';
const schema = {};
---
<script type="application/ld+json" set:html={serializeJsonLd(schema)} />
<script>
const ready = true;
</script>
`;
const result = extractFromSource('Schema.astro', code);

expect(result.errors).toEqual([]);
expect(result.nodes.find((n) => n.name === 'ready')?.startLine).toBe(7);
const call = result.unresolvedReferences.find(
(ref) => ref.referenceKind === 'calls' && ref.referenceName === 'serializeJsonLd' && ref.line === 5
);
expect(call).toBeDefined();
});
});

describe('Instantiates + Decorates edge extraction', () => {
Expand Down
Loading
Loading