homegraph 1.5.1 → 1.5.3

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 (382) hide show
  1. package/CHANGELOG.md +131 -0
  2. package/LICENSE +21 -21
  3. package/README.md +312 -305
  4. package/dist/arkui/index.d.ts +4 -0
  5. package/dist/arkui/index.d.ts.map +1 -0
  6. package/dist/arkui/index.js +23 -0
  7. package/dist/arkui/index.js.map +1 -0
  8. package/dist/arkui/migrate-passage.d.ts +28 -0
  9. package/dist/arkui/migrate-passage.d.ts.map +1 -0
  10. package/dist/arkui/migrate-passage.js +310 -0
  11. package/dist/arkui/migrate-passage.js.map +1 -0
  12. package/dist/arkui/migrate-semantics.d.ts +47 -0
  13. package/dist/arkui/migrate-semantics.d.ts.map +1 -0
  14. package/dist/arkui/migrate-semantics.js +229 -0
  15. package/dist/arkui/migrate-semantics.js.map +1 -0
  16. package/dist/arkui/migrate-snapshot.d.ts +79 -0
  17. package/dist/arkui/migrate-snapshot.d.ts.map +1 -0
  18. package/dist/arkui/migrate-snapshot.js +340 -0
  19. package/dist/arkui/migrate-snapshot.js.map +1 -0
  20. package/dist/bin/fatal-handler.js +2 -2
  21. package/dist/bin/homegraph.js +111 -162
  22. package/dist/bin/homegraph.js.map +1 -1
  23. package/dist/bin/node-version-check.d.ts +6 -15
  24. package/dist/bin/node-version-check.d.ts.map +1 -1
  25. package/dist/bin/node-version-check.js +8 -37
  26. package/dist/bin/node-version-check.js.map +1 -1
  27. package/dist/db/index.d.ts +36 -1
  28. package/dist/db/index.d.ts.map +1 -1
  29. package/dist/db/index.js +124 -38
  30. package/dist/db/index.js.map +1 -1
  31. package/dist/db/migrations.d.ts +1 -1
  32. package/dist/db/migrations.d.ts.map +1 -1
  33. package/dist/db/migrations.js +52 -38
  34. package/dist/db/migrations.js.map +1 -1
  35. package/dist/db/queries.d.ts +42 -0
  36. package/dist/db/queries.d.ts.map +1 -1
  37. package/dist/db/queries.js +330 -163
  38. package/dist/db/queries.js.map +1 -1
  39. package/dist/db/schema.sql +208 -203
  40. package/dist/db/sqlite-adapter.d.ts +8 -3
  41. package/dist/db/sqlite-adapter.d.ts.map +1 -1
  42. package/dist/db/sqlite-adapter.js +45 -3
  43. package/dist/db/sqlite-adapter.js.map +1 -1
  44. package/dist/directory.js +5 -5
  45. package/dist/extraction/extraction-version.d.ts +1 -1
  46. package/dist/extraction/extraction-version.js +1 -1
  47. package/dist/extraction/generated-detection.d.ts +42 -11
  48. package/dist/extraction/generated-detection.d.ts.map +1 -1
  49. package/dist/extraction/generated-detection.js +141 -20
  50. package/dist/extraction/generated-detection.js.map +1 -1
  51. package/dist/extraction/index.d.ts +43 -1
  52. package/dist/extraction/index.d.ts.map +1 -1
  53. package/dist/extraction/index.js +150 -43
  54. package/dist/extraction/index.js.map +1 -1
  55. package/dist/extraction/languages/arkts.d.ts +70 -6
  56. package/dist/extraction/languages/arkts.d.ts.map +1 -1
  57. package/dist/extraction/languages/arkts.js +958 -108
  58. package/dist/extraction/languages/arkts.js.map +1 -1
  59. package/dist/extraction/wasm/tree-sitter-c_sharp.wasm +0 -0
  60. package/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
  61. package/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
  62. package/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
  63. package/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
  64. package/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
  65. package/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
  66. package/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
  67. package/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
  68. package/dist/extraction/wasm-runtime-flags.d.ts.map +1 -1
  69. package/dist/extraction/wasm-runtime-flags.js +9 -6
  70. package/dist/extraction/wasm-runtime-flags.js.map +1 -1
  71. package/dist/graph-sources.d.ts +41 -0
  72. package/dist/graph-sources.d.ts.map +1 -0
  73. package/dist/graph-sources.js +82 -0
  74. package/dist/graph-sources.js.map +1 -0
  75. package/dist/index.d.ts +19 -1
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +80 -9
  78. package/dist/index.js.map +1 -1
  79. package/dist/installer/index.d.ts.map +1 -1
  80. package/dist/installer/index.js +0 -48
  81. package/dist/installer/index.js.map +1 -1
  82. package/dist/installer/instructions-template.js +9 -9
  83. package/dist/installer/targets/antigravity.js +1 -1
  84. package/dist/installer/targets/antigravity.js.map +1 -1
  85. package/dist/installer/targets/deveco.js +1 -1
  86. package/dist/installer/targets/deveco.js.map +1 -1
  87. package/dist/installer/targets/hermes.d.ts +1 -1
  88. package/dist/installer/targets/hermes.js +2 -2
  89. package/dist/installer/targets/hermes.js.map +1 -1
  90. package/dist/installer/targets/opencode.js +1 -1
  91. package/dist/installer/targets/opencode.js.map +1 -1
  92. package/dist/installer/targets/shared.js +1 -1
  93. package/dist/installer/targets/shared.js.map +1 -1
  94. package/dist/mcp/daemon-paths.d.ts +5 -4
  95. package/dist/mcp/daemon-paths.d.ts.map +1 -1
  96. package/dist/mcp/daemon-paths.js +28 -13
  97. package/dist/mcp/daemon-paths.js.map +1 -1
  98. package/dist/mcp/daemon-registry.d.ts.map +1 -1
  99. package/dist/mcp/daemon-registry.js +4 -1
  100. package/dist/mcp/daemon-registry.js.map +1 -1
  101. package/dist/mcp/daemon.d.ts +1 -1
  102. package/dist/mcp/daemon.js +1 -1
  103. package/dist/mcp/diff-impact.d.ts +131 -0
  104. package/dist/mcp/diff-impact.d.ts.map +1 -0
  105. package/dist/mcp/diff-impact.js +385 -0
  106. package/dist/mcp/diff-impact.js.map +1 -0
  107. package/dist/mcp/engine.d.ts.map +1 -1
  108. package/dist/mcp/engine.js +12 -3
  109. package/dist/mcp/engine.js.map +1 -1
  110. package/dist/mcp/explore-dedup.d.ts +137 -0
  111. package/dist/mcp/explore-dedup.d.ts.map +1 -0
  112. package/dist/mcp/explore-dedup.js +236 -0
  113. package/dist/mcp/explore-dedup.js.map +1 -0
  114. package/dist/mcp/explore-session-state.d.ts +217 -0
  115. package/dist/mcp/explore-session-state.d.ts.map +1 -0
  116. package/dist/mcp/explore-session-state.js +322 -0
  117. package/dist/mcp/explore-session-state.js.map +1 -0
  118. package/dist/mcp/index.d.ts.map +1 -1
  119. package/dist/mcp/index.js +4 -7
  120. package/dist/mcp/index.js.map +1 -1
  121. package/dist/mcp/liveness-watchdog.js +51 -51
  122. package/dist/mcp/proxy.d.ts.map +1 -1
  123. package/dist/mcp/proxy.js +5 -14
  124. package/dist/mcp/proxy.js.map +1 -1
  125. package/dist/mcp/query-worker.d.ts +1 -1
  126. package/dist/mcp/query-worker.js +1 -1
  127. package/dist/mcp/server-instructions.d.ts +7 -2
  128. package/dist/mcp/server-instructions.d.ts.map +1 -1
  129. package/dist/mcp/server-instructions.js +125 -47
  130. package/dist/mcp/server-instructions.js.map +1 -1
  131. package/dist/mcp/session.d.ts +14 -3
  132. package/dist/mcp/session.d.ts.map +1 -1
  133. package/dist/mcp/session.js +17 -14
  134. package/dist/mcp/session.js.map +1 -1
  135. package/dist/mcp/startup-handshake.d.ts +1 -1
  136. package/dist/mcp/startup-handshake.js +1 -1
  137. package/dist/mcp/tools.d.ts +138 -12
  138. package/dist/mcp/tools.d.ts.map +1 -1
  139. package/dist/mcp/tools.js +3279 -476
  140. package/dist/mcp/tools.js.map +1 -1
  141. package/dist/resolution/callback-synthesizer.d.ts +2 -1
  142. package/dist/resolution/callback-synthesizer.d.ts.map +1 -1
  143. package/dist/resolution/callback-synthesizer.js +153 -53
  144. package/dist/resolution/callback-synthesizer.js.map +1 -1
  145. package/dist/resolution/frameworks/arkts-entry.d.ts.map +1 -1
  146. package/dist/resolution/frameworks/arkts-entry.js +20 -9
  147. package/dist/resolution/frameworks/arkts-entry.js.map +1 -1
  148. package/dist/resolution/frameworks/arkts-napi.d.ts +14 -0
  149. package/dist/resolution/frameworks/arkts-napi.d.ts.map +1 -1
  150. package/dist/resolution/frameworks/arkts-napi.js +169 -49
  151. package/dist/resolution/frameworks/arkts-napi.js.map +1 -1
  152. package/dist/resolution/index.d.ts.map +1 -1
  153. package/dist/resolution/index.js +1 -1
  154. package/dist/resolution/index.js.map +1 -1
  155. package/dist/resolution/memory-budget.d.ts +33 -0
  156. package/dist/resolution/memory-budget.d.ts.map +1 -1
  157. package/dist/resolution/memory-budget.js +58 -0
  158. package/dist/resolution/memory-budget.js.map +1 -1
  159. package/dist/resolution/resolver-pool.d.ts +2 -0
  160. package/dist/resolution/resolver-pool.d.ts.map +1 -1
  161. package/dist/resolution/resolver-pool.js +4 -0
  162. package/dist/resolution/resolver-pool.js.map +1 -1
  163. package/dist/search/query-utils.d.ts +116 -4
  164. package/dist/search/query-utils.d.ts.map +1 -1
  165. package/dist/search/query-utils.js +869 -60
  166. package/dist/search/query-utils.js.map +1 -1
  167. package/dist/spec/db/commit-node.js +4 -4
  168. package/dist/spec/db/fragment-node.js +10 -10
  169. package/dist/spec/db/fts.js +8 -8
  170. package/dist/spec/db/relations.js +88 -88
  171. package/dist/spec/db/schema.js +6 -6
  172. package/dist/spec/db/schema.sql +121 -121
  173. package/dist/spec/db/spec-node.js +4 -4
  174. package/dist/spec/llm/prompts.js +53 -53
  175. package/dist/types.d.ts +8 -0
  176. package/dist/types.d.ts.map +1 -1
  177. package/dist/ui/shimmer-progress.d.ts.map +1 -1
  178. package/dist/ui/shimmer-progress.js +4 -1
  179. package/dist/ui/shimmer-progress.js.map +1 -1
  180. package/dist/upgrade/index.d.ts +14 -26
  181. package/dist/upgrade/index.d.ts.map +1 -1
  182. package/dist/upgrade/index.js +28 -123
  183. package/dist/upgrade/index.js.map +1 -1
  184. package/dist/upgrade/remove-binary.d.ts +19 -19
  185. package/dist/upgrade/remove-binary.d.ts.map +1 -1
  186. package/dist/upgrade/remove-binary.js +20 -21
  187. package/dist/upgrade/remove-binary.js.map +1 -1
  188. package/dist/upgrade/update-check.d.ts +4 -3
  189. package/dist/upgrade/update-check.d.ts.map +1 -1
  190. package/dist/upgrade/update-check.js +4 -3
  191. package/dist/upgrade/update-check.js.map +1 -1
  192. package/package.json +65 -62
  193. package/dist/extraction/languages/arkts-viewtree.d.ts +0 -22
  194. package/dist/extraction/languages/arkts-viewtree.d.ts.map +0 -1
  195. package/dist/extraction/languages/arkts-viewtree.js +0 -133
  196. package/dist/extraction/languages/arkts-viewtree.js.map +0 -1
  197. package/dist/reasoning/config.d.ts +0 -45
  198. package/dist/reasoning/config.d.ts.map +0 -1
  199. package/dist/reasoning/config.js +0 -171
  200. package/dist/reasoning/config.js.map +0 -1
  201. package/dist/reasoning/credentials.d.ts +0 -5
  202. package/dist/reasoning/credentials.d.ts.map +0 -1
  203. package/dist/reasoning/credentials.js +0 -83
  204. package/dist/reasoning/credentials.js.map +0 -1
  205. package/dist/reasoning/login.d.ts +0 -21
  206. package/dist/reasoning/login.d.ts.map +0 -1
  207. package/dist/reasoning/login.js +0 -85
  208. package/dist/reasoning/login.js.map +0 -1
  209. package/dist/reasoning/reasoner.d.ts +0 -43
  210. package/dist/reasoning/reasoner.d.ts.map +0 -1
  211. package/dist/reasoning/reasoner.js +0 -308
  212. package/dist/reasoning/reasoner.js.map +0 -1
  213. package/dist/spec/evolve/llm-client.d.ts +0 -50
  214. package/dist/spec/evolve/llm-client.d.ts.map +0 -1
  215. package/dist/spec/evolve/llm-client.js +0 -176
  216. package/dist/spec/evolve/llm-client.js.map +0 -1
  217. package/dist/spec/evolve/logic-checker.d.ts +0 -12
  218. package/dist/spec/evolve/logic-checker.d.ts.map +0 -1
  219. package/dist/spec/evolve/logic-checker.js +0 -24
  220. package/dist/spec/evolve/logic-checker.js.map +0 -1
  221. package/dist/spec/llm/index.d.ts +0 -3
  222. package/dist/spec/llm/index.d.ts.map +0 -1
  223. package/dist/spec/llm/index.js +0 -11
  224. package/dist/spec/llm/index.js.map +0 -1
  225. package/dist/spec/mining/diff-parser.d.ts +0 -33
  226. package/dist/spec/mining/diff-parser.d.ts.map +0 -1
  227. package/dist/spec/mining/diff-parser.js +0 -166
  228. package/dist/spec/mining/diff-parser.js.map +0 -1
  229. package/dist/spec/mining/git-scanner.d.ts +0 -103
  230. package/dist/spec/mining/git-scanner.d.ts.map +0 -1
  231. package/dist/spec/mining/git-scanner.js +0 -307
  232. package/dist/spec/mining/git-scanner.js.map +0 -1
  233. package/dist/spec/mining/pipeline.d.ts +0 -53
  234. package/dist/spec/mining/pipeline.d.ts.map +0 -1
  235. package/dist/spec/mining/pipeline.js +0 -178
  236. package/dist/spec/mining/pipeline.js.map +0 -1
  237. package/dist/spec/mining/scope-resolver.d.ts +0 -45
  238. package/dist/spec/mining/scope-resolver.d.ts.map +0 -1
  239. package/dist/spec/mining/scope-resolver.js +0 -103
  240. package/dist/spec/mining/scope-resolver.js.map +0 -1
  241. package/dist/spec/mining/spec-extractor.d.ts +0 -69
  242. package/dist/spec/mining/spec-extractor.d.ts.map +0 -1
  243. package/dist/spec/mining/spec-extractor.js +0 -369
  244. package/dist/spec/mining/spec-extractor.js.map +0 -1
  245. package/dist/spec/utils.d.ts +0 -167
  246. package/dist/spec/utils.d.ts.map +0 -1
  247. package/dist/spec/utils.js +0 -463
  248. package/dist/spec/utils.js.map +0 -1
  249. package/dist/telemetry/index.d.ts +0 -143
  250. package/dist/telemetry/index.d.ts.map +0 -1
  251. package/dist/telemetry/index.js +0 -541
  252. package/dist/telemetry/index.js.map +0 -1
  253. package/scripts/_tmp-cfwk-resolve.log +0 -0
  254. package/scripts/_tmp-cfwk-sig.log +0 -0
  255. package/scripts/_tmp-cfwk-vt.log +0 -0
  256. package/scripts/add-lang/bench.sh +0 -60
  257. package/scripts/add-lang/check-grammar.mjs +0 -75
  258. package/scripts/add-lang/dump-ast.mjs +0 -103
  259. package/scripts/add-lang/verify-extraction.mjs +0 -70
  260. package/scripts/agent-eval/ab-adoption.sh +0 -91
  261. package/scripts/agent-eval/ab-hook.sh +0 -86
  262. package/scripts/agent-eval/ab-impl.sh +0 -78
  263. package/scripts/agent-eval/ab-new-vs-baseline.sh +0 -102
  264. package/scripts/agent-eval/ab-sufficiency.sh +0 -78
  265. package/scripts/agent-eval/arms-F.sh +0 -21
  266. package/scripts/agent-eval/arms-matrix.sh +0 -37
  267. package/scripts/agent-eval/audit.sh +0 -68
  268. package/scripts/agent-eval/bench-readme.sh +0 -28
  269. package/scripts/agent-eval/bench-why-repo.sh +0 -22
  270. package/scripts/agent-eval/block-read-hook.sh +0 -19
  271. package/scripts/agent-eval/hook-settings.json +0 -15
  272. package/scripts/agent-eval/itrun.sh +0 -120
  273. package/scripts/agent-eval/offload-eval-3arm.sh +0 -72
  274. package/scripts/agent-eval/offload-eval-cost.mjs +0 -133
  275. package/scripts/agent-eval/offload-eval-effort.mjs +0 -108
  276. package/scripts/agent-eval/offload-eval-frontload-matrix.sh +0 -25
  277. package/scripts/agent-eval/offload-eval-frontload.sh +0 -47
  278. package/scripts/agent-eval/offload-eval-ground-truth.json +0 -18
  279. package/scripts/agent-eval/offload-eval-hook.mjs +0 -84
  280. package/scripts/agent-eval/offload-eval-judge.mjs +0 -103
  281. package/scripts/agent-eval/offload-eval-matrix.sh +0 -20
  282. package/scripts/agent-eval/offload-eval-metrics.mjs +0 -94
  283. package/scripts/agent-eval/offload-eval-refs1.sh +0 -50
  284. package/scripts/agent-eval/offload-eval-setup.sh +0 -24
  285. package/scripts/agent-eval/offload-eval-styles.sh +0 -72
  286. package/scripts/agent-eval/offload-eval-summarize.mjs +0 -68
  287. package/scripts/agent-eval/offload-eval.md +0 -76
  288. package/scripts/agent-eval/parse-arms.mjs +0 -116
  289. package/scripts/agent-eval/parse-bench-readme.mjs +0 -84
  290. package/scripts/agent-eval/parse-run.mjs +0 -45
  291. package/scripts/agent-eval/parse-session.mjs +0 -93
  292. package/scripts/agent-eval/probe-context.mjs +0 -21
  293. package/scripts/agent-eval/probe-explore.mjs +0 -40
  294. package/scripts/agent-eval/probe-node.mjs +0 -20
  295. package/scripts/agent-eval/probe-sweep.mjs +0 -119
  296. package/scripts/agent-eval/probe-trace.mjs +0 -20
  297. package/scripts/agent-eval/redirect-read-hook.sh +0 -38
  298. package/scripts/agent-eval/repro-concurrent-explore.mjs +0 -119
  299. package/scripts/agent-eval/repro-daemon-clients.mjs +0 -125
  300. package/scripts/agent-eval/run-agent.sh +0 -34
  301. package/scripts/agent-eval/run-all.sh +0 -75
  302. package/scripts/agent-eval/run-arms.sh +0 -56
  303. package/scripts/agent-eval/seq-matrix.mjs +0 -137
  304. package/scripts/bench-arkts-init-rss.log +0 -0
  305. package/scripts/build-bundle.sh +0 -123
  306. package/scripts/exp_boundary_eval/README.md +0 -247
  307. package/scripts/exp_boundary_eval/__pycache__/_utils.cpython-310.pyc +0 -0
  308. package/scripts/exp_boundary_eval/__pycache__/_utils.cpython-38.pyc +0 -0
  309. package/scripts/exp_boundary_eval/__pycache__/analyze.cpython-310.pyc +0 -0
  310. package/scripts/exp_boundary_eval/__pycache__/analyze.cpython-38.pyc +0 -0
  311. package/scripts/exp_boundary_eval/__pycache__/deveco_arm.cpython-38.pyc +0 -0
  312. package/scripts/exp_boundary_eval/__pycache__/run_all.cpython-310.pyc +0 -0
  313. package/scripts/exp_boundary_eval/__pycache__/run_all.cpython-38.pyc +0 -0
  314. package/scripts/exp_boundary_eval/__pycache__/run_one.cpython-310.pyc +0 -0
  315. package/scripts/exp_boundary_eval/__pycache__/run_one.cpython-38.pyc +0 -0
  316. package/scripts/exp_boundary_eval/__pycache__/run_session.cpython-310.pyc +0 -0
  317. package/scripts/exp_boundary_eval/__pycache__/run_session.cpython-38.pyc +0 -0
  318. package/scripts/exp_boundary_eval/__pycache__/setup.cpython-310.pyc +0 -0
  319. package/scripts/exp_boundary_eval/__pycache__/setup.cpython-38.pyc +0 -0
  320. package/scripts/exp_boundary_eval/__pycache__/win_mcp_launcher.cpython-38.pyc +0 -0
  321. package/scripts/exp_boundary_eval/_test_mcp_chain.py +0 -78
  322. package/scripts/exp_boundary_eval/_test_stdin.py +0 -8
  323. package/scripts/exp_boundary_eval/_utils.py +0 -1116
  324. package/scripts/exp_boundary_eval/analyze.py +0 -1313
  325. package/scripts/exp_boundary_eval/deveco_arm.py +0 -519
  326. package/scripts/exp_boundary_eval/run_all.py +0 -378
  327. package/scripts/exp_boundary_eval/run_one.py +0 -165
  328. package/scripts/exp_boundary_eval/run_session.py +0 -158
  329. package/scripts/exp_boundary_eval/setup.py +0 -120
  330. package/scripts/exp_boundary_eval/win_mcp_launcher.py +0 -73
  331. package/scripts/exp_boundary_eval/win_mcp_stdio_wrap.js +0 -36
  332. package/scripts/exp_boundary_eval/win_node_launcher.py +0 -24
  333. package/scripts/extract-release-notes.mjs +0 -130
  334. package/scripts/local-install.sh +0 -41
  335. package/scripts/npm-sdk.js +0 -75
  336. package/scripts/npm-shim.js +0 -275
  337. package/scripts/ohos-sdk-publish.mjs +0 -133
  338. package/scripts/pack-npm.sh +0 -119
  339. package/scripts/prepare-release.mjs +0 -270
  340. package/scripts/probe-arkts-mem-why-run.log +0 -0
  341. package/scripts/probe-banner-livecard-ir.log +0 -0
  342. package/scripts/probe-cfwk-ast.stderr.log +0 -0
  343. package/scripts/probe-cfwk-ast.stdout.log +0 -0
  344. package/scripts/probe-cfwk-attr-shape.log +0 -0
  345. package/scripts/probe-cfwk-cfgdump.stderr.log +0 -0
  346. package/scripts/probe-cfwk-cfgdump.stdout.log +0 -0
  347. package/scripts/probe-cfwk-cvc.stderr.log +0 -0
  348. package/scripts/probe-cfwk-cvc.stdout.log +0 -0
  349. package/scripts/probe-cfwk-diag2.log +0 -0
  350. package/scripts/probe-cfwk-diag3.log +0 -0
  351. package/scripts/probe-cfwk-fedbg.stderr.log +0 -0
  352. package/scripts/probe-cfwk-fedbg.stdout.log +0 -0
  353. package/scripts/probe-cfwk-fileresult.log +0 -0
  354. package/scripts/probe-cfwk-fix.stderr.log +0 -0
  355. package/scripts/probe-cfwk-fix.stdout.log +0 -0
  356. package/scripts/probe-cfwk-fix2.stderr.log +0 -0
  357. package/scripts/probe-cfwk-fix2.stdout.log +0 -0
  358. package/scripts/probe-cfwk-fix3.stderr.log +0 -0
  359. package/scripts/probe-cfwk-fix3.stdout.log +0 -0
  360. package/scripts/probe-cfwk-foreach.log +0 -0
  361. package/scripts/probe-cfwk-getmethod-throw.log +0 -0
  362. package/scripts/probe-cfwk-hg-extract.log +0 -0
  363. package/scripts/probe-cfwk-pr1003-noprior.stderr.log +0 -0
  364. package/scripts/probe-cfwk-pr1003-noprior.stdout.log +0 -0
  365. package/scripts/probe-cfwk-pr1003.stderr.log +0 -0
  366. package/scripts/probe-cfwk-pr1003.stdout.log +0 -0
  367. package/scripts/probe-cfwk-preroot-noprior.stderr.log +0 -0
  368. package/scripts/probe-cfwk-preroot-noprior.stdout.log +0 -0
  369. package/scripts/probe-cfwk-preroot-prior.stderr.log +0 -0
  370. package/scripts/probe-cfwk-preroot-prior.stdout.log +0 -0
  371. package/scripts/probe-cfwk-preroot-skipstate.stderr.log +0 -0
  372. package/scripts/probe-cfwk-preroot-skipstate.stdout.log +0 -0
  373. package/scripts/probe-cfwk-resolve-sim.log +0 -0
  374. package/scripts/probe-cfwk-tree-shape.log +0 -0
  375. package/scripts/probe-cfwk-walk-abort.log +0 -0
  376. package/scripts/probe-cfwk.log +0 -0
  377. package/scripts/probe-force-index.log +0 -0
  378. package/scripts/probe-no-force-index.log +0 -0
  379. package/scripts/probe-samefile-ir.stderr.log +0 -0
  380. package/scripts/probe-samefile-ir.stdout.log +0 -224
  381. package/scripts/probe-sdk-vs-project.log +0 -0
  382. package/scripts/probe-viewtree-downgrade.log +0 -0
