homegraph 1.5.2 → 1.5.4

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 (242) hide show
  1. package/CHANGELOG.md +156 -0
  2. package/README.md +63 -10
  3. package/dist/addons/dynamic-import.d.ts +18 -0
  4. package/dist/addons/dynamic-import.d.ts.map +1 -0
  5. package/dist/addons/dynamic-import.js +25 -0
  6. package/dist/addons/dynamic-import.js.map +1 -0
  7. package/dist/addons/init-template.d.ts +15 -0
  8. package/dist/addons/init-template.d.ts.map +1 -0
  9. package/dist/addons/init-template.js +290 -0
  10. package/dist/addons/init-template.js.map +1 -0
  11. package/dist/addons/loader.d.ts +18 -0
  12. package/dist/addons/loader.d.ts.map +1 -0
  13. package/dist/addons/loader.js +88 -0
  14. package/dist/addons/loader.js.map +1 -0
  15. package/dist/addons/manager.d.ts +106 -0
  16. package/dist/addons/manager.d.ts.map +1 -0
  17. package/dist/addons/manager.js +437 -0
  18. package/dist/addons/manager.js.map +1 -0
  19. package/dist/addons/paths.d.ts +22 -0
  20. package/dist/addons/paths.d.ts.map +1 -0
  21. package/dist/addons/paths.js +65 -0
  22. package/dist/addons/paths.js.map +1 -0
  23. package/dist/addons/registry.d.ts +38 -0
  24. package/dist/addons/registry.d.ts.map +1 -0
  25. package/dist/addons/registry.js +160 -0
  26. package/dist/addons/registry.js.map +1 -0
  27. package/dist/addons/semver.d.ts +21 -0
  28. package/dist/addons/semver.d.ts.map +1 -0
  29. package/dist/addons/semver.js +79 -0
  30. package/dist/addons/semver.js.map +1 -0
  31. package/dist/addons/types.d.ts +51 -0
  32. package/dist/addons/types.d.ts.map +1 -0
  33. package/dist/addons/types.js +14 -0
  34. package/dist/addons/types.js.map +1 -0
  35. package/dist/addons/validate.d.ts +38 -0
  36. package/dist/addons/validate.d.ts.map +1 -0
  37. package/dist/addons/validate.js +165 -0
  38. package/dist/addons/validate.js.map +1 -0
  39. package/dist/arkui/index.d.ts +4 -0
  40. package/dist/arkui/index.d.ts.map +1 -0
  41. package/dist/arkui/index.js +23 -0
  42. package/dist/arkui/index.js.map +1 -0
  43. package/dist/arkui/migrate-passage.d.ts +28 -0
  44. package/dist/arkui/migrate-passage.d.ts.map +1 -0
  45. package/dist/arkui/migrate-passage.js +310 -0
  46. package/dist/arkui/migrate-passage.js.map +1 -0
  47. package/dist/arkui/migrate-semantics.d.ts +47 -0
  48. package/dist/arkui/migrate-semantics.d.ts.map +1 -0
  49. package/dist/arkui/migrate-semantics.js +229 -0
  50. package/dist/arkui/migrate-semantics.js.map +1 -0
  51. package/dist/arkui/migrate-snapshot.d.ts +79 -0
  52. package/dist/arkui/migrate-snapshot.d.ts.map +1 -0
  53. package/dist/arkui/migrate-snapshot.js +364 -0
  54. package/dist/arkui/migrate-snapshot.js.map +1 -0
  55. package/dist/bin/addon-commands.d.ts +27 -0
  56. package/dist/bin/addon-commands.d.ts.map +1 -0
  57. package/dist/bin/addon-commands.js +254 -0
  58. package/dist/bin/addon-commands.js.map +1 -0
  59. package/dist/bin/fatal-handler.js +2 -2
  60. package/dist/bin/homegraph.js +111 -157
  61. package/dist/bin/homegraph.js.map +1 -1
  62. package/dist/bin/node-version-check.d.ts +6 -15
  63. package/dist/bin/node-version-check.d.ts.map +1 -1
  64. package/dist/bin/node-version-check.js +8 -37
  65. package/dist/bin/node-version-check.js.map +1 -1
  66. package/dist/db/index.d.ts +36 -1
  67. package/dist/db/index.d.ts.map +1 -1
  68. package/dist/db/index.js +92 -6
  69. package/dist/db/index.js.map +1 -1
  70. package/dist/db/migrations.d.ts +1 -1
  71. package/dist/db/migrations.d.ts.map +1 -1
  72. package/dist/db/migrations.js +15 -1
  73. package/dist/db/migrations.js.map +1 -1
  74. package/dist/db/queries.d.ts +42 -0
  75. package/dist/db/queries.d.ts.map +1 -1
  76. package/dist/db/queries.js +177 -10
  77. package/dist/db/queries.js.map +1 -1
  78. package/dist/db/schema.sql +6 -1
  79. package/dist/db/sqlite-adapter.d.ts +7 -3
  80. package/dist/db/sqlite-adapter.d.ts.map +1 -1
  81. package/dist/db/sqlite-adapter.js +44 -3
  82. package/dist/db/sqlite-adapter.js.map +1 -1
  83. package/dist/extraction/extraction-version.d.ts +1 -1
  84. package/dist/extraction/extraction-version.js +1 -1
  85. package/dist/extraction/generated-detection.d.ts +42 -11
  86. package/dist/extraction/generated-detection.d.ts.map +1 -1
  87. package/dist/extraction/generated-detection.js +141 -20
  88. package/dist/extraction/generated-detection.js.map +1 -1
  89. package/dist/extraction/index.d.ts +42 -0
  90. package/dist/extraction/index.d.ts.map +1 -1
  91. package/dist/extraction/index.js +96 -0
  92. package/dist/extraction/index.js.map +1 -1
  93. package/dist/extraction/languages/arkts.d.ts +33 -2
  94. package/dist/extraction/languages/arkts.d.ts.map +1 -1
  95. package/dist/extraction/languages/arkts.js +525 -129
  96. package/dist/extraction/languages/arkts.js.map +1 -1
  97. package/dist/extraction/wasm-runtime-flags.d.ts.map +1 -1
  98. package/dist/extraction/wasm-runtime-flags.js +9 -6
  99. package/dist/extraction/wasm-runtime-flags.js.map +1 -1
  100. package/dist/graph-sources.d.ts +41 -0
  101. package/dist/graph-sources.d.ts.map +1 -0
  102. package/dist/graph-sources.js +82 -0
  103. package/dist/graph-sources.js.map +1 -0
  104. package/dist/index.d.ts +20 -1
  105. package/dist/index.d.ts.map +1 -1
  106. package/dist/index.js +79 -9
  107. package/dist/index.js.map +1 -1
  108. package/dist/installer/index.d.ts.map +1 -1
  109. package/dist/installer/index.js +0 -48
  110. package/dist/installer/index.js.map +1 -1
  111. package/dist/installer/targets/antigravity.js +1 -1
  112. package/dist/installer/targets/antigravity.js.map +1 -1
  113. package/dist/installer/targets/deveco.js +1 -1
  114. package/dist/installer/targets/deveco.js.map +1 -1
  115. package/dist/installer/targets/hermes.d.ts +1 -1
  116. package/dist/installer/targets/hermes.js +2 -2
  117. package/dist/installer/targets/hermes.js.map +1 -1
  118. package/dist/installer/targets/opencode.js +1 -1
  119. package/dist/installer/targets/opencode.js.map +1 -1
  120. package/dist/installer/targets/shared.js +1 -1
  121. package/dist/installer/targets/shared.js.map +1 -1
  122. package/dist/mcp/daemon-paths.d.ts +5 -4
  123. package/dist/mcp/daemon-paths.d.ts.map +1 -1
  124. package/dist/mcp/daemon-paths.js +28 -13
  125. package/dist/mcp/daemon-paths.js.map +1 -1
  126. package/dist/mcp/daemon-registry.d.ts.map +1 -1
  127. package/dist/mcp/daemon-registry.js +4 -1
  128. package/dist/mcp/daemon-registry.js.map +1 -1
  129. package/dist/mcp/daemon.d.ts +1 -1
  130. package/dist/mcp/daemon.js +1 -1
  131. package/dist/mcp/engine.d.ts.map +1 -1
  132. package/dist/mcp/engine.js +12 -3
  133. package/dist/mcp/engine.js.map +1 -1
  134. package/dist/mcp/explore-dedup.d.ts +137 -0
  135. package/dist/mcp/explore-dedup.d.ts.map +1 -0
  136. package/dist/mcp/explore-dedup.js +236 -0
  137. package/dist/mcp/explore-dedup.js.map +1 -0
  138. package/dist/mcp/explore-repeat-guard.d.ts +123 -0
  139. package/dist/mcp/explore-repeat-guard.d.ts.map +1 -0
  140. package/dist/mcp/explore-repeat-guard.js +503 -0
  141. package/dist/mcp/explore-repeat-guard.js.map +1 -0
  142. package/dist/mcp/explore-session-state.d.ts +237 -0
  143. package/dist/mcp/explore-session-state.d.ts.map +1 -0
  144. package/dist/mcp/explore-session-state.js +353 -0
  145. package/dist/mcp/explore-session-state.js.map +1 -0
  146. package/dist/mcp/index.d.ts.map +1 -1
  147. package/dist/mcp/index.js +4 -7
  148. package/dist/mcp/index.js.map +1 -1
  149. package/dist/mcp/proxy.d.ts.map +1 -1
  150. package/dist/mcp/proxy.js +20 -18
  151. package/dist/mcp/proxy.js.map +1 -1
  152. package/dist/mcp/query-pool.d.ts.map +1 -1
  153. package/dist/mcp/query-pool.js +10 -13
  154. package/dist/mcp/query-pool.js.map +1 -1
  155. package/dist/mcp/query-worker.d.ts +1 -1
  156. package/dist/mcp/query-worker.js +1 -1
  157. package/dist/mcp/server-instructions.d.ts +5 -2
  158. package/dist/mcp/server-instructions.d.ts.map +1 -1
  159. package/dist/mcp/server-instructions.js +43 -32
  160. package/dist/mcp/server-instructions.js.map +1 -1
  161. package/dist/mcp/session.d.ts +14 -3
  162. package/dist/mcp/session.d.ts.map +1 -1
  163. package/dist/mcp/session.js +17 -14
  164. package/dist/mcp/session.js.map +1 -1
  165. package/dist/mcp/startup-handshake.d.ts +1 -1
  166. package/dist/mcp/startup-handshake.js +1 -1
  167. package/dist/mcp/tools.d.ts +149 -8
  168. package/dist/mcp/tools.d.ts.map +1 -1
  169. package/dist/mcp/tools.js +4265 -575
  170. package/dist/mcp/tools.js.map +1 -1
  171. package/dist/resolution/callback-synthesizer.d.ts.map +1 -1
  172. package/dist/resolution/callback-synthesizer.js +166 -0
  173. package/dist/resolution/callback-synthesizer.js.map +1 -1
  174. package/dist/resolution/frameworks/arkts-napi.d.ts +14 -0
  175. package/dist/resolution/frameworks/arkts-napi.d.ts.map +1 -1
  176. package/dist/resolution/frameworks/arkts-napi.js +169 -49
  177. package/dist/resolution/frameworks/arkts-napi.js.map +1 -1
  178. package/dist/search/query-utils.d.ts +157 -5
  179. package/dist/search/query-utils.d.ts.map +1 -1
  180. package/dist/search/query-utils.js +1105 -70
  181. package/dist/search/query-utils.js.map +1 -1
  182. package/dist/spec/build/scan.js +1 -1
  183. package/dist/spec/build/scan.js.map +1 -1
  184. package/dist/spec/build/scope-resolver.d.ts +24 -5
  185. package/dist/spec/build/scope-resolver.d.ts.map +1 -1
  186. package/dist/spec/build/scope-resolver.js +76 -10
  187. package/dist/spec/build/scope-resolver.js.map +1 -1
  188. package/dist/spec/config.d.ts +8 -0
  189. package/dist/spec/config.d.ts.map +1 -1
  190. package/dist/spec/config.js +3 -0
  191. package/dist/spec/config.js.map +1 -1
  192. package/dist/spec/evolve/pipeline.d.ts.map +1 -1
  193. package/dist/spec/evolve/pipeline.js +2 -1
  194. package/dist/spec/evolve/pipeline.js.map +1 -1
  195. package/dist/spec/git/commits.d.ts +8 -3
  196. package/dist/spec/git/commits.d.ts.map +1 -1
  197. package/dist/spec/git/commits.js +12 -11
  198. package/dist/spec/git/commits.js.map +1 -1
  199. package/dist/spec/mine/addon/adapter.d.ts +45 -0
  200. package/dist/spec/mine/addon/adapter.d.ts.map +1 -0
  201. package/dist/spec/mine/addon/adapter.js +118 -0
  202. package/dist/spec/mine/addon/adapter.js.map +1 -0
  203. package/dist/spec/mine/addon/render.d.ts +20 -0
  204. package/dist/spec/mine/addon/render.d.ts.map +1 -0
  205. package/dist/spec/mine/addon/render.js +41 -0
  206. package/dist/spec/mine/addon/render.js.map +1 -0
  207. package/dist/spec/mine/addon/types.d.ts +70 -0
  208. package/dist/spec/mine/addon/types.d.ts.map +1 -0
  209. package/dist/spec/mine/addon/types.js +14 -0
  210. package/dist/spec/mine/addon/types.js.map +1 -0
  211. package/dist/spec/mine/generator.d.ts +5 -1
  212. package/dist/spec/mine/generator.d.ts.map +1 -1
  213. package/dist/spec/mine/generator.js +69 -7
  214. package/dist/spec/mine/generator.js.map +1 -1
  215. package/dist/spec/mine/pipeline.d.ts.map +1 -1
  216. package/dist/spec/mine/pipeline.js +5 -1
  217. package/dist/spec/mine/pipeline.js.map +1 -1
  218. package/dist/spec/mine/scanner.d.ts +1 -0
  219. package/dist/spec/mine/scanner.d.ts.map +1 -1
  220. package/dist/spec/mine/scanner.js +1 -1
  221. package/dist/spec/mine/scanner.js.map +1 -1
  222. package/dist/spec/types.d.ts +1 -1
  223. package/dist/spec/types.d.ts.map +1 -1
  224. package/dist/types.d.ts +8 -0
  225. package/dist/types.d.ts.map +1 -1
  226. package/dist/upgrade/index.d.ts +14 -26
  227. package/dist/upgrade/index.d.ts.map +1 -1
  228. package/dist/upgrade/index.js +28 -123
  229. package/dist/upgrade/index.js.map +1 -1
  230. package/dist/upgrade/remove-binary.d.ts +19 -19
  231. package/dist/upgrade/remove-binary.d.ts.map +1 -1
  232. package/dist/upgrade/remove-binary.js +20 -21
  233. package/dist/upgrade/remove-binary.js.map +1 -1
  234. package/dist/upgrade/update-check.d.ts +4 -3
  235. package/dist/upgrade/update-check.d.ts.map +1 -1
  236. package/dist/upgrade/update-check.js +4 -3
  237. package/dist/upgrade/update-check.js.map +1 -1
  238. package/package.json +13 -9
  239. package/dist/telemetry/index.d.ts +0 -143
  240. package/dist/telemetry/index.d.ts.map +0 -1
  241. package/dist/telemetry/index.js +0 -541
  242. package/dist/telemetry/index.js.map +0 -1
