homegraph 1.5.0 → 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 (213) hide show
  1. package/dist/bin/command-supervision.d.ts.map +1 -1
  2. package/dist/bin/command-supervision.js +7 -4
  3. package/dist/bin/command-supervision.js.map +1 -1
  4. package/dist/extraction/index.d.ts +1 -1
  5. package/dist/extraction/index.d.ts.map +1 -1
  6. package/dist/extraction/index.js +54 -43
  7. package/dist/extraction/index.js.map +1 -1
  8. package/dist/extraction/languages/arkts.d.ts +69 -12
  9. package/dist/extraction/languages/arkts.d.ts.map +1 -1
  10. package/dist/extraction/languages/arkts.js +739 -86
  11. package/dist/extraction/languages/arkts.js.map +1 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +3 -2
  14. package/dist/index.js.map +1 -1
  15. package/dist/mcp/diff-impact.d.ts +131 -0
  16. package/dist/mcp/diff-impact.d.ts.map +1 -0
  17. package/dist/mcp/diff-impact.js +385 -0
  18. package/dist/mcp/diff-impact.js.map +1 -0
  19. package/dist/mcp/liveness-watchdog.d.ts +18 -0
  20. package/dist/mcp/liveness-watchdog.d.ts.map +1 -1
  21. package/dist/mcp/liveness-watchdog.js +134 -22
  22. package/dist/mcp/liveness-watchdog.js.map +1 -1
  23. package/dist/mcp/server-instructions.d.ts +1 -1
  24. package/dist/mcp/server-instructions.d.ts.map +1 -1
  25. package/dist/mcp/server-instructions.js +2 -0
  26. package/dist/mcp/server-instructions.js.map +1 -1
  27. package/dist/mcp/tools.d.ts +21 -5
  28. package/dist/mcp/tools.d.ts.map +1 -1
  29. package/dist/mcp/tools.js +305 -55
  30. package/dist/mcp/tools.js.map +1 -1
  31. package/dist/resolution/callback-synthesizer.d.ts +2 -1
  32. package/dist/resolution/callback-synthesizer.d.ts.map +1 -1
  33. package/dist/resolution/callback-synthesizer.js +153 -53
  34. package/dist/resolution/callback-synthesizer.js.map +1 -1
  35. package/dist/resolution/frameworks/arkts-entry.d.ts.map +1 -1
  36. package/dist/resolution/frameworks/arkts-entry.js +20 -9
  37. package/dist/resolution/frameworks/arkts-entry.js.map +1 -1
  38. package/dist/resolution/index.d.ts.map +1 -1
  39. package/dist/resolution/index.js +1 -1
  40. package/dist/resolution/index.js.map +1 -1
  41. package/dist/resolution/memory-budget.d.ts +33 -0
  42. package/dist/resolution/memory-budget.d.ts.map +1 -1
  43. package/dist/resolution/memory-budget.js +58 -0
  44. package/dist/resolution/memory-budget.js.map +1 -1
  45. package/dist/resolution/resolver-pool.d.ts +2 -0
  46. package/dist/resolution/resolver-pool.d.ts.map +1 -1
  47. package/dist/resolution/resolver-pool.js +4 -0
  48. package/dist/resolution/resolver-pool.js.map +1 -1
  49. package/dist/ui/shimmer-progress.d.ts.map +1 -1
  50. package/dist/ui/shimmer-progress.js +4 -1
  51. package/dist/ui/shimmer-progress.js.map +1 -1
  52. package/package.json +4 -5
  53. package/dist/arkts/ohos-api-index.d.ts +0 -15
  54. package/dist/arkts/ohos-api-index.d.ts.map +0 -1
  55. package/dist/arkts/ohos-api-index.js +0 -190
  56. package/dist/arkts/ohos-api-index.js.map +0 -1
  57. package/dist/arkts/ohos-sdk-input.d.ts +0 -36
  58. package/dist/arkts/ohos-sdk-input.d.ts.map +0 -1
  59. package/dist/arkts/ohos-sdk-input.js +0 -214
  60. package/dist/arkts/ohos-sdk-input.js.map +0 -1
  61. package/dist/extraction/languages/arkts-state-decorators.d.ts +0 -13
  62. package/dist/extraction/languages/arkts-state-decorators.d.ts.map +0 -1
  63. package/dist/extraction/languages/arkts-state-decorators.js +0 -26
  64. package/dist/extraction/languages/arkts-state-decorators.js.map +0 -1
  65. package/dist/extraction/languages/arkts-viewtree.d.ts +0 -24
  66. package/dist/extraction/languages/arkts-viewtree.d.ts.map +0 -1
  67. package/dist/extraction/languages/arkts-viewtree.js +0 -148
  68. package/dist/extraction/languages/arkts-viewtree.js.map +0 -1
  69. package/dist/extraction/languages/ohos-api-consumer.d.ts +0 -34
  70. package/dist/extraction/languages/ohos-api-consumer.d.ts.map +0 -1
  71. package/dist/extraction/languages/ohos-api-consumer.js +0 -283
  72. package/dist/extraction/languages/ohos-api-consumer.js.map +0 -1
  73. package/dist/reasoning/config.d.ts +0 -45
  74. package/dist/reasoning/config.d.ts.map +0 -1
  75. package/dist/reasoning/config.js +0 -171
  76. package/dist/reasoning/config.js.map +0 -1
  77. package/dist/reasoning/credentials.d.ts +0 -5
  78. package/dist/reasoning/credentials.d.ts.map +0 -1
  79. package/dist/reasoning/credentials.js +0 -83
  80. package/dist/reasoning/credentials.js.map +0 -1
  81. package/dist/reasoning/login.d.ts +0 -21
  82. package/dist/reasoning/login.d.ts.map +0 -1
  83. package/dist/reasoning/login.js +0 -85
  84. package/dist/reasoning/login.js.map +0 -1
  85. package/dist/reasoning/reasoner.d.ts +0 -43
  86. package/dist/reasoning/reasoner.d.ts.map +0 -1
  87. package/dist/reasoning/reasoner.js +0 -308
  88. package/dist/reasoning/reasoner.js.map +0 -1
  89. package/dist/spec/build/git-scanner.d.ts +0 -93
  90. package/dist/spec/build/git-scanner.d.ts.map +0 -1
  91. package/dist/spec/build/git-scanner.js +0 -254
  92. package/dist/spec/build/git-scanner.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/git-utils.d.ts +0 -8
  102. package/dist/spec/git-utils.d.ts.map +0 -1
  103. package/dist/spec/git-utils.js +0 -14
  104. package/dist/spec/git-utils.js.map +0 -1
  105. package/dist/spec/llm/index.d.ts +0 -3
  106. package/dist/spec/llm/index.d.ts.map +0 -1
  107. package/dist/spec/llm/index.js +0 -11
  108. package/dist/spec/llm/index.js.map +0 -1
  109. package/dist/spec/mine/clusterer.d.ts +0 -63
  110. package/dist/spec/mine/clusterer.d.ts.map +0 -1
  111. package/dist/spec/mine/clusterer.js +0 -904
  112. package/dist/spec/mine/clusterer.js.map +0 -1
  113. package/dist/spec/mine/progress-handler.d.ts +0 -22
  114. package/dist/spec/mine/progress-handler.d.ts.map +0 -1
  115. package/dist/spec/mine/progress-handler.js +0 -108
  116. package/dist/spec/mine/progress-handler.js.map +0 -1
  117. package/dist/spec/mine/progress.d.ts +0 -23
  118. package/dist/spec/mine/progress.d.ts.map +0 -1
  119. package/dist/spec/mine/progress.js +0 -12
  120. package/dist/spec/mine/progress.js.map +0 -1
  121. package/dist/spec/mining/diff-parser.d.ts +0 -33
  122. package/dist/spec/mining/diff-parser.d.ts.map +0 -1
  123. package/dist/spec/mining/diff-parser.js +0 -166
  124. package/dist/spec/mining/diff-parser.js.map +0 -1
  125. package/dist/spec/mining/git-scanner.d.ts +0 -103
  126. package/dist/spec/mining/git-scanner.d.ts.map +0 -1
  127. package/dist/spec/mining/git-scanner.js +0 -307
  128. package/dist/spec/mining/git-scanner.js.map +0 -1
  129. package/dist/spec/mining/pipeline.d.ts +0 -53
  130. package/dist/spec/mining/pipeline.d.ts.map +0 -1
  131. package/dist/spec/mining/pipeline.js +0 -178
  132. package/dist/spec/mining/pipeline.js.map +0 -1
  133. package/dist/spec/mining/scope-resolver.d.ts +0 -45
  134. package/dist/spec/mining/scope-resolver.d.ts.map +0 -1
  135. package/dist/spec/mining/scope-resolver.js +0 -103
  136. package/dist/spec/mining/scope-resolver.js.map +0 -1
  137. package/dist/spec/mining/spec-extractor.d.ts +0 -69
  138. package/dist/spec/mining/spec-extractor.d.ts.map +0 -1
  139. package/dist/spec/mining/spec-extractor.js +0 -369
  140. package/dist/spec/mining/spec-extractor.js.map +0 -1
  141. package/dist/spec/utils.d.ts +0 -155
  142. package/dist/spec/utils.d.ts.map +0 -1
  143. package/dist/spec/utils.js +0 -411
  144. package/dist/spec/utils.js.map +0 -1
  145. package/scripts/add-lang/bench.sh +0 -60
  146. package/scripts/add-lang/check-grammar.mjs +0 -75
  147. package/scripts/add-lang/dump-ast.mjs +0 -103
  148. package/scripts/add-lang/verify-extraction.mjs +0 -70
  149. package/scripts/agent-eval/ab-adoption.sh +0 -91
  150. package/scripts/agent-eval/ab-hook.sh +0 -86
  151. package/scripts/agent-eval/ab-impl.sh +0 -78
  152. package/scripts/agent-eval/ab-new-vs-baseline.sh +0 -102
  153. package/scripts/agent-eval/ab-sufficiency.sh +0 -78
  154. package/scripts/agent-eval/arms-F.sh +0 -21
  155. package/scripts/agent-eval/arms-matrix.sh +0 -37
  156. package/scripts/agent-eval/audit.sh +0 -68
  157. package/scripts/agent-eval/bench-readme.sh +0 -28
  158. package/scripts/agent-eval/bench-why-repo.sh +0 -22
  159. package/scripts/agent-eval/block-read-hook.sh +0 -19
  160. package/scripts/agent-eval/hook-settings.json +0 -15
  161. package/scripts/agent-eval/itrun.sh +0 -120
  162. package/scripts/agent-eval/offload-eval-3arm.sh +0 -72
  163. package/scripts/agent-eval/offload-eval-cost.mjs +0 -133
  164. package/scripts/agent-eval/offload-eval-effort.mjs +0 -108
  165. package/scripts/agent-eval/offload-eval-frontload-matrix.sh +0 -25
  166. package/scripts/agent-eval/offload-eval-frontload.sh +0 -47
  167. package/scripts/agent-eval/offload-eval-ground-truth.json +0 -18
  168. package/scripts/agent-eval/offload-eval-hook.mjs +0 -84
  169. package/scripts/agent-eval/offload-eval-judge.mjs +0 -103
  170. package/scripts/agent-eval/offload-eval-matrix.sh +0 -20
  171. package/scripts/agent-eval/offload-eval-metrics.mjs +0 -94
  172. package/scripts/agent-eval/offload-eval-refs1.sh +0 -50
  173. package/scripts/agent-eval/offload-eval-setup.sh +0 -24
  174. package/scripts/agent-eval/offload-eval-styles.sh +0 -72
  175. package/scripts/agent-eval/offload-eval-summarize.mjs +0 -68
  176. package/scripts/agent-eval/offload-eval.md +0 -76
  177. package/scripts/agent-eval/parse-arms.mjs +0 -116
  178. package/scripts/agent-eval/parse-bench-readme.mjs +0 -84
  179. package/scripts/agent-eval/parse-run.mjs +0 -45
  180. package/scripts/agent-eval/parse-session.mjs +0 -93
  181. package/scripts/agent-eval/probe-context.mjs +0 -21
  182. package/scripts/agent-eval/probe-explore.mjs +0 -40
  183. package/scripts/agent-eval/probe-node.mjs +0 -20
  184. package/scripts/agent-eval/probe-sweep.mjs +0 -119
  185. package/scripts/agent-eval/probe-trace.mjs +0 -20
  186. package/scripts/agent-eval/redirect-read-hook.sh +0 -38
  187. package/scripts/agent-eval/repro-concurrent-explore.mjs +0 -119
  188. package/scripts/agent-eval/repro-daemon-clients.mjs +0 -125
  189. package/scripts/agent-eval/run-agent.sh +0 -34
  190. package/scripts/agent-eval/run-all.sh +0 -75
  191. package/scripts/agent-eval/run-arms.sh +0 -56
  192. package/scripts/agent-eval/seq-matrix.mjs +0 -137
  193. package/scripts/build-bundle.sh +0 -123
  194. package/scripts/exp_boundary_eval/README.md +0 -247
  195. package/scripts/exp_boundary_eval/_test_mcp_chain.py +0 -78
  196. package/scripts/exp_boundary_eval/_test_stdin.py +0 -8
  197. package/scripts/exp_boundary_eval/_utils.py +0 -1116
  198. package/scripts/exp_boundary_eval/analyze.py +0 -1313
  199. package/scripts/exp_boundary_eval/deveco_arm.py +0 -519
  200. package/scripts/exp_boundary_eval/run_all.py +0 -378
  201. package/scripts/exp_boundary_eval/run_one.py +0 -165
  202. package/scripts/exp_boundary_eval/run_session.py +0 -158
  203. package/scripts/exp_boundary_eval/setup.py +0 -120
  204. package/scripts/exp_boundary_eval/win_mcp_launcher.py +0 -73
  205. package/scripts/exp_boundary_eval/win_mcp_stdio_wrap.js +0 -36
  206. package/scripts/exp_boundary_eval/win_node_launcher.py +0 -24
  207. package/scripts/extract-release-notes.mjs +0 -130
  208. package/scripts/local-install.sh +0 -41
  209. package/scripts/npm-sdk.js +0 -75
  210. package/scripts/npm-shim.js +0 -275
  211. package/scripts/ohos-sdk-publish.mjs +0 -133
  212. package/scripts/pack-npm.sh +0 -119
  213. package/scripts/prepare-release.mjs +0 -270
