@colbymchenry/codegraph-win32-x64 1.2.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/lib/dist/bin/codegraph.d.ts +1 -1
  2. package/lib/dist/bin/codegraph.d.ts.map +1 -1
  3. package/lib/dist/bin/codegraph.js +232 -61
  4. package/lib/dist/bin/codegraph.js.map +1 -1
  5. package/lib/dist/bin/command-supervision.d.ts.map +1 -1
  6. package/lib/dist/bin/command-supervision.js +4 -1
  7. package/lib/dist/bin/command-supervision.js.map +1 -1
  8. package/lib/dist/db/index.d.ts +17 -5
  9. package/lib/dist/db/index.d.ts.map +1 -1
  10. package/lib/dist/db/index.js +68 -12
  11. package/lib/dist/db/index.js.map +1 -1
  12. package/lib/dist/db/migrations.d.ts +1 -1
  13. package/lib/dist/db/migrations.d.ts.map +1 -1
  14. package/lib/dist/db/migrations.js +19 -1
  15. package/lib/dist/db/migrations.js.map +1 -1
  16. package/lib/dist/db/queries.d.ts +76 -1
  17. package/lib/dist/db/queries.d.ts.map +1 -1
  18. package/lib/dist/db/queries.js +180 -0
  19. package/lib/dist/db/queries.js.map +1 -1
  20. package/lib/dist/db/schema.sql +19 -0
  21. package/lib/dist/directory.d.ts +9 -5
  22. package/lib/dist/directory.d.ts.map +1 -1
  23. package/lib/dist/directory.js +210 -19
  24. package/lib/dist/directory.js.map +1 -1
  25. package/lib/dist/extraction/cfml-extractor.d.ts +107 -0
  26. package/lib/dist/extraction/cfml-extractor.d.ts.map +1 -0
  27. package/lib/dist/extraction/cfml-extractor.js +494 -0
  28. package/lib/dist/extraction/cfml-extractor.js.map +1 -0
  29. package/lib/dist/extraction/grammars.d.ts +9 -0
  30. package/lib/dist/extraction/grammars.d.ts.map +1 -1
  31. package/lib/dist/extraction/grammars.js +115 -3
  32. package/lib/dist/extraction/grammars.js.map +1 -1
  33. package/lib/dist/extraction/index.d.ts +46 -1
  34. package/lib/dist/extraction/index.d.ts.map +1 -1
  35. package/lib/dist/extraction/index.js +338 -34
  36. package/lib/dist/extraction/index.js.map +1 -1
  37. package/lib/dist/extraction/languages/arkts.d.ts +3 -0
  38. package/lib/dist/extraction/languages/arkts.d.ts.map +1 -0
  39. package/lib/dist/extraction/languages/arkts.js +127 -0
  40. package/lib/dist/extraction/languages/arkts.js.map +1 -0
  41. package/lib/dist/extraction/languages/c-cpp.d.ts +42 -0
  42. package/lib/dist/extraction/languages/c-cpp.d.ts.map +1 -1
  43. package/lib/dist/extraction/languages/c-cpp.js +369 -4
  44. package/lib/dist/extraction/languages/c-cpp.js.map +1 -1
  45. package/lib/dist/extraction/languages/cfquery.d.ts +12 -0
  46. package/lib/dist/extraction/languages/cfquery.d.ts.map +1 -0
  47. package/lib/dist/extraction/languages/cfquery.js +28 -0
  48. package/lib/dist/extraction/languages/cfquery.js.map +1 -0
  49. package/lib/dist/extraction/languages/cfscript.d.ts +3 -0
  50. package/lib/dist/extraction/languages/cfscript.d.ts.map +1 -0
  51. package/lib/dist/extraction/languages/cfscript.js +73 -0
  52. package/lib/dist/extraction/languages/cfscript.js.map +1 -0
  53. package/lib/dist/extraction/languages/cobol.d.ts +33 -0
  54. package/lib/dist/extraction/languages/cobol.d.ts.map +1 -0
  55. package/lib/dist/extraction/languages/cobol.js +499 -0
  56. package/lib/dist/extraction/languages/cobol.js.map +1 -0
  57. package/lib/dist/extraction/languages/erlang.d.ts +3 -0
  58. package/lib/dist/extraction/languages/erlang.d.ts.map +1 -0
  59. package/lib/dist/extraction/languages/erlang.js +350 -0
  60. package/lib/dist/extraction/languages/erlang.js.map +1 -0
  61. package/lib/dist/extraction/languages/index.d.ts.map +1 -1
  62. package/lib/dist/extraction/languages/index.js +18 -0
  63. package/lib/dist/extraction/languages/index.js.map +1 -1
  64. package/lib/dist/extraction/languages/nix.d.ts +3 -0
  65. package/lib/dist/extraction/languages/nix.d.ts.map +1 -0
  66. package/lib/dist/extraction/languages/nix.js +294 -0
  67. package/lib/dist/extraction/languages/nix.js.map +1 -0
  68. package/lib/dist/extraction/languages/solidity.d.ts +3 -0
  69. package/lib/dist/extraction/languages/solidity.d.ts.map +1 -0
  70. package/lib/dist/extraction/languages/solidity.js +293 -0
  71. package/lib/dist/extraction/languages/solidity.js.map +1 -0
  72. package/lib/dist/extraction/languages/terraform.d.ts +3 -0
  73. package/lib/dist/extraction/languages/terraform.d.ts.map +1 -0
  74. package/lib/dist/extraction/languages/terraform.js +641 -0
  75. package/lib/dist/extraction/languages/terraform.js.map +1 -0
  76. package/lib/dist/extraction/languages/vbnet.d.ts +11 -0
  77. package/lib/dist/extraction/languages/vbnet.d.ts.map +1 -0
  78. package/lib/dist/extraction/languages/vbnet.js +141 -0
  79. package/lib/dist/extraction/languages/vbnet.js.map +1 -0
  80. package/lib/dist/extraction/mybatis-extractor.d.ts +30 -10
  81. package/lib/dist/extraction/mybatis-extractor.d.ts.map +1 -1
  82. package/lib/dist/extraction/mybatis-extractor.js +140 -38
  83. package/lib/dist/extraction/mybatis-extractor.js.map +1 -1
  84. package/lib/dist/extraction/tree-sitter-helpers.d.ts.map +1 -1
  85. package/lib/dist/extraction/tree-sitter-helpers.js +1 -0
  86. package/lib/dist/extraction/tree-sitter-helpers.js.map +1 -1
  87. package/lib/dist/extraction/tree-sitter-types.d.ts +3 -1
  88. package/lib/dist/extraction/tree-sitter-types.d.ts.map +1 -1
  89. package/lib/dist/extraction/tree-sitter.d.ts +38 -0
  90. package/lib/dist/extraction/tree-sitter.d.ts.map +1 -1
  91. package/lib/dist/extraction/tree-sitter.js +753 -10
  92. package/lib/dist/extraction/tree-sitter.js.map +1 -1
  93. package/lib/dist/extraction/wasm/tree-sitter-arkts.wasm +0 -0
  94. package/lib/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
  95. package/lib/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
  96. package/lib/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
  97. package/lib/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
  98. package/lib/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
  99. package/lib/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
  100. package/lib/dist/extraction/wasm/tree-sitter-terraform.wasm +0 -0
  101. package/lib/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
  102. package/lib/dist/index.d.ts +63 -1
  103. package/lib/dist/index.d.ts.map +1 -1
  104. package/lib/dist/index.js +327 -5
  105. package/lib/dist/index.js.map +1 -1
  106. package/lib/dist/installer/index.d.ts.map +1 -1
  107. package/lib/dist/installer/index.js +3 -1
  108. package/lib/dist/installer/index.js.map +1 -1
  109. package/lib/dist/mcp/daemon.d.ts +25 -3
  110. package/lib/dist/mcp/daemon.d.ts.map +1 -1
  111. package/lib/dist/mcp/daemon.js +62 -8
  112. package/lib/dist/mcp/daemon.js.map +1 -1
  113. package/lib/dist/mcp/dynamic-boundaries.d.ts.map +1 -1
  114. package/lib/dist/mcp/dynamic-boundaries.js +2 -1
  115. package/lib/dist/mcp/dynamic-boundaries.js.map +1 -1
  116. package/lib/dist/mcp/early-ppid.d.ts +26 -0
  117. package/lib/dist/mcp/early-ppid.d.ts.map +1 -0
  118. package/lib/dist/mcp/early-ppid.js +29 -0
  119. package/lib/dist/mcp/early-ppid.js.map +1 -0
  120. package/lib/dist/mcp/index.d.ts.map +1 -1
  121. package/lib/dist/mcp/index.js +21 -4
  122. package/lib/dist/mcp/index.js.map +1 -1
  123. package/lib/dist/mcp/proxy.d.ts.map +1 -1
  124. package/lib/dist/mcp/proxy.js +29 -3
  125. package/lib/dist/mcp/proxy.js.map +1 -1
  126. package/lib/dist/mcp/query-pool.d.ts +14 -0
  127. package/lib/dist/mcp/query-pool.d.ts.map +1 -1
  128. package/lib/dist/mcp/query-pool.js +18 -0
  129. package/lib/dist/mcp/query-pool.js.map +1 -1
  130. package/lib/dist/mcp/session.d.ts.map +1 -1
  131. package/lib/dist/mcp/session.js +6 -0
  132. package/lib/dist/mcp/session.js.map +1 -1
  133. package/lib/dist/mcp/startup-handshake.d.ts +44 -0
  134. package/lib/dist/mcp/startup-handshake.d.ts.map +1 -0
  135. package/lib/dist/mcp/startup-handshake.js +73 -0
  136. package/lib/dist/mcp/startup-handshake.js.map +1 -0
  137. package/lib/dist/mcp/tools.d.ts +22 -0
  138. package/lib/dist/mcp/tools.d.ts.map +1 -1
  139. package/lib/dist/mcp/tools.js +88 -18
  140. package/lib/dist/mcp/tools.js.map +1 -1
  141. package/lib/dist/mcp/transport.d.ts.map +1 -1
  142. package/lib/dist/mcp/transport.js +18 -0
  143. package/lib/dist/mcp/transport.js.map +1 -1
  144. package/lib/dist/project-config.d.ts +38 -0
  145. package/lib/dist/project-config.d.ts.map +1 -1
  146. package/lib/dist/project-config.js +101 -2
  147. package/lib/dist/project-config.js.map +1 -1
  148. package/lib/dist/resolution/c-fnptr-synthesizer.d.ts +2 -1
  149. package/lib/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -1
  150. package/lib/dist/resolution/c-fnptr-synthesizer.js +170 -146
  151. package/lib/dist/resolution/c-fnptr-synthesizer.js.map +1 -1
  152. package/lib/dist/resolution/callback-synthesizer.d.ts.map +1 -1
  153. package/lib/dist/resolution/callback-synthesizer.js +982 -81
  154. package/lib/dist/resolution/callback-synthesizer.js.map +1 -1
  155. package/lib/dist/resolution/frameworks/cics.d.ts +20 -0
  156. package/lib/dist/resolution/frameworks/cics.d.ts.map +1 -0
  157. package/lib/dist/resolution/frameworks/cics.js +90 -0
  158. package/lib/dist/resolution/frameworks/cics.js.map +1 -0
  159. package/lib/dist/resolution/frameworks/index.d.ts.map +1 -1
  160. package/lib/dist/resolution/frameworks/index.js +6 -0
  161. package/lib/dist/resolution/frameworks/index.js.map +1 -1
  162. package/lib/dist/resolution/frameworks/java.d.ts.map +1 -1
  163. package/lib/dist/resolution/frameworks/java.js +14 -6
  164. package/lib/dist/resolution/frameworks/java.js.map +1 -1
  165. package/lib/dist/resolution/frameworks/terraform.d.ts +38 -0
  166. package/lib/dist/resolution/frameworks/terraform.d.ts.map +1 -0
  167. package/lib/dist/resolution/frameworks/terraform.js +277 -0
  168. package/lib/dist/resolution/frameworks/terraform.js.map +1 -0
  169. package/lib/dist/resolution/goframe-synthesizer.d.ts +2 -1
  170. package/lib/dist/resolution/goframe-synthesizer.d.ts.map +1 -1
  171. package/lib/dist/resolution/goframe-synthesizer.js +8 -3
  172. package/lib/dist/resolution/goframe-synthesizer.js.map +1 -1
  173. package/lib/dist/resolution/import-resolver.d.ts +7 -0
  174. package/lib/dist/resolution/import-resolver.d.ts.map +1 -1
  175. package/lib/dist/resolution/import-resolver.js +129 -4
  176. package/lib/dist/resolution/import-resolver.js.map +1 -1
  177. package/lib/dist/resolution/index.d.ts +49 -6
  178. package/lib/dist/resolution/index.d.ts.map +1 -1
  179. package/lib/dist/resolution/index.js +342 -65
  180. package/lib/dist/resolution/index.js.map +1 -1
  181. package/lib/dist/resolution/name-matcher.d.ts +0 -3
  182. package/lib/dist/resolution/name-matcher.d.ts.map +1 -1
  183. package/lib/dist/resolution/name-matcher.js +225 -33
  184. package/lib/dist/resolution/name-matcher.js.map +1 -1
  185. package/lib/dist/resolution/strip-comments.d.ts +1 -1
  186. package/lib/dist/resolution/strip-comments.d.ts.map +1 -1
  187. package/lib/dist/resolution/strip-comments.js +49 -0
  188. package/lib/dist/resolution/strip-comments.js.map +1 -1
  189. package/lib/dist/resolution/types.d.ts +29 -0
  190. package/lib/dist/resolution/types.d.ts.map +1 -1
  191. package/lib/dist/resolution/workspace-packages.d.ts +10 -0
  192. package/lib/dist/resolution/workspace-packages.d.ts.map +1 -1
  193. package/lib/dist/resolution/workspace-packages.js +142 -4
  194. package/lib/dist/resolution/workspace-packages.js.map +1 -1
  195. package/lib/dist/search/identifier-segments.d.ts +60 -0
  196. package/lib/dist/search/identifier-segments.d.ts.map +1 -0
  197. package/lib/dist/search/identifier-segments.js +176 -0
  198. package/lib/dist/search/identifier-segments.js.map +1 -0
  199. package/lib/dist/sync/git-hooks.d.ts.map +1 -1
  200. package/lib/dist/sync/git-hooks.js +2 -0
  201. package/lib/dist/sync/git-hooks.js.map +1 -1
  202. package/lib/dist/sync/watcher.d.ts +10 -5
  203. package/lib/dist/sync/watcher.d.ts.map +1 -1
  204. package/lib/dist/sync/watcher.js +51 -14
  205. package/lib/dist/sync/watcher.js.map +1 -1
  206. package/lib/dist/sync/worktree.d.ts.map +1 -1
  207. package/lib/dist/sync/worktree.js +5 -0
  208. package/lib/dist/sync/worktree.js.map +1 -1
  209. package/lib/dist/types.d.ts +19 -1
  210. package/lib/dist/types.d.ts.map +1 -1
  211. package/lib/dist/types.js +10 -0
  212. package/lib/dist/types.js.map +1 -1
  213. package/lib/node_modules/.package-lock.json +1 -1
  214. package/lib/package.json +1 -1
  215. package/package.json +1 -1
  216. package/lib/dist/reasoning/config.d.ts +0 -45
  217. package/lib/dist/reasoning/config.d.ts.map +0 -1
  218. package/lib/dist/reasoning/config.js +0 -171
  219. package/lib/dist/reasoning/config.js.map +0 -1
  220. package/lib/dist/reasoning/credentials.d.ts +0 -5
  221. package/lib/dist/reasoning/credentials.d.ts.map +0 -1
  222. package/lib/dist/reasoning/credentials.js +0 -83
  223. package/lib/dist/reasoning/credentials.js.map +0 -1
  224. package/lib/dist/reasoning/login.d.ts +0 -21
  225. package/lib/dist/reasoning/login.d.ts.map +0 -1
  226. package/lib/dist/reasoning/login.js +0 -85
  227. package/lib/dist/reasoning/login.js.map +0 -1
  228. package/lib/dist/reasoning/reasoner.d.ts +0 -43
  229. package/lib/dist/reasoning/reasoner.d.ts.map +0 -1
  230. package/lib/dist/reasoning/reasoner.js +0 -308
  231. 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"));
