homegraph 1.4.1 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (483) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +305 -268
  3. package/dist/bin/command-supervision.d.ts.map +1 -1
  4. package/dist/bin/command-supervision.js +7 -4
  5. package/dist/bin/command-supervision.js.map +1 -1
  6. package/dist/bin/homegraph.js +304 -81
  7. package/dist/bin/homegraph.js.map +1 -1
  8. package/dist/context/index.d.ts.map +1 -1
  9. package/dist/context/index.js +32 -11
  10. package/dist/context/index.js.map +1 -1
  11. package/dist/db/index.d.ts +143 -1
  12. package/dist/db/index.d.ts.map +1 -1
  13. package/dist/db/index.js +305 -28
  14. package/dist/db/index.js.map +1 -1
  15. package/dist/db/migrations.js +37 -37
  16. package/dist/db/queries.d.ts +43 -4
  17. package/dist/db/queries.d.ts.map +1 -1
  18. package/dist/db/queries.js +294 -170
  19. package/dist/db/queries.js.map +1 -1
  20. package/dist/db/schema.sql +203 -203
  21. package/dist/db/sqlite-adapter.d.ts +24 -17
  22. package/dist/db/sqlite-adapter.d.ts.map +1 -1
  23. package/dist/db/sqlite-adapter.js +292 -36
  24. package/dist/db/sqlite-adapter.js.map +1 -1
  25. package/dist/db/wal-valve.d.ts +15 -2
  26. package/dist/db/wal-valve.d.ts.map +1 -1
  27. package/dist/db/wal-valve.js +88 -6
  28. package/dist/db/wal-valve.js.map +1 -1
  29. package/dist/directory.js +5 -5
  30. package/dist/extraction/arkts-batch-worker.js +3 -2
  31. package/dist/extraction/arkts-batch-worker.js.map +1 -1
  32. package/dist/extraction/default-ignore.d.ts +39 -0
  33. package/dist/extraction/default-ignore.d.ts.map +1 -0
  34. package/dist/extraction/default-ignore.js +221 -0
  35. package/dist/extraction/default-ignore.js.map +1 -0
  36. package/dist/extraction/index.d.ts +9 -10
  37. package/dist/extraction/index.d.ts.map +1 -1
  38. package/dist/extraction/index.js +227 -323
  39. package/dist/extraction/index.js.map +1 -1
  40. package/dist/extraction/languages/arkts-viewtree.d.ts +2 -4
  41. package/dist/extraction/languages/arkts-viewtree.d.ts.map +1 -1
  42. package/dist/extraction/languages/arkts-viewtree.js +6 -21
  43. package/dist/extraction/languages/arkts-viewtree.js.map +1 -1
  44. package/dist/extraction/languages/arkts.d.ts +100 -6
  45. package/dist/extraction/languages/arkts.d.ts.map +1 -1
  46. package/dist/extraction/languages/arkts.js +1777 -173
  47. package/dist/extraction/languages/arkts.js.map +1 -1
  48. package/dist/extraction/store-worker.d.ts +20 -0
  49. package/dist/extraction/store-worker.d.ts.map +1 -0
  50. package/dist/extraction/store-worker.js +102 -0
  51. package/dist/extraction/store-worker.js.map +1 -0
  52. package/dist/extraction/store-writer.d.ts +48 -0
  53. package/dist/extraction/store-writer.d.ts.map +1 -0
  54. package/dist/extraction/store-writer.js +167 -0
  55. package/dist/extraction/store-writer.js.map +1 -0
  56. package/dist/extraction/tree-sitter.d.ts.map +1 -1
  57. package/dist/extraction/tree-sitter.js +47 -0
  58. package/dist/extraction/tree-sitter.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 +14 -4
  69. package/dist/extraction/wasm-runtime-flags.d.ts.map +1 -1
  70. package/dist/extraction/wasm-runtime-flags.js +55 -7
  71. package/dist/extraction/wasm-runtime-flags.js.map +1 -1
  72. package/dist/index.d.ts +16 -5
  73. package/dist/index.d.ts.map +1 -1
  74. package/dist/index.js +135 -97
  75. package/dist/index.js.map +1 -1
  76. package/dist/installer/instructions-template.js +9 -9
  77. package/dist/mcp/liveness-watchdog.d.ts +18 -0
  78. package/dist/mcp/liveness-watchdog.d.ts.map +1 -1
  79. package/dist/mcp/liveness-watchdog.js +185 -73
  80. package/dist/mcp/liveness-watchdog.js.map +1 -1
  81. package/dist/mcp/query-worker.d.ts +2 -2
  82. package/dist/mcp/query-worker.js +2 -2
  83. package/dist/mcp/server-instructions.js +47 -47
  84. package/dist/mcp/tools.d.ts.map +1 -1
  85. package/dist/mcp/tools.js +42 -26
  86. package/dist/mcp/tools.js.map +1 -1
  87. package/dist/reasoning/reasoner.js +32 -32
  88. package/dist/resolution/callback-synthesizer.d.ts +52 -16
  89. package/dist/resolution/callback-synthesizer.d.ts.map +1 -1
  90. package/dist/resolution/callback-synthesizer.js +234 -180
  91. package/dist/resolution/callback-synthesizer.js.map +1 -1
  92. package/dist/resolution/cooperative-yield.d.ts +10 -2
  93. package/dist/resolution/cooperative-yield.d.ts.map +1 -1
  94. package/dist/resolution/cooperative-yield.js +6 -4
  95. package/dist/resolution/cooperative-yield.js.map +1 -1
  96. package/dist/resolution/frameworks/go.d.ts.map +1 -1
  97. package/dist/resolution/frameworks/go.js +29 -6
  98. package/dist/resolution/frameworks/go.js.map +1 -1
  99. package/dist/resolution/import-resolver.d.ts +2 -3
  100. package/dist/resolution/import-resolver.d.ts.map +1 -1
  101. package/dist/resolution/import-resolver.js +166 -7
  102. package/dist/resolution/import-resolver.js.map +1 -1
  103. package/dist/resolution/index.d.ts +87 -2
  104. package/dist/resolution/index.d.ts.map +1 -1
  105. package/dist/resolution/index.js +739 -131
  106. package/dist/resolution/index.js.map +1 -1
  107. package/dist/resolution/memory-budget.d.ts +48 -0
  108. package/dist/resolution/memory-budget.d.ts.map +1 -0
  109. package/dist/resolution/memory-budget.js +162 -0
  110. package/dist/resolution/memory-budget.js.map +1 -0
  111. package/dist/resolution/name-matcher.d.ts +12 -1
  112. package/dist/resolution/name-matcher.d.ts.map +1 -1
  113. package/dist/resolution/name-matcher.js +439 -97
  114. package/dist/resolution/name-matcher.js.map +1 -1
  115. package/dist/resolution/resolver-pool.d.ts +106 -0
  116. package/dist/resolution/resolver-pool.d.ts.map +1 -0
  117. package/dist/resolution/resolver-pool.js +344 -0
  118. package/dist/resolution/resolver-pool.js.map +1 -0
  119. package/dist/resolution/resolver-worker.d.ts +17 -0
  120. package/dist/resolution/resolver-worker.d.ts.map +1 -0
  121. package/dist/resolution/resolver-worker.js +141 -0
  122. package/dist/resolution/resolver-worker.js.map +1 -0
  123. package/dist/resolution/types.d.ts +3 -0
  124. package/dist/resolution/types.d.ts.map +1 -1
  125. package/dist/spec/build/diff-parser.d.ts +55 -0
  126. package/dist/spec/build/diff-parser.d.ts.map +1 -0
  127. package/dist/spec/build/diff-parser.js +205 -0
  128. package/dist/spec/build/diff-parser.js.map +1 -0
  129. package/dist/spec/build/pipeline.d.ts +54 -0
  130. package/dist/spec/build/pipeline.d.ts.map +1 -0
  131. package/dist/spec/build/pipeline.js +170 -0
  132. package/dist/spec/build/pipeline.js.map +1 -0
  133. package/dist/spec/build/scan.d.ts +48 -0
  134. package/dist/spec/build/scan.d.ts.map +1 -0
  135. package/dist/spec/build/scan.js +89 -0
  136. package/dist/spec/build/scan.js.map +1 -0
  137. package/dist/spec/build/scope-resolver.d.ts +50 -0
  138. package/dist/spec/build/scope-resolver.d.ts.map +1 -0
  139. package/dist/spec/build/scope-resolver.js +105 -0
  140. package/dist/spec/build/scope-resolver.js.map +1 -0
  141. package/dist/spec/build/spec-extractor.d.ts +74 -0
  142. package/dist/spec/build/spec-extractor.d.ts.map +1 -0
  143. package/dist/spec/build/spec-extractor.js +344 -0
  144. package/dist/spec/build/spec-extractor.js.map +1 -0
  145. package/dist/spec/config.d.ts +35 -1
  146. package/dist/spec/config.d.ts.map +1 -1
  147. package/dist/spec/config.js +30 -5
  148. package/dist/spec/config.js.map +1 -1
  149. package/dist/spec/db/commit-node.js +4 -4
  150. package/dist/spec/db/fragment-node.js +10 -10
  151. package/dist/spec/db/fts.d.ts.map +1 -1
  152. package/dist/spec/db/fts.js +27 -58
  153. package/dist/spec/db/fts.js.map +1 -1
  154. package/dist/spec/db/index.d.ts +5 -3
  155. package/dist/spec/db/index.d.ts.map +1 -1
  156. package/dist/spec/db/index.js +16 -1
  157. package/dist/spec/db/index.js.map +1 -1
  158. package/dist/spec/db/persist.d.ts +45 -0
  159. package/dist/spec/db/persist.d.ts.map +1 -0
  160. package/dist/spec/db/persist.js +66 -0
  161. package/dist/spec/db/persist.js.map +1 -0
  162. package/dist/spec/db/relations.d.ts +80 -1
  163. package/dist/spec/db/relations.d.ts.map +1 -1
  164. package/dist/spec/db/relations.js +218 -47
  165. package/dist/spec/db/relations.js.map +1 -1
  166. package/dist/spec/db/schema.d.ts +4 -0
  167. package/dist/spec/db/schema.d.ts.map +1 -1
  168. package/dist/spec/db/schema.js +10 -6
  169. package/dist/spec/db/schema.js.map +1 -1
  170. package/dist/spec/db/schema.sql +121 -117
  171. package/dist/spec/db/spec-node.d.ts +5 -0
  172. package/dist/spec/db/spec-node.d.ts.map +1 -1
  173. package/dist/spec/db/spec-node.js +15 -9
  174. package/dist/spec/db/spec-node.js.map +1 -1
  175. package/dist/spec/db/sql-utils.d.ts +11 -0
  176. package/dist/spec/db/sql-utils.d.ts.map +1 -0
  177. package/dist/spec/db/sql-utils.js +19 -0
  178. package/dist/spec/db/sql-utils.js.map +1 -0
  179. package/dist/spec/evolve/cluster-context.d.ts +30 -0
  180. package/dist/spec/evolve/cluster-context.d.ts.map +1 -0
  181. package/dist/spec/evolve/cluster-context.js +78 -0
  182. package/dist/spec/evolve/cluster-context.js.map +1 -0
  183. package/dist/spec/evolve/commit-spec-analyzer.d.ts +51 -0
  184. package/dist/spec/evolve/commit-spec-analyzer.d.ts.map +1 -0
  185. package/dist/spec/evolve/commit-spec-analyzer.js +88 -0
  186. package/dist/spec/evolve/commit-spec-analyzer.js.map +1 -0
  187. package/dist/spec/evolve/commit-spec-persister.d.ts +53 -0
  188. package/dist/spec/evolve/commit-spec-persister.d.ts.map +1 -0
  189. package/dist/spec/evolve/commit-spec-persister.js +93 -0
  190. package/dist/spec/evolve/commit-spec-persister.js.map +1 -0
  191. package/dist/spec/evolve/impact-locator.d.ts +23 -7
  192. package/dist/spec/evolve/impact-locator.d.ts.map +1 -1
  193. package/dist/spec/evolve/impact-locator.js +58 -14
  194. package/dist/spec/evolve/impact-locator.js.map +1 -1
  195. package/dist/spec/evolve/pipeline.d.ts +62 -25
  196. package/dist/spec/evolve/pipeline.d.ts.map +1 -1
  197. package/dist/spec/evolve/pipeline.js +436 -457
  198. package/dist/spec/evolve/pipeline.js.map +1 -1
  199. package/dist/spec/evolve/spec-rewriter.d.ts +13 -13
  200. package/dist/spec/evolve/spec-rewriter.d.ts.map +1 -1
  201. package/dist/spec/evolve/spec-rewriter.js +45 -45
  202. package/dist/spec/evolve/spec-rewriter.js.map +1 -1
  203. package/dist/spec/git/commits.d.ts +85 -0
  204. package/dist/spec/git/commits.d.ts.map +1 -0
  205. package/dist/spec/git/commits.js +218 -0
  206. package/dist/spec/git/commits.js.map +1 -0
  207. package/dist/spec/git/exec.d.ts +13 -0
  208. package/dist/spec/git/exec.d.ts.map +1 -0
  209. package/dist/spec/git/exec.js +19 -0
  210. package/dist/spec/git/exec.js.map +1 -0
  211. package/dist/spec/git/index.d.ts +9 -0
  212. package/dist/spec/git/index.d.ts.map +1 -0
  213. package/dist/spec/git/index.js +22 -0
  214. package/dist/spec/git/index.js.map +1 -0
  215. package/dist/spec/graph/index.d.ts +8 -0
  216. package/dist/spec/graph/index.d.ts.map +1 -0
  217. package/dist/spec/graph/index.js +17 -0
  218. package/dist/spec/graph/index.js.map +1 -0
  219. package/dist/spec/graph/queries.d.ts +6 -35
  220. package/dist/spec/graph/queries.d.ts.map +1 -1
  221. package/dist/spec/graph/queries.js +71 -248
  222. package/dist/spec/graph/queries.js.map +1 -1
  223. package/dist/spec/llm/agent-client.d.ts +59 -0
  224. package/dist/spec/llm/agent-client.d.ts.map +1 -0
  225. package/dist/spec/llm/agent-client.js +213 -0
  226. package/dist/spec/llm/agent-client.js.map +1 -0
  227. package/dist/spec/llm/agents/claude-code.d.ts +32 -0
  228. package/dist/spec/llm/agents/claude-code.d.ts.map +1 -0
  229. package/dist/spec/llm/agents/claude-code.js +125 -0
  230. package/dist/spec/llm/agents/claude-code.js.map +1 -0
  231. package/dist/spec/llm/agents/codex.d.ts +32 -0
  232. package/dist/spec/llm/agents/codex.d.ts.map +1 -0
  233. package/dist/spec/llm/agents/codex.js +145 -0
  234. package/dist/spec/llm/agents/codex.js.map +1 -0
  235. package/dist/spec/llm/agents/detect-utils.d.ts +28 -0
  236. package/dist/spec/llm/agents/detect-utils.d.ts.map +1 -0
  237. package/dist/spec/llm/agents/detect-utils.js +91 -0
  238. package/dist/spec/llm/agents/detect-utils.js.map +1 -0
  239. package/dist/spec/llm/agents/deveco-code.d.ts +33 -0
  240. package/dist/spec/llm/agents/deveco-code.d.ts.map +1 -0
  241. package/dist/spec/llm/agents/deveco-code.js +165 -0
  242. package/dist/spec/llm/agents/deveco-code.js.map +1 -0
  243. package/dist/spec/llm/agents/index.d.ts +31 -0
  244. package/dist/spec/llm/agents/index.d.ts.map +1 -0
  245. package/dist/spec/llm/agents/index.js +65 -0
  246. package/dist/spec/llm/agents/index.js.map +1 -0
  247. package/dist/spec/llm/agents/types.d.ts +76 -0
  248. package/dist/spec/llm/agents/types.d.ts.map +1 -0
  249. package/dist/spec/llm/agents/types.js +15 -0
  250. package/dist/spec/llm/agents/types.js.map +1 -0
  251. package/dist/spec/llm/client.d.ts +31 -11
  252. package/dist/spec/llm/client.d.ts.map +1 -1
  253. package/dist/spec/llm/client.js +143 -81
  254. package/dist/spec/llm/client.js.map +1 -1
  255. package/dist/spec/llm/factory.d.ts +41 -0
  256. package/dist/spec/llm/factory.d.ts.map +1 -0
  257. package/dist/spec/llm/factory.js +80 -0
  258. package/dist/spec/llm/factory.js.map +1 -0
  259. package/dist/spec/llm/prompts.d.ts +24 -8
  260. package/dist/spec/llm/prompts.d.ts.map +1 -1
  261. package/dist/spec/llm/prompts.js +112 -62
  262. package/dist/spec/llm/prompts.js.map +1 -1
  263. package/dist/spec/llm/retry.d.ts +53 -0
  264. package/dist/spec/llm/retry.d.ts.map +1 -0
  265. package/dist/spec/llm/retry.js +153 -0
  266. package/dist/spec/llm/retry.js.map +1 -0
  267. package/dist/spec/mine/clustering/features.d.ts +38 -0
  268. package/dist/spec/mine/clustering/features.d.ts.map +1 -0
  269. package/dist/spec/mine/clustering/features.js +108 -0
  270. package/dist/spec/mine/clustering/features.js.map +1 -0
  271. package/dist/spec/mine/clustering/index.d.ts +62 -0
  272. package/dist/spec/mine/clustering/index.d.ts.map +1 -0
  273. package/dist/spec/mine/clustering/index.js +259 -0
  274. package/dist/spec/mine/clustering/index.js.map +1 -0
  275. package/dist/spec/mine/clustering/leiden.d.ts +61 -0
  276. package/dist/spec/mine/clustering/leiden.d.ts.map +1 -0
  277. package/dist/spec/mine/clustering/leiden.js +489 -0
  278. package/dist/spec/mine/clustering/leiden.js.map +1 -0
  279. package/dist/spec/mine/clustering/text-similarity.d.ts +22 -0
  280. package/dist/spec/mine/clustering/text-similarity.d.ts.map +1 -0
  281. package/dist/spec/mine/clustering/text-similarity.js +98 -0
  282. package/dist/spec/mine/clustering/text-similarity.js.map +1 -0
  283. package/dist/spec/mine/generator.d.ts +54 -0
  284. package/dist/spec/mine/generator.d.ts.map +1 -0
  285. package/dist/spec/mine/generator.js +267 -0
  286. package/dist/spec/mine/generator.js.map +1 -0
  287. package/dist/spec/mine/persist.d.ts +37 -0
  288. package/dist/spec/mine/persist.d.ts.map +1 -0
  289. package/dist/spec/mine/persist.js +170 -0
  290. package/dist/spec/mine/persist.js.map +1 -0
  291. package/dist/spec/mine/pipeline.d.ts +41 -0
  292. package/dist/spec/mine/pipeline.d.ts.map +1 -0
  293. package/dist/spec/mine/pipeline.js +236 -0
  294. package/dist/spec/mine/pipeline.js.map +1 -0
  295. package/dist/spec/mine/scanner.d.ts +59 -0
  296. package/dist/spec/mine/scanner.d.ts.map +1 -0
  297. package/dist/spec/mine/scanner.js +383 -0
  298. package/dist/spec/mine/scanner.js.map +1 -0
  299. package/dist/spec/types.d.ts +32 -0
  300. package/dist/spec/types.d.ts.map +1 -1
  301. package/dist/spec/ui/index.d.ts +9 -0
  302. package/dist/spec/ui/index.d.ts.map +1 -0
  303. package/dist/spec/ui/index.js +16 -0
  304. package/dist/spec/ui/index.js.map +1 -0
  305. package/dist/spec/ui/progress-handler.d.ts +33 -0
  306. package/dist/spec/ui/progress-handler.d.ts.map +1 -0
  307. package/dist/spec/ui/progress-handler.js +132 -0
  308. package/dist/spec/ui/progress-handler.js.map +1 -0
  309. package/dist/spec/ui/progress.d.ts +24 -0
  310. package/dist/spec/ui/progress.d.ts.map +1 -0
  311. package/dist/spec/ui/progress.js +13 -0
  312. package/dist/spec/ui/progress.js.map +1 -0
  313. package/dist/spec/utils/fs.d.ts +45 -0
  314. package/dist/spec/utils/fs.d.ts.map +1 -0
  315. package/dist/spec/utils/fs.js +163 -0
  316. package/dist/spec/utils/fs.js.map +1 -0
  317. package/dist/spec/utils/index.d.ts +12 -0
  318. package/dist/spec/utils/index.d.ts.map +1 -0
  319. package/dist/spec/utils/index.js +26 -0
  320. package/dist/spec/utils/index.js.map +1 -0
  321. package/dist/spec/utils/meta.d.ts +32 -0
  322. package/dist/spec/utils/meta.d.ts.map +1 -0
  323. package/dist/spec/utils/meta.js +122 -0
  324. package/dist/spec/utils/meta.js.map +1 -0
  325. package/dist/spec/utils/truncate.d.ts +86 -0
  326. package/dist/spec/utils/truncate.d.ts.map +1 -0
  327. package/dist/spec/utils/truncate.js +172 -0
  328. package/dist/spec/utils/truncate.js.map +1 -0
  329. package/dist/sync/watcher.d.ts +25 -1
  330. package/dist/sync/watcher.d.ts.map +1 -1
  331. package/dist/sync/watcher.js +76 -3
  332. package/dist/sync/watcher.js.map +1 -1
  333. package/dist/telemetry/index.js +3 -3
  334. package/dist/telemetry/index.js.map +1 -1
  335. package/dist/types.d.ts +3 -0
  336. package/dist/types.d.ts.map +1 -1
  337. package/package.json +62 -58
  338. package/scripts/_tmp-cfwk-resolve.log +0 -0
  339. package/scripts/_tmp-cfwk-sig.log +0 -0
  340. package/scripts/_tmp-cfwk-vt.log +0 -0
  341. package/scripts/add-lang/bench.sh +60 -60
  342. package/scripts/add-lang/check-grammar.mjs +75 -75
  343. package/scripts/add-lang/dump-ast.mjs +103 -103
  344. package/scripts/add-lang/verify-extraction.mjs +70 -70
  345. package/scripts/agent-eval/ab-adoption.sh +91 -91
  346. package/scripts/agent-eval/ab-hook.sh +86 -86
  347. package/scripts/agent-eval/ab-impl.sh +78 -78
  348. package/scripts/agent-eval/ab-new-vs-baseline.sh +102 -102
  349. package/scripts/agent-eval/ab-sufficiency.sh +78 -78
  350. package/scripts/agent-eval/arms-F.sh +21 -21
  351. package/scripts/agent-eval/arms-matrix.sh +37 -37
  352. package/scripts/agent-eval/audit.sh +68 -68
  353. package/scripts/agent-eval/bench-readme.sh +28 -28
  354. package/scripts/agent-eval/bench-why-repo.sh +22 -22
  355. package/scripts/agent-eval/block-read-hook.sh +19 -19
  356. package/scripts/agent-eval/hook-settings.json +15 -15
  357. package/scripts/agent-eval/itrun.sh +120 -120
  358. package/scripts/agent-eval/offload-eval-3arm.sh +72 -72
  359. package/scripts/agent-eval/offload-eval-cost.mjs +133 -133
  360. package/scripts/agent-eval/offload-eval-effort.mjs +108 -108
  361. package/scripts/agent-eval/offload-eval-frontload-matrix.sh +25 -25
  362. package/scripts/agent-eval/offload-eval-frontload.sh +47 -47
  363. package/scripts/agent-eval/offload-eval-ground-truth.json +18 -18
  364. package/scripts/agent-eval/offload-eval-hook.mjs +84 -84
  365. package/scripts/agent-eval/offload-eval-judge.mjs +103 -103
  366. package/scripts/agent-eval/offload-eval-matrix.sh +20 -20
  367. package/scripts/agent-eval/offload-eval-metrics.mjs +94 -94
  368. package/scripts/agent-eval/offload-eval-refs1.sh +50 -50
  369. package/scripts/agent-eval/offload-eval-setup.sh +24 -24
  370. package/scripts/agent-eval/offload-eval-styles.sh +71 -71
  371. package/scripts/agent-eval/offload-eval-summarize.mjs +68 -68
  372. package/scripts/agent-eval/offload-eval.md +76 -76
  373. package/scripts/agent-eval/parse-arms.mjs +116 -116
  374. package/scripts/agent-eval/parse-bench-readme.mjs +84 -84
  375. package/scripts/agent-eval/parse-run.mjs +45 -45
  376. package/scripts/agent-eval/parse-session.mjs +93 -93
  377. package/scripts/agent-eval/probe-context.mjs +21 -21
  378. package/scripts/agent-eval/probe-explore.mjs +40 -40
  379. package/scripts/agent-eval/probe-node.mjs +20 -20
  380. package/scripts/agent-eval/probe-sweep.mjs +119 -119
  381. package/scripts/agent-eval/probe-trace.mjs +20 -20
  382. package/scripts/agent-eval/redirect-read-hook.sh +38 -38
  383. package/scripts/agent-eval/repro-concurrent-explore.mjs +119 -119
  384. package/scripts/agent-eval/repro-daemon-clients.mjs +125 -125
  385. package/scripts/agent-eval/run-agent.sh +34 -34
  386. package/scripts/agent-eval/run-all.sh +75 -75
  387. package/scripts/agent-eval/run-arms.sh +56 -56
  388. package/scripts/agent-eval/seq-matrix.mjs +137 -137
  389. package/scripts/bench-arkts-init-rss.log +0 -0
  390. package/scripts/build-bundle.sh +123 -123
  391. package/scripts/exp_boundary_eval/README.md +247 -247
  392. package/scripts/exp_boundary_eval/__pycache__/_utils.cpython-310.pyc +0 -0
  393. package/scripts/exp_boundary_eval/__pycache__/_utils.cpython-38.pyc +0 -0
  394. package/scripts/exp_boundary_eval/__pycache__/analyze.cpython-310.pyc +0 -0
  395. package/scripts/exp_boundary_eval/__pycache__/analyze.cpython-38.pyc +0 -0
  396. package/scripts/exp_boundary_eval/__pycache__/deveco_arm.cpython-38.pyc +0 -0
  397. package/scripts/exp_boundary_eval/__pycache__/run_all.cpython-310.pyc +0 -0
  398. package/scripts/exp_boundary_eval/__pycache__/run_all.cpython-38.pyc +0 -0
  399. package/scripts/exp_boundary_eval/__pycache__/run_one.cpython-310.pyc +0 -0
  400. package/scripts/exp_boundary_eval/__pycache__/run_one.cpython-38.pyc +0 -0
  401. package/scripts/exp_boundary_eval/__pycache__/run_session.cpython-310.pyc +0 -0
  402. package/scripts/exp_boundary_eval/__pycache__/run_session.cpython-38.pyc +0 -0
  403. package/scripts/exp_boundary_eval/__pycache__/setup.cpython-310.pyc +0 -0
  404. package/scripts/exp_boundary_eval/__pycache__/setup.cpython-38.pyc +0 -0
  405. package/scripts/exp_boundary_eval/__pycache__/win_mcp_launcher.cpython-38.pyc +0 -0
  406. package/scripts/exp_boundary_eval/_test_mcp_chain.py +78 -78
  407. package/scripts/exp_boundary_eval/_test_stdin.py +8 -8
  408. package/scripts/exp_boundary_eval/_utils.py +1116 -1116
  409. package/scripts/exp_boundary_eval/analyze.py +1313 -1313
  410. package/scripts/exp_boundary_eval/deveco_arm.py +519 -519
  411. package/scripts/exp_boundary_eval/run_all.py +378 -378
  412. package/scripts/exp_boundary_eval/run_one.py +165 -165
  413. package/scripts/exp_boundary_eval/run_session.py +158 -158
  414. package/scripts/exp_boundary_eval/setup.py +120 -120
  415. package/scripts/exp_boundary_eval/win_mcp_launcher.py +73 -73
  416. package/scripts/exp_boundary_eval/win_mcp_stdio_wrap.js +36 -36
  417. package/scripts/exp_boundary_eval/win_node_launcher.py +24 -24
  418. package/scripts/extract-release-notes.mjs +130 -130
  419. package/scripts/local-install.sh +41 -41
  420. package/scripts/npm-sdk.js +75 -75
  421. package/scripts/npm-shim.js +275 -275
  422. package/scripts/ohos-sdk-publish.mjs +133 -133
  423. package/scripts/pack-npm.sh +119 -119
  424. package/scripts/prepare-release.mjs +270 -270
  425. package/scripts/probe-arkts-mem-why-run.log +0 -0
  426. package/scripts/probe-banner-livecard-ir.log +0 -0
  427. package/scripts/probe-cfwk-ast.stderr.log +0 -0
  428. package/scripts/probe-cfwk-ast.stdout.log +0 -0
  429. package/scripts/probe-cfwk-attr-shape.log +0 -0
  430. package/scripts/probe-cfwk-cfgdump.stderr.log +0 -0
  431. package/scripts/probe-cfwk-cfgdump.stdout.log +0 -0
  432. package/scripts/probe-cfwk-cvc.stderr.log +0 -0
  433. package/scripts/probe-cfwk-cvc.stdout.log +0 -0
  434. package/scripts/probe-cfwk-diag2.log +0 -0
  435. package/scripts/probe-cfwk-diag3.log +0 -0
  436. package/scripts/probe-cfwk-fedbg.stderr.log +0 -0
  437. package/scripts/probe-cfwk-fedbg.stdout.log +0 -0
  438. package/scripts/probe-cfwk-fileresult.log +0 -0
  439. package/scripts/probe-cfwk-fix.stderr.log +0 -0
  440. package/scripts/probe-cfwk-fix.stdout.log +0 -0
  441. package/scripts/probe-cfwk-fix2.stderr.log +0 -0
  442. package/scripts/probe-cfwk-fix2.stdout.log +0 -0
  443. package/scripts/probe-cfwk-fix3.stderr.log +0 -0
  444. package/scripts/probe-cfwk-fix3.stdout.log +0 -0
  445. package/scripts/probe-cfwk-foreach.log +0 -0
  446. package/scripts/probe-cfwk-getmethod-throw.log +0 -0
  447. package/scripts/probe-cfwk-hg-extract.log +0 -0
  448. package/scripts/probe-cfwk-pr1003-noprior.stderr.log +0 -0
  449. package/scripts/probe-cfwk-pr1003-noprior.stdout.log +0 -0
  450. package/scripts/probe-cfwk-pr1003.stderr.log +0 -0
  451. package/scripts/probe-cfwk-pr1003.stdout.log +0 -0
  452. package/scripts/probe-cfwk-preroot-noprior.stderr.log +0 -0
  453. package/scripts/probe-cfwk-preroot-noprior.stdout.log +0 -0
  454. package/scripts/probe-cfwk-preroot-prior.stderr.log +0 -0
  455. package/scripts/probe-cfwk-preroot-prior.stdout.log +0 -0
  456. package/scripts/probe-cfwk-preroot-skipstate.stderr.log +0 -0
  457. package/scripts/probe-cfwk-preroot-skipstate.stdout.log +0 -0
  458. package/scripts/probe-cfwk-resolve-sim.log +0 -0
  459. package/scripts/probe-cfwk-tree-shape.log +0 -0
  460. package/scripts/probe-cfwk-walk-abort.log +0 -0
  461. package/scripts/probe-cfwk.log +0 -0
  462. package/scripts/probe-force-index.log +0 -0
  463. package/scripts/probe-no-force-index.log +0 -0
  464. package/scripts/probe-samefile-ir.stderr.log +0 -0
  465. package/scripts/probe-samefile-ir.stdout.log +224 -0
  466. package/scripts/probe-sdk-vs-project.log +0 -0
  467. package/scripts/probe-viewtree-downgrade.log +0 -0
  468. package/dist/arkts/ohos-api-index.d.ts +0 -15
  469. package/dist/arkts/ohos-api-index.d.ts.map +0 -1
  470. package/dist/arkts/ohos-api-index.js +0 -190
  471. package/dist/arkts/ohos-api-index.js.map +0 -1
  472. package/dist/arkts/ohos-sdk-input.d.ts +0 -36
  473. package/dist/arkts/ohos-sdk-input.d.ts.map +0 -1
  474. package/dist/arkts/ohos-sdk-input.js +0 -214
  475. package/dist/arkts/ohos-sdk-input.js.map +0 -1
  476. package/dist/extraction/languages/arkts-state-decorators.d.ts +0 -13
  477. package/dist/extraction/languages/arkts-state-decorators.d.ts.map +0 -1
  478. package/dist/extraction/languages/arkts-state-decorators.js +0 -26
  479. package/dist/extraction/languages/arkts-state-decorators.js.map +0 -1
  480. package/dist/extraction/languages/ohos-api-consumer.d.ts +0 -34
  481. package/dist/extraction/languages/ohos-api-consumer.d.ts.map +0 -1
  482. package/dist/extraction/languages/ohos-api-consumer.js +0 -283
  483. package/dist/extraction/languages/ohos-api-consumer.js.map +0 -1
