homegraph 1.1.2 → 1.2.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.
Files changed (361) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +268 -243
  3. package/dist/arkts/ohos-api-index.d.ts +15 -0
  4. package/dist/arkts/ohos-api-index.d.ts.map +1 -0
  5. package/dist/arkts/ohos-api-index.js +190 -0
  6. package/dist/arkts/ohos-api-index.js.map +1 -0
  7. package/dist/arkts/ohos-sdk-input.d.ts +36 -0
  8. package/dist/arkts/ohos-sdk-input.d.ts.map +1 -0
  9. package/dist/arkts/ohos-sdk-input.js +214 -0
  10. package/dist/arkts/ohos-sdk-input.js.map +1 -0
  11. package/dist/bin/command-supervision.d.ts +12 -0
  12. package/dist/bin/command-supervision.d.ts.map +1 -0
  13. package/dist/bin/command-supervision.js +86 -0
  14. package/dist/bin/command-supervision.js.map +1 -0
  15. package/dist/bin/homegraph.d.ts +1 -1
  16. package/dist/bin/homegraph.js +971 -71
  17. package/dist/bin/homegraph.js.map +1 -1
  18. package/dist/db/index.d.ts +20 -0
  19. package/dist/db/index.d.ts.map +1 -1
  20. package/dist/db/index.js +39 -0
  21. package/dist/db/index.js.map +1 -1
  22. package/dist/db/migrations.d.ts +1 -1
  23. package/dist/db/migrations.d.ts.map +1 -1
  24. package/dist/db/migrations.js +58 -19
  25. package/dist/db/migrations.js.map +1 -1
  26. package/dist/db/queries.d.ts +14 -0
  27. package/dist/db/queries.d.ts.map +1 -1
  28. package/dist/db/queries.js +314 -143
  29. package/dist/db/queries.js.map +1 -1
  30. package/dist/db/schema.sql +172 -152
  31. package/dist/directory.d.ts +32 -0
  32. package/dist/directory.d.ts.map +1 -1
  33. package/dist/directory.js +88 -5
  34. package/dist/directory.js.map +1 -1
  35. package/dist/extraction/arkts-batch-worker.d.ts +2 -0
  36. package/dist/extraction/arkts-batch-worker.d.ts.map +1 -0
  37. package/dist/extraction/arkts-batch-worker.js +28 -0
  38. package/dist/extraction/arkts-batch-worker.js.map +1 -0
  39. package/dist/extraction/context.d.ts +11 -0
  40. package/dist/extraction/context.d.ts.map +1 -1
  41. package/dist/extraction/context.js +20 -0
  42. package/dist/extraction/context.js.map +1 -1
  43. package/dist/extraction/index.d.ts +16 -2
  44. package/dist/extraction/index.d.ts.map +1 -1
  45. package/dist/extraction/index.js +619 -380
  46. package/dist/extraction/index.js.map +1 -1
  47. package/dist/extraction/languages/arkts-state-decorators.d.ts +13 -0
  48. package/dist/extraction/languages/arkts-state-decorators.d.ts.map +1 -0
  49. package/dist/extraction/languages/arkts-state-decorators.js +26 -0
  50. package/dist/extraction/languages/arkts-state-decorators.js.map +1 -0
  51. package/dist/extraction/languages/arkts-viewtree.d.ts +4 -2
  52. package/dist/extraction/languages/arkts-viewtree.d.ts.map +1 -1
  53. package/dist/extraction/languages/arkts-viewtree.js +21 -6
  54. package/dist/extraction/languages/arkts-viewtree.js.map +1 -1
  55. package/dist/extraction/languages/arkts.d.ts +98 -2
  56. package/dist/extraction/languages/arkts.d.ts.map +1 -1
  57. package/dist/extraction/languages/arkts.js +1290 -61
  58. package/dist/extraction/languages/arkts.js.map +1 -1
  59. package/dist/extraction/languages/c-cpp.d.ts +56 -0
  60. package/dist/extraction/languages/c-cpp.d.ts.map +1 -1
  61. package/dist/extraction/languages/c-cpp.js +198 -1
  62. package/dist/extraction/languages/c-cpp.js.map +1 -1
  63. package/dist/extraction/languages/ohos-api-consumer.d.ts +34 -0
  64. package/dist/extraction/languages/ohos-api-consumer.d.ts.map +1 -0
  65. package/dist/extraction/languages/ohos-api-consumer.js +283 -0
  66. package/dist/extraction/languages/ohos-api-consumer.js.map +1 -0
  67. package/dist/extraction/parse-pool.d.ts +126 -0
  68. package/dist/extraction/parse-pool.d.ts.map +1 -0
  69. package/dist/extraction/parse-pool.js +319 -0
  70. package/dist/extraction/parse-pool.js.map +1 -0
  71. package/dist/extraction/tree-sitter-types.d.ts +17 -0
  72. package/dist/extraction/tree-sitter-types.d.ts.map +1 -1
  73. package/dist/extraction/tree-sitter.d.ts +21 -0
  74. package/dist/extraction/tree-sitter.d.ts.map +1 -1
  75. package/dist/extraction/tree-sitter.js +198 -27
  76. package/dist/extraction/tree-sitter.js.map +1 -1
  77. package/dist/extraction/wasm/tree-sitter-c_sharp.wasm +0 -0
  78. package/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
  79. package/dist/graph/traversal.d.ts.map +1 -1
  80. package/dist/graph/traversal.js +76 -17
  81. package/dist/graph/traversal.js.map +1 -1
  82. package/dist/index.d.ts +25 -0
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +78 -4
  85. package/dist/index.js.map +1 -1
  86. package/dist/installer/instructions-template.js +9 -9
  87. package/dist/installer/targets/shared.d.ts +5 -6
  88. package/dist/installer/targets/shared.d.ts.map +1 -1
  89. package/dist/installer/targets/shared.js +5 -6
  90. package/dist/installer/targets/shared.js.map +1 -1
  91. package/dist/mcp/daemon-paths.d.ts +30 -3
  92. package/dist/mcp/daemon-paths.d.ts.map +1 -1
  93. package/dist/mcp/daemon-paths.js +50 -10
  94. package/dist/mcp/daemon-paths.js.map +1 -1
  95. package/dist/mcp/daemon-registry.d.ts.map +1 -1
  96. package/dist/mcp/daemon-registry.js +7 -3
  97. package/dist/mcp/daemon-registry.js.map +1 -1
  98. package/dist/mcp/daemon.d.ts +48 -0
  99. package/dist/mcp/daemon.d.ts.map +1 -1
  100. package/dist/mcp/daemon.js +196 -32
  101. package/dist/mcp/daemon.js.map +1 -1
  102. package/dist/mcp/engine.d.ts +17 -0
  103. package/dist/mcp/engine.d.ts.map +1 -1
  104. package/dist/mcp/engine.js +73 -1
  105. package/dist/mcp/engine.js.map +1 -1
  106. package/dist/mcp/index.d.ts.map +1 -1
  107. package/dist/mcp/index.js +25 -43
  108. package/dist/mcp/index.js.map +1 -1
  109. package/dist/mcp/liveness-watchdog.js +16 -16
  110. package/dist/mcp/ppid-watchdog.d.ts +18 -0
  111. package/dist/mcp/ppid-watchdog.d.ts.map +1 -1
  112. package/dist/mcp/ppid-watchdog.js +37 -0
  113. package/dist/mcp/ppid-watchdog.js.map +1 -1
  114. package/dist/mcp/query-cache.d.ts +25 -0
  115. package/dist/mcp/query-cache.d.ts.map +1 -0
  116. package/dist/mcp/query-cache.js +191 -0
  117. package/dist/mcp/query-cache.js.map +1 -0
  118. package/dist/mcp/query-pool.d.ts +94 -0
  119. package/dist/mcp/query-pool.d.ts.map +1 -0
  120. package/dist/mcp/query-pool.js +297 -0
  121. package/dist/mcp/query-pool.js.map +1 -0
  122. package/dist/mcp/query-worker.d.ts +24 -0
  123. package/dist/mcp/query-worker.d.ts.map +1 -0
  124. package/dist/mcp/query-worker.js +87 -0
  125. package/dist/mcp/query-worker.js.map +1 -0
  126. package/dist/mcp/server-instructions.d.ts +5 -7
  127. package/dist/mcp/server-instructions.d.ts.map +1 -1
  128. package/dist/mcp/server-instructions.js +72 -74
  129. package/dist/mcp/server-instructions.js.map +1 -1
  130. package/dist/mcp/tools.d.ts +94 -4
  131. package/dist/mcp/tools.d.ts.map +1 -1
  132. package/dist/mcp/tools.js +877 -71
  133. package/dist/mcp/tools.js.map +1 -1
  134. package/dist/project-config.d.ts +20 -0
  135. package/dist/project-config.d.ts.map +1 -1
  136. package/dist/project-config.js +42 -2
  137. package/dist/project-config.js.map +1 -1
  138. package/dist/reasoning/login.js +1 -1
  139. package/dist/reasoning/login.js.map +1 -1
  140. package/dist/reasoning/reasoner.js +32 -32
  141. package/dist/resolution/c-fnptr-synthesizer.d.ts +0 -28
  142. package/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -1
  143. package/dist/resolution/c-fnptr-synthesizer.js +765 -79
  144. package/dist/resolution/c-fnptr-synthesizer.js.map +1 -1
  145. package/dist/resolution/callback-synthesizer.d.ts +1 -1
  146. package/dist/resolution/callback-synthesizer.d.ts.map +1 -1
  147. package/dist/resolution/callback-synthesizer.js +72 -11
  148. package/dist/resolution/callback-synthesizer.js.map +1 -1
  149. package/dist/resolution/cooperative-yield.d.ts +32 -0
  150. package/dist/resolution/cooperative-yield.d.ts.map +1 -0
  151. package/dist/resolution/cooperative-yield.js +42 -0
  152. package/dist/resolution/cooperative-yield.js.map +1 -0
  153. package/dist/resolution/index.d.ts +11 -2
  154. package/dist/resolution/index.d.ts.map +1 -1
  155. package/dist/resolution/index.js +72 -4
  156. package/dist/resolution/index.js.map +1 -1
  157. package/dist/resolution/name-matcher.d.ts +22 -0
  158. package/dist/resolution/name-matcher.d.ts.map +1 -1
  159. package/dist/resolution/name-matcher.js +317 -20
  160. package/dist/resolution/name-matcher.js.map +1 -1
  161. package/dist/spec/config.d.ts +39 -0
  162. package/dist/spec/config.d.ts.map +1 -0
  163. package/dist/spec/config.js +304 -0
  164. package/dist/spec/config.js.map +1 -0
  165. package/dist/spec/db/commit-node.d.ts +23 -0
  166. package/dist/spec/db/commit-node.d.ts.map +1 -0
  167. package/dist/spec/db/commit-node.js +62 -0
  168. package/dist/spec/db/commit-node.js.map +1 -0
  169. package/dist/spec/db/fragment-node.d.ts +24 -0
  170. package/dist/spec/db/fragment-node.d.ts.map +1 -0
  171. package/dist/spec/db/fragment-node.js +128 -0
  172. package/dist/spec/db/fragment-node.js.map +1 -0
  173. package/dist/spec/db/fts.d.ts +74 -0
  174. package/dist/spec/db/fts.d.ts.map +1 -0
  175. package/dist/spec/db/fts.js +324 -0
  176. package/dist/spec/db/fts.js.map +1 -0
  177. package/dist/spec/db/index.d.ts +13 -0
  178. package/dist/spec/db/index.d.ts.map +1 -0
  179. package/dist/spec/db/index.js +50 -0
  180. package/dist/spec/db/index.js.map +1 -0
  181. package/dist/spec/db/relations.d.ts +55 -0
  182. package/dist/spec/db/relations.d.ts.map +1 -0
  183. package/dist/spec/db/relations.js +158 -0
  184. package/dist/spec/db/relations.js.map +1 -0
  185. package/dist/spec/db/schema.d.ts +33 -0
  186. package/dist/spec/db/schema.d.ts.map +1 -0
  187. package/dist/spec/db/schema.js +119 -0
  188. package/dist/spec/db/schema.js.map +1 -0
  189. package/dist/spec/db/schema.sql +117 -0
  190. package/dist/spec/db/spec-node.d.ts +41 -0
  191. package/dist/spec/db/spec-node.d.ts.map +1 -0
  192. package/dist/spec/db/spec-node.js +114 -0
  193. package/dist/spec/db/spec-node.js.map +1 -0
  194. package/dist/spec/evolve/impact-locator.d.ts +13 -0
  195. package/dist/spec/evolve/impact-locator.d.ts.map +1 -0
  196. package/dist/spec/evolve/impact-locator.js +25 -0
  197. package/dist/spec/evolve/impact-locator.js.map +1 -0
  198. package/dist/spec/evolve/llm-client.d.ts +50 -0
  199. package/dist/spec/evolve/llm-client.d.ts.map +1 -0
  200. package/dist/spec/evolve/llm-client.js +176 -0
  201. package/dist/spec/evolve/llm-client.js.map +1 -0
  202. package/dist/spec/evolve/logic-checker.d.ts +12 -0
  203. package/dist/spec/evolve/logic-checker.d.ts.map +1 -0
  204. package/dist/spec/evolve/logic-checker.js +24 -0
  205. package/dist/spec/evolve/logic-checker.js.map +1 -0
  206. package/dist/spec/evolve/pipeline.d.ts +42 -0
  207. package/dist/spec/evolve/pipeline.d.ts.map +1 -0
  208. package/dist/spec/evolve/pipeline.js +567 -0
  209. package/dist/spec/evolve/pipeline.js.map +1 -0
  210. package/dist/spec/evolve/spec-rewriter.d.ts +42 -0
  211. package/dist/spec/evolve/spec-rewriter.d.ts.map +1 -0
  212. package/dist/spec/evolve/spec-rewriter.js +230 -0
  213. package/dist/spec/evolve/spec-rewriter.js.map +1 -0
  214. package/dist/spec/graph/queries.d.ts +155 -0
  215. package/dist/spec/graph/queries.d.ts.map +1 -0
  216. package/dist/spec/graph/queries.js +440 -0
  217. package/dist/spec/graph/queries.js.map +1 -0
  218. package/dist/spec/llm/client.d.ts +29 -0
  219. package/dist/spec/llm/client.d.ts.map +1 -0
  220. package/dist/spec/llm/client.js +123 -0
  221. package/dist/spec/llm/client.js.map +1 -0
  222. package/dist/spec/llm/index.d.ts +3 -0
  223. package/dist/spec/llm/index.d.ts.map +1 -0
  224. package/dist/spec/llm/index.js +11 -0
  225. package/dist/spec/llm/index.js.map +1 -0
  226. package/dist/spec/llm/prompts.d.ts +13 -0
  227. package/dist/spec/llm/prompts.d.ts.map +1 -0
  228. package/dist/spec/llm/prompts.js +75 -0
  229. package/dist/spec/llm/prompts.js.map +1 -0
  230. package/dist/spec/mining/diff-parser.d.ts +33 -0
  231. package/dist/spec/mining/diff-parser.d.ts.map +1 -0
  232. package/dist/spec/mining/diff-parser.js +166 -0
  233. package/dist/spec/mining/diff-parser.js.map +1 -0
  234. package/dist/spec/mining/git-scanner.d.ts +103 -0
  235. package/dist/spec/mining/git-scanner.d.ts.map +1 -0
  236. package/dist/spec/mining/git-scanner.js +307 -0
  237. package/dist/spec/mining/git-scanner.js.map +1 -0
  238. package/dist/spec/mining/pipeline.d.ts +53 -0
  239. package/dist/spec/mining/pipeline.d.ts.map +1 -0
  240. package/dist/spec/mining/pipeline.js +178 -0
  241. package/dist/spec/mining/pipeline.js.map +1 -0
  242. package/dist/spec/mining/scope-resolver.d.ts +45 -0
  243. package/dist/spec/mining/scope-resolver.d.ts.map +1 -0
  244. package/dist/spec/mining/scope-resolver.js +103 -0
  245. package/dist/spec/mining/scope-resolver.js.map +1 -0
  246. package/dist/spec/mining/spec-extractor.d.ts +69 -0
  247. package/dist/spec/mining/spec-extractor.d.ts.map +1 -0
  248. package/dist/spec/mining/spec-extractor.js +369 -0
  249. package/dist/spec/mining/spec-extractor.js.map +1 -0
  250. package/dist/spec/types.d.ts +149 -0
  251. package/dist/spec/types.d.ts.map +1 -0
  252. package/dist/spec/types.js +15 -0
  253. package/dist/spec/types.js.map +1 -0
  254. package/dist/spec/utils.d.ts +167 -0
  255. package/dist/spec/utils.d.ts.map +1 -0
  256. package/dist/spec/utils.js +463 -0
  257. package/dist/spec/utils.js.map +1 -0
  258. package/dist/sync/worktree.d.ts +9 -0
  259. package/dist/sync/worktree.d.ts.map +1 -1
  260. package/dist/sync/worktree.js +40 -0
  261. package/dist/sync/worktree.js.map +1 -1
  262. package/dist/types.d.ts +6 -1
  263. package/dist/types.d.ts.map +1 -1
  264. package/dist/ui/shimmer-progress.d.ts +2 -0
  265. package/dist/ui/shimmer-progress.d.ts.map +1 -1
  266. package/dist/ui/shimmer-progress.js +19 -2
  267. package/dist/ui/shimmer-progress.js.map +1 -1
  268. package/dist/upgrade/index.js +1 -1
  269. package/dist/upgrade/index.js.map +1 -1
  270. package/package.json +58 -57
  271. package/scripts/add-lang/bench.sh +60 -60
  272. package/scripts/add-lang/check-grammar.mjs +75 -75
  273. package/scripts/add-lang/dump-ast.mjs +103 -103
  274. package/scripts/add-lang/verify-extraction.mjs +70 -70
  275. package/scripts/agent-eval/ab-adoption.sh +91 -91
  276. package/scripts/agent-eval/ab-hook.sh +86 -86
  277. package/scripts/agent-eval/ab-impl.sh +78 -78
  278. package/scripts/agent-eval/ab-new-vs-baseline.sh +102 -102
  279. package/scripts/agent-eval/ab-sufficiency.sh +78 -78
  280. package/scripts/agent-eval/arms-F.sh +21 -21
  281. package/scripts/agent-eval/arms-matrix.sh +37 -37
  282. package/scripts/agent-eval/audit.sh +68 -68
  283. package/scripts/agent-eval/bench-readme.sh +28 -28
  284. package/scripts/agent-eval/bench-why-repo.sh +22 -22
  285. package/scripts/agent-eval/block-read-hook.sh +19 -19
  286. package/scripts/agent-eval/hook-settings.json +15 -15
  287. package/scripts/agent-eval/itrun.sh +120 -120
  288. package/scripts/agent-eval/offload-eval-3arm.sh +72 -72
  289. package/scripts/agent-eval/offload-eval-cost.mjs +133 -133
  290. package/scripts/agent-eval/offload-eval-effort.mjs +108 -108
  291. package/scripts/agent-eval/offload-eval-frontload-matrix.sh +25 -25
  292. package/scripts/agent-eval/offload-eval-frontload.sh +47 -47
  293. package/scripts/agent-eval/offload-eval-ground-truth.json +18 -18
  294. package/scripts/agent-eval/offload-eval-hook.mjs +84 -84
  295. package/scripts/agent-eval/offload-eval-judge.mjs +103 -103
  296. package/scripts/agent-eval/offload-eval-matrix.sh +20 -20
  297. package/scripts/agent-eval/offload-eval-metrics.mjs +94 -94
  298. package/scripts/agent-eval/offload-eval-refs1.sh +50 -50
  299. package/scripts/agent-eval/offload-eval-setup.sh +24 -24
  300. package/scripts/agent-eval/offload-eval-styles.sh +71 -71
  301. package/scripts/agent-eval/offload-eval-summarize.mjs +68 -68
  302. package/scripts/agent-eval/offload-eval.md +76 -76
  303. package/scripts/agent-eval/parse-arms.mjs +116 -116
  304. package/scripts/agent-eval/parse-bench-readme.mjs +84 -84
  305. package/scripts/agent-eval/parse-run.mjs +45 -45
  306. package/scripts/agent-eval/parse-session.mjs +93 -93
  307. package/scripts/agent-eval/probe-context.mjs +21 -21
  308. package/scripts/agent-eval/probe-explore.mjs +40 -40
  309. package/scripts/agent-eval/probe-node.mjs +20 -20
  310. package/scripts/agent-eval/probe-sweep.mjs +119 -119
  311. package/scripts/agent-eval/probe-trace.mjs +20 -20
  312. package/scripts/agent-eval/redirect-read-hook.sh +38 -38
  313. package/scripts/agent-eval/repro-concurrent-explore.mjs +119 -0
  314. package/scripts/agent-eval/repro-daemon-clients.mjs +125 -0
  315. package/scripts/agent-eval/run-agent.sh +34 -34
  316. package/scripts/agent-eval/run-all.sh +69 -69
  317. package/scripts/agent-eval/run-arms.sh +56 -56
  318. package/scripts/agent-eval/seq-matrix.mjs +137 -137
  319. package/scripts/build-bundle.sh +118 -118
  320. package/scripts/exp_boundary_eval/README.md +247 -0
  321. package/scripts/exp_boundary_eval/__pycache__/_utils.cpython-310.pyc +0 -0
  322. package/scripts/exp_boundary_eval/__pycache__/analyze.cpython-310.pyc +0 -0
  323. package/scripts/exp_boundary_eval/__pycache__/deveco_arm.cpython-310.pyc +0 -0
  324. package/scripts/exp_boundary_eval/__pycache__/run_one.cpython-310.pyc +0 -0
  325. package/scripts/exp_boundary_eval/__pycache__/run_session.cpython-310.pyc +0 -0
  326. package/scripts/exp_boundary_eval/__pycache__/setup.cpython-310.pyc +0 -0
  327. package/scripts/exp_boundary_eval/_test_mcp_chain.py +78 -0
  328. package/scripts/exp_boundary_eval/_test_stdin.py +8 -0
  329. package/scripts/exp_boundary_eval/_utils.py +1116 -0
  330. package/scripts/exp_boundary_eval/analyze.py +1313 -0
  331. package/scripts/exp_boundary_eval/data/agents.json +109 -0
  332. package/scripts/exp_boundary_eval/data/experiments.json +140 -0
  333. package/scripts/exp_boundary_eval/deveco_arm.py +519 -0
  334. package/scripts/exp_boundary_eval/run_all.py +378 -0
  335. package/scripts/exp_boundary_eval/run_one.py +165 -0
  336. package/scripts/exp_boundary_eval/run_session.py +158 -0
  337. package/scripts/exp_boundary_eval/setup.py +120 -0
  338. package/scripts/exp_boundary_eval/win_mcp_launcher.py +73 -0
  339. package/scripts/exp_boundary_eval/win_mcp_stdio_wrap.js +36 -0
  340. package/scripts/exp_boundary_eval/win_node_launcher.py +24 -0
  341. package/scripts/extract-release-notes.mjs +130 -130
  342. package/scripts/local-install.sh +41 -41
  343. package/scripts/npm-sdk.js +75 -75
  344. package/scripts/npm-shim.js +268 -246
  345. package/scripts/ohos-sdk-publish.mjs +133 -0
  346. package/scripts/pack-npm.sh +119 -119
  347. package/scripts/prepare-release.mjs +270 -270
  348. package/scripts/qa_eval/README.md +407 -404
  349. package/scripts/qa_eval/_test_deveco_probe.py +41 -41
  350. package/scripts/qa_eval/agent_runner.py +526 -526
  351. package/scripts/qa_eval/data/.gitignore +4 -4
  352. package/scripts/qa_eval/data/test-set.jsonl +2 -22
  353. package/scripts/qa_eval/eval_metrics.py +274 -233
  354. package/scripts/qa_eval/external_agent.py +976 -671
  355. package/scripts/qa_eval/llm_config.py +92 -92
  356. package/scripts/qa_eval/memory_monitor.py +132 -132
  357. package/scripts/qa_eval/my_answer_accuracy.py +187 -187
  358. package/scripts/qa_eval/requirements.txt +2 -2
  359. package/scripts/qa_eval/run_pipeline.py +804 -711
  360. package/scripts/qa_eval/stats_efficiency.py +279 -279
  361. package/scripts/qa_eval/stats_scores.py +207 -207