@@ -11,11 +11,14 @@
11
11
  * HarmonyOS multi-module projects (`build-profile.json5`) use
12
12
  * `Scene.analyseByModule` so each HAP/HSP/HAR is the atomic unit (like a file
13
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.
14
+ * the module has BODIES. Per-module RTA entries = DummyMain (Ability/Component
15
+ * lifecycle) **plus the module export surface** (exported functions and methods
16
+ * of exported classes), so non-UI libraries still grow reachable call edges.
17
+ * CFG does **not** stitch same-module calls (that densified past unlimited RTA).
18
+ * Cross-module edges come from RTA with an exact MethodSignature map, plus a
19
+ * CFG `%unk` bridge that recovers Class.method / free functions from stmt text,
20
+ * UnclearReferenceType names, or PascalCase base locals (import + unique target
21
+ * + fan-out blocklist). Deps load to SIGNATURES.
19
22
  *
20
23
  * Sync can re-index a **dirty module subset** (`primeArkTSBatch` + `changedFiles`)
21
24
  * via `ModuleAnalysisConfig.setTargetModuleIds`, instead of rebuilding every
@@ -72,7 +75,10 @@ exports.scanEtsFiles = scanEtsFiles;
72
75
  exports.runIsolatedArkTSBatchEntry = runIsolatedArkTSBatchEntry;
73
76
  exports.primeArkTSBatch = primeArkTSBatch;
74
77
  exports.isArkTSBatchPersisted = isArkTSBatchPersisted;
78
+ exports.isArkTSBatchCommitted = isArkTSBatchCommitted;
75
79
  exports.resetArkTSBatch = resetArkTSBatch;
80
+ exports.shrinkArkTSBatchPostParse = shrinkArkTSBatchPostParse;
81
+ exports.collectExportedRtaEntryPointsFromFiles = collectExportedRtaEntryPointsFromFiles;
76
82
  exports.stateDecoratorKinds = stateDecoratorKinds;
77
83
  exports.stateTransferViaForField = stateTransferViaForField;
78
84
  exports.isUnclearArkClassSignature = isUnclearArkClassSignature;
@@ -95,7 +101,12 @@ exports.parseJson5Minimal = parseJson5Minimal;
95
101
  exports.normalizeOhosApiVersion = normalizeOhosApiVersion;
96
102
  exports.detectOhosCompileSdkVersion = detectOhosCompileSdkVersion;
97
103
  exports.isOhosArktsProject = isOhosArktsProject;
104
+ exports.readOhosSdkPlatformVersion = readOhosSdkPlatformVersion;
105
+ exports.discoverLocalOhosSdkCandidates = discoverLocalOhosSdkCandidates;
106
+ exports.findLocalOhosSdkForVersion = findLocalOhosSdkForVersion;
107
+ exports.resolveOhosApiVersionForProject = resolveOhosApiVersionForProject;
98
108
  exports.ensureOhosApiDb = ensureOhosApiDb;
109
+ exports.attachExistingOhosApiDbForProject = attachExistingOhosApiDbForProject;
99
110
  exports.bindOhosApiDbForProject = bindOhosApiDbForProject;
100
111
  exports.restoreOhosApiDbAttach = restoreOhosApiDbAttach;
101
112
  const crypto = __importStar(require("crypto"));
@@ -113,6 +124,7 @@ const context_1 = require("../context");
113
124
  const default_ignore_1 = require("../default-ignore");
114
125
  const extraction_version_1 = require("../extraction-version");
115
126
  const version_1 = require("../../mcp/version");
127
+ const liveness_watchdog_1 = require("../../mcp/liveness-watchdog");
116
128
  const tree_sitter_helpers_1 = require("../tree-sitter-helpers");
117
129
  const wasm_runtime_flags_1 = require("../wasm-runtime-flags");
118
130
  const ignore_1 = __importDefault(require("ignore"));
@@ -642,6 +654,27 @@ function persistIncrementalModuleResults(rootDir, queries, index) {
642
654
  persistFileResult(queries, filePath, content, stats, fileResult, { force: true });
643
655
  }
644
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
+ }
645
678
  async function persistBatchResultsAsync(rootDir, queries, index) {
646
679
  const entries = [...index.fileResults.entries()];
647
680
  const persistTotal = entries.length;
@@ -689,18 +722,89 @@ function persistBatchResultsSync(rootDir, queries, index) {
689
722
  }
690
723
  }
