homegraph 1.5.1 → 1.5.2

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 (258) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +305 -305
  3. package/dist/bin/homegraph.js +9 -9
  4. package/dist/db/index.js +36 -36
  5. package/dist/db/migrations.js +37 -37
  6. package/dist/db/queries.js +156 -156
  7. package/dist/db/schema.sql +203 -203
  8. package/dist/directory.js +5 -5
  9. package/dist/extraction/index.d.ts +1 -1
  10. package/dist/extraction/index.d.ts.map +1 -1
  11. package/dist/extraction/index.js +54 -43
  12. package/dist/extraction/index.js.map +1 -1
  13. package/dist/extraction/languages/arkts.d.ts +53 -6
  14. package/dist/extraction/languages/arkts.d.ts.map +1 -1
  15. package/dist/extraction/languages/arkts.js +565 -65
  16. package/dist/extraction/languages/arkts.js.map +1 -1
  17. package/dist/extraction/wasm/tree-sitter-c_sharp.wasm +0 -0
  18. package/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
  19. package/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
  20. package/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
  21. package/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
  22. package/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
  23. package/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
  24. package/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
  25. package/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +3 -2
  28. package/dist/index.js.map +1 -1
  29. package/dist/installer/instructions-template.js +9 -9
  30. package/dist/mcp/diff-impact.d.ts +131 -0
  31. package/dist/mcp/diff-impact.d.ts.map +1 -0
  32. package/dist/mcp/diff-impact.js +385 -0
  33. package/dist/mcp/diff-impact.js.map +1 -0
  34. package/dist/mcp/liveness-watchdog.js +51 -51
  35. package/dist/mcp/server-instructions.d.ts +1 -1
  36. package/dist/mcp/server-instructions.d.ts.map +1 -1
  37. package/dist/mcp/server-instructions.js +49 -47
  38. package/dist/mcp/server-instructions.js.map +1 -1
  39. package/dist/mcp/tools.d.ts +21 -5
  40. package/dist/mcp/tools.d.ts.map +1 -1
  41. package/dist/mcp/tools.js +305 -55
  42. package/dist/mcp/tools.js.map +1 -1
  43. package/dist/resolution/callback-synthesizer.d.ts +2 -1
  44. package/dist/resolution/callback-synthesizer.d.ts.map +1 -1
  45. package/dist/resolution/callback-synthesizer.js +153 -53
  46. package/dist/resolution/callback-synthesizer.js.map +1 -1
  47. package/dist/resolution/frameworks/arkts-entry.d.ts.map +1 -1
  48. package/dist/resolution/frameworks/arkts-entry.js +20 -9
  49. package/dist/resolution/frameworks/arkts-entry.js.map +1 -1
  50. package/dist/resolution/index.d.ts.map +1 -1
  51. package/dist/resolution/index.js +1 -1
  52. package/dist/resolution/index.js.map +1 -1
  53. package/dist/resolution/memory-budget.d.ts +33 -0
  54. package/dist/resolution/memory-budget.d.ts.map +1 -1
  55. package/dist/resolution/memory-budget.js +58 -0
  56. package/dist/resolution/memory-budget.js.map +1 -1
  57. package/dist/resolution/resolver-pool.d.ts +2 -0
  58. package/dist/resolution/resolver-pool.d.ts.map +1 -1
  59. package/dist/resolution/resolver-pool.js +4 -0
  60. package/dist/resolution/resolver-pool.js.map +1 -1
  61. package/dist/spec/db/commit-node.js +4 -4
  62. package/dist/spec/db/fragment-node.js +10 -10
  63. package/dist/spec/db/fts.js +8 -8
  64. package/dist/spec/db/relations.js +88 -88
  65. package/dist/spec/db/schema.js +6 -6
  66. package/dist/spec/db/schema.sql +121 -121
  67. package/dist/spec/db/spec-node.js +4 -4
  68. package/dist/spec/llm/prompts.js +53 -53
  69. package/dist/ui/shimmer-progress.d.ts.map +1 -1
  70. package/dist/ui/shimmer-progress.js +4 -1
  71. package/dist/ui/shimmer-progress.js.map +1 -1
  72. package/package.json +61 -62
  73. package/dist/extraction/languages/arkts-viewtree.d.ts +0 -22
  74. package/dist/extraction/languages/arkts-viewtree.d.ts.map +0 -1
  75. package/dist/extraction/languages/arkts-viewtree.js +0 -133
  76. package/dist/extraction/languages/arkts-viewtree.js.map +0 -1
  77. package/dist/reasoning/config.d.ts +0 -45
  78. package/dist/reasoning/config.d.ts.map +0 -1
  79. package/dist/reasoning/config.js +0 -171
  80. package/dist/reasoning/config.js.map +0 -1
  81. package/dist/reasoning/credentials.d.ts +0 -5
  82. package/dist/reasoning/credentials.d.ts.map +0 -1
  83. package/dist/reasoning/credentials.js +0 -83
  84. package/dist/reasoning/credentials.js.map +0 -1
  85. package/dist/reasoning/login.d.ts +0 -21
  86. package/dist/reasoning/login.d.ts.map +0 -1
  87. package/dist/reasoning/login.js +0 -85
  88. package/dist/reasoning/login.js.map +0 -1
  89. package/dist/reasoning/reasoner.d.ts +0 -43
  90. package/dist/reasoning/reasoner.d.ts.map +0 -1
  91. package/dist/reasoning/reasoner.js +0 -308
  92. package/dist/reasoning/reasoner.js.map +0 -1
  93. package/dist/spec/evolve/llm-client.d.ts +0 -50
  94. package/dist/spec/evolve/llm-client.d.ts.map +0 -1
  95. package/dist/spec/evolve/llm-client.js +0 -176
  96. package/dist/spec/evolve/llm-client.js.map +0 -1
  97. package/dist/spec/evolve/logic-checker.d.ts +0 -12
  98. package/dist/spec/evolve/logic-checker.d.ts.map +0 -1
  99. package/dist/spec/evolve/logic-checker.js +0 -24
  100. package/dist/spec/evolve/logic-checker.js.map +0 -1
  101. package/dist/spec/llm/index.d.ts +0 -3
  102. package/dist/spec/llm/index.d.ts.map +0 -1
  103. package/dist/spec/llm/index.js +0 -11
  104. package/dist/spec/llm/index.js.map +0 -1
  105. package/dist/spec/mining/diff-parser.d.ts +0 -33
  106. package/dist/spec/mining/diff-parser.d.ts.map +0 -1
  107. package/dist/spec/mining/diff-parser.js +0 -166
  108. package/dist/spec/mining/diff-parser.js.map +0 -1
  109. package/dist/spec/mining/git-scanner.d.ts +0 -103
  110. package/dist/spec/mining/git-scanner.d.ts.map +0 -1
  111. package/dist/spec/mining/git-scanner.js +0 -307
  112. package/dist/spec/mining/git-scanner.js.map +0 -1
  113. package/dist/spec/mining/pipeline.d.ts +0 -53
  114. package/dist/spec/mining/pipeline.d.ts.map +0 -1
  115. package/dist/spec/mining/pipeline.js +0 -178
  116. package/dist/spec/mining/pipeline.js.map +0 -1
  117. package/dist/spec/mining/scope-resolver.d.ts +0 -45
  118. package/dist/spec/mining/scope-resolver.d.ts.map +0 -1
  119. package/dist/spec/mining/scope-resolver.js +0 -103
  120. package/dist/spec/mining/scope-resolver.js.map +0 -1
  121. package/dist/spec/mining/spec-extractor.d.ts +0 -69
  122. package/dist/spec/mining/spec-extractor.d.ts.map +0 -1
  123. package/dist/spec/mining/spec-extractor.js +0 -369
  124. package/dist/spec/mining/spec-extractor.js.map +0 -1
  125. package/dist/spec/utils.d.ts +0 -167
  126. package/dist/spec/utils.d.ts.map +0 -1
  127. package/dist/spec/utils.js +0 -463
  128. package/dist/spec/utils.js.map +0 -1
  129. package/scripts/_tmp-cfwk-resolve.log +0 -0
  130. package/scripts/_tmp-cfwk-sig.log +0 -0
  131. package/scripts/_tmp-cfwk-vt.log +0 -0
  132. package/scripts/add-lang/bench.sh +0 -60
  133. package/scripts/add-lang/check-grammar.mjs +0 -75
  134. package/scripts/add-lang/dump-ast.mjs +0 -103
  135. package/scripts/add-lang/verify-extraction.mjs +0 -70
  136. package/scripts/agent-eval/ab-adoption.sh +0 -91
  137. package/scripts/agent-eval/ab-hook.sh +0 -86
  138. package/scripts/agent-eval/ab-impl.sh +0 -78
  139. package/scripts/agent-eval/ab-new-vs-baseline.sh +0 -102
  140. package/scripts/agent-eval/ab-sufficiency.sh +0 -78
  141. package/scripts/agent-eval/arms-F.sh +0 -21
  142. package/scripts/agent-eval/arms-matrix.sh +0 -37
  143. package/scripts/agent-eval/audit.sh +0 -68
  144. package/scripts/agent-eval/bench-readme.sh +0 -28
  145. package/scripts/agent-eval/bench-why-repo.sh +0 -22
  146. package/scripts/agent-eval/block-read-hook.sh +0 -19
  147. package/scripts/agent-eval/hook-settings.json +0 -15
  148. package/scripts/agent-eval/itrun.sh +0 -120
  149. package/scripts/agent-eval/offload-eval-3arm.sh +0 -72
  150. package/scripts/agent-eval/offload-eval-cost.mjs +0 -133
  151. package/scripts/agent-eval/offload-eval-effort.mjs +0 -108
  152. package/scripts/agent-eval/offload-eval-frontload-matrix.sh +0 -25
  153. package/scripts/agent-eval/offload-eval-frontload.sh +0 -47
  154. package/scripts/agent-eval/offload-eval-ground-truth.json +0 -18
  155. package/scripts/agent-eval/offload-eval-hook.mjs +0 -84
  156. package/scripts/agent-eval/offload-eval-judge.mjs +0 -103
  157. package/scripts/agent-eval/offload-eval-matrix.sh +0 -20
  158. package/scripts/agent-eval/offload-eval-metrics.mjs +0 -94
  159. package/scripts/agent-eval/offload-eval-refs1.sh +0 -50
  160. package/scripts/agent-eval/offload-eval-setup.sh +0 -24
  161. package/scripts/agent-eval/offload-eval-styles.sh +0 -72
  162. package/scripts/agent-eval/offload-eval-summarize.mjs +0 -68
  163. package/scripts/agent-eval/offload-eval.md +0 -76
  164. package/scripts/agent-eval/parse-arms.mjs +0 -116
  165. package/scripts/agent-eval/parse-bench-readme.mjs +0 -84
  166. package/scripts/agent-eval/parse-run.mjs +0 -45
  167. package/scripts/agent-eval/parse-session.mjs +0 -93
  168. package/scripts/agent-eval/probe-context.mjs +0 -21
  169. package/scripts/agent-eval/probe-explore.mjs +0 -40
  170. package/scripts/agent-eval/probe-node.mjs +0 -20
  171. package/scripts/agent-eval/probe-sweep.mjs +0 -119
  172. package/scripts/agent-eval/probe-trace.mjs +0 -20
  173. package/scripts/agent-eval/redirect-read-hook.sh +0 -38
  174. package/scripts/agent-eval/repro-concurrent-explore.mjs +0 -119
  175. package/scripts/agent-eval/repro-daemon-clients.mjs +0 -125
  176. package/scripts/agent-eval/run-agent.sh +0 -34
  177. package/scripts/agent-eval/run-all.sh +0 -75
  178. package/scripts/agent-eval/run-arms.sh +0 -56
  179. package/scripts/agent-eval/seq-matrix.mjs +0 -137
  180. package/scripts/bench-arkts-init-rss.log +0 -0
  181. package/scripts/build-bundle.sh +0 -123
  182. package/scripts/exp_boundary_eval/README.md +0 -247
  183. package/scripts/exp_boundary_eval/__pycache__/_utils.cpython-310.pyc +0 -0
  184. package/scripts/exp_boundary_eval/__pycache__/_utils.cpython-38.pyc +0 -0
  185. package/scripts/exp_boundary_eval/__pycache__/analyze.cpython-310.pyc +0 -0
  186. package/scripts/exp_boundary_eval/__pycache__/analyze.cpython-38.pyc +0 -0
  187. package/scripts/exp_boundary_eval/__pycache__/deveco_arm.cpython-38.pyc +0 -0
  188. package/scripts/exp_boundary_eval/__pycache__/run_all.cpython-310.pyc +0 -0
  189. package/scripts/exp_boundary_eval/__pycache__/run_all.cpython-38.pyc +0 -0
  190. package/scripts/exp_boundary_eval/__pycache__/run_one.cpython-310.pyc +0 -0
  191. package/scripts/exp_boundary_eval/__pycache__/run_one.cpython-38.pyc +0 -0
  192. package/scripts/exp_boundary_eval/__pycache__/run_session.cpython-310.pyc +0 -0
  193. package/scripts/exp_boundary_eval/__pycache__/run_session.cpython-38.pyc +0 -0
  194. package/scripts/exp_boundary_eval/__pycache__/setup.cpython-310.pyc +0 -0
  195. package/scripts/exp_boundary_eval/__pycache__/setup.cpython-38.pyc +0 -0
  196. package/scripts/exp_boundary_eval/__pycache__/win_mcp_launcher.cpython-38.pyc +0 -0
  197. package/scripts/exp_boundary_eval/_test_mcp_chain.py +0 -78
  198. package/scripts/exp_boundary_eval/_test_stdin.py +0 -8
  199. package/scripts/exp_boundary_eval/_utils.py +0 -1116
  200. package/scripts/exp_boundary_eval/analyze.py +0 -1313
  201. package/scripts/exp_boundary_eval/deveco_arm.py +0 -519
  202. package/scripts/exp_boundary_eval/run_all.py +0 -378
  203. package/scripts/exp_boundary_eval/run_one.py +0 -165
  204. package/scripts/exp_boundary_eval/run_session.py +0 -158
  205. package/scripts/exp_boundary_eval/setup.py +0 -120
  206. package/scripts/exp_boundary_eval/win_mcp_launcher.py +0 -73
  207. package/scripts/exp_boundary_eval/win_mcp_stdio_wrap.js +0 -36
  208. package/scripts/exp_boundary_eval/win_node_launcher.py +0 -24
  209. package/scripts/extract-release-notes.mjs +0 -130
  210. package/scripts/local-install.sh +0 -41
  211. package/scripts/npm-sdk.js +0 -75
  212. package/scripts/npm-shim.js +0 -275
  213. package/scripts/ohos-sdk-publish.mjs +0 -133
  214. package/scripts/pack-npm.sh +0 -119
  215. package/scripts/prepare-release.mjs +0 -270
  216. package/scripts/probe-arkts-mem-why-run.log +0 -0
  217. package/scripts/probe-banner-livecard-ir.log +0 -0
  218. package/scripts/probe-cfwk-ast.stderr.log +0 -0
  219. package/scripts/probe-cfwk-ast.stdout.log +0 -0
  220. package/scripts/probe-cfwk-attr-shape.log +0 -0
  221. package/scripts/probe-cfwk-cfgdump.stderr.log +0 -0
  222. package/scripts/probe-cfwk-cfgdump.stdout.log +0 -0
  223. package/scripts/probe-cfwk-cvc.stderr.log +0 -0
  224. package/scripts/probe-cfwk-cvc.stdout.log +0 -0
  225. package/scripts/probe-cfwk-diag2.log +0 -0
  226. package/scripts/probe-cfwk-diag3.log +0 -0
  227. package/scripts/probe-cfwk-fedbg.stderr.log +0 -0
  228. package/scripts/probe-cfwk-fedbg.stdout.log +0 -0
  229. package/scripts/probe-cfwk-fileresult.log +0 -0
  230. package/scripts/probe-cfwk-fix.stderr.log +0 -0
  231. package/scripts/probe-cfwk-fix.stdout.log +0 -0
  232. package/scripts/probe-cfwk-fix2.stderr.log +0 -0
  233. package/scripts/probe-cfwk-fix2.stdout.log +0 -0
  234. package/scripts/probe-cfwk-fix3.stderr.log +0 -0
  235. package/scripts/probe-cfwk-fix3.stdout.log +0 -0
  236. package/scripts/probe-cfwk-foreach.log +0 -0
  237. package/scripts/probe-cfwk-getmethod-throw.log +0 -0
  238. package/scripts/probe-cfwk-hg-extract.log +0 -0
  239. package/scripts/probe-cfwk-pr1003-noprior.stderr.log +0 -0
  240. package/scripts/probe-cfwk-pr1003-noprior.stdout.log +0 -0
  241. package/scripts/probe-cfwk-pr1003.stderr.log +0 -0
  242. package/scripts/probe-cfwk-pr1003.stdout.log +0 -0
  243. package/scripts/probe-cfwk-preroot-noprior.stderr.log +0 -0
  244. package/scripts/probe-cfwk-preroot-noprior.stdout.log +0 -0
  245. package/scripts/probe-cfwk-preroot-prior.stderr.log +0 -0
  246. package/scripts/probe-cfwk-preroot-prior.stdout.log +0 -0
  247. package/scripts/probe-cfwk-preroot-skipstate.stderr.log +0 -0
  248. package/scripts/probe-cfwk-preroot-skipstate.stdout.log +0 -0
  249. package/scripts/probe-cfwk-resolve-sim.log +0 -0
  250. package/scripts/probe-cfwk-tree-shape.log +0 -0
  251. package/scripts/probe-cfwk-walk-abort.log +0 -0
  252. package/scripts/probe-cfwk.log +0 -0
  253. package/scripts/probe-force-index.log +0 -0
  254. package/scripts/probe-no-force-index.log +0 -0
  255. package/scripts/probe-samefile-ir.stderr.log +0 -0
  256. package/scripts/probe-samefile-ir.stdout.log +0 -224
  257. package/scripts/probe-sdk-vs-project.log +0 -0
  258. package/scripts/probe-viewtree-downgrade.log +0 -0