@@ -20,7 +20,7 @@
20
20
  * homegraph callers <symbol> Find what calls a function/method
21
21
  * homegraph callees <symbol> Find what a function/method calls
22
22
  * homegraph impact <symbol> Analyze what code is affected by changing a symbol
23
- * homegraph affected [files] Find test files affected by changes
23
+ * homegraph index-api <tools> Build OHOS API db from command-line-tools
24
24
  * homegraph upgrade [version] Update HomeGraph to the latest release
25
25
  */
26
26
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
@@ -61,12 +61,14 @@ const commander_1 = require("commander");
61
61
  const path = __importStar(require("path"));
62
62
  const fs = __importStar(require("fs"));
63
63
  const directory_1 = require("../directory");
64
+ const utils_1 = require("../spec/utils");
64
65
  const worktree_1 = require("../sync/worktree");
65
66
  const shimmer_progress_1 = require("../ui/shimmer-progress");
66
67
  const glyphs_1 = require("../ui/glyphs");
67
68
  const node_version_check_1 = require("./node-version-check");
68
69
  const fatal_handler_1 = require("./fatal-handler");
69
70
  const wasm_runtime_flags_1 = require("../extraction/wasm-runtime-flags");
71
+ const command_supervision_1 = require("./command-supervision");
70
72
  const extraction_version_1 = require("../extraction/extraction-version");