691
724
  function commitArkTSBatch(rootDir, batchKey, etsFiles, index, triggerFile) {
692
- 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));
693
730
  persistedBatch = { ...index, rootDir, batchKey, etsFiles };
694
731
  batchTriggerFile = normIndexPath(triggerFile);
695
- if (index.fileResults.size > 0) {
732
+ if (batchPersistedPaths.size > 0 || index.fileResults.size > 0) {
696
733
  batchBuildCommitted = true;
697
734
  }
698
735
  return persistedBatch;
699
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
+ }
700
800
  function tryReturnCachedBatch(rootDir, triggerFile, etsFiles) {
701
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.
702
805
  if (persistedBatch &&
703
806
  persistedBatch.rootDir === rootDir &&
807
+ persistedBatch.fileResults.size > 0 &&
704
808
  batchPersistedPaths.has(normalizedTrigger)) {
705
809
  return persistedBatch;
706
810
  }
@@ -794,6 +898,8 @@ function runArkTSBatchFullCore(rootDir, queries, triggerFile, etsFiles, batchKey
794
898
  queries.insertEdges(valid);
795
899
  }
796
900
  }
901
+ // Keep payload in memory for ArkTSExtractor.extract() same-batch hits.
902
+ // indexAll uses primeArkTSBatch → releaseArkTSBatchHeavyPayload after persist.
797
903
  return commitArkTSBatch(rootDir, batchKey, etsFiles, index, triggerFile);
