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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### New Features

- `rust-hybrid` indexing now handles C++ baseline symbols through the Rust-owned path, so C++ functions, classes, structs, namespaces, enums, type aliases, includes, and calls no longer depend on TypeScript fallback extraction. (#678)
- `.h` header files are now classified as C, C++, or Objective-C by content sniffing on both the Rust and TypeScript sides, using aligned regex patterns for consistent routing. (#678)
- `rust-hybrid` metadata now includes `cpp` in `rustOwnedLanguages`, so C++ files are correctly assigned to the Rust engine with no TypeScript fallback. (#678)

### Changes

- Removed the TypeScript-owned C++ extractor (`c-cpp.ts`) and `tree-sitter-cpp.wasm` grammar dependency, as C++ extraction is now fully handled by the Rust core via `tree-sitter-cpp` crate. (#678)


## [0.11.0] - 2026-07-14

Expand Down
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -587,8 +587,8 @@ zcodegraph index --engine typescript

| State | Meaning | Current languages / files |
|---|---|---|
| Rust-owned | Indexed by the Rust core on the default `rust-hybrid` path. | JavaScript, JSX, TypeScript, TSX, Go, Python, Rust, C, Java. |
| TS-indexed | Indexed by the TypeScript indexer as the mature multi-language path. | C#, PHP, Ruby, C++, Objective-C, Swift, Kotlin, Scala, Dart, Svelte, Vue, Liquid, Pascal/Delphi, Lua, Luau, and other supported non-Rust-owned sources. |
| Rust-owned | Indexed by the Rust core on the default `rust-hybrid` path. | JavaScript, JSX, TypeScript, TSX, Go, Python, Rust, C, C++, Java. |
| TS-indexed | Indexed by the TypeScript indexer as the mature multi-language path. | C#, PHP, Ruby, Objective-C, Swift, Kotlin, Scala, Dart, Svelte, Vue, Liquid, Pascal/Delphi, Lua, Luau, and other supported non-Rust-owned sources. |
| Hybrid fallback | `rust-hybrid` uses TypeScript fallback for a file or language and reports it in status/doctor. | Expected for non-Rust-owned supported files, and for Rust-owned parse gaps when recoverable. |
| Not covered | Not indexed as source symbols/edges. | Unsupported extensions, ignored paths, default-excluded dependency/build/cache directories, and files over the size limit. |

Expand Down
6 changes: 3 additions & 3 deletions __tests__/rust-index-engine-cli-fallback.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,10 @@ describe('zcodegraph rust-hybrid fallback degraded status and doctor output', ()
const plan = planRustHybridAssignments(tempDir);

expect(plan.rustOwnedFiles).toContain('plain.h');
expect(plan.fallbackFiles).toContain('widget.h');
expect(plan.rustOwnedFiles).toContain('widget.h');
expect(plan.fallbackFiles).toContain('View.h');
expect(plan.engineByLanguage).toMatchObject({ c: 'rust', cpp: 'typescript', objc: 'typescript' });
expect(plan.fallbackByLanguage).toMatchObject({ cpp: 1, objc: 1 });
expect(plan.engineByLanguage).toMatchObject({ c: 'rust', cpp: 'rust', objc: 'typescript' });
expect(plan.fallbackByLanguage).toMatchObject({ objc: 1 });
});

it('records Rust-owned per-file gap diagnostics without same-language TypeScript fallback append', () => {
Expand Down
81 changes: 81 additions & 0 deletions __tests__/rust-index-engine-cli-language-smoke.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,87 @@ describe('zcodegraph rust index language framework and MCP smoke behavior', () =
}
}, 30_000);

it('indexes C++ as Rust-owned under rust-hybrid', () => {
fs.writeFileSync(
path.join(tempDir, 'widget.h'),
[
'#pragma once',
'',
'namespace app {',
'class Widget {',
'public:',
' Widget();',
' ~Widget();',
' int render();',
'};',
'}',
'',
'using WidgetPtr = Widget*;',
].join('\n') + '\n',
);
fs.writeFileSync(
path.join(tempDir, 'main.cpp'),
[
'#include <cstdio>',
'#include "widget.h"',
'',
'namespace app {',
'Widget::Widget() {}',
'Widget::~Widget() {}',
'int Widget::render() { return 42; }',
'}',
'',
'int main() {',
' Widget w;',
' return w.render();',
'}',
].join('\n') + '\n',
);

const result = runZcodegraphCli(tempDir, ['index', '--quiet'], {
ZCODEGRAPH_RUST_CORE_BINARY: RUST_CORE_BIN,
});

expect(result.status, `stdout:\n${result.stdout}\nstderr:\n${result.stderr}`).toBe(0);
const cg = CodeGraph.openSync(tempDir);
try {
expect(cg.getStats().filesByLanguage.cpp).toBe(2);
const expectations = [
['cstdio', 'import'],
['widget.h', 'import'],
['Widget', 'class'],
['app', 'namespace'],
['WidgetPtr', 'type_alias'],
['main', 'function'],
] as const;
for (const [name, kind] of expectations) {
expect(
cg.searchNodes(name).some((match) => match.node.name === name && match.node.kind === kind && match.node.language === 'cpp'),
`${name} (${kind}) should be indexed as C++`,
).toBe(true);
}

const mainFn = cg.searchNodes('main').find((match) => match.node.kind === 'function' && match.node.language === 'cpp')?.node;
const renderFn = cg.searchNodes('render').find((match) => match.node.kind === 'function' && match.node.language === 'cpp')?.node;
expect(mainFn).toBeDefined();
expect(renderFn).toBeDefined();
const calls = cg.getOutgoingEdges(mainFn!.id).filter((edge) => edge.kind === 'calls');
expect(calls.some((edge) => edge.target === renderFn!.id)).toBe(true);

const buildInfo = cg.getIndexBuildInfo();
expect(buildInfo.engine).toBe('rust-hybrid');
expect(buildInfo.hybrid).toMatchObject({
rustOwnedLanguages: expect.arrayContaining(['cpp']),
engineByLanguage: { cpp: 'rust' },
fallbackByLanguage: {},
fallbackFileCount: 0,
fallbackReasonTaxonomy: {},
});
} finally {
cg.close();
}
}, 30_000);

it('reports Rust index-engine metadata through MCP status', async () => {
const indexResult = runZcodegraphCli(tempDir, ['index', '--engine', 'rust', '--quiet'], {
ZCODEGRAPH_RUST_CORE_BINARY: RUST_CORE_BIN,
Expand Down
2 changes: 2 additions & 0 deletions crates/zcodegraph-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ publish = false
[dependencies]
dhat = { version = "0.3", optional = true }
libc = "0.2"
regex = "1"
rusqlite = { version = "0.32", features = ["bundled"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sha2 = "0.10"
tree-sitter = "0.24"
tree-sitter-c = "0.23"
tree-sitter-cpp = "0.23"
tree-sitter-go = "0.23"
tree-sitter-java = "0.23"
tree-sitter-javascript = "0.23"
Expand Down
Loading