71
73
  const telemetry_1 = require("../telemetry");
72
74
  // Lazy-load heavy modules (HomeGraph, runInstaller) to keep CLI startup fast.
@@ -235,6 +237,38 @@ function main() {
235
237
  // Not found - return original path (will fail later with helpful error)
236
238
  return absolutePath;
237
239
  }
240
+ /**
241
+ * Resolve the project root for spec operations by walking up from a path
242
+ * looking for `${SPEC_DATA_DIR}/meta.json` or `${SPEC_DATA_DIR}/commit4spec.db`.
243
+ * Falls back to the starting path when neither marker is found (callers
244
+ * handle the not-found case).
245
+ */
246
+ function resolveSpecProjectPath(pathArg) {
247
+ const startPath = path.resolve(pathArg || process.cwd());
248
+ // Direct hit
249
+ const checkDirs = [
250
+ `${utils_1.SPEC_DATA_DIR}/meta.json`,
251
+ `${utils_1.SPEC_DATA_DIR}/commit4spec.db`,
252
+ ];
253
+ for (const check of checkDirs) {
254
+ if (fs.existsSync(path.join(startPath, check)))
255
+ return startPath;
256
+ }
257
+ // Walk up
258
+ let current = startPath;
259
+ const root = path.parse(current).root;
260
+ while (current !== root) {
261
+ const parent = path.dirname(current);
262
+ if (parent === current)
263
+ break;
264
+ current = parent;
265
+ for (const check of checkDirs) {
266
+ if (fs.existsSync(path.join(current, check)))
267
+ return current;
268
+ }
269
+ }
270
+ return startPath; // fallback — callers handle not-found
271
+ }
238
272
  /**
239
273
  * Format a number with commas
240
274
  */
@@ -383,6 +417,19 @@ function main() {
383
417
  fs.unlinkSync(logPath);
384
418
  }
385
419
  }
420
+ const ohosApiWarnings = result.errors.filter((e) => e.severity === 'warning' && e.code?.startsWith('ohos_api_'));
421
+ if (ohosApiWarnings.length > 0) {
422
+ clack.log.warn('OHOS SDK API db not attached — project index is complete; explore uses project code only.');
423
+ clack.note(ohosApiWarnings.map((e) => e.message).join('\n'), 'OHOS API notice');
424
+ }
425
+ const arktsDegraded = result.errors.filter((e) => e.code === 'arkts_degraded');
426
+ if (arktsDegraded.length > 0) {
427
+ clack.log.warn('ArkTS index quality notice — see details below.');
428
+ clack.note(`${arktsDegraded[0].message}\n\n` +
429
+ 'Large ArkTS repos on Windows use an isolated indexer with enableMethodBodyBuild=true.\n' +
430
+ 'If indexing failed, set HOMEGRAPH_ARKTS_STACK_SIZES_KB=65536,131072,262144 and retry:\n' +
431
+ ' node --stack-size=65536 dist/bin/homegraph.js index <path>', 'ArkTS quality notice');
432
+ }
386
433
  }
