@vizalice/codegraph 1.5.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -18
- package/dist/bin/codegraph.js +127 -25
- package/dist/bin/codegraph.js.map +1 -1
- package/dist/context/index.d.ts +12 -0
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +47 -8
- package/dist/context/index.js.map +1 -1
- package/dist/db/index.d.ts +2 -0
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +23 -0
- package/dist/db/index.js.map +1 -1
- package/dist/db/queries.d.ts +39 -2
- package/dist/db/queries.d.ts.map +1 -1
- package/dist/db/queries.js +104 -19
- package/dist/db/queries.js.map +1 -1
- package/dist/directory.d.ts +29 -0
- package/dist/directory.d.ts.map +1 -1
- package/dist/directory.js +52 -0
- package/dist/directory.js.map +1 -1
- package/dist/extraction/grammars.d.ts.map +1 -1
- package/dist/extraction/grammars.js +38 -2
- package/dist/extraction/grammars.js.map +1 -1
- package/dist/extraction/index.d.ts +59 -0
- package/dist/extraction/index.d.ts.map +1 -1
- package/dist/extraction/index.js +175 -53
- package/dist/extraction/index.js.map +1 -1
- package/dist/extraction/languages/erlang.d.ts.map +1 -1
- package/dist/extraction/languages/erlang.js +58 -22
- package/dist/extraction/languages/erlang.js.map +1 -1
- package/dist/extraction/languages/rust.d.ts +20 -0
- package/dist/extraction/languages/rust.d.ts.map +1 -1
- package/dist/extraction/languages/rust.js +44 -20
- package/dist/extraction/languages/rust.js.map +1 -1
- package/dist/extraction/parse-pool.d.ts +7 -0
- package/dist/extraction/parse-pool.d.ts.map +1 -1
- package/dist/extraction/parse-pool.js +20 -1
- package/dist/extraction/parse-pool.js.map +1 -1
- package/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/dist/extraction/tree-sitter.js +66 -35
- package/dist/extraction/tree-sitter.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +68 -1
- package/dist/index.js.map +1 -1
- package/dist/installer/index.d.ts +2 -2
- package/dist/installer/index.d.ts.map +1 -1
- package/dist/installer/index.js +8 -7
- package/dist/installer/index.js.map +1 -1
- package/dist/installer/targets/codex.d.ts +22 -8
- package/dist/installer/targets/codex.d.ts.map +1 -1
- package/dist/installer/targets/codex.js +65 -48
- package/dist/installer/targets/codex.js.map +1 -1
- package/dist/installer/targets/types.d.ts +4 -4
- package/dist/mcp/daemon-manager.d.ts +1 -1
- package/dist/mcp/daemon-manager.d.ts.map +1 -1
- package/dist/mcp/daemon-manager.js +1 -1
- package/dist/mcp/daemon-manager.js.map +1 -1
- package/dist/mcp/daemon-paths.d.ts +6 -0
- package/dist/mcp/daemon-paths.d.ts.map +1 -1
- package/dist/mcp/daemon-paths.js +54 -0
- package/dist/mcp/daemon-paths.js.map +1 -1
- package/dist/mcp/daemon-registry.d.ts +10 -0
- package/dist/mcp/daemon-registry.d.ts.map +1 -1
- package/dist/mcp/daemon-registry.js +51 -3
- package/dist/mcp/daemon-registry.js.map +1 -1
- package/dist/mcp/daemon.d.ts +9 -6
- package/dist/mcp/daemon.d.ts.map +1 -1
- package/dist/mcp/daemon.js +10 -8
- package/dist/mcp/daemon.js.map +1 -1
- package/dist/mcp/engine.d.ts +3 -0
- package/dist/mcp/engine.d.ts.map +1 -1
- package/dist/mcp/engine.js +45 -3
- package/dist/mcp/engine.js.map +1 -1
- package/dist/mcp/explore-diagnostics.d.ts +2 -0
- package/dist/mcp/explore-diagnostics.d.ts.map +1 -1
- package/dist/mcp/explore-diagnostics.js +3 -0
- package/dist/mcp/explore-diagnostics.js.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +17 -4
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +17 -12
- package/dist/mcp/session.js.map +1 -1
- package/dist/mcp/tools.d.ts +17 -0
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +164 -25
- package/dist/mcp/tools.js.map +1 -1
- package/dist/project-config.d.ts +23 -0
- package/dist/project-config.d.ts.map +1 -1
- package/dist/project-config.js +39 -2
- package/dist/project-config.js.map +1 -1
- package/dist/resolution/c-fnptr-synthesizer.js +2 -2
- package/dist/resolution/c-fnptr-synthesizer.js.map +1 -1
- package/dist/resolution/callback-synthesizer.d.ts.map +1 -1
- package/dist/resolution/callback-synthesizer.js +38 -10
- package/dist/resolution/callback-synthesizer.js.map +1 -1
- package/dist/resolution/frameworks/swift.d.ts.map +1 -1
- package/dist/resolution/frameworks/swift.js +11 -1
- package/dist/resolution/frameworks/swift.js.map +1 -1
- package/dist/resolution/import-resolver.d.ts.map +1 -1
- package/dist/resolution/import-resolver.js +16 -1
- package/dist/resolution/import-resolver.js.map +1 -1
- package/dist/resolution/index.d.ts.map +1 -1
- package/dist/resolution/index.js +5 -1
- package/dist/resolution/index.js.map +1 -1
- package/dist/resolution/name-matcher.d.ts +30 -0
- package/dist/resolution/name-matcher.d.ts.map +1 -1
- package/dist/resolution/name-matcher.js +278 -0
- package/dist/resolution/name-matcher.js.map +1 -1
- package/dist/resolution/path-aliases.d.ts +6 -4
- package/dist/resolution/path-aliases.d.ts.map +1 -1
- package/dist/resolution/path-aliases.js +125 -17
- package/dist/resolution/path-aliases.js.map +1 -1
- package/dist/search/identifier-segments.d.ts +10 -0
- package/dist/search/identifier-segments.d.ts.map +1 -1
- package/dist/search/identifier-segments.js +29 -0
- package/dist/search/identifier-segments.js.map +1 -1
- package/dist/search/query-paths.d.ts +57 -0
- package/dist/search/query-paths.d.ts.map +1 -0
- package/dist/search/query-paths.js +298 -0
- package/dist/search/query-paths.js.map +1 -0
- package/dist/search/query-utils.d.ts +1 -1
- package/dist/search/query-utils.d.ts.map +1 -1
- package/dist/search/query-utils.js +12 -3
- package/dist/search/query-utils.js.map +1 -1
- package/dist/sync/watcher.d.ts +22 -0
- package/dist/sync/watcher.d.ts.map +1 -1
- package/dist/sync/watcher.js +51 -5
- package/dist/sync/watcher.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/upgrade/index.d.ts +1 -1
- package/dist/upgrade/index.d.ts.map +1 -1
- package/dist/upgrade/index.js +6 -1
- package/dist/upgrade/index.js.map +1 -1
- package/package.json +2 -2
package/dist/extraction/index.js
CHANGED
|
@@ -47,6 +47,7 @@ exports.buildDefaultIgnore = buildDefaultIgnore;
|
|
|
47
47
|
exports.buildScopeIgnore = buildScopeIgnore;
|
|
48
48
|
exports.discoverEmbeddedRepoRoots = discoverEmbeddedRepoRoots;
|
|
49
49
|
exports.findUnindexedIgnoredRepos = findUnindexedIgnoredRepos;
|
|
50
|
+
exports.getGitChangedFiles = getGitChangedFiles;
|
|
50
51
|
exports.scanDirectory = scanDirectory;
|
|
51
52
|
exports.scanDirectoryAsync = scanDirectoryAsync;
|
|
52
53
|
const fs = __importStar(require("fs"));
|
|
@@ -1057,7 +1058,14 @@ function getGitChangedFiles(rootDir) {
|
|
|
1057
1058
|
}
|
|
1058
1059
|
}
|
|
1059
1060
|
function collectGitStatus(repoDir, prefix, out, overrides, includeIgnored = null, exclude = null) {
|
|
1060
|
-
const output = (0, child_process_1.execFileSync)('git',
|
|
1061
|
+
const output = (0, child_process_1.execFileSync)('git',
|
|
1062
|
+
// `-uall` lists individual untracked files instead of collapsing an
|
|
1063
|
+
// entirely-untracked directory into one `?? dir/` entry, which would
|
|
1064
|
+
// otherwise be dropped here (only embedded git repos are recursed into
|
|
1065
|
+
// below). Nested untracked git repos still collapse to `?? repo/` even
|
|
1066
|
+
// with `-uall` — git never crosses a repo boundary — so the recursion
|
|
1067
|
+
// still handles them. (#1213)
|
|
1068
|
+
['status', '--porcelain', '--no-renames', '-uall'], { cwd: repoDir, encoding: 'utf-8', timeout: 10000, maxBuffer: 50 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
|
|
1061
1069
|
// This repo's own ignore rules — built-in defaults (#407) plus its .gitignore.
|
|
1062
1070
|
// Change detection must exclude the SAME files the full index does, but git
|
|
1063
1071
|
// status hides neither: it ignores nothing for *tracked* paths, and the
|
|
@@ -1344,10 +1352,47 @@ class ExtractionOrchestrator {
|
|
|
1344
1352
|
* hasn't run yet so single-file re-index paths can detect on the spot.
|
|
1345
1353
|
*/
|
|
1346
1354
|
detectedFrameworkNames = null;
|
|
1355
|
+
/**
|
|
1356
|
+
* Scope matcher for SCOPED syncs, memoized on the mtimes of the two root
|
|
1357
|
+
* files it is derived from (`codegraph.json`, `.gitignore`). See
|
|
1358
|
+
* {@link scopedSyncMatcher}.
|
|
1359
|
+
*/
|
|
1360
|
+
scopedMatcher = null;
|
|
1347
1361
|
constructor(rootDir, queries) {
|
|
1348
1362
|
this.rootDir = rootDir;
|
|
1349
1363
|
this.queries = queries;
|
|
1350
1364
|
}
|
|
1365
|
+
/**
|
|
1366
|
+
* The scope matcher a scoped sync applies to the paths it was handed — the
|
|
1367
|
+
* same `buildScopeIgnore` the full scan uses, so an explicitly-passed path
|
|
1368
|
+
* that is OUT of scope (a user `exclude` in `codegraph.json`, a `.gitignore`
|
|
1369
|
+
* rule, a built-in default) is treated exactly as the full walk would treat
|
|
1370
|
+
* it: absent, hence removed if tracked, never parsed (#1590).
|
|
1371
|
+
*
|
|
1372
|
+
* Memoized on the root config + root `.gitignore` mtimes: building the
|
|
1373
|
+
* matcher runs embedded-repo discovery (`git ls-files`), which would defeat
|
|
1374
|
+
* the scoped path's whole point (skipping O(repo) work) if paid per sync.
|
|
1375
|
+
* Two `stat`s per sync while nothing changed. An embedded repo created
|
|
1376
|
+
* between config edits joins the scoped matcher on the next full sync, the
|
|
1377
|
+
* same lifecycle the watcher's own matcher already has.
|
|
1378
|
+
*/
|
|
1379
|
+
scopedSyncMatcher() {
|
|
1380
|
+
const key = [project_config_1.PROJECT_CONFIG_FILENAME, '.gitignore']
|
|
1381
|
+
.map((name) => {
|
|
1382
|
+
try {
|
|
1383
|
+
return String(fs.statSync(path.join(this.rootDir, name)).mtimeMs);
|
|
1384
|
+
}
|
|
1385
|
+
catch {
|
|
1386
|
+
return '-';
|
|
1387
|
+
}
|
|
1388
|
+
})
|
|
1389
|
+
.join('|');
|
|
1390
|
+
if (this.scopedMatcher && this.scopedMatcher.key === key)
|
|
1391
|
+
return this.scopedMatcher.matcher;
|
|
1392
|
+
const matcher = buildScopeIgnore(this.rootDir);
|
|
1393
|
+
this.scopedMatcher = { key, matcher };
|
|
1394
|
+
return matcher;
|
|
1395
|
+
}
|
|
1351
1396
|
/**
|
|
1352
1397
|
* Build a filesystem-backed ResolutionContext sufficient for framework
|
|
1353
1398
|
* detection. Graph-query methods (getNodesByName etc.) return empty because
|
|
@@ -1473,6 +1518,10 @@ class ExtractionOrchestrator {
|
|
|
1473
1518
|
});
|
|
1474
1519
|
if (process.env.CODEGRAPH_SYNTH_TIMINGS)
|
|
1475
1520
|
console.error(`[phase-timing] scan: ${Date.now() - tScan}ms (${files.length} files)`);
|
|
1521
|
+
// A re-index over an existing DB skips unchanged-hash files at the store,
|
|
1522
|
+
// which would preserve wiped zero-node rows (#1541) — drop them first so
|
|
1523
|
+
// this run stores their files fresh. No-op on a fresh DB.
|
|
1524
|
+
this.healZeroNodeRows();
|
|
1476
1525
|
// Detect frameworks once per indexAll run using the scanned file list.
|
|
1477
1526
|
// Names are passed to each parse call so framework-specific extractors
|
|
1478
1527
|
// (route nodes, middleware, etc.) run after the tree-sitter pass.
|
|
@@ -1619,29 +1668,27 @@ class ExtractionOrchestrator {
|
|
|
1619
1668
|
// Store: on the writer thread when active (fresh DB — bundles applied
|
|
1620
1669
|
// in the same file order this chain dispatches them), else on the main
|
|
1621
1670
|
// thread (SQLite connections are per-thread).
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
if (
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
});
|
|
1635
|
-
}
|
|
1636
|
-
else {
|
|
1637
|
-
storeWriter.send(this.buildFreshStoreBundle(filePath, content, language, stats, result));
|
|
1638
|
-
}
|
|
1639
|
-
await storeWriter.waitBelow(STORE_WRITER_WINDOW);
|
|
1671
|
+
const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
|
|
1672
|
+
if (storeWriter) {
|
|
1673
|
+
if (result.kernelBuffers) {
|
|
1674
|
+
// Buffers go to the writer as-is; the worker decodes + finalizes.
|
|
1675
|
+
// The main thread's only per-file work stays O(1) + the content hash.
|
|
1676
|
+
storeWriter.send({
|
|
1677
|
+
kernel: true,
|
|
1678
|
+
filePath,
|
|
1679
|
+
language,
|
|
1680
|
+
buffers: result.kernelBuffers,
|
|
1681
|
+
file: this.buildFileRecord(filePath, content, language, stats, nodeCount, result.errors),
|
|
1682
|
+
});
|
|
1640
1683
|
}
|
|
1641
1684
|
else {
|
|
1642
|
-
|
|
1643
|
-
await this.storeExtractionResult(filePath, content, language, stats, materialized, commitYield);
|
|
1685
|
+
storeWriter.send(this.buildFreshStoreBundle(filePath, content, language, stats, result));
|
|
1644
1686
|
}
|
|
1687
|
+
await storeWriter.waitBelow(STORE_WRITER_WINDOW);
|
|
1688
|
+
}
|
|
1689
|
+
else {
|
|
1690
|
+
const materialized = (0, kernel_1.materializeKernelResult)(result, filePath, language);
|
|
1691
|
+
await this.storeExtractionResult(filePath, content, language, stats, materialized, commitYield);
|
|
1645
1692
|
}
|
|
1646
1693
|
if (result.errors.length > 0) {
|
|
1647
1694
|
for (const err of result.errors) {
|
|
@@ -1672,16 +1719,19 @@ class ExtractionOrchestrator {
|
|
|
1672
1719
|
}
|
|
1673
1720
|
onProgress?.({ phase: 'parsing', current: processed, total, currentFile: filePath });
|
|
1674
1721
|
};
|
|
1675
|
-
const recordParseFailure = (filePath, err) => {
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1722
|
+
const recordParseFailure = async (filePath, content, stats, err) => {
|
|
1723
|
+
await storeResult(filePath, content, stats, {
|
|
1724
|
+
nodes: [],
|
|
1725
|
+
edges: [],
|
|
1726
|
+
unresolvedReferences: [],
|
|
1727
|
+
errors: [{
|
|
1728
|
+
message: err instanceof Error ? err.message : String(err),
|
|
1729
|
+
filePath,
|
|
1730
|
+
severity: 'error',
|
|
1731
|
+
code: 'parse_error',
|
|
1732
|
+
}],
|
|
1733
|
+
durationMs: 0,
|
|
1683
1734
|
});
|
|
1684
|
-
onProgress?.({ phase: 'parsing', current: processed, total });
|
|
1685
1735
|
};
|
|
1686
1736
|
// Commit buffered parses to the DB in file order, advancing the cursor over
|
|
1687
1737
|
// contiguous completed results. Runs after each parse settles (and once more
|
|
@@ -1706,7 +1756,7 @@ class ExtractionOrchestrator {
|
|
|
1706
1756
|
if (item.ok)
|
|
1707
1757
|
await storeResult(item.filePath, item.content, item.stats, item.result);
|
|
1708
1758
|
else
|
|
1709
|
-
recordParseFailure(item.filePath, item.err);
|
|
1759
|
+
await recordParseFailure(item.filePath, item.content, item.stats, item.err);
|
|
1710
1760
|
}
|
|
1711
1761
|
}
|
|
1712
1762
|
catch (err) {
|
|
@@ -1726,7 +1776,7 @@ class ExtractionOrchestrator {
|
|
|
1726
1776
|
completed.set(seq, { ok: true, filePath, content, stats, result });
|
|
1727
1777
|
}
|
|
1728
1778
|
catch (parseErr) {
|
|
1729
|
-
completed.set(seq, { ok: false, filePath, err: parseErr });
|
|
1779
|
+
completed.set(seq, { ok: false, filePath, content, stats, err: parseErr });
|
|
1730
1780
|
}
|
|
1731
1781
|
flushOrdered();
|
|
1732
1782
|
})();
|
|
@@ -1798,15 +1848,18 @@ class ExtractionOrchestrator {
|
|
|
1798
1848
|
// useful symbols. The single-file extractFile path already enforces
|
|
1799
1849
|
// this; the bulk path used to silently skip the check.
|
|
1800
1850
|
if (stats.size > MAX_FILE_SIZE) {
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1851
|
+
await storeResult(filePath, content, stats, {
|
|
1852
|
+
nodes: [],
|
|
1853
|
+
edges: [],
|
|
1854
|
+
unresolvedReferences: [],
|
|
1855
|
+
errors: [{
|
|
1856
|
+
message: `File exceeds max size (${stats.size} > ${MAX_FILE_SIZE})`,
|
|
1857
|
+
filePath,
|
|
1858
|
+
severity: 'warning',
|
|
1859
|
+
code: 'size_exceeded',
|
|
1860
|
+
}],
|
|
1861
|
+
durationMs: 0,
|
|
1808
1862
|
});
|
|
1809
|
-
onProgress?.({ phase: 'parsing', current: processed, total });
|
|
1810
1863
|
continue;
|
|
1811
1864
|
}
|
|
1812
1865
|
// Parse on the pool (main thread stays unblocked). Errors/timeouts are
|
|
@@ -1907,8 +1960,15 @@ class ExtractionOrchestrator {
|
|
|
1907
1960
|
stillFailing.push(errEntry);
|
|
1908
1961
|
continue;
|
|
1909
1962
|
}
|
|
1963
|
+
// The pool hands kernel results back as an undecoded buffer transport
|
|
1964
|
+
// (`nodes`/`edges` EMPTY, tables in kernelBuffers). The main loop
|
|
1965
|
+
// decodes or forwards to the store worker; this path stores directly,
|
|
1966
|
+
// so decode here — otherwise a kernel-language retry passes the gate
|
|
1967
|
+
// below via `errors.length === 0`, stores nothing, and the file is
|
|
1968
|
+
// permanently recorded as "(0 symbols)" with the error erased (#1541).
|
|
1969
|
+
const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
|
|
1970
|
+
result = (0, kernel_1.materializeKernelResult)(result, filePath, language);
|
|
1910
1971
|
if (result.nodes.length > 0 || result.errors.length === 0) {
|
|
1911
|
-
const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
|
|
1912
1972
|
const stats = await fsp.stat(path.join(this.rootDir, filePath));
|
|
1913
1973
|
await this.storeExtractionResult(filePath, content, language, stats, result, commitYield);
|
|
1914
1974
|
const idx = errors.indexOf(errEntry);
|
|
@@ -1955,13 +2015,20 @@ class ExtractionOrchestrator {
|
|
|
1955
2015
|
catch {
|
|
1956
2016
|
continue;
|
|
1957
2017
|
}
|
|
2018
|
+
// Same undecoded-transport hazard as the first retry pass (#1541).
|
|
2019
|
+
const language = (0, grammars_1.detectLanguage)(filePath, fullContent, overrides);
|
|
2020
|
+
result = (0, kernel_1.materializeKernelResult)(result, filePath, language);
|
|
1958
2021
|
if (result.nodes.length > 0 || result.errors.length === 0) {
|
|
1959
|
-
const language = (0, grammars_1.detectLanguage)(filePath, fullContent, overrides);
|
|
1960
2022
|
const stats = await fsp.stat(path.join(this.rootDir, filePath));
|
|
1961
2023
|
await this.storeExtractionResult(filePath, fullContent, language, stats, result, commitYield);
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
2024
|
+
// Salvaged from comment-stripped source: keep a visible trace in
|
|
2025
|
+
// the summary instead of erasing the failure outright — the
|
|
2026
|
+
// stored result may be missing whatever the failing parse choked
|
|
2027
|
+
// on, and a silently "clean" file here is how an index quietly
|
|
2028
|
+
// disagrees with a later per-file sync of the same bytes (#1565).
|
|
2029
|
+
errEntry.severity = 'warning';
|
|
2030
|
+
errEntry.code = 'salvaged_stripped';
|
|
2031
|
+
errEntry.message = `Indexed from comment-stripped source after repeated parse failures (symbols may be incomplete until the file is re-indexed): ${errEntry.message}`;
|
|
1965
2032
|
filesErrored--;
|
|
1966
2033
|
filesIndexed++;
|
|
1967
2034
|
totalNodes += result.nodes.length;
|
|
@@ -2088,9 +2155,10 @@ class ExtractionOrchestrator {
|
|
|
2088
2155
|
durationMs: 0,
|
|
2089
2156
|
};
|
|
2090
2157
|
}
|
|
2158
|
+
const language = (0, grammars_1.detectLanguage)(relativePath, content, (0, project_config_1.loadExtensionOverrides)(this.rootDir));
|
|
2091
2159
|
// Check file size
|
|
2092
2160
|
if (stats.size > MAX_FILE_SIZE) {
|
|
2093
|
-
|
|
2161
|
+
const result = {
|
|
2094
2162
|
nodes: [],
|
|
2095
2163
|
edges: [],
|
|
2096
2164
|
unresolvedReferences: [],
|
|
@@ -2104,9 +2172,10 @@ class ExtractionOrchestrator {
|
|
|
2104
2172
|
],
|
|
2105
2173
|
durationMs: 0,
|
|
2106
2174
|
};
|
|
2175
|
+
await this.storeExtractionResult(relativePath, content, language, stats, result, (0, cooperative_yield_1.createYielder)());
|
|
2176
|
+
return result;
|
|
2107
2177
|
}
|
|
2108
2178
|
// Detect language (honoring the project's codegraph.json extension overrides)
|
|
2109
|
-
const language = (0, grammars_1.detectLanguage)(relativePath, content, (0, project_config_1.loadExtensionOverrides)(this.rootDir));
|
|
2110
2179
|
if (!(0, grammars_1.isLanguageSupported)(language)) {
|
|
2111
2180
|
return {
|
|
2112
2181
|
nodes: [],
|
|
@@ -2122,15 +2191,43 @@ class ExtractionOrchestrator {
|
|
|
2122
2191
|
const frameworkNames = this.ensureDetectedFrameworks();
|
|
2123
2192
|
const result = (0, tree_sitter_1.extractFromSource)(relativePath, content, language, frameworkNames);
|
|
2124
2193
|
// Store in database
|
|
2125
|
-
|
|
2126
|
-
await this.storeExtractionResult(relativePath, content, language, stats, result, (0, cooperative_yield_1.createYielder)());
|
|
2127
|
-
}
|
|
2194
|
+
await this.storeExtractionResult(relativePath, content, language, stats, result, (0, cooperative_yield_1.createYielder)());
|
|
2128
2195
|
return result;
|
|
2129
2196
|
}
|
|
2130
2197
|
/**
|
|
2131
2198
|
* Store extraction result in database
|
|
2132
2199
|
*/
|
|
2200
|
+
/**
|
|
2201
|
+
* Delete file rows recorded with ZERO nodes so their files re-index.
|
|
2202
|
+
*
|
|
2203
|
+
* No extraction path stores an empty, error-free result for a
|
|
2204
|
+
* symbol-bearing language — even an empty file keeps its file node — so a
|
|
2205
|
+
* zero-node row is a wiped one (#1541: an interrupted parse's retry stored
|
|
2206
|
+
* an undecoded kernel transport). The wiped row's content hash matches the
|
|
2207
|
+
* on-disk bytes, so every hash-based reconcile skips the file forever;
|
|
2208
|
+
* deleting the row lets the normal add path repair it. File-level-only
|
|
2209
|
+
* languages (yaml, twig, properties) are left alone. Deleting a zero-node
|
|
2210
|
+
* row cascades nothing: it has no nodes, so no edges or refs either.
|
|
2211
|
+
*/
|
|
2212
|
+
healZeroNodeRows() {
|
|
2213
|
+
for (const f of this.queries.getAllFiles()) {
|
|
2214
|
+
// A zero-node row WITH recorded errors is a deliberate skip marker
|
|
2215
|
+
// (#1557: oversized / repeatedly-unparseable files are persisted with
|
|
2216
|
+
// their reason so syncs stop retrying them) — leave those alone. The
|
|
2217
|
+
// #1541 wipe rows are the error-FREE zero-node rows.
|
|
2218
|
+
if (f.nodeCount === 0 &&
|
|
2219
|
+
!(0, grammars_1.isFileLevelOnlyLanguage)(f.language) &&
|
|
2220
|
+
(f.errors === undefined || f.errors.length === 0)) {
|
|
2221
|
+
this.queries.deleteFile(f.path);
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2133
2225
|
async storeExtractionResult(filePath, content, language, stats, result, onYield) {
|
|
2226
|
+
// A kernel result can arrive as an undecoded buffer transport (empty
|
|
2227
|
+
// node/edge arrays, tables riding in kernelBuffers). Decode it before
|
|
2228
|
+
// storing — persisting the transport as-is records the file as having no
|
|
2229
|
+
// symbols at all (#1541). No-op for already-decoded results.
|
|
2230
|
+
result = (0, kernel_1.materializeKernelResult)(result, filePath, language);
|
|
2134
2231
|
// Bulk inserts run in bounded sub-transactions with a yield between, so a
|
|
2135
2232
|
// giant generated file (tens of thousands of symbols) can't block the
|
|
2136
2233
|
// event loop — and the #850 watchdog heartbeat — for the whole store.
|
|
@@ -2139,10 +2236,19 @@ class ExtractionOrchestrator {
|
|
|
2139
2236
|
// is unchanged: a partially-stored file has no record and re-indexes.
|
|
2140
2237
|
const STORE_CHUNK = 2000;
|
|
2141
2238
|
const contentHash = hashContent(content);
|
|
2142
|
-
// Check if file already exists and hasn't changed
|
|
2239
|
+
// Check if file already exists and hasn't changed. A skip/failure MARKER
|
|
2240
|
+
// row (zero nodes + recorded errors, #1557) never blocks a store carrying
|
|
2241
|
+
// real content: markers are written BEFORE the retry pass under the same
|
|
2242
|
+
// content hash, so treating them as "no changes" would silently discard a
|
|
2243
|
+
// successful retry's symbols — a permanent empty file presented as
|
|
2244
|
+
// recovered (the #1541 wipe, reintroduced through the marker path).
|
|
2143
2245
|
const existingFile = this.queries.getFileByPath(filePath);
|
|
2144
2246
|
if (existingFile && existingFile.contentHash === contentHash) {
|
|
2145
|
-
|
|
2247
|
+
const existingIsMarker = existingFile.nodeCount === 0 && (existingFile.errors?.length ?? 0) > 0;
|
|
2248
|
+
const incomingHasContent = result.nodes.length > 0;
|
|
2249
|
+
if (!existingIsMarker || !incomingHasContent) {
|
|
2250
|
+
return; // No changes
|
|
2251
|
+
}
|
|
2146
2252
|
}
|
|
2147
2253
|
// Re-decided on every re-index of a changed file, so a banner added (or
|
|
2148
2254
|
// removed) by an edit is reflected on the next sync (#1500). Computed after
|
|
@@ -2439,7 +2545,20 @@ class ExtractionOrchestrator {
|
|
|
2439
2545
|
// reads `filesChecked === 0 && durationMs === 0` as the
|
|
2440
2546
|
// lock-unavailable signature (#449).
|
|
2441
2547
|
const unique = [...new Set(scopedPaths)];
|
|
2442
|
-
|
|
2548
|
+
// A scoped path is "present" only if it exists AND is in scope — the
|
|
2549
|
+
// same two gates the full walk applies (source extension, scope
|
|
2550
|
+
// matcher). Without the scope gate a caller's stale view of scope
|
|
2551
|
+
// leaked straight into the index: the watcher re-parsed a file the
|
|
2552
|
+
// user had just excluded in `codegraph.json` while `codegraph sync`
|
|
2553
|
+
// removed it (#1590). Out-of-scope paths fall out of `currentFiles`,
|
|
2554
|
+
// so a tracked one takes the removal branch below, exactly as a full
|
|
2555
|
+
// sync would treat it. (`include`-forced paths pass: ScopeIgnore
|
|
2556
|
+
// applies the include precedence itself.)
|
|
2557
|
+
const scope = this.scopedSyncMatcher();
|
|
2558
|
+
const overrides = (0, project_config_1.loadExtensionOverrides)(this.rootDir);
|
|
2559
|
+
currentFiles = unique.filter((p) => (0, grammars_1.isSourceFile)(p, overrides) &&
|
|
2560
|
+
!scope.ignores(p) &&
|
|
2561
|
+
fs.existsSync(path.join(this.rootDir, p)));
|
|
2443
2562
|
trackedFiles = [];
|
|
2444
2563
|
for (const p of unique) {
|
|
2445
2564
|
const rec = this.queries.getFileByPath(p);
|
|
@@ -2455,6 +2574,9 @@ class ExtractionOrchestrator {
|
|
|
2455
2574
|
if (process.env.CODEGRAPH_SYNTH_TIMINGS)
|
|
2456
2575
|
console.error(`[phase-timing] sync-scan: ${Date.now() - tSyncScan}ms (${currentFiles.length} files)`);
|
|
2457
2576
|
filesChecked = currentFiles.length;
|
|
2577
|
+
// Full reconcile only (scoped syncs must not touch rows outside their
|
|
2578
|
+
// scope): drop zero-node rows so the wiped files re-index as adds below.
|
|
2579
|
+
this.healZeroNodeRows();
|
|
2458
2580
|
const tTracked = Date.now();
|
|
2459
2581
|
trackedFiles = this.queries.getAllFiles();
|
|
2460
2582
|
if (process.env.CODEGRAPH_SYNTH_TIMINGS)
|