homegraph 1.2.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (560) hide show
  1. package/README.md +48 -11
  2. package/dist/bin/homegraph.js +298 -72
  3. package/dist/bin/homegraph.js.map +1 -1
  4. package/dist/context/index.d.ts.map +1 -1
  5. package/dist/context/index.js +117 -11
  6. package/dist/context/index.js.map +1 -1
  7. package/dist/db/index.d.ts +207 -6
  8. package/dist/db/index.d.ts.map +1 -1
  9. package/dist/db/index.js +461 -20
  10. package/dist/db/index.js.map +1 -1
  11. package/dist/db/migrations.d.ts +1 -1
  12. package/dist/db/migrations.d.ts.map +1 -1
  13. package/dist/db/migrations.js +19 -1
  14. package/dist/db/migrations.js.map +1 -1
  15. package/dist/db/queries.d.ts +157 -3
  16. package/dist/db/queries.d.ts.map +1 -1
  17. package/dist/db/queries.js +426 -9
  18. package/dist/db/queries.js.map +1 -1
  19. package/dist/db/schema.sql +32 -1
  20. package/dist/db/sqlite-adapter.d.ts +24 -17
  21. package/dist/db/sqlite-adapter.d.ts.map +1 -1
  22. package/dist/db/sqlite-adapter.js +292 -36
  23. package/dist/db/sqlite-adapter.js.map +1 -1
  24. package/dist/db/wal-valve.d.ts +119 -0
  25. package/dist/db/wal-valve.d.ts.map +1 -0
  26. package/dist/db/wal-valve.js +290 -0
  27. package/dist/db/wal-valve.js.map +1 -0
  28. package/dist/directory.d.ts +9 -5
  29. package/dist/directory.d.ts.map +1 -1
  30. package/dist/directory.js +210 -19
  31. package/dist/directory.js.map +1 -1
  32. package/dist/extraction/arkts-batch-worker.js +3 -2
  33. package/dist/extraction/arkts-batch-worker.js.map +1 -1
  34. package/dist/extraction/cfml-extractor.d.ts +107 -0
  35. package/dist/extraction/cfml-extractor.d.ts.map +1 -0
  36. package/dist/extraction/cfml-extractor.js +494 -0
  37. package/dist/extraction/cfml-extractor.js.map +1 -0
  38. package/dist/extraction/default-ignore.d.ts +39 -0
  39. package/dist/extraction/default-ignore.d.ts.map +1 -0
  40. package/dist/extraction/default-ignore.js +221 -0
  41. package/dist/extraction/default-ignore.js.map +1 -0
  42. package/dist/extraction/grammars.d.ts +25 -1
  43. package/dist/extraction/grammars.d.ts.map +1 -1
  44. package/dist/extraction/grammars.js +177 -24
  45. package/dist/extraction/grammars.js.map +1 -1
  46. package/dist/extraction/index.d.ts +52 -14
  47. package/dist/extraction/index.d.ts.map +1 -1
  48. package/dist/extraction/index.js +521 -314
  49. package/dist/extraction/index.js.map +1 -1
  50. package/dist/extraction/languages/arkts.d.ts +90 -6
  51. package/dist/extraction/languages/arkts.d.ts.map +1 -1
  52. package/dist/extraction/languages/arkts.js +1579 -128
  53. package/dist/extraction/languages/arkts.js.map +1 -1
  54. package/dist/extraction/languages/c-cpp.d.ts +42 -0
  55. package/dist/extraction/languages/c-cpp.d.ts.map +1 -1
  56. package/dist/extraction/languages/c-cpp.js +369 -4
  57. package/dist/extraction/languages/c-cpp.js.map +1 -1
  58. package/dist/extraction/languages/cfquery.d.ts +12 -0
  59. package/dist/extraction/languages/cfquery.d.ts.map +1 -0
  60. package/dist/extraction/languages/cfquery.js +28 -0
  61. package/dist/extraction/languages/cfquery.js.map +1 -0
  62. package/dist/extraction/languages/cfscript.d.ts +3 -0
  63. package/dist/extraction/languages/cfscript.d.ts.map +1 -0
  64. package/dist/extraction/languages/cfscript.js +73 -0
  65. package/dist/extraction/languages/cfscript.js.map +1 -0
  66. package/dist/extraction/languages/cobol.d.ts +33 -0
  67. package/dist/extraction/languages/cobol.d.ts.map +1 -0
  68. package/dist/extraction/languages/cobol.js +499 -0
  69. package/dist/extraction/languages/cobol.js.map +1 -0
  70. package/dist/extraction/languages/erlang.d.ts +3 -0
  71. package/dist/extraction/languages/erlang.d.ts.map +1 -0
  72. package/dist/extraction/languages/erlang.js +350 -0
  73. package/dist/extraction/languages/erlang.js.map +1 -0
  74. package/dist/extraction/languages/index.d.ts.map +1 -1
  75. package/dist/extraction/languages/index.js +16 -0
  76. package/dist/extraction/languages/index.js.map +1 -1
  77. package/dist/extraction/languages/nix.d.ts +3 -0
  78. package/dist/extraction/languages/nix.d.ts.map +1 -0
  79. package/dist/extraction/languages/nix.js +294 -0
  80. package/dist/extraction/languages/nix.js.map +1 -0
  81. package/dist/extraction/languages/solidity.d.ts +3 -0
  82. package/dist/extraction/languages/solidity.d.ts.map +1 -0
  83. package/dist/extraction/languages/solidity.js +293 -0
  84. package/dist/extraction/languages/solidity.js.map +1 -0
  85. package/dist/extraction/languages/terraform.d.ts +3 -0
  86. package/dist/extraction/languages/terraform.d.ts.map +1 -0
  87. package/dist/extraction/languages/terraform.js +641 -0
  88. package/dist/extraction/languages/terraform.js.map +1 -0
  89. package/dist/extraction/languages/vbnet.d.ts +11 -0
  90. package/dist/extraction/languages/vbnet.d.ts.map +1 -0
  91. package/dist/extraction/languages/vbnet.js +141 -0
  92. package/dist/extraction/languages/vbnet.js.map +1 -0
  93. package/dist/extraction/mybatis-extractor.d.ts +30 -10
  94. package/dist/extraction/mybatis-extractor.d.ts.map +1 -1
  95. package/dist/extraction/mybatis-extractor.js +140 -38
  96. package/dist/extraction/mybatis-extractor.js.map +1 -1
  97. package/dist/extraction/parse-pool.d.ts +27 -1
  98. package/dist/extraction/parse-pool.d.ts.map +1 -1
  99. package/dist/extraction/parse-pool.js +75 -7
  100. package/dist/extraction/parse-pool.js.map +1 -1
  101. package/dist/extraction/parse-worker.js +9 -2
  102. package/dist/extraction/parse-worker.js.map +1 -1
  103. package/dist/extraction/store-worker.d.ts +20 -0
  104. package/dist/extraction/store-worker.d.ts.map +1 -0
  105. package/dist/extraction/store-worker.js +102 -0
  106. package/dist/extraction/store-worker.js.map +1 -0
  107. package/dist/extraction/store-writer.d.ts +48 -0
  108. package/dist/extraction/store-writer.d.ts.map +1 -0
  109. package/dist/extraction/store-writer.js +167 -0
  110. package/dist/extraction/store-writer.js.map +1 -0
  111. package/dist/extraction/tree-sitter-helpers.d.ts.map +1 -1
  112. package/dist/extraction/tree-sitter-helpers.js +1 -0
  113. package/dist/extraction/tree-sitter-helpers.js.map +1 -1
  114. package/dist/extraction/tree-sitter-types.d.ts +3 -1
  115. package/dist/extraction/tree-sitter-types.d.ts.map +1 -1
  116. package/dist/extraction/tree-sitter.d.ts +38 -0
  117. package/dist/extraction/tree-sitter.d.ts.map +1 -1
  118. package/dist/extraction/tree-sitter.js +801 -10
  119. package/dist/extraction/tree-sitter.js.map +1 -1
  120. package/dist/extraction/wasm/tree-sitter-arkts.wasm +0 -0
  121. package/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
  122. package/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
  123. package/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
  124. package/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
  125. package/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
  126. package/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
  127. package/dist/extraction/wasm/tree-sitter-terraform.wasm +0 -0
  128. package/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
  129. package/dist/extraction/wasm-runtime-flags.d.ts +14 -4
  130. package/dist/extraction/wasm-runtime-flags.d.ts.map +1 -1
  131. package/dist/extraction/wasm-runtime-flags.js +55 -7
  132. package/dist/extraction/wasm-runtime-flags.js.map +1 -1
  133. package/dist/graph/traversal.d.ts.map +1 -1
  134. package/dist/graph/traversal.js +1 -1
  135. package/dist/graph/traversal.js.map +1 -1
  136. package/dist/index.d.ts +74 -5
  137. package/dist/index.d.ts.map +1 -1
  138. package/dist/index.js +436 -55
  139. package/dist/index.js.map +1 -1
  140. package/dist/installer/index.d.ts +42 -0
  141. package/dist/installer/index.d.ts.map +1 -1
  142. package/dist/installer/index.js +97 -2
  143. package/dist/installer/index.js.map +1 -1
  144. package/dist/mcp/daemon.d.ts +25 -3
  145. package/dist/mcp/daemon.d.ts.map +1 -1
  146. package/dist/mcp/daemon.js +62 -8
  147. package/dist/mcp/daemon.js.map +1 -1
  148. package/dist/mcp/dynamic-boundaries.d.ts.map +1 -1
  149. package/dist/mcp/dynamic-boundaries.js +2 -1
  150. package/dist/mcp/dynamic-boundaries.js.map +1 -1
  151. package/dist/mcp/early-ppid.d.ts +26 -0
  152. package/dist/mcp/early-ppid.d.ts.map +1 -0
  153. package/dist/mcp/early-ppid.js +29 -0
  154. package/dist/mcp/early-ppid.js.map +1 -0
  155. package/dist/mcp/engine.d.ts +4 -5
  156. package/dist/mcp/engine.d.ts.map +1 -1
  157. package/dist/mcp/engine.js +27 -1
  158. package/dist/mcp/engine.js.map +1 -1
  159. package/dist/mcp/index.d.ts.map +1 -1
  160. package/dist/mcp/index.js +41 -6
  161. package/dist/mcp/index.js.map +1 -1
  162. package/dist/mcp/liveness-watchdog.d.ts +18 -1
  163. package/dist/mcp/liveness-watchdog.d.ts.map +1 -1
  164. package/dist/mcp/liveness-watchdog.js +73 -13
  165. package/dist/mcp/liveness-watchdog.js.map +1 -1
  166. package/dist/mcp/memory-budget.d.ts +30 -0
  167. package/dist/mcp/memory-budget.d.ts.map +1 -0
  168. package/dist/mcp/memory-budget.js +123 -0
  169. package/dist/mcp/memory-budget.js.map +1 -0
  170. package/dist/mcp/proxy.d.ts.map +1 -1
  171. package/dist/mcp/proxy.js +88 -4
  172. package/dist/mcp/proxy.js.map +1 -1
  173. package/dist/mcp/query-cache.d.ts +35 -4
  174. package/dist/mcp/query-cache.d.ts.map +1 -1
  175. package/dist/mcp/query-cache.js +89 -19
  176. package/dist/mcp/query-cache.js.map +1 -1
  177. package/dist/mcp/query-pool.d.ts +46 -12
  178. package/dist/mcp/query-pool.d.ts.map +1 -1
  179. package/dist/mcp/query-pool.js +100 -23
  180. package/dist/mcp/query-pool.js.map +1 -1
  181. package/dist/mcp/query-worker.d.ts +2 -2
  182. package/dist/mcp/query-worker.js +2 -2
  183. package/dist/mcp/server-instructions.d.ts +4 -27
  184. package/dist/mcp/server-instructions.d.ts.map +1 -1
  185. package/dist/mcp/server-instructions.js +39 -84
  186. package/dist/mcp/server-instructions.js.map +1 -1
  187. package/dist/mcp/session.d.ts +14 -0
  188. package/dist/mcp/session.d.ts.map +1 -1
  189. package/dist/mcp/session.js +28 -1
  190. package/dist/mcp/session.js.map +1 -1
  191. package/dist/mcp/startup-handshake.d.ts +44 -0
  192. package/dist/mcp/startup-handshake.d.ts.map +1 -0
  193. package/dist/mcp/startup-handshake.js +73 -0
  194. package/dist/mcp/startup-handshake.js.map +1 -0
  195. package/dist/mcp/tools.d.ts +104 -4
  196. package/dist/mcp/tools.d.ts.map +1 -1
  197. package/dist/mcp/tools.js +2432 -217
  198. package/dist/mcp/tools.js.map +1 -1
  199. package/dist/mcp/transport.d.ts.map +1 -1
  200. package/dist/mcp/transport.js +18 -0
  201. package/dist/mcp/transport.js.map +1 -1
  202. package/dist/project-config.d.ts +38 -0
  203. package/dist/project-config.d.ts.map +1 -1
  204. package/dist/project-config.js +101 -2
  205. package/dist/project-config.js.map +1 -1
  206. package/dist/resolution/c-fnptr-synthesizer.d.ts +2 -1
  207. package/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -1
  208. package/dist/resolution/c-fnptr-synthesizer.js +178 -146
  209. package/dist/resolution/c-fnptr-synthesizer.js.map +1 -1
  210. package/dist/resolution/callback-synthesizer.d.ts +54 -10
  211. package/dist/resolution/callback-synthesizer.d.ts.map +1 -1
  212. package/dist/resolution/callback-synthesizer.js +1299 -257
  213. package/dist/resolution/callback-synthesizer.js.map +1 -1
  214. package/dist/resolution/cooperative-yield.d.ts +10 -2
  215. package/dist/resolution/cooperative-yield.d.ts.map +1 -1
  216. package/dist/resolution/cooperative-yield.js +6 -4
  217. package/dist/resolution/cooperative-yield.js.map +1 -1
  218. package/dist/resolution/frameworks/cics.d.ts +20 -0
  219. package/dist/resolution/frameworks/cics.d.ts.map +1 -0
  220. package/dist/resolution/frameworks/cics.js +90 -0
  221. package/dist/resolution/frameworks/cics.js.map +1 -0
  222. package/dist/resolution/frameworks/go.d.ts.map +1 -1
  223. package/dist/resolution/frameworks/go.js +29 -6
  224. package/dist/resolution/frameworks/go.js.map +1 -1
  225. package/dist/resolution/frameworks/index.d.ts.map +1 -1
  226. package/dist/resolution/frameworks/index.js +3 -0
  227. package/dist/resolution/frameworks/index.js.map +1 -1
  228. package/dist/resolution/frameworks/java.d.ts.map +1 -1
  229. package/dist/resolution/frameworks/java.js +14 -6
  230. package/dist/resolution/frameworks/java.js.map +1 -1
  231. package/dist/resolution/frameworks/terraform.d.ts +38 -0
  232. package/dist/resolution/frameworks/terraform.d.ts.map +1 -0
  233. package/dist/resolution/frameworks/terraform.js +277 -0
  234. package/dist/resolution/frameworks/terraform.js.map +1 -0
  235. package/dist/resolution/goframe-synthesizer.d.ts +2 -1
  236. package/dist/resolution/goframe-synthesizer.d.ts.map +1 -1
  237. package/dist/resolution/goframe-synthesizer.js +8 -3
  238. package/dist/resolution/goframe-synthesizer.js.map +1 -1
  239. package/dist/resolution/import-resolver.d.ts +9 -3
  240. package/dist/resolution/import-resolver.d.ts.map +1 -1
  241. package/dist/resolution/import-resolver.js +295 -11
  242. package/dist/resolution/import-resolver.js.map +1 -1
  243. package/dist/resolution/index.d.ts +146 -8
  244. package/dist/resolution/index.d.ts.map +1 -1
  245. package/dist/resolution/index.js +1089 -131
  246. package/dist/resolution/index.js.map +1 -1
  247. package/dist/resolution/memory-budget.d.ts +48 -0
  248. package/dist/resolution/memory-budget.d.ts.map +1 -0
  249. package/dist/resolution/memory-budget.js +162 -0
  250. package/dist/resolution/memory-budget.js.map +1 -0
  251. package/dist/resolution/name-matcher.d.ts +12 -4
  252. package/dist/resolution/name-matcher.d.ts.map +1 -1
  253. package/dist/resolution/name-matcher.js +793 -130
  254. package/dist/resolution/name-matcher.js.map +1 -1
  255. package/dist/resolution/resolver-pool.d.ts +106 -0
  256. package/dist/resolution/resolver-pool.d.ts.map +1 -0
  257. package/dist/resolution/resolver-pool.js +344 -0
  258. package/dist/resolution/resolver-pool.js.map +1 -0
  259. package/dist/resolution/resolver-worker.d.ts +17 -0
  260. package/dist/resolution/resolver-worker.d.ts.map +1 -0
  261. package/dist/resolution/resolver-worker.js +141 -0
  262. package/dist/resolution/resolver-worker.js.map +1 -0
  263. package/dist/resolution/strip-comments.d.ts +1 -1
  264. package/dist/resolution/strip-comments.d.ts.map +1 -1
  265. package/dist/resolution/strip-comments.js +49 -0
  266. package/dist/resolution/strip-comments.js.map +1 -1
  267. package/dist/resolution/types.d.ts +32 -0
  268. package/dist/resolution/types.d.ts.map +1 -1
  269. package/dist/resolution/workspace-packages.d.ts +10 -0
  270. package/dist/resolution/workspace-packages.d.ts.map +1 -1
  271. package/dist/resolution/workspace-packages.js +142 -4
  272. package/dist/resolution/workspace-packages.js.map +1 -1
  273. package/dist/search/identifier-segments.d.ts +60 -0
  274. package/dist/search/identifier-segments.d.ts.map +1 -0
  275. package/dist/search/identifier-segments.js +176 -0
  276. package/dist/search/identifier-segments.js.map +1 -0
  277. package/dist/search/query-utils.d.ts +201 -0
  278. package/dist/search/query-utils.d.ts.map +1 -1
  279. package/dist/search/query-utils.js +919 -2
  280. package/dist/search/query-utils.js.map +1 -1
  281. package/dist/spec/build/diff-parser.d.ts +55 -0
  282. package/dist/spec/build/diff-parser.d.ts.map +1 -0
  283. package/dist/spec/build/diff-parser.js +205 -0
  284. package/dist/spec/build/diff-parser.js.map +1 -0
  285. package/dist/spec/build/git-scanner.d.ts +93 -0
  286. package/dist/spec/build/git-scanner.d.ts.map +1 -0
  287. package/dist/spec/build/git-scanner.js +254 -0
  288. package/dist/spec/build/git-scanner.js.map +1 -0
  289. package/dist/spec/build/pipeline.d.ts +54 -0
  290. package/dist/spec/build/pipeline.d.ts.map +1 -0
  291. package/dist/spec/build/pipeline.js +170 -0
  292. package/dist/spec/build/pipeline.js.map +1 -0
  293. package/dist/spec/build/scan.d.ts +48 -0
  294. package/dist/spec/build/scan.d.ts.map +1 -0
  295. package/dist/spec/build/scan.js +89 -0
  296. package/dist/spec/build/scan.js.map +1 -0
  297. package/dist/spec/build/scope-resolver.d.ts +50 -0
  298. package/dist/spec/build/scope-resolver.d.ts.map +1 -0
  299. package/dist/spec/build/scope-resolver.js +105 -0
  300. package/dist/spec/build/scope-resolver.js.map +1 -0
  301. package/dist/spec/build/spec-extractor.d.ts +74 -0
  302. package/dist/spec/build/spec-extractor.d.ts.map +1 -0
  303. package/dist/spec/build/spec-extractor.js +344 -0
  304. package/dist/spec/build/spec-extractor.js.map +1 -0
  305. package/dist/spec/config.d.ts +35 -1
  306. package/dist/spec/config.d.ts.map +1 -1
  307. package/dist/spec/config.js +30 -5
  308. package/dist/spec/config.js.map +1 -1
  309. package/dist/spec/db/fts.d.ts.map +1 -1
  310. package/dist/spec/db/fts.js +19 -50
  311. package/dist/spec/db/fts.js.map +1 -1
  312. package/dist/spec/db/index.d.ts +5 -3
  313. package/dist/spec/db/index.d.ts.map +1 -1
  314. package/dist/spec/db/index.js +16 -1
  315. package/dist/spec/db/index.js.map +1 -1
  316. package/dist/spec/db/persist.d.ts +45 -0
  317. package/dist/spec/db/persist.d.ts.map +1 -0
  318. package/dist/spec/db/persist.js +66 -0
  319. package/dist/spec/db/persist.js.map +1 -0
  320. package/dist/spec/db/relations.d.ts +80 -1
  321. package/dist/spec/db/relations.d.ts.map +1 -1
  322. package/dist/spec/db/relations.js +175 -4
  323. package/dist/spec/db/relations.js.map +1 -1
  324. package/dist/spec/db/schema.d.ts +4 -0
  325. package/dist/spec/db/schema.d.ts.map +1 -1
  326. package/dist/spec/db/schema.js +4 -0
  327. package/dist/spec/db/schema.js.map +1 -1
  328. package/dist/spec/db/schema.sql +5 -1
  329. package/dist/spec/db/spec-node.d.ts +5 -0
  330. package/dist/spec/db/spec-node.d.ts.map +1 -1
  331. package/dist/spec/db/spec-node.js +11 -5
  332. package/dist/spec/db/spec-node.js.map +1 -1
  333. package/dist/spec/db/sql-utils.d.ts +11 -0
  334. package/dist/spec/db/sql-utils.d.ts.map +1 -0
  335. package/dist/spec/db/sql-utils.js +19 -0
  336. package/dist/spec/db/sql-utils.js.map +1 -0
  337. package/dist/spec/evolve/cluster-context.d.ts +30 -0
  338. package/dist/spec/evolve/cluster-context.d.ts.map +1 -0
  339. package/dist/spec/evolve/cluster-context.js +78 -0
  340. package/dist/spec/evolve/cluster-context.js.map +1 -0
  341. package/dist/spec/evolve/commit-spec-analyzer.d.ts +51 -0
  342. package/dist/spec/evolve/commit-spec-analyzer.d.ts.map +1 -0
  343. package/dist/spec/evolve/commit-spec-analyzer.js +88 -0
  344. package/dist/spec/evolve/commit-spec-analyzer.js.map +1 -0
  345. package/dist/spec/evolve/commit-spec-persister.d.ts +53 -0
  346. package/dist/spec/evolve/commit-spec-persister.d.ts.map +1 -0
  347. package/dist/spec/evolve/commit-spec-persister.js +93 -0
  348. package/dist/spec/evolve/commit-spec-persister.js.map +1 -0
  349. package/dist/spec/evolve/impact-locator.d.ts +23 -7
  350. package/dist/spec/evolve/impact-locator.d.ts.map +1 -1
  351. package/dist/spec/evolve/impact-locator.js +58 -14
  352. package/dist/spec/evolve/impact-locator.js.map +1 -1
  353. package/dist/spec/evolve/pipeline.d.ts +62 -25
  354. package/dist/spec/evolve/pipeline.d.ts.map +1 -1
  355. package/dist/spec/evolve/pipeline.js +436 -457
  356. package/dist/spec/evolve/pipeline.js.map +1 -1
  357. package/dist/spec/evolve/spec-rewriter.d.ts +13 -13
  358. package/dist/spec/evolve/spec-rewriter.d.ts.map +1 -1
  359. package/dist/spec/evolve/spec-rewriter.js +45 -45
  360. package/dist/spec/evolve/spec-rewriter.js.map +1 -1
  361. package/dist/spec/git/commits.d.ts +85 -0
  362. package/dist/spec/git/commits.d.ts.map +1 -0
  363. package/dist/spec/git/commits.js +218 -0
  364. package/dist/spec/git/commits.js.map +1 -0
  365. package/dist/spec/git/exec.d.ts +13 -0
  366. package/dist/spec/git/exec.d.ts.map +1 -0
  367. package/dist/spec/git/exec.js +19 -0
  368. package/dist/spec/git/exec.js.map +1 -0
  369. package/dist/spec/git/index.d.ts +9 -0
  370. package/dist/spec/git/index.d.ts.map +1 -0
  371. package/dist/spec/git/index.js +22 -0
  372. package/dist/spec/git/index.js.map +1 -0
  373. package/dist/spec/git-utils.d.ts +8 -0
  374. package/dist/spec/git-utils.d.ts.map +1 -0
  375. package/dist/spec/git-utils.js +14 -0
  376. package/dist/spec/git-utils.js.map +1 -0
  377. package/dist/spec/graph/index.d.ts +8 -0
  378. package/dist/spec/graph/index.d.ts.map +1 -0
  379. package/dist/spec/graph/index.js +17 -0
  380. package/dist/spec/graph/index.js.map +1 -0
  381. package/dist/spec/graph/queries.d.ts +6 -35
  382. package/dist/spec/graph/queries.d.ts.map +1 -1
  383. package/dist/spec/graph/queries.js +71 -248
  384. package/dist/spec/graph/queries.js.map +1 -1
  385. package/dist/spec/llm/agent-client.d.ts +59 -0
  386. package/dist/spec/llm/agent-client.d.ts.map +1 -0
  387. package/dist/spec/llm/agent-client.js +213 -0
  388. package/dist/spec/llm/agent-client.js.map +1 -0
  389. package/dist/spec/llm/agents/claude-code.d.ts +32 -0
  390. package/dist/spec/llm/agents/claude-code.d.ts.map +1 -0
  391. package/dist/spec/llm/agents/claude-code.js +125 -0
  392. package/dist/spec/llm/agents/claude-code.js.map +1 -0
  393. package/dist/spec/llm/agents/codex.d.ts +32 -0
  394. package/dist/spec/llm/agents/codex.d.ts.map +1 -0
  395. package/dist/spec/llm/agents/codex.js +145 -0
  396. package/dist/spec/llm/agents/codex.js.map +1 -0
  397. package/dist/spec/llm/agents/detect-utils.d.ts +28 -0
  398. package/dist/spec/llm/agents/detect-utils.d.ts.map +1 -0
  399. package/dist/spec/llm/agents/detect-utils.js +91 -0
  400. package/dist/spec/llm/agents/detect-utils.js.map +1 -0
  401. package/dist/spec/llm/agents/deveco-code.d.ts +33 -0
  402. package/dist/spec/llm/agents/deveco-code.d.ts.map +1 -0
  403. package/dist/spec/llm/agents/deveco-code.js +165 -0
  404. package/dist/spec/llm/agents/deveco-code.js.map +1 -0
  405. package/dist/spec/llm/agents/index.d.ts +31 -0
  406. package/dist/spec/llm/agents/index.d.ts.map +1 -0
  407. package/dist/spec/llm/agents/index.js +65 -0
  408. package/dist/spec/llm/agents/index.js.map +1 -0
  409. package/dist/spec/llm/agents/types.d.ts +76 -0
  410. package/dist/spec/llm/agents/types.d.ts.map +1 -0
  411. package/dist/spec/llm/agents/types.js +15 -0
  412. package/dist/spec/llm/agents/types.js.map +1 -0
  413. package/dist/spec/llm/client.d.ts +31 -11
  414. package/dist/spec/llm/client.d.ts.map +1 -1
  415. package/dist/spec/llm/client.js +143 -81
  416. package/dist/spec/llm/client.js.map +1 -1
  417. package/dist/spec/llm/factory.d.ts +41 -0
  418. package/dist/spec/llm/factory.d.ts.map +1 -0
  419. package/dist/spec/llm/factory.js +80 -0
  420. package/dist/spec/llm/factory.js.map +1 -0
  421. package/dist/spec/llm/prompts.d.ts +24 -8
  422. package/dist/spec/llm/prompts.d.ts.map +1 -1
  423. package/dist/spec/llm/prompts.js +100 -50
  424. package/dist/spec/llm/prompts.js.map +1 -1
  425. package/dist/spec/llm/retry.d.ts +53 -0
  426. package/dist/spec/llm/retry.d.ts.map +1 -0
  427. package/dist/spec/llm/retry.js +153 -0
  428. package/dist/spec/llm/retry.js.map +1 -0
  429. package/dist/spec/mine/clusterer.d.ts +63 -0
  430. package/dist/spec/mine/clusterer.d.ts.map +1 -0
  431. package/dist/spec/mine/clusterer.js +904 -0
  432. package/dist/spec/mine/clusterer.js.map +1 -0
  433. package/dist/spec/mine/clustering/features.d.ts +38 -0
  434. package/dist/spec/mine/clustering/features.d.ts.map +1 -0
  435. package/dist/spec/mine/clustering/features.js +108 -0
  436. package/dist/spec/mine/clustering/features.js.map +1 -0
  437. package/dist/spec/mine/clustering/index.d.ts +62 -0
  438. package/dist/spec/mine/clustering/index.d.ts.map +1 -0
  439. package/dist/spec/mine/clustering/index.js +259 -0
  440. package/dist/spec/mine/clustering/index.js.map +1 -0
  441. package/dist/spec/mine/clustering/leiden.d.ts +61 -0
  442. package/dist/spec/mine/clustering/leiden.d.ts.map +1 -0
  443. package/dist/spec/mine/clustering/leiden.js +489 -0
  444. package/dist/spec/mine/clustering/leiden.js.map +1 -0
  445. package/dist/spec/mine/clustering/text-similarity.d.ts +22 -0
  446. package/dist/spec/mine/clustering/text-similarity.d.ts.map +1 -0
  447. package/dist/spec/mine/clustering/text-similarity.js +98 -0
  448. package/dist/spec/mine/clustering/text-similarity.js.map +1 -0
  449. package/dist/spec/mine/generator.d.ts +54 -0
  450. package/dist/spec/mine/generator.d.ts.map +1 -0
  451. package/dist/spec/mine/generator.js +267 -0
  452. package/dist/spec/mine/generator.js.map +1 -0
  453. package/dist/spec/mine/persist.d.ts +37 -0
  454. package/dist/spec/mine/persist.d.ts.map +1 -0
  455. package/dist/spec/mine/persist.js +170 -0
  456. package/dist/spec/mine/persist.js.map +1 -0
  457. package/dist/spec/mine/pipeline.d.ts +41 -0
  458. package/dist/spec/mine/pipeline.d.ts.map +1 -0
  459. package/dist/spec/mine/pipeline.js +236 -0
  460. package/dist/spec/mine/pipeline.js.map +1 -0
  461. package/dist/spec/mine/progress-handler.d.ts +22 -0
  462. package/dist/spec/mine/progress-handler.d.ts.map +1 -0
  463. package/dist/spec/mine/progress-handler.js +108 -0
  464. package/dist/spec/mine/progress-handler.js.map +1 -0
  465. package/dist/spec/mine/progress.d.ts +23 -0
  466. package/dist/spec/mine/progress.d.ts.map +1 -0
  467. package/dist/spec/mine/progress.js +12 -0
  468. package/dist/spec/mine/progress.js.map +1 -0
  469. package/dist/spec/mine/scanner.d.ts +59 -0
  470. package/dist/spec/mine/scanner.d.ts.map +1 -0
  471. package/dist/spec/mine/scanner.js +383 -0
  472. package/dist/spec/mine/scanner.js.map +1 -0
  473. package/dist/spec/types.d.ts +32 -0
  474. package/dist/spec/types.d.ts.map +1 -1
  475. package/dist/spec/ui/index.d.ts +9 -0
  476. package/dist/spec/ui/index.d.ts.map +1 -0
  477. package/dist/spec/ui/index.js +16 -0
  478. package/dist/spec/ui/index.js.map +1 -0
  479. package/dist/spec/ui/progress-handler.d.ts +33 -0
  480. package/dist/spec/ui/progress-handler.d.ts.map +1 -0
  481. package/dist/spec/ui/progress-handler.js +132 -0
  482. package/dist/spec/ui/progress-handler.js.map +1 -0
  483. package/dist/spec/ui/progress.d.ts +24 -0
  484. package/dist/spec/ui/progress.d.ts.map +1 -0
  485. package/dist/spec/ui/progress.js +13 -0
  486. package/dist/spec/ui/progress.js.map +1 -0
  487. package/dist/spec/utils/fs.d.ts +45 -0
  488. package/dist/spec/utils/fs.d.ts.map +1 -0
  489. package/dist/spec/utils/fs.js +163 -0
  490. package/dist/spec/utils/fs.js.map +1 -0
  491. package/dist/spec/utils/index.d.ts +12 -0
  492. package/dist/spec/utils/index.d.ts.map +1 -0
  493. package/dist/spec/utils/index.js +26 -0
  494. package/dist/spec/utils/index.js.map +1 -0
  495. package/dist/spec/utils/meta.d.ts +32 -0
  496. package/dist/spec/utils/meta.d.ts.map +1 -0
  497. package/dist/spec/utils/meta.js +122 -0
  498. package/dist/spec/utils/meta.js.map +1 -0
  499. package/dist/spec/utils/truncate.d.ts +86 -0
  500. package/dist/spec/utils/truncate.d.ts.map +1 -0
  501. package/dist/spec/utils/truncate.js +172 -0
  502. package/dist/spec/utils/truncate.js.map +1 -0
  503. package/dist/spec/utils.d.ts +4 -16
  504. package/dist/spec/utils.d.ts.map +1 -1
  505. package/dist/spec/utils.js +6 -58
  506. package/dist/spec/utils.js.map +1 -1
  507. package/dist/sync/git-hooks.d.ts.map +1 -1
  508. package/dist/sync/git-hooks.js +2 -0
  509. package/dist/sync/git-hooks.js.map +1 -1
  510. package/dist/sync/watcher.d.ts +35 -6
  511. package/dist/sync/watcher.d.ts.map +1 -1
  512. package/dist/sync/watcher.js +127 -17
  513. package/dist/sync/watcher.js.map +1 -1
  514. package/dist/sync/worktree.d.ts.map +1 -1
  515. package/dist/sync/worktree.js +5 -0
  516. package/dist/sync/worktree.js.map +1 -1
  517. package/dist/telemetry/index.js +3 -3
  518. package/dist/telemetry/index.js.map +1 -1
  519. package/dist/types.d.ts +22 -1
  520. package/dist/types.d.ts.map +1 -1
  521. package/dist/types.js +10 -0
  522. package/dist/types.js.map +1 -1
  523. package/dist/upgrade/index.d.ts +32 -0
  524. package/dist/upgrade/index.d.ts.map +1 -1
  525. package/dist/upgrade/index.js +162 -14
  526. package/dist/upgrade/index.js.map +1 -1
  527. package/dist/upgrade/remove-binary.d.ts +87 -0
  528. package/dist/upgrade/remove-binary.d.ts.map +1 -0
  529. package/dist/upgrade/remove-binary.js +289 -0
  530. package/dist/upgrade/remove-binary.js.map +1 -0
  531. package/dist/upgrade/update-check.d.ts +92 -0
  532. package/dist/upgrade/update-check.d.ts.map +1 -0
  533. package/dist/upgrade/update-check.js +258 -0
  534. package/dist/upgrade/update-check.js.map +1 -0
  535. package/package.json +6 -2
  536. package/scripts/agent-eval/run-all.sh +6 -0
  537. package/scripts/build-bundle.sh +5 -0
  538. package/scripts/npm-shim.js +8 -1
  539. package/scripts/exp_boundary_eval/__pycache__/_utils.cpython-310.pyc +0 -0
  540. package/scripts/exp_boundary_eval/__pycache__/analyze.cpython-310.pyc +0 -0
  541. package/scripts/exp_boundary_eval/__pycache__/deveco_arm.cpython-310.pyc +0 -0
  542. package/scripts/exp_boundary_eval/__pycache__/run_one.cpython-310.pyc +0 -0
  543. package/scripts/exp_boundary_eval/__pycache__/run_session.cpython-310.pyc +0 -0
  544. package/scripts/exp_boundary_eval/__pycache__/setup.cpython-310.pyc +0 -0
  545. package/scripts/exp_boundary_eval/data/agents.json +0 -109
  546. package/scripts/exp_boundary_eval/data/experiments.json +0 -140
  547. package/scripts/qa_eval/README.md +0 -407
  548. package/scripts/qa_eval/_test_deveco_probe.py +0 -41
  549. package/scripts/qa_eval/agent_runner.py +0 -526
  550. package/scripts/qa_eval/data/.gitignore +0 -4
  551. package/scripts/qa_eval/data/test-set.jsonl +0 -2
  552. package/scripts/qa_eval/eval_metrics.py +0 -274
  553. package/scripts/qa_eval/external_agent.py +0 -976
  554. package/scripts/qa_eval/llm_config.py +0 -92
  555. package/scripts/qa_eval/memory_monitor.py +0 -132
  556. package/scripts/qa_eval/my_answer_accuracy.py +0 -187
  557. package/scripts/qa_eval/requirements.txt +0 -2
  558. package/scripts/qa_eval/run_pipeline.py +0 -804
  559. package/scripts/qa_eval/stats_efficiency.py +0 -279
  560. package/scripts/qa_eval/stats_scores.py +0 -207