@@ -3,10 +3,27 @@
3
3
  * ArkTS (.ets) extraction via ArkAnalyzer.
4
4
  *
5
5
  * Unlike tree-sitter languages, ArkTS is batch-complete: the first `.ets`
6
- * parse builds one Scene for the whole project, runs RTA, persists every
7
- * `.ets` file (nodes, edges, cross-file RTA calls) directly to the DB, then
8
- * returns the requested file's result. Later `.ets` hits in the same batch
9
- * short-circuit (orchestrator store skips via matching content hash).
6
+ * parse builds a Scene, runs RTA, persists every `.ets` file (nodes, edges,
7
+ * cross-file RTA calls) directly to the DB, then returns the requested file's
8
+ * result. Later `.ets` hits in the same batch short-circuit (orchestrator store
9
+ * skips via matching content hash).
10
+ *
11
+ * HarmonyOS multi-module projects (`build-profile.json5`) use
12
+ * `Scene.analyseByModule` so each HAP/HSP/HAR is the atomic unit (like a file
13
+ * for tree-sitter languages): symbols + ViewTree + **intra-module RTA** while
14
+ * the module has BODIES. Per-module RTA entries = DummyMain (Ability/Component
15
+ * lifecycle) **plus the module export surface** (exported functions and methods
16
+ * of exported classes), so non-UI libraries still grow reachable call edges.
17
+ * CFG does **not** stitch same-module calls (that densified past unlimited RTA).
18
+ * Cross-module edges come from RTA with an exact MethodSignature map, plus a
19
+ * CFG `%unk` bridge that recovers Class.method / free functions from stmt text,
20
+ * UnclearReferenceType names, or PascalCase base locals (import + unique target
21
+ * + fan-out blocklist). Deps load to SIGNATURES.
22
+ *
23
+ * Sync can re-index a **dirty module subset** (`primeArkTSBatch` + `changedFiles`)
24
+ * via `ModuleAnalysisConfig.setTargetModuleIds`, instead of rebuilding every
25
+ * PROJECT module. Unchanged modules stay in the DB; incoming cross-file edges
26
+ * into dirty files are snapshotted and re-bound like file-level sync (#899).
10
27
  */
11
28
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
12
29
  if (k2 === undefined) k2 = k;
@@ -41,18 +58,30 @@ var __importStar = (this && this.__importStar) || (function () {
41
58
  return result;
42
59
  };
43
60
  })();
61
+ var __importDefault = (this && this.__importDefault) || function (mod) {
62
+ return (mod && mod.__esModule) ? mod : { "default": mod };
63
+ };
44
64
  Object.defineProperty(exports, "__esModule", { value: true });
45
65
  exports.OHOS_API_DB_PATH_META = exports.OHOS_API_VERSION_META = exports.OHOS_API_FILE_PREFIX = exports.OhosSdkInputError = exports.ArkTSExtractor = void 0;
46
66
  exports.cleanArkTypeString = cleanArkTypeString;
47
67
  exports.normalizeArktsReturnType = normalizeArktsReturnType;
48
68
  exports.buildArkMethodSignatureFields = buildArkMethodSignatureFields;