@@ -75,7 +75,9 @@ exports.scanEtsFiles = scanEtsFiles;
75
75
  exports.runIsolatedArkTSBatchEntry = runIsolatedArkTSBatchEntry;
76
76
  exports.primeArkTSBatch = primeArkTSBatch;
77
77
  exports.isArkTSBatchPersisted = isArkTSBatchPersisted;
78
+ exports.isArkTSBatchCommitted = isArkTSBatchCommitted;
78
79
  exports.resetArkTSBatch = resetArkTSBatch;
80
+ exports.shrinkArkTSBatchPostParse = shrinkArkTSBatchPostParse;
79
81
  exports.collectExportedRtaEntryPointsFromFiles = collectExportedRtaEntryPointsFromFiles;
80
82
  exports.stateDecoratorKinds = stateDecoratorKinds;
81
83
  exports.stateTransferViaForField = stateTransferViaForField;
@@ -99,7 +101,12 @@ exports.parseJson5Minimal = parseJson5Minimal;
99
101
  exports.normalizeOhosApiVersion = normalizeOhosApiVersion;
100
102
  exports.detectOhosCompileSdkVersion = detectOhosCompileSdkVersion;
101
103
  exports.isOhosArktsProject = isOhosArktsProject;
104
+ exports.readOhosSdkPlatformVersion = readOhosSdkPlatformVersion;
105
+ exports.discoverLocalOhosSdkCandidates = discoverLocalOhosSdkCandidates;
106
+ exports.findLocalOhosSdkForVersion = findLocalOhosSdkForVersion;
107
+ exports.resolveOhosApiVersionForProject = resolveOhosApiVersionForProject;
102
108
  exports.ensureOhosApiDb = ensureOhosApiDb;