798
904
  }
799
905
  finally {
@@ -845,6 +951,27 @@ function runArkTSBatch(rootDir, queries, triggerFile) {
845
951
  if (cached) {
846
952
  return cached;
847
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
+ }
848
975
  const batchKey = etsFiles.length > 0 ? computeBatchKey(rootDir, etsFiles) : '';
849
976
  return runArkTSBatchFull(rootDir, queries, normalizedTrigger, etsFiles, batchKey);
850
977
  }
@@ -887,13 +1014,25 @@ async function primeArkTSBatch(rootDir, queries, triggerFile, options) {
887
1014
  (0, context_1.setArktsBatchRunning)(true);
888
1015
  try {
889
1016
  reportBatchProgress('scene', 0, etsFiles.length);
890
- const index = buildArkTSIndex(rootDir, etsFiles);
891
- 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) {
892
1027
  const fatal = index.errors.find((e) => e.severity === 'error');
893
1028
  throw new Error(fatal?.message ?? 'ArkTS batch produced no indexed files');
894
1029
  }
895
- queries.deleteArkTSCrossFileCallEdges();
896
- 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
+ }
897
1036
  if (index.crossFileEdges.length > 0) {
898
1037
  const valid = index.crossFileEdges.filter((e) => index.nodeIds.has(e.source) && index.nodeIds.has(e.target));
899
1038
  if (valid.length > 0) {
@@ -901,6 +1040,8 @@ async function primeArkTSBatch(rootDir, queries, triggerFile, options) {
901
1040
  }
902
1041
  }
903
1042
  commitArkTSBatch(rootDir, batchKey, etsFiles, index, normalizedTrigger);
1043
+ releaseArkTSBatchHeavyPayload();
1044
+ tryForceGc();
904
1045
  }
905
1046
  finally {
906
1047
  (0, context_1.setArktsBatchRunning)(false);
@@ -918,8 +1059,13 @@ async function runArkTSBatchIncrementalModules(rootDir, queries, triggerFile, et
918
1059
  (0, context_1.setArktsBatchRunning)(true);
919
1060
  try {
920
1061
  reportBatchProgress('scene', 0, etsFiles.length);
921
- const index = buildArkTSIndexByModule(rootDir, etsFiles, { targetModuleSrcPaths: moduleSrcPaths });
922
- 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) {
923
1069
  const fatal = index.errors.find((e) => e.severity === 'error');
924
1070
  // Fall back to full project batch when the subset produced nothing.
925
1071
  const batchKey = etsFiles.length > 0 ? computeBatchKey(rootDir, etsFiles) : '';
@@ -927,20 +1073,26 @@ async function runArkTSBatchIncrementalModules(rootDir, queries, triggerFile, et
927
1073
  process.stderr.write(`\n\x1b[33m ArkTS: incremental modules failed (${fatal.message}); full rebuild\x1b[0m\n`);
928
1074
  }
929
1075
  const full = buildArkTSIndex(rootDir, etsFiles);
930
- if (full.fileResults.size === 0) {
1076
+ if (full.fileResults.size === 0 && (full.streamedPersistedPaths?.size ?? 0) === 0) {
931
1077
  throw new Error(fatal?.message ?? 'ArkTS incremental batch produced no indexed files');
932
1078
  }
933
1079
  queries.deleteArkTSCrossFileCallEdges();
934
- await persistBatchResultsAsync(rootDir, queries, full);
1080
+ if (full.fileResults.size > 0) {
1081
+ await persistBatchResultsAsync(rootDir, queries, full);
1082
+ }
935
1083
  if (full.crossFileEdges.length > 0) {
936
1084
  const valid = full.crossFileEdges.filter((e) => full.nodeIds.has(e.source) && full.nodeIds.has(e.target));
937
1085
  if (valid.length > 0)
938
1086
  queries.insertEdges(valid);
939
1087
  }
940
1088
  commitArkTSBatch(rootDir, batchKey, etsFiles, full, triggerFile);
1089
+ releaseArkTSBatchHeavyPayload();
1090
+ tryForceGc();
941
1091
  return;
942
1092
  }
943
- persistIncrementalModuleResults(rootDir, queries, index);
1093
+ if (index.fileResults.size > 0) {
1094
+ persistIncrementalModuleResults(rootDir, queries, index);
1095
+ }
944
1096
  if (index.crossFileEdges.length > 0) {
945
1097
  const valid = index.crossFileEdges.filter((e) => index.nodeIds.has(e.source) && index.nodeIds.has(e.target));
946
1098
  if (valid.length > 0) {
@@ -950,6 +1102,8 @@ async function runArkTSBatchIncrementalModules(rootDir, queries, triggerFile, et
950
1102
  // Unique key so a partial index is never mistaken for a full-project cache hit.
951
1103
  const batchKey = `incremental:${moduleSrcPaths.join(',')}`;
952
1104
  commitArkTSBatch(rootDir, batchKey, etsFiles, index, triggerFile);
1105
+ releaseArkTSBatchHeavyPayload();
1106
+ tryForceGc();
953
1107
  process.stderr.write(`\n\x1b[33m ArkTS: incremental module reindex done (${moduleSrcPaths.join(', ')})\x1b[0m\n`);
954
1108
  }
955
1109
  finally {
@@ -960,6 +1114,10 @@ async function runArkTSBatchIncrementalModules(rootDir, queries, triggerFile, et
960
1114
  function isArkTSBatchPersisted(filePath) {
961
1115
  return batchPersistedPaths.has(normIndexPath(filePath));
962
1116
  }
1117
+ /** True after a successful ArkTS batch was committed to SQLite this index run. */
1118
+ function isArkTSBatchCommitted() {
1119
+ return batchBuildCommitted;
1120
+ }
963
1121
  /** Clear cached batch state (tests and full re-index). */
964
1122
  function resetArkTSBatch() {
965
1123
  persistedBatch = null;
@@ -968,6 +1126,16 @@ function resetArkTSBatch() {
968
1126
  batchBuildCommitted = false;
969
1127
  arktsIndexNotices = [];
970
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
+ }
971
1139
  class ArkTSExtractor {
972
1140
  filePath;
973
1141
  constructor(filePath, _source) {
@@ -1303,6 +1471,140 @@ function collectNonUiRtaEntryPointsFromFiles(files) {
1303
1471
  }
1304
1472
  return entries;
1305
1473
  }
1474
+ /**
1475
+ * Methods another module can call into: module `export` surface as RTA entries.
1476
+ * - exported function/method → itself
1477
+ * - exported class/struct → all of its non-skip methods (with body)
1478
+ * Skips `export … from` re-exports (not this module's IR).
1479
+ */
1480
+ function collectExportedRtaEntryPointsFromFiles(files) {
1481
+ const seen = new Set();
1482
+ const out = [];
1483
+ const addMethod = (method) => {
1484
+ if (shouldSkipArkMethod(method))
1485
+ return;
1486
+ try {
1487
+ if (!method.getCfg())
1488
+ return;
1489
+ }
1490
+ catch {
1491
+ return;
1492
+ }
1493
+ try {
1494
+ const sig = method.getSignature();
1495
+ const key = sig.toString();
1496
+ if (seen.has(key))
1497
+ return;
1498
+ seen.add(key);
1499
+ out.push(sig);
1500
+ }
1501
+ catch {
1502
+ // signature unavailable
1503
+ }
1504
+ };
1505
+ const addClass = (cls) => {
1506
+ try {
1507
+ for (const method of cls.getMethods(true)) {
1508
+ addMethod(method);
1509
+ }
1510
+ }
1511
+ catch {
1512
+ // class IR unavailable
1513
+ }
1514
+ };
1515
+ const visitExportInfos = (infos) => {
1516
+ for (const info of infos) {
1517
+ try {
1518
+ if (info.getFrom?.())
1519
+ continue;
1520
+ }
1521
+ catch {
1522
+ continue;
1523
+ }
1524
+ let arkExport;
1525
+ try {
1526
+ arkExport = info.getArkExport?.() ?? null;
1527
+ }
1528
+ catch {
1529
+ continue;
1530
+ }
1531
+ if (!arkExport || typeof arkExport !== 'object')
1532
+ continue;
1533
+ const exp = arkExport;
1534
+ // Prefer ExportType when present (METHOD=2, CLASS=1, NAME_SPACE=0).
1535
+ let exportType;
1536
+ try {
1537
+ exportType = exp.getExportType?.();
1538
+ }
1539
+ catch {
1540
+ exportType = undefined;
1541
+ }
1542
+ if (exportType === 2 || (exportType === undefined && typeof exp.getDeclaringArkClass === 'function' && typeof exp.getCfg === 'function')) {
1543
+ addMethod(arkExport);
1544
+ continue;
1545
+ }
1546
+ if (exportType === 1 || (exportType === undefined && typeof exp.getMethods === 'function')) {
1547
+ addClass(arkExport);
1548
+ continue;
1549
+ }
1550
+ if (exportType === 0 || typeof exp.getClasses === 'function') {
1551
+ try {
1552
+ for (const cls of exp.getClasses?.() ?? []) {
1553
+ addClass(cls);
1554
+ }
1555
+ }
1556
+ catch {
1557
+ // ignore
1558
+ }
1559
+ const nested = exp.getExportInfos?.();
1560
+ if (nested)
1561
+ visitExportInfos(nested);
1562
+ }
1563
+ }
1564
+ };
1565
+ for (const file of files) {
1566
+ try {
1567
+ visitExportInfos(file.getExportInfos());
1568
+ }
1569
+ catch {
1570
+ // ignore
1571
+ }
1572
+ try {
1573
+ for (const ns of file.getNamespaces()) {
1574
+ try {
1575
+ visitExportInfos(ns.getExportInfos());
1576
+ }
1577
+ catch {
1578
+ // ignore
1579
+ }
1580
+ }
1581
+ }
1582
+ catch {
1583
+ // ignore
1584
+ }
1585
+ }
1586
+ return out;
1587
+ }
1588
+ /** Merge RTA entry signatures, preserving order and dropping duplicates. */
1589
+ function mergeRtaEntryPoints(...groups) {
1590
+ const seen = new Set();
1591
+ const out = [];
1592
+ for (const group of groups) {
1593
+ for (const sig of group) {
1594
+ try {
1595
+ const key = sig.toString();
1596
+ if (seen.has(key))
1597
+ continue;
1598
+ seen.add(key);
1599
+ out.push(sig);
1600
+ }
1601
+ catch {
1602
+ // ignore
1603
+ }
1604
+ }
1605
+ }
1606
+ return out;
1607
+ }
1306
1608
  function resolveRtaEntryPoints(scene) {
1307
1609
  return resolveRtaEntryPointsFromFiles(scene, scene.getFiles());
1308
1610
  }
@@ -1889,6 +2191,9 @@ class ArkTSAdapter {
1889
2191
  this.stitchModuleCfgCallsAndRefs(files, moduleFiles);
1890
2192
  // Drop live IR object keys so ArkAnalyzer can GC unloaded modules; keep sig→id maps.
1891
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();
1892
2197
  }
1893
2198
  /**
1894
2199
  * Index symbols from already-loaded non-target modules into sig/name maps only.
@@ -2054,6 +2359,10 @@ class ArkTSAdapter {
2054
2359
  if (dummyMain) {
2055
2360
  this.indexArkanalyzerDummyMain(dummyMain);
2056
2361
  }
2362
+ // Export surface = cross-module call-ins; seed them as RTA entries so
2363
+ // non-UI libraries (Manager.getInstance → …) grow intra-module edges
2364
+ // without waiting for a Component DummyMain to reach them.
2365
+ entryPoints = mergeRtaEntryPoints(entryPoints, collectExportedRtaEntryPointsFromFiles(files));
2057
2366
  if (entryPoints.length === 0)
2058
2367
  return;
2059
2368
  let callGraph;
@@ -2323,6 +2632,30 @@ class ArkTSAdapter {
2323
2632
  errors: [...errors],
2324
2633
  };
2325
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
+ }
2326
2659
  indexScannedFile(arkFile) {
2327
2660
  const relativePath = normalizeRelPath(this.rootDir, arkFile.getFilePath());
2328
2661
  if (!this.scanned.has(relativePath))
@@ -3072,8 +3405,17 @@ function emptyArkTSBatchIndex(errors) {
3072
3405
  * HomeGraph structures in the callback, let ArkAnalyzer evict under memoryLimitMB.
3073
3406
  * When `targetModuleSrcPaths` is set, only those modules are BODIES targets
3074
3407
  * (deps still load at SIGNATURES) — used for incremental sync.
3408
+ *
3409
+ * Runs with the #850 liveness watchdog suspended: ArkAnalyzer's native BODIES
3410
+ * load between module callbacks routinely exceeds the 60s heartbeat window on
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).
3075
3414
  */
3076
3415
  function buildArkTSIndexByModule(rootDir, scannedFiles, options) {
3416
+ return (0, liveness_watchdog_1.runWithoutLivenessWatchdog)(() => buildArkTSIndexByModuleInner(rootDir, scannedFiles, options));
3417
+ }
3418
+ function buildArkTSIndexByModuleInner(rootDir, scannedFiles, options) {
3077
3419
  const errors = [];
3078
3420
  const memoryLimitMB = resolveArkTSSceneMemoryLimitMB();
3079
3421
  const sceneConfig = buildSceneConfig(rootDir, { memoryLimitMB });
@@ -3082,6 +3424,13 @@ function buildArkTSIndexByModule(rootDir, scannedFiles, options) {
3082
3424
  let modulesIndexed = 0;
3083
3425
  const targetSrc = options?.targetModuleSrcPaths?.map(normalizeHarmonyModuleSrcPath);
3084
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
+ };
3085
3434
  try {
3086
3435
  scene.config(sceneConfig);
3087
3436
  const config = new arkanalyzer_1.ModuleAnalysisConfig();
@@ -3124,18 +3473,83 @@ function buildArkTSIndexByModule(rootDir, scannedFiles, options) {
3124
3473
  return emptyArkTSBatchIndex(errors);
3125
3474
  }
3126
3475
  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`);
3476
+ process.stderr.write(`\n\x1b[33m ArkTS: analyseByModule incremental (${ids.length} module(s)→BODIES, deps→SIGNATURES, memoryLimitMB=${memoryLimitMB}${streamQueries ? ', streamPersist' : ''})...\x1b[0m\n`);
3128
3477
  }
3129
3478
  else {
3130
3479
  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`);
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`);
3132
3481
  }
3133
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);
3134
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
+ }
3135
3537
  modulesIndexed++;
3136
3538
  const name = module.getModuleName() || path.basename(module.getModulePath());
3137
- 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`);
3138
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
+ }
3139
3553
  if (modulesIndexed === 0) {
3140
3554
  errors.push({
3141
3555
  message: 'ArkTS analyseByModule found no PROJECT modules (check build-profile.json5); falling back to full Scene build',
@@ -3153,8 +3567,15 @@ function buildArkTSIndexByModule(rootDir, scannedFiles, options) {
3153
3567
  severity: 'warning',
3154
3568
  });
3155
3569
  }
3570
+ if (streamQueries) {
3571
+ const leftover = adapter.takeFileResults(adapter.fileResultPaths());
3572
+ persistAndDropModuleSlice(rootDir, streamQueries, leftover, streamedPersistedPaths, persistOrdinal);
3573
+ }
3156
3574
  const index = adapter.toBatchIndex(errors);
3157
- if (index.fileResults.size === 0) {
3575
+ if (streamQueries) {
3576
+ index.streamedPersistedPaths = streamedPersistedPaths;
3577
+ }
3578
+ if (index.fileResults.size === 0 && streamedPersistedPaths.size === 0) {
3158
3579
  errors.push({
3159
3580
  message: 'ArkTS analyseByModule indexed modules but no scanned source files matched',
3160
3581
  severity: 'warning',
@@ -3173,7 +3594,7 @@ function buildArkTSIndexByModule(rootDir, scannedFiles, options) {
3173
3594
  }
3174
3595
  finally {
3175
3596
  try {
3176
- scene.dispose();
3597
+ releaseArkAnalyzerScene(scene);
3177
3598
  }
3178
3599
  catch {
3179
3600
  // ignore dispose failures
@@ -3181,28 +3602,44 @@ function buildArkTSIndexByModule(rootDir, scannedFiles, options) {
3181
3602
  }
3182
3603
  }
3183
3604
  function buildArkTSIndexFull(rootDir, scannedFiles, priorErrors = []) {
3184
- const { scene, errors } = tryBuildArkScene(rootDir);
3185
- const allErrors = [...priorErrors, ...errors];
3186
- if (!scene) {
3187
- return emptyArkTSBatchIndex(allErrors);
3188
- }
3189
- try {
3190
- const adapter = new ArkTSAdapter(rootDir, scannedFiles);
3191
- const index = adapter.build(scene);
3192
- index.errors.push(...allErrors);
3193
- return index;
3194
- }
3195
- catch (e) {
3196
- const message = e instanceof Error ? e.message : String(e);
3197
- allErrors.push({ message: `ArkTS adapter failed: ${message}`, severity: 'error' });
3198
- return emptyArkTSBatchIndex(allErrors);
3199
- }
3605
+ // Full Scene build is the same opaque native stall as analyseByModule.
3606
+ return (0, liveness_watchdog_1.runWithoutLivenessWatchdog)(() => {
3607
+ const { scene, errors } = tryBuildArkScene(rootDir);
3608
+ const allErrors = [...priorErrors, ...errors];
3609
+ if (!scene) {
3610
+ return emptyArkTSBatchIndex(allErrors);
3611
+ }
3612
+ try {
3613
+ const adapter = new ArkTSAdapter(rootDir, scannedFiles);
3614
+ const index = adapter.build(scene);
3615
+ index.errors.push(...allErrors);
3616
+ return index;
3617
+ }
3618
+ catch (e) {
3619
+ const message = e instanceof Error ? e.message : String(e);
3620
+ allErrors.push({ message: `ArkTS adapter failed: ${message}`, severity: 'error' });
3621
+ return emptyArkTSBatchIndex(allErrors);
3622
+ }
3623
+ finally {
3624
+ try {
3625
+ releaseArkAnalyzerScene(scene);
3626
+ }
3627
+ catch {
3628
+ // ignore
3629
+ }
3630
+ }
3631
+ });
3200
3632
  }
3201
- function buildArkTSIndex(rootDir, scannedFiles) {
3633
+ function buildArkTSIndex(rootDir, scannedFiles, options) {
3202
3634
  if (shouldUseModularArkTSBuild(rootDir)) {
3203
- const modular = buildArkTSIndexByModule(rootDir, scannedFiles);
3635
+ const modular = buildArkTSIndexByModule(rootDir, scannedFiles, {
3636
+ streamQueries: options?.streamQueries,
3637
+ });
3204
3638
  const fatal = modular.errors.find((e) => e.severity === 'error');
3205
- 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) {
3206
3643
  return modular;
3207
3644
  }
3208
3645
  // Warnings like "no PROJECT modules" / modular failure → full Scene fallback.
@@ -3367,7 +3804,7 @@ function extractOhosSdkArchive(archivePath) {
3367
3804
  function ohosApiDbFilename(version) {
3368
3805
  return `ohos-api-${version}.db`;
3369
3806
  }
3370
- /** 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). */
3371
3808
  function ohosApiDbPackageName(version) {
3372
3809
  return `homegraph-ohos-api-db-${version}`;
3373
3810
  }
@@ -3515,7 +3952,7 @@ async function indexOhosApiDb(options) {
3515
3952
  }
3516
3953
  }
3517
3954
  // =============================================================================
3518
- // OHOS API db consumer: compileSdkVersion detect, npm fetch, ATTACH for queries
3955
+ // OHOS API db consumer: compileSdkVersion detect, local SDK build, ATTACH
3519
3956
  // =============================================================================
3520
3957
  exports.OHOS_API_FILE_PREFIX = 'ohos-sdk:';
3521
3958
  exports.OHOS_API_VERSION_META = 'ohos_api_version';
@@ -3657,75 +4094,291 @@ function hasEtsUnder(dir, depth) {
3657
4094
  }
3658
4095
  return false;
3659
4096
  }
3660
- /** Install npm package if local db is missing; non-throwing on failure. */
3661
- function ensureOhosApiDb(version) {
3662
- const dbPath = ohosApiDbInstallPath(version);
3663
- if (fs.existsSync(dbPath)) {
3664
- return {
3665
- version,
3666
- dbPath,
3667
- packageName: ohosApiDbPackageName(version),
3668
- installed: false,
3669
- };
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
+ }
3670
4122
  }
3671
- const packageName = ohosApiDbPackageName(version);
3672
- 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;
3673
4142
  try {
3674
- (0, child_process_1.execFileSync)('npm', ['install', '--no-save', packageName], {
3675
- cwd: installDir,
3676
- stdio: 'pipe',
3677
- timeout: 180_000,
3678
- env: { ...process.env, npm_config_loglevel: 'error' },
3679
- });
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;
3680
4231
  }
3681
4232
  catch (err) {
3682
4233
  const msg = err instanceof Error ? err.message : String(err);
3683
4234
  return {
3684
- code: 'ohos_api_install_failed',
3685
- message: `Could not fetch ${packageName} (${msg}). ` +
3686
- '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}`,
3687
4237
  };
3688
4238
  }
3689
- finally {
3690
- 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 };
3691
4249
  }
3692
- if (!fs.existsSync(dbPath)) {
4250
+ if (options.build === false || process.env.HOMEGRAPH_OHOS_API_SKIP === '1') {
3693
4251
  return {
3694
4252
  code: 'ohos_api_db_missing',
3695
- 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>`. ' +
3696
4255
  'Explore will use project code only.',
3697
4256
  };
3698
4257
  }
3699
- 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
+ }
3700
4342
  }
3701
- /** Detect version, ensure db, persist metadata, ATTACH — never throws. */
3702
- function bindOhosApiDbForProject(projectRoot, queries, languages) {
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
+ });
4361
+ }
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 = {}) {
3703
4367
  if (!isOhosArktsProject(projectRoot, languages))
3704
4368
  return null;
3705
- const version = detectOhosCompileSdkVersion(projectRoot);
4369
+ const version = resolveOhosApiVersionForProject(projectRoot);
3706
4370
  if (!version) {
3707
4371
  return {
3708
4372
  code: 'ohos_api_version_unknown',
3709
- 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). ' +
3710
4375
  'SDK API lookup disabled; project indexing continues normally.',
3711
4376
  };
3712
4377
  }
3713
- const ensured = ensureOhosApiDb(version);
4378
+ const ensured = await ensureOhosApiDb(version, options);
3714
4379
  if ('code' in ensured)
3715
4380
  return ensured;
3716
- try {
3717
- queries.attachOhosApiDb(ensured.dbPath);
3718
- queries.setMetadata(exports.OHOS_API_VERSION_META, ensured.version);
3719
- queries.setMetadata(exports.OHOS_API_DB_PATH_META, ensured.dbPath);
3720
- }
3721
- catch (err) {
3722
- const msg = err instanceof Error ? err.message : String(err);
3723
- return {
3724
- code: 'ohos_api_db_missing',
3725
- message: `Failed to attach OHOS API db at ${ensured.dbPath}: ${msg}`,
3726
- };
3727
- }
3728
- return ensured;
4381
+ return attachOhosApiDbBinding(queries, ensured);
3729
4382
  }
3730
4383
  /** Re-ATTACH from project metadata after reopen / new QueryBuilder. */
3731
4384
  function restoreOhosApiDbAttach(queries) {