@@ -63,6 +64,7 @@ const errors_1 = require("../errors");
63
64
  const utils_1 = require("../utils");
64
65
  const ignore_1 = __importDefault(require("ignore"));
65
66
  const frameworks_1 = require("../resolution/frameworks");
67
+ const cooperative_yield_1 = require("../resolution/cooperative-yield");
66
68
  /**
67
69
  * Number of files to read in parallel during indexing.
68
70
  * File reads are I/O-bound; batching overlaps I/O wait with CPU parse work.
@@ -302,6 +304,160 @@ function loadExcludeMatcher(rootDir) {
302
304
  const patterns = (0, project_config_1.loadExcludePatterns)(rootDir);
303
305
  return patterns.length > 0 ? (0, ignore_1.default)().add(patterns) : null;
304
306
  }
307
+ /**
308
+ * Matcher for the project's `codegraph.json` `include` patterns — first-party
309
+ * source to force INTO the index even when `.gitignore` drops it (the general
310
+ * whitelist `includeIgnored` never was — that one only revives *embedded git
311
+ * repos*). The case it exists for: a project under a second VCS (SVN/Perforce)
312
+ * `.gitignore`s its own real source so it stays out of Git, yet we still want it
313
+ * indexed. Returns `null` when nothing is force-included (the zero-config
314
+ * default → no overhead, no extra walk). Built once per scan/sync/scope
315
+ * operation from the scan root.
316
+ */
317
+ function loadIncludeMatcher(rootDir) {
318
+ const patterns = (0, project_config_1.loadIncludePatterns)(rootDir);
319
+ return patterns.length > 0 ? (0, ignore_1.default)().add(patterns) : null;
320
+ }
321
+ /** Glob metacharacters that end the static (literal) prefix of an `include` pattern. */
322
+ const GLOB_META = /[*?[\]{}!]/;
323
+ /**
324
+ * The static directory prefix of each `include` pattern — the literal leading
325
+ * path up to the first glob segment — trailing-slashed, used to (a) walk only
326
+ * the opted-in subtrees in `collectIncludedFiles` and (b) let `ScopeIgnore` keep
327
+ * the watcher descending toward them. `Tools/` stays `Tools/`; a recursive
328
+ * `Tools/**` glob yields `Tools/`; `src/local/file.ts` yields `src/local/` (the
329
+ * file's dir); a pattern that starts with a glob (like a leading `**`) yields
330
+ * `''`, meaning "no static root — walk the whole tree". Duplicates and roots
331
+ * nested under a broader root are collapsed so each subtree is walked once.
332
+ */
333
+ function includeStaticRoots(patterns) {
334
+ const roots = new Set();
335
+ for (const pattern of patterns) {
336
+ let p = pattern.replace(/^\/+/, '');
337
+ const trailingSlash = p.endsWith('/');
338
+ if (trailingSlash)
339
+ p = p.slice(0, -1);
340
+ const segs = p.split('/').filter(Boolean);
341
+ const lead = [];
342
+ for (const s of segs) {
343
+ if (GLOB_META.test(s))
344
+ break;
345
+ lead.push(s);
346
+ }
347
+ const hadWildcard = lead.length < segs.length;
348
+ // A wholly-literal pattern with no trailing slash names a file (or a dir we
349
+ // can't tell apart) — drop its last segment so we walk the containing dir
350
+ // and let the matcher pick the file. A trailing slash or a glob means the
351
+ // remaining `lead` is already the directory to walk.
352
+ if (!hadWildcard && !trailingSlash && lead.length > 0)
353
+ lead.pop();
354
+ if (lead.length === 0) {
355
+ roots.clear();
356
+ roots.add('');
357
+ return ['']; // a top-level glob forces a whole-tree walk; nothing narrower matters
358
+ }
359
+ roots.add(lead.join('/') + '/');
360
+ }
361
+ // Collapse roots nested under a broader one (e.g. drop `a/b/` if `a/` is present).
362
+ const all = [...roots];
363
+ return all.filter((r) => !all.some((other) => other !== r && r.startsWith(other)));
364
+ }
365
+ /**
366
+ * Actively discover the source files an `include` whitelist forces in. `git
367
+ * ls-files` never lists gitignored files, so a filtered filesystem walk of just
368
+ * the opted-in subtrees (`includeStaticRoots`) is the only way to find them.
369
+ * Returns project-root-relative, normalized source-file paths.
370
+ *
371
+ * A file is collected when it MATCHES `include`, is NOT hit by `exclude` (an
372
+ * explicit exclude always wins), is a recognized source file, and does not live
373
+ * under a built-in default-ignored dir (`node_modules`, `dist`, …), `.git`, or
374
+ * CodeGraph's data dir — those are never resurfaced, mirroring `ScopeIgnore`.
375
+ * `.gitignore` is deliberately NOT consulted: overriding it is the whole point.
376
+ */
377
+ function collectIncludedFiles(rootDir, include, exclude, roots, overrides) {
378
+ const out = new Set();
379
+ const defaults = defaultsOnlyIgnore();
380
+ const visited = new Set();
381
+ const consider = (abs, rel, isDir) => {
382
+ if (isDir) {
383
+ if (defaults.ignores(rel + '/'))
384
+ return; // never node_modules/dist/… via include
385
+ // An explicit `exclude` always wins over `include`; prune the whole subtree
386
+ // here so a large excluded dir (a committed frontend's own vendored deps,
387
+ // build output, …) is never walked — the per-file guard below still catches
388
+ // anything a directory pattern doesn't, so this is a pure efficiency win.
389
+ if (exclude && exclude.ignores(rel + '/'))
390
+ return;
391
+ walk(abs);
392
+ }
393
+ else {
394
+ if (defaults.ignores(rel))
395
+ return;
396
+ if (!include.ignores(rel))
397
+ return;
398
+ if (exclude && exclude.ignores(rel))
399
+ return;
400
+ if (!(0, grammars_1.isSourceFile)(rel, overrides))
401
+ return;
402
+ out.add(rel);
403
+ }
404
+ };
405
+ function walk(absDir) {
406
+ let realDir;
407
+ try {
408
+ realDir = fs.realpathSync(absDir);
409
+ }
410
+ catch {
411
+ return;
412
+ }
413
+ if (visited.has(realDir))
414
+ return; // symlink-cycle guard
415
+ visited.add(realDir);
416
+ let entries;
417
+ try {
418
+ entries = fs.readdirSync(absDir, { withFileTypes: true });
419
+ }
420
+ catch {
421
+ return;
422
+ }
423
+ for (const entry of entries) {
424
+ if (entry.name === '.git' || (0, directory_1.isCodeGraphDataDir)(entry.name))
425
+ continue;
426
+ const abs = path.join(absDir, entry.name);
427
+ const rel = (0, utils_1.normalizePath)(path.relative(rootDir, abs));
428
+ if (!rel || rel.startsWith('..'))
429
+ continue;
430
+ if (entry.isSymbolicLink()) {
431
+ try {
432
+ const st = fs.statSync(fs.realpathSync(abs));
433
+ consider(abs, rel, st.isDirectory());
434
+ }
435
+ catch {
436
+ // broken symlink — skip
437
+ }
438
+ continue;
439
+ }
440
+ consider(abs, rel, entry.isDirectory());
441
+ }
442
+ }
443
+ for (const root of roots) {
444
+ walk(root === '' ? rootDir : path.join(rootDir, root));
445
+ }
446
+ return out;
447
+ }
448
+ /**
449
+ * The included source files (`codegraph.json` `include`) for a scan root, or an
450
+ * empty set when nothing is force-included. Centralizes loading the matcher,
451
+ * roots, exclude, and overrides so both enumeration paths (git and filesystem
452
+ * walk) add the same files.
453
+ */
454
+ function collectIncludedFilesForRoot(rootDir) {
455
+ const include = loadIncludeMatcher(rootDir);
456
+ if (!include)
457
+ return new Set();
458
+ const roots = includeStaticRoots((0, project_config_1.loadIncludePatterns)(rootDir));
459
+ return collectIncludedFiles(rootDir, include, loadExcludeMatcher(rootDir), roots, (0, project_config_1.loadExtensionOverrides)(rootDir));
460
+ }
305
461
  /**
306
462
  * `git ls-files --directory` collapses a wholly-untracked/ignored directory into
307
463
  * one entry — and when the command's own cwd is such a directory (the indexed
@@ -442,6 +598,8 @@ function findNestedGitRepos(absDir, relPrefix) {
442
598
  class ScopeIgnore {
443
599
  rootMatcher;
444
600
  exclude;
601
+ include;
602
+ includeRoots;
445
603
  embedded;
446
604
  defaults = defaultsOnlyIgnore();
447
605
  constructor(rootMatcher, embedded,
@@ -450,9 +608,21 @@ class ScopeIgnore {
450
608
  * full root-relative path. Wins over everything else — an explicit user
451
609
  * exclude applies even to tracked files and even inside embedded repos.
452
610
  */