@@ -75,9 +75,13 @@ exports.scanEtsFiles = scanEtsFiles;
75
75
  exports.runIsolatedArkTSBatchEntry = runIsolatedArkTSBatchEntry;
76
76
  exports.primeArkTSBatch = primeArkTSBatch;
77
77
  exports.isArkTSBatchPersisted = isArkTSBatchPersisted;
78
+ exports.isArkTSBatchCommitted = isArkTSBatchCommitted;
78
79
  exports.resetArkTSBatch = resetArkTSBatch;
80
+ exports.shrinkArkTSBatchPostParse = shrinkArkTSBatchPostParse;
79
81
  exports.collectExportedRtaEntryPointsFromFiles = collectExportedRtaEntryPointsFromFiles;
80
82
  exports.stateDecoratorKinds = stateDecoratorKinds;
83
+ exports.fieldDecoratorEntries = fieldDecoratorEntries;
84
+ exports.fieldDecoratorsEncoded = fieldDecoratorsEncoded;
81
85
  exports.stateTransferViaForField = stateTransferViaForField;
82
86
  exports.isUnclearArkClassSignature = isUnclearArkClassSignature;
83
87
  exports.isUnclearArkMethodSignature = isUnclearArkMethodSignature;
@@ -99,7 +103,13 @@ exports.parseJson5Minimal = parseJson5Minimal;
99
103
  exports.normalizeOhosApiVersion = normalizeOhosApiVersion;