69
+ exports.shouldUseIsolatedArkTSBuild = shouldUseIsolatedArkTSBuild;
70
+ exports.normalizeHarmonyModuleSrcPath = normalizeHarmonyModuleSrcPath;
71
+ exports.listHarmonyProjectModules = listHarmonyProjectModules;
72
+ exports.resolveDirtyHarmonyModules = resolveDirtyHarmonyModules;
49
73
  exports.isArkAnalyzerSourcePath = isArkAnalyzerSourcePath;
74
+ exports.scanEtsFiles = scanEtsFiles;
50
75
  exports.runIsolatedArkTSBatchEntry = runIsolatedArkTSBatchEntry;
51
76
  exports.primeArkTSBatch = primeArkTSBatch;
52
77
  exports.isArkTSBatchPersisted = isArkTSBatchPersisted;
53
78
  exports.resetArkTSBatch = resetArkTSBatch;
79
+ exports.collectExportedRtaEntryPointsFromFiles = collectExportedRtaEntryPointsFromFiles;
54
80
  exports.stateDecoratorKinds = stateDecoratorKinds;
55
81
  exports.stateTransferViaForField = stateTransferViaForField;
82
+ exports.isUnclearArkClassSignature = isUnclearArkClassSignature;
83
+ exports.isUnclearArkMethodSignature = isUnclearArkMethodSignature;
84
+ exports.pickPreferredClassNodeId = pickPreferredClassNodeId;
56
85
  exports.drainArkTSIndexNotices = drainArkTSIndexNotices;
57
86
  exports.parseOhosToolsVersionFromName = parseOhosToolsVersionFromName;
58
87
  exports.isArchivePath = isArchivePath;
@@ -82,11 +111,16 @@ const child_process_1 = require("child_process");
82
111
  const arkanalyzer_1 = require("arkanalyzer");
83
112
  const db_1 = require("../../db");
84
113
  const queries_1 = require("../../db/queries");
114
+ const directory_1 = require("../../directory");
115
+ const project_config_1 = require("../../project-config");
85
116
  const context_1 = require("../context");
117
+ const default_ignore_1 = require("../default-ignore");
86
118
  const extraction_version_1 = require("../extraction-version");
87
119
  const version_1 = require("../../mcp/version");
120
+ const liveness_watchdog_1 = require("../../mcp/liveness-watchdog");
88
121
  const tree_sitter_helpers_1 = require("../tree-sitter-helpers");
89
122
  const wasm_runtime_flags_1 = require("../wasm-runtime-flags");
123
+ const ignore_1 = __importDefault(require("ignore"));
90
124
  /** Primitive / void return types — not stored in nodes.return_type (inference-only column). */