453
- exclude = null) {
611
+ exclude = null,
612
+ /**
613
+ * Project `codegraph.json` `include` patterns — first-party source forced
614
+ * INTO the index despite `.gitignore`. When a path matches, it is NOT
615
+ * ignored (so the watcher watches it), overriding `.gitignore`/`rootMatcher`
616
+ * — but never `exclude` (checked first) and never a built-in default-ignored
617
+ * dir. `includeRoots` are the static prefixes so a gitignored ANCESTOR
618
+ * directory of an included subtree still isn't pruned by the directory
619
+ * walker/watcher.
620
+ */
621
+ include = null, includeRoots = []) {
454
622
  this.rootMatcher = rootMatcher;
455
623
  this.exclude = exclude;
624
+ this.include = include;
625
+ this.includeRoots = includeRoots;
456
626
  // Longest root first so paths in nested embedded repos hit the innermost matcher.
457
627
  this.embedded = [...embedded].sort((a, b) => b.root.length - a.root.length);
458
628
  }
@@ -462,6 +632,20 @@ class ScopeIgnore {
462
632
  // everywhere, including ancestors of embedded repos.
463
633
  if (this.exclude && this.exclude.ignores(rel))
464
634
  return true;
635
+ // User `include`: force first-party source in despite `.gitignore`. Never
636
+ // resurfaces a built-in default-ignored dir (node_modules/dist/…), so an
637
+ // include pattern can't accidentally pull in dependency/build trees.
638
+ if (this.include && !this.defaults.ignores(rel)) {
639
+ if (rel.endsWith('/')) {
640
+ // A directory on (or leading to) an included subtree must stay walkable
641
+ // so the watcher/walker descends to reach the forced-in files.
642
+ if (this.includeRoots.some((r) => r.startsWith(rel) || rel.startsWith(r)))
643
+ return false;
644
+ }
645
+ else if (this.include.ignores(rel)) {
646
+ return false;
647
+ }
648
+ }
465
649
  for (const { root, matcher } of this.embedded) {
466
650
  if (rel.startsWith(root)) {
467
651
  const inner = rel.slice(root.length);
@@ -488,7 +672,8 @@ exports.ScopeIgnore = ScopeIgnore;
488
672
  */
489
673
  function buildScopeIgnore(rootDir, embeddedRoots) {
490
674
  const roots = embeddedRoots ? [...embeddedRoots] : discoverEmbeddedRepoRoots(rootDir);
491
- return new ScopeIgnore(buildDefaultIgnore(rootDir), roots.map((root) => ({ root, matcher: buildDefaultIgnore(path.join(rootDir, root)) })), loadExcludeMatcher(rootDir));
675
+ const include = loadIncludeMatcher(rootDir);
676
+ 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)) : []);
492
677
  }