100
104
  exports.detectOhosCompileSdkVersion = detectOhosCompileSdkVersion;
101
105
  exports.isOhosArktsProject = isOhosArktsProject;
106
+ exports.readOhosSdkPlatformVersion = readOhosSdkPlatformVersion;
107
+ exports.discoverLocalOhosSdkCandidates = discoverLocalOhosSdkCandidates;
108
+ exports.findLocalOhosSdkForVersion = findLocalOhosSdkForVersion;
109
+ exports.resolveOhosApiVersionForProject = resolveOhosApiVersionForProject;
110
+ exports.tryDownloadOhosApiDbFromNpm = tryDownloadOhosApiDbFromNpm;
102
111
  exports.ensureOhosApiDb = ensureOhosApiDb;
112
+ exports.attachExistingOhosApiDbForProject = attachExistingOhosApiDbForProject;
103
113
  exports.bindOhosApiDbForProject = bindOhosApiDbForProject;
104
114
  exports.restoreOhosApiDbAttach = restoreOhosApiDbAttach;
105
115
  const crypto = __importStar(require("crypto"));
@@ -115,9 +125,12 @@ const directory_1 = require("../../directory");
115
125
  const project_config_1 = require("../../project-config");
116
126
  const context_1 = require("../context");
117
127
  const default_ignore_1 = require("../default-ignore");
128
+ const generated_detection_1 = require("../generated-detection");
118
129
  const extraction_version_1 = require("../extraction-version");
119
130
  const version_1 = require("../../mcp/version");
120
131
  const liveness_watchdog_1 = require("../../mcp/liveness-watchdog");
132
+ const migrate_semantics_1 = require("../../arkui/migrate-semantics");
133
+ const migrate_passage_1 = require("../../arkui/migrate-passage");
121
134
  const tree_sitter_helpers_1 = require("../tree-sitter-helpers");
122
135
  const wasm_runtime_flags_1 = require("../wasm-runtime-flags");
123
136
  const ignore_1 = __importDefault(require("ignore"));
@@ -473,11 +486,16 @@ function hasHarmonyProjectMarkers(rootDir) {
473
486
  function normIndexPath(filePath) {
474
487
  return filePath.replace(/\\/g, '/');
475
488
  }
476
- /** Member calls through a native `.so` binding: `sdk.Asset_setCropRect(`. */
489
+ /** Photos-style member calls: `sdk.Asset_setCropRect(`. */
477
490
  const NAPI_MEMBER_CALL_RE = /\.\s*([A-Z][A-Za-z0-9_]*(?:_[A-Za-z0-9_]+)+)\s*\(/g;
491
+ /** `libFoo.so` module path from ArkAnalyzer import infos. */
492
+ const LIB_SO_MODULE_RE = /^lib[A-Za-z0-9_]+\.so$/;
478
493
  function isNapiSymbolName(name) {
479
494
  return /^[A-Z][A-Za-z0-9_]*_[A-Za-z0-9_]+$/.test(name);
480
495
  }
496
+ function escapeRegExp(s) {
497
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
498
+ }
481
499
  let persistedBatch = null;
482
500
  /** File that triggered the most recent batch persist (for indexAll edge stats). */
483
501
  let batchTriggerFile = null;
@@ -628,6 +646,7 @@ function persistFileResult(queries, filePath, content, stats, result, options) {
628
646
  indexedAt: Date.now(),
629
647
  nodeCount: result.nodes.length,
630
648
  errors: result.errors.length > 0 ? result.errors : undefined,
649
+ generated: (0, generated_detection_1.detectGeneratedFile)(filePath, content),
631
650
  };
632
651
  queries.upsertFile(fileRecord);
633
652
  }
@@ -647,6 +666,27 @@ function persistIncrementalModuleResults(rootDir, queries, index) {
647
666
  persistFileResult(queries, filePath, content, stats, fileResult, { force: true });
648
667
  }
649
668
  }
669
+ /**
670
+ * Persist a module slice then drop it from the in-memory map (streaming modular build).
671
+ * Returns the number of files written.
672
+ */
673
+ function persistAndDropModuleSlice(rootDir, queries, slice, streamedPersistedPaths, persistOrdinal) {
674
+ if (slice.size === 0)
675
+ return 0;
676
+ for (const [filePath, fileResult] of slice) {
677
+ persistOrdinal.done++;
678
+ reportBatchProgress('persist', Math.min(persistOrdinal.done, persistOrdinal.totalHint), persistOrdinal.totalHint, filePath);
679
+ const isVirtual = filePath.startsWith('@');
680
+ const full = path.join(rootDir, filePath);
681
+ const content = isVirtual ? '' : fs.readFileSync(full, 'utf-8');
682
+ const stats = isVirtual
683
+ ? { size: 0, mtimeMs: Date.now() }
684
+ : fs.statSync(full);
685
+ persistFileResult(queries, filePath, content, stats, fileResult, { force: true });
686
+ streamedPersistedPaths.add(normIndexPath(filePath));
687
+ }
688
+ return slice.size;
689
+ }
650
690
  async function persistBatchResultsAsync(rootDir, queries, index) {
651
691
  const entries = [...index.fileResults.entries()];
652
692
  const persistTotal = entries.length;
@@ -694,18 +734,89 @@ function persistBatchResultsSync(rootDir, queries, index) {
694
734
  }
695
735
  }