@@ -78,8 +78,11 @@ exports.isArkTSBatchPersisted = isArkTSBatchPersisted;
78
78
  exports.isArkTSBatchCommitted = isArkTSBatchCommitted;
79
79
  exports.resetArkTSBatch = resetArkTSBatch;
80
80
  exports.shrinkArkTSBatchPostParse = shrinkArkTSBatchPostParse;
81
+ exports.createArkRelPathNormalizer = createArkRelPathNormalizer;
81
82
  exports.collectExportedRtaEntryPointsFromFiles = collectExportedRtaEntryPointsFromFiles;
82
83
  exports.stateDecoratorKinds = stateDecoratorKinds;
84
+ exports.fieldDecoratorEntries = fieldDecoratorEntries;
85
+ exports.fieldDecoratorsEncoded = fieldDecoratorsEncoded;
83
86
  exports.stateTransferViaForField = stateTransferViaForField;
84
87
  exports.isUnclearArkClassSignature = isUnclearArkClassSignature;
85
88
  exports.isUnclearArkMethodSignature = isUnclearArkMethodSignature;
@@ -105,6 +108,7 @@ exports.readOhosSdkPlatformVersion = readOhosSdkPlatformVersion;
105
108
  exports.discoverLocalOhosSdkCandidates = discoverLocalOhosSdkCandidates;
106
109
  exports.findLocalOhosSdkForVersion = findLocalOhosSdkForVersion;
