@aroman22/codegraph-vba-darwin-arm64 1.5.2 → 1.6.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/bin/codegraph +5 -0
- package/lib/dist/bin/codegraph.d.ts +1 -1
- package/lib/dist/bin/codegraph.d.ts.map +1 -1
- package/lib/dist/bin/codegraph.js +307 -81
- package/lib/dist/bin/codegraph.js.map +1 -1
- package/lib/dist/bin/command-supervision.d.ts +38 -1
- package/lib/dist/bin/command-supervision.d.ts.map +1 -1
- package/lib/dist/bin/command-supervision.js +22 -3
- package/lib/dist/bin/command-supervision.js.map +1 -1
- package/lib/dist/db/index.d.ts +84 -5
- package/lib/dist/db/index.d.ts.map +1 -1
- package/lib/dist/db/index.js +209 -12
- package/lib/dist/db/index.js.map +1 -1
- package/lib/dist/db/migrations.d.ts +1 -1
- package/lib/dist/db/migrations.d.ts.map +1 -1
- package/lib/dist/db/migrations.js +80 -3
- package/lib/dist/db/migrations.js.map +1 -1
- package/lib/dist/db/queries.d.ts +116 -4
- package/lib/dist/db/queries.d.ts.map +1 -1
- package/lib/dist/db/queries.js +294 -7
- package/lib/dist/db/queries.js.map +1 -1
- package/lib/dist/db/schema.sql +43 -1
- package/lib/dist/db/wal-valve.d.ts +106 -0
- package/lib/dist/db/wal-valve.d.ts.map +1 -0
- package/lib/dist/db/wal-valve.js +208 -0
- package/lib/dist/db/wal-valve.js.map +1 -0
- package/lib/dist/directory.d.ts +9 -5
- package/lib/dist/directory.d.ts.map +1 -1
- package/lib/dist/directory.js +210 -19
- package/lib/dist/directory.js.map +1 -1
- package/lib/dist/extraction/cfml-extractor.d.ts +107 -0
- package/lib/dist/extraction/cfml-extractor.d.ts.map +1 -0
- package/lib/dist/extraction/cfml-extractor.js +494 -0
- package/lib/dist/extraction/cfml-extractor.js.map +1 -0
- package/lib/dist/extraction/grammars.d.ts +25 -1
- package/lib/dist/extraction/grammars.d.ts.map +1 -1
- package/lib/dist/extraction/grammars.js +177 -23
- package/lib/dist/extraction/grammars.js.map +1 -1
- package/lib/dist/extraction/index.d.ts +47 -2
- package/lib/dist/extraction/index.d.ts.map +1 -1
- package/lib/dist/extraction/index.js +568 -51
- package/lib/dist/extraction/index.js.map +1 -1
- package/lib/dist/extraction/languages/arkts.d.ts +3 -0
- package/lib/dist/extraction/languages/arkts.d.ts.map +1 -0
- package/lib/dist/extraction/languages/arkts.js +127 -0
- package/lib/dist/extraction/languages/arkts.js.map +1 -0
- package/lib/dist/extraction/languages/c-cpp.d.ts +98 -0
- package/lib/dist/extraction/languages/c-cpp.d.ts.map +1 -1
- package/lib/dist/extraction/languages/c-cpp.js +563 -1
- package/lib/dist/extraction/languages/c-cpp.js.map +1 -1
- package/lib/dist/extraction/languages/cfquery.d.ts +12 -0
- package/lib/dist/extraction/languages/cfquery.d.ts.map +1 -0
- package/lib/dist/extraction/languages/cfquery.js +28 -0
- package/lib/dist/extraction/languages/cfquery.js.map +1 -0
- package/lib/dist/extraction/languages/cfscript.d.ts +3 -0
- package/lib/dist/extraction/languages/cfscript.d.ts.map +1 -0
- package/lib/dist/extraction/languages/cfscript.js +73 -0
- package/lib/dist/extraction/languages/cfscript.js.map +1 -0
- package/lib/dist/extraction/languages/cobol.d.ts +33 -0
- package/lib/dist/extraction/languages/cobol.d.ts.map +1 -0
- package/lib/dist/extraction/languages/cobol.js +499 -0
- package/lib/dist/extraction/languages/cobol.js.map +1 -0
- package/lib/dist/extraction/languages/erlang.d.ts +3 -0
- package/lib/dist/extraction/languages/erlang.d.ts.map +1 -0
- package/lib/dist/extraction/languages/erlang.js +350 -0
- package/lib/dist/extraction/languages/erlang.js.map +1 -0
- package/lib/dist/extraction/languages/index.d.ts.map +1 -1
- package/lib/dist/extraction/languages/index.js +18 -0
- package/lib/dist/extraction/languages/index.js.map +1 -1
- package/lib/dist/extraction/languages/nix.d.ts +3 -0
- package/lib/dist/extraction/languages/nix.d.ts.map +1 -0
- package/lib/dist/extraction/languages/nix.js +294 -0
- package/lib/dist/extraction/languages/nix.js.map +1 -0
- package/lib/dist/extraction/languages/solidity.d.ts +3 -0
- package/lib/dist/extraction/languages/solidity.d.ts.map +1 -0
- package/lib/dist/extraction/languages/solidity.js +293 -0
- package/lib/dist/extraction/languages/solidity.js.map +1 -0
- package/lib/dist/extraction/languages/terraform.d.ts +3 -0
- package/lib/dist/extraction/languages/terraform.d.ts.map +1 -0
- package/lib/dist/extraction/languages/terraform.js +641 -0
- package/lib/dist/extraction/languages/terraform.js.map +1 -0
- package/lib/dist/extraction/languages/vbnet.d.ts +11 -0
- package/lib/dist/extraction/languages/vbnet.d.ts.map +1 -0
- package/lib/dist/extraction/languages/vbnet.js +141 -0
- package/lib/dist/extraction/languages/vbnet.js.map +1 -0
- package/lib/dist/extraction/mybatis-extractor.d.ts +30 -10
- package/lib/dist/extraction/mybatis-extractor.d.ts.map +1 -1
- package/lib/dist/extraction/mybatis-extractor.js +140 -38
- package/lib/dist/extraction/mybatis-extractor.js.map +1 -1
- package/lib/dist/extraction/parse-pool.d.ts +28 -1
- package/lib/dist/extraction/parse-pool.d.ts.map +1 -1
- package/lib/dist/extraction/parse-pool.js +76 -7
- package/lib/dist/extraction/parse-pool.js.map +1 -1
- package/lib/dist/extraction/parse-worker.js +11 -4
- package/lib/dist/extraction/parse-worker.js.map +1 -1
- package/lib/dist/extraction/tree-sitter-helpers.d.ts.map +1 -1
- package/lib/dist/extraction/tree-sitter-helpers.js +1 -0
- package/lib/dist/extraction/tree-sitter-helpers.js.map +1 -1
- package/lib/dist/extraction/tree-sitter-types.d.ts +20 -1
- package/lib/dist/extraction/tree-sitter-types.d.ts.map +1 -1
- package/lib/dist/extraction/tree-sitter.d.ts +60 -1
- package/lib/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/lib/dist/extraction/tree-sitter.js +905 -20
- package/lib/dist/extraction/tree-sitter.js.map +1 -1
- package/lib/dist/extraction/vba/call-sweep.d.ts +3 -0
- package/lib/dist/extraction/vba/call-sweep.d.ts.map +1 -0
- package/lib/dist/extraction/vba/call-sweep.js +206 -0
- package/lib/dist/extraction/vba/call-sweep.js.map +1 -0
- package/lib/dist/extraction/vba/calls.d.ts +55 -0
- package/lib/dist/extraction/vba/calls.d.ts.map +1 -0
- package/lib/dist/extraction/vba/calls.js +439 -0
- package/lib/dist/extraction/vba/calls.js.map +1 -0
- package/lib/dist/extraction/vba/constants.d.ts +42 -0
- package/lib/dist/extraction/vba/constants.d.ts.map +1 -0
- package/lib/dist/extraction/vba/constants.js +112 -0
- package/lib/dist/extraction/vba/constants.js.map +1 -0
- package/lib/dist/extraction/vba/context.d.ts +175 -0
- package/lib/dist/extraction/vba/context.d.ts.map +1 -0
- package/lib/dist/extraction/vba/context.js +268 -0
- package/lib/dist/extraction/vba/context.js.map +1 -0
- package/lib/dist/extraction/vba/controls.d.ts +24 -0
- package/lib/dist/extraction/vba/controls.d.ts.map +1 -0
- package/lib/dist/extraction/vba/controls.js +89 -0
- package/lib/dist/extraction/vba/controls.js.map +1 -0
- package/lib/dist/extraction/vba/declarations.d.ts +3 -0
- package/lib/dist/extraction/vba/declarations.d.ts.map +1 -0
- package/lib/dist/extraction/vba/declarations.js +154 -0
- package/lib/dist/extraction/vba/declarations.js.map +1 -0
- package/lib/dist/extraction/vba/dims.d.ts +3 -0
- package/lib/dist/extraction/vba/dims.d.ts.map +1 -0
- package/lib/dist/extraction/vba/dims.js +186 -0
- package/lib/dist/extraction/vba/dims.js.map +1 -0
- package/lib/dist/extraction/vba/docmd.d.ts +16 -0
- package/lib/dist/extraction/vba/docmd.d.ts.map +1 -0
- package/lib/dist/extraction/vba/docmd.js +179 -0
- package/lib/dist/extraction/vba/docmd.js.map +1 -0
- package/lib/dist/extraction/vba/enums-consts.d.ts +15 -0
- package/lib/dist/extraction/vba/enums-consts.d.ts.map +1 -0
- package/lib/dist/extraction/vba/enums-consts.js +178 -0
- package/lib/dist/extraction/vba/enums-consts.js.map +1 -0
- package/lib/dist/extraction/vba/implements.d.ts +3 -0
- package/lib/dist/extraction/vba/implements.d.ts.map +1 -0
- package/lib/dist/extraction/vba/implements.js +52 -0
- package/lib/dist/extraction/vba/implements.js.map +1 -0
- package/lib/dist/extraction/vba/procedures.d.ts +3 -0
- package/lib/dist/extraction/vba/procedures.d.ts.map +1 -0
- package/lib/dist/extraction/vba/procedures.js +239 -0
- package/lib/dist/extraction/vba/procedures.js.map +1 -0
- package/lib/dist/extraction/vba/sql-wrapper.d.ts +17 -0
- package/lib/dist/extraction/vba/sql-wrapper.d.ts.map +1 -0
- package/lib/dist/extraction/vba/sql-wrapper.js +212 -0
- package/lib/dist/extraction/vba/sql-wrapper.js.map +1 -0
- package/lib/dist/extraction/vba/tempvars.d.ts +10 -0
- package/lib/dist/extraction/vba/tempvars.d.ts.map +1 -0
- package/lib/dist/extraction/vba/tempvars.js +138 -0
- package/lib/dist/extraction/vba/tempvars.js.map +1 -0
- package/lib/dist/extraction/vba/text-utils.d.ts +73 -0
- package/lib/dist/extraction/vba/text-utils.d.ts.map +1 -0
- package/lib/dist/extraction/vba/text-utils.js +195 -0
- package/lib/dist/extraction/vba/text-utils.js.map +1 -0
- package/lib/dist/extraction/vba-extractor.d.ts +3 -951
- package/lib/dist/extraction/vba-extractor.d.ts.map +1 -1
- package/lib/dist/extraction/vba-extractor.js +46 -2934
- package/lib/dist/extraction/vba-extractor.js.map +1 -1
- package/lib/dist/extraction/vba-preprocess.d.ts +1 -1
- package/lib/dist/extraction/vba-preprocess.d.ts.map +1 -1
- package/lib/dist/extraction/vba-preprocess.js +300 -88
- package/lib/dist/extraction/vba-preprocess.js.map +1 -1
- package/lib/dist/extraction/wasm/tree-sitter-arkts.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-terraform.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
- package/lib/dist/graph/traversal.d.ts.map +1 -1
- package/lib/dist/graph/traversal.js +76 -17
- package/lib/dist/graph/traversal.js.map +1 -1
- package/lib/dist/index.d.ts +81 -1
- package/lib/dist/index.d.ts.map +1 -1
- package/lib/dist/index.js +460 -10
- package/lib/dist/index.js.map +1 -1
- package/lib/dist/installer/index.d.ts +42 -0
- package/lib/dist/installer/index.d.ts.map +1 -1
- package/lib/dist/installer/index.js +97 -2
- package/lib/dist/installer/index.js.map +1 -1
- package/lib/dist/mcp/daemon.d.ts +35 -3
- package/lib/dist/mcp/daemon.d.ts.map +1 -1
- package/lib/dist/mcp/daemon.js +101 -9
- package/lib/dist/mcp/daemon.js.map +1 -1
- package/lib/dist/mcp/dynamic-boundaries.d.ts.map +1 -1
- package/lib/dist/mcp/dynamic-boundaries.js +2 -1
- package/lib/dist/mcp/dynamic-boundaries.js.map +1 -1
- package/lib/dist/mcp/early-ppid.d.ts +26 -0
- package/lib/dist/mcp/early-ppid.d.ts.map +1 -0
- package/lib/dist/mcp/early-ppid.js +29 -0
- package/lib/dist/mcp/early-ppid.js.map +1 -0
- package/lib/dist/mcp/index.d.ts.map +1 -1
- package/lib/dist/mcp/index.js +29 -4
- package/lib/dist/mcp/index.js.map +1 -1
- package/lib/dist/mcp/liveness-watchdog.d.ts +18 -1
- package/lib/dist/mcp/liveness-watchdog.d.ts.map +1 -1
- package/lib/dist/mcp/liveness-watchdog.js +73 -13
- package/lib/dist/mcp/liveness-watchdog.js.map +1 -1
- package/lib/dist/mcp/proxy.d.ts.map +1 -1
- package/lib/dist/mcp/proxy.js +30 -4
- package/lib/dist/mcp/proxy.js.map +1 -1
- package/lib/dist/mcp/query-pool.d.ts +14 -0
- package/lib/dist/mcp/query-pool.d.ts.map +1 -1
- package/lib/dist/mcp/query-pool.js +18 -0
- package/lib/dist/mcp/query-pool.js.map +1 -1
- package/lib/dist/mcp/session.d.ts +14 -0
- package/lib/dist/mcp/session.d.ts.map +1 -1
- package/lib/dist/mcp/session.js +28 -1
- package/lib/dist/mcp/session.js.map +1 -1
- package/lib/dist/mcp/startup-handshake.d.ts +44 -0
- package/lib/dist/mcp/startup-handshake.d.ts.map +1 -0
- package/lib/dist/mcp/startup-handshake.js +73 -0
- package/lib/dist/mcp/startup-handshake.js.map +1 -0
- package/lib/dist/mcp/tools.d.ts +22 -0
- package/lib/dist/mcp/tools.d.ts.map +1 -1
- package/lib/dist/mcp/tools.js +289 -23
- package/lib/dist/mcp/tools.js.map +1 -1
- package/lib/dist/mcp/transport.d.ts.map +1 -1
- package/lib/dist/mcp/transport.js +18 -0
- package/lib/dist/mcp/transport.js.map +1 -1
- package/lib/dist/project-config.d.ts +44 -0
- package/lib/dist/project-config.d.ts.map +1 -1
- package/lib/dist/project-config.js +151 -2
- package/lib/dist/project-config.js.map +1 -1
- package/lib/dist/resolution/c-fnptr-synthesizer.d.ts +2 -1
- package/lib/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -1
- package/lib/dist/resolution/c-fnptr-synthesizer.js +170 -146
- package/lib/dist/resolution/c-fnptr-synthesizer.js.map +1 -1
- package/lib/dist/resolution/callback-synthesizer.d.ts +1 -1
- package/lib/dist/resolution/callback-synthesizer.d.ts.map +1 -1
- package/lib/dist/resolution/callback-synthesizer.js +1109 -97
- package/lib/dist/resolution/callback-synthesizer.js.map +1 -1
- package/lib/dist/resolution/cooperative-yield.d.ts +32 -0
- package/lib/dist/resolution/cooperative-yield.d.ts.map +1 -0
- package/lib/dist/resolution/cooperative-yield.js +42 -0
- package/lib/dist/resolution/cooperative-yield.js.map +1 -0
- package/lib/dist/resolution/frameworks/cics.d.ts +20 -0
- package/lib/dist/resolution/frameworks/cics.d.ts.map +1 -0
- package/lib/dist/resolution/frameworks/cics.js +90 -0
- package/lib/dist/resolution/frameworks/cics.js.map +1 -0
- package/lib/dist/resolution/frameworks/index.d.ts.map +1 -1
- package/lib/dist/resolution/frameworks/index.js +6 -0
- package/lib/dist/resolution/frameworks/index.js.map +1 -1
- package/lib/dist/resolution/frameworks/java.d.ts.map +1 -1
- package/lib/dist/resolution/frameworks/java.js +14 -6
- package/lib/dist/resolution/frameworks/java.js.map +1 -1
- package/lib/dist/resolution/frameworks/terraform.d.ts +38 -0
- package/lib/dist/resolution/frameworks/terraform.d.ts.map +1 -0
- package/lib/dist/resolution/frameworks/terraform.js +277 -0
- package/lib/dist/resolution/frameworks/terraform.js.map +1 -0
- package/lib/dist/resolution/goframe-synthesizer.d.ts +2 -1
- package/lib/dist/resolution/goframe-synthesizer.d.ts.map +1 -1
- package/lib/dist/resolution/goframe-synthesizer.js +8 -3
- package/lib/dist/resolution/goframe-synthesizer.js.map +1 -1
- package/lib/dist/resolution/import-resolver.d.ts +7 -0
- package/lib/dist/resolution/import-resolver.d.ts.map +1 -1
- package/lib/dist/resolution/import-resolver.js +129 -4
- package/lib/dist/resolution/import-resolver.js.map +1 -1
- package/lib/dist/resolution/index.d.ts +64 -2
- package/lib/dist/resolution/index.d.ts.map +1 -1
- package/lib/dist/resolution/index.js +476 -58
- package/lib/dist/resolution/index.js.map +1 -1
- package/lib/dist/resolution/name-matcher.d.ts +22 -3
- package/lib/dist/resolution/name-matcher.d.ts.map +1 -1
- package/lib/dist/resolution/name-matcher.js +613 -39
- package/lib/dist/resolution/name-matcher.js.map +1 -1
- package/lib/dist/resolution/strip-comments.d.ts +1 -1
- package/lib/dist/resolution/strip-comments.d.ts.map +1 -1
- package/lib/dist/resolution/strip-comments.js +49 -0
- package/lib/dist/resolution/strip-comments.js.map +1 -1
- package/lib/dist/resolution/types.d.ts +29 -0
- package/lib/dist/resolution/types.d.ts.map +1 -1
- package/lib/dist/resolution/workspace-packages.d.ts +10 -0
- package/lib/dist/resolution/workspace-packages.d.ts.map +1 -1
- package/lib/dist/resolution/workspace-packages.js +142 -4
- package/lib/dist/resolution/workspace-packages.js.map +1 -1
- package/lib/dist/search/identifier-segments.d.ts +60 -0
- package/lib/dist/search/identifier-segments.d.ts.map +1 -0
- package/lib/dist/search/identifier-segments.js +176 -0
- package/lib/dist/search/identifier-segments.js.map +1 -0
- package/lib/dist/sync/git-hooks.d.ts.map +1 -1
- package/lib/dist/sync/git-hooks.js +2 -0
- package/lib/dist/sync/git-hooks.js.map +1 -1
- package/lib/dist/sync/watcher.d.ts +10 -5
- package/lib/dist/sync/watcher.d.ts.map +1 -1
- package/lib/dist/sync/watcher.js +51 -14
- package/lib/dist/sync/watcher.js.map +1 -1
- package/lib/dist/sync/worktree.d.ts +9 -0
- package/lib/dist/sync/worktree.d.ts.map +1 -1
- package/lib/dist/sync/worktree.js +45 -0
- package/lib/dist/sync/worktree.js.map +1 -1
- package/lib/dist/types.d.ts +25 -2
- package/lib/dist/types.d.ts.map +1 -1
- package/lib/dist/types.js +10 -0
- package/lib/dist/types.js.map +1 -1
- package/lib/dist/upgrade/index.d.ts +32 -0
- package/lib/dist/upgrade/index.d.ts.map +1 -1
- package/lib/dist/upgrade/index.js +163 -14
- package/lib/dist/upgrade/index.js.map +1 -1
- package/lib/dist/upgrade/remove-binary.d.ts +87 -0
- package/lib/dist/upgrade/remove-binary.d.ts.map +1 -0
- package/lib/dist/upgrade/remove-binary.js +289 -0
- package/lib/dist/upgrade/remove-binary.js.map +1 -0
- package/lib/dist/upgrade/update-check.d.ts +92 -0
- package/lib/dist/upgrade/update-check.d.ts.map +1 -0
- package/lib/dist/upgrade/update-check.js +258 -0
- package/lib/dist/upgrade/update-check.js.map +1 -0
- package/lib/node_modules/.modules.yaml +1 -1
- package/lib/node_modules/.pnpm-workspace-state-v1.json +1 -1
- package/lib/package.json +1 -1
- package/package.json +1 -1
- package/lib/dist/reasoning/config.d.ts +0 -45
- package/lib/dist/reasoning/config.d.ts.map +0 -1
- package/lib/dist/reasoning/config.js +0 -171
- package/lib/dist/reasoning/config.js.map +0 -1
- package/lib/dist/reasoning/credentials.d.ts +0 -5
- package/lib/dist/reasoning/credentials.d.ts.map +0 -1
- package/lib/dist/reasoning/credentials.js +0 -83
- package/lib/dist/reasoning/credentials.js.map +0 -1
- package/lib/dist/reasoning/login.d.ts +0 -21
- package/lib/dist/reasoning/login.d.ts.map +0 -1
- package/lib/dist/reasoning/login.js +0 -85
- package/lib/dist/reasoning/login.js.map +0 -1
- package/lib/dist/reasoning/reasoner.d.ts +0 -43
- package/lib/dist/reasoning/reasoner.d.ts.map +0 -1
- package/lib/dist/reasoning/reasoner.js +0 -308
- package/lib/dist/reasoning/reasoner.js.map +0 -1
|
@@ -46,6 +46,7 @@ exports.hashContent = hashContent;
|
|
|
46
46
|
exports.buildDefaultIgnore = buildDefaultIgnore;
|
|
47
47
|
exports.buildScopeIgnore = buildScopeIgnore;
|
|
48
48
|
exports.discoverEmbeddedRepoRoots = discoverEmbeddedRepoRoots;
|
|
49
|
+
exports.findUnindexedIgnoredRepos = findUnindexedIgnoredRepos;
|
|
49
50
|
exports.scanDirectory = scanDirectory;
|
|
50
51
|
exports.scanDirectoryAsync = scanDirectoryAsync;
|
|
51
52
|
const fs = __importStar(require("fs"));
|
|
@@ -64,6 +65,7 @@ const errors_1 = require("../errors");
|
|
|
64
65
|
const utils_1 = require("../utils");
|
|
65
66
|
const ignore_1 = __importDefault(require("ignore"));
|
|
66
67
|
const frameworks_1 = require("../resolution/frameworks");
|
|
68
|
+
const cooperative_yield_1 = require("../resolution/cooperative-yield");
|
|
67
69
|
/**
|
|
68
70
|
* Number of files to read in parallel during indexing.
|
|
69
71
|
* File reads are I/O-bound; batching overlaps I/O wait with CPU parse work.
|
|
@@ -84,9 +86,10 @@ const SYNC_RECONCILE_YIELD_INTERVAL = 1000;
|
|
|
84
86
|
/**
|
|
85
87
|
* Maximum time (ms) to wait for a single file to parse in the worker thread.
|
|
86
88
|
* If tree-sitter hangs or WASM runs out of memory, this prevents the entire
|
|
87
|
-
* indexing run from freezing. The worker is restarted after a timeout.
|
|
89
|
+
* indexing run from freezing. The worker is restarted after a (hard) timeout.
|
|
90
|
+
* Env-overridable via CODEGRAPH_PARSE_TIMEOUT_MS for slow storage (#1231).
|
|
88
91
|
*/
|
|
89
|
-
const PARSE_TIMEOUT_MS =
|
|
92
|
+
const PARSE_TIMEOUT_MS = (0, parse_pool_1.resolveParseTimeoutMs)(process.env.CODEGRAPH_PARSE_TIMEOUT_MS);
|
|
90
93
|
/**
|
|
91
94
|
* Number of files to parse before recycling the worker thread.
|
|
92
95
|
* WASM linear memory can grow but NEVER shrink (WebAssembly spec limitation).
|
|
@@ -176,12 +179,34 @@ const DEFAULT_IGNORE_DIRS = new Set([
|
|
|
176
179
|
// Generic cache
|
|
177
180
|
'.cache',
|
|
178
181
|
]);
|
|
182
|
+
/**
|
|
183
|
+
* Android resource directory types. A `res/` tree holds ONLY non-code resources —
|
|
184
|
+
* layouts, drawables, value bags (strings/colors/styles), menus, navigation
|
|
185
|
+
* graphs — split into one typed subdirectory per kind, optionally density/locale/
|
|
186
|
+
* version-qualified (`values-es`, `drawable-hdpi`, `layout-v21`, …). None of it
|
|
187
|
+
* yields an extractable code symbol, yet on an Android app it DOMINATES the tree
|
|
188
|
+
* (one report: 26k XML files = 97% of the project, 0 symbols), bloating the DB,
|
|
189
|
+
* slowing indexing, and skewing both the file count and `codegraph_explore`
|
|
190
|
+
* results (#1047). So these are excluded by default. The structure is
|
|
191
|
+
* self-identifying — a non-Android project has no `res/layout/` etc., so it's
|
|
192
|
+
* untouched — and the only XML that DOES produce symbols (MyBatis mappers) lives
|
|
193
|
+
* under `src/main/resources/`, never `res/`, so nothing useful is dropped.
|
|
194
|
+
* `res/raw/` is deliberately NOT here: it holds arbitrary bundled assets that can
|
|
195
|
+
* be code-ish (a `.sql` schema, a `.js`), so we leave it indexed. Override any of
|
|
196
|
+
* these with a `.gitignore` negation (e.g. `!res/values/`).
|
|
197
|
+
*/
|
|
198
|
+
const ANDROID_RES_TYPES = [
|
|
199
|
+
'anim', 'animator', 'color', 'drawable', 'font', 'layout',
|
|
200
|
+
'menu', 'mipmap', 'navigation', 'transition', 'values', 'xml',
|
|
201
|
+
];
|
|
179
202
|
/** Gitignore-style patterns for the `ignore` matcher: the dirs above plus a few globs. */
|
|
180
203
|
const DEFAULT_IGNORE_PATTERNS = [
|
|
181
204
|
...Array.from(DEFAULT_IGNORE_DIRS, (d) => `${d}/`),
|
|
182
205
|
'*.egg-info/', // Python packaging metadata
|
|
183
206
|
'cmake-build-*/', // CLion / CMake build trees
|
|
184
207
|
'bazel-*/', // Bazel output symlink trees
|
|
208
|
+
// Android resource dirs at any depth, with their qualifier variants (#1047).
|
|
209
|
+
...ANDROID_RES_TYPES.map((t) => `**/res/${t}*/`),
|
|
185
210
|
];
|
|
186
211
|
/** True if `buf` decodes as strict UTF-8 (no invalid byte sequences). */
|
|
187
212
|
function isValidUtf8(buf) {
|
|
@@ -299,6 +324,160 @@ function loadExcludeMatcher(rootDir) {
|
|
|
299
324
|
const patterns = (0, project_config_1.loadExcludePatterns)(rootDir);
|
|
300
325
|
return patterns.length > 0 ? (0, ignore_1.default)().add(patterns) : null;
|
|
301
326
|
}
|
|
327
|
+
/**
|
|
328
|
+
* Matcher for the project's `codegraph.json` `include` patterns — first-party
|
|
329
|
+
* source to force INTO the index even when `.gitignore` drops it (the general
|
|
330
|
+
* whitelist `includeIgnored` never was — that one only revives *embedded git
|
|
331
|
+
* repos*). The case it exists for: a project under a second VCS (SVN/Perforce)
|
|
332
|
+
* `.gitignore`s its own real source so it stays out of Git, yet we still want it
|
|
333
|
+
* indexed. Returns `null` when nothing is force-included (the zero-config
|
|
334
|
+
* default → no overhead, no extra walk). Built once per scan/sync/scope
|
|
335
|
+
* operation from the scan root.
|
|
336
|
+
*/
|
|
337
|
+
function loadIncludeMatcher(rootDir) {
|
|
338
|
+
const patterns = (0, project_config_1.loadIncludePatterns)(rootDir);
|
|
339
|
+
return patterns.length > 0 ? (0, ignore_1.default)().add(patterns) : null;
|
|
340
|
+
}
|
|
341
|
+
/** Glob metacharacters that end the static (literal) prefix of an `include` pattern. */
|
|
342
|
+
const GLOB_META = /[*?[\]{}!]/;
|
|
343
|
+
/**
|
|
344
|
+
* The static directory prefix of each `include` pattern — the literal leading
|
|
345
|
+
* path up to the first glob segment — trailing-slashed, used to (a) walk only
|
|
346
|
+
* the opted-in subtrees in `collectIncludedFiles` and (b) let `ScopeIgnore` keep
|
|
347
|
+
* the watcher descending toward them. `Tools/` stays `Tools/`; a recursive
|
|
348
|
+
* `Tools/**` glob yields `Tools/`; `src/local/file.ts` yields `src/local/` (the
|
|
349
|
+
* file's dir); a pattern that starts with a glob (like a leading `**`) yields
|
|
350
|
+
* `''`, meaning "no static root — walk the whole tree". Duplicates and roots
|
|
351
|
+
* nested under a broader root are collapsed so each subtree is walked once.
|
|
352
|
+
*/
|
|
353
|
+
function includeStaticRoots(patterns) {
|
|
354
|
+
const roots = new Set();
|
|
355
|
+
for (const pattern of patterns) {
|
|
356
|
+
let p = pattern.replace(/^\/+/, '');
|
|
357
|
+
const trailingSlash = p.endsWith('/');
|
|
358
|
+
if (trailingSlash)
|
|
359
|
+
p = p.slice(0, -1);
|
|
360
|
+
const segs = p.split('/').filter(Boolean);
|
|
361
|
+
const lead = [];
|
|
362
|
+
for (const s of segs) {
|
|
363
|
+
if (GLOB_META.test(s))
|
|
364
|
+
break;
|
|
365
|
+
lead.push(s);
|
|
366
|
+
}
|
|
367
|
+
const hadWildcard = lead.length < segs.length;
|
|
368
|
+
// A wholly-literal pattern with no trailing slash names a file (or a dir we
|
|
369
|
+
// can't tell apart) — drop its last segment so we walk the containing dir
|
|
370
|
+
// and let the matcher pick the file. A trailing slash or a glob means the
|
|
371
|
+
// remaining `lead` is already the directory to walk.
|
|
372
|
+
if (!hadWildcard && !trailingSlash && lead.length > 0)
|
|
373
|
+
lead.pop();
|
|
374
|
+
if (lead.length === 0) {
|
|
375
|
+
roots.clear();
|
|
376
|
+
roots.add('');
|
|
377
|
+
return ['']; // a top-level glob forces a whole-tree walk; nothing narrower matters
|
|
378
|
+
}
|
|
379
|
+
roots.add(lead.join('/') + '/');
|
|
380
|
+
}
|
|
381
|
+
// Collapse roots nested under a broader one (e.g. drop `a/b/` if `a/` is present).
|
|
382
|
+
const all = [...roots];
|
|
383
|
+
return all.filter((r) => !all.some((other) => other !== r && r.startsWith(other)));
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Actively discover the source files an `include` whitelist forces in. `git
|
|
387
|
+
* ls-files` never lists gitignored files, so a filtered filesystem walk of just
|
|
388
|
+
* the opted-in subtrees (`includeStaticRoots`) is the only way to find them.
|
|
389
|
+
* Returns project-root-relative, normalized source-file paths.
|
|
390
|
+
*
|
|
391
|
+
* A file is collected when it MATCHES `include`, is NOT hit by `exclude` (an
|
|
392
|
+
* explicit exclude always wins), is a recognized source file, and does not live
|
|
393
|
+
* under a built-in default-ignored dir (`node_modules`, `dist`, …), `.git`, or
|
|
394
|
+
* CodeGraph's data dir — those are never resurfaced, mirroring `ScopeIgnore`.
|
|
395
|
+
* `.gitignore` is deliberately NOT consulted: overriding it is the whole point.
|
|
396
|
+
*/
|
|
397
|
+
function collectIncludedFiles(rootDir, include, exclude, roots, overrides) {
|
|
398
|
+
const out = new Set();
|
|
399
|
+
const defaults = defaultsOnlyIgnore();
|
|
400
|
+
const visited = new Set();
|
|
401
|
+
const consider = (abs, rel, isDir) => {
|
|
402
|
+
if (isDir) {
|
|
403
|
+
if (defaults.ignores(rel + '/'))
|
|
404
|
+
return; // never node_modules/dist/… via include
|
|
405
|
+
// An explicit `exclude` always wins over `include`; prune the whole subtree
|
|
406
|
+
// here so a large excluded dir (a committed frontend's own vendored deps,
|
|
407
|
+
// build output, …) is never walked — the per-file guard below still catches
|
|
408
|
+
// anything a directory pattern doesn't, so this is a pure efficiency win.
|
|
409
|
+
if (exclude && exclude.ignores(rel + '/'))
|
|
410
|
+
return;
|
|
411
|
+
walk(abs);
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
if (defaults.ignores(rel))
|
|
415
|
+
return;
|
|
416
|
+
if (!include.ignores(rel))
|
|
417
|
+
return;
|
|
418
|
+
if (exclude && exclude.ignores(rel))
|
|
419
|
+
return;
|
|
420
|
+
if (!(0, grammars_1.isSourceFile)(rel, overrides))
|
|
421
|
+
return;
|
|
422
|
+
out.add(rel);
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
function walk(absDir) {
|
|
426
|
+
let realDir;
|
|
427
|
+
try {
|
|
428
|
+
realDir = fs.realpathSync(absDir);
|
|
429
|
+
}
|
|
430
|
+
catch {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
if (visited.has(realDir))
|
|
434
|
+
return; // symlink-cycle guard
|
|
435
|
+
visited.add(realDir);
|
|
436
|
+
let entries;
|
|
437
|
+
try {
|
|
438
|
+
entries = fs.readdirSync(absDir, { withFileTypes: true });
|
|
439
|
+
}
|
|
440
|
+
catch {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
for (const entry of entries) {
|
|
444
|
+
if (entry.name === '.git' || (0, directory_1.isCodeGraphDataDir)(entry.name))
|
|
445
|
+
continue;
|
|
446
|
+
const abs = path.join(absDir, entry.name);
|
|
447
|
+
const rel = (0, utils_1.normalizePath)(path.relative(rootDir, abs));
|
|
448
|
+
if (!rel || rel.startsWith('..'))
|
|
449
|
+
continue;
|
|
450
|
+
if (entry.isSymbolicLink()) {
|
|
451
|
+
try {
|
|
452
|
+
const st = fs.statSync(fs.realpathSync(abs));
|
|
453
|
+
consider(abs, rel, st.isDirectory());
|
|
454
|
+
}
|
|
455
|
+
catch {
|
|
456
|
+
// broken symlink — skip
|
|
457
|
+
}
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
consider(abs, rel, entry.isDirectory());
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
for (const root of roots) {
|
|
464
|
+
walk(root === '' ? rootDir : path.join(rootDir, root));
|
|
465
|
+
}
|
|
466
|
+
return out;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* The included source files (`codegraph.json` `include`) for a scan root, or an
|
|
470
|
+
* empty set when nothing is force-included. Centralizes loading the matcher,
|
|
471
|
+
* roots, exclude, and overrides so both enumeration paths (git and filesystem
|
|
472
|
+
* walk) add the same files.
|
|
473
|
+
*/
|
|
474
|
+
function collectIncludedFilesForRoot(rootDir) {
|
|
475
|
+
const include = loadIncludeMatcher(rootDir);
|
|
476
|
+
if (!include)
|
|
477
|
+
return new Set();
|
|
478
|
+
const roots = includeStaticRoots((0, project_config_1.loadIncludePatterns)(rootDir));
|
|
479
|
+
return collectIncludedFiles(rootDir, include, loadExcludeMatcher(rootDir), roots, (0, project_config_1.loadExtensionOverrides)(rootDir));
|
|
480
|
+
}
|
|
302
481
|
/**
|
|
303
482
|
* `git ls-files --directory` collapses a wholly-untracked/ignored directory into
|
|
304
483
|
* one entry — and when the command's own cwd is such a directory (the indexed
|
|
@@ -439,6 +618,8 @@ function findNestedGitRepos(absDir, relPrefix) {
|
|
|
439
618
|
class ScopeIgnore {
|
|
440
619
|
rootMatcher;
|
|
441
620
|
exclude;
|
|
621
|
+
include;
|
|
622
|
+
includeRoots;
|
|
442
623
|
embedded;
|
|
443
624
|
defaults = defaultsOnlyIgnore();
|
|
444
625
|
constructor(rootMatcher, embedded,
|
|
@@ -447,9 +628,21 @@ class ScopeIgnore {
|
|
|
447
628
|
* full root-relative path. Wins over everything else — an explicit user
|
|
448
629
|
* exclude applies even to tracked files and even inside embedded repos.
|
|
449
630
|
*/
|
|
450
|
-
exclude = null
|
|
631
|
+
exclude = null,
|
|
632
|
+
/**
|
|
633
|
+
* Project `codegraph.json` `include` patterns — first-party source forced
|
|
634
|
+
* INTO the index despite `.gitignore`. When a path matches, it is NOT
|
|
635
|
+
* ignored (so the watcher watches it), overriding `.gitignore`/`rootMatcher`
|
|
636
|
+
* — but never `exclude` (checked first) and never a built-in default-ignored
|
|
637
|
+
* dir. `includeRoots` are the static prefixes so a gitignored ANCESTOR
|
|
638
|
+
* directory of an included subtree still isn't pruned by the directory
|
|
639
|
+
* walker/watcher.
|
|
640
|
+
*/
|
|
641
|
+
include = null, includeRoots = []) {
|
|
451
642
|
this.rootMatcher = rootMatcher;
|
|
452
643
|
this.exclude = exclude;
|
|
644
|
+
this.include = include;
|
|
645
|
+
this.includeRoots = includeRoots;
|
|
453
646
|
// Longest root first so paths in nested embedded repos hit the innermost matcher.
|
|
454
647
|
this.embedded = [...embedded].sort((a, b) => b.root.length - a.root.length);
|
|
455
648
|
}
|
|
@@ -459,6 +652,20 @@ class ScopeIgnore {
|
|
|
459
652
|
// everywhere, including ancestors of embedded repos.
|
|
460
653
|
if (this.exclude && this.exclude.ignores(rel))
|
|
461
654
|
return true;
|
|
655
|
+
// User `include`: force first-party source in despite `.gitignore`. Never
|
|
656
|
+
// resurfaces a built-in default-ignored dir (node_modules/dist/…), so an
|
|
657
|
+
// include pattern can't accidentally pull in dependency/build trees.
|
|
658
|
+
if (this.include && !this.defaults.ignores(rel)) {
|
|
659
|
+
if (rel.endsWith('/')) {
|
|
660
|
+
// A directory on (or leading to) an included subtree must stay walkable
|
|
661
|
+
// so the watcher/walker descends to reach the forced-in files.
|
|
662
|
+
if (this.includeRoots.some((r) => r.startsWith(rel) || rel.startsWith(r)))
|
|
663
|
+
return false;
|
|
664
|
+
}
|
|
665
|
+
else if (this.include.ignores(rel)) {
|
|
666
|
+
return false;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
462
669
|
for (const { root, matcher } of this.embedded) {
|
|
463
670
|
if (rel.startsWith(root)) {
|
|
464
671
|
const inner = rel.slice(root.length);
|
|
@@ -485,7 +692,37 @@ exports.ScopeIgnore = ScopeIgnore;
|
|
|
485
692
|
*/
|
|
486
693
|
function buildScopeIgnore(rootDir, embeddedRoots) {
|
|
487
694
|
const roots = embeddedRoots ? [...embeddedRoots] : discoverEmbeddedRepoRoots(rootDir);
|
|
488
|
-
|
|
695
|
+
const include = loadIncludeMatcher(rootDir);
|
|
696
|
+
return new ScopeIgnore(buildDefaultIgnore(rootDir), roots.map((root) => ({ root, matcher: buildDefaultIgnore(path.join(rootDir, root)) })), loadExcludeMatcher(rootDir), include, include ? includeStaticRoots((0, project_config_1.loadIncludePatterns)(rootDir)) : []);
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Whether an embedded repo found as a tracked gitlink (mode 160000, #1031/#1033)
|
|
700
|
+
* must be SKIPPED rather than indexed. A gitlink is tracked, so `.gitignore`
|
|
701
|
+
* can't untrack it — but the discovery passes for it must still honor the same
|
|
702
|
+
* scope rules as every other path, or a gitignored reference/data dir full of
|
|
703
|
+
* `git add`ed clones gets pulled into the index against the user's stated intent
|
|
704
|
+
* (#1065). Two reasons to skip:
|
|
705
|
+
* 1. It sits in a built-in default-ignored location — an npm git-dependency
|
|
706
|
+
* under `node_modules` is never project code; not even an explicit opt-in
|
|
707
|
+
* revives it (matches `findIgnoredEmbeddedRepos`).
|
|
708
|
+
* 2. The parent repo's own `.gitignore` covers its path and the project did
|
|
709
|
+
* NOT opt that path in via `codegraph.json` `includeIgnored`. The gitignore
|
|
710
|
+
* rule is the user's stated intent to keep that path out of scope, exactly
|
|
711
|
+
* as for an UNtracked embedded repo — respect it by default, opt back in
|
|
712
|
+
* with `includeIgnored` (#514, #970, #976).
|
|
713
|
+
* `relDir` is repoDir-relative (trailing-slashed); `prefix` is repoDir's
|
|
714
|
+
* scan-root-relative path so the `includeIgnored` pattern is matched on the full
|
|
715
|
+
* scan-root-relative path. `defaults` is `defaultsOnlyIgnore()` and `repoIgnore`
|
|
716
|
+
* is `buildDefaultIgnore(repoDir)` (defaults + the repo's own `.gitignore`),
|
|
717
|
+
* both passed in so they're built once per repo level rather than per gitlink.
|
|
718
|
+
*/
|
|
719
|
+
function gitlinkEmbeddedRepoSkipped(relDir, prefix, defaults, repoIgnore, includeIgnored) {
|
|
720
|
+
if (defaults.ignores(relDir))
|
|
721
|
+
return true; // default-ignored — never index, opt-in can't revive
|
|
722
|
+
if (!repoIgnore.ignores(relDir))
|
|
723
|
+
return false; // not ignored at all — index as before (#1031/#1033)
|
|
724
|
+
// Gitignored by the repo's own rules — skip unless the project opted it in.
|
|
725
|
+
return !includeIgnored?.ignores((0, utils_1.normalizePath)(prefix + relDir));
|
|
489
726
|
}
|
|
490
727
|
/**
|
|
491
728
|
* Standalone discovery of every embedded repo root under `rootDir` (relative,
|
|
@@ -517,6 +754,31 @@ function discoverEmbeddedRepoRoots(rootDir) {
|
|
|
517
754
|
}
|
|
518
755
|
}
|
|
519
756
|
catch { /* untracked listing failed — ignored-side discovery still runs */ }
|
|
757
|
+
// Unexpanded gitlinks (mode 160000) with a real checkout on disk — embedded
|
|
758
|
+
// repos `git add`ed without `.gitmodules`, or submodules not active here. The
|
|
759
|
+
// untracked listing above can't see them (they're tracked), so find them the
|
|
760
|
+
// same way collectGitFiles does, keeping watcher scope == indexer scope.
|
|
761
|
+
// (#1031, #1033)
|
|
762
|
+
try {
|
|
763
|
+
const staged = (0, child_process_1.execFileSync)('git', ['ls-files', '-z', '-s', '--recurse-submodules'], { cwd: repoAbs, encoding: 'utf-8', timeout: 30000, maxBuffer: 50 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
|
|
764
|
+
const repoIgnore = buildDefaultIgnore(repoAbs);
|
|
765
|
+
for (const entry of staged.split('\0')) {
|
|
766
|
+
if (!entry || entry.slice(0, 6) !== '160000')
|
|
767
|
+
continue;
|
|
768
|
+
const tab = entry.indexOf('\t');
|
|
769
|
+
if (tab === -1)
|
|
770
|
+
continue;
|
|
771
|
+
const rel = entry.slice(tab + 1);
|
|
772
|
+
const relDir = rel.endsWith('/') ? rel : rel + '/';
|
|
773
|
+
// A gitlink under a gitignored path is respected (not indexed) unless the
|
|
774
|
+
// project opted it in — same rule as the untracked-ignored kind (#1065).
|
|
775
|
+
if (gitlinkEmbeddedRepoSkipped(relDir, prefix, defaults, repoIgnore, includeIgnored))
|
|
776
|
+
continue;
|
|
777
|
+
if (classifyGitDir(path.join(repoAbs, rel)) === 'embedded')
|
|
778
|
+
candidates.push(relDir);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
catch { /* staged listing failed — other discovery still runs */ }
|
|
520
782
|
candidates.push(...findIgnoredEmbeddedRepos(repoAbs, includeIgnored, prefix));
|
|
521
783
|
for (const rel of candidates) {
|
|
522
784
|
const full = (0, utils_1.normalizePath)(prefix + rel);
|
|
@@ -527,6 +789,50 @@ function discoverEmbeddedRepoRoots(rootDir) {
|
|
|
527
789
|
visit(rootDir, '');
|
|
528
790
|
return out;
|
|
529
791
|
}
|
|
792
|
+
/**
|
|
793
|
+
* Cap on how many skipped gitignored repos the CLI hint enumerates — a huge
|
|
794
|
+
* gitignored data dir full of clones must never turn the hint scan into a long
|
|
795
|
+
* walk. Enough to make the point; the caller says "+N more" past this.
|
|
796
|
+
*/
|
|
797
|
+
const UNINDEXED_IGNORED_REPO_HINT_CAP = 100;
|
|
798
|
+
/**
|
|
799
|
+
* The INVERSE of the gitignored side of {@link discoverEmbeddedRepoRoots}:
|
|
800
|
+
* nested git repositories under a gitignored directory that the project has NOT
|
|
801
|
+
* opted into via `codegraph.json` `includeIgnored`. These are real repos the
|
|
802
|
+
* default `init`/`index` deliberately skips because `.gitignore` excludes them
|
|
803
|
+
* (#970, #976) — most visibly the "super-repo `.gitignore`s its child repos"
|
|
804
|
+
* layout (#1156), where `init` at the parent correctly indexes ~nothing while
|
|
805
|
+
* `init` inside each child works. The CLI uses this to turn that silent empty
|
|
806
|
+
* index into an actionable hint: it names the skipped repos and offers to opt
|
|
807
|
+
* them in. Paths are `rootDir`-relative and trailing-slashed (valid
|
|
808
|
+
* `includeIgnored` patterns as-is). Returns `[]` for a non-git root (a
|
|
809
|
+
* filesystem walk already descends into nested repos there), skips built-in
|
|
810
|
+
* default-ignored dirs (`node_modules`, …), and is bounded so it never stalls
|
|
811
|
+
* on a giant ignored tree.
|
|
812
|
+
*/
|
|
813
|
+
function findUnindexedIgnoredRepos(rootDir) {
|
|
814
|
+
try {
|
|
815
|
+
(0, child_process_1.execFileSync)('git', ['rev-parse', '--git-dir'], { cwd: rootDir, encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
|
|
816
|
+
}
|
|
817
|
+
catch {
|
|
818
|
+
return [];
|
|
819
|
+
}
|
|
820
|
+
const defaults = defaultsOnlyIgnore();
|
|
821
|
+
const includeIgnored = loadIncludeIgnoredMatcher(rootDir);
|
|
822
|
+
const repos = [];
|
|
823
|
+
for (const dir of listIgnoredDirs(rootDir)) {
|
|
824
|
+
if (defaults.ignores(dir))
|
|
825
|
+
continue; // node_modules etc. — never project code
|
|
826
|
+
if (includeIgnored?.ignores((0, utils_1.normalizePath)(dir)))
|
|
827
|
+
continue; // already opted in — nothing to nag about
|
|
828
|
+
for (const repo of findNestedGitRepos(path.join(rootDir, dir), dir)) {
|
|
829
|
+
repos.push(repo);
|
|
830
|
+
if (repos.length >= UNINDEXED_IGNORED_REPO_HINT_CAP)
|
|
831
|
+
return repos;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
return repos;
|
|
835
|
+
}
|
|
530
836
|
/**
|
|
531
837
|
* Discover embedded repos hidden by `repoDir`'s OWN gitignore rules: for each
|
|
532
838
|
* gitignored directory, search for nested `.git` roots. Returns repo paths
|
|
@@ -582,14 +888,37 @@ function collectGitFiles(repoDir, prefix, files, embeddedRoots, includeIgnored =
|
|
|
582
888
|
// Without this, monorepos using submodules index 0 files. (See issue #147.)
|
|
583
889
|
// Note: --recurse-submodules only supports -c/--cached and --stage modes — it
|
|
584
890
|
// can't be combined with -o, so untracked files are gathered separately below.
|
|
891
|
+
//
|
|
892
|
+
// We use --stage (-s) rather than -c so each entry carries its file mode. That
|
|
893
|
+
// lets us spot gitlink entries (mode 160000) that --recurse-submodules did NOT
|
|
894
|
+
// expand: a nested repo `git add`ed without a `.gitmodules` entry, or a
|
|
895
|
+
// submodule that isn't active/initialized in this checkout. Such a gitlink
|
|
896
|
+
// falls through every pass — it's tracked, so the untracked `-o` listing below
|
|
897
|
+
// never reports it, and --recurse-submodules only expands ACTIVE submodules —
|
|
898
|
+
// so its source would be silently skipped, leaving only the super-repo's own
|
|
899
|
+
// files indexed. We collect those gitlinks here and recurse into them below.
|
|
900
|
+
// (An active submodule is expanded inline by --recurse-submodules and so never
|
|
901
|
+
// surfaces as a 160000 entry — only the unhandled gitlinks do.) (#1031, #1033)
|
|
902
|
+
//
|
|
585
903
|
// -z gives NUL-separated, unquoted output so non-ASCII (e.g. CJK) paths
|
|
586
904
|
// survive verbatim. Without it git octal-escapes and double-quotes such paths
|
|
587
905
|
// (the core.quotepath default), and the quoted form never matches a real file
|
|
588
|
-
// on disk → those files are silently dropped from the index. (#541)
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
906
|
+
// on disk → those files are silently dropped from the index. (#541) With -s the
|
|
907
|
+
// path follows a TAB after the `<mode> <object> <stage>` prefix.
|
|
908
|
+
const gitlinkRels = [];
|
|
909
|
+
const tracked = (0, child_process_1.execFileSync)('git', ['ls-files', '-z', '-s', '--recurse-submodules'], gitOpts);
|
|
910
|
+
for (const entry of tracked.split('\0')) {
|
|
911
|
+
if (!entry)
|
|
912
|
+
continue;
|
|
913
|
+
const tab = entry.indexOf('\t');
|
|
914
|
+
if (tab === -1)
|
|
915
|
+
continue; // --stage always emits "<mode> <object> <stage>\t<path>"
|
|
916
|
+
const rel = entry.slice(tab + 1);
|
|
917
|
+
if (entry.slice(0, 6) === '160000') {
|
|
918
|
+
gitlinkRels.push(rel); // an unexpanded gitlink — recursed into below, not a source file itself
|
|
919
|
+
continue;
|
|
920
|
+
}
|
|
921
|
+
files.add((0, utils_1.normalizePath)(prefix + rel));
|
|
593
922
|
}
|
|
594
923
|
// Untracked files (submodules manage their own untracked state). Embedded git
|
|
595
924
|
// repos surface here as a single "subdir/" entry that git refuses to descend
|
|
@@ -615,6 +944,32 @@ function collectGitFiles(repoDir, prefix, files, embeddedRoots, includeIgnored =
|
|
|
615
944
|
}
|
|
616
945
|
files.add((0, utils_1.normalizePath)(prefix + rel));
|
|
617
946
|
}
|
|
947
|
+
// Gitlink entries (mode 160000) that --recurse-submodules left unexpanded —
|
|
948
|
+
// an embedded repo `git add`ed without `.gitmodules`, or a submodule not
|
|
949
|
+
// active/initialized in this checkout. When such a gitlink has a real working
|
|
950
|
+
// tree on disk it is distinct first-party code we must index as its own
|
|
951
|
+
// embedded repo: the tracked pass skipped its contents and the untracked pass
|
|
952
|
+
// never sees it (it's tracked, not "other"). A gitlink with no checkout on disk
|
|
953
|
+
// (an uninitialized submodule — empty dir, no `.git`) has nothing to index and
|
|
954
|
+
// is left alone, as is a submodule worktree (a duplicate view, #945). (#1031, #1033)
|
|
955
|
+
if (gitlinkRels.length > 0) {
|
|
956
|
+
const defaults = defaultsOnlyIgnore();
|
|
957
|
+
const repoIgnore = buildDefaultIgnore(repoDir);
|
|
958
|
+
for (const rel of gitlinkRels) {
|
|
959
|
+
const relDir = rel.endsWith('/') ? rel : rel + '/';
|
|
960
|
+
// A gitlink under a gitignored path is respected (not indexed) unless the
|
|
961
|
+
// project opted it in via `includeIgnored` — keep tracked gitlinks under
|
|
962
|
+
// the same scope rule as the untracked-ignored kind below (#1065).
|
|
963
|
+
if (gitlinkEmbeddedRepoSkipped(relDir, prefix, defaults, repoIgnore, includeIgnored))
|
|
964
|
+
continue;
|
|
965
|
+
const childDir = path.join(repoDir, rel);
|
|
966
|
+
// 'embedded' = a real .git checkout on disk; 'worktree' and 'none' are skipped.
|
|
967
|
+
if (classifyGitDir(childDir) !== 'embedded')
|
|
968
|
+
continue;
|
|
969
|
+
embeddedRoots?.add((0, utils_1.normalizePath)(prefix + relDir));
|
|
970
|
+
collectGitFiles(childDir, prefix + relDir, files, embeddedRoots, includeIgnored);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
618
973
|
// Embedded repos hidden by THIS repo's ignore rules (`/packages/` in a
|
|
619
974
|
// super-repo .gitignore) never appear in any listing above. By default they
|
|
620
975
|
// stay hidden — `.gitignore` is respected (#970, #976). They are recursed into
|
|
@@ -658,7 +1013,15 @@ function getGitVisibleFiles(rootDir) {
|
|
|
658
1013
|
// not the parent's: the parent's .gitignore hides the child repo from git,
|
|
659
1014
|
// not from the index. (#514)
|
|
660
1015
|
const ig = buildScopeIgnore(rootDir, embeddedRoots);
|
|
661
|
-
|
|
1016
|
+
const visible = new Set([...files].filter((f) => !ig.ignores(f)));
|
|
1017
|
+
// Force-include first-party source the project whitelisted in
|
|
1018
|
+
// `codegraph.json` `include`. These are gitignored, so `git ls-files` never
|
|
1019
|
+
// listed them above — discover them directly off disk and add them. (The
|
|
1020
|
+
// common SVN+Git dual-VCS case: source committed to SVN, gitignored out of
|
|
1021
|
+
// Git, but still wanted in the graph.)
|
|
1022
|
+
for (const f of collectIncludedFilesForRoot(rootDir))
|
|
1023
|
+
visible.add(f);
|
|
1024
|
+
return visible;
|
|
662
1025
|
}
|
|
663
1026
|
catch {
|
|
664
1027
|
return null;
|
|
@@ -981,8 +1344,51 @@ function scanDirectoryWalk(rootDir, onProgress) {
|
|
|
981
1344
|
if (exclude)
|
|
982
1345
|
baseMatchers.push({ dir: rootDir, ig: exclude });
|
|
983
1346
|
walk(rootDir, baseMatchers);
|
|
1347
|
+
// Force-include first-party source whitelisted in `codegraph.json` `include`
|
|
1348
|
+
// — the walk above honours `.gitignore`, so anything gitignored was dropped;
|
|
1349
|
+
// add it back here (deduped). Mirrors the git path's union.
|
|
1350
|
+
const included = collectIncludedFilesForRoot(rootDir);
|
|
1351
|
+
if (included.size > 0) {
|
|
1352
|
+
const seen = new Set(files);
|
|
1353
|
+
for (const f of included) {
|
|
1354
|
+
if (!seen.has(f)) {
|
|
1355
|
+
files.push(f);
|
|
1356
|
+
seen.add(f);
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
984
1360
|
return files;
|
|
985
1361
|
}
|
|
1362
|
+
/**
|
|
1363
|
+
* Resurrect a resolution edge that is about to be dropped (its target symbol
|
|
1364
|
+
* was removed, renamed, or its whole file deleted) as the ORIGINAL unresolved
|
|
1365
|
+
* reference that created it, read from the refName/refKind stamp
|
|
1366
|
+
* `createEdges` writes into edge metadata. Inserted as status='pending', the
|
|
1367
|
+
* ref is consumed by the same sync's resolution sweep: it rebinds to an
|
|
1368
|
+
* alternative definition if one exists, or parks as status='failed' where the
|
|
1369
|
+
* #1240 retry finds it if the symbol later reappears.
|
|
1370
|
+
*
|
|
1371
|
+
* Returns null — drop silently, the pre-#1240 behavior — for edges without a
|
|
1372
|
+
* refName stamp (created before the stamp existed, or synthesized): rebuilding
|
|
1373
|
+
* a ref from the target's plain node name would strip the receiver/qualifier
|
|
1374
|
+
* context the original text carried (`h.greet` → `greet`) and could rebind
|
|
1375
|
+
* somewhere a full re-index never would. Silent beats wrong.
|
|
1376
|
+
*/
|
|
1377
|
+
function resurrectRefFromDroppedEdge(e) {
|
|
1378
|
+
const refName = e.metadata?.refName;
|
|
1379
|
+
if (typeof refName !== 'string' || refName.length === 0)
|
|
1380
|
+
return null;
|
|
1381
|
+
const refKind = typeof e.metadata?.refKind === 'string' ? e.metadata.refKind : e.kind;
|
|
1382
|
+
return {
|
|
1383
|
+
fromNodeId: e.source,
|
|
1384
|
+
referenceName: refName,
|
|
1385
|
+
referenceKind: refKind,
|
|
1386
|
+
line: e.line ?? 0,
|
|
1387
|
+
column: e.column ?? 0,
|
|
1388
|
+
filePath: e.sourceFilePath,
|
|
1389
|
+
language: e.sourceLanguage,
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
986
1392
|
/**
|
|
987
1393
|
* Extraction orchestrator
|
|
988
1394
|
*/
|
|
@@ -1075,7 +1481,12 @@ class ExtractionOrchestrator {
|
|
|
1075
1481
|
/**
|
|
1076
1482
|
* Index all files in the project
|
|
1077
1483
|
*/
|
|
1078
|
-
async indexAll(onProgress, signal, verbose
|
|
1484
|
+
async indexAll(onProgress, signal, verbose,
|
|
1485
|
+
// Writer-side backstop for deferred WAL checkpointing (#1231): returns
|
|
1486
|
+
// null in the normal case, or a promise to await (at this safe,
|
|
1487
|
+
// between-transactions boundary) when the WAL has outrun the off-thread
|
|
1488
|
+
// checkpointer past its hard cap. See db/wal-valve.ts.
|
|
1489
|
+
walBackpressure) {
|
|
1079
1490
|
await (0, grammars_1.initGrammars)();
|
|
1080
1491
|
const startTime = Date.now();
|
|
1081
1492
|
const errors = [];
|
|
@@ -1088,6 +1499,7 @@ class ExtractionOrchestrator {
|
|
|
1088
1499
|
// Threaded into language detection so custom-extension files load the right
|
|
1089
1500
|
// grammar and store under the mapped language.
|
|
1090
1501
|
const overrides = (0, project_config_1.loadExtensionOverrides)(this.rootDir);
|
|
1502
|
+
const vbaTargets = (0, project_config_1.loadVbaConfig)(this.rootDir).targets;
|
|
1091
1503
|
const log = verbose
|
|
1092
1504
|
? (msg) => { console.log(`[worker] ${msg}`); }
|
|
1093
1505
|
: (_msg) => { };
|
|
@@ -1097,6 +1509,10 @@ class ExtractionOrchestrator {
|
|
|
1097
1509
|
current: 0,
|
|
1098
1510
|
total: 0,
|
|
1099
1511
|
});
|
|
1512
|
+
// Phase attribution to stderr (same opt-in as the synthesis timings):
|
|
1513
|
+
// early-run 5-10s single stalls were observed on 95k-file repos but never
|
|
1514
|
+
// attributed — these labels settle scan vs framework-detect vs grammars.
|
|
1515
|
+
const tScan = Date.now();
|
|
1100
1516
|
const files = await scanDirectoryAsync(this.rootDir, (current, file) => {
|
|
1101
1517
|
onProgress?.({
|
|
1102
1518
|
phase: 'scanning',
|
|
@@ -1105,13 +1521,18 @@ class ExtractionOrchestrator {
|
|
|
1105
1521
|
currentFile: file,
|
|
1106
1522
|
});
|
|
1107
1523
|
});
|
|
1524
|
+
if (process.env.CODEGRAPH_SYNTH_TIMINGS)
|
|
1525
|
+
console.error(`[phase-timing] scan: ${Date.now() - tScan}ms (${files.length} files)`);
|
|
1108
1526
|
// Detect frameworks once per indexAll run using the scanned file list.
|
|
1109
1527
|
// Names are passed to each parse call so framework-specific extractors
|
|
1110
1528
|
// (route nodes, middleware, etc.) run after the tree-sitter pass.
|
|
1111
1529
|
// Framework detection is reset each run so adding e.g. requirements.txt
|
|
1112
1530
|
// between runs is picked up without restarting the process.
|
|
1113
1531
|
this.detectedFrameworkNames = null;
|
|
1532
|
+
const tFw = Date.now();
|
|
1114
1533
|
const frameworkNames = this.ensureDetectedFrameworks(files);
|
|
1534
|
+
if (process.env.CODEGRAPH_SYNTH_TIMINGS)
|
|
1535
|
+
console.error(`[phase-timing] framework-detect: ${Date.now() - tFw}ms`);
|
|
1115
1536
|
if (signal?.aborted) {
|
|
1116
1537
|
return {
|
|
1117
1538
|
success: false,
|
|
@@ -1152,6 +1573,11 @@ class ExtractionOrchestrator {
|
|
|
1152
1573
|
// CODEGRAPH_PARSE_WORKERS: explicit worker count; 1 = the old single-worker
|
|
1153
1574
|
// behaviour (the conservative rollback). Unset → clamp(cores-1, 1, 8).
|
|
1154
1575
|
const poolSize = (0, parse_pool_1.resolveParsePoolSize)(process.env.CODEGRAPH_PARSE_WORKERS, os.cpus().length);
|
|
1576
|
+
// Read each needed grammar's WASM ONCE here and hand the bytes to every
|
|
1577
|
+
// worker, so spawns/respawns load grammars from memory instead of
|
|
1578
|
+
// re-reading them from disk (#1231: on an HDD, respawn re-reads amplify
|
|
1579
|
+
// the very I/O contention that caused the respawn).
|
|
1580
|
+
const grammarBuffers = await (0, grammars_1.readGrammarWasmBytes)(neededLanguages);
|
|
1155
1581
|
pool = new parse_pool_1.ParseWorkerPool({
|
|
1156
1582
|
languages: neededLanguages,
|
|
1157
1583
|
size: poolSize,
|
|
@@ -1159,6 +1585,7 @@ class ExtractionOrchestrator {
|
|
|
1159
1585
|
recycleInterval: WORKER_RECYCLE_INTERVAL,
|
|
1160
1586
|
parseTimeoutMs: PARSE_TIMEOUT_MS,
|
|
1161
1587
|
log,
|
|
1588
|
+
grammarBuffers,
|
|
1162
1589
|
});
|
|
1163
1590
|
log(`Parse worker pool: ${poolSize} worker(s)`);
|
|
1164
1591
|
}
|
|
@@ -1175,8 +1602,8 @@ class ExtractionOrchestrator {
|
|
|
1175
1602
|
const parseFile = (filePath, content) => {
|
|
1176
1603
|
const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
|
|
1177
1604
|
if (!pool)
|
|
1178
|
-
return Promise.resolve((0, tree_sitter_1.extractFromSource)(filePath, content, language, frameworkNames));
|
|
1179
|
-
return pool.requestParse({ filePath, content, language, frameworkNames });
|
|
1605
|
+
return Promise.resolve((0, tree_sitter_1.extractFromSource)(filePath, content, language, frameworkNames, vbaTargets));
|
|
1606
|
+
return pool.requestParse({ filePath, content, language, frameworkNames, vbaTargets });
|
|
1180
1607
|
};
|
|
1181
1608
|
// --- Bounded rolling-window dispatch, ordered commit ---
|
|
1182
1609
|
// Reads stay batched/parallel; parses run concurrently across the pool; the
|
|
@@ -1194,12 +1621,23 @@ class ExtractionOrchestrator {
|
|
|
1194
1621
|
let nextSeq = 0; // file-order sequence assigned at dispatch
|
|
1195
1622
|
let nextToStore = 0; // cursor: next sequence to commit
|
|
1196
1623
|
let aborted = false;
|
|
1197
|
-
|
|
1624
|
+
// Yielder for the in-order commit path: a single giant generated file's
|
|
1625
|
+
// store is otherwise one unyielding multi-second transaction span on the
|
|
1626
|
+
// main thread (5–14s single stalls measured on llvm-project), starving
|
|
1627
|
+
// the #850 watchdog heartbeat on slow hardware.
|
|
1628
|
+
const commitYield = (0, cooperative_yield_1.createYielder)();
|
|
1629
|
+
const storeResult = async (filePath, content, stats, result) => {
|
|
1198
1630
|
processed++;
|
|
1631
|
+
// WAL hard-cap backstop: between files (never mid-transaction), pause
|
|
1632
|
+
// the store until the off-thread checkpoint catches up. Resolves to
|
|
1633
|
+
// null in the normal case — a single size check, no cost.
|
|
1634
|
+
const bp = walBackpressure?.();
|
|
1635
|
+
if (bp)
|
|
1636
|
+
await bp;
|
|
1199
1637
|
// Store in database on main thread (SQLite is not thread-safe)
|
|
1200
1638
|
if (result.nodes.length > 0 || result.errors.length === 0) {
|
|
1201
1639
|
const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
|
|
1202
|
-
this.storeExtractionResult(filePath, content, language, stats, result);
|
|
1640
|
+
await this.storeExtractionResult(filePath, content, language, stats, result, commitYield);
|
|
1203
1641
|
}
|
|
1204
1642
|
if (result.errors.length > 0) {
|
|
1205
1643
|
for (const err of result.errors) {
|
|
@@ -1243,20 +1681,35 @@ class ExtractionOrchestrator {
|
|
|
1243
1681
|
};
|
|
1244
1682
|
// Commit buffered parses to the DB in file order, advancing the cursor over
|
|
1245
1683
|
// contiguous completed results. Runs after each parse settles (and once more
|
|
1246
|
-
// after the drain). storeResult
|
|
1247
|
-
// so
|
|
1684
|
+
// after the drain). storeResult is now async (it yields between chunked
|
|
1685
|
+
// inserts), so commits are SERIALIZED on a promise chain — concurrent parse
|
|
1686
|
+
// completions append to the chain instead of interleaving mid-store, which
|
|
1687
|
+
// preserves both the file-order commit invariant (#1015: resolution
|
|
1688
|
+
// disambiguates same-named candidates by insertion order) and the
|
|
1689
|
+
// single-writer discipline for SQLite. Errors are recorded and re-thrown
|
|
1690
|
+
// at the drain, matching the old synchronous propagation.
|
|
1691
|
+
let flushChain = Promise.resolve();
|
|
1692
|
+
let flushError = null;
|
|
1248
1693
|
const flushOrdered = () => {
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1694
|
+
flushChain = flushChain.then(async () => {
|
|
1695
|
+
if (aborted || flushError)
|
|
1696
|
+
return;
|
|
1697
|
+
try {
|
|
1698
|
+
while (completed.has(nextToStore)) {
|
|
1699
|
+
const item = completed.get(nextToStore);
|
|
1700
|
+
completed.delete(nextToStore);
|
|
1701
|
+
nextToStore++;
|
|
1702
|
+
if (item.ok)
|
|
1703
|
+
await storeResult(item.filePath, item.content, item.stats, item.result);
|
|
1704
|
+
else
|
|
1705
|
+
recordParseFailure(item.filePath, item.err);
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
catch (err) {
|
|
1709
|
+
flushError = err;
|
|
1710
|
+
}
|
|
1711
|
+
});
|
|
1712
|
+
return flushChain;
|
|
1260
1713
|
};
|
|
1261
1714
|
// Dispatch one file's parse (parses run concurrently across the pool), tagged
|
|
1262
1715
|
// with its file-order sequence so flushOrdered commits results in order. The
|
|
@@ -1279,10 +1732,15 @@ class ExtractionOrchestrator {
|
|
|
1279
1732
|
// buffered), not just in-flight: a slow file sitting at the commit cursor
|
|
1280
1733
|
// lets later parses finish and buffer, which would otherwise grow without
|
|
1281
1734
|
// bound. Wait for parses to settle (each may advance the cursor) until the
|
|
1282
|
-
// window has room.
|
|
1283
|
-
//
|
|
1284
|
-
|
|
1285
|
-
|
|
1735
|
+
// window has room. When nothing is in flight but the window is still full,
|
|
1736
|
+
// the async commit chain is what's behind — await it so the cursor
|
|
1737
|
+
// advances (buffered items hold whole file contents, so this bound is
|
|
1738
|
+
// load-bearing for memory).
|
|
1739
|
+
while (nextSeq - nextToStore >= windowSize) {
|
|
1740
|
+
if (inFlight.size > 0)
|
|
1741
|
+
await Promise.race(inFlight);
|
|
1742
|
+
else
|
|
1743
|
+
await flushOrdered();
|
|
1286
1744
|
}
|
|
1287
1745
|
};
|
|
1288
1746
|
for (let i = 0; i < files.length; i += FILE_IO_BATCH_SIZE) {
|
|
@@ -1357,7 +1815,9 @@ class ExtractionOrchestrator {
|
|
|
1357
1815
|
// then commit any results the cursor hasn't reached yet.
|
|
1358
1816
|
if (!aborted) {
|
|
1359
1817
|
await Promise.all(inFlight);
|
|
1360
|
-
flushOrdered();
|
|
1818
|
+
await flushOrdered();
|
|
1819
|
+
if (flushError)
|
|
1820
|
+
throw flushError;
|
|
1361
1821
|
}
|
|
1362
1822
|
if (signal?.aborted || aborted) {
|
|
1363
1823
|
if (pool)
|
|
@@ -1367,6 +1827,7 @@ class ExtractionOrchestrator {
|
|
|
1367
1827
|
filesIndexed,
|
|
1368
1828
|
filesSkipped,
|
|
1369
1829
|
filesErrored,
|
|
1830
|
+
filesDiscovered: total,
|
|
1370
1831
|
nodesCreated: totalNodes,
|
|
1371
1832
|
edgesCreated: totalEdges,
|
|
1372
1833
|
errors: [{ message: 'Aborted', severity: 'error' }, ...errors],
|
|
@@ -1385,11 +1846,16 @@ class ExtractionOrchestrator {
|
|
|
1385
1846
|
await new Promise(resolve => setImmediate(resolve));
|
|
1386
1847
|
// Retry pass: files that failed due to WASM memory corruption may succeed
|
|
1387
1848
|
// on a fresh worker with a clean heap. Recycle before each attempt so
|
|
1388
|
-
// every file gets the absolute cleanest WASM state possible.
|
|
1849
|
+
// every file gets the absolute cleanest WASM state possible. Timeouts are
|
|
1850
|
+
// retried too (#1231): most are main-thread-stall artifacts, not slow
|
|
1851
|
+
// parses, and this pass parses one file at a time with the store strictly
|
|
1852
|
+
// after each parse resolves, so the stall window can't recur here.
|
|
1389
1853
|
const retryableErrors = errors.filter((e) => e.code === 'parse_error' && e.filePath &&
|
|
1390
|
-
(e.message.includes('Worker exited') ||
|
|
1854
|
+
(e.message.includes('Worker exited') ||
|
|
1855
|
+
e.message.includes('memory access out of bounds') ||
|
|
1856
|
+
e.message.includes('timed out')));
|
|
1391
1857
|
if (retryableErrors.length > 0 && pool) {
|
|
1392
|
-
log(`Retrying ${retryableErrors.length} files that failed due to WASM memory errors...`);
|
|
1858
|
+
log(`Retrying ${retryableErrors.length} files that failed due to WASM memory errors or timeouts...`);
|
|
1393
1859
|
// Fresh WASM heaps for the retry phase. A retry that still crashes its
|
|
1394
1860
|
// worker makes the pool respawn it, so later retries keep landing on clean
|
|
1395
1861
|
// workers too.
|
|
@@ -1420,7 +1886,7 @@ class ExtractionOrchestrator {
|
|
|
1420
1886
|
if (result.nodes.length > 0 || result.errors.length === 0) {
|
|
1421
1887
|
const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
|
|
1422
1888
|
const stats = await fsp.stat(path.join(this.rootDir, filePath));
|
|
1423
|
-
this.storeExtractionResult(filePath, content, language, stats, result);
|
|
1889
|
+
await this.storeExtractionResult(filePath, content, language, stats, result, commitYield);
|
|
1424
1890
|
const idx = errors.indexOf(errEntry);
|
|
1425
1891
|
if (idx >= 0)
|
|
1426
1892
|
errors.splice(idx, 1);
|
|
@@ -1468,7 +1934,7 @@ class ExtractionOrchestrator {
|
|
|
1468
1934
|
if (result.nodes.length > 0 || result.errors.length === 0) {
|
|
1469
1935
|
const language = (0, grammars_1.detectLanguage)(filePath, fullContent, overrides);
|
|
1470
1936
|
const stats = await fsp.stat(path.join(this.rootDir, filePath));
|
|
1471
|
-
this.storeExtractionResult(filePath, fullContent, language, stats, result);
|
|
1937
|
+
await this.storeExtractionResult(filePath, fullContent, language, stats, result, commitYield);
|
|
1472
1938
|
const idx = errors.indexOf(errEntry);
|
|
1473
1939
|
if (idx >= 0)
|
|
1474
1940
|
errors.splice(idx, 1);
|
|
@@ -1489,6 +1955,7 @@ class ExtractionOrchestrator {
|
|
|
1489
1955
|
filesIndexed,
|
|
1490
1956
|
filesSkipped,
|
|
1491
1957
|
filesErrored,
|
|
1958
|
+
filesDiscovered: total,
|
|
1492
1959
|
nodesCreated: totalNodes,
|
|
1493
1960
|
edgesCreated: totalEdges,
|
|
1494
1961
|
errors,
|
|
@@ -1632,14 +2099,21 @@ class ExtractionOrchestrator {
|
|
|
1632
2099
|
const result = (0, tree_sitter_1.extractFromSource)(relativePath, content, language, frameworkNames);
|
|
1633
2100
|
// Store in database
|
|
1634
2101
|
if (result.nodes.length > 0 || result.errors.length === 0) {
|
|
1635
|
-
this.storeExtractionResult(relativePath, content, language, stats, result);
|
|
2102
|
+
await this.storeExtractionResult(relativePath, content, language, stats, result, (0, cooperative_yield_1.createYielder)());
|
|
1636
2103
|
}
|
|
1637
2104
|
return result;
|
|
1638
2105
|
}
|
|
1639
2106
|
/**
|
|
1640
2107
|
* Store extraction result in database
|
|
1641
2108
|
*/
|
|
1642
|
-
storeExtractionResult(filePath, content, language, stats, result) {
|
|
2109
|
+
async storeExtractionResult(filePath, content, language, stats, result, onYield) {
|
|
2110
|
+
// Bulk inserts run in bounded sub-transactions with a yield between, so a
|
|
2111
|
+
// giant generated file (tens of thousands of symbols) can't block the
|
|
2112
|
+
// event loop — and the #850 watchdog heartbeat — for the whole store.
|
|
2113
|
+
// The file was NEVER one atomic transaction (each insert call has its
|
|
2114
|
+
// own), and the files-table record still lands last, so crash recovery
|
|
2115
|
+
// is unchanged: a partially-stored file has no record and re-indexes.
|
|
2116
|
+
const STORE_CHUNK = 2000;
|
|
1643
2117
|
const contentHash = hashContent(content);
|
|
1644
2118
|
// Check if file already exists and hasn't changed
|
|
1645
2119
|
const existingFile = this.queries.getFileByPath(filePath);
|
|
@@ -1671,16 +2145,18 @@ class ExtractionOrchestrator {
|
|
|
1671
2145
|
// This prevents FK violations when edges reference nodes that would
|
|
1672
2146
|
// be silently skipped by insertNode() (see issue #42).
|
|
1673
2147
|
const validNodes = result.nodes.filter((n) => n.id && n.kind && n.name && n.filePath && n.language);
|
|
1674
|
-
// Insert nodes
|
|
1675
|
-
|
|
1676
|
-
this.queries.insertNodes(validNodes);
|
|
2148
|
+
// Insert nodes (chunked — see STORE_CHUNK above)
|
|
2149
|
+
for (let i = 0; i < validNodes.length; i += STORE_CHUNK) {
|
|
2150
|
+
this.queries.insertNodes(validNodes.slice(i, i + STORE_CHUNK));
|
|
2151
|
+
await onYield?.();
|
|
1677
2152
|
}
|
|
1678
2153
|
// Filter edges to only reference nodes that were actually inserted
|
|
1679
2154
|
if (result.edges.length > 0) {
|
|
1680
2155
|
const insertedIds = new Set(validNodes.map((n) => n.id));
|
|
1681
2156
|
const validEdges = result.edges.filter((e) => insertedIds.has(e.source) && insertedIds.has(e.target));
|
|
1682
|
-
|
|
1683
|
-
this.queries.insertEdges(validEdges);
|
|
2157
|
+
for (let i = 0; i < validEdges.length; i += STORE_CHUNK) {
|
|
2158
|
+
this.queries.insertEdges(validEdges.slice(i, i + STORE_CHUNK));
|
|
2159
|
+
await onYield?.();
|
|
1684
2160
|
}
|
|
1685
2161
|
}
|
|
1686
2162
|
// Re-insert cross-file incoming edges snapshotted before the delete,
|
|
@@ -1688,25 +2164,43 @@ class ExtractionOrchestrator {
|
|
|
1688
2164
|
// (filePath, kind, name). Node ids include the source line, so any line
|
|
1689
2165
|
// shift in the callee file (e.g. a docstring-only edit above the symbol)
|
|
1690
2166
|
// changes every target id and a naive re-insert by old id would drop them
|
|
1691
|
-
// all. `insertEdges` still filters to endpoints that exist
|
|
1692
|
-
//
|
|
1693
|
-
//
|
|
1694
|
-
//
|
|
2167
|
+
// all. `insertEdges` still filters to endpoints that exist. This closes
|
|
2168
|
+
// the #899 edge-drop on `sync`.
|
|
2169
|
+
//
|
|
2170
|
+
// Edges whose callee (target) was renamed/removed during the re-index (no
|
|
2171
|
+
// match in `newNodesByKindName`) are not silently dropped anymore: each is
|
|
2172
|
+
// resurrected as its ORIGINAL unresolved ref (stamped on the edge as
|
|
2173
|
+
// metadata.refName/refKind at creation) so the same sync's resolution
|
|
2174
|
+
// sweep can rebind it to an alternative definition elsewhere, or park it
|
|
2175
|
+
// as status='failed' to be retried when the symbol reappears — the
|
|
2176
|
+
// removal-side counterpart of #1240. Edges without refName (built before
|
|
2177
|
+
// the stamp existed, or synthesized) still drop silently: reconstructing
|
|
2178
|
+
// a ref from the target's plain name would strip receiver/qualifier
|
|
2179
|
+
// context and risk a rebind a full re-index would never make.
|
|
1695
2180
|
if (crossFileIncomingEdges.length > 0) {
|
|
1696
2181
|
const newNodesByKindName = new Map();
|
|
1697
2182
|
for (const n of validNodes) {
|
|
1698
2183
|
newNodesByKindName.set(`${n.kind}\0${n.name}`, n.id);
|
|
1699
2184
|
}
|
|
1700
2185
|
const reinserted = [];
|
|
2186
|
+
const resurrected = [];
|
|
1701
2187
|
for (const e of crossFileIncomingEdges) {
|
|
1702
2188
|
const newTargetId = newNodesByKindName.get(`${e.targetKind}\0${e.targetName}`);
|
|
1703
2189
|
if (newTargetId) {
|
|
1704
2190
|
reinserted.push({ source: e.source, target: newTargetId, kind: e.kind, metadata: e.metadata, line: e.line, column: e.column, provenance: e.provenance });
|
|
1705
2191
|
}
|
|
2192
|
+
else {
|
|
2193
|
+
const ref = resurrectRefFromDroppedEdge(e);
|
|
2194
|
+
if (ref)
|
|
2195
|
+
resurrected.push(ref);
|
|
2196
|
+
}
|
|
1706
2197
|
}
|
|
1707
2198
|
if (reinserted.length > 0) {
|
|
1708
2199
|
this.queries.insertEdges(reinserted);
|
|
1709
2200
|
}
|
|
2201
|
+
if (resurrected.length > 0) {
|
|
2202
|
+
this.queries.insertUnresolvedRefsBatch(resurrected);
|
|
2203
|
+
}
|
|
1710
2204
|
}
|
|
1711
2205
|
// Insert unresolved references in batch with denormalized filePath/language
|
|
1712
2206
|
if (result.unresolvedReferences.length > 0) {
|
|
@@ -1718,8 +2212,9 @@ class ExtractionOrchestrator {
|
|
|
1718
2212
|
filePath: ref.filePath ?? filePath,
|
|
1719
2213
|
language: ref.language ?? language,
|
|
1720
2214
|
}));
|
|
1721
|
-
|
|
1722
|
-
this.queries.insertUnresolvedRefsBatch(refsWithContext);
|
|
2215
|
+
for (let i = 0; i < refsWithContext.length; i += STORE_CHUNK) {
|
|
2216
|
+
this.queries.insertUnresolvedRefsBatch(refsWithContext.slice(i, i + STORE_CHUNK));
|
|
2217
|
+
await onYield?.();
|
|
1723
2218
|
}
|
|
1724
2219
|
}
|
|
1725
2220
|
// Insert file record
|
|
@@ -1767,10 +2262,16 @@ class ExtractionOrchestrator {
|
|
|
1767
2262
|
// whether or not the project uses git, and crucially also catches committed
|
|
1768
2263
|
// changes from `git pull`/`checkout`/`merge`/`rebase` — which `git status`
|
|
1769
2264
|
// cannot see, because the working tree is clean afterward.
|
|
2265
|
+
const tSyncScan = Date.now();
|
|
1770
2266
|
const currentFiles = await scanDirectoryAsync(this.rootDir);
|
|
2267
|
+
if (process.env.CODEGRAPH_SYNTH_TIMINGS)
|
|
2268
|
+
console.error(`[phase-timing] sync-scan: ${Date.now() - tSyncScan}ms (${currentFiles.length} files)`);
|
|
1771
2269
|
filesChecked = currentFiles.length;
|
|
1772
2270
|
const currentSet = new Set(currentFiles);
|
|
2271
|
+
const tTracked = Date.now();
|
|
1773
2272
|
const trackedFiles = this.queries.getAllFiles();
|
|
2273
|
+
if (process.env.CODEGRAPH_SYNTH_TIMINGS)
|
|
2274
|
+
console.error(`[phase-timing] sync-tracked-load: ${Date.now() - tTracked}ms (${trackedFiles.length} tracked)`);
|
|
1774
2275
|
const trackedMap = new Map();
|
|
1775
2276
|
for (const f of trackedFiles) {
|
|
1776
2277
|
trackedMap.set(f.path, f);
|
|
@@ -1783,6 +2284,22 @@ class ExtractionOrchestrator {
|
|
|
1783
2284
|
let reconcileChecks = 0;
|
|
1784
2285
|
for (const tracked of trackedFiles) {
|
|
1785
2286
|
if (!currentSet.has(tracked.path) || !fs.existsSync(path.join(this.rootDir, tracked.path))) {
|
|
2287
|
+
// Before the cascade deletes them, resurrect incoming cross-file
|
|
2288
|
+
// resolution edges as their original refs (#1240 removal case): the
|
|
2289
|
+
// callers live in files this sync will NOT revisit, so this is their
|
|
2290
|
+
// only chance to rebind to an alternative definition — or to park as
|
|
2291
|
+
// failed until the symbol reappears somewhere. (A deleted file whose
|
|
2292
|
+
// CALLERS are also being deleted is fine: their nodes cascade later
|
|
2293
|
+
// in this loop and take the resurrected rows with them.)
|
|
2294
|
+
const incoming = this.queries.getCrossFileIncomingEdgesWithTarget(tracked.path);
|
|
2295
|
+
if (incoming.length > 0) {
|
|
2296
|
+
const resurrected = incoming
|
|
2297
|
+
.map((e) => resurrectRefFromDroppedEdge(e))
|
|
2298
|
+
.filter((r) => r !== null);
|
|
2299
|
+
if (resurrected.length > 0) {
|
|
2300
|
+
this.queries.insertUnresolvedRefsBatch(resurrected);
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
1786
2303
|
this.queries.deleteFile(tracked.path);
|
|
1787
2304
|
filesRemoved++;
|
|
1788
2305
|
}
|