@aroman22/codegraph-vba-darwin-arm64 1.15.0 → 1.16.0
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/lib/dist/bin/codegraph.js +13 -7
- package/lib/dist/bin/codegraph.js.map +1 -1
- package/lib/dist/bin/daemon-release.d.ts +7 -0
- package/lib/dist/bin/daemon-release.d.ts.map +1 -0
- package/lib/dist/bin/daemon-release.js +18 -0
- package/lib/dist/bin/daemon-release.js.map +1 -0
- package/lib/dist/db/queries.d.ts +30 -0
- package/lib/dist/db/queries.d.ts.map +1 -1
- package/lib/dist/db/queries.js +51 -1
- package/lib/dist/db/queries.js.map +1 -1
- package/lib/dist/extraction/access-erd-extractor.d.ts +57 -0
- package/lib/dist/extraction/access-erd-extractor.d.ts.map +1 -0
- package/lib/dist/extraction/access-erd-extractor.js +389 -0
- package/lib/dist/extraction/access-erd-extractor.js.map +1 -0
- package/lib/dist/extraction/extraction-version.d.ts +1 -1
- package/lib/dist/extraction/extraction-version.js +1 -1
- package/lib/dist/extraction/grammars.d.ts +20 -0
- package/lib/dist/extraction/grammars.d.ts.map +1 -1
- package/lib/dist/extraction/grammars.js +34 -0
- package/lib/dist/extraction/grammars.js.map +1 -1
- package/lib/dist/extraction/index.d.ts +1 -0
- package/lib/dist/extraction/index.d.ts.map +1 -1
- package/lib/dist/extraction/index.js +371 -351
- package/lib/dist/extraction/index.js.map +1 -1
- package/lib/dist/extraction/parse-pool.d.ts +8 -3
- package/lib/dist/extraction/parse-pool.d.ts.map +1 -1
- package/lib/dist/extraction/parse-pool.js +1 -2
- package/lib/dist/extraction/parse-pool.js.map +1 -1
- package/lib/dist/extraction/parse-worker.js +7 -6
- package/lib/dist/extraction/parse-worker.js.map +1 -1
- package/lib/dist/extraction/sql-query-extractor.d.ts +16 -14
- package/lib/dist/extraction/sql-query-extractor.d.ts.map +1 -1
- package/lib/dist/extraction/sql-query-extractor.js +43 -31
- package/lib/dist/extraction/sql-query-extractor.js.map +1 -1
- package/lib/dist/extraction/sql-table-scan.d.ts +184 -0
- package/lib/dist/extraction/sql-table-scan.d.ts.map +1 -0
- package/lib/dist/extraction/sql-table-scan.js +288 -0
- package/lib/dist/extraction/sql-table-scan.js.map +1 -0
- package/lib/dist/extraction/tree-sitter.d.ts +10 -1
- package/lib/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/lib/dist/extraction/tree-sitter.js +34 -3
- package/lib/dist/extraction/tree-sitter.js.map +1 -1
- package/lib/dist/extraction/vba/call-sweep.d.ts +2 -7
- package/lib/dist/extraction/vba/call-sweep.d.ts.map +1 -1
- package/lib/dist/extraction/vba/call-sweep.js +144 -90
- package/lib/dist/extraction/vba/call-sweep.js.map +1 -1
- package/lib/dist/extraction/vba/calls.d.ts +24 -4
- package/lib/dist/extraction/vba/calls.d.ts.map +1 -1
- package/lib/dist/extraction/vba/calls.js +42 -12
- package/lib/dist/extraction/vba/calls.js.map +1 -1
- package/lib/dist/extraction/vba/constants.d.ts +7 -14
- package/lib/dist/extraction/vba/constants.d.ts.map +1 -1
- package/lib/dist/extraction/vba/constants.js +8 -36
- package/lib/dist/extraction/vba/constants.js.map +1 -1
- package/lib/dist/extraction/vba/context.d.ts +443 -7
- package/lib/dist/extraction/vba/context.d.ts.map +1 -1
- package/lib/dist/extraction/vba/context.js +325 -29
- package/lib/dist/extraction/vba/context.js.map +1 -1
- package/lib/dist/extraction/vba/controls.d.ts +18 -1
- package/lib/dist/extraction/vba/controls.d.ts.map +1 -1
- package/lib/dist/extraction/vba/controls.js +99 -27
- package/lib/dist/extraction/vba/controls.js.map +1 -1
- package/lib/dist/extraction/vba/declarations.d.ts +1 -6
- package/lib/dist/extraction/vba/declarations.d.ts.map +1 -1
- package/lib/dist/extraction/vba/declarations.js +5 -39
- package/lib/dist/extraction/vba/declarations.js.map +1 -1
- package/lib/dist/extraction/vba/dims.d.ts +11 -7
- package/lib/dist/extraction/vba/dims.d.ts.map +1 -1
- package/lib/dist/extraction/vba/dims.js +265 -29
- package/lib/dist/extraction/vba/dims.js.map +1 -1
- package/lib/dist/extraction/vba/docmd.d.ts +29 -2
- package/lib/dist/extraction/vba/docmd.d.ts.map +1 -1
- package/lib/dist/extraction/vba/docmd.js +297 -18
- package/lib/dist/extraction/vba/docmd.js.map +1 -1
- package/lib/dist/extraction/vba/enums-consts.d.ts +7 -14
- package/lib/dist/extraction/vba/enums-consts.d.ts.map +1 -1
- package/lib/dist/extraction/vba/enums-consts.js +11 -45
- package/lib/dist/extraction/vba/enums-consts.js.map +1 -1
- package/lib/dist/extraction/vba/error-channel.d.ts +57 -0
- package/lib/dist/extraction/vba/error-channel.d.ts.map +1 -0
- package/lib/dist/extraction/vba/error-channel.js +128 -0
- package/lib/dist/extraction/vba/error-channel.js.map +1 -0
- package/lib/dist/extraction/vba/errors.d.ts +64 -0
- package/lib/dist/extraction/vba/errors.d.ts.map +1 -0
- package/lib/dist/extraction/vba/errors.js +775 -0
- package/lib/dist/extraction/vba/errors.js.map +1 -0
- package/lib/dist/extraction/vba/filesystem-statements.d.ts +23 -0
- package/lib/dist/extraction/vba/filesystem-statements.d.ts.map +1 -0
- package/lib/dist/extraction/vba/filesystem-statements.js +70 -0
- package/lib/dist/extraction/vba/filesystem-statements.js.map +1 -0
- package/lib/dist/extraction/vba/implements.d.ts +1 -6
- package/lib/dist/extraction/vba/implements.d.ts.map +1 -1
- package/lib/dist/extraction/vba/implements.js +1 -22
- package/lib/dist/extraction/vba/implements.js.map +1 -1
- package/lib/dist/extraction/vba/labels.d.ts +26 -0
- package/lib/dist/extraction/vba/labels.d.ts.map +1 -0
- package/lib/dist/extraction/vba/labels.js +180 -0
- package/lib/dist/extraction/vba/labels.js.map +1 -0
- package/lib/dist/extraction/vba/module-vars.d.ts +36 -0
- package/lib/dist/extraction/vba/module-vars.d.ts.map +1 -0
- package/lib/dist/extraction/vba/module-vars.js +145 -0
- package/lib/dist/extraction/vba/module-vars.js.map +1 -0
- package/lib/dist/extraction/vba/options.d.ts +88 -0
- package/lib/dist/extraction/vba/options.d.ts.map +1 -0
- package/lib/dist/extraction/vba/options.js +39 -0
- package/lib/dist/extraction/vba/options.js.map +1 -0
- package/lib/dist/extraction/vba/parameters.d.ts +35 -0
- package/lib/dist/extraction/vba/parameters.d.ts.map +1 -0
- package/lib/dist/extraction/vba/parameters.js +110 -0
- package/lib/dist/extraction/vba/parameters.js.map +1 -0
- package/lib/dist/extraction/vba/procedures.d.ts +1 -9
- package/lib/dist/extraction/vba/procedures.d.ts.map +1 -1
- package/lib/dist/extraction/vba/procedures.js +174 -112
- package/lib/dist/extraction/vba/procedures.js.map +1 -1
- package/lib/dist/extraction/vba/rules.d.ts +10 -5
- package/lib/dist/extraction/vba/rules.d.ts.map +1 -1
- package/lib/dist/extraction/vba/rules.js +19 -0
- package/lib/dist/extraction/vba/rules.js.map +1 -1
- package/lib/dist/extraction/vba/runtime-objects.d.ts +59 -0
- package/lib/dist/extraction/vba/runtime-objects.d.ts.map +1 -0
- package/lib/dist/extraction/vba/runtime-objects.js +106 -0
- package/lib/dist/extraction/vba/runtime-objects.js.map +1 -0
- package/lib/dist/extraction/vba/signature.d.ts +57 -0
- package/lib/dist/extraction/vba/signature.d.ts.map +1 -0
- package/lib/dist/extraction/vba/signature.js +164 -0
- package/lib/dist/extraction/vba/signature.js.map +1 -0
- package/lib/dist/extraction/vba/sql-wrapper.d.ts +76 -3
- package/lib/dist/extraction/vba/sql-wrapper.d.ts.map +1 -1
- package/lib/dist/extraction/vba/sql-wrapper.js +725 -151
- package/lib/dist/extraction/vba/sql-wrapper.js.map +1 -1
- package/lib/dist/extraction/vba/tempvars.d.ts.map +1 -1
- package/lib/dist/extraction/vba/tempvars.js +4 -0
- package/lib/dist/extraction/vba/tempvars.js.map +1 -1
- package/lib/dist/extraction/vba/text-utils.d.ts +62 -2
- package/lib/dist/extraction/vba/text-utils.d.ts.map +1 -1
- package/lib/dist/extraction/vba/text-utils.js +84 -6
- package/lib/dist/extraction/vba/text-utils.js.map +1 -1
- package/lib/dist/extraction/vba-extractor.d.ts +18 -1
- package/lib/dist/extraction/vba-extractor.d.ts.map +1 -1
- package/lib/dist/extraction/vba-extractor.js +111 -16
- package/lib/dist/extraction/vba-extractor.js.map +1 -1
- package/lib/dist/extraction/vba-form-extractor.d.ts +28 -14
- package/lib/dist/extraction/vba-form-extractor.d.ts.map +1 -1
- package/lib/dist/extraction/vba-form-extractor.js +56 -21
- package/lib/dist/extraction/vba-form-extractor.js.map +1 -1
- package/lib/dist/extraction/vba-preprocess.d.ts +89 -3
- package/lib/dist/extraction/vba-preprocess.d.ts.map +1 -1
- package/lib/dist/extraction/vba-preprocess.js +221 -57
- package/lib/dist/extraction/vba-preprocess.js.map +1 -1
- package/lib/dist/extraction/vba-source.d.ts +0 -10
- package/lib/dist/extraction/vba-source.d.ts.map +1 -1
- package/lib/dist/extraction/vba-source.js +0 -13
- package/lib/dist/extraction/vba-source.js.map +1 -1
- package/lib/dist/extraction/vba-test-manifest-extractor.d.ts +0 -6
- package/lib/dist/extraction/vba-test-manifest-extractor.d.ts.map +1 -1
- package/lib/dist/extraction/vba-test-manifest-extractor.js +0 -1
- package/lib/dist/extraction/vba-test-manifest-extractor.js.map +1 -1
- package/lib/dist/index.d.ts.map +1 -1
- package/lib/dist/index.js +9 -0
- package/lib/dist/index.js.map +1 -1
- package/lib/dist/mcp/daemon-manager.d.ts.map +1 -1
- package/lib/dist/mcp/daemon-manager.js +7 -3
- package/lib/dist/mcp/daemon-manager.js.map +1 -1
- package/lib/dist/mcp/daemon-paths.d.ts +6 -0
- package/lib/dist/mcp/daemon-paths.d.ts.map +1 -1
- package/lib/dist/mcp/daemon-paths.js +15 -0
- package/lib/dist/mcp/daemon-paths.js.map +1 -1
- package/lib/dist/mcp/daemon-registry.d.ts +82 -7
- package/lib/dist/mcp/daemon-registry.d.ts.map +1 -1
- package/lib/dist/mcp/daemon-registry.js +557 -45
- package/lib/dist/mcp/daemon-registry.js.map +1 -1
- package/lib/dist/mcp/daemon-watchdog.d.ts +12 -0
- package/lib/dist/mcp/daemon-watchdog.d.ts.map +1 -1
- package/lib/dist/mcp/daemon-watchdog.js +37 -7
- package/lib/dist/mcp/daemon-watchdog.js.map +1 -1
- package/lib/dist/mcp/daemon.d.ts +20 -1
- package/lib/dist/mcp/daemon.d.ts.map +1 -1
- package/lib/dist/mcp/daemon.js +123 -30
- package/lib/dist/mcp/daemon.js.map +1 -1
- package/lib/dist/mcp/index.d.ts.map +1 -1
- package/lib/dist/mcp/index.js +13 -2
- package/lib/dist/mcp/index.js.map +1 -1
- package/lib/dist/mcp/proxy.d.ts +32 -0
- package/lib/dist/mcp/proxy.d.ts.map +1 -1
- package/lib/dist/mcp/proxy.js +163 -31
- package/lib/dist/mcp/proxy.js.map +1 -1
- package/lib/dist/mcp/tools.d.ts.map +1 -1
- package/lib/dist/mcp/tools.js +29 -1
- package/lib/dist/mcp/tools.js.map +1 -1
- package/lib/dist/project-config.d.ts +43 -2
- package/lib/dist/project-config.d.ts.map +1 -1
- package/lib/dist/project-config.js +86 -2
- package/lib/dist/project-config.js.map +1 -1
- package/lib/dist/resolution/index.d.ts +47 -2
- package/lib/dist/resolution/index.d.ts.map +1 -1
- package/lib/dist/resolution/index.js +160 -5
- package/lib/dist/resolution/index.js.map +1 -1
- package/lib/dist/resolution/name-matcher.d.ts +25 -0
- package/lib/dist/resolution/name-matcher.d.ts.map +1 -1
- package/lib/dist/resolution/name-matcher.js +59 -0
- package/lib/dist/resolution/name-matcher.js.map +1 -1
- package/lib/dist/resolution/vba-runtime-objects.d.ts +11 -11
- package/lib/dist/resolution/vba-runtime-objects.d.ts.map +1 -1
- package/lib/dist/resolution/vba-runtime-objects.js +18 -38
- package/lib/dist/resolution/vba-runtime-objects.js.map +1 -1
- package/lib/dist/types.d.ts +13 -5
- package/lib/dist/types.d.ts.map +1 -1
- package/lib/dist/types.js +17 -0
- package/lib/dist/types.js.map +1 -1
- package/lib/node_modules/.modules.yaml +1 -1
- package/lib/node_modules/.pnpm-workspace-state-v1.json +1 -1
- package/lib/package.json +2 -1
- package/package.json +1 -1
|
@@ -1502,9 +1502,22 @@ class ExtractionOrchestrator {
|
|
|
1502
1502
|
// Issue #152: pull the optional `vba.maxRaiseFanout` knob from the
|
|
1503
1503
|
// same `codegraph.json` → `vba` block the targets live in. The VBA
|
|
1504
1504
|
// extractor applies the per-file fanout gate at this threshold.
|
|
1505
|
+
// Issue #243: the knobs travel as ONE structured-cloneable options object
|
|
1506
|
+
// from here to the extractor, on the pool path and the in-process path
|
|
1507
|
+
// alike, so a new knob is a field here instead of a new positional in
|
|
1508
|
+
// five signatures.
|
|
1505
1509
|
const vbaConfig = (0, project_config_1.loadVbaConfig)(this.rootDir);
|
|
1506
|
-
const
|
|
1507
|
-
|
|
1510
|
+
const vbaOptions = {
|
|
1511
|
+
targets: vbaConfig.targets,
|
|
1512
|
+
maxRaiseFanout: vbaConfig.maxRaiseFanout,
|
|
1513
|
+
// Issue #244: extra SQL execution-site receivers. Plain strings — the
|
|
1514
|
+
// compiled matcher is built inside the extractor because a RegExp
|
|
1515
|
+
// cannot cross the `structuredClone` worker boundary.
|
|
1516
|
+
sqlWrappers: vbaConfig.sqlWrappers,
|
|
1517
|
+
// Issue #261: the project's error-channel variable names. Plain strings
|
|
1518
|
+
// for the same worker-boundary reason as `sqlWrappers`.
|
|
1519
|
+
errorChannel: vbaConfig.errorChannel,
|
|
1520
|
+
};
|
|
1508
1521
|
// Issue #154 — gate the 3 Dysflow-specific VBA sub-extractors. `true`
|
|
1509
1522
|
// (the default) keeps the pre-refactor behavior; `false` opts out so
|
|
1510
1523
|
// form/report/manifest/sequence files are tracked as just a `file`
|
|
@@ -1579,376 +1592,328 @@ class ExtractionOrchestrator {
|
|
|
1579
1592
|
const parseWorkerPath = path.join(__dirname, 'parse-worker.js');
|
|
1580
1593
|
const useWorker = fs.existsSync(parseWorkerPath);
|
|
1581
1594
|
let pool = null;
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
else {
|
|
1603
|
-
// In-process fallback: load grammars locally and parse on the main thread.
|
|
1604
|
-
await (0, grammars_1.loadGrammarsForLanguages)(neededLanguages);
|
|
1605
|
-
}
|
|
1606
|
-
/**
|
|
1607
|
-
* Parse one file: on the pool when available (the promise REJECTS on a worker
|
|
1608
|
-
* crash/timeout — the caller records it and the retry pass re-attempts), or
|
|
1609
|
-
* in-process synchronously as the no-worker fallback. The language is resolved
|
|
1610
|
-
* here on the main thread, where the codegraph.json overrides are loaded.
|
|
1611
|
-
*/
|
|
1612
|
-
const parseFile = (filePath, content) => {
|
|
1613
|
-
const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
|
|
1614
|
-
// Issue #152: thread the optional `vba.maxRaiseFanout` knob through
|
|
1615
|
-
// the worker boundary (or the in-process fallback) so every VBA
|
|
1616
|
-
// file sees the same gate.
|
|
1617
|
-
// Issue #154: also thread the optional `vba.dysflowExport` flag.
|
|
1618
|
-
if (!pool)
|
|
1619
|
-
return Promise.resolve((0, tree_sitter_1.extractFromSource)(filePath, content, language, frameworkNames, vbaTargets, maxRaiseFanout, dysflowExport));
|
|
1620
|
-
return pool.requestParse({ filePath, content, language, frameworkNames, vbaTargets, maxRaiseFanout, dysflowExport });
|
|
1621
|
-
};
|
|
1622
|
-
// --- Bounded rolling-window dispatch, ordered commit ---
|
|
1623
|
-
// Reads stay batched/parallel; parses run concurrently across the pool; the
|
|
1624
|
-
// SQLite store stays on the main thread (it isn't thread-safe). Crucially we
|
|
1625
|
-
// COMMIT results in original file order, not parse-completion order: the
|
|
1626
|
-
// resolution phase (run after indexing) resolves an ambiguous reference to one
|
|
1627
|
-
// of several same-named candidates by the nodes' DB insertion order, so a
|
|
1628
|
-
// stable commit order keeps the resulting graph deterministic — byte-identical
|
|
1629
|
-
// to the single-worker path — instead of drifting with parse timing. The
|
|
1630
|
-
// `completed` buffer holds at most ~windowSize out-of-order results, so memory
|
|
1631
|
-
// stays bounded.
|
|
1632
|
-
const windowSize = pool ? Math.max(4, pool.size * 2) : 1;
|
|
1633
|
-
const inFlight = new Set();
|
|
1634
|
-
const completed = new Map();
|
|
1635
|
-
let nextSeq = 0; // file-order sequence assigned at dispatch
|
|
1636
|
-
let nextToStore = 0; // cursor: next sequence to commit
|
|
1637
|
-
let aborted = false;
|
|
1638
|
-
// Yielder for the in-order commit path: a single giant generated file's
|
|
1639
|
-
// store is otherwise one unyielding multi-second transaction span on the
|
|
1640
|
-
// main thread (5–14s single stalls measured on llvm-project), starving
|
|
1641
|
-
// the #850 watchdog heartbeat on slow hardware.
|
|
1642
|
-
const commitYield = (0, cooperative_yield_1.createYielder)();
|
|
1643
|
-
const storeResult = async (filePath, content, stats, result) => {
|
|
1644
|
-
processed++;
|
|
1645
|
-
// WAL hard-cap backstop: between files (never mid-transaction), pause
|
|
1646
|
-
// the store until the off-thread checkpoint catches up. Resolves to
|
|
1647
|
-
// null in the normal case — a single size check, no cost.
|
|
1648
|
-
const bp = walBackpressure?.();
|
|
1649
|
-
if (bp)
|
|
1650
|
-
await bp;
|
|
1651
|
-
// Store in database on main thread (SQLite is not thread-safe)
|
|
1652
|
-
if (result.nodes.length > 0 || result.errors.length === 0) {
|
|
1653
|
-
const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
|
|
1654
|
-
await this.storeExtractionResult(filePath, content, language, stats, result, commitYield);
|
|
1655
|
-
}
|
|
1656
|
-
if (result.errors.length > 0) {
|
|
1657
|
-
for (const err of result.errors) {
|
|
1658
|
-
if (!err.filePath)
|
|
1659
|
-
err.filePath = filePath;
|
|
1660
|
-
}
|
|
1661
|
-
errors.push(...result.errors);
|
|
1662
|
-
}
|
|
1663
|
-
if (result.nodes.length > 0) {
|
|
1664
|
-
filesIndexed++;
|
|
1665
|
-
totalNodes += result.nodes.length;
|
|
1666
|
-
totalEdges += result.edges.length;
|
|
1667
|
-
}
|
|
1668
|
-
else if (result.errors.some((e) => e.severity === 'error')) {
|
|
1669
|
-
filesErrored++;
|
|
1595
|
+
try {
|
|
1596
|
+
if (useWorker) {
|
|
1597
|
+
// CODEGRAPH_PARSE_WORKERS: explicit worker count; 1 = the old single-worker
|
|
1598
|
+
// behaviour (the conservative rollback). Unset → clamp(cores-1, 1, 8).
|
|
1599
|
+
const poolSize = (0, parse_pool_1.resolveParsePoolSize)(process.env.CODEGRAPH_PARSE_WORKERS, os.cpus().length);
|
|
1600
|
+
// Read each needed grammar's WASM ONCE here and hand the bytes to every
|
|
1601
|
+
// worker, so spawns/respawns load grammars from memory instead of
|
|
1602
|
+
// re-reading them from disk (#1231: on an HDD, respawn re-reads amplify
|
|
1603
|
+
// the very I/O contention that caused the respawn).
|
|
1604
|
+
const grammarBuffers = await (0, grammars_1.readGrammarWasmBytes)(neededLanguages);
|
|
1605
|
+
pool = new parse_pool_1.ParseWorkerPool({
|
|
1606
|
+
languages: neededLanguages,
|
|
1607
|
+
size: poolSize,
|
|
1608
|
+
workerScriptPath: parseWorkerPath,
|
|
1609
|
+
recycleInterval: WORKER_RECYCLE_INTERVAL,
|
|
1610
|
+
parseTimeoutMs: PARSE_TIMEOUT_MS,
|
|
1611
|
+
log,
|
|
1612
|
+
grammarBuffers,
|
|
1613
|
+
});
|
|
1614
|
+
log(`Parse worker pool: ${poolSize} worker(s)`);
|
|
1670
1615
|
}
|
|
1671
1616
|
else {
|
|
1672
|
-
//
|
|
1673
|
-
|
|
1674
|
-
// misleadingly report "No files found to index".
|
|
1675
|
-
const lang = (0, grammars_1.detectLanguage)(filePath, content, overrides);
|
|
1676
|
-
if ((0, grammars_1.isFileLevelOnlyLanguage)(lang)) {
|
|
1677
|
-
filesIndexed++;
|
|
1678
|
-
}
|
|
1679
|
-
else {
|
|
1680
|
-
filesSkipped++;
|
|
1681
|
-
}
|
|
1617
|
+
// In-process fallback: load grammars locally and parse on the main thread.
|
|
1618
|
+
await (0, grammars_1.loadGrammarsForLanguages)(neededLanguages);
|
|
1682
1619
|
}
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
filePath,
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1620
|
+
/**
|
|
1621
|
+
* Parse one file: on the pool when available (the promise REJECTS on a worker
|
|
1622
|
+
* crash/timeout — the caller records it and the retry pass re-attempts), or
|
|
1623
|
+
* in-process synchronously as the no-worker fallback. The language is resolved
|
|
1624
|
+
* here on the main thread, where the codegraph.json overrides are loaded.
|
|
1625
|
+
*/
|
|
1626
|
+
const parseFile = (filePath, content) => {
|
|
1627
|
+
const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
|
|
1628
|
+
// Issue #152: thread the optional `vba.maxRaiseFanout` knob through
|
|
1629
|
+
// the worker boundary (or the in-process fallback) so every VBA
|
|
1630
|
+
// file sees the same gate.
|
|
1631
|
+
// Issue #154: also thread the optional `vba.dysflowExport` flag.
|
|
1632
|
+
// Issue #243: both knobs now ride in the `vbaOptions` object.
|
|
1633
|
+
if (!pool)
|
|
1634
|
+
return Promise.resolve((0, tree_sitter_1.extractFromSource)(filePath, content, language, frameworkNames, undefined, undefined, dysflowExport, vbaOptions));
|
|
1635
|
+
return pool.requestParse({ filePath, content, language, frameworkNames, vbaOptions, dysflowExport });
|
|
1636
|
+
};
|
|
1637
|
+
// --- Bounded rolling-window dispatch, ordered commit ---
|
|
1638
|
+
// Reads stay batched/parallel; parses run concurrently across the pool; the
|
|
1639
|
+
// SQLite store stays on the main thread (it isn't thread-safe). Crucially we
|
|
1640
|
+
// COMMIT results in original file order, not parse-completion order: the
|
|
1641
|
+
// resolution phase (run after indexing) resolves an ambiguous reference to one
|
|
1642
|
+
// of several same-named candidates by the nodes' DB insertion order, so a
|
|
1643
|
+
// stable commit order keeps the resulting graph deterministic — byte-identical
|
|
1644
|
+
// to the single-worker path — instead of drifting with parse timing. The
|
|
1645
|
+
// `completed` buffer holds at most ~windowSize out-of-order results, so memory
|
|
1646
|
+
// stays bounded.
|
|
1647
|
+
const windowSize = pool ? Math.max(4, pool.size * 2) : 1;
|
|
1648
|
+
const inFlight = new Set();
|
|
1649
|
+
const completed = new Map();
|
|
1650
|
+
let nextSeq = 0; // file-order sequence assigned at dispatch
|
|
1651
|
+
let nextToStore = 0; // cursor: next sequence to commit
|
|
1652
|
+
let aborted = false;
|
|
1653
|
+
// Yielder for the in-order commit path: a single giant generated file's
|
|
1654
|
+
// store is otherwise one unyielding multi-second transaction span on the
|
|
1655
|
+
// main thread (5–14s single stalls measured on llvm-project), starving
|
|
1656
|
+
// the #850 watchdog heartbeat on slow hardware.
|
|
1657
|
+
const commitYield = (0, cooperative_yield_1.createYielder)();
|
|
1658
|
+
const storeResult = async (filePath, content, stats, result) => {
|
|
1659
|
+
processed++;
|
|
1660
|
+
// WAL hard-cap backstop: between files (never mid-transaction), pause
|
|
1661
|
+
// the store until the off-thread checkpoint catches up. Resolves to
|
|
1662
|
+
// null in the normal case — a single size check, no cost.
|
|
1663
|
+
const bp = walBackpressure?.();
|
|
1664
|
+
if (bp)
|
|
1665
|
+
await bp;
|
|
1666
|
+
// Store in database on main thread (SQLite is not thread-safe)
|
|
1667
|
+
if (result.nodes.length > 0 || result.errors.length === 0) {
|
|
1668
|
+
const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
|
|
1669
|
+
await this.storeExtractionResult(filePath, content, language, stats, result, commitYield);
|
|
1721
1670
|
}
|
|
1722
|
-
|
|
1723
|
-
|
|
1671
|
+
if (result.errors.length > 0) {
|
|
1672
|
+
for (const err of result.errors) {
|
|
1673
|
+
if (!err.filePath)
|
|
1674
|
+
err.filePath = filePath;
|
|
1675
|
+
}
|
|
1676
|
+
errors.push(...result.errors);
|
|
1724
1677
|
}
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
// with its file-order sequence so flushOrdered commits results in order. The
|
|
1730
|
-
// backpressure below bounds how far parsing runs ahead of the in-order commit.
|
|
1731
|
-
const feed = async (filePath, content, stats) => {
|
|
1732
|
-
const seq = nextSeq++;
|
|
1733
|
-
const p = (async () => {
|
|
1734
|
-
try {
|
|
1735
|
-
const result = await parseFile(filePath, content);
|
|
1736
|
-
completed.set(seq, { ok: true, filePath, content, stats, result });
|
|
1678
|
+
if (result.nodes.length > 0) {
|
|
1679
|
+
filesIndexed++;
|
|
1680
|
+
totalNodes += result.nodes.length;
|
|
1681
|
+
totalEdges += result.edges.length;
|
|
1737
1682
|
}
|
|
1738
|
-
|
|
1739
|
-
|
|
1683
|
+
else if (result.errors.some((e) => e.severity === 'error')) {
|
|
1684
|
+
filesErrored++;
|
|
1740
1685
|
}
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
// advances (buffered items hold whole file contents, so this bound is
|
|
1752
|
-
// load-bearing for memory).
|
|
1753
|
-
while (nextSeq - nextToStore >= windowSize) {
|
|
1754
|
-
if (inFlight.size > 0)
|
|
1755
|
-
await Promise.race(inFlight);
|
|
1756
|
-
else
|
|
1757
|
-
await flushOrdered();
|
|
1758
|
-
}
|
|
1759
|
-
};
|
|
1760
|
-
for (let i = 0; i < files.length; i += FILE_IO_BATCH_SIZE) {
|
|
1761
|
-
if (signal?.aborted) {
|
|
1762
|
-
aborted = true;
|
|
1763
|
-
break;
|
|
1764
|
-
}
|
|
1765
|
-
const batch = files.slice(i, i + FILE_IO_BATCH_SIZE);
|
|
1766
|
-
// Read files in parallel (with path validation before any I/O)
|
|
1767
|
-
const fileContents = await Promise.all(batch.map(async (fp) => {
|
|
1768
|
-
try {
|
|
1769
|
-
// Indexing read: follow in-root symlinks the directory walk already
|
|
1770
|
-
// descended into (the `../` guard still applies) so files reached
|
|
1771
|
-
// via an in-root symlink-to-outside still index (#935).
|
|
1772
|
-
const fullPath = (0, utils_1.validatePathWithinRoot)(this.rootDir, fp, { allowSymlinkEscape: true });
|
|
1773
|
-
if (!fullPath) {
|
|
1774
|
-
(0, errors_1.logWarn)('Path traversal blocked in batch reader', { filePath: fp });
|
|
1775
|
-
return { filePath: fp, content: null, stats: null, error: new Error('Path traversal blocked') };
|
|
1686
|
+
else {
|
|
1687
|
+
// Files with no symbols but no errors (yaml, twig, properties) are
|
|
1688
|
+
// tracked at the file level — count them as indexed so the CLI doesn't
|
|
1689
|
+
// misleadingly report "No files found to index".
|
|
1690
|
+
const lang = (0, grammars_1.detectLanguage)(filePath, content, overrides);
|
|
1691
|
+
if ((0, grammars_1.isFileLevelOnlyLanguage)(lang)) {
|
|
1692
|
+
filesIndexed++;
|
|
1693
|
+
}
|
|
1694
|
+
else {
|
|
1695
|
+
filesSkipped++;
|
|
1776
1696
|
}
|
|
1777
|
-
const content = await readForExtraction(fullPath);
|
|
1778
|
-
const stats = await fsp.stat(fullPath);
|
|
1779
|
-
return { filePath: fp, content, stats, error: null };
|
|
1780
1697
|
}
|
|
1781
|
-
|
|
1782
|
-
|
|
1698
|
+
onProgress?.({ phase: 'parsing', current: processed, total, currentFile: filePath });
|
|
1699
|
+
};
|
|
1700
|
+
const recordParseFailure = (filePath, err) => {
|
|
1701
|
+
processed++;
|
|
1702
|
+
filesErrored++;
|
|
1703
|
+
errors.push({
|
|
1704
|
+
message: err instanceof Error ? err.message : String(err),
|
|
1705
|
+
filePath,
|
|
1706
|
+
severity: 'error',
|
|
1707
|
+
code: 'parse_error',
|
|
1708
|
+
});
|
|
1709
|
+
onProgress?.({ phase: 'parsing', current: processed, total });
|
|
1710
|
+
};
|
|
1711
|
+
// Commit buffered parses to the DB in file order, advancing the cursor over
|
|
1712
|
+
// contiguous completed results. Runs after each parse settles (and once more
|
|
1713
|
+
// after the drain). storeResult is now async (it yields between chunked
|
|
1714
|
+
// inserts), so commits are SERIALIZED on a promise chain — concurrent parse
|
|
1715
|
+
// completions append to the chain instead of interleaving mid-store, which
|
|
1716
|
+
// preserves both the file-order commit invariant (#1015: resolution
|
|
1717
|
+
// disambiguates same-named candidates by insertion order) and the
|
|
1718
|
+
// single-writer discipline for SQLite. Errors are recorded and re-thrown
|
|
1719
|
+
// at the drain, matching the old synchronous propagation.
|
|
1720
|
+
let flushChain = Promise.resolve();
|
|
1721
|
+
let flushError = null;
|
|
1722
|
+
const flushOrdered = () => {
|
|
1723
|
+
flushChain = flushChain.then(async () => {
|
|
1724
|
+
if (aborted || flushError)
|
|
1725
|
+
return;
|
|
1726
|
+
try {
|
|
1727
|
+
while (completed.has(nextToStore)) {
|
|
1728
|
+
const item = completed.get(nextToStore);
|
|
1729
|
+
completed.delete(nextToStore);
|
|
1730
|
+
nextToStore++;
|
|
1731
|
+
if (item.ok)
|
|
1732
|
+
await storeResult(item.filePath, item.content, item.stats, item.result);
|
|
1733
|
+
else
|
|
1734
|
+
recordParseFailure(item.filePath, item.err);
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
catch (err) {
|
|
1738
|
+
flushError = err;
|
|
1739
|
+
}
|
|
1740
|
+
});
|
|
1741
|
+
return flushChain;
|
|
1742
|
+
};
|
|
1743
|
+
// Dispatch one file's parse (parses run concurrently across the pool), tagged
|
|
1744
|
+
// with its file-order sequence so flushOrdered commits results in order. The
|
|
1745
|
+
// backpressure below bounds how far parsing runs ahead of the in-order commit.
|
|
1746
|
+
const feed = async (filePath, content, stats) => {
|
|
1747
|
+
const seq = nextSeq++;
|
|
1748
|
+
const p = (async () => {
|
|
1749
|
+
try {
|
|
1750
|
+
const result = await parseFile(filePath, content);
|
|
1751
|
+
completed.set(seq, { ok: true, filePath, content, stats, result });
|
|
1752
|
+
}
|
|
1753
|
+
catch (parseErr) {
|
|
1754
|
+
completed.set(seq, { ok: false, filePath, err: parseErr });
|
|
1755
|
+
}
|
|
1756
|
+
flushOrdered();
|
|
1757
|
+
})();
|
|
1758
|
+
const tracked = p.finally(() => { inFlight.delete(tracked); });
|
|
1759
|
+
inFlight.add(tracked);
|
|
1760
|
+
// Backpressure on the dispatched-but-not-yet-committed count (in-flight +
|
|
1761
|
+
// buffered), not just in-flight: a slow file sitting at the commit cursor
|
|
1762
|
+
// lets later parses finish and buffer, which would otherwise grow without
|
|
1763
|
+
// bound. Wait for parses to settle (each may advance the cursor) until the
|
|
1764
|
+
// window has room. When nothing is in flight but the window is still full,
|
|
1765
|
+
// the async commit chain is what's behind — await it so the cursor
|
|
1766
|
+
// advances (buffered items hold whole file contents, so this bound is
|
|
1767
|
+
// load-bearing for memory).
|
|
1768
|
+
while (nextSeq - nextToStore >= windowSize) {
|
|
1769
|
+
if (inFlight.size > 0)
|
|
1770
|
+
await Promise.race(inFlight);
|
|
1771
|
+
else
|
|
1772
|
+
await flushOrdered();
|
|
1783
1773
|
}
|
|
1784
|
-
}
|
|
1785
|
-
|
|
1786
|
-
// stores results on the main thread as they arrive.
|
|
1787
|
-
for (const { filePath, content, stats, error } of fileContents) {
|
|
1774
|
+
};
|
|
1775
|
+
for (let i = 0; i < files.length; i += FILE_IO_BATCH_SIZE) {
|
|
1788
1776
|
if (signal?.aborted) {
|
|
1789
1777
|
aborted = true;
|
|
1790
1778
|
break;
|
|
1791
1779
|
}
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
// then commit any results the cursor hasn't reached yet.
|
|
1830
|
-
if (!aborted) {
|
|
1831
|
-
await Promise.all(inFlight);
|
|
1832
|
-
await flushOrdered();
|
|
1833
|
-
if (flushError)
|
|
1834
|
-
throw flushError;
|
|
1835
|
-
}
|
|
1836
|
-
if (signal?.aborted || aborted) {
|
|
1837
|
-
if (pool)
|
|
1838
|
-
await pool.destroy();
|
|
1839
|
-
return {
|
|
1840
|
-
success: false,
|
|
1841
|
-
filesIndexed,
|
|
1842
|
-
filesSkipped,
|
|
1843
|
-
filesErrored,
|
|
1844
|
-
filesDiscovered: total,
|
|
1845
|
-
nodesCreated: totalNodes,
|
|
1846
|
-
edgesCreated: totalEdges,
|
|
1847
|
-
errors: [{ message: 'Aborted', severity: 'error' }, ...errors],
|
|
1848
|
-
durationMs: Date.now() - startTime,
|
|
1849
|
-
};
|
|
1850
|
-
}
|
|
1851
|
-
// Report 100% so the progress bar doesn't hang at 99%
|
|
1852
|
-
onProgress?.({
|
|
1853
|
-
phase: 'parsing',
|
|
1854
|
-
current: total,
|
|
1855
|
-
total,
|
|
1856
|
-
});
|
|
1857
|
-
// Yield so the shimmer worker's buffered stdout writes can flush.
|
|
1858
|
-
// Worker thread stdout is proxied through the main thread's event loop,
|
|
1859
|
-
// so synchronous work here blocks the animation from rendering.
|
|
1860
|
-
await new Promise(resolve => setImmediate(resolve));
|
|
1861
|
-
// Retry pass: files that failed due to WASM memory corruption may succeed
|
|
1862
|
-
// on a fresh worker with a clean heap. Recycle before each attempt so
|
|
1863
|
-
// every file gets the absolute cleanest WASM state possible. Timeouts are
|
|
1864
|
-
// retried too (#1231): most are main-thread-stall artifacts, not slow
|
|
1865
|
-
// parses, and this pass parses one file at a time with the store strictly
|
|
1866
|
-
// after each parse resolves, so the stall window can't recur here.
|
|
1867
|
-
const retryableErrors = errors.filter((e) => e.code === 'parse_error' && e.filePath &&
|
|
1868
|
-
(e.message.includes('Worker exited') ||
|
|
1869
|
-
e.message.includes('memory access out of bounds') ||
|
|
1870
|
-
e.message.includes('timed out')));
|
|
1871
|
-
if (retryableErrors.length > 0 && pool) {
|
|
1872
|
-
log(`Retrying ${retryableErrors.length} files that failed due to WASM memory errors or timeouts...`);
|
|
1873
|
-
// Fresh WASM heaps for the retry phase. A retry that still crashes its
|
|
1874
|
-
// worker makes the pool respawn it, so later retries keep landing on clean
|
|
1875
|
-
// workers too.
|
|
1876
|
-
pool.recycleAll();
|
|
1877
|
-
const stillFailing = [];
|
|
1878
|
-
for (const errEntry of retryableErrors) {
|
|
1879
|
-
const filePath = errEntry.filePath;
|
|
1880
|
-
if (signal?.aborted)
|
|
1881
|
-
break;
|
|
1882
|
-
let content;
|
|
1883
|
-
try {
|
|
1884
|
-
const fullPath = (0, utils_1.validatePathWithinRoot)(this.rootDir, filePath);
|
|
1885
|
-
if (!fullPath)
|
|
1780
|
+
const batch = files.slice(i, i + FILE_IO_BATCH_SIZE);
|
|
1781
|
+
// Read files in parallel (with path validation before any I/O)
|
|
1782
|
+
const fileContents = await Promise.all(batch.map(async (fp) => {
|
|
1783
|
+
try {
|
|
1784
|
+
// Indexing read: follow in-root symlinks the directory walk already
|
|
1785
|
+
// descended into (the `../` guard still applies) so files reached
|
|
1786
|
+
// via an in-root symlink-to-outside still index (#935).
|
|
1787
|
+
const fullPath = (0, utils_1.validatePathWithinRoot)(this.rootDir, fp, { allowSymlinkEscape: true });
|
|
1788
|
+
if (!fullPath) {
|
|
1789
|
+
(0, errors_1.logWarn)('Path traversal blocked in batch reader', { filePath: fp });
|
|
1790
|
+
return { filePath: fp, content: null, stats: null, error: new Error('Path traversal blocked') };
|
|
1791
|
+
}
|
|
1792
|
+
const content = await readForExtraction(fullPath);
|
|
1793
|
+
const stats = await fsp.stat(fullPath);
|
|
1794
|
+
return { filePath: fp, content, stats, error: null };
|
|
1795
|
+
}
|
|
1796
|
+
catch (err) {
|
|
1797
|
+
return { filePath: fp, content: null, stats: null, error: err };
|
|
1798
|
+
}
|
|
1799
|
+
}));
|
|
1800
|
+
// Dispatch each readable file into the bounded parse window; the window
|
|
1801
|
+
// stores results on the main thread as they arrive.
|
|
1802
|
+
for (const { filePath, content, stats, error } of fileContents) {
|
|
1803
|
+
if (signal?.aborted) {
|
|
1804
|
+
aborted = true;
|
|
1805
|
+
break;
|
|
1806
|
+
}
|
|
1807
|
+
if (error || content === null || stats === null) {
|
|
1808
|
+
processed++;
|
|
1809
|
+
filesErrored++;
|
|
1810
|
+
errors.push({
|
|
1811
|
+
message: `Failed to read file: ${error instanceof Error ? error.message : String(error)}`,
|
|
1812
|
+
filePath,
|
|
1813
|
+
severity: 'error',
|
|
1814
|
+
code: 'read_error',
|
|
1815
|
+
});
|
|
1816
|
+
onProgress?.({ phase: 'parsing', current: processed, total });
|
|
1886
1817
|
continue;
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
filesIndexed++;
|
|
1909
|
-
totalNodes += result.nodes.length;
|
|
1910
|
-
totalEdges += result.edges.length;
|
|
1911
|
-
log(`Retry OK: ${filePath} (${result.nodes.length} nodes)`);
|
|
1818
|
+
}
|
|
1819
|
+
// Honour MAX_FILE_SIZE. Without this check, vendored generated
|
|
1820
|
+
// headers, minified bundles, and other multi-MB files get indexed,
|
|
1821
|
+
// wasting WASM heap and the worker recycle budget on inputs with no
|
|
1822
|
+
// useful symbols. The single-file extractFile path already enforces
|
|
1823
|
+
// this; the bulk path used to silently skip the check.
|
|
1824
|
+
if (stats.size > MAX_FILE_SIZE) {
|
|
1825
|
+
processed++;
|
|
1826
|
+
filesSkipped++;
|
|
1827
|
+
errors.push({
|
|
1828
|
+
message: `File exceeds max size (${stats.size} > ${MAX_FILE_SIZE})`,
|
|
1829
|
+
filePath,
|
|
1830
|
+
severity: 'warning',
|
|
1831
|
+
code: 'size_exceeded',
|
|
1832
|
+
});
|
|
1833
|
+
onProgress?.({ phase: 'parsing', current: processed, total });
|
|
1834
|
+
continue;
|
|
1835
|
+
}
|
|
1836
|
+
// Parse on the pool (main thread stays unblocked). Errors/timeouts are
|
|
1837
|
+
// handled inside feed() → recordParseFailure, feeding the retry pass.
|
|
1838
|
+
await feed(filePath, content, stats);
|
|
1912
1839
|
}
|
|
1840
|
+
if (aborted)
|
|
1841
|
+
break;
|
|
1913
1842
|
}
|
|
1914
|
-
//
|
|
1915
|
-
//
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1843
|
+
// Drain parses still in flight (skip on abort — we tear down below instead),
|
|
1844
|
+
// then commit any results the cursor hasn't reached yet.
|
|
1845
|
+
if (!aborted) {
|
|
1846
|
+
await Promise.all(inFlight);
|
|
1847
|
+
await flushOrdered();
|
|
1848
|
+
if (flushError)
|
|
1849
|
+
throw flushError;
|
|
1850
|
+
}
|
|
1851
|
+
if (signal?.aborted || aborted) {
|
|
1852
|
+
return {
|
|
1853
|
+
success: false,
|
|
1854
|
+
filesIndexed,
|
|
1855
|
+
filesSkipped,
|
|
1856
|
+
filesErrored,
|
|
1857
|
+
filesDiscovered: total,
|
|
1858
|
+
nodesCreated: totalNodes,
|
|
1859
|
+
edgesCreated: totalEdges,
|
|
1860
|
+
errors: [{ message: 'Aborted', severity: 'error' }, ...errors],
|
|
1861
|
+
durationMs: Date.now() - startTime,
|
|
1862
|
+
};
|
|
1863
|
+
}
|
|
1864
|
+
// Report 100% so the progress bar doesn't hang at 99%
|
|
1865
|
+
onProgress?.({
|
|
1866
|
+
phase: 'parsing',
|
|
1867
|
+
current: total,
|
|
1868
|
+
total,
|
|
1869
|
+
});
|
|
1870
|
+
// Yield so the shimmer worker's buffered stdout writes can flush.
|
|
1871
|
+
// Worker thread stdout is proxied through the main thread's event loop,
|
|
1872
|
+
// so synchronous work here blocks the animation from rendering.
|
|
1873
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
1874
|
+
// Retry pass: files that failed due to WASM memory corruption may succeed
|
|
1875
|
+
// on a fresh worker with a clean heap. Recycle before each attempt so
|
|
1876
|
+
// every file gets the absolute cleanest WASM state possible. Timeouts are
|
|
1877
|
+
// retried too (#1231): most are main-thread-stall artifacts, not slow
|
|
1878
|
+
// parses, and this pass parses one file at a time with the store strictly
|
|
1879
|
+
// after each parse resolves, so the stall window can't recur here.
|
|
1880
|
+
const retryableErrors = errors.filter((e) => e.code === 'parse_error' && e.filePath &&
|
|
1881
|
+
(e.message.includes('Worker exited') ||
|
|
1882
|
+
e.message.includes('memory access out of bounds') ||
|
|
1883
|
+
e.message.includes('timed out')));
|
|
1884
|
+
if (retryableErrors.length > 0 && pool) {
|
|
1885
|
+
log(`Retrying ${retryableErrors.length} files that failed due to WASM memory errors or timeouts...`);
|
|
1886
|
+
// Fresh WASM heaps for the retry phase. A retry that still crashes its
|
|
1887
|
+
// worker makes the pool respawn it, so later retries keep landing on clean
|
|
1888
|
+
// workers too.
|
|
1920
1889
|
pool.recycleAll();
|
|
1921
|
-
|
|
1890
|
+
const stillFailing = [];
|
|
1891
|
+
for (const errEntry of retryableErrors) {
|
|
1922
1892
|
const filePath = errEntry.filePath;
|
|
1923
1893
|
if (signal?.aborted)
|
|
1924
1894
|
break;
|
|
1925
|
-
let
|
|
1895
|
+
let content;
|
|
1926
1896
|
try {
|
|
1927
1897
|
const fullPath = (0, utils_1.validatePathWithinRoot)(this.rootDir, filePath);
|
|
1928
1898
|
if (!fullPath)
|
|
1929
1899
|
continue;
|
|
1930
|
-
|
|
1900
|
+
content = await readForExtraction(fullPath);
|
|
1931
1901
|
}
|
|
1932
1902
|
catch {
|
|
1933
1903
|
continue;
|
|
1934
1904
|
}
|
|
1935
|
-
// Strip lines that are entirely comments (preserving line numbers
|
|
1936
|
-
// by replacing with empty lines so node positions stay correct)
|
|
1937
|
-
const stripped = fullContent
|
|
1938
|
-
.split('\n')
|
|
1939
|
-
.map(line => /^\s*\/\//.test(line) ? '' : line)
|
|
1940
|
-
.join('\n');
|
|
1941
1905
|
let result;
|
|
1942
1906
|
try {
|
|
1943
|
-
result = await parseFile(filePath,
|
|
1907
|
+
result = await parseFile(filePath, content);
|
|
1944
1908
|
}
|
|
1945
1909
|
catch {
|
|
1910
|
+
stillFailing.push(errEntry);
|
|
1946
1911
|
continue;
|
|
1947
1912
|
}
|
|
1948
1913
|
if (result.nodes.length > 0 || result.errors.length === 0) {
|
|
1949
|
-
const language = (0, grammars_1.detectLanguage)(filePath,
|
|
1914
|
+
const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
|
|
1950
1915
|
const stats = await fsp.stat(path.join(this.rootDir, filePath));
|
|
1951
|
-
await this.storeExtractionResult(filePath,
|
|
1916
|
+
await this.storeExtractionResult(filePath, content, language, stats, result, commitYield);
|
|
1952
1917
|
const idx = errors.indexOf(errEntry);
|
|
1953
1918
|
if (idx >= 0)
|
|
1954
1919
|
errors.splice(idx, 1);
|
|
@@ -1956,25 +1921,75 @@ class ExtractionOrchestrator {
|
|
|
1956
1921
|
filesIndexed++;
|
|
1957
1922
|
totalNodes += result.nodes.length;
|
|
1958
1923
|
totalEdges += result.edges.length;
|
|
1959
|
-
log(`Retry
|
|
1924
|
+
log(`Retry OK: ${filePath} (${result.nodes.length} nodes)`);
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
// Last resort: for files that still crash on a clean worker, strip
|
|
1928
|
+
// comment-only lines to reduce WASM memory pressure. Many compiler
|
|
1929
|
+
// test files are 90%+ comments (CHECK directives) that don't contribute
|
|
1930
|
+
// code nodes but consume parser memory.
|
|
1931
|
+
if (stillFailing.length > 0) {
|
|
1932
|
+
log(`${stillFailing.length} files still failing — retrying with comments stripped...`);
|
|
1933
|
+
pool.recycleAll();
|
|
1934
|
+
for (const errEntry of stillFailing) {
|
|
1935
|
+
const filePath = errEntry.filePath;
|
|
1936
|
+
if (signal?.aborted)
|
|
1937
|
+
break;
|
|
1938
|
+
let fullContent;
|
|
1939
|
+
try {
|
|
1940
|
+
const fullPath = (0, utils_1.validatePathWithinRoot)(this.rootDir, filePath);
|
|
1941
|
+
if (!fullPath)
|
|
1942
|
+
continue;
|
|
1943
|
+
fullContent = await readForExtraction(fullPath);
|
|
1944
|
+
}
|
|
1945
|
+
catch {
|
|
1946
|
+
continue;
|
|
1947
|
+
}
|
|
1948
|
+
// Strip lines that are entirely comments (preserving line numbers
|
|
1949
|
+
// by replacing with empty lines so node positions stay correct)
|
|
1950
|
+
const stripped = fullContent
|
|
1951
|
+
.split('\n')
|
|
1952
|
+
.map(line => /^\s*\/\//.test(line) ? '' : line)
|
|
1953
|
+
.join('\n');
|
|
1954
|
+
let result;
|
|
1955
|
+
try {
|
|
1956
|
+
result = await parseFile(filePath, stripped);
|
|
1957
|
+
}
|
|
1958
|
+
catch {
|
|
1959
|
+
continue;
|
|
1960
|
+
}
|
|
1961
|
+
if (result.nodes.length > 0 || result.errors.length === 0) {
|
|
1962
|
+
const language = (0, grammars_1.detectLanguage)(filePath, fullContent, overrides);
|
|
1963
|
+
const stats = await fsp.stat(path.join(this.rootDir, filePath));
|
|
1964
|
+
await this.storeExtractionResult(filePath, fullContent, language, stats, result, commitYield);
|
|
1965
|
+
const idx = errors.indexOf(errEntry);
|
|
1966
|
+
if (idx >= 0)
|
|
1967
|
+
errors.splice(idx, 1);
|
|
1968
|
+
filesErrored--;
|
|
1969
|
+
filesIndexed++;
|
|
1970
|
+
totalNodes += result.nodes.length;
|
|
1971
|
+
totalEdges += result.edges.length;
|
|
1972
|
+
log(`Retry (stripped) OK: ${filePath} (${result.nodes.length} nodes)`);
|
|
1973
|
+
}
|
|
1960
1974
|
}
|
|
1961
1975
|
}
|
|
1962
1976
|
}
|
|
1977
|
+
return {
|
|
1978
|
+
success: filesIndexed > 0 || errors.filter((e) => e.severity === 'error').length === 0,
|
|
1979
|
+
filesIndexed,
|
|
1980
|
+
filesSkipped,
|
|
1981
|
+
filesErrored,
|
|
1982
|
+
filesDiscovered: total,
|
|
1983
|
+
nodesCreated: totalNodes,
|
|
1984
|
+
edgesCreated: totalEdges,
|
|
1985
|
+
errors,
|
|
1986
|
+
durationMs: Date.now() - startTime,
|
|
1987
|
+
};
|
|
1988
|
+
}
|
|
1989
|
+
finally {
|
|
1990
|
+
if (pool)
|
|
1991
|
+
await pool.destroy();
|
|
1963
1992
|
}
|
|
1964
|
-
// Shut down the parse worker pool.
|
|
1965
|
-
if (pool)
|
|
1966
|
-
await pool.destroy();
|
|
1967
|
-
return {
|
|
1968
|
-
success: filesIndexed > 0 || errors.filter((e) => e.severity === 'error').length === 0,
|
|
1969
|
-
filesIndexed,
|
|
1970
|
-
filesSkipped,
|
|
1971
|
-
filesErrored,
|
|
1972
|
-
filesDiscovered: total,
|
|
1973
|
-
nodesCreated: totalNodes,
|
|
1974
|
-
edgesCreated: totalEdges,
|
|
1975
|
-
errors,
|
|
1976
|
-
durationMs: Date.now() - startTime,
|
|
1977
|
-
};
|
|
1978
1993
|
}
|
|
1979
1994
|
/**
|
|
1980
1995
|
* Index specific files
|
|
@@ -2116,7 +2131,12 @@ class ExtractionOrchestrator {
|
|
|
2116
2131
|
// Issue #154: also thread the `vba.dysflowExport` flag so the
|
|
2117
2132
|
// single-file re-index path honours the same opt-out the full scan does.
|
|
2118
2133
|
const dysflowExport = (0, project_config_1.loadDysflowExportConfig)(this.rootDir);
|
|
2119
|
-
const result = (0, tree_sitter_1.extractFromSource)(relativePath, content, language, frameworkNames,
|
|
2134
|
+
const result = (0, tree_sitter_1.extractFromSource)(relativePath, content, language, frameworkNames, undefined, undefined, dysflowExport, {
|
|
2135
|
+
targets: vbaReindexConfig.targets,
|
|
2136
|
+
maxRaiseFanout: vbaReindexConfig.maxRaiseFanout,
|
|
2137
|
+
sqlWrappers: vbaReindexConfig.sqlWrappers,
|
|
2138
|
+
errorChannel: vbaReindexConfig.errorChannel,
|
|
2139
|
+
});
|
|
2120
2140
|
// Store in database
|
|
2121
2141
|
if (result.nodes.length > 0 || result.errors.length === 0) {
|
|
2122
2142
|
await this.storeExtractionResult(relativePath, content, language, stats, result, (0, cooperative_yield_1.createYielder)());
|