387
434
  /**
388
435
  * Write detailed error log to .homegraph/errors.log
@@ -479,20 +526,27 @@ function main() {
479
526
  // Indexing runs by default now. The legacy -i/--index flag is still
480
527
  // accepted (so existing muscle memory and scripts don't break) but is a
481
528
  // no-op — initializing always builds the initial index.
529
+ // Supervise the index: self-terminate if orphaned or wedged (#999).
530
+ const supervision = (0, command_supervision_1.installCommandSupervision)('init');
482
531
  let result;
483
- if (options.verbose) {
484
- result = await cg.indexAll({
485
- onProgress: createVerboseProgress(),
486
- verbose: true,
487
- });
532
+ try {
533
+ if (options.verbose) {
534
+ result = await cg.indexAll({
535
+ onProgress: createVerboseProgress(),
536
+ verbose: true,
537
+ });
538
+ }
539
+ else {
540
+ process.stdout.write(`${colors.dim}${(0, glyphs_1.getGlyphs)().rail}${colors.reset}\n`);
541
+ const progress = (0, shimmer_progress_1.createShimmerProgress)();
542
+ result = await cg.indexAll({
543
+ onProgress: progress.onProgress,
544
+ });
545
+ await progress.stop();
546
+ }
488
547
  }
489
- else {
490
- process.stdout.write(`${colors.dim}${(0, glyphs_1.getGlyphs)().rail}${colors.reset}\n`);
491
- const progress = (0, shimmer_progress_1.createShimmerProgress)();
492
- result = await cg.indexAll({
493
- onProgress: progress.onProgress,
494
- });
495
- await progress.stop();
548
+ finally {
549
+ supervision.stop();
496
550
  }
497
551
  printIndexResult(clack, result, projectPath);
498
552
  await recordIndexTelemetry(cg, result);
@@ -587,47 +641,56 @@ function main() {
587
641
  process.exit(1);
588
642
  }
589
643
  const { default: HomeGraph } = await loadHomeGraph();
590
- const cg = await HomeGraph.open(projectPath);
591
- if (options.quiet) {
592
- // Quiet mode: no UI, just run. `index` is a full re-index, so clear the
593
- // existing graph and rebuild from scratch (see the note below — #874).
594
- cg.clear();
595
- const result = await cg.indexAll();
596
- if (!result.success)
644
+ // `index` is a FULL re-index — identical to a fresh `init`. RECREATE the
645
+ // database from scratch (discard .homegraph/homegraph.db + its WAL) rather
646
+ // than opening the old graph and DELETE-ing every row. The clear-then-index
647
+ // approach reported "0 nodes" without the clear (#874); the recreate keeps
648
+ // that fixed AND avoids the failure mode where, on a large or pre-fix
649
+ // poisoned index, the per-row FTS delete churn wedged the main thread long
650
+ // enough to trip the liveness watchdog before scanning even began (#1067).
651
+ // recreate() hands back a fresh, empty instance — no clear() needed. For
652
+ // fast incremental updates use `sync`.
653
+ const cg = await HomeGraph.recreate(projectPath);
654
+ // Supervise the indexer: self-terminate if orphaned (parent shim killed)
655
+ // or if the main thread wedges — neither was guarded on this path (#999).
656
+ const supervision = (0, command_supervision_1.installCommandSupervision)('index');
657
+ try {
658
+ if (options.quiet) {
659
+ // Quiet mode: no UI, just run against the freshly-recreated graph.
660
+ const result = await cg.indexAll();
661
+ if (!result.success)
662
+ process.exit(1);
663
+ cg.destroy();
664
+ return;
665
+ }
666
+ const clack = await importESM('@clack/prompts');
667
+ clack.intro('Indexing project');
668
+ let result;
669
+ if (options.verbose) {
670
+ result = await cg.indexAll({
671
+ onProgress: createVerboseProgress(),
672
+ verbose: true,
673
+ });
674
+ }
675
+ else {
676
+ process.stdout.write(`${colors.dim}${(0, glyphs_1.getGlyphs)().rail}${colors.reset}\n`);
677
+ const progress = (0, shimmer_progress_1.createShimmerProgress)();
678
+ result = await cg.indexAll({
679
+ onProgress: progress.onProgress,
680
+ });
681
+ await progress.stop();
682
+ }
683
+ printIndexResult(clack, result, projectPath);
684
+ await recordIndexTelemetry(cg, result);
685
+ if (!result.success) {
597
686
  process.exit(1);
687
+ }
688
+ clack.outro('Done');
598
689
  cg.destroy();
599
- return;
600
- }
601
- const clack = await importESM('@clack/prompts');
602
- clack.intro('Indexing project');
603
- // `index` is a FULL re-index: clear the existing graph and rebuild it from
604
- // scratch so the result is identical to a fresh `init`. Without the clear,
605
- // indexAll() skips every unchanged file by its content hash and reports
606
- // "0 nodes, 0 edges" against the already-populated graph — which reads as
607
- // "index wiped my index" (#874). For fast incremental updates use `sync`.
608
- cg.clear();
609
- let result;
610
- if (options.verbose) {
611
- result = await cg.indexAll({
612
- onProgress: createVerboseProgress(),
613
- verbose: true,
614
- });
615
- }
616
- else {
617
- process.stdout.write(`${colors.dim}${(0, glyphs_1.getGlyphs)().rail}${colors.reset}\n`);
618
- const progress = (0, shimmer_progress_1.createShimmerProgress)();
619
- result = await cg.indexAll({
620
- onProgress: progress.onProgress,
621
- });
622
- await progress.stop();
623
690
  }
624
- printIndexResult(clack, result, projectPath);
625
- await recordIndexTelemetry(cg, result);
626
- if (!result.success) {
627
- process.exit(1);
691
+ finally {
692
+ supervision.stop();
628
693
  }
629
- clack.outro('Done');
630
- cg.destroy();
631
694
  }
632
695
  catch (err) {
633
696
  error(`Failed to index: ${err instanceof Error ? err.message : String(err)}`);
@@ -730,6 +793,7 @@ function main() {
730
793
  const journalMode = cg.getJournalMode();
731
794
  const buildInfo = cg.getIndexBuildInfo();
732
795
  const reindexRecommended = cg.isIndexStale();
796
+ const ohosApi = cg.getOhosApiBinding();
733
797
  // JSON output mode
734
798
  if (options.json) {
735
799
  const lastIndexedMs = cg.getLastIndexedAt();
@@ -755,6 +819,9 @@ function main() {
755
819
  worktreeMismatch: worktreeMismatch
756
820
  ? { worktreeRoot: worktreeMismatch.worktreeRoot, indexRoot: worktreeMismatch.indexRoot }
757
821
  : null,
822
+ ohosApi: ohosApi
823
+ ? { version: ohosApi.version, packageName: ohosApi.packageName, dbPath: ohosApi.dbPath }
824
+ : null,
758
825
  index: {
759
826
  builtWithVersion: buildInfo.version,
760
827
  builtWithExtractionVersion: buildInfo.extractionVersion,
@@ -809,6 +876,13 @@ function main() {
809
876
  console.log(` ${lang.padEnd(15)} ${formatNumber(count)}`);
810
877
  }
811
878
  console.log();
879
+ if (ohosApi) {
880
+ console.log(chalk.bold('OHOS API Database:'));
881
+ console.log(` Version: ${ohosApi.version}`);
882
+ console.log(` Package: ${ohosApi.packageName}`);
883
+ console.log(` DB: ${ohosApi.dbPath}`);
884
+ console.log();
885
+ }
812
886
  // Pending changes
813
887
  const totalChanges = changes.added.length + changes.modified.length + changes.removed.length;
814
888
  if (totalChanges > 0) {
@@ -885,13 +959,16 @@ function main() {
885
959
  }
886
960
  else {
887
961
  console.log(chalk.bold(`\nSearch Results for "${search}":\n`));
962
+ // Results arrive already ranked by relevance, so the order conveys
963
+ // it. We don't print the raw score: it's an unbounded BM25/FTS value
964
+ // (relative-ranking only), and the old `(score * 100)%` rendered it
965
+ // as nonsensical percentages like "12042%" (#1045). The MCP search
966
+ // tool likewise shows no score. Raw `score` stays in --json output.
888
967
  for (const result of results) {
889
968
  const node = result.node;
890
969
  const location = `${node.filePath}:${node.startLine}`;
891
- const score = chalk.dim(`(${(result.score * 100).toFixed(0)}%)`);
892
970
  console.log(chalk.cyan(node.kind.padEnd(12)) +
893
- chalk.white(node.name) +
894
- ' ' + score);
971
+ chalk.white(node.name));
895
972
  console.log(chalk.dim(` ${location}`));
896
973
  if (node.signature) {
897
974
  console.log(chalk.dim(` ${node.signature}`));
@@ -986,11 +1063,15 @@ function main() {
986
1063
  return;
987
1064
  }
988
1065
  const prompt = String(input.prompt || '');
989
- // Gate: only structural / flow / impact / where-how prompts get context.
990
- // A cheap regex keeps every other prompt ("fix this typo") a zero-cost
991
- // no-op so we never add latency where there's no structural answer to give.
992
- const STRUCTURAL = /\b(how|where|trace|flow|path|reach(?:es|ed)?|call(?:s|ed|er|ers|ee)?|depend|impact|affect|wired?|connect|implement|architect|structure|breaks?|what calls|why does)\b/i;
993
- if (!prompt || !STRUCTURAL.test(prompt))
1066
+ // Gate: only structural / flow / impact / where-how prompts get context, so
1067
+ // every other prompt ("fix this typo") stays a zero-cost no-op. Language-aware
1068
+ // (English + CJK keywords, plus code-shaped tokens) so it fires for non-English
1069
+ // prompts too (issue #994). A keyword fires on its own; a code-token is only a
1070
+ // CANDIDATE verified against the graph below, so a tech brand ("JavaScript")
1071
+ // that looks like a symbol but isn't one here doesn't inject spurious context.
1072
+ const keyworded = (0, directory_1.hasStructuralKeyword)(prompt);
1073
+ const codeTokens = keyworded ? [] : (0, directory_1.extractCodeTokens)(prompt);
1074
+ if (!keyworded && codeTokens.length === 0)
994
1075
  return;
995
1076
  // Decide what to inject, shaped by WHERE the index(es) are: the nearest
996
1077
  // indexed ancestor of cwd, or — when cwd is an un-indexed workspace root
@@ -1010,6 +1091,13 @@ function main() {
1010
1091
  const { default: HomeGraph } = await loadHomeGraph();
1011
1092
  const cg = await HomeGraph.open(plan.exploreRoot);
1012
1093
  try {
1094
+ // Code-token-only prompt: require that at least one token is a REAL symbol
1095
+ // in THIS index before front-loading. Without it, a brand name or common
1096
+ // word that merely looks like code ("JavaScript", "GitHub") would run
1097
+ // explore and inject ~16KB of low-relevance context (issue #994 follow-up).
1098
+ // A keyword-bearing prompt skips this — the keyword is signal enough.
1099
+ if (!keyworded && !codeTokens.some((t) => cg.getNodesByName(t).length > 0))
1100
+ return;
1013
1101
  const { ToolHandler } = await Promise.resolve().then(() => __importStar(require('../mcp/tools')));
1014
1102
  const handler = new ToolHandler(cg);
1015
1103
  const result = await handler.execute('homegraph_explore', { query: prompt });
@@ -1045,14 +1133,17 @@ function main() {
1045
1133
  }
1046
1134
  });
1047
1135
  /**
1048
- * homegraph node <name>
1136
+ * homegraph node [name]
1049
1137
  *
1050
1138
  * The CLI face of the MCP homegraph_node tool: one symbol's source +
1051
1139
  * caller/callee trail, or a whole file with line numbers + dependents
1052
1140
  * (Read-parity). Same subagent/non-MCP rationale as `explore`.
1141
+ *
1142
+ * `name` is OPTIONAL because `--file` (file-read mode) carries no symbol —
1143
+ * a required `<name>` made `codegraph node -f <file>` unreachable (#1044).
1053
1144
  */
1054
1145
  program
1055
- .command('node <name>')
1146
+ .command('node [name]')
1056
1147
  .description('One symbol\'s source + caller/callee trail, or read a file with line numbers + dependents (same output as the homegraph_node MCP tool)')
1057
1148
  .option('-p, --path <path>', 'Project path')
1058
1149
  .option('-f, --file <file>', 'Treat as file mode (or disambiguate a symbol to this file)')