696
736
  function commitArkTSBatch(rootDir, batchKey, etsFiles, index, triggerFile) {
697
- batchPersistedPaths = new Set([...index.fileResults.keys()].map(normIndexPath));
737
+ const streamed = index.streamedPersistedPaths;
738
+ batchPersistedPaths =
739
+ streamed && streamed.size > 0
740
+ ? new Set([...streamed].map(normIndexPath))
741
+ : new Set([...index.fileResults.keys()].map(normIndexPath));
698
742
  persistedBatch = { ...index, rootDir, batchKey, etsFiles };
699
743
  batchTriggerFile = normIndexPath(triggerFile);
700
- if (index.fileResults.size > 0) {
744
+ if (batchPersistedPaths.size > 0 || index.fileResults.size > 0) {
701
745
  batchBuildCommitted = true;
702
746
  }
703
747
  return persistedBatch;
704
748
  }
749
+ /**
750
+ * Drop in-memory ExtractionResults / edges after they are in SQLite.
751
+ * Keep path sets so `isArkTSBatchPersisted` still skips re-parse; matches the
752
+ * isolated-worker commit shape (empty fileResults).
753
+ */
754
+ function releaseArkTSBatchHeavyPayload() {
755
+ if (!persistedBatch)
756
+ return;
757
+ persistedBatch.fileResults.clear();
758
+ persistedBatch.crossFileEdges = [];
759
+ persistedBatch.nodeIds.clear();
760
+ persistedBatch.errors = [];
761
+ }
762
+ /** Best-effort reclaim after Scene / batch teardown (needs `node --expose-gc`). */
763
+ function tryForceGc() {
764
+ const gc = globalThis.gc;
765
+ if (typeof gc === 'function') {
766
+ try {
767
+ gc();
768
+ }
769
+ catch {
770
+ // ignore
771
+ }
772
+ }
773
+ }
774
+ /**
775
+ * Unload every module's IR then dispose Scene globals. Plain `scene.dispose()`
776
+ * only clears SDK helpers / moduleCache — project ArkFiles stay reachable and
777
+ * pin multi-GB RSS into Parsing/Resolving.
778
+ */
779
+ function releaseArkAnalyzerScene(scene) {
780
+ try {
781
+ for (const mod of scene.getModules()) {
782
+ try {
783
+ scene.disposeModule(mod);
784
+ mod.setFileDepGraph(undefined);
785
+ mod.clearFilesMap();
786
+ mod.setLoadState(arkanalyzer_1.ModuleLoadState.NOT_LOADED);
787
+ }
788
+ catch {
789
+ // per-module best-effort
790
+ }
791
+ }
792
+ }
793
+ catch {
794
+ // ignore
795
+ }
796
+ try {
797
+ scene.dispose();
798
+ }
799
+ catch {
800
+ // ignore
801
+ }
802
+ tryForceGc();
803
+ }
804
+ /**
805
+ * ArkAnalyzer 1.0.92 keeps INDEX hollow on {@link ModuleBuilder} as a private
806
+ * method (public eviction paths call it internally). HomeGraph needs the same
807
+ * post-ingest hollow; call through a narrow cast until a public API exists.
808
+ */
809
+ function hollowModuleToIndex(builder, moduleId) {
810
+ builder.downgradeModule(moduleId, arkanalyzer_1.ModuleDepthLevel.INDEX);
811
+ }
705
812
  function tryReturnCachedBatch(rootDir, triggerFile, etsFiles) {
706
813
  const normalizedTrigger = normIndexPath(triggerFile);
814
+ // Only reuse when payload is still in memory. After
815
+ // releaseArkTSBatchHeavyPayload(), fileResults is empty — fall through to
816
+ // rebuild (same as isolated-worker commit) rather than return a hollow cache.
707
817
  if (persistedBatch &&
708
818
  persistedBatch.rootDir === rootDir &&
819
+ persistedBatch.fileResults.size > 0 &&
709
820
  batchPersistedPaths.has(normalizedTrigger)) {
710
821
  return persistedBatch;
711
822
  }
@@ -789,7 +900,9 @@ function runArkTSBatchFullCore(rootDir, queries, triggerFile, etsFiles, batchKey
789
900
  const index = buildArkTSIndex(rootDir, etsFiles);
790
901
  if (index.fileResults.size === 0) {
791
902
  const fatal = index.errors.find((e) => e.severity === 'error');
792
- throw new Error(fatal?.message ?? 'ArkTS batch produced no indexed files');
903
+ const detail = index.errors.map((e) => `[${e.severity}] ${e.message}`).join(' | ');
904
+ throw new Error(fatal?.message ??
905
+ `ArkTS batch produced no indexed files${detail ? ` (${detail})` : ' (no Scene/adapter errors recorded)'}`);
793
906
  }
794
907
  queries.deleteArkTSCrossFileCallEdges();
795
908
  persistBatchResultsSync(rootDir, queries, index);
@@ -799,6 +912,8 @@ function runArkTSBatchFullCore(rootDir, queries, triggerFile, etsFiles, batchKey
799
912
  queries.insertEdges(valid);
800
913
  }
801
914
  }
915
+ // Keep payload in memory for ArkTSExtractor.extract() same-batch hits.
916
+ // indexAll uses primeArkTSBatch → releaseArkTSBatchHeavyPayload after persist.
802
917
  return commitArkTSBatch(rootDir, batchKey, etsFiles, index, triggerFile);
803
918
  }
804
919
  finally {
@@ -850,6 +965,27 @@ function runArkTSBatch(rootDir, queries, triggerFile) {
850
965
  if (cached) {
851
966
  return cached;
852
967
  }
968
+ // indexAll releases the in-memory payload after SQLite persist. A later
969
+ // ArkTSExtractor hit must NOT rebuild the entire Scene (looks like a restart).
970
+ if (batchBuildCommitted &&
971
+ persistedBatch &&
972
+ persistedBatch.rootDir === rootDir &&
973
+ batchPersistedPaths.has(normalizedTrigger)) {
974
+ return {
975
+ fileResults: new Map(),
976
+ crossFileEdges: [],
977
+ nodeIds: new Set(),
978
+ errors: [
979
+ {
980
+ message: 'ArkTS batch already persisted this run; in-memory payload released',
981
+ severity: 'warning',
982
+ },
983
+ ],
984
+ rootDir,
985
+ batchKey: persistedBatch.batchKey,
986
+ etsFiles: persistedBatch.etsFiles,
987
+ };
988
+ }
853
989
  const batchKey = etsFiles.length > 0 ? computeBatchKey(rootDir, etsFiles) : '';
854
990
  return runArkTSBatchFull(rootDir, queries, normalizedTrigger, etsFiles, batchKey);
855
991
  }
@@ -892,13 +1028,27 @@ async function primeArkTSBatch(rootDir, queries, triggerFile, options) {
892
1028
  (0, context_1.setArktsBatchRunning)(true);
893
1029
  try {
894
1030
  reportBatchProgress('scene', 0, etsFiles.length);
895
- const index = buildArkTSIndex(rootDir, etsFiles);
896
- if (index.fileResults.size === 0) {
1031
+ const streamModular = shouldUseModularArkTSBuild(rootDir);
1032
+ if (streamModular) {
1033
+ // Clear stale cross-file RTA edges before streaming module persists.
1034
+ queries.deleteArkTSCrossFileCallEdges();
1035
+ }
1036
+ const index = buildArkTSIndex(rootDir, etsFiles, {
1037
+ streamQueries: streamModular ? queries : undefined,
1038
+ });
1039
+ const streamed = index.streamedPersistedPaths?.size ?? 0;
1040
+ if (index.fileResults.size === 0 && streamed === 0) {
897
1041
  const fatal = index.errors.find((e) => e.severity === 'error');
898
- throw new Error(fatal?.message ?? 'ArkTS batch produced no indexed files');
1042
+ const detail = index.errors.map((e) => `[${e.severity}] ${e.message}`).join(' | ');
1043
+ throw new Error(fatal?.message ??
1044
+ `ArkTS batch produced no indexed files${detail ? ` (${detail})` : ' (no Scene/adapter errors recorded)'}`);
1045
+ }
1046
+ if (!streamModular) {
1047
+ queries.deleteArkTSCrossFileCallEdges();
1048
+ }
1049
+ if (index.fileResults.size > 0) {
1050
+ await persistBatchResultsAsync(rootDir, queries, index);
899
1051
  }
900
- queries.deleteArkTSCrossFileCallEdges();
901
- await persistBatchResultsAsync(rootDir, queries, index);
902
1052
  if (index.crossFileEdges.length > 0) {
903
1053
  const valid = index.crossFileEdges.filter((e) => index.nodeIds.has(e.source) && index.nodeIds.has(e.target));
904
1054
  if (valid.length > 0) {
@@ -906,6 +1056,8 @@ async function primeArkTSBatch(rootDir, queries, triggerFile, options) {
906
1056
  }
907
1057
  }
908
1058
  commitArkTSBatch(rootDir, batchKey, etsFiles, index, normalizedTrigger);
1059
+ releaseArkTSBatchHeavyPayload();
1060
+ tryForceGc();
909
1061
  }
910
1062
  finally {
911
1063
  (0, context_1.setArktsBatchRunning)(false);
@@ -923,8 +1075,13 @@ async function runArkTSBatchIncrementalModules(rootDir, queries, triggerFile, et
923
1075
  (0, context_1.setArktsBatchRunning)(true);
924
1076
  try {
925
1077
  reportBatchProgress('scene', 0, etsFiles.length);
926
- const index = buildArkTSIndexByModule(rootDir, etsFiles, { targetModuleSrcPaths: moduleSrcPaths });
927
- if (index.fileResults.size === 0) {
1078
+ queries.deleteArkTSCrossFileCallEdges();
1079
+ const index = buildArkTSIndexByModule(rootDir, etsFiles, {
1080
+ targetModuleSrcPaths: moduleSrcPaths,
1081
+ streamQueries: queries,
1082
+ });
1083
+ const streamed = index.streamedPersistedPaths?.size ?? 0;
1084
+ if (index.fileResults.size === 0 && streamed === 0) {
928
1085
  const fatal = index.errors.find((e) => e.severity === 'error');
929
1086
  // Fall back to full project batch when the subset produced nothing.
930
1087
  const batchKey = etsFiles.length > 0 ? computeBatchKey(rootDir, etsFiles) : '';
@@ -932,20 +1089,26 @@ async function runArkTSBatchIncrementalModules(rootDir, queries, triggerFile, et
932
1089
  process.stderr.write(`\n\x1b[33m ArkTS: incremental modules failed (${fatal.message}); full rebuild\x1b[0m\n`);
933
1090
  }
934
1091
  const full = buildArkTSIndex(rootDir, etsFiles);
935
- if (full.fileResults.size === 0) {
1092
+ if (full.fileResults.size === 0 && (full.streamedPersistedPaths?.size ?? 0) === 0) {
936
1093
  throw new Error(fatal?.message ?? 'ArkTS incremental batch produced no indexed files');
937
1094
  }
938
1095
  queries.deleteArkTSCrossFileCallEdges();
939
- await persistBatchResultsAsync(rootDir, queries, full);
1096
+ if (full.fileResults.size > 0) {
1097
+ await persistBatchResultsAsync(rootDir, queries, full);
1098
+ }
940
1099
  if (full.crossFileEdges.length > 0) {
941
1100
  const valid = full.crossFileEdges.filter((e) => full.nodeIds.has(e.source) && full.nodeIds.has(e.target));
942
1101
  if (valid.length > 0)
943
1102
  queries.insertEdges(valid);
944
1103
  }
945
1104
  commitArkTSBatch(rootDir, batchKey, etsFiles, full, triggerFile);
1105
+ releaseArkTSBatchHeavyPayload();
1106
+ tryForceGc();
946
1107
  return;
947
1108
  }
948
- persistIncrementalModuleResults(rootDir, queries, index);
1109
+ if (index.fileResults.size > 0) {
1110
+ persistIncrementalModuleResults(rootDir, queries, index);
1111
+ }
949
1112
  if (index.crossFileEdges.length > 0) {
950
1113
  const valid = index.crossFileEdges.filter((e) => index.nodeIds.has(e.source) && index.nodeIds.has(e.target));
951
1114
  if (valid.length > 0) {
@@ -955,6 +1118,8 @@ async function runArkTSBatchIncrementalModules(rootDir, queries, triggerFile, et
955
1118
  // Unique key so a partial index is never mistaken for a full-project cache hit.
956
1119
  const batchKey = `incremental:${moduleSrcPaths.join(',')}`;
957
1120
  commitArkTSBatch(rootDir, batchKey, etsFiles, index, triggerFile);
1121
+ releaseArkTSBatchHeavyPayload();
1122
+ tryForceGc();
958
1123
  process.stderr.write(`\n\x1b[33m ArkTS: incremental module reindex done (${moduleSrcPaths.join(', ')})\x1b[0m\n`);
959
1124
  }
960
1125
  finally {
@@ -965,6 +1130,10 @@ async function runArkTSBatchIncrementalModules(rootDir, queries, triggerFile, et
965
1130
  function isArkTSBatchPersisted(filePath) {
966
1131
  return batchPersistedPaths.has(normIndexPath(filePath));
967
1132
  }
1133
+ /** True after a successful ArkTS batch was committed to SQLite this index run. */
1134
+ function isArkTSBatchCommitted() {
1135
+ return batchBuildCommitted;
1136
+ }
968
1137
  /** Clear cached batch state (tests and full re-index). */
969
1138
  function resetArkTSBatch() {
970
1139
  persistedBatch = null;
@@ -973,6 +1142,16 @@ function resetArkTSBatch() {
973
1142
  batchBuildCommitted = false;
974
1143
  arktsIndexNotices = [];
975
1144
  }
1145
+ /**
1146
+ * After parse workers no longer need skip-path metadata beyond
1147
+ * `batchPersistedPaths`, drop the heavy path-list / batchKey strings.
1148
+ */
1149
+ function shrinkArkTSBatchPostParse() {
1150
+ if (!persistedBatch)
1151
+ return;
1152
+ persistedBatch.etsFiles = [];
1153
+ persistedBatch.batchKey = '';
1154
+ }
976
1155
  class ArkTSExtractor {
977
1156
  filePath;
978
1157
  constructor(filePath, _source) {
@@ -1035,8 +1214,33 @@ const FIELD_CATEGORY = {
1035
1214
  };
1036
1215
  /** {@link ModifierType.CONST} — not re-exported from the arkanalyzer npm entry. */
1037
1216
  const MODIFIER_CONST = 128;
1217
+ /**
1218
+ * Project-relative path for an ArkAnalyzer absolute file path.
1219
+ *
1220
+ * ArkAnalyzer returns realpath'd absolutes (macOS: `/private/var/...`), while
1221
+ * HomeGraph's `rootDir` from `mkdtemp` / callers is often the unresolved form
1222
+ * (`/var/...`). Without canonicalizing both sides, `path.relative` escapes the
1223
+ * project (`../../../../private/var/.../file.ets`) and every file is skipped
1224
+ * by the `scanned` set → empty batch / "no indexed files".
1225
+ */
1038
1226
  function normalizeRelPath(rootDir, absolutePath) {
1039
- return path.relative(rootDir, absolutePath).replace(/\\/g, '/');
1227
+ const root = realpathExisting(rootDir);
1228
+ const abs = realpathExisting(absolutePath);
1229
+ return path.relative(root, abs).replace(/\\/g, '/');
1230
+ }
1231
+ /** `realpathSync` when the path (or its dirname) exists; otherwise `path.resolve`. */
1232
+ function realpathExisting(p) {
1233
+ try {
1234
+ return fs.realpathSync(p);
1235
+ }
1236
+ catch {
1237
+ try {
1238
+ return path.join(fs.realpathSync(path.dirname(p)), path.basename(p));
1239
+ }
1240
+ catch {
1241
+ return path.resolve(p);
1242
+ }
1243
+ }
1040
1244
  }
1041
1245
  /** Map ArkAnalyzer synthetic files (e.g. @dummyFile) to our virtual indexed path. */
1042
1246
  function resolveArkanalyzerVirtualPath(arkFile) {
@@ -1492,6 +1696,52 @@ function stateDecoratorKinds(field) {
1492
1696
  return [];
1493
1697
  return [...decs].map((d) => d.getKind()).filter(Boolean);
1494
1698
  }
1699
+ /** All field decorators with optional string args (Provide('theme') → theme). */
1700
+ function fieldDecoratorEntries(field) {
1701
+ const out = [];
1702
+ const seen = new Set();
1703
+ const push = (kind, arg) => {
1704
+ if (!kind || seen.has(kind))
1705
+ return;
1706
+ seen.add(kind);
1707
+ out.push(arg ? { kind, arg } : { kind });
1708
+ };
1709
+ try {
1710
+ for (const d of field.getDecorators?.() ?? []) {
1711
+ push(d.getKind(), (0, migrate_semantics_1.parseDecoratorArgFromContent)(d.getContent?.() ?? ''));
1712
+ }
1713
+ }
1714
+ catch {
1715
+ /* ignore */
1716
+ }
1717
+ if (out.length === 0) {
1718
+ for (const kind of stateDecoratorKinds(field))
1719
+ push(kind);
1720
+ }
1721
+ return out;
1722
+ }
1723
+ /** Encoded decorators list for node.decorators (bare kinds + Kind@arg). */
1724
+ function fieldDecoratorsEncoded(field) {
1725
+ return (0, migrate_semantics_1.encodeDecoratorEntries)(fieldDecoratorEntries(field));
1726
+ }
1727
+ function modelDecoratorEntries(model) {
1728
+ const out = [];
1729
+ const seen = new Set();
1730
+ try {
1731
+ for (const d of model.getDecorators?.() ?? []) {
1732
+ const kind = d.getKind();
1733
+ if (!kind || seen.has(kind))
1734
+ continue;
1735
+ seen.add(kind);
1736
+ const arg = (0, migrate_semantics_1.parseDecoratorArgFromContent)(d.getContent?.() ?? '');
1737
+ out.push(arg ? { kind, arg } : { kind });
1738
+ }
1739
+ }
1740
+ catch {
1741
+ /* ignore */
1742
+ }
1743
+ return out;
1744
+ }
1495
1745
  /** Child @Prop / @Link — edge `via` uses the decorator name as-is. */
1496
1746
  function stateTransferViaForField(field) {
1497
1747
  for (const kind of stateDecoratorKinds(field)) {
@@ -1838,12 +2088,12 @@ function indexViewTreeForClass(ctx, cls, result, relativePath) {
1838
2088
  if (!buildId)
1839
2089
  return;
1840
2090
  const seen = new Set();
1841
- const link = (source, target, kind, via, line) => {
2091
+ const link = (source, target, kind, via, line, extraMeta) => {
1842
2092
  const key = `${source}>${target}>${kind}>${via}`;
1843
2093
  if (seen.has(key))
1844
2094
  return;
1845
2095
  seen.add(key);
1846
- ctx.addEdge(result, source, target, kind, relativePath, via, line);
2096
+ ctx.addEdge(result, source, target, kind, relativePath, via, line, extraMeta);
1847
2097
  };
1848
2098
  for (const [field] of viewTree.getStateValues()) {
1849
2099
  const fieldId = ctx.resolveFieldNodeId(field);
@@ -1911,32 +2161,62 @@ function indexViewTreeForClass(ctx, cls, result, relativePath) {
1911
2161
  // Skip this node's signature edge; keep walking siblings/children.
1912
2162
  }
1913
2163
  }
1914
- if (node.stateValuesTransfer) {
1915
- for (const [childField, parentValue] of node.stateValuesTransfer) {
1916
- const childFieldId = ctx.resolveFieldNodeId(childField);
1917
- if (parentValue instanceof arkanalyzer_1.ArkField) {
1918
- const via = stateTransferViaForField(childField);
1919
- if (!via || !childFieldId)
1920
- continue;
1921
- ctx.markFieldStateDecorators?.(childField, childFieldId, result);
1922
- const parentFieldId = ctx.resolveFieldNodeId(parentValue);
1923
- if (parentFieldId) {
1924
- ctx.markFieldStateDecorators?.(parentValue, parentFieldId, result);
1925
- link(parentFieldId, childFieldId, 'references', via, childField.getOriginFullPosition()?.getFirstLine() ?? 1);
1926
- }
2164
+ // Data passages for migrate snapshot (+ Prop/Link edges for explore).
2165
+ try {
2166
+ const passages = (0, migrate_passage_1.collectPassagesForViewTreeNode)(node, ctx.scene, cls);
2167
+ for (const p of passages) {
2168
+ const toId = ctx.resolveFieldNodeId(p.toField);
2169
+ if (!toId)
2170
+ continue;
2171
+ ctx.markFieldStateDecorators?.(p.toField, toId, result);
2172
+ let fromId = null;
2173
+ if (p.fromField) {
2174
+ fromId = ctx.resolveFieldNodeId(p.fromField);
2175
+ if (fromId)
2176
+ ctx.markFieldStateDecorators?.(p.fromField, fromId, result);
1927
2177
  }
1928
- else if (parentValue instanceof arkanalyzer_1.ArkMethod) {
1929
- let builderId = ctx.ensureMethodNode(parentValue, relativePath, result, classNodeId);
1930
- if (!builderId) {
1931
- try {
1932
- builderId = ctx.resolveMethodIdBySig(parentValue.getSignature());
1933
- }
1934
- catch {
1935
- builderId = null;
2178
+ if (!fromId && p.fromIsParentComponent)
2179
+ fromId = classNodeId;
2180
+ if (!fromId)
2181
+ continue;
2182
+ const line = p.toField.getOriginFullPosition()?.getFirstLine() ?? 1;
2183
+ link(fromId, toId, 'references', p.via || 'data-passage', line, {
2184
+ passageType: p.passageType,
2185
+ valueType: p.valueType,
2186
+ forcesMigration: p.forcesMigration,
2187
+ parentExpression: p.parentExpression,
2188
+ });
2189
+ }
2190
+ }
2191
+ catch {
2192
+ // Fall back to legacy Prop/Link-only transfer below.
2193
+ if (node.stateValuesTransfer) {
2194
+ for (const [childField, parentValue] of node.stateValuesTransfer) {
2195
+ const childFieldId = ctx.resolveFieldNodeId(childField);
2196
+ if (parentValue instanceof arkanalyzer_1.ArkField) {
2197
+ const via = stateTransferViaForField(childField);
2198
+ if (!via || !childFieldId)
2199
+ continue;
2200
+ ctx.markFieldStateDecorators?.(childField, childFieldId, result);
2201
+ const parentFieldId = ctx.resolveFieldNodeId(parentValue);
2202
+ if (parentFieldId) {
2203
+ ctx.markFieldStateDecorators?.(parentValue, parentFieldId, result);
2204
+ link(parentFieldId, childFieldId, 'references', via, childField.getOriginFullPosition()?.getFirstLine() ?? 1);
1936
2205
  }
1937
2206
  }
1938
- if (childFieldId && builderId) {
1939
- link(builderId, childFieldId, 'references', 'builder-param', childField.getOriginFullPosition()?.getFirstLine() ?? 1);
2207
+ else if (parentValue instanceof arkanalyzer_1.ArkMethod) {
2208
+ let builderId = ctx.ensureMethodNode(parentValue, relativePath, result, classNodeId);
2209
+ if (!builderId) {
2210
+ try {
2211
+ builderId = ctx.resolveMethodIdBySig(parentValue.getSignature());
2212
+ }
2213
+ catch {
2214
+ builderId = null;
2215
+ }
2216
+ }
2217
+ if (childFieldId && builderId) {
2218
+ link(builderId, childFieldId, 'references', 'builder-param', childField.getOriginFullPosition()?.getFirstLine() ?? 1);
2219
+ }
1940
2220
  }
1941
2221
  }
1942
2222
  }
@@ -1992,6 +2272,12 @@ class ArkTSAdapter {
1992
2272
  crossFileEdges = [];
1993
2273
  /** Per-module `@dummyMain@…` created during analyseByModule (for scene aggregator). */
1994
2274
  moduleDummyMains = [];
2275
+ /**
2276
+ * Local import binding names from `lib*.so` per relative file path.
2277
+ * Filled before method bodies are scanned so `multimodalinput.getTidByName(`
2278
+ * can emit NAPI call refs (not only photos-style `Class_method` names).
2279
+ */
2280
+ soImportBindingsByFile = new Map();
1995
2281
  constructor(rootDir, scannedFiles) {
1996
2282
  this.rootDir = rootDir;
1997
2283
  this.scanned = new Set(scannedFiles);
@@ -2028,6 +2314,9 @@ class ArkTSAdapter {
2028
2314
  this.stitchModuleCfgCallsAndRefs(files, moduleFiles);
2029
2315
  // Drop live IR object keys so ArkAnalyzer can GC unloaded modules; keep sig→id maps.
2030
2316
  this.releaseLiveIrMaps();
2317
+ // Edges for this module are already in fileResults / crossFileEdges — drop
2318
+ // the string dedupe set so it does not grow across the whole project.
2319
+ this.emittedEdgeKeys.clear();
2031
2320
  }
2032
2321
  /**
2033
2322
  * Index symbols from already-loaded non-target modules into sig/name maps only.
@@ -2466,6 +2755,30 @@ class ArkTSAdapter {
2466
2755
  errors: [...errors],
2467
2756
  };
2468
2757
  }
2758
+ /**
2759
+ * Remove and return ExtractionResults for the given paths so callers can
2760
+ * persist+GC mid-Scene (streaming modular build). Keeps sig→id maps / nodeIds.
2761
+ */
2762
+ takeFileResults(paths) {
2763
+ const out = new Map();
2764
+ for (const raw of paths) {
2765
+ const p = normIndexPath(raw);
2766
+ const result = this.fileResults.get(p);
2767
+ if (!result)
2768
+ continue;
2769
+ out.set(p, result);
2770
+ this.fileResults.delete(p);
2771
+ }
2772
+ return out;
2773
+ }
2774
+ /** Re-insert a previously taken file result (stream persist failure recovery). */
2775
+ putFileResult(filePath, result) {
2776
+ this.fileResults.set(normIndexPath(filePath), result);
2777
+ }
2778
+ /** All paths currently held in fileResults (for final flush). */
2779
+ fileResultPaths() {
2780
+ return [...this.fileResults.keys()];
2781
+ }
2469
2782
  indexScannedFile(arkFile) {
2470
2783
  const relativePath = normalizeRelPath(this.rootDir, arkFile.getFilePath());
2471
2784
  if (!this.scanned.has(relativePath))
@@ -2498,6 +2811,70 @@ class ArkTSAdapter {
2498
2811
  }
2499
2812
  }
2500
2813
  }
2814
+ this.indexObservedClassRefs();
2815
+ }
2816
+ /**
2817
+ * State / Param fields whose type names an @Observed / @ObservedV2 class →
2818
+ * `references` via `observed-ref` (arkui-migrate).
2819
+ */
2820
+ indexObservedClassRefs() {
2821
+ const observedByName = new Map();
2822
+ for (const [, result] of this.fileResults) {
2823
+ for (const n of result.nodes) {
2824
+ if (n.kind !== 'class' && n.kind !== 'struct')
2825
+ continue;
2826
+ const decs = n.decorators ?? [];
2827
+ if (!decs.some((d) => migrate_semantics_1.OBSERVATION_DECORATORS.has(d.split('@')[0])))
2828
+ continue;
2829
+ const arr = observedByName.get(n.name) ?? [];
2830
+ arr.push(n.id);
2831
+ observedByName.set(n.name, arr);
2832
+ }
2833
+ }
2834
+ if (observedByName.size === 0)
2835
+ return;
2836
+ for (const [relativePath, result] of this.fileResults) {
2837
+ for (const field of result.nodes) {
2838
+ if (field.kind !== 'property' && field.kind !== 'field')
2839
+ continue;
2840
+ const typeStr = field.signature ?? '';
2841
+ const bare = typeStr.replace(/<.*>/, '').split(/[.\s|]/).filter(Boolean).pop();
2842
+ if (!bare)
2843
+ continue;
2844
+ const targets = observedByName.get(bare);
2845
+ if (!targets || targets.length === 0)
2846
+ continue;
2847
+ // Prefer unique name; if ambiguous, same-file first
2848
+ let targetId = targets[0];
2849
+ if (targets.length > 1) {
2850
+ const sameFile = targets.find((id) => {
2851
+ const n = result.nodes.find((x) => x.id === id);
2852
+ return n?.filePath === relativePath;
2853
+ });
2854
+ if (sameFile)
2855
+ targetId = sameFile;
2856
+ else
2857
+ continue; // ambiguous cross-file — skip (precision > recall)
2858
+ }
2859
+ const edgeKey = `${field.id}\0${targetId}\0observed-ref`;
2860
+ if (this.emittedEdgeKeys.has(edgeKey))
2861
+ continue;
2862
+ this.emittedEdgeKeys.add(edgeKey);
2863
+ const edge = arkEdge(field.id, targetId, 'references', {
2864
+ metadata: {
2865
+ synthesizedBy: 'arkui-migrate',
2866
+ via: 'observed-ref',
2867
+ registeredAt: `${relativePath}:${field.startLine}`,
2868
+ },
2869
+ });
2870
+ if (this.nodeInFile(targetId, relativePath)) {
2871
+ result.edges.push(edge);
2872
+ }
2873
+ else {
2874
+ this.crossFileEdges.push(edge);
2875
+ }
2876
+ }
2877
+ }
2501
2878
  }
2502
2879
  /** Index ArkAnalyzer's in-scene @dummyMain (DummyMainCreater output), not a HomeGraph synthetic node. */
2503
2880
  indexArkanalyzerDummyMain(dummyMain) {
@@ -2547,6 +2924,8 @@ class ArkTSAdapter {
2547
2924
  }
2548
2925
  const result = this.ensureFileResult(relativePath, lineCount);
2549
2926
  const fileId = `file:${relativePath}`;
2927
+ // Before methods: record `lib*.so` bindings so body NAPI call scan can see them.
2928
+ this.collectSoImportBindings(relativePath, arkFile);
2550
2929
  for (const ns of arkFile.getNamespaces()) {
2551
2930
  this.indexNamespace(relativePath, language, result, ns, fileId);
2552
2931
  }
@@ -2555,6 +2934,59 @@ class ArkTSAdapter {
2555
2934
  this.indexTypeAliases(relativePath, language, result, arkFile, undefined, fileId);
2556
2935
  this.indexModuleLocals(relativePath, language, result, arkFile, undefined, fileId);
2557
2936
  this.indexImports(relativePath, language, result, arkFile, fileId);
2937
+ this.indexStorageApiKeys(relativePath, arkFile, result);
2938
+ }
2939
+ /** AppStorage / LocalStorage / … literal keys → arkui-migrate edges from each component. */
2940
+ indexStorageApiKeys(relativePath, arkFile, result) {
2941
+ let code = '';
2942
+ try {
2943
+ code = arkFile.getCode() ?? '';
2944
+ }
2945
+ catch {
2946
+ return;
2947
+ }
2948
+ if (!code)
2949
+ return;
2950
+ const hits = (0, migrate_semantics_1.scanStorageApiKeys)(code);
2951
+ if (hits.length === 0)
2952
+ return;
2953
+ const componentIds = result.nodes
2954
+ .filter((n) => n.kind === 'component' && n.filePath === relativePath)
2955
+ .map((n) => n.id);
2956
+ if (componentIds.length === 0)
2957
+ return;
2958
+ for (const hit of hits) {
2959
+ const line = (0, migrate_semantics_1.lineOfOffset)(code, hit.index);
2960
+ for (const compId of componentIds) {
2961
+ const edgeKey = `${compId}\0storage-api\0${hit.channel}\0${hit.key}`;
2962
+ if (this.emittedEdgeKeys.has(edgeKey))
2963
+ continue;
2964
+ this.emittedEdgeKeys.add(edgeKey);
2965
+ result.edges.push(arkEdge(compId, compId, 'references', {
2966
+ metadata: {
2967
+ synthesizedBy: 'arkui-migrate',
2968
+ via: 'storage-api',
2969
+ channel: hit.channel,
2970
+ key: hit.key,
2971
+ method: hit.method,
2972
+ registeredAt: `${relativePath}:${line}`,
2973
+ },
2974
+ }));
2975
+ }
2976
+ }
2977
+ }
2978
+ collectSoImportBindings(relativePath, arkFile) {
2979
+ const set = new Set();
2980
+ for (const importInfo of arkFile.getImportInfos()) {
2981
+ const modulePath = importInfo.getFrom();
2982
+ if (!modulePath || !LIB_SO_MODULE_RE.test(modulePath))
2983
+ continue;
2984
+ const clause = importInfo.getImportClauseName();
2985
+ if (clause)
2986
+ set.add(clause);
2987
+ }
2988
+ if (set.size > 0)
2989
+ this.soImportBindingsByFile.set(relativePath, set);
2558
2990
  }
2559
2991
  indexModuleLocals(relativePath, language, result, arkFile, ns, parentId) {
2560
2992
  const declaringClass = ns ? ns.getDefaultClass() : arkFile.getDefaultClass();
@@ -2648,8 +3080,10 @@ class ArkTSAdapter {
2648
3080
  const qn = buildClassQualifiedName(cls);
2649
3081
  const kind = classNodeKind(cls);
2650
3082
  const { line, endLine, col } = linesFromPosition(cls.getOriginFullPosition());
3083
+ const classDecs = (0, migrate_semantics_1.encodeDecoratorEntries)(modelDecoratorEntries(cls));
2651
3084
  const classNode = makeNode(relativePath, language, kind, displayName, qn, line, endLine, col, {
2652
3085
  ...modelModifiersToNodeExtras(cls),
3086
+ ...(classDecs.length > 0 ? { decorators: classDecs } : {}),
2653
3087
  });
2654
3088
  this.addNode(result, classNode);
2655
3089
  this.classToId.set(cls, classNode.id);
@@ -2677,7 +3111,10 @@ class ArkTSAdapter {
2677
3111
  }
2678
3112
  }
2679
3113
  if (cls.hasComponentDecorator()) {
2680
- const componentNode = makeNode(relativePath, language, 'component', displayName, qn, line, endLine, col, { ...modelModifiersToNodeExtras(cls) });
3114
+ const componentNode = makeNode(relativePath, language, 'component', displayName, qn, line, endLine, col, {
3115
+ ...modelModifiersToNodeExtras(cls),
3116
+ ...(classDecs.length > 0 ? { decorators: classDecs } : {}),
3117
+ });
2681
3118
  this.addNode(result, componentNode);
2682
3119
  this.componentToId.set(cls, componentNode.id);
2683
3120
  try {
@@ -2763,10 +3200,10 @@ class ArkTSAdapter {
2763
3200
  return;
2764
3201
  const { line, endLine, col } = linesFromPosition(field.getOriginFullPosition());
2765
3202
  const qn = buildFieldQualifiedName(field);
2766
- const stateDecs = stateDecoratorKinds(field);
3203
+ const encodedDecs = fieldDecoratorsEncoded(field);
2767
3204
  const fieldNode = makeNode(relativePath, language, kind, name, qn, line, endLine, col, {
2768
3205
  signature: field.getType()?.toString(),
2769
- ...(stateDecs.length > 0 ? { decorators: stateDecs } : {}),
3206
+ ...(encodedDecs.length > 0 ? { decorators: encodedDecs } : {}),
2770
3207
  ...modelModifiersToNodeExtras(field),
2771
3208
  });
2772
3209
  this.addNode(result, fieldNode);
@@ -2792,19 +3229,19 @@ class ArkTSAdapter {
2792
3229
  classToId: this.classToId,
2793
3230
  fieldToId: this.fieldToId,
2794
3231
  nodeIds: this.nodeIds,
2795
- addEdge: (result, source, target, kind, callerFile, via, line) => this.addViewTreeEdge(result, source, target, kind, callerFile, via, line),
3232
+ addEdge: (result, source, target, kind, callerFile, via, line, extraMeta) => this.addViewTreeEdge(result, source, target, kind, callerFile, via, line, extraMeta),
2796
3233
  ensureMethodNode: (method, relativePath, result, parentId) => this.ensureMethodNode(method, relativePath, result, parentId),
2797
3234
  resolveClassNodeId: (cls) => this.resolveClassNodeId(cls),
2798
3235
  resolveClassIdBySig: (sig, importer) => this.resolveClassIdBySig(sig, importer),
2799
3236
  resolveMethodIdBySig: (sig, importer) => this.resolveMethodIdBySig(sig, importer),
2800
3237
  resolveFieldNodeId: (field) => this.resolveFieldNodeId(field),
2801
3238
  markFieldStateDecorators: (field, fieldNodeId, result) => {
2802
- const kinds = stateDecoratorKinds(field);
2803
- if (kinds.length === 0)
3239
+ const encoded = fieldDecoratorsEncoded(field);
3240
+ if (encoded.length === 0)
2804
3241
  return;
2805
3242
  const node = result.nodes.find((n) => n.id === fieldNodeId);
2806
3243
  if (node)
2807
- node.decorators = kinds;
3244
+ node.decorators = encoded;
2808
3245
  },
2809
3246
  };
2810
3247
  }
@@ -3028,7 +3465,7 @@ class ArkTSAdapter {
3028
3465
  this.indexMethod(relativePath, 'arkts', result, method, parentId, kind, displayName);
3029
3466
  return this.methodToId.get(method) ?? null;
3030
3467
  }
3031
- addViewTreeEdge(result, source, target, kind, callerFile, via, line) {
3468
+ addViewTreeEdge(result, source, target, kind, callerFile, via, line, extraMeta) {
3032
3469
  const edgeKey = `${source}\0${target}\0${kind}\0${via}`;
3033
3470
  if (this.emittedEdgeKeys.has(edgeKey))
3034
3471
  return;
@@ -3038,6 +3475,7 @@ class ArkTSAdapter {
3038
3475
  synthesizedBy: 'viewtree',
3039
3476
  via,
3040
3477
  registeredAt: `${callerFile}:${line}`,
3478
+ ...(extraMeta ?? {}),
3041
3479
  },
3042
3480
  });
3043
3481
  if (this.nodeInFile(target, callerFile)) {
@@ -3115,17 +3553,14 @@ class ArkTSAdapter {
3115
3553
  return;
3116
3554
  const baseLine = method.getImplOriginFullPosition()?.getFirstLine() ?? 1;
3117
3555
  const seenAtLine = new Set();
3118
- NAPI_MEMBER_CALL_RE.lastIndex = 0;
3119
- let m;
3120
- while ((m = NAPI_MEMBER_CALL_RE.exec(code)) !== null) {
3121
- const symbol = m[1];
3122
- if (!isNapiSymbolName(symbol))
3123
- continue;
3124
- const lineOffset = code.slice(0, m.index).split('\n').length - 1;
3556
+ const pushCall = (symbol, matchIndex) => {
3557
+ if (!symbol)
3558
+ return;
3559
+ const lineOffset = code.slice(0, matchIndex).split('\n').length - 1;
3125
3560
  const line = baseLine + lineOffset;
3126
3561
  const dedupKey = `${symbol}:${line}`;
3127
3562
  if (seenAtLine.has(dedupKey))
3128
- continue;
3563
+ return;
3129
3564
  seenAtLine.add(dedupKey);
3130
3565
  result.unresolvedReferences.push({
3131
3566
  fromNodeId,
@@ -3136,6 +3571,35 @@ class ArkTSAdapter {
3136
3571
  filePath: relativePath,
3137
3572
  language,
3138
3573
  });
3574
+ };
3575
+ // Photos-style `Class_method` on any receiver.
3576
+ NAPI_MEMBER_CALL_RE.lastIndex = 0;
3577
+ let m;
3578
+ while ((m = NAPI_MEMBER_CALL_RE.exec(code)) !== null) {
3579
+ const symbol = m[1];
3580
+ if (!isNapiSymbolName(symbol))
3581
+ continue;
3582
+ pushCall(symbol, m.index);
3583
+ }
3584
+ // `import x from 'libFoo.so'` → `x.draw(` / `x.getTidByName(` (camelCase OK).
3585
+ const soBindings = this.soImportBindingsByFile.get(relativePath);
3586
+ if (soBindings) {
3587
+ for (const binding of soBindings) {
3588
+ const re = new RegExp(`\\b${escapeRegExp(binding)}\\s*\\.\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*\\(`, 'g');
3589
+ while ((m = re.exec(code)) !== null) {
3590
+ pushCall(m[1], m.index);
3591
+ }
3592
+ }
3593
+ // define_class / instance path: `this.inst?.addVerticalRuler(` — receiver is
3594
+ // not the import binding. Only in files that already import a lib*.so.
3595
+ const anyMemberRe = /\?\.\s*([A-Za-z_][A-Za-z0-9_]*)\s*\(|\.\s*([A-Za-z_][A-Za-z0-9_]*)\s*\(/g;
3596
+ while ((m = anyMemberRe.exec(code)) !== null) {
3597
+ const symbol = m[1] ?? m[2];
3598
+ if (!symbol)
3599
+ continue;
3600
+ // Skip photos-style names already handled above; keep camelCase / plain ids.
3601
+ pushCall(symbol, m.index);
3602
+ }
3139
3603
  }
3140
3604
  }
3141
3605
  indexImports(relativePath, language, result, arkFile, fileId) {
@@ -3219,6 +3683,8 @@ function emptyArkTSBatchIndex(errors) {
3219
3683
  * Runs with the #850 liveness watchdog suspended: ArkAnalyzer's native BODIES
3220
3684
  * load between module callbacks routinely exceeds the 60s heartbeat window on
3221
3685
  * large Harmony projects, and there is no JS yield point inside that span.
3686
+ * When `streamQueries` is set, each module's ExtractionResults are written to
3687
+ * SQLite immediately and dropped from RAM (cross-file call edges stay until end).
3222
3688
  */
3223
3689
  function buildArkTSIndexByModule(rootDir, scannedFiles, options) {
3224
3690
  return (0, liveness_watchdog_1.runWithoutLivenessWatchdog)(() => buildArkTSIndexByModuleInner(rootDir, scannedFiles, options));
@@ -3232,6 +3698,13 @@ function buildArkTSIndexByModuleInner(rootDir, scannedFiles, options) {
3232
3698
  let modulesIndexed = 0;
3233
3699
  const targetSrc = options?.targetModuleSrcPaths?.map(normalizeHarmonyModuleSrcPath);
3234
3700
  const targetSrcSet = targetSrc && targetSrc.length > 0 ? new Set(targetSrc) : null;
3701
+ const streamQueries = options?.streamQueries;
3702
+ const streamedPersistedPaths = new Set();
3703
+ const scannedList = [...scannedFiles].map(normIndexPath);
3704
+ const persistOrdinal = {
3705
+ done: 0,
3706
+ totalHint: Math.max(1, scannedList.filter((f) => isArkAnalyzerSourcePath(f)).length),
3707
+ };
3235
3708
  try {
3236
3709
  scene.config(sceneConfig);
3237
3710
  const config = new arkanalyzer_1.ModuleAnalysisConfig();
@@ -3274,18 +3747,83 @@ function buildArkTSIndexByModuleInner(rootDir, scannedFiles, options) {
3274
3747
  return emptyArkTSBatchIndex(errors);
3275
3748
  }
3276
3749
  config.setTargetModuleIds(ids);
3277
- process.stderr.write(`\n\x1b[33m ArkTS: analyseByModule incremental (${ids.length} module(s)→BODIES, deps→SIGNATURES, memoryLimitMB=${memoryLimitMB})...\x1b[0m\n`);
3750
+ process.stderr.write(`\n\x1b[33m ArkTS: analyseByModule incremental (${ids.length} module(s)→BODIES, deps→SIGNATURES, memoryLimitMB=${memoryLimitMB}${streamQueries ? ', streamPersist' : ''})...\x1b[0m\n`);
3278
3751
  }
3279
3752
  else {
3280
3753
  config.setIncludeType(arkanalyzer_1.ModuleType.PROJECT, true);
3281
- process.stderr.write(`\n\x1b[33m ArkTS: analyseByModule (PROJECT→BODIES, deps→SIGNATURES, memoryLimitMB=${memoryLimitMB}, maxOldSpace=${(0, wasm_runtime_flags_1.resolveMaxOldSpaceSizeMb)()})...\x1b[0m\n`);
3754
+ process.stderr.write(`\n\x1b[33m ArkTS: analyseByModule (PROJECT→BODIES, deps→SIGNATURES, memoryLimitMB=${memoryLimitMB}, maxOldSpace=${(0, wasm_runtime_flags_1.resolveMaxOldSpaceSizeMb)()}${streamQueries ? ', streamPersist' : ''})...\x1b[0m\n`);
3282
3755
  }
3283
3756
  scene.analyseByModule((module, scn) => {
3757
+ const moduleFileRels = [...module.getFilesMap().values()]
3758
+ .map((f) => normalizeRelPath(rootDir, f.getFilePath()))
3759
+ .filter((p) => p && isArkAnalyzerSourcePath(p))
3760
+ .map(normIndexPath);
3284
3761
  adapter.indexModule(module, scn);
3762
+ // HomeGraph already captured this module's symbols/edges. Hollow every
3763
+ // currently-loaded module still above INDEX (target + oh_modules /
3764
+ // SIGNATURES deps left in ModuleCache). Topo order means PROJECT deps of
3765
+ // the current target are often already INDEX from their own callback, so
3766
+ // BFS-from-self alone left depHollowed=0; sweeping the cache catches the
3767
+ // fat non-target SIGNATURES that memoryLimitMB rarely evicts under a large
3768
+ // soft budget. INDEX keeps export-reachable shells; loadModule upgrades
3769
+ // when a later target needs SIGNATURES/BODIES.
3770
+ let depHollowed = 0;
3771
+ try {
3772
+ const builder = new arkanalyzer_1.ModuleBuilder(scn);
3773
+ const selfId = scn.getModuleId(module);
3774
+ hollowModuleToIndex(builder, selfId);
3775
+ const cache = scn.getModuleCache();
3776
+ if (cache) {
3777
+ for (const modId of cache.getLoadedModules()) {
3778
+ if (modId === selfId)
3779
+ continue;
3780
+ const depMod = builder.getModule(modId);
3781
+ if (!depMod || depMod.getLoadState() <= arkanalyzer_1.ModuleLoadState.INDEX)
3782
+ continue;
3783
+ hollowModuleToIndex(builder, modId);
3784
+ depHollowed++;
3785
+ }
3786
+ }
3787
+ }
3788
+ catch (e) {
3789
+ const message = e instanceof Error ? e.message : String(e);
3790
+ errors.push({
3791
+ message: `ArkTS post-index INDEX hollow failed for ${module.getModuleName() || module.getModulePath()}: ${message}`,
3792
+ severity: 'warning',
3793
+ });
3794
+ }
3795
+ if (streamQueries) {
3796
+ // Persist this module's file bodies now; keep @dummyFile until finalize.
3797
+ const slicePaths = moduleFileRels.filter((p) => p !== ARKANALYZER_DUMMY_FILE);
3798
+ const slice = adapter.takeFileResults(slicePaths);
3799
+ try {
3800
+ persistAndDropModuleSlice(rootDir, streamQueries, slice, streamedPersistedPaths, persistOrdinal);
3801
+ }
3802
+ catch (e) {
3803
+ const message = e instanceof Error ? e.message : String(e);
3804
+ errors.push({
3805
+ message: `ArkTS stream persist failed for ${module.getModuleName() || module.getModulePath()}: ${message}`,
3806
+ severity: 'error',
3807
+ });
3808
+ throw e;
3809
+ }
3810
+ }
3285
3811
  modulesIndexed++;
3286
3812
  const name = module.getModuleName() || path.basename(module.getModulePath());
3287
- process.stderr.write(`\x1b[33m ArkTS: indexed module ${modulesIndexed}: ${name}\x1b[0m\n`);
3813
+ const st = arkanalyzer_1.ModuleLoadState[module.getLoadState()] ?? String(module.getLoadState());
3814
+ const depNote = depHollowed > 0 ? `, hollowed ${depHollowed} deps` : '';
3815
+ process.stderr.write(`\x1b[33m ArkTS: indexed module ${modulesIndexed}: ${name} → ${st}${depNote}${streamQueries ? ` (streamed ${moduleFileRels.length} files)` : ''}\x1b[0m\n`);
3288
3816
  }, config);
3817
+ if (process.env.HOMEGRAPH_ARKTS_DIAG === '1') {
3818
+ const sceneDiag = scene;
3819
+ const ev = typeof sceneDiag.getModuleEvictionStats === 'function'
3820
+ ? sceneDiag.getModuleEvictionStats()
3821
+ : null;
3822
+ const mu = process.memoryUsage();
3823
+ const cacheN = scene.getModuleCache()?.size?.() ?? scene.getModuleCache()?.getLoadedModules?.()?.length ?? -1;
3824
+ process.stderr.write(`\x1b[36m ArkTS DIAG eviction: ${JSON.stringify(ev)} cacheLoaded=${cacheN} ` +
3825
+ `heapUsedMB=${(mu.heapUsed / 1024 / 1024).toFixed(1)} rssMB=${(mu.rss / 1024 / 1024).toFixed(1)}\x1b[0m\n`);
3826
+ }
3289
3827
  if (modulesIndexed === 0) {
3290
3828
  errors.push({
3291
3829
  message: 'ArkTS analyseByModule found no PROJECT modules (check build-profile.json5); falling back to full Scene build',
@@ -3303,8 +3841,15 @@ function buildArkTSIndexByModuleInner(rootDir, scannedFiles, options) {
3303
3841
  severity: 'warning',
3304
3842
  });
3305
3843
  }
3844
+ if (streamQueries) {
3845
+ const leftover = adapter.takeFileResults(adapter.fileResultPaths());
3846
+ persistAndDropModuleSlice(rootDir, streamQueries, leftover, streamedPersistedPaths, persistOrdinal);
3847
+ }
3306
3848
  const index = adapter.toBatchIndex(errors);
3307
- if (index.fileResults.size === 0) {
3849
+ if (streamQueries) {
3850
+ index.streamedPersistedPaths = streamedPersistedPaths;
3851
+ }
3852
+ if (index.fileResults.size === 0 && streamedPersistedPaths.size === 0) {
3308
3853
  errors.push({
3309
3854
  message: 'ArkTS analyseByModule indexed modules but no scanned source files matched',
3310
3855
  severity: 'warning',
@@ -3323,7 +3868,7 @@ function buildArkTSIndexByModuleInner(rootDir, scannedFiles, options) {
3323
3868
  }
3324
3869
  finally {
3325
3870
  try {
3326
- scene.dispose();
3871
+ releaseArkAnalyzerScene(scene);
3327
3872
  }
3328
3873
  catch {
3329
3874
  // ignore dispose failures
@@ -3349,13 +3894,26 @@ function buildArkTSIndexFull(rootDir, scannedFiles, priorErrors = []) {
3349
3894
  allErrors.push({ message: `ArkTS adapter failed: ${message}`, severity: 'error' });
3350
3895
  return emptyArkTSBatchIndex(allErrors);
3351
3896
  }
3897
+ finally {
3898
+ try {
3899
+ releaseArkAnalyzerScene(scene);
3900
+ }
3901
+ catch {
3902
+ // ignore
3903
+ }
3904
+ }
3352
3905
  });
3353
3906
  }
3354
- function buildArkTSIndex(rootDir, scannedFiles) {
3907
+ function buildArkTSIndex(rootDir, scannedFiles, options) {
3355
3908
  if (shouldUseModularArkTSBuild(rootDir)) {
3356
- const modular = buildArkTSIndexByModule(rootDir, scannedFiles);
3909
+ const modular = buildArkTSIndexByModule(rootDir, scannedFiles, {
3910
+ streamQueries: options?.streamQueries,
3911
+ });
3357
3912
  const fatal = modular.errors.find((e) => e.severity === 'error');
3358
- if (modular.fileResults.size > 0 && !fatal) {
3913
+ const hasOutput = modular.fileResults.size > 0 ||
3914
+ (modular.streamedPersistedPaths?.size ?? 0) > 0 ||
3915
+ modular.nodeIds.size > 0;
3916
+ if (hasOutput && !fatal) {
3359
3917
  return modular;
3360
3918
  }
3361
3919
  // Warnings like "no PROJECT modules" / modular failure → full Scene fallback.
@@ -3520,7 +4078,7 @@ function extractOhosSdkArchive(archivePath) {
3520
4078
  function ohosApiDbFilename(version) {
3521
4079
  return `ohos-api-${version}.db`;
3522
4080
  }
3523
- /** npm package name — one package per API version (e.g. homegraph-ohos-api-db-6.0.1). */
4081
+ /** Metadata / publish id — one package per API version (e.g. homegraph-ohos-api-db-6.0.1). */
3524
4082
  function ohosApiDbPackageName(version) {
3525
4083
  return `homegraph-ohos-api-db-${version}`;
3526
4084
  }
@@ -3668,7 +4226,7 @@ async function indexOhosApiDb(options) {
3668
4226
  }
3669
4227
  }
3670
4228
  // =============================================================================
3671
- // OHOS API db consumer: compileSdkVersion detect, npm fetch, ATTACH for queries
4229
+ // OHOS API db consumer: compileSdkVersion detect, local SDK build, ATTACH
3672
4230
  // =============================================================================
3673
4231
  exports.OHOS_API_FILE_PREFIX = 'ohos-sdk:';
3674
4232
  exports.OHOS_API_VERSION_META = 'ohos_api_version';
@@ -3810,75 +4368,367 @@ function hasEtsUnder(dir, depth) {
3810
4368
  }
3811
4369
  return false;
3812
4370
  }
3813
- /** Install npm package if local db is missing; non-throwing on failure. */
3814
- function ensureOhosApiDb(version) {
3815
- const dbPath = ohosApiDbInstallPath(version);
3816
- if (fs.existsSync(dbPath)) {
4371
+ /** Read platformVersion from sdk-pkg.json / oh-uni-package.json under an SDK home. */
4372
+ function readOhosSdkPlatformVersion(sdkHome) {
4373
+ const pkgPaths = [
4374
+ path.join(sdkHome, 'sdk-pkg.json'),
4375
+ path.join(sdkHome, 'openharmony', 'ets', 'oh-uni-package.json'),
4376
+ path.join(sdkHome, 'openharmony', 'oh-uni-package.json'),
4377
+ ];
4378
+ for (const pkgPath of pkgPaths) {
4379
+ if (!fs.existsSync(pkgPath))
4380
+ continue;
4381
+ try {
4382
+ const raw = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
4383
+ const data = raw.data && typeof raw.data === 'object'
4384
+ ? raw.data
4385
+ : raw;
4386
+ const v = normalizeOhosApiVersion(data.platformVersion) ||
4387
+ normalizeOhosApiVersion(data.version) ||
4388
+ normalizeOhosApiVersion(raw.platformVersion) ||
4389
+ normalizeOhosApiVersion(raw.version);
4390
+ if (v)
4391
+ return v;
4392
+ }
4393
+ catch {
4394
+ /* try next */
4395
+ }
4396
+ }
4397
+ return normalizeOhosApiVersion(path.basename(sdkHome));
4398
+ }
4399
+ /** Expand a user/env path into concrete sdk/default-style homes that contain openharmony/ets. */
4400
+ function expandLocalOhosSdkHomes(input) {
4401
+ const resolved = path.resolve(input);
4402
+ if (!fs.existsSync(resolved))
4403
+ return [];
4404
+ const homes = [];
4405
+ const push = (p) => {
4406
+ if (hasOpenHarmonyEts(p))
4407
+ homes.push(p);
4408
+ };
4409
+ push(resolved);
4410
+ push(path.join(resolved, 'default'));
4411
+ push(path.join(resolved, 'sdk', 'default'));
4412
+ const viaFind = findOhosSdkHome(resolved);
4413
+ if (viaFind)
4414
+ homes.push(viaFind);
4415
+ let entries;
4416
+ try {
4417
+ entries = fs.readdirSync(resolved, { withFileTypes: true });
4418
+ }
4419
+ catch {
4420
+ return [...new Set(homes)];
4421
+ }
4422
+ for (const entry of entries) {
4423
+ if (!entry.isDirectory())
4424
+ continue;
4425
+ if (entry.name === 'node_modules' || entry.name === '.git')
4426
+ continue;
4427
+ const child = path.join(resolved, entry.name);
4428
+ push(child);
4429
+ push(path.join(child, 'default'));
4430
+ }
4431
+ return [...new Set(homes)];
4432
+ }
4433
+ function localOhosSdkSearchRoots() {
4434
+ const roots = [];
4435
+ for (const key of ['HOMEGRAPH_OHOS_SDK', 'OHOS_SDK_HOME', 'DEVECO_SDK_HOME', 'HOS_SDK_HOME']) {
4436
+ const v = process.env[key]?.trim();
4437
+ if (v)
4438
+ roots.push(v);
4439
+ }
4440
+ const home = os.homedir();
4441
+ if (process.platform === 'win32') {
4442
+ const lad = process.env.LOCALAPPDATA;
4443
+ if (lad) {
4444
+ roots.push(path.join(lad, 'OpenHarmony', 'Sdk'));
4445
+ roots.push(path.join(lad, 'Huawei', 'Sdk'));
4446
+ }
4447
+ roots.push(path.join('C:\\', 'Program Files', 'Huawei', 'DevEco Studio', 'sdk'));
4448
+ roots.push(path.join(home, 'Huawei', 'Sdk'));
4449
+ roots.push(path.join(home, 'OpenHarmony', 'Sdk'));
4450
+ }
4451
+ else if (process.platform === 'darwin') {
4452
+ roots.push(path.join(home, 'Library', 'Huawei', 'Sdk'));
4453
+ roots.push(path.join(home, 'Library', 'OpenHarmony', 'Sdk'));
4454
+ roots.push('/Applications/DevEco-Studio.app/Contents/sdk');
4455
+ }
4456
+ else {
4457
+ roots.push(path.join(home, 'Huawei', 'Sdk'));
4458
+ roots.push(path.join(home, 'OpenHarmony', 'Sdk'));
4459
+ }
4460
+ return roots;
4461
+ }
4462
+ /** Discover local DevEco / OpenHarmony SDK homes (env vars + common install paths). */
4463
+ function discoverLocalOhosSdkCandidates() {
4464
+ const out = [];
4465
+ const seen = new Set();
4466
+ for (const root of localOhosSdkSearchRoots()) {
4467
+ for (const sdkHome of expandLocalOhosSdkHomes(root)) {
4468
+ if (seen.has(sdkHome))
4469
+ continue;
4470
+ seen.add(sdkHome);
4471
+ out.push({ sdkHome, version: readOhosSdkPlatformVersion(sdkHome) });
4472
+ }
4473
+ }
4474
+ return out;
4475
+ }
4476
+ /**
4477
+ * Pick a local SDK home.
4478
+ * When `preferredVersion` is set, require an exact platformVersion match.
4479
+ * When unset, return the first candidate that has a readable version (else any).
4480
+ */
4481
+ function findLocalOhosSdkForVersion(preferredVersion) {
4482
+ const candidates = discoverLocalOhosSdkCandidates();
4483
+ if (candidates.length === 0)
4484
+ return null;
4485
+ if (preferredVersion) {
4486
+ return candidates.find((c) => c.version === preferredVersion) ?? null;
4487
+ }
4488
+ return candidates.find((c) => c.version) ?? candidates[0];
4489
+ }
4490
+ /**
4491
+ * Resolve the API db version for a project: build-profile / env first, else local SDK.
4492
+ */
4493
+ function resolveOhosApiVersionForProject(projectRoot) {
4494
+ const fromProject = detectOhosCompileSdkVersion(projectRoot);
4495
+ if (fromProject)
4496
+ return fromProject;
4497
+ return findLocalOhosSdkForVersion(null)?.version ?? null;
4498
+ }
4499
+ function attachOhosApiDbBinding(queries, binding) {
4500
+ try {
4501
+ queries.attachOhosApiDb(binding.dbPath);
4502
+ queries.setMetadata(exports.OHOS_API_VERSION_META, binding.version);
4503
+ queries.setMetadata(exports.OHOS_API_DB_PATH_META, binding.dbPath);
4504
+ return binding;
4505
+ }
4506
+ catch (err) {
4507
+ const msg = err instanceof Error ? err.message : String(err);
3817
4508
  return {
3818
- version,
3819
- dbPath,
3820
- packageName: ohosApiDbPackageName(version),
3821
- installed: false,
4509
+ code: 'ohos_api_db_missing',
4510
+ message: `Failed to attach OHOS API db at ${binding.dbPath}: ${msg}`,
3822
4511
  };
3823
4512
  }
4513
+ }
4514
+ /** Last-resort prebuilt npm package when the project's API version cannot be obtained. */
4515
+ const OHOS_API_FALLBACK_VERSION = '6.1.0';
4516
+ function runNpmPack(args, cwd) {
4517
+ const inv = process.platform === 'win32'
4518
+ ? { cmd: 'cmd.exe', args: ['/d', '/s', '/c', ['npm', ...args].join(' ')] }
4519
+ : { cmd: 'npm', args };
4520
+ (0, child_process_1.execFileSync)(inv.cmd, inv.args, {
4521
+ cwd,
4522
+ stdio: 'pipe',
4523
+ timeout: 180_000,
4524
+ windowsHide: true,
4525
+ env: process.env,
4526
+ });
4527
+ }
4528
+ /**
4529
+ * Fetch `homegraph-ohos-api-db-<version>` from npm and copy the db into
4530
+ * `~/.homegraph/api/`. Returns null when downloads are disabled, the package
4531
+ * is missing, or install fails. Opt out: HOMEGRAPH_OHOS_API_NO_DOWNLOAD=1.
4532
+ */
4533
+ function tryDownloadOhosApiDbFromNpm(version) {
4534
+ if (process.env.HOMEGRAPH_OHOS_API_NO_DOWNLOAD === '1')
4535
+ return null;
4536
+ const dbPath = ohosApiDbInstallPath(version);
3824
4537
  const packageName = ohosApiDbPackageName(version);
3825
- const installDir = fs.mkdtempSync(path.join(os.tmpdir(), 'hg-ohos-npm-'));
4538
+ if (fs.existsSync(dbPath)) {
4539
+ return { version, dbPath, packageName, installed: false };
4540
+ }
4541
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'hg-ohos-api-npm-'));
3826
4542
  try {
3827
- (0, child_process_1.execFileSync)('npm', ['install', '--no-save', packageName], {
3828
- cwd: installDir,
4543
+ runNpmPack(['pack', packageName, '--silent'], tmp);
4544
+ const tgz = fs.readdirSync(tmp).find((f) => f.endsWith('.tgz'));
4545
+ if (!tgz)
4546
+ return null;
4547
+ const extractDir = path.join(tmp, 'extract');
4548
+ fs.mkdirSync(extractDir, { recursive: true });
4549
+ (0, child_process_1.execFileSync)('tar', ['-xzf', path.join(tmp, tgz), '-C', extractDir], {
3829
4550
  stdio: 'pipe',
3830
- timeout: 180_000,
3831
- env: { ...process.env, npm_config_loglevel: 'error' },
4551
+ timeout: 60_000,
4552
+ windowsHide: true,
3832
4553
  });
4554
+ const dbName = ohosApiDbFilename(version);
4555
+ const src = [
4556
+ path.join(extractDir, 'package', dbName),
4557
+ path.join(extractDir, dbName),
4558
+ ].find((p) => fs.existsSync(p)) ?? null;
4559
+ if (!src)
4560
+ return null;
4561
+ fs.mkdirSync(path.dirname(dbPath), { recursive: true });
4562
+ fs.copyFileSync(src, dbPath);
4563
+ return { version, dbPath, packageName, installed: true };
3833
4564
  }
3834
- catch (err) {
3835
- const msg = err instanceof Error ? err.message : String(err);
3836
- return {
3837
- code: 'ohos_api_install_failed',
3838
- message: `Could not fetch ${packageName} (${msg}). ` +
3839
- 'Explore will use project code only until the matching API db is available.',
3840
- };
4565
+ catch {
4566
+ return null;
3841
4567
  }
3842
4568
  finally {
3843
- fs.rmSync(installDir, { recursive: true, force: true });
4569
+ try {
4570
+ fs.rmSync(tmp, { recursive: true, force: true });
4571
+ }
4572
+ catch {
4573
+ /* ignore */
4574
+ }
3844
4575
  }
3845
- if (!fs.existsSync(dbPath)) {
4576
+ }
4577
+ /**
4578
+ * Build (or reuse) ~/.homegraph/api/ohos-api-<version>.db from a local SDK,
4579
+ * then npm prebuilts. Never throws — missing SDK / build / download failure
4580
+ * returns a warning.
4581
+ *
4582
+ * Order: existing db → local SDK build → npm pack for that version →
4583
+ * npm pack homegraph-ohos-api-db-6.1.0 fallback.
4584
+ */
4585
+ async function ensureOhosApiDb(version, options = {}) {
4586
+ const dbPath = ohosApiDbInstallPath(version);
4587
+ const packageName = ohosApiDbPackageName(version);
4588
+ // 1. Local db for the requested version
4589
+ if (fs.existsSync(dbPath)) {
4590
+ return { version, dbPath, packageName, installed: false };
4591
+ }
4592
+ if (options.build === false || process.env.HOMEGRAPH_OHOS_API_SKIP === '1') {
3846
4593
  return {
3847
4594
  code: 'ohos_api_db_missing',
3848
- message: `${packageName} was fetched but ${ohosApiDbFilename(version)} is missing under ~/.homegraph/api/. ` +
4595
+ message: `${ohosApiDbFilename(version)} is not under ~/.homegraph/api/ yet. ` +
4596
+ 'Re-run `homegraph init -i` / `homegraph index` with a local OHOS SDK, or `homegraph index-api <sdk>`. ' +
3849
4597
  'Explore will use project code only.',
3850
4598
  };
3851
4599
  }
3852
- return { version, dbPath, packageName, installed: true };
4600
+ // 2. Build from a local DevEco / OpenHarmony SDK
4601
+ let sdkHome = null;
4602
+ if (options.sdkHomeHint) {
4603
+ const hintHomes = expandLocalOhosSdkHomes(options.sdkHomeHint);
4604
+ const match = hintHomes.find((h) => readOhosSdkPlatformVersion(h) === version) ?? hintHomes[0];
4605
+ if (match && hasOpenHarmonyEts(match))
4606
+ sdkHome = match;
4607
+ }
4608
+ if (!sdkHome) {
4609
+ sdkHome = findLocalOhosSdkForVersion(version)?.sdkHome ?? null;
4610
+ }
4611
+ if (sdkHome) {
4612
+ fs.mkdirSync(path.dirname(dbPath), { recursive: true });
4613
+ options.onProgress?.({
4614
+ phase: 'ohos-api',
4615
+ current: 0,
4616
+ total: 1,
4617
+ currentFile: version,
4618
+ });
4619
+ try {
4620
+ const result = await indexOhosApiDb({
4621
+ sdkHome,
4622
+ version,
4623
+ outputPath: dbPath,
4624
+ onProgress: options.onProgress
4625
+ ? (progress) => {
4626
+ options.onProgress?.({
4627
+ phase: 'ohos-api',
4628
+ current: progress.current,
4629
+ total: progress.total || 1,
4630
+ currentFile: progress.currentFile
4631
+ ? `${version} ${path.basename(progress.currentFile)}`
4632
+ : version,
4633
+ subphase: progress.subphase,
4634
+ });
4635
+ }
4636
+ : undefined,
4637
+ });
4638
+ if (result.success && fs.existsSync(dbPath)) {
4639
+ options.onProgress?.({
4640
+ phase: 'ohos-api',
4641
+ current: 1,
4642
+ total: 1,
4643
+ currentFile: version,
4644
+ });
4645
+ return { version, dbPath, packageName, installed: true };
4646
+ }
4647
+ try {
4648
+ fs.unlinkSync(dbPath);
4649
+ }
4650
+ catch {
4651
+ /* ignore */
4652
+ }
4653
+ }
4654
+ catch {
4655
+ try {
4656
+ if (fs.existsSync(dbPath))
4657
+ fs.unlinkSync(dbPath);
4658
+ }
4659
+ catch {
4660
+ /* ignore */
4661
+ }
4662
+ }
4663
+ }
4664
+ // 3. Download prebuilt for the requested version from npm
4665
+ const fromNpm = tryDownloadOhosApiDbFromNpm(version);
4666
+ if (fromNpm)
4667
+ return fromNpm;
4668
+ // 4. Last resort: homegraph-ohos-api-db-6.1.0
4669
+ if (version !== OHOS_API_FALLBACK_VERSION) {
4670
+ const fallbackPath = ohosApiDbInstallPath(OHOS_API_FALLBACK_VERSION);
4671
+ if (fs.existsSync(fallbackPath)) {
4672
+ return {
4673
+ version: OHOS_API_FALLBACK_VERSION,
4674
+ dbPath: fallbackPath,
4675
+ packageName: ohosApiDbPackageName(OHOS_API_FALLBACK_VERSION),
4676
+ installed: false,
4677
+ };
4678
+ }
4679
+ const fallback = tryDownloadOhosApiDbFromNpm(OHOS_API_FALLBACK_VERSION);
4680
+ if (fallback)
4681
+ return fallback;
4682
+ }
4683
+ return {
4684
+ code: 'ohos_api_sdk_missing',
4685
+ message: `OHOS API db for ${version} unavailable (no local SDK build, npm ${packageName} failed` +
4686
+ (version !== OHOS_API_FALLBACK_VERSION
4687
+ ? `, and fallback ${ohosApiDbPackageName(OHOS_API_FALLBACK_VERSION)} failed`
4688
+ : '') +
4689
+ '). Set OHOS_SDK_HOME / DEVECO_SDK_HOME or install the matching DevEco SDK. ' +
4690
+ 'SDK API lookup disabled; project indexing continues normally.',
4691
+ };
4692
+ }
4693
+ /**
4694
+ * Attach a prebuilt API db if present — never builds. Used on open / wireLayers.
4695
+ */
4696
+ function attachExistingOhosApiDbForProject(projectRoot, queries, languages) {
4697
+ if (!isOhosArktsProject(projectRoot, languages))
4698
+ return null;
4699
+ const version = resolveOhosApiVersionForProject(projectRoot);
4700
+ if (!version)
4701
+ return null;
4702
+ const dbPath = ohosApiDbInstallPath(version);
4703
+ if (!fs.existsSync(dbPath))
4704
+ return null;
4705
+ return attachOhosApiDbBinding(queries, {
4706
+ version,
4707
+ dbPath,
4708
+ packageName: ohosApiDbPackageName(version),
4709
+ installed: false,
4710
+ });
3853
4711
  }
3854
- /** Detect version, ensure db, persist metadata, ATTACH — never throws. */
3855
- function bindOhosApiDbForProject(projectRoot, queries, languages) {
4712
+ /**
4713
+ * Detect version, build from local SDK when needed, persist metadata, ATTACH — never throws.
4714
+ * Pass `{ build: true }` (default) after init/index; `{ build: false }` on open.
4715
+ */
4716
+ async function bindOhosApiDbForProject(projectRoot, queries, languages, options = {}) {
3856
4717
  if (!isOhosArktsProject(projectRoot, languages))
3857
4718
  return null;
3858
- const version = detectOhosCompileSdkVersion(projectRoot);
4719
+ const version = resolveOhosApiVersionForProject(projectRoot);
3859
4720
  if (!version) {
3860
4721
  return {
3861
4722
  code: 'ohos_api_version_unknown',
3862
- message: 'HarmonyOS project detected but compileSdkVersion not found in build-profile.json5. ' +
4723
+ message: 'HarmonyOS project detected but compileSdkVersion was not found in build-profile.json5 ' +
4724
+ 'and no local OHOS SDK was discovered (set OHOS_SDK_HOME / DEVECO_SDK_HOME). ' +
3863
4725
  'SDK API lookup disabled; project indexing continues normally.',
3864
4726
  };
3865
4727
  }
3866
- const ensured = ensureOhosApiDb(version);
4728
+ const ensured = await ensureOhosApiDb(version, options);
3867
4729
  if ('code' in ensured)
3868
4730
  return ensured;
3869
- try {
3870
- queries.attachOhosApiDb(ensured.dbPath);
3871
- queries.setMetadata(exports.OHOS_API_VERSION_META, ensured.version);
3872
- queries.setMetadata(exports.OHOS_API_DB_PATH_META, ensured.dbPath);
3873
- }
3874
- catch (err) {
3875
- const msg = err instanceof Error ? err.message : String(err);
3876
- return {
3877
- code: 'ohos_api_db_missing',
3878
- message: `Failed to attach OHOS API db at ${ensured.dbPath}: ${msg}`,
3879
- };
3880
- }
3881
- return ensured;
4731
+ return attachOhosApiDbBinding(queries, ensured);
3882
4732
  }
3883
4733
  /** Re-ATTACH from project metadata after reopen / new QueryBuilder. */
3884
4734
  function restoreOhosApiDbAttach(queries) {