homegraph 1.2.0 → 1.4.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 (284) hide show
  1. package/dist/bin/homegraph.js +3 -0
  2. package/dist/bin/homegraph.js.map +1 -1
  3. package/dist/context/index.d.ts.map +1 -1
  4. package/dist/context/index.js +85 -0
  5. package/dist/context/index.js.map +1 -1
  6. package/dist/db/index.d.ts +64 -5
  7. package/dist/db/index.d.ts.map +1 -1
  8. package/dist/db/index.js +178 -14
  9. package/dist/db/index.js.map +1 -1
  10. package/dist/db/migrations.d.ts +1 -1
  11. package/dist/db/migrations.d.ts.map +1 -1
  12. package/dist/db/migrations.js +19 -1
  13. package/dist/db/migrations.js.map +1 -1
  14. package/dist/db/queries.d.ts +117 -2
  15. package/dist/db/queries.d.ts.map +1 -1
  16. package/dist/db/queries.js +299 -6
  17. package/dist/db/queries.js.map +1 -1
  18. package/dist/db/schema.sql +32 -1
  19. package/dist/db/wal-valve.d.ts +106 -0
  20. package/dist/db/wal-valve.d.ts.map +1 -0
  21. package/dist/db/wal-valve.js +208 -0
  22. package/dist/db/wal-valve.js.map +1 -0
  23. package/dist/directory.d.ts +9 -5
  24. package/dist/directory.d.ts.map +1 -1
  25. package/dist/directory.js +210 -19
  26. package/dist/directory.js.map +1 -1
  27. package/dist/extraction/cfml-extractor.d.ts +107 -0
  28. package/dist/extraction/cfml-extractor.d.ts.map +1 -0
  29. package/dist/extraction/cfml-extractor.js +494 -0
  30. package/dist/extraction/cfml-extractor.js.map +1 -0
  31. package/dist/extraction/grammars.d.ts +25 -1
  32. package/dist/extraction/grammars.d.ts.map +1 -1
  33. package/dist/extraction/grammars.js +177 -24
  34. package/dist/extraction/grammars.js.map +1 -1
  35. package/dist/extraction/index.d.ts +43 -4
  36. package/dist/extraction/index.d.ts.map +1 -1
  37. package/dist/extraction/index.js +308 -5
  38. package/dist/extraction/index.js.map +1 -1
  39. package/dist/extraction/languages/c-cpp.d.ts +42 -0
  40. package/dist/extraction/languages/c-cpp.d.ts.map +1 -1
  41. package/dist/extraction/languages/c-cpp.js +369 -4
  42. package/dist/extraction/languages/c-cpp.js.map +1 -1
  43. package/dist/extraction/languages/cfquery.d.ts +12 -0
  44. package/dist/extraction/languages/cfquery.d.ts.map +1 -0
  45. package/dist/extraction/languages/cfquery.js +28 -0
  46. package/dist/extraction/languages/cfquery.js.map +1 -0
  47. package/dist/extraction/languages/cfscript.d.ts +3 -0
  48. package/dist/extraction/languages/cfscript.d.ts.map +1 -0
  49. package/dist/extraction/languages/cfscript.js +73 -0
  50. package/dist/extraction/languages/cfscript.js.map +1 -0
  51. package/dist/extraction/languages/cobol.d.ts +33 -0
  52. package/dist/extraction/languages/cobol.d.ts.map +1 -0
  53. package/dist/extraction/languages/cobol.js +499 -0
  54. package/dist/extraction/languages/cobol.js.map +1 -0
  55. package/dist/extraction/languages/erlang.d.ts +3 -0
  56. package/dist/extraction/languages/erlang.d.ts.map +1 -0
  57. package/dist/extraction/languages/erlang.js +350 -0
  58. package/dist/extraction/languages/erlang.js.map +1 -0
  59. package/dist/extraction/languages/index.d.ts.map +1 -1
  60. package/dist/extraction/languages/index.js +16 -0
  61. package/dist/extraction/languages/index.js.map +1 -1
  62. package/dist/extraction/languages/nix.d.ts +3 -0
  63. package/dist/extraction/languages/nix.d.ts.map +1 -0
  64. package/dist/extraction/languages/nix.js +294 -0
  65. package/dist/extraction/languages/nix.js.map +1 -0
  66. package/dist/extraction/languages/solidity.d.ts +3 -0
  67. package/dist/extraction/languages/solidity.d.ts.map +1 -0
  68. package/dist/extraction/languages/solidity.js +293 -0
  69. package/dist/extraction/languages/solidity.js.map +1 -0
  70. package/dist/extraction/languages/terraform.d.ts +3 -0
  71. package/dist/extraction/languages/terraform.d.ts.map +1 -0
  72. package/dist/extraction/languages/terraform.js +641 -0
  73. package/dist/extraction/languages/terraform.js.map +1 -0
  74. package/dist/extraction/languages/vbnet.d.ts +11 -0
  75. package/dist/extraction/languages/vbnet.d.ts.map +1 -0
  76. package/dist/extraction/languages/vbnet.js +141 -0
  77. package/dist/extraction/languages/vbnet.js.map +1 -0
  78. package/dist/extraction/mybatis-extractor.d.ts +30 -10
  79. package/dist/extraction/mybatis-extractor.d.ts.map +1 -1
  80. package/dist/extraction/mybatis-extractor.js +140 -38
  81. package/dist/extraction/mybatis-extractor.js.map +1 -1
  82. package/dist/extraction/parse-pool.d.ts +27 -1
  83. package/dist/extraction/parse-pool.d.ts.map +1 -1
  84. package/dist/extraction/parse-pool.js +75 -7
  85. package/dist/extraction/parse-pool.js.map +1 -1
  86. package/dist/extraction/parse-worker.js +9 -2
  87. package/dist/extraction/parse-worker.js.map +1 -1
  88. package/dist/extraction/tree-sitter-helpers.d.ts.map +1 -1
  89. package/dist/extraction/tree-sitter-helpers.js +1 -0
  90. package/dist/extraction/tree-sitter-helpers.js.map +1 -1
  91. package/dist/extraction/tree-sitter-types.d.ts +3 -1
  92. package/dist/extraction/tree-sitter-types.d.ts.map +1 -1
  93. package/dist/extraction/tree-sitter.d.ts +38 -0
  94. package/dist/extraction/tree-sitter.d.ts.map +1 -1
  95. package/dist/extraction/tree-sitter.js +754 -10
  96. package/dist/extraction/tree-sitter.js.map +1 -1
  97. package/dist/extraction/wasm/tree-sitter-arkts.wasm +0 -0
  98. package/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
  99. package/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
  100. package/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
  101. package/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
  102. package/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
  103. package/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
  104. package/dist/extraction/wasm/tree-sitter-terraform.wasm +0 -0
  105. package/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
  106. package/dist/graph/traversal.d.ts.map +1 -1
  107. package/dist/graph/traversal.js +1 -1
  108. package/dist/graph/traversal.js.map +1 -1
  109. package/dist/index.d.ts +60 -2
  110. package/dist/index.d.ts.map +1 -1
  111. package/dist/index.js +348 -5
  112. package/dist/index.js.map +1 -1
  113. package/dist/installer/index.d.ts +42 -0
  114. package/dist/installer/index.d.ts.map +1 -1
  115. package/dist/installer/index.js +97 -2
  116. package/dist/installer/index.js.map +1 -1
  117. package/dist/mcp/daemon.d.ts +25 -3
  118. package/dist/mcp/daemon.d.ts.map +1 -1
  119. package/dist/mcp/daemon.js +62 -8
  120. package/dist/mcp/daemon.js.map +1 -1
  121. package/dist/mcp/dynamic-boundaries.d.ts.map +1 -1
  122. package/dist/mcp/dynamic-boundaries.js +2 -1
  123. package/dist/mcp/dynamic-boundaries.js.map +1 -1
  124. package/dist/mcp/early-ppid.d.ts +26 -0
  125. package/dist/mcp/early-ppid.d.ts.map +1 -0
  126. package/dist/mcp/early-ppid.js +29 -0
  127. package/dist/mcp/early-ppid.js.map +1 -0
  128. package/dist/mcp/engine.d.ts +4 -5
  129. package/dist/mcp/engine.d.ts.map +1 -1
  130. package/dist/mcp/engine.js +27 -1
  131. package/dist/mcp/engine.js.map +1 -1
  132. package/dist/mcp/index.d.ts.map +1 -1
  133. package/dist/mcp/index.js +41 -6
  134. package/dist/mcp/index.js.map +1 -1
  135. package/dist/mcp/liveness-watchdog.d.ts +18 -1
  136. package/dist/mcp/liveness-watchdog.d.ts.map +1 -1
  137. package/dist/mcp/liveness-watchdog.js +73 -13
  138. package/dist/mcp/liveness-watchdog.js.map +1 -1
  139. package/dist/mcp/memory-budget.d.ts +30 -0
  140. package/dist/mcp/memory-budget.d.ts.map +1 -0
  141. package/dist/mcp/memory-budget.js +123 -0
  142. package/dist/mcp/memory-budget.js.map +1 -0
  143. package/dist/mcp/proxy.d.ts.map +1 -1
  144. package/dist/mcp/proxy.js +88 -4
  145. package/dist/mcp/proxy.js.map +1 -1
  146. package/dist/mcp/query-cache.d.ts +35 -4
  147. package/dist/mcp/query-cache.d.ts.map +1 -1
  148. package/dist/mcp/query-cache.js +89 -19
  149. package/dist/mcp/query-cache.js.map +1 -1
  150. package/dist/mcp/query-pool.d.ts +46 -12
  151. package/dist/mcp/query-pool.d.ts.map +1 -1
  152. package/dist/mcp/query-pool.js +100 -23
  153. package/dist/mcp/query-pool.js.map +1 -1
  154. package/dist/mcp/server-instructions.d.ts +4 -27
  155. package/dist/mcp/server-instructions.d.ts.map +1 -1
  156. package/dist/mcp/server-instructions.js +39 -84
  157. package/dist/mcp/server-instructions.js.map +1 -1
  158. package/dist/mcp/session.d.ts +14 -0
  159. package/dist/mcp/session.d.ts.map +1 -1
  160. package/dist/mcp/session.js +28 -1
  161. package/dist/mcp/session.js.map +1 -1
  162. package/dist/mcp/startup-handshake.d.ts +44 -0
  163. package/dist/mcp/startup-handshake.d.ts.map +1 -0
  164. package/dist/mcp/startup-handshake.js +73 -0
  165. package/dist/mcp/startup-handshake.js.map +1 -0
  166. package/dist/mcp/tools.d.ts +104 -4
  167. package/dist/mcp/tools.d.ts.map +1 -1
  168. package/dist/mcp/tools.js +2390 -191
  169. package/dist/mcp/tools.js.map +1 -1
  170. package/dist/mcp/transport.d.ts.map +1 -1
  171. package/dist/mcp/transport.js +18 -0
  172. package/dist/mcp/transport.js.map +1 -1
  173. package/dist/project-config.d.ts +38 -0
  174. package/dist/project-config.d.ts.map +1 -1
  175. package/dist/project-config.js +101 -2
  176. package/dist/project-config.js.map +1 -1
  177. package/dist/resolution/c-fnptr-synthesizer.d.ts +2 -1
  178. package/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -1
  179. package/dist/resolution/c-fnptr-synthesizer.js +178 -146
  180. package/dist/resolution/c-fnptr-synthesizer.js.map +1 -1
  181. package/dist/resolution/callback-synthesizer.d.ts +9 -1
  182. package/dist/resolution/callback-synthesizer.d.ts.map +1 -1
  183. package/dist/resolution/callback-synthesizer.js +1164 -176
  184. package/dist/resolution/callback-synthesizer.js.map +1 -1
  185. package/dist/resolution/frameworks/cics.d.ts +20 -0
  186. package/dist/resolution/frameworks/cics.d.ts.map +1 -0
  187. package/dist/resolution/frameworks/cics.js +90 -0
  188. package/dist/resolution/frameworks/cics.js.map +1 -0
  189. package/dist/resolution/frameworks/index.d.ts.map +1 -1
  190. package/dist/resolution/frameworks/index.js +3 -0
  191. package/dist/resolution/frameworks/index.js.map +1 -1
  192. package/dist/resolution/frameworks/java.d.ts.map +1 -1
  193. package/dist/resolution/frameworks/java.js +14 -6
  194. package/dist/resolution/frameworks/java.js.map +1 -1
  195. package/dist/resolution/frameworks/terraform.d.ts +38 -0
  196. package/dist/resolution/frameworks/terraform.d.ts.map +1 -0
  197. package/dist/resolution/frameworks/terraform.js +277 -0
  198. package/dist/resolution/frameworks/terraform.js.map +1 -0
  199. package/dist/resolution/goframe-synthesizer.d.ts +2 -1
  200. package/dist/resolution/goframe-synthesizer.d.ts.map +1 -1
  201. package/dist/resolution/goframe-synthesizer.js +8 -3
  202. package/dist/resolution/goframe-synthesizer.js.map +1 -1
  203. package/dist/resolution/import-resolver.d.ts +7 -0
  204. package/dist/resolution/import-resolver.d.ts.map +1 -1
  205. package/dist/resolution/import-resolver.js +129 -4
  206. package/dist/resolution/import-resolver.js.map +1 -1
  207. package/dist/resolution/index.d.ts +60 -7
  208. package/dist/resolution/index.d.ts.map +1 -1
  209. package/dist/resolution/index.js +430 -80
  210. package/dist/resolution/index.js.map +1 -1
  211. package/dist/resolution/name-matcher.d.ts +0 -3
  212. package/dist/resolution/name-matcher.d.ts.map +1 -1
  213. package/dist/resolution/name-matcher.js +354 -33
  214. package/dist/resolution/name-matcher.js.map +1 -1
  215. package/dist/resolution/strip-comments.d.ts +1 -1
  216. package/dist/resolution/strip-comments.d.ts.map +1 -1
  217. package/dist/resolution/strip-comments.js +49 -0
  218. package/dist/resolution/strip-comments.js.map +1 -1
  219. package/dist/resolution/types.d.ts +29 -0
  220. package/dist/resolution/types.d.ts.map +1 -1
  221. package/dist/resolution/workspace-packages.d.ts +10 -0
  222. package/dist/resolution/workspace-packages.d.ts.map +1 -1
  223. package/dist/resolution/workspace-packages.js +142 -4
  224. package/dist/resolution/workspace-packages.js.map +1 -1
  225. package/dist/search/identifier-segments.d.ts +60 -0
  226. package/dist/search/identifier-segments.d.ts.map +1 -0
  227. package/dist/search/identifier-segments.js +176 -0
  228. package/dist/search/identifier-segments.js.map +1 -0
  229. package/dist/search/query-utils.d.ts +201 -0
  230. package/dist/search/query-utils.d.ts.map +1 -1
  231. package/dist/search/query-utils.js +919 -2
  232. package/dist/search/query-utils.js.map +1 -1
  233. package/dist/sync/git-hooks.d.ts.map +1 -1
  234. package/dist/sync/git-hooks.js +2 -0
  235. package/dist/sync/git-hooks.js.map +1 -1
  236. package/dist/sync/watcher.d.ts +10 -5
  237. package/dist/sync/watcher.d.ts.map +1 -1
  238. package/dist/sync/watcher.js +51 -14
  239. package/dist/sync/watcher.js.map +1 -1
  240. package/dist/sync/worktree.d.ts.map +1 -1
  241. package/dist/sync/worktree.js +5 -0
  242. package/dist/sync/worktree.js.map +1 -1
  243. package/dist/types.d.ts +19 -1
  244. package/dist/types.d.ts.map +1 -1
  245. package/dist/types.js +10 -0
  246. package/dist/types.js.map +1 -1
  247. package/dist/upgrade/index.d.ts +32 -0
  248. package/dist/upgrade/index.d.ts.map +1 -1
  249. package/dist/upgrade/index.js +162 -14
  250. package/dist/upgrade/index.js.map +1 -1
  251. package/dist/upgrade/remove-binary.d.ts +87 -0
  252. package/dist/upgrade/remove-binary.d.ts.map +1 -0
  253. package/dist/upgrade/remove-binary.js +289 -0
  254. package/dist/upgrade/remove-binary.js.map +1 -0
  255. package/dist/upgrade/update-check.d.ts +92 -0
  256. package/dist/upgrade/update-check.d.ts.map +1 -0
  257. package/dist/upgrade/update-check.js +258 -0
  258. package/dist/upgrade/update-check.js.map +1 -0
  259. package/package.json +2 -2
  260. package/scripts/agent-eval/run-all.sh +6 -0
  261. package/scripts/build-bundle.sh +5 -0
  262. package/scripts/npm-shim.js +8 -1
  263. package/scripts/exp_boundary_eval/__pycache__/_utils.cpython-310.pyc +0 -0
  264. package/scripts/exp_boundary_eval/__pycache__/analyze.cpython-310.pyc +0 -0
  265. package/scripts/exp_boundary_eval/__pycache__/deveco_arm.cpython-310.pyc +0 -0
  266. package/scripts/exp_boundary_eval/__pycache__/run_one.cpython-310.pyc +0 -0
  267. package/scripts/exp_boundary_eval/__pycache__/run_session.cpython-310.pyc +0 -0
  268. package/scripts/exp_boundary_eval/__pycache__/setup.cpython-310.pyc +0 -0
  269. package/scripts/exp_boundary_eval/data/agents.json +0 -109
  270. package/scripts/exp_boundary_eval/data/experiments.json +0 -140
  271. package/scripts/qa_eval/README.md +0 -407
  272. package/scripts/qa_eval/_test_deveco_probe.py +0 -41
  273. package/scripts/qa_eval/agent_runner.py +0 -526
  274. package/scripts/qa_eval/data/.gitignore +0 -4
  275. package/scripts/qa_eval/data/test-set.jsonl +0 -2
  276. package/scripts/qa_eval/eval_metrics.py +0 -274
  277. package/scripts/qa_eval/external_agent.py +0 -976
  278. package/scripts/qa_eval/llm_config.py +0 -92
  279. package/scripts/qa_eval/memory_monitor.py +0 -132
  280. package/scripts/qa_eval/my_answer_accuracy.py +0 -187
  281. package/scripts/qa_eval/requirements.txt +0 -2
  282. package/scripts/qa_eval/run_pipeline.py +0 -804
  283. package/scripts/qa_eval/stats_efficiency.py +0 -279
  284. package/scripts/qa_eval/stats_scores.py +0 -207