493
678
  /**
494
679
  * Whether an embedded repo found as a tracked gitlink (mode 160000, #1031/#1033)
@@ -584,6 +769,50 @@ function discoverEmbeddedRepoRoots(rootDir) {
584
769
  visit(rootDir, '');
585
770
  return out;
586
771
  }
772
+ /**
773
+ * Cap on how many skipped gitignored repos the CLI hint enumerates — a huge
774
+ * gitignored data dir full of clones must never turn the hint scan into a long
775
+ * walk. Enough to make the point; the caller says "+N more" past this.
776
+ */
777
+ const UNINDEXED_IGNORED_REPO_HINT_CAP = 100;
778
+ /**
779
+ * The INVERSE of the gitignored side of {@link discoverEmbeddedRepoRoots}:
780
+ * nested git repositories under a gitignored directory that the project has NOT
781
+ * opted into via `codegraph.json` `includeIgnored`. These are real repos the
782
+ * default `init`/`index` deliberately skips because `.gitignore` excludes them
783
+ * (#970, #976) — most visibly the "super-repo `.gitignore`s its child repos"
784
+ * layout (#1156), where `init` at the parent correctly indexes ~nothing while
785
+ * `init` inside each child works. The CLI uses this to turn that silent empty
786
+ * index into an actionable hint: it names the skipped repos and offers to opt
787
+ * them in. Paths are `rootDir`-relative and trailing-slashed (valid
788
+ * `includeIgnored` patterns as-is). Returns `[]` for a non-git root (a
789
+ * filesystem walk already descends into nested repos there), skips built-in
790
+ * default-ignored dirs (`node_modules`, …), and is bounded so it never stalls
791
+ * on a giant ignored tree.
792
+ */
793
+ function findUnindexedIgnoredRepos(rootDir) {
794
+ try {
795
+ (0, child_process_1.execFileSync)('git', ['rev-parse', '--git-dir'], { cwd: rootDir, encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
796
+ }
797
+ catch {
798
+ return [];
799
+ }
800
+ const defaults = defaultsOnlyIgnore();
801
+ const includeIgnored = loadIncludeIgnoredMatcher(rootDir);
802
+ const repos = [];
803
+ for (const dir of listIgnoredDirs(rootDir)) {
804
+ if (defaults.ignores(dir))
805
+ continue; // node_modules etc. — never project code
806
+ if (includeIgnored?.ignores((0, utils_1.normalizePath)(dir)))
807
+ continue; // already opted in — nothing to nag about
808
+ for (const repo of findNestedGitRepos(path.join(rootDir, dir), dir)) {
809
+ repos.push(repo);
810
+ if (repos.length >= UNINDEXED_IGNORED_REPO_HINT_CAP)
811
+ return repos;
812
+ }
813
+ }
814
+ return repos;
815
+ }
587
816
  /**
588
817
  * Discover embedded repos hidden by `repoDir`'s OWN gitignore rules: for each
589
818
  * gitignored directory, search for nested `.git` roots. Returns repo paths
@@ -764,7 +993,15 @@ function getGitVisibleFiles(rootDir) {
764
993
  // not the parent's: the parent's .gitignore hides the child repo from git,
765
994
  // not from the index. (#514)
766
995
  const ig = buildScopeIgnore(rootDir, embeddedRoots);
767
- return new Set([...files].filter((f) => !ig.ignores(f)));
996
+ const visible = new Set([...files].filter((f) => !ig.ignores(f)));
997
+ // Force-include first-party source the project whitelisted in
998
+ // `codegraph.json` `include`. These are gitignored, so `git ls-files` never
999
+ // listed them above — discover them directly off disk and add them. (The
1000
+ // common SVN+Git dual-VCS case: source committed to SVN, gitignored out of
1001
+ // Git, but still wanted in the graph.)
1002
+ for (const f of collectIncludedFilesForRoot(rootDir))
1003
+ visible.add(f);
1004
+ return visible;
768
1005
  }
769
1006
  catch {
770
1007
  return null;
@@ -1027,6 +1264,19 @@ function scanDirectoryWalk(rootDir, onProgress) {
1027
1264
  if (exclude)
1028
1265
  baseMatchers.push({ dir: rootDir, ig: exclude });
1029
1266
  walk(rootDir, baseMatchers);
1267
+ // Force-include first-party source whitelisted in `codegraph.json` `include`
1268
+ // — the walk above honours `.gitignore`, so anything gitignored was dropped;
1269
+ // add it back here (deduped). Mirrors the git path's union.
1270
+ const included = collectIncludedFilesForRoot(rootDir);
1271
+ if (included.size > 0) {
1272
+ const seen = new Set(files);
1273
+ for (const f of included) {
1274
+ if (!seen.has(f)) {
1275
+ files.push(f);
1276
+ seen.add(f);
1277
+ }
1278
+ }
1279
+ }
1030
1280
  return files;
1031
1281
  }
1032
1282
  /**
@@ -1143,6 +1393,10 @@ class ExtractionOrchestrator {
1143
1393
  current: 0,
1144
1394
  total: 0,
1145
1395
  });
1396
+ // Phase attribution to stderr (same opt-in as the synthesis timings):
1397
+ // early-run 5-10s single stalls were observed on 95k-file repos but never
1398
+ // attributed — these labels settle scan vs framework-detect vs grammars.
1399
+ const tScan = Date.now();
1146
1400
  const files = await scanDirectoryAsync(this.rootDir, (current, file) => {
1147
1401
  onProgress?.({
1148
1402
  phase: 'scanning',
@@ -1151,13 +1405,18 @@ class ExtractionOrchestrator {
1151
1405
  currentFile: file,
1152
1406
  });
1153
1407
  });
1408
+ if (process.env.CODEGRAPH_SYNTH_TIMINGS)
1409
+ console.error(`[phase-timing] scan: ${Date.now() - tScan}ms (${files.length} files)`);
1154
1410
  // Detect frameworks once per indexAll run using the scanned file list.
1155
1411
  // Names are passed to each parse call so framework-specific extractors
1156
1412
  // (route nodes, middleware, etc.) run after the tree-sitter pass.
1157
1413
  // Framework detection is reset each run so adding e.g. requirements.txt
1158
1414
  // between runs is picked up without restarting the process.
1159
1415
  this.detectedFrameworkNames = null;
1416
+ const tFw = Date.now();
1160
1417
  const frameworkNames = this.ensureDetectedFrameworks(files);
1418
+ if (process.env.CODEGRAPH_SYNTH_TIMINGS)
1419
+ console.error(`[phase-timing] framework-detect: ${Date.now() - tFw}ms`);
1161
1420
  if (signal?.aborted) {
1162
1421
  return {
1163
1422
  success: false,
@@ -1240,12 +1499,17 @@ class ExtractionOrchestrator {
1240
1499
  let nextSeq = 0; // file-order sequence assigned at dispatch
1241
1500
  let nextToStore = 0; // cursor: next sequence to commit
1242
1501
  let aborted = false;
1243
- const storeResult = (filePath, content, stats, result) => {
1502
+ // Yielder for the in-order commit path: a single giant generated file's
1503
+ // store is otherwise one unyielding multi-second transaction span on the
1504
+ // main thread (5–14s single stalls measured on llvm-project), starving
1505
+ // the #850 watchdog heartbeat on slow hardware.
1506
+ const commitYield = (0, cooperative_yield_1.createYielder)();
1507
+ const storeResult = async (filePath, content, stats, result) => {
1244
1508
  processed++;
1245
1509
  // Store in database on main thread (SQLite is not thread-safe)
1246
1510
  if (result.nodes.length > 0 || result.errors.length === 0) {
1247
1511
  const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
1248
- this.storeExtractionResult(filePath, content, language, stats, result);
1512
+ await this.storeExtractionResult(filePath, content, language, stats, result, commitYield);
1249
1513
  }
1250
1514
  if (result.errors.length > 0) {
1251
1515
  for (const err of result.errors) {
@@ -1289,20 +1553,35 @@ class ExtractionOrchestrator {
1289
1553
  };
1290
1554
  // Commit buffered parses to the DB in file order, advancing the cursor over
1291
1555
  // contiguous completed results. Runs after each parse settles (and once more
1292
- // after the drain). storeResult / recordParseFailure run here single-threaded,
1293
- // so shared counters and SQLite writes never race despite parallel parsing.
1556
+ // after the drain). storeResult is now async (it yields between chunked
1557
+ // inserts), so commits are SERIALIZED on a promise chain concurrent parse
1558
+ // completions append to the chain instead of interleaving mid-store, which
1559
+ // preserves both the file-order commit invariant (#1015: resolution
1560
+ // disambiguates same-named candidates by insertion order) and the
1561
+ // single-writer discipline for SQLite. Errors are recorded and re-thrown
1562
+ // at the drain, matching the old synchronous propagation.
1563
+ let flushChain = Promise.resolve();
1564
+ let flushError = null;
1294
1565
  const flushOrdered = () => {
1295
- if (aborted)
1296
- return;
1297
- while (completed.has(nextToStore)) {
1298
- const item = completed.get(nextToStore);
1299
- completed.delete(nextToStore);
1300
- nextToStore++;
1301
- if (item.ok)
1302
- storeResult(item.filePath, item.content, item.stats, item.result);
1303
- else
1304
- recordParseFailure(item.filePath, item.err);
1305
- }
1566
+ flushChain = flushChain.then(async () => {
1567
+ if (aborted || flushError)
1568
+ return;
1569
+ try {
1570
+ while (completed.has(nextToStore)) {
1571
+ const item = completed.get(nextToStore);
1572
+ completed.delete(nextToStore);
1573
+ nextToStore++;
1574
+ if (item.ok)
1575
+ await storeResult(item.filePath, item.content, item.stats, item.result);
1576
+ else
1577
+ recordParseFailure(item.filePath, item.err);
1578
+ }
1579
+ }
1580
+ catch (err) {
1581
+ flushError = err;
1582
+ }
1583
+ });
1584
+ return flushChain;
1306
1585
  };
1307
1586
  // Dispatch one file's parse (parses run concurrently across the pool), tagged
1308
1587
  // with its file-order sequence so flushOrdered commits results in order. The
@@ -1325,10 +1604,15 @@ class ExtractionOrchestrator {
1325
1604
  // buffered), not just in-flight: a slow file sitting at the commit cursor
1326
1605
  // lets later parses finish and buffer, which would otherwise grow without
1327
1606
  // bound. Wait for parses to settle (each may advance the cursor) until the
1328
- // window has room. `inFlight.size > 0` guards against an empty race the
1329
- // cursor file is always still in flight when the window is full.
1330
- while (nextSeq - nextToStore >= windowSize && inFlight.size > 0) {
1331
- await Promise.race(inFlight);
1607
+ // window has room. When nothing is in flight but the window is still full,
1608
+ // the async commit chain is what's behind await it so the cursor
1609
+ // advances (buffered items hold whole file contents, so this bound is
1610
+ // load-bearing for memory).
1611
+ while (nextSeq - nextToStore >= windowSize) {
1612
+ if (inFlight.size > 0)
1613
+ await Promise.race(inFlight);
1614
+ else
1615
+ await flushOrdered();
1332
1616
  }
1333
1617
  };
1334
1618
  for (let i = 0; i < files.length; i += FILE_IO_BATCH_SIZE) {
@@ -1403,7 +1687,9 @@ class ExtractionOrchestrator {
1403
1687
  // then commit any results the cursor hasn't reached yet.
1404
1688
  if (!aborted) {
1405
1689
  await Promise.all(inFlight);
1406
- flushOrdered();
1690
+ await flushOrdered();
1691
+ if (flushError)
1692
+ throw flushError;
1407
1693
  }
1408
1694
  if (signal?.aborted || aborted) {
1409
1695
  if (pool)
@@ -1413,6 +1699,7 @@ class ExtractionOrchestrator {
1413
1699
  filesIndexed,
1414
1700
  filesSkipped,
1415
1701
  filesErrored,
1702
+ filesDiscovered: total,
1416
1703
  nodesCreated: totalNodes,
1417
1704
  edgesCreated: totalEdges,
1418
1705
  errors: [{ message: 'Aborted', severity: 'error' }, ...errors],
@@ -1466,7 +1753,7 @@ class ExtractionOrchestrator {
1466
1753
  if (result.nodes.length > 0 || result.errors.length === 0) {
1467
1754
  const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
1468
1755
  const stats = await fsp.stat(path.join(this.rootDir, filePath));
1469
- this.storeExtractionResult(filePath, content, language, stats, result);
1756
+ await this.storeExtractionResult(filePath, content, language, stats, result, commitYield);
1470
1757
  const idx = errors.indexOf(errEntry);
1471
1758
  if (idx >= 0)
1472
1759
  errors.splice(idx, 1);
@@ -1514,7 +1801,7 @@ class ExtractionOrchestrator {
1514
1801
  if (result.nodes.length > 0 || result.errors.length === 0) {
1515
1802
  const language = (0, grammars_1.detectLanguage)(filePath, fullContent, overrides);
1516
1803
  const stats = await fsp.stat(path.join(this.rootDir, filePath));
1517
- this.storeExtractionResult(filePath, fullContent, language, stats, result);
1804
+ await this.storeExtractionResult(filePath, fullContent, language, stats, result, commitYield);
1518
1805
  const idx = errors.indexOf(errEntry);
1519
1806
  if (idx >= 0)
1520
1807
  errors.splice(idx, 1);
@@ -1535,6 +1822,7 @@ class ExtractionOrchestrator {
1535
1822
  filesIndexed,
1536
1823
  filesSkipped,
1537
1824
  filesErrored,
1825
+ filesDiscovered: total,
1538
1826
  nodesCreated: totalNodes,
1539
1827
  edgesCreated: totalEdges,
1540
1828
  errors,
@@ -1678,14 +1966,21 @@ class ExtractionOrchestrator {
1678
1966
  const result = (0, tree_sitter_1.extractFromSource)(relativePath, content, language, frameworkNames);
1679
1967
  // Store in database
1680
1968
  if (result.nodes.length > 0 || result.errors.length === 0) {
1681
- this.storeExtractionResult(relativePath, content, language, stats, result);
1969
+ await this.storeExtractionResult(relativePath, content, language, stats, result, (0, cooperative_yield_1.createYielder)());
1682
1970
  }
1683
1971
  return result;
1684
1972
  }
1685
1973
  /**
1686
1974
  * Store extraction result in database
1687
1975
  */
1688
- storeExtractionResult(filePath, content, language, stats, result) {
1976
+ async storeExtractionResult(filePath, content, language, stats, result, onYield) {
1977
+ // Bulk inserts run in bounded sub-transactions with a yield between, so a
1978
+ // giant generated file (tens of thousands of symbols) can't block the
1979
+ // event loop — and the #850 watchdog heartbeat — for the whole store.
1980
+ // The file was NEVER one atomic transaction (each insert call has its
1981
+ // own), and the files-table record still lands last, so crash recovery
1982
+ // is unchanged: a partially-stored file has no record and re-indexes.
1983
+ const STORE_CHUNK = 2000;
1689
1984
  const contentHash = hashContent(content);
1690
1985
  // Check if file already exists and hasn't changed
1691
1986
  const existingFile = this.queries.getFileByPath(filePath);
@@ -1719,16 +2014,18 @@ class ExtractionOrchestrator {
1719
2014
  // This prevents FK violations when edges reference nodes that would
1720
2015
  // be silently skipped by insertNode() (see issue #42).
1721
2016
  const validNodes = result.nodes.filter((n) => n.id && n.kind && n.name && n.filePath && n.language);
1722
- // Insert nodes
1723
- if (validNodes.length > 0) {
1724
- this.queries.insertNodes(validNodes);
2017
+ // Insert nodes (chunked — see STORE_CHUNK above)
2018
+ for (let i = 0; i < validNodes.length; i += STORE_CHUNK) {
2019
+ this.queries.insertNodes(validNodes.slice(i, i + STORE_CHUNK));
2020
+ await onYield?.();
1725
2021
  }
1726
2022
  // Filter edges to only reference nodes that were actually inserted
1727
2023
  if (result.edges.length > 0) {
1728
2024
  const insertedIds = new Set(validNodes.map((n) => n.id));
1729
2025
  const validEdges = result.edges.filter((e) => insertedIds.has(e.source) && insertedIds.has(e.target));
1730
- if (validEdges.length > 0) {
1731
- this.queries.insertEdges(validEdges);
2026
+ for (let i = 0; i < validEdges.length; i += STORE_CHUNK) {
2027
+ this.queries.insertEdges(validEdges.slice(i, i + STORE_CHUNK));
2028
+ await onYield?.();
1732
2029
  }
1733
2030
  }
1734
2031
  // Re-insert cross-file incoming edges snapshotted before the delete,
@@ -1766,8 +2063,9 @@ class ExtractionOrchestrator {
1766
2063
  filePath: ref.filePath ?? filePath,
1767
2064
  language: ref.language ?? language,
1768
2065
  }));
1769
- if (refsWithContext.length > 0) {
1770
- this.queries.insertUnresolvedRefsBatch(refsWithContext);
2066
+ for (let i = 0; i < refsWithContext.length; i += STORE_CHUNK) {
2067
+ this.queries.insertUnresolvedRefsBatch(refsWithContext.slice(i, i + STORE_CHUNK));
2068
+ await onYield?.();
1771
2069
  }
1772
2070
  }
1773
2071
  // Insert file record
@@ -1815,10 +2113,16 @@ class ExtractionOrchestrator {
1815
2113
  // whether or not the project uses git, and crucially also catches committed
1816
2114
  // changes from `git pull`/`checkout`/`merge`/`rebase` — which `git status`
1817
2115
  // cannot see, because the working tree is clean afterward.
2116
+ const tSyncScan = Date.now();
1818
2117
  const currentFiles = await scanDirectoryAsync(this.rootDir);
2118
+ if (process.env.CODEGRAPH_SYNTH_TIMINGS)
2119
+ console.error(`[phase-timing] sync-scan: ${Date.now() - tSyncScan}ms (${currentFiles.length} files)`);
1819
2120
  filesChecked = currentFiles.length;
1820
2121
  const currentSet = new Set(currentFiles);
2122
+ const tTracked = Date.now();
1821
2123
  const trackedFiles = this.queries.getAllFiles();
2124
+ if (process.env.CODEGRAPH_SYNTH_TIMINGS)
2125
+ console.error(`[phase-timing] sync-tracked-load: ${Date.now() - tTracked}ms (${trackedFiles.length} tracked)`);
1822
2126
  const trackedMap = new Map();
1823
2127
  for (const f of trackedFiles) {
1824
2128
  trackedMap.set(f.path, f);