@harmonyos-arkts/droid2hmos 0.0.7 → 0.0.9

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 (42) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +249 -251
  3. package/config/preflight-checklist.json +65 -65
  4. package/dist/cli.js +1827 -745
  5. package/dist/cli.js.map +1 -1
  6. package/package.json +36 -36
  7. package/templates/ohos/AppScope/app.json5 +10 -10
  8. package/templates/ohos/AppScope/resources/base/element/string.json +8 -8
  9. package/templates/ohos/AppScope/resources/base/media/layered_image.json +6 -6
  10. package/templates/ohos/build-profile.json5 +41 -41
  11. package/templates/ohos/code-linter.json5 +31 -31
  12. package/templates/ohos/entry/build-profile.json5 +32 -32
  13. package/templates/ohos/entry/hvigorfile.ts +5 -5
  14. package/templates/ohos/entry/obfuscation-rules.txt +22 -22
  15. package/templates/ohos/entry/oh-package.json5 +10 -10
  16. package/templates/ohos/entry/src/main/ets/entryability/EntryAbility.ets +47 -47
  17. package/templates/ohos/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets +15 -15
  18. package/templates/ohos/entry/src/main/ets/pages/Index.ets +22 -22
  19. package/templates/ohos/entry/src/main/module.json5 +49 -49
  20. package/templates/ohos/entry/src/main/resources/base/element/color.json +7 -7
  21. package/templates/ohos/entry/src/main/resources/base/element/float.json +8 -8
  22. package/templates/ohos/entry/src/main/resources/base/element/string.json +15 -15
  23. package/templates/ohos/entry/src/main/resources/base/media/layered_image.json +6 -6
  24. package/templates/ohos/entry/src/main/resources/base/profile/backup_config.json +2 -2
  25. package/templates/ohos/entry/src/main/resources/base/profile/main_pages.json +5 -5
  26. package/templates/ohos/entry/src/main/resources/dark/element/color.json +7 -7
  27. package/templates/ohos/entry/src/mock/mock-config.json5 +1 -1
  28. package/templates/ohos/entry/src/ohosTest/ets/test/Ability.test.ets +34 -34
  29. package/templates/ohos/entry/src/ohosTest/ets/test/List.test.ets +4 -4
  30. package/templates/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets +48 -48
  31. package/templates/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets +14 -14
  32. package/templates/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ets +41 -41
  33. package/templates/ohos/entry/src/ohosTest/module.json5 +25 -25
  34. package/templates/ohos/entry/src/ohosTest/resources/base/element/color.json +7 -7
  35. package/templates/ohos/entry/src/ohosTest/resources/base/element/string.json +12 -12
  36. package/templates/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json +5 -5
  37. package/templates/ohos/entry/src/test/List.test.ets +4 -4
  38. package/templates/ohos/entry/src/test/LocalUnit.test.ets +32 -32
  39. package/templates/ohos/hvigor/hvigor-config.json5 +23 -23
  40. package/templates/ohos/hvigorfile.ts +5 -5
  41. package/templates/ohos/oh-package-lock.json5 +27 -27
  42. package/templates/ohos/oh-package.json5 +10 -10
package/dist/cli.js CHANGED
@@ -12,19 +12,19 @@ function quoteWinArg(arg) {
12
12
  if (!/[\s"&|<>()^%]/.test(arg)) return arg;
13
13
  return `"${arg.replace(/"/g, '""')}"`;
14
14
  }
15
- function windowsCommandLine(command3, args) {
16
- return [command3, ...args].map((value) => quoteWinArg(String(value))).join(" ");
15
+ function windowsCommandLine(command2, args) {
16
+ return [command2, ...args].map((value) => quoteWinArg(String(value))).join(" ");
17
17
  }
18
- function resolveSpawnCommand(command3, args, platform = process.platform) {
19
- if (platform !== "win32") return { command: command3, args: [...args], shell: false };
20
- return { command: windowsCommandLine(command3, args), args: [], shell: true };
18
+ function resolveSpawnCommand(command2, args, platform = process.platform) {
19
+ if (platform !== "win32") return { command: command2, args: [...args], shell: false };
20
+ return { command: windowsCommandLine(command2, args), args: [], shell: true };
21
21
  }
22
- function spawnCommandSync(command3, args, options = {}) {
23
- const invocation = resolveSpawnCommand(command3, args);
22
+ function spawnCommandSync(command2, args, options = {}) {
23
+ const invocation = resolveSpawnCommand(command2, args);
24
24
  return nodeSpawnSync(invocation.command, invocation.args, { ...options, shell: invocation.shell });
25
25
  }
26
- function spawnCommand(command3, args, options = {}) {
27
- const invocation = resolveSpawnCommand(command3, args);
26
+ function spawnCommand(command2, args, options = {}) {
27
+ const invocation = resolveSpawnCommand(command2, args);
28
28
  return nodeSpawn(invocation.command, invocation.args, { ...options, shell: invocation.shell });
29
29
  }
30
30
 
@@ -110,7 +110,7 @@ function checkAndroidBuild(argv) {
110
110
  present,
111
111
  kind: "android-apk-inventory",
112
112
  status,
113
- summary: present ? `\u53D1\u73B0 ${artifacts.length} \u4E2A APK \u6784\u5EFA\u4EA7\u7269\uFF0C\u53EF\u7528\u4E8E Android/HarmonyOS \u53CC\u7AEF\u8FD0\u884C\u5BF9\u7167` : "\u672A\u53D1\u73B0 APK \u6784\u5EFA\u4EA7\u7269\uFF08\u8FC1\u79FB\u4E0D\u4F9D\u8D56 Android \u7F16\u8BD1\uFF09\uFF1B\u53CC\u7AEF\u9875\u9762\u5BF9\u7167\u5C06\u4EE5\u6E90\u7801\u4E0E\u9759\u6001 facts \u4E3A\u57FA\u51C6",
113
+ summary: present ? `\u53D1\u73B0 ${artifacts.length} \u4E2A APK \u6784\u5EFA\u4EA7\u7269\uFF08\u4EC5\u4F5C\u53C2\u8003\u4FE1\u606F\uFF09` : "\u672A\u53D1\u73B0 APK \u6784\u5EFA\u4EA7\u7269\uFF08\u8FC1\u79FB\u4E0D\u4F9D\u8D56 Android \u7F16\u8BD1\uFF09",
114
114
  artifacts: artifacts.length,
115
115
  report: reportPath
116
116
  };
@@ -138,8 +138,8 @@ function sourceRoots(projectRootArgument) {
138
138
  const projectRoot = path3.resolve(projectRootArgument);
139
139
  const config = readJson(path3.join(migrationDirectory(projectRoot), "config.json"));
140
140
  const androidRoot = path3.resolve(projectRoot, config?.androidProject ?? projectRoot);
141
- const harmonyRoot = path3.resolve(projectRoot, config?.harmonyProject ?? "ohos");
142
- return { projectRoot, androidRoot, harmonyRoot };
141
+ const harmonyRoot2 = path3.resolve(projectRoot, config?.harmonyProject ?? "ohos");
142
+ return { projectRoot, androidRoot, harmonyRoot: harmonyRoot2 };
143
143
  }
144
144
  function inside(root, absolute) {
145
145
  const relative2 = path3.relative(root, absolute);
@@ -147,10 +147,10 @@ function inside(root, absolute) {
147
147
  }
148
148
  function sourceFile(projectRoot, value) {
149
149
  if (typeof value !== "string" || !value.trim()) throw new Error("Android source path must be a non-empty string");
150
- const { androidRoot, harmonyRoot } = sourceRoots(projectRoot);
150
+ const { androidRoot, harmonyRoot: harmonyRoot2 } = sourceRoots(projectRoot);
151
151
  const absolute = path3.resolve(androidRoot, value);
152
152
  if (!inside(androidRoot, absolute)) throw new Error(`Android source must stay inside the managed Android project: ${value}`);
153
- if (inside(harmonyRoot, absolute)) throw new Error(`Android source cannot point into the HarmonyOS project: ${value}`);
153
+ if (inside(harmonyRoot2, absolute)) throw new Error(`Android source cannot point into the HarmonyOS project: ${value}`);
154
154
  if (!fs3.existsSync(absolute) || !fs3.statSync(absolute).isFile()) throw new Error(`Android source file not found: ${value}`);
155
155
  const realRoot = fs3.realpathSync(androidRoot);
156
156
  const realFile = fs3.realpathSync(absolute);
@@ -161,12 +161,12 @@ function sourceFile(projectRoot, value) {
161
161
  }
162
162
  function managedHarmonyFile(projectRootArgument, value) {
163
163
  if (typeof value !== "string" || !value.trim()) throw new Error("HarmonyOS path must be a non-empty string");
164
- const { projectRoot, harmonyRoot } = sourceRoots(projectRootArgument);
164
+ const { projectRoot, harmonyRoot: harmonyRoot2 } = sourceRoots(projectRootArgument);
165
165
  const absolute = path3.resolve(projectRoot, value);
166
- if (!inside(harmonyRoot, absolute)) throw new Error(`HarmonyOS file must stay inside the configured HarmonyOS project: ${value}`);
166
+ if (!inside(harmonyRoot2, absolute)) throw new Error(`HarmonyOS file must stay inside the configured HarmonyOS project: ${value}`);
167
167
  if (!fs3.existsSync(absolute) || !fs3.statSync(absolute).isFile()) throw new Error(`HarmonyOS file not found: ${value}`);
168
- if (!fs3.existsSync(harmonyRoot) || !fs3.statSync(harmonyRoot).isDirectory()) throw new Error(`Configured HarmonyOS project not found: ${harmonyRoot}`);
169
- const realRoot = fs3.realpathSync(harmonyRoot);
168
+ if (!fs3.existsSync(harmonyRoot2) || !fs3.statSync(harmonyRoot2).isDirectory()) throw new Error(`Configured HarmonyOS project not found: ${harmonyRoot2}`);
169
+ const realRoot = fs3.realpathSync(harmonyRoot2);
170
170
  const realFile = fs3.realpathSync(absolute);
171
171
  if (!inside(realRoot, realFile)) throw new Error(`HarmonyOS file resolves outside the configured HarmonyOS project: ${value}`);
172
172
  return { absolute, relative: path3.relative(projectRoot, absolute).replaceAll("\\", "/") };
@@ -183,14 +183,14 @@ function provenanceForFiles(projectRoot, values) {
183
183
  return [...result.values()].sort((left, right) => left.androidFile.localeCompare(right.androidFile));
184
184
  }
185
185
  function managedSourceManifest(projectRootArgument) {
186
- const { projectRoot, androidRoot, harmonyRoot } = sourceRoots(projectRootArgument);
186
+ const { projectRoot, androidRoot, harmonyRoot: harmonyRoot2 } = sourceRoots(projectRootArgument);
187
187
  if (!fs3.existsSync(androidRoot) || !fs3.statSync(androidRoot).isDirectory()) throw new Error(`Managed Android project not found: ${androidRoot}`);
188
188
  const members = [];
189
189
  const visit = (directory) => {
190
190
  for (const entry of fs3.readdirSync(directory, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name))) {
191
191
  if (ignoredSourceParts.has(entry.name)) continue;
192
192
  const absolute = path3.join(directory, entry.name);
193
- if (inside(harmonyRoot, absolute)) continue;
193
+ if (inside(harmonyRoot2, absolute)) continue;
194
194
  if (entry.isSymbolicLink()) continue;
195
195
  if (entry.isDirectory()) visit(absolute);
196
196
  else if (entry.isFile()) {
@@ -387,7 +387,9 @@ function auditSourceOrder(projectRootArgument, items, options = {}) {
387
387
  }
388
388
  function auditSourceClosure(projectRootArgument, items) {
389
389
  const projectRoot = path3.resolve(projectRootArgument);
390
- const graph = readJson(path3.join(migrationDirectory(projectRoot), "tasks", "graph.json"));
390
+ const tasksRoot = path3.join(migrationDirectory(projectRoot), "tasks");
391
+ const executionGraph = path3.join(tasksRoot, "execution-graph.json");
392
+ const graph = readJson(fs3.existsSync(executionGraph) ? executionGraph : path3.join(tasksRoot, "graph.json"));
391
393
  const auditable = items.filter((item) => ["implemented", "verified"].includes(item?.status) && item?.kind !== "feature");
392
394
  if (!graph || !Array.isArray(graph.tasks)) return { auditVersion: "1.0.0", status: "unavailable", auditedRefs: [], findings: ["Task Plan source closure is unavailable; sourceProvenance hashes remain enforceable"] };
393
395
  const findings = [];
@@ -496,7 +498,7 @@ function initMigrationReport(argv) {
496
498
  const fail = (message) => {
497
499
  throw new Error(message);
498
500
  };
499
- const readJson8 = (file) => JSON.parse(fs4.readFileSync(file, "utf8"));
501
+ const readJson9 = (file) => JSON.parse(fs4.readFileSync(file, "utf8"));
500
502
  const writeJson2 = (file, value) => {
501
503
  fs4.mkdirSync(path4.dirname(file), { recursive: true });
502
504
  fs4.writeFileSync(file, `${JSON.stringify(value, null, 2)}
@@ -510,7 +512,7 @@ function initMigrationReport(argv) {
510
512
  const relReport = (file) => path4.relative(reportRoot, file).replaceAll("\\", "/");
511
513
  const relProject = (file) => path4.relative(workspaceRoot, file).replaceAll("\\", "/");
512
514
  const reportItemValues = (document) => [document.page, ...document.controls ?? [], ...document.entries ?? [], ...document.interactions ?? [], ...document.items ?? []].filter(Boolean);
513
- const confinedReportFile = (relative2) => {
515
+ const confinedReportFile2 = (relative2) => {
514
516
  if (typeof relative2 !== "string" || !relative2.trim() || path4.isAbsolute(relative2)) fail(`Legacy report shard path must be report-relative: ${String(relative2)}`);
515
517
  const absolute = path4.resolve(reportRoot, relative2);
516
518
  const relation = path4.relative(reportRoot, absolute);
@@ -523,18 +525,18 @@ function initMigrationReport(argv) {
523
525
  return realFile;
524
526
  };
525
527
  if (!fs4.existsSync(indexPath)) fail(`Deterministic SPEC index not found: ${indexPath}`);
526
- const specIndex = readJson8(indexPath);
528
+ const specIndex = readJson9(indexPath);
527
529
  if (specIndex.shardFormatVersion !== "2.0.0" || !specIndex.files?.facts) fail("report init requires deterministic SPEC facts in shardFormatVersion 2.0.0");
528
530
  if (!specIndex.factsSha256) fail("SPEC index does not contain factsSha256");
529
531
  const specFile = (file) => path4.join(specRoot, file);
530
532
  const files = specIndex.files.facts;
531
- const appFacts = readJson8(specFile(files.app));
532
- const pageIndex = readJson8(specFile(files.pages));
533
- const interactionFacts = readJson8(specFile(files.interactions));
534
- const unresolvedFacts = readJson8(specFile(files.unresolved));
533
+ const appFacts = readJson9(specFile(files.app));
534
+ const pageIndex = readJson9(specFile(files.pages));
535
+ const interactionFacts = readJson9(specFile(files.interactions));
536
+ const unresolvedFacts = readJson9(specFile(files.unresolved));
535
537
  const reportIndexPath = path4.join(reportRoot, "index.json");
536
538
  if (fs4.existsSync(reportIndexPath)) {
537
- const previous = readJson8(reportIndexPath);
539
+ const previous = readJson9(reportIndexPath);
538
540
  if (previous.baseline?.factsSha256 !== specIndex.factsSha256) fail(`Existing report targets facts ${previous.baseline?.factsSha256 ?? "<unknown>"}; current facts are ${specIndex.factsSha256}. Remove .migration/report or preserve it under another run.`);
539
541
  if (previous.reportVersion === "1.1.0") {
540
542
  const shardFiles = [
@@ -543,10 +545,10 @@ function initMigrationReport(argv) {
543
545
  previous.interactionsFile,
544
546
  previous.discoveriesFile
545
547
  ].filter(Boolean);
546
- const migrationTargets = shardFiles.map((relative2) => confinedReportFile(relative2));
548
+ const migrationTargets = shardFiles.map((relative2) => confinedReportFile2(relative2));
547
549
  let migratedFiles = 0;
548
550
  for (const file of migrationTargets) {
549
- const document = readJson8(file);
551
+ const document = readJson9(file);
550
552
  for (const value of reportItemValues(document)) if (!Array.isArray(value.sourceProvenance)) value.sourceProvenance = [];
551
553
  if (document.reportShardVersion === "1.1.0") document.reportShardVersion = REPORT_VERSION;
552
554
  writeJson2(file, document);
@@ -579,7 +581,7 @@ function initMigrationReport(argv) {
579
581
  const pages = [];
580
582
  for (const descriptor of pageIndex.pages ?? []) {
581
583
  const factPath = specFile(descriptor.file);
582
- const facts = readJson8(factPath);
584
+ const facts = readJson9(factPath);
583
585
  if (!facts.surface?.surfaceRef) fail(`Invalid surface facts shard: ${factPath}`);
584
586
  const output = path4.join(reportRoot, "pages", path4.basename(descriptor.file));
585
587
  const controlRefByCarrier = new Map((facts.controls ?? []).map((value) => [value.sourceCarrierRef, value.controlRef]));
@@ -605,7 +607,7 @@ function initMigrationReport(argv) {
605
607
  let unassignedEntriesFile = null;
606
608
  if (pageIndex.unassignedEntriesFile) {
607
609
  const factPath = specFile(pageIndex.unassignedEntriesFile);
608
- const facts = readJson8(factPath);
610
+ const facts = readJson9(factPath);
609
611
  const output = path4.join(reportRoot, "pages", "unassigned.json");
610
612
  const shard = { reportShardVersion: REPORT_VERSION, kind: "unassigned-entry-report", factsFile: relProject(factPath), entries: (facts.entries ?? []).map((value) => item(value.entryRef, "entry", value.displayLabel || value.event || value.entryRef, { controlRef: null })) };
611
613
  if (writeIfMissing(output, shard)) createdFiles++;
@@ -2147,9 +2149,12 @@ function initHmosProject(options) {
2147
2149
  // src/commands/run-appgraph.ts
2148
2150
  import { rmSync } from "fs";
2149
2151
  import { join, resolve as resolve2 } from "path";
2150
- function runAppGraph(argv) {
2152
+ function runAppGraph(argv, runCommand = spawnCommandSync) {
2153
+ if (argv.some((arg) => arg === "--resolve-dependencies" || arg.startsWith("--resolve-dependencies="))) {
2154
+ throw new Error("Android Gradle \u4F9D\u8D56\u5206\u6790\u5165\u53E3\u6682\u65F6\u5173\u95ED\uFF0C\u8BF7\u79FB\u9664 --resolve-dependencies \u540E\u6267\u884C\u6E90\u7801\u9759\u6001\u5206\u6790\u3002");
2155
+ }
2151
2156
  const runManaged = (args) => {
2152
- const result = spawnCommandSync("appgraph", args, { stdio: "inherit" });
2157
+ const result = runCommand("appgraph", args, { stdio: "inherit" });
2153
2158
  if (result.error) {
2154
2159
  throw new Error(`\u65E0\u6CD5\u6267\u884C appgraph \u547D\u4EE4\uFF1A${result.error.message}\u3002\u8BF7\u5148\u5B89\u88C5 AppGraph \u5E76\u786E\u4FDD appgraph \u5728 PATH \u4E2D\u3002`);
2155
2160
  }
@@ -2164,10 +2169,10 @@ function runAppGraph(argv) {
2164
2169
  const projectRoot = resolve2(projectPath);
2165
2170
  ensureMigrationWorkspace(projectRoot);
2166
2171
  const outputPath = join(migrationDirectory(projectRoot), "spec");
2167
- const command3 = ["spec-analysis", projectRoot, "--out", outputPath];
2172
+ const command2 = ["spec-analysis", projectRoot, "--out", outputPath];
2168
2173
  const graphIndex = argv.indexOf("--graph");
2169
- if (graphIndex >= 0 && argv[graphIndex + 1]) command3.push("--graph", argv[graphIndex + 1]);
2170
- const analysis = runManaged(command3);
2174
+ if (graphIndex >= 0 && argv[graphIndex + 1]) command2.push("--graph", argv[graphIndex + 1]);
2175
+ const analysis = runManaged(command2);
2171
2176
  if ((analysis.status ?? 1) !== 0) return analysis.status ?? 1;
2172
2177
  console.log(JSON.stringify(renderSpecHtml([join(outputPath, "index.json"), join(outputPath, "spec.html")]), null, 2));
2173
2178
  const legacyDirectory = join(projectRoot, ".migration", "appgraph");
@@ -2177,18 +2182,19 @@ function runAppGraph(argv) {
2177
2182
  return 0;
2178
2183
  }
2179
2184
 
2185
+ // src/commands/harmony-toolchain.ts
2186
+ import { execFileSync } from "child_process";
2187
+ import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
2188
+ import path15 from "path";
2189
+ import JSON52 from "json5";
2190
+
2180
2191
  // src/commands/run-config-command.ts
2181
2192
  import { closeSync as closeSync2, createWriteStream, existsSync as existsSync2, mkdirSync as mkdirSync2, openSync as openSync2, readFileSync as readFileSync2 } from "fs";
2182
2193
  import { dirname as dirname2, isAbsolute, join as join2, resolve as resolve3 } from "path";
2183
2194
  import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
2184
- function androidProjectRoot(absoluteConfig, androidProject) {
2185
- const workspaceParent = resolve3(dirname2(absoluteConfig), "..", "..");
2186
- return typeof androidProject === "string" && androidProject ? resolve3(workspaceParent, androidProject) : workspaceParent;
2187
- }
2188
2195
  var DEFAULT_TIMEOUT_MS = 30 * 60 * 1e3;
2189
- var COMMAND_KEYS = /* @__PURE__ */ new Set(["androidTest", "androidBuild", "harmonyStatic"]);
2190
- function validateFiniteCommand(command3) {
2191
- const value = String(command3 ?? "").trim();
2196
+ function validateFiniteCommand(command2) {
2197
+ const value = String(command2 ?? "").trim();
2192
2198
  const errors = [];
2193
2199
  if (!value) return ["\u547D\u4EE4\u4E3A\u7A7A"];
2194
2200
  if (value.includes("|")) errors.push("\u7981\u6B62\u5728\u53D7\u7BA1\u547D\u4EE4\u4E2D\u4F7F\u7528\u8F93\u51FA\u7BA1\u9053\uFF1B\u65E5\u5FD7\u7531\u8FD0\u884C\u5668\u76F4\u63A5\u5199\u6587\u4EF6");
@@ -2205,47 +2211,9 @@ function validateFiniteCommand(command3) {
2205
2211
  }
2206
2212
  return errors;
2207
2213
  }
2208
- function runConfigCommand({ configPath, key, timeoutMs = DEFAULT_TIMEOUT_MS, runner = spawnSync2, replacements = {}, requiredTokens = [] }) {
2209
- if (!configPath || !key) throw new Error("\u7F3A\u5C11 configPath \u6216 command key");
2210
- if (!COMMAND_KEYS.has(key)) throw new Error(`\u4E0D\u652F\u6301\u7684 command key: ${key}`);
2211
- const absoluteConfig = resolve3(configPath);
2212
- const config = JSON.parse(readFileSync2(absoluteConfig, "utf8"));
2213
- const configuredCommand = config.commands?.[key];
2214
- if (configuredCommand === null) return { status: "not_applicable", key, summary: `${key} \u672A\u914D\u7F6E` };
2215
- for (const token of requiredTokens) {
2216
- if (!String(configuredCommand).includes(token)) {
2217
- return { status: "rejected", key, errors: [`${key} \u7F3A\u5C11\u5FC5\u8981\u6A21\u677F ${token}`], summary: `${key} \u4E0D\u652F\u6301\u672C\u6B21\u589E\u91CF\u9009\u62E9\u5668` };
2218
- }
2219
- }
2220
- let command3 = String(configuredCommand);
2221
- for (const [token, value] of Object.entries(replacements)) command3 = command3.replaceAll(token, String(value));
2222
- const projectRoot = androidProjectRoot(absoluteConfig, config.androidProject);
2223
- const configuredCwd = key.startsWith("android") ? config.androidProject : config.harmonyProject;
2224
- const cwd = isAbsolute(configuredCwd) ? configuredCwd : resolve3(projectRoot, configuredCwd);
2225
- return runFiniteCommand({ command: command3, key, cwd, projectRoot, timeoutMs, runner });
2226
- }
2227
- function runConfigCommandAsync({ configPath, key, timeoutMs = DEFAULT_TIMEOUT_MS, replacements = {}, requiredTokens = [] }) {
2228
- if (!configPath || !key) throw new Error("\u7F3A\u5C11 configPath \u6216 command key");
2229
- if (!COMMAND_KEYS.has(key)) throw new Error(`\u4E0D\u652F\u6301\u7684 command key: ${key}`);
2230
- const absoluteConfig = resolve3(configPath);
2231
- const config = JSON.parse(readFileSync2(absoluteConfig, "utf8"));
2232
- const configuredCommand = config.commands?.[key];
2233
- if (configuredCommand === null) return Promise.resolve({ status: "not_applicable", key, summary: `${key} \u672A\u914D\u7F6E` });
2234
- for (const token of requiredTokens) {
2235
- if (!String(configuredCommand).includes(token)) {
2236
- return Promise.resolve({ status: "rejected", key, errors: [`${key} \u7F3A\u5C11\u5FC5\u8981\u6A21\u677F ${token}`], summary: `${key} \u4E0D\u652F\u6301\u672C\u6B21\u589E\u91CF\u9009\u62E9\u5668` });
2237
- }
2238
- }
2239
- let command3 = String(configuredCommand);
2240
- for (const [token, value] of Object.entries(replacements)) command3 = command3.replaceAll(token, String(value));
2241
- const projectRoot = androidProjectRoot(absoluteConfig, config.androidProject);
2242
- const configuredCwd = key.startsWith("android") ? config.androidProject : config.harmonyProject;
2243
- const cwd = isAbsolute(configuredCwd) ? configuredCwd : resolve3(projectRoot, configuredCwd);
2244
- return runFiniteCommandAsync({ command: command3, key, cwd, projectRoot, timeoutMs });
2245
- }
2246
- function runFiniteCommand({ command: command3, key, cwd, projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS, runner = spawnSync2 }) {
2214
+ function runFiniteCommand({ command: command2, key, cwd, projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS, runner = spawnSync2 }) {
2247
2215
  if (!key || !cwd || !projectRoot) throw new Error("\u7F3A\u5C11 key\u3001cwd \u6216 projectRoot");
2248
- const errors = validateFiniteCommand(command3);
2216
+ const errors = validateFiniteCommand(command2);
2249
2217
  if (errors.length) return { status: "rejected", key, errors, summary: `${key} \u547D\u4EE4\u8FDD\u53CD\u6D41\u7A0B\u5B58\u6D3B\u95E8\u7981` };
2250
2218
  if (!existsSync2(cwd)) throw new Error(`${key} \u5DE5\u4F5C\u76EE\u5F55\u4E0D\u5B58\u5728: ${cwd}`);
2251
2219
  const logDir = join2(migrationDirectory(projectRoot), "preflight-logs");
@@ -2257,7 +2225,7 @@ function runFiniteCommand({ command: command3, key, cwd, projectRoot, timeoutMs
2257
2225
  let result;
2258
2226
  try {
2259
2227
  const executable = process.platform === "win32" ? "powershell.exe" : "/bin/sh";
2260
- const args = process.platform === "win32" ? ["-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", command3] : ["-lc", command3];
2228
+ const args = process.platform === "win32" ? ["-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", command2] : ["-lc", command2];
2261
2229
  result = runner(executable, args, {
2262
2230
  cwd,
2263
2231
  windowsHide: true,
@@ -2329,69 +2297,6 @@ function runFiniteExecutable({ executable, args, key, cwd, projectRoot, timeoutM
2329
2297
  })()
2330
2298
  };
2331
2299
  }
2332
- async function runFiniteCommandAsync({ command: command3, key, cwd, projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS }) {
2333
- if (!key || !cwd || !projectRoot) throw new Error("\u7F3A\u5C11 key\u3001cwd \u6216 projectRoot");
2334
- const errors = validateFiniteCommand(command3);
2335
- if (errors.length) return { status: "rejected", key, errors, summary: `${key} \u547D\u4EE4\u8FDD\u53CD\u6D41\u7A0B\u5B58\u6D3B\u95E8\u7981` };
2336
- if (!existsSync2(cwd)) throw new Error(`${key} \u5DE5\u4F5C\u76EE\u5F55\u4E0D\u5B58\u5728: ${cwd}`);
2337
- const logDir = join2(migrationDirectory(projectRoot), "preflight-logs");
2338
- mkdirSync2(logDir, { recursive: true });
2339
- const stdoutPath = join2(logDir, `${key}.stdout.log`);
2340
- const stderrPath = join2(logDir, `${key}.stderr.log`);
2341
- const stdout = createWriteStream(stdoutPath, { flags: "w" });
2342
- const stderr = createWriteStream(stderrPath, { flags: "w" });
2343
- const executable = process.platform === "win32" ? "powershell.exe" : "/bin/sh";
2344
- const args = process.platform === "win32" ? ["-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", command3] : ["-lc", command3];
2345
- const child = spawn2(executable, args, {
2346
- cwd,
2347
- windowsHide: true,
2348
- shell: false,
2349
- detached: process.platform !== "win32",
2350
- stdio: ["ignore", "pipe", "pipe"]
2351
- });
2352
- child.stdout.pipe(stdout);
2353
- child.stderr.pipe(stderr);
2354
- let timedOut = false;
2355
- const timer = setTimeout(() => {
2356
- timedOut = true;
2357
- if (process.platform !== "win32" && child.pid) {
2358
- try {
2359
- process.kill(-child.pid, "SIGKILL");
2360
- } catch {
2361
- child.kill("SIGKILL");
2362
- }
2363
- } else child.kill("SIGKILL");
2364
- }, Number(timeoutMs));
2365
- const exitCode = await new Promise((resolveExit) => {
2366
- child.once("error", () => resolveExit(null));
2367
- child.once("close", (code) => resolveExit(code));
2368
- });
2369
- clearTimeout(timer);
2370
- await Promise.all([
2371
- new Promise((done) => stdout.end(done)),
2372
- new Promise((done) => stderr.end(done))
2373
- ]);
2374
- return {
2375
- status: !timedOut && exitCode === 0 ? "passed" : "failed",
2376
- key,
2377
- exitCode,
2378
- timedOut,
2379
- timeoutMs: Number(timeoutMs),
2380
- cwd,
2381
- stdout: stdoutPath,
2382
- stderr: stderrPath,
2383
- summary: timedOut ? `${key} \u8D85\u8FC7 ${timeoutMs}ms\uFF0C\u5DF2\u7EC8\u6B62\u53D7\u7BA1\u547D\u4EE4` : (() => {
2384
- if (exitCode === 0) return `${key} \u6267\u884C\u6210\u529F`;
2385
- return `${key} \u6267\u884C\u5931\u8D25\uFF0C\u9000\u51FA\u7801 ${exitCode ?? "unknown"}`;
2386
- })()
2387
- };
2388
- }
2389
-
2390
- // src/commands/harmony-toolchain.ts
2391
- import { execFileSync } from "child_process";
2392
- import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
2393
- import path15 from "path";
2394
- import JSON52 from "json5";
2395
2300
 
2396
2301
  // src/commands/check-hmos-device.ts
2397
2302
  import { spawnSync as spawnSync3 } from "child_process";
@@ -2490,6 +2395,14 @@ import { existsSync as existsSync3, readdirSync, statSync } from "fs";
2490
2395
  import path13 from "path";
2491
2396
  var DEVECO_CLI_PACKAGE = "@deveco/deveco-cli@latest";
2492
2397
  var DEVECO_CLI_INSTALL_COMMAND = `npm install -g ${DEVECO_CLI_PACKAGE}`;
2398
+ var NODE_INSPECTOR_OPTION = /(?:^|\s)--inspect(?:-brk|-port|-wait)?(?:=(?:"[^"]*"|'[^']*'|\S+))?(?=\s|$)/gu;
2399
+ function devEcoCliEnvironment(environment = process.env) {
2400
+ const result = { ...environment, DEVECO_CLI_SKIP_VERSION_CHECK: "1" };
2401
+ const nodeOptions = String(environment.NODE_OPTIONS ?? "").replace(NODE_INSPECTOR_OPTION, "").trim();
2402
+ if (nodeOptions) result.NODE_OPTIONS = nodeOptions;
2403
+ else delete result.NODE_OPTIONS;
2404
+ return result;
2405
+ }
2493
2406
  function executableName() {
2494
2407
  return process.env.DEVECO_CLI_BINARY || "devecocli";
2495
2408
  }
@@ -2497,14 +2410,14 @@ function devEcoCliSpawn(executable, args) {
2497
2410
  const scriptWrapper = /\.m?js$/i.test(executable);
2498
2411
  return scriptWrapper ? [process.execPath, executable, ...args] : [executable, ...args];
2499
2412
  }
2500
- function runDevEcoCli(args, { executable = executableName(), runner = spawnCommandSync, timeoutMs = 15e3 } = {}, cwd) {
2501
- const [command3, ...commandArgs] = devEcoCliSpawn(executable, args);
2502
- const result = runner(command3, commandArgs, {
2413
+ function runDevEcoCli(args, { executable = executableName(), runner = spawnCommandSync, timeoutMs = 15e3, environment = process.env } = {}, cwd) {
2414
+ const [command2, ...commandArgs] = devEcoCliSpawn(executable, args);
2415
+ const result = runner(command2, commandArgs, {
2503
2416
  cwd,
2504
2417
  encoding: "utf8",
2505
2418
  timeout: timeoutMs,
2506
2419
  windowsHide: true,
2507
- env: { ...process.env, DEVECO_CLI_SKIP_VERSION_CHECK: "1" }
2420
+ env: devEcoCliEnvironment(environment)
2508
2421
  });
2509
2422
  return {
2510
2423
  executable,
@@ -2516,13 +2429,13 @@ function runDevEcoCli(args, { executable = executableName(), runner = spawnComma
2516
2429
  errorCode: result.error && "code" in result.error ? result.error.code : null
2517
2430
  };
2518
2431
  }
2519
- async function runDevEcoCliAsync(args, { executable = executableName(), timeoutMs = 15e3 } = {}, cwd) {
2520
- const [command3, ...commandArgs] = devEcoCliSpawn(executable, args);
2521
- const child = spawnCommand(command3, commandArgs, {
2432
+ async function runDevEcoCliAsync(args, { executable = executableName(), timeoutMs = 15e3, environment = process.env } = {}, cwd) {
2433
+ const [command2, ...commandArgs] = devEcoCliSpawn(executable, args);
2434
+ const child = spawnCommand(command2, commandArgs, {
2522
2435
  cwd,
2523
2436
  windowsHide: true,
2524
2437
  detached: process.platform !== "win32",
2525
- env: { ...process.env, DEVECO_CLI_SKIP_VERSION_CHECK: "1" },
2438
+ env: devEcoCliEnvironment(environment),
2526
2439
  stdio: ["ignore", "pipe", "pipe"]
2527
2440
  });
2528
2441
  let stdout = "";
@@ -2603,7 +2516,7 @@ function checkDevEcoCliAuth(cli = {}) {
2603
2516
  const result = runDevEcoCli(["auth", "status"], cli);
2604
2517
  const output = [result.stdout, result.stderr].map((value) => value.trim()).filter(Boolean).join("\n");
2605
2518
  const loggedOut = /(?:^|\n)Not logged in(?:\n|$)/i.test(output);
2606
- const currentUser = output.match(/(?:^|\n)Current user:\s*(.+?)(?:\n|$)/i)?.[1]?.trim();
2519
+ const currentUser = output.match(/Current user:\s*([^\r\n]+)/i)?.[1]?.trim();
2607
2520
  if (result.status !== "passed" || loggedOut || !currentUser) {
2608
2521
  return {
2609
2522
  status: "failed",
@@ -2773,8 +2686,8 @@ function isRealDeviceTarget(target) {
2773
2686
  }
2774
2687
  return true;
2775
2688
  }
2776
- function readSigningMaterialFingerprints(harmonyRoot) {
2777
- const profile = path14.join(harmonyRoot, "build-profile.json5");
2689
+ function readSigningMaterialFingerprints(harmonyRoot2) {
2690
+ const profile = path14.join(harmonyRoot2, "build-profile.json5");
2778
2691
  if (!fs13.existsSync(profile)) return [];
2779
2692
  let parsed;
2780
2693
  try {
@@ -2783,7 +2696,7 @@ function readSigningMaterialFingerprints(harmonyRoot) {
2783
2696
  return [];
2784
2697
  }
2785
2698
  const signingConfigs = parsed?.app?.signingConfigs ?? [];
2786
- const files = [...new Set(signingConfigs.flatMap((config) => [config?.material?.certpath, config?.material?.profile, config?.material?.storeFile]).filter((file) => typeof file === "string" && file.length > 0).map((file) => path14.isAbsolute(file) ? file : path14.resolve(harmonyRoot, file)))];
2699
+ const files = [...new Set(signingConfigs.flatMap((config) => [config?.material?.certpath, config?.material?.profile, config?.material?.storeFile]).filter((file) => typeof file === "string" && file.length > 0).map((file) => path14.isAbsolute(file) ? file : path14.resolve(harmonyRoot2, file)))];
2787
2700
  return [...new Set(files)].flatMap((file) => {
2788
2701
  if (!fs13.existsSync(file)) return [];
2789
2702
  const stat = fs13.statSync(file);
@@ -2900,10 +2813,10 @@ function detectDeviceSdkVersion(target) {
2900
2813
  diverged: systemAbc !== null && systemAbc.major < claimedApiLevel
2901
2814
  };
2902
2815
  }
2903
- function ensureDeviceInstallable(harmonyRoot, target) {
2816
+ function ensureDeviceInstallable(harmonyRoot2, target) {
2904
2817
  const device = detectDeviceSdkVersion(target);
2905
2818
  if (!device) return { status: "skipped", changed: false, summary: "\u65E0\u6CD5\u8BFB\u53D6\u771F\u673A\u7CFB\u7EDF\u7248\u672C\uFF0C\u8DF3\u8FC7\u517C\u5BB9\u7248\u672C\u68C0\u67E5" };
2906
- const capped = capCompatibleSdkVersionTo(harmonyRoot, device.sdkVersion);
2819
+ const capped = capCompatibleSdkVersionTo(harmonyRoot2, device.sdkVersion);
2907
2820
  const divergence = device.diverged ? `\uFF1B\u771F\u673A param \u58F0\u79F0 API ${device.claimedApiLevel}\uFF0C\u4F46\u7CFB\u7EDF ABC \u5B57\u8282\u7801\u4E0A\u9650\u4E3A ${device.systemAbcVersion}\uFF08log \u7248\u955C\u50CF\u5E38\u89C1\u865A\u62A5\uFF09\uFF0C\u5DF2\u6309\u8FD0\u884C\u65F6\u5B9E\u9645\u80FD\u529B\u53D6 API ${device.apiLevel}` : "";
2908
2821
  return {
2909
2822
  ...capped,
@@ -2994,10 +2907,10 @@ function readJson5(file) {
2994
2907
  function firstExisting(values) {
2995
2908
  return values.find((value) => Boolean(value && existsSync4(value)));
2996
2909
  }
2997
- function pathLookup(command3) {
2910
+ function pathLookup(command2) {
2998
2911
  try {
2999
2912
  const lookup = process.platform === "win32" ? "where.exe" : "which";
3000
- return execFileSync(lookup, [command3], { encoding: "utf8", windowsHide: true }).split(/\r?\n/).map((value) => value.trim()).find(Boolean);
2913
+ return execFileSync(lookup, [command2], { encoding: "utf8", windowsHide: true }).split(/\r?\n/).map((value) => value.trim()).find(Boolean);
3001
2914
  } catch {
3002
2915
  return null;
3003
2916
  }
@@ -3158,8 +3071,8 @@ function runHarmonyTest({ projectRoot, harmonyProject, timeoutMs, testClass = nu
3158
3071
  if (appBuild.status !== "passed") return { status: "failed", key: "harmonyTest", stage: "app-build", appBuild };
3159
3072
  const ohpmInstall = ensureOhpmDependencies({ projectRoot, harmonyProject, timeoutMs });
3160
3073
  if (ohpmInstall.status !== "passed") return { status: "failed", key: "harmonyTest", stage: "ohpm-install", appBuild, ohpmInstall };
3161
- const { info, command: command3 } = buildHarmonyTestCommand({ projectRoot, harmonyProject });
3162
- const testBuild = runFiniteCommand({ command: command3, key: "harmonyTest-build", cwd: info.root, projectRoot, timeoutMs });
3074
+ const { info, command: command2 } = buildHarmonyTestCommand({ projectRoot, harmonyProject });
3075
+ const testBuild = runFiniteCommand({ command: command2, key: "harmonyTest-build", cwd: info.root, projectRoot, timeoutMs });
3163
3076
  if (testBuild.status !== "passed") return { status: "failed", key: "harmonyTest", stage: "test-build", appBuild, testBuild };
3164
3077
  const testArtifact = newestHap(info.moduleRoot, (name) => name.toLowerCase().includes(info.testTarget.toLowerCase()));
3165
3078
  if (!testArtifact) return { status: "failed", key: "harmonyTest", stage: "test-artifact", summary: "Hvigor \u8FD4\u56DE\u6210\u529F\uFF0C\u4F46\u6CA1\u6709\u53EF\u5B89\u88C5\u7684\u6D4B\u8BD5 HAP", appBuild, testBuild };
@@ -3345,8 +3258,8 @@ function detectHarmonySdk({ project, sdkPath, writeConfig = false, env = process
3345
3258
  push(sdkPath, "explicit");
3346
3259
  push(config?.harmonySdk?.path, "existing-config");
3347
3260
  const configuredHarmony = config?.harmonyProject ?? defaultHarmonyProjectPath(projectRoot);
3348
- const harmonyRoot = path17.isAbsolute(configuredHarmony) ? configuredHarmony : path17.resolve(projectRoot, configuredHarmony);
3349
- const localProperties = path17.join(harmonyRoot, "local.properties");
3261
+ const harmonyRoot2 = path17.isAbsolute(configuredHarmony) ? configuredHarmony : path17.resolve(projectRoot, configuredHarmony);
3262
+ const localProperties = path17.join(harmonyRoot2, "local.properties");
3350
3263
  if (fs14.existsSync(localProperties)) {
3351
3264
  const properties = parseProperties(fs14.readFileSync(localProperties, "utf8"));
3352
3265
  push(properties["sdk.dir"] ?? properties["harmony.sdk.dir"] ?? properties["ohos.sdk.dir"], "local-properties");
@@ -3485,22 +3398,19 @@ var atomicJson = (file, value) => {
3485
3398
  `, "utf8");
3486
3399
  fs15.renameSync(temporary, file);
3487
3400
  };
3488
- function defaultAndroidBuild(_projectRoot, _platform = process.platform) {
3489
- return null;
3490
- }
3491
- function normalizeAndroidBuildCommand(command3, platform = process.platform) {
3492
- if (typeof command3 !== "string") return command3;
3401
+ function normalizeAndroidBuildCommand(command2, platform = process.platform) {
3402
+ if (typeof command2 !== "string") return command2;
3493
3403
  if (platform === "win32") {
3494
- return command3.replace(/^(\s*)(?:&\s*)?["']?\.\/gradlew["']?(?=\s|$)/i, "$1.\\gradlew.bat");
3404
+ return command2.replace(/^(\s*)(?:&\s*)?["']?\.\/gradlew["']?(?=\s|$)/i, "$1.\\gradlew.bat");
3495
3405
  }
3496
- return command3.replace(/^(\s*)(?:&\s*)?["']?(?:\.\\gradlew(?:\.bat)?|gradlew\.bat)["']?(?=\s|$)/i, "$1./gradlew");
3406
+ return command2.replace(/^(\s*)(?:&\s*)?["']?(?:\.\\gradlew(?:\.bat)?|gradlew\.bat)["']?(?=\s|$)/i, "$1./gradlew");
3497
3407
  }
3498
- function validateAndroidBuildPlatform(command3, platform = process.platform) {
3499
- if (typeof command3 !== "string") return null;
3500
- if (platform === "win32" && /(?:^|\s)\.\/gradlew(?:\s|$)/i.test(command3)) {
3408
+ function validateAndroidBuildPlatform(command2, platform = process.platform) {
3409
+ if (typeof command2 !== "string") return null;
3410
+ if (platform === "win32" && /(?:^|\s)\.\/gradlew(?:\s|$)/i.test(command2)) {
3501
3411
  return "commands.androidBuild uses a Unix Gradle wrapper on Windows; use .\\gradlew.bat";
3502
3412
  }
3503
- if (platform !== "win32" && /(?:gradlew\.bat|\.\\gradlew)(?:\s|$)/i.test(command3)) {
3413
+ if (platform !== "win32" && /(?:gradlew\.bat|\.\\gradlew)(?:\s|$)/i.test(command2)) {
3504
3414
  return `commands.androidBuild uses a Windows Gradle wrapper on ${platform}; use ./gradlew`;
3505
3415
  }
3506
3416
  return null;
@@ -3522,12 +3432,12 @@ function validateMigrationConfigValue(config, projectRoot) {
3522
3432
  if (path19.isAbsolute(config.androidProject)) return config.androidProject;
3523
3433
  return path19.resolve(projectRoot, config.androidProject);
3524
3434
  })() : null;
3525
- const harmonyRoot = config?.harmonyProject ? (() => {
3435
+ const harmonyRoot2 = config?.harmonyProject ? (() => {
3526
3436
  if (path19.isAbsolute(config.harmonyProject)) return config.harmonyProject;
3527
3437
  return path19.resolve(projectRoot, config.harmonyProject);
3528
3438
  })() : null;
3529
3439
  if (androidRoot && (!fs15.existsSync(androidRoot) || !fs15.statSync(androidRoot).isDirectory())) errors.push(`androidProject directory not found: ${androidRoot}`);
3530
- return { valid: errors.length === 0, errors, projectRoot, androidRoot, harmonyRoot };
3440
+ return { valid: errors.length === 0, errors, projectRoot, androidRoot, harmonyRoot: harmonyRoot2 };
3531
3441
  }
3532
3442
  function initMigrationConfig(options) {
3533
3443
  const projectRoot = path19.resolve(options.project);
@@ -3536,7 +3446,6 @@ function initMigrationConfig(options) {
3536
3446
  const configPath = path19.join(migrationDirectory(projectRoot), "config.json");
3537
3447
  const created = !fs15.existsSync(configPath);
3538
3448
  const existing = created ? {} : JSON.parse(fs15.readFileSync(configPath, "utf8"));
3539
- const configuredAndroidBuild = options.androidBuild ?? existing.commands?.androidBuild ?? defaultAndroidBuild(projectRoot);
3540
3449
  const config = {
3541
3450
  ...existing,
3542
3451
  configVersion: existing.configVersion ?? "1.0.0",
@@ -3545,8 +3454,8 @@ function initMigrationConfig(options) {
3545
3454
  ...existing.harmonySdk ? { harmonySdk: existing.harmonySdk } : {},
3546
3455
  commands: {
3547
3456
  ...existing.commands ?? {},
3548
- androidBuild: normalizeAndroidBuildCommand(configuredAndroidBuild),
3549
- androidTest: options.androidTest ?? existing.commands?.androidTest ?? null,
3457
+ androidBuild: normalizeAndroidBuildCommand(existing.commands?.androidBuild ?? null),
3458
+ androidTest: existing.commands?.androidTest ?? null,
3550
3459
  harmonyStatic: existing.commands?.harmonyStatic ?? null
3551
3460
  }
3552
3461
  };
@@ -3570,7 +3479,7 @@ function validateMigrationConfig(project) {
3570
3479
 
3571
3480
  // src/commands/preflight.ts
3572
3481
  import fs20 from "fs";
3573
- import path24 from "path";
3482
+ import path23 from "path";
3574
3483
 
3575
3484
  // src/commands/detect-android-ui-stack.ts
3576
3485
  import fs16 from "fs";
@@ -3641,69 +3550,13 @@ function detectAndroidUiStack(projectRoot) {
3641
3550
  };
3642
3551
  }
3643
3552
 
3644
- // src/commands/check-android-emulator.ts
3645
- import { spawnSync as spawnSync5 } from "child_process";
3646
- var READY_MARKER2 = "A2H_ANDROID_EMULATOR_READY";
3647
- function command2(executable, args, runner) {
3648
- const result = runner(executable, args, { encoding: "utf8", shell: false, timeout: 15e3 });
3649
- return {
3650
- exitCode: Number.isInteger(result.status) ? result.status : 1,
3651
- stdout: result.stdout ?? "",
3652
- stderr: result.stderr ?? result.error?.message ?? ""
3653
- };
3654
- }
3655
- function parseAdbDevices(output) {
3656
- const devices = [];
3657
- for (const rawLine of String(output).split(/\r?\n/)) {
3658
- const line = rawLine.trim();
3659
- if (!line || line.startsWith("List of devices attached")) continue;
3660
- const [serial, state] = line.split(/\s+/, 2);
3661
- if (serial && state) devices.push({ serial, state });
3662
- }
3663
- return devices;
3664
- }
3665
- function checkAndroidEmulator({ adb = process.env.ADB_BINARY || "adb", runner = spawnSync5 } = {}) {
3666
- const list = command2(adb, ["devices"], runner);
3667
- if (list.exitCode !== 0) {
3668
- return optionalResult(adb, [], [], "ADB \u4E0D\u53EF\u7528\uFF0C\u672A\u68C0\u6D4B Android \u6A21\u62DF\u5668\uFF1B\u5EFA\u8BAE\u5B89\u88C5 Android SDK Platform-Tools \u548C Android \u6A21\u62DF\u5668\uFF0C\u540E\u7EED\u53EF\u8FDB\u884C Android/HarmonyOS \u5BF9\u7167\u9A8C\u8BC1", [list.stderr || list.stdout]);
3669
- }
3670
- const devices = parseAdbDevices(list.stdout).filter((device) => device.state === "device");
3671
- const probes = devices.map((device) => {
3672
- const qemu = command2(adb, ["-s", device.serial, "shell", "getprop", "ro.kernel.qemu"], runner);
3673
- return { ...device, qemu, isEmulator: qemu.exitCode === 0 && qemu.stdout.trim() === "1" };
3674
- });
3675
- const emulators = probes.filter((probe2) => probe2.isEmulator).map((probe2) => probe2.serial);
3676
- if (!emulators.length) {
3677
- return optionalResult(adb, devices.map((device) => device.serial), [], "\u672A\u68C0\u6D4B\u5230 Android \u6A21\u62DF\u5668\uFF08\u53EF\u9009\uFF09\uFF1B\u5EFA\u8BAE\u5B89\u88C5\u5E76\u542F\u52A8\u6A21\u62DF\u5668\uFF0C\u4EE5\u4FBF\u8FC1\u79FB\u65F6\u5BF9\u7167 Android \u4E0E HarmonyOS \u9875\u9762", [list.stdout || "adb devices returned no emulator"]);
3678
- }
3679
- const shellProbes = emulators.map((serial) => ({
3680
- serial,
3681
- result: command2(adb, ["-s", serial, "shell", "echo", READY_MARKER2], runner)
3682
- }));
3683
- const responsiveEmulators = shellProbes.filter(({ result }) => result.exitCode === 0 && result.stdout.includes(READY_MARKER2)).map(({ serial }) => serial);
3684
- if (!responsiveEmulators.length) {
3685
- return optionalResult(adb, emulators, [], "\u68C0\u6D4B\u5230 Android \u6A21\u62DF\u5668\u4F46 Shell \u65E0\u54CD\u5E94\uFF08\u53EF\u9009\uFF09\uFF1B\u5EFA\u8BAE\u4FEE\u590D\u6216\u91CD\u542F\u6A21\u62DF\u5668\uFF0C\u4EE5\u4FBF\u8FDB\u884C\u53CC\u7AEF\u9875\u9762\u5BF9\u7167", shellProbes.map(({ serial, result }) => `${serial}: ${result.stderr || result.stdout || `exit ${result.exitCode}`}`));
3686
- }
3687
- return {
3688
- status: "passed",
3689
- adb,
3690
- emulators,
3691
- responsiveEmulators,
3692
- summary: `\u53D1\u73B0 ${responsiveEmulators.length} \u4E2A\u53EF\u54CD\u5E94\u7684 Android \u6A21\u62DF\u5668\uFF0C\u53EF\u7528\u4E8E Android/HarmonyOS \u5BF9\u7167\u9A8C\u8BC1`,
3693
- evidence: responsiveEmulators
3694
- };
3695
- }
3696
- function optionalResult(adb, emulators, responsiveEmulators, summary, evidence2) {
3697
- return { status: "not_applicable", adb, emulators, responsiveEmulators, summary, evidence: evidence2 };
3698
- }
3699
-
3700
3553
  // src/commands/git-checkpoint.ts
3701
3554
  import path21 from "path";
3702
3555
  import fs17 from "fs";
3703
3556
  import { createHash as createHash5 } from "crypto";
3704
- import { spawnSync as spawnSync6 } from "child_process";
3557
+ import { spawnSync as spawnSync5 } from "child_process";
3705
3558
  function git(projectRoot, args) {
3706
- const result = spawnSync6("git", ["-C", projectRoot, ...args], { encoding: "utf8", shell: false, timeout: 3e4, windowsHide: true });
3559
+ const result = spawnSync5("git", ["-C", projectRoot, ...args], { encoding: "utf8", shell: false, timeout: 3e4, windowsHide: true });
3707
3560
  return {
3708
3561
  ok: !result.error && result.status === 0,
3709
3562
  exitCode: Number.isInteger(result.status) ? result.status : null,
@@ -3733,17 +3586,17 @@ function unstage(root, files) {
3733
3586
  if (head.ok) git(root, ["restore", "--staged", "--", ...files]);
3734
3587
  else git(root, ["rm", "--cached", "-r", "--ignore-unmatch", "--", ...files]);
3735
3588
  }
3736
- function initializeHarmonyRepository(androidRoot, harmonyRoot) {
3737
- const existing = repositoryRoot(harmonyRoot);
3589
+ function initializeHarmonyRepository(androidRoot, harmonyRoot2) {
3590
+ const existing = repositoryRoot(harmonyRoot2);
3738
3591
  if (existing) return { root: existing, initialized: false };
3739
3592
  const androidBranch = git(androidRoot, ["branch", "--show-current"]).stdout || "main";
3740
- const initialized = git(harmonyRoot, ["init", "-b", androidBranch]);
3741
- if (!initialized.ok) throw new Error(initialized.stderr || `Cannot initialize HarmonyOS Git repository: ${harmonyRoot}`);
3593
+ const initialized = git(harmonyRoot2, ["init", "-b", androidBranch]);
3594
+ if (!initialized.ok) throw new Error(initialized.stderr || `Cannot initialize HarmonyOS Git repository: ${harmonyRoot2}`);
3742
3595
  for (const key of ["user.name", "user.email"]) {
3743
3596
  const value = git(androidRoot, ["config", "--get", key]);
3744
- if (value.ok && value.stdout) git(harmonyRoot, ["config", key, value.stdout]);
3597
+ if (value.ok && value.stdout) git(harmonyRoot2, ["config", key, value.stdout]);
3745
3598
  }
3746
- return { root: path21.resolve(harmonyRoot), initialized: true };
3599
+ return { root: path21.resolve(harmonyRoot2), initialized: true };
3747
3600
  }
3748
3601
  function relevantStatus(status, ignoreUntrackedRoots = []) {
3749
3602
  const ignored = ignoreUntrackedRoots.map((value) => value.replaceAll("\\", "/").replace(/^\.\//, "").replace(/\/$/, ""));
@@ -3756,8 +3609,8 @@ function relevantStatus(status, ignoreUntrackedRoots = []) {
3756
3609
  }
3757
3610
  function gitWorkspaceStatus(project, options = {}) {
3758
3611
  const projectRoot = path21.resolve(project);
3759
- const inside4 = git(projectRoot, ["rev-parse", "--is-inside-work-tree"]);
3760
- if (!inside4.ok || inside4.stdout !== "true") return { status: "failed", repository: false, projectRoot, errors: [inside4.stderr || "not a Git worktree"] };
3612
+ const inside5 = git(projectRoot, ["rev-parse", "--is-inside-work-tree"]);
3613
+ if (!inside5.ok || inside5.stdout !== "true") return { status: "failed", repository: false, projectRoot, errors: [inside5.stderr || "not a Git worktree"] };
3761
3614
  const head = git(projectRoot, ["rev-parse", "HEAD"]);
3762
3615
  const branch = git(projectRoot, ["branch", "--show-current"]);
3763
3616
  const porcelain = git(projectRoot, ["status", "--porcelain=v1", "--untracked-files=all"]);
@@ -3777,13 +3630,13 @@ function createGitCheckpoint(options) {
3777
3630
  const projectRoot = fs17.realpathSync(path21.resolve(options.project));
3778
3631
  const requestedHarmonyRoot = options.harmonyProject ? path21.resolve(options.harmonyProject) : null;
3779
3632
  if (requestedHarmonyRoot && (!fs17.existsSync(requestedHarmonyRoot) || !fs17.statSync(requestedHarmonyRoot).isDirectory())) throw new Error(`HarmonyOS project not found: ${requestedHarmonyRoot}`);
3780
- const harmonyRoot = requestedHarmonyRoot ? fs17.realpathSync(requestedHarmonyRoot) : null;
3633
+ const harmonyRoot2 = requestedHarmonyRoot ? fs17.realpathSync(requestedHarmonyRoot) : null;
3781
3634
  if (!options.message?.trim()) throw new Error("checkpoint create requires --message");
3782
3635
  if (!options.all && options.include.length === 0) throw new Error("checkpoint create requires at least one --include or explicit --all");
3783
3636
  const baseline = gitWorkspaceStatus(projectRoot);
3784
3637
  if (!baseline.repository) throw new Error(baseline.errors?.[0] ?? "not a Git repository");
3785
3638
  const androidRepository = repositoryRoot(projectRoot);
3786
- const harmonyRepository = harmonyRoot ? repositoryRoot(harmonyRoot) ?? initializeHarmonyRepository(androidRepository, harmonyRoot).root : androidRepository;
3639
+ const harmonyRepository = harmonyRoot2 ? repositoryRoot(harmonyRoot2) ?? initializeHarmonyRepository(androidRepository, harmonyRoot2).root : androidRepository;
3787
3640
  const repositories = [.../* @__PURE__ */ new Set([androidRepository, harmonyRepository])];
3788
3641
  for (const root of repositories) {
3789
3642
  const before = stagedFiles(root);
@@ -3796,18 +3649,18 @@ function createGitCheckpoint(options) {
3796
3649
  if (options.all) {
3797
3650
  const androidAdd = git(projectRoot, ["add", "-A", "--", ".", ":!.migration"]);
3798
3651
  if (!androidAdd.ok) throw new Error(androidAdd.stderr || "git add failed for Android project");
3799
- if (harmonyRoot && harmonyRepository !== androidRepository) {
3800
- const harmonyAdd = git(harmonyRoot, ["add", "-A", "--", ".", ":!.migration"]);
3652
+ if (harmonyRoot2 && harmonyRepository !== androidRepository) {
3653
+ const harmonyAdd = git(harmonyRoot2, ["add", "-A", "--", ".", ":!.migration"]);
3801
3654
  if (!harmonyAdd.ok) throw new Error(harmonyAdd.stderr || "git add failed for HarmonyOS project");
3802
- } else if (harmonyRoot && !inside2(projectRoot, harmonyRoot)) {
3803
- const harmonyPath = normalized(path21.relative(androidRepository, harmonyRoot));
3655
+ } else if (harmonyRoot2 && !inside2(projectRoot, harmonyRoot2)) {
3656
+ const harmonyPath = normalized(path21.relative(androidRepository, harmonyRoot2));
3804
3657
  const harmonyAdd = git(androidRepository, ["add", "-A", "--", harmonyPath]);
3805
3658
  if (!harmonyAdd.ok) throw new Error(harmonyAdd.stderr || "git add failed for HarmonyOS project");
3806
3659
  }
3807
3660
  } else {
3808
3661
  for (const value of options.include) {
3809
3662
  const absolute = path21.resolve(projectRoot, value);
3810
- const root = harmonyRoot && inside2(harmonyRoot, absolute) ? harmonyRepository : (() => {
3663
+ const root = harmonyRoot2 && inside2(harmonyRoot2, absolute) ? harmonyRepository : (() => {
3811
3664
  if (inside2(projectRoot, absolute)) return androidRepository;
3812
3665
  return null;
3813
3666
  })();
@@ -3829,7 +3682,7 @@ function createGitCheckpoint(options) {
3829
3682
  try {
3830
3683
  committedFiles = [...new Map((options.provenanceFiles ?? []).map((item) => [`${item.role}:${item.file}`, item])).values()].sort((left, right) => left.file.localeCompare(right.file) || left.role.localeCompare(right.role)).map((item) => {
3831
3684
  const absolute = path21.resolve(projectRoot, item.file);
3832
- const expectedRoot = item.role === "harmony-implementation" ? harmonyRoot : projectRoot;
3685
+ const expectedRoot = item.role === "harmony-implementation" ? harmonyRoot2 : projectRoot;
3833
3686
  const root = item.role === "harmony-implementation" ? harmonyRepository : androidRepository;
3834
3687
  const relative2 = normalized(path21.relative(projectRoot, absolute));
3835
3688
  if (!expectedRoot || !inside2(expectedRoot, absolute) || !fs17.existsSync(absolute) || !fs17.statSync(absolute).isFile()) {
@@ -3859,12 +3712,12 @@ function createGitCheckpoint(options) {
3859
3712
  committedFiles,
3860
3713
  repositories: {
3861
3714
  android: { root: ".", gitRoot: normalized(path21.relative(projectRoot, androidRepository)) || "." },
3862
- harmony: harmonyRoot ? { root: normalized(path21.relative(projectRoot, harmonyRoot)), gitRoot: normalized(path21.relative(projectRoot, harmonyRepository)) } : null
3715
+ harmony: harmonyRoot2 ? { root: normalized(path21.relative(projectRoot, harmonyRoot2)), gitRoot: normalized(path21.relative(projectRoot, harmonyRepository)) } : null
3863
3716
  }
3864
3717
  };
3865
3718
  }
3866
3719
  const commitResults = [];
3867
- const separateHarmony = harmonyRoot && harmonyRepository !== androidRepository;
3720
+ const separateHarmony = harmonyRoot2 && harmonyRepository !== androidRepository;
3868
3721
  if (separateHarmony) {
3869
3722
  const files2 = stagedFiles(harmonyRepository);
3870
3723
  if (files2.length) {
@@ -3886,9 +3739,9 @@ function createGitCheckpoint(options) {
3886
3739
  commitResults.push({ kind: "android", root: androidRepository, commit: git(androidRepository, ["rev-parse", "HEAD"]).stdout, files: androidFiles, summary: androidCommit.stdout });
3887
3740
  }
3888
3741
  const androidCheckpoint = commitResults.find((item) => item.kind === "android");
3889
- if (harmonyRoot && harmonyRepository === androidRepository) {
3890
- commitResults.push({ kind: "harmony", root: harmonyRepository, commit: androidCheckpoint.commit, files: androidFiles.filter((file) => inside2(harmonyRoot, path21.resolve(harmonyRepository, file))), summary: androidCheckpoint.summary });
3891
- } else if (harmonyRoot && !commitResults.some((item) => item.kind === "harmony")) {
3742
+ if (harmonyRoot2 && harmonyRepository === androidRepository) {
3743
+ commitResults.push({ kind: "harmony", root: harmonyRepository, commit: androidCheckpoint.commit, files: androidFiles.filter((file) => inside2(harmonyRoot2, path21.resolve(harmonyRepository, file))), summary: androidCheckpoint.summary });
3744
+ } else if (harmonyRoot2 && !commitResults.some((item) => item.kind === "harmony")) {
3892
3745
  const harmonyHead = git(harmonyRepository, ["rev-parse", "HEAD"]).stdout;
3893
3746
  if (harmonyHead) commitResults.push({ kind: "harmony", root: harmonyRepository, commit: harmonyHead, files: [], summary: "HarmonyOS repository already clean" });
3894
3747
  }
@@ -3921,12 +3774,10 @@ function createGitCheckpoint(options) {
3921
3774
 
3922
3775
  // src/commands/system-checks.ts
3923
3776
  import fs18 from "fs";
3924
- import os3 from "os";
3925
- import path22 from "path";
3926
- function probe(command3, args, timeout = 15e3) {
3927
- const result = spawnCommandSync(command3, args, { encoding: "utf8", timeout, windowsHide: true });
3777
+ function probe(command2, args, timeout = 15e3) {
3778
+ const result = spawnCommandSync(command2, args, { encoding: "utf8", timeout, windowsHide: true });
3928
3779
  return {
3929
- command: command3,
3780
+ command: command2,
3930
3781
  available: !result.error && result.status === 0,
3931
3782
  exitCode: Number.isInteger(result.status) ? result.status : null,
3932
3783
  output: String(result.stdout || result.stderr || result.error?.message || "").trim().split(/\r?\n/)[0] ?? ""
@@ -3935,8 +3786,7 @@ function probe(command3, args, timeout = 15e3) {
3935
3786
  function firstDirectory(values) {
3936
3787
  return values.find((value) => Boolean(value && fs18.existsSync(value) && fs18.statSync(value).isDirectory())) ?? null;
3937
3788
  }
3938
- function checkToolchain(project, env = process.env) {
3939
- const projectRoot = path22.resolve(project);
3789
+ function checkToolchain(_project, env = process.env) {
3940
3790
  const pythonCandidates = [
3941
3791
  env.PYTHON_BINARY,
3942
3792
  process.platform === "win32" ? "python" : "python3",
@@ -3956,23 +3806,14 @@ function checkToolchain(project, env = process.env) {
3956
3806
  java: probe("java", ["-version"]),
3957
3807
  appgraph: probe("appgraph", ["--version"])
3958
3808
  };
3959
- const androidSdk = firstDirectory([
3960
- env.ANDROID_SDK_ROOT,
3961
- env.ANDROID_HOME,
3962
- process.platform === "darwin" ? path22.join(os3.homedir(), "Library", "Android", "sdk") : null,
3963
- process.platform === "win32" && env.LOCALAPPDATA ? path22.join(env.LOCALAPPDATA, "Android", "Sdk") : null
3964
- ]);
3965
3809
  const devecoHome = firstDirectory([
3966
3810
  env.DEVECO_STUDIO_HOME,
3967
3811
  env.DEVECO_HOME,
3968
3812
  process.platform === "darwin" ? "/Applications/DevEco-Studio.app/Contents" : null
3969
3813
  ]);
3970
- const androidBuildEntry = ["gradlew", "gradlew.bat", "settings.gradle", "settings.gradle.kts"].map((name) => path22.join(projectRoot, name)).find((file) => fs18.existsSync(file)) ?? null;
3971
3814
  const checks = [
3972
3815
  ...Object.entries(commands).map(([id, value]) => ({ id, status: value.available ? "passed" : "failed", summary: value.available ? value.output || `${id} available` : `${id} unavailable`, evidence: [value.command] })),
3973
- { id: "android-sdk", status: androidSdk ? "passed" : "failed", summary: androidSdk ?? "Android SDK not found", evidence: androidSdk ? [androidSdk] : [] },
3974
- { id: "deveco", status: devecoHome ? "passed" : "failed", summary: devecoHome ?? "DevEco Studio not found", evidence: devecoHome ? [devecoHome] : [] },
3975
- { id: "android-build-entry", status: androidBuildEntry ? "passed" : "failed", summary: androidBuildEntry ?? "Android build entry not found", evidence: androidBuildEntry ? [androidBuildEntry] : [] }
3816
+ { id: "deveco", status: devecoHome ? "passed" : "failed", summary: devecoHome ?? "DevEco Studio not found", evidence: devecoHome ? [devecoHome] : [] }
3976
3817
  ];
3977
3818
  const nodeMajor = Number.parseInt(process.versions.node.split(".")[0], 10);
3978
3819
  const pythonMatch = commands.python.output.match(/(\d+)\.(\d+)/);
@@ -3981,7 +3822,7 @@ function checkToolchain(project, env = process.env) {
3981
3822
  if (nodeCheck && (nodeMajor < 20 || nodeMajor > 24)) Object.assign(nodeCheck, { status: "failed", summary: `Node.js ${process.versions.node} is outside supported range 20-24` });
3982
3823
  const pythonCheck = checks.find((item) => item.id === "python");
3983
3824
  if (pythonCheck && !pythonSupported) Object.assign(pythonCheck, { status: "failed", summary: `${commands.python.output || "Python unavailable"}; Python 3.10+ is required` });
3984
- return { status: checks.some((item) => item.status === "failed") ? "failed" : "passed", checks, commands, androidSdk, devecoHome, androidBuildEntry };
3825
+ return { status: checks.some((item) => item.status === "failed") ? "failed" : "passed", checks, commands, devecoHome };
3985
3826
  }
3986
3827
  function checkAppGraphHealth() {
3987
3828
  const version = probe("appgraph", ["--version"]);
@@ -3993,7 +3834,7 @@ function checkAppGraphHealth() {
3993
3834
 
3994
3835
  // src/commands/preflight-checklist.ts
3995
3836
  import fs19 from "fs";
3996
- import path23 from "path";
3837
+ import path22 from "path";
3997
3838
  var PREFLIGHT_CHECKS = [
3998
3839
  { id: "android-ui-stack", name: "Android UI \u6280\u672F\u6808", required: true },
3999
3840
  { id: "git", name: "Git \u4ED3\u5E93\u548C\u5DE5\u4F5C\u533A", required: true },
@@ -4027,7 +3868,7 @@ function validateChecklist(value) {
4027
3868
  return value;
4028
3869
  }
4029
3870
  function loadPreflightChecklist(file) {
4030
- const checklistPath = path23.resolve(file);
3871
+ const checklistPath = path22.resolve(file);
4031
3872
  if (!fs19.existsSync(checklistPath)) throw new Error(`Preflight checklist not found: ${checklistPath}`);
4032
3873
  let checklist;
4033
3874
  try {
@@ -4045,7 +3886,7 @@ function loadPreflightChecklist(file) {
4045
3886
 
4046
3887
  // src/commands/preflight.ts
4047
3888
  var atomicJson2 = (file, value) => {
4048
- fs20.mkdirSync(path24.dirname(file), { recursive: true });
3889
+ fs20.mkdirSync(path23.dirname(file), { recursive: true });
4049
3890
  const temporary = `${file}.${process.pid}.tmp`;
4050
3891
  fs20.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}
4051
3892
  `, "utf8");
@@ -4059,7 +3900,7 @@ function check(id, result, required = true) {
4059
3900
  return entry;
4060
3901
  }
4061
3902
  async function runPreflight(options) {
4062
- const projectRoot = path24.resolve(options.project);
3903
+ const projectRoot = path23.resolve(options.project);
4063
3904
  if (!fs20.existsSync(projectRoot) || !fs20.statSync(projectRoot).isDirectory()) throw new Error(`Android project directory not found: ${projectRoot}`);
4064
3905
  const timeoutMs = options.timeoutMs ?? 30 * 60 * 1e3;
4065
3906
  const checks = [];
@@ -4071,7 +3912,7 @@ async function runPreflight(options) {
4071
3912
  const enabled = (id) => checklist.isEnabled(id);
4072
3913
  const skip = (id) => checks.push(check(id, { status: "skipped", summary: "Skipped by preflight checklist", evidence: [checklist.checklistPath] }, required(id)));
4073
3914
  const environmentRemediation2 = {
4074
- toolchain: "\u4E3B\u673A\u5DE5\u5177\u94FE\u4E0D\u5B8C\u6574\u3002\u8BF7\u5B89\u88C5/\u914D\u7F6E\u7F3A\u5931\u7EC4\u4EF6\uFF08git\u3001Node.js 20-24\u3001npm\u3001Python 3.10+\u3001JDK\u3001Android SDK\u3001DevEco \u5DE5\u5177\u94FE\uFF09\uFF0C\u7136\u540E\u91CD\u65B0\u8FD0\u884C droid2hmos preflight\u3002",
3915
+ toolchain: "\u4E3B\u673A\u5DE5\u5177\u94FE\u4E0D\u5B8C\u6574\u3002\u8BF7\u5B89\u88C5/\u914D\u7F6E\u7F3A\u5931\u7EC4\u4EF6\uFF08git\u3001Node.js 20-24\u3001npm\u3001Python 3.10+\u3001JDK\u3001DevEco \u5DE5\u5177\u94FE\uFF09\uFF0C\u7136\u540E\u91CD\u65B0\u8FD0\u884C droid2hmos preflight\u3002",
4075
3916
  appgraph: "appgraph CLI \u4E0D\u53EF\u7528\u6216\u4E0D\u517C\u5BB9\u3002\u8BF7\u5148\u6267\u884C npm install -g @harmonyos-arkts/appgraph@latest --force\uFF0C\u7136\u540E\u91CD\u65B0\u8FD0\u884C droid2hmos preflight\u3002",
4076
3917
  "harmony-sdk": "\u672A\u53D1\u73B0\u53EF\u7528\u7684 HarmonyOS SDK\u3002\u8BF7\u5728 DevEco Studio \u4E2D\u5B89\u88C5 SDK\uFF0C\u7136\u540E\u91CD\u65B0\u8FD0\u884C droid2hmos preflight\u3002",
4077
3918
  "harmony-device": "\u672A\u53D1\u73B0\u53EF\u54CD\u5E94\u7684 HarmonyOS \u8BBE\u5907/\u6A21\u62DF\u5668\u3002\u8BF7\u8FDE\u63A5\u5E76\u6388\u6743\u771F\u673A\u6216\u542F\u52A8\u6A21\u62DF\u5668\uFF0C\u786E\u4FDD DevEco CLI \u53EF\u53D1\u73B0\u4E14 shell \u53EF\u54CD\u5E94\uFF0C\u7136\u540E\u91CD\u65B0\u8FD0\u884C droid2hmos preflight\u3002"
@@ -4104,12 +3945,12 @@ async function runPreflight(options) {
4104
3945
  let harmonyProject = defaultHarmonyProjectPath(projectRoot);
4105
3946
  let existingConfig = null;
4106
3947
  try {
4107
- existingConfig = JSON.parse(fs20.readFileSync(path24.join(migrationDirectory(projectRoot), "config.json"), "utf8"));
3948
+ existingConfig = JSON.parse(fs20.readFileSync(path23.join(migrationDirectory(projectRoot), "config.json"), "utf8"));
4108
3949
  const configuredHarmony = existingConfig.harmonyProject ?? defaultHarmonyProjectPath(projectRoot);
4109
- harmonyProject = path24.isAbsolute(configuredHarmony) ? configuredHarmony : path24.resolve(projectRoot, configuredHarmony);
3950
+ harmonyProject = path23.isAbsolute(configuredHarmony) ? configuredHarmony : path23.resolve(projectRoot, configuredHarmony);
4110
3951
  } catch {
4111
3952
  }
4112
- const relativeHarmony = path24.relative(projectRoot, harmonyProject).replaceAll("\\", "/");
3953
+ const relativeHarmony = path23.relative(projectRoot, harmonyProject).replaceAll("\\", "/");
4113
3954
  const ignoreUntrackedRoots = existingConfig && relativeHarmony && !relativeHarmony.startsWith("../") ? [relativeHarmony] : [];
4114
3955
  git2 = gitWorkspaceStatus(projectRoot, { ignoreUntrackedRoots });
4115
3956
  const hasRequiredFailures = () => checks.some((item) => item.required && item.status !== "passed" && item.status !== "skipped");
@@ -4126,7 +3967,7 @@ async function runPreflight(options) {
4126
3967
  result,
4127
3968
  checks: checks.map(({ id, status, summary, evidence: evidence2, retry }) => retry ? { id, status, summary, evidence: evidence2, retry } : { id, status, summary, evidence: evidence2 })
4128
3969
  };
4129
- const reportPath = path24.join(migrationDirectory(projectRoot), "preflight-report.json");
3970
+ const reportPath = path23.join(migrationDirectory(projectRoot), "preflight-report.json");
4130
3971
  atomicJson2(reportPath, report);
4131
3972
  if (showProgress) progressOutput.write(`[preflight] \u5B8C\u6210: ${result}\uFF08\u5171 ${checks.length} \u9879\u68C0\u67E5\uFF0C\u5FC5\u9700\u5931\u8D25 ${requiredFailures.length} \u9879\uFF09\uFF0C\u62A5\u544A: ${reportPath}
4132
3973
  `);
@@ -4174,7 +4015,7 @@ async function runPreflight(options) {
4174
4015
  let configInit;
4175
4016
  if (enabled("config")) displayGate("config");
4176
4017
  try {
4177
- const existingConfigPath = path24.join(migrationDirectory(projectRoot), "config.json");
4018
+ const existingConfigPath = path23.join(migrationDirectory(projectRoot), "config.json");
4178
4019
  configInit = !enabled("config") && fs20.existsSync(existingConfigPath) ? { configPath: existingConfigPath, config: JSON.parse(fs20.readFileSync(existingConfigPath, "utf8")) } : initMigrationConfig({ project: projectRoot });
4179
4020
  } catch (error) {
4180
4021
  checks.push(check("config", { status: "failed", summary: error instanceof Error ? error.message : String(error) }));
@@ -4182,26 +4023,27 @@ async function runPreflight(options) {
4182
4023
  }
4183
4024
  const configPath = configInit.configPath;
4184
4025
  const config = configInit.config;
4185
- const configuredHarmonyProject = typeof config.harmonyProject === "string" ? config.harmonyProject : path24.relative(projectRoot, defaultHarmonyProjectPath(projectRoot));
4186
- harmonyProject = path24.isAbsolute(configuredHarmonyProject) ? configuredHarmonyProject : path24.resolve(projectRoot, configuredHarmonyProject);
4026
+ const configuredHarmonyProject = typeof config.harmonyProject === "string" ? config.harmonyProject : path23.relative(projectRoot, defaultHarmonyProjectPath(projectRoot));
4027
+ harmonyProject = path23.isAbsolute(configuredHarmonyProject) ? configuredHarmonyProject : path23.resolve(projectRoot, configuredHarmonyProject);
4187
4028
  if (enabled("config")) checks.push(check("config", { ...validateMigrationConfig(projectRoot), summary: "Migration config initialized and validated", evidence: [configPath] }));
4188
4029
  else skip("config");
4189
4030
  if (hasRequiredFailures()) return finish();
4190
4031
  if (!enabled("android-build")) skip("android-build");
4191
- else if (config.commands?.androidBuild) {
4192
- const androidBuild = await displayBuildProgress("android-build", () => runConfigCommandAsync({ configPath, key: "androidBuild", timeoutMs }));
4193
- checks.push(check("android-build", androidBuild, required("android-build")));
4194
- } else {
4032
+ else {
4195
4033
  displayGate("android-build");
4196
4034
  checks.push(check("android-build", {
4197
4035
  status: "not_applicable",
4198
- summary: "Android \u6784\u5EFA\u672A\u914D\u7F6E\uFF08\u8FC1\u79FB\u9ED8\u8BA4\u4E0D\u7F16\u8BD1 Android \u5DE5\u7A0B\uFF09\uFF1B\u5982\u9700\u53CC\u7AEF\u8FD0\u884C\u5BF9\u7167\u53EF\u663E\u5F0F\u914D\u7F6E commands.androidBuild"
4036
+ summary: "Android \u6784\u5EFA\u80FD\u529B\u5DF2\u79FB\u9664\uFF08\u8FC1\u79FB\u6D41\u7A0B\u4E0D\u4F9D\u8D56 Android \u7F16\u8BD1\u73AF\u5883\uFF09\uFF1B\u53CC\u7AEF\u5BF9\u7167\u4EE5\u6E90\u7801\u4E0E\u9759\u6001 facts \u4E3A\u57FA\u51C6"
4199
4037
  }, required("android-build")));
4200
4038
  }
4201
- if (enabled("android-emulator")) {
4039
+ if (!enabled("android-emulator")) skip("android-emulator");
4040
+ else {
4202
4041
  displayGate("android-emulator");
4203
- checks.push(check("android-emulator", checkAndroidEmulator(), false));
4204
- } else skip("android-emulator");
4042
+ checks.push(check("android-emulator", {
4043
+ status: "not_applicable",
4044
+ summary: "Android \u6A21\u62DF\u5668\u68C0\u6D4B\u5DF2\u79FB\u9664\uFF08\u8FC1\u79FB\u6D41\u7A0B\u4E0D\u4F9D\u8D56 Android \u8FD0\u884C\u73AF\u5883\uFF09"
4045
+ }, required("android-emulator")));
4046
+ }
4205
4047
  if (hasRequiredFailures()) return finish();
4206
4048
  if (enabled("harmony-project")) {
4207
4049
  displayGate("harmony-project");
@@ -4240,9 +4082,9 @@ async function runPreflight(options) {
4240
4082
  }
4241
4083
 
4242
4084
  // src/commands/deveco-auth-login.ts
4243
- import { spawn as spawn3, spawnSync as spawnSync7 } from "child_process";
4085
+ import { spawnSync as spawnSync6 } from "child_process";
4244
4086
  import { closeSync as closeSync3, existsSync as existsSync6, mkdirSync as mkdirSync3, openSync as openSync3, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
4245
- import path25 from "path";
4087
+ import path24 from "path";
4246
4088
  import { setTimeout as delay2 } from "timers/promises";
4247
4089
  var MANAGED_LOGIN_SERVICE_NAME = "devecocli-auth";
4248
4090
  var LOGIN_URL_PATTERN = /https?:\/\/[^\s"'<>]+/i;
@@ -4251,13 +4093,13 @@ function isDevEcoCliLoginCommand(commandLine) {
4251
4093
  return DEVECO_LOGIN_COMMAND_PATTERN.test(commandLine);
4252
4094
  }
4253
4095
  function managedLoginStatePaths(project) {
4254
- const directory = path25.join(migrationDirectory(project), "services");
4255
- const pidFile = path25.join(directory, `${MANAGED_LOGIN_SERVICE_NAME}.pid`);
4096
+ const directory = path24.join(migrationDirectory(project), "services");
4097
+ const pidFile = path24.join(directory, `${MANAGED_LOGIN_SERVICE_NAME}.pid`);
4256
4098
  return {
4257
4099
  directory,
4258
4100
  pidFile,
4259
- stdoutLog: path25.join(directory, `${MANAGED_LOGIN_SERVICE_NAME}.stdout.log`),
4260
- stderrLog: path25.join(directory, `${MANAGED_LOGIN_SERVICE_NAME}.stderr.log`),
4101
+ stdoutLog: path24.join(directory, `${MANAGED_LOGIN_SERVICE_NAME}.stdout.log`),
4102
+ stderrLog: path24.join(directory, `${MANAGED_LOGIN_SERVICE_NAME}.stderr.log`),
4261
4103
  exitFile: `${pidFile}.exit`,
4262
4104
  errorFile: `${pidFile}.error`
4263
4105
  };
@@ -4288,7 +4130,7 @@ function tailFile(file, maxChars) {
4288
4130
  }
4289
4131
  }
4290
4132
  function listPosixProcesses() {
4291
- const result = spawnSync7("ps", ["-eo", "pid=,command="], { encoding: "utf8", timeout: 15e3 });
4133
+ const result = spawnSync6("ps", ["-eo", "pid=,command="], { encoding: "utf8", timeout: 15e3 });
4292
4134
  if (result.error || result.status !== 0) return [];
4293
4135
  return result.stdout.split("\n").flatMap((line) => {
4294
4136
  const trimmed = line.trim();
@@ -4296,15 +4138,15 @@ function listPosixProcesses() {
4296
4138
  const separator = trimmed.indexOf(" ");
4297
4139
  if (separator <= 0) return [];
4298
4140
  const pid = Number.parseInt(trimmed.slice(0, separator), 10);
4299
- const command3 = trimmed.slice(separator + 1);
4300
- return Number.isInteger(pid) && pid > 0 ? [{ pid, command: command3 }] : [];
4141
+ const command2 = trimmed.slice(separator + 1);
4142
+ return Number.isInteger(pid) && pid > 0 ? [{ pid, command: command2 }] : [];
4301
4143
  });
4302
4144
  }
4303
4145
  function listWindowsProcesses() {
4304
4146
  const source = 'Get-CimInstance Win32_Process -Filter "CommandLine IS NOT NULL" | ForEach-Object { "$($_.ProcessId)`t$($_.CommandLine)" }';
4305
4147
  const encoded = Buffer.from(source, "utf16le").toString("base64");
4306
- const powershell = process.env.SystemRoot ? path25.resolve(process.env.SystemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe") : "powershell.exe";
4307
- const result = spawnSync7(powershell, ["-NoLogo", "-NoProfile", "-NonInteractive", "-EncodedCommand", encoded], {
4148
+ const powershell = process.env.SystemRoot ? path24.resolve(process.env.SystemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe") : "powershell.exe";
4149
+ const result = spawnSync6(powershell, ["-NoLogo", "-NoProfile", "-NonInteractive", "-EncodedCommand", encoded], {
4308
4150
  encoding: "utf8",
4309
4151
  timeout: 3e4,
4310
4152
  windowsHide: true
@@ -4314,8 +4156,8 @@ function listWindowsProcesses() {
4314
4156
  const separator = line.indexOf(" ");
4315
4157
  if (separator <= 0) return [];
4316
4158
  const pid = Number.parseInt(line.slice(0, separator), 10);
4317
- const command3 = line.slice(separator + 1);
4318
- return Number.isInteger(pid) && pid > 0 ? [{ pid, command: command3 }] : [];
4159
+ const command2 = line.slice(separator + 1);
4160
+ return Number.isInteger(pid) && pid > 0 ? [{ pid, command: command2 }] : [];
4319
4161
  });
4320
4162
  }
4321
4163
  function listDevEcoCliLoginProcesses(options = {}) {
@@ -4327,7 +4169,7 @@ function listDevEcoCliLoginProcesses(options = {}) {
4327
4169
  }
4328
4170
  async function terminateProcessTree(pid) {
4329
4171
  if (process.platform === "win32") {
4330
- spawnSync7("taskkill", ["/PID", String(pid), "/T", "/F"], { encoding: "utf8", timeout: 15e3, windowsHide: true });
4172
+ spawnSync6("taskkill", ["/PID", String(pid), "/T", "/F"], { encoding: "utf8", timeout: 15e3, windowsHide: true });
4331
4173
  return;
4332
4174
  }
4333
4175
  try {
@@ -4345,7 +4187,7 @@ async function terminateProcessTree(pid) {
4345
4187
  }
4346
4188
  }
4347
4189
  async function killOrphanDevEcoCliLogins(options) {
4348
- const state = managedLoginStatePaths(path25.resolve(options.project));
4190
+ const state = managedLoginStatePaths(path24.resolve(options.project));
4349
4191
  const terminate = options.terminator ?? terminateProcessTree;
4350
4192
  const targets = /* @__PURE__ */ new Set();
4351
4193
  const managedPid = readPidFile(state.pidFile);
@@ -4373,12 +4215,12 @@ async function killOrphanDevEcoCliLogins(options) {
4373
4215
  }
4374
4216
  function runDetachedDevEcoAuthHost(requestPath) {
4375
4217
  const request = JSON.parse(readFileSync5(requestPath, "utf8"));
4376
- mkdirSync3(path25.dirname(request.stdout), { recursive: true });
4377
- mkdirSync3(path25.dirname(request.stderr), { recursive: true });
4218
+ mkdirSync3(path24.dirname(request.stdout), { recursive: true });
4219
+ mkdirSync3(path24.dirname(request.stderr), { recursive: true });
4378
4220
  const stdout = openSync3(request.stdout, "a");
4379
4221
  const stderr = openSync3(request.stderr, "a");
4380
4222
  try {
4381
- const child = spawn3(request.command[0], request.command.slice(1), {
4223
+ const child = spawnCommand(request.command[0], request.command.slice(1), {
4382
4224
  cwd: request.cwd,
4383
4225
  detached: true,
4384
4226
  env: { ...process.env, ...request.env ?? {} },
@@ -4386,6 +4228,13 @@ function runDetachedDevEcoAuthHost(requestPath) {
4386
4228
  windowsHide: true
4387
4229
  });
4388
4230
  const stdin = child.stdin;
4231
+ if (!stdin) {
4232
+ try {
4233
+ child.kill();
4234
+ } catch {
4235
+ }
4236
+ throw new Error("DevEco CLI \u767B\u5F55\u8FDB\u7A0B\u7684 stdin \u4E0D\u53EF\u7528");
4237
+ }
4389
4238
  stdin.on("error", () => {
4390
4239
  });
4391
4240
  stdin.write("\n");
@@ -4437,7 +4286,7 @@ async function managedDevEcoCliLogin(options) {
4437
4286
  const statusTimeoutMs = options.statusTimeoutMs ?? 15e3;
4438
4287
  const progress = options.onProgress ?? (() => {
4439
4288
  });
4440
- const projectRoot = path25.resolve(options.project);
4289
+ const projectRoot = path24.resolve(options.project);
4441
4290
  const state = managedLoginStatePaths(projectRoot);
4442
4291
  const failure = (summary, extra = {}) => ({
4443
4292
  status: "failed",
@@ -4476,7 +4325,7 @@ async function managedDevEcoCliLogin(options) {
4476
4325
  stdout: state.stdoutLog,
4477
4326
  stderr: state.stderrLog,
4478
4327
  command: devEcoCliSpawn(executable, ["auth", "login"]),
4479
- env: { DEVECO_CLI_SKIP_VERSION_CHECK: "1" }
4328
+ env: devEcoCliEnvironment()
4480
4329
  }, null, 2), "utf8");
4481
4330
  launchHostOutsideRunner(requestPath, options.hostCommand);
4482
4331
  progress(`\u5DF2\u6301\u4E45\u62C9\u8D77\u767B\u5F55\u8FDB\u7A0B\uFF08\u56DE\u8C03\u670D\u52A1\u5668\u968F\u8FDB\u7A0B\u5B58\u6D3B\uFF09\uFF0C\u65E5\u5FD7: ${state.stdoutLog}`);
@@ -4531,17 +4380,17 @@ async function managedDevEcoCliLogin(options) {
4531
4380
 
4532
4381
  // src/commands/device-evidence.ts
4533
4382
  import fs21 from "fs";
4534
- import path26 from "path";
4383
+ import path25 from "path";
4535
4384
  import { createHash as createHash6 } from "crypto";
4536
- import { spawnSync as spawnSync8 } from "child_process";
4385
+ import { spawnSync as spawnSync7 } from "child_process";
4537
4386
  import { inflateSync } from "zlib";
4538
4387
  function safeName(value) {
4539
4388
  if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(value)) throw new Error("evidence name must use letters, digits, dot, underscore, or dash");
4540
4389
  return value;
4541
4390
  }
4542
- function run(command3, args, encoding = "utf8") {
4543
- const script = /\.m?js$/i.test(command3);
4544
- const result = spawnSync8(script ? process.execPath : command3, script ? [command3, ...args] : args, {
4391
+ function run(command2, args, encoding = "utf8") {
4392
+ const script = /\.m?js$/i.test(command2);
4393
+ const result = spawnSync7(script ? process.execPath : command2, script ? [command2, ...args] : args, {
4545
4394
  encoding: encoding === "buffer" ? null : encoding,
4546
4395
  shell: false,
4547
4396
  timeout: 3e4,
@@ -4553,7 +4402,7 @@ function run(command3, args, encoding = "utf8") {
4553
4402
  status: Number.isInteger(result.status) ? result.status : null,
4554
4403
  stdout: result.stdout ?? (encoding === "buffer" ? Buffer.alloc(0) : ""),
4555
4404
  stderr: String(result.stderr ?? result.error?.message ?? ""),
4556
- invocation: [command3, ...args]
4405
+ invocation: [command2, ...args]
4557
4406
  };
4558
4407
  }
4559
4408
  function writeResult(file, result) {
@@ -4649,72 +4498,39 @@ function pixelMetrics(leftFile, rightFile) {
4649
4498
  return null;
4650
4499
  }
4651
4500
  }
4652
- function selectTarget(command3, listArgs, explicit) {
4653
- if (explicit) return explicit;
4654
- const listed = run(command3, listArgs);
4655
- if (!listed.ok) throw new Error(`${command3} target discovery failed: ${listed.stderr}`);
4656
- const lines = String(listed.stdout).split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
4657
- if (command3 === "adb") {
4658
- const found = lines.filter((line) => !line.startsWith("List of devices")).map((line) => line.split(/\s+/)).find((parts) => parts[1] === "device");
4659
- if (found) return found[0];
4660
- } else {
4661
- const found = lines.find((line) => !/^\[?empty\]?$/i.test(line) && !/^list of targets/i.test(line));
4662
- if (found) return found;
4663
- }
4664
- throw new Error(`No responsive ${command3} target found`);
4665
- }
4666
4501
  function captureDeviceEvidence(options) {
4667
- const projectRoot = path26.resolve(options.project);
4502
+ const projectRoot = path25.resolve(options.project);
4668
4503
  const migrationRoot = migrationDirectory(projectRoot);
4669
4504
  const workspaceRoot = migrationWorkspaceRoot(projectRoot);
4670
4505
  const name = safeName(options.name);
4671
4506
  const capturedAt = /* @__PURE__ */ new Date();
4672
- const directory = path26.join(migrationRoot, "evidence", name, options.platform);
4507
+ const directory = path25.join(migrationRoot, "evidence", name, options.platform);
4673
4508
  fs21.mkdirSync(directory, { recursive: true });
4674
- const screenshot = path26.join(directory, "screenshot.png");
4675
- const layout = path26.join(directory, options.platform === "android" ? "layout.xml" : "layout.json");
4676
- const logs = path26.join(directory, "device.log");
4509
+ const screenshot = path25.join(directory, "screenshot.png");
4510
+ const layout = path25.join(directory, "layout.json");
4511
+ const logs = path25.join(directory, "device.log");
4677
4512
  const commands = [];
4678
4513
  let target;
4679
- if (options.platform === "android") {
4680
- const adb = options.adb ?? process.env.ADB_BINARY ?? "adb";
4681
- target = selectTarget(adb, ["devices"], options.target);
4682
- const prefix = ["-s", target];
4683
- const shot = run(adb, [...prefix, "exec-out", "screencap", "-p"], "buffer");
4684
- commands.push(shot);
4685
- writeResult(screenshot, shot);
4686
- const remote = `/sdcard/droid2hmos-${process.pid}.xml`;
4687
- const dump = run(adb, [...prefix, "shell", "uiautomator", "dump", remote]);
4688
- commands.push(dump);
4689
- const tree = run(adb, [...prefix, "exec-out", "cat", remote]);
4690
- commands.push(tree);
4691
- writeResult(layout, tree);
4692
- run(adb, [...prefix, "shell", "rm", "-f", remote]);
4693
- const log = run(adb, [...prefix, "logcat", "-d", "-t", "1000"]);
4694
- commands.push(log);
4695
- writeResult(logs, log);
4696
- } else {
4697
- const hdc = options.hdc ?? process.env.HDC_BINARY ?? "hdc";
4698
- const listed = run(hdc, ["list", "targets"]);
4699
- const candidates = String(listed.stdout).split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !/^\[?empty\]?$/i.test(line) && !/^list of targets/i.test(line));
4700
- if (!listed.ok || !candidates.length) throw new Error(`${hdc} target discovery failed: ${listed.stderr || "no targets"}`);
4701
- target = options.target ?? process.env.DROID2HMOS_HDC_TARGET ?? selectPreferredTarget(candidates);
4702
- const prefix = ["-t", target];
4703
- const remoteShot = `/data/local/tmp/droid2hmos-${process.pid}.png`;
4704
- const remoteLayout = `/data/local/tmp/droid2hmos-${process.pid}.json`;
4705
- const shot = run(hdc, [...prefix, "shell", "snapshot_display", "-f", remoteShot]);
4706
- commands.push(shot);
4707
- const recvShot = run(hdc, [...prefix, "file", "recv", remoteShot, screenshot]);
4708
- commands.push(recvShot);
4709
- const dump = run(hdc, [...prefix, "shell", "uitest", "dumpLayout", "-p", remoteLayout]);
4710
- commands.push(dump);
4711
- const recvLayout = run(hdc, [...prefix, "file", "recv", remoteLayout, layout]);
4712
- commands.push(recvLayout);
4713
- run(hdc, [...prefix, "shell", "rm", "-f", remoteShot, remoteLayout]);
4714
- const log = run(hdc, [...prefix, "shell", "hilog", "-x"]);
4715
- commands.push(log);
4716
- writeResult(logs, log);
4717
- }
4514
+ const hdc = options.hdc ?? process.env.HDC_BINARY ?? "hdc";
4515
+ const listed = run(hdc, ["list", "targets"]);
4516
+ const candidates = String(listed.stdout).split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !/^\[?empty\]?$/i.test(line) && !/^list of targets/i.test(line));
4517
+ if (!listed.ok || !candidates.length) throw new Error(`${hdc} target discovery failed: ${listed.stderr || "no targets"}`);
4518
+ target = options.target ?? process.env.DROID2HMOS_HDC_TARGET ?? selectPreferredTarget(candidates);
4519
+ const prefix = ["-t", target];
4520
+ const remoteShot = `/data/local/tmp/droid2hmos-${process.pid}.png`;
4521
+ const remoteLayout = `/data/local/tmp/droid2hmos-${process.pid}.json`;
4522
+ const shot = run(hdc, [...prefix, "shell", "snapshot_display", "-f", remoteShot]);
4523
+ commands.push(shot);
4524
+ const recvShot = run(hdc, [...prefix, "file", "recv", remoteShot, screenshot]);
4525
+ commands.push(recvShot);
4526
+ const dump = run(hdc, [...prefix, "shell", "uitest", "dumpLayout", "-p", remoteLayout]);
4527
+ commands.push(dump);
4528
+ const recvLayout = run(hdc, [...prefix, "file", "recv", remoteLayout, layout]);
4529
+ commands.push(recvLayout);
4530
+ run(hdc, [...prefix, "shell", "rm", "-f", remoteShot, remoteLayout]);
4531
+ const log = run(hdc, [...prefix, "shell", "hilog", "-x"]);
4532
+ commands.push(log);
4533
+ writeResult(logs, log);
4718
4534
  const artifacts = [screenshot, layout, logs].map(fileInfo).filter(Boolean);
4719
4535
  const failures = commands.filter((item) => !item.ok).map((item) => ({ invocation: item.invocation, status: item.status, stderr: item.stderr }));
4720
4536
  const metadata = {
@@ -4724,10 +4540,10 @@ function captureDeviceEvidence(options) {
4724
4540
  target,
4725
4541
  capturedAt: capturedAt.toISOString(),
4726
4542
  status: failures.length === 0 && artifacts.length === 3 ? "passed" : "failed",
4727
- artifacts: artifacts.map((item) => ({ ...item, path: path26.relative(workspaceRoot, item.path).replaceAll("\\", "/") })),
4543
+ artifacts: artifacts.map((item) => ({ ...item, path: path25.relative(workspaceRoot, item.path).replaceAll("\\", "/") })),
4728
4544
  failures
4729
4545
  };
4730
- const metadataPath = path26.join(directory, "evidence.json");
4546
+ const metadataPath = path25.join(directory, "evidence.json");
4731
4547
  fs21.writeFileSync(metadataPath, `${JSON.stringify(metadata, null, 2)}
4732
4548
  `, "utf8");
4733
4549
  return { ...metadata, metadataPath };
@@ -4737,24 +4553,24 @@ function tokens(file) {
4737
4553
  return new Set(fs21.readFileSync(file, "utf8").toLowerCase().match(/[\p{L}\p{N}_-]{2,}/gu) ?? []);
4738
4554
  }
4739
4555
  function compareEvidence(options) {
4740
- const projectRoot = path26.resolve(options.project);
4556
+ const projectRoot = path25.resolve(options.project);
4741
4557
  const migrationRoot = migrationDirectory(projectRoot);
4742
4558
  const resolveEvidence = (value) => {
4743
4559
  const normalized2 = value.replaceAll("\\", "/");
4744
- const root = path26.isAbsolute(value) ? value : (() => {
4745
- if (normalized2.startsWith(".migration/")) return path26.resolve(migrationWorkspaceRoot(projectRoot), value);
4746
- return path26.resolve(projectRoot, value);
4560
+ const root = path25.isAbsolute(value) ? value : (() => {
4561
+ if (normalized2.startsWith(".migration/")) return path25.resolve(migrationWorkspaceRoot(projectRoot), value);
4562
+ return path25.resolve(projectRoot, value);
4747
4563
  })();
4748
- return fs21.statSync(root).isDirectory() ? root : path26.dirname(root);
4564
+ return fs21.statSync(root).isDirectory() ? root : path25.dirname(root);
4749
4565
  };
4750
4566
  const androidRoot = resolveEvidence(options.android);
4751
- const harmonyRoot = resolveEvidence(options.harmony);
4752
- const androidShot = fileInfo(path26.join(androidRoot, "screenshot.png"));
4753
- const harmonyShot = fileInfo(path26.join(harmonyRoot, "screenshot.png"));
4754
- const pixels = pixelMetrics(path26.join(androidRoot, "screenshot.png"), path26.join(harmonyRoot, "screenshot.png"));
4755
- const layoutFile = (root) => [path26.join(root, "layout.json"), path26.join(root, "layout.xml")].find((file) => fs21.existsSync(file)) ?? path26.join(root, "layout.json");
4567
+ const harmonyRoot2 = resolveEvidence(options.harmony);
4568
+ const androidShot = fileInfo(path25.join(androidRoot, "screenshot.png"));
4569
+ const harmonyShot = fileInfo(path25.join(harmonyRoot2, "screenshot.png"));
4570
+ const pixels = pixelMetrics(path25.join(androidRoot, "screenshot.png"), path25.join(harmonyRoot2, "screenshot.png"));
4571
+ const layoutFile = (root) => [path25.join(root, "layout.json"), path25.join(root, "layout.xml")].find((file) => fs21.existsSync(file)) ?? path25.join(root, "layout.json");
4756
4572
  const left = tokens(layoutFile(androidRoot));
4757
- const right = tokens(layoutFile(harmonyRoot));
4573
+ const right = tokens(layoutFile(harmonyRoot2));
4758
4574
  const intersection = [...left].filter((value) => right.has(value));
4759
4575
  const union = /* @__PURE__ */ new Set([...left, ...right]);
4760
4576
  const result = {
@@ -4762,7 +4578,7 @@ function compareEvidence(options) {
4762
4578
  name: safeName(options.name),
4763
4579
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4764
4580
  android: { root: androidRoot, screenshot: androidShot, layoutTokens: left.size },
4765
- harmony: { root: harmonyRoot, screenshot: harmonyShot, layoutTokens: right.size },
4581
+ harmony: { root: harmonyRoot2, screenshot: harmonyShot, layoutTokens: right.size },
4766
4582
  screenshot: {
4767
4583
  sameBytes: Boolean(androidShot && harmonyShot && androidShot.sha256 === harmonyShot.sha256),
4768
4584
  sameDimensions: Boolean(androidShot && harmonyShot && androidShot.width === harmonyShot.width && androidShot.height === harmonyShot.height),
@@ -4771,27 +4587,27 @@ function compareEvidence(options) {
4771
4587
  layout: { sharedTokens: intersection.length, unionTokens: union.size, jaccard: union.size ? intersection.length / union.size : 1 },
4772
4588
  limitation: "This deterministic comparison checks PNG pixels when decodable plus screenshot identity/dimensions and layout-token overlap; semantic visual parity still requires image inspection."
4773
4589
  };
4774
- const output = path26.join(migrationRoot, "evidence", safeName(options.name), "comparison.json");
4775
- fs21.mkdirSync(path26.dirname(output), { recursive: true });
4590
+ const output = path25.join(migrationRoot, "evidence", safeName(options.name), "comparison.json");
4591
+ fs21.mkdirSync(path25.dirname(output), { recursive: true });
4776
4592
  fs21.writeFileSync(output, `${JSON.stringify(result, null, 2)}
4777
4593
  `, "utf8");
4778
4594
  return { status: "passed", output, result };
4779
4595
  }
4780
4596
  function writeFlowBundle(options) {
4781
- const projectRoot = path26.resolve(options.project);
4597
+ const projectRoot = path25.resolve(options.project);
4782
4598
  const migrationRoot = migrationDirectory(projectRoot);
4783
- const output = path26.join(migrationRoot, "evidence", safeName(options.name), "flow.json");
4599
+ const output = path25.join(migrationRoot, "evidence", safeName(options.name), "flow.json");
4784
4600
  const value = {
4785
4601
  flowVersion: "1.0.0",
4786
4602
  name: options.name,
4787
4603
  platform: options.platform,
4788
4604
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4789
4605
  status: options.before.status === "passed" && options.execution.status === "passed" && options.after.status === "passed" ? "passed" : "failed",
4790
- before: path26.relative(migrationWorkspaceRoot(projectRoot), options.before.metadataPath).replaceAll("\\", "/"),
4606
+ before: path25.relative(migrationWorkspaceRoot(projectRoot), options.before.metadataPath).replaceAll("\\", "/"),
4791
4607
  execution: options.execution,
4792
- after: path26.relative(migrationWorkspaceRoot(projectRoot), options.after.metadataPath).replaceAll("\\", "/")
4608
+ after: path25.relative(migrationWorkspaceRoot(projectRoot), options.after.metadataPath).replaceAll("\\", "/")
4793
4609
  };
4794
- fs21.mkdirSync(path26.dirname(output), { recursive: true });
4610
+ fs21.mkdirSync(path25.dirname(output), { recursive: true });
4795
4611
  fs21.writeFileSync(output, `${JSON.stringify(value, null, 2)}
4796
4612
  `, "utf8");
4797
4613
  return { ...value, output };
@@ -4799,7 +4615,7 @@ function writeFlowBundle(options) {
4799
4615
 
4800
4616
  // src/commands/report-update.ts
4801
4617
  import fs22 from "fs";
4802
- import path27 from "path";
4618
+ import path26 from "path";
4803
4619
  var allowedStatuses = /* @__PURE__ */ new Set(["pending", "implemented", "verified", "blocked"]);
4804
4620
  function readJson3(file) {
4805
4621
  return JSON.parse(fs22.readFileSync(file, "utf8"));
@@ -4811,48 +4627,48 @@ function atomicJson3(file, value) {
4811
4627
  fs22.renameSync(temporary, file);
4812
4628
  }
4813
4629
  function readHarmonyRoot(projectRoot) {
4814
- const configPath = path27.join(migrationDirectory(projectRoot), "config.json");
4630
+ const configPath = path26.join(migrationDirectory(projectRoot), "config.json");
4815
4631
  if (!fs22.existsSync(configPath)) return null;
4816
4632
  const config = readJson3(configPath);
4817
4633
  const harmonyProject = config?.harmonyProject;
4818
4634
  if (typeof harmonyProject !== "string" || !harmonyProject) return null;
4819
- const harmonyRoot = path27.isAbsolute(harmonyProject) ? harmonyProject : path27.resolve(projectRoot, harmonyProject);
4820
- return fs22.existsSync(harmonyRoot) ? harmonyRoot : null;
4635
+ const harmonyRoot2 = path26.isAbsolute(harmonyProject) ? harmonyProject : path26.resolve(projectRoot, harmonyProject);
4636
+ return fs22.existsSync(harmonyRoot2) ? harmonyRoot2 : null;
4821
4637
  }
4822
- function insideProject(projectRoot, harmonyRoot, workspaceRoot, value, mustExist) {
4823
- const absolute = value.replaceAll("\\", "/").startsWith(".migration/") ? path27.resolve(workspaceRoot, value) : path27.resolve(projectRoot, value);
4824
- const relative2 = path27.relative(projectRoot, absolute).replaceAll("\\", "/");
4825
- const inProject = !!relative2 && !relative2.startsWith("../") && !path27.isAbsolute(relative2);
4638
+ function insideProject(projectRoot, harmonyRoot2, workspaceRoot, value, mustExist) {
4639
+ const absolute = value.replaceAll("\\", "/").startsWith(".migration/") ? path26.resolve(workspaceRoot, value) : path26.resolve(projectRoot, value);
4640
+ const relative2 = path26.relative(projectRoot, absolute).replaceAll("\\", "/");
4641
+ const inProject = !!relative2 && !relative2.startsWith("../") && !path26.isAbsolute(relative2);
4826
4642
  let inHarmony = false;
4827
- if (!inProject && harmonyRoot) {
4828
- const relativeHarmony = path27.relative(harmonyRoot, absolute).replaceAll("\\", "/");
4829
- inHarmony = !!relativeHarmony && !relativeHarmony.startsWith("../") && !path27.isAbsolute(relativeHarmony) && !relative2.startsWith("/");
4643
+ if (!inProject && harmonyRoot2) {
4644
+ const relativeHarmony = path26.relative(harmonyRoot2, absolute).replaceAll("\\", "/");
4645
+ inHarmony = !!relativeHarmony && !relativeHarmony.startsWith("../") && !path26.isAbsolute(relativeHarmony) && !relative2.startsWith("/");
4830
4646
  }
4831
- const migrationRelative = path27.relative(workspaceRoot, absolute).replaceAll("\\", "/");
4832
- const inMigration = !!migrationRelative && !migrationRelative.startsWith("../") && !path27.isAbsolute(migrationRelative);
4647
+ const migrationRelative = path26.relative(workspaceRoot, absolute).replaceAll("\\", "/");
4648
+ const inMigration = !!migrationRelative && !migrationRelative.startsWith("../") && !path26.isAbsolute(migrationRelative);
4833
4649
  if (!inProject && !inHarmony && !inMigration) throw new Error(`Path must be inside the project: ${value}`);
4834
4650
  if (mustExist && !fs22.existsSync(absolute)) throw new Error(`Referenced file not found: ${migrationRelative !== "" && inMigration ? migrationRelative : relative2}`);
4835
4651
  return inMigration ? migrationRelative : relative2;
4836
4652
  }
4837
4653
  function updateReportItem(options) {
4838
4654
  if (!options.status && options.harmonyosFiles === void 0 && options.androidSourceFiles === void 0 && options.sourceProvenance === void 0 && options.testEvidence === void 0 && options.difference === void 0) throw new Error("report update requires at least one mutable field");
4839
- const projectRoot = path27.resolve(options.project);
4840
- const harmonyRoot = readHarmonyRoot(projectRoot);
4841
- const reportRoot = path27.join(migrationDirectory(projectRoot), "report");
4842
- const indexPath = path27.join(reportRoot, "index.json");
4655
+ const projectRoot = path26.resolve(options.project);
4656
+ const harmonyRoot2 = readHarmonyRoot(projectRoot);
4657
+ const reportRoot = path26.join(migrationDirectory(projectRoot), "report");
4658
+ const indexPath = path26.join(reportRoot, "index.json");
4843
4659
  if (!fs22.existsSync(indexPath)) throw new Error("Migration report is not initialized");
4844
4660
  if (options.status && !allowedStatuses.has(options.status)) throw new Error(`Invalid report status: ${options.status}`);
4845
4661
  const index = readJson3(indexPath);
4846
4662
  const reportFile = (value) => {
4847
- if (typeof value !== "string" || !value.trim() || path27.isAbsolute(value)) throw new Error(`Report shard path must be report-relative: ${String(value)}`);
4848
- const absolute = path27.resolve(reportRoot, value);
4849
- const relation = path27.relative(reportRoot, absolute);
4850
- if (!relation || relation === ".." || relation.startsWith(`..${path27.sep}`) || path27.isAbsolute(relation)) throw new Error(`Report shard escapes report root: ${value}`);
4663
+ if (typeof value !== "string" || !value.trim() || path26.isAbsolute(value)) throw new Error(`Report shard path must be report-relative: ${String(value)}`);
4664
+ const absolute = path26.resolve(reportRoot, value);
4665
+ const relation = path26.relative(reportRoot, absolute);
4666
+ if (!relation || relation === ".." || relation.startsWith(`..${path26.sep}`) || path26.isAbsolute(relation)) throw new Error(`Report shard escapes report root: ${value}`);
4851
4667
  if (!fs22.existsSync(absolute) || !fs22.statSync(absolute).isFile()) throw new Error(`Report shard not found: ${value}`);
4852
4668
  const realRoot = fs22.realpathSync(reportRoot);
4853
4669
  const realFile = fs22.realpathSync(absolute);
4854
- const realRelation = path27.relative(realRoot, realFile);
4855
- if (realRelation === ".." || realRelation.startsWith(`..${path27.sep}`) || path27.isAbsolute(realRelation)) throw new Error(`Report shard resolves outside report root: ${value}`);
4670
+ const realRelation = path26.relative(realRoot, realFile);
4671
+ if (realRelation === ".." || realRelation.startsWith(`..${path26.sep}`) || path26.isAbsolute(realRelation)) throw new Error(`Report shard resolves outside report root: ${value}`);
4856
4672
  return realFile;
4857
4673
  };
4858
4674
  const candidates = [
@@ -4875,19 +4691,19 @@ function updateReportItem(options) {
4875
4691
  if (options.harmonyosFiles) match.item.harmonyosFiles = [...new Set(options.harmonyosFiles.map((file) => managedHarmonyFile(projectRoot, file).relative))];
4876
4692
  if (options.androidSourceFiles) match.item.sourceProvenance = provenanceForFiles(projectRoot, options.androidSourceFiles);
4877
4693
  if (options.sourceProvenance) match.item.sourceProvenance = options.sourceProvenance;
4878
- if (options.testEvidence) match.item.testEvidence = [...new Set(options.testEvidence.map((file) => insideProject(projectRoot, harmonyRoot, workspaceRoot, file, true)))];
4694
+ if (options.testEvidence) match.item.testEvidence = [...new Set(options.testEvidence.map((file) => insideProject(projectRoot, harmonyRoot2, workspaceRoot, file, true)))];
4879
4695
  if (options.difference !== void 0) match.item.difference = options.difference?.trim() || null;
4880
4696
  if (["implemented", "verified"].includes(match.item.status) && !match.item.harmonyosFiles?.length) throw new Error(`${match.item.status} requires at least one HarmonyOS implementation file`);
4881
4697
  if (["implemented", "verified"].includes(match.item.status) && !match.item.sourceProvenance?.length) throw new Error(`${match.item.status} requires at least one Android source provenance entry`);
4882
4698
  if (match.item.status === "verified" && !match.item.testEvidence?.length) throw new Error("verified requires at least one test evidence file");
4883
4699
  if (match.item.status === "blocked" && !match.item.difference) throw new Error("blocked requires --difference");
4884
4700
  atomicJson3(match.file, match.document);
4885
- return { status: "passed", ref: options.ref, file: path27.relative(projectRoot, match.file).replaceAll("\\", "/"), item: match.item };
4701
+ return { status: "passed", ref: options.ref, file: path26.relative(projectRoot, match.file).replaceAll("\\", "/"), item: match.item };
4886
4702
  }
4887
4703
 
4888
4704
  // src/commands/mock-server-generator.ts
4889
4705
  import fs23 from "fs";
4890
- import path28 from "path";
4706
+ import path27 from "path";
4891
4707
  var requiredScenarios = ["success", "empty", "business-failure", "boundary"];
4892
4708
  function validateSpec(spec) {
4893
4709
  const errors = [];
@@ -4947,21 +4763,21 @@ const server = http.createServer(async (request, response) => {
4947
4763
  server.listen(port, host, () => console.log(JSON.stringify({ status: "ready", host, port })));
4948
4764
  `;
4949
4765
  function generateMockServer(options) {
4950
- const projectRoot = path28.resolve(options.project);
4951
- const specFile = path28.resolve(options.specFile);
4766
+ const projectRoot = path27.resolve(options.project);
4767
+ const specFile = path27.resolve(options.specFile);
4952
4768
  if (!fs23.existsSync(specFile)) throw new Error(`Protocol spec not found: ${specFile}`);
4953
4769
  const spec = JSON.parse(fs23.readFileSync(specFile, "utf8"));
4954
4770
  const errors = validateSpec(spec);
4955
4771
  if (errors.length) throw new Error(`Invalid protocol spec: ${errors.join("; ")}`);
4956
- const root = path28.join(projectRoot, "mock-server");
4772
+ const root = path27.join(projectRoot, "mock-server");
4957
4773
  if (fs23.existsSync(root) && fs23.readdirSync(root).length && !options.force) throw new Error(`Mock server directory is not empty: ${root}; use --force to replace generated files`);
4958
4774
  fs23.mkdirSync(root, { recursive: true });
4959
- fs23.writeFileSync(path28.join(root, "scenarios.json"), `${JSON.stringify(spec, null, 2)}
4775
+ fs23.writeFileSync(path27.join(root, "scenarios.json"), `${JSON.stringify(spec, null, 2)}
4960
4776
  `, "utf8");
4961
- fs23.writeFileSync(path28.join(root, "server.mjs"), serverSource, "utf8");
4962
- fs23.writeFileSync(path28.join(root, "README.md"), `# Mock Server
4777
+ fs23.writeFileSync(path27.join(root, "server.mjs"), serverSource, "utf8");
4778
+ fs23.writeFileSync(path27.join(root, "README.md"), `# Mock Server
4963
4779
 
4964
- Generated from \`${path28.relative(projectRoot, specFile).replaceAll("\\", "/")}\`.
4780
+ Generated from \`${path27.relative(projectRoot, specFile).replaceAll("\\", "/")}\`.
4965
4781
 
4966
4782
  Start safely:
4967
4783
 
@@ -4976,16 +4792,16 @@ Select a scenario with query parameter \`?scenario=empty\` or header \`x-droid2h
4976
4792
 
4977
4793
  // src/commands/logs.ts
4978
4794
  import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync as readdirSync3 } from "fs";
4979
- import path29 from "path";
4795
+ import path28 from "path";
4980
4796
  function loadRecords(projectRoot) {
4981
- const directory = path29.join(migrationDirectory(projectRoot), "logs", "daily");
4797
+ const directory = path28.join(migrationDirectory(projectRoot), "logs", "daily");
4982
4798
  if (!existsSync7(directory)) return [];
4983
4799
  return readdirSync3(directory).filter((file) => file.endsWith(".jsonl")).sort().flatMap(
4984
- (file) => readFileSync6(path29.join(directory, file), "utf8").split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line))
4800
+ (file) => readFileSync6(path28.join(directory, file), "utf8").split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line))
4985
4801
  );
4986
4802
  }
4987
4803
  function queryCommandLogs(project, action, value) {
4988
- const projectRoot = path29.resolve(project);
4804
+ const projectRoot = path28.resolve(project);
4989
4805
  const records = loadRecords(projectRoot);
4990
4806
  if (action === "list") {
4991
4807
  const selected = value ? records.filter((record) => record.date === value) : records;
@@ -5016,7 +4832,7 @@ function queryCommandLogs(project, action, value) {
5016
4832
  // src/logging/command-log.ts
5017
4833
  import { appendFileSync, existsSync as existsSync8, mkdirSync as mkdirSync4, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
5018
4834
  import { randomUUID } from "crypto";
5019
- import path30 from "path";
4835
+ import path29 from "path";
5020
4836
  var COMMAND_LOG_SCHEMA_VERSION = 1;
5021
4837
  var SECRET_OPTIONS = /^(--?(?:password|pass|token|secret|api[-_]?key|access[-_]?key|signing[-_]?key))$/i;
5022
4838
  function localDate(date) {
@@ -5056,10 +4872,10 @@ function discoverProjectRoot(argv, cwd = process.cwd()) {
5056
4872
  continue;
5057
4873
  }
5058
4874
  if (argument.startsWith("-") || reserved.has(argument)) continue;
5059
- const candidate = path30.resolve(cwd, argument);
4875
+ const candidate = path29.resolve(cwd, argument);
5060
4876
  if (existsSync8(candidate)) return candidate;
5061
4877
  }
5062
- return path30.resolve(cwd);
4878
+ return path29.resolve(cwd);
5063
4879
  }
5064
4880
  function knownArtifacts(projectRoot) {
5065
4881
  const candidates = [
@@ -5072,12 +4888,12 @@ function knownArtifacts(projectRoot) {
5072
4888
  ["managed-evidence", ".migration/evidence/index.json"]
5073
4889
  ];
5074
4890
  const workspaceRoot = migrationWorkspaceRoot(projectRoot);
5075
- return candidates.filter(([, relative2]) => existsSync8(path30.join(workspaceRoot, relative2))).map(([kind, relative2]) => ({ kind, path: relative2.replaceAll("\\", "/") }));
4891
+ return candidates.filter(([, relative2]) => existsSync8(path29.join(workspaceRoot, relative2))).map(([kind, relative2]) => ({ kind, path: relative2.replaceAll("\\", "/") }));
5076
4892
  }
5077
4893
  function commandName(argv) {
5078
- const command3 = argv[0] ?? "unknown";
4894
+ const command2 = argv[0] ?? "unknown";
5079
4895
  const grouped = /* @__PURE__ */ new Set(["analyze", "report", "build", "test", "tools", "config", "checkpoint", "device", "evidence", "mock", "service"]);
5080
- return grouped.has(command3) && argv[1] ? `${command3} ${argv[1]}` : command3;
4896
+ return grouped.has(command2) && argv[1] ? `${command2} ${argv[1]}` : command2;
5081
4897
  }
5082
4898
  function atomicJson4(file, value) {
5083
4899
  const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`;
@@ -5120,18 +4936,18 @@ async function withCommandLog(argv, version, execute) {
5120
4936
  artifacts: knownArtifacts(projectRoot)
5121
4937
  };
5122
4938
  ensureMigrationWorkspace(projectRoot);
5123
- const logsRoot = path30.join(migrationWorkspaceRoot(projectRoot), ".migration", "logs");
5124
- const runDirectory = path30.join(logsRoot, "runs", date);
5125
- const dailyDirectory = path30.join(logsRoot, "daily");
4939
+ const logsRoot = path29.join(migrationWorkspaceRoot(projectRoot), ".migration", "logs");
4940
+ const runDirectory = path29.join(logsRoot, "runs", date);
4941
+ const dailyDirectory = path29.join(logsRoot, "daily");
5126
4942
  mkdirSync4(runDirectory, { recursive: true });
5127
4943
  mkdirSync4(dailyDirectory, { recursive: true });
5128
- atomicJson4(path30.join(runDirectory, `${runId}.json`), record);
5129
- appendFileSync(path30.join(dailyDirectory, `${date}.jsonl`), `${JSON.stringify(record)}
4944
+ atomicJson4(path29.join(runDirectory, `${runId}.json`), record);
4945
+ appendFileSync(path29.join(dailyDirectory, `${date}.jsonl`), `${JSON.stringify(record)}
5130
4946
  `, "utf8");
5131
- atomicJson4(path30.join(logsRoot, "index.json"), {
4947
+ atomicJson4(path29.join(logsRoot, "index.json"), {
5132
4948
  schemaVersion: COMMAND_LOG_SCHEMA_VERSION,
5133
4949
  lastRunId: runId,
5134
- lastRun: path30.relative(path30.dirname(logsRoot), path30.join(runDirectory, `${runId}.json`)).replaceAll("\\", "/"),
4950
+ lastRun: path29.relative(path29.dirname(logsRoot), path29.join(runDirectory, `${runId}.json`)).replaceAll("\\", "/"),
5135
4951
  updatedAt: finished.toISOString()
5136
4952
  });
5137
4953
  } catch (logError) {
@@ -5142,30 +4958,30 @@ async function withCommandLog(argv, version, execute) {
5142
4958
 
5143
4959
  // src/runtime-paths.ts
5144
4960
  import fs24 from "fs";
5145
- import path31 from "path";
4961
+ import path30 from "path";
5146
4962
  import { fileURLToPath } from "url";
5147
4963
  function resolveRuntimePaths(entryUrl) {
5148
4964
  const entryFile = fileURLToPath(entryUrl);
5149
- const directory = path31.dirname(entryFile);
5150
- const roots = [path31.resolve(directory, ".."), directory];
5151
- const packageRoot = roots.find((candidate) => fs24.existsSync(path31.join(candidate, "package.json")));
5152
- const runtimeRoot = packageRoot ?? path31.resolve(directory, "..");
4965
+ const directory = path30.dirname(entryFile);
4966
+ const roots = [path30.resolve(directory, ".."), directory];
4967
+ const packageRoot = roots.find((candidate) => fs24.existsSync(path30.join(candidate, "package.json")));
4968
+ const runtimeRoot = packageRoot ?? path30.resolve(directory, "..");
5153
4969
  const templatesRoot = [
5154
- packageRoot && path31.join(packageRoot, "templates")
4970
+ packageRoot && path30.join(packageRoot, "templates")
5155
4971
  ].filter(Boolean).find((candidate) => fs24.existsSync(candidate));
5156
- const preflightChecklistPath = packageRoot && path31.join(packageRoot, "config", "preflight-checklist.json");
4972
+ const preflightChecklistPath = packageRoot && path30.join(packageRoot, "config", "preflight-checklist.json");
5157
4973
  return { entryFile, runtimeRoot, packageRoot, templatesRoot, preflightChecklistPath };
5158
4974
  }
5159
4975
 
5160
4976
  // src/kernel/migration-kernel.ts
5161
4977
  import { createHash as createHash8, randomUUID as randomUUID2 } from "crypto";
5162
4978
  import { execFileSync as execFileSync2 } from "child_process";
5163
- import fs28 from "fs";
4979
+ import fs29 from "fs";
5164
4980
  import path35 from "path";
5165
4981
 
5166
4982
  // src/planning/facts/reader.ts
5167
4983
  import fs25 from "fs";
5168
- import path32 from "path";
4984
+ import path31 from "path";
5169
4985
 
5170
4986
  // src/planning/util.ts
5171
4987
  import { createHash as createHash7 } from "crypto";
@@ -5210,17 +5026,17 @@ function readJson4(file) {
5210
5026
  }
5211
5027
  function inside3(root, relative2, label) {
5212
5028
  if (typeof relative2 !== "string" || !relative2.trim()) throw new PlanningFactsError("FACTS_SHARD_MISSING", `${label} path is missing`);
5213
- const resolvedRoot = path32.resolve(root);
5214
- const absolute = path32.resolve(resolvedRoot, relative2);
5215
- const rel = path32.relative(resolvedRoot, absolute);
5216
- if (!rel || rel.startsWith(`..${path32.sep}`) || path32.isAbsolute(rel)) {
5029
+ const resolvedRoot = path31.resolve(root);
5030
+ const absolute = path31.resolve(resolvedRoot, relative2);
5031
+ const rel = path31.relative(resolvedRoot, absolute);
5032
+ if (!rel || rel.startsWith(`..${path31.sep}`) || path31.isAbsolute(rel)) {
5217
5033
  throw new PlanningFactsError("FACTS_PATH_OUTSIDE_ROOT", `${label} must stay inside ${resolvedRoot}: ${relative2}`);
5218
5034
  }
5219
5035
  if (!fs25.existsSync(absolute) || !fs25.statSync(absolute).isFile()) throw new PlanningFactsError("FACTS_SHARD_MISSING", `${label} not found: ${absolute}`);
5220
5036
  const realRoot = fs25.realpathSync(resolvedRoot);
5221
5037
  const realFile = fs25.realpathSync(absolute);
5222
- const realRelative = path32.relative(realRoot, realFile);
5223
- if (!realRelative || realRelative.startsWith(`..${path32.sep}`) || path32.isAbsolute(realRelative)) {
5038
+ const realRelative = path31.relative(realRoot, realFile);
5039
+ if (!realRelative || realRelative.startsWith(`..${path31.sep}`) || path31.isAbsolute(realRelative)) {
5224
5040
  throw new PlanningFactsError("FACTS_PATH_OUTSIDE_ROOT", `${label} resolves outside ${realRoot}: ${relative2}`);
5225
5041
  }
5226
5042
  return realFile;
@@ -5233,9 +5049,9 @@ function requireVersion(value, expected, label) {
5233
5049
  if (value !== expected) throw new PlanningFactsError("FACTS_VERSION_UNSUPPORTED", `${label} must be ${expected}; found ${value ?? "<missing>"}`);
5234
5050
  }
5235
5051
  function readFactsBundleV2(project) {
5236
- const projectRoot = path32.resolve(project);
5237
- const specRoot = path32.join(migrationDirectory(projectRoot), "spec");
5238
- const indexFile = path32.join(specRoot, "index.json");
5052
+ const projectRoot = path31.resolve(project);
5053
+ const specRoot = path31.join(migrationDirectory(projectRoot), "spec");
5054
+ const indexFile = path31.join(specRoot, "index.json");
5239
5055
  if (!fs25.existsSync(indexFile)) throw new PlanningFactsError("FACTS_SHARD_MISSING", `SPEC index not found: ${indexFile}`);
5240
5056
  const index = readJson4(indexFile);
5241
5057
  requireVersion(index.specVersion, "2.0.0", "SPEC specVersion");
@@ -5303,17 +5119,30 @@ function readFactsBundleV2(project) {
5303
5119
  }
5304
5120
 
5305
5121
  // src/planning/types.ts
5306
- var TASK_PLAN_VERSION = "1.0.0";
5307
- var TASK_ENVELOPE_VERSION = "1.0.0";
5308
- var PLANNING_MODEL_VERSION = "1.0.0";
5122
+ var TASK_PLAN_VERSION = "2.3.0";
5123
+ var TASK_ENVELOPE_VERSION = "3.0.0";
5124
+ var BEHAVIOR_PLAN_VERSION = "1.0.0";
5125
+ var EXECUTION_PLAN_VERSION = "1.3.0";
5126
+ var PLANNING_MODEL_VERSION = "2.0.0";
5309
5127
  var FACTS_ADAPTER_VERSION = "1.0.0";
5310
- var PLANNER_POLICY_VERSION = "1.1.0";
5128
+ var PLANNER_POLICY_VERSION = "2.3.0";
5311
5129
  var DEFAULT_CLOSURE_BUDGET = {
5312
5130
  maxFactCount: 30,
5313
5131
  maxSourceFileCount: 15,
5314
5132
  maxTraceNodeCount: 50,
5315
5133
  maxEstimatedFactTokens: 12e3
5316
5134
  };
5135
+ var DEFAULT_EXECUTION_BUDGET = {
5136
+ maxBehaviorCount: 5,
5137
+ maxObservableOutcomeCount: 3,
5138
+ maxProductionFileCount: 4,
5139
+ maxTestFileCount: 2,
5140
+ maxCapabilityCount: 2,
5141
+ maxFactCount: 45,
5142
+ maxSourceFileCount: 8,
5143
+ maxTraceNodeCount: 60,
5144
+ maxEstimatedFactTokens: 14e3
5145
+ };
5317
5146
 
5318
5147
  // src/planning/facts/v2-adapter.ts
5319
5148
  function evidence(values) {
@@ -5617,7 +5446,8 @@ function candidateForEntry(entryId, facts) {
5617
5446
  ...effects.flatMap((effect) => [effect.id, effect.targetId])
5618
5447
  ]));
5619
5448
  const issues = facts.issues.filter((issue) => issue.relatedRefs.some((ref) => related.has(ref)));
5620
- const blocking = issues.some((issue) => issue.blocking && !issue.resolved);
5449
+ const unresolvedIssues = issues.filter((issue) => issue.blocking && !issue.resolved);
5450
+ const blocking = unresolvedIssues.length > 0;
5621
5451
  const isLaunch = facts.app.launcherEntryIds.includes(entry.id) || entry.triggerCategory === "application" && entry.event === "launch";
5622
5452
  const type = blocking ? "resolution" : (() => {
5623
5453
  if (isLaunch) return "bootstrap";
@@ -5650,7 +5480,7 @@ function candidateForEntry(entryId, facts) {
5650
5480
  surfaceRefs: new Set(surfaces),
5651
5481
  controlRefs: new Set(controls.map((control) => control.id)),
5652
5482
  interactionRefs: new Set(effects.map((effect) => effect.id)),
5653
- issueRefs: new Set(issues.map((issue) => issue.id)),
5483
+ issueRefs: new Set(unresolvedIssues.map((issue) => issue.id)),
5654
5484
  warningRefs: new Set(warningRefs),
5655
5485
  sourceSurfaceRefs: new Set(entry.ownerSurfaceId ? [entry.ownerSurfaceId] : []),
5656
5486
  targetEdges: effects.flatMap((effect) => targetSurfaceIds(effect, facts).map((targetSurfaceRef) => ({ sourceSurfaceRef: entry.ownerSurfaceId, targetSurfaceRef, effectRef: effect.id }))),
@@ -5658,7 +5488,7 @@ function candidateForEntry(entryId, facts) {
5658
5488
  ...entry.evidence,
5659
5489
  ...controls.flatMap((control) => control.evidence),
5660
5490
  ...effects.flatMap((effect) => effect.evidence),
5661
- ...issues.flatMap((issue) => issue.evidence)
5491
+ ...unresolvedIssues.flatMap((issue) => issue.evidence)
5662
5492
  ], evidenceKey),
5663
5493
  codeRefs: entry.trace.codeRefs,
5664
5494
  requiresSourceReview: effects.length === 0 || blocking || entry.trace.warnings.length > 0,
@@ -5954,8 +5784,8 @@ function chooseSurfaceOwners(candidates, facts) {
5954
5784
  function reportableRefs(facts) {
5955
5785
  return new Set(facts.requiredFactRefs);
5956
5786
  }
5957
- function taskIdFor(candidate) {
5958
- return `task:${sha2562({
5787
+ function behaviorIdFor(candidate) {
5788
+ return `behavior:${sha2562({
5959
5789
  type: candidate.type,
5960
5790
  primaryFactRefs: uniqueSorted(candidate.primary),
5961
5791
  path: {
@@ -5967,7 +5797,7 @@ function taskIdFor(candidate) {
5967
5797
  }
5968
5798
  function downstreamCounts(tasks) {
5969
5799
  const children = /* @__PURE__ */ new Map();
5970
- for (const task of tasks) for (const dependency of task.dependsOnTaskIds) children.set(dependency, [...children.get(dependency) ?? [], task.id]);
5800
+ for (const task of tasks) for (const dependency of task.dependsOnBehaviorIds) children.set(dependency, [...children.get(dependency) ?? [], task.id]);
5971
5801
  const out = /* @__PURE__ */ new Map();
5972
5802
  for (const task of tasks) {
5973
5803
  const seen = /* @__PURE__ */ new Set();
@@ -5982,7 +5812,7 @@ function downstreamCounts(tasks) {
5982
5812
  }
5983
5813
  return out;
5984
5814
  }
5985
- function compileFromPlanningFacts(facts, androidFingerprint) {
5815
+ function compileBehaviorPlanFromPlanningFacts(facts, androidFingerprint) {
5986
5816
  let candidates = mergeCompatibleEntryCandidates(facts.entries.map((entry) => candidateForEntry(entry.id, facts)), facts);
5987
5817
  for (const surfaceRef of facts.app.launcherSurfaceIds) {
5988
5818
  if (!candidates.some((candidate) => candidate.type === "bootstrap" && candidate.surfaceRefs.has(surfaceRef))) {
@@ -6036,8 +5866,8 @@ function compileFromPlanningFacts(facts, androidFingerprint) {
6036
5866
  const closureRefs = /* @__PURE__ */ new Set([...candidate.entryRefs, ...candidate.surfaceRefs, ...candidate.controlRefs, ...candidate.interactionRefs, ...candidate.issueRefs]);
6037
5867
  candidate.supporting = new Set([...closureRefs].filter((ref) => !candidate.primary.has(ref)));
6038
5868
  }
6039
- const idByKey = new Map(candidates.map((candidate) => [candidate.key, taskIdFor(candidate)]));
6040
- if (new Set(idByKey.values()).size !== candidates.length) throw new Error("Task ID collision while compiling plan");
5869
+ const idByKey = new Map(candidates.map((candidate) => [candidate.key, behaviorIdFor(candidate)]));
5870
+ if (new Set(idByKey.values()).size !== candidates.length) throw new Error("Behavior ID collision while compiling plan");
6041
5871
  const launcherResolvers = candidates.filter((candidate) => candidate.type === "resolution" && candidate.issueRefs.has("issue:launcher-unresolved"));
6042
5872
  const taskDrafts = candidates.map((candidate) => {
6043
5873
  const dependencies = /* @__PURE__ */ new Set();
@@ -6049,11 +5879,11 @@ function compileFromPlanningFacts(facts, androidFingerprint) {
6049
5879
  }
6050
5880
  if (facts.app.launcherOrigin === "unresolved") for (const resolver of launcherResolvers) dependencies.add(idByKey.get(resolver.key));
6051
5881
  }
6052
- return { candidate, id: idByKey.get(candidate.key), dependsOnTaskIds: uniqueSorted(dependencies) };
5882
+ return { candidate, id: idByKey.get(candidate.key), dependsOnBehaviorIds: uniqueSorted(dependencies) };
6053
5883
  });
6054
5884
  const downstream = downstreamCounts(taskDrafts);
6055
5885
  const reportable = reportableRefs(facts);
6056
- const tasks = taskDrafts.map(({ candidate, id, dependsOnTaskIds }) => {
5886
+ const responsibilities = taskDrafts.map(({ candidate, id, dependsOnBehaviorIds }) => {
6057
5887
  const primaryFactRefs = uniqueSorted(candidate.primary);
6058
5888
  const supportingFactRefs = uniqueSorted(candidate.supporting);
6059
5889
  const core = {
@@ -6070,7 +5900,7 @@ function compileFromPlanningFacts(facts, androidFingerprint) {
6070
5900
  title: candidate.title,
6071
5901
  primaryFactRefs,
6072
5902
  supportingFactRefs,
6073
- dependsOnTaskIds,
5903
+ dependsOnBehaviorIds,
6074
5904
  closure: {
6075
5905
  entryRefs: core.entries,
6076
5906
  surfaceRefs: core.surfaces,
@@ -6098,12 +5928,12 @@ function compileFromPlanningFacts(facts, androidFingerprint) {
6098
5928
  };
6099
5929
  }).sort((a, b) => typeOrder[a.type] - typeOrder[b.type] || a.id.localeCompare(b.id));
6100
5930
  const primaryOwnerByFactRef = Object.fromEntries([...ownerKeyByFactRef.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([ref, key]) => [ref, idByKey.get(key)]));
6101
- const supportingTaskIdsByFactRef = {};
6102
- for (const task of tasks) for (const ref of task.supportingFactRefs) supportingTaskIdsByFactRef[ref] = uniqueSorted([...supportingTaskIdsByFactRef[ref] ?? [], task.id]);
5931
+ const supportingBehaviorIdsByFactRef = {};
5932
+ for (const responsibility of responsibilities) for (const ref of responsibility.supportingFactRefs) supportingBehaviorIdsByFactRef[ref] = uniqueSorted([...supportingBehaviorIdsByFactRef[ref] ?? [], responsibility.id]);
6103
5933
  const planningWarnings = [...facts.diagnostics];
6104
- for (const task of tasks.filter((item) => item.closure.requiresSourceReview && item.type !== "resolution")) planningWarnings.push({ code: "SOURCE_REVIEW_REQUIRED", message: `${task.id} has no direct observable interaction and requires Android source review`, relatedRefs: task.closure.entryRefs });
5934
+ for (const responsibility of responsibilities.filter((item) => item.closure.requiresSourceReview && item.type !== "resolution")) planningWarnings.push({ code: "SOURCE_REVIEW_REQUIRED", message: `${responsibility.id} has no direct observable interaction and requires Android source review`, relatedRefs: responsibility.closure.entryRefs });
6105
5935
  return {
6106
- taskPlanVersion: TASK_PLAN_VERSION,
5936
+ behaviorPlanVersion: BEHAVIOR_PLAN_VERSION,
6107
5937
  planningModelVersion: PLANNING_MODEL_VERSION,
6108
5938
  factsAdapterVersion: FACTS_ADAPTER_VERSION,
6109
5939
  plannerPolicyVersion: PLANNER_POLICY_VERSION,
@@ -6119,22 +5949,716 @@ function compileFromPlanningFacts(facts, androidFingerprint) {
6119
5949
  launcherEntryRefs: facts.app.launcherEntryIds,
6120
5950
  origin: facts.app.launcherOrigin
6121
5951
  },
6122
- tasks,
6123
- ownership: { primaryOwnerByFactRef, supportingTaskIdsByFactRef: Object.fromEntries(Object.entries(supportingTaskIdsByFactRef).sort(([a], [b]) => a.localeCompare(b))) },
5952
+ responsibilities,
5953
+ ownership: { primaryOwnerByFactRef, supportingBehaviorIdsByFactRef: Object.fromEntries(Object.entries(supportingBehaviorIdsByFactRef).sort(([a], [b]) => a.localeCompare(b))) },
6124
5954
  planningWarnings: uniqueBy(planningWarnings, (item) => `${item.code}:${item.message}:${item.relatedRefs.join(",")}`)
6125
5955
  };
6126
5956
  }
6127
5957
 
6128
- // src/planning/storage.ts
5958
+ // src/planning/execution-context.ts
6129
5959
  import fs26 from "fs";
5960
+ import path32 from "path";
5961
+ function readJson6(file) {
5962
+ try {
5963
+ return JSON.parse(fs26.readFileSync(file, "utf8"));
5964
+ } catch {
5965
+ return null;
5966
+ }
5967
+ }
5968
+ function inside4(root, target) {
5969
+ const relative2 = path32.relative(root, target);
5970
+ return relative2 === "" || relative2 !== ".." && !relative2.startsWith(`..${path32.sep}`) && !path32.isAbsolute(relative2);
5971
+ }
5972
+ function validatedHarmonyFile(projectRoot, root, value) {
5973
+ if (typeof value !== "string" || !value.trim()) throw new Error("Report HarmonyOS mapping must be a non-empty string");
5974
+ const absolute = path32.resolve(projectRoot, value);
5975
+ if (!inside4(root, absolute)) throw new Error(`Report HarmonyOS mapping escapes the configured project: ${value}`);
5976
+ if (!fs26.existsSync(absolute) || !fs26.statSync(absolute).isFile()) throw new Error(`Report HarmonyOS mapping does not exist: ${value}`);
5977
+ const realRoot = fs26.realpathSync(root);
5978
+ const realFile = fs26.realpathSync(absolute);
5979
+ if (!inside4(realRoot, realFile)) throw new Error(`Report HarmonyOS mapping resolves outside the configured project: ${value}`);
5980
+ return path32.relative(projectRoot, absolute).replaceAll("\\", "/");
5981
+ }
5982
+ function confinedReportFile(reportRoot, relative2) {
5983
+ if (!relative2.trim() || path32.isAbsolute(relative2)) throw new Error(`Report shard path must be relative: ${relative2}`);
5984
+ const absolute = path32.resolve(reportRoot, relative2);
5985
+ if (!inside4(reportRoot, absolute) || absolute === reportRoot) throw new Error(`Report shard escapes report root: ${relative2}`);
5986
+ if (!fs26.existsSync(absolute) || !fs26.statSync(absolute).isFile()) throw new Error(`Report shard does not exist: ${relative2}`);
5987
+ const realRoot = fs26.realpathSync(reportRoot);
5988
+ const realFile = fs26.realpathSync(absolute);
5989
+ if (!inside4(realRoot, realFile)) throw new Error(`Report shard resolves outside report root: ${relative2}`);
5990
+ return realFile;
5991
+ }
5992
+ function reportHarmonyFiles(projectRoot, root) {
5993
+ const reportRoot = path32.join(migrationDirectory(projectRoot), "report");
5994
+ const index = readJson6(path32.join(reportRoot, "index.json"));
5995
+ if (!index) return {};
5996
+ const shardFiles = [
5997
+ ...(index.pages ?? []).map((item) => item.file),
5998
+ index.unassignedEntriesFile,
5999
+ index.interactionsFile,
6000
+ index.discoveriesFile
6001
+ ].filter((value) => typeof value === "string");
6002
+ const result = {};
6003
+ for (const relative2 of shardFiles) {
6004
+ const document = readJson6(confinedReportFile(reportRoot, relative2));
6005
+ if (!document) continue;
6006
+ const items = [
6007
+ document.page,
6008
+ ...document.controls ?? [],
6009
+ ...document.entries ?? [],
6010
+ ...document.interactions ?? [],
6011
+ ...document.items ?? []
6012
+ ].filter(Boolean);
6013
+ for (const item of items) {
6014
+ if (typeof item.ref !== "string" || !Array.isArray(item.harmonyosFiles)) continue;
6015
+ result[item.ref] = uniqueSorted(item.harmonyosFiles.map((value) => validatedHarmonyFile(projectRoot, root, value)));
6016
+ }
6017
+ }
6018
+ return Object.fromEntries(Object.entries(result).sort(([left], [right]) => left.localeCompare(right)));
6019
+ }
6020
+ function harmonyRoot(projectRoot) {
6021
+ const config = readJson6(path32.join(migrationDirectory(projectRoot), "config.json"));
6022
+ const configured = typeof config?.harmonyProject === "string" ? config.harmonyProject : "ohos";
6023
+ return path32.isAbsolute(configured) ? configured : path32.resolve(projectRoot, configured);
6024
+ }
6025
+ function existingGlobalHotspots(projectRoot) {
6026
+ const root = harmonyRoot(projectRoot);
6027
+ if (!fs26.existsSync(root) || !fs26.statSync(root).isDirectory()) return [];
6028
+ const relativeRoot = path32.relative(projectRoot, root).replaceAll("\\", "/");
6029
+ const candidates = [
6030
+ "build-profile.json5",
6031
+ "AppScope/app.json5",
6032
+ "oh-package.json5",
6033
+ "entry/oh-package.json5",
6034
+ "entry/src/main/module.json5",
6035
+ "entry/src/main/resources/base/profile/main_pages.json",
6036
+ "entry/src/main/ets/entryability/EntryAbility.ets",
6037
+ "entry/src/main/ets/pages/Index.ets",
6038
+ "entry/src/main/ets/common/RouteConstants.ets"
6039
+ ];
6040
+ return candidates.filter((relative2) => fs26.existsSync(path32.join(root, relative2))).map((relative2) => `${relativeRoot}/${relative2}`.replace(/^\.\//, "")).sort();
6041
+ }
6042
+ function loadExecutionPlanningContext(projectRootArgument) {
6043
+ const projectRoot = path32.resolve(projectRootArgument);
6044
+ const root = harmonyRoot(projectRoot);
6045
+ const rootAvailable = fs26.existsSync(root) && fs26.statSync(root).isDirectory();
6046
+ return {
6047
+ harmonyFilesByFactRef: rootAvailable ? reportHarmonyFiles(projectRoot, root) : {},
6048
+ globalHotspotFiles: existingGlobalHotspots(projectRoot)
6049
+ };
6050
+ }
6051
+ var emptyExecutionPlanningContext = {
6052
+ harmonyFilesByFactRef: {},
6053
+ globalHotspotFiles: []
6054
+ };
6055
+
6056
+ // src/planning/execution-planner.ts
6057
+ var typeOrder2 = {
6058
+ resolution: 0,
6059
+ bootstrap: 1,
6060
+ capability: 2,
6061
+ journey: 3,
6062
+ assembly: 4,
6063
+ regression: 5
6064
+ };
6065
+ function isReusableSurface(kind) {
6066
+ return /(?:list[-_ ]?item|item|reusable|layout|custom[-_ ]?view|cell|row)/i.test(kind);
6067
+ }
6068
+ function isHostedOverlay(kind) {
6069
+ return /(?:dialog|bottom[-_ ]?sheet|sheet|popup|overlay)/i.test(kind);
6070
+ }
6071
+ function canonicalPageRefs(facts) {
6072
+ const byId = new Map(facts.surfaces.map((surface) => [surface.id, surface]));
6073
+ const memo = /* @__PURE__ */ new Map();
6074
+ const resolve4 = (surfaceRef, visiting = /* @__PURE__ */ new Set()) => {
6075
+ if (memo.has(surfaceRef)) return memo.get(surfaceRef);
6076
+ const surface = byId.get(surfaceRef);
6077
+ if (!surface || visiting.has(surfaceRef)) return surfaceRef;
6078
+ visiting.add(surfaceRef);
6079
+ const parentPages = uniqueSorted(surface.parentSurfaceIds.filter((ref) => byId.has(ref)).map((ref) => resolve4(ref, visiting)));
6080
+ const canonical2 = parentPages.length === 1 && (isReusableSurface(surface.kind) || isHostedOverlay(surface.kind) || !surface.topLevel) ? parentPages[0] : surfaceRef;
6081
+ visiting.delete(surfaceRef);
6082
+ memo.set(surfaceRef, canonical2);
6083
+ return canonical2;
6084
+ };
6085
+ for (const surface of facts.surfaces) resolve4(surface.id);
6086
+ return Object.fromEntries([...memo.entries()].sort(([left], [right]) => left.localeCompare(right)));
6087
+ }
6088
+ function behaviorSourceSurfaceRefs(behavior, facts) {
6089
+ const entryRefs = new Set(behavior.closure.entryRefs);
6090
+ const fromEntries = facts.entries.filter((entry) => entryRefs.has(entry.id) && entry.ownerSurfaceId).map((entry) => entry.ownerSurfaceId);
6091
+ if (fromEntries.length) return uniqueSorted(fromEntries);
6092
+ const primarySurfaces = behavior.primaryFactRefs.filter((ref) => facts.surfaces.some((surface) => surface.id === ref));
6093
+ return uniqueSorted(primarySurfaces.length ? primarySurfaces : behavior.closure.surfaceRefs);
6094
+ }
6095
+ function behaviorCanonicalPage(behavior, facts, canonicalBySurface) {
6096
+ const pages = uniqueSorted(behaviorSourceSurfaceRefs(behavior, facts).map((ref) => canonicalBySurface[ref] ?? ref));
6097
+ return pages[0] ?? null;
6098
+ }
6099
+ function codeRefKey(value) {
6100
+ if (typeof value.id === "string") return value.id;
6101
+ return stableJson(Object.fromEntries(["file", "name", "symbol", "kind", "type", "capabilityRef"].filter((key) => value[key] !== void 0).map((key) => [key, value[key]])));
6102
+ }
6103
+ function behaviorCapabilityRefs(behavior) {
6104
+ return uniqueSorted(behavior.closure.codeRefs.flatMap((item) => {
6105
+ if (typeof item.capabilityRef === "string" && item.capabilityRef.length > 0) return [item.capabilityRef];
6106
+ if (item.kind === "capability" && typeof item.id === "string" && item.id.length > 0) return [item.id];
6107
+ return [];
6108
+ }));
6109
+ }
6110
+ function candidateForBehavior(behavior, facts, canonicalBySurface) {
6111
+ const effectRefs = new Set(behavior.closure.interactionRefs);
6112
+ const targets = facts.effects.filter((effect) => effectRefs.has(effect.id) && effect.targetKind === "surface").map((effect) => canonicalBySurface[effect.targetId] ?? effect.targetId);
6113
+ return {
6114
+ behaviorIds: [behavior.id],
6115
+ canonicalPageRef: behaviorCanonicalPage(behavior, facts, canonicalBySurface),
6116
+ type: behavior.type,
6117
+ sourceFiles: uniqueSorted([
6118
+ ...behavior.closure.sourceEvidence.map((item) => item.file),
6119
+ ...behavior.closure.codeRefs.flatMap((item) => typeof item.file === "string" ? [item.file] : [])
6120
+ ]),
6121
+ targetPageRefs: uniqueSorted(targets),
6122
+ codeRefKeys: uniqueSorted(behavior.closure.codeRefs.map(codeRefKey)),
6123
+ capabilityRefs: behaviorCapabilityRefs(behavior)
6124
+ };
6125
+ }
6126
+ function combineCandidates2(left, right) {
6127
+ return {
6128
+ behaviorIds: uniqueSorted([...left.behaviorIds, ...right.behaviorIds]),
6129
+ canonicalPageRef: left.canonicalPageRef,
6130
+ type: left.type,
6131
+ sourceFiles: uniqueSorted([...left.sourceFiles, ...right.sourceFiles]),
6132
+ targetPageRefs: uniqueSorted([...left.targetPageRefs, ...right.targetPageRefs]),
6133
+ codeRefKeys: uniqueSorted([...left.codeRefKeys, ...right.codeRefKeys]),
6134
+ capabilityRefs: uniqueSorted([...left.capabilityRefs, ...right.capabilityRefs])
6135
+ };
6136
+ }
6137
+ function intersects(left, right) {
6138
+ const values = new Set(left);
6139
+ return right.some((value) => values.has(value));
6140
+ }
6141
+ function behaviorRefs(candidate, behaviors) {
6142
+ return candidate.behaviorIds.map((id) => behaviors.get(id)).filter(Boolean);
6143
+ }
6144
+ function isRoutingAssembly(candidate) {
6145
+ return candidate.type === "journey" && candidate.targetPageRefs.some((target) => target !== candidate.canonicalPageRef);
6146
+ }
6147
+ function routingHotspotFiles(context) {
6148
+ return context.globalHotspotFiles.filter((file) => /(?:main_pages\.json|RouteConstants\.ets|\/pages\/Index\.ets)$/i.test(file));
6149
+ }
6150
+ function mappedTargetFiles(candidate, primaryRefs, context) {
6151
+ const mapped = uniqueSorted(primaryRefs.flatMap((ref) => context.harmonyFilesByFactRef[ref] ?? []));
6152
+ if (candidate.type === "bootstrap") return uniqueSorted([...mapped, ...context.globalHotspotFiles]);
6153
+ if (isRoutingAssembly(candidate)) return uniqueSorted([...mapped, ...routingHotspotFiles(context)]);
6154
+ return mapped;
6155
+ }
6156
+ function executionCost(candidate, behaviors, context) {
6157
+ const members = behaviorRefs(candidate, behaviors);
6158
+ const factRefs = uniqueSorted(members.flatMap((item) => [...item.primaryFactRefs, ...item.supportingFactRefs]));
6159
+ const mappedFiles = mappedTargetFiles(candidate, factRefs, context);
6160
+ const productionFiles = mappedFiles.filter((file) => !/(?:^|\/)(?:ohosTest|test|tests)(?:\/|$)|(?:Test|\.test)\.(?:ets|ts)$/i.test(file));
6161
+ const testFiles = mappedFiles.filter((file) => !productionFiles.includes(file));
6162
+ return {
6163
+ behaviorCount: members.length,
6164
+ observableOutcomeCount: uniqueSorted(members.map((item) => item.acceptance.observableOutcome)).length,
6165
+ productionFileCount: productionFiles.length,
6166
+ testFileCount: testFiles.length,
6167
+ capabilityCount: candidate.capabilityRefs.length,
6168
+ factCount: new Set(members.flatMap((item) => [...item.primaryFactRefs, ...item.supportingFactRefs])).size,
6169
+ sourceFileCount: uniqueSorted(members.flatMap((item) => [
6170
+ ...item.closure.sourceEvidence.map((evidence2) => evidence2.file),
6171
+ ...item.closure.codeRefs.flatMap((codeRef) => typeof codeRef.file === "string" ? [codeRef.file] : [])
6172
+ ])).length,
6173
+ traceNodeCount: uniqueBy(members.flatMap((item) => item.closure.codeRefs), codeRefKey).length,
6174
+ estimatedFactTokens: members.reduce((total, item) => total + item.estimate.estimatedFactTokens, 0)
6175
+ };
6176
+ }
6177
+ function withinExecutionBudget(candidate, behaviors, context) {
6178
+ const cost = executionCost(candidate, behaviors, context);
6179
+ return cost.behaviorCount <= DEFAULT_EXECUTION_BUDGET.maxBehaviorCount && cost.observableOutcomeCount <= DEFAULT_EXECUTION_BUDGET.maxObservableOutcomeCount && cost.productionFileCount <= DEFAULT_EXECUTION_BUDGET.maxProductionFileCount && cost.testFileCount <= DEFAULT_EXECUTION_BUDGET.maxTestFileCount && cost.capabilityCount <= DEFAULT_EXECUTION_BUDGET.maxCapabilityCount && cost.factCount <= DEFAULT_EXECUTION_BUDGET.maxFactCount && cost.sourceFileCount <= DEFAULT_EXECUTION_BUDGET.maxSourceFileCount && cost.traceNodeCount <= DEFAULT_EXECUTION_BUDGET.maxTraceNodeCount && cost.estimatedFactTokens <= DEFAULT_EXECUTION_BUDGET.maxEstimatedFactTokens;
6180
+ }
6181
+ function shouldMerge(left, right, behaviors, facts, context) {
6182
+ if (left.type !== "journey" || right.type !== "journey" || left.canonicalPageRef !== right.canonicalPageRef) return false;
6183
+ const members = [...behaviorRefs(left, behaviors), ...behaviorRefs(right, behaviors)];
6184
+ if (members.some((item) => item.closure.requiresSourceReview || item.closure.unresolvedRefs.length > 0)) return false;
6185
+ const combined = combineCandidates2(left, right);
6186
+ if (!withinExecutionBudget(combined, behaviors, context)) return false;
6187
+ const behaviorEntries = (candidate) => uniqueSorted(behaviorRefs(candidate, behaviors).flatMap((item) => item.closure.entryRefs));
6188
+ const targetedEntries = (candidate) => {
6189
+ const effects = new Set(behaviorRefs(candidate, behaviors).flatMap((item) => item.closure.interactionRefs));
6190
+ return uniqueSorted([...effects].flatMap((effectRef) => {
6191
+ const effect = facts.effects.find((item) => item.id === effectRef);
6192
+ return effect?.targetKind === "entry" ? [effect.targetId] : [];
6193
+ }));
6194
+ };
6195
+ const callbackLinked = intersects(targetedEntries(left), behaviorEntries(right)) || intersects(targetedEntries(right), behaviorEntries(left));
6196
+ if (callbackLinked || intersects(left.codeRefKeys, right.codeRefKeys)) return true;
6197
+ const sharedSource = intersects(left.sourceFiles, right.sourceFiles);
6198
+ const sharedTarget = left.targetPageRefs.length > 0 && right.targetPageRefs.length > 0 && intersects(left.targetPageRefs, right.targetPageRefs);
6199
+ const sharedCapability = intersects(left.capabilityRefs, right.capabilityRefs);
6200
+ return sharedSource && (sharedTarget || sharedCapability);
6201
+ }
6202
+ function clusterBehaviors(plan, facts, context) {
6203
+ const behaviors = new Map(plan.responsibilities.map((item) => [item.id, item]));
6204
+ const canonicalBySurface = canonicalPageRefs(facts);
6205
+ const clusters = [];
6206
+ for (const next of plan.responsibilities.map((item) => candidateForBehavior(item, facts, canonicalBySurface))) {
6207
+ const compatible = clusters.findIndex((current) => shouldMerge(current, next, behaviors, facts, context));
6208
+ if (compatible < 0) clusters.push(next);
6209
+ else clusters[compatible] = combineCandidates2(clusters[compatible], next);
6210
+ }
6211
+ return clusters;
6212
+ }
6213
+ function workPackageId(candidate) {
6214
+ if (candidate.type === "resolution") return null;
6215
+ return `package:${sha2562({ kind: "page", page: candidate.canonicalPageRef ?? "unassigned" }).slice(0, 16)}`;
6216
+ }
6217
+ function taskId(candidate) {
6218
+ return `task:${sha2562({ page: candidate.canonicalPageRef, type: candidate.type, behaviorIds: candidate.behaviorIds }).slice(0, 16)}`;
6219
+ }
6220
+ function taskTitle(candidate, members, facts) {
6221
+ if (members.length === 1) return members[0].title;
6222
+ const pageName = facts.surfaces.find((surface) => surface.id === candidate.canonicalPageRef)?.name ?? candidate.canonicalPageRef ?? "\u672A\u5F52\u5C5E\u6A21\u5757";
6223
+ return `${pageName}\uFF1A\u5B8C\u6210 ${members.length} \u4E2A\u76F8\u5173\u884C\u4E3A\u95ED\u73AF`;
6224
+ }
6225
+ function aggregateClosure(members) {
6226
+ return {
6227
+ entryRefs: uniqueSorted(members.flatMap((item) => item.closure.entryRefs)),
6228
+ surfaceRefs: uniqueSorted(members.flatMap((item) => item.closure.surfaceRefs)),
6229
+ controlRefs: uniqueSorted(members.flatMap((item) => item.closure.controlRefs)),
6230
+ interactionRefs: uniqueSorted(members.flatMap((item) => item.closure.interactionRefs)),
6231
+ sourceEvidence: uniqueBy(members.flatMap((item) => item.closure.sourceEvidence), (item) => `${item.file}:${item.symbol ?? ""}:${item.line ?? ""}:${item.endLine ?? ""}`),
6232
+ codeRefs: uniqueBy(members.flatMap((item) => item.closure.codeRefs), codeRefKey),
6233
+ unresolvedRefs: uniqueSorted(members.flatMap((item) => item.closure.unresolvedRefs)),
6234
+ warningRefs: uniqueSorted(members.flatMap((item) => item.closure.warningRefs)),
6235
+ requiresSourceReview: members.some((item) => item.closure.requiresSourceReview)
6236
+ };
6237
+ }
6238
+ function primaryModule(candidate) {
6239
+ if (candidate.type === "bootstrap") return "foundation:application-shell";
6240
+ if (candidate.type === "resolution") return `resolution:${candidate.behaviorIds[0]}`;
6241
+ if (isRoutingAssembly(candidate)) return "assembly:routing";
6242
+ return candidate.canonicalPageRef ? `page:${candidate.canonicalPageRef}` : "page:unassigned";
6243
+ }
6244
+ function initialTasks(clusters, behaviorPlan, facts, canonicalBySurface, context) {
6245
+ const behaviors = new Map(behaviorPlan.responsibilities.map((item) => [item.id, item]));
6246
+ return clusters.map((candidate) => {
6247
+ const members = behaviorRefs(candidate, behaviors);
6248
+ const primaryFactRefs = uniqueSorted(members.flatMap((item2) => item2.primaryFactRefs));
6249
+ const supportingFactRefs = uniqueSorted(members.flatMap((item2) => item2.supportingFactRefs).filter((ref) => !primaryFactRefs.includes(ref)));
6250
+ const mappedFiles = mappedTargetFiles(candidate, [...primaryFactRefs, ...supportingFactRefs], context);
6251
+ let ownedFiles;
6252
+ if (candidate.type === "resolution") ownedFiles = [];
6253
+ else if (candidate.type === "bootstrap") ownedFiles = mappedFiles;
6254
+ else ownedFiles = mappedFiles.filter((file) => isRoutingAssembly(candidate) || !context.globalHotspotFiles.includes(file));
6255
+ const cost = executionCost(candidate, behaviors, context);
6256
+ const outcomes = uniqueSorted(members.map((item2) => item2.acceptance.observableOutcome));
6257
+ const closure = aggregateClosure(members);
6258
+ const pageScopes = uniqueSorted(closure.surfaceRefs.map((surfaceRef) => `page:${canonicalBySurface[surfaceRef] ?? surfaceRef}`));
6259
+ const exclusiveScopes = candidate.type === "resolution" ? [`resolution:${candidate.behaviorIds[0]}`] : uniqueSorted([
6260
+ ...candidate.type === "bootstrap" ? ["foundation:application-shell"] : [],
6261
+ ...isRoutingAssembly(candidate) ? ["assembly:routing"] : [],
6262
+ ...pageScopes,
6263
+ ...pageScopes.length === 0 ? [candidate.canonicalPageRef ? `page:${candidate.canonicalPageRef}` : `${candidate.type}:unassigned`] : []
6264
+ ]);
6265
+ const item = {
6266
+ id: taskId(candidate),
6267
+ type: candidate.type,
6268
+ title: taskTitle(candidate, members, facts),
6269
+ workPackageId: workPackageId(candidate),
6270
+ canonicalPageRef: candidate.canonicalPageRef,
6271
+ primaryModule: primaryModule(candidate),
6272
+ behaviorIds: candidate.behaviorIds,
6273
+ primaryFactRefs,
6274
+ supportingFactRefs,
6275
+ dependsOnTaskIds: [],
6276
+ conflictsWithTaskIds: [],
6277
+ writeOwnership: {
6278
+ ownedFiles,
6279
+ readOnlyFiles: [],
6280
+ exclusiveScopes,
6281
+ forbiddenGlobalFiles: candidate.type === "bootstrap" ? [] : context.globalHotspotFiles.filter((file) => !ownedFiles.includes(file))
6282
+ },
6283
+ dependencyInterfaces: [],
6284
+ readyGate: {
6285
+ semanticsResolved: candidate.type === "resolution" || closure.unresolvedRefs.length === 0,
6286
+ dependencyInterfacesStable: true,
6287
+ writeOwnershipResolved: ownedFiles.length > 0 || exclusiveScopes.length > 0,
6288
+ contextWithinBudget: members.length === 1 || withinExecutionBudget(candidate, behaviors, context),
6289
+ acceptanceExecutable: members.every((member) => member.acceptance.verificationRequirements.length > 0)
6290
+ },
6291
+ closure,
6292
+ acceptance: {
6293
+ observableOutcome: outcomes.join("\uFF1B"),
6294
+ observableOutcomes: outcomes,
6295
+ requiredFactRefs: uniqueSorted(members.flatMap((item2) => item2.acceptance.requiredFactRefs)),
6296
+ verificationRequirements: uniqueSorted(members.flatMap((item2) => item2.acceptance.verificationRequirements)),
6297
+ reportRefs: uniqueSorted(members.flatMap((item2) => item2.acceptance.reportRefs))
6298
+ },
6299
+ estimate: {
6300
+ factCount: cost.factCount,
6301
+ sourceFileCount: cost.sourceFileCount,
6302
+ traceNodeCount: cost.traceNodeCount,
6303
+ estimatedFactTokens: cost.estimatedFactTokens,
6304
+ downstreamUnlockCount: 0,
6305
+ behaviorCount: cost.behaviorCount,
6306
+ observableOutcomeCount: cost.observableOutcomeCount,
6307
+ productionFileCount: cost.productionFileCount,
6308
+ testFileCount: cost.testFileCount,
6309
+ capabilityCount: cost.capabilityCount
6310
+ }
6311
+ };
6312
+ return item;
6313
+ }).sort((left, right) => typeOrder2[left.type] - typeOrder2[right.type] || left.id.localeCompare(right.id));
6314
+ }
6315
+ function hasPath(tasks, from, to) {
6316
+ const seen = /* @__PURE__ */ new Set();
6317
+ const queue = [...tasks.get(from)?.dependsOnTaskIds ?? []];
6318
+ while (queue.length) {
6319
+ const next = queue.shift();
6320
+ if (next === to) return true;
6321
+ if (seen.has(next)) continue;
6322
+ seen.add(next);
6323
+ queue.push(...tasks.get(next)?.dependsOnTaskIds ?? []);
6324
+ }
6325
+ return false;
6326
+ }
6327
+ function addDependency(tasks, taskId2, dependencyId) {
6328
+ if (taskId2 === dependencyId || hasPath(tasks, dependencyId, taskId2)) return;
6329
+ const task = tasks.get(taskId2);
6330
+ task.dependsOnTaskIds = uniqueSorted([...task.dependsOnTaskIds, dependencyId]);
6331
+ }
6332
+ function orderPackageLane(items) {
6333
+ const ids = new Set(items.map((item) => item.id));
6334
+ const remaining = new Set(ids);
6335
+ const result = [];
6336
+ while (remaining.size) {
6337
+ const ready = items.filter((item) => remaining.has(item.id) && item.dependsOnTaskIds.filter((dependency) => ids.has(dependency)).every((dependency) => !remaining.has(dependency))).sort((left, right) => typeOrder2[left.type] - typeOrder2[right.type] || left.id.localeCompare(right.id));
6338
+ const selected = ready[0] ?? items.filter((item) => remaining.has(item.id)).sort((left, right) => left.id.localeCompare(right.id))[0];
6339
+ result.push(selected);
6340
+ remaining.delete(selected.id);
6341
+ }
6342
+ return result;
6343
+ }
6344
+ function packageSerializationRanks(tasks) {
6345
+ const packageIds = uniqueSorted(tasks.flatMap((task) => task.workPackageId ? [task.workPackageId] : []));
6346
+ const taskById = new Map(tasks.map((task) => [task.id, task]));
6347
+ const dependencies = new Map(packageIds.map((packageId) => [packageId, /* @__PURE__ */ new Set()]));
6348
+ for (const task of tasks) {
6349
+ if (!task.workPackageId) continue;
6350
+ for (const dependencyId of task.dependsOnTaskIds) {
6351
+ const dependencyPackageId = taskById.get(dependencyId)?.workPackageId;
6352
+ if (dependencyPackageId && dependencyPackageId !== task.workPackageId) dependencies.get(task.workPackageId).add(dependencyPackageId);
6353
+ }
6354
+ }
6355
+ const remaining = new Set(packageIds);
6356
+ const ranks = /* @__PURE__ */ new Map();
6357
+ while (remaining.size) {
6358
+ const ready = packageIds.filter((packageId) => remaining.has(packageId) && [...dependencies.get(packageId)].every((dependencyId) => !remaining.has(dependencyId)));
6359
+ const selected = ready[0] ?? packageIds.find((packageId) => remaining.has(packageId));
6360
+ ranks.set(selected, ranks.size);
6361
+ remaining.delete(selected);
6362
+ }
6363
+ return ranks;
6364
+ }
6365
+ function conflictReasons(left, right) {
6366
+ const ownedOverlap = left.writeOwnership.ownedFiles.filter((file) => right.writeOwnership.ownedFiles.includes(file)).map((file) => `owned-file:${file}`);
6367
+ const scopeOverlap = left.writeOwnership.exclusiveScopes.filter((scope) => right.writeOwnership.exclusiveScopes.includes(scope)).map((scope) => `exclusive-scope:${scope}`);
6368
+ return uniqueSorted([...ownedOverlap, ...scopeOverlap]);
6369
+ }
6370
+ function wireDependenciesAndConflicts(tasks, behaviorPlan) {
6371
+ const byId = new Map(tasks.map((item) => [item.id, item]));
6372
+ const taskByBehavior = new Map(tasks.flatMap((item) => item.behaviorIds.map((behaviorId) => [behaviorId, item.id])));
6373
+ const behaviorById = new Map(behaviorPlan.responsibilities.map((item) => [item.id, item]));
6374
+ for (const task of tasks) {
6375
+ const dependencies = task.behaviorIds.flatMap((behaviorId) => behaviorById.get(behaviorId)?.dependsOnBehaviorIds ?? []).map((behaviorId) => taskByBehavior.get(behaviorId)).filter((id) => Boolean(id) && id !== task.id);
6376
+ task.dependsOnTaskIds = uniqueSorted(dependencies);
6377
+ }
6378
+ const packages = /* @__PURE__ */ new Map();
6379
+ for (const task of tasks) {
6380
+ if (task.workPackageId === null) continue;
6381
+ packages.set(task.workPackageId, [...packages.get(task.workPackageId) ?? [], task]);
6382
+ }
6383
+ for (const items of packages.values()) {
6384
+ const lane = orderPackageLane(items);
6385
+ for (let index = 1; index < lane.length; index++) addDependency(byId, lane[index].id, lane[index - 1].id);
6386
+ }
6387
+ const packageRanks = packageSerializationRanks(tasks);
6388
+ const edges = [];
6389
+ for (let leftIndex = 0; leftIndex < tasks.length; leftIndex++) {
6390
+ for (let rightIndex = leftIndex + 1; rightIndex < tasks.length; rightIndex++) {
6391
+ const left = tasks[leftIndex];
6392
+ const right = tasks[rightIndex];
6393
+ const reasons = conflictReasons(left, right);
6394
+ if (!reasons.length) continue;
6395
+ edges.push({ leftTaskId: left.id, rightTaskId: right.id, reasons });
6396
+ left.conflictsWithTaskIds = uniqueSorted([...left.conflictsWithTaskIds, right.id]);
6397
+ right.conflictsWithTaskIds = uniqueSorted([...right.conflictsWithTaskIds, left.id]);
6398
+ if (hasPath(byId, left.id, right.id) || hasPath(byId, right.id, left.id)) continue;
6399
+ const leftPackageRank = left.workPackageId ? packageRanks.get(left.workPackageId) : void 0;
6400
+ const rightPackageRank = right.workPackageId ? packageRanks.get(right.workPackageId) : void 0;
6401
+ if (leftPackageRank !== void 0 && rightPackageRank !== void 0 && leftPackageRank !== rightPackageRank) {
6402
+ const [later, earlier] = leftPackageRank > rightPackageRank ? [left, right] : [right, left];
6403
+ addDependency(byId, later.id, earlier.id);
6404
+ } else {
6405
+ addDependency(byId, right.id, left.id);
6406
+ }
6407
+ }
6408
+ }
6409
+ for (const task of tasks) {
6410
+ const providers = task.dependsOnTaskIds.map((id) => byId.get(id)).filter((item) => Boolean(item));
6411
+ const businessProviderIds = new Set(task.behaviorIds.flatMap((behaviorId) => behaviorById.get(behaviorId)?.dependsOnBehaviorIds ?? []).map((behaviorId) => taskByBehavior.get(behaviorId)).filter((id) => Boolean(id)));
6412
+ task.writeOwnership.readOnlyFiles = uniqueSorted(providers.flatMap((provider) => provider.writeOwnership.ownedFiles).filter((file) => !task.writeOwnership.ownedFiles.includes(file)));
6413
+ task.dependencyInterfaces = providers.map((provider) => {
6414
+ const kind = businessProviderIds.has(provider.id) ? "module" : "ordering";
6415
+ return {
6416
+ kind,
6417
+ ref: kind === "module" ? `interface:${provider.primaryModule}` : `ordering:${provider.id}`,
6418
+ providerTaskId: provider.id,
6419
+ files: provider.writeOwnership.ownedFiles.filter((file) => !task.writeOwnership.ownedFiles.includes(file)),
6420
+ errorModes: kind === "module" ? ["provider task is not locally verified", "provider Interface changed after this execution plan was generated"] : ["serialized predecessor is not locally verified", "write ownership changed after this execution plan was generated"],
6421
+ orderingConstraints: [`${provider.id} must be locally verified before ${task.id}`]
6422
+ };
6423
+ });
6424
+ }
6425
+ return edges.sort((left, right) => `${left.leftTaskId}:${left.rightTaskId}`.localeCompare(`${right.leftTaskId}:${right.rightTaskId}`));
6426
+ }
6427
+ function downstreamCounts2(tasks) {
6428
+ const children = /* @__PURE__ */ new Map();
6429
+ for (const task of tasks) for (const dependency of task.dependsOnTaskIds) children.set(dependency, [...children.get(dependency) ?? [], task.id]);
6430
+ const result = /* @__PURE__ */ new Map();
6431
+ for (const task of tasks) {
6432
+ const seen = /* @__PURE__ */ new Set();
6433
+ const queue = [...children.get(task.id) ?? []];
6434
+ while (queue.length) {
6435
+ const next = queue.shift();
6436
+ if (seen.has(next)) continue;
6437
+ seen.add(next);
6438
+ queue.push(...children.get(next) ?? []);
6439
+ }
6440
+ result.set(task.id, seen.size);
6441
+ }
6442
+ return result;
6443
+ }
6444
+ function pageWorkPackages(tasks, facts, canonicalBySurface) {
6445
+ const grouped = /* @__PURE__ */ new Map();
6446
+ const taskById = new Map(tasks.map((task) => [task.id, task]));
6447
+ for (const task of tasks) {
6448
+ if (task.workPackageId === null) continue;
6449
+ grouped.set(task.workPackageId, [...grouped.get(task.workPackageId) ?? [], task]);
6450
+ }
6451
+ return [...grouped.entries()].map(([id, items]) => {
6452
+ const canonicalPageRef = items[0]?.canonicalPageRef ?? null;
6453
+ const title = facts.surfaces.find((surface) => surface.id === canonicalPageRef)?.name ?? canonicalPageRef ?? "Unassigned";
6454
+ const ordered = orderPackageLane(items);
6455
+ return {
6456
+ id,
6457
+ canonicalPageRef,
6458
+ title: `${title} Page Work Package`,
6459
+ surfaceRefs: uniqueSorted(facts.surfaces.filter((surface) => (canonicalBySurface[surface.id] ?? surface.id) === canonicalPageRef).map((surface) => surface.id)),
6460
+ workItemIds: ordered.map((item) => item.id),
6461
+ dependsOnPackageIds: uniqueSorted(items.flatMap((item) => item.dependsOnTaskIds).map((taskId2) => taskById.get(taskId2)?.workPackageId).filter((packageId) => Boolean(packageId && packageId !== id))),
6462
+ serialLane: true,
6463
+ exclusiveScopes: uniqueSorted(items.flatMap((item) => item.writeOwnership.exclusiveScopes))
6464
+ };
6465
+ }).sort((left, right) => left.id.localeCompare(right.id));
6466
+ }
6467
+ function compileExecutionPlan(behaviorPlan, facts, context = emptyExecutionPlanningContext) {
6468
+ const planningPhase = behaviorPlan.responsibilities.some((behavior) => behavior.type === "resolution") ? "resolution" : "execution";
6469
+ const activeBehaviorPlan = planningPhase === "resolution" ? { ...behaviorPlan, responsibilities: behaviorPlan.responsibilities.filter((behavior) => behavior.type === "resolution") } : behaviorPlan;
6470
+ const canonicalBySurface = canonicalPageRefs(facts);
6471
+ const clusters = planningPhase === "resolution" ? activeBehaviorPlan.responsibilities.map((behavior) => candidateForBehavior(behavior, facts, canonicalBySurface)) : clusterBehaviors(activeBehaviorPlan, facts, context);
6472
+ const tasks = initialTasks(clusters, activeBehaviorPlan, facts, canonicalBySurface, context);
6473
+ const fileConflictEdges = wireDependenciesAndConflicts(tasks, activeBehaviorPlan);
6474
+ const downstream = downstreamCounts2(tasks);
6475
+ for (const task of tasks) task.estimate.downstreamUnlockCount = downstream.get(task.id) ?? 0;
6476
+ const taskByBehavior = new Map(tasks.flatMap((task) => task.behaviorIds.map((behaviorId) => [behaviorId, task.id])));
6477
+ const primaryOwnerByFactRef = Object.fromEntries(Object.entries(behaviorPlan.ownership.primaryOwnerByFactRef).flatMap(([ref, behaviorId]) => {
6478
+ const taskId2 = taskByBehavior.get(behaviorId);
6479
+ return taskId2 ? [[ref, taskId2]] : [];
6480
+ }).sort(([left], [right]) => left.localeCompare(right)));
6481
+ const supportingTaskIdsByFactRef = {};
6482
+ for (const task of tasks) for (const ref of task.supportingFactRefs) supportingTaskIdsByFactRef[ref] = uniqueSorted([...supportingTaskIdsByFactRef[ref] ?? [], task.id]);
6483
+ return {
6484
+ taskPlanVersion: TASK_PLAN_VERSION,
6485
+ executionPlanVersion: EXECUTION_PLAN_VERSION,
6486
+ planningModelVersion: behaviorPlan.planningModelVersion,
6487
+ factsAdapterVersion: behaviorPlan.factsAdapterVersion,
6488
+ plannerPolicyVersion: behaviorPlan.plannerPolicyVersion,
6489
+ baseline: behaviorPlan.baseline,
6490
+ planningPhase,
6491
+ entryResolution: behaviorPlan.entryResolution,
6492
+ behaviorResponsibilities: behaviorPlan.responsibilities,
6493
+ pageWorkPackages: pageWorkPackages(tasks, facts, canonicalBySurface),
6494
+ tasks,
6495
+ ownership: {
6496
+ primaryOwnerByFactRef,
6497
+ supportingTaskIdsByFactRef: Object.fromEntries(Object.entries(supportingTaskIdsByFactRef).sort(([left], [right]) => left.localeCompare(right)))
6498
+ },
6499
+ behaviorOwnership: behaviorPlan.ownership,
6500
+ fileConflictEdges,
6501
+ planningWarnings: behaviorPlan.planningWarnings
6502
+ };
6503
+ }
6504
+ function compileFromPlanningFacts(facts, androidFingerprint, context = emptyExecutionPlanningContext) {
6505
+ return compileExecutionPlan(compileBehaviorPlanFromPlanningFacts(facts, androidFingerprint), facts, context);
6506
+ }
6507
+
6508
+ // src/planning/storage.ts
6509
+ import fs27 from "fs";
6130
6510
  import path33 from "path";
6131
6511
  function taskPlanRoot(projectRoot) {
6132
6512
  return path33.join(migrationDirectory(projectRoot), "tasks");
6133
6513
  }
6134
- function taskArtifactStem(taskId) {
6135
- return taskId.replace(/^task:/, "");
6514
+ function taskArtifactStem(taskId2) {
6515
+ return taskId2.replace(/^task:/, "");
6516
+ }
6517
+ function workPackageArtifactStem(workPackageId2) {
6518
+ return workPackageId2.replace(/^package:/, "");
6519
+ }
6520
+ function codeRefIndex(value) {
6521
+ const allowed = ["id", "file", "name", "symbol", "kind", "type", "capabilityRef", "line", "startLine", "endLine"];
6522
+ return Object.fromEntries(allowed.filter((key) => value[key] !== void 0).map((key) => [key, value[key]]));
6136
6523
  }
6137
- function renderTaskBrief(task) {
6524
+ function taskContractView(task) {
6525
+ return { ...task, closure: { ...task.closure, codeRefs: task.closure.codeRefs.map(codeRefIndex) } };
6526
+ }
6527
+ function coreFactsForTask(task, facts) {
6528
+ return coreFactsForRefs([...task.primaryFactRefs, ...task.supportingFactRefs], task.closure.unresolvedRefs, facts);
6529
+ }
6530
+ function coreFactsForRefs(refsInput, unresolvedRefs, facts) {
6531
+ const refs = new Set(refsInput);
6532
+ return [
6533
+ ...facts.surfaces.filter((item) => refs.has(item.id)).map((value) => ({ kind: "surface", value })),
6534
+ ...facts.controls.filter((item) => refs.has(item.id)).map((value) => ({ kind: "control", value })),
6535
+ ...facts.entries.filter((item) => refs.has(item.id)).map((value) => ({
6536
+ kind: "entry",
6537
+ value: { ...value, trace: { ...value.trace, codeRefs: value.trace.codeRefs.map(codeRefIndex) } }
6538
+ })),
6539
+ ...facts.effects.filter((item) => refs.has(item.id)).map((value) => ({ kind: "effect", value })),
6540
+ ...facts.issues.filter((item) => refs.has(item.id) || unresolvedRefs.includes(item.id)).map((value) => ({ kind: "issue", value }))
6541
+ ];
6542
+ }
6543
+ function sourceContextForTask(task) {
6544
+ return uniqueBy([
6545
+ ...task.closure.sourceEvidence.map((value) => ({ kind: "source-evidence", value })),
6546
+ ...task.closure.codeRefs.map((value) => ({ kind: "code-ref", value: codeRefIndex(value) }))
6547
+ ], stableJson);
6548
+ }
6549
+ function tasksForPackage(workPackage, plan) {
6550
+ const byId = new Map(plan.tasks.map((task) => [task.id, task]));
6551
+ return workPackage.workItemIds.map((id) => byId.get(id)).filter((task) => Boolean(task));
6552
+ }
6553
+ function effectOutcome2(effectRef, facts) {
6554
+ const effect = facts.effects.find((item) => item.id === effectRef);
6555
+ if (!effect) return effectRef;
6556
+ const target = effect.targetKind === "surface" ? facts.surfaces.find((surface) => surface.id === effect.targetId)?.name : effect.targetKind === "entry" ? facts.entries.find((entry) => entry.id === effect.targetId)?.label : null;
6557
+ const label = target ?? effect.targetId;
6558
+ if (effect.kind === "opens") return `\u6253\u5F00 ${label}`;
6559
+ if (effect.kind === "navigates-to") return `\u8FDB\u5165 ${label}`;
6560
+ if (effect.kind === "launches-for-result") return `\u542F\u52A8 ${label} \u5E76\u5904\u7406\u8FD4\u56DE\u7ED3\u679C`;
6561
+ return `\u5904\u7406 ${label}`;
6562
+ }
6563
+ function pageWorkPackageContractView(workPackage, plan, facts) {
6564
+ const tasks = tasksForPackage(workPackage, plan);
6565
+ const taskByBehavior = new Map(tasks.flatMap((task) => task.behaviorIds.map((behaviorId) => [behaviorId, task])));
6566
+ const behaviorById = new Map(plan.behaviorResponsibilities.map((behavior) => [behavior.id, behavior]));
6567
+ const orderedBehaviors = tasks.flatMap((task) => task.behaviorIds.map((id) => behaviorById.get(id))).filter((item) => Boolean(item));
6568
+ const ownedFiles = uniqueSorted(tasks.flatMap((task) => task.writeOwnership.ownedFiles));
6569
+ const packageByTask = new Map(plan.tasks.flatMap((task) => task.workPackageId ? [[task.id, task.workPackageId]] : []));
6570
+ const dependencyGroups = /* @__PURE__ */ new Map();
6571
+ for (const task of tasks) for (const dependency of task.dependencyInterfaces) {
6572
+ const providerTaskId = dependency.providerTaskId;
6573
+ const providerPackageId = providerTaskId ? packageByTask.get(providerTaskId) : null;
6574
+ if (!providerTaskId || !providerPackageId || providerPackageId === workPackage.id) continue;
6575
+ const key = `${dependency.kind}:${dependency.ref}:${providerPackageId}`;
6576
+ const current = dependencyGroups.get(key) ?? {
6577
+ kind: dependency.kind,
6578
+ ref: dependency.ref,
6579
+ providerPackageId,
6580
+ providerWorkItemIds: [],
6581
+ files: [],
6582
+ errorModes: [],
6583
+ orderingConstraints: []
6584
+ };
6585
+ current.providerWorkItemIds = uniqueSorted([...current.providerWorkItemIds, providerTaskId]);
6586
+ current.files = uniqueSorted([...current.files, ...dependency.files]);
6587
+ current.errorModes = uniqueSorted([...current.errorModes, ...dependency.errorModes]);
6588
+ current.orderingConstraints = uniqueSorted([...current.orderingConstraints, ...dependency.orderingConstraints]);
6589
+ dependencyGroups.set(key, current);
6590
+ }
6591
+ const sourceFiles = uniqueSorted(tasks.flatMap((task) => [
6592
+ ...task.closure.sourceEvidence.map((item) => item.file),
6593
+ ...task.closure.codeRefs.flatMap((item) => typeof item.file === "string" ? [item.file] : [])
6594
+ ]));
6595
+ return {
6596
+ ...workPackage,
6597
+ behaviorIds: orderedBehaviors.map((behavior) => behavior.id),
6598
+ workItems: tasks.map((task, index) => ({
6599
+ ordinal: index + 1,
6600
+ id: task.id,
6601
+ type: task.type,
6602
+ title: task.title,
6603
+ behaviorIds: task.behaviorIds
6604
+ })),
6605
+ features: orderedBehaviors.map((behavior, index) => ({
6606
+ ordinal: index + 1,
6607
+ behaviorId: behavior.id,
6608
+ workItemId: taskByBehavior.get(behavior.id).id,
6609
+ type: behavior.type,
6610
+ title: behavior.title,
6611
+ triggerLabels: uniqueSorted(facts.entries.filter((entry) => behavior.closure.entryRefs.includes(entry.id)).map((entry) => entry.label)),
6612
+ controlNames: uniqueSorted(facts.controls.filter((control) => behavior.closure.controlRefs.includes(control.id)).map((control) => control.name)),
6613
+ interactionOutcomes: uniqueSorted(behavior.closure.interactionRefs.map((ref) => effectOutcome2(ref, facts))),
6614
+ entryRefs: behavior.closure.entryRefs,
6615
+ surfaceRefs: behavior.closure.surfaceRefs,
6616
+ controlRefs: behavior.closure.controlRefs,
6617
+ interactionRefs: behavior.closure.interactionRefs,
6618
+ unresolvedRefs: behavior.closure.unresolvedRefs,
6619
+ primaryFactRefs: behavior.primaryFactRefs,
6620
+ supportingFactRefs: behavior.supportingFactRefs,
6621
+ requiresSourceReview: behavior.closure.requiresSourceReview,
6622
+ observableOutcome: behavior.acceptance.observableOutcome,
6623
+ requiredFactRefs: behavior.acceptance.requiredFactRefs,
6624
+ verificationRequirements: behavior.acceptance.verificationRequirements,
6625
+ reportRefs: behavior.acceptance.reportRefs
6626
+ })),
6627
+ writeOwnership: {
6628
+ ownedFiles,
6629
+ readOnlyFiles: uniqueSorted(tasks.flatMap((task) => task.writeOwnership.readOnlyFiles).filter((file) => !ownedFiles.includes(file))),
6630
+ exclusiveScopes: workPackage.exclusiveScopes,
6631
+ forbiddenGlobalFiles: uniqueSorted(tasks.flatMap((task) => task.writeOwnership.forbiddenGlobalFiles).filter((file) => !ownedFiles.includes(file)))
6632
+ },
6633
+ dependencyInterfaces: [...dependencyGroups.values()].sort((left, right) => `${left.providerPackageId}:${left.ref}`.localeCompare(`${right.providerPackageId}:${right.ref}`)),
6634
+ acceptance: {
6635
+ observableOutcomes: uniqueSorted(orderedBehaviors.map((behavior) => behavior.acceptance.observableOutcome)),
6636
+ requiredFactRefs: uniqueSorted(orderedBehaviors.flatMap((behavior) => behavior.acceptance.requiredFactRefs)),
6637
+ verificationRequirements: uniqueSorted(orderedBehaviors.flatMap((behavior) => behavior.acceptance.verificationRequirements)),
6638
+ reportRefs: uniqueSorted(orderedBehaviors.flatMap((behavior) => behavior.acceptance.reportRefs))
6639
+ },
6640
+ estimate: {
6641
+ behaviorCount: orderedBehaviors.length,
6642
+ workItemCount: tasks.length,
6643
+ factCount: uniqueSorted(tasks.flatMap((task) => [...task.primaryFactRefs, ...task.supportingFactRefs])).length,
6644
+ sourceFileCount: sourceFiles.length,
6645
+ traceNodeCount: uniqueBy(tasks.flatMap((task) => task.closure.codeRefs), stableJson).length,
6646
+ estimatedFactTokens: tasks.reduce((total, task) => total + task.estimate.estimatedFactTokens, 0),
6647
+ productionFileCount: uniqueSorted(tasks.flatMap((task) => task.writeOwnership.ownedFiles).filter((file) => !/(^|\/)(test|ohosTest)(\/|$)|\.(test|spec)\./i.test(file))).length,
6648
+ testFileCount: uniqueSorted(tasks.flatMap((task) => task.writeOwnership.ownedFiles).filter((file) => /(^|\/)(test|ohosTest)(\/|$)|\.(test|spec)\./i.test(file))).length,
6649
+ capabilityCount: tasks.reduce((total, task) => total + task.estimate.capabilityCount, 0)
6650
+ }
6651
+ };
6652
+ }
6653
+ function coreFactsForPageWorkPackage(workPackage, facts) {
6654
+ const tasksRefs = workPackage.features.flatMap((feature) => [...feature.primaryFactRefs, ...feature.supportingFactRefs]);
6655
+ return coreFactsForRefs(tasksRefs, workPackage.features.flatMap((feature) => feature.unresolvedRefs), facts);
6656
+ }
6657
+ function sourceContextForPageWorkPackage(workPackage, plan) {
6658
+ return uniqueBy(tasksForPackage(workPackage, plan).flatMap(sourceContextForTask), stableJson);
6659
+ }
6660
+ function renderResolutionTaskBrief(task, plan) {
6661
+ if (task.type !== "resolution") throw new Error("Resolution brief requires a Resolution Task");
6138
6662
  const list = (values) => values.length ? values.map((value) => `- \`${value}\``).join("\n") : "- \u65E0";
6139
6663
  const sourceLocations = [.../* @__PURE__ */ new Set([
6140
6664
  ...task.closure.sourceEvidence.map((item) => `${item.file}${item.line ? `:${item.line}${(() => {
@@ -6152,15 +6676,54 @@ function renderTaskBrief(task) {
6152
6676
  return "";
6153
6677
  })()}`] : [])
6154
6678
  ])].sort();
6155
- return `# ${task.title}
6679
+ const sourcePreview = sourceLocations.slice(0, 20);
6680
+ const hiddenSourceCount = sourceLocations.length - sourcePreview.length;
6681
+ const taskKind = "Resolution Task";
6682
+ const identity = [
6683
+ `- ${taskKind} ID\uFF1A\`${task.id}\``,
6684
+ `- Execution Plan Hash\uFF1A\`${sha2562(plan)}\``,
6685
+ `- Facts SHA256\uFF1A\`${plan.baseline.factsSha256}\``,
6686
+ `- Android Fingerprint\uFF1A${plan.baseline.androidFingerprint ? `\`${plan.baseline.androidFingerprint}\`` : "\u672A\u63D0\u4F9B"}`,
6687
+ `- Behavior IDs\uFF1A${task.behaviorIds.map((id) => `\`${id}\``).join("\u3001")}`,
6688
+ `- Primary Page / Module\uFF1A\`${task.canonicalPageRef ?? "unassigned"}\` / \`${task.primaryModule}\``
6689
+ ].join("\n");
6690
+ const ownership = (values, fallback) => values.length ? values.map((value) => `- \`${value}\``).join("\n") : `- ${fallback}`;
6691
+ return `# ${taskKind}\uFF1A${task.title}
6156
6692
 
6157
- ## \u76EE\u6807
6693
+ ## 1. \u6700\u7EC8\u76EE\u6807
6158
6694
 
6159
6695
  ${task.acceptance.observableOutcome}
6160
6696
 
6161
- ## \u884C\u4E3A\u95ED\u5305
6697
+ ## 2. \u8EAB\u4EFD\u4E0E\u57FA\u7EBF
6698
+
6699
+ ${identity}
6700
+
6701
+ ## 3. \u5B8C\u6210\u5B9A\u4E49
6702
+
6703
+ ${task.acceptance.observableOutcomes.map((outcome) => `- ${outcome}`).join("\n")}
6704
+
6705
+ ## 4. \u5199\u5165\u6240\u6709\u6743
6706
+
6707
+ ### \u5141\u8BB8\u4FEE\u6539
6708
+
6709
+ ${ownership(task.writeOwnership.ownedFiles, `\u65E0\uFF1B\`${task.writeOwnership.exclusiveScopes.join("\u3001")}\` \u662F\u88C1\u51B3\u4E92\u65A5\u8303\u56F4\uFF0C\u4E0D\u6388\u4E88 HarmonyOS \u6587\u4EF6\u5199\u5165\u6743`)}
6710
+
6711
+ ### \u53EA\u8BFB\u4F9D\u8D56
6712
+
6713
+ ${ownership(task.writeOwnership.readOnlyFiles, "\u65E0")}
6714
+
6715
+ ### \u7981\u6B62\u4FEE\u6539
6716
+
6717
+ ${ownership(task.writeOwnership.forbiddenGlobalFiles, "\u65E0")}
6718
+
6719
+ ## 5. \u524D\u7F6E\u4F9D\u8D56\u4E0E Interface
6720
+
6721
+ ${task.dependencyInterfaces.length ? task.dependencyInterfaces.map((item) => `- ${item.kind === "module" ? "Module Interface" : "\u8C03\u5EA6\u987A\u5E8F"} \`${item.ref}\`\uFF0C\u63D0\u4F9B\u8005\uFF1A${item.providerTaskId ? `\`${item.providerTaskId}\`` : "\u5916\u90E8\u7A33\u5B9A Interface"}\uFF1B\u987A\u5E8F\uFF1A${item.orderingConstraints.join("\uFF1B") || "\u65E0"}\uFF1B\u9519\u8BEF\u6A21\u5F0F\uFF1A${item.errorModes.join("\uFF1B") || "\u65E0"}`).join("\n") : "- \u65E0"}
6722
+
6723
+ ## 6. \u884C\u4E3A\u4E0E\u72B6\u6001\u673A
6162
6724
 
6163
6725
  - Task \u7C7B\u578B\uFF1A\`${task.type}\`
6726
+ - Work Package\uFF1A${task.workPackageId ? `\`${task.workPackageId}\`` : "\u4E0D\u9002\u7528\uFF08Resolution Task \u4E0D\u53C2\u4E0E Coding Work Package\uFF09"}
6164
6727
  - \u5165\u53E3\u6570\uFF1A${task.closure.entryRefs.length}
6165
6728
  - \u9875\u9762\u6570\uFF1A${task.closure.surfaceRefs.length}
6166
6729
  - \u63A7\u4EF6\u6570\uFF1A${task.closure.controlRefs.length}
@@ -6172,171 +6735,388 @@ ${task.acceptance.observableOutcome}
6172
6735
  - \u4EA4\u4E92\uFF1A${task.closure.interactionRefs.map((ref) => `\`${ref}\``).join("\u3001") || "\u65E0"}
6173
6736
  - \u9700\u8981\u6E90\u7801\u590D\u6838\uFF1A${task.closure.requiresSourceReview ? "\u662F" : "\u5426"}
6174
6737
 
6175
- ## \u6E90\u7801\u4E0A\u4E0B\u6587
6738
+ ## 7. Android \u8BC1\u636E
6739
+
6740
+ ${sourcePreview.length ? `${sourcePreview.map((value) => `- ${value}`).join("\n")}${hiddenSourceCount > 0 ? `
6741
+ - \u5176\u4F59 ${hiddenSourceCount} \u4E2A\u4F4D\u7F6E\u89C1 source-context.json \u6216 task show \u5206\u9875` : ""}` : "- \u65E0\u76F4\u63A5\u6E90\u7801\u4F4D\u7F6E\uFF0C\u8BF7\u4ECE coreFacts \u548C unresolvedRefs \u5F00\u59CB\u88C1\u51B3"}
6176
6742
 
6177
- ${sourceLocations.length ? sourceLocations.map((value) => `- ${value}`).join("\n") : "- \u65E0\u76F4\u63A5\u6E90\u7801\u4F4D\u7F6E\uFF0C\u8BF7\u4ECE coreFacts \u548C unresolvedRefs \u5F00\u59CB\u88C1\u51B3"}
6743
+ ## 8. Facts \u95ED\u5305
6178
6744
 
6179
- ## \u5F85\u88C1\u51B3\u95EE\u9898
6745
+ ### \u5F85\u88C1\u51B3\u95EE\u9898
6180
6746
 
6181
6747
  ${list(task.closure.unresolvedRefs)}
6182
6748
 
6183
- ## \u4E3B\u6240\u6709\u6743 Facts
6749
+ ### \u4E3B\u6240\u6709\u6743 Facts
6184
6750
 
6185
6751
  ${list(task.primaryFactRefs)}
6186
6752
 
6187
- ## \u652F\u6301 Facts
6753
+ ### Supporting Facts
6188
6754
 
6189
6755
  ${list(task.supportingFactRefs)}
6190
6756
 
6191
- ## \u4F9D\u8D56 Task
6757
+ ## 9. \u5B9E\u73B0\u7EA6\u675F
6758
+
6759
+ - \u672C Resolution Task \u53EA\u88C1\u51B3 Planning Facts\uFF1B\u4E0D\u5F97\u4FEE\u6539 HarmonyOS \u4E1A\u52A1\u5B9E\u73B0\u3002
6760
+ - \u5FC5\u987B\u628A\u7ED3\u8BBA\u5199\u5165\u53D7\u7BA1 fact correction \u6216 unresolved review\uFF1BReport discovery \u4E0D\u80FD\u5355\u72EC\u89E3\u9664\u89C4\u5212\u963B\u585E\u3002
6761
+ - \u89C4\u5212\u8F93\u5165\u53D8\u5316\u540E\u5FC5\u987B\u91CD\u65B0\u751F\u6210 Behavior Plan \u4E0E Resolution Plan\uFF1B\u53EA\u6709 Resolution \u6E05\u96F6\u540E\u624D\u7EC4\u88C5 Coding Work Item\u3002
6762
+ - Ready Gate\uFF1A\u8BED\u4E49=${task.readyGate.semanticsResolved ? "\u901A\u8FC7" : "\u672A\u901A\u8FC7"}\uFF0C\u4F9D\u8D56 Interface=${task.readyGate.dependencyInterfacesStable ? "\u7A33\u5B9A" : "\u4E0D\u7A33\u5B9A"}\uFF0C\u5199\u5165\u6240\u6709\u6743=${task.readyGate.writeOwnershipResolved ? "\u5DF2\u786E\u5B9A" : "\u672A\u786E\u5B9A"}\uFF0C\u4E0A\u4E0B\u6587\u9884\u7B97=${task.readyGate.contextWithinBudget ? "\u901A\u8FC7" : "\u8D85\u9650"}\uFF0C\u9A8C\u6536=${task.readyGate.acceptanceExecutable ? "\u53EF\u6267\u884C" : "\u4E0D\u53EF\u6267\u884C"}\u3002
6763
+
6764
+ ## 10. \u975E\u76EE\u6807
6765
+
6766
+ - \u4E0D\u6269\u5927\u5230\u5F53\u524D Behavior IDs \u4E4B\u5916\u7684\u72EC\u7ACB\u4E1A\u52A1\u7ED3\u679C\u3002
6767
+ - \u4E0D\u7F16\u5199 ArkTS/ArkUI\uFF0C\u4E0D\u628A\u6E90\u7801\u9605\u8BFB\u672C\u8EAB\u89C6\u4E3A\u88C1\u51B3\u5B8C\u6210\u3002
6768
+ - \u4E0D\u4EE5\u62A5\u544A\u5B57\u6BB5\u3001\u6CE8\u91CA\u3001\u6869\u4EE3\u7801\u6216\u4EC5\u7F16\u8BD1\u6210\u529F\u66FF\u4EE3\u771F\u5B9E\u884C\u4E3A\u5B9E\u73B0\u3002
6769
+
6770
+ ## 11. \u9A8C\u6536\u547D\u4EE4\u4E0E\u573A\u666F
6771
+
6772
+ ${task.acceptance.verificationRequirements.map((value) => `- ${value}`).join("\n")}
6773
+
6774
+ \u9A8C\u6536\u5FC5\u987B\u8986\u76D6\u7684 Report refs\uFF1A${task.acceptance.reportRefs.map((ref) => `\`${ref}\``).join("\u3001") || "\u65E0"}\u3002
6192
6775
 
6193
- ${list(task.dependsOnTaskIds)}
6776
+ ## 12. \u5B8C\u6210\u56DE\u62A5\u683C\u5F0F
6777
+
6778
+ - \u88C1\u51B3\u7684 issue refs\u3001\u8BFB\u53D6\u7684 Android \u6E90\u7801/\u8FD0\u884C\u8BC1\u636E\uFF0C\u4EE5\u53CA\u5199\u5165\u7684 correction/review\u3002
6779
+ - \u6BCF\u4E2A Behavior ID \u5BF9\u5E94\u7684\u88C1\u51B3\u7ED3\u679C\u3002
6780
+ - \u91CD\u65B0\u89C4\u5212\u540E\u7684 Plan Hash\u3001planningPhase\uFF0C\u4EE5\u53CA\u662F\u5426\u4ECD\u6709 Resolution\uFF1B\u6E05\u96F6\u540E\u8BB0\u5F55\u65B0\u751F\u6210\u7684\u51BB\u7ED3 Coding Plan Hash\u3002
6781
+ - \u672A\u89E3\u51B3\u95EE\u9898\uFF1B\u6CA1\u6709\u5219\u660E\u786E\u5199\u201C\u65E0\u201D\u3002
6194
6782
 
6195
6783
  ## \u5DE5\u4F5C\u91CF\u63D0\u793A
6196
6784
 
6197
6785
  - Fact \u6570\uFF1A${task.estimate.factCount}
6786
+ - Behavior \u6570\uFF1A${task.estimate.behaviorCount}
6787
+ - \u53EF\u89C2\u5BDF\u7ED3\u679C\u6570\uFF1A${task.estimate.observableOutcomeCount}
6198
6788
  - \u6E90\u7801\u6587\u4EF6\u6570\uFF1A${task.estimate.sourceFileCount}
6789
+ - \u751F\u4EA7\u4EE3\u7801\u6587\u4EF6\u6570\uFF1A${task.estimate.productionFileCount}
6790
+ - \u6D4B\u8BD5\u6587\u4EF6\u6570\uFF1A${task.estimate.testFileCount}
6791
+ - Capability \u6570\uFF1A${task.estimate.capabilityCount}
6199
6792
  - \u8C03\u7528\u94FE\u8282\u70B9\u6570\uFF1A${task.estimate.traceNodeCount}
6200
6793
  - \u4F30\u7B97 Facts Token\uFF1A${task.estimate.estimatedFactTokens}
6201
6794
  - \u53EF\u89E3\u9501\u4E0B\u6E38 Task\uFF1A${task.estimate.downstreamUnlockCount}
6795
+ `;
6796
+ }
6797
+ function renderPageWorkPackageBrief(workPackage, plan, facts) {
6798
+ const quoteList = (values, fallback = "\u65E0") => values.length ? values.map((value) => `- \`${value}\``).join("\n") : `- ${fallback}`;
6799
+ const surfaceNames = workPackage.surfaceRefs.map((ref) => `${facts.surfaces.find((surface) => surface.id === ref)?.name ?? ref}\uFF08\`${ref}\`\uFF09`);
6800
+ const featureSections = workPackage.features.map((feature) => {
6801
+ const behavior = plan.behaviorResponsibilities.find((item) => item.id === feature.behaviorId);
6802
+ const sources = uniqueSorted([
6803
+ ...behavior.closure.sourceEvidence.map((item) => `${item.file}${item.line ? `:${item.line}${item.endLine && item.endLine !== item.line ? `-${item.endLine}` : ""}` : ""}${item.symbol ? ` \u2014 ${item.symbol}` : ""}`),
6804
+ ...behavior.closure.codeRefs.flatMap((item) => typeof item.file === "string" ? [`${item.file}${typeof item.startLine === "number" ? `:${item.startLine}${typeof item.endLine === "number" && item.endLine !== item.startLine ? `-${item.endLine}` : ""}` : ""}${typeof item.name === "string" ? ` \u2014 ${item.name}` : ""}`] : [])
6805
+ ]);
6806
+ return `### F-${String(feature.ordinal).padStart(3, "0")}\uFF1A${feature.title}
6202
6807
 
6203
- ## \u9A8C\u6536\u8981\u6C42
6808
+ - Behavior / Work Item\uFF1A\`${feature.behaviorId}\` / \`${feature.workItemId}\`
6809
+ - \u89E6\u53D1\uFF1A${feature.triggerLabels.join("\u3001") || "\u7531 Android \u6E90\u7801\u5165\u53E3\u786E\u5B9A"}
6810
+ - \u63A7\u4EF6\uFF1A${feature.controlNames.join("\u3001") || "\u65E0\u663E\u5F0F\u63A7\u4EF6"}
6811
+ - \u4EA4\u4E92\u7ED3\u679C\uFF1A${feature.interactionOutcomes.join("\u3001") || "\u5FC5\u987B\u6CBF\u6E90\u7801\u8C03\u7528\u94FE\u786E\u8BA4\u72B6\u6001\u3001\u6570\u636E\u6216\u7CFB\u7EDF\u526F\u4F5C\u7528"}
6812
+ - \u5B9E\u73B0\u7ED3\u679C\uFF1A${feature.observableOutcome}
6813
+ - Required Fact refs\uFF1A${feature.requiredFactRefs.map((ref) => `\`${ref}\``).join("\u3001") || "\u65E0"}
6814
+ - \u6E90\u7801\u590D\u6838\uFF1A${feature.requiresSourceReview ? "\u5FC5\u987B\uFF1BFacts \u672A\u5B8C\u6574\u8868\u8FBE\u53EF\u89C2\u5BDF\u7ED3\u679C\u6216\u5206\u652F" : "\u5FC5\u987B\u8BFB\u53D6\u4E0B\u5217\u8BC1\u636E\u5E76\u8986\u76D6\u5176\u4E2D\u5168\u90E8\u5B9E\u9645\u5206\u652F"}
6815
+ - Android \u8BC1\u636E\uFF1A
6816
+ ${sources.length ? sources.map((value) => ` - ${value}`).join("\n") : " - \u65E0\u76F4\u63A5\u4F4D\u7F6E\uFF1B\u4E0D\u5F97\u731C\u6D4B\u5B9E\u73B0\uFF0C\u5148\u4ECE\u5BF9\u5E94 core facts \u7EE7\u7EED\u5B9A\u4F4D"}
6817
+ - Report refs\uFF1A${feature.reportRefs.map((ref) => `\`${ref}\``).join("\u3001") || "\u65E0"}`;
6818
+ }).join("\n\n");
6819
+ const tasksById = new Map(plan.tasks.map((task) => [task.id, task]));
6820
+ const packageTaskIds = new Set(workPackage.workItemIds);
6821
+ const internalSequence = workPackage.workItems.map((item) => {
6822
+ const dependencies = (tasksById.get(item.id)?.dependsOnTaskIds ?? []).filter((id) => packageTaskIds.has(id));
6823
+ return `${item.ordinal}. \`${item.id}\` \u2014 ${item.title}${dependencies.length ? `\uFF1B\u9875\u9762\u5185\u524D\u7F6E\uFF1A${dependencies.map((id) => `\`${id}\``).join("\u3001")}` : ""}`;
6824
+ }).join("\n");
6825
+ const dependencyText = workPackage.dependencyInterfaces.length ? workPackage.dependencyInterfaces.map((item) => `- \`${item.providerPackageId}\` \u63D0\u4F9B ${item.kind === "module" ? "Module Interface" : "\u8C03\u5EA6\u524D\u7F6E"} \`${item.ref}\`\uFF1B\u6765\u6E90 Work Items\uFF1A${item.providerWorkItemIds.map((id) => `\`${id}\``).join("\u3001")}\uFF1B\u9519\u8BEF\u6A21\u5F0F\uFF1A${item.errorModes.join("\uFF1B") || "\u65E0"}`).join("\n") : "- \u65E0";
6826
+ const acceptanceSequences = workPackage.features.map((feature) => `### F-${String(feature.ordinal).padStart(3, "0")}
6204
6827
 
6205
- ${task.acceptance.verificationRequirements.map((value) => `- ${value}`).join("\n")}
6828
+ 1. \u4ECE\u771F\u5B9E\u5E94\u7528\u5165\u53E3\u5230\u8FBE\u672C Page Work Package \u627F\u8F7D\u9762\u3002
6829
+ 2. \u89E6\u53D1\u201C${feature.triggerLabels.join(" / ") || feature.title}\u201D\u3002
6830
+ 3. \u65AD\u8A00\uFF1A${feature.observableOutcome}
6831
+ 4. \u6309 Android \u8BC1\u636E\u8986\u76D6\u6210\u529F\u3001\u53D6\u6D88\u3001\u5931\u8D25\u3001\u7A7A\u503C\u548C\u7CFB\u7EDF\u56DE\u8C03\u5206\u652F\u4E2D\u5B9E\u9645\u5B58\u5728\u7684\u573A\u666F\uFF1B\u4E0D\u5F97\u7528\u6869\u4EE3\u7801\u6216\u4EC5\u7F16\u8BD1\u6210\u529F\u4EE3\u66FF\u3002`).join("\n\n");
6832
+ return `# Page Work Package\uFF1A${workPackage.title.replace(/ Page Work Package$/, "")}
6833
+
6834
+ > \u672C Markdown \u662F execution \u9636\u6BB5\u4E00\u6B21\u6A21\u578B\u8C03\u7528\u7684\u6700\u7EC8\u9875\u9762\u5B9E\u73B0\u8F93\u5165\uFF0C\u7531\u51BB\u7ED3\u7684 Execution Plan \u548C Canonical Facts \u786E\u5B9A\u6027\u751F\u6210\u3002Work Item \u4E0E Behavior \u4EC5\u4F5C\u4E3A\u672C\u5305\u5185\u90E8\u987A\u5E8F\u3001\u8BC1\u636E\u548C\u9A8C\u6536\u8D23\u4EFB\uFF0C\u4E0D\u518D\u5206\u522B\u751F\u6210\u53EF\u6267\u884C Markdown\u3002
6835
+
6836
+ ## 1. \u9875\u9762\u6267\u884C\u5951\u7EA6
6837
+
6838
+ - Page Work Package ID\uFF1A\`${workPackage.id}\`
6839
+ - Execution Plan Hash\uFF1A\`${sha2562(plan)}\`
6840
+ - Facts SHA256\uFF1A\`${plan.baseline.factsSha256}\`
6841
+ - Android Fingerprint\uFF1A${plan.baseline.androidFingerprint ? `\`${plan.baseline.androidFingerprint}\`` : "\u672A\u63D0\u4F9B"}
6842
+ - Canonical Page\uFF1A\`${workPackage.canonicalPageRef ?? "unassigned"}\`
6843
+ - \u627F\u8F7D\u9762\uFF1A${surfaceNames.join("\u3001") || "\u65E0"}
6844
+ - Work Item \u6570\uFF1A${workPackage.workItems.length}
6845
+ - Behavior / \u9875\u9762\u529F\u80FD\u6570\uFF1A${workPackage.features.length}
6846
+
6847
+ ## 2. \u9875\u9762\u8303\u56F4\u4E0E\u6700\u7EC8\u76EE\u6807
6848
+
6849
+ \u672C\u5305\u5FC5\u987B\u5728\u540C\u4E00\u4E2A\u5B9E\u73B0 epoch \u5185\u5B8C\u6210\u4EE5\u4E0B\u5168\u90E8\u9875\u9762\u529F\u80FD\uFF0C\u5E76\u4FDD\u6301\u9875\u9762\u5185\u987A\u5E8F\u3002\u627F\u8F7D\u9762\u5185\u7684 Dialog\u3001Sheet \u6216\u7CFB\u7EDF\u56DE\u8C03\u5C5E\u4E8E\u672C\u5305\uFF1B\u672A\u51FA\u73B0\u5728\u529F\u80FD\u5217\u8868\u548C Facts \u95ED\u5305\u4E2D\u7684\u72EC\u7ACB\u4E1A\u52A1\u7ED3\u679C\u4E0D\u5C5E\u4E8E\u672C\u5305\u3002
6850
+
6851
+ ${workPackage.acceptance.observableOutcomes.map((outcome) => `- ${outcome}`).join("\n")}
6852
+
6853
+ ## 3. \u5177\u4F53\u5B9E\u73B0\u529F\u80FD
6854
+
6855
+ ${featureSections}
6856
+
6857
+ ## 4. \u9875\u9762\u72B6\u6001\u4E0E Facts \u95ED\u5305
6858
+
6859
+ - Entry refs\uFF1A${uniqueSorted(workPackage.features.flatMap((feature) => feature.entryRefs)).map((ref) => `\`${ref}\``).join("\u3001") || "\u65E0"}
6860
+ - Surface refs\uFF1A${uniqueSorted(workPackage.features.flatMap((feature) => feature.surfaceRefs)).map((ref) => `\`${ref}\``).join("\u3001") || "\u65E0"}
6861
+ - Control refs\uFF1A${uniqueSorted(workPackage.features.flatMap((feature) => feature.controlRefs)).map((ref) => `\`${ref}\``).join("\u3001") || "\u65E0"}
6862
+ - Interaction refs\uFF1A${uniqueSorted(workPackage.features.flatMap((feature) => feature.interactionRefs)).map((ref) => `\`${ref}\``).join("\u3001") || "\u65E0"}
6863
+ - \u5F85\u88C1\u51B3 refs\uFF1A${uniqueSorted(workPackage.features.flatMap((feature) => feature.unresolvedRefs)).map((ref) => `\`${ref}\``).join("\u3001") || "\u65E0"}
6864
+
6865
+ \u5B9E\u73B0\u524D\u5FC5\u987B\u6CBF source-context \u7D22\u5F15\u8BFB\u53D6\u5F53\u524D\u9875\u9762\u6D89\u53CA\u7684 Android \u6E90\u7801\u4E0E\u5FC5\u8981\u8C03\u7528\u94FE\uFF1B\u672C\u8282\u7684 refs \u548C\u6458\u8981\u4E0D\u80FD\u66FF\u4EE3\u6E90\u7801\u9605\u8BFB\u3002
6866
+
6867
+ ## 5. \u9875\u9762\u5185\u5B9E\u65BD\u987A\u5E8F
6868
+
6869
+ ${internalSequence}
6870
+
6871
+ \u9875\u9762\u5185 Work Item \u987A\u5E8F\u662F\u51BB\u7ED3\u8BA1\u5212\u7684\u4E00\u90E8\u5206\u3002\u53EF\u4EE5\u5728\u4E00\u6B21\u6A21\u578B\u8C03\u7528\u4E2D\u8FDE\u7EED\u5B8C\u6210\uFF0C\u4F46\u4E0D\u5F97\u9057\u6F0F\u3001\u91CD\u6392\u6216\u628A\u5176\u4E2D\u4E00\u9879\u5355\u72EC\u58F0\u660E\u4E3A\u6574\u4E2A\u9875\u9762\u5B8C\u6210\u3002
6872
+
6873
+ ## 6. \u5199\u5165\u6240\u6709\u6743
6874
+
6875
+ ### \u5141\u8BB8\u4FEE\u6539
6876
+
6877
+ ${quoteList(workPackage.writeOwnership.ownedFiles, `\u5C1A\u65E0\u5DF2\u5B58\u5728\u6587\u4EF6\uFF1B\u4EC5\u53EF\u5728\u72EC\u5360\u8303\u56F4 ${workPackage.writeOwnership.exclusiveScopes.map((scope) => `\`${scope}\``).join("\u3001")} \u5185\u521B\u5EFA\u6216\u8BA4\u9886\u6587\u4EF6`)}
6878
+
6879
+ ### \u53EA\u8BFB\u4F9D\u8D56
6880
+
6881
+ ${quoteList(workPackage.writeOwnership.readOnlyFiles)}
6882
+
6883
+ ### \u7981\u6B62\u4FEE\u6539
6884
+
6885
+ ${quoteList(workPackage.writeOwnership.forbiddenGlobalFiles)}
6886
+
6887
+ ### \u72EC\u5360\u8303\u56F4
6888
+
6889
+ ${quoteList(workPackage.writeOwnership.exclusiveScopes)}
6890
+
6891
+ ## 7. \u8DE8\u9875\u9762\u4F9D\u8D56\u4E0E Interface
6206
6892
 
6207
- ## \u975E\u76EE\u6807
6893
+ ${dependencyText}
6208
6894
 
6209
- - \u4E0D\u6269\u5927\u5230\u5F53\u524D\u884C\u4E3A\u95ED\u5305\u4E4B\u5916\u7684\u72EC\u7ACB\u5165\u53E3\u3002
6210
- - \u4E0D\u4EE5\u62A5\u544A\u5B57\u6BB5\u3001\u6CE8\u91CA\u6216\u6869\u4EE3\u7801\u66FF\u4EE3\u771F\u5B9E HarmonyOS \u5B9E\u73B0\u3002
6211
- - \u4E0D\u4EE5\u7F16\u8BD1\u6210\u529F\u66FF\u4EE3\u8BBE\u5907\u7AEF\u53EF\u89C2\u5BDF\u7ED3\u679C\u9A8C\u8BC1\u3002
6895
+ \u53EA\u6709\u5168\u90E8\u524D\u7F6E Page Work Package \u5DF2\u53D6\u5F97\u65B0\u9C9C\u7684\u672C\u5730\u9A8C\u8BC1\u8BC1\u636E\uFF0C\u5F53\u524D\u5305\u624D\u53EF\u6267\u884C\u3002\u4E0D\u5F97\u901A\u8FC7\u4FEE\u6539\u4E0A\u6E38\u5B9E\u73B0\u7ED5\u8FC7 Interface\u3002
6896
+
6897
+ ## 8. UI \u64CD\u4F5C\u4E0E\u9A8C\u6536\u5E8F\u5217
6898
+
6899
+ ${acceptanceSequences}
6900
+
6901
+ \u901A\u7528\u9A8C\u8BC1\u8981\u6C42\uFF1A
6902
+
6903
+ ${workPackage.acceptance.verificationRequirements.map((value) => `- ${value}`).join("\n")}
6904
+
6905
+ \u9A8C\u6536\u5FC5\u987B\u8986\u76D6\u7684 Report refs\uFF1A${workPackage.acceptance.reportRefs.map((ref) => `\`${ref}\``).join("\u3001") || "\u65E0"}\u3002
6906
+
6907
+ ## 9. \u975E\u76EE\u6807\u4E0E\u5B9E\u73B0\u7EA6\u675F
6908
+
6909
+ - \u4E0D\u6269\u5927\u5230\u672C\u5305 Behavior IDs \u4E4B\u5916\u7684\u72EC\u7ACB\u4E1A\u52A1\u7ED3\u679C\u3002
6910
+ - \u4E0D\u4FEE\u6539\u53EA\u8BFB\u6216\u7981\u6B62\u6587\u4EF6\uFF0C\u4E0D\u91CD\u6784\u4E0A\u6E38 Module \u7684 Implementation\u3002
6911
+ - \u4E0D\u9884\u5148\u865A\u6784\u5C1A\u65E0\u8BC1\u636E\u7684 ArkTS \u6587\u4EF6\u540D\u3001\u72B6\u6001\u5B57\u6BB5\u6216\u5931\u8D25\u884C\u4E3A\u3002
6912
+ - \u4E0D\u4EE5\u62A5\u544A\u5B57\u6BB5\u3001\u6CE8\u91CA\u3001\u6869\u4EE3\u7801\u6216\u4EC5\u7F16\u8BD1\u6210\u529F\u66FF\u4EE3\u771F\u5B9E\u884C\u4E3A\u5B9E\u73B0\u3002
6913
+ - \u53D1\u73B0 Facts\u3001\u9875\u9762\u5F52\u5C5E\u6216 Android \u884C\u4E3A\u9519\u8BEF\u65F6\uFF0C\u7EC8\u6B62\u5F53\u524D execution epoch \u5E76\u8FD4\u56DE\u89C4\u5212\u9636\u6BB5\uFF0C\u4E0D\u5728\u5B9E\u73B0\u4E2D\u6539\u5199\u6D3E\u751F\u8BA1\u5212\u3002
6914
+
6915
+ ## 10. \u5B8C\u6210\u56DE\u62A5\u683C\u5F0F
6916
+
6917
+ - \u4FEE\u6539\u6587\u4EF6\u53CA\u5BF9\u5E94\u5199\u5165\u6240\u6709\u6743\u3002
6918
+ - F-001 \u81F3 F-${String(workPackage.features.length).padStart(3, "0")} \u6BCF\u9879\u7684\u5B9E\u73B0\u3001\u6D4B\u8BD5\u548C Report refs \u7ED3\u679C\u3002
6919
+ - \u6784\u5EFA\u3001\u5355\u5143\u6D4B\u8BD5\u3001\u9875\u9762/Journey \u6D4B\u8BD5\u547D\u4EE4\u4E0E\u7ED3\u679C\u3002
6920
+ - \u672A\u89E3\u51B3\u95EE\u9898\uFF1B\u6CA1\u6709\u5219\u660E\u786E\u5199\u201C\u65E0\u201D\u3002
6921
+
6922
+ ## \u5DE5\u4F5C\u91CF\u63D0\u793A
6923
+
6924
+ - Work Item \u6570\uFF1A${workPackage.estimate.workItemCount}
6925
+ - Behavior \u6570\uFF1A${workPackage.estimate.behaviorCount}
6926
+ - Fact \u6570\uFF1A${workPackage.estimate.factCount}
6927
+ - \u6E90\u7801\u6587\u4EF6\u6570\uFF1A${workPackage.estimate.sourceFileCount}
6928
+ - \u751F\u4EA7\u4EE3\u7801\u6587\u4EF6\u6570\uFF1A${workPackage.estimate.productionFileCount}
6929
+ - \u6D4B\u8BD5\u6587\u4EF6\u6570\uFF1A${workPackage.estimate.testFileCount}
6930
+ - Capability \u6570\uFF1A${workPackage.estimate.capabilityCount}
6931
+ - \u8C03\u7528\u94FE\u8282\u70B9\u6570\uFF1A${workPackage.estimate.traceNodeCount}
6932
+ - \u4F30\u7B97 Facts Token\uFF1A${workPackage.estimate.estimatedFactTokens}
6212
6933
  `;
6213
6934
  }
6214
6935
  function writeFile(file, content) {
6215
- fs26.mkdirSync(path33.dirname(file), { recursive: true });
6216
- fs26.writeFileSync(file, content, "utf8");
6936
+ fs27.mkdirSync(path33.dirname(file), { recursive: true });
6937
+ fs27.writeFileSync(file, content, "utf8");
6217
6938
  }
6218
- function writeTaskPlan(projectRoot, plan) {
6939
+ function writeTaskPlan(projectRoot, plan, facts) {
6219
6940
  const root = taskPlanRoot(projectRoot);
6220
6941
  const parent = path33.dirname(root);
6221
- fs26.mkdirSync(parent, { recursive: true });
6942
+ fs27.mkdirSync(parent, { recursive: true });
6222
6943
  const temporary = path33.join(parent, `tasks.${process.pid}.${Date.now()}.tmp`);
6223
6944
  const backup = path33.join(parent, `tasks.${process.pid}.backup`);
6224
- fs26.mkdirSync(path33.join(temporary, "briefs"), { recursive: true });
6225
- fs26.mkdirSync(path33.join(temporary, "acceptance"), { recursive: true });
6226
- const graphFile = path33.join(temporary, "graph.json");
6227
- writeFile(graphFile, prettyStableJson(plan));
6945
+ fs27.mkdirSync(temporary, { recursive: true });
6228
6946
  const taskPlanSha256 = sha2562(plan);
6229
- const descriptors = plan.tasks.map((task) => {
6947
+ const graphFile = path33.join(temporary, "execution-graph.json");
6948
+ writeFile(graphFile, prettyStableJson(plan));
6949
+ writeFile(path33.join(temporary, "behavior-plan.json"), prettyStableJson({
6950
+ behaviorPlanVersion: BEHAVIOR_PLAN_VERSION,
6951
+ planningModelVersion: plan.planningModelVersion,
6952
+ factsAdapterVersion: plan.factsAdapterVersion,
6953
+ plannerPolicyVersion: plan.plannerPolicyVersion,
6954
+ baseline: plan.baseline,
6955
+ entryResolution: plan.entryResolution,
6956
+ responsibilities: plan.behaviorResponsibilities,
6957
+ ownership: plan.behaviorOwnership,
6958
+ planningWarnings: plan.planningWarnings
6959
+ }));
6960
+ const resolutionTargets = plan.tasks.filter((task) => task.type === "resolution").map((task) => {
6230
6961
  const stem = taskArtifactStem(task.id);
6231
- const briefFile = `briefs/${stem}.md`;
6232
- const acceptanceFile = `acceptance/${stem}.json`;
6233
- writeFile(path33.join(temporary, briefFile), renderTaskBrief(task));
6962
+ const taskRoot = `resolution-tasks/${stem}`;
6963
+ const briefFile = `${taskRoot}/brief.md`;
6964
+ const contractFile = `${taskRoot}/contract.json`;
6965
+ const coreFactsFile = `${taskRoot}/core-facts.json`;
6966
+ const sourceContextFile = `${taskRoot}/source-context.json`;
6967
+ const acceptanceFile = `${taskRoot}/acceptance.json`;
6968
+ writeFile(path33.join(temporary, briefFile), renderResolutionTaskBrief(task, plan));
6969
+ writeFile(path33.join(temporary, contractFile), prettyStableJson({ executionPlanSha256: taskPlanSha256, baseline: plan.baseline, task: taskContractView(task) }));
6970
+ writeFile(path33.join(temporary, coreFactsFile), prettyStableJson(coreFactsForTask(task, facts)));
6971
+ writeFile(path33.join(temporary, sourceContextFile), prettyStableJson(sourceContextForTask(task)));
6234
6972
  writeFile(path33.join(temporary, acceptanceFile), prettyStableJson(task.acceptance));
6235
- return { id: task.id, type: task.type, title: task.title, briefFile, acceptanceFile };
6973
+ return { id: task.id, kind: "resolution-task", title: task.title, briefFile, contractFile, coreFactsFile, sourceContextFile, acceptanceFile };
6974
+ });
6975
+ const pageWorkPackageTargets = plan.pageWorkPackages.map((workPackage) => {
6976
+ const stem = workPackageArtifactStem(workPackage.id);
6977
+ const packageRoot = `page-work-packages/${stem}`;
6978
+ const briefFile = `page-work-packages/${stem}.md`;
6979
+ const contractFile = `${packageRoot}/contract.json`;
6980
+ const coreFactsFile = `${packageRoot}/core-facts.json`;
6981
+ const sourceContextFile = `${packageRoot}/source-context.json`;
6982
+ const acceptanceFile = `${packageRoot}/acceptance.json`;
6983
+ const contract = pageWorkPackageContractView(workPackage, plan, facts);
6984
+ writeFile(path33.join(temporary, briefFile), renderPageWorkPackageBrief(contract, plan, facts));
6985
+ writeFile(path33.join(temporary, contractFile), prettyStableJson({ executionPlanSha256: taskPlanSha256, baseline: plan.baseline, pageWorkPackage: contract }));
6986
+ writeFile(path33.join(temporary, coreFactsFile), prettyStableJson(coreFactsForPageWorkPackage(contract, facts)));
6987
+ writeFile(path33.join(temporary, sourceContextFile), prettyStableJson(sourceContextForPageWorkPackage(workPackage, plan)));
6988
+ writeFile(path33.join(temporary, acceptanceFile), prettyStableJson(contract.acceptance));
6989
+ return { id: workPackage.id, kind: "page-work-package", title: workPackage.title, workItemIds: workPackage.workItemIds, briefFile, contractFile, coreFactsFile, sourceContextFile, acceptanceFile };
6236
6990
  });
6991
+ const targets = plan.planningPhase === "resolution" ? resolutionTargets : pageWorkPackageTargets;
6237
6992
  const index = {
6238
6993
  taskPlanVersion: plan.taskPlanVersion,
6239
6994
  taskPlanSha256,
6240
6995
  baseline: plan.baseline,
6241
- graphFile: "graph.json",
6996
+ executionPlanVersion: plan.executionPlanVersion,
6997
+ planningPhase: plan.planningPhase,
6998
+ graphFile: "execution-graph.json",
6999
+ behaviorPlanFile: "behavior-plan.json",
6242
7000
  counts: {
6243
7001
  total: plan.tasks.length,
6244
7002
  resolution: plan.tasks.filter((task) => task.type === "resolution").length,
6245
7003
  bootstrap: plan.tasks.filter((task) => task.type === "bootstrap").length,
6246
- journey: plan.tasks.filter((task) => task.type === "journey").length
7004
+ journey: plan.tasks.filter((task) => task.type === "journey").length,
7005
+ workPackages: plan.pageWorkPackages.length,
7006
+ behaviors: plan.behaviorResponsibilities.length,
7007
+ executionTargets: targets.length
6247
7008
  },
6248
- tasks: descriptors
7009
+ targets,
7010
+ workItems: plan.planningPhase === "execution" ? plan.tasks.map((task) => ({ id: task.id, type: task.type, title: task.title, workPackageId: task.workPackageId, behaviorIds: task.behaviorIds })) : []
6249
7011
  };
6250
7012
  writeFile(path33.join(temporary, "index.json"), prettyStableJson(index));
6251
7013
  try {
6252
- if (fs26.existsSync(backup)) fs26.rmSync(backup, { recursive: true, force: true });
6253
- if (fs26.existsSync(root)) fs26.renameSync(root, backup);
6254
- fs26.renameSync(temporary, root);
6255
- if (fs26.existsSync(backup)) fs26.rmSync(backup, { recursive: true, force: true });
7014
+ if (fs27.existsSync(backup)) fs27.rmSync(backup, { recursive: true, force: true });
7015
+ if (fs27.existsSync(root)) fs27.renameSync(root, backup);
7016
+ fs27.renameSync(temporary, root);
7017
+ if (fs27.existsSync(backup)) fs27.rmSync(backup, { recursive: true, force: true });
6256
7018
  } catch (error) {
6257
- if (!fs26.existsSync(root) && fs26.existsSync(backup)) fs26.renameSync(backup, root);
6258
- if (fs26.existsSync(temporary)) fs26.rmSync(temporary, { recursive: true, force: true });
7019
+ if (!fs27.existsSync(root) && fs27.existsSync(backup)) fs27.renameSync(backup, root);
7020
+ if (fs27.existsSync(temporary)) fs27.rmSync(temporary, { recursive: true, force: true });
6259
7021
  throw error;
6260
7022
  }
6261
7023
  return {
6262
7024
  taskPlanSha256,
6263
- graph: path33.relative(path33.resolve(projectRoot), path33.join(root, "graph.json")).replaceAll("\\", "/"),
7025
+ graph: path33.relative(path33.resolve(projectRoot), path33.join(root, "execution-graph.json")).replaceAll("\\", "/"),
6264
7026
  index: path33.relative(path33.resolve(projectRoot), path33.join(root, "index.json")).replaceAll("\\", "/")
6265
7027
  };
6266
7028
  }
6267
7029
  function readTaskPlan(projectRoot) {
6268
- const file = path33.join(taskPlanRoot(projectRoot), "graph.json");
6269
- if (!fs26.existsSync(file)) return null;
6270
- return JSON.parse(fs26.readFileSync(file, "utf8"));
7030
+ const root = taskPlanRoot(projectRoot);
7031
+ const file = fs27.existsSync(path33.join(root, "execution-graph.json")) ? path33.join(root, "execution-graph.json") : path33.join(root, "graph.json");
7032
+ if (!fs27.existsSync(file)) return null;
7033
+ return JSON.parse(fs27.readFileSync(file, "utf8"));
6271
7034
  }
6272
7035
  function hasTaskPlan(projectRoot) {
6273
- return fs26.existsSync(path33.join(taskPlanRoot(projectRoot), "graph.json"));
7036
+ return fs27.existsSync(path33.join(taskPlanRoot(projectRoot), "execution-graph.json")) || fs27.existsSync(path33.join(taskPlanRoot(projectRoot), "graph.json"));
6274
7037
  }
6275
7038
 
6276
7039
  // src/planning/materialize.ts
6277
- function cursorFor(taskId, taskPlanSha256, offset) {
6278
- return Buffer.from(JSON.stringify({ taskId, taskPlanSha256, offset }), "utf8").toString("base64url");
7040
+ function cursorFor(targetId, taskPlanSha256, offset) {
7041
+ return Buffer.from(JSON.stringify({ targetId, taskPlanSha256, offset }), "utf8").toString("base64url");
6279
7042
  }
6280
- function cursorOffset(cursor, taskId, taskPlanSha256) {
7043
+ function cursorOffset(cursor, targetId, taskPlanSha256) {
6281
7044
  if (!cursor) return 0;
6282
7045
  try {
6283
7046
  const value = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
6284
- if (value.taskId !== taskId || value.taskPlanSha256 !== taskPlanSha256 || !Number.isInteger(value.offset) || value.offset < 0) throw new Error("mismatch");
7047
+ if (value.targetId !== targetId || value.taskPlanSha256 !== taskPlanSha256 || !Number.isInteger(value.offset) || value.offset < 0) throw new Error("mismatch");
6285
7048
  return value.offset;
6286
7049
  } catch {
6287
7050
  throw new Error("Invalid or stale task envelope cursor");
6288
7051
  }
6289
7052
  }
6290
- function codeRefIndex(value) {
6291
- const allowed = ["id", "file", "name", "symbol", "kind", "type", "line", "startLine", "endLine"];
6292
- return Object.fromEntries(allowed.filter((key) => value[key] !== void 0).map((key) => [key, value[key]]));
6293
- }
6294
- function materializeEnvelope(plan, facts, taskId, options = {}) {
6295
- const task = plan.tasks.find((item) => item.id === taskId);
6296
- if (!task) throw new Error(`Task not found: ${taskId}`);
7053
+ function materializeEnvelope(plan, facts, targetId, options = {}) {
6297
7054
  const taskPlanSha256 = sha2562(plan);
6298
- const refs = /* @__PURE__ */ new Set([...task.primaryFactRefs, ...task.supportingFactRefs]);
6299
- const coreFacts = [
6300
- ...facts.surfaces.filter((item) => refs.has(item.id)).map((value) => ({ kind: "surface", value })),
6301
- ...facts.controls.filter((item) => refs.has(item.id)).map((value) => ({ kind: "control", value })),
6302
- ...facts.entries.filter((item) => refs.has(item.id)).map((value) => ({
6303
- kind: "entry",
6304
- value: { ...value, trace: { ...value.trace, codeRefs: value.trace.codeRefs.map(codeRefIndex) } }
6305
- })),
6306
- ...facts.effects.filter((item) => refs.has(item.id)).map((value) => ({ kind: "effect", value })),
6307
- ...facts.issues.filter((item) => refs.has(item.id) || task.closure.unresolvedRefs.includes(item.id)).map((value) => ({ kind: "issue", value }))
6308
- ];
6309
- const envelopeTask = { ...task, closure: { ...task.closure, codeRefs: task.closure.codeRefs.map(codeRefIndex) } };
6310
- const sourceContext = uniqueBy([
6311
- ...task.closure.sourceEvidence.map((value) => ({ kind: "source-evidence", value })),
6312
- ...task.closure.codeRefs.map((value) => ({ kind: "code-ref", value: codeRefIndex(value) }))
6313
- ], stableJson);
6314
7055
  const limit = Math.max(1, Math.min(200, Math.floor(options.maxSourceContext ?? 25)));
6315
- const offset = cursorOffset(options.cursor, taskId, taskPlanSha256);
7056
+ const offset = cursorOffset(options.cursor, targetId, taskPlanSha256);
7057
+ const task = plan.tasks.find((item) => item.id === targetId);
7058
+ if (task) {
7059
+ if (plan.planningPhase !== "resolution" || task.type !== "resolution") throw new Error("Execution phase materializes a Page Work Package, not an internal Coding Work Item");
7060
+ const coreFacts2 = coreFactsForTask(task, facts);
7061
+ const envelopeTask = taskContractView(task);
7062
+ const sourceContext2 = sourceContextForTask(task);
7063
+ if (offset > sourceContext2.length) throw new Error("Task envelope cursor is past the available source context");
7064
+ const page2 = sourceContext2.slice(offset, offset + limit);
7065
+ const nextOffset2 = offset + page2.length;
7066
+ return {
7067
+ taskEnvelopeVersion: TASK_ENVELOPE_VERSION,
7068
+ taskPlanSha256,
7069
+ targetKind: "resolution-task",
7070
+ targetId,
7071
+ task: envelopeTask,
7072
+ pageWorkPackage: null,
7073
+ contract: { executionPlanSha256: taskPlanSha256, baseline: plan.baseline, task: envelopeTask },
7074
+ brief: renderResolutionTaskBrief(task, plan),
7075
+ coreFacts: coreFacts2,
7076
+ dependencyInterfaces: task.dependencyInterfaces,
7077
+ acceptance: task.acceptance,
7078
+ sourceContext: page2,
7079
+ continuation: {
7080
+ cursor: nextOffset2 < sourceContext2.length ? cursorFor(targetId, taskPlanSha256, nextOffset2) : null,
7081
+ remainingSourceContext: Math.max(0, sourceContext2.length - nextOffset2)
7082
+ }
7083
+ };
7084
+ }
7085
+ const workPackage = plan.pageWorkPackages.find((item) => item.id === targetId);
7086
+ if (!workPackage) throw new Error(`Execution target not found: ${targetId}`);
7087
+ if (plan.planningPhase !== "execution") throw new Error("Resolution phase materializes a Resolution Task, not a Page Work Package");
7088
+ const contract = pageWorkPackageContractView(workPackage, plan, facts);
7089
+ const coreFacts = coreFactsForPageWorkPackage(contract, facts);
7090
+ const sourceContext = sourceContextForPageWorkPackage(workPackage, plan);
6316
7091
  if (offset > sourceContext.length) throw new Error("Task envelope cursor is past the available source context");
6317
7092
  const page = sourceContext.slice(offset, offset + limit);
6318
7093
  const nextOffset = offset + page.length;
6319
7094
  return {
6320
7095
  taskEnvelopeVersion: TASK_ENVELOPE_VERSION,
6321
7096
  taskPlanSha256,
6322
- task: envelopeTask,
6323
- brief: renderTaskBrief(task),
7097
+ targetKind: "page-work-package",
7098
+ targetId,
7099
+ task: null,
7100
+ pageWorkPackage: contract,
7101
+ contract: { executionPlanSha256: taskPlanSha256, baseline: plan.baseline, pageWorkPackage: contract },
7102
+ brief: renderPageWorkPackageBrief(contract, plan, facts),
6324
7103
  coreFacts,
7104
+ dependencyInterfaces: contract.dependencyInterfaces,
7105
+ acceptance: contract.acceptance,
6325
7106
  sourceContext: page,
6326
7107
  continuation: {
6327
- cursor: nextOffset < sourceContext.length ? cursorFor(taskId, taskPlanSha256, nextOffset) : null,
7108
+ cursor: nextOffset < sourceContext.length ? cursorFor(targetId, taskPlanSha256, nextOffset) : null,
6328
7109
  remainingSourceContext: Math.max(0, sourceContext.length - nextOffset)
6329
7110
  }
6330
7111
  };
6331
7112
  }
6332
7113
 
6333
7114
  // src/planning/runtime.ts
6334
- import fs27 from "fs";
7115
+ import fs28 from "fs";
6335
7116
  import path34 from "path";
6336
7117
 
6337
7118
  // src/planning/validate.ts
6338
- function findCycle(plan) {
6339
- const dependencies = new Map(plan.tasks.map((task) => [task.id, task.dependsOnTaskIds]));
7119
+ function findDependencyCycle(dependencies) {
6340
7120
  const visiting = /* @__PURE__ */ new Set();
6341
7121
  const visited = /* @__PURE__ */ new Set();
6342
7122
  const stack = [];
@@ -6360,33 +7140,101 @@ function findCycle(plan) {
6360
7140
  }
6361
7141
  return null;
6362
7142
  }
7143
+ function findCycle(plan) {
7144
+ return findDependencyCycle(new Map(plan.tasks.map((task) => [task.id, task.dependsOnTaskIds])));
7145
+ }
7146
+ function hasDependencyPath(plan, from, to) {
7147
+ const byId = new Map(plan.tasks.map((task) => [task.id, task]));
7148
+ const queue = [...byId.get(from)?.dependsOnTaskIds ?? []];
7149
+ const seen = /* @__PURE__ */ new Set();
7150
+ while (queue.length) {
7151
+ const next = queue.shift();
7152
+ if (next === to) return true;
7153
+ if (seen.has(next)) continue;
7154
+ seen.add(next);
7155
+ queue.push(...byId.get(next)?.dependsOnTaskIds ?? []);
7156
+ }
7157
+ return false;
7158
+ }
6363
7159
  function validateTaskPlan(plan, facts, androidFingerprint) {
6364
7160
  const errors = [];
6365
7161
  const warnings = [];
6366
7162
  if (plan.taskPlanVersion !== TASK_PLAN_VERSION) errors.push(`taskPlanVersion must be ${TASK_PLAN_VERSION}`);
7163
+ if (plan.executionPlanVersion !== EXECUTION_PLAN_VERSION) errors.push(`executionPlanVersion must be ${EXECUTION_PLAN_VERSION}`);
6367
7164
  if (plan.planningModelVersion !== PLANNING_MODEL_VERSION) errors.push(`planningModelVersion must be ${PLANNING_MODEL_VERSION}`);
6368
7165
  if (plan.factsAdapterVersion !== FACTS_ADAPTER_VERSION) errors.push(`factsAdapterVersion must be ${FACTS_ADAPTER_VERSION}`);
6369
7166
  if (plan.plannerPolicyVersion !== PLANNER_POLICY_VERSION) errors.push(`plannerPolicyVersion must be ${PLANNER_POLICY_VERSION}`);
6370
7167
  const fresh = plan.baseline?.factsSha256 === facts.baseline.factsSha256 && plan.baseline?.planningInputSha256 === facts.baseline.planningInputSha256 && plan.baseline?.factsWireVersion === facts.baseline.wireVersion && plan.baseline?.androidFingerprint === androidFingerprint;
6371
7168
  if (!fresh) warnings.push("Task plan baseline does not match current Facts, planning inputs, or Android source fingerprint");
7169
+ const behaviorIds = /* @__PURE__ */ new Set();
7170
+ for (const behavior of plan.behaviorResponsibilities ?? []) {
7171
+ if (!/^behavior:[a-f0-9]{16}$/.test(behavior.id)) errors.push(`invalid behavior id ${behavior.id}`);
7172
+ if (behaviorIds.has(behavior.id)) errors.push(`duplicate behavior id ${behavior.id}`);
7173
+ else behaviorIds.add(behavior.id);
7174
+ if (!behavior.primaryFactRefs?.length) errors.push(`${behavior.id}: primaryFactRefs must not be empty`);
7175
+ if (!behavior.acceptance?.observableOutcome?.trim()) errors.push(`${behavior.id}: observableOutcome is required`);
7176
+ for (const ref of behavior.supportingFactRefs ?? []) if (behavior.primaryFactRefs.includes(ref)) errors.push(`${behavior.id}: ${ref} cannot be both primary and supporting`);
7177
+ }
7178
+ for (const behavior of plan.behaviorResponsibilities ?? []) for (const dependency of behavior.dependsOnBehaviorIds ?? []) {
7179
+ if (!behaviorIds.has(dependency)) errors.push(`${behavior.id}: unknown behavior dependency ${dependency}`);
7180
+ if (dependency === behavior.id) errors.push(`${behavior.id}: behavior cannot depend on itself`);
7181
+ }
7182
+ const expectedPlanningPhase = (plan.behaviorResponsibilities ?? []).some((behavior) => behavior.type === "resolution") ? "resolution" : "execution";
7183
+ if (plan.planningPhase !== expectedPlanningPhase) errors.push(`planningPhase must be ${expectedPlanningPhase}`);
7184
+ const activeBehaviors = (plan.behaviorResponsibilities ?? []).filter((behavior) => expectedPlanningPhase === "execution" || behavior.type === "resolution");
7185
+ const activeBehaviorIds = new Set(activeBehaviors.map((behavior) => behavior.id));
7186
+ if (expectedPlanningPhase === "resolution" && (plan.tasks ?? []).some((task) => task.type !== "resolution")) {
7187
+ errors.push("resolution phase must materialize only Resolution Tasks; Coding Work Items are assembled after Resolution is cleared");
7188
+ }
7189
+ if (expectedPlanningPhase === "execution" && (plan.tasks ?? []).some((task) => task.type === "resolution")) {
7190
+ errors.push("execution phase must not contain Resolution Tasks");
7191
+ }
7192
+ if (expectedPlanningPhase === "resolution" && (plan.pageWorkPackages ?? []).length > 0) {
7193
+ errors.push("resolution phase must not materialize Coding Work Packages");
7194
+ }
7195
+ const behaviorOwners = /* @__PURE__ */ new Map();
7196
+ const behaviorSupporters = /* @__PURE__ */ new Map();
7197
+ for (const behavior of plan.behaviorResponsibilities ?? []) for (const ref of behavior.primaryFactRefs ?? []) behaviorOwners.set(ref, [...behaviorOwners.get(ref) ?? [], behavior.id]);
7198
+ for (const behavior of plan.behaviorResponsibilities ?? []) for (const ref of behavior.supportingFactRefs ?? []) behaviorSupporters.set(ref, [...behaviorSupporters.get(ref) ?? [], behavior.id]);
6372
7199
  const taskIds = /* @__PURE__ */ new Set();
6373
7200
  for (const task of plan.tasks ?? []) {
6374
7201
  if (!/^task:[a-f0-9]{16}$/.test(task.id)) errors.push(`invalid task id ${task.id}`);
6375
7202
  if (taskIds.has(task.id)) errors.push(`duplicate task id ${task.id}`);
6376
7203
  else taskIds.add(task.id);
6377
7204
  if (!task.title?.trim()) errors.push(`${task.id}: title is required`);
7205
+ if (expectedPlanningPhase === "resolution" && task.workPackageId !== null) errors.push(`${task.id}: Resolution Task must not belong to a Work Package`);
7206
+ if (expectedPlanningPhase === "execution" && !task.workPackageId?.trim()) errors.push(`${task.id}: Coding Work Item requires workPackageId`);
7207
+ if (!task.primaryModule?.trim()) errors.push(`${task.id}: primaryModule is required`);
7208
+ if (!task.behaviorIds?.length) errors.push(`${task.id}: behaviorIds must not be empty`);
7209
+ if (expectedPlanningPhase === "resolution" && task.behaviorIds?.length !== 1) errors.push(`${task.id}: Resolution Task must project exactly one Resolution Behavior`);
7210
+ for (const behaviorId of task.behaviorIds ?? []) if (!behaviorIds.has(behaviorId)) errors.push(`${task.id}: unknown behavior ${behaviorId}`);
6378
7211
  if (!task.primaryFactRefs?.length) errors.push(`${task.id}: primaryFactRefs must not be empty`);
6379
7212
  if (!task.acceptance?.observableOutcome?.trim()) errors.push(`${task.id}: observableOutcome is required`);
6380
7213
  if (!task.acceptance?.verificationRequirements?.length) errors.push(`${task.id}: verificationRequirements must not be empty`);
6381
- const overBudget = task.estimate.factCount > DEFAULT_CLOSURE_BUDGET.maxFactCount || task.estimate.sourceFileCount > DEFAULT_CLOSURE_BUDGET.maxSourceFileCount || task.estimate.traceNodeCount > DEFAULT_CLOSURE_BUDGET.maxTraceNodeCount || task.estimate.estimatedFactTokens > DEFAULT_CLOSURE_BUDGET.maxEstimatedFactTokens;
6382
- const sourceEntryCount = new Set(task.closure.interactionRefs.map((ref) => facts.effects.find((effect) => effect.id === ref)?.sourceEntryId).filter(Boolean)).size;
6383
- if (overBudget && sourceEntryCount > 1) errors.push(`${task.id}: merged closure exceeds the planning budget`);
6384
- else if (overBudget) warnings.push(`${task.id}: single-entry closure exceeds the soft budget and requires paged source context`);
7214
+ const overBudget = task.estimate.behaviorCount > DEFAULT_EXECUTION_BUDGET.maxBehaviorCount || task.estimate.observableOutcomeCount > DEFAULT_EXECUTION_BUDGET.maxObservableOutcomeCount || task.estimate.productionFileCount > DEFAULT_EXECUTION_BUDGET.maxProductionFileCount || task.estimate.testFileCount > DEFAULT_EXECUTION_BUDGET.maxTestFileCount || task.estimate.capabilityCount > DEFAULT_EXECUTION_BUDGET.maxCapabilityCount || task.estimate.factCount > DEFAULT_EXECUTION_BUDGET.maxFactCount || task.estimate.sourceFileCount > DEFAULT_EXECUTION_BUDGET.maxSourceFileCount || task.estimate.traceNodeCount > DEFAULT_EXECUTION_BUDGET.maxTraceNodeCount || task.estimate.estimatedFactTokens > DEFAULT_EXECUTION_BUDGET.maxEstimatedFactTokens;
7215
+ if (overBudget && (task.behaviorIds?.length ?? 0) > 1) errors.push(`${task.id}: merged Coding Work Item exceeds the execution budget`);
7216
+ else if (overBudget) warnings.push(`${task.id}: single-Behavior ${task.type === "resolution" ? "Resolution Task" : "Coding Work Item"} exceeds a soft execution budget; responsibility is preserved and source context must be paged`);
7217
+ const owned = task.writeOwnership?.ownedFiles ?? [];
7218
+ const readOnly = task.writeOwnership?.readOnlyFiles ?? [];
7219
+ const forbidden = task.writeOwnership?.forbiddenGlobalFiles ?? [];
7220
+ for (const file of owned) if (readOnly.includes(file)) errors.push(`${task.id}: ${file} cannot be both owned and read-only`);
7221
+ for (const file of owned) if (forbidden.includes(file)) errors.push(`${task.id}: ${file} cannot be both owned and forbidden`);
7222
+ if (!(task.writeOwnership?.exclusiveScopes?.length || owned.length)) errors.push(`${task.id}: write ownership requires ownedFiles or an exclusive scope`);
7223
+ if (task.readyGate?.writeOwnershipResolved !== Boolean(task.writeOwnership?.exclusiveScopes?.length || owned.length)) errors.push(`${task.id}: writeOwnershipResolved gate disagrees with the contract`);
7224
+ if (task.readyGate?.semanticsResolved !== (task.type === "resolution" || task.closure.unresolvedRefs.length === 0)) errors.push(`${task.id}: semanticsResolved gate disagrees with unresolved refs`);
7225
+ if (task.readyGate?.contextWithinBudget !== (!overBudget || (task.behaviorIds?.length ?? 0) === 1)) errors.push(`${task.id}: contextWithinBudget gate disagrees with execution estimate`);
6385
7226
  }
6386
7227
  for (const task of plan.tasks ?? []) for (const dependency of task.dependsOnTaskIds ?? []) {
6387
7228
  if (!taskIds.has(dependency)) errors.push(`${task.id}: unknown dependency ${dependency}`);
6388
7229
  if (dependency === task.id) errors.push(`${task.id}: task cannot depend on itself`);
6389
7230
  }
7231
+ for (const task of plan.tasks ?? []) {
7232
+ const interfaceProviders = (task.dependencyInterfaces ?? []).map((item) => item.providerTaskId).filter((id) => Boolean(id)).sort();
7233
+ if (JSON.stringify(interfaceProviders) !== JSON.stringify([...task.dependsOnTaskIds ?? []].sort())) errors.push(`${task.id}: dependencyInterfaces do not match dependsOnTaskIds`);
7234
+ for (const dependencyInterface of task.dependencyInterfaces ?? []) {
7235
+ if (dependencyInterface.providerTaskId && !taskIds.has(dependencyInterface.providerTaskId)) errors.push(`${task.id}: dependency Interface references unknown provider ${dependencyInterface.providerTaskId}`);
7236
+ }
7237
+ }
6390
7238
  const cycle = findCycle(plan);
6391
7239
  if (cycle) errors.push(`task dependency cycle: ${cycle.join(" -> ")}`);
6392
7240
  const owners = /* @__PURE__ */ new Map();
@@ -6398,14 +7246,20 @@ function validateTaskPlan(plan, facts, androidFingerprint) {
6398
7246
  supporters.set(ref, [...supporters.get(ref) ?? [], task.id]);
6399
7247
  }
6400
7248
  }
6401
- const orphanFactRefs = facts.requiredFactRefs.filter((ref) => (owners.get(ref) ?? []).length === 0);
6402
- for (const ref of orphanFactRefs) errors.push(`required fact has no primary owner: ${ref}`);
7249
+ const activePrimaryFactRefs = uniqueSorted(activeBehaviors.flatMap((behavior) => behavior.primaryFactRefs));
7250
+ const behaviorOrphanFactRefs = facts.requiredFactRefs.filter((ref) => (behaviorOwners.get(ref) ?? []).length === 0);
7251
+ const activeTaskOrphanFactRefs = activePrimaryFactRefs.filter((ref) => (owners.get(ref) ?? []).length === 0);
7252
+ const orphanFactRefs = uniqueSorted([
7253
+ ...behaviorOrphanFactRefs,
7254
+ ...activeTaskOrphanFactRefs.filter((ref) => facts.requiredFactRefs.includes(ref))
7255
+ ]);
7256
+ for (const ref of activeTaskOrphanFactRefs) errors.push(`active Behavior fact has no primary Task owner: ${ref}`);
6403
7257
  for (const [ref, ownerIds] of owners) if (ownerIds.length > 1) errors.push(`fact has multiple primary owners: ${ref} -> ${ownerIds.join(", ")}`);
6404
7258
  for (const [ref, ownerId] of Object.entries(plan.ownership?.primaryOwnerByFactRef ?? {})) {
6405
7259
  if (!taskIds.has(ownerId)) errors.push(`ownership ${ref} references unknown task ${ownerId}`);
6406
7260
  if (!(owners.get(ref) ?? []).includes(ownerId)) errors.push(`ownership map disagrees with task primaryFactRefs for ${ref}`);
6407
7261
  }
6408
- for (const ref of facts.requiredFactRefs) if (plan.ownership?.primaryOwnerByFactRef?.[ref] !== owners.get(ref)?.[0]) errors.push(`ownership map is missing or incorrect for ${ref}`);
7262
+ for (const ref of activePrimaryFactRefs) if (plan.ownership?.primaryOwnerByFactRef?.[ref] !== owners.get(ref)?.[0]) errors.push(`active Task ownership map is missing or incorrect for ${ref}`);
6409
7263
  for (const [ref, taskIdsForRef] of Object.entries(plan.ownership?.supportingTaskIdsByFactRef ?? {})) {
6410
7264
  const expected = [...supporters.get(ref) ?? []].sort();
6411
7265
  const actual = [...taskIdsForRef ?? []].sort();
@@ -6414,6 +7268,125 @@ function validateTaskPlan(plan, facts, androidFingerprint) {
6414
7268
  for (const [ref, expectedIds] of supporters) {
6415
7269
  if (!(ref in (plan.ownership?.supportingTaskIdsByFactRef ?? {}))) errors.push(`supporting ownership map is missing for ${ref}: ${[...expectedIds].sort().join(", ")}`);
6416
7270
  }
7271
+ const behaviorMembership = /* @__PURE__ */ new Map();
7272
+ for (const task of plan.tasks ?? []) for (const behaviorId of task.behaviorIds ?? []) behaviorMembership.set(behaviorId, [...behaviorMembership.get(behaviorId) ?? [], task.id]);
7273
+ for (const behavior of plan.behaviorResponsibilities ?? []) {
7274
+ const ownersForBehavior = behaviorMembership.get(behavior.id) ?? [];
7275
+ const expectedMembership = activeBehaviorIds.has(behavior.id) ? 1 : 0;
7276
+ if (ownersForBehavior.length !== expectedMembership) errors.push(`${behavior.id}: must belong to ${expectedMembership} ${expectedPlanningPhase === "resolution" ? "Resolution Task" : "Coding Work Item"}, found ${ownersForBehavior.length}`);
7277
+ const task = plan.tasks.find((item) => item.id === ownersForBehavior[0]);
7278
+ if (!task) continue;
7279
+ for (const ref of behavior.primaryFactRefs) if (!task.primaryFactRefs.includes(ref)) errors.push(`${task.id}: missing primary fact ${ref} from ${behavior.id}`);
7280
+ for (const ref of behavior.closure.entryRefs) if (!task.closure.entryRefs.includes(ref)) errors.push(`${task.id}: missing entry ${ref} from ${behavior.id}`);
7281
+ for (const ref of behavior.closure.interactionRefs) if (!task.closure.interactionRefs.includes(ref)) errors.push(`${task.id}: missing interaction ${ref} from ${behavior.id}`);
7282
+ }
7283
+ const behaviorById = new Map((plan.behaviorResponsibilities ?? []).map((behavior) => [behavior.id, behavior]));
7284
+ for (const task of plan.tasks ?? []) {
7285
+ const members = (task.behaviorIds ?? []).map((id) => behaviorById.get(id)).filter((item) => Boolean(item));
7286
+ const expectedPrimary = uniqueSorted(members.flatMap((item) => item.primaryFactRefs));
7287
+ const expectedSupporting = uniqueSorted(members.flatMap((item) => item.supportingFactRefs).filter((ref) => !expectedPrimary.includes(ref)));
7288
+ if (JSON.stringify(expectedPrimary) !== JSON.stringify([...task.primaryFactRefs ?? []].sort())) errors.push(`${task.id}: primaryFactRefs disagree with member Behaviors`);
7289
+ if (JSON.stringify(expectedSupporting) !== JSON.stringify([...task.supportingFactRefs ?? []].sort())) errors.push(`${task.id}: supportingFactRefs disagree with member Behaviors`);
7290
+ for (const key of ["entryRefs", "surfaceRefs", "controlRefs", "interactionRefs", "unresolvedRefs", "warningRefs"]) {
7291
+ const expected = uniqueSorted(members.flatMap((item) => item.closure[key]));
7292
+ if (JSON.stringify(expected) !== JSON.stringify([...task.closure?.[key] ?? []].sort())) errors.push(`${task.id}: closure.${key} disagrees with member Behaviors`);
7293
+ }
7294
+ const expectedOutcomes = uniqueSorted(members.map((item) => item.acceptance.observableOutcome));
7295
+ const expectedRequired = uniqueSorted(members.flatMap((item) => item.acceptance.requiredFactRefs));
7296
+ const expectedReports = uniqueSorted(members.flatMap((item) => item.acceptance.reportRefs));
7297
+ if (JSON.stringify(expectedOutcomes) !== JSON.stringify([...task.acceptance?.observableOutcomes ?? []].sort())) errors.push(`${task.id}: observableOutcomes disagree with member Behaviors`);
7298
+ if (JSON.stringify(expectedRequired) !== JSON.stringify([...task.acceptance?.requiredFactRefs ?? []].sort())) errors.push(`${task.id}: requiredFactRefs disagree with member Behaviors`);
7299
+ if (JSON.stringify(expectedReports) !== JSON.stringify([...task.acceptance?.reportRefs ?? []].sort())) errors.push(`${task.id}: reportRefs disagree with member Behaviors`);
7300
+ if (members.some((member) => member.type !== task.type)) errors.push(`${task.id}: task type disagrees with a member Behavior`);
7301
+ }
7302
+ for (const ref of facts.requiredFactRefs) {
7303
+ const expectedBehavior = behaviorOwners.get(ref)?.[0];
7304
+ if (!expectedBehavior || behaviorOwners.get(ref)?.length !== 1) errors.push(`required fact must have exactly one Behavior owner: ${ref}`);
7305
+ if (plan.behaviorOwnership?.primaryOwnerByFactRef?.[ref] !== expectedBehavior) errors.push(`behavior ownership map is missing or incorrect for ${ref}`);
7306
+ }
7307
+ for (const [ref, behaviorId] of Object.entries(plan.behaviorOwnership?.primaryOwnerByFactRef ?? {})) {
7308
+ if (!behaviorIds.has(behaviorId)) errors.push(`behavior ownership ${ref} references unknown Behavior ${behaviorId}`);
7309
+ if (!(behaviorOwners.get(ref) ?? []).includes(behaviorId)) errors.push(`behavior ownership map disagrees with Behavior primaryFactRefs for ${ref}`);
7310
+ }
7311
+ for (const [ref, expectedIds] of behaviorSupporters) {
7312
+ const actual = [...plan.behaviorOwnership?.supportingBehaviorIdsByFactRef?.[ref] ?? []].sort();
7313
+ if (JSON.stringify(actual) !== JSON.stringify([...expectedIds].sort())) errors.push(`behavior supporting ownership map is missing or incorrect for ${ref}`);
7314
+ }
7315
+ for (const [ref, actualIds] of Object.entries(plan.behaviorOwnership?.supportingBehaviorIdsByFactRef ?? {})) {
7316
+ const expected = [...behaviorSupporters.get(ref) ?? []].sort();
7317
+ if (JSON.stringify([...actualIds].sort()) !== JSON.stringify(expected)) errors.push(`behavior supporting ownership map disagrees with Behaviors for ${ref}`);
7318
+ }
7319
+ const packageIds = /* @__PURE__ */ new Set();
7320
+ const knownPackageIds = new Set((plan.pageWorkPackages ?? []).map((workPackage) => workPackage.id));
7321
+ const packageMembership = /* @__PURE__ */ new Map();
7322
+ const canonicalBySurface = canonicalPageRefs(facts);
7323
+ for (const workPackage of plan.pageWorkPackages ?? []) {
7324
+ if (!/^package:[a-f0-9]{16}$/.test(workPackage.id)) errors.push(`invalid work package id ${workPackage.id}`);
7325
+ if (packageIds.has(workPackage.id)) errors.push(`duplicate work package id ${workPackage.id}`);
7326
+ else packageIds.add(workPackage.id);
7327
+ for (const dependency of workPackage.dependsOnPackageIds ?? []) {
7328
+ if (!knownPackageIds.has(dependency)) errors.push(`${workPackage.id}: unknown Page Work Package dependency ${dependency}`);
7329
+ if (dependency === workPackage.id) errors.push(`${workPackage.id}: Page Work Package cannot depend on itself`);
7330
+ }
7331
+ for (const taskId2 of workPackage.workItemIds ?? []) {
7332
+ if (!taskIds.has(taskId2)) errors.push(`${workPackage.id}: unknown Coding Work Item ${taskId2}`);
7333
+ packageMembership.set(taskId2, [...packageMembership.get(taskId2) ?? [], workPackage.id]);
7334
+ }
7335
+ if ((workPackage.workItemIds ?? []).some((taskId2) => plan.tasks.find((task) => task.id === taskId2)?.type === "resolution")) errors.push(`${workPackage.id}: Resolution Tasks must not enter a Coding Work Package`);
7336
+ if (workPackage.canonicalPageRef !== null && !facts.surfaces.some((surface) => surface.id === workPackage.canonicalPageRef)) errors.push(`${workPackage.id}: unknown canonical page ${workPackage.canonicalPageRef}`);
7337
+ for (const surfaceRef of workPackage.surfaceRefs ?? []) {
7338
+ if (!facts.surfaces.some((surface) => surface.id === surfaceRef)) errors.push(`${workPackage.id}: unknown surface ${surfaceRef}`);
7339
+ if ((canonicalBySurface[surfaceRef] ?? surfaceRef) !== workPackage.canonicalPageRef) errors.push(`${workPackage.id}: ${surfaceRef} does not belong to canonical page ${workPackage.canonicalPageRef}`);
7340
+ }
7341
+ if (workPackage.serialLane) for (let index = 1; index < workPackage.workItemIds.length; index++) {
7342
+ if (!hasDependencyPath(plan, workPackage.workItemIds[index], workPackage.workItemIds[index - 1])) errors.push(`${workPackage.id}: serial lane is missing dependency ${workPackage.workItemIds[index]} -> ${workPackage.workItemIds[index - 1]}`);
7343
+ }
7344
+ const expectedScopes = [...new Set(plan.tasks.filter((task) => task.workPackageId === workPackage.id).flatMap((task) => task.writeOwnership?.exclusiveScopes ?? []))].sort();
7345
+ if (JSON.stringify([...workPackage.exclusiveScopes ?? []].sort()) !== JSON.stringify(expectedScopes)) errors.push(`${workPackage.id}: exclusiveScopes disagree with member Work Items`);
7346
+ const expectedDependencies = uniqueSorted((workPackage.workItemIds ?? []).flatMap((taskId2) => plan.tasks.find((task) => task.id === taskId2)?.dependsOnTaskIds ?? []).map((taskId2) => plan.tasks.find((task) => task.id === taskId2)?.workPackageId).filter((packageId) => Boolean(packageId && packageId !== workPackage.id)));
7347
+ if (JSON.stringify([...workPackage.dependsOnPackageIds ?? []].sort()) !== JSON.stringify(expectedDependencies)) errors.push(`${workPackage.id}: dependsOnPackageIds disagree with cross-package Work Item dependencies`);
7348
+ }
7349
+ const packageCycle = findDependencyCycle(new Map((plan.pageWorkPackages ?? []).map((workPackage) => [workPackage.id, workPackage.dependsOnPackageIds ?? []])));
7350
+ if (packageCycle) errors.push(`Page Work Package dependency cycle: ${packageCycle.join(" -> ")}`);
7351
+ for (const task of plan.tasks ?? []) {
7352
+ const memberships = packageMembership.get(task.id) ?? [];
7353
+ if (task.type === "resolution") {
7354
+ if (memberships.length > 0 || task.workPackageId !== null) errors.push(`${task.id}: Resolution Task must not have Work Package membership`);
7355
+ continue;
7356
+ }
7357
+ if (memberships.length !== 1 || memberships[0] !== task.workPackageId) errors.push(`${task.id}: work package membership is missing or inconsistent`);
7358
+ const workPackage = plan.pageWorkPackages?.find((item) => item.id === task.workPackageId);
7359
+ if (workPackage && workPackage.canonicalPageRef !== task.canonicalPageRef) errors.push(`${task.id}: canonicalPageRef disagrees with its Work Package`);
7360
+ }
7361
+ const conflictEdges = /* @__PURE__ */ new Map();
7362
+ for (const edge of plan.fileConflictEdges ?? []) {
7363
+ if (!taskIds.has(edge.leftTaskId) || !taskIds.has(edge.rightTaskId)) errors.push(`file conflict references an unknown task: ${edge.leftTaskId}, ${edge.rightTaskId}`);
7364
+ if (edge.leftTaskId === edge.rightTaskId) errors.push(`${edge.leftTaskId}: file conflict cannot reference itself`);
7365
+ if (!edge.reasons?.length) errors.push(`${edge.leftTaskId}/${edge.rightTaskId}: file conflict requires a reason`);
7366
+ const key = [edge.leftTaskId, edge.rightTaskId].sort().join(":");
7367
+ if (conflictEdges.has(key)) errors.push(`duplicate file conflict edge: ${key}`);
7368
+ else conflictEdges.set(key, edge);
7369
+ }
7370
+ for (let leftIndex = 0; leftIndex < (plan.tasks ?? []).length; leftIndex++) for (let rightIndex = leftIndex + 1; rightIndex < plan.tasks.length; rightIndex++) {
7371
+ const left = plan.tasks[leftIndex];
7372
+ const right = plan.tasks[rightIndex];
7373
+ const leftOwned = left.writeOwnership?.ownedFiles ?? [];
7374
+ const rightOwned = right.writeOwnership?.ownedFiles ?? [];
7375
+ const leftScopes = left.writeOwnership?.exclusiveScopes ?? [];
7376
+ const rightScopes = right.writeOwnership?.exclusiveScopes ?? [];
7377
+ const overlappingFiles = leftOwned.filter((file) => rightOwned.includes(file));
7378
+ const overlappingScopes = leftScopes.filter((scope) => rightScopes.includes(scope));
7379
+ const hasConflict = overlappingFiles.length > 0 || overlappingScopes.length > 0;
7380
+ const edge = conflictEdges.get([left.id, right.id].sort().join(":"));
7381
+ if (hasConflict && !edge) errors.push(`${left.id} and ${right.id}: overlapping write ownership requires a conflict edge`);
7382
+ if (!hasConflict && edge) errors.push(`${left.id} and ${right.id}: file conflict edge has no overlapping write ownership`);
7383
+ if (edge) {
7384
+ const expectedReasons = [...overlappingFiles.map((file) => `owned-file:${file}`), ...overlappingScopes.map((scope) => `exclusive-scope:${scope}`)].sort();
7385
+ if (JSON.stringify([...edge.reasons].sort()) !== JSON.stringify(expectedReasons)) errors.push(`${left.id} and ${right.id}: file conflict reasons disagree with write ownership`);
7386
+ }
7387
+ if (hasConflict && !(hasDependencyPath(plan, left.id, right.id) || hasDependencyPath(plan, right.id, left.id))) errors.push(`${left.id} and ${right.id}: file conflict is not serialized`);
7388
+ if ((left.conflictsWithTaskIds ?? []).includes(right.id) !== hasConflict || (right.conflictsWithTaskIds ?? []).includes(left.id) !== hasConflict) errors.push(`${left.id} and ${right.id}: conflictsWithTaskIds disagrees with write ownership`);
7389
+ }
6417
7390
  const knownRefs = /* @__PURE__ */ new Set([...facts.requiredFactRefs, ...facts.issues.map((issue) => issue.id)]);
6418
7391
  const entryRefs = new Set(facts.entries.map((entry) => entry.id));
6419
7392
  const surfaceRefs = new Set(facts.surfaces.map((surface) => surface.id));
@@ -6447,16 +7420,16 @@ function validateTaskPlan(plan, facts, androidFingerprint) {
6447
7420
  }
6448
7421
 
6449
7422
  // src/planning/runtime.ts
6450
- function readJson6(file) {
7423
+ function readJson7(file) {
6451
7424
  try {
6452
- return JSON.parse(fs27.readFileSync(file, "utf8"));
7425
+ return JSON.parse(fs28.readFileSync(file, "utf8"));
6453
7426
  } catch {
6454
7427
  return null;
6455
7428
  }
6456
7429
  }
6457
7430
  function reportItems(projectRoot) {
6458
7431
  const root = path34.join(migrationDirectory(projectRoot), "report");
6459
- const index = readJson6(path34.join(root, "index.json"));
7432
+ const index = readJson7(path34.join(root, "index.json"));
6460
7433
  const out = /* @__PURE__ */ new Map();
6461
7434
  if (!index) return out;
6462
7435
  const files = [
@@ -6466,7 +7439,7 @@ function reportItems(projectRoot) {
6466
7439
  index.discoveriesFile
6467
7440
  ].filter((value) => typeof value === "string");
6468
7441
  for (const relative2 of files) {
6469
- const document = readJson6(path34.join(root, relative2));
7442
+ const document = readJson7(path34.join(root, relative2));
6470
7443
  if (!document) continue;
6471
7444
  const values = [document.page, ...document.controls ?? [], ...document.entries ?? [], ...document.interactions ?? [], ...document.items ?? []].filter(Boolean);
6472
7445
  for (const item of values) if (typeof item.ref === "string") out.set(item.ref, { ref: item.ref, status: item.status ?? "pending", testEvidence: Array.isArray(item.testEvidence) ? item.testEvidence : [] });
@@ -6477,7 +7450,7 @@ function managedReceipt(projectRoot, reference) {
6477
7450
  const managedRoot = path34.resolve(migrationDirectory(projectRoot), "evidence", "managed");
6478
7451
  const absolute = reference.replaceAll("\\", "/").startsWith(".migration/") ? path34.resolve(migrationWorkspaceRoot(projectRoot), reference) : path34.resolve(projectRoot, reference);
6479
7452
  if (absolute !== managedRoot && !absolute.startsWith(`${managedRoot}${path34.sep}`)) return null;
6480
- return readJson6(absolute);
7453
+ return readJson7(absolute);
6481
7454
  }
6482
7455
  function freshTaskEvidence(projectRoot, reference, factsSha256, harmonyFingerprint) {
6483
7456
  const receipt = managedReceipt(projectRoot, reference);
@@ -6503,7 +7476,24 @@ function stateReasons(task, state, dependencyStates) {
6503
7476
  })(),
6504
7477
  task.estimate.downstreamUnlockCount > 0 ? `\u5B8C\u6210\u540E\u53EF\u89E3\u9664 ${task.estimate.downstreamUnlockCount} \u4E2A\u4E0B\u6E38\u4EFB\u52A1` : null
6505
7478
  ]);
6506
- if (state === "blocked") return dependencyStates.length ? ["\u4F9D\u8D56\u4EFB\u52A1\u5C1A\u672A\u53D6\u5F97\u65B0\u9C9C\u7684\u5B9A\u5411\u9A8C\u8BC1\u8BC1\u636E"] : ["\u4EFB\u52A1\u5BF9\u5E94\u7684\u62A5\u544A\u9879\u6216\u8BA1\u5212\u6821\u9A8C\u4ECD\u5904\u4E8E\u963B\u585E\u72B6\u6001"];
7479
+ if (state === "blocked") {
7480
+ const gate = task.readyGate ?? {
7481
+ semanticsResolved: false,
7482
+ dependencyInterfacesStable: false,
7483
+ writeOwnershipResolved: false,
7484
+ contextWithinBudget: false,
7485
+ acceptanceExecutable: false
7486
+ };
7487
+ return uniqueSorted([
7488
+ dependencyStates.length ? "\u4F9D\u8D56\u4EFB\u52A1\u5C1A\u672A\u53D6\u5F97\u65B0\u9C9C\u7684\u5B9A\u5411\u9A8C\u8BC1\u8BC1\u636E" : null,
7489
+ !gate.semanticsResolved ? "\u4ECD\u6709\u672A\u88C1\u51B3\u8BED\u4E49\u95EE\u9898" : null,
7490
+ !gate.dependencyInterfacesStable ? "\u4E0A\u6E38 Module Interface \u5C1A\u672A\u7A33\u5B9A" : null,
7491
+ !gate.writeOwnershipResolved ? "\u5199\u5165\u6587\u4EF6\u6216\u72EC\u5360\u8303\u56F4\u5C1A\u672A\u5206\u914D" : null,
7492
+ !gate.contextWithinBudget ? "\u5185\u90E8 Work Item \u95ED\u5305\u8D85\u51FA\u5F53\u524D\u6267\u884C\u9884\u7B97" : null,
7493
+ !gate.acceptanceExecutable ? "\u9A8C\u6536\u6761\u4EF6\u5F53\u524D\u4E0D\u53EF\u6267\u884C" : null,
7494
+ !dependencyStates.length && Object.values(gate).every(Boolean) ? "\u4EFB\u52A1\u5BF9\u5E94\u7684\u62A5\u544A\u9879\u6216\u8BA1\u5212\u6821\u9A8C\u4ECD\u5904\u4E8E\u963B\u585E\u72B6\u6001" : null
7495
+ ]);
7496
+ }
6507
7497
  if (state === "implemented") return ["\u5B9E\u73B0\u6620\u5C04\u5DF2\u5B58\u5728\uFF0C\u4F46\u5B9A\u5411\u8BBE\u5907\u9A8C\u8BC1\u5C1A\u672A\u5B8C\u6210"];
6508
7498
  if (state === "stale") return ["\u62A5\u544A\u58F0\u660E\u7684\u6D4B\u8BD5\u8BC1\u636E\u4E0E\u5F53\u524D Facts \u6216 HarmonyOS \u6E90\u7801\u4E0D\u4E00\u81F4"];
6509
7499
  if (state === "locally-verified") return ["\u5F53\u524D\u4EFB\u52A1\u5DF2\u6709\u65B0\u9C9C\u5B9A\u5411\u8BBE\u5907\u8BC1\u636E"];
@@ -6512,25 +7502,72 @@ function stateReasons(task, state, dependencyStates) {
6512
7502
  }
6513
7503
  function rankReady(tasks, states) {
6514
7504
  const priority = (task) => {
6515
- if (task.type === "resolution") {
6516
- const blocksLauncher = task.closure.unresolvedRefs.some((ref) => ref.includes("launcher"));
6517
- return blocksLauncher || task.estimate.downstreamUnlockCount > 0 ? 0 : 5;
6518
- }
7505
+ if (task.type === "resolution") return 0;
6519
7506
  const order = { bootstrap: 1, journey: 2, capability: 3, assembly: 4, regression: 6 };
6520
7507
  return order[task.type] ?? 6;
6521
7508
  };
6522
7509
  return tasks.filter((task) => states.get(task.id) === "ready").sort((a, b) => priority(a) - priority(b) || b.estimate.downstreamUnlockCount - a.estimate.downstreamUnlockCount || a.estimate.estimatedFactTokens - b.estimate.estimatedFactTokens || a.id.localeCompare(b.id));
6523
7510
  }
7511
+ function completedState(state) {
7512
+ return ["locally-verified", "globally-verified"].includes(state);
7513
+ }
7514
+ function aggregatePackageOwnState(states) {
7515
+ if (states.length > 0 && states.every((state) => state === "globally-verified")) return "globally-verified";
7516
+ if (states.length > 0 && states.every(completedState)) return "locally-verified";
7517
+ if (states.some((state) => state === "blocked")) return "blocked";
7518
+ if (states.some((state) => state === "stale")) return "stale";
7519
+ if (states.length > 0 && states.every((state) => ["implemented", "locally-verified", "globally-verified"].includes(state))) return "implemented";
7520
+ return "pending";
7521
+ }
7522
+ function packageStateReasons(workPackage, state, dependencyStates, ownStates) {
7523
+ if (state === "ready") return [
7524
+ `\u9875\u9762\u5305\u5C06\u5728\u4E00\u6B21\u5B9E\u73B0 epoch \u5185\u6309\u51BB\u7ED3\u987A\u5E8F\u5B8C\u6210 ${workPackage.workItemIds.length} \u4E2A Work Item`,
7525
+ "\u8DE8\u9875\u9762\u4F9D\u8D56\u5DF2\u6EE1\u8DB3\uFF0C\u53EF\u52A0\u8F7D\u9875\u9762\u7EA7\u6A21\u578B\u8F93\u5165"
7526
+ ];
7527
+ if (state === "blocked") return uniqueSorted([
7528
+ dependencyStates.some((dependency) => !completedState(dependency)) ? "\u524D\u7F6E Page Work Package \u5C1A\u672A\u53D6\u5F97\u65B0\u9C9C\u7684\u5B9A\u5411\u9A8C\u8BC1\u8BC1\u636E" : null,
7529
+ ownStates.some((own) => own === "blocked") ? "\u9875\u9762\u5305\u5185\u5B58\u5728\u88AB\u62A5\u544A\u6807\u8BB0\u4E3A blocked \u7684\u884C\u4E3A" : null,
7530
+ !dependencyStates.length && !ownStates.some((own) => own === "blocked") ? "\u9875\u9762\u5305\u7684\u9759\u6001 Ready Gate \u672A\u5168\u90E8\u901A\u8FC7" : null
7531
+ ]);
7532
+ if (state === "implemented") return ["\u9875\u9762\u5305\u5185\u5168\u90E8\u884C\u4E3A\u5DF2\u6709\u5B9E\u73B0\u6620\u5C04\uFF0C\u4F46\u4ECD\u7F3A\u5C11\u5B8C\u6574\u7684\u5B9A\u5411\u8BBE\u5907\u9A8C\u8BC1"];
7533
+ if (state === "stale") return ["\u9875\u9762\u5305\u5185\u81F3\u5C11\u4E00\u9879\u9A8C\u8BC1\u8BC1\u636E\u4E0E\u5F53\u524D Facts \u6216 HarmonyOS \u6E90\u7801\u4E0D\u4E00\u81F4"];
7534
+ if (state === "locally-verified") return ["\u9875\u9762\u5305\u5185\u5168\u90E8\u884C\u4E3A\u5747\u6709\u65B0\u9C9C\u5B9A\u5411\u8BBE\u5907\u8BC1\u636E"];
7535
+ if (state === "globally-verified") return ["\u9875\u9762\u5305\u5185\u5168\u90E8\u884C\u4E3A\u53CA\u5168\u91CF\u56DE\u5F52\u8BC1\u636E\u5747\u65B0\u9C9C"];
7536
+ return [];
7537
+ }
7538
+ function packageDownstreamCounts(packages) {
7539
+ const children = /* @__PURE__ */ new Map();
7540
+ for (const workPackage of packages) for (const dependency of workPackage.dependsOnPackageIds) children.set(dependency, [...children.get(dependency) ?? [], workPackage.id]);
7541
+ const result = /* @__PURE__ */ new Map();
7542
+ for (const workPackage of packages) {
7543
+ const seen = /* @__PURE__ */ new Set();
7544
+ const queue = [...children.get(workPackage.id) ?? []];
7545
+ while (queue.length) {
7546
+ const next = queue.shift();
7547
+ if (seen.has(next)) continue;
7548
+ seen.add(next);
7549
+ queue.push(...children.get(next) ?? []);
7550
+ }
7551
+ result.set(workPackage.id, seen.size);
7552
+ }
7553
+ return result;
7554
+ }
6524
7555
  function emptyTaskPlanSnapshot(error) {
6525
7556
  return {
6526
7557
  present: false,
6527
7558
  valid: false,
6528
7559
  fresh: false,
6529
7560
  taskPlanSha256: null,
7561
+ planningPhase: null,
6530
7562
  totalTasks: 0,
7563
+ resolutionTasks: 0,
7564
+ totalTargets: 0,
7565
+ readyTargets: 0,
7566
+ completedTargets: 0,
6531
7567
  readyTasks: 0,
6532
7568
  blockedTasks: 0,
6533
7569
  completedTasks: 0,
7570
+ recommendedTargetId: null,
6534
7571
  recommendedTaskId: null,
6535
7572
  alternatives: [],
6536
7573
  reasons: {},
@@ -6539,7 +7576,8 @@ function emptyTaskPlanSnapshot(error) {
6539
7576
  orphanFactRefs: [],
6540
7577
  errors: error ? [error] : [],
6541
7578
  warnings: [],
6542
- tasks: []
7579
+ tasks: [],
7580
+ targets: []
6543
7581
  };
6544
7582
  }
6545
7583
  function inspectCompiledTaskPlan(projectRootArgument, facts, source) {
@@ -6548,6 +7586,7 @@ function inspectCompiledTaskPlan(projectRootArgument, facts, source) {
6548
7586
  if (!plan) return emptyTaskPlanSnapshot();
6549
7587
  const validation = validateTaskPlan(plan, facts, source.androidFingerprint);
6550
7588
  const reports = reportItems(projectRoot);
7589
+ const ownStates = new Map(plan.tasks.map((task) => [task.id, initialState(task, reports, projectRoot, plan, source)]));
6551
7590
  const states = /* @__PURE__ */ new Map();
6552
7591
  const resolving = /* @__PURE__ */ new Set();
6553
7592
  const byId = new Map(plan.tasks.map((task) => [task.id, task]));
@@ -6557,12 +7596,13 @@ function inspectCompiledTaskPlan(projectRootArgument, facts, source) {
6557
7596
  if (!validation.fresh) return "stale";
6558
7597
  if (resolving.has(task.id)) return "blocked";
6559
7598
  resolving.add(task.id);
6560
- const own = initialState(task, reports, projectRoot, plan, source);
7599
+ const own = ownStates.get(task.id);
6561
7600
  let state = own;
6562
7601
  if (own === "pending") {
6563
7602
  const dependencyStates = task.dependsOnTaskIds.map((id) => byId.get(id)).filter((item) => Boolean(item)).map(resolveState);
6564
7603
  const satisfied = dependencyStates.every((value) => ["locally-verified", "globally-verified"].includes(value));
6565
- state = satisfied ? "ready" : "blocked";
7604
+ const readyGatePassed = Object.values(task.readyGate).every(Boolean);
7605
+ state = satisfied && readyGatePassed ? "ready" : "blocked";
6566
7606
  }
6567
7607
  resolving.delete(task.id);
6568
7608
  states.set(task.id, state);
@@ -6575,8 +7615,59 @@ function inspectCompiledTaskPlan(projectRootArgument, facts, source) {
6575
7615
  const dependencyStates = task.dependsOnTaskIds.map((id) => states.get(id)).filter(Boolean);
6576
7616
  return { taskId: task.id, state, reasons: stateReasons(task, state, dependencyStates) };
6577
7617
  });
6578
- const reasons = Object.fromEntries(runtimeItems.filter((item) => item.reasons.length).map((item) => [item.taskId, item.reasons]));
6579
- const ownerCount = facts.requiredFactRefs.filter((ref) => plan.ownership.primaryOwnerByFactRef[ref]).length;
7618
+ let targets;
7619
+ let orderedReadyTargetIds;
7620
+ if (plan.planningPhase === "resolution") {
7621
+ targets = runtimeItems.map((item) => ({ targetId: item.taskId, kind: "resolution-task", state: item.state, reasons: item.reasons, workItemIds: [item.taskId] }));
7622
+ orderedReadyTargetIds = ready.map((task) => task.id);
7623
+ } else {
7624
+ const packageById = new Map(plan.pageWorkPackages.map((workPackage) => [workPackage.id, workPackage]));
7625
+ const packageStates = /* @__PURE__ */ new Map();
7626
+ const packageResolving = /* @__PURE__ */ new Set();
7627
+ const resolvePackageState = (workPackage) => {
7628
+ if (packageStates.has(workPackage.id)) return packageStates.get(workPackage.id);
7629
+ if (!validation.valid) return "blocked";
7630
+ if (!validation.fresh) return "stale";
7631
+ if (packageResolving.has(workPackage.id)) return "blocked";
7632
+ packageResolving.add(workPackage.id);
7633
+ const memberTasks = workPackage.workItemIds.map((id) => byId.get(id)).filter((task) => Boolean(task));
7634
+ const memberOwnStates = memberTasks.map((task) => ownStates.get(task.id));
7635
+ const own = aggregatePackageOwnState(memberOwnStates);
7636
+ let state = own;
7637
+ if (own === "pending") {
7638
+ const dependencyStates = workPackage.dependsOnPackageIds.map((id) => packageById.get(id)).filter((item) => Boolean(item)).map(resolvePackageState);
7639
+ const dependenciesSatisfied = dependencyStates.every(completedState);
7640
+ const readyGatePassed = memberTasks.every((task) => Object.values(task.readyGate).every(Boolean));
7641
+ state = dependenciesSatisfied && readyGatePassed ? "ready" : "blocked";
7642
+ }
7643
+ packageResolving.delete(workPackage.id);
7644
+ packageStates.set(workPackage.id, state);
7645
+ return state;
7646
+ };
7647
+ for (const workPackage of plan.pageWorkPackages) resolvePackageState(workPackage);
7648
+ targets = plan.pageWorkPackages.map((workPackage) => {
7649
+ const state = packageStates.get(workPackage.id);
7650
+ const dependencyStates = workPackage.dependsOnPackageIds.map((id) => packageStates.get(id)).filter(Boolean);
7651
+ const memberOwnStates = workPackage.workItemIds.map((id) => ownStates.get(id)).filter(Boolean);
7652
+ return { targetId: workPackage.id, kind: "page-work-package", state, reasons: packageStateReasons(workPackage, state, dependencyStates, memberOwnStates), workItemIds: workPackage.workItemIds };
7653
+ });
7654
+ const downstream = packageDownstreamCounts(plan.pageWorkPackages);
7655
+ const targetById = new Map(targets.map((target) => [target.targetId, target]));
7656
+ orderedReadyTargetIds = plan.pageWorkPackages.filter((workPackage) => targetById.get(workPackage.id)?.state === "ready").sort((left, right) => {
7657
+ const leftTasks = left.workItemIds.map((id) => byId.get(id)).filter((task) => Boolean(task));
7658
+ const rightTasks = right.workItemIds.map((id) => byId.get(id)).filter((task) => Boolean(task));
7659
+ const leftBootstrap = leftTasks.some((task) => task.type === "bootstrap") ? 0 : 1;
7660
+ const rightBootstrap = rightTasks.some((task) => task.type === "bootstrap") ? 0 : 1;
7661
+ const leftTokens = leftTasks.reduce((total, task) => total + task.estimate.estimatedFactTokens, 0);
7662
+ const rightTokens = rightTasks.reduce((total, task) => total + task.estimate.estimatedFactTokens, 0);
7663
+ return leftBootstrap - rightBootstrap || (downstream.get(right.id) ?? 0) - (downstream.get(left.id) ?? 0) || leftTokens - rightTokens || left.id.localeCompare(right.id);
7664
+ }).map((workPackage) => workPackage.id);
7665
+ }
7666
+ const reasons = Object.fromEntries([
7667
+ ...runtimeItems.filter((item) => item.reasons.length).map((item) => [item.taskId, item.reasons]),
7668
+ ...targets.filter((item) => item.reasons.length).map((item) => [item.targetId, item.reasons])
7669
+ ]);
7670
+ const ownerCount = facts.requiredFactRefs.filter((ref) => plan.behaviorOwnership.primaryOwnerByFactRef[ref]).length;
6580
7671
  const behaviorTasks = plan.tasks.filter((task) => task.type !== "resolution");
6581
7672
  const closedTasks = behaviorTasks.filter((task) => !task.closure.requiresSourceReview && (task.closure.interactionRefs.length > 0 || task.type === "bootstrap" && task.closure.surfaceRefs.length > 0)).length;
6582
7673
  return {
@@ -6584,19 +7675,26 @@ function inspectCompiledTaskPlan(projectRootArgument, facts, source) {
6584
7675
  valid: validation.valid,
6585
7676
  fresh: validation.fresh,
6586
7677
  taskPlanSha256: validation.taskPlanSha256,
7678
+ planningPhase: plan.planningPhase,
6587
7679
  totalTasks: plan.tasks.length,
7680
+ resolutionTasks: plan.tasks.filter((task) => task.type === "resolution").length,
7681
+ totalTargets: targets.length,
7682
+ readyTargets: orderedReadyTargetIds.length,
7683
+ completedTargets: targets.filter((item) => completedState(item.state)).length,
6588
7684
  readyTasks: ready.length,
6589
7685
  blockedTasks: runtimeItems.filter((item) => ["blocked", "stale"].includes(item.state)).length,
6590
- completedTasks: runtimeItems.filter((item) => ["locally-verified", "globally-verified"].includes(item.state)).length,
6591
- recommendedTaskId: ready[0]?.id ?? null,
6592
- alternatives: ready.slice(1, 4).map((task) => task.id),
7686
+ completedTasks: runtimeItems.filter((item) => completedState(item.state)).length,
7687
+ recommendedTargetId: orderedReadyTargetIds[0] ?? null,
7688
+ recommendedTaskId: plan.planningPhase === "resolution" ? orderedReadyTargetIds[0] ?? null : null,
7689
+ alternatives: orderedReadyTargetIds.slice(1, 4),
6593
7690
  reasons,
6594
7691
  ownershipCoverage: facts.requiredFactRefs.length ? ownerCount / facts.requiredFactRefs.length : 1,
6595
- closureCoverage: behaviorTasks.length ? closedTasks / behaviorTasks.length : 1,
7692
+ closureCoverage: plan.planningPhase === "resolution" ? 0 : behaviorTasks.length ? closedTasks / behaviorTasks.length : 1,
6596
7693
  orphanFactRefs: validation.orphanFactRefs,
6597
7694
  errors: validation.errors,
6598
7695
  warnings: validation.warnings,
6599
- tasks: runtimeItems
7696
+ tasks: runtimeItems,
7697
+ targets
6600
7698
  };
6601
7699
  }
6602
7700
 
@@ -6605,10 +7703,10 @@ function createTaskPlanningModule(factsSource = v2PlanningFactsSource) {
6605
7703
  return {
6606
7704
  compile(projectRoot, options) {
6607
7705
  const facts = factsSource.load(projectRoot);
6608
- const plan = compileFromPlanningFacts(facts, options.androidFingerprint);
7706
+ const plan = compileFromPlanningFacts(facts, options.androidFingerprint, loadExecutionPlanningContext(projectRoot));
6609
7707
  const validation = validateTaskPlan(plan, facts, options.androidFingerprint);
6610
7708
  if (!validation.valid || !validation.fresh) throw new Error(`Task plan compilation failed: ${[...validation.errors, ...validation.warnings].join("; ")}`);
6611
- writeTaskPlan(projectRoot, plan);
7709
+ writeTaskPlan(projectRoot, plan, facts);
6612
7710
  return plan;
6613
7711
  },
6614
7712
  inspect(projectRoot, source) {
@@ -6620,19 +7718,19 @@ function createTaskPlanningModule(factsSource = v2PlanningFactsSource) {
6620
7718
  return { ...emptyTaskPlanSnapshot(error instanceof Error ? error.message : String(error)), present: hasTaskPlan(projectRoot) };
6621
7719
  }
6622
7720
  },
6623
- materialize(projectRoot, task, request) {
7721
+ materialize(projectRoot, target, request) {
6624
7722
  const facts = factsSource.load(projectRoot);
6625
7723
  const plan = readTaskPlan(projectRoot);
6626
7724
  if (!plan) throw new Error("Task plan is not present; run droid2hmos task plan first");
6627
7725
  const validation = validateTaskPlan(plan, facts, request.source.androidFingerprint);
6628
7726
  if (!validation.valid || !validation.fresh) throw new Error(`Task plan is invalid or stale: ${[...validation.errors, ...validation.warnings].join("; ")}`);
6629
- let taskId = task;
6630
- if (task === "next") {
7727
+ let targetId = target;
7728
+ if (target === "next") {
6631
7729
  const snapshot = inspectCompiledTaskPlan(projectRoot, facts, request.source);
6632
- if (!snapshot.recommendedTaskId) throw new Error("No ready task is available");
6633
- taskId = snapshot.recommendedTaskId;
7730
+ if (!snapshot.recommendedTargetId) throw new Error("No ready execution target is available");
7731
+ targetId = snapshot.recommendedTargetId;
6634
7732
  }
6635
- return materializeEnvelope(plan, facts, taskId, request);
7733
+ return materializeEnvelope(plan, facts, targetId, request);
6636
7734
  }
6637
7735
  };
6638
7736
  }
@@ -6648,9 +7746,7 @@ var actions = [
6648
7746
  "init",
6649
7747
  "analyze:android",
6650
7748
  "analyze:harmony",
6651
- "build:android",
6652
7749
  "build:harmony",
6653
- "test:android",
6654
7750
  "test:harmony:targeted",
6655
7751
  "test:harmony:all",
6656
7752
  "report:init",
@@ -6672,9 +7768,9 @@ var actions = [
6672
7768
  "complete"
6673
7769
  ];
6674
7770
  var ignoredDirectories = /* @__PURE__ */ new Set([".git", ".gradle", ".hvigor", ".idea", ".migration", ".codegraph-appgraph", "build", "node_modules", "oh_modules"]);
6675
- function readJson7(file) {
7771
+ function readJson8(file) {
6676
7772
  try {
6677
- return JSON.parse(fs28.readFileSync(file, "utf8"));
7773
+ return JSON.parse(fs29.readFileSync(file, "utf8"));
6678
7774
  } catch {
6679
7775
  return null;
6680
7776
  }
@@ -6691,11 +7787,11 @@ function sha2563(value) {
6691
7787
  return createHash8("sha256").update(typeof value === "string" ? value : stableJson2(value)).digest("hex");
6692
7788
  }
6693
7789
  function hashDirectory(root, excludedRoots = []) {
6694
- if (!root || !fs28.existsSync(root) || !fs28.statSync(root).isDirectory()) return null;
7790
+ if (!root || !fs29.existsSync(root) || !fs29.statSync(root).isDirectory()) return null;
6695
7791
  const excluded = excludedRoots.map((value) => path35.resolve(value));
6696
7792
  const hash2 = createHash8("sha256");
6697
7793
  const visit = (directory) => {
6698
- const entries = fs28.readdirSync(directory, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
7794
+ const entries = fs29.readdirSync(directory, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
6699
7795
  for (const entry of entries) {
6700
7796
  const absolute = path35.join(directory, entry.name);
6701
7797
  if (entry.isDirectory() && (ignoredDirectories.has(entry.name) || excluded.some((value) => absolute === value || absolute.startsWith(`${value}${path35.sep}`)))) continue;
@@ -6705,10 +7801,10 @@ function hashDirectory(root, excludedRoots = []) {
6705
7801
  hash2.update(`d:${relative2}\0`);
6706
7802
  visit(absolute);
6707
7803
  } else if (entry.isSymbolicLink()) {
6708
- hash2.update(`l:${relative2}\0${fs28.readlinkSync(absolute)}\0`);
7804
+ hash2.update(`l:${relative2}\0${fs29.readlinkSync(absolute)}\0`);
6709
7805
  } else if (entry.isFile()) {
6710
7806
  hash2.update(`f:${relative2}\0`);
6711
- hash2.update(fs28.readFileSync(absolute));
7807
+ hash2.update(fs29.readFileSync(absolute));
6712
7808
  hash2.update("\0");
6713
7809
  }
6714
7810
  }
@@ -6732,21 +7828,21 @@ function androidProjectFor(projectRoot, config) {
6732
7828
  return path35.isAbsolute(configured) ? configured : path35.resolve(projectRoot, configured);
6733
7829
  }
6734
7830
  function isAndroidProject(root) {
6735
- if (!fs28.existsSync(root) || !fs28.statSync(root).isDirectory()) return false;
6736
- const hasBuildRoot = ["settings.gradle", "settings.gradle.kts", "build.gradle", "build.gradle.kts", "gradlew", "gradlew.bat"].some((name) => fs28.existsSync(path35.join(root, name)));
7831
+ if (!fs29.existsSync(root) || !fs29.statSync(root).isDirectory()) return false;
7832
+ const hasBuildRoot = ["settings.gradle", "settings.gradle.kts", "build.gradle", "build.gradle.kts", "gradlew", "gradlew.bat"].some((name) => fs29.existsSync(path35.join(root, name)));
6737
7833
  if (!hasBuildRoot) return false;
6738
- if (fs28.existsSync(path35.join(root, "app", "src", "main", "AndroidManifest.xml"))) return true;
6739
- return fs28.readdirSync(root, { withFileTypes: true }).some((entry) => entry.isDirectory() && !ignoredDirectories.has(entry.name) && fs28.existsSync(path35.join(root, entry.name, "src", "main", "AndroidManifest.xml")));
7834
+ if (fs29.existsSync(path35.join(root, "app", "src", "main", "AndroidManifest.xml"))) return true;
7835
+ return fs29.readdirSync(root, { withFileTypes: true }).some((entry) => entry.isDirectory() && !ignoredDirectories.has(entry.name) && fs29.existsSync(path35.join(root, entry.name, "src", "main", "AndroidManifest.xml")));
6740
7836
  }
6741
7837
  function androidProjectCandidates(root) {
6742
- if (!fs28.existsSync(root) || !fs28.statSync(root).isDirectory()) return [];
6743
- return fs28.readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !ignoredDirectories.has(entry.name)).map((entry) => path35.join(root, entry.name)).filter(isAndroidProject).sort();
7838
+ if (!fs29.existsSync(root) || !fs29.statSync(root).isDirectory()) return [];
7839
+ return fs29.readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !ignoredDirectories.has(entry.name)).map((entry) => path35.join(root, entry.name)).filter(isAndroidProject).sort();
6744
7840
  }
6745
7841
  function evidenceRoot(projectRoot) {
6746
7842
  return path35.join(migrationDirectory(projectRoot), "evidence");
6747
7843
  }
6748
7844
  function evidenceIndex(projectRoot) {
6749
- return readJson7(path35.join(evidenceRoot(projectRoot), "index.json")) ?? { evidenceIndexVersion: "1.0.0", latestByAction: {} };
7845
+ return readJson8(path35.join(evidenceRoot(projectRoot), "index.json")) ?? { evidenceIndexVersion: "1.0.0", latestByAction: {} };
6750
7846
  }
6751
7847
  function projectRelative(projectRoot, absolute) {
6752
7848
  return path35.relative(projectRoot, absolute).replaceAll("\\", "/");
@@ -6759,7 +7855,7 @@ function managedReceipt2(projectRoot, reference) {
6759
7855
  const root = path35.resolve(evidenceRoot(projectRoot), "managed");
6760
7856
  const absolute = path35.resolve(migrationWorkspaceRoot(projectRoot), reference);
6761
7857
  if (absolute !== root && !absolute.startsWith(`${root}${path35.sep}`)) return null;
6762
- return readJson7(absolute);
7858
+ return readJson8(absolute);
6763
7859
  }
6764
7860
  function reportClaims(projectRoot, reportIndex) {
6765
7861
  if (!reportIndex) return [];
@@ -6775,16 +7871,16 @@ function reportClaims(projectRoot, reportIndex) {
6775
7871
  });
6776
7872
  };
6777
7873
  for (const descriptor of reportIndex.pages ?? []) {
6778
- const shard = readJson7(path35.join(reportRoot, descriptor.file));
7874
+ const shard = readJson8(path35.join(reportRoot, descriptor.file));
6779
7875
  if (!shard) continue;
6780
7876
  addItems([shard.page], descriptor.file);
6781
7877
  addItems(shard.controls, descriptor.file);
6782
7878
  addItems(shard.entries, descriptor.file);
6783
7879
  }
6784
- if (reportIndex.unassignedEntriesFile) addItems(readJson7(path35.join(reportRoot, reportIndex.unassignedEntriesFile))?.entries, reportIndex.unassignedEntriesFile);
6785
- if (reportIndex.interactionsFile) addItems(readJson7(path35.join(reportRoot, reportIndex.interactionsFile))?.interactions, reportIndex.interactionsFile);
6786
- if (reportIndex.discoveriesFile) addItems(readJson7(path35.join(reportRoot, reportIndex.discoveriesFile))?.items, "discoveries", true);
6787
- const acceptance = reportIndex.finalAcceptanceFile ? readJson7(path35.join(reportRoot, reportIndex.finalAcceptanceFile)) : null;
7880
+ if (reportIndex.unassignedEntriesFile) addItems(readJson8(path35.join(reportRoot, reportIndex.unassignedEntriesFile))?.entries, reportIndex.unassignedEntriesFile);
7881
+ if (reportIndex.interactionsFile) addItems(readJson8(path35.join(reportRoot, reportIndex.interactionsFile))?.interactions, reportIndex.interactionsFile);
7882
+ if (reportIndex.discoveriesFile) addItems(readJson8(path35.join(reportRoot, reportIndex.discoveriesFile))?.items, "discoveries", true);
7883
+ const acceptance = reportIndex.finalAcceptanceFile ? readJson8(path35.join(reportRoot, reportIndex.finalAcceptanceFile)) : null;
6788
7884
  for (const [index, test] of (acceptance?.tests ?? []).entries()) claims.push({
6789
7885
  ref: test?.ref ?? `final-acceptance:${test?.name ?? index + 1}`,
6790
7886
  source: reportIndex.finalAcceptanceFile,
@@ -6799,8 +7895,8 @@ function receiptArtifactsAreIntact(projectRoot, receipt) {
6799
7895
  return receipt.artifacts.every((artifact) => {
6800
7896
  if (!artifact || typeof artifact.path !== "string" || !/^[a-f0-9]{64}$/.test(artifact.sha256) || !Number.isInteger(artifact.size)) return false;
6801
7897
  const absolute = path35.resolve(migrationWorkspaceRoot(projectRoot), artifact.path);
6802
- if (!absolute.startsWith(`${blobsRoot}${path35.sep}`) || !fs28.existsSync(absolute) || !fs28.statSync(absolute).isFile()) return false;
6803
- const content = fs28.readFileSync(absolute);
7898
+ if (!absolute.startsWith(`${blobsRoot}${path35.sep}`) || !fs29.existsSync(absolute) || !fs29.statSync(absolute).isFile()) return false;
7899
+ const content = fs29.readFileSync(absolute);
6804
7900
  return content.byteLength === artifact.size && createHash8("sha256").update(content).digest("hex") === artifact.sha256;
6805
7901
  });
6806
7902
  }
@@ -6862,7 +7958,7 @@ function actionRequirements(action, snapshot) {
6862
7958
  if (action === "config:init" && snapshot.workspace.configPresent) missing.push(unmet("config-present", ".migration/config.json \u5DF2\u5B58\u5728"));
6863
7959
  if (action === "config:validate") requireConfig();
6864
7960
  if (action === "init" && snapshot.workspace.harmonyProjectPresent) missing.push(unmet("harmony-project-present", "HarmonyOS \u5DE5\u7A0B\u5DF2\u5B58\u5728\uFF0C\u65E0\u9700\u91CD\u590D\u521D\u59CB\u5316"));
6865
- if (["analyze:android", "analyze:harmony", "build:android", "build:harmony", "test:android", "test:harmony:targeted", "test:harmony:all", "evidence:flow"].includes(action)) requireConfig();
7961
+ if (["analyze:android", "analyze:harmony", "build:harmony", "test:harmony:targeted", "test:harmony:all", "evidence:flow"].includes(action)) requireConfig();
6866
7962
  if (["analyze:harmony", "build:harmony", "test:harmony:targeted", "test:harmony:all"].includes(action)) requireHarmony();
6867
7963
  if (action === "report:init") {
6868
7964
  requireFacts();
@@ -6874,6 +7970,10 @@ function actionRequirements(action, snapshot) {
6874
7970
  requireFacts();
6875
7971
  requireReport();
6876
7972
  requireFreshAnalysis();
7973
+ if (snapshot.planning.present && snapshot.planning.valid && snapshot.planning.fresh) {
7974
+ const message = snapshot.planning.planningPhase === "execution" ? "\u5DF2\u6709\u65B0\u9C9C\u7684\u51BB\u7ED3 Execution Plan\uFF1B\u5B9E\u73B0\u9636\u6BB5\u5FC5\u987B\u4E25\u683C\u6267\u884C\u73B0\u6709 Page Work Package\uFF0C\u7981\u6B62\u52A8\u6001 task plan" : "\u5DF2\u6709\u65B0\u9C9C\u7684 Resolution Plan\uFF1B\u5FC5\u987B\u5148\u5199\u5165\u6709\u6548 correction/review \u4F7F\u8BA1\u5212\u8FC7\u671F\uFF0C\u518D\u91CD\u65B0 task plan";
7975
+ missing.push(unmet("task-plan-already-fresh", message));
7976
+ }
6877
7977
  }
6878
7978
  if (action === "task:validate" && !snapshot.planning.present) missing.push(unmet("task-plan-missing", "Task Plan \u5C1A\u672A\u751F\u6210"));
6879
7979
  if (action === "checkpoint:create") {
@@ -6897,14 +7997,14 @@ function derivePhase(snapshot) {
6897
7997
  function inspectMigration(project = ".") {
6898
7998
  const projectRoot = path35.resolve(project);
6899
7999
  const migrationRoot = migrationDirectory(projectRoot);
6900
- const config = readJson7(path35.join(migrationRoot, "config.json"));
8000
+ const config = readJson8(path35.join(migrationRoot, "config.json"));
6901
8001
  const harmonyProject = harmonyProjectFor(projectRoot, config);
6902
8002
  const androidProject = androidProjectFor(projectRoot, config);
6903
8003
  const projectPresent = isAndroidProject(androidProject);
6904
8004
  const projectCandidates = projectPresent ? [] : androidProjectCandidates(projectRoot);
6905
- const preflight = readJson7(path35.join(migrationRoot, "preflight-report.json"));
6906
- const specIndex = readJson7(path35.join(migrationRoot, "spec", "index.json"));
6907
- const reportIndex = readJson7(path35.join(migrationRoot, "report", "index.json"));
8005
+ const preflight = readJson8(path35.join(migrationRoot, "preflight-report.json"));
8006
+ const specIndex = readJson8(path35.join(migrationRoot, "spec", "index.json"));
8007
+ const reportIndex = readJson8(path35.join(migrationRoot, "report", "index.json"));
6908
8008
  let validation;
6909
8009
  try {
6910
8010
  validation = validateMigrationReport([projectRoot], { writeResult: false });
@@ -6928,7 +8028,7 @@ function inspectMigration(project = ".") {
6928
8028
  projectCandidates,
6929
8029
  configPresent: Boolean(config),
6930
8030
  harmonyProject,
6931
- harmonyProjectPresent: fs28.existsSync(harmonyProject) && fs28.statSync(harmonyProject).isDirectory(),
8031
+ harmonyProjectPresent: fs29.existsSync(harmonyProject) && fs29.statSync(harmonyProject).isDirectory(),
6932
8032
  gitHead: projectPresent ? gitHead(androidProject) : null
6933
8033
  },
6934
8034
  preflight: {
@@ -6960,10 +8060,16 @@ function inspectMigration(project = ".") {
6960
8060
  valid: false,
6961
8061
  fresh: false,
6962
8062
  taskPlanSha256: null,
8063
+ planningPhase: null,
6963
8064
  totalTasks: 0,
8065
+ resolutionTasks: 0,
8066
+ totalTargets: 0,
8067
+ readyTargets: 0,
8068
+ completedTargets: 0,
6964
8069
  readyTasks: 0,
6965
8070
  blockedTasks: 0,
6966
8071
  completedTasks: 0,
8072
+ recommendedTargetId: null,
6967
8073
  recommendedTaskId: null,
6968
8074
  alternatives: [],
6969
8075
  reasons: {},
@@ -6972,7 +8078,8 @@ function inspectMigration(project = ".") {
6972
8078
  orphanFactRefs: [],
6973
8079
  errors: [],
6974
8080
  warnings: [],
6975
- tasks: []
8081
+ tasks: [],
8082
+ targets: []
6976
8083
  },
6977
8084
  evidence: {
6978
8085
  androidAnalysis: { reference: null, present: false, passed: false, fresh: false },
@@ -7018,7 +8125,7 @@ function inspectMigration(project = ".") {
7018
8125
  else {
7019
8126
  if (!snapshot.planning.valid) completionUnmet.push(unmet("task-plan-invalid", "Task Plan \u5B8C\u6574\u6027\u6821\u9A8C\u5931\u8D25"));
7020
8127
  if (!snapshot.planning.fresh) completionUnmet.push(unmet("task-plan-stale", "Task Plan \u4E0E\u5F53\u524D Facts\u3001\u5BA1\u6838\u8F93\u5165\u6216 Android \u6E90\u7801\u4E0D\u4E00\u81F4"));
7021
- if (snapshot.planning.completedTasks !== snapshot.planning.totalTasks) completionUnmet.push(unmet("task-plan-incomplete", `${snapshot.planning.totalTasks - snapshot.planning.completedTasks} \u4E2A Task \u5C1A\u672A\u53D6\u5F97\u65B0\u9C9C\u5B9A\u5411\u9A8C\u8BC1\u8BC1\u636E`));
8128
+ if (snapshot.planning.completedTasks !== snapshot.planning.totalTasks) completionUnmet.push(unmet("task-plan-incomplete", `${snapshot.planning.totalTasks - snapshot.planning.completedTasks} \u4E2A\u5185\u90E8 Work Item \u5C1A\u672A\u53D6\u5F97\u65B0\u9C9C\u5B9A\u5411\u9A8C\u8BC1\u8BC1\u636E`));
7022
8129
  if (snapshot.planning.orphanFactRefs.length) completionUnmet.push(unmet("task-plan-orphans", `${snapshot.planning.orphanFactRefs.length} \u4E2A\u5FC5\u8981 Fact \u6CA1\u6709\u4E3B\u6240\u6709\u6743`));
7023
8130
  }
7024
8131
  if (!snapshot.evidence.fullRegression.fresh) completionUnmet.push(unmet("full-regression-missing-or-stale", "\u7F3A\u5C11\u4E0E\u5F53\u524D facts \u548C HarmonyOS \u6E90\u7801\u4E00\u81F4\u7684\u5168\u91CF\u53D7\u7BA1\u56DE\u5F52\u8BC1\u636E"));
@@ -7104,13 +8211,13 @@ function archiveArtifacts(projectRoot, artifacts) {
7104
8211
  const archived = [];
7105
8212
  for (const value of artifacts) {
7106
8213
  const absolute = path35.isAbsolute(value) ? value : path35.resolve(projectRoot, value);
7107
- if (!fs28.existsSync(absolute) || !fs28.statSync(absolute).isFile()) continue;
7108
- const content = fs28.readFileSync(absolute);
8214
+ if (!fs29.existsSync(absolute) || !fs29.statSync(absolute).isFile()) continue;
8215
+ const content = fs29.readFileSync(absolute);
7109
8216
  const digest = createHash8("sha256").update(content).digest("hex");
7110
8217
  const destination = path35.join(evidenceRoot(projectRoot), "blobs", digest, path35.basename(absolute));
7111
- fs28.mkdirSync(path35.dirname(destination), { recursive: true });
7112
- const existingIsIntact = fs28.existsSync(destination) && fs28.statSync(destination).isFile() && fs28.statSync(destination).size === content.byteLength && createHash8("sha256").update(fs28.readFileSync(destination)).digest("hex") === digest;
7113
- if (!existingIsIntact) fs28.copyFileSync(absolute, destination);
8218
+ fs29.mkdirSync(path35.dirname(destination), { recursive: true });
8219
+ const existingIsIntact = fs29.existsSync(destination) && fs29.statSync(destination).isFile() && fs29.statSync(destination).size === content.byteLength && createHash8("sha256").update(fs29.readFileSync(destination)).digest("hex") === digest;
8220
+ if (!existingIsIntact) fs29.copyFileSync(absolute, destination);
7114
8221
  archived.push({
7115
8222
  path: workspaceRelative(projectRoot, destination),
7116
8223
  source: projectRelative(projectRoot, absolute),
@@ -7135,11 +8242,11 @@ function receiptResult(action, result) {
7135
8242
  };
7136
8243
  }
7137
8244
  function writeJsonAtomic(file, value) {
7138
- fs28.mkdirSync(path35.dirname(file), { recursive: true });
8245
+ fs29.mkdirSync(path35.dirname(file), { recursive: true });
7139
8246
  const temporary = `${file}.${process.pid}.tmp`;
7140
- fs28.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}
8247
+ fs29.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}
7141
8248
  `, "utf8");
7142
- fs28.renameSync(temporary, file);
8249
+ fs29.renameSync(temporary, file);
7143
8250
  }
7144
8251
  function recordObservation(options) {
7145
8252
  const projectRoot = path35.resolve(options.project ?? ".");
@@ -7201,9 +8308,8 @@ Usage:
7201
8308
  droid2hmos complete [project]
7202
8309
  droid2hmos analyze <android|harmony|all> [project]
7203
8310
  droid2hmos preflight [project] [--sdk-path <path>] [--timeout-ms <ms>] [--no-init-harmony]
7204
- droid2hmos config <init|validate> [project] [--harmony-project <path>] [--android-build <command>] [--android-test <command>]
7205
- droid2hmos build <android|harmony|all> [project] [--timeout-ms <ms>]
7206
- droid2hmos test android [project] [--task <task>] [--timeout-ms <ms>]
8311
+ droid2hmos config <init|validate> [project] [--harmony-project <path>]
8312
+ droid2hmos build harmony [project] [--timeout-ms <ms>]
7207
8313
  droid2hmos test harmony [project] [--runner arkts] (--class <class> [--case <case>] | --all) [--target <serial>] [--case-timeout-ms <ms>] [--ref <report-ref>...] [--timeout-ms <ms>]
7208
8314
  droid2hmos test harmony [project] --runner python --hypium-project <path> --test-list <name>... [--target <serial>] [--python <path>] [--report-path <path>] [--ref <report-ref>...] [--timeout-ms <ms>]
7209
8315
  droid2hmos report [project] [--require-complete]
@@ -7212,16 +8318,16 @@ Usage:
7212
8318
  droid2hmos task plan [project]
7213
8319
  droid2hmos task status [project]
7214
8320
  droid2hmos task next [project]
7215
- droid2hmos task show [project] <task-id> [--cursor <cursor>] [--max-source-context <count>]
8321
+ droid2hmos task show [project] <task-or-package-id> [--cursor <cursor>] [--max-source-context <count>]
7216
8322
  droid2hmos task validate [project]
7217
8323
  droid2hmos checkpoint status [project]
7218
8324
  droid2hmos checkpoint create [project] --message <message> (--include <path>...|--all)
7219
- droid2hmos device check <android|harmony> [--adb <path>|--hdc <path>]
8325
+ droid2hmos device check harmony [--hdc <path>]
7220
8326
  droid2hmos auth login [project] [--timeout-ms <ms>]
7221
8327
  droid2hmos sign [project] [--target <serial>] [--force] [--timeout-ms <ms>]
7222
- droid2hmos evidence capture <android|harmony> [project] --name <name> [--target <id>]
8328
+ droid2hmos evidence capture harmony [project] --name <name> [--target <id>]
7223
8329
  droid2hmos evidence compare [project] --name <name> --android <evidence-dir> --harmony <evidence-dir>
7224
- droid2hmos evidence flow <android|harmony> [project] --name <name> [--target <id>] [--task <task>|--class <class> [--case <case>]|--all] [--case-timeout-ms <ms>] [--ref <report-ref>...]
8330
+ droid2hmos evidence flow harmony [project] --name <name> [--target <id>] [--class <class> [--case <case>]|--all] [--case-timeout-ms <ms>] [--ref <report-ref>...]
7225
8331
  droid2hmos mock init [project] --spec <protocol-spec.json> [--force]
7226
8332
  droid2hmos logs <list|show|failures|stats> [project] [value]
7227
8333
  droid2hmos upgrade
@@ -7274,13 +8380,13 @@ function observed(project, action, result, scope, guardResult) {
7274
8380
  }
7275
8381
  async function runCli(argv, runtime) {
7276
8382
  const args = [...argv];
7277
- let command3 = args.shift();
7278
- if (command3 && ["-v", "--version"].includes(command3)) {
7279
- if (args.length > 0) throw new CliError(`${command3} does not accept arguments`);
8383
+ let command2 = args.shift();
8384
+ if (command2 && ["-v", "--version"].includes(command2)) {
8385
+ if (args.length > 0) throw new CliError(`${command2} does not accept arguments`);
7280
8386
  console.log(readPackageMetadata(runtime.packageRoot).version);
7281
8387
  return 0;
7282
8388
  }
7283
- if (!command3 || ["help", "-h", "--help"].includes(command3)) {
8389
+ if (!command2 || ["help", "-h", "--help"].includes(command2)) {
7284
8390
  console.log(HELP);
7285
8391
  return 0;
7286
8392
  }
@@ -7288,13 +8394,13 @@ async function runCli(argv, runtime) {
7288
8394
  console.log(HELP);
7289
8395
  return 0;
7290
8396
  }
7291
- if (command3 === "status") {
8397
+ if (command2 === "status") {
7292
8398
  const projects = positionals(args);
7293
8399
  if (projects.length > 1) throw new CliError("status accepts at most one project directory");
7294
8400
  json(inspectMigration(projects[0] ?? "."));
7295
8401
  return 0;
7296
8402
  }
7297
- if (command3 === "complete") {
8403
+ if (command2 === "complete") {
7298
8404
  const projects = positionals(args);
7299
8405
  if (projects.length > 1) throw new CliError("complete accepts at most one project directory");
7300
8406
  const project = projects[0] ?? ".";
@@ -7309,7 +8415,7 @@ async function runCli(argv, runtime) {
7309
8415
  if (exitCode !== 0) return exitCode;
7310
8416
  return 0;
7311
8417
  }
7312
- if (command3 === "init") {
8418
+ if (command2 === "init") {
7313
8419
  const projects = positionals(args, ["--target"]);
7314
8420
  if (projects.length > 1) throw new Error("init accepts at most one Android project directory");
7315
8421
  if (!runtime.templatesRoot) throw new Error(`Cannot locate bundled HarmonyOS template (templates/ohos) from ${runtime.entryFile}`);
@@ -7335,28 +8441,26 @@ async function runCli(argv, runtime) {
7335
8441
  json(observed(project, "init", { ...result, signature }, { target: optionValue(args, "--target") ?? null }, guardResult));
7336
8442
  return 0;
7337
8443
  }
7338
- if (command3 === "upgrade") {
8444
+ if (command2 === "upgrade") {
7339
8445
  if (args.length > 0) throw new CliError("upgrade does not accept arguments");
7340
8446
  json(upgradeCli({ packageRoot: runtime.packageRoot }));
7341
8447
  return 0;
7342
8448
  }
7343
- if (command3 === "config") {
8449
+ if (command2 === "config") {
7344
8450
  const action = args.shift();
7345
8451
  if (!(/* @__PURE__ */ new Set(["init", "validate"])).has(action)) throw new CliError("config requires init or validate");
7346
- const projects = positionals(args, ["--harmony-project", "--android-build", "--android-test"]);
8452
+ const projects = positionals(args, ["--harmony-project"]);
7347
8453
  if (projects.length > 1) throw new CliError("config accepts at most one project directory");
7348
8454
  const project = projects[0] ?? ".";
7349
8455
  const guardResult = guard(`config:${action}`, project);
7350
8456
  const result = action === "init" ? initMigrationConfig({
7351
8457
  project,
7352
- harmonyProject: optionValue(args, "--harmony-project"),
7353
- androidBuild: optionValue(args, "--android-build"),
7354
- androidTest: optionValue(args, "--android-test")
8458
+ harmonyProject: optionValue(args, "--harmony-project")
7355
8459
  }) : validateMigrationConfig(project);
7356
8460
  json(observed(project, `config:${action}`, result, null, guardResult));
7357
8461
  return action === "validate" && !result.valid ? 1 : 0;
7358
8462
  }
7359
- if (command3 === "preflight") {
8463
+ if (command2 === "preflight") {
7360
8464
  const projects = positionals(args, ["--sdk-path", "--timeout-ms"]);
7361
8465
  if (projects.length > 1) throw new CliError("preflight accepts at most one project directory");
7362
8466
  if (!runtime.templatesRoot) throw new Error("Cannot locate bundled HarmonyOS template");
@@ -7368,7 +8472,7 @@ async function runCli(argv, runtime) {
7368
8472
  json(observed(project, "preflight", result, { initializeHarmony: !args.includes("--no-init-harmony") }, guardResult));
7369
8473
  return result.status === "passed" ? 0 : 1;
7370
8474
  }
7371
- if (command3 === "analyze") {
8475
+ if (command2 === "analyze") {
7372
8476
  const target = args.shift();
7373
8477
  if (!(/* @__PURE__ */ new Set(["android", "harmony", "all"])).has(target)) throw new CliError("analyze target must be android, harmony, or all");
7374
8478
  const projects = positionals(args);
@@ -7401,7 +8505,7 @@ async function runCli(argv, runtime) {
7401
8505
  }
7402
8506
  return 0;
7403
8507
  }
7404
- if (command3 === "report") {
8508
+ if (command2 === "report") {
7405
8509
  const explicitAction = ["init", "validate", "render", "update"].includes(args[0] ?? "") ? args.shift() : null;
7406
8510
  const action = explicitAction ?? "render";
7407
8511
  const projects = positionals(args, ["--ref", "--status", "--harmony-file", "--android-source", "--evidence", "--difference"]);
@@ -7464,19 +8568,19 @@ async function runCli(argv, runtime) {
7464
8568
  }
7465
8569
  throw new Error("report requires init, validate, or render");
7466
8570
  }
7467
- if (command3 === "task") {
8571
+ if (command2 === "task") {
7468
8572
  const action = args.shift();
7469
8573
  if (!(/* @__PURE__ */ new Set(["plan", "status", "next", "show", "validate"])).has(action)) throw new CliError("task requires plan, status, next, show, or validate");
7470
8574
  if (action === "show") {
7471
8575
  const values = positionals(args, ["--cursor", "--max-source-context"]);
7472
- if (values.length < 1 || values.length > 2) throw new CliError("task show requires [project] <task-id>");
8576
+ if (values.length < 1 || values.length > 2) throw new CliError("task show requires [project] <task-or-package-id>");
7473
8577
  const project2 = values.length === 2 ? values[0] : ".";
7474
- const taskId = values.at(-1);
7475
- if (!/^task:[a-f0-9]{16}$/.test(taskId)) throw new CliError("task show requires a valid task id");
8578
+ const targetId = values.at(-1);
8579
+ if (!/^(task|package):[a-f0-9]{16}$/.test(targetId)) throw new CliError("task show requires a valid Resolution Task or Page Work Package id");
7476
8580
  const maxSourceContext = Number(optionValue(args, "--max-source-context") ?? 25);
7477
8581
  if (!Number.isInteger(maxSourceContext) || maxSourceContext <= 0 || maxSourceContext > 200) throw new CliError("--max-source-context must be an integer from 1 to 200");
7478
8582
  const snapshot2 = inspectMigration(project2);
7479
- json(taskPlanning.materialize(project2, taskId, {
8583
+ json(taskPlanning.materialize(project2, targetId, {
7480
8584
  source: {
7481
8585
  androidFingerprint: snapshot2.source.androidFingerprint,
7482
8586
  harmonyFingerprint: snapshot2.source.harmonyFingerprint,
@@ -7498,8 +8602,13 @@ async function runCli(argv, runtime) {
7498
8602
  const result = {
7499
8603
  status: planning.valid && planning.fresh ? "passed" : "failed",
7500
8604
  taskPlanVersion: plan.taskPlanVersion,
8605
+ executionPlanVersion: plan.executionPlanVersion,
8606
+ planningPhase: plan.planningPhase,
7501
8607
  taskPlanSha256: planning.taskPlanSha256,
7502
8608
  totalTasks: plan.tasks.length,
8609
+ behaviorResponsibilities: plan.behaviorResponsibilities.length,
8610
+ pageWorkPackages: plan.pageWorkPackages.length,
8611
+ executionTargets: plan.planningPhase === "resolution" ? plan.tasks.length : plan.pageWorkPackages.length,
7503
8612
  resolutionTasks: plan.tasks.filter((task) => task.type === "resolution").length,
7504
8613
  errors: planning.errors,
7505
8614
  warnings: planning.warnings
@@ -7525,19 +8634,19 @@ async function runCli(argv, runtime) {
7525
8634
  }
7526
8635
  const allComplete = snapshot.totalTasks > 0 && snapshot.completedTasks === snapshot.totalTasks;
7527
8636
  json({
7528
- status: snapshot.recommendedTaskId ? "ready" : (() => {
8637
+ status: snapshot.recommendedTargetId ? "ready" : (() => {
7529
8638
  if (allComplete) return "complete";
7530
8639
  return "blocked";
7531
8640
  })(),
7532
- recommended: snapshot.recommendedTaskId,
8641
+ recommended: snapshot.recommendedTargetId,
7533
8642
  alternatives: snapshot.alternatives,
7534
8643
  reasons: snapshot.reasons
7535
8644
  });
7536
- return snapshot.recommendedTaskId || allComplete ? 0 : 1;
8645
+ return snapshot.recommendedTargetId || allComplete ? 0 : 1;
7537
8646
  }
7538
- if (command3 === "build") {
8647
+ if (command2 === "build") {
7539
8648
  const target = args.shift();
7540
- if (!(/* @__PURE__ */ new Set(["android", "harmony", "all"])).has(target)) throw new CliError("build target must be android, harmony, or all");
8649
+ if (target !== "harmony") throw new CliError("build target must be harmony (Android \u6784\u5EFA\u5DF2\u79FB\u9664\uFF1A\u8FC1\u79FB\u6D41\u7A0B\u4E0D\u4F9D\u8D56 Android \u7F16\u8BD1\u73AF\u5883)");
7541
8650
  const projects = positionals(args, ["--timeout-ms"]);
7542
8651
  if (projects.length > 1) throw new CliError("build accepts at most one project directory");
7543
8652
  const projectRoot = path36.resolve(projects[0] ?? ".");
@@ -7545,52 +8654,29 @@ async function runCli(argv, runtime) {
7545
8654
  if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) throw new CliError("--timeout-ms must be a positive number");
7546
8655
  const configPath = path36.join(migrationDirectory(projectRoot), "config.json");
7547
8656
  if (!existsSync9(configPath)) throw new Error(`Migration config not found: ${configPath}`);
7548
- const keys = target === "all" ? ["androidBuild", "harmonyBuild"] : [`${target}Build`];
7549
- for (const key of keys) {
7550
- const action = key === "harmonyBuild" ? "build:harmony" : "build:android";
7551
- const guardResult = guard(action, projectRoot);
7552
- const config = JSON.parse(readFileSync7(configPath, "utf8"));
7553
- const configuredHarmony = config.harmonyProject ?? defaultHarmonyProjectPath(projectRoot);
7554
- const harmonyProject = path36.isAbsolute(configuredHarmony) ? configuredHarmony : path36.resolve(projectRoot, configuredHarmony);
7555
- let result;
7556
- try {
7557
- result = key === "harmonyBuild" ? runHarmonyBuild({ projectRoot, harmonyProject, timeoutMs }) : runConfigCommand({ configPath, key, timeoutMs });
7558
- } catch (error) {
7559
- result = { status: "failed", key, summary: error?.message ?? String(error) };
7560
- }
7561
- if (key === "androidBuild" && result?.status === "not_applicable") {
7562
- result = {
7563
- ...result,
7564
- summary: 'androidBuild \u672A\u914D\u7F6E\uFF08\u8FC1\u79FB\u9ED8\u8BA4\u4E0D\u7F16\u8BD1 Android \u5DE5\u7A0B\uFF09\u3002\u5982\u9700 Android/HarmonyOS \u53CC\u7AEF\u8FD0\u884C\u5BF9\u7167\uFF0C\u53EF\u8FD0\u884C droid2hmos config init <project> --android-build "./gradlew --no-daemon assembleDebug" \u663E\u5F0F\u542F\u7528\u3002'
7565
- };
7566
- }
7567
- result = observed(projectRoot, action, result, null, guardResult);
7568
- json(result);
7569
- if (result.status !== "passed") return 1;
8657
+ const guardResult = guard("build:harmony", projectRoot);
8658
+ const config = JSON.parse(readFileSync7(configPath, "utf8"));
8659
+ const configuredHarmony = config.harmonyProject ?? defaultHarmonyProjectPath(projectRoot);
8660
+ const harmonyProject = path36.isAbsolute(configuredHarmony) ? configuredHarmony : path36.resolve(projectRoot, configuredHarmony);
8661
+ let result;
8662
+ try {
8663
+ result = runHarmonyBuild({ projectRoot, harmonyProject, timeoutMs });
8664
+ } catch (error) {
8665
+ result = { status: "failed", key: "harmonyBuild", summary: error?.message ?? String(error) };
7570
8666
  }
7571
- return 0;
8667
+ result = observed(projectRoot, "build:harmony", result, null, guardResult);
8668
+ json(result);
8669
+ return result.status === "passed" ? 0 : 1;
7572
8670
  }
7573
- if (command3 === "test") {
8671
+ if (command2 === "test") {
7574
8672
  const target = args.shift();
7575
- if (!(/* @__PURE__ */ new Set(["android", "harmony"])).has(target)) throw new CliError("test target must be android or harmony");
7576
- const projects = positionals(args, ["--timeout-ms", "--case-timeout-ms", "--class", "--case", "--task", "--target", "--ref", "--runner", "--hypium-project", "--test-list", "--python", "--report-path"]);
8673
+ if (target !== "harmony") throw new CliError("test target must be harmony (Android \u6D4B\u8BD5\u5DF2\u79FB\u9664\uFF1A\u8FC1\u79FB\u6D41\u7A0B\u4E0D\u4F9D\u8D56 Android \u8FD0\u884C\u73AF\u5883)");
8674
+ const projects = positionals(args, ["--timeout-ms", "--case-timeout-ms", "--class", "--case", "--target", "--ref", "--runner", "--hypium-project", "--test-list", "--python", "--report-path"]);
7577
8675
  if (projects.length > 1) throw new CliError("test accepts at most one project directory");
7578
8676
  const projectRoot = path36.resolve(projects[0] ?? ".");
7579
8677
  const timeoutMs = Number(optionValue(args, "--timeout-ms") ?? 30 * 60 * 1e3);
7580
8678
  const harmonyTarget = optionValue(args, "--target") ?? null;
7581
8679
  if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) throw new CliError("--timeout-ms must be a positive number");
7582
- if (target === "android") {
7583
- const guardResult2 = guard("test:android", projectRoot);
7584
- if (args.includes("--runner")) throw new CliError("--runner is only supported by test harmony");
7585
- const configPath2 = path36.join(migrationDirectory(projectRoot), "config.json");
7586
- if (!existsSync9(configPath2)) throw new Error(`Migration config not found: ${configPath2}`);
7587
- const task = optionValue(args, "--task");
7588
- if (task && !/^[A-Za-z0-9_:.-]+$/.test(task)) throw new CliError("--task contains unsupported characters");
7589
- let result2 = runConfigCommand({ configPath: configPath2, key: "androidTest", timeoutMs, replacements: task ? { "{task}": task } : {}, requiredTokens: task ? ["{task}"] : [] });
7590
- result2 = observed(projectRoot, "test:android", result2, { task: task ?? null }, guardResult2);
7591
- json(result2);
7592
- return result2.status === "passed" || result2.status === "not_applicable" ? 0 : 1;
7593
- }
7594
8680
  const runner = optionValue(args, "--runner") ?? "arkts";
7595
8681
  if (!(/* @__PURE__ */ new Set(["arkts", "python"])).has(runner)) throw new CliError("--runner must be arkts or python");
7596
8682
  const testClass = optionValue(args, "--class") ?? null;
@@ -7651,7 +8737,7 @@ async function runCli(argv, runtime) {
7651
8737
  json(result);
7652
8738
  return result.status === "passed" ? 0 : 1;
7653
8739
  }
7654
- if (command3 === "checkpoint") {
8740
+ if (command2 === "checkpoint") {
7655
8741
  const action = args.shift();
7656
8742
  if (!(/* @__PURE__ */ new Set(["status", "create"])).has(action)) throw new CliError("checkpoint requires status or create");
7657
8743
  const projects = positionals(args, ["--message", "--include"]);
@@ -7688,16 +8774,16 @@ async function runCli(argv, runtime) {
7688
8774
  json(createGitCheckpoint({ project, harmonyProject: roots.harmonyRoot, message: optionValue(args, "--message") ?? "", include: optionValues(args, "--include"), all: args.includes("--all"), provenanceReceipt, provenanceFiles }));
7689
8775
  return 0;
7690
8776
  }
7691
- if (command3 === "device") {
8777
+ if (command2 === "device") {
7692
8778
  if (args.shift() !== "check") throw new CliError("device requires check");
7693
8779
  const platform = args.shift();
7694
- if (!(/* @__PURE__ */ new Set(["android", "harmony"])).has(platform)) throw new CliError("device check requires android or harmony");
8780
+ if (platform !== "harmony") throw new CliError("device check requires harmony (Android \u8BBE\u5907\u68C0\u6D4B\u5DF2\u79FB\u9664)");
7695
8781
  guard("device:check", ".");
7696
- const result = platform === "android" ? checkAndroidEmulator({ adb: optionValue(args, "--adb") }) : checkHarmonyDevice({ hdc: optionValue(args, "--hdc") });
8782
+ const result = checkHarmonyDevice({ hdc: optionValue(args, "--hdc") });
7697
8783
  json(result);
7698
8784
  return result.status === "failed" ? 1 : 0;
7699
8785
  }
7700
- if (command3 === "auth") {
8786
+ if (command2 === "auth") {
7701
8787
  if (args.shift() !== "login") throw new CliError("auth requires login");
7702
8788
  const projects = positionals(args, ["--timeout-ms"]);
7703
8789
  if (projects.length > 1) throw new CliError("auth accepts at most one project directory");
@@ -7712,7 +8798,7 @@ async function runCli(argv, runtime) {
7712
8798
  json(result);
7713
8799
  return result.status === "passed" ? 0 : 1;
7714
8800
  }
7715
- if (command3 === "sign") {
8801
+ if (command2 === "sign") {
7716
8802
  const projects = positionals(args, ["--target", "--timeout-ms"]);
7717
8803
  if (projects.length > 1) throw new CliError("sign accepts at most one project directory");
7718
8804
  const project = projects[0] ?? ".";
@@ -7734,7 +8820,7 @@ async function runCli(argv, runtime) {
7734
8820
  json(result);
7735
8821
  return result.status === "passed" || result.status === "skipped" ? 0 : 1;
7736
8822
  }
7737
- if (command3 === "evidence") {
8823
+ if (command2 === "evidence") {
7738
8824
  const action = args.shift();
7739
8825
  if (!(/* @__PURE__ */ new Set(["capture", "compare", "flow"])).has(action)) throw new CliError("evidence requires capture, compare, or flow");
7740
8826
  if (action === "compare") {
@@ -7749,8 +8835,8 @@ async function runCli(argv, runtime) {
7749
8835
  return 0;
7750
8836
  }
7751
8837
  const platform = args.shift();
7752
- if (!(/* @__PURE__ */ new Set(["android", "harmony"])).has(platform)) throw new CliError(`evidence ${action} requires android or harmony`);
7753
- const projects = positionals(args, ["--name", "--target", "--class", "--case", "--task", "--timeout-ms", "--case-timeout-ms", "--ref"]);
8838
+ if (platform !== "harmony") throw new CliError(`evidence ${action} requires harmony (Android \u8BC1\u636E\u91C7\u96C6\u5DF2\u79FB\u9664)`);
8839
+ const projects = positionals(args, ["--name", "--target", "--class", "--case", "--timeout-ms", "--case-timeout-ms", "--ref"]);
7754
8840
  const project = projects[0] ?? ".";
7755
8841
  const name = optionValue(args, "--name");
7756
8842
  if (!name) throw new CliError(`evidence ${action} requires --name`);
@@ -7766,11 +8852,7 @@ async function runCli(argv, runtime) {
7766
8852
  const timeoutMs = Number(optionValue(args, "--timeout-ms") ?? 30 * 60 * 1e3);
7767
8853
  let execution;
7768
8854
  if (before.status !== "passed") execution = { status: "failed", skipped: true, summary: "Baseline evidence capture failed; interaction was not executed" };
7769
- else if (platform === "android") {
7770
- const task = optionValue(args, "--task");
7771
- if (task && !/^[A-Za-z0-9_:.-]+$/.test(task)) throw new CliError("--task contains unsupported characters");
7772
- execution = runConfigCommand({ configPath: path36.join(migrationDirectory(projectRoot), "config.json"), key: "androidTest", timeoutMs, replacements: task ? { "{task}": task } : {}, requiredTokens: task ? ["{task}"] : [] });
7773
- } else {
8855
+ else {
7774
8856
  const config = JSON.parse(readFileSync7(path36.join(migrationDirectory(projectRoot), "config.json"), "utf8"));
7775
8857
  const harmonyProject = path36.isAbsolute(config.harmonyProject) ? config.harmonyProject : path36.resolve(projectRoot, config.harmonyProject);
7776
8858
  const testClass = optionValue(args, "--class") ?? null;
@@ -7782,7 +8864,7 @@ async function runCli(argv, runtime) {
7782
8864
  }
7783
8865
  const after = captureDeviceEvidence({ project, platform, name: `${name}-after`, target: optionValue(args, "--target") });
7784
8866
  let result = writeFlowBundle({ project, name, platform, before, execution, after });
7785
- if (platform === "harmony") {
8867
+ {
7786
8868
  const testAction = args.includes("--all") ? "test:harmony:all" : "test:harmony:targeted";
7787
8869
  result = observed(project, testAction, result, {
7788
8870
  evidenceFlow: name,
@@ -7797,7 +8879,7 @@ async function runCli(argv, runtime) {
7797
8879
  json(result);
7798
8880
  return result.status === "passed" ? 0 : 1;
7799
8881
  }
7800
- if (command3 === "mock") {
8882
+ if (command2 === "mock") {
7801
8883
  if (args.shift() !== "init") throw new CliError("mock requires init");
7802
8884
  const projects = positionals(args, ["--spec"]);
7803
8885
  const specFile = optionValue(args, "--spec");
@@ -7807,14 +8889,14 @@ async function runCli(argv, runtime) {
7807
8889
  json(generateMockServer({ project, specFile, force: args.includes("--force") }));
7808
8890
  return 0;
7809
8891
  }
7810
- if (command3 === "logs") {
8892
+ if (command2 === "logs") {
7811
8893
  const action = args.shift();
7812
8894
  if (!(/* @__PURE__ */ new Set(["list", "show", "failures", "stats"])).has(action)) throw new CliError("logs requires list, show, failures, or stats");
7813
8895
  const project = args.shift() ?? ".";
7814
8896
  json(queryCommandLogs(project, action, args.shift()));
7815
8897
  return 0;
7816
8898
  }
7817
- if (command3 === "service") {
8899
+ if (command2 === "service") {
7818
8900
  const action = args.shift();
7819
8901
  if (!(/* @__PURE__ */ new Set(["start", "status", "stop"])).has(action)) throw new CliError("service action must be start, status, or stop");
7820
8902
  const name = args.shift();
@@ -7858,7 +8940,7 @@ async function runCli(argv, runtime) {
7858
8940
  }));
7859
8941
  return 0;
7860
8942
  }
7861
- if (command3 === "internal") {
8943
+ if (command2 === "internal") {
7862
8944
  const action = args.shift();
7863
8945
  if (action === "detect-harmony-sdk") {
7864
8946
  const projects = positionals(args, ["--sdk-path"]);
@@ -7890,14 +8972,14 @@ async function runCli(argv, runtime) {
7890
8972
  }
7891
8973
  throw new Error("unknown internal command");
7892
8974
  }
7893
- if (command3 === "dev") {
8975
+ if (command2 === "dev") {
7894
8976
  const action = args.shift();
7895
8977
  if (!runtime.packageRoot || action !== "build") throw new Error("dev requires build from a source checkout");
7896
8978
  const result = spawnCommandSync("npm", ["run", "build"], { cwd: runtime.packageRoot, stdio: "inherit" });
7897
8979
  if (result.error) throw result.error;
7898
8980
  return result.status ?? 1;
7899
8981
  }
7900
- throw new CliError(`Unknown command: ${command3}`);
8982
+ throw new CliError(`Unknown command: ${command2}`);
7901
8983
  }
7902
8984
  var entryHref = import.meta.url;
7903
8985
  var invokedHref = (() => {