@@ -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"));
@@ -304,6 +305,160 @@ function loadExcludeMatcher(rootDir) {
304
305
  const patterns = (0, project_config_1.loadExcludePatterns)(rootDir);
305
306
  return patterns.length > 0 ? (0, ignore_1.default)().add(patterns) : null;
306
307
  }
308
+ /**
309
+ * Matcher for the project's `homegraph.json` `include` patterns — first-party
310
+ * source to force INTO the index even when `.gitignore` drops it (the general
311
+ * whitelist `includeIgnored` never was — that one only revives *embedded git
312
+ * repos*). The case it exists for: a project under a second VCS (SVN/Perforce)
313
+ * `.gitignore`s its own real source so it stays out of Git, yet we still want it
314
+ * indexed. Returns `null` when nothing is force-included (the zero-config
315
+ * default → no overhead, no extra walk). Built once per scan/sync/scope
316
+ * operation from the scan root.
317
+ */
318
+ function loadIncludeMatcher(rootDir) {
319
+ const patterns = (0, project_config_1.loadIncludePatterns)(rootDir);
320
+ return patterns.length > 0 ? (0, ignore_1.default)().add(patterns) : null;
321
+ }
322
+ /** Glob metacharacters that end the static (literal) prefix of an `include` pattern. */
323
+ const GLOB_META = /[*?[\]{}!]/;
324
+ /**
325
+ * The static directory prefix of each `include` pattern — the literal leading
326
+ * path up to the first glob segment — trailing-slashed, used to (a) walk only
327
+ * the opted-in subtrees in `collectIncludedFiles` and (b) let `ScopeIgnore` keep
328
+ * the watcher descending toward them. `Tools/` stays `Tools/`; a recursive
329
+ * `Tools/**` glob yields `Tools/`; `src/local/file.ts` yields `src/local/` (the
330
+ * file's dir); a pattern that starts with a glob (like a leading `**`) yields
331
+ * `''`, meaning "no static root — walk the whole tree". Duplicates and roots
332
+ * nested under a broader root are collapsed so each subtree is walked once.
333
+ */
334
+ function includeStaticRoots(patterns) {
335
+ const roots = new Set();
336
+ for (const pattern of patterns) {
337
+ let p = pattern.replace(/^\/+/, '');
338
+ const trailingSlash = p.endsWith('/');
339
+ if (trailingSlash)
340
+ p = p.slice(0, -1);
341
+ const segs = p.split('/').filter(Boolean);
342
+ const lead = [];
343
+ for (const s of segs) {
344
+ if (GLOB_META.test(s))
345
+ break;
346
+ lead.push(s);
347
+ }
348
+ const hadWildcard = lead.length < segs.length;
349
+ // A wholly-literal pattern with no trailing slash names a file (or a dir we
350
+ // can't tell apart) — drop its last segment so we walk the containing dir
351
+ // and let the matcher pick the file. A trailing slash or a glob means the
352
+ // remaining `lead` is already the directory to walk.
353
+ if (!hadWildcard && !trailingSlash && lead.length > 0)
354
+ lead.pop();
355
+ if (lead.length === 0) {
356
+ roots.clear();
357
+ roots.add('');
358
+ return ['']; // a top-level glob forces a whole-tree walk; nothing narrower matters
359
+ }
360
+ roots.add(lead.join('/') + '/');
361
+ }
362
+ // Collapse roots nested under a broader one (e.g. drop `a/b/` if `a/` is present).
363
+ const all = [...roots];
364
+ return all.filter((r) => !all.some((other) => other !== r && r.startsWith(other)));
365
+ }
366
+ /**
367
+ * Actively discover the source files an `include` whitelist forces in. `git
368
+ * ls-files` never lists gitignored files, so a filtered filesystem walk of just
369
+ * the opted-in subtrees (`includeStaticRoots`) is the only way to find them.
370
+ * Returns project-root-relative, normalized source-file paths.
371
+ *
372
+ * A file is collected when it MATCHES `include`, is NOT hit by `exclude` (an
373
+ * explicit exclude always wins), is a recognized source file, and does not live
374
+ * under a built-in default-ignored dir (`node_modules`, `dist`, …), `.git`, or
375
+ * HomeGraph's data dir — those are never resurfaced, mirroring `ScopeIgnore`.
376
+ * `.gitignore` is deliberately NOT consulted: overriding it is the whole point.
377
+ */
378
+ function collectIncludedFiles(rootDir, include, exclude, roots, overrides) {
379
+ const out = new Set();
380
+ const defaults = defaultsOnlyIgnore();
381
+ const visited = new Set();
382
+ const consider = (abs, rel, isDir) => {
383
+ if (isDir) {
384
+ if (defaults.ignores(rel + '/'))
385
+ return; // never node_modules/dist/… via include
386
+ // An explicit `exclude` always wins over `include`; prune the whole subtree
387
+ // here so a large excluded dir (a committed frontend's own vendored deps,
388
+ // build output, …) is never walked — the per-file guard below still catches
389
+ // anything a directory pattern doesn't, so this is a pure efficiency win.
390
+ if (exclude && exclude.ignores(rel + '/'))
391
+ return;
392
+ walk(abs);
393
+ }
394
+ else {
395
+ if (defaults.ignores(rel))
396
+ return;
397
+ if (!include.ignores(rel))
398
+ return;
399
+ if (exclude && exclude.ignores(rel))
400
+ return;
401
+ if (!(0, grammars_1.isSourceFile)(rel, overrides))
402
+ return;
403
+ out.add(rel);
404
+ }
405
+ };
406
+ function walk(absDir) {
407
+ let realDir;
408
+ try {
409
+ realDir = fs.realpathSync(absDir);
410
+ }
411
+ catch {
412
+ return;
413
+ }
414
+ if (visited.has(realDir))
415
+ return; // symlink-cycle guard
416
+ visited.add(realDir);
417
+ let entries;
418
+ try {
419
+ entries = fs.readdirSync(absDir, { withFileTypes: true });
420
+ }
421
+ catch {
422
+ return;
423
+ }
424
+ for (const entry of entries) {
425
+ if (entry.name === '.git' || (0, directory_1.isHomeGraphDataDir)(entry.name))
426
+ continue;
427
+ const abs = path.join(absDir, entry.name);
428
+ const rel = (0, utils_1.normalizePath)(path.relative(rootDir, abs));
429
+ if (!rel || rel.startsWith('..'))
430
+ continue;
431
+ if (entry.isSymbolicLink()) {
432
+ try {
433
+ const st = fs.statSync(fs.realpathSync(abs));
434
+ consider(abs, rel, st.isDirectory());
435
+ }
436
+ catch {
437
+ // broken symlink — skip
438
+ }
439
+ continue;
440
+ }
441
+ consider(abs, rel, entry.isDirectory());
442
+ }
443
+ }
444
+ for (const root of roots) {
445
+ walk(root === '' ? rootDir : path.join(rootDir, root));
446
+ }
447
+ return out;
448
+ }
449
+ /**
450
+ * The included source files (`homegraph.json` `include`) for a scan root, or an
451
+ * empty set when nothing is force-included. Centralizes loading the matcher,
452
+ * roots, exclude, and overrides so both enumeration paths (git and filesystem
453
+ * walk) add the same files.
454
+ */
455
+ function collectIncludedFilesForRoot(rootDir) {
456
+ const include = loadIncludeMatcher(rootDir);
457
+ if (!include)
458
+ return new Set();
459
+ const roots = includeStaticRoots((0, project_config_1.loadIncludePatterns)(rootDir));
460
+ return collectIncludedFiles(rootDir, include, loadExcludeMatcher(rootDir), roots, (0, project_config_1.loadExtensionOverrides)(rootDir));
461
+ }
307
462
  /**
308
463
  * `git ls-files --directory` collapses a wholly-untracked/ignored directory into
309
464
  * one entry — and when the command's own cwd is such a directory (the indexed
@@ -444,17 +599,31 @@ function findNestedGitRepos(absDir, relPrefix) {
444
599
  class ScopeIgnore {
445
600
  rootMatcher;
446
601
  exclude;
602
+ include;
603
+ includeRoots;
447
604
  embedded;
448
605
  defaults = defaultsOnlyIgnore();
449
606
  constructor(rootMatcher, embedded,
450
607
  /**
451
- * Project `codegraph.json` `exclude` patterns (#999), matched against the
608
+ * Project `homegraph.json` `exclude` patterns (#999), matched against the
452
609
  * full root-relative path. Wins over everything else — an explicit user
453
610
  * exclude applies even to tracked files and even inside embedded repos.
454
611
  */