@@ -3,10 +3,24 @@
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. CFG does **not** stitch same-module calls (that
15
+ * densified past unlimited RTA). Cross-module edges come from RTA with an exact
16
+ * MethodSignature map, plus a CFG `%unk` bridge that recovers Class.method /
17
+ * free functions from stmt text, UnclearReferenceType names, or PascalCase base
18
+ * locals (import + unique target + fan-out blocklist). Deps load to SIGNATURES.
19
+ *
20
+ * Sync can re-index a **dirty module subset** (`primeArkTSBatch` + `changedFiles`)
21
+ * via `ModuleAnalysisConfig.setTargetModuleIds`, instead of rebuilding every
22
+ * PROJECT module. Unchanged modules stay in the DB; incoming cross-file edges
23
+ * into dirty files are snapshotted and re-bound like file-level sync (#899).
10
24
  */
11
25
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
12
26
  if (k2 === undefined) k2 = k;
@@ -41,18 +55,29 @@ var __importStar = (this && this.__importStar) || (function () {
41
55
  return result;
42
56
  };
43
57
  })();
58
+ var __importDefault = (this && this.__importDefault) || function (mod) {
59
+ return (mod && mod.__esModule) ? mod : { "default": mod };
60
+ };
44
61
  Object.defineProperty(exports, "__esModule", { value: true });