107
110
  exports.resolveOhosApiVersionForProject = resolveOhosApiVersionForProject;
111
+ exports.tryDownloadOhosApiDbFromNpm = tryDownloadOhosApiDbFromNpm;
108
112
  exports.ensureOhosApiDb = ensureOhosApiDb;
109
113
  exports.attachExistingOhosApiDbForProject = attachExistingOhosApiDbForProject;
110
114
  exports.bindOhosApiDbForProject = bindOhosApiDbForProject;
@@ -122,9 +126,12 @@ const directory_1 = require("../../directory");
122
126
  const project_config_1 = require("../../project-config");
123
127
  const context_1 = require("../context");
124
128
  const default_ignore_1 = require("../default-ignore");
129
+ const generated_detection_1 = require("../generated-detection");
125
130
  const extraction_version_1 = require("../extraction-version");
126
131
  const version_1 = require("../../mcp/version");
127
132
  const liveness_watchdog_1 = require("../../mcp/liveness-watchdog");
133
+ const migrate_semantics_1 = require("../../arkui/migrate-semantics");
134
+ const migrate_passage_1 = require("../../arkui/migrate-passage");
128
135
  const tree_sitter_helpers_1 = require("../tree-sitter-helpers");
129
136
  const wasm_runtime_flags_1 = require("../wasm-runtime-flags");
130
137
  const ignore_1 = __importDefault(require("ignore"));
