Skip to content

Add Julia language support and CI tests#373

Open
kongdd wants to merge 5 commits into
colbymchenry:mainfrom
kongdd:julia
Open

Add Julia language support and CI tests#373
kongdd wants to merge 5 commits into
colbymchenry:mainfrom
kongdd:julia

Conversation

@kongdd
Copy link
Copy Markdown

@kongdd kongdd commented May 24, 2026

This pull request introduces comprehensive support for the Julia programming language to the code extraction and analysis pipeline, including grammar integration, language detection, and extensive test coverage. Additionally, it improves Node.js compatibility for test environments and enhances test reliability on Windows by refining child process management. The most significant changes are grouped below.

Julia Language Support:

  • Added Julia grammar (tree-sitter-julia.wasm) to the extraction pipeline and updated the grammar loader to handle Julia as a special case, similar to other vendored grammars. (src/extraction/grammars.ts: [1] [2]
  • Registered .jl as a recognized Julia file extension and included Julia in language display names and the supported languages list. (src/extraction/grammars.ts: [1] [2]; src/types.ts: [3]
  • Implemented and registered a Julia-specific extractor in the language extractors registry. (src/extraction/languages/index.ts: [1] [2]
  • Enhanced the tree-sitter extraction framework to support Julia's unique naming and body resolution patterns, including custom handling for constants and improved name extraction hooks. (src/extraction/tree-sitter-types.ts: [1]; src/extraction/tree-sitter.ts: [2] [3] [4]
  • Added a comprehensive suite of tests for Julia extraction, covering detection, function/type/import extraction, struct fields, module handling, and more. (__tests__/extraction.test.ts: tests/extraction.test.tsR3903-R4060)

Node.js and CI Compatibility:

  • Updated the Node.js engine constraints to allow Node 26+ (in addition to 20–24) for compatibility with newer environments. (package.json: package.jsonL54-R54)
  • Added a GitHub Actions CI workflow to test across Node 24 and 26 on Ubuntu and macOS, ensuring cross-platform reliability. (.github/workflows/CI.yml: .github/workflows/CI.ymlR1-R33)

Test Suite Robustness and Windows Support:

  • Improved test child process management by introducing a robust stopChild function to ensure deterministic cleanup, especially on Windows, and refactored afterEach hooks to use this logic. (__tests__/mcp-initialize.test.ts: [1] [2] [3] [4]; __tests__/mcp-roots.test.ts: [5] [6] [7] [8]

These changes collectively enable Julia language analysis, ensure compatibility with the latest Node.js versions, and make the test suite more reliable across platforms.

Copilot AI review requested due to automatic review settings May 24, 2026 16:32
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds Julia language support to the tree-sitter extraction pipeline and updates Node/Vitest/CI configuration to work around Node/V8 WASM issues while expanding supported Node versions.

Changes:

  • Add Julia grammar + extractor + extension mapping and display name.
  • Introduce extractor hooks (getName, resolveBody) and Julia-specific extraction logic/tests.
  • Update Node version guards/config (engines, CLI Node-25 block, Vitest fork execArgv) and add CI matrix for Node 24/26.

Reviewed changes

Copilot reviewed 12 out of 14 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
vitest.config.ts Adds conditional --liftoff-only for Vitest fork pool based on Node major version.
src/types.ts Adds julia to supported Language union list.
src/extraction/tree-sitter.ts Adds getName + resolveBody hooks usage and a Julia-specific const extraction branch.
src/extraction/tree-sitter-types.ts Extends LanguageExtractor with optional getName hook.
src/extraction/languages/julia.ts New Julia extractor implementing name/signature/body handling and custom visitation.
src/extraction/languages/index.ts Registers the new Julia extractor.
src/extraction/grammars.ts Adds Julia wasm grammar mapping, .jl extension detection, and display name.
src/bin/codegraph.ts Narrows the Node safety guard to Node 25 only.
package.json Updates Node engine range to exclude 25 but allow 26+.
tests/mcp-roots.test.ts Updates MCP server spawn flags/env and adds helper for deterministic child teardown.
tests/mcp-initialize.test.ts Same MCP spawn/env changes and teardown helper as above.
tests/extraction.test.ts Adds Julia extraction test coverage.
.github/workflows/CI.yml Adds CI workflow running tests on Node 24/26 on macOS/Ubuntu.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

CODEGRAPH_NO_RELAUNCH: '1',
},
stdio: ['pipe', 'pipe', 'pipe'],
env: { ...process.env, CODEGRAPH_NO_RELAUNCH: '1' },
CODEGRAPH_NO_RELAUNCH: '1',
},
stdio: ['pipe', 'pipe', 'pipe'],
env: { ...process.env, CODEGRAPH_NO_RELAUNCH: '1' },
function spawnServer(cwd: string): ChildProcessWithoutNullStreams {
// --no-watch keeps the test deterministic and avoids watcher startup noise.
return spawn(process.execPath, [BIN, 'serve', '--mcp', '--no-watch'], {
return spawn(process.execPath, ['--liftoff-only', BIN, 'serve', '--mcp', '--no-watch'], {

function spawnServer(cwd: string): ChildProcessWithoutNullStreams {
return spawn(process.execPath, [BIN, 'serve', '--mcp'], {
return spawn(process.execPath, ['--liftoff-only', BIN, 'serve', '--mcp'], {
Comment on lines +88 to +109
async function stopChild(child: ChildProcessWithoutNullStreams | null): Promise<void> {
if (!child) return;
const pid = child.pid;
if (pid === undefined) return;

if (process.platform === 'win32') {
// On Windows, ChildProcess.kill('SIGKILL') is not a real SIGKILL and may
// leave descendants alive. Kill the full process tree deterministically.
try { spawnSync('taskkill', ['/PID', String(pid), '/T', '/F'], { stdio: 'ignore' }); } catch { /* ignore */ }
} else if (!child.killed) {
try { child.kill('SIGKILL'); } catch { /* ignore */ }
}

await new Promise<void>((resolve) => {
if (child.exitCode !== null) return resolve();
const timer = setTimeout(resolve, 2000);
child.once('exit', () => {
clearTimeout(timer);
resolve();
});
});
}
Comment on lines +123 to +126
if (!child.killed) child.kill('SIGKILL');
if (child.exitCode === null) {
await new Promise<void>(resolve => child!.once('close', resolve));
}
Comment on lines +105 to +126
async function stopChild(child: ChildProcessWithoutNullStreams | null): Promise<void> {
if (!child) return;
const pid = child.pid;
if (pid === undefined) return;

if (process.platform === 'win32') {
// On Windows, ChildProcess.kill('SIGKILL') is not a real SIGKILL and may
// leave descendants alive. Kill the full process tree deterministically.
try { spawnSync('taskkill', ['/PID', String(pid), '/T', '/F'], { stdio: 'ignore' }); } catch { /* ignore */ }
} else if (!child.killed) {
try { child.kill('SIGKILL'); } catch { /* ignore */ }
}

await new Promise<void>((resolve) => {
if (child.exitCode !== null) return resolve();
const timer = setTimeout(resolve, 2000);
child.once('exit', () => {
clearTimeout(timer);
resolve();
});
});
}
Comment on lines +137 to 143
if (child) {
if (!child.killed) child.kill('SIGKILL');
if (child.exitCode === null) {
await new Promise<void>(resolve => child!.once('close', resolve));
}
child = null;
}
Comment thread vitest.config.ts
include: ['__tests__/**/*.test.ts'],
poolOptions: {
forks: {
execArgv: NEEDS_LIFTOFF_ONLY ? ['--liftoff-only'] : [],
Comment on lines +1187 to +1203
} else if (this.language === 'julia') {
// Julia: const_statement → assignment → identifier (name) [op] value
const assignment = node.namedChild(0);
if (assignment) {
const nameNode = assignment.namedChild(0);
if (nameNode?.type === 'identifier') {
const name = getNodeText(nameNode, this.source);
const valueNode = assignment.namedChildCount > 1
? assignment.namedChild(assignment.namedChildCount - 1)
: null;
const initValue = valueNode && valueNode !== nameNode
? getNodeText(valueNode, this.source).slice(0, 100)
: undefined;
const initSignature = initValue ? `= ${initValue}` : undefined;
this.createNode('constant', name, nameNode, { docstring, signature: initSignature, isExported });
}
}
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.

2 participants