109
+ exports.attachExistingOhosApiDbForProject = attachExistingOhosApiDbForProject;
103
110
  exports.bindOhosApiDbForProject = bindOhosApiDbForProject;
104
111
  exports.restoreOhosApiDbAttach = restoreOhosApiDbAttach;
105
112
  const crypto = __importStar(require("crypto"));
@@ -647,6 +654,27 @@ function persistIncrementalModuleResults(rootDir, queries, index) {
647
654
  persistFileResult(queries, filePath, content, stats, fileResult, { force: true });
648
655
  }
649
656
  }
657
+ /**
658
+ * Persist a module slice then drop it from the in-memory map (streaming modular build).
659
+ * Returns the number of files written.
660
+ */
661
+ function persistAndDropModuleSlice(rootDir, queries, slice, streamedPersistedPaths, persistOrdinal) {
662
+ if (slice.size === 0)
663
+ return 0;
664
+ for (const [filePath, fileResult] of slice) {
665
+ persistOrdinal.done++;
666
+ reportBatchProgress('persist', Math.min(persistOrdinal.done, persistOrdinal.totalHint), persistOrdinal.totalHint, filePath);
667
+ const isVirtual = filePath.startsWith('@');
668
+ const full = path.join(rootDir, filePath);
669
+ const content = isVirtual ? '' : fs.readFileSync(full, 'utf-8');
670
+ const stats = isVirtual
671
+ ? { size: 0, mtimeMs: Date.now() }
672
+ : fs.statSync(full);
673
+ persistFileResult(queries, filePath, content, stats, fileResult, { force: true });
674
+ streamedPersistedPaths.add(normIndexPath(filePath));
675
+ }
676
+ return slice.size;
677
+ }
650
678
  async function persistBatchResultsAsync(rootDir, queries, index) {
651
679
  const entries = [...index.fileResults.entries()];
652
680
  const persistTotal = entries.length;
@@ -694,18 +722,89 @@ function persistBatchResultsSync(rootDir, queries, index) {
694
722
  }
695
723
  }
696
724
  function commitArkTSBatch(rootDir, batchKey, etsFiles, index, triggerFile) {
697
- batchPersistedPaths = new Set([...index.fileResults.keys()].map(normIndexPath));
725
+ const streamed = index.streamedPersistedPaths;
726
+ batchPersistedPaths =
727
+ streamed && streamed.size > 0
728
+ ? new Set([...streamed].map(normIndexPath))
729
+ : new Set([...index.fileResults.keys()].map(normIndexPath));
698
730
  persistedBatch = { ...index, rootDir, batchKey, etsFiles };
699
731
  batchTriggerFile = normIndexPath(triggerFile);
700
- if (index.fileResults.size > 0) {
732
+ if (batchPersistedPaths.size > 0 || index.fileResults.size > 0) {
701
733
  batchBuildCommitted = true;
702
734
  }
703
735
  return persistedBatch;
704
736
  }
737
+ /**
738
+ * Drop in-memory ExtractionResults / edges after they are in SQLite.
739
+ * Keep path sets so `isArkTSBatchPersisted` still skips re-parse; matches the
740
+ * isolated-worker commit shape (empty fileResults).
741
+ */
742
+ function releaseArkTSBatchHeavyPayload() {
743
+ if (!persistedBatch)
744
+ return;
745
+ persistedBatch.fileResults.clear();
746
+ persistedBatch.crossFileEdges = [];
747
+ persistedBatch.nodeIds.clear();
748
+ persistedBatch.errors = [];
749
+ }
750
+ /** Best-effort reclaim after Scene / batch teardown (needs `node --expose-gc`). */
751
+ function tryForceGc() {
752
+ const gc = globalThis.gc;
753
+ if (typeof gc === 'function') {
754
+ try {
755
+ gc();
756
+ }
757
+ catch {
758
+ // ignore
759
+ }
760
+ }
761
+ }
762
+ /**
763
+ * Unload every module's IR then dispose Scene globals. Plain `scene.dispose()`
764
+ * only clears SDK helpers / moduleCache — project ArkFiles stay reachable and
765
+ * pin multi-GB RSS into Parsing/Resolving.
766
+ */
767
+ function releaseArkAnalyzerScene(scene) {
768
+ try {
769
+ for (const mod of scene.getModules()) {
770
+ try {
771
+ scene.disposeModule(mod);
772
+ mod.setFileDepGraph(undefined);
773
+ mod.clearFilesMap();
774
+ mod.setLoadState(arkanalyzer_1.ModuleLoadState.NOT_LOADED);
775
+ }
776
+ catch {
777
+ // per-module best-effort
778
+ }
779
+ }
780
+ }
781
+ catch {
782
+ // ignore
783
+ }
784
+ try {
785
+ scene.dispose();
786
+ }
787
+ catch {
788
+ // ignore
789
+ }
790
+ tryForceGc();
791
+ }
792
+ /**
793
+ * ArkAnalyzer 1.0.92 keeps INDEX hollow on {@link ModuleBuilder} as a private
794
+ * method (public eviction paths call it internally). HomeGraph needs the same
795
+ * post-ingest hollow; call through a narrow cast until a public API exists.
796
+ */
797
+ function hollowModuleToIndex(builder, moduleId) {
798
+ builder.downgradeModule(moduleId, arkanalyzer_1.ModuleDepthLevel.INDEX);
799
+ }
705
800
  function tryReturnCachedBatch(rootDir, triggerFile, etsFiles) {
706
801
  const normalizedTrigger = normIndexPath(triggerFile);
802
+ // Only reuse when payload is still in memory. After
803
+ // releaseArkTSBatchHeavyPayload(), fileResults is empty — fall through to
804
+ // rebuild (same as isolated-worker commit) rather than return a hollow cache.
707
805
  if (persistedBatch &&
708
806
  persistedBatch.rootDir === rootDir &&
807
+ persistedBatch.fileResults.size > 0 &&
709
808
  batchPersistedPaths.has(normalizedTrigger)) {
710
809
  return persistedBatch;
711
810
  }
@@ -799,6 +898,8 @@ function runArkTSBatchFullCore(rootDir, queries, triggerFile, etsFiles, batchKey
799
898
  queries.insertEdges(valid);
800
899
  }
801
900
  }
901
+ // Keep payload in memory for ArkTSExtractor.extract() same-batch hits.
902
+ // indexAll uses primeArkTSBatch → releaseArkTSBatchHeavyPayload after persist.
802
903
  return commitArkTSBatch(rootDir, batchKey, etsFiles, index, triggerFile);
803
904
  }