91
125
  const ARKTS_NON_CLASS_RETURN = new Set([
92
126
  'void',
@@ -222,16 +256,148 @@ function isStackOverflowExitCode(code) {
222
256
  return false;
223
257
  return code === 3221225725 || code === -1073741571;
224
258
  }
225
- function shouldUseIsolatedArkTSBuild(etsCount) {
259
+ /**
260
+ * Opt-in isolated ArkTS Scene build (child process + enlarged `--stack-size`).
261
+ * Default is in-process: faster and avoids a Windows-only auto-isolated path that
262
+ * could burn minutes on failed stack-size retries before giving up. Set
263
+ * `HOMEGRAPH_ARKTS_ISOLATED=1` only when an in-process Scene build stack-overflows
264
+ * the indexer (rare Photos-scale Windows cases).
265
+ */
266
+ function shouldUseIsolatedArkTSBuild() {
226
267
  const mode = process.env.HOMEGRAPH_ARKTS_ISOLATED?.trim();
227
- if (mode === '1' || mode === 'true')
228
- return true;
268
+ return mode === '1' || mode === 'true';
269
+ }
270
+ /**
271
+ * Scene eviction budget sits below the V8 heap cap (heap default 3584MB so process
272
+ * RSS stays near 4GB — native memory sits outside the heap). Small reserve only.
273
+ */
274
+ const ARKTS_HOMEGRAPH_RESERVE_MB = 256;
275
+ /** Default Scene `memoryLimitMB`. Override with HOMEGRAPH_ARKTS_MEMORY_LIMIT_MB. */
276
+ function defaultArkTSSceneMemoryLimitMB() {
277
+ return Math.max(512, (0, wasm_runtime_flags_1.resolveMaxOldSpaceSizeMb)() - ARKTS_HOMEGRAPH_RESERVE_MB);
278
+ }
279
+ /**
280
+ * RSS limit passed to ArkAnalyzer `SceneOptions.memoryLimitMB`.
281
+ * When exceeded, `analyseByModule` evicts cached modules before loading the next.
282
+ * Set `HOMEGRAPH_ARKTS_MEMORY_LIMIT_MB=0` to disable Scene eviction.
283
+ */
284
+ function resolveArkTSSceneMemoryLimitMB() {
285
+ const raw = process.env.HOMEGRAPH_ARKTS_MEMORY_LIMIT_MB?.trim();
286
+ if (raw !== undefined && raw !== '') {
287
+ const n = Number(raw);
288
+ if (Number.isFinite(n) && n >= 0)
289
+ return Math.floor(n);
290
+ }
291
+ return defaultArkTSSceneMemoryLimitMB();
292
+ }
293
+ /** Prefer modular Scene build when the project has Harmony module metadata. */
294
+ function shouldUseModularArkTSBuild(rootDir) {
295
+ const mode = process.env.HOMEGRAPH_ARKTS_MODULAR?.trim();
229
296
  if (mode === '0' || mode === 'false')
230
297
  return false;
231
- // Photos-scale repos on Windows: build Scene in a child with a larger stack.
232
- if (process.platform === 'win32' && etsCount >= 500)
298
+ if (mode === '1' || mode === 'true')
233
299
  return true;
234
- return false;
300
+ return fs.existsSync(path.join(rootDir, 'build-profile.json5'));
301
+ }
302
+ /** Normalize build-profile `srcPath` / relative file paths for prefix matching. */
303
+ function normalizeHarmonyModuleSrcPath(srcPath) {
304
+ return srcPath.replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/$/, '');
305
+ }
306
+ /**
307
+ * Read PROJECT modules from root `build-profile.json5` (name + srcPath).
308
+ * Returns [] when missing or unparsable — callers should fall back to full Scene.
309
+ */
310
+ function listHarmonyProjectModules(rootDir) {
311
+ const profilePath = path.join(rootDir, 'build-profile.json5');
312
+ if (!fs.existsSync(profilePath))
313
+ return [];
314
+ let raw;
315
+ try {
316
+ raw = parseJson5Minimal(fs.readFileSync(profilePath, 'utf-8'));
317
+ }
318
+ catch {
319
+ return [];
320
+ }
321
+ if (!raw || typeof raw !== 'object')
322
+ return [];
323
+ const modules = raw.modules;
324
+ if (!Array.isArray(modules))
325
+ return [];
326
+ const out = [];
327
+ for (const entry of modules) {
328
+ if (!entry || typeof entry !== 'object')
329
+ continue;
330
+ const rec = entry;
331
+ if (typeof rec.name !== 'string' || typeof rec.srcPath !== 'string')
332
+ continue;
333
+ const srcPath = normalizeHarmonyModuleSrcPath(rec.srcPath);
334
+ if (!srcPath)
335
+ continue;
336
+ out.push({ name: rec.name, srcPath });
337
+ }
338
+ return out;
339
+ }
340
+ function findLongestHarmonyModule(fileRel, modules) {
341
+ const file = normIndexPath(fileRel);
342
+ let best = null;
343
+ for (const mod of modules) {
344
+ const src = mod.srcPath;
345
+ if (file === src || file.startsWith(`${src}/`)) {
346
+ if (!best || mod.srcPath.length > best.srcPath.length)
347
+ best = mod;
348
+ }
349
+ }
350
+ return best;
351
+ }
352
+ /**
353
+ * Map changed/removed paths to Harmony modules for incremental ArkTS sync.
354
+ * Structural project files (`build-profile.json5`, root `oh-package.json5`) or
355
+ * ArkAnalyzer sources outside every module force a full rebuild.
356
+ */
357
+ function resolveDirtyHarmonyModules(rootDir, changedFiles) {
358
+ if (!shouldUseModularArkTSBuild(rootDir)) {
359
+ return { mode: 'full', reason: 'non-modular ArkTS project' };
360
+ }
361
+ const modules = listHarmonyProjectModules(rootDir);
362
+ if (modules.length === 0) {
363
+ return { mode: 'full', reason: 'no PROJECT modules in build-profile.json5' };
364
+ }
365
+ const changed = [...changedFiles].map(normIndexPath).filter(Boolean);
366
+ if (changed.length === 0)
367
+ return { mode: 'none' };
368
+ for (const f of changed) {
369
+ if (f === 'build-profile.json5' || f.endsWith('/build-profile.json5')) {
370
+ return { mode: 'full', reason: 'build-profile.json5 changed' };
371
+ }
372
+ if (f === 'oh-package.json5') {
373
+ return { mode: 'full', reason: 'root oh-package.json5 changed' };
374
+ }
375
+ }
376
+ const dirty = new Set();
377
+ const unmatched = [];
378
+ for (const f of changed) {
379
+ // Virtual ArkAnalyzer paths are batch artifacts, not Harmony module sources.
380
+ if (f.startsWith('@'))
381
+ continue;
382
+ const isModuleMeta = f.endsWith('/module.json5') || f.endsWith('/oh-package.json5');
383
+ const isArkSrc = isArkAnalyzerSourcePath(f);
384
+ if (!isArkSrc && !isModuleMeta)
385
+ continue;
386
+ const match = findLongestHarmonyModule(f, modules);
387
+ if (match)
388
+ dirty.add(match.srcPath);
389
+ else if (isArkSrc)
390
+ unmatched.push(f);
391
+ }
392
+ if (unmatched.length > 0) {
393
+ return {
394
+ mode: 'full',
395
+ reason: `ArkTS sources outside modules: ${unmatched.slice(0, 5).join(', ')}`,
396
+ };
397
+ }
398
+ if (dirty.size === 0)
399
+ return { mode: 'none' };
400
+ return { mode: 'modules', moduleSrcPaths: [...dirty].sort() };
235
401
  }
236
402
  function homegraphDbPath(rootDir) {
237
403
  return path.join(rootDir, '.homegraph', 'homegraph.db');
@@ -239,6 +405,9 @@ function homegraphDbPath(rootDir) {
239
405
  const ARK_PROVENANCE = 'heuristic';
240
406
  /** Virtual file path for ArkAnalyzer's in-scene dummy entry (not on disk). */
241
407
  const ARKANALYZER_DUMMY_FILE = '@dummyFile.ets';
408
+ function fieldSigKey(classSig, fieldName) {
409
+ return `${classSig}::${fieldName}`;
410
+ }
242
411
  /** HarmonyOS sources indexed via ArkAnalyzer Scene batch (`.ets`, `.ts`, `.d.ts`). */
243
412
  function isArkAnalyzerSourcePath(filePath) {
244
413
  const base = path.basename(filePath);
@@ -251,8 +420,24 @@ function isEtsFileName(name) {
251
420
  function isCoLocatedArkTsFileName(name) {
252
421
  return isArkAnalyzerSourcePath(name) && !isEtsFileName(name);
253
422
  }
423
+ /**
424
+ * Ignore matchers aligned with `scanDirectoryWalk`'s base set (built-in defaults
425
+ * + root `.gitignore` + `homegraph.json` `exclude`).
426
+ */
427
+ function arktsScanIgnoreMatchers(rootDir) {
428
+ const matchers = [(0, default_ignore_1.buildDefaultIgnore)(rootDir)];
429
+ const exclude = (0, project_config_1.loadExcludePatterns)(rootDir);
430
+ if (exclude.length > 0)
431
+ matchers.push((0, ignore_1.default)().add(exclude));
432
+ return matchers;
433
+ }
434
+ function isArktsScanIgnored(relPath, isDir, matchers) {
435
+ const probe = isDir ? `${relPath.replace(/\/$/, '')}/` : relPath;
436
+ return matchers.some((ig) => ig.ignores(probe));
437
+ }
254
438
  /** True when the tree looks like a HarmonyOS / ArkTS project (not a generic TS repo). */
255
439
  function hasHarmonyProjectMarkers(rootDir) {
440
+ const matchers = arktsScanIgnoreMatchers(rootDir);
256
441
  const found = [];
257
442
  function walk(dir) {
258
443
  if (found.length > 0)
@@ -266,13 +451,17 @@ function hasHarmonyProjectMarkers(rootDir) {
266
451
  }
267
452
  for (const entry of entries) {
268
453
  const full = path.join(dir, entry.name);
454
+ const rel = path.relative(rootDir, full).replace(/\\/g, '/');
269
455
  if (entry.isDirectory()) {
270
- if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === '.homegraph') {
456
+ if (entry.name === '.git' || (0, directory_1.isHomeGraphDataDir)(entry.name))
457
+ continue;
458
+ if (isArktsScanIgnored(rel, true, matchers))
271
459
  continue;
272
- }
273
460
  walk(full);
274
461
  }
275
462
  else if (entry.isFile() && (isEtsFileName(entry.name) || entry.name === 'module.json5')) {
463
+ if (isArktsScanIgnored(rel, false, matchers))
464
+ continue;
276
465
  found.push(full);
277
466
  return;
278
467
  }
@@ -306,9 +495,17 @@ function emptyResult(filePath, message, severity) {
306
495
  durationMs: 0,
307
496
  };
308
497
  }
498
+ /**
499
+ * Enumerate ArkAnalyzer sources under `rootDir`, using the same ignore rules as
500
+ * `scanDirectory` (defaults like `build/`/`dist/`, root `.gitignore`, exclude).
501
+ * Previously this walk only skipped `node_modules`/`.git`/`.homegraph`, so
502
+ * HarmonyOS build-output `.ets` files got written into the DB and non-git
503
+ * `homegraph status` reported them as Pending Removed.
504
+ */
309
505
  function scanEtsFiles(rootDir) {
310
506
  const etsFiles = [];
311
507
  const coLocated = [];
508
+ const matchers = arktsScanIgnoreMatchers(rootDir);
312
509
  function walk(dir) {
313
510
  let entries;
314
511
  try {
@@ -319,13 +516,17 @@ function scanEtsFiles(rootDir) {
319
516
  }
320
517
  for (const entry of entries) {
321
518
  const full = path.join(dir, entry.name);
519
+ const rel = path.relative(rootDir, full).replace(/\\/g, '/');
322
520
  if (entry.isDirectory()) {
323
- if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === '.homegraph')
521
+ if (entry.name === '.git' || (0, directory_1.isHomeGraphDataDir)(entry.name))
522
+ continue;
523
+ if (isArktsScanIgnored(rel, true, matchers))
324
524
  continue;
325
525
  walk(full);
326
526
  }
327
527
  else if (entry.isFile()) {
328
- const rel = path.relative(rootDir, full).replace(/\\/g, '/');
528
+ if (isArktsScanIgnored(rel, false, matchers))
529
+ continue;
329
530
  if (isEtsFileName(entry.name)) {
330
531
  etsFiles.push(rel);
331
532
  }
@@ -358,12 +559,15 @@ let batchBuildCommitted = false;
358
559
  function reportBatchProgress(subphase, current, total, currentFile) {
359
560
  (0, context_1.reportArkTSBatchProgress)({ subphase, current, total, currentFile });
360
561
  }
361
- function persistFileResult(queries, filePath, content, stats, result) {
562
+ function persistFileResult(queries, filePath, content, stats, result, options) {
362
563
  const contentHash = hashContent(content);
363
564
  const existingFile = queries.getFileByPath(filePath);
364
- if (existingFile?.contentHash === contentHash) {
565
+ if (!options?.force && existingFile?.contentHash === contentHash) {
365
566
  return;
366
567
  }
568
+ const crossFileIncomingEdges = existingFile && typeof queries.getCrossFileIncomingEdgesWithTarget === 'function'
569
+ ? queries.getCrossFileIncomingEdgesWithTarget(filePath)
570
+ : [];
367
571
  if (existingFile) {
368
572
  queries.deleteFile(filePath);
369
573
  }
@@ -378,6 +582,30 @@ function persistFileResult(queries, filePath, content, stats, result) {
378
582
  queries.insertEdges(validEdges);
379
583
  }
380
584
  }
585
+ if (crossFileIncomingEdges.length > 0) {
586
+ const newNodesByKindName = new Map();
587
+ for (const n of validNodes) {
588
+ newNodesByKindName.set(`${n.kind}\0${n.name}`, n.id);
589
+ }
590
+ const reinserted = [];
591
+ for (const e of crossFileIncomingEdges) {
592
+ const newTargetId = newNodesByKindName.get(`${e.targetKind}\0${e.targetName}`);
593
+ if (newTargetId) {
594
+ reinserted.push({
595
+ source: e.source,
596
+ target: newTargetId,
597
+ kind: e.kind,
598
+ metadata: e.metadata,
599
+ line: e.line,
600
+ column: e.column,
601
+ provenance: e.provenance,
602
+ });
603
+ }
604
+ }
605
+ if (reinserted.length > 0) {
606
+ queries.insertEdges(reinserted);
607
+ }
608
+ }
381
609
  if (result.unresolvedReferences.length > 0) {
382
610
  const insertedIds = new Set(validNodes.map((n) => n.id));
383
611
  const refsWithContext = result.unresolvedReferences
@@ -403,6 +631,22 @@ function persistFileResult(queries, filePath, content, stats, result) {
403
631
  };
404
632
  queries.upsertFile(fileRecord);
405
633
  }
634
+ /** Force-rewrite every file in an incremental module index (edges may change without content). */
635
+ function persistIncrementalModuleResults(rootDir, queries, index) {
636
+ const persistTotal = index.fileResults.size;
637
+ let persistIndex = 0;
638
+ for (const [filePath, fileResult] of index.fileResults) {
639
+ persistIndex++;
640
+ reportBatchProgress('persist', persistIndex, persistTotal, filePath);
641
+ const isVirtual = filePath.startsWith('@');
642
+ const full = path.join(rootDir, filePath);
643
+ const content = isVirtual ? '' : fs.readFileSync(full, 'utf-8');
644
+ const stats = isVirtual
645
+ ? { size: 0, mtimeMs: Date.now() }
646
+ : fs.statSync(full);
647
+ persistFileResult(queries, filePath, content, stats, fileResult, { force: true });
648
+ }
649
+ }
406
650
  async function persistBatchResultsAsync(rootDir, queries, index) {
407
651
  const entries = [...index.fileResults.entries()];
408
652
  const persistTotal = entries.length;
@@ -501,8 +745,12 @@ function spawnIsolatedArkTSBatchWorker(rootDir, dbPath, triggerFile) {
501
745
  const workerPath = path.join(__dirname, '..', 'arkts-batch-worker.js');
502
746
  let lastCode = null;
503
747
  for (const stackKb of resolveArkTSWorkerStackSizesKb()) {
504
- process.stderr.write(`\n\x1b[33m ArkTS: full Scene build in isolated process (--stack-size=${stackKb} KB, enableMethodBodyBuild=true)...\x1b[0m\n`);
505
- const argv = (0, wasm_runtime_flags_1.buildRelaunchArgv)(workerPath, [rootDir, dbPath, triggerFile], [`--stack-size=${stackKb}`]);
748
+ process.stderr.write(`\n\x1b[33m ArkTS: Scene build in isolated process (--stack-size=${stackKb} KB; modular if build-profile.json5)...\x1b[0m\n`);
749
+ const argv = (0, wasm_runtime_flags_1.buildRelaunchArgv)(workerPath, [rootDir, dbPath, triggerFile], [
750
+ ...process.execArgv,
751
+ `--stack-size=${stackKb}`,
752
+ (0, wasm_runtime_flags_1.maxOldSpaceSizeFlag)(),
753
+ ]);
506
754
  const result = (0, child_process_1.spawnSync)(process.execPath, argv, {
507
755
  stdio: 'inherit',
508
756
  env: process.env,
@@ -558,7 +806,7 @@ function runArkTSBatchFullCore(rootDir, queries, triggerFile, etsFiles, batchKey
558
806
  }
559
807
  }
560
808
  function runArkTSBatchFull(rootDir, queries, triggerFile, etsFiles, batchKey) {
561
- if (shouldUseIsolatedArkTSBuild(etsFiles.length)) {
809
+ if (shouldUseIsolatedArkTSBuild()) {
562
810
  const dbPath = homegraphDbPath(rootDir);
563
811
  if (!fs.existsSync(dbPath)) {
564
812
  throw new Error(`ArkTS isolated build requires ${dbPath}`);
@@ -569,7 +817,8 @@ function runArkTSBatchFull(rootDir, queries, triggerFile, etsFiles, batchKey) {
569
817
  const spawned = spawnIsolatedArkTSBatchWorker(rootDir, dbPath, triggerFile);
570
818
  if (!spawned.ok) {
571
819
  throw new Error(`ArkTS isolated Scene build failed (exit ${spawned.lastCode ?? 'unknown'}). ` +
572
- 'Try HOMEGRAPH_ARKTS_STACK_SIZES_KB=65536,131072,262144');
820
+ 'Unset HOMEGRAPH_ARKTS_ISOLATED to use the default in-process build, ' +
821
+ 'or raise HOMEGRAPH_ARKTS_STACK_SIZES_KB (e.g. 65536,131072,262144)');
573
822
  }
574
823
  return markBatchCommittedAfterWorker(rootDir, triggerFile, etsFiles, batchKey);
575
824
  }
@@ -605,12 +854,28 @@ function runArkTSBatch(rootDir, queries, triggerFile) {
605
854
  return runArkTSBatchFull(rootDir, queries, normalizedTrigger, etsFiles, batchKey);
606
855
  }
607
856
  /** Async batch build with parallel file reads during persist (used by indexAll). */
608
- async function primeArkTSBatch(rootDir, queries, triggerFile) {
857
+ async function primeArkTSBatch(rootDir, queries, triggerFile, options) {
609
858
  const normalizedTrigger = normIndexPath(triggerFile);
610
859
  const etsFiles = scanEtsFiles(rootDir);
611
- const cached = tryReturnCachedBatch(rootDir, normalizedTrigger, etsFiles);
612
- if (cached) {
613
- return;
860
+ const changed = options?.changedFiles
861
+ ? [...options.changedFiles].map(normIndexPath).filter(Boolean)
862
+ : null;
863
+ if (changed) {
864
+ const resolution = resolveDirtyHarmonyModules(rootDir, changed);
865
+ if (resolution.mode === 'none') {
866
+ return;
867
+ }
868
+ if (resolution.mode === 'modules') {
869
+ await runArkTSBatchIncrementalModules(rootDir, queries, normalizedTrigger, etsFiles, resolution.moduleSrcPaths);
870
+ return;
871
+ }
872
+ // mode === 'full': fall through to full rebuild (ignore in-memory cache).
873
+ }
874
+ else {
875
+ const cached = tryReturnCachedBatch(rootDir, normalizedTrigger, etsFiles);
876
+ if (cached) {
877
+ return;
878
+ }
614
879
  }
615
880
  const batchKey = etsFiles.length > 0 ? computeBatchKey(rootDir, etsFiles) : '';
616
881
  batchTriggerFile = null;
@@ -620,7 +885,7 @@ async function primeArkTSBatch(rootDir, queries, triggerFile) {
620
885
  batchBuildCommitted = false;
621
886
  return;
622
887
  }
623
- if (shouldUseIsolatedArkTSBuild(etsFiles.length)) {
888
+ if (shouldUseIsolatedArkTSBuild()) {
624
889
  runArkTSBatchFull(rootDir, queries, normalizedTrigger, etsFiles, batchKey);
625
890
  return;
626
891
  }
@@ -646,6 +911,56 @@ async function primeArkTSBatch(rootDir, queries, triggerFile) {
646
911
  (0, context_1.setArktsBatchRunning)(false);
647
912
  }
648
913
  }
914
+ async function runArkTSBatchIncrementalModules(rootDir, queries, triggerFile, etsFiles, moduleSrcPaths) {
915
+ // Isolated worker has no module-subset protocol yet — fall back to full.
916
+ if (shouldUseIsolatedArkTSBuild()) {
917
+ const batchKey = etsFiles.length > 0 ? computeBatchKey(rootDir, etsFiles) : '';
918
+ runArkTSBatchFull(rootDir, queries, triggerFile, etsFiles, batchKey);
919
+ return;
920
+ }
921
+ batchTriggerFile = null;
922
+ batchPersistedPaths = new Set();
923
+ (0, context_1.setArktsBatchRunning)(true);
924
+ try {
925
+ reportBatchProgress('scene', 0, etsFiles.length);
926
+ const index = buildArkTSIndexByModule(rootDir, etsFiles, { targetModuleSrcPaths: moduleSrcPaths });
927
+ if (index.fileResults.size === 0) {
928
+ const fatal = index.errors.find((e) => e.severity === 'error');
929
+ // Fall back to full project batch when the subset produced nothing.
930
+ const batchKey = etsFiles.length > 0 ? computeBatchKey(rootDir, etsFiles) : '';
931
+ if (fatal) {
932
+ process.stderr.write(`\n\x1b[33m ArkTS: incremental modules failed (${fatal.message}); full rebuild\x1b[0m\n`);
933
+ }
934
+ const full = buildArkTSIndex(rootDir, etsFiles);
935
+ if (full.fileResults.size === 0) {
936
+ throw new Error(fatal?.message ?? 'ArkTS incremental batch produced no indexed files');
937
+ }
938
+ queries.deleteArkTSCrossFileCallEdges();
939
+ await persistBatchResultsAsync(rootDir, queries, full);
940
+ if (full.crossFileEdges.length > 0) {
941
+ const valid = full.crossFileEdges.filter((e) => full.nodeIds.has(e.source) && full.nodeIds.has(e.target));
942
+ if (valid.length > 0)
943
+ queries.insertEdges(valid);
944
+ }
945
+ commitArkTSBatch(rootDir, batchKey, etsFiles, full, triggerFile);
946
+ return;
947
+ }
948
+ persistIncrementalModuleResults(rootDir, queries, index);
949
+ if (index.crossFileEdges.length > 0) {
950
+ const valid = index.crossFileEdges.filter((e) => index.nodeIds.has(e.source) && index.nodeIds.has(e.target));
951
+ if (valid.length > 0) {
952
+ queries.insertEdges(valid);
953
+ }
954
+ }
955
+ // Unique key so a partial index is never mistaken for a full-project cache hit.
956
+ const batchKey = `incremental:${moduleSrcPaths.join(',')}`;
957
+ commitArkTSBatch(rootDir, batchKey, etsFiles, index, triggerFile);
958
+ process.stderr.write(`\n\x1b[33m ArkTS: incremental module reindex done (${moduleSrcPaths.join(', ')})\x1b[0m\n`);
959
+ }
960
+ finally {
961
+ (0, context_1.setArktsBatchRunning)(false);
962
+ }
963
+ }
649
964
  /** True when this `.ets` file was already written by the in-memory ArkTS batch. */
650
965
  function isArkTSBatchPersisted(filePath) {
651
966
  return batchPersistedPaths.has(normIndexPath(filePath));
@@ -965,20 +1280,22 @@ function arkEdge(source, target, kind, extra) {
965
1280
  ...extra,
966
1281
  };
967
1282
  }
968
- function collectComponentScope(scene) {
1283
+ function collectComponentScopeFromFiles(files) {
969
1284
  const scope = [];
970
- for (const cls of scene.getClasses()) {
971
- if (cls.isDefaultArkClass())
972
- continue;
973
- if (cls.hasComponentDecorator()) {
974
- scope.push(cls);
1285
+ for (const file of files) {
1286
+ for (const cls of file.getClasses()) {
1287
+ if (cls.isDefaultArkClass())
1288
+ continue;
1289
+ if (cls.hasComponentDecorator()) {
1290
+ scope.push(cls);
1291
+ }
975
1292
  }
976
1293
  }
977
1294
  return scope;
978
1295
  }
979
- function collectNonUiRtaEntryPoints(scene) {
1296
+ function collectNonUiRtaEntryPointsFromFiles(files) {
980
1297
  const entries = [];
981
- for (const file of scene.getFiles()) {
1298
+ for (const file of files) {
982
1299
  const defaultClass = file.getDefaultClass();
983
1300
  for (const method of defaultClass.getMethods()) {
984
1301
  if (shouldSkipArkMethod(method))
@@ -991,14 +1308,152 @@ function collectNonUiRtaEntryPoints(scene) {
991
1308
  }
992
1309
  return entries;
993
1310
  }
1311
+ /**
1312
+ * Methods another module can call into: module `export` surface as RTA entries.
1313
+ * - exported function/method → itself
1314
+ * - exported class/struct → all of its non-skip methods (with body)
1315
+ * Skips `export … from` re-exports (not this module's IR).
1316
+ */
1317
+ function collectExportedRtaEntryPointsFromFiles(files) {
1318
+ const seen = new Set();
1319
+ const out = [];
1320
+ const addMethod = (method) => {
1321
+ if (shouldSkipArkMethod(method))
1322
+ return;
1323
+ try {
1324
+ if (!method.getCfg())
1325
+ return;
1326
+ }
1327
+ catch {
1328
+ return;
1329
+ }
1330
+ try {
1331
+ const sig = method.getSignature();
1332
+ const key = sig.toString();
1333
+ if (seen.has(key))
1334
+ return;
1335
+ seen.add(key);
1336
+ out.push(sig);
1337
+ }
1338
+ catch {
1339
+ // signature unavailable
1340
+ }
1341
+ };
1342
+ const addClass = (cls) => {
1343
+ try {
1344
+ for (const method of cls.getMethods(true)) {
1345
+ addMethod(method);
1346
+ }
1347
+ }
1348
+ catch {
1349
+ // class IR unavailable
1350
+ }
1351
+ };
1352
+ const visitExportInfos = (infos) => {
1353
+ for (const info of infos) {
1354
+ try {
1355
+ if (info.getFrom?.())
1356
+ continue;
1357
+ }
1358
+ catch {
1359
+ continue;
1360
+ }
1361
+ let arkExport;
1362
+ try {
1363
+ arkExport = info.getArkExport?.() ?? null;
1364
+ }
1365
+ catch {
1366
+ continue;
1367
+ }
1368
+ if (!arkExport || typeof arkExport !== 'object')
1369
+ continue;
1370
+ const exp = arkExport;
1371
+ // Prefer ExportType when present (METHOD=2, CLASS=1, NAME_SPACE=0).
1372
+ let exportType;
1373
+ try {
1374
+ exportType = exp.getExportType?.();
1375
+ }
1376
+ catch {
1377
+ exportType = undefined;
1378
+ }
1379
+ if (exportType === 2 || (exportType === undefined && typeof exp.getDeclaringArkClass === 'function' && typeof exp.getCfg === 'function')) {
1380
+ addMethod(arkExport);
1381
+ continue;
1382
+ }
1383
+ if (exportType === 1 || (exportType === undefined && typeof exp.getMethods === 'function')) {
1384
+ addClass(arkExport);
1385
+ continue;
1386
+ }
1387
+ if (exportType === 0 || typeof exp.getClasses === 'function') {
1388
+ try {
1389
+ for (const cls of exp.getClasses?.() ?? []) {
1390
+ addClass(cls);
1391
+ }
1392
+ }
1393
+ catch {
1394
+ // ignore
1395
+ }
1396
+ const nested = exp.getExportInfos?.();
1397
+ if (nested)
1398
+ visitExportInfos(nested);
1399
+ }
1400
+ }
1401
+ };
1402
+ for (const file of files) {
1403
+ try {
1404
+ visitExportInfos(file.getExportInfos());
1405
+ }
1406
+ catch {
1407
+ // ignore
1408
+ }
1409
+ try {
1410
+ for (const ns of file.getNamespaces()) {
1411
+ try {
1412
+ visitExportInfos(ns.getExportInfos());
1413
+ }
1414
+ catch {
1415
+ // ignore
1416
+ }
1417
+ }
1418
+ }
1419
+ catch {
1420
+ // ignore
1421
+ }
1422
+ }
1423
+ return out;
1424
+ }
1425
+ /** Merge RTA entry signatures, preserving order and dropping duplicates. */
1426
+ function mergeRtaEntryPoints(...groups) {
1427
+ const seen = new Set();
1428
+ const out = [];
1429
+ for (const group of groups) {
1430
+ for (const sig of group) {
1431
+ try {
1432
+ const key = sig.toString();
1433
+ if (seen.has(key))
1434
+ continue;
1435
+ seen.add(key);
1436
+ out.push(sig);
1437
+ }
1438
+ catch {
1439
+ // ignore
1440
+ }
1441
+ }
1442
+ }
1443
+ return out;
1444
+ }
994
1445
  function resolveRtaEntryPoints(scene) {
1446
+ return resolveRtaEntryPointsFromFiles(scene, scene.getFiles());
1447
+ }
1448
+ /** RTA entries scoped to the given files (one Harmony module while it is loaded). */
1449
+ function resolveRtaEntryPointsFromFiles(scene, files) {
995
1450
  const explicit = scene.getEntryPoints();
996
1451
  if (explicit.length > 0) {
997
1452
  return { entryPoints: explicit, dummyMain: null };
998
1453
  }
999
- if (sceneHasArkUiEntries(scene)) {
1454
+ if (filesHaveArkUiEntries(files)) {
1000
1455
  try {
1001
- const componentScope = collectComponentScope(scene);
1456
+ const componentScope = collectComponentScopeFromFiles(files);
1002
1457
  const dummy = new arkanalyzer_1.DummyMainCreater(scene, undefined, componentScope.length > 0 ? componentScope : undefined, true);
1003
1458
  dummy.createDummyMain();
1004
1459
  const dummyMain = dummy.getDummyMain();
@@ -1008,11 +1463,11 @@ function resolveRtaEntryPoints(scene) {
1008
1463
  // fall through
1009
1464
  }
1010
1465
  }
1011
- const fallback = collectNonUiRtaEntryPoints(scene);
1466
+ const fallback = collectNonUiRtaEntryPointsFromFiles(files);
1012
1467
  return { entryPoints: fallback, dummyMain: null };
1013
1468
  }
1014
- function sceneHasArkUiEntries(scene) {
1015
- for (const file of scene.getFiles()) {
1469
+ function filesHaveArkUiEntries(files) {
1470
+ for (const file of files) {
1016
1471
  for (const cls of file.getClasses()) {
1017
1472
  if (cls.isDefaultArkClass())
1018
1473
  continue;
@@ -1049,92 +1504,437 @@ function viewTreeLineFromStmt(stmt) {
1049
1504
  return stmt?.getOriginFullPosition()?.getFirstLine() ?? 1;
1050
1505
  }
1051
1506
  function isViewTreeClassSignature(sig) {
1052
- return sig instanceof arkanalyzer_1.ClassSignature;
1507
+ // Prefer instanceof; duck-type fallback if duplicate arkanalyzer copies break instanceof.
1508
+ if (sig instanceof arkanalyzer_1.ClassSignature)
1509
+ return true;
1510
+ if (sig instanceof arkanalyzer_1.MethodSignature)
1511
+ return false;
1512
+ const anySig = sig;
1513
+ // Require ClassSignature shape — MethodSignature-like and other sigs must not enter getClass().
1514
+ return (typeof anySig.getClassName === 'function' &&
1515
+ typeof anySig.getDeclaringFileSignature === 'function' &&
1516
+ typeof anySig.getMethodSubSignature !== 'function');
1053
1517
  }
1054
- function walkViewTree(node, visit) {
1055
- visit(node);
1056
- for (const child of node.children) {
1057
- walkViewTree(child, visit);
1518
+ /** Skip anonymous / synthetic Ark names for name→id fallback maps. */
1519
+ function isUsableArkTypeName(name) {
1520
+ return !!name && !name.startsWith('%') && !name.startsWith('<');
1521
+ }
1522
+ /** Escape a literal for use inside a RegExp source. */
1523
+ function escapeRegExpLiteral(s) {
1524
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1525
+ }
1526
+ /**
1527
+ * ArkUI framework IR receivers — `View.create` / `Column.pop` / `If.branch`
1528
+ * are synthesizer noise, not project call sites. Never seed name-match from them.
1529
+ */
1530
+ const ARKUI_IR_CALL_RECEIVERS = new Set([
1531
+ 'View',
1532
+ 'If',
1533
+ 'ForEach',
1534
+ 'LazyForEach',
1535
+ 'Repeat',
1536
+ 'Column',
1537
+ 'Row',
1538
+ 'Stack',
1539
+ 'Flex',
1540
+ 'Grid',
1541
+ 'List',
1542
+ 'Swiper',
1543
+ 'Tabs',
1544
+ 'TabContent',
1545
+ 'Button',
1546
+ 'Text',
1547
+ 'Image',
1548
+ 'Scroll',
1549
+ 'WaterFlow',
1550
+ 'RelativeContainer',
1551
+ 'Blank',
1552
+ 'Divider',
1553
+ 'Span',
1554
+ 'Canvas',
1555
+ 'XComponent',
1556
+ ]);
1557
+ const ARKUI_IR_CALL_METHODS = new Set(['create', 'pop', 'branch', 'iterator']);
1558
+ const ARKTS_STDLIB_CALL_RECEIVERS = new Set([
1559
+ 'Array',
1560
+ 'Map',
1561
+ 'Set',
1562
+ 'Object',
1563
+ 'Promise',
1564
+ 'JSON',
1565
+ 'String',
1566
+ 'Number',
1567
+ 'Boolean',
1568
+ 'Error',
1569
+ 'Date',
1570
+ 'Math',
1571
+ 'RegExp',
1572
+ 'Symbol',
1573
+ ]);
1574
+ function isJunkArkCallSeed(className, methodName) {
1575
+ if (ARKTS_STDLIB_CALL_RECEIVERS.has(className))
1576
+ return true;
1577
+ if (ARKUI_IR_CALL_RECEIVERS.has(className) && ARKUI_IR_CALL_METHODS.has(methodName)) {
1578
+ return true;
1058
1579
  }
1580
+ return false;
1059
1581
  }
1060
- function indexViewTreeForClass(ctx, cls, result, relativePath) {
1061
- if (!cls.hasViewTree())
1062
- return;
1063
- const viewTree = cls.getViewTree();
1064
- if (!viewTree)
1065
- return;
1066
- const root = viewTree.getRoot();
1067
- if (!root)
1068
- return;
1069
- const classNodeId = ctx.resolveClassNodeId(cls);
1070
- if (!classNodeId)
1071
- return;
1072
- const buildMethod = cls.getMethods(true).find((m) => m.getName() === 'build');
1073
- if (!buildMethod)
1074
- return;
1075
- const buildId = ctx.ensureMethodNode(buildMethod, relativePath, result, classNodeId);
1076
- if (!buildId)
1077
- return;
1078
- const seen = new Set();
1079
- const link = (source, target, kind, via, line) => {
1080
- const key = `${source}>${target}>${kind}>${via}`;
1081
- if (seen.has(key))
1082
- return;
1083
- seen.add(key);
1084
- ctx.addEdge(result, source, target, kind, relativePath, via, line);
1085
- };
1086
- for (const [field] of viewTree.getStateValues()) {
1087
- const fieldId = ctx.fieldToId.get(field);
1088
- if (fieldId) {
1089
- ctx.markFieldStateDecorators?.(field, fieldId, result);
1090
- const line = field.getOriginFullPosition?.()?.getFirstLine?.() ??
1091
- buildMethod.getImplOriginFullPosition()?.getFirstLine() ??
1092
- 1;
1093
- link(fieldId, buildId, 'references', 'state-binding', line);
1094
- }
1582
+ /**
1583
+ * CFG must not name-link these across modules: each is a unique `Class::method`
1584
+ * key that still fans out to tens of thousands of invoke sites (singletons,
1585
+ * loggers, constructors) — far denser than RTA reachability on unlimited indexes.
1586
+ */
1587
+ const ARKTS_CFG_CROSS_MODULE_METHOD_BLOCKLIST = new Set([
1588
+ 'getInstance',
1589
+ 'constructor',
1590
+ 'toString',
1591
+ 'valueOf',
1592
+ 'showInfo',
1593
+ 'showError',
1594
+ 'showWarn',
1595
+ 'showDebug',
1596
+ 'show',
1597
+ 'create',
1598
+ 'pop',
1599
+ 'push',
1600
+ 'splice',
1601
+ 'isEmpty',
1602
+ 'getContext',
1603
+ 'copyFrom',
1604
+ // High fan-out helpers seen densifying same-module CFG on scene_board
1605
+ 'getGridOccupyStatus',
1606
+ 'isInStatusForGrid',
1607
+ 'isInStatus',
1608
+ 'getStatus',
1609
+ 'isOccupied',
1610
+ 'isFree',
1611
+ 'markStatus',
1612
+ 'markGridForCellAndSpan',
1613
+ 'createRelativePath',
1614
+ 'dealWantParams',
1615
+ 'getPreviewContentWidth',
1616
+ 'getIconArray',
1617
+ 'getSizeX',
1618
+ 'getSizeY',
1619
+ ]);
1620
+ /**
1621
+ * When ArkAnalyzer leaves a cross-module static call as `%unk/.use()`, recover
1622
+ * `Lib.use` from the invoke's original source. Only PascalCase receivers — so
1623
+ * `obj.foo()` / `this.bar()` stay unresolved rather than bare-method magnets.
1624
+ *
1625
+ * Trust short snippets only. Allow `return` / simple `const|let|var x =` prefixes
1626
+ * (`const x = Lib.use(b)` is the common modular IR original-text shape).
1627
+ */
1628
+ function recoverClassMethodFromStmt(stmt, methodName) {
1629
+ if (!stmt || !methodName)
1630
+ return null;
1631
+ let text;
1632
+ try {
1633
+ text = stmt.getOriginalText?.();
1095
1634
  }
1096
- walkViewTree(root, (node) => {
1097
- const sig = node.signature;
1098
- if (sig) {
1099
- if (isViewTreeClassSignature(sig)) {
1100
- const childCls = ctx.scene.getClass(sig);
1101
- if (childCls) {
1102
- const childId = ctx.resolveClassNodeId(childCls);
1103
- if (childId) {
1104
- const firstAttr = node.attributes.values().next().value;
1105
- const line = firstAttr
1106
- ? viewTreeLineFromStmt(firstAttr[0])
1107
- : buildMethod.getImplOriginFullPosition()?.getFirstLine() ?? 1;
1108
- link(buildId, childId, 'references', 'child-component', line);
1109
- }
1110
- }
1111
- }
1112
- else {
1113
- const builderMethod = ctx.scene.getMethod(sig);
1114
- if (builderMethod) {
1115
- const builderId = ctx.ensureMethodNode(builderMethod, relativePath, result, classNodeId);
1635
+ catch {
1636
+ text = undefined;
1637
+ }
1638
+ if (!text)
1639
+ return null;
1640
+ const trimmed = text.trim();
1641
+ if (trimmed.length === 0 || trimmed.length > 160)
1642
+ return null;
1643
+ // Mock / matcher wrappers are not real call seeds.
1644
+ if (/^(?:when|mock|spyOn)\s*\(/i.test(trimmed))
1645
+ return null;
1646
+ const re = new RegExp(`(?:^|(?:return\\s+)|(?:(?:const|let|var)\\s+[\\w$]+\\s*=\\s*))([A-Z][\\w$]*)\\.${escapeRegExpLiteral(methodName)}\\s*\\(`);
1647
+ const m = trimmed.match(re);
1648
+ return m?.[1] ? `${m[1]}.${methodName}` : null;
1649
+ }
1650
+ /**
1651
+ * Free-function call left as `%unk/.getOpaque()`: short text still contains
1652
+ * `getOpaque(` (not `Class.getOpaque`). Caller must import-gate + unique name.
1653
+ */
1654
+ function recoverFreeFunctionFromStmt(stmt, methodName) {
1655
+ if (!stmt || !methodName)
1656
+ return null;
1657
+ if (!/^[a-z][\w$]*$/.test(methodName))
1658
+ return null;
1659
+ let text;
1660
+ try {
1661
+ text = stmt.getOriginalText?.();
1662
+ }
1663
+ catch {
1664
+ text = undefined;
1665
+ }
1666
+ if (!text)
1667
+ return null;
1668
+ const trimmed = text.trim();
1669
+ if (trimmed.length === 0 || trimmed.length > 160)
1670
+ return null;
1671
+ if (/^(?:when|mock|spyOn)\s*\(/i.test(trimmed))
1672
+ return null;
1673
+ // Reject Class.method — that path belongs to recoverClassMethodFromStmt.
1674
+ if (new RegExp(`[A-Z][\\w$]*\\.${escapeRegExpLiteral(methodName)}\\s*\\(`).test(trimmed)) {
1675
+ return null;
1676
+ }
1677
+ const re = new RegExp(`(?:^|[^\\w.$])${escapeRegExpLiteral(methodName)}\\s*\\(`);
1678
+ return re.test(trimmed) ? methodName : null;
1679
+ }
1680
+ /**
1681
+ * Instance invoke whose MethodSignature is `%unk` may still carry a typed base:
1682
+ * ClassType (full sig) or UnclearReferenceType (type name string only — common
1683
+ * when deps are SIGNATURES-only).
1684
+ */
1685
+ function recoverClassMethodFromInstanceType(invoke, methodName) {
1686
+ if (!invoke || !methodName)
1687
+ return null;
1688
+ try {
1689
+ const base = invoke.getBase?.();
1690
+ if (!base)
1691
+ return null;
1692
+ const type = base.getType?.();
1693
+ if (!type)
1694
+ return null;
1695
+ const fromClassSig = type.getClassSignature?.()?.getClassName?.() ??
1696
+ '';
1697
+ if (isUsableArkTypeName(fromClassSig) && !fromClassSig.includes('%')) {
1698
+ return `${fromClassSig}.${methodName}`;
1699
+ }
1700
+ // UnclearReferenceType: getName() === "Base" (no file path).
1701
+ const fromName = type.getName?.() ??
1702
+ '';
1703
+ if (isUsableArkTypeName(fromName) && !fromName.includes('%') && /^[A-Z]/.test(fromName)) {
1704
+ return `${fromName}.${methodName}`;
1705
+ }
1706
+ return null;
1707
+ }
1708
+ catch {
1709
+ return null;
1710
+ }
1711
+ }
1712
+ /**
1713
+ * Modular IR often models `Lib.use` as an instance invoke with base Local named
1714
+ * `Lib` and type unknown — recover from the PascalCase base name.
1715
+ */
1716
+ function recoverClassMethodFromBaseName(invoke, methodName) {
1717
+ if (!invoke || !methodName)
1718
+ return null;
1719
+ try {
1720
+ const base = invoke.getBase?.();
1721
+ if (!base)
1722
+ return null;
1723
+ const baseName = base.getName?.() ?? '';
1724
+ if (!isUsableArkTypeName(baseName) || !/^[A-Z]/.test(baseName) || baseName.includes('%')) {
1725
+ return null;
1726
+ }
1727
+ return `${baseName}.${methodName}`;
1728
+ }
1729
+ catch {
1730
+ return null;
1731
+ }
1732
+ }
1733
+ /**
1734
+ * Cross-module `%unk` invoke → `Class.method`, free function name, or
1735
+ * `instantiates` class. Never emit a bare instance-method name alone.
1736
+ */
1737
+ function callReferenceNameFromSig(sig, opts) {
1738
+ try {
1739
+ const methodName = sig.getMethodSubSignature()?.getMethodName?.() ?? '';
1740
+ if (!methodName || methodName.startsWith('%') || methodName.startsWith('<'))
1741
+ return null;
1742
+ const className = sig.getDeclaringClassSignature()?.getClassName?.() ?? '';
1743
+ if (isUsableArkTypeName(className) && !className.includes('%')) {
1744
+ if (methodName === 'constructor') {
1745
+ return { name: className, kind: 'instantiates' };
1746
+ }
1747
+ if (isJunkArkCallSeed(className, methodName))
1748
+ return null;
1749
+ return { name: `${className}.${methodName}`, kind: 'calls' };
1750
+ }
1751
+ if (methodName === 'constructor')
1752
+ return null;
1753
+ const recovered = recoverClassMethodFromStmt(opts?.stmt, methodName) ??
1754
+ recoverClassMethodFromInstanceType(opts?.invoke, methodName) ??
1755
+ recoverClassMethodFromBaseName(opts?.invoke, methodName);
1756
+ if (recovered) {
1757
+ const dot = recovered.indexOf('.');
1758
+ if (dot > 0 && isJunkArkCallSeed(recovered.slice(0, dot), recovered.slice(dot + 1))) {
1759
+ return null;
1760
+ }
1761
+ return { name: recovered, kind: 'calls' };
1762
+ }
1763
+ const free = recoverFreeFunctionFromStmt(opts?.stmt, methodName);
1764
+ if (free)
1765
+ return { name: free, kind: 'calls', freeFunction: true };
1766
+ return null;
1767
+ }
1768
+ catch {
1769
+ return null;
1770
+ }
1771
+ }
1772
+ /**
1773
+ * True when a ClassSignature still has ArkAnalyzer's placeholder file
1774
+ * (`%unk`) — common on ViewTree stubs after a dependency was evicted before
1775
+ * type inference could rewrite `new LiveCardListView` to a real file path.
1776
+ */
1777
+ function isUnclearArkClassSignature(sig) {
1778
+ try {
1779
+ const fileSig = sig.getDeclaringFileSignature();
1780
+ const fileName = fileSig.getFileName();
1781
+ const projectName = fileSig.getProjectName();
1782
+ if (fileName === '%unk' || projectName === '%unk')
1783
+ return true;
1784
+ if (fileName.includes('%unk') || projectName.includes('%unk'))
1785
+ return true;
1786
+ return sig.toString().includes('%unk');
1787
+ }
1788
+ catch {
1789
+ return true;
1790
+ }
1791
+ }
1792
+ function isUnclearArkMethodSignature(sig) {
1793
+ try {
1794
+ return isUnclearArkClassSignature(sig.getDeclaringClassSignature());
1795
+ }
1796
+ catch {
1797
+ return true;
1798
+ }
1799
+ }
1800
+ /** Among candidate node ids, prefer a single `component` over same-name struct/class. */
1801
+ function pickPreferredClassNodeId(candidateIds, isComponent) {
1802
+ const ids = [...candidateIds];
1803
+ if (ids.length === 0)
1804
+ return null;
1805
+ if (ids.length === 1)
1806
+ return ids[0] ?? null;
1807
+ const components = ids.filter((id) => isComponent(id));
1808
+ if (components.length === 1)
1809
+ return components[0] ?? null;
1810
+ return null;
1811
+ }
1812
+ /** Walk ViewTree with cycle protection (shared/cloned nodes can form graphs). */
1813
+ function walkViewTree(node, visit, seen = new Set()) {
1814
+ if (seen.has(node))
1815
+ return;
1816
+ seen.add(node);
1817
+ visit(node);
1818
+ for (const child of node.children) {
1819
+ walkViewTree(child, visit, seen);
1820
+ }
1821
+ }
1822
+ function indexViewTreeForClass(ctx, cls, result, relativePath) {
1823
+ if (!cls.hasViewTree())
1824
+ return;
1825
+ const viewTree = cls.getViewTree();
1826
+ if (!viewTree)
1827
+ return;
1828
+ const root = viewTree.getRoot();
1829
+ if (!root)
1830
+ return;
1831
+ const classNodeId = ctx.resolveClassNodeId(cls);
1832
+ if (!classNodeId)
1833
+ return;
1834
+ const buildMethod = cls.getMethods(true).find((m) => m.getName() === 'build');
1835
+ if (!buildMethod)
1836
+ return;
1837
+ const buildId = ctx.ensureMethodNode(buildMethod, relativePath, result, classNodeId);
1838
+ if (!buildId)
1839
+ return;
1840
+ const seen = new Set();
1841
+ const link = (source, target, kind, via, line) => {
1842
+ const key = `${source}>${target}>${kind}>${via}`;
1843
+ if (seen.has(key))
1844
+ return;
1845
+ seen.add(key);
1846
+ ctx.addEdge(result, source, target, kind, relativePath, via, line);
1847
+ };
1848
+ for (const [field] of viewTree.getStateValues()) {
1849
+ const fieldId = ctx.resolveFieldNodeId(field);
1850
+ if (fieldId) {
1851
+ ctx.markFieldStateDecorators?.(field, fieldId, result);
1852
+ const line = field.getOriginFullPosition?.()?.getFirstLine?.() ??
1853
+ buildMethod.getImplOriginFullPosition()?.getFirstLine() ??
1854
+ 1;
1855
+ link(fieldId, buildId, 'references', 'state-binding', line);
1856
+ }
1857
+ }
1858
+ walkViewTree(root, (node) => {
1859
+ const sig = node.signature;
1860
+ if (sig) {
1861
+ try {
1862
+ if (isViewTreeClassSignature(sig)) {
1863
+ // Stub or live: prefer already-indexed sig→id (works after dep unload);
1864
+ // %unk stubs also fall back to class name / parent-file import.
1865
+ // Then fall back to live IR object maps when the class is still resident.
1866
+ // Guard getClass — a mis-typed signature must not abort the whole ViewTree walk.
1867
+ let childCls = null;
1868
+ try {
1869
+ childCls = ctx.scene.getClass(sig);
1870
+ }
1871
+ catch {
1872
+ childCls = null;
1873
+ }
1874
+ const childId = ctx.resolveClassIdBySig(sig, cls) ??
1875
+ (childCls ? ctx.resolveClassNodeId(childCls) : null);
1876
+ if (childId) {
1877
+ let line = buildMethod.getImplOriginFullPosition()?.getFirstLine() ?? 1;
1878
+ try {
1879
+ const firstAttr = node.attributes?.values()?.next()?.value;
1880
+ if (firstAttr)
1881
+ line = viewTreeLineFromStmt(firstAttr[0]);
1882
+ }
1883
+ catch {
1884
+ // keep build() line
1885
+ }
1886
+ link(buildId, childId, 'references', 'child-component', line);
1887
+ }
1888
+ }
1889
+ else {
1890
+ // MethodSignature: builder stub (sig only) or live ArkMethod.
1891
+ let builderMethod = null;
1892
+ try {
1893
+ builderMethod = ctx.scene.getMethod(sig);
1894
+ }
1895
+ catch {
1896
+ builderMethod = null;
1897
+ }
1898
+ const builderId = ctx.resolveMethodIdBySig(sig, cls) ??
1899
+ (builderMethod
1900
+ ? ctx.ensureMethodNode(builderMethod, relativePath, result, classNodeId)
1901
+ : null);
1116
1902
  if (builderId) {
1117
- link(buildId, builderId, 'references', 'builder', builderMethod.getImplOriginFullPosition()?.getFirstLine() ?? 1);
1903
+ const line = builderMethod
1904
+ ? builderMethod.getImplOriginFullPosition()?.getFirstLine() ?? 1
1905
+ : buildMethod.getImplOriginFullPosition()?.getFirstLine() ?? 1;
1906
+ link(buildId, builderId, 'references', 'builder', line);
1118
1907
  }
1119
1908
  }
1120
1909
  }
1910
+ catch {
1911
+ // Skip this node's signature edge; keep walking siblings/children.
1912
+ }
1121
1913
  }
1122
1914
  if (node.stateValuesTransfer) {
1123
1915
  for (const [childField, parentValue] of node.stateValuesTransfer) {
1124
- const childFieldId = ctx.fieldToId.get(childField);
1916
+ const childFieldId = ctx.resolveFieldNodeId(childField);
1125
1917
  if (parentValue instanceof arkanalyzer_1.ArkField) {
1126
1918
  const via = stateTransferViaForField(childField);
1127
1919
  if (!via || !childFieldId)
1128
1920
  continue;
1129
1921
  ctx.markFieldStateDecorators?.(childField, childFieldId, result);
1130
- const parentFieldId = ctx.fieldToId.get(parentValue);
1922
+ const parentFieldId = ctx.resolveFieldNodeId(parentValue);
1131
1923
  if (parentFieldId) {
1132
1924
  ctx.markFieldStateDecorators?.(parentValue, parentFieldId, result);
1133
1925
  link(parentFieldId, childFieldId, 'references', via, childField.getOriginFullPosition()?.getFirstLine() ?? 1);
1134
1926
  }
1135
1927
  }
1136
1928
  else if (parentValue instanceof arkanalyzer_1.ArkMethod) {
1137
- const builderId = ctx.ensureMethodNode(parentValue, relativePath, result, classNodeId);
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;
1936
+ }
1937
+ }
1138
1938
  if (childFieldId && builderId) {
1139
1939
  link(builderId, childFieldId, 'references', 'builder-param', childField.getOriginFullPosition()?.getFirstLine() ?? 1);
1140
1940
  }
@@ -1146,12 +1946,15 @@ function indexViewTreeForClass(ctx, cls, result, relativePath) {
1146
1946
  continue;
1147
1947
  const line = viewTreeLineFromStmt(stmt);
1148
1948
  for (const v of values) {
1149
- if (!(v instanceof arkanalyzer_1.MethodSignature))
1949
+ const isMethodSig = v instanceof arkanalyzer_1.MethodSignature ||
1950
+ (v != null &&
1951
+ typeof v.getMethodSubSignature === 'function');
1952
+ if (!isMethodSig)
1150
1953
  continue;
1151
- const handler = ctx.scene.getMethod(v);
1152
- if (!handler)
1153
- continue;
1154
- const handlerId = ctx.ensureMethodNode(handler, relativePath, result, classNodeId);
1954
+ const methodSig = v;
1955
+ const handler = ctx.scene.getMethod(methodSig);
1956
+ const handlerId = ctx.resolveMethodIdBySig(methodSig, cls) ??
1957
+ (handler ? ctx.ensureMethodNode(handler, relativePath, result, classNodeId) : null);
1155
1958
  if (handlerId)
1156
1959
  link(buildId, handlerId, 'references', attr, line);
1157
1960
  }
@@ -1166,100 +1969,536 @@ class ArkTSAdapter {
1166
1969
  classToId = new Map();
1167
1970
  componentToId = new Map();
1168
1971
  fieldToId = new Map();
1972
+ /** Stable across module unload — signature string → HomeGraph node id. */
1973
+ methodSigToId = new Map();
1974
+ classSigToId = new Map();
1975
+ fieldSigToId = new Map();
1976
+ /**
1977
+ * Name → candidate node ids for ViewTree stub stitch when the signature is still
1978
+ * `@%unk/%unk: ClassName` after dependency eviction. Prefer `component` over
1979
+ * same-name struct/class; ambiguous multi-component names use the importer.
1980
+ */
1981
+ classNameToIds = new Map();
1982
+ classIdFilePath = new Map();
1983
+ classIdIsComponent = new Map();
1984
+ /** `ClassName::methodName` → candidate method node ids (unclear MethodSignature stubs). */
1985
+ methodNameKeyToIds = new Map();
1986
+ /** Bare default-class function name → ids (unique import-gated CFG bridge). */
1987
+ functionNameToIds = new Map();
1988
+ methodIdFilePath = new Map();
1989
+ emittedEdgeKeys = new Set();
1169
1990
  nodeIds = new Set();
1170
1991
  fileResults = new Map();
1171
1992
  crossFileEdges = [];
1993
+ /** Per-module `@dummyMain@…` created during analyseByModule (for scene aggregator). */
1994
+ moduleDummyMains = [];
1172
1995
  constructor(rootDir, scannedFiles) {
1173
1996
  this.rootDir = rootDir;
1174
1997
  this.scanned = new Set(scannedFiles);
1175
1998
  }
1176
1999
  build(scene) {
1177
2000
  this.scene = scene;
1178
- const { entryPoints, dummyMain } = resolveRtaEntryPoints(scene);
1179
2001
  for (const arkFile of scene.getFiles()) {
1180
- const relativePath = normalizeRelPath(this.rootDir, arkFile.getFilePath());
1181
- if (!this.scanned.has(relativePath))
1182
- continue;
1183
- if (!isArkAnalyzerSourcePath(relativePath))
2002
+ this.indexScannedFile(arkFile);
2003
+ }
2004
+ this.indexViewTreesFromFiles(scene.getFiles());
2005
+ this.finalizeCallGraph(scene);
2006
+ return this.toBatchIndex();
2007
+ }
2008
+ /**
2009
+ * Index one Harmony module while loaded (baseline = this module):
2010
+ * symbols → rebind resident deps → ViewTree/RTA from this module (B→A via
2011
+ * live IR or sig→id maps for already-indexed unloaded deps).
2012
+ */
2013
+ indexModule(module, scene) {
2014
+ this.scene = scene;
2015
+ const files = [...module.getFilesMap().values()];
2016
+ const moduleFiles = new Set(files.map((f) => normalizeRelPath(this.rootDir, f.getFilePath())).filter(Boolean));
2017
+ for (const arkFile of files) {
2018
+ this.indexScannedFile(arkFile);
2019
+ }
2020
+ // Rebind still-resident deps so live getClass/getMethod works when topo kept them warm.
2021
+ this.bindLiveMapsFromResident(scene);
2022
+ // Incremental / first-in-topo: populate sig→id maps from other loaded modules
2023
+ // (deps at SIGNATURES) without persisting those files into this batch.
2024
+ this.seedMapsFromOtherLoadedFiles(scene, moduleFiles);
2025
+ this.indexViewTreesFromFiles(files);
2026
+ this.ingestModuleCallGraph(scene, module, files, moduleFiles);
2027
+ // CFG: only narrow `%unk` cross-module bridge (no same-module CFG calls).
2028
+ this.stitchModuleCfgCallsAndRefs(files, moduleFiles);
2029
+ // Drop live IR object keys so ArkAnalyzer can GC unloaded modules; keep sig→id maps.
2030
+ this.releaseLiveIrMaps();
2031
+ }
2032
+ /**
2033
+ * Index symbols from already-loaded non-target modules into sig/name maps only.
2034
+ * Used so incremental BODIES targets can still resolve cross-module RTA/ViewTree
2035
+ * edges against deps that stay at SIGNATURES (and are not re-persisted).
2036
+ */
2037
+ seedMapsFromOtherLoadedFiles(scene, excludeFiles) {
2038
+ const seeded = [];
2039
+ for (const mod of scene.getModules()) {
2040
+ let filesMap;
2041
+ try {
2042
+ filesMap = mod.getFilesMap();
2043
+ }
2044
+ catch {
1184
2045
  continue;
1185
- this.indexFile(arkFile);
2046
+ }
2047
+ for (const arkFile of filesMap.values()) {
2048
+ const rel = normalizeRelPath(this.rootDir, arkFile.getFilePath());
2049
+ if (!rel || excludeFiles.has(rel) || this.fileResults.has(rel))
2050
+ continue;
2051
+ if (!this.scanned.has(rel) || !isArkAnalyzerSourcePath(rel))
2052
+ continue;
2053
+ this.indexScannedFile(arkFile);
2054
+ if (this.fileResults.has(rel))
2055
+ seeded.push(rel);
2056
+ }
2057
+ }
2058
+ for (const rel of seeded) {
2059
+ this.fileResults.delete(rel);
1186
2060
  }
1187
- for (const cls of scene.getClasses()) {
1188
- if (!cls.hasViewTree())
2061
+ }
2062
+ bindLiveMapsFromResident(scene) {
2063
+ const cache = scene.getModuleCache();
2064
+ if (!cache)
2065
+ return;
2066
+ const files = [];
2067
+ for (const id of cache.getLoadedModules()) {
2068
+ const mod = scene.getModule(id);
2069
+ if (!mod || mod.getModuleType() === arkanalyzer_1.ModuleType.SDK)
2070
+ continue;
2071
+ if (mod.getLoadState() < arkanalyzer_1.ModuleLoadState.SIGNATURES)
1189
2072
  continue;
1190
- const arkFile = cls.getDeclaringArkFile();
2073
+ files.push(...mod.getFilesMap().values());
2074
+ }
2075
+ if (files.length > 0)
2076
+ this.bindLiveMapsFromFiles(files);
2077
+ }
2078
+ /** Re-attach live Ark* objects to already-indexed HomeGraph node ids (sig maps). */
2079
+ bindLiveMapsFromFiles(files) {
2080
+ for (const arkFile of files) {
1191
2081
  const relativePath = normalizeRelPath(this.rootDir, arkFile.getFilePath());
1192
2082
  if (!this.scanned.has(relativePath) || !isArkAnalyzerSourcePath(relativePath))
1193
2083
  continue;
1194
- const result = this.fileResults.get(relativePath);
1195
- if (!result)
1196
- continue;
2084
+ this.bindLiveClass(arkFile.getDefaultClass());
2085
+ for (const cls of arkFile.getClasses()) {
2086
+ this.bindLiveClass(cls);
2087
+ }
2088
+ for (const ns of arkFile.getNamespaces()) {
2089
+ this.bindLiveNamespace(ns);
2090
+ }
2091
+ }
2092
+ }
2093
+ bindLiveNamespace(ns) {
2094
+ this.bindLiveClass(ns.getDefaultClass());
2095
+ for (const cls of ns.getClasses()) {
2096
+ this.bindLiveClass(cls);
2097
+ }
2098
+ for (const child of ns.getNamespaces()) {
2099
+ this.bindLiveNamespace(child);
2100
+ }
2101
+ }
2102
+ bindLiveClass(cls) {
2103
+ try {
2104
+ const classSig = cls.getSignature().toString();
2105
+ const classId = this.classSigToId.get(classSig);
2106
+ if (classId) {
2107
+ this.classToId.set(cls, classId);
2108
+ if (cls.hasComponentDecorator()) {
2109
+ this.componentToId.set(cls, classId);
2110
+ }
2111
+ }
2112
+ for (const method of cls.getMethods(true)) {
2113
+ try {
2114
+ const mid = this.methodSigToId.get(method.getSignature().toString());
2115
+ if (mid)
2116
+ this.methodToId.set(method, mid);
2117
+ }
2118
+ catch {
2119
+ // skip
2120
+ }
2121
+ }
2122
+ for (const field of cls.getFields()) {
2123
+ const fid = this.fieldSigToId.get(fieldSigKey(classSig, field.getName()));
2124
+ if (fid)
2125
+ this.fieldToId.set(field, fid);
2126
+ }
2127
+ }
2128
+ catch {
2129
+ // signature unavailable
2130
+ }
2131
+ }
2132
+ finalizeCallGraph(scene) {
2133
+ this.scene = scene;
2134
+ // Modular path: intra-module RTA already ran per module. Do NOT run a
2135
+ // scene aggregator RTA — that re-walks overlapping reachable sets and was
2136
+ // a major wall-clock cost. Cross-module gaps link only via exact
2137
+ // signature / unique Class.method maps (no unresolved name-match seeds).
2138
+ if (this.moduleDummyMains.length > 0) {
1197
2139
  try {
1198
- indexViewTreeForClass(this.viewTreeContext(), cls, result, relativePath);
2140
+ for (const m of this.moduleDummyMains) {
2141
+ this.indexArkanalyzerDummyMain(m);
2142
+ }
1199
2143
  }
1200
- catch (e) {
1201
- const message = e instanceof Error ? e.message : String(e);
1202
- result.errors.push({
1203
- message: `ViewTree indexing skipped for ${cls.getName()}: ${message}`,
1204
- filePath: relativePath,
1205
- severity: 'warning',
1206
- });
2144
+ catch {
2145
+ // ignore
1207
2146
  }
2147
+ return;
1208
2148
  }
2149
+ const { entryPoints, dummyMain } = resolveRtaEntryPoints(scene);
1209
2150
  if (dummyMain) {
1210
2151
  this.indexArkanalyzerDummyMain(dummyMain);
1211
2152
  }
2153
+ if (entryPoints.length === 0)
2154
+ return;
1212
2155
  let callGraph;
1213
2156
  try {
1214
- callGraph =
1215
- entryPoints.length > 0 ? scene.makeCallGraphRTA(entryPoints) : null;
2157
+ callGraph = scene.makeCallGraphRTA(entryPoints);
1216
2158
  }
1217
2159
  catch {
1218
- callGraph = null;
2160
+ return;
1219
2161
  }
1220
- if (callGraph) {
1221
- for (const edge of callGraph.getCallEdges()) {
1222
- const caller = callGraph.getArkMethodByFuncID(edge.getSrcID());
1223
- const callee = callGraph.getArkMethodByFuncID(edge.getDstID());
1224
- if (!caller || !callee)
1225
- continue;
1226
- if (shouldSkipArkMethod(caller) &&
1227
- !isAnonymousArkMethod(caller) &&
1228
- !caller.getDeclaringArkClass().isDefaultArkClass() &&
1229
- !caller.isGenerated()) {
1230
- continue;
1231
- }
1232
- const callerId = this.resolveMethodNodeId(caller);
1233
- const calleeId = this.resolveMethodNodeId(callee);
1234
- if (!callerId || !calleeId)
2162
+ this.ingestCallGraph(callGraph, null);
2163
+ }
2164
+ ingestModuleCallGraph(scene, module, files, moduleFiles) {
2165
+ let entryPoints = [];
2166
+ let dummyMain = null;
2167
+ try {
2168
+ // Prefer AA's forModule when present; otherwise DIY with the public ctor
2169
+ // (module-scoped name + this module's classes).
2170
+ const forModule = arkanalyzer_1.DummyMainCreater.forModule;
2171
+ let creater;
2172
+ if (typeof forModule === 'function') {
2173
+ creater = forModule(scene, module, true);
2174
+ }
2175
+ else {
2176
+ const classScope = collectComponentScopeFromFiles(files);
2177
+ const moduleName = module.getModuleName() || 'module';
2178
+ creater = new arkanalyzer_1.DummyMainCreater(scene, `@dummyMain@${moduleName}`, classScope.length > 0 ? classScope : undefined, true);
2179
+ }
2180
+ creater.createDummyMain();
2181
+ dummyMain = creater.getDummyMain();
2182
+ if (dummyMain) {
2183
+ entryPoints = [dummyMain.getSignature()];
2184
+ this.moduleDummyMains.push(dummyMain);
2185
+ }
2186
+ }
2187
+ catch {
2188
+ // Fall back to file-scoped DummyMain (components only) if creation fails.
2189
+ const resolved = resolveRtaEntryPointsFromFiles(scene, files);
2190
+ entryPoints = resolved.entryPoints;
2191
+ dummyMain = resolved.dummyMain;
2192
+ }
2193
+ if (dummyMain) {
2194
+ this.indexArkanalyzerDummyMain(dummyMain);
2195
+ }
2196
+ // Export surface = cross-module call-ins; seed them as RTA entries so
2197
+ // non-UI libraries (Manager.getInstance → …) grow intra-module edges
2198
+ // without waiting for a Component DummyMain to reach them.
2199
+ entryPoints = mergeRtaEntryPoints(entryPoints, collectExportedRtaEntryPointsFromFiles(files));
2200
+ if (entryPoints.length === 0)
2201
+ return;
2202
+ let callGraph;
2203
+ try {
2204
+ callGraph = scene.makeCallGraphRTA(entryPoints);
2205
+ }
2206
+ catch {
2207
+ return;
2208
+ }
2209
+ this.ingestCallGraph(callGraph, moduleFiles);
2210
+ }
2211
+ /**
2212
+ * Keep ArkAnalyzer RTA edges whose **caller** is in the current module.
2213
+ *
2214
+ * - Same-module: live IR id, exact MethodSignature map, or unique Class.method
2215
+ * name fallback (path-drift).
2216
+ * - Cross-module: **exact MethodSignature string or live callee only** — no
2217
+ * Class.method name fallback (that + CFG densified scene_board to ~400k
2218
+ * ark calls vs ~76k on an unlimited RTA index).
2219
+ */
2220
+ ingestCallGraph(callGraph, moduleFiles) {
2221
+ for (const edge of callGraph.getCallEdges()) {
2222
+ const callerArk = callGraph.getArkMethodByFuncID(edge.getSrcID());
2223
+ const calleeArk = callGraph.getArkMethodByFuncID(edge.getDstID());
2224
+ const callerSig = callGraph.getMethodByFuncID(edge.getSrcID());
2225
+ const calleeSig = callGraph.getMethodByFuncID(edge.getDstID());
2226
+ if (callerArk &&
2227
+ shouldSkipArkMethod(callerArk) &&
2228
+ !isAnonymousArkMethod(callerArk) &&
2229
+ !callerArk.getDeclaringArkClass().isDefaultArkClass() &&
2230
+ !callerArk.isGenerated()) {
2231
+ continue;
2232
+ }
2233
+ const callerFile = (callerArk ? relPathForArkFile(this.rootDir, callerArk.getDeclaringArkFile()) : null) ??
2234
+ (callerSig ? this.relPathFromMethodSignature(callerSig) : null) ??
2235
+ '';
2236
+ if (moduleFiles && callerFile && !moduleFiles.has(callerFile)) {
2237
+ continue;
2238
+ }
2239
+ const calleeFile = (calleeArk ? relPathForArkFile(this.rootDir, calleeArk.getDeclaringArkFile()) : null) ??
2240
+ (calleeSig ? this.relPathFromMethodSignature(calleeSig) : null);
2241
+ const callerId = (callerArk ? this.resolveMethodNodeId(callerArk) : null) ??
2242
+ (callerSig ? this.resolveMethodIdBySigExact(callerSig) : null);
2243
+ if (!callerId)
2244
+ continue;
2245
+ const sameModule = !!moduleFiles && !!calleeFile && moduleFiles.has(calleeFile);
2246
+ const dispatch = edge.hasDirectCall() ? 'direct' : 'indirect';
2247
+ // Full-scene path (no moduleFiles): keep prior resolveMethodIdBySig behavior.
2248
+ if (!moduleFiles) {
2249
+ const calleeId = (calleeArk ? this.resolveMethodNodeId(calleeArk) : null) ??
2250
+ (calleeSig ? this.resolveMethodIdBySig(calleeSig) : null);
2251
+ if (!calleeId)
1235
2252
  continue;
1236
- const callerFile = relPathForArkFile(this.rootDir, caller.getDeclaringArkFile());
1237
- const callEdge = arkEdge(callerId, calleeId, 'calls', {
1238
- metadata: {
1239
- synthesizedBy: 'arkanalyzer',
1240
- dispatch: edge.hasDirectCall() ? 'direct' : 'indirect',
1241
- sourceFile: callerFile,
1242
- rtaEntry: caller.isGenerated?.() && caller.getName() === '@dummyMain'
1243
- ? '@dummyMain'
1244
- : undefined,
1245
- },
1246
- });
1247
- const callerResult = this.fileResults.get(callerFile);
1248
- if (callerResult && this.nodeInFile(calleeId, callerFile)) {
1249
- callerResult.edges.push(callEdge);
2253
+ this.emitArkCallEdge(callerId, calleeId, callerFile, dispatch, callerArk);
2254
+ continue;
2255
+ }
2256
+ if (!sameModule) {
2257
+ const calleeId = (calleeArk ? this.resolveMethodNodeId(calleeArk) : null) ??
2258
+ (calleeSig ? this.resolveMethodIdBySigExact(calleeSig) : null);
2259
+ if (calleeId) {
2260
+ this.emitArkCallEdge(callerId, calleeId, callerFile, dispatch, callerArk);
1250
2261
  }
1251
- else {
1252
- this.crossFileEdges.push(callEdge);
2262
+ continue;
2263
+ }
2264
+ const calleeId = (calleeArk ? this.resolveMethodNodeId(calleeArk) : null) ??
2265
+ (calleeSig ? this.resolveMethodIdBySig(calleeSig) : null);
2266
+ if (!calleeId)
2267
+ continue;
2268
+ this.emitArkCallEdge(callerId, calleeId, callerFile, dispatch, callerArk);
2269
+ }
2270
+ }
2271
+ /**
2272
+ * CFG call stitch (modular): no same-module CFG call edges (those were the
2273
+ * ~150k `direct` flood past unlimited RTA). Only the narrow cross-module
2274
+ * `%unk` bridge for `return Lib.use(…)`-style static calls.
2275
+ */
2276
+ stitchModuleCfgCallsAndRefs(files, _moduleFiles) {
2277
+ for (const arkFile of files) {
2278
+ const callerFile = normalizeRelPath(this.rootDir, arkFile.getFilePath());
2279
+ const visitClass = (cls) => {
2280
+ for (const method of cls.getMethods(true)) {
2281
+ if (shouldSkipArkMethod(method) &&
2282
+ !isAnonymousArkMethod(method) &&
2283
+ !method.isGenerated()) {
2284
+ continue;
2285
+ }
2286
+ const callerId = this.resolveMethodNodeId(method) ??
2287
+ (() => {
2288
+ try {
2289
+ return this.methodSigToId.get(method.getSignature().toString()) ?? null;
2290
+ }
2291
+ catch {
2292
+ return null;
2293
+ }
2294
+ })();
2295
+ if (!callerId)
2296
+ continue;
2297
+ let cfg;
2298
+ try {
2299
+ cfg = method.getCfg();
2300
+ }
2301
+ catch {
2302
+ continue;
2303
+ }
2304
+ if (!cfg)
2305
+ continue;
2306
+ for (const stmt of cfg.getStmts()) {
2307
+ let invoke;
2308
+ try {
2309
+ invoke = stmt.getInvokeExpr();
2310
+ }
2311
+ catch {
2312
+ continue;
2313
+ }
2314
+ if (!invoke)
2315
+ continue;
2316
+ let calleeSig;
2317
+ try {
2318
+ calleeSig = invoke.getMethodSignature();
2319
+ }
2320
+ catch {
2321
+ continue;
2322
+ }
2323
+ if (!calleeSig)
2324
+ continue;
2325
+ // Skip clear same-module paths — RTA owns those. Only `%unk` /
2326
+ // missing-path invokes may take the narrow cross-module bridge.
2327
+ const calleeFile = this.relPathFromMethodSignature(calleeSig);
2328
+ if (calleeFile)
2329
+ continue;
2330
+ this.tryLinkCfgCrossModuleUnk(callerId, callerFile, calleeSig, method, stmt, invoke, arkFile);
2331
+ }
1253
2332
  }
2333
+ };
2334
+ visitClass(arkFile.getDefaultClass());
2335
+ for (const cls of arkFile.getClasses())
2336
+ visitClass(cls);
2337
+ const walkNs = (ns) => {
2338
+ visitClass(ns.getDefaultClass());
2339
+ for (const cls of ns.getClasses())
2340
+ visitClass(cls);
2341
+ for (const child of ns.getNamespaces())
2342
+ walkNs(child);
2343
+ };
2344
+ for (const ns of arkFile.getNamespaces())
2345
+ walkNs(ns);
2346
+ }
2347
+ }
2348
+ emitArkCallEdge(callerId, calleeId, callerFile, dispatch, callerArk) {
2349
+ const edgeKey = `${callerId}\0${calleeId}\0calls\0${dispatch}`;
2350
+ if (this.emittedEdgeKeys.has(edgeKey))
2351
+ return;
2352
+ if (this.emittedEdgeKeys.has(`${callerId}\0${calleeId}\0calls\0direct`))
2353
+ return;
2354
+ if (this.emittedEdgeKeys.has(`${callerId}\0${calleeId}\0calls\0indirect`))
2355
+ return;
2356
+ this.emittedEdgeKeys.add(edgeKey);
2357
+ const callEdge = arkEdge(callerId, calleeId, 'calls', {
2358
+ metadata: {
2359
+ synthesizedBy: 'arkanalyzer',
2360
+ dispatch,
2361
+ sourceFile: callerFile || undefined,
2362
+ rtaEntry: callerArk?.isGenerated?.() && callerArk.getName() === '@dummyMain'
2363
+ ? '@dummyMain'
2364
+ : undefined,
2365
+ },
2366
+ });
2367
+ const callerResult = callerFile ? this.fileResults.get(callerFile) : undefined;
2368
+ if (callerResult && this.nodeInFile(calleeId, callerFile)) {
2369
+ callerResult.edges.push(callEdge);
2370
+ }
2371
+ else {
2372
+ this.crossFileEdges.push(callEdge);
2373
+ }
2374
+ }
2375
+ /**
2376
+ * CFG `%unk` bridge: recover Class.method / free function from invoke crumbs
2377
+ * (stmt text, UnclearReferenceType name, PascalCase base local), require
2378
+ * import + unique indexed target, skip fan-out blocklist. No unresolved refs.
2379
+ */
2380
+ tryLinkCfgCrossModuleUnk(callerId, callerFile, calleeSig, callerArk, stmt, invoke, arkFile) {
2381
+ // Only for unclear / %unk signatures — clear typed cross-module invokes
2382
+ // stay RTA-only so we do not densify past unlimited RTA.
2383
+ if (!isUnclearArkMethodSignature(calleeSig)) {
2384
+ try {
2385
+ const className = calleeSig.getDeclaringClassSignature()?.getClassName?.() ?? '';
2386
+ if (isUsableArkTypeName(className) && !className.includes('%'))
2387
+ return;
1254
2388
  }
2389
+ catch {
2390
+ // treat as unclear
2391
+ }
2392
+ }
2393
+ const parsed = callReferenceNameFromSig(calleeSig, { stmt, invoke });
2394
+ if (!parsed || parsed.kind !== 'calls')
2395
+ return;
2396
+ if (parsed.freeFunction) {
2397
+ const fnName = parsed.name;
2398
+ if (!fnName || ARKTS_CFG_CROSS_MODULE_METHOD_BLOCKLIST.has(fnName))
2399
+ return;
2400
+ if (!this.fileImportsLocalName(arkFile, fnName))
2401
+ return;
2402
+ const calleeId = this.resolveFunctionIdByName(fnName);
2403
+ if (!calleeId)
2404
+ return;
2405
+ this.emitArkCallEdge(callerId, calleeId, callerFile, 'direct', callerArk);
2406
+ return;
2407
+ }
2408
+ const dot = parsed.name.indexOf('.');
2409
+ if (dot <= 0)
2410
+ return;
2411
+ const className = parsed.name.slice(0, dot);
2412
+ const methodName = parsed.name.slice(dot + 1);
2413
+ if (!className || !methodName)
2414
+ return;
2415
+ if (ARKTS_CFG_CROSS_MODULE_METHOD_BLOCKLIST.has(methodName))
2416
+ return;
2417
+ if (isJunkArkCallSeed(className, methodName))
2418
+ return;
2419
+ if (!this.fileImportsLocalName(arkFile, className))
2420
+ return;
2421
+ const calleeId = this.resolveMethodIdByName(className, methodName);
2422
+ if (!calleeId)
2423
+ return;
2424
+ this.emitArkCallEdge(callerId, calleeId, callerFile, 'direct', callerArk);
2425
+ }
2426
+ fileImportsLocalName(arkFile, localName) {
2427
+ try {
2428
+ return !!arkFile.getImportInfoBy(localName);
1255
2429
  }
2430
+ catch {
2431
+ return false;
2432
+ }
2433
+ }
2434
+ /** Exact MethodSignature.toString() → node id (no Class.method name fallback). */
2435
+ resolveMethodIdBySigExact(sig) {
2436
+ try {
2437
+ return this.methodSigToId.get(sig.toString()) ?? null;
2438
+ }
2439
+ catch {
2440
+ return null;
2441
+ }
2442
+ }
2443
+ /** Project-relative path from a MethodSignature's declaring file (may be %unk). */
2444
+ relPathFromMethodSignature(sig) {
2445
+ try {
2446
+ const fileName = sig.getDeclaringClassSignature().getDeclaringFileSignature().getFileName();
2447
+ if (!fileName || fileName === '%unk' || fileName.includes('%unk'))
2448
+ return null;
2449
+ return normalizeRelPath(this.rootDir, fileName);
2450
+ }
2451
+ catch {
2452
+ return null;
2453
+ }
2454
+ }
2455
+ releaseLiveIrMaps() {
2456
+ this.methodToId.clear();
2457
+ this.classToId.clear();
2458
+ this.componentToId.clear();
2459
+ this.fieldToId.clear();
2460
+ }
2461
+ toBatchIndex(errors = []) {
1256
2462
  return {
1257
2463
  fileResults: this.fileResults,
1258
2464
  crossFileEdges: this.crossFileEdges,
1259
2465
  nodeIds: this.nodeIds,
1260
- errors: [],
2466
+ errors: [...errors],
1261
2467
  };
1262
2468
  }
2469
+ indexScannedFile(arkFile) {
2470
+ const relativePath = normalizeRelPath(this.rootDir, arkFile.getFilePath());
2471
+ if (!this.scanned.has(relativePath))
2472
+ return;
2473
+ if (!isArkAnalyzerSourcePath(relativePath))
2474
+ return;
2475
+ this.indexFile(arkFile);
2476
+ }
2477
+ indexViewTreesFromFiles(files) {
2478
+ for (const arkFile of files) {
2479
+ const relativePath = normalizeRelPath(this.rootDir, arkFile.getFilePath());
2480
+ if (!this.scanned.has(relativePath) || !isArkAnalyzerSourcePath(relativePath))
2481
+ continue;
2482
+ const result = this.fileResults.get(relativePath);
2483
+ if (!result)
2484
+ continue;
2485
+ for (const cls of arkFile.getClasses()) {
2486
+ if (!cls.hasViewTree())
2487
+ continue;
2488
+ try {
2489
+ indexViewTreeForClass(this.viewTreeContext(), cls, result, relativePath);
2490
+ }
2491
+ catch (e) {
2492
+ const message = e instanceof Error ? e.message : String(e);
2493
+ result.errors.push({
2494
+ message: `ViewTree indexing skipped for ${cls.getName()}: ${message}`,
2495
+ filePath: relativePath,
2496
+ severity: 'warning',
2497
+ });
2498
+ }
2499
+ }
2500
+ }
2501
+ }
1263
2502
  /** Index ArkAnalyzer's in-scene @dummyMain (DummyMainCreater output), not a HomeGraph synthetic node. */
1264
2503
  indexArkanalyzerDummyMain(dummyMain) {
1265
2504
  const result = this.ensureFileResult(ARKANALYZER_DUMMY_FILE, 1);
@@ -1414,6 +2653,13 @@ class ArkTSAdapter {
1414
2653
  });
1415
2654
  this.addNode(result, classNode);
1416
2655
  this.classToId.set(cls, classNode.id);
2656
+ try {
2657
+ this.classSigToId.set(cls.getSignature().toString(), classNode.id);
2658
+ }
2659
+ catch {
2660
+ // signature unavailable
2661
+ }
2662
+ this.rememberClassName(displayName, classNode.id, relativePath, false);
1417
2663
  result.edges.push(arkEdge(parentId, classNode.id, 'contains'));
1418
2664
  this.indexDecorators(result, classNode.id, cls, relativePath, language, line);
1419
2665
  if (cls.isAnonymousClass()) {
@@ -1434,6 +2680,13 @@ class ArkTSAdapter {
1434
2680
  const componentNode = makeNode(relativePath, language, 'component', displayName, qn, line, endLine, col, { ...modelModifiersToNodeExtras(cls) });
1435
2681
  this.addNode(result, componentNode);
1436
2682
  this.componentToId.set(cls, componentNode.id);
2683
+ try {
2684
+ this.classSigToId.set(cls.getSignature().toString(), componentNode.id);
2685
+ }
2686
+ catch {
2687
+ // signature unavailable
2688
+ }
2689
+ this.rememberClassName(displayName, componentNode.id, relativePath, true);
1437
2690
  result.edges.push(arkEdge(classNode.id, componentNode.id, 'contains'));
1438
2691
  }
1439
2692
  const superName = cls.getSuperClassName();
@@ -1518,6 +2771,12 @@ class ArkTSAdapter {
1518
2771
  });
1519
2772
  this.addNode(result, fieldNode);
1520
2773
  this.fieldToId.set(field, fieldNode.id);
2774
+ try {
2775
+ this.fieldSigToId.set(fieldSigKey(cls.getSignature().toString(), name), fieldNode.id);
2776
+ }
2777
+ catch {
2778
+ // signature unavailable
2779
+ }
1521
2780
  result.edges.push(arkEdge(parentId, fieldNode.id, 'contains'));
1522
2781
  this.indexDecorators(result, fieldNode.id, field, relativePath, language, line);
1523
2782
  }
@@ -1536,6 +2795,9 @@ class ArkTSAdapter {
1536
2795
  addEdge: (result, source, target, kind, callerFile, via, line) => this.addViewTreeEdge(result, source, target, kind, callerFile, via, line),
1537
2796
  ensureMethodNode: (method, relativePath, result, parentId) => this.ensureMethodNode(method, relativePath, result, parentId),
1538
2797
  resolveClassNodeId: (cls) => this.resolveClassNodeId(cls),
2798
+ resolveClassIdBySig: (sig, importer) => this.resolveClassIdBySig(sig, importer),
2799
+ resolveMethodIdBySig: (sig, importer) => this.resolveMethodIdBySig(sig, importer),
2800
+ resolveFieldNodeId: (field) => this.resolveFieldNodeId(field),
1539
2801
  markFieldStateDecorators: (field, fieldNodeId, result) => {
1540
2802
  const kinds = stateDecoratorKinds(field);
1541
2803
  if (kinds.length === 0)
@@ -1547,7 +2809,170 @@ class ArkTSAdapter {
1547
2809
  };
1548
2810
  }
1549
2811
  resolveClassNodeId(cls) {
1550
- return this.componentToId.get(cls) ?? this.classToId.get(cls) ?? null;
2812
+ const live = this.componentToId.get(cls) ?? this.classToId.get(cls);
2813
+ if (live)
2814
+ return live;
2815
+ try {
2816
+ return this.classSigToId.get(cls.getSignature().toString()) ?? null;
2817
+ }
2818
+ catch {
2819
+ return null;
2820
+ }
2821
+ }
2822
+ rememberClassName(name, nodeId, filePath, isComponent) {
2823
+ if (!isUsableArkTypeName(name))
2824
+ return;
2825
+ let set = this.classNameToIds.get(name);
2826
+ if (!set) {
2827
+ set = new Set();
2828
+ this.classNameToIds.set(name, set);
2829
+ }
2830
+ set.add(nodeId);
2831
+ this.classIdFilePath.set(nodeId, filePath);
2832
+ if (isComponent)
2833
+ this.classIdIsComponent.set(nodeId, true);
2834
+ }
2835
+ rememberMethodName(className, methodName, nodeId, filePath) {
2836
+ const key = `${className}::${methodName}`;
2837
+ let set = this.methodNameKeyToIds.get(key);
2838
+ if (!set) {
2839
+ set = new Set();
2840
+ this.methodNameKeyToIds.set(key, set);
2841
+ }
2842
+ set.add(nodeId);
2843
+ this.methodIdFilePath.set(nodeId, filePath);
2844
+ }
2845
+ rememberFunctionName(functionName, nodeId, filePath) {
2846
+ if (!isUsableArkTypeName(functionName) || !/^[a-z]/.test(functionName))
2847
+ return;
2848
+ let set = this.functionNameToIds.get(functionName);
2849
+ if (!set) {
2850
+ set = new Set();
2851
+ this.functionNameToIds.set(functionName, set);
2852
+ }
2853
+ set.add(nodeId);
2854
+ this.methodIdFilePath.set(nodeId, filePath);
2855
+ }
2856
+ resolveFunctionIdByName(functionName) {
2857
+ const ids = this.functionNameToIds.get(functionName);
2858
+ if (!ids || ids.size !== 1)
2859
+ return null;
2860
+ return [...ids][0] ?? null;
2861
+ }
2862
+ resolveClassIdByName(className, importer) {
2863
+ const ids = this.classNameToIds.get(className);
2864
+ if (!ids || ids.size === 0)
2865
+ return null;
2866
+ let candidates = [...ids];
2867
+ // Same-file components (ForEach item builders, local @Component) have no import —
2868
+ // prefer ids from the importer's file before treating the name as ambiguous.
2869
+ if (importer) {
2870
+ try {
2871
+ const importerPath = normalizeRelPath(this.rootDir, importer.getDeclaringArkFile().getFilePath());
2872
+ const sameFile = candidates.filter((id) => this.classIdFilePath.get(id) === importerPath);
2873
+ if (sameFile.length > 0)
2874
+ candidates = sameFile;
2875
+ }
2876
+ catch {
2877
+ // keep full candidate set
2878
+ }
2879
+ }
2880
+ const preferred = pickPreferredClassNodeId(candidates, (id) => !!this.classIdIsComponent.get(id));
2881
+ if (preferred)
2882
+ return preferred;
2883
+ if (!importer)
2884
+ return null;
2885
+ try {
2886
+ const importInfo = importer.getDeclaringArkFile().getImportInfoBy(className);
2887
+ if (!importInfo)
2888
+ return null;
2889
+ const exportInfo = importInfo.getLazyExportInfo?.() ?? null;
2890
+ const arkExport = exportInfo?.getArkExport?.() ?? null;
2891
+ if (arkExport instanceof arkanalyzer_1.ArkClass) {
2892
+ const byLive = this.resolveClassNodeId(arkExport);
2893
+ if (byLive)
2894
+ return byLive;
2895
+ }
2896
+ const from = importInfo.getFrom?.();
2897
+ if (!from)
2898
+ return null;
2899
+ const fromBase = path.basename(from).replace(/\.(ets|ts|d\.ts)$/i, '');
2900
+ const byPath = [...ids].filter((id) => {
2901
+ const fp = this.classIdFilePath.get(id);
2902
+ if (!fp)
2903
+ return false;
2904
+ const norm = fp.replace(/\\/g, '/');
2905
+ const fromNorm = from.replace(/\\/g, '/');
2906
+ return (norm.includes(fromNorm) ||
2907
+ fromNorm.includes(norm) ||
2908
+ path.basename(norm, path.extname(norm)) === fromBase ||
2909
+ path.basename(norm, path.extname(norm)) === className);
2910
+ });
2911
+ return pickPreferredClassNodeId(byPath, (id) => !!this.classIdIsComponent.get(id));
2912
+ }
2913
+ catch {
2914
+ return null;
2915
+ }
2916
+ }
2917
+ resolveMethodIdByName(className, methodName) {
2918
+ const key = `${className}::${methodName}`;
2919
+ const ids = this.methodNameKeyToIds.get(key);
2920
+ if (!ids || ids.size !== 1)
2921
+ return null;
2922
+ return [...ids][0] ?? null;
2923
+ }
2924
+ resolveClassIdBySig(sig, importer) {
2925
+ try {
2926
+ const exact = this.classSigToId.get(sig.toString());
2927
+ if (exact)
2928
+ return exact;
2929
+ }
2930
+ catch {
2931
+ // fall through to name fallback when possible
2932
+ }
2933
+ // Name fallback for %unk stubs AND clear-but-unmapped sig strings (path drift).
2934
+ // Same-file / import disambiguation happens inside resolveClassIdByName.
2935
+ try {
2936
+ const className = sig.getClassName();
2937
+ return className ? this.resolveClassIdByName(className, importer) : null;
2938
+ }
2939
+ catch {
2940
+ return null;
2941
+ }
2942
+ }
2943
+ resolveMethodIdBySig(sig, _importer) {
2944
+ try {
2945
+ const exact = this.methodSigToId.get(sig.toString());
2946
+ if (exact)
2947
+ return exact;
2948
+ }
2949
+ catch {
2950
+ // fall through
2951
+ }
2952
+ // Name fallback for %unk stubs AND path-drifted but clear signatures so
2953
+ // RTA edges to signature-only / unloaded callees still map to indexed nodes.
2954
+ try {
2955
+ const className = sig.getDeclaringClassSignature().getClassName();
2956
+ const methodName = sig.getMethodSubSignature().getMethodName();
2957
+ if (!className || !methodName)
2958
+ return null;
2959
+ return this.resolveMethodIdByName(className, methodName);
2960
+ }
2961
+ catch {
2962
+ return null;
2963
+ }
2964
+ }
2965
+ resolveFieldNodeId(field) {
2966
+ const live = this.fieldToId.get(field);
2967
+ if (live)
2968
+ return live;
2969
+ try {
2970
+ const classSig = field.getDeclaringArkClass().getSignature().toString();
2971
+ return this.fieldSigToId.get(fieldSigKey(classSig, field.getName())) ?? null;
2972
+ }
2973
+ catch {
2974
+ return null;
2975
+ }
1551
2976
  }
1552
2977
  /** Parent node id for a method: owning class/component, or file for default-class methods. */
1553
2978
  resolveMethodParentId(method, relativePath, result) {
@@ -1570,6 +2995,16 @@ class ArkTSAdapter {
1570
2995
  const existing = this.methodToId.get(method);
1571
2996
  if (existing)
1572
2997
  return existing;
2998
+ try {
2999
+ const bySig = this.methodSigToId.get(method.getSignature().toString());
3000
+ if (bySig) {
3001
+ this.methodToId.set(method, bySig);
3002
+ return bySig;
3003
+ }
3004
+ }
3005
+ catch {
3006
+ // fall through
3007
+ }
1573
3008
  if (shouldSkipArkMethod(method) && !isAnonymousArkMethod(method))
1574
3009
  return null;
1575
3010
  const displayName = arkMethodDisplayName(method);
@@ -1594,6 +3029,10 @@ class ArkTSAdapter {
1594
3029
  return this.methodToId.get(method) ?? null;
1595
3030
  }
1596
3031
  addViewTreeEdge(result, source, target, kind, callerFile, via, line) {
3032
+ const edgeKey = `${source}\0${target}\0${kind}\0${via}`;
3033
+ if (this.emittedEdgeKeys.has(edgeKey))
3034
+ return;
3035
+ this.emittedEdgeKeys.add(edgeKey);
1597
3036
  const edge = arkEdge(source, target, kind, {
1598
3037
  metadata: {
1599
3038
  synthesizedBy: 'viewtree',
@@ -1612,6 +3051,16 @@ class ArkTSAdapter {
1612
3051
  const existing = this.methodToId.get(method);
1613
3052
  if (existing)
1614
3053
  return existing;
3054
+ try {
3055
+ const bySig = this.methodSigToId.get(method.getSignature().toString());
3056
+ if (bySig) {
3057
+ this.methodToId.set(method, bySig);
3058
+ return bySig;
3059
+ }
3060
+ }
3061
+ catch {
3062
+ // fall through
3063
+ }
1615
3064
  if (shouldSkipArkMethod(method) && !isAnonymousArkMethod(method))
1616
3065
  return null;
1617
3066
  const displayName = arkMethodDisplayName(method);
@@ -1638,6 +3087,24 @@ class ArkTSAdapter {
1638
3087
  });
1639
3088
  this.addNode(result, methodNode);
1640
3089
  this.methodToId.set(method, methodNode.id);
3090
+ try {
3091
+ this.methodSigToId.set(method.getSignature().toString(), methodNode.id);
3092
+ }
3093
+ catch {
3094
+ // signature unavailable
3095
+ }
3096
+ try {
3097
+ const ownerName = classDisplayName(cls);
3098
+ if (isUsableArkTypeName(ownerName) && isUsableArkTypeName(displayName)) {
3099
+ this.rememberMethodName(ownerName, displayName, methodNode.id, relativePath);
3100
+ }
3101
+ else if (cls.isDefaultArkClass() && kind === 'function') {
3102
+ this.rememberFunctionName(displayName, methodNode.id, relativePath);
3103
+ }
3104
+ }
3105
+ catch {
3106
+ // class name unavailable
3107
+ }
1641
3108
  result.edges.push(arkEdge(parentId, methodNode.id, 'contains'));
1642
3109
  this.indexDecorators(result, methodNode.id, method, relativePath, language, line);
1643
3110
  this.indexNativeNapiCalls(method, methodNode.id, relativePath, language, result);
@@ -1714,10 +3181,11 @@ function drainArkTSIndexNotices() {
1714
3181
  arktsIndexNotices = [];
1715
3182
  return out;
1716
3183
  }
1717
- function buildSceneConfig(rootDir) {
3184
+ function buildSceneConfig(rootDir, extra) {
1718
3185
  return (0, arkanalyzer_1.buildSceneConfigFromProject)(rootDir, process.env.OHOS_SDK_HOME, {
1719
3186
  supportFileExts: ['.ets', '.ts', '.d.ts'],
1720
3187
  enableMethodBodyBuild: true,
3188
+ ...(extra?.memoryLimitMB !== undefined ? { memoryLimitMB: extra.memoryLimitMB } : {}),
1721
3189
  });
1722
3190
  }
1723
3191
  function tryBuildArkScene(rootDir) {
@@ -1742,22 +3210,158 @@ function emptyArkTSBatchIndex(errors) {
1742
3210
  errors,
1743
3211
  };
1744
3212
  }
1745
- function buildArkTSIndex(rootDir, scannedFiles) {
1746
- const { scene, errors } = tryBuildArkScene(rootDir);
1747
- if (!scene) {
1748
- return emptyArkTSBatchIndex(errors);
1749
- }
3213
+ /**
3214
+ * Harmony multi-module path: load each PROJECT module to BODIES, index into
3215
+ * HomeGraph structures in the callback, let ArkAnalyzer evict under memoryLimitMB.
3216
+ * When `targetModuleSrcPaths` is set, only those modules are BODIES targets
3217
+ * (deps still load at SIGNATURES) — used for incremental sync.
3218
+ *
3219
+ * Runs with the #850 liveness watchdog suspended: ArkAnalyzer's native BODIES
3220
+ * load between module callbacks routinely exceeds the 60s heartbeat window on
3221
+ * large Harmony projects, and there is no JS yield point inside that span.
3222
+ */
3223
+ function buildArkTSIndexByModule(rootDir, scannedFiles, options) {
3224
+ return (0, liveness_watchdog_1.runWithoutLivenessWatchdog)(() => buildArkTSIndexByModuleInner(rootDir, scannedFiles, options));
3225
+ }
3226
+ function buildArkTSIndexByModuleInner(rootDir, scannedFiles, options) {
3227
+ const errors = [];
3228
+ const memoryLimitMB = resolveArkTSSceneMemoryLimitMB();
3229
+ const sceneConfig = buildSceneConfig(rootDir, { memoryLimitMB });
3230
+ const scene = new arkanalyzer_1.Scene();
3231
+ const adapter = new ArkTSAdapter(rootDir, scannedFiles);
3232
+ let modulesIndexed = 0;
3233
+ const targetSrc = options?.targetModuleSrcPaths?.map(normalizeHarmonyModuleSrcPath);
3234
+ const targetSrcSet = targetSrc && targetSrc.length > 0 ? new Set(targetSrc) : null;
1750
3235
  try {
1751
- const adapter = new ArkTSAdapter(rootDir, scannedFiles);
1752
- const index = adapter.build(scene);
1753
- index.errors.push(...errors);
3236
+ scene.config(sceneConfig);
3237
+ const config = new arkanalyzer_1.ModuleAnalysisConfig();
3238
+ config.setLoadLevel(arkanalyzer_1.ModuleDepthLevel.BODIES);
3239
+ // SIGNATURES is enough for ViewTree stubs / types; BODIES on deps caused
3240
+ // each module's RTA to re-walk shared library CFGs (multi-hour indexes).
3241
+ config.setDependencyLoadLevel(arkanalyzer_1.ModuleDepthLevel.SIGNATURES);
3242
+ if (targetSrcSet) {
3243
+ // Prep + SDK only (no PROJECT targets) so we can resolve ModuleIDs.
3244
+ scene.analyseByModule(() => { }, new arkanalyzer_1.ModuleAnalysisConfig());
3245
+ const wantAbs = new Set([...targetSrcSet].map((src) => {
3246
+ const resolved = path.resolve(rootDir, src);
3247
+ try {
3248
+ return fs.realpathSync(resolved).toLowerCase();
3249
+ }
3250
+ catch {
3251
+ return resolved.toLowerCase();
3252
+ }
3253
+ }));
3254
+ const ids = [];
3255
+ for (const mod of scene.getModules()) {
3256
+ if (mod.getModuleType() !== arkanalyzer_1.ModuleType.PROJECT)
3257
+ continue;
3258
+ let modAbs;
3259
+ try {
3260
+ modAbs = fs.realpathSync(mod.getModulePath());
3261
+ }
3262
+ catch {
3263
+ modAbs = path.resolve(mod.getModulePath());
3264
+ }
3265
+ if (wantAbs.has(modAbs.toLowerCase())) {
3266
+ ids.push(scene.getModuleId(mod));
3267
+ }
3268
+ }
3269
+ if (ids.length === 0) {
3270
+ errors.push({
3271
+ message: `ArkTS incremental: no modules matched [${[...targetSrcSet].join(', ')}]`,
3272
+ severity: 'warning',
3273
+ });
3274
+ return emptyArkTSBatchIndex(errors);
3275
+ }
3276
+ 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`);
3278
+ }
3279
+ else {
3280
+ 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`);
3282
+ }
3283
+ scene.analyseByModule((module, scn) => {
3284
+ adapter.indexModule(module, scn);
3285
+ modulesIndexed++;
3286
+ const name = module.getModuleName() || path.basename(module.getModulePath());
3287
+ process.stderr.write(`\x1b[33m ArkTS: indexed module ${modulesIndexed}: ${name}\x1b[0m\n`);
3288
+ }, config);
3289
+ if (modulesIndexed === 0) {
3290
+ errors.push({
3291
+ message: 'ArkTS analyseByModule found no PROJECT modules (check build-profile.json5); falling back to full Scene build',
3292
+ severity: 'warning',
3293
+ });
3294
+ return emptyArkTSBatchIndex(errors);
3295
+ }
3296
+ try {
3297
+ adapter.finalizeCallGraph(scene);
3298
+ }
3299
+ catch (e) {
3300
+ const message = e instanceof Error ? e.message : String(e);
3301
+ errors.push({
3302
+ message: `ArkTS modular leftover RTA skipped: ${message}`,
3303
+ severity: 'warning',
3304
+ });
3305
+ }
3306
+ const index = adapter.toBatchIndex(errors);
3307
+ if (index.fileResults.size === 0) {
3308
+ errors.push({
3309
+ message: 'ArkTS analyseByModule indexed modules but no scanned source files matched',
3310
+ severity: 'warning',
3311
+ });
3312
+ return emptyArkTSBatchIndex(errors);
3313
+ }
1754
3314
  return index;
1755
3315
  }
1756
3316
  catch (e) {
1757
3317
  const message = e instanceof Error ? e.message : String(e);
1758
- errors.push({ message: `ArkTS adapter failed: ${message}`, severity: 'error' });
3318
+ errors.push({
3319
+ message: `ArkTS analyseByModule failed: ${message}; falling back to full Scene build`,
3320
+ severity: 'warning',
3321
+ });
1759
3322
  return emptyArkTSBatchIndex(errors);
1760
3323
  }
3324
+ finally {
3325
+ try {
3326
+ scene.dispose();
3327
+ }
3328
+ catch {
3329
+ // ignore dispose failures
3330
+ }
3331
+ }
3332
+ }
3333
+ function buildArkTSIndexFull(rootDir, scannedFiles, priorErrors = []) {
3334
+ // Full Scene build is the same opaque native stall as analyseByModule.
3335
+ return (0, liveness_watchdog_1.runWithoutLivenessWatchdog)(() => {
3336
+ const { scene, errors } = tryBuildArkScene(rootDir);
3337
+ const allErrors = [...priorErrors, ...errors];
3338
+ if (!scene) {
3339
+ return emptyArkTSBatchIndex(allErrors);
3340
+ }
3341
+ try {
3342
+ const adapter = new ArkTSAdapter(rootDir, scannedFiles);
3343
+ const index = adapter.build(scene);
3344
+ index.errors.push(...allErrors);
3345
+ return index;
3346
+ }
3347
+ catch (e) {
3348
+ const message = e instanceof Error ? e.message : String(e);
3349
+ allErrors.push({ message: `ArkTS adapter failed: ${message}`, severity: 'error' });
3350
+ return emptyArkTSBatchIndex(allErrors);
3351
+ }
3352
+ });
3353
+ }
3354
+ function buildArkTSIndex(rootDir, scannedFiles) {
3355
+ if (shouldUseModularArkTSBuild(rootDir)) {
3356
+ const modular = buildArkTSIndexByModule(rootDir, scannedFiles);
3357
+ const fatal = modular.errors.find((e) => e.severity === 'error');
3358
+ if (modular.fileResults.size > 0 && !fatal) {
3359
+ return modular;
3360
+ }
3361
+ // Warnings like "no PROJECT modules" / modular failure → full Scene fallback.
3362
+ return buildArkTSIndexFull(rootDir, scannedFiles, modular.errors);
3363
+ }
3364
+ return buildArkTSIndexFull(rootDir, scannedFiles);
1761
3365
  }
1762
3366
  // =============================================================================
1763
3367
  // OHOS SDK input resolution + prebuilt API database indexing