45
62
  exports.OHOS_API_DB_PATH_META = exports.OHOS_API_VERSION_META = exports.OHOS_API_FILE_PREFIX = exports.OhosSdkInputError = exports.ArkTSExtractor = void 0;
46
63
  exports.cleanArkTypeString = cleanArkTypeString;
47
64
  exports.normalizeArktsReturnType = normalizeArktsReturnType;
48
65
  exports.buildArkMethodSignatureFields = buildArkMethodSignatureFields;
66
+ exports.shouldUseIsolatedArkTSBuild = shouldUseIsolatedArkTSBuild;
67
+ exports.normalizeHarmonyModuleSrcPath = normalizeHarmonyModuleSrcPath;
68
+ exports.listHarmonyProjectModules = listHarmonyProjectModules;
69
+ exports.resolveDirtyHarmonyModules = resolveDirtyHarmonyModules;
49
70
  exports.isArkAnalyzerSourcePath = isArkAnalyzerSourcePath;
71
+ exports.scanEtsFiles = scanEtsFiles;
50
72
  exports.runIsolatedArkTSBatchEntry = runIsolatedArkTSBatchEntry;
51
73
  exports.primeArkTSBatch = primeArkTSBatch;
52
74
  exports.isArkTSBatchPersisted = isArkTSBatchPersisted;
53
75
  exports.resetArkTSBatch = resetArkTSBatch;
54
76
  exports.stateDecoratorKinds = stateDecoratorKinds;
55
77
  exports.stateTransferViaForField = stateTransferViaForField;
78
+ exports.isUnclearArkClassSignature = isUnclearArkClassSignature;
79
+ exports.isUnclearArkMethodSignature = isUnclearArkMethodSignature;
80
+ exports.pickPreferredClassNodeId = pickPreferredClassNodeId;
56
81
  exports.drainArkTSIndexNotices = drainArkTSIndexNotices;
57
82
  exports.parseOhosToolsVersionFromName = parseOhosToolsVersionFromName;
58
83
  exports.isArchivePath = isArchivePath;
@@ -82,11 +107,15 @@ const child_process_1 = require("child_process");
82
107
  const arkanalyzer_1 = require("arkanalyzer");
83
108
  const db_1 = require("../../db");
84
109
  const queries_1 = require("../../db/queries");
110
+ const directory_1 = require("../../directory");
111
+ const project_config_1 = require("../../project-config");
85
112
  const context_1 = require("../context");
113
+ const default_ignore_1 = require("../default-ignore");
86
114
  const extraction_version_1 = require("../extraction-version");
87
115
  const version_1 = require("../../mcp/version");
88
116
  const tree_sitter_helpers_1 = require("../tree-sitter-helpers");
89
117
  const wasm_runtime_flags_1 = require("../wasm-runtime-flags");
118
+ const ignore_1 = __importDefault(require("ignore"));
90
119
  /** Primitive / void return types — not stored in nodes.return_type (inference-only column). */