804
905
  finally {
@@ -850,6 +951,27 @@ function runArkTSBatch(rootDir, queries, triggerFile) {
850
951
  if (cached) {
851
952
  return cached;
852
953
  }
954
+ // indexAll releases the in-memory payload after SQLite persist. A later
955
+ // ArkTSExtractor hit must NOT rebuild the entire Scene (looks like a restart).
956
+ if (batchBuildCommitted &&
957
+ persistedBatch &&
958
+ persistedBatch.rootDir === rootDir &&
959
+ batchPersistedPaths.has(normalizedTrigger)) {
960
+ return {
961
+ fileResults: new Map(),
962
+ crossFileEdges: [],
963
+ nodeIds: new Set(),
964
+ errors: [
965
+ {
966
+ message: 'ArkTS batch already persisted this run; in-memory payload released',
967
+ severity: 'warning',
968
+ },
969
+ ],
970
+ rootDir,
971
+ batchKey: persistedBatch.batchKey,
972
+ etsFiles: persistedBatch.etsFiles,
973
+ };
974
+ }
853
975
  const batchKey = etsFiles.length > 0 ? computeBatchKey(rootDir, etsFiles) : '';
854
976
  return runArkTSBatchFull(rootDir, queries, normalizedTrigger, etsFiles, batchKey);
855
977
  }
@@ -892,13 +1014,25 @@ async function primeArkTSBatch(rootDir, queries, triggerFile, options) {
892
1014
  (0, context_1.setArktsBatchRunning)(true);
893
1015
  try {
894
1016
  reportBatchProgress('scene', 0, etsFiles.length);
895
- const index = buildArkTSIndex(rootDir, etsFiles);
896
- if (index.fileResults.size === 0) {
1017
+ const streamModular = shouldUseModularArkTSBuild(rootDir);
1018
+ if (streamModular) {
1019
+ // Clear stale cross-file RTA edges before streaming module persists.
1020
+ queries.deleteArkTSCrossFileCallEdges();
1021
+ }
1022
+ const index = buildArkTSIndex(rootDir, etsFiles, {
1023
+ streamQueries: streamModular ? queries : undefined,
1024
+ });
1025
+ const streamed = index.streamedPersistedPaths?.size ?? 0;
1026
+ if (index.fileResults.size === 0 && streamed === 0) {
897
1027
  const fatal = index.errors.find((e) => e.severity === 'error');
898
1028
  throw new Error(fatal?.message ?? 'ArkTS batch produced no indexed files');
899
1029
  }
900
- queries.deleteArkTSCrossFileCallEdges();
901
- await persistBatchResultsAsync(rootDir, queries, index);
1030
+ if (!streamModular) {
1031
+ queries.deleteArkTSCrossFileCallEdges();
1032
+ }
1033
+ if (index.fileResults.size > 0) {
1034
+ await persistBatchResultsAsync(rootDir, queries, index);
1035
+ }
902
1036
  if (index.crossFileEdges.length > 0) {
903
1037
  const valid = index.crossFileEdges.filter((e) => index.nodeIds.has(e.source) && index.nodeIds.has(e.target));
904
1038
  if (valid.length > 0) {
@@ -906,6 +1040,8 @@ async function primeArkTSBatch(rootDir, queries, triggerFile, options) {
906
1040
  }
907
1041
  }
908
1042
  commitArkTSBatch(rootDir, batchKey, etsFiles, index, normalizedTrigger);
1043
+ releaseArkTSBatchHeavyPayload();
1044
+ tryForceGc();
909
1045
  }
910
1046
  finally {
911
1047
  (0, context_1.setArktsBatchRunning)(false);
@@ -923,8 +1059,13 @@ async function runArkTSBatchIncrementalModules(rootDir, queries, triggerFile, et
923
1059
  (0, context_1.setArktsBatchRunning)(true);
924
1060
  try {
925
1061
  reportBatchProgress('scene', 0, etsFiles.length);
926
- const index = buildArkTSIndexByModule(rootDir, etsFiles, { targetModuleSrcPaths: moduleSrcPaths });
927
- if (index.fileResults.size === 0) {
1062
+ queries.deleteArkTSCrossFileCallEdges();
1063
+ const index = buildArkTSIndexByModule(rootDir, etsFiles, {
1064
+ targetModuleSrcPaths: moduleSrcPaths,
1065
+ streamQueries: queries,
1066
+ });
1067
+ const streamed = index.streamedPersistedPaths?.size ?? 0;
1068
+ if (index.fileResults.size === 0 && streamed === 0) {
928
1069
  const fatal = index.errors.find((e) => e.severity === 'error');
929
1070
  // Fall back to full project batch when the subset produced nothing.
930
1071
  const batchKey = etsFiles.length > 0 ? computeBatchKey(rootDir, etsFiles) : '';
@@ -932,20 +1073,26 @@ async function runArkTSBatchIncrementalModules(rootDir, queries, triggerFile, et
932
1073
  process.stderr.write(`\n\x1b[33m ArkTS: incremental modules failed (${fatal.message}); full rebuild\x1b[0m\n`);
933
1074
  }
934
1075
  const full = buildArkTSIndex(rootDir, etsFiles);
935
- if (full.fileResults.size === 0) {
1076
+ if (full.fileResults.size === 0 && (full.streamedPersistedPaths?.size ?? 0) === 0) {
936
1077
  throw new Error(fatal?.message ?? 'ArkTS incremental batch produced no indexed files');
937
1078
  }
938
1079
  queries.deleteArkTSCrossFileCallEdges();
939
- await persistBatchResultsAsync(rootDir, queries, full);
1080
+ if (full.fileResults.size > 0) {
1081
+ await persistBatchResultsAsync(rootDir, queries, full);
1082
+ }
940
1083
  if (full.crossFileEdges.length > 0) {
941
1084
  const valid = full.crossFileEdges.filter((e) => full.nodeIds.has(e.source) && full.nodeIds.has(e.target));
942
1085
  if (valid.length > 0)
943
1086
  queries.insertEdges(valid);
944
1087
  }
945
1088
  commitArkTSBatch(rootDir, batchKey, etsFiles, full, triggerFile);
1089
+ releaseArkTSBatchHeavyPayload();
1090
+ tryForceGc();
946
1091
  return;
947
1092
  }
948
- persistIncrementalModuleResults(rootDir, queries, index);
1093
+ if (index.fileResults.size > 0) {
1094
+ persistIncrementalModuleResults(rootDir, queries, index);
1095
+ }
949
1096
  if (index.crossFileEdges.length > 0) {
950
1097
  const valid = index.crossFileEdges.filter((e) => index.nodeIds.has(e.source) && index.nodeIds.has(e.target));
951
1098
  if (valid.length > 0) {
@@ -955,6 +1102,8 @@ async function runArkTSBatchIncrementalModules(rootDir, queries, triggerFile, et
955
1102
  // Unique key so a partial index is never mistaken for a full-project cache hit.
956
1103
  const batchKey = `incremental:${moduleSrcPaths.join(',')}`;
957
1104
  commitArkTSBatch(rootDir, batchKey, etsFiles, index, triggerFile);
1105
+ releaseArkTSBatchHeavyPayload();
1106
+ tryForceGc();
958
1107
  process.stderr.write(`\n\x1b[33m ArkTS: incremental module reindex done (${moduleSrcPaths.join(', ')})\x1b[0m\n`);
959
1108
  }
960
1109
  finally {
@@ -965,6 +1114,10 @@ async function runArkTSBatchIncrementalModules(rootDir, queries, triggerFile, et
965
1114
  function isArkTSBatchPersisted(filePath) {
966
1115
  return batchPersistedPaths.has(normIndexPath(filePath));
967
1116
  }
1117
+ /** True after a successful ArkTS batch was committed to SQLite this index run. */
1118
+ function isArkTSBatchCommitted() {
1119
+ return batchBuildCommitted;
1120
+ }
968
1121
  /** Clear cached batch state (tests and full re-index). */
969
1122
  function resetArkTSBatch() {
970
1123
  persistedBatch = null;
@@ -973,6 +1126,16 @@ function resetArkTSBatch() {
973
1126
  batchBuildCommitted = false;
974
1127
  arktsIndexNotices = [];
975
1128
  }
1129
+ /**
1130
+ * After parse workers no longer need skip-path metadata beyond
1131
+ * `batchPersistedPaths`, drop the heavy path-list / batchKey strings.
1132
+ */
1133
+ function shrinkArkTSBatchPostParse() {
1134
+ if (!persistedBatch)
1135
+ return;
1136
+ persistedBatch.etsFiles = [];
1137
+ persistedBatch.batchKey = '';
1138
+ }
976
1139
  class ArkTSExtractor {
977
1140
  filePath;
978
1141
  constructor(filePath, _source) {
@@ -2028,6 +2191,9 @@ class ArkTSAdapter {
2028
2191
  this.stitchModuleCfgCallsAndRefs(files, moduleFiles);
2029
2192
  // Drop live IR object keys so ArkAnalyzer can GC unloaded modules; keep sig→id maps.
2030
2193
  this.releaseLiveIrMaps();
2194
+ // Edges for this module are already in fileResults / crossFileEdges — drop
2195
+ // the string dedupe set so it does not grow across the whole project.
2196
+ this.emittedEdgeKeys.clear();
2031
2197
  }
2032
2198
  /**
2033
2199
  * Index symbols from already-loaded non-target modules into sig/name maps only.
@@ -2466,6 +2632,30 @@ class ArkTSAdapter {
2466
2632
  errors: [...errors],
2467
2633
  };
2468
2634
  }
2635
+ /**
2636
+ * Remove and return ExtractionResults for the given paths so callers can
2637
+ * persist+GC mid-Scene (streaming modular build). Keeps sig→id maps / nodeIds.
2638
+ */
2639
+ takeFileResults(paths) {
2640
+ const out = new Map();
2641
+ for (const raw of paths) {
2642
+ const p = normIndexPath(raw);
2643
+ const result = this.fileResults.get(p);
2644
+ if (!result)
2645
+ continue;
2646
+ out.set(p, result);
2647
+ this.fileResults.delete(p);
2648
+ }
2649
+ return out;
2650
+ }
2651
+ /** Re-insert a previously taken file result (stream persist failure recovery). */
2652
+ putFileResult(filePath, result) {
2653
+ this.fileResults.set(normIndexPath(filePath), result);
2654
+ }
2655
+ /** All paths currently held in fileResults (for final flush). */
2656
+ fileResultPaths() {
2657
+ return [...this.fileResults.keys()];
2658
+ }
2469
2659
  indexScannedFile(arkFile) {
2470
2660
  const relativePath = normalizeRelPath(this.rootDir, arkFile.getFilePath());
2471
2661
  if (!this.scanned.has(relativePath))
@@ -3219,6 +3409,8 @@ function emptyArkTSBatchIndex(errors) {
3219
3409
  * Runs with the #850 liveness watchdog suspended: ArkAnalyzer's native BODIES
3220
3410
  * load between module callbacks routinely exceeds the 60s heartbeat window on
3221
3411
  * large Harmony projects, and there is no JS yield point inside that span.
3412
+ * When `streamQueries` is set, each module's ExtractionResults are written to
3413
+ * SQLite immediately and dropped from RAM (cross-file call edges stay until end).
3222
3414
  */
3223
3415
  function buildArkTSIndexByModule(rootDir, scannedFiles, options) {
3224
3416
  return (0, liveness_watchdog_1.runWithoutLivenessWatchdog)(() => buildArkTSIndexByModuleInner(rootDir, scannedFiles, options));
@@ -3232,6 +3424,13 @@ function buildArkTSIndexByModuleInner(rootDir, scannedFiles, options) {
3232
3424
  let modulesIndexed = 0;
3233
3425
  const targetSrc = options?.targetModuleSrcPaths?.map(normalizeHarmonyModuleSrcPath);
3234
3426
  const targetSrcSet = targetSrc && targetSrc.length > 0 ? new Set(targetSrc) : null;
3427
+ const streamQueries = options?.streamQueries;
3428
+ const streamedPersistedPaths = new Set();
3429
+ const scannedList = [...scannedFiles].map(normIndexPath);
3430
+ const persistOrdinal = {
3431
+ done: 0,
3432
+ totalHint: Math.max(1, scannedList.filter((f) => isArkAnalyzerSourcePath(f)).length),
3433
+ };
3235
3434
  try {
3236
3435
  scene.config(sceneConfig);
3237
3436
  const config = new arkanalyzer_1.ModuleAnalysisConfig();
@@ -3274,18 +3473,83 @@ function buildArkTSIndexByModuleInner(rootDir, scannedFiles, options) {
3274
3473
  return emptyArkTSBatchIndex(errors);
3275
3474
  }
3276
3475
  config.setTargetModuleIds(ids);
3277
- process.stderr.write(`\n\x1b[33m ArkTS: analyseByModule incremental (${ids.length} module(s)→BODIES, deps→SIGNATURES, memoryLimitMB=${memoryLimitMB})...\x1b[0m\n`);
3476
+ process.stderr.write(`\n\x1b[33m ArkTS: analyseByModule incremental (${ids.length} module(s)→BODIES, deps→SIGNATURES, memoryLimitMB=${memoryLimitMB}${streamQueries ? ', streamPersist' : ''})...\x1b[0m\n`);
3278
3477
  }
3279
3478
  else {
3280
3479
  config.setIncludeType(arkanalyzer_1.ModuleType.PROJECT, true);
3281
- process.stderr.write(`\n\x1b[33m ArkTS: analyseByModule (PROJECT→BODIES, deps→SIGNATURES, memoryLimitMB=${memoryLimitMB}, maxOldSpace=${(0, wasm_runtime_flags_1.resolveMaxOldSpaceSizeMb)()})...\x1b[0m\n`);
3480
+ process.stderr.write(`\n\x1b[33m ArkTS: analyseByModule (PROJECT→BODIES, deps→SIGNATURES, memoryLimitMB=${memoryLimitMB}, maxOldSpace=${(0, wasm_runtime_flags_1.resolveMaxOldSpaceSizeMb)()}${streamQueries ? ', streamPersist' : ''})...\x1b[0m\n`);
3282
3481
  }
3283
3482
  scene.analyseByModule((module, scn) => {
3483
+ const moduleFileRels = [...module.getFilesMap().values()]
3484
+ .map((f) => normalizeRelPath(rootDir, f.getFilePath()))
3485
+ .filter((p) => p && isArkAnalyzerSourcePath(p))
3486
+ .map(normIndexPath);
3284
3487
  adapter.indexModule(module, scn);
3488
+ // HomeGraph already captured this module's symbols/edges. Hollow every
3489
+ // currently-loaded module still above INDEX (target + oh_modules /
3490
+ // SIGNATURES deps left in ModuleCache). Topo order means PROJECT deps of
3491
+ // the current target are often already INDEX from their own callback, so
3492
+ // BFS-from-self alone left depHollowed=0; sweeping the cache catches the
3493
+ // fat non-target SIGNATURES that memoryLimitMB rarely evicts under a large
3494
+ // soft budget. INDEX keeps export-reachable shells; loadModule upgrades
3495
+ // when a later target needs SIGNATURES/BODIES.
3496
+ let depHollowed = 0;
3497
+ try {
3498
+ const builder = new arkanalyzer_1.ModuleBuilder(scn);
3499
+ const selfId = scn.getModuleId(module);
3500
+ hollowModuleToIndex(builder, selfId);
3501
+ const cache = scn.getModuleCache();
3502
+ if (cache) {
3503
+ for (const modId of cache.getLoadedModules()) {
3504
+ if (modId === selfId)
3505
+ continue;
3506
+ const depMod = builder.getModule(modId);
3507
+ if (!depMod || depMod.getLoadState() <= arkanalyzer_1.ModuleLoadState.INDEX)
3508
+ continue;
3509
+ hollowModuleToIndex(builder, modId);
3510
+ depHollowed++;
3511
+ }
3512
+ }
3513
+ }
3514
+ catch (e) {
3515
+ const message = e instanceof Error ? e.message : String(e);
3516
+ errors.push({
3517
+ message: `ArkTS post-index INDEX hollow failed for ${module.getModuleName() || module.getModulePath()}: ${message}`,
3518
+ severity: 'warning',
3519
+ });
3520
+ }
3521
+ if (streamQueries) {
3522
+ // Persist this module's file bodies now; keep @dummyFile until finalize.
3523
+ const slicePaths = moduleFileRels.filter((p) => p !== ARKANALYZER_DUMMY_FILE);
3524
+ const slice = adapter.takeFileResults(slicePaths);
3525
+ try {
3526
+ persistAndDropModuleSlice(rootDir, streamQueries, slice, streamedPersistedPaths, persistOrdinal);
3527
+ }
3528
+ catch (e) {
3529
+ const message = e instanceof Error ? e.message : String(e);
3530
+ errors.push({
3531
+ message: `ArkTS stream persist failed for ${module.getModuleName() || module.getModulePath()}: ${message}`,
3532
+ severity: 'error',
3533
+ });
3534
+ throw e;
3535
+ }
3536
+ }
3285
3537
  modulesIndexed++;
3286
3538
  const name = module.getModuleName() || path.basename(module.getModulePath());
3287
- process.stderr.write(`\x1b[33m ArkTS: indexed module ${modulesIndexed}: ${name}\x1b[0m\n`);
3539
+ const st = arkanalyzer_1.ModuleLoadState[module.getLoadState()] ?? String(module.getLoadState());
3540
+ const depNote = depHollowed > 0 ? `, hollowed ${depHollowed} deps` : '';
3541
+ process.stderr.write(`\x1b[33m ArkTS: indexed module ${modulesIndexed}: ${name} → ${st}${depNote}${streamQueries ? ` (streamed ${moduleFileRels.length} files)` : ''}\x1b[0m\n`);
3288
3542
  }, config);
3543
+ if (process.env.HOMEGRAPH_ARKTS_DIAG === '1') {
3544
+ const sceneDiag = scene;
3545
+ const ev = typeof sceneDiag.getModuleEvictionStats === 'function'
3546
+ ? sceneDiag.getModuleEvictionStats()
3547
+ : null;
3548
+ const mu = process.memoryUsage();
3549
+ const cacheN = scene.getModuleCache()?.size?.() ?? scene.getModuleCache()?.getLoadedModules?.()?.length ?? -1;
3550
+ process.stderr.write(`\x1b[36m ArkTS DIAG eviction: ${JSON.stringify(ev)} cacheLoaded=${cacheN} ` +
3551
+ `heapUsedMB=${(mu.heapUsed / 1024 / 1024).toFixed(1)} rssMB=${(mu.rss / 1024 / 1024).toFixed(1)}\x1b[0m\n`);
3552
+ }
3289
3553
  if (modulesIndexed === 0) {
3290
3554
  errors.push({
3291
3555
  message: 'ArkTS analyseByModule found no PROJECT modules (check build-profile.json5); falling back to full Scene build',
@@ -3303,8 +3567,15 @@ function buildArkTSIndexByModuleInner(rootDir, scannedFiles, options) {
3303
3567
  severity: 'warning',
3304
3568
  });
3305
3569
  }
3570
+ if (streamQueries) {
3571
+ const leftover = adapter.takeFileResults(adapter.fileResultPaths());
3572
+ persistAndDropModuleSlice(rootDir, streamQueries, leftover, streamedPersistedPaths, persistOrdinal);
3573
+ }
3306
3574
  const index = adapter.toBatchIndex(errors);
3307
- if (index.fileResults.size === 0) {
3575
+ if (streamQueries) {
3576
+ index.streamedPersistedPaths = streamedPersistedPaths;
3577
+ }
3578
+ if (index.fileResults.size === 0 && streamedPersistedPaths.size === 0) {
3308
3579
  errors.push({
3309
3580
  message: 'ArkTS analyseByModule indexed modules but no scanned source files matched',
3310
3581
  severity: 'warning',
@@ -3323,7 +3594,7 @@ function buildArkTSIndexByModuleInner(rootDir, scannedFiles, options) {
3323
3594
  }
3324
3595
  finally {
3325
3596
  try {
3326
- scene.dispose();
3597
+ releaseArkAnalyzerScene(scene);
3327
3598
  }
3328
3599
  catch {
3329
3600
  // ignore dispose failures
@@ -3349,13 +3620,26 @@ function buildArkTSIndexFull(rootDir, scannedFiles, priorErrors = []) {
3349
3620
  allErrors.push({ message: `ArkTS adapter failed: ${message}`, severity: 'error' });
3350
3621
  return emptyArkTSBatchIndex(allErrors);
3351
3622
  }
3623
+ finally {
3624
+ try {
3625
+ releaseArkAnalyzerScene(scene);
3626
+ }
3627
+ catch {
3628
+ // ignore
3629
+ }
3630
+ }
3352
3631
  });
3353
3632
  }
3354
- function buildArkTSIndex(rootDir, scannedFiles) {
3633
+ function buildArkTSIndex(rootDir, scannedFiles, options) {
3355
3634
  if (shouldUseModularArkTSBuild(rootDir)) {
3356
- const modular = buildArkTSIndexByModule(rootDir, scannedFiles);
3635
+ const modular = buildArkTSIndexByModule(rootDir, scannedFiles, {
3636
+ streamQueries: options?.streamQueries,
3637
+ });
3357
3638
  const fatal = modular.errors.find((e) => e.severity === 'error');
3358
- if (modular.fileResults.size > 0 && !fatal) {
3639
+ const hasOutput = modular.fileResults.size > 0 ||
3640
+ (modular.streamedPersistedPaths?.size ?? 0) > 0 ||
3641
+ modular.nodeIds.size > 0;
3642
+ if (hasOutput && !fatal) {
3359
3643
  return modular;
3360
3644
  }
3361
3645
  // Warnings like "no PROJECT modules" / modular failure → full Scene fallback.
@@ -3520,7 +3804,7 @@ function extractOhosSdkArchive(archivePath) {
3520
3804
  function ohosApiDbFilename(version) {
3521
3805
  return `ohos-api-${version}.db`;
3522
3806
  }
3523
- /** npm package name — one package per API version (e.g. homegraph-ohos-api-db-6.0.1). */
3807
+ /** Metadata / publish id — one package per API version (e.g. homegraph-ohos-api-db-6.0.1). */
3524
3808
  function ohosApiDbPackageName(version) {
3525
3809
  return `homegraph-ohos-api-db-${version}`;
3526
3810
  }
@@ -3668,7 +3952,7 @@ async function indexOhosApiDb(options) {
3668
3952
  }
3669
3953
  }
3670
3954
  // =============================================================================
3671
- // OHOS API db consumer: compileSdkVersion detect, npm fetch, ATTACH for queries
3955
+ // OHOS API db consumer: compileSdkVersion detect, local SDK build, ATTACH
3672
3956
  // =============================================================================
3673
3957
  exports.OHOS_API_FILE_PREFIX = 'ohos-sdk:';
3674
3958
  exports.OHOS_API_VERSION_META = 'ohos_api_version';
@@ -3810,75 +4094,291 @@ function hasEtsUnder(dir, depth) {
3810
4094
  }
3811
4095
  return false;
3812
4096
  }
3813
- /** Install npm package if local db is missing; non-throwing on failure. */
3814
- function ensureOhosApiDb(version) {
3815
- const dbPath = ohosApiDbInstallPath(version);
3816
- if (fs.existsSync(dbPath)) {
3817
- return {
3818
- version,
3819
- dbPath,
3820
- packageName: ohosApiDbPackageName(version),
3821
- installed: false,
3822
- };
4097
+ /** Read platformVersion from sdk-pkg.json / oh-uni-package.json under an SDK home. */
4098
+ function readOhosSdkPlatformVersion(sdkHome) {
4099
+ const pkgPaths = [
4100
+ path.join(sdkHome, 'sdk-pkg.json'),
4101
+ path.join(sdkHome, 'openharmony', 'ets', 'oh-uni-package.json'),
4102
+ path.join(sdkHome, 'openharmony', 'oh-uni-package.json'),
4103
+ ];
4104
+ for (const pkgPath of pkgPaths) {
4105
+ if (!fs.existsSync(pkgPath))
4106
+ continue;
4107
+ try {
4108
+ const raw = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
4109
+ const data = raw.data && typeof raw.data === 'object'
4110
+ ? raw.data
4111
+ : raw;
4112
+ const v = normalizeOhosApiVersion(data.platformVersion) ||
4113
+ normalizeOhosApiVersion(data.version) ||
4114
+ normalizeOhosApiVersion(raw.platformVersion) ||
4115
+ normalizeOhosApiVersion(raw.version);
4116
+ if (v)
4117
+ return v;
4118
+ }
4119
+ catch {
4120
+ /* try next */
4121
+ }
3823
4122
  }
3824
- const packageName = ohosApiDbPackageName(version);
3825
- const installDir = fs.mkdtempSync(path.join(os.tmpdir(), 'hg-ohos-npm-'));
4123
+ return normalizeOhosApiVersion(path.basename(sdkHome));
4124
+ }
4125
+ /** Expand a user/env path into concrete sdk/default-style homes that contain openharmony/ets. */
4126
+ function expandLocalOhosSdkHomes(input) {
4127
+ const resolved = path.resolve(input);
4128
+ if (!fs.existsSync(resolved))
4129
+ return [];
4130
+ const homes = [];
4131
+ const push = (p) => {
4132
+ if (hasOpenHarmonyEts(p))
4133
+ homes.push(p);
4134
+ };
4135
+ push(resolved);
4136
+ push(path.join(resolved, 'default'));
4137
+ push(path.join(resolved, 'sdk', 'default'));
4138
+ const viaFind = findOhosSdkHome(resolved);
4139
+ if (viaFind)
4140
+ homes.push(viaFind);
4141
+ let entries;
3826
4142
  try {
3827
- (0, child_process_1.execFileSync)('npm', ['install', '--no-save', packageName], {
3828
- cwd: installDir,
3829
- stdio: 'pipe',
3830
- timeout: 180_000,
3831
- env: { ...process.env, npm_config_loglevel: 'error' },
3832
- });
4143
+ entries = fs.readdirSync(resolved, { withFileTypes: true });
4144
+ }
4145
+ catch {
4146
+ return [...new Set(homes)];
4147
+ }
4148
+ for (const entry of entries) {
4149
+ if (!entry.isDirectory())
4150
+ continue;
4151
+ if (entry.name === 'node_modules' || entry.name === '.git')
4152
+ continue;
4153
+ const child = path.join(resolved, entry.name);
4154
+ push(child);
4155
+ push(path.join(child, 'default'));
4156
+ }
4157
+ return [...new Set(homes)];
4158
+ }
4159
+ function localOhosSdkSearchRoots() {
4160
+ const roots = [];
4161
+ for (const key of ['HOMEGRAPH_OHOS_SDK', 'OHOS_SDK_HOME', 'DEVECO_SDK_HOME', 'HOS_SDK_HOME']) {
4162
+ const v = process.env[key]?.trim();
4163
+ if (v)
4164
+ roots.push(v);
4165
+ }
4166
+ const home = os.homedir();
4167
+ if (process.platform === 'win32') {
4168
+ const lad = process.env.LOCALAPPDATA;
4169
+ if (lad) {
4170
+ roots.push(path.join(lad, 'OpenHarmony', 'Sdk'));
4171
+ roots.push(path.join(lad, 'Huawei', 'Sdk'));
4172
+ }
4173
+ roots.push(path.join('C:\\', 'Program Files', 'Huawei', 'DevEco Studio', 'sdk'));
4174
+ roots.push(path.join(home, 'Huawei', 'Sdk'));
4175
+ roots.push(path.join(home, 'OpenHarmony', 'Sdk'));
4176
+ }
4177
+ else if (process.platform === 'darwin') {
4178
+ roots.push(path.join(home, 'Library', 'Huawei', 'Sdk'));
4179
+ roots.push(path.join(home, 'Library', 'OpenHarmony', 'Sdk'));
4180
+ roots.push('/Applications/DevEco-Studio.app/Contents/sdk');
4181
+ }
4182
+ else {
4183
+ roots.push(path.join(home, 'Huawei', 'Sdk'));
4184
+ roots.push(path.join(home, 'OpenHarmony', 'Sdk'));
4185
+ }
4186
+ return roots;
4187
+ }
4188
+ /** Discover local DevEco / OpenHarmony SDK homes (env vars + common install paths). */
4189
+ function discoverLocalOhosSdkCandidates() {
4190
+ const out = [];
4191
+ const seen = new Set();
4192
+ for (const root of localOhosSdkSearchRoots()) {
4193
+ for (const sdkHome of expandLocalOhosSdkHomes(root)) {
4194
+ if (seen.has(sdkHome))
4195
+ continue;
4196
+ seen.add(sdkHome);
4197
+ out.push({ sdkHome, version: readOhosSdkPlatformVersion(sdkHome) });
4198
+ }
4199
+ }
4200
+ return out;
4201
+ }
4202
+ /**
4203
+ * Pick a local SDK home.
4204
+ * When `preferredVersion` is set, require an exact platformVersion match.
4205
+ * When unset, return the first candidate that has a readable version (else any).
4206
+ */
4207
+ function findLocalOhosSdkForVersion(preferredVersion) {
4208
+ const candidates = discoverLocalOhosSdkCandidates();
4209
+ if (candidates.length === 0)
4210
+ return null;
4211
+ if (preferredVersion) {
4212
+ return candidates.find((c) => c.version === preferredVersion) ?? null;
4213
+ }
4214
+ return candidates.find((c) => c.version) ?? candidates[0];
4215
+ }
4216
+ /**
4217
+ * Resolve the API db version for a project: build-profile / env first, else local SDK.
4218
+ */
4219
+ function resolveOhosApiVersionForProject(projectRoot) {
4220
+ const fromProject = detectOhosCompileSdkVersion(projectRoot);
4221
+ if (fromProject)
4222
+ return fromProject;
4223
+ return findLocalOhosSdkForVersion(null)?.version ?? null;
4224
+ }
4225
+ function attachOhosApiDbBinding(queries, binding) {
4226
+ try {
4227
+ queries.attachOhosApiDb(binding.dbPath);
4228
+ queries.setMetadata(exports.OHOS_API_VERSION_META, binding.version);
4229
+ queries.setMetadata(exports.OHOS_API_DB_PATH_META, binding.dbPath);
4230
+ return binding;
3833
4231
  }
3834
4232
  catch (err) {
3835
4233
  const msg = err instanceof Error ? err.message : String(err);
3836
4234
  return {
3837
- code: 'ohos_api_install_failed',
3838
- message: `Could not fetch ${packageName} (${msg}). ` +
3839
- 'Explore will use project code only until the matching API db is available.',
4235
+ code: 'ohos_api_db_missing',
4236
+ message: `Failed to attach OHOS API db at ${binding.dbPath}: ${msg}`,
3840
4237
  };
3841
4238
  }
3842
- finally {
3843
- fs.rmSync(installDir, { recursive: true, force: true });
4239
+ }
4240
+ /**
4241
+ * Build (or reuse) ~/.homegraph/api/ohos-api-<version>.db from a local SDK.
4242
+ * Never throws — missing SDK / build failure returns a warning.
4243
+ */
4244
+ async function ensureOhosApiDb(version, options = {}) {
4245
+ const dbPath = ohosApiDbInstallPath(version);
4246
+ const packageName = ohosApiDbPackageName(version);
4247
+ if (fs.existsSync(dbPath)) {
4248
+ return { version, dbPath, packageName, installed: false };
3844
4249
  }
3845
- if (!fs.existsSync(dbPath)) {
4250
+ if (options.build === false || process.env.HOMEGRAPH_OHOS_API_SKIP === '1') {
3846
4251
  return {
3847
4252
  code: 'ohos_api_db_missing',
3848
- message: `${packageName} was fetched but ${ohosApiDbFilename(version)} is missing under ~/.homegraph/api/. ` +
4253
+ message: `${ohosApiDbFilename(version)} is not under ~/.homegraph/api/ yet. ` +
4254
+ 'Re-run `homegraph init -i` / `homegraph index` with a local OHOS SDK, or `homegraph index-api <sdk>`. ' +
3849
4255
  'Explore will use project code only.',
3850
4256
  };
3851
4257
  }
3852
- return { version, dbPath, packageName, installed: true };
4258
+ let sdkHome = null;
4259
+ if (options.sdkHomeHint) {
4260
+ const hintHomes = expandLocalOhosSdkHomes(options.sdkHomeHint);
4261
+ const match = hintHomes.find((h) => readOhosSdkPlatformVersion(h) === version) ?? hintHomes[0];
4262
+ if (match && hasOpenHarmonyEts(match))
4263
+ sdkHome = match;
4264
+ }
4265
+ if (!sdkHome) {
4266
+ sdkHome = findLocalOhosSdkForVersion(version)?.sdkHome ?? null;
4267
+ }
4268
+ if (!sdkHome) {
4269
+ return {
4270
+ code: 'ohos_api_sdk_missing',
4271
+ message: `Local OHOS SDK for API ${version} not found (set OHOS_SDK_HOME / DEVECO_SDK_HOME / HOMEGRAPH_OHOS_SDK, ` +
4272
+ 'or install the matching DevEco SDK). SDK API lookup disabled; project indexing continues normally.',
4273
+ };
4274
+ }
4275
+ fs.mkdirSync(path.dirname(dbPath), { recursive: true });
4276
+ options.onProgress?.({
4277
+ phase: 'ohos-api',
4278
+ current: 0,
4279
+ total: 1,
4280
+ currentFile: version,
4281
+ });
4282
+ try {
4283
+ const result = await indexOhosApiDb({
4284
+ sdkHome,
4285
+ version,
4286
+ outputPath: dbPath,
4287
+ onProgress: options.onProgress
4288
+ ? (progress) => {
4289
+ options.onProgress?.({
4290
+ phase: 'ohos-api',
4291
+ current: progress.current,
4292
+ total: progress.total || 1,
4293
+ currentFile: progress.currentFile
4294
+ ? `${version} ${path.basename(progress.currentFile)}`
4295
+ : version,
4296
+ subphase: progress.subphase,
4297
+ });
4298
+ }
4299
+ : undefined,
4300
+ });
4301
+ if (!result.success || !fs.existsSync(dbPath)) {
4302
+ try {
4303
+ fs.unlinkSync(dbPath);
4304
+ }
4305
+ catch {
4306
+ /* ignore */
4307
+ }
4308
+ const detail = result.errors
4309
+ .filter((e) => e.severity === 'error')
4310
+ .map((e) => e.message)
4311
+ .join('; ');
4312
+ return {
4313
+ code: 'ohos_api_build_failed',
4314
+ message: `Failed to build OHOS API db for ${version} from ${sdkHome}` +
4315
+ (detail ? ` (${detail})` : '') +
4316
+ '. SDK API lookup disabled; project indexing continues normally.',
4317
+ };
4318
+ }
4319
+ options.onProgress?.({
4320
+ phase: 'ohos-api',
4321
+ current: 1,
4322
+ total: 1,
4323
+ currentFile: version,
4324
+ });
4325
+ return { version, dbPath, packageName, installed: true };
4326
+ }
4327
+ catch (err) {
4328
+ try {
4329
+ if (fs.existsSync(dbPath))
4330
+ fs.unlinkSync(dbPath);
4331
+ }
4332
+ catch {
4333
+ /* ignore */
4334
+ }
4335
+ const msg = err instanceof Error ? err.message : String(err);
4336
+ return {
4337
+ code: 'ohos_api_build_failed',
4338
+ message: `Failed to build OHOS API db for ${version} from ${sdkHome} (${msg}). ` +
4339
+ 'SDK API lookup disabled; project indexing continues normally.',
4340
+ };
4341
+ }
4342
+ }
4343
+ /**
4344
+ * Attach a prebuilt API db if present — never builds. Used on open / wireLayers.
4345
+ */
4346
+ function attachExistingOhosApiDbForProject(projectRoot, queries, languages) {
4347
+ if (!isOhosArktsProject(projectRoot, languages))
4348
+ return null;
4349
+ const version = resolveOhosApiVersionForProject(projectRoot);
4350
+ if (!version)
4351
+ return null;
4352
+ const dbPath = ohosApiDbInstallPath(version);
4353
+ if (!fs.existsSync(dbPath))
4354
+ return null;
4355
+ return attachOhosApiDbBinding(queries, {
4356
+ version,
4357
+ dbPath,
4358
+ packageName: ohosApiDbPackageName(version),
4359
+ installed: false,
4360
+ });
3853
4361
  }
3854
- /** Detect version, ensure db, persist metadata, ATTACH — never throws. */
3855
- function bindOhosApiDbForProject(projectRoot, queries, languages) {
4362
+ /**
4363
+ * Detect version, build from local SDK when needed, persist metadata, ATTACH — never throws.
4364
+ * Pass `{ build: true }` (default) after init/index; `{ build: false }` on open.
4365
+ */
4366
+ async function bindOhosApiDbForProject(projectRoot, queries, languages, options = {}) {
3856
4367
  if (!isOhosArktsProject(projectRoot, languages))
3857
4368
  return null;
3858
- const version = detectOhosCompileSdkVersion(projectRoot);
4369
+ const version = resolveOhosApiVersionForProject(projectRoot);
3859
4370
  if (!version) {
3860
4371
  return {
3861
4372
  code: 'ohos_api_version_unknown',
3862
- message: 'HarmonyOS project detected but compileSdkVersion not found in build-profile.json5. ' +
4373
+ message: 'HarmonyOS project detected but compileSdkVersion was not found in build-profile.json5 ' +
4374
+ 'and no local OHOS SDK was discovered (set OHOS_SDK_HOME / DEVECO_SDK_HOME). ' +
3863
4375
  'SDK API lookup disabled; project indexing continues normally.',
3864
4376
  };
3865
4377
  }
3866
- const ensured = ensureOhosApiDb(version);
4378
+ const ensured = await ensureOhosApiDb(version, options);
3867
4379
  if ('code' in ensured)
3868
4380
  return ensured;
3869
- try {
3870
- queries.attachOhosApiDb(ensured.dbPath);
3871
- queries.setMetadata(exports.OHOS_API_VERSION_META, ensured.version);
3872
- queries.setMetadata(exports.OHOS_API_DB_PATH_META, ensured.dbPath);
3873
- }
3874
- catch (err) {
3875
- const msg = err instanceof Error ? err.message : String(err);
3876
- return {
3877
- code: 'ohos_api_db_missing',
3878
- message: `Failed to attach OHOS API db at ${ensured.dbPath}: ${msg}`,
3879
- };
3880
- }
3881
- return ensured;
4381
+ return attachOhosApiDbBinding(queries, ensured);
3882
4382
  }
3883
4383
  /** Re-ATTACH from project metadata after reopen / new QueryBuilder. */
3884
4384
  function restoreOhosApiDbAttach(queries) {