@@ -1060,6 +1151,13 @@ function main() {
1060
1151
  .option('--limit <number>', 'File mode: maximum lines')
1061
1152
  .option('--symbols-only', 'File mode: just the symbol map + dependents')
1062
1153
  .action(async (name, options) => {
1154
+ // Need a symbol (positional) OR a file (--file / a path-like positional).
1155
+ // With [name] optional, a bare `codegraph node` reaches here with neither
1156
+ // and must be told what to pass, rather than crashing downstream.
1157
+ if (!name && !options.file) {
1158
+ error("Pass a symbol name (e.g. 'codegraph node parseToken') or a file (e.g. 'codegraph node -f src/auth.ts', or 'codegraph node src/auth.ts').");
1159
+ process.exit(1);
1160
+ }
1063
1161
  const projectPath = resolveProjectPath(options.path);
1064
1162
  try {
1065
1163
  if (!(0, directory_1.isInitialized)(projectPath)) {
@@ -1081,10 +1179,10 @@ function main() {
1081
1179
  if (name && name !== options.file)
1082
1180
  args.symbol = name;
1083
1181
  }
1084
- else if (name.includes('/') || name.includes('\\')) {
1182
+ else if (name && (name.includes('/') || name.includes('\\'))) {
1085
1183
  args.file = name.replace(/\\/g, '/');
1086
1184
  }
1087
- else {
1185
+ else if (name) {
1088
1186
  args.symbol = name;
1089
1187
  args.includeCode = true;
1090
1188
  }
@@ -1393,15 +1491,15 @@ function main() {
1393
1491
  console.error(chalk.bold('\nHomeGraph MCP Server\n'));
1394
1492
  console.error(chalk.blue((0, glyphs_1.getGlyphs)().info) + ' Use --mcp flag to start the MCP server');
1395
1493
  console.error('\nTo use with Claude Code, add to your MCP configuration:');
1396
- console.error(chalk.dim(`
1397
- {
1398
- "mcpServers": {
1399
- "homegraph": {
1400
- "command": "homegraph",
1401
- "args": ["serve", "--mcp"]
1402
- }
1403
- }
1404
- }
1494
+ console.error(chalk.dim(`
1495
+ {
1496
+ "mcpServers": {
1497
+ "homegraph": {
1498
+ "command": "homegraph",
1499
+ "args": ["serve", "--mcp"]
1500
+ }
1501
+ }
1502
+ }
1405
1503
  `));
1406
1504
  console.error('Available tools:');
1407
1505
  console.error(chalk.cyan(' homegraph_explore') + ' - Primary: source of the relevant symbols for any question');
@@ -1903,6 +2001,751 @@ function main() {
1903
2001
  process.exit(1);
1904
2002
  }
1905
2003
  });
2004
+ // =============================================================================
2005
+ // Spec commands (Commit4Spec knowledge graph)
2006
+ // =============================================================================
2007
+ const specCommand = program
2008
+ .command('spec')
2009
+ .description('Manage spec knowledge graph (reverse-mining, search, self-evolve)');
2010
+ /**
2011
+ * homegraph spec mine
2012
+ */
2013
+ specCommand
2014
+ .command('mine')
2015
+ .description('Reverse-mine spec knowledge from Git history')
2016
+ .option('-p, --path <path>', 'Path to the repository')
2017
+ .option('--spec-storage-path <path>', 'Path to the .spec directory')
2018
+ .option('--db-path <path>', 'Path to the SQLite database file')
2019
+ .option('-v, --verbose', 'Show detailed output')
2020
+ .option('-j, --json', 'Output as JSON')
2021
+ .action(async (options) => {
2022
+ try {
2023
+ const repoPath = path.resolve(options.path || process.cwd());
2024
+ const specStoragePath = options.specStoragePath ?? path.join(repoPath, '.spec');
2025
+ const { createDatabase } = await Promise.resolve().then(() => __importStar(require('../db/sqlite-adapter')));
2026
+ const { runMiningPipeline } = await Promise.resolve().then(() => __importStar(require('../spec/mining/pipeline')));
2027
+ const { isGitRepo } = await Promise.resolve().then(() => __importStar(require('../spec/mining/git-scanner')));
2028
+ const { resolveDbPath } = await Promise.resolve().then(() => __importStar(require('../spec/utils')));
2029
+ const dbPath = resolveDbPath(repoPath, options.dbPath);
2030
+ if (!isGitRepo(repoPath)) {
2031
+ error(`Not a git repository: ${repoPath}`);
2032
+ process.exit(1);
2033
+ }
2034
+ if (options.verbose) {
2035
+ info(`Repository: ${repoPath}`);
2036
+ info(`Spec storage: ${specStoragePath}`);
2037
+ info(`Database: ${dbPath}`);
2038
+ }
2039
+ const { db } = createDatabase(dbPath);
2040
+ const result = runMiningPipeline(repoPath, specStoragePath, db);
2041
+ if (options.json) {
2042
+ console.log(JSON.stringify(result, null, 2));
2043
+ }
2044
+ else {
2045
+ info(`Specs found: ${result.specsFound}`);
2046
+ info(`Commits found: ${result.commitsFound}`);
2047
+ info(`Fragments: ${result.fragmentsFound}`);
2048
+ info(`Relations: ${result.relationsCreated}`);
2049
+ info(`Total entries: ${result.totalEntries}`);
2050
+ info(`Skipped: ${result.skippedEntries.length}`);
2051
+ if (result.skippedEntries.length > 0) {
2052
+ for (const entry of result.skippedEntries) {
2053
+ warn(`${entry.specId}: ${entry.reason}`);
2054
+ }
2055
+ }
2056
+ }
2057
+ }
2058
+ catch (err) {
2059
+ error(`Mining failed: ${err instanceof Error ? err.message : String(err)}`);
2060
+ process.exit(1);
2061
+ }
2062
+ });
2063
+ /**
2064
+ * homegraph spec match <text>
2065
+ */
2066
+ specCommand
2067
+ .command('match <text>')
2068
+ .description('Search the spec knowledge graph for a term or phrase')
2069
+ .option('-p, --path <path>', 'Path to the repository')
2070
+ .option('--db-path <path>', 'Path to the SQLite database file')
2071
+ .option('--top-k <number>', 'Number of results to return', '5')
2072
+ .option('--no-fragments', 'Exclude code fragments from results')
2073
+ .option('-j, --json', 'Output as JSON')
2074
+ .action(async (text, options) => {
2075
+ let db;
2076
+ try {
2077
+ const repoPath = resolveSpecProjectPath(options.path);
2078
+ const { createDatabase } = await Promise.resolve().then(() => __importStar(require('../db/sqlite-adapter')));
2079
+ const { resolveDbPath, computeBudgetProfile } = await Promise.resolve().then(() => __importStar(require('../spec/utils')));
2080
+ const { searchAndGetContext } = await Promise.resolve().then(() => __importStar(require('../spec/graph/queries')));
2081
+ const dbPath = resolveDbPath(repoPath, options.dbPath);
2082
+ if (!fs.existsSync(dbPath)) {
2083
+ error(`Database not found at ${dbPath}. Run 'homegraph spec mine' first.`);
2084
+ process.exit(1);
2085
+ }
2086
+ const created = createDatabase(dbPath);
2087
+ db = created.db;
2088
+ const topK = Math.max(1, Math.min(parseInt(options.topK || '5', 10) || 5, 50));
2089
+ const includeFragments = options.fragments !== false;
2090
+ const contexts = searchAndGetContext(db, text, topK, includeFragments);
2091
+ if (contexts.length === 0) {
2092
+ info('No specs matched your query.');
2093
+ return;
2094
+ }
2095
+ const profile = computeBudgetProfile(contexts.length);
2096
+ const results = contexts.map((ctx) => ({
2097
+ spec_id: ctx.spec.id,
2098
+ title: ctx.spec.title,
2099
+ subtitles: ctx.spec.subtitles,
2100
+ file_path: ctx.spec.filePath,
2101
+ commits: ctx.commits.map((c) => ({
2102
+ hash: c.commit.hash,
2103
+ message: c.commit.message,
2104
+ relation_type: c.relationType,
2105
+ fragments: c.fragments.map((f) => ({
2106
+ file_path: f.filePath,
2107
+ change_type: f.changeType,
2108
+ start_line: f.startLine,
2109
+ end_line: f.endLine,
2110
+ code_diff: f.codeDiff,
2111
+ })),
2112
+ })),
2113
+ }));
2114
+ if (options.json) {
2115
+ console.log(JSON.stringify({
2116
+ query: text.slice(0, 200),
2117
+ matched_count: contexts.length,
2118
+ results,
2119
+ }, null, 2));
2120
+ }
2121
+ else {
2122
+ console.log(chalk.bold(`\n${contexts.length} spec${contexts.length !== 1 ? 's' : ''} matched:\n`));
2123
+ for (const r of results) {
2124
+ console.log(chalk.bold(r.title));
2125
+ for (const commit of r.commits) {
2126
+ console.log(` ${chalk.yellow(commit.hash.slice(0, 7))} ${commit.message}`);
2127
+ if (includeFragments && commit.fragments && commit.fragments.length > 0) {
2128
+ for (const fragment of commit.fragments.slice(0, profile.maxFragments || 3)) {
2129
+ console.log(chalk.dim(` ${fragment.file_path}:${fragment.start_line}-${fragment.end_line} [${fragment.change_type}]`));
2130
+ }
2131
+ }
2132
+ }
2133
+ console.log();
2134
+ }
2135
+ }
2136
+ }
2137
+ catch (err) {
2138
+ error(`Match failed: ${err instanceof Error ? err.message : String(err)}`);
2139
+ process.exit(1);
2140
+ }
2141
+ finally {
2142
+ try {
2143
+ db?.close();
2144
+ }
2145
+ catch { /* best effort */ }
2146
+ }
2147
+ });
2148
+ /**
2149
+ * homegraph spec find <filePath>
2150
+ */
2151
+ specCommand
2152
+ .command('find <filePath>')
2153
+ .description('Find specs related to the given file path via code-fragment matching')
2154
+ .option('-p, --path <path>', 'Path to the repository')
2155
+ .option('--db-path <path>', 'Path to the SQLite database file')
2156
+ .option('-j, --json', 'Output as JSON')
2157
+ .action(async (filePath, options) => {
2158
+ let db;
2159
+ try {
2160
+ const repoPath = resolveSpecProjectPath(options.path);
2161
+ const { createDatabase } = await Promise.resolve().then(() => __importStar(require('../db/sqlite-adapter')));
2162
+ const { resolveDbPath } = await Promise.resolve().then(() => __importStar(require('../spec/utils')));
2163
+ const { findSpecsByFilePath } = await Promise.resolve().then(() => __importStar(require('../spec/graph/queries')));
2164
+ const dbPath = resolveDbPath(repoPath, options.dbPath);
2165
+ if (!fs.existsSync(dbPath)) {
2166
+ error(`Database not found at ${dbPath}. Run 'homegraph spec mine' first.`);
2167
+ process.exit(1);
2168
+ }
2169
+ const created = createDatabase(dbPath);
2170
+ db = created.db;
2171
+ const result = findSpecsByFilePath(db, filePath);
2172
+ if (options.json) {
2173
+ console.log(JSON.stringify({
2174
+ filePath,
2175
+ matched_count: result.matched_count,
2176
+ truncated: result.truncated,
2177
+ results: result.results,
2178
+ }, null, 2));
2179
+ }
2180
+ else {
2181
+ console.log(chalk.bold(`\n${result.matched_count} spec${result.matched_count !== 1 ? 's' : ''} matched for ${filePath}:\n`));
2182
+ for (const r of result.results) {
2183
+ console.log(` ${chalk.cyan(r.id.padEnd(16))} ${r.title.padEnd(32)} ${chalk.green(r.status.padEnd(12))} v${r.version} ${chalk.dim(r.filePath)}`);
2184
+ }
2185
+ console.log();
2186
+ if (result.truncated) {
2187
+ console.log(chalk.yellow(` ... and more (showing first ${result.matched_count} of >${result.matched_count} results)`));
2188
+ console.log();
2189
+ }
2190
+ if (result.matched_count === 0) {
2191
+ info(`No specs found for file path "${filePath}". Try a partial path (e.g. "src/auth" instead of "src/auth/login.ts").`);
2192
+ }
2193
+ }
2194
+ }
2195
+ catch (err) {
2196
+ error(`Find failed: ${err instanceof Error ? err.message : String(err)}`);
2197
+ process.exit(1);
2198
+ }
2199
+ finally {
2200
+ try {
2201
+ db?.close();
2202
+ }
2203
+ catch { /* best effort */ }
2204
+ }
2205
+ });
2206
+ /**
2207
+ * homegraph spec trace <symbol>
2208
+ */
2209
+ specCommand
2210
+ .command('trace <symbol>')
2211
+ .description('Trace a code symbol back to its associated design Specs')
2212
+ .option('-f, --file <path>', 'File path for symbol disambiguation')
2213
+ .option('-l, --line <number>', 'Line number for symbol disambiguation')
2214
+ .option('-p, --path <path>', 'Path to the repository')
2215
+ .option('--db-path <path>', 'Path to the SQLite database file')
2216
+ .option('--top-k <number>', 'Number of results to return', '10')
2217
+ .option('-j, --json', 'Output as JSON')
2218
+ .action(async (symbol, options) => {
2219
+ let db;
2220
+ let cg;
2221
+ try {
2222
+ const repoPath = resolveSpecProjectPath(options.path);
2223
+ // isNaN check so --line 0 is handled correctly (parseInt('0') → 0 is falsy)
2224
+ const lineRaw = options.line !== undefined ? parseInt(options.line, 10) : NaN;
2225
+ const line = isNaN(lineRaw) ? undefined : lineRaw;
2226
+ // Open HomeGraph code index
2227
+ const { default: HomeGraph } = await Promise.resolve().then(() => __importStar(require('../index')));
2228
+ cg = await HomeGraph.open(repoPath);
2229
+ // Inline symbol resolution (mirrors findSymbolMatches in tools.ts)
2230
+ const isQualified = /[.\/]|::/.test(symbol);
2231
+ const { isGeneratedFile } = await Promise.resolve().then(() => __importStar(require('../extraction/generated-detection')));
2232
+ let nodes = [];
2233
+ if (!isQualified) {
2234
+ const exact = cg.getNodesByName(symbol);
2235
+ if (exact.length > 0) {
2236
+ nodes = [...exact].sort((a, b) => (isGeneratedFile(a.filePath) ? 1 : 0) - (isGeneratedFile(b.filePath) ? 1 : 0));
2237
+ }
2238
+ else {
2239
+ const fuzzy = cg.searchNodes(symbol, { limit: 10 });
2240
+ if (fuzzy[0])
2241
+ nodes = [fuzzy[0].node];
2242
+ }
2243
+ }
2244
+ else {
2245
+ let results = cg.searchNodes(symbol, { limit: 50 });
2246
+ if (results.length === 0) {
2247
+ const parts = symbol.split(/::|[./]/).filter((p) => p.length > 0);
2248
+ const tail = parts[parts.length - 1];
2249
+ if (tail && tail !== symbol)
2250
+ results = cg.searchNodes(tail, { limit: 50 });
2251
+ }
2252
+ // Rough matchesSymbol equivalent for CLI
2253
+ const exactMatches = results.filter((r) => {
2254
+ const n = r.node;
2255
+ if (n.name === symbol)
2256
+ return true;
2257
+ const fileBase = n.filePath.split('/').pop()?.replace(/\.[^.]+$/, '');
2258
+ if (fileBase === symbol)
2259
+ return true;
2260
+ const parts = symbol.split(/::|[./]/);
2261
+ if (parts[parts.length - 1] === n.name) {
2262
+ const firstPart = parts[0];
2263
+ if (n.qualifiedName.includes(firstPart))
2264
+ return true;
2265
+ if (n.filePath.includes(firstPart))
2266
+ return true;
2267
+ return parts.length === 1;
2268
+ }
2269
+ return false;
2270
+ });
2271
+ if (exactMatches.length > 0) {
2272
+ nodes = exactMatches
2273
+ .sort((a, b) => (isGeneratedFile(a.node.filePath) ? 1 : 0) - (isGeneratedFile(b.node.filePath) ? 1 : 0))
2274
+ .map((r) => r.node);
2275
+ }
2276
+ }
2277
+ if (nodes.length === 0) {
2278
+ console.log(chalk.yellow(`No code entities found for symbol "${symbol}".`));
2279
+ return;
2280
+ }
2281
+ // Disambiguate
2282
+ if (options.file) {
2283
+ // Use endsWith for precise file name matching (not substring matching)
2284
+ const filePattern = options.file;
2285
+ nodes = nodes.filter((n) => n.filePath.endsWith(filePattern));
2286
+ }
2287
+ if (line !== undefined && nodes.length > 1) {
2288
+ const closest = nodes.reduce((best, n) => {
2289
+ const bestDist = Math.abs(best.startLine - line) + Math.abs(best.endLine - line);
2290
+ const curDist = Math.abs(n.startLine - line) + Math.abs(n.endLine - line);
2291
+ return curDist < bestDist ? n : best;
2292
+ });
2293
+ nodes = [closest];
2294
+ }
2295
+ const node = nodes[0];
2296
+ if (!node) {
2297
+ console.log(chalk.yellow(`Could not resolve symbol "${symbol}" to a specific code entity.`));
2298
+ return;
2299
+ }
2300
+ // Open Spec database
2301
+ const { createDatabase } = await Promise.resolve().then(() => __importStar(require('../db/sqlite-adapter')));
2302
+ const { resolveDbPath } = await Promise.resolve().then(() => __importStar(require('../spec/utils')));
2303
+ const { findSpecsByCodeSymbol } = await Promise.resolve().then(() => __importStar(require('../spec/graph/queries')));
2304
+ const { initSpecSchema, runSpecMigrations, getCurrentSpecVersion, CURRENT_SPEC_SCHEMA_VERSION } = await Promise.resolve().then(() => __importStar(require('../spec/db/schema')));
2305
+ const dbPath = resolveDbPath(repoPath, options.dbPath);
2306
+ if (!fs.existsSync(dbPath)) {
2307
+ console.log(chalk.yellow(`Database not found at ${dbPath}. Run 'homegraph spec mine' first.`));
2308
+ return;
2309
+ }
2310
+ const created = createDatabase(dbPath);
2311
+ db = created.db;
2312
+ initSpecSchema(db);
2313
+ const currentVersion = getCurrentSpecVersion(db);
2314
+ if (currentVersion < CURRENT_SPEC_SCHEMA_VERSION) {
2315
+ runSpecMigrations(db, currentVersion);
2316
+ }
2317
+ const topKRaw = parseInt(options.topK || '10', 10);
2318
+ const topK = Math.max(1, Math.min(isNaN(topKRaw) ? 10 : topKRaw, 50));
2319
+ console.log(chalk.dim(`Resolved: ${node.name} (${node.kind}) in ${node.filePath}:${node.startLine}-${node.endLine}`));
2320
+ const result = findSpecsByCodeSymbol(db, {
2321
+ name: node.name,
2322
+ qualifiedName: node.qualifiedName,
2323
+ kind: node.kind,
2324
+ filePath: node.filePath,
2325
+ startLine: node.startLine,
2326
+ endLine: node.endLine,
2327
+ }, topK);
2328
+ if (options.json) {
2329
+ console.log(JSON.stringify({
2330
+ symbol,
2331
+ entity: {
2332
+ name: node.name,
2333
+ qualifiedName: node.qualifiedName,
2334
+ kind: node.kind,
2335
+ filePath: node.filePath,
2336
+ startLine: node.startLine,
2337
+ endLine: node.endLine,
2338
+ },
2339
+ matched_count: result.matches.length,
2340
+ total_candidates: result.totalCandidates,
2341
+ matches: result.matches.map((m) => ({
2342
+ spec_id: m.spec.id,
2343
+ title: m.spec.title,
2344
+ status: m.spec.status,
2345
+ version: m.spec.version,
2346
+ file_path: m.spec.filePath,
2347
+ score: Math.round(m.score * 1000) / 1000,
2348
+ score_detail: {
2349
+ file_path: Math.round(m.scoreDetail.filePathScore * 1000) / 1000,
2350
+ content: Math.round(m.scoreDetail.contentScore * 1000) / 1000,
2351
+ name: Math.round(m.scoreDetail.nameScore * 1000) / 1000,
2352
+ recency: Math.round(m.scoreDetail.recencyScore * 1000) / 1000,
2353
+ line_overlap: Math.round(m.scoreDetail.overlapScore * 1000) / 1000,
2354
+ },
2355
+ fragment_count: m.fragmentCount,
2356
+ commit_count: m.commitCount,
2357
+ })),
2358
+ }, null, 2));
2359
+ }
2360
+ else {
2361
+ console.log(chalk.bold(`\n${result.matches.length} spec${result.matches.length !== 1 ? 's' : ''} matched (${result.totalCandidates} candidates):\n`));
2362
+ for (const m of result.matches) {
2363
+ const scoreColor = m.score >= 0.7 ? chalk.green : m.score >= 0.4 ? chalk.yellow : chalk.red;
2364
+ console.log(` ${chalk.cyan(m.spec.id.padEnd(14))} ${m.spec.title.padEnd(36)} ${scoreColor((m.score * 100).toFixed(1) + '%')}`);
2365
+ console.log(chalk.dim(` fp=${m.scoreDetail.filePathScore.toFixed(2)} content=${m.scoreDetail.contentScore.toFixed(2)} name=${m.scoreDetail.nameScore.toFixed(2)} recency=${m.scoreDetail.recencyScore.toFixed(2)} overlap=${m.scoreDetail.overlapScore.toFixed(2)}`));
2366
+ console.log();
2367
+ }
2368
+ if (result.matches.length === 0) {
2369
+ info(`No Specs found for symbol "${symbol}". Try a more specific file path or check that specs have been mined for this project.`);
2370
+ }
2371
+ }
2372
+ }
2373
+ catch (err) {
2374
+ error(`Trace failed: ${err instanceof Error ? err.message : String(err)}`);
2375
+ process.exit(1);
2376
+ }
2377
+ finally {
2378
+ try {
2379
+ await cg?.close();
2380
+ }
2381
+ catch { /* best effort */ }
2382
+ try {
2383
+ db?.close();
2384
+ }
2385
+ catch { /* best effort */ }
2386
+ }
2387
+ });
2388
+ /**
2389
+ * homegraph spec stats
2390
+ */
2391
+ specCommand
2392
+ .command('stats')
2393
+ .description('Show statistics about the spec knowledge graph')
2394
+ .option('-p, --path <path>', 'Path to the repository')
2395
+ .option('--db-path <path>', 'Path to the SQLite database file')
2396
+ .option('-j, --json', 'Output as JSON')
2397
+ .action(async (options) => {
2398
+ let db;
2399
+ try {
2400
+ const repoPath = resolveSpecProjectPath(options.path);
2401
+ const { createDatabase } = await Promise.resolve().then(() => __importStar(require('../db/sqlite-adapter')));
2402
+ const { resolveDbPath } = await Promise.resolve().then(() => __importStar(require('../spec/utils')));
2403
+ const { getSpecStats } = await Promise.resolve().then(() => __importStar(require('../spec/graph/queries')));
2404
+ const dbPath = resolveDbPath(repoPath, options.dbPath);
2405
+ if (!fs.existsSync(dbPath)) {
2406
+ error(`Database not found at ${dbPath}. Run 'homegraph spec mine' first.`);
2407
+ process.exit(1);
2408
+ }
2409
+ const created = createDatabase(dbPath);
2410
+ db = created.db;
2411
+ const stats = getSpecStats(db);
2412
+ if (options.json) {
2413
+ console.log(JSON.stringify(stats, null, 2));
2414
+ }
2415
+ else {
2416
+ console.log(chalk.bold('\nSpec Knowledge Graph Stats\n'));
2417
+ console.log(` Specs: ${chalk.cyan(String(stats.specCount))}`);
2418
+ console.log(` Active: ${chalk.green(String(stats.activeSpecCount))}`);
2419
+ console.log(` Deprecated: ${chalk.yellow(String(stats.deprecatedSpecCount))}`);
2420
+ console.log(` Commits: ${stats.commitCount}`);
2421
+ console.log(` Fragments: ${stats.fragmentCount}`);
2422
+ console.log(` Relations: ${stats.relationCount}`);
2423
+ console.log();
2424
+ if (stats.specCount === 0) {
2425
+ info("No specs yet. Run 'homegraph spec mine' to build the knowledge graph.");
2426
+ }
2427
+ }
2428
+ }
2429
+ catch (err) {
2430
+ error(`Stats failed: ${err instanceof Error ? err.message : String(err)}`);
2431
+ process.exit(1);
2432
+ }
2433
+ finally {
2434
+ try {
2435
+ db?.close();
2436
+ }
2437
+ catch { /* best effort */ }
2438
+ }
2439
+ });
2440
+ /**
2441
+ * homegraph spec evolve
2442
+ */
2443
+ const evolveCommand = specCommand
2444
+ .command('evolve')
2445
+ .description('Self-evolve specs after code changes');
2446
+ /**
2447
+ * homegraph spec evolve install
2448
+ */
2449
+ evolveCommand
2450
+ .command('install')
2451
+ .description('Install a git post-commit hook that triggers spec evolution')
2452
+ .option('-p, --path <path>', 'Path to the repository')
2453
+ .option('-f, --force', 'Overwrite existing hook without prompting')
2454
+ .action(async (options) => {
2455
+ try {
2456
+ const repoPath = resolveSpecProjectPath(options.path);
2457
+ const { isGitRepo } = await Promise.resolve().then(() => __importStar(require('../spec/mining/git-scanner')));
2458
+ if (!isGitRepo(repoPath)) {
2459
+ error(`Not a git repository: ${repoPath}`);
2460
+ process.exit(1);
2461
+ }
2462
+ const { execFileSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
2463
+ // Resolve the absolute path to homegraph so the hook works
2464
+ // regardless of PATH (matching Python's shutil.which("c4s")).
2465
+ let homegraphBin;
2466
+ try {
2467
+ homegraphBin = execFileSync('command', ['-v', 'homegraph'], {
2468
+ encoding: 'utf8',
2469
+ stdio: ['ignore', 'pipe', 'ignore'],
2470
+ }).trim();
2471
+ }
2472
+ catch {
2473
+ homegraphBin = '';
2474
+ }
2475
+ if (!homegraphBin) {
2476
+ error('homegraph command not found in PATH.\n' +
2477
+ ' Make sure homegraph is installed globally: npm install -g homegraph');
2478
+ process.exit(1);
2479
+ }
2480
+ const hooksDir = execFileSync('git', ['rev-parse', '--git-path', 'hooks'], {
2481
+ cwd: repoPath,
2482
+ encoding: 'utf8',
2483
+ stdio: ['ignore', 'pipe', 'ignore'],
2484
+ windowsHide: true,
2485
+ }).trim();
2486
+ const resolvedHooksDir = path.isAbsolute(hooksDir)
2487
+ ? hooksDir
2488
+ : path.resolve(repoPath, hooksDir);
2489
+ fs.mkdirSync(resolvedHooksDir, { recursive: true });
2490
+ const hookPath = path.join(resolvedHooksDir, 'post-commit');
2491
+ if (fs.existsSync(hookPath) && !options.force) {
2492
+ const readline = await Promise.resolve().then(() => __importStar(require('readline')));
2493
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
2494
+ const answer = await new Promise((resolve) => {
2495
+ rl.question(chalk.yellow('A post-commit hook already exists. Overwrite? (y/N) '), resolve);
2496
+ });
2497
+ rl.close();
2498
+ if (answer.toLowerCase() !== 'y') {
2499
+ info('Cancelled');
2500
+ return;
2501
+ }
2502
+ }
2503
+ // Ensure logs directory
2504
+ fs.mkdirSync(path.join(repoPath, utils_1.SPEC_DATA_DIR, 'logs'), { recursive: true });
2505
+ // Marker constants matching the git-hooks pattern
2506
+ const MARKER_BEGIN = '# >>> homegraph spec evolve hook >>>';
2507
+ const MARKER_END = '# <<< homegraph spec evolve hook <<<';
2508
+ const hookBlock = [
2509
+ MARKER_BEGIN,
2510
+ '# Triggers spec self-evolution after each commit. Runs in background.',
2511
+ '# Installed by: homegraph spec evolve install',
2512
+ `# Logs: ${utils_1.SPEC_DATA_DIR}/logs/evolve-hook.log`,
2513
+ '# Runtime guard: skip if homegraph is not available',
2514
+ `HOMEGRAPH_BIN="${homegraphBin}"`,
2515
+ 'if [ -x "$HOMEGRAPH_BIN" ] || command -v homegraph >/dev/null 2>&1; then',
2516
+ ' "${HOMEGRAPH_BIN:-homegraph}" spec evolve process --path "$(pwd)" --json \\',
2517
+ ` >> ${utils_1.SPEC_DATA_DIR}/logs/evolve-hook.log 2>&1 &`,
2518
+ 'fi',
2519
+ MARKER_END,
2520
+ ].join('\n');
2521
+ let content;
2522
+ if (fs.existsSync(hookPath)) {
2523
+ // Strip any prior marker block, then re-append the current one
2524
+ const existing = fs.readFileSync(hookPath, 'utf8');
2525
+ const lines = existing.split('\n');
2526
+ const kept = [];
2527
+ let inBlock = false;
2528
+ for (const line of lines) {
2529
+ const trimmed = line.trim();
2530
+ if (trimmed === MARKER_BEGIN) {
2531
+ inBlock = true;
2532
+ continue;
2533
+ }
2534
+ if (trimmed === MARKER_END) {
2535
+ inBlock = false;
2536
+ continue;
2537
+ }
2538
+ if (!inBlock)
2539
+ kept.push(line);
2540
+ }
2541
+ const base = kept.join('\n').replace(/\s*$/, '');
2542
+ content = base.length > 0
2543
+ ? `${base}\n\n${hookBlock}\n`
2544
+ : `#!/bin/sh\n${hookBlock}\n`;
2545
+ }
2546
+ else {
2547
+ content = `#!/bin/sh\n${hookBlock}\n`;
2548
+ }
2549
+ fs.writeFileSync(hookPath, content);
2550
+ fs.chmodSync(hookPath, 0o755);
2551
+ success(`Post-commit hook installed at ${hookPath}`);
2552
+ info(`Logs written to ${utils_1.SPEC_DATA_DIR}/logs/evolve-hook.log`);
2553
+ }
2554
+ catch (err) {
2555
+ error(`Hook install failed: ${err instanceof Error ? err.message : String(err)}`);
2556
+ process.exit(1);
2557
+ }
2558
+ });
2559
+ /**
2560
+ * homegraph spec evolve uninstall
2561
+ */
2562
+ evolveCommand
2563
+ .command('uninstall')
2564
+ .description('Remove the git post-commit hook installed by spec evolve')
2565
+ .option('-p, --path <path>', 'Path to the repository')
2566
+ .action(async (options) => {
2567
+ try {
2568
+ const repoPath = resolveSpecProjectPath(options.path);
2569
+ const { isGitRepo } = await Promise.resolve().then(() => __importStar(require('../spec/mining/git-scanner')));
2570
+ if (!isGitRepo(repoPath)) {
2571
+ error(`Not a git repository: ${repoPath}`);
2572
+ process.exit(1);
2573
+ }
2574
+ const { execFileSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
2575
+ const hooksDir = execFileSync('git', ['rev-parse', '--git-path', 'hooks'], {
2576
+ cwd: repoPath,
2577
+ encoding: 'utf8',
2578
+ stdio: ['ignore', 'pipe', 'ignore'],
2579
+ windowsHide: true,
2580
+ }).trim();
2581
+ const resolvedHooksDir = path.isAbsolute(hooksDir)
2582
+ ? hooksDir
2583
+ : path.resolve(repoPath, hooksDir);
2584
+ const hookPath = path.join(resolvedHooksDir, 'post-commit');
2585
+ if (!fs.existsSync(hookPath)) {
2586
+ info('No post-commit hook found — nothing to uninstall.');
2587
+ return;
2588
+ }
2589
+ const MARKER_BEGIN = '# >>> homegraph spec evolve hook >>>';
2590
+ const MARKER_END = '# <<< homegraph spec evolve hook <<<';
2591
+ const existing = fs.readFileSync(hookPath, 'utf8');
2592
+ const lines = existing.split('\n');
2593
+ const kept = [];
2594
+ let inBlock = false;
2595
+ let removedBlock = false;
2596
+ for (const line of lines) {
2597
+ const trimmed = line.trim();
2598
+ if (trimmed === MARKER_BEGIN) {
2599
+ inBlock = true;
2600
+ removedBlock = true;
2601
+ continue;
2602
+ }
2603
+ if (trimmed === MARKER_END) {
2604
+ inBlock = false;
2605
+ continue;
2606
+ }
2607
+ if (!inBlock)
2608
+ kept.push(line);
2609
+ }
2610
+ if (!removedBlock) {
2611
+ info('No homegraph spec evolve hook block found — nothing to uninstall.');
2612
+ return;
2613
+ }
2614
+ const remaining = kept.join('\n').trim();
2615
+ if (remaining.length === 0 || /^#!\/bin\/sh\s*$/.test(remaining)) {
2616
+ // Hook only contained our block (or just a shebang) — delete entirely
2617
+ fs.unlinkSync(hookPath);
2618
+ success(`Removed post-commit hook at ${hookPath}`);
2619
+ }
2620
+ else {
2621
+ fs.writeFileSync(hookPath, remaining + '\n');
2622
+ success(`Removed spec evolve hook block from ${hookPath} (user content preserved)`);
2623
+ }
2624
+ }
2625
+ catch (err) {
2626
+ error(`Hook uninstall failed: ${err instanceof Error ? err.message : String(err)}`);
2627
+ process.exit(1);
2628
+ }
2629
+ });
2630
+ /**
2631
+ * homegraph spec evolve process
2632
+ */
2633
+ evolveCommand
2634
+ .command('process')
2635
+ .description('Process commits through the spec self-evolve pipeline since last evolve')
2636
+ .option('-p, --path <path>', 'Path to the repository')
2637
+ .option('--db-path <path>', 'Path to the SQLite database file')
2638
+ .option('-j, --json', 'Output as JSON')
2639
+ .action(async (options) => {
2640
+ let db;
2641
+ try {
2642
+ const repoPath = resolveSpecProjectPath(options.path);
2643
+ const { createDatabase } = await Promise.resolve().then(() => __importStar(require('../db/sqlite-adapter')));
2644
+ const { resolveDbPath } = await Promise.resolve().then(() => __importStar(require('../spec/utils')));
2645
+ const { initSpecSchema } = await Promise.resolve().then(() => __importStar(require('../spec/db/schema')));
2646
+ const { loadSpecConfig } = await Promise.resolve().then(() => __importStar(require('../spec/config')));
2647
+ const { runBatchEvolvePipeline } = await Promise.resolve().then(() => __importStar(require('../spec/evolve/pipeline')));
2648
+ const { isGitRepo } = await Promise.resolve().then(() => __importStar(require('../spec/mining/git-scanner')));
2649
+ if (!isGitRepo(repoPath)) {
2650
+ error(`Not a git repository: ${repoPath}`);
2651
+ process.exit(1);
2652
+ }
2653
+ const dbPath = resolveDbPath(repoPath, options.dbPath);
2654
+ const created = createDatabase(dbPath);
2655
+ db = created.db;
2656
+ initSpecSchema(db);
2657
+ const config = loadSpecConfig(repoPath);
2658
+ const llmConfig = config.llm;
2659
+ if (!llmConfig) {
2660
+ warn('LLM not configured — Path B (LLM-based spec evolution) will be skipped.');
2661
+ warn('Only Path A (commit message scope → GENERATE) will be processed.');
2662
+ warn('Configure LLM in .homegraph/commit4spec/configs.json for full functionality:\n' +
2663
+ '{\n' +
2664
+ ' "llm": {\n' +
2665
+ ' "provider": "openai",\n' +
2666
+ ' "apiKeyEnv": "OPENAI_API_KEY",\n' +
2667
+ ' "model": "gpt-4o"\n' +
2668
+ ' }\n' +
2669
+ '}');
2670
+ }
2671
+ const result = await runBatchEvolvePipeline(repoPath, db, llmConfig ?? undefined);
2672
+ if (options.json) {
2673
+ console.log(JSON.stringify(result, null, 2));
2674
+ }
2675
+ else {
2676
+ if (result.commitsProcessed === 0) {
2677
+ console.log(chalk.green('No new commits to evolve.'));
2678
+ console.log(`Last evolved commit: ${result.fromCommit ? result.fromCommit.slice(0, 7) : 'none'}`);
2679
+ console.log(`Current HEAD: ${result.toCommit.slice(0, 7)}`);
2680
+ }
2681
+ else {
2682
+ console.log(chalk.bold(`Evolve complete: ${result.commitsProcessed} commit(s) processed`));
2683
+ console.log(`Range: ${result.fromCommit ? result.fromCommit.slice(0, 7) : 'none'} → ${result.toCommit.slice(0, 7)}`);
2684
+ if (result.metaUpdated) {
2685
+ console.log(chalk.green(`meta.json updated (currentCommitID = ${result.toCommit.slice(0, 7)})`));
2686
+ }
2687
+ else if (result.failures > 0) {
2688
+ console.log(chalk.yellow(`⚠ meta.json NOT updated — ${result.failures} commit(s) failed`));
2689
+ }
2690
+ else if (result.skippedCommits > 0) {
2691
+ console.log(chalk.yellow('⚠ meta.json NOT updated — all commits were skipped (no Path A match, no LLM)'));
2692
+ }
2693
+ else {
2694
+ console.log(chalk.yellow('⚠ meta.json NOT updated'));
2695
+ }
2696
+ // Show per-commit summary
2697
+ for (const r of result.perCommitResults) {
2698
+ let prefix;
2699
+ if (r.skipped) {
2700
+ prefix = chalk.yellow(' ⚠');
2701
+ }
2702
+ else {
2703
+ prefix = r.persisted ? chalk.green(' ✓') : chalk.red(' ✗');
2704
+ }
2705
+ console.log(`${prefix} ${r.commitHash.slice(0, 7)}`);
2706
+ if (r.skipped) {
2707
+ console.log(` skipped: ${r.skipReason}`);
2708
+ }
2709
+ if (r.isLogicChange) {
2710
+ console.log(` logic change: ${r.logicCheckReason}`);
2711
+ }
2712
+ if (r.generateSpecId) {
2713
+ console.log(` Path A - GENERATE ${r.generateSpecId}`);
2714
+ }
2715
+ for (const ev of r.evolvedSpecs) {
2716
+ console.log(` ${ev.specId}: ${ev.action}`);
2717
+ }
2718
+ }
2719
+ // Aggregate summary
2720
+ const totalFragments = result.perCommitResults.reduce((s, r) => s + r.fragmentsCount, 0);
2721
+ const totalRelations = result.perCommitResults.reduce((s, r) => s + r.relationsCreated, 0);
2722
+ if (result.skippedCommits > 0) {
2723
+ console.log(chalk.yellow(`${result.skippedCommits} commit(s) skipped — no LLM configured, did not match Path A`));
2724
+ }
2725
+ if (result.failures > 0) {
2726
+ console.log(chalk.red(`${result.failures} commit(s) failed — see details above`));
2727
+ }
2728
+ console.log(`Summary: fragments=${totalFragments}, relations=${totalRelations}`);
2729
+ }
2730
+ }
2731
+ }
2732
+ catch (err) {
2733
+ const message = err instanceof Error ? err.message : String(err);
2734
+ if (message.includes('No meta.json found')) {
2735
+ error("No meta.json found. Run 'homegraph spec mine' first.");
2736
+ }
2737
+ else {
2738
+ error(`Evolve failed: ${message}`);
2739
+ }
2740
+ process.exit(1);
2741
+ }
2742
+ finally {
2743
+ try {
2744
+ db?.close();
2745
+ }
2746
+ catch { /* best effort */ }
2747
+ }
2748
+ });
1906
2749
  /**
1907
2750
  * homegraph telemetry [on|off|status]
1908
2751
  */
@@ -1942,6 +2785,63 @@ function main() {
1942
2785
  console.log(`Config: ${s.configPath}`);
1943
2786
  console.log(chalk.dim(`\nExactly what is collected (and never collected): ${telemetry_1.TELEMETRY_DOCS}\n`));
1944
2787
  });
2788
+ /**
2789
+ * homegraph index-api <input> [version]
2790
+ * Build a standalone OHOS API database from command-line-tools SDK.
2791
+ */
2792
+ program
2793
+ .command('index-api <input> [version]')
2794
+ .description('Index OpenHarmony SDK API declarations into a standalone SQLite db')
2795
+ .option('-o, --output <path>', 'Output database path')
2796
+ .option('-q, --quiet', 'Suppress progress output')
2797
+ .action(async (input, versionArg, options) => {
2798
+ try {
2799
+ const { resolveOhosSdkInput, ohosApiDbFilename, indexOhosApiDb } = await Promise.resolve().then(() => __importStar(require('../extraction/languages/arkts')));
2800
+ const resolved = resolveOhosSdkInput({
2801
+ inputPath: input,
2802
+ versionOverride: versionArg,
2803
+ });
2804
+ const outputPath = options.output ?? path.join(process.cwd(), ohosApiDbFilename(resolved.version));
2805
+ if (!options.quiet) {
2806
+ info(`SDK home: ${resolved.sdkHome}`);
2807
+ info(`API version: ${resolved.version}`);
2808
+ info(`Output: ${outputPath}`);
2809
+ }
2810
+ const result = await indexOhosApiDb({
2811
+ sdkHome: resolved.sdkHome,
2812
+ version: resolved.version,
2813
+ outputPath,
2814
+ onProgress: options.quiet
2815
+ ? undefined
2816
+ : (progress) => {
2817
+ if (progress.phase === 'arkts-batch') {
2818
+ const label = progress.subphase === 'scene' ? 'Scene' : 'Persist';
2819
+ process.stdout.write(`\r${label}: ${progress.current}/${progress.total} ${progress.currentFile ?? ''}`.padEnd(80));
2820
+ }
2821
+ },
2822
+ });
2823
+ resolved.cleanup?.();
2824
+ if (!options.quiet) {
2825
+ process.stdout.write('\n');
2826
+ if (result.success) {
2827
+ info(`Indexed ${result.filesIndexed} SDK files → ${result.nodesCreated} nodes, ${result.edgesCreated} edges (${result.durationMs}ms)`);
2828
+ }
2829
+ else {
2830
+ error('OHOS API indexing failed');
2831
+ for (const err of result.errors.filter((e) => e.severity === 'error')) {
2832
+ error(err.message);
2833
+ }
2834
+ }
2835
+ }
2836
+ if (!result.success) {
2837
+ process.exit(1);
2838
+ }
2839
+ }
2840
+ catch (err) {
2841
+ error(err instanceof Error ? err.message : String(err));
2842
+ process.exit(1);
2843
+ }
2844
+ });
1945
2845
  /**
1946
2846
  * homegraph upgrade [version]
1947
2847
  *