91
120
  const ARKTS_NON_CLASS_RETURN = new Set([
92
121
  'void',
@@ -222,16 +251,148 @@ function isStackOverflowExitCode(code) {
222
251
  return false;
223
252
  return code === 3221225725 || code === -1073741571;
224
253
  }
225
- function shouldUseIsolatedArkTSBuild(etsCount) {
254
+ /**
255
+ * Opt-in isolated ArkTS Scene build (child process + enlarged `--stack-size`).
256
+ * Default is in-process: faster and avoids a Windows-only auto-isolated path that
257
+ * could burn minutes on failed stack-size retries before giving up. Set
258
+ * `HOMEGRAPH_ARKTS_ISOLATED=1` only when an in-process Scene build stack-overflows
259
+ * the indexer (rare Photos-scale Windows cases).
260
+ */
261
+ function shouldUseIsolatedArkTSBuild() {
226
262
  const mode = process.env.HOMEGRAPH_ARKTS_ISOLATED?.trim();
227
- if (mode === '1' || mode === 'true')
228
- return true;
263
+ return mode === '1' || mode === 'true';
264
+ }
265
+ /**
266
+ * Scene eviction budget sits below the V8 heap cap (heap default 3584MB so process
267
+ * RSS stays near 4GB — native memory sits outside the heap). Small reserve only.
268
+ */
269
+ const ARKTS_HOMEGRAPH_RESERVE_MB = 256;
270
+ /** Default Scene `memoryLimitMB`. Override with HOMEGRAPH_ARKTS_MEMORY_LIMIT_MB. */
271
+ function defaultArkTSSceneMemoryLimitMB() {
272
+ return Math.max(512, (0, wasm_runtime_flags_1.resolveMaxOldSpaceSizeMb)() - ARKTS_HOMEGRAPH_RESERVE_MB);
273
+ }
274
+ /**
275
+ * RSS limit passed to ArkAnalyzer `SceneOptions.memoryLimitMB`.
276
+ * When exceeded, `analyseByModule` evicts cached modules before loading the next.
277
+ * Set `HOMEGRAPH_ARKTS_MEMORY_LIMIT_MB=0` to disable Scene eviction.
278
+ */
279
+ function resolveArkTSSceneMemoryLimitMB() {
280
+ const raw = process.env.HOMEGRAPH_ARKTS_MEMORY_LIMIT_MB?.trim();
281
+ if (raw !== undefined && raw !== '') {
282
+ const n = Number(raw);
283
+ if (Number.isFinite(n) && n >= 0)
284
+ return Math.floor(n);
285
+ }
286
+ return defaultArkTSSceneMemoryLimitMB();
287
+ }
288
+ /** Prefer modular Scene build when the project has Harmony module metadata. */
289
+ function shouldUseModularArkTSBuild(rootDir) {
290
+ const mode = process.env.HOMEGRAPH_ARKTS_MODULAR?.trim();
229
291
  if (mode === '0' || mode === 'false')
230
292
  return false;
231
- // Photos-scale repos on Windows: build Scene in a child with a larger stack.
232
- if (process.platform === 'win32' && etsCount >= 500)
293
+ if (mode === '1' || mode === 'true')
233
294
  return true;
234
- return false;
295
+ return fs.existsSync(path.join(rootDir, 'build-profile.json5'));
296
+ }
297
+ /** Normalize build-profile `srcPath` / relative file paths for prefix matching. */
298
+ function normalizeHarmonyModuleSrcPath(srcPath) {
299
+ return srcPath.replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/$/, '');
300
+ }
301
+ /**
302
+ * Read PROJECT modules from root `build-profile.json5` (name + srcPath).
303
+ * Returns [] when missing or unparsable — callers should fall back to full Scene.
304
+ */
305
+ function listHarmonyProjectModules(rootDir) {
306
+ const profilePath = path.join(rootDir, 'build-profile.json5');
307
+ if (!fs.existsSync(profilePath))
308
+ return [];
309
+ let raw;
310
+ try {
311
+ raw = parseJson5Minimal(fs.readFileSync(profilePath, 'utf-8'));
312
+ }
313
+ catch {
314
+ return [];
315
+ }
316
+ if (!raw || typeof raw !== 'object')
317
+ return [];
318
+ const modules = raw.modules;
319
+ if (!Array.isArray(modules))
320
+ return [];
321
+ const out = [];
322
+ for (const entry of modules) {
323
+ if (!entry || typeof entry !== 'object')
324
+ continue;
325
+ const rec = entry;
326
+ if (typeof rec.name !== 'string' || typeof rec.srcPath !== 'string')
327
+ continue;
328
+ const srcPath = normalizeHarmonyModuleSrcPath(rec.srcPath);
329
+ if (!srcPath)
330
+ continue;
331
+ out.push({ name: rec.name, srcPath });
332
+ }
333
+ return out;
334
+ }
335
+ function findLongestHarmonyModule(fileRel, modules) {
336
+ const file = normIndexPath(fileRel);
337
+ let best = null;
338
+ for (const mod of modules) {
339
+ const src = mod.srcPath;
340
+ if (file === src || file.startsWith(`${src}/`)) {
341
+ if (!best || mod.srcPath.length > best.srcPath.length)
342
+ best = mod;
343
+ }
344
+ }
345
+ return best;
346
+ }
347
+ /**
348
+ * Map changed/removed paths to Harmony modules for incremental ArkTS sync.
349
+ * Structural project files (`build-profile.json5`, root `oh-package.json5`) or
350
+ * ArkAnalyzer sources outside every module force a full rebuild.
351
+ */
352
+ function resolveDirtyHarmonyModules(rootDir, changedFiles) {
353
+ if (!shouldUseModularArkTSBuild(rootDir)) {
354
+ return { mode: 'full', reason: 'non-modular ArkTS project' };
355
+ }
356
+ const modules = listHarmonyProjectModules(rootDir);
357
+ if (modules.length === 0) {
358
+ return { mode: 'full', reason: 'no PROJECT modules in build-profile.json5' };
359
+ }
360
+ const changed = [...changedFiles].map(normIndexPath).filter(Boolean);
361
+ if (changed.length === 0)
362
+ return { mode: 'none' };
363
+ for (const f of changed) {
364
+ if (f === 'build-profile.json5' || f.endsWith('/build-profile.json5')) {
365
+ return { mode: 'full', reason: 'build-profile.json5 changed' };
366
+ }
367
+ if (f === 'oh-package.json5') {
368
+ return { mode: 'full', reason: 'root oh-package.json5 changed' };
369
+ }
370
+ }
371
+ const dirty = new Set();
372
+ const unmatched = [];
373
+ for (const f of changed) {
374
+ // Virtual ArkAnalyzer paths are batch artifacts, not Harmony module sources.
375
+ if (f.startsWith('@'))
376
+ continue;
377
+ const isModuleMeta = f.endsWith('/module.json5') || f.endsWith('/oh-package.json5');
378
+ const isArkSrc = isArkAnalyzerSourcePath(f);
379
+ if (!isArkSrc && !isModuleMeta)
380
+ continue;
381
+ const match = findLongestHarmonyModule(f, modules);
382
+ if (match)
383
+ dirty.add(match.srcPath);
384
+ else if (isArkSrc)
385
+ unmatched.push(f);
386
+ }
387
+ if (unmatched.length > 0) {
388
+ return {
389
+ mode: 'full',
390
+ reason: `ArkTS sources outside modules: ${unmatched.slice(0, 5).join(', ')}`,
391
+ };
392
+ }
393
+ if (dirty.size === 0)
394
+ return { mode: 'none' };
395
+ return { mode: 'modules', moduleSrcPaths: [...dirty].sort() };
235
396
  }
236
397
  function homegraphDbPath(rootDir) {
237
398
  return path.join(rootDir, '.homegraph', 'homegraph.db');
@@ -239,6 +400,9 @@ function homegraphDbPath(rootDir) {
239
400
  const ARK_PROVENANCE = 'heuristic';
240
401
  /** Virtual file path for ArkAnalyzer's in-scene dummy entry (not on disk). */
241
402
  const ARKANALYZER_DUMMY_FILE = '@dummyFile.ets';
403
+ function fieldSigKey(classSig, fieldName) {
404
+ return `${classSig}::${fieldName}`;
405
+ }
242
406
  /** HarmonyOS sources indexed via ArkAnalyzer Scene batch (`.ets`, `.ts`, `.d.ts`). */
243
407
  function isArkAnalyzerSourcePath(filePath) {
244
408
  const base = path.basename(filePath);
@@ -251,8 +415,24 @@ function isEtsFileName(name) {
251
415
  function isCoLocatedArkTsFileName(name) {
252
416
  return isArkAnalyzerSourcePath(name) && !isEtsFileName(name);
253
417
  }
418
+ /**
419
+ * Ignore matchers aligned with `scanDirectoryWalk`'s base set (built-in defaults
420
+ * + root `.gitignore` + `homegraph.json` `exclude`).
421
+ */
422
+ function arktsScanIgnoreMatchers(rootDir) {
423
+ const matchers = [(0, default_ignore_1.buildDefaultIgnore)(rootDir)];
424
+ const exclude = (0, project_config_1.loadExcludePatterns)(rootDir);
425
+ if (exclude.length > 0)
426
+ matchers.push((0, ignore_1.default)().add(exclude));
427
+ return matchers;
428
+ }
429
+ function isArktsScanIgnored(relPath, isDir, matchers) {
430
+ const probe = isDir ? `${relPath.replace(/\/$/, '')}/` : relPath;
431
+ return matchers.some((ig) => ig.ignores(probe));
432
+ }
254
433
  /** True when the tree looks like a HarmonyOS / ArkTS project (not a generic TS repo). */
255
434
  function hasHarmonyProjectMarkers(rootDir) {
435
+ const matchers = arktsScanIgnoreMatchers(rootDir);
256
436
  const found = [];
257
437
  function walk(dir) {
258
438
  if (found.length > 0)
@@ -266,13 +446,17 @@ function hasHarmonyProjectMarkers(rootDir) {
266
446
  }
267
447
  for (const entry of entries) {
268
448
  const full = path.join(dir, entry.name);
449
+ const rel = path.relative(rootDir, full).replace(/\\/g, '/');
269
450
  if (entry.isDirectory()) {
270
- if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === '.homegraph') {
451
+ if (entry.name === '.git' || (0, directory_1.isHomeGraphDataDir)(entry.name))
452
+ continue;
453
+ if (isArktsScanIgnored(rel, true, matchers))
271
454
  continue;
272
- }
273
455
  walk(full);
274
456
  }
275
457
  else if (entry.isFile() && (isEtsFileName(entry.name) || entry.name === 'module.json5')) {
458
+ if (isArktsScanIgnored(rel, false, matchers))
459
+ continue;
276
460
  found.push(full);
277
461
  return;
278
462
  }
@@ -306,9 +490,17 @@ function emptyResult(filePath, message, severity) {
306
490
  durationMs: 0,
307
491
  };
308
492
  }
493
+ /**
494
+ * Enumerate ArkAnalyzer sources under `rootDir`, using the same ignore rules as
495
+ * `scanDirectory` (defaults like `build/`/`dist/`, root `.gitignore`, exclude).
496
+ * Previously this walk only skipped `node_modules`/`.git`/`.homegraph`, so
497
+ * HarmonyOS build-output `.ets` files got written into the DB and non-git
498
+ * `homegraph status` reported them as Pending Removed.
499
+ */
309
500
  function scanEtsFiles(rootDir) {
310
501
  const etsFiles = [];
311
502
  const coLocated = [];
503
+ const matchers = arktsScanIgnoreMatchers(rootDir);
312
504
  function walk(dir) {
313
505
  let entries;
314
506
  try {
@@ -319,13 +511,17 @@ function scanEtsFiles(rootDir) {
319
511
  }
320
512
  for (const entry of entries) {
321
513
  const full = path.join(dir, entry.name);
514
+ const rel = path.relative(rootDir, full).replace(/\\/g, '/');
322
515
  if (entry.isDirectory()) {
323
- if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === '.homegraph')
516
+ if (entry.name === '.git' || (0, directory_1.isHomeGraphDataDir)(entry.name))
517
+ continue;
518
+ if (isArktsScanIgnored(rel, true, matchers))
324
519
  continue;
325
520
  walk(full);
326
521
  }
327
522
  else if (entry.isFile()) {
328
- const rel = path.relative(rootDir, full).replace(/\\/g, '/');
523
+ if (isArktsScanIgnored(rel, false, matchers))
524
+ continue;
329
525
  if (isEtsFileName(entry.name)) {
330
526
  etsFiles.push(rel);
331
527
  }
@@ -358,12 +554,15 @@ let batchBuildCommitted = false;
358
554
  function reportBatchProgress(subphase, current, total, currentFile) {
359
555
  (0, context_1.reportArkTSBatchProgress)({ subphase, current, total, currentFile });
360
556
  }
361
- function persistFileResult(queries, filePath, content, stats, result) {
557
+ function persistFileResult(queries, filePath, content, stats, result, options) {
362
558
  const contentHash = hashContent(content);
363
559
  const existingFile = queries.getFileByPath(filePath);
364
- if (existingFile?.contentHash === contentHash) {
560
+ if (!options?.force && existingFile?.contentHash === contentHash) {
365
561
  return;
366
562
  }
563
+ const crossFileIncomingEdges = existingFile && typeof queries.getCrossFileIncomingEdgesWithTarget === 'function'
564
+ ? queries.getCrossFileIncomingEdgesWithTarget(filePath)
565
+ : [];
367
566
  if (existingFile) {
368
567
  queries.deleteFile(filePath);
369
568
  }
@@ -378,6 +577,30 @@ function persistFileResult(queries, filePath, content, stats, result) {
378
577
  queries.insertEdges(validEdges);
379
578
  }
380
579
  }
580
+ if (crossFileIncomingEdges.length > 0) {
581
+ const newNodesByKindName = new Map();
582
+ for (const n of validNodes) {
583
+ newNodesByKindName.set(`${n.kind}\0${n.name}`, n.id);
584
+ }
585
+ const reinserted = [];
586
+ for (const e of crossFileIncomingEdges) {
587
+ const newTargetId = newNodesByKindName.get(`${e.targetKind}\0${e.targetName}`);
588
+ if (newTargetId) {
589
+ reinserted.push({
590
+ source: e.source,
591
+ target: newTargetId,
592
+ kind: e.kind,
593
+ metadata: e.metadata,
594
+ line: e.line,
595
+ column: e.column,
596
+ provenance: e.provenance,
597
+ });
598
+ }
599
+ }
600
+ if (reinserted.length > 0) {
601
+ queries.insertEdges(reinserted);
602
+ }
603
+ }
381
604
  if (result.unresolvedReferences.length > 0) {
382
605
  const insertedIds = new Set(validNodes.map((n) => n.id));
383
606
  const refsWithContext = result.unresolvedReferences
@@ -403,6 +626,22 @@ function persistFileResult(queries, filePath, content, stats, result) {
403
626
  };
404
627
  queries.upsertFile(fileRecord);
405
628
  }
629
+ /** Force-rewrite every file in an incremental module index (edges may change without content). */
630
+ function persistIncrementalModuleResults(rootDir, queries, index) {
631
+ const persistTotal = index.fileResults.size;
632
+ let persistIndex = 0;
633
+ for (const [filePath, fileResult] of index.fileResults) {
634
+ persistIndex++;
635
+ reportBatchProgress('persist', persistIndex, persistTotal, filePath);
636
+ const isVirtual = filePath.startsWith('@');
637
+ const full = path.join(rootDir, filePath);
638
+ const content = isVirtual ? '' : fs.readFileSync(full, 'utf-8');
639
+ const stats = isVirtual
640
+ ? { size: 0, mtimeMs: Date.now() }
641
+ : fs.statSync(full);
642
+ persistFileResult(queries, filePath, content, stats, fileResult, { force: true });
643
+ }
644
+ }
406
645
  async function persistBatchResultsAsync(rootDir, queries, index) {
407
646
  const entries = [...index.fileResults.entries()];
408
647
  const persistTotal = entries.length;
@@ -501,8 +740,12 @@ function spawnIsolatedArkTSBatchWorker(rootDir, dbPath, triggerFile) {
501
740
  const workerPath = path.join(__dirname, '..', 'arkts-batch-worker.js');
502
741
  let lastCode = null;
503
742
  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}`]);
743
+ process.stderr.write(`\n\x1b[33m ArkTS: Scene build in isolated process (--stack-size=${stackKb} KB; modular if build-profile.json5)...\x1b[0m\n`);
744
+ const argv = (0, wasm_runtime_flags_1.buildRelaunchArgv)(workerPath, [rootDir, dbPath, triggerFile], [
745
+ ...process.execArgv,
746
+ `--stack-size=${stackKb}`,
747
+ (0, wasm_runtime_flags_1.maxOldSpaceSizeFlag)(),
748
+ ]);
506
749
  const result = (0, child_process_1.spawnSync)(process.execPath, argv, {
507
750
  stdio: 'inherit',
508
751
  env: process.env,
@@ -558,7 +801,7 @@ function runArkTSBatchFullCore(rootDir, queries, triggerFile, etsFiles, batchKey
558
801
  }
559
802
  }
560
803
  function runArkTSBatchFull(rootDir, queries, triggerFile, etsFiles, batchKey) {
561
- if (shouldUseIsolatedArkTSBuild(etsFiles.length)) {
804
+ if (shouldUseIsolatedArkTSBuild()) {
562
805
  const dbPath = homegraphDbPath(rootDir);
563
806
  if (!fs.existsSync(dbPath)) {
564
807
  throw new Error(`ArkTS isolated build requires ${dbPath}`);
@@ -569,7 +812,8 @@ function runArkTSBatchFull(rootDir, queries, triggerFile, etsFiles, batchKey) {
569
812
  const spawned = spawnIsolatedArkTSBatchWorker(rootDir, dbPath, triggerFile);
570
813
  if (!spawned.ok) {
571
814
  throw new Error(`ArkTS isolated Scene build failed (exit ${spawned.lastCode ?? 'unknown'}). ` +
572
- 'Try HOMEGRAPH_ARKTS_STACK_SIZES_KB=65536,131072,262144');
815
+ 'Unset HOMEGRAPH_ARKTS_ISOLATED to use the default in-process build, ' +
816
+ 'or raise HOMEGRAPH_ARKTS_STACK_SIZES_KB (e.g. 65536,131072,262144)');
573
817
  }
574
818
  return markBatchCommittedAfterWorker(rootDir, triggerFile, etsFiles, batchKey);
575
819
  }
@@ -605,12 +849,28 @@ function runArkTSBatch(rootDir, queries, triggerFile) {
605
849
  return runArkTSBatchFull(rootDir, queries, normalizedTrigger, etsFiles, batchKey);
606
850
  }
607
851
  /** Async batch build with parallel file reads during persist (used by indexAll). */
608
- async function primeArkTSBatch(rootDir, queries, triggerFile) {
852
+ async function primeArkTSBatch(rootDir, queries, triggerFile, options) {
609
853
  const normalizedTrigger = normIndexPath(triggerFile);
610
854
  const etsFiles = scanEtsFiles(rootDir);
611
- const cached = tryReturnCachedBatch(rootDir, normalizedTrigger, etsFiles);
612
- if (cached) {
613
- return;
855
+ const changed = options?.changedFiles
856
+ ? [...options.changedFiles].map(normIndexPath).filter(Boolean)
857
+ : null;
858
+ if (changed) {
859
+ const resolution = resolveDirtyHarmonyModules(rootDir, changed);
860
+ if (resolution.mode === 'none') {
861
+ return;
862
+ }
863
+ if (resolution.mode === 'modules') {
864
+ await runArkTSBatchIncrementalModules(rootDir, queries, normalizedTrigger, etsFiles, resolution.moduleSrcPaths);
865
+ return;
866
+ }
867
+ // mode === 'full': fall through to full rebuild (ignore in-memory cache).
868
+ }
869
+ else {
870
+ const cached = tryReturnCachedBatch(rootDir, normalizedTrigger, etsFiles);
871
+ if (cached) {
872
+ return;
873
+ }
614
874
  }
615
875
  const batchKey = etsFiles.length > 0 ? computeBatchKey(rootDir, etsFiles) : '';
616
876
  batchTriggerFile = null;
@@ -620,7 +880,7 @@ async function primeArkTSBatch(rootDir, queries, triggerFile) {
620
880
  batchBuildCommitted = false;
621
881
  return;
622
882
  }
623
- if (shouldUseIsolatedArkTSBuild(etsFiles.length)) {
883
+ if (shouldUseIsolatedArkTSBuild()) {
624
884
  runArkTSBatchFull(rootDir, queries, normalizedTrigger, etsFiles, batchKey);
625
885
  return;
626
886
  }
@@ -646,6 +906,56 @@ async function primeArkTSBatch(rootDir, queries, triggerFile) {
646
906
  (0, context_1.setArktsBatchRunning)(false);
647
907
  }
648
908
  }
909
+ async function runArkTSBatchIncrementalModules(rootDir, queries, triggerFile, etsFiles, moduleSrcPaths) {
910
+ // Isolated worker has no module-subset protocol yet — fall back to full.
911
+ if (shouldUseIsolatedArkTSBuild()) {
912
+ const batchKey = etsFiles.length > 0 ? computeBatchKey(rootDir, etsFiles) : '';
913
+ runArkTSBatchFull(rootDir, queries, triggerFile, etsFiles, batchKey);
914
+ return;
915
+ }
916
+ batchTriggerFile = null;
917
+ batchPersistedPaths = new Set();
918
+ (0, context_1.setArktsBatchRunning)(true);
919
+ try {
920
+ reportBatchProgress('scene', 0, etsFiles.length);
921
+ const index = buildArkTSIndexByModule(rootDir, etsFiles, { targetModuleSrcPaths: moduleSrcPaths });
922
+ if (index.fileResults.size === 0) {
923
+ const fatal = index.errors.find((e) => e.severity === 'error');
924
+ // Fall back to full project batch when the subset produced nothing.
925
+ const batchKey = etsFiles.length > 0 ? computeBatchKey(rootDir, etsFiles) : '';
926
+ if (fatal) {
927
+ process.stderr.write(`\n\x1b[33m ArkTS: incremental modules failed (${fatal.message}); full rebuild\x1b[0m\n`);
928
+ }
929
+ const full = buildArkTSIndex(rootDir, etsFiles);
930
+ if (full.fileResults.size === 0) {
931
+ throw new Error(fatal?.message ?? 'ArkTS incremental batch produced no indexed files');
932
+ }
933
+ queries.deleteArkTSCrossFileCallEdges();
934
+ await persistBatchResultsAsync(rootDir, queries, full);
935
+ if (full.crossFileEdges.length > 0) {
936
+ const valid = full.crossFileEdges.filter((e) => full.nodeIds.has(e.source) && full.nodeIds.has(e.target));
937
+ if (valid.length > 0)
938
+ queries.insertEdges(valid);
939
+ }
940
+ commitArkTSBatch(rootDir, batchKey, etsFiles, full, triggerFile);
941
+ return;
942
+ }
943
+ persistIncrementalModuleResults(rootDir, queries, index);
944
+ if (index.crossFileEdges.length > 0) {
945
+ const valid = index.crossFileEdges.filter((e) => index.nodeIds.has(e.source) && index.nodeIds.has(e.target));
946
+ if (valid.length > 0) {
947
+ queries.insertEdges(valid);
948
+ }
949
+ }
950
+ // Unique key so a partial index is never mistaken for a full-project cache hit.
951
+ const batchKey = `incremental:${moduleSrcPaths.join(',')}`;
952
+ commitArkTSBatch(rootDir, batchKey, etsFiles, index, triggerFile);
953
+ process.stderr.write(`\n\x1b[33m ArkTS: incremental module reindex done (${moduleSrcPaths.join(', ')})\x1b[0m\n`);
954
+ }
955
+ finally {
956
+ (0, context_1.setArktsBatchRunning)(false);
957
+ }
958
+ }
649
959
  /** True when this `.ets` file was already written by the in-memory ArkTS batch. */
650
960
  function isArkTSBatchPersisted(filePath) {
651
961
  return batchPersistedPaths.has(normIndexPath(filePath));
@@ -965,20 +1275,22 @@ function arkEdge(source, target, kind, extra) {
965
1275
  ...extra,
966
1276
  };
967
1277
  }
968
- function collectComponentScope(scene) {
1278
+ function collectComponentScopeFromFiles(files) {
969
1279
  const scope = [];
970
- for (const cls of scene.getClasses()) {
971
- if (cls.isDefaultArkClass())
972
- continue;
973
- if (cls.hasComponentDecorator()) {
974
- scope.push(cls);
1280
+ for (const file of files) {
1281
+ for (const cls of file.getClasses()) {
1282
+ if (cls.isDefaultArkClass())
1283
+ continue;
1284
+ if (cls.hasComponentDecorator()) {
1285
+ scope.push(cls);
1286
+ }
975
1287
  }
976
1288
  }
977
1289
  return scope;
978
1290
  }
979
- function collectNonUiRtaEntryPoints(scene) {
1291
+ function collectNonUiRtaEntryPointsFromFiles(files) {
980
1292
  const entries = [];
981
- for (const file of scene.getFiles()) {
1293
+ for (const file of files) {
982
1294
  const defaultClass = file.getDefaultClass();
983
1295
  for (const method of defaultClass.getMethods()) {
984
1296
  if (shouldSkipArkMethod(method))
@@ -992,13 +1304,17 @@ function collectNonUiRtaEntryPoints(scene) {
992
1304
  return entries;
993
1305
  }
994
1306
  function resolveRtaEntryPoints(scene) {
1307
+ return resolveRtaEntryPointsFromFiles(scene, scene.getFiles());
1308
+ }
1309
+ /** RTA entries scoped to the given files (one Harmony module while it is loaded). */
1310
+ function resolveRtaEntryPointsFromFiles(scene, files) {
995
1311
  const explicit = scene.getEntryPoints();
996
1312
  if (explicit.length > 0) {
997
1313
  return { entryPoints: explicit, dummyMain: null };
998
1314
  }
999
- if (sceneHasArkUiEntries(scene)) {
1315
+ if (filesHaveArkUiEntries(files)) {
1000
1316
  try {
1001
- const componentScope = collectComponentScope(scene);
1317
+ const componentScope = collectComponentScopeFromFiles(files);
1002
1318
  const dummy = new arkanalyzer_1.DummyMainCreater(scene, undefined, componentScope.length > 0 ? componentScope : undefined, true);
1003
1319
  dummy.createDummyMain();
1004
1320
  const dummyMain = dummy.getDummyMain();
@@ -1008,11 +1324,11 @@ function resolveRtaEntryPoints(scene) {
1008
1324
  // fall through
1009
1325
  }
1010
1326
  }
1011
- const fallback = collectNonUiRtaEntryPoints(scene);
1327
+ const fallback = collectNonUiRtaEntryPointsFromFiles(files);
1012
1328
  return { entryPoints: fallback, dummyMain: null };
1013
1329
  }
1014
- function sceneHasArkUiEntries(scene) {
1015
- for (const file of scene.getFiles()) {
1330
+ function filesHaveArkUiEntries(files) {
1331
+ for (const file of files) {
1016
1332
  for (const cls of file.getClasses()) {
1017
1333
  if (cls.isDefaultArkClass())
1018
1334
  continue;
@@ -1049,12 +1365,319 @@ function viewTreeLineFromStmt(stmt) {
1049
1365
  return stmt?.getOriginFullPosition()?.getFirstLine() ?? 1;
1050
1366
  }
1051
1367
  function isViewTreeClassSignature(sig) {
1052
- return sig instanceof arkanalyzer_1.ClassSignature;
1368
+ // Prefer instanceof; duck-type fallback if duplicate arkanalyzer copies break instanceof.
1369
+ if (sig instanceof arkanalyzer_1.ClassSignature)
1370
+ return true;
1371
+ if (sig instanceof arkanalyzer_1.MethodSignature)
1372
+ return false;
1373
+ const anySig = sig;
1374
+ // Require ClassSignature shape — MethodSignature-like and other sigs must not enter getClass().
1375
+ return (typeof anySig.getClassName === 'function' &&
1376
+ typeof anySig.getDeclaringFileSignature === 'function' &&
1377
+ typeof anySig.getMethodSubSignature !== 'function');
1378
+ }
1379
+ /** Skip anonymous / synthetic Ark names for name→id fallback maps. */
1380
+ function isUsableArkTypeName(name) {
1381
+ return !!name && !name.startsWith('%') && !name.startsWith('<');
1382
+ }
1383
+ /** Escape a literal for use inside a RegExp source. */
1384
+ function escapeRegExpLiteral(s) {
1385
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1386
+ }
1387
+ /**
1388
+ * ArkUI framework IR receivers — `View.create` / `Column.pop` / `If.branch`
1389
+ * are synthesizer noise, not project call sites. Never seed name-match from them.
1390
+ */
1391
+ const ARKUI_IR_CALL_RECEIVERS = new Set([
1392
+ 'View',
1393
+ 'If',
1394
+ 'ForEach',
1395
+ 'LazyForEach',
1396
+ 'Repeat',
1397
+ 'Column',
1398
+ 'Row',
1399
+ 'Stack',
1400
+ 'Flex',
1401
+ 'Grid',
1402
+ 'List',
1403
+ 'Swiper',
1404
+ 'Tabs',
1405
+ 'TabContent',
1406
+ 'Button',
1407
+ 'Text',
1408
+ 'Image',
1409
+ 'Scroll',
1410
+ 'WaterFlow',
1411
+ 'RelativeContainer',
1412
+ 'Blank',
1413
+ 'Divider',
1414
+ 'Span',
1415
+ 'Canvas',
1416
+ 'XComponent',
1417
+ ]);
1418
+ const ARKUI_IR_CALL_METHODS = new Set(['create', 'pop', 'branch', 'iterator']);
1419
+ const ARKTS_STDLIB_CALL_RECEIVERS = new Set([
1420
+ 'Array',
1421
+ 'Map',
1422
+ 'Set',
1423
+ 'Object',
1424
+ 'Promise',
1425
+ 'JSON',
1426
+ 'String',
1427
+ 'Number',
1428
+ 'Boolean',
1429
+ 'Error',
1430
+ 'Date',
1431
+ 'Math',
1432
+ 'RegExp',
1433
+ 'Symbol',
1434
+ ]);
1435
+ function isJunkArkCallSeed(className, methodName) {
1436
+ if (ARKTS_STDLIB_CALL_RECEIVERS.has(className))
1437
+ return true;
1438
+ if (ARKUI_IR_CALL_RECEIVERS.has(className) && ARKUI_IR_CALL_METHODS.has(methodName)) {
1439
+ return true;
1440
+ }
1441
+ return false;
1442
+ }
1443
+ /**
1444
+ * CFG must not name-link these across modules: each is a unique `Class::method`
1445
+ * key that still fans out to tens of thousands of invoke sites (singletons,
1446
+ * loggers, constructors) — far denser than RTA reachability on unlimited indexes.
1447
+ */
1448
+ const ARKTS_CFG_CROSS_MODULE_METHOD_BLOCKLIST = new Set([
1449
+ 'getInstance',
1450
+ 'constructor',
1451
+ 'toString',
1452
+ 'valueOf',
1453
+ 'showInfo',
1454
+ 'showError',
1455
+ 'showWarn',
1456
+ 'showDebug',
1457
+ 'show',
1458
+ 'create',
1459
+ 'pop',
1460
+ 'push',
1461
+ 'splice',
1462
+ 'isEmpty',
1463
+ 'getContext',
1464
+ 'copyFrom',
1465
+ // High fan-out helpers seen densifying same-module CFG on scene_board
1466
+ 'getGridOccupyStatus',
1467
+ 'isInStatusForGrid',
1468
+ 'isInStatus',
1469
+ 'getStatus',
1470
+ 'isOccupied',
1471
+ 'isFree',
1472
+ 'markStatus',
1473
+ 'markGridForCellAndSpan',
1474
+ 'createRelativePath',
1475
+ 'dealWantParams',
1476
+ 'getPreviewContentWidth',
1477
+ 'getIconArray',
1478
+ 'getSizeX',
1479
+ 'getSizeY',
1480
+ ]);
1481
+ /**
1482
+ * When ArkAnalyzer leaves a cross-module static call as `%unk/.use()`, recover
1483
+ * `Lib.use` from the invoke's original source. Only PascalCase receivers — so
1484
+ * `obj.foo()` / `this.bar()` stay unresolved rather than bare-method magnets.
1485
+ *
1486
+ * Trust short snippets only. Allow `return` / simple `const|let|var x =` prefixes
1487
+ * (`const x = Lib.use(b)` is the common modular IR original-text shape).
1488
+ */
1489
+ function recoverClassMethodFromStmt(stmt, methodName) {
1490
+ if (!stmt || !methodName)
1491
+ return null;
1492
+ let text;
1493
+ try {
1494
+ text = stmt.getOriginalText?.();
1495
+ }
1496
+ catch {
1497
+ text = undefined;
1498
+ }
1499
+ if (!text)
1500
+ return null;
1501
+ const trimmed = text.trim();
1502
+ if (trimmed.length === 0 || trimmed.length > 160)
1503
+ return null;
1504
+ // Mock / matcher wrappers are not real call seeds.
1505
+ if (/^(?:when|mock|spyOn)\s*\(/i.test(trimmed))
1506
+ return null;
1507
+ const re = new RegExp(`(?:^|(?:return\\s+)|(?:(?:const|let|var)\\s+[\\w$]+\\s*=\\s*))([A-Z][\\w$]*)\\.${escapeRegExpLiteral(methodName)}\\s*\\(`);
1508
+ const m = trimmed.match(re);
1509
+ return m?.[1] ? `${m[1]}.${methodName}` : null;
1510
+ }
1511
+ /**
1512
+ * Free-function call left as `%unk/.getOpaque()`: short text still contains
1513
+ * `getOpaque(` (not `Class.getOpaque`). Caller must import-gate + unique name.
1514
+ */
1515
+ function recoverFreeFunctionFromStmt(stmt, methodName) {
1516
+ if (!stmt || !methodName)
1517
+ return null;
1518
+ if (!/^[a-z][\w$]*$/.test(methodName))
1519
+ return null;
1520
+ let text;
1521
+ try {
1522
+ text = stmt.getOriginalText?.();
1523
+ }
1524
+ catch {
1525
+ text = undefined;
1526
+ }
1527
+ if (!text)
1528
+ return null;
1529
+ const trimmed = text.trim();
1530
+ if (trimmed.length === 0 || trimmed.length > 160)
1531
+ return null;
1532
+ if (/^(?:when|mock|spyOn)\s*\(/i.test(trimmed))
1533
+ return null;
1534
+ // Reject Class.method — that path belongs to recoverClassMethodFromStmt.
1535
+ if (new RegExp(`[A-Z][\\w$]*\\.${escapeRegExpLiteral(methodName)}\\s*\\(`).test(trimmed)) {
1536
+ return null;
1537
+ }
1538
+ const re = new RegExp(`(?:^|[^\\w.$])${escapeRegExpLiteral(methodName)}\\s*\\(`);
1539
+ return re.test(trimmed) ? methodName : null;
1540
+ }
1541
+ /**
1542
+ * Instance invoke whose MethodSignature is `%unk` may still carry a typed base:
1543
+ * ClassType (full sig) or UnclearReferenceType (type name string only — common
1544
+ * when deps are SIGNATURES-only).
1545
+ */
1546
+ function recoverClassMethodFromInstanceType(invoke, methodName) {
1547
+ if (!invoke || !methodName)
1548
+ return null;
1549
+ try {
1550
+ const base = invoke.getBase?.();
1551
+ if (!base)
1552
+ return null;
1553
+ const type = base.getType?.();
1554
+ if (!type)
1555
+ return null;
1556
+ const fromClassSig = type.getClassSignature?.()?.getClassName?.() ??
1557
+ '';
1558
+ if (isUsableArkTypeName(fromClassSig) && !fromClassSig.includes('%')) {
1559
+ return `${fromClassSig}.${methodName}`;
1560
+ }
1561
+ // UnclearReferenceType: getName() === "Base" (no file path).
1562
+ const fromName = type.getName?.() ??
1563
+ '';
1564
+ if (isUsableArkTypeName(fromName) && !fromName.includes('%') && /^[A-Z]/.test(fromName)) {
1565
+ return `${fromName}.${methodName}`;
1566
+ }
1567
+ return null;
1568
+ }
1569
+ catch {
1570
+ return null;
1571
+ }
1572
+ }
1573
+ /**
1574
+ * Modular IR often models `Lib.use` as an instance invoke with base Local named
1575
+ * `Lib` and type unknown — recover from the PascalCase base name.
1576
+ */
1577
+ function recoverClassMethodFromBaseName(invoke, methodName) {
1578
+ if (!invoke || !methodName)
1579
+ return null;
1580
+ try {
1581
+ const base = invoke.getBase?.();
1582
+ if (!base)
1583
+ return null;
1584
+ const baseName = base.getName?.() ?? '';
1585
+ if (!isUsableArkTypeName(baseName) || !/^[A-Z]/.test(baseName) || baseName.includes('%')) {
1586
+ return null;
1587
+ }
1588
+ return `${baseName}.${methodName}`;
1589
+ }
1590
+ catch {
1591
+ return null;
1592
+ }
1593
+ }
1594
+ /**
1595
+ * Cross-module `%unk` invoke → `Class.method`, free function name, or
1596
+ * `instantiates` class. Never emit a bare instance-method name alone.
1597
+ */
1598
+ function callReferenceNameFromSig(sig, opts) {
1599
+ try {
1600
+ const methodName = sig.getMethodSubSignature()?.getMethodName?.() ?? '';
1601
+ if (!methodName || methodName.startsWith('%') || methodName.startsWith('<'))
1602
+ return null;
1603
+ const className = sig.getDeclaringClassSignature()?.getClassName?.() ?? '';
1604
+ if (isUsableArkTypeName(className) && !className.includes('%')) {
1605
+ if (methodName === 'constructor') {
1606
+ return { name: className, kind: 'instantiates' };
1607
+ }
1608
+ if (isJunkArkCallSeed(className, methodName))
1609
+ return null;
1610
+ return { name: `${className}.${methodName}`, kind: 'calls' };
1611
+ }
1612
+ if (methodName === 'constructor')
1613
+ return null;
1614
+ const recovered = recoverClassMethodFromStmt(opts?.stmt, methodName) ??
1615
+ recoverClassMethodFromInstanceType(opts?.invoke, methodName) ??
1616
+ recoverClassMethodFromBaseName(opts?.invoke, methodName);
1617
+ if (recovered) {
1618
+ const dot = recovered.indexOf('.');
1619
+ if (dot > 0 && isJunkArkCallSeed(recovered.slice(0, dot), recovered.slice(dot + 1))) {
1620
+ return null;
1621
+ }
1622
+ return { name: recovered, kind: 'calls' };
1623
+ }
1624
+ const free = recoverFreeFunctionFromStmt(opts?.stmt, methodName);
1625
+ if (free)
1626
+ return { name: free, kind: 'calls', freeFunction: true };
1627
+ return null;
1628
+ }
1629
+ catch {
1630
+ return null;
1631
+ }
1632
+ }
1633
+ /**
1634
+ * True when a ClassSignature still has ArkAnalyzer's placeholder file
1635
+ * (`%unk`) — common on ViewTree stubs after a dependency was evicted before
1636
+ * type inference could rewrite `new LiveCardListView` to a real file path.
1637
+ */
1638
+ function isUnclearArkClassSignature(sig) {
1639
+ try {
1640
+ const fileSig = sig.getDeclaringFileSignature();
1641
+ const fileName = fileSig.getFileName();
1642
+ const projectName = fileSig.getProjectName();
1643
+ if (fileName === '%unk' || projectName === '%unk')
1644
+ return true;
1645
+ if (fileName.includes('%unk') || projectName.includes('%unk'))
1646
+ return true;
1647
+ return sig.toString().includes('%unk');
1648
+ }
1649
+ catch {
1650
+ return true;
1651
+ }
1652
+ }
1653
+ function isUnclearArkMethodSignature(sig) {
1654
+ try {
1655
+ return isUnclearArkClassSignature(sig.getDeclaringClassSignature());
1656
+ }
1657
+ catch {
1658
+ return true;
1659
+ }
1660
+ }
1661
+ /** Among candidate node ids, prefer a single `component` over same-name struct/class. */
1662
+ function pickPreferredClassNodeId(candidateIds, isComponent) {
1663
+ const ids = [...candidateIds];
1664
+ if (ids.length === 0)
1665
+ return null;
1666
+ if (ids.length === 1)
1667
+ return ids[0] ?? null;
1668
+ const components = ids.filter((id) => isComponent(id));
1669
+ if (components.length === 1)
1670
+ return components[0] ?? null;
1671
+ return null;
1053
1672
  }
1054
- function walkViewTree(node, visit) {
1673
+ /** Walk ViewTree with cycle protection (shared/cloned nodes can form graphs). */
1674
+ function walkViewTree(node, visit, seen = new Set()) {
1675
+ if (seen.has(node))
1676
+ return;
1677
+ seen.add(node);
1055
1678
  visit(node);
1056
1679
  for (const child of node.children) {
1057
- walkViewTree(child, visit);
1680
+ walkViewTree(child, visit, seen);
1058
1681
  }
1059
1682
  }
1060
1683
  function indexViewTreeForClass(ctx, cls, result, relativePath) {
@@ -1084,7 +1707,7 @@ function indexViewTreeForClass(ctx, cls, result, relativePath) {
1084
1707
  ctx.addEdge(result, source, target, kind, relativePath, via, line);
1085
1708
  };
1086
1709
  for (const [field] of viewTree.getStateValues()) {
1087
- const fieldId = ctx.fieldToId.get(field);
1710
+ const fieldId = ctx.resolveFieldNodeId(field);
1088
1711
  if (fieldId) {
1089
1712
  ctx.markFieldStateDecorators?.(field, fieldId, result);
1090
1713
  const line = field.getOriginFullPosition?.()?.getFirstLine?.() ??
@@ -1096,45 +1719,83 @@ function indexViewTreeForClass(ctx, cls, result, relativePath) {
1096
1719
  walkViewTree(root, (node) => {
1097
1720
  const sig = node.signature;
1098
1721
  if (sig) {
1099
- if (isViewTreeClassSignature(sig)) {
1100
- const childCls = ctx.scene.getClass(sig);
1101
- if (childCls) {
1102
- const childId = ctx.resolveClassNodeId(childCls);
1722
+ try {
1723
+ if (isViewTreeClassSignature(sig)) {
1724
+ // Stub or live: prefer already-indexed sig→id (works after dep unload);
1725
+ // %unk stubs also fall back to class name / parent-file import.
1726
+ // Then fall back to live IR object maps when the class is still resident.
1727
+ // Guard getClass — a mis-typed signature must not abort the whole ViewTree walk.
1728
+ let childCls = null;
1729
+ try {
1730
+ childCls = ctx.scene.getClass(sig);
1731
+ }
1732
+ catch {
1733
+ childCls = null;
1734
+ }
1735
+ const childId = ctx.resolveClassIdBySig(sig, cls) ??
1736
+ (childCls ? ctx.resolveClassNodeId(childCls) : null);
1103
1737
  if (childId) {
1104
- const firstAttr = node.attributes.values().next().value;
1105
- const line = firstAttr
1106
- ? viewTreeLineFromStmt(firstAttr[0])
1107
- : buildMethod.getImplOriginFullPosition()?.getFirstLine() ?? 1;
1738
+ let line = buildMethod.getImplOriginFullPosition()?.getFirstLine() ?? 1;
1739
+ try {
1740
+ const firstAttr = node.attributes?.values()?.next()?.value;
1741
+ if (firstAttr)
1742
+ line = viewTreeLineFromStmt(firstAttr[0]);
1743
+ }
1744
+ catch {
1745
+ // keep build() line
1746
+ }
1108
1747
  link(buildId, childId, 'references', 'child-component', line);
1109
1748
  }
1110
1749
  }
1111
- }
1112
- else {
1113
- const builderMethod = ctx.scene.getMethod(sig);
1114
- if (builderMethod) {
1115
- const builderId = ctx.ensureMethodNode(builderMethod, relativePath, result, classNodeId);
1750
+ else {
1751
+ // MethodSignature: builder stub (sig only) or live ArkMethod.
1752
+ let builderMethod = null;
1753
+ try {
1754
+ builderMethod = ctx.scene.getMethod(sig);
1755
+ }
1756
+ catch {
1757
+ builderMethod = null;
1758
+ }
1759
+ const builderId = ctx.resolveMethodIdBySig(sig, cls) ??
1760
+ (builderMethod
1761
+ ? ctx.ensureMethodNode(builderMethod, relativePath, result, classNodeId)
1762
+ : null);
1116
1763
  if (builderId) {
1117
- link(buildId, builderId, 'references', 'builder', builderMethod.getImplOriginFullPosition()?.getFirstLine() ?? 1);
1764
+ const line = builderMethod
1765
+ ? builderMethod.getImplOriginFullPosition()?.getFirstLine() ?? 1
1766
+ : buildMethod.getImplOriginFullPosition()?.getFirstLine() ?? 1;
1767
+ link(buildId, builderId, 'references', 'builder', line);
1118
1768
  }
1119
1769
  }
1120
1770
  }
1771
+ catch {
1772
+ // Skip this node's signature edge; keep walking siblings/children.
1773
+ }
1121
1774
  }
1122
1775
  if (node.stateValuesTransfer) {
1123
1776
  for (const [childField, parentValue] of node.stateValuesTransfer) {
1124
- const childFieldId = ctx.fieldToId.get(childField);
1777
+ const childFieldId = ctx.resolveFieldNodeId(childField);
1125
1778
  if (parentValue instanceof arkanalyzer_1.ArkField) {
1126
1779
  const via = stateTransferViaForField(childField);
1127
1780
  if (!via || !childFieldId)
1128
1781
  continue;
1129
1782
  ctx.markFieldStateDecorators?.(childField, childFieldId, result);
1130
- const parentFieldId = ctx.fieldToId.get(parentValue);
1783
+ const parentFieldId = ctx.resolveFieldNodeId(parentValue);
1131
1784
  if (parentFieldId) {
1132
1785
  ctx.markFieldStateDecorators?.(parentValue, parentFieldId, result);
1133
1786
  link(parentFieldId, childFieldId, 'references', via, childField.getOriginFullPosition()?.getFirstLine() ?? 1);
1134
1787
  }
1135
1788
  }
1136
1789
  else if (parentValue instanceof arkanalyzer_1.ArkMethod) {
1137
- const builderId = ctx.ensureMethodNode(parentValue, relativePath, result, classNodeId);
1790
+ let builderId = ctx.ensureMethodNode(parentValue, relativePath, result, classNodeId);
1791
+ if (!builderId) {
1792
+ try {
1793
+ builderId = ctx.resolveMethodIdBySig(parentValue.getSignature());
1794
+ }
1795
+ catch {
1796
+ builderId = null;
1797
+ }
1798
+ }
1138
1799
  if (childFieldId && builderId) {
1139
1800
  link(builderId, childFieldId, 'references', 'builder-param', childField.getOriginFullPosition()?.getFirstLine() ?? 1);
1140
1801
  }
@@ -1146,12 +1807,15 @@ function indexViewTreeForClass(ctx, cls, result, relativePath) {
1146
1807
  continue;
1147
1808
  const line = viewTreeLineFromStmt(stmt);
1148
1809
  for (const v of values) {
1149
- if (!(v instanceof arkanalyzer_1.MethodSignature))
1810
+ const isMethodSig = v instanceof arkanalyzer_1.MethodSignature ||
1811
+ (v != null &&
1812
+ typeof v.getMethodSubSignature === 'function');
1813
+ if (!isMethodSig)
1150
1814
  continue;
1151
- const handler = ctx.scene.getMethod(v);
1152
- if (!handler)
1153
- continue;
1154
- const handlerId = ctx.ensureMethodNode(handler, relativePath, result, classNodeId);
1815
+ const methodSig = v;
1816
+ const handler = ctx.scene.getMethod(methodSig);
1817
+ const handlerId = ctx.resolveMethodIdBySig(methodSig, cls) ??
1818
+ (handler ? ctx.ensureMethodNode(handler, relativePath, result, classNodeId) : null);
1155
1819
  if (handlerId)
1156
1820
  link(buildId, handlerId, 'references', attr, line);
1157
1821
  }
@@ -1166,100 +1830,532 @@ class ArkTSAdapter {
1166
1830
  classToId = new Map();
1167
1831
  componentToId = new Map();
1168
1832
  fieldToId = new Map();
1833
+ /** Stable across module unload — signature string → HomeGraph node id. */
1834
+ methodSigToId = new Map();
1835
+ classSigToId = new Map();
1836
+ fieldSigToId = new Map();
1837
+ /**
1838
+ * Name → candidate node ids for ViewTree stub stitch when the signature is still
1839
+ * `@%unk/%unk: ClassName` after dependency eviction. Prefer `component` over
1840
+ * same-name struct/class; ambiguous multi-component names use the importer.
1841
+ */
1842
+ classNameToIds = new Map();
1843
+ classIdFilePath = new Map();
1844
+ classIdIsComponent = new Map();
1845
+ /** `ClassName::methodName` → candidate method node ids (unclear MethodSignature stubs). */
1846
+ methodNameKeyToIds = new Map();
1847
+ /** Bare default-class function name → ids (unique import-gated CFG bridge). */
1848
+ functionNameToIds = new Map();
1849
+ methodIdFilePath = new Map();
1850
+ emittedEdgeKeys = new Set();
1169
1851
  nodeIds = new Set();
1170
1852
  fileResults = new Map();
1171
1853
  crossFileEdges = [];
1854
+ /** Per-module `@dummyMain@…` created during analyseByModule (for scene aggregator). */
1855
+ moduleDummyMains = [];
1172
1856
  constructor(rootDir, scannedFiles) {
1173
1857
  this.rootDir = rootDir;
1174
1858
  this.scanned = new Set(scannedFiles);
1175
1859
  }
1176
1860
  build(scene) {
1177
1861
  this.scene = scene;
1178
- const { entryPoints, dummyMain } = resolveRtaEntryPoints(scene);
1179
1862
  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))
1184
- continue;
1185
- this.indexFile(arkFile);
1863
+ this.indexScannedFile(arkFile);
1186
1864
  }
1187
- for (const cls of scene.getClasses()) {
1188
- if (!cls.hasViewTree())
1865
+ this.indexViewTreesFromFiles(scene.getFiles());
1866
+ this.finalizeCallGraph(scene);
1867
+ return this.toBatchIndex();
1868
+ }
1869
+ /**
1870
+ * Index one Harmony module while loaded (baseline = this module):
1871
+ * symbols → rebind resident deps → ViewTree/RTA from this module (B→A via
1872
+ * live IR or sig→id maps for already-indexed unloaded deps).
1873
+ */
1874
+ indexModule(module, scene) {
1875
+ this.scene = scene;
1876
+ const files = [...module.getFilesMap().values()];
1877
+ const moduleFiles = new Set(files.map((f) => normalizeRelPath(this.rootDir, f.getFilePath())).filter(Boolean));
1878
+ for (const arkFile of files) {
1879
+ this.indexScannedFile(arkFile);
1880
+ }
1881
+ // Rebind still-resident deps so live getClass/getMethod works when topo kept them warm.
1882
+ this.bindLiveMapsFromResident(scene);
1883
+ // Incremental / first-in-topo: populate sig→id maps from other loaded modules
1884
+ // (deps at SIGNATURES) without persisting those files into this batch.
1885
+ this.seedMapsFromOtherLoadedFiles(scene, moduleFiles);
1886
+ this.indexViewTreesFromFiles(files);
1887
+ this.ingestModuleCallGraph(scene, module, files, moduleFiles);
1888
+ // CFG: only narrow `%unk` cross-module bridge (no same-module CFG calls).
1889
+ this.stitchModuleCfgCallsAndRefs(files, moduleFiles);
1890
+ // Drop live IR object keys so ArkAnalyzer can GC unloaded modules; keep sig→id maps.
1891
+ this.releaseLiveIrMaps();
1892
+ }
1893
+ /**
1894
+ * Index symbols from already-loaded non-target modules into sig/name maps only.
1895
+ * Used so incremental BODIES targets can still resolve cross-module RTA/ViewTree
1896
+ * edges against deps that stay at SIGNATURES (and are not re-persisted).
1897
+ */
1898
+ seedMapsFromOtherLoadedFiles(scene, excludeFiles) {
1899
+ const seeded = [];
1900
+ for (const mod of scene.getModules()) {
1901
+ let filesMap;
1902
+ try {
1903
+ filesMap = mod.getFilesMap();
1904
+ }
1905
+ catch {
1189
1906
  continue;
1190
- const arkFile = cls.getDeclaringArkFile();
1191
- const relativePath = normalizeRelPath(this.rootDir, arkFile.getFilePath());
1192
- if (!this.scanned.has(relativePath) || !isArkAnalyzerSourcePath(relativePath))
1907
+ }
1908
+ for (const arkFile of filesMap.values()) {
1909
+ const rel = normalizeRelPath(this.rootDir, arkFile.getFilePath());
1910
+ if (!rel || excludeFiles.has(rel) || this.fileResults.has(rel))
1911
+ continue;
1912
+ if (!this.scanned.has(rel) || !isArkAnalyzerSourcePath(rel))
1913
+ continue;
1914
+ this.indexScannedFile(arkFile);
1915
+ if (this.fileResults.has(rel))
1916
+ seeded.push(rel);
1917
+ }
1918
+ }
1919
+ for (const rel of seeded) {
1920
+ this.fileResults.delete(rel);
1921
+ }
1922
+ }
1923
+ bindLiveMapsFromResident(scene) {
1924
+ const cache = scene.getModuleCache();
1925
+ if (!cache)
1926
+ return;
1927
+ const files = [];
1928
+ for (const id of cache.getLoadedModules()) {
1929
+ const mod = scene.getModule(id);
1930
+ if (!mod || mod.getModuleType() === arkanalyzer_1.ModuleType.SDK)
1193
1931
  continue;
1194
- const result = this.fileResults.get(relativePath);
1195
- if (!result)
1932
+ if (mod.getLoadState() < arkanalyzer_1.ModuleLoadState.SIGNATURES)
1196
1933
  continue;
1934
+ files.push(...mod.getFilesMap().values());
1935
+ }
1936
+ if (files.length > 0)
1937
+ this.bindLiveMapsFromFiles(files);
1938
+ }
1939
+ /** Re-attach live Ark* objects to already-indexed HomeGraph node ids (sig maps). */
1940
+ bindLiveMapsFromFiles(files) {
1941
+ for (const arkFile of files) {
1942
+ const relativePath = normalizeRelPath(this.rootDir, arkFile.getFilePath());
1943
+ if (!this.scanned.has(relativePath) || !isArkAnalyzerSourcePath(relativePath))
1944
+ continue;
1945
+ this.bindLiveClass(arkFile.getDefaultClass());
1946
+ for (const cls of arkFile.getClasses()) {
1947
+ this.bindLiveClass(cls);
1948
+ }
1949
+ for (const ns of arkFile.getNamespaces()) {
1950
+ this.bindLiveNamespace(ns);
1951
+ }
1952
+ }
1953
+ }
1954
+ bindLiveNamespace(ns) {
1955
+ this.bindLiveClass(ns.getDefaultClass());
1956
+ for (const cls of ns.getClasses()) {
1957
+ this.bindLiveClass(cls);
1958
+ }
1959
+ for (const child of ns.getNamespaces()) {
1960
+ this.bindLiveNamespace(child);
1961
+ }
1962
+ }
1963
+ bindLiveClass(cls) {
1964
+ try {
1965
+ const classSig = cls.getSignature().toString();
1966
+ const classId = this.classSigToId.get(classSig);
1967
+ if (classId) {
1968
+ this.classToId.set(cls, classId);
1969
+ if (cls.hasComponentDecorator()) {
1970
+ this.componentToId.set(cls, classId);
1971
+ }
1972
+ }
1973
+ for (const method of cls.getMethods(true)) {
1974
+ try {
1975
+ const mid = this.methodSigToId.get(method.getSignature().toString());
1976
+ if (mid)
1977
+ this.methodToId.set(method, mid);
1978
+ }
1979
+ catch {
1980
+ // skip
1981
+ }
1982
+ }
1983
+ for (const field of cls.getFields()) {
1984
+ const fid = this.fieldSigToId.get(fieldSigKey(classSig, field.getName()));
1985
+ if (fid)
1986
+ this.fieldToId.set(field, fid);
1987
+ }
1988
+ }
1989
+ catch {
1990
+ // signature unavailable
1991
+ }
1992
+ }
1993
+ finalizeCallGraph(scene) {
1994
+ this.scene = scene;
1995
+ // Modular path: intra-module RTA already ran per module. Do NOT run a
1996
+ // scene aggregator RTA — that re-walks overlapping reachable sets and was
1997
+ // a major wall-clock cost. Cross-module gaps link only via exact
1998
+ // signature / unique Class.method maps (no unresolved name-match seeds).
1999
+ if (this.moduleDummyMains.length > 0) {
1197
2000
  try {
1198
- indexViewTreeForClass(this.viewTreeContext(), cls, result, relativePath);
2001
+ for (const m of this.moduleDummyMains) {
2002
+ this.indexArkanalyzerDummyMain(m);
2003
+ }
1199
2004
  }
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
- });
2005
+ catch {
2006
+ // ignore
1207
2007
  }
2008
+ return;
1208
2009
  }
2010
+ const { entryPoints, dummyMain } = resolveRtaEntryPoints(scene);
1209
2011
  if (dummyMain) {
1210
2012
  this.indexArkanalyzerDummyMain(dummyMain);
1211
2013
  }
2014
+ if (entryPoints.length === 0)
2015
+ return;
1212
2016
  let callGraph;
1213
2017
  try {
1214
- callGraph =
1215
- entryPoints.length > 0 ? scene.makeCallGraphRTA(entryPoints) : null;
2018
+ callGraph = scene.makeCallGraphRTA(entryPoints);
1216
2019
  }
1217
2020
  catch {
1218
- callGraph = null;
2021
+ return;
1219
2022
  }
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)
2023
+ this.ingestCallGraph(callGraph, null);
2024
+ }
2025
+ ingestModuleCallGraph(scene, module, files, moduleFiles) {
2026
+ let entryPoints = [];
2027
+ let dummyMain = null;
2028
+ try {
2029
+ // Prefer AA's forModule when present; otherwise DIY with the public ctor
2030
+ // (module-scoped name + this module's classes).
2031
+ const forModule = arkanalyzer_1.DummyMainCreater.forModule;
2032
+ let creater;
2033
+ if (typeof forModule === 'function') {
2034
+ creater = forModule(scene, module, true);
2035
+ }
2036
+ else {
2037
+ const classScope = collectComponentScopeFromFiles(files);
2038
+ const moduleName = module.getModuleName() || 'module';
2039
+ creater = new arkanalyzer_1.DummyMainCreater(scene, `@dummyMain@${moduleName}`, classScope.length > 0 ? classScope : undefined, true);
2040
+ }
2041
+ creater.createDummyMain();
2042
+ dummyMain = creater.getDummyMain();
2043
+ if (dummyMain) {
2044
+ entryPoints = [dummyMain.getSignature()];
2045
+ this.moduleDummyMains.push(dummyMain);
2046
+ }
2047
+ }
2048
+ catch {
2049
+ // Fall back to file-scoped DummyMain (components only) if creation fails.
2050
+ const resolved = resolveRtaEntryPointsFromFiles(scene, files);
2051
+ entryPoints = resolved.entryPoints;
2052
+ dummyMain = resolved.dummyMain;
2053
+ }
2054
+ if (dummyMain) {
2055
+ this.indexArkanalyzerDummyMain(dummyMain);
2056
+ }
2057
+ if (entryPoints.length === 0)
2058
+ return;
2059
+ let callGraph;
2060
+ try {
2061
+ callGraph = scene.makeCallGraphRTA(entryPoints);
2062
+ }
2063
+ catch {
2064
+ return;
2065
+ }
2066
+ this.ingestCallGraph(callGraph, moduleFiles);
2067
+ }
2068
+ /**
2069
+ * Keep ArkAnalyzer RTA edges whose **caller** is in the current module.
2070
+ *
2071
+ * - Same-module: live IR id, exact MethodSignature map, or unique Class.method
2072
+ * name fallback (path-drift).
2073
+ * - Cross-module: **exact MethodSignature string or live callee only** — no
2074
+ * Class.method name fallback (that + CFG densified scene_board to ~400k
2075
+ * ark calls vs ~76k on an unlimited RTA index).
2076
+ */
2077
+ ingestCallGraph(callGraph, moduleFiles) {
2078
+ for (const edge of callGraph.getCallEdges()) {
2079
+ const callerArk = callGraph.getArkMethodByFuncID(edge.getSrcID());
2080
+ const calleeArk = callGraph.getArkMethodByFuncID(edge.getDstID());
2081
+ const callerSig = callGraph.getMethodByFuncID(edge.getSrcID());
2082
+ const calleeSig = callGraph.getMethodByFuncID(edge.getDstID());
2083
+ if (callerArk &&
2084
+ shouldSkipArkMethod(callerArk) &&
2085
+ !isAnonymousArkMethod(callerArk) &&
2086
+ !callerArk.getDeclaringArkClass().isDefaultArkClass() &&
2087
+ !callerArk.isGenerated()) {
2088
+ continue;
2089
+ }
2090
+ const callerFile = (callerArk ? relPathForArkFile(this.rootDir, callerArk.getDeclaringArkFile()) : null) ??
2091
+ (callerSig ? this.relPathFromMethodSignature(callerSig) : null) ??
2092
+ '';
2093
+ if (moduleFiles && callerFile && !moduleFiles.has(callerFile)) {
2094
+ continue;
2095
+ }
2096
+ const calleeFile = (calleeArk ? relPathForArkFile(this.rootDir, calleeArk.getDeclaringArkFile()) : null) ??
2097
+ (calleeSig ? this.relPathFromMethodSignature(calleeSig) : null);
2098
+ const callerId = (callerArk ? this.resolveMethodNodeId(callerArk) : null) ??
2099
+ (callerSig ? this.resolveMethodIdBySigExact(callerSig) : null);
2100
+ if (!callerId)
2101
+ continue;
2102
+ const sameModule = !!moduleFiles && !!calleeFile && moduleFiles.has(calleeFile);
2103
+ const dispatch = edge.hasDirectCall() ? 'direct' : 'indirect';
2104
+ // Full-scene path (no moduleFiles): keep prior resolveMethodIdBySig behavior.
2105
+ if (!moduleFiles) {
2106
+ const calleeId = (calleeArk ? this.resolveMethodNodeId(calleeArk) : null) ??
2107
+ (calleeSig ? this.resolveMethodIdBySig(calleeSig) : null);
2108
+ if (!calleeId)
1235
2109
  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);
2110
+ this.emitArkCallEdge(callerId, calleeId, callerFile, dispatch, callerArk);
2111
+ continue;
2112
+ }
2113
+ if (!sameModule) {
2114
+ const calleeId = (calleeArk ? this.resolveMethodNodeId(calleeArk) : null) ??
2115
+ (calleeSig ? this.resolveMethodIdBySigExact(calleeSig) : null);
2116
+ if (calleeId) {
2117
+ this.emitArkCallEdge(callerId, calleeId, callerFile, dispatch, callerArk);
1250
2118
  }
1251
- else {
1252
- this.crossFileEdges.push(callEdge);
2119
+ continue;
2120
+ }
2121
+ const calleeId = (calleeArk ? this.resolveMethodNodeId(calleeArk) : null) ??
2122
+ (calleeSig ? this.resolveMethodIdBySig(calleeSig) : null);
2123
+ if (!calleeId)
2124
+ continue;
2125
+ this.emitArkCallEdge(callerId, calleeId, callerFile, dispatch, callerArk);
2126
+ }
2127
+ }
2128
+ /**
2129
+ * CFG call stitch (modular): no same-module CFG call edges (those were the
2130
+ * ~150k `direct` flood past unlimited RTA). Only the narrow cross-module
2131
+ * `%unk` bridge for `return Lib.use(…)`-style static calls.
2132
+ */
2133
+ stitchModuleCfgCallsAndRefs(files, _moduleFiles) {
2134
+ for (const arkFile of files) {
2135
+ const callerFile = normalizeRelPath(this.rootDir, arkFile.getFilePath());
2136
+ const visitClass = (cls) => {
2137
+ for (const method of cls.getMethods(true)) {
2138
+ if (shouldSkipArkMethod(method) &&
2139
+ !isAnonymousArkMethod(method) &&
2140
+ !method.isGenerated()) {
2141
+ continue;
2142
+ }
2143
+ const callerId = this.resolveMethodNodeId(method) ??
2144
+ (() => {
2145
+ try {
2146
+ return this.methodSigToId.get(method.getSignature().toString()) ?? null;
2147
+ }
2148
+ catch {
2149
+ return null;
2150
+ }
2151
+ })();
2152
+ if (!callerId)
2153
+ continue;
2154
+ let cfg;
2155
+ try {
2156
+ cfg = method.getCfg();
2157
+ }
2158
+ catch {
2159
+ continue;
2160
+ }
2161
+ if (!cfg)
2162
+ continue;
2163
+ for (const stmt of cfg.getStmts()) {
2164
+ let invoke;
2165
+ try {
2166
+ invoke = stmt.getInvokeExpr();
2167
+ }
2168
+ catch {
2169
+ continue;
2170
+ }
2171
+ if (!invoke)
2172
+ continue;
2173
+ let calleeSig;
2174
+ try {
2175
+ calleeSig = invoke.getMethodSignature();
2176
+ }
2177
+ catch {
2178
+ continue;
2179
+ }
2180
+ if (!calleeSig)
2181
+ continue;
2182
+ // Skip clear same-module paths — RTA owns those. Only `%unk` /
2183
+ // missing-path invokes may take the narrow cross-module bridge.
2184
+ const calleeFile = this.relPathFromMethodSignature(calleeSig);
2185
+ if (calleeFile)
2186
+ continue;
2187
+ this.tryLinkCfgCrossModuleUnk(callerId, callerFile, calleeSig, method, stmt, invoke, arkFile);
2188
+ }
1253
2189
  }
2190
+ };
2191
+ visitClass(arkFile.getDefaultClass());
2192
+ for (const cls of arkFile.getClasses())
2193
+ visitClass(cls);
2194
+ const walkNs = (ns) => {
2195
+ visitClass(ns.getDefaultClass());
2196
+ for (const cls of ns.getClasses())
2197
+ visitClass(cls);
2198
+ for (const child of ns.getNamespaces())
2199
+ walkNs(child);
2200
+ };
2201
+ for (const ns of arkFile.getNamespaces())
2202
+ walkNs(ns);
2203
+ }
2204
+ }
2205
+ emitArkCallEdge(callerId, calleeId, callerFile, dispatch, callerArk) {
2206
+ const edgeKey = `${callerId}\0${calleeId}\0calls\0${dispatch}`;
2207
+ if (this.emittedEdgeKeys.has(edgeKey))
2208
+ return;
2209
+ if (this.emittedEdgeKeys.has(`${callerId}\0${calleeId}\0calls\0direct`))
2210
+ return;
2211
+ if (this.emittedEdgeKeys.has(`${callerId}\0${calleeId}\0calls\0indirect`))
2212
+ return;
2213
+ this.emittedEdgeKeys.add(edgeKey);
2214
+ const callEdge = arkEdge(callerId, calleeId, 'calls', {
2215
+ metadata: {
2216
+ synthesizedBy: 'arkanalyzer',
2217
+ dispatch,
2218
+ sourceFile: callerFile || undefined,
2219
+ rtaEntry: callerArk?.isGenerated?.() && callerArk.getName() === '@dummyMain'
2220
+ ? '@dummyMain'
2221
+ : undefined,
2222
+ },
2223
+ });
2224
+ const callerResult = callerFile ? this.fileResults.get(callerFile) : undefined;
2225
+ if (callerResult && this.nodeInFile(calleeId, callerFile)) {
2226
+ callerResult.edges.push(callEdge);
2227
+ }
2228
+ else {
2229
+ this.crossFileEdges.push(callEdge);
2230
+ }
2231
+ }
2232
+ /**
2233
+ * CFG `%unk` bridge: recover Class.method / free function from invoke crumbs
2234
+ * (stmt text, UnclearReferenceType name, PascalCase base local), require
2235
+ * import + unique indexed target, skip fan-out blocklist. No unresolved refs.
2236
+ */
2237
+ tryLinkCfgCrossModuleUnk(callerId, callerFile, calleeSig, callerArk, stmt, invoke, arkFile) {
2238
+ // Only for unclear / %unk signatures — clear typed cross-module invokes
2239
+ // stay RTA-only so we do not densify past unlimited RTA.
2240
+ if (!isUnclearArkMethodSignature(calleeSig)) {
2241
+ try {
2242
+ const className = calleeSig.getDeclaringClassSignature()?.getClassName?.() ?? '';
2243
+ if (isUsableArkTypeName(className) && !className.includes('%'))
2244
+ return;
2245
+ }
2246
+ catch {
2247
+ // treat as unclear
1254
2248
  }
1255
2249
  }
2250
+ const parsed = callReferenceNameFromSig(calleeSig, { stmt, invoke });
2251
+ if (!parsed || parsed.kind !== 'calls')
2252
+ return;
2253
+ if (parsed.freeFunction) {
2254
+ const fnName = parsed.name;
2255
+ if (!fnName || ARKTS_CFG_CROSS_MODULE_METHOD_BLOCKLIST.has(fnName))
2256
+ return;
2257
+ if (!this.fileImportsLocalName(arkFile, fnName))
2258
+ return;
2259
+ const calleeId = this.resolveFunctionIdByName(fnName);
2260
+ if (!calleeId)
2261
+ return;
2262
+ this.emitArkCallEdge(callerId, calleeId, callerFile, 'direct', callerArk);
2263
+ return;
2264
+ }
2265
+ const dot = parsed.name.indexOf('.');
2266
+ if (dot <= 0)
2267
+ return;
2268
+ const className = parsed.name.slice(0, dot);
2269
+ const methodName = parsed.name.slice(dot + 1);
2270
+ if (!className || !methodName)
2271
+ return;
2272
+ if (ARKTS_CFG_CROSS_MODULE_METHOD_BLOCKLIST.has(methodName))
2273
+ return;
2274
+ if (isJunkArkCallSeed(className, methodName))
2275
+ return;
2276
+ if (!this.fileImportsLocalName(arkFile, className))
2277
+ return;
2278
+ const calleeId = this.resolveMethodIdByName(className, methodName);
2279
+ if (!calleeId)
2280
+ return;
2281
+ this.emitArkCallEdge(callerId, calleeId, callerFile, 'direct', callerArk);
2282
+ }
2283
+ fileImportsLocalName(arkFile, localName) {
2284
+ try {
2285
+ return !!arkFile.getImportInfoBy(localName);
2286
+ }
2287
+ catch {
2288
+ return false;
2289
+ }
2290
+ }
2291
+ /** Exact MethodSignature.toString() → node id (no Class.method name fallback). */
2292
+ resolveMethodIdBySigExact(sig) {
2293
+ try {
2294
+ return this.methodSigToId.get(sig.toString()) ?? null;
2295
+ }
2296
+ catch {
2297
+ return null;
2298
+ }
2299
+ }
2300
+ /** Project-relative path from a MethodSignature's declaring file (may be %unk). */
2301
+ relPathFromMethodSignature(sig) {
2302
+ try {
2303
+ const fileName = sig.getDeclaringClassSignature().getDeclaringFileSignature().getFileName();
2304
+ if (!fileName || fileName === '%unk' || fileName.includes('%unk'))
2305
+ return null;
2306
+ return normalizeRelPath(this.rootDir, fileName);
2307
+ }
2308
+ catch {
2309
+ return null;
2310
+ }
2311
+ }
2312
+ releaseLiveIrMaps() {
2313
+ this.methodToId.clear();
2314
+ this.classToId.clear();
2315
+ this.componentToId.clear();
2316
+ this.fieldToId.clear();
2317
+ }
2318
+ toBatchIndex(errors = []) {
1256
2319
  return {
1257
2320
  fileResults: this.fileResults,
1258
2321
  crossFileEdges: this.crossFileEdges,
1259
2322
  nodeIds: this.nodeIds,
1260
- errors: [],
2323
+ errors: [...errors],
1261
2324
  };
1262
2325
  }
2326
+ indexScannedFile(arkFile) {
2327
+ const relativePath = normalizeRelPath(this.rootDir, arkFile.getFilePath());
2328
+ if (!this.scanned.has(relativePath))
2329
+ return;
2330
+ if (!isArkAnalyzerSourcePath(relativePath))
2331
+ return;
2332
+ this.indexFile(arkFile);
2333
+ }
2334
+ indexViewTreesFromFiles(files) {
2335
+ for (const arkFile of files) {
2336
+ const relativePath = normalizeRelPath(this.rootDir, arkFile.getFilePath());
2337
+ if (!this.scanned.has(relativePath) || !isArkAnalyzerSourcePath(relativePath))
2338
+ continue;
2339
+ const result = this.fileResults.get(relativePath);
2340
+ if (!result)
2341
+ continue;
2342
+ for (const cls of arkFile.getClasses()) {
2343
+ if (!cls.hasViewTree())
2344
+ continue;
2345
+ try {
2346
+ indexViewTreeForClass(this.viewTreeContext(), cls, result, relativePath);
2347
+ }
2348
+ catch (e) {
2349
+ const message = e instanceof Error ? e.message : String(e);
2350
+ result.errors.push({
2351
+ message: `ViewTree indexing skipped for ${cls.getName()}: ${message}`,
2352
+ filePath: relativePath,
2353
+ severity: 'warning',
2354
+ });
2355
+ }
2356
+ }
2357
+ }
2358
+ }
1263
2359
  /** Index ArkAnalyzer's in-scene @dummyMain (DummyMainCreater output), not a HomeGraph synthetic node. */
1264
2360
  indexArkanalyzerDummyMain(dummyMain) {
1265
2361
  const result = this.ensureFileResult(ARKANALYZER_DUMMY_FILE, 1);
@@ -1414,6 +2510,13 @@ class ArkTSAdapter {
1414
2510
  });
1415
2511
  this.addNode(result, classNode);
1416
2512
  this.classToId.set(cls, classNode.id);
2513
+ try {
2514
+ this.classSigToId.set(cls.getSignature().toString(), classNode.id);
2515
+ }
2516
+ catch {
2517
+ // signature unavailable
2518
+ }
2519
+ this.rememberClassName(displayName, classNode.id, relativePath, false);
1417
2520
  result.edges.push(arkEdge(parentId, classNode.id, 'contains'));
1418
2521
  this.indexDecorators(result, classNode.id, cls, relativePath, language, line);
1419
2522
  if (cls.isAnonymousClass()) {
@@ -1434,6 +2537,13 @@ class ArkTSAdapter {
1434
2537
  const componentNode = makeNode(relativePath, language, 'component', displayName, qn, line, endLine, col, { ...modelModifiersToNodeExtras(cls) });
1435
2538
  this.addNode(result, componentNode);
1436
2539
  this.componentToId.set(cls, componentNode.id);
2540
+ try {
2541
+ this.classSigToId.set(cls.getSignature().toString(), componentNode.id);
2542
+ }
2543
+ catch {
2544
+ // signature unavailable
2545
+ }
2546
+ this.rememberClassName(displayName, componentNode.id, relativePath, true);
1437
2547
  result.edges.push(arkEdge(classNode.id, componentNode.id, 'contains'));
1438
2548
  }
1439
2549
  const superName = cls.getSuperClassName();
@@ -1518,6 +2628,12 @@ class ArkTSAdapter {
1518
2628
  });
1519
2629
  this.addNode(result, fieldNode);
1520
2630
  this.fieldToId.set(field, fieldNode.id);
2631
+ try {
2632
+ this.fieldSigToId.set(fieldSigKey(cls.getSignature().toString(), name), fieldNode.id);
2633
+ }
2634
+ catch {
2635
+ // signature unavailable
2636
+ }
1521
2637
  result.edges.push(arkEdge(parentId, fieldNode.id, 'contains'));
1522
2638
  this.indexDecorators(result, fieldNode.id, field, relativePath, language, line);
1523
2639
  }
@@ -1536,6 +2652,9 @@ class ArkTSAdapter {
1536
2652
  addEdge: (result, source, target, kind, callerFile, via, line) => this.addViewTreeEdge(result, source, target, kind, callerFile, via, line),
1537
2653
  ensureMethodNode: (method, relativePath, result, parentId) => this.ensureMethodNode(method, relativePath, result, parentId),
1538
2654
  resolveClassNodeId: (cls) => this.resolveClassNodeId(cls),
2655
+ resolveClassIdBySig: (sig, importer) => this.resolveClassIdBySig(sig, importer),
2656
+ resolveMethodIdBySig: (sig, importer) => this.resolveMethodIdBySig(sig, importer),
2657
+ resolveFieldNodeId: (field) => this.resolveFieldNodeId(field),
1539
2658
  markFieldStateDecorators: (field, fieldNodeId, result) => {
1540
2659
  const kinds = stateDecoratorKinds(field);
1541
2660
  if (kinds.length === 0)
@@ -1547,7 +2666,170 @@ class ArkTSAdapter {
1547
2666
  };
1548
2667
  }
1549
2668
  resolveClassNodeId(cls) {
1550
- return this.componentToId.get(cls) ?? this.classToId.get(cls) ?? null;
2669
+ const live = this.componentToId.get(cls) ?? this.classToId.get(cls);
2670
+ if (live)
2671
+ return live;
2672
+ try {
2673
+ return this.classSigToId.get(cls.getSignature().toString()) ?? null;
2674
+ }
2675
+ catch {
2676
+ return null;
2677
+ }
2678
+ }
2679
+ rememberClassName(name, nodeId, filePath, isComponent) {
2680
+ if (!isUsableArkTypeName(name))
2681
+ return;
2682
+ let set = this.classNameToIds.get(name);
2683
+ if (!set) {
2684
+ set = new Set();
2685
+ this.classNameToIds.set(name, set);
2686
+ }
2687
+ set.add(nodeId);
2688
+ this.classIdFilePath.set(nodeId, filePath);
2689
+ if (isComponent)
2690
+ this.classIdIsComponent.set(nodeId, true);
2691
+ }
2692
+ rememberMethodName(className, methodName, nodeId, filePath) {
2693
+ const key = `${className}::${methodName}`;
2694
+ let set = this.methodNameKeyToIds.get(key);
2695
+ if (!set) {
2696
+ set = new Set();
2697
+ this.methodNameKeyToIds.set(key, set);
2698
+ }
2699
+ set.add(nodeId);
2700
+ this.methodIdFilePath.set(nodeId, filePath);
2701
+ }
2702
+ rememberFunctionName(functionName, nodeId, filePath) {
2703
+ if (!isUsableArkTypeName(functionName) || !/^[a-z]/.test(functionName))
2704
+ return;
2705
+ let set = this.functionNameToIds.get(functionName);
2706
+ if (!set) {
2707
+ set = new Set();
2708
+ this.functionNameToIds.set(functionName, set);
2709
+ }
2710
+ set.add(nodeId);
2711
+ this.methodIdFilePath.set(nodeId, filePath);
2712
+ }
2713
+ resolveFunctionIdByName(functionName) {
2714
+ const ids = this.functionNameToIds.get(functionName);
2715
+ if (!ids || ids.size !== 1)
2716
+ return null;
2717
+ return [...ids][0] ?? null;
2718
+ }
2719
+ resolveClassIdByName(className, importer) {
2720
+ const ids = this.classNameToIds.get(className);
2721
+ if (!ids || ids.size === 0)
2722
+ return null;
2723
+ let candidates = [...ids];
2724
+ // Same-file components (ForEach item builders, local @Component) have no import —
2725
+ // prefer ids from the importer's file before treating the name as ambiguous.
2726
+ if (importer) {
2727
+ try {
2728
+ const importerPath = normalizeRelPath(this.rootDir, importer.getDeclaringArkFile().getFilePath());
2729
+ const sameFile = candidates.filter((id) => this.classIdFilePath.get(id) === importerPath);
2730
+ if (sameFile.length > 0)
2731
+ candidates = sameFile;
2732
+ }
2733
+ catch {
2734
+ // keep full candidate set
2735
+ }
2736
+ }
2737
+ const preferred = pickPreferredClassNodeId(candidates, (id) => !!this.classIdIsComponent.get(id));
2738
+ if (preferred)
2739
+ return preferred;
2740
+ if (!importer)
2741
+ return null;
2742
+ try {
2743
+ const importInfo = importer.getDeclaringArkFile().getImportInfoBy(className);
2744
+ if (!importInfo)
2745
+ return null;
2746
+ const exportInfo = importInfo.getLazyExportInfo?.() ?? null;
2747
+ const arkExport = exportInfo?.getArkExport?.() ?? null;
2748
+ if (arkExport instanceof arkanalyzer_1.ArkClass) {
2749
+ const byLive = this.resolveClassNodeId(arkExport);
2750
+ if (byLive)
2751
+ return byLive;
2752
+ }
2753
+ const from = importInfo.getFrom?.();
2754
+ if (!from)
2755
+ return null;
2756
+ const fromBase = path.basename(from).replace(/\.(ets|ts|d\.ts)$/i, '');
2757
+ const byPath = [...ids].filter((id) => {
2758
+ const fp = this.classIdFilePath.get(id);
2759
+ if (!fp)
2760
+ return false;
2761
+ const norm = fp.replace(/\\/g, '/');
2762
+ const fromNorm = from.replace(/\\/g, '/');
2763
+ return (norm.includes(fromNorm) ||
2764
+ fromNorm.includes(norm) ||
2765
+ path.basename(norm, path.extname(norm)) === fromBase ||
2766
+ path.basename(norm, path.extname(norm)) === className);
2767
+ });
2768
+ return pickPreferredClassNodeId(byPath, (id) => !!this.classIdIsComponent.get(id));
2769
+ }
2770
+ catch {
2771
+ return null;
2772
+ }
2773
+ }
2774
+ resolveMethodIdByName(className, methodName) {
2775
+ const key = `${className}::${methodName}`;
2776
+ const ids = this.methodNameKeyToIds.get(key);
2777
+ if (!ids || ids.size !== 1)
2778
+ return null;
2779
+ return [...ids][0] ?? null;
2780
+ }
2781
+ resolveClassIdBySig(sig, importer) {
2782
+ try {
2783
+ const exact = this.classSigToId.get(sig.toString());
2784
+ if (exact)
2785
+ return exact;
2786
+ }
2787
+ catch {
2788
+ // fall through to name fallback when possible
2789
+ }
2790
+ // Name fallback for %unk stubs AND clear-but-unmapped sig strings (path drift).
2791
+ // Same-file / import disambiguation happens inside resolveClassIdByName.
2792
+ try {
2793
+ const className = sig.getClassName();
2794
+ return className ? this.resolveClassIdByName(className, importer) : null;
2795
+ }
2796
+ catch {
2797
+ return null;
2798
+ }
2799
+ }
2800
+ resolveMethodIdBySig(sig, _importer) {
2801
+ try {
2802
+ const exact = this.methodSigToId.get(sig.toString());
2803
+ if (exact)
2804
+ return exact;
2805
+ }
2806
+ catch {
2807
+ // fall through
2808
+ }
2809
+ // Name fallback for %unk stubs AND path-drifted but clear signatures so
2810
+ // RTA edges to signature-only / unloaded callees still map to indexed nodes.
2811
+ try {
2812
+ const className = sig.getDeclaringClassSignature().getClassName();
2813
+ const methodName = sig.getMethodSubSignature().getMethodName();
2814
+ if (!className || !methodName)
2815
+ return null;
2816
+ return this.resolveMethodIdByName(className, methodName);
2817
+ }
2818
+ catch {
2819
+ return null;
2820
+ }
2821
+ }
2822
+ resolveFieldNodeId(field) {
2823
+ const live = this.fieldToId.get(field);
2824
+ if (live)
2825
+ return live;
2826
+ try {
2827
+ const classSig = field.getDeclaringArkClass().getSignature().toString();
2828
+ return this.fieldSigToId.get(fieldSigKey(classSig, field.getName())) ?? null;
2829
+ }
2830
+ catch {
2831
+ return null;
2832
+ }
1551
2833
  }
1552
2834
  /** Parent node id for a method: owning class/component, or file for default-class methods. */
1553
2835
  resolveMethodParentId(method, relativePath, result) {
@@ -1570,6 +2852,16 @@ class ArkTSAdapter {
1570
2852
  const existing = this.methodToId.get(method);
1571
2853
  if (existing)
1572
2854
  return existing;
2855
+ try {
2856
+ const bySig = this.methodSigToId.get(method.getSignature().toString());
2857
+ if (bySig) {
2858
+ this.methodToId.set(method, bySig);
2859
+ return bySig;
2860
+ }
2861
+ }
2862
+ catch {
2863
+ // fall through
2864
+ }
1573
2865
  if (shouldSkipArkMethod(method) && !isAnonymousArkMethod(method))
1574
2866
  return null;
1575
2867
  const displayName = arkMethodDisplayName(method);
@@ -1594,6 +2886,10 @@ class ArkTSAdapter {
1594
2886
  return this.methodToId.get(method) ?? null;
1595
2887
  }
1596
2888
  addViewTreeEdge(result, source, target, kind, callerFile, via, line) {
2889
+ const edgeKey = `${source}\0${target}\0${kind}\0${via}`;
2890
+ if (this.emittedEdgeKeys.has(edgeKey))
2891
+ return;
2892
+ this.emittedEdgeKeys.add(edgeKey);
1597
2893
  const edge = arkEdge(source, target, kind, {
1598
2894
  metadata: {
1599
2895
  synthesizedBy: 'viewtree',
@@ -1612,6 +2908,16 @@ class ArkTSAdapter {
1612
2908
  const existing = this.methodToId.get(method);
1613
2909
  if (existing)
1614
2910
  return existing;
2911
+ try {
2912
+ const bySig = this.methodSigToId.get(method.getSignature().toString());
2913
+ if (bySig) {
2914
+ this.methodToId.set(method, bySig);
2915
+ return bySig;
2916
+ }
2917
+ }
2918
+ catch {
2919
+ // fall through
2920
+ }
1615
2921
  if (shouldSkipArkMethod(method) && !isAnonymousArkMethod(method))
1616
2922
  return null;
1617
2923
  const displayName = arkMethodDisplayName(method);
@@ -1638,6 +2944,24 @@ class ArkTSAdapter {
1638
2944
  });
1639
2945
  this.addNode(result, methodNode);
1640
2946
  this.methodToId.set(method, methodNode.id);
2947
+ try {
2948
+ this.methodSigToId.set(method.getSignature().toString(), methodNode.id);
2949
+ }
2950
+ catch {
2951
+ // signature unavailable
2952
+ }
2953
+ try {
2954
+ const ownerName = classDisplayName(cls);
2955
+ if (isUsableArkTypeName(ownerName) && isUsableArkTypeName(displayName)) {
2956
+ this.rememberMethodName(ownerName, displayName, methodNode.id, relativePath);
2957
+ }
2958
+ else if (cls.isDefaultArkClass() && kind === 'function') {
2959
+ this.rememberFunctionName(displayName, methodNode.id, relativePath);
2960
+ }
2961
+ }
2962
+ catch {
2963
+ // class name unavailable
2964
+ }
1641
2965
  result.edges.push(arkEdge(parentId, methodNode.id, 'contains'));
1642
2966
  this.indexDecorators(result, methodNode.id, method, relativePath, language, line);
1643
2967
  this.indexNativeNapiCalls(method, methodNode.id, relativePath, language, result);
@@ -1714,10 +3038,11 @@ function drainArkTSIndexNotices() {
1714
3038
  arktsIndexNotices = [];
1715
3039
  return out;
1716
3040
  }
1717
- function buildSceneConfig(rootDir) {
3041
+ function buildSceneConfig(rootDir, extra) {
1718
3042
  return (0, arkanalyzer_1.buildSceneConfigFromProject)(rootDir, process.env.OHOS_SDK_HOME, {
1719
3043
  supportFileExts: ['.ets', '.ts', '.d.ts'],
1720
3044
  enableMethodBodyBuild: true,
3045
+ ...(extra?.memoryLimitMB !== undefined ? { memoryLimitMB: extra.memoryLimitMB } : {}),
1721
3046
  });
1722
3047
  }
1723
3048
  function tryBuildArkScene(rootDir) {
@@ -1742,22 +3067,148 @@ function emptyArkTSBatchIndex(errors) {
1742
3067
  errors,
1743
3068
  };
1744
3069
  }
1745
- function buildArkTSIndex(rootDir, scannedFiles) {
3070
+ /**
3071
+ * Harmony multi-module path: load each PROJECT module to BODIES, index into
3072
+ * HomeGraph structures in the callback, let ArkAnalyzer evict under memoryLimitMB.
3073
+ * When `targetModuleSrcPaths` is set, only those modules are BODIES targets
3074
+ * (deps still load at SIGNATURES) — used for incremental sync.
3075
+ */
3076
+ function buildArkTSIndexByModule(rootDir, scannedFiles, options) {
3077
+ const errors = [];
3078
+ const memoryLimitMB = resolveArkTSSceneMemoryLimitMB();
3079
+ const sceneConfig = buildSceneConfig(rootDir, { memoryLimitMB });
3080
+ const scene = new arkanalyzer_1.Scene();
3081
+ const adapter = new ArkTSAdapter(rootDir, scannedFiles);
3082
+ let modulesIndexed = 0;
3083
+ const targetSrc = options?.targetModuleSrcPaths?.map(normalizeHarmonyModuleSrcPath);
3084
+ const targetSrcSet = targetSrc && targetSrc.length > 0 ? new Set(targetSrc) : null;
3085
+ try {
3086
+ scene.config(sceneConfig);
3087
+ const config = new arkanalyzer_1.ModuleAnalysisConfig();
3088
+ config.setLoadLevel(arkanalyzer_1.ModuleDepthLevel.BODIES);
3089
+ // SIGNATURES is enough for ViewTree stubs / types; BODIES on deps caused
3090
+ // each module's RTA to re-walk shared library CFGs (multi-hour indexes).
3091
+ config.setDependencyLoadLevel(arkanalyzer_1.ModuleDepthLevel.SIGNATURES);
3092
+ if (targetSrcSet) {
3093
+ // Prep + SDK only (no PROJECT targets) so we can resolve ModuleIDs.
3094
+ scene.analyseByModule(() => { }, new arkanalyzer_1.ModuleAnalysisConfig());
3095
+ const wantAbs = new Set([...targetSrcSet].map((src) => {
3096
+ const resolved = path.resolve(rootDir, src);
3097
+ try {
3098
+ return fs.realpathSync(resolved).toLowerCase();
3099
+ }
3100
+ catch {
3101
+ return resolved.toLowerCase();
3102
+ }
3103
+ }));
3104
+ const ids = [];
3105
+ for (const mod of scene.getModules()) {
3106
+ if (mod.getModuleType() !== arkanalyzer_1.ModuleType.PROJECT)
3107
+ continue;
3108
+ let modAbs;
3109
+ try {
3110
+ modAbs = fs.realpathSync(mod.getModulePath());
3111
+ }
3112
+ catch {
3113
+ modAbs = path.resolve(mod.getModulePath());
3114
+ }
3115
+ if (wantAbs.has(modAbs.toLowerCase())) {
3116
+ ids.push(scene.getModuleId(mod));
3117
+ }
3118
+ }
3119
+ if (ids.length === 0) {
3120
+ errors.push({
3121
+ message: `ArkTS incremental: no modules matched [${[...targetSrcSet].join(', ')}]`,
3122
+ severity: 'warning',
3123
+ });
3124
+ return emptyArkTSBatchIndex(errors);
3125
+ }
3126
+ config.setTargetModuleIds(ids);
3127
+ process.stderr.write(`\n\x1b[33m ArkTS: analyseByModule incremental (${ids.length} module(s)→BODIES, deps→SIGNATURES, memoryLimitMB=${memoryLimitMB})...\x1b[0m\n`);
3128
+ }
3129
+ else {
3130
+ config.setIncludeType(arkanalyzer_1.ModuleType.PROJECT, true);
3131
+ process.stderr.write(`\n\x1b[33m ArkTS: analyseByModule (PROJECT→BODIES, deps→SIGNATURES, memoryLimitMB=${memoryLimitMB}, maxOldSpace=${(0, wasm_runtime_flags_1.resolveMaxOldSpaceSizeMb)()})...\x1b[0m\n`);
3132
+ }
3133
+ scene.analyseByModule((module, scn) => {
3134
+ adapter.indexModule(module, scn);
3135
+ modulesIndexed++;
3136
+ const name = module.getModuleName() || path.basename(module.getModulePath());
3137
+ process.stderr.write(`\x1b[33m ArkTS: indexed module ${modulesIndexed}: ${name}\x1b[0m\n`);
3138
+ }, config);
3139
+ if (modulesIndexed === 0) {
3140
+ errors.push({
3141
+ message: 'ArkTS analyseByModule found no PROJECT modules (check build-profile.json5); falling back to full Scene build',
3142
+ severity: 'warning',
3143
+ });
3144
+ return emptyArkTSBatchIndex(errors);
3145
+ }
3146
+ try {
3147
+ adapter.finalizeCallGraph(scene);
3148
+ }
3149
+ catch (e) {
3150
+ const message = e instanceof Error ? e.message : String(e);
3151
+ errors.push({
3152
+ message: `ArkTS modular leftover RTA skipped: ${message}`,
3153
+ severity: 'warning',
3154
+ });
3155
+ }
3156
+ const index = adapter.toBatchIndex(errors);
3157
+ if (index.fileResults.size === 0) {
3158
+ errors.push({
3159
+ message: 'ArkTS analyseByModule indexed modules but no scanned source files matched',
3160
+ severity: 'warning',
3161
+ });
3162
+ return emptyArkTSBatchIndex(errors);
3163
+ }
3164
+ return index;
3165
+ }
3166
+ catch (e) {
3167
+ const message = e instanceof Error ? e.message : String(e);
3168
+ errors.push({
3169
+ message: `ArkTS analyseByModule failed: ${message}; falling back to full Scene build`,
3170
+ severity: 'warning',
3171
+ });
3172
+ return emptyArkTSBatchIndex(errors);
3173
+ }
3174
+ finally {
3175
+ try {
3176
+ scene.dispose();
3177
+ }
3178
+ catch {
3179
+ // ignore dispose failures
3180
+ }
3181
+ }
3182
+ }
3183
+ function buildArkTSIndexFull(rootDir, scannedFiles, priorErrors = []) {
1746
3184
  const { scene, errors } = tryBuildArkScene(rootDir);
3185
+ const allErrors = [...priorErrors, ...errors];
1747
3186
  if (!scene) {
1748
- return emptyArkTSBatchIndex(errors);
3187
+ return emptyArkTSBatchIndex(allErrors);
1749
3188
  }
1750
3189
  try {
1751
3190
  const adapter = new ArkTSAdapter(rootDir, scannedFiles);
1752
3191
  const index = adapter.build(scene);
1753
- index.errors.push(...errors);
3192
+ index.errors.push(...allErrors);
1754
3193
  return index;
1755
3194
  }
1756
3195
  catch (e) {
1757
3196
  const message = e instanceof Error ? e.message : String(e);
1758
- errors.push({ message: `ArkTS adapter failed: ${message}`, severity: 'error' });
1759
- return emptyArkTSBatchIndex(errors);
3197
+ allErrors.push({ message: `ArkTS adapter failed: ${message}`, severity: 'error' });
3198
+ return emptyArkTSBatchIndex(allErrors);
3199
+ }
3200
+ }
3201
+ function buildArkTSIndex(rootDir, scannedFiles) {
3202
+ if (shouldUseModularArkTSBuild(rootDir)) {
3203
+ const modular = buildArkTSIndexByModule(rootDir, scannedFiles);
3204
+ const fatal = modular.errors.find((e) => e.severity === 'error');
3205
+ if (modular.fileResults.size > 0 && !fatal) {
3206
+ return modular;
3207
+ }
3208
+ // Warnings like "no PROJECT modules" / modular failure → full Scene fallback.
3209
+ return buildArkTSIndexFull(rootDir, scannedFiles, modular.errors);
1760
3210
  }
3211
+ return buildArkTSIndexFull(rootDir, scannedFiles);
1761
3212
  }
1762
3213
  // =============================================================================
1763
3214
  // OHOS SDK input resolution + prebuilt API database indexing