@@ -480,11 +487,16 @@ function hasHarmonyProjectMarkers(rootDir) {
480
487
  function normIndexPath(filePath) {
481
488
  return filePath.replace(/\\/g, '/');
482
489
  }
483
- /** Member calls through a native `.so` binding: `sdk.Asset_setCropRect(`. */
490
+ /** Photos-style member calls: `sdk.Asset_setCropRect(`. */
484
491
  const NAPI_MEMBER_CALL_RE = /\.\s*([A-Z][A-Za-z0-9_]*(?:_[A-Za-z0-9_]+)+)\s*\(/g;
492
+ /** `libFoo.so` module path from ArkAnalyzer import infos. */
493
+ const LIB_SO_MODULE_RE = /^lib[A-Za-z0-9_]+\.so$/;
485
494
  function isNapiSymbolName(name) {
486
495
  return /^[A-Z][A-Za-z0-9_]*_[A-Za-z0-9_]+$/.test(name);
487
496
  }
497
+ function escapeRegExp(s) {
498
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
499
+ }
488
500
  let persistedBatch = null;
489
501
  /** File that triggered the most recent batch persist (for indexAll edge stats). */
490
502
  let batchTriggerFile = null;
@@ -635,6 +647,7 @@ function persistFileResult(queries, filePath, content, stats, result, options) {
635
647
  indexedAt: Date.now(),
636
648
  nodeCount: result.nodes.length,
637
649
  errors: result.errors.length > 0 ? result.errors : undefined,
650
+ generated: (0, generated_detection_1.detectGeneratedFile)(filePath, content),
638
651
  };
639
652
  queries.upsertFile(fileRecord);
640
653
  }
@@ -888,7 +901,9 @@ function runArkTSBatchFullCore(rootDir, queries, triggerFile, etsFiles, batchKey
888
901
  const index = buildArkTSIndex(rootDir, etsFiles);
889
902
  if (index.fileResults.size === 0) {
890
903
  const fatal = index.errors.find((e) => e.severity === 'error');
891
- throw new Error(fatal?.message ?? 'ArkTS batch produced no indexed files');
904
+ const detail = index.errors.map((e) => `[${e.severity}] ${e.message}`).join(' | ');
905
+ throw new Error(fatal?.message ??
906
+ `ArkTS batch produced no indexed files${detail ? ` (${detail})` : ' (no Scene/adapter errors recorded)'}`);
892
907
  }
893
908
  queries.deleteArkTSCrossFileCallEdges();
894
909
  persistBatchResultsSync(rootDir, queries, index);
@@ -1025,7 +1040,9 @@ async function primeArkTSBatch(rootDir, queries, triggerFile, options) {
1025
1040
  const streamed = index.streamedPersistedPaths?.size ?? 0;
1026
1041
  if (index.fileResults.size === 0 && streamed === 0) {
1027
1042
  const fatal = index.errors.find((e) => e.severity === 'error');
1028
- throw new Error(fatal?.message ?? 'ArkTS batch produced no indexed files');
1043
+ const detail = index.errors.map((e) => `[${e.severity}] ${e.message}`).join(' | ');
1044
+ throw new Error(fatal?.message ??
1045
+ `ArkTS batch produced no indexed files${detail ? ` (${detail})` : ' (no Scene/adapter errors recorded)'}`);
1029
1046
  }
1030
1047
  if (!streamModular) {
1031
1048
  queries.deleteArkTSCrossFileCallEdges();
@@ -1198,8 +1215,72 @@ const FIELD_CATEGORY = {
1198
1215
  };
1199
1216
  /** {@link ModifierType.CONST} — not re-exported from the arkanalyzer npm entry. */
1200
1217
  const MODIFIER_CONST = 128;
1201
- function normalizeRelPath(rootDir, absolutePath) {
1202
- return path.relative(rootDir, absolutePath).replace(/\\/g, '/');
1218
+ /** `realpathSync` when the path (or its dirname) exists; otherwise `path.resolve`. */
1219
+ function realpathExisting(p) {
1220
+ try {
1221
+ return fs.realpathSync(p);
1222
+ }
1223
+ catch {
1224
+ try {
1225
+ return path.join(fs.realpathSync(path.dirname(p)), path.basename(p));
1226
+ }
1227
+ catch {
1228
+ return path.resolve(p);
1229
+ }
1230
+ }
1231
+ }
1232
+ function toPosixPath(p) {
1233
+ return p.replace(/\\/g, '/');
1234
+ }
1235
+ /**
1236
+ * If `absolutePath` is under `canonicalRoot`, return the project-relative posix
1237
+ * path; otherwise null. Windows compares case-insensitively.
1238
+ */
1239
+ function tryStripRootPrefix(canonicalRoot, absolutePath) {
1240
+ const root = toPosixPath(canonicalRoot);
1241
+ const abs = toPosixPath(absolutePath);
1242
+ const rootCmp = process.platform === 'win32' ? root.toLowerCase() : root;
1243
+ const absCmp = process.platform === 'win32' ? abs.toLowerCase() : abs;
1244
+ if (absCmp === rootCmp)
1245
+ return '';
1246
+ const prefix = rootCmp.endsWith('/') ? rootCmp : `${rootCmp}/`;
1247
+ if (!absCmp.startsWith(prefix))
1248
+ return null;
1249
+ const sliceFrom = root.endsWith('/') ? root.length : root.length + 1;
1250
+ return abs.slice(sliceFrom);
1251
+ }
1252
+ /**
1253
+ * Project-relative paths for ArkAnalyzer absolutes.
1254
+ *
1255
+ * ArkAnalyzer often returns realpath'd absolutes (macOS: `/private/var/...`),
1256
+ * while HomeGraph's `rootDir` from `mkdtemp` / callers may be unresolved
1257
+ * (`/var/...`). We realpath the root **once**, then strip by prefix on the hot
1258
+ * path; only prefix misses pay a per-path `realpath` (cached for the batch).
1259
+ */
1260
+ function createArkRelPathNormalizer(rootDir) {
1261
+ const canonicalRoot = realpathExisting(rootDir);
1262
+ const cache = new Map();
1263
+ const normalizeOnce = (absolutePath) => {
1264
+ const fast = tryStripRootPrefix(canonicalRoot, absolutePath);
1265
+ if (fast !== null)
1266
+ return fast;
1267
+ const abs = realpathExisting(absolutePath);
1268
+ const after = tryStripRootPrefix(canonicalRoot, abs);
1269
+ if (after !== null)
1270
+ return after;
1271
+ return toPosixPath(path.relative(canonicalRoot, abs));
1272
+ };
1273
+ return {
1274
+ rootDir: canonicalRoot,
1275
+ normalize(absolutePath) {
1276
+ const hit = cache.get(absolutePath);
1277
+ if (hit !== undefined)
1278
+ return hit;
1279
+ const rel = normalizeOnce(absolutePath);
1280
+ cache.set(absolutePath, rel);
1281
+ return rel;
1282
+ },
1283
+ };
1203
1284
  }
1204
1285
  /** Map ArkAnalyzer synthetic files (e.g. @dummyFile) to our virtual indexed path. */
1205
1286
  function resolveArkanalyzerVirtualPath(arkFile) {
@@ -1209,9 +1290,6 @@ function resolveArkanalyzerVirtualPath(arkFile) {
1209
1290
  }
1210
1291
  return null;
1211
1292
  }
1212
- function relPathForArkFile(rootDir, arkFile) {
1213
- return resolveArkanalyzerVirtualPath(arkFile) ?? normalizeRelPath(rootDir, arkFile.getFilePath());
1214
- }
1215
1293
  function buildQualifiedName(parts) {
1216
1294
  return parts.filter(Boolean).join('::');
1217
1295
  }
@@ -1655,6 +1733,52 @@ function stateDecoratorKinds(field) {
1655
1733
  return [];
1656
1734
  return [...decs].map((d) => d.getKind()).filter(Boolean);
1657
1735
  }
1736
+ /** All field decorators with optional string args (Provide('theme') → theme). */
1737
+ function fieldDecoratorEntries(field) {
1738
+ const out = [];
1739
+ const seen = new Set();
1740
+ const push = (kind, arg) => {
1741
+ if (!kind || seen.has(kind))
1742
+ return;
1743
+ seen.add(kind);
1744
+ out.push(arg ? { kind, arg } : { kind });
1745
+ };
1746
+ try {
1747
+ for (const d of field.getDecorators?.() ?? []) {
1748
+ push(d.getKind(), (0, migrate_semantics_1.parseDecoratorArgFromContent)(d.getContent?.() ?? ''));
1749
+ }
1750
+ }
1751
+ catch {
1752
+ /* ignore */
1753
+ }
1754
+ if (out.length === 0) {
1755
+ for (const kind of stateDecoratorKinds(field))
1756
+ push(kind);
1757
+ }
1758
+ return out;
1759
+ }
1760
+ /** Encoded decorators list for node.decorators (bare kinds + Kind@arg). */
1761
+ function fieldDecoratorsEncoded(field) {
1762
+ return (0, migrate_semantics_1.encodeDecoratorEntries)(fieldDecoratorEntries(field));
1763
+ }
1764
+ function modelDecoratorEntries(model) {
1765
+ const out = [];
1766
+ const seen = new Set();
1767
+ try {
1768
+ for (const d of model.getDecorators?.() ?? []) {
1769
+ const kind = d.getKind();
1770
+ if (!kind || seen.has(kind))
1771
+ continue;
1772
+ seen.add(kind);
1773
+ const arg = (0, migrate_semantics_1.parseDecoratorArgFromContent)(d.getContent?.() ?? '');
1774
+ out.push(arg ? { kind, arg } : { kind });
1775
+ }
1776
+ }
1777
+ catch {
1778
+ /* ignore */
1779
+ }
1780
+ return out;
1781
+ }
1658
1782
  /** Child @Prop / @Link — edge `via` uses the decorator name as-is. */
1659
1783
  function stateTransferViaForField(field) {
1660
1784
  for (const kind of stateDecoratorKinds(field)) {
@@ -2001,12 +2125,12 @@ function indexViewTreeForClass(ctx, cls, result, relativePath) {
2001
2125
  if (!buildId)
2002
2126
  return;
2003
2127
  const seen = new Set();
2004
- const link = (source, target, kind, via, line) => {
2128
+ const link = (source, target, kind, via, line, extraMeta) => {
2005
2129
  const key = `${source}>${target}>${kind}>${via}`;
2006
2130
  if (seen.has(key))
2007
2131
  return;
2008
2132
  seen.add(key);
2009
- ctx.addEdge(result, source, target, kind, relativePath, via, line);
2133
+ ctx.addEdge(result, source, target, kind, relativePath, via, line, extraMeta);
2010
2134
  };
2011
2135
  for (const [field] of viewTree.getStateValues()) {
2012
2136
  const fieldId = ctx.resolveFieldNodeId(field);
@@ -2074,32 +2198,62 @@ function indexViewTreeForClass(ctx, cls, result, relativePath) {
2074
2198
  // Skip this node's signature edge; keep walking siblings/children.
2075
2199
  }
2076
2200
  }
2077
- if (node.stateValuesTransfer) {
2078
- for (const [childField, parentValue] of node.stateValuesTransfer) {
2079
- const childFieldId = ctx.resolveFieldNodeId(childField);
2080
- if (parentValue instanceof arkanalyzer_1.ArkField) {
2081
- const via = stateTransferViaForField(childField);
2082
- if (!via || !childFieldId)
2083
- continue;
2084
- ctx.markFieldStateDecorators?.(childField, childFieldId, result);
2085
- const parentFieldId = ctx.resolveFieldNodeId(parentValue);
2086
- if (parentFieldId) {
2087
- ctx.markFieldStateDecorators?.(parentValue, parentFieldId, result);
2088
- link(parentFieldId, childFieldId, 'references', via, childField.getOriginFullPosition()?.getFirstLine() ?? 1);
2089
- }
2201
+ // Data passages for migrate snapshot (+ Prop/Link edges for explore).
2202
+ try {
2203
+ const passages = (0, migrate_passage_1.collectPassagesForViewTreeNode)(node, ctx.scene, cls);
2204
+ for (const p of passages) {
2205
+ const toId = ctx.resolveFieldNodeId(p.toField);
2206
+ if (!toId)
2207
+ continue;
2208
+ ctx.markFieldStateDecorators?.(p.toField, toId, result);
2209
+ let fromId = null;
2210
+ if (p.fromField) {
2211
+ fromId = ctx.resolveFieldNodeId(p.fromField);
2212
+ if (fromId)
2213
+ ctx.markFieldStateDecorators?.(p.fromField, fromId, result);
2090
2214
  }
2091
- else if (parentValue instanceof arkanalyzer_1.ArkMethod) {
2092
- let builderId = ctx.ensureMethodNode(parentValue, relativePath, result, classNodeId);
2093
- if (!builderId) {
2094
- try {
2095
- builderId = ctx.resolveMethodIdBySig(parentValue.getSignature());
2096
- }
2097
- catch {
2098
- builderId = null;
2215
+ if (!fromId && p.fromIsParentComponent)
2216
+ fromId = classNodeId;
2217
+ if (!fromId)
2218
+ continue;
2219
+ const line = p.toField.getOriginFullPosition()?.getFirstLine() ?? 1;
2220
+ link(fromId, toId, 'references', p.via || 'data-passage', line, {
2221
+ passageType: p.passageType,
2222
+ valueType: p.valueType,
2223
+ forcesMigration: p.forcesMigration,
2224
+ parentExpression: p.parentExpression,
2225
+ });
2226
+ }
2227
+ }
2228
+ catch {
2229
+ // Fall back to legacy Prop/Link-only transfer below.
2230
+ if (node.stateValuesTransfer) {
2231
+ for (const [childField, parentValue] of node.stateValuesTransfer) {
2232
+ const childFieldId = ctx.resolveFieldNodeId(childField);
2233
+ if (parentValue instanceof arkanalyzer_1.ArkField) {
2234
+ const via = stateTransferViaForField(childField);
2235
+ if (!via || !childFieldId)
2236
+ continue;
2237
+ ctx.markFieldStateDecorators?.(childField, childFieldId, result);
2238
+ const parentFieldId = ctx.resolveFieldNodeId(parentValue);
2239
+ if (parentFieldId) {
2240
+ ctx.markFieldStateDecorators?.(parentValue, parentFieldId, result);
2241
+ link(parentFieldId, childFieldId, 'references', via, childField.getOriginFullPosition()?.getFirstLine() ?? 1);
2099
2242
  }
2100
2243
  }
2101
- if (childFieldId && builderId) {
2102
- link(builderId, childFieldId, 'references', 'builder-param', childField.getOriginFullPosition()?.getFirstLine() ?? 1);
2244
+ else if (parentValue instanceof arkanalyzer_1.ArkMethod) {
2245
+ let builderId = ctx.ensureMethodNode(parentValue, relativePath, result, classNodeId);
2246
+ if (!builderId) {
2247
+ try {
2248
+ builderId = ctx.resolveMethodIdBySig(parentValue.getSignature());
2249
+ }
2250
+ catch {
2251
+ builderId = null;
2252
+ }
2253
+ }
2254
+ if (childFieldId && builderId) {
2255
+ link(builderId, childFieldId, 'references', 'builder-param', childField.getOriginFullPosition()?.getFirstLine() ?? 1);
2256
+ }
2103
2257
  }
2104
2258
  }
2105
2259
  }
@@ -2126,6 +2280,7 @@ function indexViewTreeForClass(ctx, cls, result, relativePath) {
2126
2280
  }
2127
2281
  class ArkTSAdapter {
2128
2282
  rootDir;
2283
+ pathNorm;
2129
2284
  scanned;
2130
2285
  scene = null;
2131
2286
  methodToId = new Map();
@@ -2155,10 +2310,24 @@ class ArkTSAdapter {
2155
2310
  crossFileEdges = [];
2156
2311
  /** Per-module `@dummyMain@…` created during analyseByModule (for scene aggregator). */
2157
2312
  moduleDummyMains = [];
2313
+ /**
2314
+ * Local import binding names from `lib*.so` per relative file path.
2315
+ * Filled before method bodies are scanned so `multimodalinput.getTidByName(`
2316
+ * can emit NAPI call refs (not only photos-style `Class_method` names).
2317
+ */
2318
+ soImportBindingsByFile = new Map();
2158
2319
  constructor(rootDir, scannedFiles) {
2159
- this.rootDir = rootDir;
2320
+ this.pathNorm = createArkRelPathNormalizer(rootDir);
2321
+ this.rootDir = this.pathNorm.rootDir;
2160
2322
  this.scanned = new Set(scannedFiles);
2161
2323
  }
2324
+ /** ArkAnalyzer absolute → project-relative (shared batch cache). */
2325
+ normalizeRelPath(absolutePath) {
2326
+ return this.pathNorm.normalize(absolutePath);
2327
+ }
2328
+ relPathForArkFile(arkFile) {
2329
+ return resolveArkanalyzerVirtualPath(arkFile) ?? this.normalizeRelPath(arkFile.getFilePath());
2330
+ }
2162
2331
  build(scene) {
2163
2332
  this.scene = scene;
2164
2333
  for (const arkFile of scene.getFiles()) {
@@ -2176,7 +2345,7 @@ class ArkTSAdapter {
2176
2345
  indexModule(module, scene) {
2177
2346
  this.scene = scene;
2178
2347
  const files = [...module.getFilesMap().values()];
2179
- const moduleFiles = new Set(files.map((f) => normalizeRelPath(this.rootDir, f.getFilePath())).filter(Boolean));
2348
+ const moduleFiles = new Set(files.map((f) => this.normalizeRelPath(f.getFilePath())).filter(Boolean));
2180
2349
  for (const arkFile of files) {
2181
2350
  this.indexScannedFile(arkFile);
2182
2351
  }
@@ -2211,7 +2380,7 @@ class ArkTSAdapter {
2211
2380
  continue;
2212
2381
  }
2213
2382
  for (const arkFile of filesMap.values()) {
2214
- const rel = normalizeRelPath(this.rootDir, arkFile.getFilePath());
2383
+ const rel = this.normalizeRelPath(arkFile.getFilePath());
2215
2384
  if (!rel || excludeFiles.has(rel) || this.fileResults.has(rel))
2216
2385
  continue;
2217
2386
  if (!this.scanned.has(rel) || !isArkAnalyzerSourcePath(rel))
@@ -2244,7 +2413,7 @@ class ArkTSAdapter {
2244
2413
  /** Re-attach live Ark* objects to already-indexed HomeGraph node ids (sig maps). */
2245
2414
  bindLiveMapsFromFiles(files) {
2246
2415
  for (const arkFile of files) {
2247
- const relativePath = normalizeRelPath(this.rootDir, arkFile.getFilePath());
2416
+ const relativePath = this.normalizeRelPath(arkFile.getFilePath());
2248
2417
  if (!this.scanned.has(relativePath) || !isArkAnalyzerSourcePath(relativePath))
2249
2418
  continue;
2250
2419
  this.bindLiveClass(arkFile.getDefaultClass());
@@ -2396,13 +2565,13 @@ class ArkTSAdapter {
2396
2565
  !callerArk.isGenerated()) {
2397
2566
  continue;
2398
2567
  }
2399
- const callerFile = (callerArk ? relPathForArkFile(this.rootDir, callerArk.getDeclaringArkFile()) : null) ??
2568
+ const callerFile = (callerArk ? this.relPathForArkFile(callerArk.getDeclaringArkFile()) : null) ??
2400
2569
  (callerSig ? this.relPathFromMethodSignature(callerSig) : null) ??
2401
2570
  '';
2402
2571
  if (moduleFiles && callerFile && !moduleFiles.has(callerFile)) {
2403
2572
  continue;
2404
2573
  }
2405
- const calleeFile = (calleeArk ? relPathForArkFile(this.rootDir, calleeArk.getDeclaringArkFile()) : null) ??
2574
+ const calleeFile = (calleeArk ? this.relPathForArkFile(calleeArk.getDeclaringArkFile()) : null) ??
2406
2575
  (calleeSig ? this.relPathFromMethodSignature(calleeSig) : null);
2407
2576
  const callerId = (callerArk ? this.resolveMethodNodeId(callerArk) : null) ??
2408
2577
  (callerSig ? this.resolveMethodIdBySigExact(callerSig) : null);
@@ -2441,7 +2610,7 @@ class ArkTSAdapter {
2441
2610
  */
2442
2611
  stitchModuleCfgCallsAndRefs(files, _moduleFiles) {
2443
2612
  for (const arkFile of files) {
2444
- const callerFile = normalizeRelPath(this.rootDir, arkFile.getFilePath());
2613
+ const callerFile = this.normalizeRelPath(arkFile.getFilePath());
2445
2614
  const visitClass = (cls) => {
2446
2615
  for (const method of cls.getMethods(true)) {
2447
2616
  if (shouldSkipArkMethod(method) &&
@@ -2612,7 +2781,7 @@ class ArkTSAdapter {
2612
2781
  const fileName = sig.getDeclaringClassSignature().getDeclaringFileSignature().getFileName();
2613
2782
  if (!fileName || fileName === '%unk' || fileName.includes('%unk'))
2614
2783
  return null;
2615
- return normalizeRelPath(this.rootDir, fileName);
2784
+ return this.normalizeRelPath(fileName);
2616
2785
  }
2617
2786
  catch {
2618
2787
  return null;
@@ -2657,7 +2826,7 @@ class ArkTSAdapter {
2657
2826
  return [...this.fileResults.keys()];
2658
2827
  }
2659
2828
  indexScannedFile(arkFile) {
2660
- const relativePath = normalizeRelPath(this.rootDir, arkFile.getFilePath());
2829
+ const relativePath = this.normalizeRelPath(arkFile.getFilePath());
2661
2830
  if (!this.scanned.has(relativePath))
2662
2831
  return;
2663
2832
  if (!isArkAnalyzerSourcePath(relativePath))
@@ -2666,7 +2835,7 @@ class ArkTSAdapter {
2666
2835
  }
2667
2836
  indexViewTreesFromFiles(files) {
2668
2837
  for (const arkFile of files) {
2669
- const relativePath = normalizeRelPath(this.rootDir, arkFile.getFilePath());
2838
+ const relativePath = this.normalizeRelPath(arkFile.getFilePath());
2670
2839
  if (!this.scanned.has(relativePath) || !isArkAnalyzerSourcePath(relativePath))
2671
2840
  continue;
2672
2841
  const result = this.fileResults.get(relativePath);
@@ -2688,6 +2857,70 @@ class ArkTSAdapter {
2688
2857
  }
2689
2858
  }
2690
2859
  }
2860
+ this.indexObservedClassRefs();
2861
+ }
2862
+ /**
2863
+ * State / Param fields whose type names an @Observed / @ObservedV2 class →
2864
+ * `references` via `observed-ref` (arkui-migrate).
2865
+ */
2866
+ indexObservedClassRefs() {
2867
+ const observedByName = new Map();
2868
+ for (const [, result] of this.fileResults) {
2869
+ for (const n of result.nodes) {
2870
+ if (n.kind !== 'class' && n.kind !== 'struct')
2871
+ continue;
2872
+ const decs = n.decorators ?? [];
2873
+ if (!decs.some((d) => migrate_semantics_1.OBSERVATION_DECORATORS.has(d.split('@')[0])))
2874
+ continue;
2875
+ const arr = observedByName.get(n.name) ?? [];
2876
+ arr.push(n.id);
2877
+ observedByName.set(n.name, arr);
2878
+ }
2879
+ }
2880
+ if (observedByName.size === 0)
2881
+ return;
2882
+ for (const [relativePath, result] of this.fileResults) {
2883
+ for (const field of result.nodes) {
2884
+ if (field.kind !== 'property' && field.kind !== 'field')
2885
+ continue;
2886
+ const typeStr = field.signature ?? '';
2887
+ const bare = typeStr.replace(/<.*>/, '').split(/[.\s|]/).filter(Boolean).pop();
2888
+ if (!bare)
2889
+ continue;
2890
+ const targets = observedByName.get(bare);
2891
+ if (!targets || targets.length === 0)
2892
+ continue;
2893
+ // Prefer unique name; if ambiguous, same-file first
2894
+ let targetId = targets[0];
2895
+ if (targets.length > 1) {
2896
+ const sameFile = targets.find((id) => {
2897
+ const n = result.nodes.find((x) => x.id === id);
2898
+ return n?.filePath === relativePath;
2899
+ });
2900
+ if (sameFile)
2901
+ targetId = sameFile;
2902
+ else
2903
+ continue; // ambiguous cross-file — skip (precision > recall)
2904
+ }
2905
+ const edgeKey = `${field.id}\0${targetId}\0observed-ref`;
2906
+ if (this.emittedEdgeKeys.has(edgeKey))
2907
+ continue;
2908
+ this.emittedEdgeKeys.add(edgeKey);
2909
+ const edge = arkEdge(field.id, targetId, 'references', {
2910
+ metadata: {
2911
+ synthesizedBy: 'arkui-migrate',
2912
+ via: 'observed-ref',
2913
+ registeredAt: `${relativePath}:${field.startLine}`,
2914
+ },
2915
+ });
2916
+ if (this.nodeInFile(targetId, relativePath)) {
2917
+ result.edges.push(edge);
2918
+ }
2919
+ else {
2920
+ this.crossFileEdges.push(edge);
2921
+ }
2922
+ }
2923
+ }
2691
2924
  }
2692
2925
  /** Index ArkAnalyzer's in-scene @dummyMain (DummyMainCreater output), not a HomeGraph synthetic node. */
2693
2926
  indexArkanalyzerDummyMain(dummyMain) {
@@ -2726,7 +2959,7 @@ class ArkTSAdapter {
2726
2959
  this.nodeIds.add(node.id);
2727
2960
  }
2728
2961
  indexFile(arkFile) {
2729
- const relativePath = normalizeRelPath(this.rootDir, arkFile.getFilePath());
2962
+ const relativePath = this.normalizeRelPath(arkFile.getFilePath());
2730
2963
  const language = 'arkts';
2731
2964
  let lineCount = 1;
2732
2965
  try {
@@ -2737,6 +2970,8 @@ class ArkTSAdapter {
2737
2970
  }
2738
2971
  const result = this.ensureFileResult(relativePath, lineCount);
2739
2972
  const fileId = `file:${relativePath}`;
2973
+ // Before methods: record `lib*.so` bindings so body NAPI call scan can see them.
2974
+ this.collectSoImportBindings(relativePath, arkFile);
2740
2975
  for (const ns of arkFile.getNamespaces()) {
2741
2976
  this.indexNamespace(relativePath, language, result, ns, fileId);
2742
2977
  }
@@ -2745,6 +2980,59 @@ class ArkTSAdapter {
2745
2980
  this.indexTypeAliases(relativePath, language, result, arkFile, undefined, fileId);
2746
2981
  this.indexModuleLocals(relativePath, language, result, arkFile, undefined, fileId);
2747
2982
  this.indexImports(relativePath, language, result, arkFile, fileId);
2983
+ this.indexStorageApiKeys(relativePath, arkFile, result);
2984
+ }
2985
+ /** AppStorage / LocalStorage / … literal keys → arkui-migrate edges from each component. */
2986
+ indexStorageApiKeys(relativePath, arkFile, result) {
2987
+ let code = '';
2988
+ try {
2989
+ code = arkFile.getCode() ?? '';
2990
+ }
2991
+ catch {
2992
+ return;
2993
+ }
2994
+ if (!code)
2995
+ return;
2996
+ const hits = (0, migrate_semantics_1.scanStorageApiKeys)(code);
2997
+ if (hits.length === 0)
2998
+ return;
2999
+ const componentIds = result.nodes
3000
+ .filter((n) => n.kind === 'component' && n.filePath === relativePath)
3001
+ .map((n) => n.id);
3002
+ if (componentIds.length === 0)
3003
+ return;
3004
+ for (const hit of hits) {
3005
+ const line = (0, migrate_semantics_1.lineOfOffset)(code, hit.index);
3006
+ for (const compId of componentIds) {
3007
+ const edgeKey = `${compId}\0storage-api\0${hit.channel}\0${hit.key}`;
3008
+ if (this.emittedEdgeKeys.has(edgeKey))
3009
+ continue;
3010
+ this.emittedEdgeKeys.add(edgeKey);
3011
+ result.edges.push(arkEdge(compId, compId, 'references', {
3012
+ metadata: {
3013
+ synthesizedBy: 'arkui-migrate',
3014
+ via: 'storage-api',
3015
+ channel: hit.channel,
3016
+ key: hit.key,
3017
+ method: hit.method,
3018
+ registeredAt: `${relativePath}:${line}`,
3019
+ },
3020
+ }));
3021
+ }
3022
+ }
3023
+ }
3024
+ collectSoImportBindings(relativePath, arkFile) {
3025
+ const set = new Set();
3026
+ for (const importInfo of arkFile.getImportInfos()) {
3027
+ const modulePath = importInfo.getFrom();
3028
+ if (!modulePath || !LIB_SO_MODULE_RE.test(modulePath))
3029
+ continue;
3030
+ const clause = importInfo.getImportClauseName();
3031
+ if (clause)
3032
+ set.add(clause);
3033
+ }
3034
+ if (set.size > 0)
3035
+ this.soImportBindingsByFile.set(relativePath, set);
2748
3036
  }
2749
3037
  indexModuleLocals(relativePath, language, result, arkFile, ns, parentId) {
2750
3038
  const declaringClass = ns ? ns.getDefaultClass() : arkFile.getDefaultClass();
@@ -2838,8 +3126,10 @@ class ArkTSAdapter {
2838
3126
  const qn = buildClassQualifiedName(cls);
2839
3127
  const kind = classNodeKind(cls);
2840
3128
  const { line, endLine, col } = linesFromPosition(cls.getOriginFullPosition());
3129
+ const classDecs = (0, migrate_semantics_1.encodeDecoratorEntries)(modelDecoratorEntries(cls));
2841
3130
  const classNode = makeNode(relativePath, language, kind, displayName, qn, line, endLine, col, {
2842
3131
  ...modelModifiersToNodeExtras(cls),
3132
+ ...(classDecs.length > 0 ? { decorators: classDecs } : {}),
2843
3133
  });
2844
3134
  this.addNode(result, classNode);
2845
3135
  this.classToId.set(cls, classNode.id);
@@ -2867,7 +3157,10 @@ class ArkTSAdapter {
2867
3157
  }
2868
3158
  }
2869
3159
  if (cls.hasComponentDecorator()) {
2870
- const componentNode = makeNode(relativePath, language, 'component', displayName, qn, line, endLine, col, { ...modelModifiersToNodeExtras(cls) });
3160
+ const componentNode = makeNode(relativePath, language, 'component', displayName, qn, line, endLine, col, {
3161
+ ...modelModifiersToNodeExtras(cls),
3162
+ ...(classDecs.length > 0 ? { decorators: classDecs } : {}),
3163
+ });
2871
3164
  this.addNode(result, componentNode);
2872
3165
  this.componentToId.set(cls, componentNode.id);
2873
3166
  try {
@@ -2953,10 +3246,10 @@ class ArkTSAdapter {
2953
3246
  return;
2954
3247
  const { line, endLine, col } = linesFromPosition(field.getOriginFullPosition());
2955
3248
  const qn = buildFieldQualifiedName(field);
2956
- const stateDecs = stateDecoratorKinds(field);
3249
+ const encodedDecs = fieldDecoratorsEncoded(field);
2957
3250
  const fieldNode = makeNode(relativePath, language, kind, name, qn, line, endLine, col, {
2958
3251
  signature: field.getType()?.toString(),
2959
- ...(stateDecs.length > 0 ? { decorators: stateDecs } : {}),
3252
+ ...(encodedDecs.length > 0 ? { decorators: encodedDecs } : {}),
2960
3253
  ...modelModifiersToNodeExtras(field),
2961
3254
  });
2962
3255
  this.addNode(result, fieldNode);
@@ -2982,19 +3275,19 @@ class ArkTSAdapter {
2982
3275
  classToId: this.classToId,
2983
3276
  fieldToId: this.fieldToId,
2984
3277
  nodeIds: this.nodeIds,
2985
- addEdge: (result, source, target, kind, callerFile, via, line) => this.addViewTreeEdge(result, source, target, kind, callerFile, via, line),
3278
+ addEdge: (result, source, target, kind, callerFile, via, line, extraMeta) => this.addViewTreeEdge(result, source, target, kind, callerFile, via, line, extraMeta),
2986
3279
  ensureMethodNode: (method, relativePath, result, parentId) => this.ensureMethodNode(method, relativePath, result, parentId),
2987
3280
  resolveClassNodeId: (cls) => this.resolveClassNodeId(cls),
2988
3281
  resolveClassIdBySig: (sig, importer) => this.resolveClassIdBySig(sig, importer),
2989
3282
  resolveMethodIdBySig: (sig, importer) => this.resolveMethodIdBySig(sig, importer),
2990
3283
  resolveFieldNodeId: (field) => this.resolveFieldNodeId(field),
2991
3284
  markFieldStateDecorators: (field, fieldNodeId, result) => {
2992
- const kinds = stateDecoratorKinds(field);
2993
- if (kinds.length === 0)
3285
+ const encoded = fieldDecoratorsEncoded(field);
3286
+ if (encoded.length === 0)
2994
3287
  return;
2995
3288
  const node = result.nodes.find((n) => n.id === fieldNodeId);
2996
3289
  if (node)
2997
- node.decorators = kinds;
3290
+ node.decorators = encoded;
2998
3291
  },
2999
3292
  };
3000
3293
  }
@@ -3058,7 +3351,7 @@ class ArkTSAdapter {
3058
3351
  // prefer ids from the importer's file before treating the name as ambiguous.
3059
3352
  if (importer) {
3060
3353
  try {
3061
- const importerPath = normalizeRelPath(this.rootDir, importer.getDeclaringArkFile().getFilePath());
3354
+ const importerPath = this.normalizeRelPath(importer.getDeclaringArkFile().getFilePath());
3062
3355
  const sameFile = candidates.filter((id) => this.classIdFilePath.get(id) === importerPath);
3063
3356
  if (sameFile.length > 0)
3064
3357
  candidates = sameFile;
@@ -3201,7 +3494,7 @@ class ArkTSAdapter {
3201
3494
  if (!displayName)
3202
3495
  return null;
3203
3496
  const arkFile = method.getDeclaringArkFile();
3204
- const relativePath = normalizeRelPath(this.rootDir, arkFile.getFilePath());
3497
+ const relativePath = this.normalizeRelPath(arkFile.getFilePath());
3205
3498
  if (!this.scanned.has(relativePath) || !isArkAnalyzerSourcePath(relativePath))
3206
3499
  return null;
3207
3500
  let lineCount = 1;
@@ -3218,7 +3511,7 @@ class ArkTSAdapter {
3218
3511
  this.indexMethod(relativePath, 'arkts', result, method, parentId, kind, displayName);
3219
3512
  return this.methodToId.get(method) ?? null;
3220
3513
  }
3221
- addViewTreeEdge(result, source, target, kind, callerFile, via, line) {
3514
+ addViewTreeEdge(result, source, target, kind, callerFile, via, line, extraMeta) {
3222
3515
  const edgeKey = `${source}\0${target}\0${kind}\0${via}`;
3223
3516
  if (this.emittedEdgeKeys.has(edgeKey))
3224
3517
  return;
@@ -3228,6 +3521,7 @@ class ArkTSAdapter {
3228
3521
  synthesizedBy: 'viewtree',
3229
3522
  via,
3230
3523
  registeredAt: `${callerFile}:${line}`,
3524
+ ...(extraMeta ?? {}),
3231
3525
  },
3232
3526
  });
3233
3527
  if (this.nodeInFile(target, callerFile)) {
@@ -3305,17 +3599,14 @@ class ArkTSAdapter {
3305
3599
  return;
3306
3600
  const baseLine = method.getImplOriginFullPosition()?.getFirstLine() ?? 1;
3307
3601
  const seenAtLine = new Set();
3308
- NAPI_MEMBER_CALL_RE.lastIndex = 0;
3309
- let m;
3310
- while ((m = NAPI_MEMBER_CALL_RE.exec(code)) !== null) {
3311
- const symbol = m[1];
3312
- if (!isNapiSymbolName(symbol))
3313
- continue;
3314
- const lineOffset = code.slice(0, m.index).split('\n').length - 1;
3602
+ const pushCall = (symbol, matchIndex) => {
3603
+ if (!symbol)
3604
+ return;
3605
+ const lineOffset = code.slice(0, matchIndex).split('\n').length - 1;
3315
3606
  const line = baseLine + lineOffset;
3316
3607
  const dedupKey = `${symbol}:${line}`;
3317
3608
  if (seenAtLine.has(dedupKey))
3318
- continue;
3609
+ return;
3319
3610
  seenAtLine.add(dedupKey);
3320
3611
  result.unresolvedReferences.push({
3321
3612
  fromNodeId,
@@ -3326,6 +3617,35 @@ class ArkTSAdapter {
3326
3617
  filePath: relativePath,
3327
3618
  language,
3328
3619
  });
3620
+ };
3621
+ // Photos-style `Class_method` on any receiver.
3622
+ NAPI_MEMBER_CALL_RE.lastIndex = 0;
3623
+ let m;
3624
+ while ((m = NAPI_MEMBER_CALL_RE.exec(code)) !== null) {
3625
+ const symbol = m[1];
3626
+ if (!isNapiSymbolName(symbol))
3627
+ continue;
3628
+ pushCall(symbol, m.index);
3629
+ }
3630
+ // `import x from 'libFoo.so'` → `x.draw(` / `x.getTidByName(` (camelCase OK).
3631
+ const soBindings = this.soImportBindingsByFile.get(relativePath);
3632
+ if (soBindings) {
3633
+ for (const binding of soBindings) {
3634
+ const re = new RegExp(`\\b${escapeRegExp(binding)}\\s*\\.\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*\\(`, 'g');
3635
+ while ((m = re.exec(code)) !== null) {
3636
+ pushCall(m[1], m.index);
3637
+ }
3638
+ }
3639
+ // define_class / instance path: `this.inst?.addVerticalRuler(` — receiver is
3640
+ // not the import binding. Only in files that already import a lib*.so.
3641
+ const anyMemberRe = /\?\.\s*([A-Za-z_][A-Za-z0-9_]*)\s*\(|\.\s*([A-Za-z_][A-Za-z0-9_]*)\s*\(/g;
3642
+ while ((m = anyMemberRe.exec(code)) !== null) {
3643
+ const symbol = m[1] ?? m[2];
3644
+ if (!symbol)
3645
+ continue;
3646
+ // Skip photos-style names already handled above; keep camelCase / plain ids.
3647
+ pushCall(symbol, m.index);
3648
+ }
3329
3649
  }
3330
3650
  }
3331
3651
  indexImports(relativePath, language, result, arkFile, fileId) {
@@ -3340,7 +3660,7 @@ class ArkTSAdapter {
3340
3660
  if (exportInfo) {
3341
3661
  const targetFile = exportInfo.getDeclaringArkFile();
3342
3662
  if (targetFile) {
3343
- const targetRel = normalizeRelPath(this.rootDir, targetFile.getFilePath());
3663
+ const targetRel = this.normalizeRelPath(targetFile.getFilePath());
3344
3664
  const targetFileId = `file:${targetRel}`;
3345
3665
  if (this.nodeIds.has(targetFileId)) {
3346
3666
  result.edges.push(arkEdge(importNode.id, targetFileId, 'imports', {
@@ -3481,7 +3801,7 @@ function buildArkTSIndexByModuleInner(rootDir, scannedFiles, options) {
3481
3801
  }
3482
3802
  scene.analyseByModule((module, scn) => {
3483
3803
  const moduleFileRels = [...module.getFilesMap().values()]
3484
- .map((f) => normalizeRelPath(rootDir, f.getFilePath()))
3804
+ .map((f) => adapter.normalizeRelPath(f.getFilePath()))
3485
3805
  .filter((p) => p && isArkAnalyzerSourcePath(p))
3486
3806
  .map(normIndexPath);
3487
3807
  adapter.indexModule(module, scn);
@@ -4237,13 +4557,81 @@ function attachOhosApiDbBinding(queries, binding) {
4237
4557
  };
4238
4558
  }
4239
4559
  }
4560
+ /** Last-resort prebuilt npm package when the project's API version cannot be obtained. */
4561
+ const OHOS_API_FALLBACK_VERSION = '6.1.0';
4562
+ function runNpmPack(args, cwd) {
4563
+ const inv = process.platform === 'win32'
4564
+ ? { cmd: 'cmd.exe', args: ['/d', '/s', '/c', ['npm', ...args].join(' ')] }
4565
+ : { cmd: 'npm', args };
4566
+ (0, child_process_1.execFileSync)(inv.cmd, inv.args, {
4567
+ cwd,
4568
+ stdio: 'pipe',
4569
+ timeout: 180_000,
4570
+ windowsHide: true,
4571
+ env: process.env,
4572
+ });
4573
+ }
4240
4574
  /**
4241
- * Build (or reuse) ~/.homegraph/api/ohos-api-<version>.db from a local SDK.
4242
- * Never throws missing SDK / build failure returns a warning.
4575
+ * Fetch `homegraph-ohos-api-db-<version>` from npm and copy the db into
4576
+ * `~/.homegraph/api/`. Returns null when downloads are disabled, the package
4577
+ * is missing, or install fails. Opt out: HOMEGRAPH_OHOS_API_NO_DOWNLOAD=1.
4578
+ */
4579
+ function tryDownloadOhosApiDbFromNpm(version) {
4580
+ if (process.env.HOMEGRAPH_OHOS_API_NO_DOWNLOAD === '1')
4581
+ return null;
4582
+ const dbPath = ohosApiDbInstallPath(version);
4583
+ const packageName = ohosApiDbPackageName(version);
4584
+ if (fs.existsSync(dbPath)) {
4585
+ return { version, dbPath, packageName, installed: false };
4586
+ }
4587
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'hg-ohos-api-npm-'));
4588
+ try {
4589
+ runNpmPack(['pack', packageName, '--silent'], tmp);
4590
+ const tgz = fs.readdirSync(tmp).find((f) => f.endsWith('.tgz'));
4591
+ if (!tgz)
4592
+ return null;
4593
+ const extractDir = path.join(tmp, 'extract');
4594
+ fs.mkdirSync(extractDir, { recursive: true });
4595
+ (0, child_process_1.execFileSync)('tar', ['-xzf', path.join(tmp, tgz), '-C', extractDir], {
4596
+ stdio: 'pipe',
4597
+ timeout: 60_000,
4598
+ windowsHide: true,
4599
+ });
4600
+ const dbName = ohosApiDbFilename(version);
4601
+ const src = [
4602
+ path.join(extractDir, 'package', dbName),
4603
+ path.join(extractDir, dbName),
4604
+ ].find((p) => fs.existsSync(p)) ?? null;
4605
+ if (!src)
4606
+ return null;
4607
+ fs.mkdirSync(path.dirname(dbPath), { recursive: true });
4608
+ fs.copyFileSync(src, dbPath);
4609
+ return { version, dbPath, packageName, installed: true };
4610
+ }
4611
+ catch {
4612
+ return null;
4613
+ }
4614
+ finally {
4615
+ try {
4616
+ fs.rmSync(tmp, { recursive: true, force: true });
4617
+ }
4618
+ catch {
4619
+ /* ignore */
4620
+ }
4621
+ }
4622
+ }
4623
+ /**
4624
+ * Build (or reuse) ~/.homegraph/api/ohos-api-<version>.db from a local SDK,
4625
+ * then npm prebuilts. Never throws — missing SDK / build / download failure
4626
+ * returns a warning.
4627
+ *
4628
+ * Order: existing db → local SDK build → npm pack for that version →
4629
+ * npm pack homegraph-ohos-api-db-6.1.0 fallback.
4243
4630
  */
4244
4631
  async function ensureOhosApiDb(version, options = {}) {
4245
4632
  const dbPath = ohosApiDbInstallPath(version);
4246
4633
  const packageName = ohosApiDbPackageName(version);
4634
+ // 1. Local db for the requested version
4247
4635
  if (fs.existsSync(dbPath)) {
4248
4636
  return { version, dbPath, packageName, installed: false };
4249
4637
  }
@@ -4255,6 +4643,7 @@ async function ensureOhosApiDb(version, options = {}) {
4255
4643
  'Explore will use project code only.',
4256
4644
  };
4257
4645
  }
4646
+ // 2. Build from a local DevEco / OpenHarmony SDK
4258
4647
  let sdkHome = null;
4259
4648
  if (options.sdkHomeHint) {
4260
4649
  const hintHomes = expandLocalOhosSdkHomes(options.sdkHomeHint);
@@ -4265,80 +4654,87 @@ async function ensureOhosApiDb(version, options = {}) {
4265
4654
  if (!sdkHome) {
4266
4655
  sdkHome = findLocalOhosSdkForVersion(version)?.sdkHome ?? null;
4267
4656
  }
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
- }
4657
+ if (sdkHome) {
4658
+ fs.mkdirSync(path.dirname(dbPath), { recursive: true });
4319
4659
  options.onProgress?.({
4320
4660
  phase: 'ohos-api',
4321
- current: 1,
4661
+ current: 0,
4322
4662
  total: 1,
4323
4663
  currentFile: version,
4324
4664
  });
4325
- return { version, dbPath, packageName, installed: true };
4326
- }
4327
- catch (err) {
4328
4665
  try {
4329
- if (fs.existsSync(dbPath))
4666
+ const result = await indexOhosApiDb({
4667
+ sdkHome,
4668
+ version,
4669
+ outputPath: dbPath,
4670
+ onProgress: options.onProgress
4671
+ ? (progress) => {
4672
+ options.onProgress?.({
4673
+ phase: 'ohos-api',
4674
+ current: progress.current,
4675
+ total: progress.total || 1,
4676
+ currentFile: progress.currentFile
4677
+ ? `${version} ${path.basename(progress.currentFile)}`
4678
+ : version,
4679
+ subphase: progress.subphase,
4680
+ });
4681
+ }
4682
+ : undefined,
4683
+ });
4684
+ if (result.success && fs.existsSync(dbPath)) {
4685
+ options.onProgress?.({
4686
+ phase: 'ohos-api',
4687
+ current: 1,
4688
+ total: 1,
4689
+ currentFile: version,
4690
+ });
4691
+ return { version, dbPath, packageName, installed: true };
4692
+ }
4693
+ try {
4330
4694
  fs.unlinkSync(dbPath);
4695
+ }
4696
+ catch {
4697
+ /* ignore */
4698
+ }
4331
4699
  }
4332
4700
  catch {
4333
- /* ignore */
4701
+ try {
4702
+ if (fs.existsSync(dbPath))
4703
+ fs.unlinkSync(dbPath);
4704
+ }
4705
+ catch {
4706
+ /* ignore */
4707
+ }
4334
4708
  }
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
4709
  }
4710
+ // 3. Download prebuilt for the requested version from npm
4711
+ const fromNpm = tryDownloadOhosApiDbFromNpm(version);
4712
+ if (fromNpm)
4713
+ return fromNpm;
4714
+ // 4. Last resort: homegraph-ohos-api-db-6.1.0
4715
+ if (version !== OHOS_API_FALLBACK_VERSION) {
4716
+ const fallbackPath = ohosApiDbInstallPath(OHOS_API_FALLBACK_VERSION);
4717
+ if (fs.existsSync(fallbackPath)) {
4718
+ return {
4719
+ version: OHOS_API_FALLBACK_VERSION,
4720
+ dbPath: fallbackPath,
4721
+ packageName: ohosApiDbPackageName(OHOS_API_FALLBACK_VERSION),
4722
+ installed: false,
4723
+ };
4724
+ }
4725
+ const fallback = tryDownloadOhosApiDbFromNpm(OHOS_API_FALLBACK_VERSION);
4726
+ if (fallback)
4727
+ return fallback;
4728
+ }
4729
+ return {
4730
+ code: 'ohos_api_sdk_missing',
4731
+ message: `OHOS API db for ${version} unavailable (no local SDK build, npm ${packageName} failed` +
4732
+ (version !== OHOS_API_FALLBACK_VERSION
4733
+ ? `, and fallback ${ohosApiDbPackageName(OHOS_API_FALLBACK_VERSION)} failed`
4734
+ : '') +
4735
+ '). Set OHOS_SDK_HOME / DEVECO_SDK_HOME or install the matching DevEco SDK. ' +
4736
+ 'SDK API lookup disabled; project indexing continues normally.',
4737
+ };
4342
4738
  }
4343
4739
  /**
4344
4740
  * Attach a prebuilt API db if present — never builds. Used on open / wireLayers.