455
- exclude = null) {
612
+ exclude = null,
613
+ /**
614
+ * Project `homegraph.json` `include` patterns — first-party source forced
615
+ * INTO the index despite `.gitignore`. When a path matches, it is NOT
616
+ * ignored (so the watcher watches it), overriding `.gitignore`/`rootMatcher`
617
+ * — but never `exclude` (checked first) and never a built-in default-ignored
618
+ * dir. `includeRoots` are the static prefixes so a gitignored ANCESTOR
619
+ * directory of an included subtree still isn't pruned by the directory
620
+ * walker/watcher.
621
+ */
622
+ include = null, includeRoots = []) {
456
623
  this.rootMatcher = rootMatcher;
457
624
  this.exclude = exclude;
625
+ this.include = include;
626
+ this.includeRoots = includeRoots;
458
627
  // Longest root first so paths in nested embedded repos hit the innermost matcher.
459
628
  this.embedded = [...embedded].sort((a, b) => b.root.length - a.root.length);
460
629
  }
@@ -464,6 +633,20 @@ class ScopeIgnore {
464
633
  // everywhere, including ancestors of embedded repos.
465
634
  if (this.exclude && this.exclude.ignores(rel))
466
635
  return true;
636
+ // User `include`: force first-party source in despite `.gitignore`. Never
637
+ // resurfaces a built-in default-ignored dir (node_modules/dist/…), so an
638
+ // include pattern can't accidentally pull in dependency/build trees.
639
+ if (this.include && !this.defaults.ignores(rel)) {
640
+ if (rel.endsWith('/')) {
641
+ // A directory on (or leading to) an included subtree must stay walkable
642
+ // so the watcher/walker descends to reach the forced-in files.
643
+ if (this.includeRoots.some((r) => r.startsWith(rel) || rel.startsWith(r)))
644
+ return false;
645
+ }
646
+ else if (this.include.ignores(rel)) {
647
+ return false;
648
+ }
649
+ }
467
650
  for (const { root, matcher } of this.embedded) {
468
651
  if (rel.startsWith(root)) {
469
652
  const inner = rel.slice(root.length);
@@ -490,7 +673,8 @@ exports.ScopeIgnore = ScopeIgnore;
490
673
  */
491
674
  function buildScopeIgnore(rootDir, embeddedRoots) {
492
675
  const roots = embeddedRoots ? [...embeddedRoots] : discoverEmbeddedRepoRoots(rootDir);
493
- return new ScopeIgnore(buildDefaultIgnore(rootDir), roots.map((root) => ({ root, matcher: buildDefaultIgnore(path.join(rootDir, root)) })), loadExcludeMatcher(rootDir));
676
+ const include = loadIncludeMatcher(rootDir);
677
+ 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)) : []);
494
678
  }
495
679
  /**
496
680
  * Whether an embedded repo found as a tracked gitlink (mode 160000, #1031/#1033)
@@ -503,7 +687,7 @@ function buildScopeIgnore(rootDir, embeddedRoots) {
503
687
  * under `node_modules` is never project code; not even an explicit opt-in
504
688
  * revives it (matches `findIgnoredEmbeddedRepos`).
505
689
  * 2. The parent repo's own `.gitignore` covers its path and the project did
506
- * NOT opt that path in via `codegraph.json` `includeIgnored`. The gitignore
690
+ * NOT opt that path in via `homegraph.json` `includeIgnored`. The gitignore
507
691
  * rule is the user's stated intent to keep that path out of scope, exactly
508
692
  * as for an UNtracked embedded repo — respect it by default, opt back in
509
693
  * with `includeIgnored` (#514, #970, #976).
@@ -580,6 +764,50 @@ function discoverEmbeddedRepoRoots(rootDir) {
580
764
  visit(rootDir, '');
581
765
  return out;
582
766
  }
767
+ /**
768
+ * Cap on how many skipped gitignored repos the CLI hint enumerates — a huge
769
+ * gitignored data dir full of clones must never turn the hint scan into a long
770
+ * walk. Enough to make the point; the caller says "+N more" past this.
771
+ */
772
+ const UNINDEXED_IGNORED_REPO_HINT_CAP = 100;
773
+ /**
774
+ * The INVERSE of the gitignored side of {@link discoverEmbeddedRepoRoots}:
775
+ * nested git repositories under a gitignored directory that the project has NOT
776
+ * opted into via `homegraph.json` `includeIgnored`. These are real repos the
777
+ * default `init`/`index` deliberately skips because `.gitignore` excludes them
778
+ * (#970, #976) — most visibly the "super-repo `.gitignore`s its child repos"
779
+ * layout (#1156), where `init` at the parent correctly indexes ~nothing while
780
+ * `init` inside each child works. The CLI uses this to turn that silent empty
781
+ * index into an actionable hint: it names the skipped repos and offers to opt
782
+ * them in. Paths are `rootDir`-relative and trailing-slashed (valid
783
+ * `includeIgnored` patterns as-is). Returns `[]` for a non-git root (a
784
+ * filesystem walk already descends into nested repos there), skips built-in
785
+ * default-ignored dirs (`node_modules`, …), and is bounded so it never stalls
786
+ * on a giant ignored tree.
787
+ */
788
+ function findUnindexedIgnoredRepos(rootDir) {
789
+ try {
790
+ (0, child_process_1.execFileSync)('git', ['rev-parse', '--git-dir'], { cwd: rootDir, encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
791
+ }
792
+ catch {
793
+ return [];
794
+ }
795
+ const defaults = defaultsOnlyIgnore();
796
+ const includeIgnored = loadIncludeIgnoredMatcher(rootDir);
797
+ const repos = [];
798
+ for (const dir of listIgnoredDirs(rootDir)) {
799
+ if (defaults.ignores(dir))
800
+ continue; // node_modules etc. — never project code
801
+ if (includeIgnored?.ignores((0, utils_1.normalizePath)(dir)))
802
+ continue; // already opted in — nothing to nag about
803
+ for (const repo of findNestedGitRepos(path.join(rootDir, dir), dir)) {
804
+ repos.push(repo);
805
+ if (repos.length >= UNINDEXED_IGNORED_REPO_HINT_CAP)
806
+ return repos;
807
+ }
808
+ }
809
+ return repos;
810
+ }
583
811
  /**
584
812
  * Discover embedded repos hidden by `repoDir`'s OWN gitignore rules: for each
585
813
  * gitignored directory, search for nested `.git` roots. Returns repo paths
@@ -787,7 +1015,11 @@ function getGitVisibleFiles(rootDir) {
787
1015
  // not the parent's: the parent's .gitignore hides the child repo from git,
788
1016
  // not from the index. (#514)
789
1017
  const ig = buildScopeIgnore(rootDir, embeddedRoots);
790
- return new Set([...files].filter((f) => !ig.ignores(f)));
1018
+ const visible = new Set([...files].filter((f) => !ig.ignores(f)));
1019
+ // Force-include first-party source whitelisted in `homegraph.json` `include`.
1020
+ for (const f of collectIncludedFilesForRoot(rootDir))
1021
+ visible.add(f);
1022
+ return visible;
791
1023
  }
792
1024
  catch {
793
1025
  return null;
@@ -1050,8 +1282,52 @@ function scanDirectoryWalk(rootDir, onProgress) {
1050
1282
  if (exclude)
1051
1283
  baseMatchers.push({ dir: rootDir, ig: exclude });
1052
1284
  walk(rootDir, baseMatchers);
1285
+ // Force-include first-party source whitelisted in `homegraph.json` `include`.
1286
+ const included = collectIncludedFilesForRoot(rootDir);
1287
+ if (included.size > 0) {
1288
+ const seen = new Set(files);
1289
+ for (const f of included) {
1290
+ if (!seen.has(f)) {
1291
+ files.push(f);
1292
+ seen.add(f);
1293
+ }
1294
+ }
1295
+ }
1053
1296
  return files;
1054
1297
  }
1298
+ /**
1299
+ * Extraction orchestrator
1300
+ */
1301
+ /**
1302
+ * Resurrect a resolution edge that is about to be dropped (its target symbol
1303
+ * was removed, renamed, or its whole file deleted) as the ORIGINAL unresolved
1304
+ * reference that created it, read from the refName/refKind stamp
1305
+ * `createEdges` writes into edge metadata. Inserted as status='pending', the
1306
+ * ref is consumed by the same sync's resolution sweep: it rebinds to an
1307
+ * alternative definition if one exists, or parks as status='failed' where the
1308
+ * #1240 retry finds it if the symbol later reappears.
1309
+ *
1310
+ * Returns null — drop silently, the pre-#1240 behavior — for edges without a
1311
+ * refName stamp (created before the stamp existed, or synthesized): rebuilding
1312
+ * a ref from the target's plain node name would strip the receiver/qualifier
1313
+ * context the original text carried (`h.greet` → `greet`) and could rebind
1314
+ * somewhere a full re-index never would. Silent beats wrong.
1315
+ */
1316
+ function resurrectRefFromDroppedEdge(e) {
1317
+ const refName = e.metadata?.refName;
1318
+ if (typeof refName !== 'string' || refName.length === 0)
1319
+ return null;
1320
+ const refKind = typeof e.metadata?.refKind === 'string' ? e.metadata.refKind : e.kind;
1321
+ return {
1322
+ fromNodeId: e.source,
1323
+ referenceName: refName,
1324
+ referenceKind: refKind,
1325
+ line: e.line ?? 0,
1326
+ column: e.column ?? 0,
1327
+ filePath: e.sourceFilePath,
1328
+ language: e.sourceLanguage,
1329
+ };
1330
+ }
1055
1331
  /**
1056
1332
  * Extraction orchestrator
1057
1333
  */
@@ -1478,6 +1754,7 @@ class ExtractionOrchestrator {
1478
1754
  filesIndexed,
1479
1755
  filesSkipped,
1480
1756
  filesErrored,
1757
+ filesDiscovered: total,
1481
1758
  nodesCreated: totalNodes,
1482
1759
  edgesCreated: totalEdges,
1483
1760
  errors: [{ message: 'Aborted', severity: 'error' }, ...errors],
@@ -1600,6 +1877,7 @@ class ExtractionOrchestrator {
1600
1877
  filesIndexed,
1601
1878
  filesSkipped,
1602
1879
  filesErrored,
1880
+ filesDiscovered: total,
1603
1881
  nodesCreated: totalNodes,
1604
1882
  edgesCreated: totalEdges,
1605
1883
  errors,
@@ -1824,15 +2102,24 @@ class ExtractionOrchestrator {
1824
2102
  newNodesByKindName.set(`${n.kind}\0${n.name}`, n.id);
1825
2103
  }
1826
2104
  const reinserted = [];
2105
+ const resurrected = [];
1827
2106
  for (const e of crossFileIncomingEdges) {
1828
2107
  const newTargetId = newNodesByKindName.get(`${e.targetKind}\0${e.targetName}`);
1829
2108
  if (newTargetId) {
1830
2109
  reinserted.push({ source: e.source, target: newTargetId, kind: e.kind, metadata: e.metadata, line: e.line, column: e.column, provenance: e.provenance });
1831
2110
  }
2111
+ else {
2112
+ const ref = resurrectRefFromDroppedEdge(e);
2113
+ if (ref)
2114
+ resurrected.push(ref);
2115
+ }
1832
2116
  }
1833
2117
  if (reinserted.length > 0) {
1834
2118
  this.queries.insertEdges(reinserted);
1835
2119
  }
2120
+ if (resurrected.length > 0) {
2121
+ this.queries.insertUnresolvedRefsBatch(resurrected);
2122
+ }
1836
2123
  }
1837
2124
  // Insert unresolved references in batch with denormalized filePath/language
1838
2125
  if (result.unresolvedReferences.length > 0) {
@@ -1909,6 +2196,17 @@ class ExtractionOrchestrator {
1909
2196
  let reconcileChecks = 0;
1910
2197
  for (const tracked of trackedFiles) {
1911
2198
  if (!currentSet.has(tracked.path) || !fs.existsSync(path.join(this.rootDir, tracked.path))) {
2199
+ // Before the cascade deletes them, resurrect incoming cross-file
2200
+ // resolution edges as their original refs (#1240 removal case).
2201
+ const incoming = this.queries.getCrossFileIncomingEdgesWithTarget(tracked.path);
2202
+ if (incoming.length > 0) {
2203
+ const resurrected = incoming
2204
+ .map((e) => resurrectRefFromDroppedEdge(e))
2205
+ .filter((r) => r !== null);
2206
+ if (resurrected.length > 0) {
2207
+ this.queries.insertUnresolvedRefsBatch(resurrected);
2208
+ }
2209
+ }
1912
2210
  this.queries.deleteFile(tracked.path);
1913
2211
  filesRemoved++;
1914
2212
  }
@@ -1922,6 +2220,11 @@ class ExtractionOrchestrator {
1922
2220
  // synchronous-stat loop that wedges the main thread on a large repo (#905).
1923
2221
  // Yield at the top of the body so the `continue` fast-paths below still hit it.
1924
2222
  if (++reconcileChecks % SYNC_RECONCILE_YIELD_INTERVAL === 0) {
2223
+ onProgress?.({
2224
+ phase: 'scanning',
2225
+ current: reconcileChecks,
2226
+ total: currentFiles.length,
2227
+ });
1925
2228
  await new Promise((resolve) => setImmediate(resolve));
1926
2229
  }
1927
2230
  const fullPath = path.join(this.rootDir, filePath);