@harmonyos-arkts/droid2hmos 0.0.7 → 0.0.8

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.
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";
@@ -2498,8 +2403,8 @@ function devEcoCliSpawn(executable, args) {
2498
2403
  return scriptWrapper ? [process.execPath, executable, ...args] : [executable, ...args];
2499
2404
  }
2500
2405
  function runDevEcoCli(args, { executable = executableName(), runner = spawnCommandSync, timeoutMs = 15e3 } = {}, cwd) {
2501
- const [command3, ...commandArgs] = devEcoCliSpawn(executable, args);
2502
- const result = runner(command3, commandArgs, {
2406
+ const [command2, ...commandArgs] = devEcoCliSpawn(executable, args);
2407
+ const result = runner(command2, commandArgs, {
2503
2408
  cwd,
2504
2409
  encoding: "utf8",
2505
2410
  timeout: timeoutMs,
@@ -2517,8 +2422,8 @@ function runDevEcoCli(args, { executable = executableName(), runner = spawnComma
2517
2422
  };
2518
2423
  }
2519
2424
  async function runDevEcoCliAsync(args, { executable = executableName(), timeoutMs = 15e3 } = {}, cwd) {
2520
- const [command3, ...commandArgs] = devEcoCliSpawn(executable, args);
2521
- const child = spawnCommand(command3, commandArgs, {
2425
+ const [command2, ...commandArgs] = devEcoCliSpawn(executable, args);
2426
+ const child = spawnCommand(command2, commandArgs, {
2522
2427
  cwd,
2523
2428
  windowsHide: true,
2524
2429
  detached: process.platform !== "win32",
@@ -2773,8 +2678,8 @@ function isRealDeviceTarget(target) {
2773
2678
  }
2774
2679
  return true;
2775
2680
  }
2776
- function readSigningMaterialFingerprints(harmonyRoot) {
2777
- const profile = path14.join(harmonyRoot, "build-profile.json5");
2681
+ function readSigningMaterialFingerprints(harmonyRoot2) {
2682
+ const profile = path14.join(harmonyRoot2, "build-profile.json5");
2778
2683
  if (!fs13.existsSync(profile)) return [];
2779
2684
  let parsed;
2780
2685
  try {
@@ -2783,7 +2688,7 @@ function readSigningMaterialFingerprints(harmonyRoot) {
2783
2688
  return [];
2784
2689
  }
2785
2690
  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)))];
2691
+ 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
2692
  return [...new Set(files)].flatMap((file) => {
2788
2693
  if (!fs13.existsSync(file)) return [];
2789
2694
  const stat = fs13.statSync(file);
@@ -2900,10 +2805,10 @@ function detectDeviceSdkVersion(target) {
2900
2805
  diverged: systemAbc !== null && systemAbc.major < claimedApiLevel
2901
2806
  };
2902
2807
  }
2903
- function ensureDeviceInstallable(harmonyRoot, target) {
2808
+ function ensureDeviceInstallable(harmonyRoot2, target) {
2904
2809
  const device = detectDeviceSdkVersion(target);
2905
2810
  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);
2811
+ const capped = capCompatibleSdkVersionTo(harmonyRoot2, device.sdkVersion);
2907
2812
  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
2813
  return {
2909
2814
  ...capped,
@@ -2994,10 +2899,10 @@ function readJson5(file) {
2994
2899
  function firstExisting(values) {
2995
2900
  return values.find((value) => Boolean(value && existsSync4(value)));
2996
2901
  }
2997
- function pathLookup(command3) {
2902
+ function pathLookup(command2) {
2998
2903
  try {
2999
2904
  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);
2905
+ return execFileSync(lookup, [command2], { encoding: "utf8", windowsHide: true }).split(/\r?\n/).map((value) => value.trim()).find(Boolean);
3001
2906
  } catch {
3002
2907
  return null;
3003
2908
  }
@@ -3158,8 +3063,8 @@ function runHarmonyTest({ projectRoot, harmonyProject, timeoutMs, testClass = nu
3158
3063
  if (appBuild.status !== "passed") return { status: "failed", key: "harmonyTest", stage: "app-build", appBuild };
3159
3064
  const ohpmInstall = ensureOhpmDependencies({ projectRoot, harmonyProject, timeoutMs });
3160
3065
  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 });
3066
+ const { info, command: command2 } = buildHarmonyTestCommand({ projectRoot, harmonyProject });
3067
+ const testBuild = runFiniteCommand({ command: command2, key: "harmonyTest-build", cwd: info.root, projectRoot, timeoutMs });
3163
3068
  if (testBuild.status !== "passed") return { status: "failed", key: "harmonyTest", stage: "test-build", appBuild, testBuild };
3164
3069
  const testArtifact = newestHap(info.moduleRoot, (name) => name.toLowerCase().includes(info.testTarget.toLowerCase()));
3165
3070
  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 +3250,8 @@ function detectHarmonySdk({ project, sdkPath, writeConfig = false, env = process
3345
3250
  push(sdkPath, "explicit");
3346
3251
  push(config?.harmonySdk?.path, "existing-config");
3347
3252
  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");
3253
+ const harmonyRoot2 = path17.isAbsolute(configuredHarmony) ? configuredHarmony : path17.resolve(projectRoot, configuredHarmony);
3254
+ const localProperties = path17.join(harmonyRoot2, "local.properties");
3350
3255
  if (fs14.existsSync(localProperties)) {
3351
3256
  const properties = parseProperties(fs14.readFileSync(localProperties, "utf8"));
3352
3257
  push(properties["sdk.dir"] ?? properties["harmony.sdk.dir"] ?? properties["ohos.sdk.dir"], "local-properties");
@@ -3485,22 +3390,19 @@ var atomicJson = (file, value) => {
3485
3390
  `, "utf8");
3486
3391
  fs15.renameSync(temporary, file);
3487
3392
  };
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;
3393
+ function normalizeAndroidBuildCommand(command2, platform = process.platform) {
3394
+ if (typeof command2 !== "string") return command2;
3493
3395
  if (platform === "win32") {
3494
- return command3.replace(/^(\s*)(?:&\s*)?["']?\.\/gradlew["']?(?=\s|$)/i, "$1.\\gradlew.bat");
3396
+ return command2.replace(/^(\s*)(?:&\s*)?["']?\.\/gradlew["']?(?=\s|$)/i, "$1.\\gradlew.bat");
3495
3397
  }
3496
- return command3.replace(/^(\s*)(?:&\s*)?["']?(?:\.\\gradlew(?:\.bat)?|gradlew\.bat)["']?(?=\s|$)/i, "$1./gradlew");
3398
+ return command2.replace(/^(\s*)(?:&\s*)?["']?(?:\.\\gradlew(?:\.bat)?|gradlew\.bat)["']?(?=\s|$)/i, "$1./gradlew");
3497
3399
  }
3498
- function validateAndroidBuildPlatform(command3, platform = process.platform) {
3499
- if (typeof command3 !== "string") return null;
3500
- if (platform === "win32" && /(?:^|\s)\.\/gradlew(?:\s|$)/i.test(command3)) {
3400
+ function validateAndroidBuildPlatform(command2, platform = process.platform) {
3401
+ if (typeof command2 !== "string") return null;
3402
+ if (platform === "win32" && /(?:^|\s)\.\/gradlew(?:\s|$)/i.test(command2)) {
3501
3403
  return "commands.androidBuild uses a Unix Gradle wrapper on Windows; use .\\gradlew.bat";
3502
3404
  }
3503
- if (platform !== "win32" && /(?:gradlew\.bat|\.\\gradlew)(?:\s|$)/i.test(command3)) {
3405
+ if (platform !== "win32" && /(?:gradlew\.bat|\.\\gradlew)(?:\s|$)/i.test(command2)) {
3504
3406
  return `commands.androidBuild uses a Windows Gradle wrapper on ${platform}; use ./gradlew`;
3505
3407
  }
3506
3408
  return null;
@@ -3522,12 +3424,12 @@ function validateMigrationConfigValue(config, projectRoot) {
3522
3424
  if (path19.isAbsolute(config.androidProject)) return config.androidProject;
3523
3425
  return path19.resolve(projectRoot, config.androidProject);
3524
3426
  })() : null;
3525
- const harmonyRoot = config?.harmonyProject ? (() => {
3427
+ const harmonyRoot2 = config?.harmonyProject ? (() => {
3526
3428
  if (path19.isAbsolute(config.harmonyProject)) return config.harmonyProject;
3527
3429
  return path19.resolve(projectRoot, config.harmonyProject);
3528
3430
  })() : null;
3529
3431
  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 };
3432
+ return { valid: errors.length === 0, errors, projectRoot, androidRoot, harmonyRoot: harmonyRoot2 };
3531
3433
  }
3532
3434
  function initMigrationConfig(options) {
3533
3435
  const projectRoot = path19.resolve(options.project);
@@ -3536,7 +3438,6 @@ function initMigrationConfig(options) {
3536
3438
  const configPath = path19.join(migrationDirectory(projectRoot), "config.json");
3537
3439
  const created = !fs15.existsSync(configPath);
3538
3440
  const existing = created ? {} : JSON.parse(fs15.readFileSync(configPath, "utf8"));
3539
- const configuredAndroidBuild = options.androidBuild ?? existing.commands?.androidBuild ?? defaultAndroidBuild(projectRoot);
3540
3441
  const config = {
3541
3442
  ...existing,
3542
3443
  configVersion: existing.configVersion ?? "1.0.0",
@@ -3545,8 +3446,8 @@ function initMigrationConfig(options) {
3545
3446
  ...existing.harmonySdk ? { harmonySdk: existing.harmonySdk } : {},
3546
3447
  commands: {
3547
3448
  ...existing.commands ?? {},
3548
- androidBuild: normalizeAndroidBuildCommand(configuredAndroidBuild),
3549
- androidTest: options.androidTest ?? existing.commands?.androidTest ?? null,
3449
+ androidBuild: normalizeAndroidBuildCommand(existing.commands?.androidBuild ?? null),
3450
+ androidTest: existing.commands?.androidTest ?? null,
3550
3451
  harmonyStatic: existing.commands?.harmonyStatic ?? null
3551
3452
  }
3552
3453
  };
@@ -3570,7 +3471,7 @@ function validateMigrationConfig(project) {
3570
3471
 
3571
3472
  // src/commands/preflight.ts
3572
3473
  import fs20 from "fs";
3573
- import path24 from "path";
3474
+ import path23 from "path";
3574
3475
 
3575
3476
  // src/commands/detect-android-ui-stack.ts
3576
3477
  import fs16 from "fs";
@@ -3641,69 +3542,13 @@ function detectAndroidUiStack(projectRoot) {
3641
3542
  };
3642
3543
  }
3643
3544
 
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
3545
  // src/commands/git-checkpoint.ts
3701
3546
  import path21 from "path";
3702
3547
  import fs17 from "fs";
3703
3548
  import { createHash as createHash5 } from "crypto";
3704
- import { spawnSync as spawnSync6 } from "child_process";
3549
+ import { spawnSync as spawnSync5 } from "child_process";
3705
3550
  function git(projectRoot, args) {
3706
- const result = spawnSync6("git", ["-C", projectRoot, ...args], { encoding: "utf8", shell: false, timeout: 3e4, windowsHide: true });
3551
+ const result = spawnSync5("git", ["-C", projectRoot, ...args], { encoding: "utf8", shell: false, timeout: 3e4, windowsHide: true });
3707
3552
  return {
3708
3553
  ok: !result.error && result.status === 0,
3709
3554
  exitCode: Number.isInteger(result.status) ? result.status : null,
@@ -3733,17 +3578,17 @@ function unstage(root, files) {
3733
3578
  if (head.ok) git(root, ["restore", "--staged", "--", ...files]);
3734
3579
  else git(root, ["rm", "--cached", "-r", "--ignore-unmatch", "--", ...files]);
3735
3580
  }
3736
- function initializeHarmonyRepository(androidRoot, harmonyRoot) {
3737
- const existing = repositoryRoot(harmonyRoot);
3581
+ function initializeHarmonyRepository(androidRoot, harmonyRoot2) {
3582
+ const existing = repositoryRoot(harmonyRoot2);
3738
3583
  if (existing) return { root: existing, initialized: false };
3739
3584
  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}`);
3585
+ const initialized = git(harmonyRoot2, ["init", "-b", androidBranch]);
3586
+ if (!initialized.ok) throw new Error(initialized.stderr || `Cannot initialize HarmonyOS Git repository: ${harmonyRoot2}`);
3742
3587
  for (const key of ["user.name", "user.email"]) {
3743
3588
  const value = git(androidRoot, ["config", "--get", key]);
3744
- if (value.ok && value.stdout) git(harmonyRoot, ["config", key, value.stdout]);
3589
+ if (value.ok && value.stdout) git(harmonyRoot2, ["config", key, value.stdout]);
3745
3590
  }
3746
- return { root: path21.resolve(harmonyRoot), initialized: true };
3591
+ return { root: path21.resolve(harmonyRoot2), initialized: true };
3747
3592
  }
3748
3593
  function relevantStatus(status, ignoreUntrackedRoots = []) {
3749
3594
  const ignored = ignoreUntrackedRoots.map((value) => value.replaceAll("\\", "/").replace(/^\.\//, "").replace(/\/$/, ""));
@@ -3756,8 +3601,8 @@ function relevantStatus(status, ignoreUntrackedRoots = []) {
3756
3601
  }
3757
3602
  function gitWorkspaceStatus(project, options = {}) {
3758
3603
  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"] };
3604
+ const inside5 = git(projectRoot, ["rev-parse", "--is-inside-work-tree"]);
3605
+ if (!inside5.ok || inside5.stdout !== "true") return { status: "failed", repository: false, projectRoot, errors: [inside5.stderr || "not a Git worktree"] };
3761
3606
  const head = git(projectRoot, ["rev-parse", "HEAD"]);
3762
3607
  const branch = git(projectRoot, ["branch", "--show-current"]);
3763
3608
  const porcelain = git(projectRoot, ["status", "--porcelain=v1", "--untracked-files=all"]);
@@ -3777,13 +3622,13 @@ function createGitCheckpoint(options) {
3777
3622
  const projectRoot = fs17.realpathSync(path21.resolve(options.project));
3778
3623
  const requestedHarmonyRoot = options.harmonyProject ? path21.resolve(options.harmonyProject) : null;
3779
3624
  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;
3625
+ const harmonyRoot2 = requestedHarmonyRoot ? fs17.realpathSync(requestedHarmonyRoot) : null;
3781
3626
  if (!options.message?.trim()) throw new Error("checkpoint create requires --message");
3782
3627
  if (!options.all && options.include.length === 0) throw new Error("checkpoint create requires at least one --include or explicit --all");
3783
3628
  const baseline = gitWorkspaceStatus(projectRoot);
3784
3629
  if (!baseline.repository) throw new Error(baseline.errors?.[0] ?? "not a Git repository");
3785
3630
  const androidRepository = repositoryRoot(projectRoot);
3786
- const harmonyRepository = harmonyRoot ? repositoryRoot(harmonyRoot) ?? initializeHarmonyRepository(androidRepository, harmonyRoot).root : androidRepository;
3631
+ const harmonyRepository = harmonyRoot2 ? repositoryRoot(harmonyRoot2) ?? initializeHarmonyRepository(androidRepository, harmonyRoot2).root : androidRepository;
3787
3632
  const repositories = [.../* @__PURE__ */ new Set([androidRepository, harmonyRepository])];
3788
3633
  for (const root of repositories) {
3789
3634
  const before = stagedFiles(root);
@@ -3796,18 +3641,18 @@ function createGitCheckpoint(options) {
3796
3641
  if (options.all) {
3797
3642
  const androidAdd = git(projectRoot, ["add", "-A", "--", ".", ":!.migration"]);
3798
3643
  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"]);
3644
+ if (harmonyRoot2 && harmonyRepository !== androidRepository) {
3645
+ const harmonyAdd = git(harmonyRoot2, ["add", "-A", "--", ".", ":!.migration"]);
3801
3646
  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));
3647
+ } else if (harmonyRoot2 && !inside2(projectRoot, harmonyRoot2)) {
3648
+ const harmonyPath = normalized(path21.relative(androidRepository, harmonyRoot2));
3804
3649
  const harmonyAdd = git(androidRepository, ["add", "-A", "--", harmonyPath]);
3805
3650
  if (!harmonyAdd.ok) throw new Error(harmonyAdd.stderr || "git add failed for HarmonyOS project");
3806
3651
  }
3807
3652
  } else {
3808
3653
  for (const value of options.include) {
3809
3654
  const absolute = path21.resolve(projectRoot, value);
3810
- const root = harmonyRoot && inside2(harmonyRoot, absolute) ? harmonyRepository : (() => {
3655
+ const root = harmonyRoot2 && inside2(harmonyRoot2, absolute) ? harmonyRepository : (() => {
3811
3656
  if (inside2(projectRoot, absolute)) return androidRepository;
3812
3657
  return null;
3813
3658
  })();
@@ -3829,7 +3674,7 @@ function createGitCheckpoint(options) {
3829
3674
  try {
3830
3675
  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
3676
  const absolute = path21.resolve(projectRoot, item.file);
3832
- const expectedRoot = item.role === "harmony-implementation" ? harmonyRoot : projectRoot;
3677
+ const expectedRoot = item.role === "harmony-implementation" ? harmonyRoot2 : projectRoot;
3833
3678
  const root = item.role === "harmony-implementation" ? harmonyRepository : androidRepository;
3834
3679
  const relative2 = normalized(path21.relative(projectRoot, absolute));
3835
3680
  if (!expectedRoot || !inside2(expectedRoot, absolute) || !fs17.existsSync(absolute) || !fs17.statSync(absolute).isFile()) {
@@ -3859,12 +3704,12 @@ function createGitCheckpoint(options) {
3859
3704
  committedFiles,
3860
3705
  repositories: {
3861
3706
  android: { root: ".", gitRoot: normalized(path21.relative(projectRoot, androidRepository)) || "." },
3862
- harmony: harmonyRoot ? { root: normalized(path21.relative(projectRoot, harmonyRoot)), gitRoot: normalized(path21.relative(projectRoot, harmonyRepository)) } : null
3707
+ harmony: harmonyRoot2 ? { root: normalized(path21.relative(projectRoot, harmonyRoot2)), gitRoot: normalized(path21.relative(projectRoot, harmonyRepository)) } : null
3863
3708
  }
3864
3709
  };
3865
3710
  }
3866
3711
  const commitResults = [];
3867
- const separateHarmony = harmonyRoot && harmonyRepository !== androidRepository;
3712
+ const separateHarmony = harmonyRoot2 && harmonyRepository !== androidRepository;
3868
3713
  if (separateHarmony) {
3869
3714
  const files2 = stagedFiles(harmonyRepository);
3870
3715
  if (files2.length) {
@@ -3886,9 +3731,9 @@ function createGitCheckpoint(options) {
3886
3731
  commitResults.push({ kind: "android", root: androidRepository, commit: git(androidRepository, ["rev-parse", "HEAD"]).stdout, files: androidFiles, summary: androidCommit.stdout });
3887
3732
  }
3888
3733
  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")) {
3734
+ if (harmonyRoot2 && harmonyRepository === androidRepository) {
3735
+ commitResults.push({ kind: "harmony", root: harmonyRepository, commit: androidCheckpoint.commit, files: androidFiles.filter((file) => inside2(harmonyRoot2, path21.resolve(harmonyRepository, file))), summary: androidCheckpoint.summary });
3736
+ } else if (harmonyRoot2 && !commitResults.some((item) => item.kind === "harmony")) {
3892
3737
  const harmonyHead = git(harmonyRepository, ["rev-parse", "HEAD"]).stdout;
3893
3738
  if (harmonyHead) commitResults.push({ kind: "harmony", root: harmonyRepository, commit: harmonyHead, files: [], summary: "HarmonyOS repository already clean" });
3894
3739
  }
@@ -3921,12 +3766,10 @@ function createGitCheckpoint(options) {
3921
3766
 
3922
3767
  // src/commands/system-checks.ts
3923
3768
  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 });
3769
+ function probe(command2, args, timeout = 15e3) {
3770
+ const result = spawnCommandSync(command2, args, { encoding: "utf8", timeout, windowsHide: true });
3928
3771
  return {
3929
- command: command3,
3772
+ command: command2,
3930
3773
  available: !result.error && result.status === 0,
3931
3774
  exitCode: Number.isInteger(result.status) ? result.status : null,
3932
3775
  output: String(result.stdout || result.stderr || result.error?.message || "").trim().split(/\r?\n/)[0] ?? ""
@@ -3935,8 +3778,7 @@ function probe(command3, args, timeout = 15e3) {
3935
3778
  function firstDirectory(values) {
3936
3779
  return values.find((value) => Boolean(value && fs18.existsSync(value) && fs18.statSync(value).isDirectory())) ?? null;
3937
3780
  }
3938
- function checkToolchain(project, env = process.env) {
3939
- const projectRoot = path22.resolve(project);
3781
+ function checkToolchain(_project, env = process.env) {
3940
3782
  const pythonCandidates = [
3941
3783
  env.PYTHON_BINARY,
3942
3784
  process.platform === "win32" ? "python" : "python3",
@@ -3956,23 +3798,14 @@ function checkToolchain(project, env = process.env) {
3956
3798
  java: probe("java", ["-version"]),
3957
3799
  appgraph: probe("appgraph", ["--version"])
3958
3800
  };
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
3801
  const devecoHome = firstDirectory([
3966
3802
  env.DEVECO_STUDIO_HOME,
3967
3803
  env.DEVECO_HOME,
3968
3804
  process.platform === "darwin" ? "/Applications/DevEco-Studio.app/Contents" : null
3969
3805
  ]);
3970
- const androidBuildEntry = ["gradlew", "gradlew.bat", "settings.gradle", "settings.gradle.kts"].map((name) => path22.join(projectRoot, name)).find((file) => fs18.existsSync(file)) ?? null;
3971
3806
  const checks = [
3972
3807
  ...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] : [] }
3808
+ { id: "deveco", status: devecoHome ? "passed" : "failed", summary: devecoHome ?? "DevEco Studio not found", evidence: devecoHome ? [devecoHome] : [] }
3976
3809
  ];
3977
3810
  const nodeMajor = Number.parseInt(process.versions.node.split(".")[0], 10);
3978
3811
  const pythonMatch = commands.python.output.match(/(\d+)\.(\d+)/);
@@ -3981,7 +3814,7 @@ function checkToolchain(project, env = process.env) {
3981
3814
  if (nodeCheck && (nodeMajor < 20 || nodeMajor > 24)) Object.assign(nodeCheck, { status: "failed", summary: `Node.js ${process.versions.node} is outside supported range 20-24` });
3982
3815
  const pythonCheck = checks.find((item) => item.id === "python");
3983
3816
  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 };
3817
+ return { status: checks.some((item) => item.status === "failed") ? "failed" : "passed", checks, commands, devecoHome };
3985
3818
  }
3986
3819
  function checkAppGraphHealth() {
3987
3820
  const version = probe("appgraph", ["--version"]);
@@ -3993,7 +3826,7 @@ function checkAppGraphHealth() {
3993
3826
 
3994
3827
  // src/commands/preflight-checklist.ts
3995
3828
  import fs19 from "fs";
3996
- import path23 from "path";
3829
+ import path22 from "path";
3997
3830
  var PREFLIGHT_CHECKS = [
3998
3831
  { id: "android-ui-stack", name: "Android UI \u6280\u672F\u6808", required: true },
3999
3832
  { id: "git", name: "Git \u4ED3\u5E93\u548C\u5DE5\u4F5C\u533A", required: true },
@@ -4027,7 +3860,7 @@ function validateChecklist(value) {
4027
3860
  return value;
4028
3861
  }
4029
3862
  function loadPreflightChecklist(file) {
4030
- const checklistPath = path23.resolve(file);
3863
+ const checklistPath = path22.resolve(file);
4031
3864
  if (!fs19.existsSync(checklistPath)) throw new Error(`Preflight checklist not found: ${checklistPath}`);
4032
3865
  let checklist;
4033
3866
  try {
@@ -4045,7 +3878,7 @@ function loadPreflightChecklist(file) {
4045
3878
 
4046
3879
  // src/commands/preflight.ts
4047
3880
  var atomicJson2 = (file, value) => {
4048
- fs20.mkdirSync(path24.dirname(file), { recursive: true });
3881
+ fs20.mkdirSync(path23.dirname(file), { recursive: true });
4049
3882
  const temporary = `${file}.${process.pid}.tmp`;
4050
3883
  fs20.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}
4051
3884
  `, "utf8");
@@ -4059,7 +3892,7 @@ function check(id, result, required = true) {
4059
3892
  return entry;
4060
3893
  }
4061
3894
  async function runPreflight(options) {
4062
- const projectRoot = path24.resolve(options.project);
3895
+ const projectRoot = path23.resolve(options.project);
4063
3896
  if (!fs20.existsSync(projectRoot) || !fs20.statSync(projectRoot).isDirectory()) throw new Error(`Android project directory not found: ${projectRoot}`);
4064
3897
  const timeoutMs = options.timeoutMs ?? 30 * 60 * 1e3;
4065
3898
  const checks = [];
@@ -4071,7 +3904,7 @@ async function runPreflight(options) {
4071
3904
  const enabled = (id) => checklist.isEnabled(id);
4072
3905
  const skip = (id) => checks.push(check(id, { status: "skipped", summary: "Skipped by preflight checklist", evidence: [checklist.checklistPath] }, required(id)));
4073
3906
  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",
3907
+ 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
3908
  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
3909
  "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
3910
  "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 +3937,12 @@ async function runPreflight(options) {
4104
3937
  let harmonyProject = defaultHarmonyProjectPath(projectRoot);
4105
3938
  let existingConfig = null;
4106
3939
  try {
4107
- existingConfig = JSON.parse(fs20.readFileSync(path24.join(migrationDirectory(projectRoot), "config.json"), "utf8"));
3940
+ existingConfig = JSON.parse(fs20.readFileSync(path23.join(migrationDirectory(projectRoot), "config.json"), "utf8"));
4108
3941
  const configuredHarmony = existingConfig.harmonyProject ?? defaultHarmonyProjectPath(projectRoot);
4109
- harmonyProject = path24.isAbsolute(configuredHarmony) ? configuredHarmony : path24.resolve(projectRoot, configuredHarmony);
3942
+ harmonyProject = path23.isAbsolute(configuredHarmony) ? configuredHarmony : path23.resolve(projectRoot, configuredHarmony);
4110
3943
  } catch {
4111
3944
  }
4112
- const relativeHarmony = path24.relative(projectRoot, harmonyProject).replaceAll("\\", "/");
3945
+ const relativeHarmony = path23.relative(projectRoot, harmonyProject).replaceAll("\\", "/");
4113
3946
  const ignoreUntrackedRoots = existingConfig && relativeHarmony && !relativeHarmony.startsWith("../") ? [relativeHarmony] : [];
4114
3947
  git2 = gitWorkspaceStatus(projectRoot, { ignoreUntrackedRoots });
4115
3948
  const hasRequiredFailures = () => checks.some((item) => item.required && item.status !== "passed" && item.status !== "skipped");
@@ -4126,7 +3959,7 @@ async function runPreflight(options) {
4126
3959
  result,
4127
3960
  checks: checks.map(({ id, status, summary, evidence: evidence2, retry }) => retry ? { id, status, summary, evidence: evidence2, retry } : { id, status, summary, evidence: evidence2 })
4128
3961
  };
4129
- const reportPath = path24.join(migrationDirectory(projectRoot), "preflight-report.json");
3962
+ const reportPath = path23.join(migrationDirectory(projectRoot), "preflight-report.json");
4130
3963
  atomicJson2(reportPath, report);
4131
3964
  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
3965
  `);
@@ -4174,7 +4007,7 @@ async function runPreflight(options) {
4174
4007
  let configInit;
4175
4008
  if (enabled("config")) displayGate("config");
4176
4009
  try {
4177
- const existingConfigPath = path24.join(migrationDirectory(projectRoot), "config.json");
4010
+ const existingConfigPath = path23.join(migrationDirectory(projectRoot), "config.json");
4178
4011
  configInit = !enabled("config") && fs20.existsSync(existingConfigPath) ? { configPath: existingConfigPath, config: JSON.parse(fs20.readFileSync(existingConfigPath, "utf8")) } : initMigrationConfig({ project: projectRoot });
4179
4012
  } catch (error) {
4180
4013
  checks.push(check("config", { status: "failed", summary: error instanceof Error ? error.message : String(error) }));
@@ -4182,26 +4015,27 @@ async function runPreflight(options) {
4182
4015
  }
4183
4016
  const configPath = configInit.configPath;
4184
4017
  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);
4018
+ const configuredHarmonyProject = typeof config.harmonyProject === "string" ? config.harmonyProject : path23.relative(projectRoot, defaultHarmonyProjectPath(projectRoot));
4019
+ harmonyProject = path23.isAbsolute(configuredHarmonyProject) ? configuredHarmonyProject : path23.resolve(projectRoot, configuredHarmonyProject);
4187
4020
  if (enabled("config")) checks.push(check("config", { ...validateMigrationConfig(projectRoot), summary: "Migration config initialized and validated", evidence: [configPath] }));
4188
4021
  else skip("config");
4189
4022
  if (hasRequiredFailures()) return finish();
4190
4023
  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 {
4024
+ else {
4195
4025
  displayGate("android-build");
4196
4026
  checks.push(check("android-build", {
4197
4027
  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"
4028
+ 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
4029
  }, required("android-build")));
4200
4030
  }
4201
- if (enabled("android-emulator")) {
4031
+ if (!enabled("android-emulator")) skip("android-emulator");
4032
+ else {
4202
4033
  displayGate("android-emulator");
4203
- checks.push(check("android-emulator", checkAndroidEmulator(), false));
4204
- } else skip("android-emulator");
4034
+ checks.push(check("android-emulator", {
4035
+ status: "not_applicable",
4036
+ summary: "Android \u6A21\u62DF\u5668\u68C0\u6D4B\u5DF2\u79FB\u9664\uFF08\u8FC1\u79FB\u6D41\u7A0B\u4E0D\u4F9D\u8D56 Android \u8FD0\u884C\u73AF\u5883\uFF09"
4037
+ }, required("android-emulator")));
4038
+ }
4205
4039
  if (hasRequiredFailures()) return finish();
4206
4040
  if (enabled("harmony-project")) {
4207
4041
  displayGate("harmony-project");
@@ -4240,9 +4074,9 @@ async function runPreflight(options) {
4240
4074
  }
4241
4075
 
4242
4076
  // src/commands/deveco-auth-login.ts
4243
- import { spawn as spawn3, spawnSync as spawnSync7 } from "child_process";
4077
+ import { spawn as spawn3, spawnSync as spawnSync6 } from "child_process";
4244
4078
  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";
4079
+ import path24 from "path";
4246
4080
  import { setTimeout as delay2 } from "timers/promises";
4247
4081
  var MANAGED_LOGIN_SERVICE_NAME = "devecocli-auth";
4248
4082
  var LOGIN_URL_PATTERN = /https?:\/\/[^\s"'<>]+/i;
@@ -4251,13 +4085,13 @@ function isDevEcoCliLoginCommand(commandLine) {
4251
4085
  return DEVECO_LOGIN_COMMAND_PATTERN.test(commandLine);
4252
4086
  }
4253
4087
  function managedLoginStatePaths(project) {
4254
- const directory = path25.join(migrationDirectory(project), "services");
4255
- const pidFile = path25.join(directory, `${MANAGED_LOGIN_SERVICE_NAME}.pid`);
4088
+ const directory = path24.join(migrationDirectory(project), "services");
4089
+ const pidFile = path24.join(directory, `${MANAGED_LOGIN_SERVICE_NAME}.pid`);
4256
4090
  return {
4257
4091
  directory,
4258
4092
  pidFile,
4259
- stdoutLog: path25.join(directory, `${MANAGED_LOGIN_SERVICE_NAME}.stdout.log`),
4260
- stderrLog: path25.join(directory, `${MANAGED_LOGIN_SERVICE_NAME}.stderr.log`),
4093
+ stdoutLog: path24.join(directory, `${MANAGED_LOGIN_SERVICE_NAME}.stdout.log`),
4094
+ stderrLog: path24.join(directory, `${MANAGED_LOGIN_SERVICE_NAME}.stderr.log`),
4261
4095
  exitFile: `${pidFile}.exit`,
4262
4096
  errorFile: `${pidFile}.error`
4263
4097
  };
@@ -4288,7 +4122,7 @@ function tailFile(file, maxChars) {
4288
4122
  }
4289
4123
  }
4290
4124
  function listPosixProcesses() {
4291
- const result = spawnSync7("ps", ["-eo", "pid=,command="], { encoding: "utf8", timeout: 15e3 });
4125
+ const result = spawnSync6("ps", ["-eo", "pid=,command="], { encoding: "utf8", timeout: 15e3 });
4292
4126
  if (result.error || result.status !== 0) return [];
4293
4127
  return result.stdout.split("\n").flatMap((line) => {
4294
4128
  const trimmed = line.trim();
@@ -4296,15 +4130,15 @@ function listPosixProcesses() {
4296
4130
  const separator = trimmed.indexOf(" ");
4297
4131
  if (separator <= 0) return [];
4298
4132
  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 }] : [];
4133
+ const command2 = trimmed.slice(separator + 1);
4134
+ return Number.isInteger(pid) && pid > 0 ? [{ pid, command: command2 }] : [];
4301
4135
  });
4302
4136
  }
4303
4137
  function listWindowsProcesses() {
4304
4138
  const source = 'Get-CimInstance Win32_Process -Filter "CommandLine IS NOT NULL" | ForEach-Object { "$($_.ProcessId)`t$($_.CommandLine)" }';
4305
4139
  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], {
4140
+ const powershell = process.env.SystemRoot ? path24.resolve(process.env.SystemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe") : "powershell.exe";
4141
+ const result = spawnSync6(powershell, ["-NoLogo", "-NoProfile", "-NonInteractive", "-EncodedCommand", encoded], {
4308
4142
  encoding: "utf8",
4309
4143
  timeout: 3e4,
4310
4144
  windowsHide: true
@@ -4314,8 +4148,8 @@ function listWindowsProcesses() {
4314
4148
  const separator = line.indexOf(" ");
4315
4149
  if (separator <= 0) return [];
4316
4150
  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 }] : [];
4151
+ const command2 = line.slice(separator + 1);
4152
+ return Number.isInteger(pid) && pid > 0 ? [{ pid, command: command2 }] : [];
4319
4153
  });
4320
4154
  }
4321
4155
  function listDevEcoCliLoginProcesses(options = {}) {
@@ -4327,7 +4161,7 @@ function listDevEcoCliLoginProcesses(options = {}) {
4327
4161
  }
4328
4162
  async function terminateProcessTree(pid) {
4329
4163
  if (process.platform === "win32") {
4330
- spawnSync7("taskkill", ["/PID", String(pid), "/T", "/F"], { encoding: "utf8", timeout: 15e3, windowsHide: true });
4164
+ spawnSync6("taskkill", ["/PID", String(pid), "/T", "/F"], { encoding: "utf8", timeout: 15e3, windowsHide: true });
4331
4165
  return;
4332
4166
  }
4333
4167
  try {
@@ -4345,7 +4179,7 @@ async function terminateProcessTree(pid) {
4345
4179
  }
4346
4180
  }
4347
4181
  async function killOrphanDevEcoCliLogins(options) {
4348
- const state = managedLoginStatePaths(path25.resolve(options.project));
4182
+ const state = managedLoginStatePaths(path24.resolve(options.project));
4349
4183
  const terminate = options.terminator ?? terminateProcessTree;
4350
4184
  const targets = /* @__PURE__ */ new Set();
4351
4185
  const managedPid = readPidFile(state.pidFile);
@@ -4373,8 +4207,8 @@ async function killOrphanDevEcoCliLogins(options) {
4373
4207
  }
4374
4208
  function runDetachedDevEcoAuthHost(requestPath) {
4375
4209
  const request = JSON.parse(readFileSync5(requestPath, "utf8"));
4376
- mkdirSync3(path25.dirname(request.stdout), { recursive: true });
4377
- mkdirSync3(path25.dirname(request.stderr), { recursive: true });
4210
+ mkdirSync3(path24.dirname(request.stdout), { recursive: true });
4211
+ mkdirSync3(path24.dirname(request.stderr), { recursive: true });
4378
4212
  const stdout = openSync3(request.stdout, "a");
4379
4213
  const stderr = openSync3(request.stderr, "a");
4380
4214
  try {
@@ -4437,7 +4271,7 @@ async function managedDevEcoCliLogin(options) {
4437
4271
  const statusTimeoutMs = options.statusTimeoutMs ?? 15e3;
4438
4272
  const progress = options.onProgress ?? (() => {
4439
4273
  });
4440
- const projectRoot = path25.resolve(options.project);
4274
+ const projectRoot = path24.resolve(options.project);
4441
4275
  const state = managedLoginStatePaths(projectRoot);
4442
4276
  const failure = (summary, extra = {}) => ({
4443
4277
  status: "failed",
@@ -4531,17 +4365,17 @@ async function managedDevEcoCliLogin(options) {
4531
4365
 
4532
4366
  // src/commands/device-evidence.ts
4533
4367
  import fs21 from "fs";
4534
- import path26 from "path";
4368
+ import path25 from "path";
4535
4369
  import { createHash as createHash6 } from "crypto";
4536
- import { spawnSync as spawnSync8 } from "child_process";
4370
+ import { spawnSync as spawnSync7 } from "child_process";
4537
4371
  import { inflateSync } from "zlib";
4538
4372
  function safeName(value) {
4539
4373
  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
4374
  return value;
4541
4375
  }
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, {
4376
+ function run(command2, args, encoding = "utf8") {
4377
+ const script = /\.m?js$/i.test(command2);
4378
+ const result = spawnSync7(script ? process.execPath : command2, script ? [command2, ...args] : args, {
4545
4379
  encoding: encoding === "buffer" ? null : encoding,
4546
4380
  shell: false,
4547
4381
  timeout: 3e4,
@@ -4553,7 +4387,7 @@ function run(command3, args, encoding = "utf8") {
4553
4387
  status: Number.isInteger(result.status) ? result.status : null,
4554
4388
  stdout: result.stdout ?? (encoding === "buffer" ? Buffer.alloc(0) : ""),
4555
4389
  stderr: String(result.stderr ?? result.error?.message ?? ""),
4556
- invocation: [command3, ...args]
4390
+ invocation: [command2, ...args]
4557
4391
  };
4558
4392
  }
4559
4393
  function writeResult(file, result) {
@@ -4649,72 +4483,39 @@ function pixelMetrics(leftFile, rightFile) {
4649
4483
  return null;
4650
4484
  }
4651
4485
  }
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
4486
  function captureDeviceEvidence(options) {
4667
- const projectRoot = path26.resolve(options.project);
4487
+ const projectRoot = path25.resolve(options.project);
4668
4488
  const migrationRoot = migrationDirectory(projectRoot);
4669
4489
  const workspaceRoot = migrationWorkspaceRoot(projectRoot);
4670
4490
  const name = safeName(options.name);
4671
4491
  const capturedAt = /* @__PURE__ */ new Date();
4672
- const directory = path26.join(migrationRoot, "evidence", name, options.platform);
4492
+ const directory = path25.join(migrationRoot, "evidence", name, options.platform);
4673
4493
  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");
4494
+ const screenshot = path25.join(directory, "screenshot.png");
4495
+ const layout = path25.join(directory, "layout.json");
4496
+ const logs = path25.join(directory, "device.log");
4677
4497
  const commands = [];
4678
4498
  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
- }
4499
+ const hdc = options.hdc ?? process.env.HDC_BINARY ?? "hdc";
4500
+ const listed = run(hdc, ["list", "targets"]);
4501
+ 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));
4502
+ if (!listed.ok || !candidates.length) throw new Error(`${hdc} target discovery failed: ${listed.stderr || "no targets"}`);
4503
+ target = options.target ?? process.env.DROID2HMOS_HDC_TARGET ?? selectPreferredTarget(candidates);
4504
+ const prefix = ["-t", target];
4505
+ const remoteShot = `/data/local/tmp/droid2hmos-${process.pid}.png`;
4506
+ const remoteLayout = `/data/local/tmp/droid2hmos-${process.pid}.json`;
4507
+ const shot = run(hdc, [...prefix, "shell", "snapshot_display", "-f", remoteShot]);
4508
+ commands.push(shot);
4509
+ const recvShot = run(hdc, [...prefix, "file", "recv", remoteShot, screenshot]);
4510
+ commands.push(recvShot);
4511
+ const dump = run(hdc, [...prefix, "shell", "uitest", "dumpLayout", "-p", remoteLayout]);
4512
+ commands.push(dump);
4513
+ const recvLayout = run(hdc, [...prefix, "file", "recv", remoteLayout, layout]);
4514
+ commands.push(recvLayout);
4515
+ run(hdc, [...prefix, "shell", "rm", "-f", remoteShot, remoteLayout]);
4516
+ const log = run(hdc, [...prefix, "shell", "hilog", "-x"]);
4517
+ commands.push(log);
4518
+ writeResult(logs, log);
4718
4519
  const artifacts = [screenshot, layout, logs].map(fileInfo).filter(Boolean);
4719
4520
  const failures = commands.filter((item) => !item.ok).map((item) => ({ invocation: item.invocation, status: item.status, stderr: item.stderr }));
4720
4521
  const metadata = {
@@ -4724,10 +4525,10 @@ function captureDeviceEvidence(options) {
4724
4525
  target,
4725
4526
  capturedAt: capturedAt.toISOString(),
4726
4527
  status: failures.length === 0 && artifacts.length === 3 ? "passed" : "failed",
4727
- artifacts: artifacts.map((item) => ({ ...item, path: path26.relative(workspaceRoot, item.path).replaceAll("\\", "/") })),
4528
+ artifacts: artifacts.map((item) => ({ ...item, path: path25.relative(workspaceRoot, item.path).replaceAll("\\", "/") })),
4728
4529
  failures
4729
4530
  };
4730
- const metadataPath = path26.join(directory, "evidence.json");
4531
+ const metadataPath = path25.join(directory, "evidence.json");
4731
4532
  fs21.writeFileSync(metadataPath, `${JSON.stringify(metadata, null, 2)}
4732
4533
  `, "utf8");
4733
4534
  return { ...metadata, metadataPath };
@@ -4737,24 +4538,24 @@ function tokens(file) {
4737
4538
  return new Set(fs21.readFileSync(file, "utf8").toLowerCase().match(/[\p{L}\p{N}_-]{2,}/gu) ?? []);
4738
4539
  }
4739
4540
  function compareEvidence(options) {
4740
- const projectRoot = path26.resolve(options.project);
4541
+ const projectRoot = path25.resolve(options.project);
4741
4542
  const migrationRoot = migrationDirectory(projectRoot);
4742
4543
  const resolveEvidence = (value) => {
4743
4544
  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);
4545
+ const root = path25.isAbsolute(value) ? value : (() => {
4546
+ if (normalized2.startsWith(".migration/")) return path25.resolve(migrationWorkspaceRoot(projectRoot), value);
4547
+ return path25.resolve(projectRoot, value);
4747
4548
  })();
4748
- return fs21.statSync(root).isDirectory() ? root : path26.dirname(root);
4549
+ return fs21.statSync(root).isDirectory() ? root : path25.dirname(root);
4749
4550
  };
4750
4551
  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");
4552
+ const harmonyRoot2 = resolveEvidence(options.harmony);
4553
+ const androidShot = fileInfo(path25.join(androidRoot, "screenshot.png"));
4554
+ const harmonyShot = fileInfo(path25.join(harmonyRoot2, "screenshot.png"));
4555
+ const pixels = pixelMetrics(path25.join(androidRoot, "screenshot.png"), path25.join(harmonyRoot2, "screenshot.png"));
4556
+ const layoutFile = (root) => [path25.join(root, "layout.json"), path25.join(root, "layout.xml")].find((file) => fs21.existsSync(file)) ?? path25.join(root, "layout.json");
4756
4557
  const left = tokens(layoutFile(androidRoot));
4757
- const right = tokens(layoutFile(harmonyRoot));
4558
+ const right = tokens(layoutFile(harmonyRoot2));
4758
4559
  const intersection = [...left].filter((value) => right.has(value));
4759
4560
  const union = /* @__PURE__ */ new Set([...left, ...right]);
4760
4561
  const result = {
@@ -4762,7 +4563,7 @@ function compareEvidence(options) {
4762
4563
  name: safeName(options.name),
4763
4564
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4764
4565
  android: { root: androidRoot, screenshot: androidShot, layoutTokens: left.size },
4765
- harmony: { root: harmonyRoot, screenshot: harmonyShot, layoutTokens: right.size },
4566
+ harmony: { root: harmonyRoot2, screenshot: harmonyShot, layoutTokens: right.size },
4766
4567
  screenshot: {
4767
4568
  sameBytes: Boolean(androidShot && harmonyShot && androidShot.sha256 === harmonyShot.sha256),
4768
4569
  sameDimensions: Boolean(androidShot && harmonyShot && androidShot.width === harmonyShot.width && androidShot.height === harmonyShot.height),
@@ -4771,27 +4572,27 @@ function compareEvidence(options) {
4771
4572
  layout: { sharedTokens: intersection.length, unionTokens: union.size, jaccard: union.size ? intersection.length / union.size : 1 },
4772
4573
  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
4574
  };
4774
- const output = path26.join(migrationRoot, "evidence", safeName(options.name), "comparison.json");
4775
- fs21.mkdirSync(path26.dirname(output), { recursive: true });
4575
+ const output = path25.join(migrationRoot, "evidence", safeName(options.name), "comparison.json");
4576
+ fs21.mkdirSync(path25.dirname(output), { recursive: true });
4776
4577
  fs21.writeFileSync(output, `${JSON.stringify(result, null, 2)}
4777
4578
  `, "utf8");
4778
4579
  return { status: "passed", output, result };
4779
4580
  }
4780
4581
  function writeFlowBundle(options) {
4781
- const projectRoot = path26.resolve(options.project);
4582
+ const projectRoot = path25.resolve(options.project);
4782
4583
  const migrationRoot = migrationDirectory(projectRoot);
4783
- const output = path26.join(migrationRoot, "evidence", safeName(options.name), "flow.json");
4584
+ const output = path25.join(migrationRoot, "evidence", safeName(options.name), "flow.json");
4784
4585
  const value = {
4785
4586
  flowVersion: "1.0.0",
4786
4587
  name: options.name,
4787
4588
  platform: options.platform,
4788
4589
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4789
4590
  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("\\", "/"),
4591
+ before: path25.relative(migrationWorkspaceRoot(projectRoot), options.before.metadataPath).replaceAll("\\", "/"),
4791
4592
  execution: options.execution,
4792
- after: path26.relative(migrationWorkspaceRoot(projectRoot), options.after.metadataPath).replaceAll("\\", "/")
4593
+ after: path25.relative(migrationWorkspaceRoot(projectRoot), options.after.metadataPath).replaceAll("\\", "/")
4793
4594
  };
4794
- fs21.mkdirSync(path26.dirname(output), { recursive: true });
4595
+ fs21.mkdirSync(path25.dirname(output), { recursive: true });
4795
4596
  fs21.writeFileSync(output, `${JSON.stringify(value, null, 2)}
4796
4597
  `, "utf8");
4797
4598
  return { ...value, output };
@@ -4799,7 +4600,7 @@ function writeFlowBundle(options) {
4799
4600
 
4800
4601
  // src/commands/report-update.ts
4801
4602
  import fs22 from "fs";
4802
- import path27 from "path";
4603
+ import path26 from "path";
4803
4604
  var allowedStatuses = /* @__PURE__ */ new Set(["pending", "implemented", "verified", "blocked"]);
4804
4605
  function readJson3(file) {
4805
4606
  return JSON.parse(fs22.readFileSync(file, "utf8"));
@@ -4811,48 +4612,48 @@ function atomicJson3(file, value) {
4811
4612
  fs22.renameSync(temporary, file);
4812
4613
  }
4813
4614
  function readHarmonyRoot(projectRoot) {
4814
- const configPath = path27.join(migrationDirectory(projectRoot), "config.json");
4615
+ const configPath = path26.join(migrationDirectory(projectRoot), "config.json");
4815
4616
  if (!fs22.existsSync(configPath)) return null;
4816
4617
  const config = readJson3(configPath);
4817
4618
  const harmonyProject = config?.harmonyProject;
4818
4619
  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;
4620
+ const harmonyRoot2 = path26.isAbsolute(harmonyProject) ? harmonyProject : path26.resolve(projectRoot, harmonyProject);
4621
+ return fs22.existsSync(harmonyRoot2) ? harmonyRoot2 : null;
4821
4622
  }
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);
4623
+ function insideProject(projectRoot, harmonyRoot2, workspaceRoot, value, mustExist) {
4624
+ const absolute = value.replaceAll("\\", "/").startsWith(".migration/") ? path26.resolve(workspaceRoot, value) : path26.resolve(projectRoot, value);
4625
+ const relative2 = path26.relative(projectRoot, absolute).replaceAll("\\", "/");
4626
+ const inProject = !!relative2 && !relative2.startsWith("../") && !path26.isAbsolute(relative2);
4826
4627
  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("/");
4628
+ if (!inProject && harmonyRoot2) {
4629
+ const relativeHarmony = path26.relative(harmonyRoot2, absolute).replaceAll("\\", "/");
4630
+ inHarmony = !!relativeHarmony && !relativeHarmony.startsWith("../") && !path26.isAbsolute(relativeHarmony) && !relative2.startsWith("/");
4830
4631
  }
4831
- const migrationRelative = path27.relative(workspaceRoot, absolute).replaceAll("\\", "/");
4832
- const inMigration = !!migrationRelative && !migrationRelative.startsWith("../") && !path27.isAbsolute(migrationRelative);
4632
+ const migrationRelative = path26.relative(workspaceRoot, absolute).replaceAll("\\", "/");
4633
+ const inMigration = !!migrationRelative && !migrationRelative.startsWith("../") && !path26.isAbsolute(migrationRelative);
4833
4634
  if (!inProject && !inHarmony && !inMigration) throw new Error(`Path must be inside the project: ${value}`);
4834
4635
  if (mustExist && !fs22.existsSync(absolute)) throw new Error(`Referenced file not found: ${migrationRelative !== "" && inMigration ? migrationRelative : relative2}`);
4835
4636
  return inMigration ? migrationRelative : relative2;
4836
4637
  }
4837
4638
  function updateReportItem(options) {
4838
4639
  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");
4640
+ const projectRoot = path26.resolve(options.project);
4641
+ const harmonyRoot2 = readHarmonyRoot(projectRoot);
4642
+ const reportRoot = path26.join(migrationDirectory(projectRoot), "report");
4643
+ const indexPath = path26.join(reportRoot, "index.json");
4843
4644
  if (!fs22.existsSync(indexPath)) throw new Error("Migration report is not initialized");
4844
4645
  if (options.status && !allowedStatuses.has(options.status)) throw new Error(`Invalid report status: ${options.status}`);
4845
4646
  const index = readJson3(indexPath);
4846
4647
  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}`);
4648
+ if (typeof value !== "string" || !value.trim() || path26.isAbsolute(value)) throw new Error(`Report shard path must be report-relative: ${String(value)}`);
4649
+ const absolute = path26.resolve(reportRoot, value);
4650
+ const relation = path26.relative(reportRoot, absolute);
4651
+ if (!relation || relation === ".." || relation.startsWith(`..${path26.sep}`) || path26.isAbsolute(relation)) throw new Error(`Report shard escapes report root: ${value}`);
4851
4652
  if (!fs22.existsSync(absolute) || !fs22.statSync(absolute).isFile()) throw new Error(`Report shard not found: ${value}`);
4852
4653
  const realRoot = fs22.realpathSync(reportRoot);
4853
4654
  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}`);
4655
+ const realRelation = path26.relative(realRoot, realFile);
4656
+ if (realRelation === ".." || realRelation.startsWith(`..${path26.sep}`) || path26.isAbsolute(realRelation)) throw new Error(`Report shard resolves outside report root: ${value}`);
4856
4657
  return realFile;
4857
4658
  };
4858
4659
  const candidates = [
@@ -4875,19 +4676,19 @@ function updateReportItem(options) {
4875
4676
  if (options.harmonyosFiles) match.item.harmonyosFiles = [...new Set(options.harmonyosFiles.map((file) => managedHarmonyFile(projectRoot, file).relative))];
4876
4677
  if (options.androidSourceFiles) match.item.sourceProvenance = provenanceForFiles(projectRoot, options.androidSourceFiles);
4877
4678
  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)))];
4679
+ if (options.testEvidence) match.item.testEvidence = [...new Set(options.testEvidence.map((file) => insideProject(projectRoot, harmonyRoot2, workspaceRoot, file, true)))];
4879
4680
  if (options.difference !== void 0) match.item.difference = options.difference?.trim() || null;
4880
4681
  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
4682
  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
4683
  if (match.item.status === "verified" && !match.item.testEvidence?.length) throw new Error("verified requires at least one test evidence file");
4883
4684
  if (match.item.status === "blocked" && !match.item.difference) throw new Error("blocked requires --difference");
4884
4685
  atomicJson3(match.file, match.document);
4885
- return { status: "passed", ref: options.ref, file: path27.relative(projectRoot, match.file).replaceAll("\\", "/"), item: match.item };
4686
+ return { status: "passed", ref: options.ref, file: path26.relative(projectRoot, match.file).replaceAll("\\", "/"), item: match.item };
4886
4687
  }
4887
4688
 
4888
4689
  // src/commands/mock-server-generator.ts
4889
4690
  import fs23 from "fs";
4890
- import path28 from "path";
4691
+ import path27 from "path";
4891
4692
  var requiredScenarios = ["success", "empty", "business-failure", "boundary"];
4892
4693
  function validateSpec(spec) {
4893
4694
  const errors = [];
@@ -4947,21 +4748,21 @@ const server = http.createServer(async (request, response) => {
4947
4748
  server.listen(port, host, () => console.log(JSON.stringify({ status: "ready", host, port })));
4948
4749
  `;
4949
4750
  function generateMockServer(options) {
4950
- const projectRoot = path28.resolve(options.project);
4951
- const specFile = path28.resolve(options.specFile);
4751
+ const projectRoot = path27.resolve(options.project);
4752
+ const specFile = path27.resolve(options.specFile);
4952
4753
  if (!fs23.existsSync(specFile)) throw new Error(`Protocol spec not found: ${specFile}`);
4953
4754
  const spec = JSON.parse(fs23.readFileSync(specFile, "utf8"));
4954
4755
  const errors = validateSpec(spec);
4955
4756
  if (errors.length) throw new Error(`Invalid protocol spec: ${errors.join("; ")}`);
4956
- const root = path28.join(projectRoot, "mock-server");
4757
+ const root = path27.join(projectRoot, "mock-server");
4957
4758
  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
4759
  fs23.mkdirSync(root, { recursive: true });
4959
- fs23.writeFileSync(path28.join(root, "scenarios.json"), `${JSON.stringify(spec, null, 2)}
4760
+ fs23.writeFileSync(path27.join(root, "scenarios.json"), `${JSON.stringify(spec, null, 2)}
4960
4761
  `, "utf8");
4961
- fs23.writeFileSync(path28.join(root, "server.mjs"), serverSource, "utf8");
4962
- fs23.writeFileSync(path28.join(root, "README.md"), `# Mock Server
4762
+ fs23.writeFileSync(path27.join(root, "server.mjs"), serverSource, "utf8");
4763
+ fs23.writeFileSync(path27.join(root, "README.md"), `# Mock Server
4963
4764
 
4964
- Generated from \`${path28.relative(projectRoot, specFile).replaceAll("\\", "/")}\`.
4765
+ Generated from \`${path27.relative(projectRoot, specFile).replaceAll("\\", "/")}\`.
4965
4766
 
4966
4767
  Start safely:
4967
4768
 
@@ -4976,16 +4777,16 @@ Select a scenario with query parameter \`?scenario=empty\` or header \`x-droid2h
4976
4777
 
4977
4778
  // src/commands/logs.ts
4978
4779
  import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync as readdirSync3 } from "fs";
4979
- import path29 from "path";
4780
+ import path28 from "path";
4980
4781
  function loadRecords(projectRoot) {
4981
- const directory = path29.join(migrationDirectory(projectRoot), "logs", "daily");
4782
+ const directory = path28.join(migrationDirectory(projectRoot), "logs", "daily");
4982
4783
  if (!existsSync7(directory)) return [];
4983
4784
  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))
4785
+ (file) => readFileSync6(path28.join(directory, file), "utf8").split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line))
4985
4786
  );
4986
4787
  }
4987
4788
  function queryCommandLogs(project, action, value) {
4988
- const projectRoot = path29.resolve(project);
4789
+ const projectRoot = path28.resolve(project);
4989
4790
  const records = loadRecords(projectRoot);
4990
4791
  if (action === "list") {
4991
4792
  const selected = value ? records.filter((record) => record.date === value) : records;
@@ -5016,7 +4817,7 @@ function queryCommandLogs(project, action, value) {
5016
4817
  // src/logging/command-log.ts
5017
4818
  import { appendFileSync, existsSync as existsSync8, mkdirSync as mkdirSync4, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
5018
4819
  import { randomUUID } from "crypto";
5019
- import path30 from "path";
4820
+ import path29 from "path";
5020
4821
  var COMMAND_LOG_SCHEMA_VERSION = 1;
5021
4822
  var SECRET_OPTIONS = /^(--?(?:password|pass|token|secret|api[-_]?key|access[-_]?key|signing[-_]?key))$/i;
5022
4823
  function localDate(date) {
@@ -5056,10 +4857,10 @@ function discoverProjectRoot(argv, cwd = process.cwd()) {
5056
4857
  continue;
5057
4858
  }
5058
4859
  if (argument.startsWith("-") || reserved.has(argument)) continue;
5059
- const candidate = path30.resolve(cwd, argument);
4860
+ const candidate = path29.resolve(cwd, argument);
5060
4861
  if (existsSync8(candidate)) return candidate;
5061
4862
  }
5062
- return path30.resolve(cwd);
4863
+ return path29.resolve(cwd);
5063
4864
  }
5064
4865
  function knownArtifacts(projectRoot) {
5065
4866
  const candidates = [
@@ -5072,12 +4873,12 @@ function knownArtifacts(projectRoot) {
5072
4873
  ["managed-evidence", ".migration/evidence/index.json"]
5073
4874
  ];
5074
4875
  const workspaceRoot = migrationWorkspaceRoot(projectRoot);
5075
- return candidates.filter(([, relative2]) => existsSync8(path30.join(workspaceRoot, relative2))).map(([kind, relative2]) => ({ kind, path: relative2.replaceAll("\\", "/") }));
4876
+ return candidates.filter(([, relative2]) => existsSync8(path29.join(workspaceRoot, relative2))).map(([kind, relative2]) => ({ kind, path: relative2.replaceAll("\\", "/") }));
5076
4877
  }
5077
4878
  function commandName(argv) {
5078
- const command3 = argv[0] ?? "unknown";
4879
+ const command2 = argv[0] ?? "unknown";
5079
4880
  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;
4881
+ return grouped.has(command2) && argv[1] ? `${command2} ${argv[1]}` : command2;
5081
4882
  }
5082
4883
  function atomicJson4(file, value) {
5083
4884
  const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`;
@@ -5120,18 +4921,18 @@ async function withCommandLog(argv, version, execute) {
5120
4921
  artifacts: knownArtifacts(projectRoot)
5121
4922
  };
5122
4923
  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");
4924
+ const logsRoot = path29.join(migrationWorkspaceRoot(projectRoot), ".migration", "logs");
4925
+ const runDirectory = path29.join(logsRoot, "runs", date);
4926
+ const dailyDirectory = path29.join(logsRoot, "daily");
5126
4927
  mkdirSync4(runDirectory, { recursive: true });
5127
4928
  mkdirSync4(dailyDirectory, { recursive: true });
5128
- atomicJson4(path30.join(runDirectory, `${runId}.json`), record);
5129
- appendFileSync(path30.join(dailyDirectory, `${date}.jsonl`), `${JSON.stringify(record)}
4929
+ atomicJson4(path29.join(runDirectory, `${runId}.json`), record);
4930
+ appendFileSync(path29.join(dailyDirectory, `${date}.jsonl`), `${JSON.stringify(record)}
5130
4931
  `, "utf8");
5131
- atomicJson4(path30.join(logsRoot, "index.json"), {
4932
+ atomicJson4(path29.join(logsRoot, "index.json"), {
5132
4933
  schemaVersion: COMMAND_LOG_SCHEMA_VERSION,
5133
4934
  lastRunId: runId,
5134
- lastRun: path30.relative(path30.dirname(logsRoot), path30.join(runDirectory, `${runId}.json`)).replaceAll("\\", "/"),
4935
+ lastRun: path29.relative(path29.dirname(logsRoot), path29.join(runDirectory, `${runId}.json`)).replaceAll("\\", "/"),
5135
4936
  updatedAt: finished.toISOString()
5136
4937
  });
5137
4938
  } catch (logError) {
@@ -5142,30 +4943,30 @@ async function withCommandLog(argv, version, execute) {
5142
4943
 
5143
4944
  // src/runtime-paths.ts
5144
4945
  import fs24 from "fs";
5145
- import path31 from "path";
4946
+ import path30 from "path";
5146
4947
  import { fileURLToPath } from "url";
5147
4948
  function resolveRuntimePaths(entryUrl) {
5148
4949
  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, "..");
4950
+ const directory = path30.dirname(entryFile);
4951
+ const roots = [path30.resolve(directory, ".."), directory];
4952
+ const packageRoot = roots.find((candidate) => fs24.existsSync(path30.join(candidate, "package.json")));
4953
+ const runtimeRoot = packageRoot ?? path30.resolve(directory, "..");
5153
4954
  const templatesRoot = [
5154
- packageRoot && path31.join(packageRoot, "templates")
4955
+ packageRoot && path30.join(packageRoot, "templates")
5155
4956
  ].filter(Boolean).find((candidate) => fs24.existsSync(candidate));
5156
- const preflightChecklistPath = packageRoot && path31.join(packageRoot, "config", "preflight-checklist.json");
4957
+ const preflightChecklistPath = packageRoot && path30.join(packageRoot, "config", "preflight-checklist.json");
5157
4958
  return { entryFile, runtimeRoot, packageRoot, templatesRoot, preflightChecklistPath };
5158
4959
  }
5159
4960
 
5160
4961
  // src/kernel/migration-kernel.ts
5161
4962
  import { createHash as createHash8, randomUUID as randomUUID2 } from "crypto";
5162
4963
  import { execFileSync as execFileSync2 } from "child_process";
5163
- import fs28 from "fs";
4964
+ import fs29 from "fs";
5164
4965
  import path35 from "path";
5165
4966
 
5166
4967
  // src/planning/facts/reader.ts
5167
4968
  import fs25 from "fs";
5168
- import path32 from "path";
4969
+ import path31 from "path";
5169
4970
 
5170
4971
  // src/planning/util.ts
5171
4972
  import { createHash as createHash7 } from "crypto";
@@ -5210,17 +5011,17 @@ function readJson4(file) {
5210
5011
  }
5211
5012
  function inside3(root, relative2, label) {
5212
5013
  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)) {
5014
+ const resolvedRoot = path31.resolve(root);
5015
+ const absolute = path31.resolve(resolvedRoot, relative2);
5016
+ const rel = path31.relative(resolvedRoot, absolute);
5017
+ if (!rel || rel.startsWith(`..${path31.sep}`) || path31.isAbsolute(rel)) {
5217
5018
  throw new PlanningFactsError("FACTS_PATH_OUTSIDE_ROOT", `${label} must stay inside ${resolvedRoot}: ${relative2}`);
5218
5019
  }
5219
5020
  if (!fs25.existsSync(absolute) || !fs25.statSync(absolute).isFile()) throw new PlanningFactsError("FACTS_SHARD_MISSING", `${label} not found: ${absolute}`);
5220
5021
  const realRoot = fs25.realpathSync(resolvedRoot);
5221
5022
  const realFile = fs25.realpathSync(absolute);
5222
- const realRelative = path32.relative(realRoot, realFile);
5223
- if (!realRelative || realRelative.startsWith(`..${path32.sep}`) || path32.isAbsolute(realRelative)) {
5023
+ const realRelative = path31.relative(realRoot, realFile);
5024
+ if (!realRelative || realRelative.startsWith(`..${path31.sep}`) || path31.isAbsolute(realRelative)) {
5224
5025
  throw new PlanningFactsError("FACTS_PATH_OUTSIDE_ROOT", `${label} resolves outside ${realRoot}: ${relative2}`);
5225
5026
  }
5226
5027
  return realFile;
@@ -5233,9 +5034,9 @@ function requireVersion(value, expected, label) {
5233
5034
  if (value !== expected) throw new PlanningFactsError("FACTS_VERSION_UNSUPPORTED", `${label} must be ${expected}; found ${value ?? "<missing>"}`);
5234
5035
  }
5235
5036
  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");
5037
+ const projectRoot = path31.resolve(project);
5038
+ const specRoot = path31.join(migrationDirectory(projectRoot), "spec");
5039
+ const indexFile = path31.join(specRoot, "index.json");
5239
5040
  if (!fs25.existsSync(indexFile)) throw new PlanningFactsError("FACTS_SHARD_MISSING", `SPEC index not found: ${indexFile}`);
5240
5041
  const index = readJson4(indexFile);
5241
5042
  requireVersion(index.specVersion, "2.0.0", "SPEC specVersion");
@@ -5303,17 +5104,30 @@ function readFactsBundleV2(project) {
5303
5104
  }
5304
5105
 
5305
5106
  // 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";
5107
+ var TASK_PLAN_VERSION = "2.3.0";
5108
+ var TASK_ENVELOPE_VERSION = "3.0.0";
5109
+ var BEHAVIOR_PLAN_VERSION = "1.0.0";
5110
+ var EXECUTION_PLAN_VERSION = "1.3.0";
5111
+ var PLANNING_MODEL_VERSION = "2.0.0";
5309
5112
  var FACTS_ADAPTER_VERSION = "1.0.0";
5310
- var PLANNER_POLICY_VERSION = "1.1.0";
5113
+ var PLANNER_POLICY_VERSION = "2.3.0";
5311
5114
  var DEFAULT_CLOSURE_BUDGET = {
5312
5115
  maxFactCount: 30,
5313
5116
  maxSourceFileCount: 15,
5314
5117
  maxTraceNodeCount: 50,
5315
5118
  maxEstimatedFactTokens: 12e3
5316
5119
  };
5120
+ var DEFAULT_EXECUTION_BUDGET = {
5121
+ maxBehaviorCount: 5,
5122
+ maxObservableOutcomeCount: 3,
5123
+ maxProductionFileCount: 4,
5124
+ maxTestFileCount: 2,
5125
+ maxCapabilityCount: 2,
5126
+ maxFactCount: 45,
5127
+ maxSourceFileCount: 8,
5128
+ maxTraceNodeCount: 60,
5129
+ maxEstimatedFactTokens: 14e3
5130
+ };
5317
5131
 
5318
5132
  // src/planning/facts/v2-adapter.ts
5319
5133
  function evidence(values) {
@@ -5617,7 +5431,8 @@ function candidateForEntry(entryId, facts) {
5617
5431
  ...effects.flatMap((effect) => [effect.id, effect.targetId])
5618
5432
  ]));
5619
5433
  const issues = facts.issues.filter((issue) => issue.relatedRefs.some((ref) => related.has(ref)));
5620
- const blocking = issues.some((issue) => issue.blocking && !issue.resolved);
5434
+ const unresolvedIssues = issues.filter((issue) => issue.blocking && !issue.resolved);
5435
+ const blocking = unresolvedIssues.length > 0;
5621
5436
  const isLaunch = facts.app.launcherEntryIds.includes(entry.id) || entry.triggerCategory === "application" && entry.event === "launch";
5622
5437
  const type = blocking ? "resolution" : (() => {
5623
5438
  if (isLaunch) return "bootstrap";
@@ -5650,7 +5465,7 @@ function candidateForEntry(entryId, facts) {
5650
5465
  surfaceRefs: new Set(surfaces),
5651
5466
  controlRefs: new Set(controls.map((control) => control.id)),
5652
5467
  interactionRefs: new Set(effects.map((effect) => effect.id)),
5653
- issueRefs: new Set(issues.map((issue) => issue.id)),
5468
+ issueRefs: new Set(unresolvedIssues.map((issue) => issue.id)),
5654
5469
  warningRefs: new Set(warningRefs),
5655
5470
  sourceSurfaceRefs: new Set(entry.ownerSurfaceId ? [entry.ownerSurfaceId] : []),
5656
5471
  targetEdges: effects.flatMap((effect) => targetSurfaceIds(effect, facts).map((targetSurfaceRef) => ({ sourceSurfaceRef: entry.ownerSurfaceId, targetSurfaceRef, effectRef: effect.id }))),
@@ -5658,7 +5473,7 @@ function candidateForEntry(entryId, facts) {
5658
5473
  ...entry.evidence,
5659
5474
  ...controls.flatMap((control) => control.evidence),
5660
5475
  ...effects.flatMap((effect) => effect.evidence),
5661
- ...issues.flatMap((issue) => issue.evidence)
5476
+ ...unresolvedIssues.flatMap((issue) => issue.evidence)
5662
5477
  ], evidenceKey),
5663
5478
  codeRefs: entry.trace.codeRefs,
5664
5479
  requiresSourceReview: effects.length === 0 || blocking || entry.trace.warnings.length > 0,
@@ -5954,8 +5769,8 @@ function chooseSurfaceOwners(candidates, facts) {
5954
5769
  function reportableRefs(facts) {
5955
5770
  return new Set(facts.requiredFactRefs);
5956
5771
  }
5957
- function taskIdFor(candidate) {
5958
- return `task:${sha2562({
5772
+ function behaviorIdFor(candidate) {
5773
+ return `behavior:${sha2562({
5959
5774
  type: candidate.type,
5960
5775
  primaryFactRefs: uniqueSorted(candidate.primary),
5961
5776
  path: {
@@ -5967,7 +5782,7 @@ function taskIdFor(candidate) {
5967
5782
  }
5968
5783
  function downstreamCounts(tasks) {
5969
5784
  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]);
5785
+ for (const task of tasks) for (const dependency of task.dependsOnBehaviorIds) children.set(dependency, [...children.get(dependency) ?? [], task.id]);
5971
5786
  const out = /* @__PURE__ */ new Map();
5972
5787
  for (const task of tasks) {
5973
5788
  const seen = /* @__PURE__ */ new Set();
@@ -5982,7 +5797,7 @@ function downstreamCounts(tasks) {
5982
5797
  }
5983
5798
  return out;
5984
5799
  }
5985
- function compileFromPlanningFacts(facts, androidFingerprint) {
5800
+ function compileBehaviorPlanFromPlanningFacts(facts, androidFingerprint) {
5986
5801
  let candidates = mergeCompatibleEntryCandidates(facts.entries.map((entry) => candidateForEntry(entry.id, facts)), facts);
5987
5802
  for (const surfaceRef of facts.app.launcherSurfaceIds) {
5988
5803
  if (!candidates.some((candidate) => candidate.type === "bootstrap" && candidate.surfaceRefs.has(surfaceRef))) {
@@ -6036,8 +5851,8 @@ function compileFromPlanningFacts(facts, androidFingerprint) {
6036
5851
  const closureRefs = /* @__PURE__ */ new Set([...candidate.entryRefs, ...candidate.surfaceRefs, ...candidate.controlRefs, ...candidate.interactionRefs, ...candidate.issueRefs]);
6037
5852
  candidate.supporting = new Set([...closureRefs].filter((ref) => !candidate.primary.has(ref)));
6038
5853
  }
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");
5854
+ const idByKey = new Map(candidates.map((candidate) => [candidate.key, behaviorIdFor(candidate)]));
5855
+ if (new Set(idByKey.values()).size !== candidates.length) throw new Error("Behavior ID collision while compiling plan");
6041
5856
  const launcherResolvers = candidates.filter((candidate) => candidate.type === "resolution" && candidate.issueRefs.has("issue:launcher-unresolved"));
6042
5857
  const taskDrafts = candidates.map((candidate) => {
6043
5858
  const dependencies = /* @__PURE__ */ new Set();
@@ -6049,11 +5864,11 @@ function compileFromPlanningFacts(facts, androidFingerprint) {
6049
5864
  }
6050
5865
  if (facts.app.launcherOrigin === "unresolved") for (const resolver of launcherResolvers) dependencies.add(idByKey.get(resolver.key));
6051
5866
  }
6052
- return { candidate, id: idByKey.get(candidate.key), dependsOnTaskIds: uniqueSorted(dependencies) };
5867
+ return { candidate, id: idByKey.get(candidate.key), dependsOnBehaviorIds: uniqueSorted(dependencies) };
6053
5868
  });
6054
5869
  const downstream = downstreamCounts(taskDrafts);
6055
5870
  const reportable = reportableRefs(facts);
6056
- const tasks = taskDrafts.map(({ candidate, id, dependsOnTaskIds }) => {
5871
+ const responsibilities = taskDrafts.map(({ candidate, id, dependsOnBehaviorIds }) => {
6057
5872
  const primaryFactRefs = uniqueSorted(candidate.primary);
6058
5873
  const supportingFactRefs = uniqueSorted(candidate.supporting);
6059
5874
  const core = {
@@ -6070,7 +5885,7 @@ function compileFromPlanningFacts(facts, androidFingerprint) {
6070
5885
  title: candidate.title,
6071
5886
  primaryFactRefs,
6072
5887
  supportingFactRefs,
6073
- dependsOnTaskIds,
5888
+ dependsOnBehaviorIds,
6074
5889
  closure: {
6075
5890
  entryRefs: core.entries,
6076
5891
  surfaceRefs: core.surfaces,
@@ -6098,12 +5913,12 @@ function compileFromPlanningFacts(facts, androidFingerprint) {
6098
5913
  };
6099
5914
  }).sort((a, b) => typeOrder[a.type] - typeOrder[b.type] || a.id.localeCompare(b.id));
6100
5915
  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]);
5916
+ const supportingBehaviorIdsByFactRef = {};
5917
+ for (const responsibility of responsibilities) for (const ref of responsibility.supportingFactRefs) supportingBehaviorIdsByFactRef[ref] = uniqueSorted([...supportingBehaviorIdsByFactRef[ref] ?? [], responsibility.id]);
6103
5918
  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 });
5919
+ 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
5920
  return {
6106
- taskPlanVersion: TASK_PLAN_VERSION,
5921
+ behaviorPlanVersion: BEHAVIOR_PLAN_VERSION,
6107
5922
  planningModelVersion: PLANNING_MODEL_VERSION,
6108
5923
  factsAdapterVersion: FACTS_ADAPTER_VERSION,
6109
5924
  plannerPolicyVersion: PLANNER_POLICY_VERSION,
@@ -6119,22 +5934,716 @@ function compileFromPlanningFacts(facts, androidFingerprint) {
6119
5934
  launcherEntryRefs: facts.app.launcherEntryIds,
6120
5935
  origin: facts.app.launcherOrigin
6121
5936
  },
6122
- tasks,
6123
- ownership: { primaryOwnerByFactRef, supportingTaskIdsByFactRef: Object.fromEntries(Object.entries(supportingTaskIdsByFactRef).sort(([a], [b]) => a.localeCompare(b))) },
5937
+ responsibilities,
5938
+ ownership: { primaryOwnerByFactRef, supportingBehaviorIdsByFactRef: Object.fromEntries(Object.entries(supportingBehaviorIdsByFactRef).sort(([a], [b]) => a.localeCompare(b))) },
6124
5939
  planningWarnings: uniqueBy(planningWarnings, (item) => `${item.code}:${item.message}:${item.relatedRefs.join(",")}`)
6125
5940
  };
6126
5941
  }
6127
5942
 
6128
- // src/planning/storage.ts
5943
+ // src/planning/execution-context.ts
6129
5944
  import fs26 from "fs";
5945
+ import path32 from "path";
5946
+ function readJson6(file) {
5947
+ try {
5948
+ return JSON.parse(fs26.readFileSync(file, "utf8"));
5949
+ } catch {
5950
+ return null;
5951
+ }
5952
+ }
5953
+ function inside4(root, target) {
5954
+ const relative2 = path32.relative(root, target);
5955
+ return relative2 === "" || relative2 !== ".." && !relative2.startsWith(`..${path32.sep}`) && !path32.isAbsolute(relative2);
5956
+ }
5957
+ function validatedHarmonyFile(projectRoot, root, value) {
5958
+ if (typeof value !== "string" || !value.trim()) throw new Error("Report HarmonyOS mapping must be a non-empty string");
5959
+ const absolute = path32.resolve(projectRoot, value);
5960
+ if (!inside4(root, absolute)) throw new Error(`Report HarmonyOS mapping escapes the configured project: ${value}`);
5961
+ if (!fs26.existsSync(absolute) || !fs26.statSync(absolute).isFile()) throw new Error(`Report HarmonyOS mapping does not exist: ${value}`);
5962
+ const realRoot = fs26.realpathSync(root);
5963
+ const realFile = fs26.realpathSync(absolute);
5964
+ if (!inside4(realRoot, realFile)) throw new Error(`Report HarmonyOS mapping resolves outside the configured project: ${value}`);
5965
+ return path32.relative(projectRoot, absolute).replaceAll("\\", "/");
5966
+ }
5967
+ function confinedReportFile(reportRoot, relative2) {
5968
+ if (!relative2.trim() || path32.isAbsolute(relative2)) throw new Error(`Report shard path must be relative: ${relative2}`);
5969
+ const absolute = path32.resolve(reportRoot, relative2);
5970
+ if (!inside4(reportRoot, absolute) || absolute === reportRoot) throw new Error(`Report shard escapes report root: ${relative2}`);
5971
+ if (!fs26.existsSync(absolute) || !fs26.statSync(absolute).isFile()) throw new Error(`Report shard does not exist: ${relative2}`);
5972
+ const realRoot = fs26.realpathSync(reportRoot);
5973
+ const realFile = fs26.realpathSync(absolute);
5974
+ if (!inside4(realRoot, realFile)) throw new Error(`Report shard resolves outside report root: ${relative2}`);
5975
+ return realFile;
5976
+ }
5977
+ function reportHarmonyFiles(projectRoot, root) {
5978
+ const reportRoot = path32.join(migrationDirectory(projectRoot), "report");
5979
+ const index = readJson6(path32.join(reportRoot, "index.json"));
5980
+ if (!index) return {};
5981
+ const shardFiles = [
5982
+ ...(index.pages ?? []).map((item) => item.file),
5983
+ index.unassignedEntriesFile,
5984
+ index.interactionsFile,
5985
+ index.discoveriesFile
5986
+ ].filter((value) => typeof value === "string");
5987
+ const result = {};
5988
+ for (const relative2 of shardFiles) {
5989
+ const document = readJson6(confinedReportFile(reportRoot, relative2));
5990
+ if (!document) continue;
5991
+ const items = [
5992
+ document.page,
5993
+ ...document.controls ?? [],
5994
+ ...document.entries ?? [],
5995
+ ...document.interactions ?? [],
5996
+ ...document.items ?? []
5997
+ ].filter(Boolean);
5998
+ for (const item of items) {
5999
+ if (typeof item.ref !== "string" || !Array.isArray(item.harmonyosFiles)) continue;
6000
+ result[item.ref] = uniqueSorted(item.harmonyosFiles.map((value) => validatedHarmonyFile(projectRoot, root, value)));
6001
+ }
6002
+ }
6003
+ return Object.fromEntries(Object.entries(result).sort(([left], [right]) => left.localeCompare(right)));
6004
+ }
6005
+ function harmonyRoot(projectRoot) {
6006
+ const config = readJson6(path32.join(migrationDirectory(projectRoot), "config.json"));
6007
+ const configured = typeof config?.harmonyProject === "string" ? config.harmonyProject : "ohos";
6008
+ return path32.isAbsolute(configured) ? configured : path32.resolve(projectRoot, configured);
6009
+ }
6010
+ function existingGlobalHotspots(projectRoot) {
6011
+ const root = harmonyRoot(projectRoot);
6012
+ if (!fs26.existsSync(root) || !fs26.statSync(root).isDirectory()) return [];
6013
+ const relativeRoot = path32.relative(projectRoot, root).replaceAll("\\", "/");
6014
+ const candidates = [
6015
+ "build-profile.json5",
6016
+ "AppScope/app.json5",
6017
+ "oh-package.json5",
6018
+ "entry/oh-package.json5",
6019
+ "entry/src/main/module.json5",
6020
+ "entry/src/main/resources/base/profile/main_pages.json",
6021
+ "entry/src/main/ets/entryability/EntryAbility.ets",
6022
+ "entry/src/main/ets/pages/Index.ets",
6023
+ "entry/src/main/ets/common/RouteConstants.ets"
6024
+ ];
6025
+ return candidates.filter((relative2) => fs26.existsSync(path32.join(root, relative2))).map((relative2) => `${relativeRoot}/${relative2}`.replace(/^\.\//, "")).sort();
6026
+ }
6027
+ function loadExecutionPlanningContext(projectRootArgument) {
6028
+ const projectRoot = path32.resolve(projectRootArgument);
6029
+ const root = harmonyRoot(projectRoot);
6030
+ const rootAvailable = fs26.existsSync(root) && fs26.statSync(root).isDirectory();
6031
+ return {
6032
+ harmonyFilesByFactRef: rootAvailable ? reportHarmonyFiles(projectRoot, root) : {},
6033
+ globalHotspotFiles: existingGlobalHotspots(projectRoot)
6034
+ };
6035
+ }
6036
+ var emptyExecutionPlanningContext = {
6037
+ harmonyFilesByFactRef: {},
6038
+ globalHotspotFiles: []
6039
+ };
6040
+
6041
+ // src/planning/execution-planner.ts
6042
+ var typeOrder2 = {
6043
+ resolution: 0,
6044
+ bootstrap: 1,
6045
+ capability: 2,
6046
+ journey: 3,
6047
+ assembly: 4,
6048
+ regression: 5
6049
+ };
6050
+ function isReusableSurface(kind) {
6051
+ return /(?:list[-_ ]?item|item|reusable|layout|custom[-_ ]?view|cell|row)/i.test(kind);
6052
+ }
6053
+ function isHostedOverlay(kind) {
6054
+ return /(?:dialog|bottom[-_ ]?sheet|sheet|popup|overlay)/i.test(kind);
6055
+ }
6056
+ function canonicalPageRefs(facts) {
6057
+ const byId = new Map(facts.surfaces.map((surface) => [surface.id, surface]));
6058
+ const memo = /* @__PURE__ */ new Map();
6059
+ const resolve4 = (surfaceRef, visiting = /* @__PURE__ */ new Set()) => {
6060
+ if (memo.has(surfaceRef)) return memo.get(surfaceRef);
6061
+ const surface = byId.get(surfaceRef);
6062
+ if (!surface || visiting.has(surfaceRef)) return surfaceRef;
6063
+ visiting.add(surfaceRef);
6064
+ const parentPages = uniqueSorted(surface.parentSurfaceIds.filter((ref) => byId.has(ref)).map((ref) => resolve4(ref, visiting)));
6065
+ const canonical2 = parentPages.length === 1 && (isReusableSurface(surface.kind) || isHostedOverlay(surface.kind) || !surface.topLevel) ? parentPages[0] : surfaceRef;
6066
+ visiting.delete(surfaceRef);
6067
+ memo.set(surfaceRef, canonical2);
6068
+ return canonical2;
6069
+ };
6070
+ for (const surface of facts.surfaces) resolve4(surface.id);
6071
+ return Object.fromEntries([...memo.entries()].sort(([left], [right]) => left.localeCompare(right)));
6072
+ }
6073
+ function behaviorSourceSurfaceRefs(behavior, facts) {
6074
+ const entryRefs = new Set(behavior.closure.entryRefs);
6075
+ const fromEntries = facts.entries.filter((entry) => entryRefs.has(entry.id) && entry.ownerSurfaceId).map((entry) => entry.ownerSurfaceId);
6076
+ if (fromEntries.length) return uniqueSorted(fromEntries);
6077
+ const primarySurfaces = behavior.primaryFactRefs.filter((ref) => facts.surfaces.some((surface) => surface.id === ref));
6078
+ return uniqueSorted(primarySurfaces.length ? primarySurfaces : behavior.closure.surfaceRefs);
6079
+ }
6080
+ function behaviorCanonicalPage(behavior, facts, canonicalBySurface) {
6081
+ const pages = uniqueSorted(behaviorSourceSurfaceRefs(behavior, facts).map((ref) => canonicalBySurface[ref] ?? ref));
6082
+ return pages[0] ?? null;
6083
+ }
6084
+ function codeRefKey(value) {
6085
+ if (typeof value.id === "string") return value.id;
6086
+ return stableJson(Object.fromEntries(["file", "name", "symbol", "kind", "type", "capabilityRef"].filter((key) => value[key] !== void 0).map((key) => [key, value[key]])));
6087
+ }
6088
+ function behaviorCapabilityRefs(behavior) {
6089
+ return uniqueSorted(behavior.closure.codeRefs.flatMap((item) => {
6090
+ if (typeof item.capabilityRef === "string" && item.capabilityRef.length > 0) return [item.capabilityRef];
6091
+ if (item.kind === "capability" && typeof item.id === "string" && item.id.length > 0) return [item.id];
6092
+ return [];
6093
+ }));
6094
+ }
6095
+ function candidateForBehavior(behavior, facts, canonicalBySurface) {
6096
+ const effectRefs = new Set(behavior.closure.interactionRefs);
6097
+ const targets = facts.effects.filter((effect) => effectRefs.has(effect.id) && effect.targetKind === "surface").map((effect) => canonicalBySurface[effect.targetId] ?? effect.targetId);
6098
+ return {
6099
+ behaviorIds: [behavior.id],
6100
+ canonicalPageRef: behaviorCanonicalPage(behavior, facts, canonicalBySurface),
6101
+ type: behavior.type,
6102
+ sourceFiles: uniqueSorted([
6103
+ ...behavior.closure.sourceEvidence.map((item) => item.file),
6104
+ ...behavior.closure.codeRefs.flatMap((item) => typeof item.file === "string" ? [item.file] : [])
6105
+ ]),
6106
+ targetPageRefs: uniqueSorted(targets),
6107
+ codeRefKeys: uniqueSorted(behavior.closure.codeRefs.map(codeRefKey)),
6108
+ capabilityRefs: behaviorCapabilityRefs(behavior)
6109
+ };
6110
+ }
6111
+ function combineCandidates2(left, right) {
6112
+ return {
6113
+ behaviorIds: uniqueSorted([...left.behaviorIds, ...right.behaviorIds]),
6114
+ canonicalPageRef: left.canonicalPageRef,
6115
+ type: left.type,
6116
+ sourceFiles: uniqueSorted([...left.sourceFiles, ...right.sourceFiles]),
6117
+ targetPageRefs: uniqueSorted([...left.targetPageRefs, ...right.targetPageRefs]),
6118
+ codeRefKeys: uniqueSorted([...left.codeRefKeys, ...right.codeRefKeys]),
6119
+ capabilityRefs: uniqueSorted([...left.capabilityRefs, ...right.capabilityRefs])
6120
+ };
6121
+ }
6122
+ function intersects(left, right) {
6123
+ const values = new Set(left);
6124
+ return right.some((value) => values.has(value));
6125
+ }
6126
+ function behaviorRefs(candidate, behaviors) {
6127
+ return candidate.behaviorIds.map((id) => behaviors.get(id)).filter(Boolean);
6128
+ }
6129
+ function isRoutingAssembly(candidate) {
6130
+ return candidate.type === "journey" && candidate.targetPageRefs.some((target) => target !== candidate.canonicalPageRef);
6131
+ }
6132
+ function routingHotspotFiles(context) {
6133
+ return context.globalHotspotFiles.filter((file) => /(?:main_pages\.json|RouteConstants\.ets|\/pages\/Index\.ets)$/i.test(file));
6134
+ }
6135
+ function mappedTargetFiles(candidate, primaryRefs, context) {
6136
+ const mapped = uniqueSorted(primaryRefs.flatMap((ref) => context.harmonyFilesByFactRef[ref] ?? []));
6137
+ if (candidate.type === "bootstrap") return uniqueSorted([...mapped, ...context.globalHotspotFiles]);
6138
+ if (isRoutingAssembly(candidate)) return uniqueSorted([...mapped, ...routingHotspotFiles(context)]);
6139
+ return mapped;
6140
+ }
6141
+ function executionCost(candidate, behaviors, context) {
6142
+ const members = behaviorRefs(candidate, behaviors);
6143
+ const factRefs = uniqueSorted(members.flatMap((item) => [...item.primaryFactRefs, ...item.supportingFactRefs]));
6144
+ const mappedFiles = mappedTargetFiles(candidate, factRefs, context);
6145
+ const productionFiles = mappedFiles.filter((file) => !/(?:^|\/)(?:ohosTest|test|tests)(?:\/|$)|(?:Test|\.test)\.(?:ets|ts)$/i.test(file));
6146
+ const testFiles = mappedFiles.filter((file) => !productionFiles.includes(file));
6147
+ return {
6148
+ behaviorCount: members.length,
6149
+ observableOutcomeCount: uniqueSorted(members.map((item) => item.acceptance.observableOutcome)).length,
6150
+ productionFileCount: productionFiles.length,
6151
+ testFileCount: testFiles.length,
6152
+ capabilityCount: candidate.capabilityRefs.length,
6153
+ factCount: new Set(members.flatMap((item) => [...item.primaryFactRefs, ...item.supportingFactRefs])).size,
6154
+ sourceFileCount: uniqueSorted(members.flatMap((item) => [
6155
+ ...item.closure.sourceEvidence.map((evidence2) => evidence2.file),
6156
+ ...item.closure.codeRefs.flatMap((codeRef) => typeof codeRef.file === "string" ? [codeRef.file] : [])
6157
+ ])).length,
6158
+ traceNodeCount: uniqueBy(members.flatMap((item) => item.closure.codeRefs), codeRefKey).length,
6159
+ estimatedFactTokens: members.reduce((total, item) => total + item.estimate.estimatedFactTokens, 0)
6160
+ };
6161
+ }
6162
+ function withinExecutionBudget(candidate, behaviors, context) {
6163
+ const cost = executionCost(candidate, behaviors, context);
6164
+ 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;
6165
+ }
6166
+ function shouldMerge(left, right, behaviors, facts, context) {
6167
+ if (left.type !== "journey" || right.type !== "journey" || left.canonicalPageRef !== right.canonicalPageRef) return false;
6168
+ const members = [...behaviorRefs(left, behaviors), ...behaviorRefs(right, behaviors)];
6169
+ if (members.some((item) => item.closure.requiresSourceReview || item.closure.unresolvedRefs.length > 0)) return false;
6170
+ const combined = combineCandidates2(left, right);
6171
+ if (!withinExecutionBudget(combined, behaviors, context)) return false;
6172
+ const behaviorEntries = (candidate) => uniqueSorted(behaviorRefs(candidate, behaviors).flatMap((item) => item.closure.entryRefs));
6173
+ const targetedEntries = (candidate) => {
6174
+ const effects = new Set(behaviorRefs(candidate, behaviors).flatMap((item) => item.closure.interactionRefs));
6175
+ return uniqueSorted([...effects].flatMap((effectRef) => {
6176
+ const effect = facts.effects.find((item) => item.id === effectRef);
6177
+ return effect?.targetKind === "entry" ? [effect.targetId] : [];
6178
+ }));
6179
+ };
6180
+ const callbackLinked = intersects(targetedEntries(left), behaviorEntries(right)) || intersects(targetedEntries(right), behaviorEntries(left));
6181
+ if (callbackLinked || intersects(left.codeRefKeys, right.codeRefKeys)) return true;
6182
+ const sharedSource = intersects(left.sourceFiles, right.sourceFiles);
6183
+ const sharedTarget = left.targetPageRefs.length > 0 && right.targetPageRefs.length > 0 && intersects(left.targetPageRefs, right.targetPageRefs);
6184
+ const sharedCapability = intersects(left.capabilityRefs, right.capabilityRefs);
6185
+ return sharedSource && (sharedTarget || sharedCapability);
6186
+ }
6187
+ function clusterBehaviors(plan, facts, context) {
6188
+ const behaviors = new Map(plan.responsibilities.map((item) => [item.id, item]));
6189
+ const canonicalBySurface = canonicalPageRefs(facts);
6190
+ const clusters = [];
6191
+ for (const next of plan.responsibilities.map((item) => candidateForBehavior(item, facts, canonicalBySurface))) {
6192
+ const compatible = clusters.findIndex((current) => shouldMerge(current, next, behaviors, facts, context));
6193
+ if (compatible < 0) clusters.push(next);
6194
+ else clusters[compatible] = combineCandidates2(clusters[compatible], next);
6195
+ }
6196
+ return clusters;
6197
+ }
6198
+ function workPackageId(candidate) {
6199
+ if (candidate.type === "resolution") return null;
6200
+ return `package:${sha2562({ kind: "page", page: candidate.canonicalPageRef ?? "unassigned" }).slice(0, 16)}`;
6201
+ }
6202
+ function taskId(candidate) {
6203
+ return `task:${sha2562({ page: candidate.canonicalPageRef, type: candidate.type, behaviorIds: candidate.behaviorIds }).slice(0, 16)}`;
6204
+ }
6205
+ function taskTitle(candidate, members, facts) {
6206
+ if (members.length === 1) return members[0].title;
6207
+ const pageName = facts.surfaces.find((surface) => surface.id === candidate.canonicalPageRef)?.name ?? candidate.canonicalPageRef ?? "\u672A\u5F52\u5C5E\u6A21\u5757";
6208
+ return `${pageName}\uFF1A\u5B8C\u6210 ${members.length} \u4E2A\u76F8\u5173\u884C\u4E3A\u95ED\u73AF`;
6209
+ }
6210
+ function aggregateClosure(members) {
6211
+ return {
6212
+ entryRefs: uniqueSorted(members.flatMap((item) => item.closure.entryRefs)),
6213
+ surfaceRefs: uniqueSorted(members.flatMap((item) => item.closure.surfaceRefs)),
6214
+ controlRefs: uniqueSorted(members.flatMap((item) => item.closure.controlRefs)),
6215
+ interactionRefs: uniqueSorted(members.flatMap((item) => item.closure.interactionRefs)),
6216
+ sourceEvidence: uniqueBy(members.flatMap((item) => item.closure.sourceEvidence), (item) => `${item.file}:${item.symbol ?? ""}:${item.line ?? ""}:${item.endLine ?? ""}`),
6217
+ codeRefs: uniqueBy(members.flatMap((item) => item.closure.codeRefs), codeRefKey),
6218
+ unresolvedRefs: uniqueSorted(members.flatMap((item) => item.closure.unresolvedRefs)),
6219
+ warningRefs: uniqueSorted(members.flatMap((item) => item.closure.warningRefs)),
6220
+ requiresSourceReview: members.some((item) => item.closure.requiresSourceReview)
6221
+ };
6222
+ }
6223
+ function primaryModule(candidate) {
6224
+ if (candidate.type === "bootstrap") return "foundation:application-shell";
6225
+ if (candidate.type === "resolution") return `resolution:${candidate.behaviorIds[0]}`;
6226
+ if (isRoutingAssembly(candidate)) return "assembly:routing";
6227
+ return candidate.canonicalPageRef ? `page:${candidate.canonicalPageRef}` : "page:unassigned";
6228
+ }
6229
+ function initialTasks(clusters, behaviorPlan, facts, canonicalBySurface, context) {
6230
+ const behaviors = new Map(behaviorPlan.responsibilities.map((item) => [item.id, item]));
6231
+ return clusters.map((candidate) => {
6232
+ const members = behaviorRefs(candidate, behaviors);
6233
+ const primaryFactRefs = uniqueSorted(members.flatMap((item2) => item2.primaryFactRefs));
6234
+ const supportingFactRefs = uniqueSorted(members.flatMap((item2) => item2.supportingFactRefs).filter((ref) => !primaryFactRefs.includes(ref)));
6235
+ const mappedFiles = mappedTargetFiles(candidate, [...primaryFactRefs, ...supportingFactRefs], context);
6236
+ let ownedFiles;
6237
+ if (candidate.type === "resolution") ownedFiles = [];
6238
+ else if (candidate.type === "bootstrap") ownedFiles = mappedFiles;
6239
+ else ownedFiles = mappedFiles.filter((file) => isRoutingAssembly(candidate) || !context.globalHotspotFiles.includes(file));
6240
+ const cost = executionCost(candidate, behaviors, context);
6241
+ const outcomes = uniqueSorted(members.map((item2) => item2.acceptance.observableOutcome));
6242
+ const closure = aggregateClosure(members);
6243
+ const pageScopes = uniqueSorted(closure.surfaceRefs.map((surfaceRef) => `page:${canonicalBySurface[surfaceRef] ?? surfaceRef}`));
6244
+ const exclusiveScopes = candidate.type === "resolution" ? [`resolution:${candidate.behaviorIds[0]}`] : uniqueSorted([
6245
+ ...candidate.type === "bootstrap" ? ["foundation:application-shell"] : [],
6246
+ ...isRoutingAssembly(candidate) ? ["assembly:routing"] : [],
6247
+ ...pageScopes,
6248
+ ...pageScopes.length === 0 ? [candidate.canonicalPageRef ? `page:${candidate.canonicalPageRef}` : `${candidate.type}:unassigned`] : []
6249
+ ]);
6250
+ const item = {
6251
+ id: taskId(candidate),
6252
+ type: candidate.type,
6253
+ title: taskTitle(candidate, members, facts),
6254
+ workPackageId: workPackageId(candidate),
6255
+ canonicalPageRef: candidate.canonicalPageRef,
6256
+ primaryModule: primaryModule(candidate),
6257
+ behaviorIds: candidate.behaviorIds,
6258
+ primaryFactRefs,
6259
+ supportingFactRefs,
6260
+ dependsOnTaskIds: [],
6261
+ conflictsWithTaskIds: [],
6262
+ writeOwnership: {
6263
+ ownedFiles,
6264
+ readOnlyFiles: [],
6265
+ exclusiveScopes,
6266
+ forbiddenGlobalFiles: candidate.type === "bootstrap" ? [] : context.globalHotspotFiles.filter((file) => !ownedFiles.includes(file))
6267
+ },
6268
+ dependencyInterfaces: [],
6269
+ readyGate: {
6270
+ semanticsResolved: candidate.type === "resolution" || closure.unresolvedRefs.length === 0,
6271
+ dependencyInterfacesStable: true,
6272
+ writeOwnershipResolved: ownedFiles.length > 0 || exclusiveScopes.length > 0,
6273
+ contextWithinBudget: members.length === 1 || withinExecutionBudget(candidate, behaviors, context),
6274
+ acceptanceExecutable: members.every((member) => member.acceptance.verificationRequirements.length > 0)
6275
+ },
6276
+ closure,
6277
+ acceptance: {
6278
+ observableOutcome: outcomes.join("\uFF1B"),
6279
+ observableOutcomes: outcomes,
6280
+ requiredFactRefs: uniqueSorted(members.flatMap((item2) => item2.acceptance.requiredFactRefs)),
6281
+ verificationRequirements: uniqueSorted(members.flatMap((item2) => item2.acceptance.verificationRequirements)),
6282
+ reportRefs: uniqueSorted(members.flatMap((item2) => item2.acceptance.reportRefs))
6283
+ },
6284
+ estimate: {
6285
+ factCount: cost.factCount,
6286
+ sourceFileCount: cost.sourceFileCount,
6287
+ traceNodeCount: cost.traceNodeCount,
6288
+ estimatedFactTokens: cost.estimatedFactTokens,
6289
+ downstreamUnlockCount: 0,
6290
+ behaviorCount: cost.behaviorCount,
6291
+ observableOutcomeCount: cost.observableOutcomeCount,
6292
+ productionFileCount: cost.productionFileCount,
6293
+ testFileCount: cost.testFileCount,
6294
+ capabilityCount: cost.capabilityCount
6295
+ }
6296
+ };
6297
+ return item;
6298
+ }).sort((left, right) => typeOrder2[left.type] - typeOrder2[right.type] || left.id.localeCompare(right.id));
6299
+ }
6300
+ function hasPath(tasks, from, to) {
6301
+ const seen = /* @__PURE__ */ new Set();
6302
+ const queue = [...tasks.get(from)?.dependsOnTaskIds ?? []];
6303
+ while (queue.length) {
6304
+ const next = queue.shift();
6305
+ if (next === to) return true;
6306
+ if (seen.has(next)) continue;
6307
+ seen.add(next);
6308
+ queue.push(...tasks.get(next)?.dependsOnTaskIds ?? []);
6309
+ }
6310
+ return false;
6311
+ }
6312
+ function addDependency(tasks, taskId2, dependencyId) {
6313
+ if (taskId2 === dependencyId || hasPath(tasks, dependencyId, taskId2)) return;
6314
+ const task = tasks.get(taskId2);
6315
+ task.dependsOnTaskIds = uniqueSorted([...task.dependsOnTaskIds, dependencyId]);
6316
+ }
6317
+ function orderPackageLane(items) {
6318
+ const ids = new Set(items.map((item) => item.id));
6319
+ const remaining = new Set(ids);
6320
+ const result = [];
6321
+ while (remaining.size) {
6322
+ 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));
6323
+ const selected = ready[0] ?? items.filter((item) => remaining.has(item.id)).sort((left, right) => left.id.localeCompare(right.id))[0];
6324
+ result.push(selected);
6325
+ remaining.delete(selected.id);
6326
+ }
6327
+ return result;
6328
+ }
6329
+ function packageSerializationRanks(tasks) {
6330
+ const packageIds = uniqueSorted(tasks.flatMap((task) => task.workPackageId ? [task.workPackageId] : []));
6331
+ const taskById = new Map(tasks.map((task) => [task.id, task]));
6332
+ const dependencies = new Map(packageIds.map((packageId) => [packageId, /* @__PURE__ */ new Set()]));
6333
+ for (const task of tasks) {
6334
+ if (!task.workPackageId) continue;
6335
+ for (const dependencyId of task.dependsOnTaskIds) {
6336
+ const dependencyPackageId = taskById.get(dependencyId)?.workPackageId;
6337
+ if (dependencyPackageId && dependencyPackageId !== task.workPackageId) dependencies.get(task.workPackageId).add(dependencyPackageId);
6338
+ }
6339
+ }
6340
+ const remaining = new Set(packageIds);
6341
+ const ranks = /* @__PURE__ */ new Map();
6342
+ while (remaining.size) {
6343
+ const ready = packageIds.filter((packageId) => remaining.has(packageId) && [...dependencies.get(packageId)].every((dependencyId) => !remaining.has(dependencyId)));
6344
+ const selected = ready[0] ?? packageIds.find((packageId) => remaining.has(packageId));
6345
+ ranks.set(selected, ranks.size);
6346
+ remaining.delete(selected);
6347
+ }
6348
+ return ranks;
6349
+ }
6350
+ function conflictReasons(left, right) {
6351
+ const ownedOverlap = left.writeOwnership.ownedFiles.filter((file) => right.writeOwnership.ownedFiles.includes(file)).map((file) => `owned-file:${file}`);
6352
+ const scopeOverlap = left.writeOwnership.exclusiveScopes.filter((scope) => right.writeOwnership.exclusiveScopes.includes(scope)).map((scope) => `exclusive-scope:${scope}`);
6353
+ return uniqueSorted([...ownedOverlap, ...scopeOverlap]);
6354
+ }
6355
+ function wireDependenciesAndConflicts(tasks, behaviorPlan) {
6356
+ const byId = new Map(tasks.map((item) => [item.id, item]));
6357
+ const taskByBehavior = new Map(tasks.flatMap((item) => item.behaviorIds.map((behaviorId) => [behaviorId, item.id])));
6358
+ const behaviorById = new Map(behaviorPlan.responsibilities.map((item) => [item.id, item]));
6359
+ for (const task of tasks) {
6360
+ const dependencies = task.behaviorIds.flatMap((behaviorId) => behaviorById.get(behaviorId)?.dependsOnBehaviorIds ?? []).map((behaviorId) => taskByBehavior.get(behaviorId)).filter((id) => Boolean(id) && id !== task.id);
6361
+ task.dependsOnTaskIds = uniqueSorted(dependencies);
6362
+ }
6363
+ const packages = /* @__PURE__ */ new Map();
6364
+ for (const task of tasks) {
6365
+ if (task.workPackageId === null) continue;
6366
+ packages.set(task.workPackageId, [...packages.get(task.workPackageId) ?? [], task]);
6367
+ }
6368
+ for (const items of packages.values()) {
6369
+ const lane = orderPackageLane(items);
6370
+ for (let index = 1; index < lane.length; index++) addDependency(byId, lane[index].id, lane[index - 1].id);
6371
+ }
6372
+ const packageRanks = packageSerializationRanks(tasks);
6373
+ const edges = [];
6374
+ for (let leftIndex = 0; leftIndex < tasks.length; leftIndex++) {
6375
+ for (let rightIndex = leftIndex + 1; rightIndex < tasks.length; rightIndex++) {
6376
+ const left = tasks[leftIndex];
6377
+ const right = tasks[rightIndex];
6378
+ const reasons = conflictReasons(left, right);
6379
+ if (!reasons.length) continue;
6380
+ edges.push({ leftTaskId: left.id, rightTaskId: right.id, reasons });
6381
+ left.conflictsWithTaskIds = uniqueSorted([...left.conflictsWithTaskIds, right.id]);
6382
+ right.conflictsWithTaskIds = uniqueSorted([...right.conflictsWithTaskIds, left.id]);
6383
+ if (hasPath(byId, left.id, right.id) || hasPath(byId, right.id, left.id)) continue;
6384
+ const leftPackageRank = left.workPackageId ? packageRanks.get(left.workPackageId) : void 0;
6385
+ const rightPackageRank = right.workPackageId ? packageRanks.get(right.workPackageId) : void 0;
6386
+ if (leftPackageRank !== void 0 && rightPackageRank !== void 0 && leftPackageRank !== rightPackageRank) {
6387
+ const [later, earlier] = leftPackageRank > rightPackageRank ? [left, right] : [right, left];
6388
+ addDependency(byId, later.id, earlier.id);
6389
+ } else {
6390
+ addDependency(byId, right.id, left.id);
6391
+ }
6392
+ }
6393
+ }
6394
+ for (const task of tasks) {
6395
+ const providers = task.dependsOnTaskIds.map((id) => byId.get(id)).filter((item) => Boolean(item));
6396
+ const businessProviderIds = new Set(task.behaviorIds.flatMap((behaviorId) => behaviorById.get(behaviorId)?.dependsOnBehaviorIds ?? []).map((behaviorId) => taskByBehavior.get(behaviorId)).filter((id) => Boolean(id)));
6397
+ task.writeOwnership.readOnlyFiles = uniqueSorted(providers.flatMap((provider) => provider.writeOwnership.ownedFiles).filter((file) => !task.writeOwnership.ownedFiles.includes(file)));
6398
+ task.dependencyInterfaces = providers.map((provider) => {
6399
+ const kind = businessProviderIds.has(provider.id) ? "module" : "ordering";
6400
+ return {
6401
+ kind,
6402
+ ref: kind === "module" ? `interface:${provider.primaryModule}` : `ordering:${provider.id}`,
6403
+ providerTaskId: provider.id,
6404
+ files: provider.writeOwnership.ownedFiles.filter((file) => !task.writeOwnership.ownedFiles.includes(file)),
6405
+ 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"],
6406
+ orderingConstraints: [`${provider.id} must be locally verified before ${task.id}`]
6407
+ };
6408
+ });
6409
+ }
6410
+ return edges.sort((left, right) => `${left.leftTaskId}:${left.rightTaskId}`.localeCompare(`${right.leftTaskId}:${right.rightTaskId}`));
6411
+ }
6412
+ function downstreamCounts2(tasks) {
6413
+ const children = /* @__PURE__ */ new Map();
6414
+ for (const task of tasks) for (const dependency of task.dependsOnTaskIds) children.set(dependency, [...children.get(dependency) ?? [], task.id]);
6415
+ const result = /* @__PURE__ */ new Map();
6416
+ for (const task of tasks) {
6417
+ const seen = /* @__PURE__ */ new Set();
6418
+ const queue = [...children.get(task.id) ?? []];
6419
+ while (queue.length) {
6420
+ const next = queue.shift();
6421
+ if (seen.has(next)) continue;
6422
+ seen.add(next);
6423
+ queue.push(...children.get(next) ?? []);
6424
+ }
6425
+ result.set(task.id, seen.size);
6426
+ }
6427
+ return result;
6428
+ }
6429
+ function pageWorkPackages(tasks, facts, canonicalBySurface) {
6430
+ const grouped = /* @__PURE__ */ new Map();
6431
+ const taskById = new Map(tasks.map((task) => [task.id, task]));
6432
+ for (const task of tasks) {
6433
+ if (task.workPackageId === null) continue;
6434
+ grouped.set(task.workPackageId, [...grouped.get(task.workPackageId) ?? [], task]);
6435
+ }
6436
+ return [...grouped.entries()].map(([id, items]) => {
6437
+ const canonicalPageRef = items[0]?.canonicalPageRef ?? null;
6438
+ const title = facts.surfaces.find((surface) => surface.id === canonicalPageRef)?.name ?? canonicalPageRef ?? "Unassigned";
6439
+ const ordered = orderPackageLane(items);
6440
+ return {
6441
+ id,
6442
+ canonicalPageRef,
6443
+ title: `${title} Page Work Package`,
6444
+ surfaceRefs: uniqueSorted(facts.surfaces.filter((surface) => (canonicalBySurface[surface.id] ?? surface.id) === canonicalPageRef).map((surface) => surface.id)),
6445
+ workItemIds: ordered.map((item) => item.id),
6446
+ dependsOnPackageIds: uniqueSorted(items.flatMap((item) => item.dependsOnTaskIds).map((taskId2) => taskById.get(taskId2)?.workPackageId).filter((packageId) => Boolean(packageId && packageId !== id))),
6447
+ serialLane: true,
6448
+ exclusiveScopes: uniqueSorted(items.flatMap((item) => item.writeOwnership.exclusiveScopes))
6449
+ };
6450
+ }).sort((left, right) => left.id.localeCompare(right.id));
6451
+ }
6452
+ function compileExecutionPlan(behaviorPlan, facts, context = emptyExecutionPlanningContext) {
6453
+ const planningPhase = behaviorPlan.responsibilities.some((behavior) => behavior.type === "resolution") ? "resolution" : "execution";
6454
+ const activeBehaviorPlan = planningPhase === "resolution" ? { ...behaviorPlan, responsibilities: behaviorPlan.responsibilities.filter((behavior) => behavior.type === "resolution") } : behaviorPlan;
6455
+ const canonicalBySurface = canonicalPageRefs(facts);
6456
+ const clusters = planningPhase === "resolution" ? activeBehaviorPlan.responsibilities.map((behavior) => candidateForBehavior(behavior, facts, canonicalBySurface)) : clusterBehaviors(activeBehaviorPlan, facts, context);
6457
+ const tasks = initialTasks(clusters, activeBehaviorPlan, facts, canonicalBySurface, context);
6458
+ const fileConflictEdges = wireDependenciesAndConflicts(tasks, activeBehaviorPlan);
6459
+ const downstream = downstreamCounts2(tasks);
6460
+ for (const task of tasks) task.estimate.downstreamUnlockCount = downstream.get(task.id) ?? 0;
6461
+ const taskByBehavior = new Map(tasks.flatMap((task) => task.behaviorIds.map((behaviorId) => [behaviorId, task.id])));
6462
+ const primaryOwnerByFactRef = Object.fromEntries(Object.entries(behaviorPlan.ownership.primaryOwnerByFactRef).flatMap(([ref, behaviorId]) => {
6463
+ const taskId2 = taskByBehavior.get(behaviorId);
6464
+ return taskId2 ? [[ref, taskId2]] : [];
6465
+ }).sort(([left], [right]) => left.localeCompare(right)));
6466
+ const supportingTaskIdsByFactRef = {};
6467
+ for (const task of tasks) for (const ref of task.supportingFactRefs) supportingTaskIdsByFactRef[ref] = uniqueSorted([...supportingTaskIdsByFactRef[ref] ?? [], task.id]);
6468
+ return {
6469
+ taskPlanVersion: TASK_PLAN_VERSION,
6470
+ executionPlanVersion: EXECUTION_PLAN_VERSION,
6471
+ planningModelVersion: behaviorPlan.planningModelVersion,
6472
+ factsAdapterVersion: behaviorPlan.factsAdapterVersion,
6473
+ plannerPolicyVersion: behaviorPlan.plannerPolicyVersion,
6474
+ baseline: behaviorPlan.baseline,
6475
+ planningPhase,
6476
+ entryResolution: behaviorPlan.entryResolution,
6477
+ behaviorResponsibilities: behaviorPlan.responsibilities,
6478
+ pageWorkPackages: pageWorkPackages(tasks, facts, canonicalBySurface),
6479
+ tasks,
6480
+ ownership: {
6481
+ primaryOwnerByFactRef,
6482
+ supportingTaskIdsByFactRef: Object.fromEntries(Object.entries(supportingTaskIdsByFactRef).sort(([left], [right]) => left.localeCompare(right)))
6483
+ },
6484
+ behaviorOwnership: behaviorPlan.ownership,
6485
+ fileConflictEdges,
6486
+ planningWarnings: behaviorPlan.planningWarnings
6487
+ };
6488
+ }
6489
+ function compileFromPlanningFacts(facts, androidFingerprint, context = emptyExecutionPlanningContext) {
6490
+ return compileExecutionPlan(compileBehaviorPlanFromPlanningFacts(facts, androidFingerprint), facts, context);
6491
+ }
6492
+
6493
+ // src/planning/storage.ts
6494
+ import fs27 from "fs";
6130
6495
  import path33 from "path";
6131
6496
  function taskPlanRoot(projectRoot) {
6132
6497
  return path33.join(migrationDirectory(projectRoot), "tasks");
6133
6498
  }
6134
- function taskArtifactStem(taskId) {
6135
- return taskId.replace(/^task:/, "");
6499
+ function taskArtifactStem(taskId2) {
6500
+ return taskId2.replace(/^task:/, "");
6501
+ }
6502
+ function workPackageArtifactStem(workPackageId2) {
6503
+ return workPackageId2.replace(/^package:/, "");
6504
+ }
6505
+ function codeRefIndex(value) {
6506
+ const allowed = ["id", "file", "name", "symbol", "kind", "type", "capabilityRef", "line", "startLine", "endLine"];
6507
+ return Object.fromEntries(allowed.filter((key) => value[key] !== void 0).map((key) => [key, value[key]]));
6508
+ }
6509
+ function taskContractView(task) {
6510
+ return { ...task, closure: { ...task.closure, codeRefs: task.closure.codeRefs.map(codeRefIndex) } };
6136
6511
  }
6137
- function renderTaskBrief(task) {
6512
+ function coreFactsForTask(task, facts) {
6513
+ return coreFactsForRefs([...task.primaryFactRefs, ...task.supportingFactRefs], task.closure.unresolvedRefs, facts);
6514
+ }
6515
+ function coreFactsForRefs(refsInput, unresolvedRefs, facts) {
6516
+ const refs = new Set(refsInput);
6517
+ return [
6518
+ ...facts.surfaces.filter((item) => refs.has(item.id)).map((value) => ({ kind: "surface", value })),
6519
+ ...facts.controls.filter((item) => refs.has(item.id)).map((value) => ({ kind: "control", value })),
6520
+ ...facts.entries.filter((item) => refs.has(item.id)).map((value) => ({
6521
+ kind: "entry",
6522
+ value: { ...value, trace: { ...value.trace, codeRefs: value.trace.codeRefs.map(codeRefIndex) } }
6523
+ })),
6524
+ ...facts.effects.filter((item) => refs.has(item.id)).map((value) => ({ kind: "effect", value })),
6525
+ ...facts.issues.filter((item) => refs.has(item.id) || unresolvedRefs.includes(item.id)).map((value) => ({ kind: "issue", value }))
6526
+ ];
6527
+ }
6528
+ function sourceContextForTask(task) {
6529
+ return uniqueBy([
6530
+ ...task.closure.sourceEvidence.map((value) => ({ kind: "source-evidence", value })),
6531
+ ...task.closure.codeRefs.map((value) => ({ kind: "code-ref", value: codeRefIndex(value) }))
6532
+ ], stableJson);
6533
+ }
6534
+ function tasksForPackage(workPackage, plan) {
6535
+ const byId = new Map(plan.tasks.map((task) => [task.id, task]));
6536
+ return workPackage.workItemIds.map((id) => byId.get(id)).filter((task) => Boolean(task));
6537
+ }
6538
+ function effectOutcome2(effectRef, facts) {
6539
+ const effect = facts.effects.find((item) => item.id === effectRef);
6540
+ if (!effect) return effectRef;
6541
+ 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;
6542
+ const label = target ?? effect.targetId;
6543
+ if (effect.kind === "opens") return `\u6253\u5F00 ${label}`;
6544
+ if (effect.kind === "navigates-to") return `\u8FDB\u5165 ${label}`;
6545
+ if (effect.kind === "launches-for-result") return `\u542F\u52A8 ${label} \u5E76\u5904\u7406\u8FD4\u56DE\u7ED3\u679C`;
6546
+ return `\u5904\u7406 ${label}`;
6547
+ }
6548
+ function pageWorkPackageContractView(workPackage, plan, facts) {
6549
+ const tasks = tasksForPackage(workPackage, plan);
6550
+ const taskByBehavior = new Map(tasks.flatMap((task) => task.behaviorIds.map((behaviorId) => [behaviorId, task])));
6551
+ const behaviorById = new Map(plan.behaviorResponsibilities.map((behavior) => [behavior.id, behavior]));
6552
+ const orderedBehaviors = tasks.flatMap((task) => task.behaviorIds.map((id) => behaviorById.get(id))).filter((item) => Boolean(item));
6553
+ const ownedFiles = uniqueSorted(tasks.flatMap((task) => task.writeOwnership.ownedFiles));
6554
+ const packageByTask = new Map(plan.tasks.flatMap((task) => task.workPackageId ? [[task.id, task.workPackageId]] : []));
6555
+ const dependencyGroups = /* @__PURE__ */ new Map();
6556
+ for (const task of tasks) for (const dependency of task.dependencyInterfaces) {
6557
+ const providerTaskId = dependency.providerTaskId;
6558
+ const providerPackageId = providerTaskId ? packageByTask.get(providerTaskId) : null;
6559
+ if (!providerTaskId || !providerPackageId || providerPackageId === workPackage.id) continue;
6560
+ const key = `${dependency.kind}:${dependency.ref}:${providerPackageId}`;
6561
+ const current = dependencyGroups.get(key) ?? {
6562
+ kind: dependency.kind,
6563
+ ref: dependency.ref,
6564
+ providerPackageId,
6565
+ providerWorkItemIds: [],
6566
+ files: [],
6567
+ errorModes: [],
6568
+ orderingConstraints: []
6569
+ };
6570
+ current.providerWorkItemIds = uniqueSorted([...current.providerWorkItemIds, providerTaskId]);
6571
+ current.files = uniqueSorted([...current.files, ...dependency.files]);
6572
+ current.errorModes = uniqueSorted([...current.errorModes, ...dependency.errorModes]);
6573
+ current.orderingConstraints = uniqueSorted([...current.orderingConstraints, ...dependency.orderingConstraints]);
6574
+ dependencyGroups.set(key, current);
6575
+ }
6576
+ const sourceFiles = uniqueSorted(tasks.flatMap((task) => [
6577
+ ...task.closure.sourceEvidence.map((item) => item.file),
6578
+ ...task.closure.codeRefs.flatMap((item) => typeof item.file === "string" ? [item.file] : [])
6579
+ ]));
6580
+ return {
6581
+ ...workPackage,
6582
+ behaviorIds: orderedBehaviors.map((behavior) => behavior.id),
6583
+ workItems: tasks.map((task, index) => ({
6584
+ ordinal: index + 1,
6585
+ id: task.id,
6586
+ type: task.type,
6587
+ title: task.title,
6588
+ behaviorIds: task.behaviorIds
6589
+ })),
6590
+ features: orderedBehaviors.map((behavior, index) => ({
6591
+ ordinal: index + 1,
6592
+ behaviorId: behavior.id,
6593
+ workItemId: taskByBehavior.get(behavior.id).id,
6594
+ type: behavior.type,
6595
+ title: behavior.title,
6596
+ triggerLabels: uniqueSorted(facts.entries.filter((entry) => behavior.closure.entryRefs.includes(entry.id)).map((entry) => entry.label)),
6597
+ controlNames: uniqueSorted(facts.controls.filter((control) => behavior.closure.controlRefs.includes(control.id)).map((control) => control.name)),
6598
+ interactionOutcomes: uniqueSorted(behavior.closure.interactionRefs.map((ref) => effectOutcome2(ref, facts))),
6599
+ entryRefs: behavior.closure.entryRefs,
6600
+ surfaceRefs: behavior.closure.surfaceRefs,
6601
+ controlRefs: behavior.closure.controlRefs,
6602
+ interactionRefs: behavior.closure.interactionRefs,
6603
+ unresolvedRefs: behavior.closure.unresolvedRefs,
6604
+ primaryFactRefs: behavior.primaryFactRefs,
6605
+ supportingFactRefs: behavior.supportingFactRefs,
6606
+ requiresSourceReview: behavior.closure.requiresSourceReview,
6607
+ observableOutcome: behavior.acceptance.observableOutcome,
6608
+ requiredFactRefs: behavior.acceptance.requiredFactRefs,
6609
+ verificationRequirements: behavior.acceptance.verificationRequirements,
6610
+ reportRefs: behavior.acceptance.reportRefs
6611
+ })),
6612
+ writeOwnership: {
6613
+ ownedFiles,
6614
+ readOnlyFiles: uniqueSorted(tasks.flatMap((task) => task.writeOwnership.readOnlyFiles).filter((file) => !ownedFiles.includes(file))),
6615
+ exclusiveScopes: workPackage.exclusiveScopes,
6616
+ forbiddenGlobalFiles: uniqueSorted(tasks.flatMap((task) => task.writeOwnership.forbiddenGlobalFiles).filter((file) => !ownedFiles.includes(file)))
6617
+ },
6618
+ dependencyInterfaces: [...dependencyGroups.values()].sort((left, right) => `${left.providerPackageId}:${left.ref}`.localeCompare(`${right.providerPackageId}:${right.ref}`)),
6619
+ acceptance: {
6620
+ observableOutcomes: uniqueSorted(orderedBehaviors.map((behavior) => behavior.acceptance.observableOutcome)),
6621
+ requiredFactRefs: uniqueSorted(orderedBehaviors.flatMap((behavior) => behavior.acceptance.requiredFactRefs)),
6622
+ verificationRequirements: uniqueSorted(orderedBehaviors.flatMap((behavior) => behavior.acceptance.verificationRequirements)),
6623
+ reportRefs: uniqueSorted(orderedBehaviors.flatMap((behavior) => behavior.acceptance.reportRefs))
6624
+ },
6625
+ estimate: {
6626
+ behaviorCount: orderedBehaviors.length,
6627
+ workItemCount: tasks.length,
6628
+ factCount: uniqueSorted(tasks.flatMap((task) => [...task.primaryFactRefs, ...task.supportingFactRefs])).length,
6629
+ sourceFileCount: sourceFiles.length,
6630
+ traceNodeCount: uniqueBy(tasks.flatMap((task) => task.closure.codeRefs), stableJson).length,
6631
+ estimatedFactTokens: tasks.reduce((total, task) => total + task.estimate.estimatedFactTokens, 0),
6632
+ productionFileCount: uniqueSorted(tasks.flatMap((task) => task.writeOwnership.ownedFiles).filter((file) => !/(^|\/)(test|ohosTest)(\/|$)|\.(test|spec)\./i.test(file))).length,
6633
+ testFileCount: uniqueSorted(tasks.flatMap((task) => task.writeOwnership.ownedFiles).filter((file) => /(^|\/)(test|ohosTest)(\/|$)|\.(test|spec)\./i.test(file))).length,
6634
+ capabilityCount: tasks.reduce((total, task) => total + task.estimate.capabilityCount, 0)
6635
+ }
6636
+ };
6637
+ }
6638
+ function coreFactsForPageWorkPackage(workPackage, facts) {
6639
+ const tasksRefs = workPackage.features.flatMap((feature) => [...feature.primaryFactRefs, ...feature.supportingFactRefs]);
6640
+ return coreFactsForRefs(tasksRefs, workPackage.features.flatMap((feature) => feature.unresolvedRefs), facts);
6641
+ }
6642
+ function sourceContextForPageWorkPackage(workPackage, plan) {
6643
+ return uniqueBy(tasksForPackage(workPackage, plan).flatMap(sourceContextForTask), stableJson);
6644
+ }
6645
+ function renderResolutionTaskBrief(task, plan) {
6646
+ if (task.type !== "resolution") throw new Error("Resolution brief requires a Resolution Task");
6138
6647
  const list = (values) => values.length ? values.map((value) => `- \`${value}\``).join("\n") : "- \u65E0";
6139
6648
  const sourceLocations = [.../* @__PURE__ */ new Set([
6140
6649
  ...task.closure.sourceEvidence.map((item) => `${item.file}${item.line ? `:${item.line}${(() => {
@@ -6152,15 +6661,54 @@ function renderTaskBrief(task) {
6152
6661
  return "";
6153
6662
  })()}`] : [])
6154
6663
  ])].sort();
6155
- return `# ${task.title}
6664
+ const sourcePreview = sourceLocations.slice(0, 20);
6665
+ const hiddenSourceCount = sourceLocations.length - sourcePreview.length;
6666
+ const taskKind = "Resolution Task";
6667
+ const identity = [
6668
+ `- ${taskKind} ID\uFF1A\`${task.id}\``,
6669
+ `- Execution Plan Hash\uFF1A\`${sha2562(plan)}\``,
6670
+ `- Facts SHA256\uFF1A\`${plan.baseline.factsSha256}\``,
6671
+ `- Android Fingerprint\uFF1A${plan.baseline.androidFingerprint ? `\`${plan.baseline.androidFingerprint}\`` : "\u672A\u63D0\u4F9B"}`,
6672
+ `- Behavior IDs\uFF1A${task.behaviorIds.map((id) => `\`${id}\``).join("\u3001")}`,
6673
+ `- Primary Page / Module\uFF1A\`${task.canonicalPageRef ?? "unassigned"}\` / \`${task.primaryModule}\``
6674
+ ].join("\n");
6675
+ const ownership = (values, fallback) => values.length ? values.map((value) => `- \`${value}\``).join("\n") : `- ${fallback}`;
6676
+ return `# ${taskKind}\uFF1A${task.title}
6156
6677
 
6157
- ## \u76EE\u6807
6678
+ ## 1. \u6700\u7EC8\u76EE\u6807
6158
6679
 
6159
6680
  ${task.acceptance.observableOutcome}
6160
6681
 
6161
- ## \u884C\u4E3A\u95ED\u5305
6682
+ ## 2. \u8EAB\u4EFD\u4E0E\u57FA\u7EBF
6683
+
6684
+ ${identity}
6685
+
6686
+ ## 3. \u5B8C\u6210\u5B9A\u4E49
6687
+
6688
+ ${task.acceptance.observableOutcomes.map((outcome) => `- ${outcome}`).join("\n")}
6689
+
6690
+ ## 4. \u5199\u5165\u6240\u6709\u6743
6691
+
6692
+ ### \u5141\u8BB8\u4FEE\u6539
6693
+
6694
+ ${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`)}
6695
+
6696
+ ### \u53EA\u8BFB\u4F9D\u8D56
6697
+
6698
+ ${ownership(task.writeOwnership.readOnlyFiles, "\u65E0")}
6699
+
6700
+ ### \u7981\u6B62\u4FEE\u6539
6701
+
6702
+ ${ownership(task.writeOwnership.forbiddenGlobalFiles, "\u65E0")}
6703
+
6704
+ ## 5. \u524D\u7F6E\u4F9D\u8D56\u4E0E Interface
6705
+
6706
+ ${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"}
6707
+
6708
+ ## 6. \u884C\u4E3A\u4E0E\u72B6\u6001\u673A
6162
6709
 
6163
6710
  - Task \u7C7B\u578B\uFF1A\`${task.type}\`
6711
+ - Work Package\uFF1A${task.workPackageId ? `\`${task.workPackageId}\`` : "\u4E0D\u9002\u7528\uFF08Resolution Task \u4E0D\u53C2\u4E0E Coding Work Package\uFF09"}
6164
6712
  - \u5165\u53E3\u6570\uFF1A${task.closure.entryRefs.length}
6165
6713
  - \u9875\u9762\u6570\uFF1A${task.closure.surfaceRefs.length}
6166
6714
  - \u63A7\u4EF6\u6570\uFF1A${task.closure.controlRefs.length}
@@ -6172,171 +6720,388 @@ ${task.acceptance.observableOutcome}
6172
6720
  - \u4EA4\u4E92\uFF1A${task.closure.interactionRefs.map((ref) => `\`${ref}\``).join("\u3001") || "\u65E0"}
6173
6721
  - \u9700\u8981\u6E90\u7801\u590D\u6838\uFF1A${task.closure.requiresSourceReview ? "\u662F" : "\u5426"}
6174
6722
 
6175
- ## \u6E90\u7801\u4E0A\u4E0B\u6587
6723
+ ## 7. Android \u8BC1\u636E
6724
+
6725
+ ${sourcePreview.length ? `${sourcePreview.map((value) => `- ${value}`).join("\n")}${hiddenSourceCount > 0 ? `
6726
+ - \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
6727
 
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"}
6728
+ ## 8. Facts \u95ED\u5305
6178
6729
 
6179
- ## \u5F85\u88C1\u51B3\u95EE\u9898
6730
+ ### \u5F85\u88C1\u51B3\u95EE\u9898
6180
6731
 
6181
6732
  ${list(task.closure.unresolvedRefs)}
6182
6733
 
6183
- ## \u4E3B\u6240\u6709\u6743 Facts
6734
+ ### \u4E3B\u6240\u6709\u6743 Facts
6184
6735
 
6185
6736
  ${list(task.primaryFactRefs)}
6186
6737
 
6187
- ## \u652F\u6301 Facts
6738
+ ### Supporting Facts
6188
6739
 
6189
6740
  ${list(task.supportingFactRefs)}
6190
6741
 
6191
- ## \u4F9D\u8D56 Task
6742
+ ## 9. \u5B9E\u73B0\u7EA6\u675F
6743
+
6744
+ - \u672C Resolution Task \u53EA\u88C1\u51B3 Planning Facts\uFF1B\u4E0D\u5F97\u4FEE\u6539 HarmonyOS \u4E1A\u52A1\u5B9E\u73B0\u3002
6745
+ - \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
6746
+ - \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
6747
+ - 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
6748
+
6749
+ ## 10. \u975E\u76EE\u6807
6750
+
6751
+ - \u4E0D\u6269\u5927\u5230\u5F53\u524D Behavior IDs \u4E4B\u5916\u7684\u72EC\u7ACB\u4E1A\u52A1\u7ED3\u679C\u3002
6752
+ - \u4E0D\u7F16\u5199 ArkTS/ArkUI\uFF0C\u4E0D\u628A\u6E90\u7801\u9605\u8BFB\u672C\u8EAB\u89C6\u4E3A\u88C1\u51B3\u5B8C\u6210\u3002
6753
+ - \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
6192
6754
 
6193
- ${list(task.dependsOnTaskIds)}
6755
+ ## 11. \u9A8C\u6536\u547D\u4EE4\u4E0E\u573A\u666F
6756
+
6757
+ ${task.acceptance.verificationRequirements.map((value) => `- ${value}`).join("\n")}
6758
+
6759
+ \u9A8C\u6536\u5FC5\u987B\u8986\u76D6\u7684 Report refs\uFF1A${task.acceptance.reportRefs.map((ref) => `\`${ref}\``).join("\u3001") || "\u65E0"}\u3002
6760
+
6761
+ ## 12. \u5B8C\u6210\u56DE\u62A5\u683C\u5F0F
6762
+
6763
+ - \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
6764
+ - \u6BCF\u4E2A Behavior ID \u5BF9\u5E94\u7684\u88C1\u51B3\u7ED3\u679C\u3002
6765
+ - \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
6766
+ - \u672A\u89E3\u51B3\u95EE\u9898\uFF1B\u6CA1\u6709\u5219\u660E\u786E\u5199\u201C\u65E0\u201D\u3002
6194
6767
 
6195
6768
  ## \u5DE5\u4F5C\u91CF\u63D0\u793A
6196
6769
 
6197
6770
  - Fact \u6570\uFF1A${task.estimate.factCount}
6771
+ - Behavior \u6570\uFF1A${task.estimate.behaviorCount}
6772
+ - \u53EF\u89C2\u5BDF\u7ED3\u679C\u6570\uFF1A${task.estimate.observableOutcomeCount}
6198
6773
  - \u6E90\u7801\u6587\u4EF6\u6570\uFF1A${task.estimate.sourceFileCount}
6774
+ - \u751F\u4EA7\u4EE3\u7801\u6587\u4EF6\u6570\uFF1A${task.estimate.productionFileCount}
6775
+ - \u6D4B\u8BD5\u6587\u4EF6\u6570\uFF1A${task.estimate.testFileCount}
6776
+ - Capability \u6570\uFF1A${task.estimate.capabilityCount}
6199
6777
  - \u8C03\u7528\u94FE\u8282\u70B9\u6570\uFF1A${task.estimate.traceNodeCount}
6200
6778
  - \u4F30\u7B97 Facts Token\uFF1A${task.estimate.estimatedFactTokens}
6201
6779
  - \u53EF\u89E3\u9501\u4E0B\u6E38 Task\uFF1A${task.estimate.downstreamUnlockCount}
6780
+ `;
6781
+ }
6782
+ function renderPageWorkPackageBrief(workPackage, plan, facts) {
6783
+ const quoteList = (values, fallback = "\u65E0") => values.length ? values.map((value) => `- \`${value}\``).join("\n") : `- ${fallback}`;
6784
+ const surfaceNames = workPackage.surfaceRefs.map((ref) => `${facts.surfaces.find((surface) => surface.id === ref)?.name ?? ref}\uFF08\`${ref}\`\uFF09`);
6785
+ const featureSections = workPackage.features.map((feature) => {
6786
+ const behavior = plan.behaviorResponsibilities.find((item) => item.id === feature.behaviorId);
6787
+ const sources = uniqueSorted([
6788
+ ...behavior.closure.sourceEvidence.map((item) => `${item.file}${item.line ? `:${item.line}${item.endLine && item.endLine !== item.line ? `-${item.endLine}` : ""}` : ""}${item.symbol ? ` \u2014 ${item.symbol}` : ""}`),
6789
+ ...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}` : ""}`] : [])
6790
+ ]);
6791
+ return `### F-${String(feature.ordinal).padStart(3, "0")}\uFF1A${feature.title}
6202
6792
 
6203
- ## \u9A8C\u6536\u8981\u6C42
6793
+ - Behavior / Work Item\uFF1A\`${feature.behaviorId}\` / \`${feature.workItemId}\`
6794
+ - \u89E6\u53D1\uFF1A${feature.triggerLabels.join("\u3001") || "\u7531 Android \u6E90\u7801\u5165\u53E3\u786E\u5B9A"}
6795
+ - \u63A7\u4EF6\uFF1A${feature.controlNames.join("\u3001") || "\u65E0\u663E\u5F0F\u63A7\u4EF6"}
6796
+ - \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"}
6797
+ - \u5B9E\u73B0\u7ED3\u679C\uFF1A${feature.observableOutcome}
6798
+ - Required Fact refs\uFF1A${feature.requiredFactRefs.map((ref) => `\`${ref}\``).join("\u3001") || "\u65E0"}
6799
+ - \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"}
6800
+ - Android \u8BC1\u636E\uFF1A
6801
+ ${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"}
6802
+ - Report refs\uFF1A${feature.reportRefs.map((ref) => `\`${ref}\``).join("\u3001") || "\u65E0"}`;
6803
+ }).join("\n\n");
6804
+ const tasksById = new Map(plan.tasks.map((task) => [task.id, task]));
6805
+ const packageTaskIds = new Set(workPackage.workItemIds);
6806
+ const internalSequence = workPackage.workItems.map((item) => {
6807
+ const dependencies = (tasksById.get(item.id)?.dependsOnTaskIds ?? []).filter((id) => packageTaskIds.has(id));
6808
+ return `${item.ordinal}. \`${item.id}\` \u2014 ${item.title}${dependencies.length ? `\uFF1B\u9875\u9762\u5185\u524D\u7F6E\uFF1A${dependencies.map((id) => `\`${id}\``).join("\u3001")}` : ""}`;
6809
+ }).join("\n");
6810
+ 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";
6811
+ const acceptanceSequences = workPackage.features.map((feature) => `### F-${String(feature.ordinal).padStart(3, "0")}
6204
6812
 
6205
- ${task.acceptance.verificationRequirements.map((value) => `- ${value}`).join("\n")}
6813
+ 1. \u4ECE\u771F\u5B9E\u5E94\u7528\u5165\u53E3\u5230\u8FBE\u672C Page Work Package \u627F\u8F7D\u9762\u3002
6814
+ 2. \u89E6\u53D1\u201C${feature.triggerLabels.join(" / ") || feature.title}\u201D\u3002
6815
+ 3. \u65AD\u8A00\uFF1A${feature.observableOutcome}
6816
+ 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");
6817
+ return `# Page Work Package\uFF1A${workPackage.title.replace(/ Page Work Package$/, "")}
6818
+
6819
+ > \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
6820
+
6821
+ ## 1. \u9875\u9762\u6267\u884C\u5951\u7EA6
6822
+
6823
+ - Page Work Package ID\uFF1A\`${workPackage.id}\`
6824
+ - Execution Plan Hash\uFF1A\`${sha2562(plan)}\`
6825
+ - Facts SHA256\uFF1A\`${plan.baseline.factsSha256}\`
6826
+ - Android Fingerprint\uFF1A${plan.baseline.androidFingerprint ? `\`${plan.baseline.androidFingerprint}\`` : "\u672A\u63D0\u4F9B"}
6827
+ - Canonical Page\uFF1A\`${workPackage.canonicalPageRef ?? "unassigned"}\`
6828
+ - \u627F\u8F7D\u9762\uFF1A${surfaceNames.join("\u3001") || "\u65E0"}
6829
+ - Work Item \u6570\uFF1A${workPackage.workItems.length}
6830
+ - Behavior / \u9875\u9762\u529F\u80FD\u6570\uFF1A${workPackage.features.length}
6831
+
6832
+ ## 2. \u9875\u9762\u8303\u56F4\u4E0E\u6700\u7EC8\u76EE\u6807
6833
+
6834
+ \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
6835
+
6836
+ ${workPackage.acceptance.observableOutcomes.map((outcome) => `- ${outcome}`).join("\n")}
6837
+
6838
+ ## 3. \u5177\u4F53\u5B9E\u73B0\u529F\u80FD
6839
+
6840
+ ${featureSections}
6841
+
6842
+ ## 4. \u9875\u9762\u72B6\u6001\u4E0E Facts \u95ED\u5305
6843
+
6844
+ - Entry refs\uFF1A${uniqueSorted(workPackage.features.flatMap((feature) => feature.entryRefs)).map((ref) => `\`${ref}\``).join("\u3001") || "\u65E0"}
6845
+ - Surface refs\uFF1A${uniqueSorted(workPackage.features.flatMap((feature) => feature.surfaceRefs)).map((ref) => `\`${ref}\``).join("\u3001") || "\u65E0"}
6846
+ - Control refs\uFF1A${uniqueSorted(workPackage.features.flatMap((feature) => feature.controlRefs)).map((ref) => `\`${ref}\``).join("\u3001") || "\u65E0"}
6847
+ - Interaction refs\uFF1A${uniqueSorted(workPackage.features.flatMap((feature) => feature.interactionRefs)).map((ref) => `\`${ref}\``).join("\u3001") || "\u65E0"}
6848
+ - \u5F85\u88C1\u51B3 refs\uFF1A${uniqueSorted(workPackage.features.flatMap((feature) => feature.unresolvedRefs)).map((ref) => `\`${ref}\``).join("\u3001") || "\u65E0"}
6849
+
6850
+ \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
6851
+
6852
+ ## 5. \u9875\u9762\u5185\u5B9E\u65BD\u987A\u5E8F
6853
+
6854
+ ${internalSequence}
6855
+
6856
+ \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
6857
+
6858
+ ## 6. \u5199\u5165\u6240\u6709\u6743
6859
+
6860
+ ### \u5141\u8BB8\u4FEE\u6539
6861
+
6862
+ ${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`)}
6863
+
6864
+ ### \u53EA\u8BFB\u4F9D\u8D56
6865
+
6866
+ ${quoteList(workPackage.writeOwnership.readOnlyFiles)}
6867
+
6868
+ ### \u7981\u6B62\u4FEE\u6539
6869
+
6870
+ ${quoteList(workPackage.writeOwnership.forbiddenGlobalFiles)}
6871
+
6872
+ ### \u72EC\u5360\u8303\u56F4
6873
+
6874
+ ${quoteList(workPackage.writeOwnership.exclusiveScopes)}
6875
+
6876
+ ## 7. \u8DE8\u9875\u9762\u4F9D\u8D56\u4E0E Interface
6877
+
6878
+ ${dependencyText}
6879
+
6880
+ \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
6206
6881
 
6207
- ## \u975E\u76EE\u6807
6882
+ ## 8. UI \u64CD\u4F5C\u4E0E\u9A8C\u6536\u5E8F\u5217
6208
6883
 
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
6884
+ ${acceptanceSequences}
6885
+
6886
+ \u901A\u7528\u9A8C\u8BC1\u8981\u6C42\uFF1A
6887
+
6888
+ ${workPackage.acceptance.verificationRequirements.map((value) => `- ${value}`).join("\n")}
6889
+
6890
+ \u9A8C\u6536\u5FC5\u987B\u8986\u76D6\u7684 Report refs\uFF1A${workPackage.acceptance.reportRefs.map((ref) => `\`${ref}\``).join("\u3001") || "\u65E0"}\u3002
6891
+
6892
+ ## 9. \u975E\u76EE\u6807\u4E0E\u5B9E\u73B0\u7EA6\u675F
6893
+
6894
+ - \u4E0D\u6269\u5927\u5230\u672C\u5305 Behavior IDs \u4E4B\u5916\u7684\u72EC\u7ACB\u4E1A\u52A1\u7ED3\u679C\u3002
6895
+ - \u4E0D\u4FEE\u6539\u53EA\u8BFB\u6216\u7981\u6B62\u6587\u4EF6\uFF0C\u4E0D\u91CD\u6784\u4E0A\u6E38 Module \u7684 Implementation\u3002
6896
+ - \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
6897
+ - \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
6898
+ - \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
6899
+
6900
+ ## 10. \u5B8C\u6210\u56DE\u62A5\u683C\u5F0F
6901
+
6902
+ - \u4FEE\u6539\u6587\u4EF6\u53CA\u5BF9\u5E94\u5199\u5165\u6240\u6709\u6743\u3002
6903
+ - 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
6904
+ - \u6784\u5EFA\u3001\u5355\u5143\u6D4B\u8BD5\u3001\u9875\u9762/Journey \u6D4B\u8BD5\u547D\u4EE4\u4E0E\u7ED3\u679C\u3002
6905
+ - \u672A\u89E3\u51B3\u95EE\u9898\uFF1B\u6CA1\u6709\u5219\u660E\u786E\u5199\u201C\u65E0\u201D\u3002
6906
+
6907
+ ## \u5DE5\u4F5C\u91CF\u63D0\u793A
6908
+
6909
+ - Work Item \u6570\uFF1A${workPackage.estimate.workItemCount}
6910
+ - Behavior \u6570\uFF1A${workPackage.estimate.behaviorCount}
6911
+ - Fact \u6570\uFF1A${workPackage.estimate.factCount}
6912
+ - \u6E90\u7801\u6587\u4EF6\u6570\uFF1A${workPackage.estimate.sourceFileCount}
6913
+ - \u751F\u4EA7\u4EE3\u7801\u6587\u4EF6\u6570\uFF1A${workPackage.estimate.productionFileCount}
6914
+ - \u6D4B\u8BD5\u6587\u4EF6\u6570\uFF1A${workPackage.estimate.testFileCount}
6915
+ - Capability \u6570\uFF1A${workPackage.estimate.capabilityCount}
6916
+ - \u8C03\u7528\u94FE\u8282\u70B9\u6570\uFF1A${workPackage.estimate.traceNodeCount}
6917
+ - \u4F30\u7B97 Facts Token\uFF1A${workPackage.estimate.estimatedFactTokens}
6212
6918
  `;
6213
6919
  }
6214
6920
  function writeFile(file, content) {
6215
- fs26.mkdirSync(path33.dirname(file), { recursive: true });
6216
- fs26.writeFileSync(file, content, "utf8");
6921
+ fs27.mkdirSync(path33.dirname(file), { recursive: true });
6922
+ fs27.writeFileSync(file, content, "utf8");
6217
6923
  }
6218
- function writeTaskPlan(projectRoot, plan) {
6924
+ function writeTaskPlan(projectRoot, plan, facts) {
6219
6925
  const root = taskPlanRoot(projectRoot);
6220
6926
  const parent = path33.dirname(root);
6221
- fs26.mkdirSync(parent, { recursive: true });
6927
+ fs27.mkdirSync(parent, { recursive: true });
6222
6928
  const temporary = path33.join(parent, `tasks.${process.pid}.${Date.now()}.tmp`);
6223
6929
  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));
6930
+ fs27.mkdirSync(temporary, { recursive: true });
6228
6931
  const taskPlanSha256 = sha2562(plan);
6229
- const descriptors = plan.tasks.map((task) => {
6932
+ const graphFile = path33.join(temporary, "execution-graph.json");
6933
+ writeFile(graphFile, prettyStableJson(plan));
6934
+ writeFile(path33.join(temporary, "behavior-plan.json"), prettyStableJson({
6935
+ behaviorPlanVersion: BEHAVIOR_PLAN_VERSION,
6936
+ planningModelVersion: plan.planningModelVersion,
6937
+ factsAdapterVersion: plan.factsAdapterVersion,
6938
+ plannerPolicyVersion: plan.plannerPolicyVersion,
6939
+ baseline: plan.baseline,
6940
+ entryResolution: plan.entryResolution,
6941
+ responsibilities: plan.behaviorResponsibilities,
6942
+ ownership: plan.behaviorOwnership,
6943
+ planningWarnings: plan.planningWarnings
6944
+ }));
6945
+ const resolutionTargets = plan.tasks.filter((task) => task.type === "resolution").map((task) => {
6230
6946
  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));
6947
+ const taskRoot = `resolution-tasks/${stem}`;
6948
+ const briefFile = `${taskRoot}/brief.md`;
6949
+ const contractFile = `${taskRoot}/contract.json`;
6950
+ const coreFactsFile = `${taskRoot}/core-facts.json`;
6951
+ const sourceContextFile = `${taskRoot}/source-context.json`;
6952
+ const acceptanceFile = `${taskRoot}/acceptance.json`;
6953
+ writeFile(path33.join(temporary, briefFile), renderResolutionTaskBrief(task, plan));
6954
+ writeFile(path33.join(temporary, contractFile), prettyStableJson({ executionPlanSha256: taskPlanSha256, baseline: plan.baseline, task: taskContractView(task) }));
6955
+ writeFile(path33.join(temporary, coreFactsFile), prettyStableJson(coreFactsForTask(task, facts)));
6956
+ writeFile(path33.join(temporary, sourceContextFile), prettyStableJson(sourceContextForTask(task)));
6234
6957
  writeFile(path33.join(temporary, acceptanceFile), prettyStableJson(task.acceptance));
6235
- return { id: task.id, type: task.type, title: task.title, briefFile, acceptanceFile };
6958
+ return { id: task.id, kind: "resolution-task", title: task.title, briefFile, contractFile, coreFactsFile, sourceContextFile, acceptanceFile };
6959
+ });
6960
+ const pageWorkPackageTargets = plan.pageWorkPackages.map((workPackage) => {
6961
+ const stem = workPackageArtifactStem(workPackage.id);
6962
+ const packageRoot = `page-work-packages/${stem}`;
6963
+ const briefFile = `page-work-packages/${stem}.md`;
6964
+ const contractFile = `${packageRoot}/contract.json`;
6965
+ const coreFactsFile = `${packageRoot}/core-facts.json`;
6966
+ const sourceContextFile = `${packageRoot}/source-context.json`;
6967
+ const acceptanceFile = `${packageRoot}/acceptance.json`;
6968
+ const contract = pageWorkPackageContractView(workPackage, plan, facts);
6969
+ writeFile(path33.join(temporary, briefFile), renderPageWorkPackageBrief(contract, plan, facts));
6970
+ writeFile(path33.join(temporary, contractFile), prettyStableJson({ executionPlanSha256: taskPlanSha256, baseline: plan.baseline, pageWorkPackage: contract }));
6971
+ writeFile(path33.join(temporary, coreFactsFile), prettyStableJson(coreFactsForPageWorkPackage(contract, facts)));
6972
+ writeFile(path33.join(temporary, sourceContextFile), prettyStableJson(sourceContextForPageWorkPackage(workPackage, plan)));
6973
+ writeFile(path33.join(temporary, acceptanceFile), prettyStableJson(contract.acceptance));
6974
+ return { id: workPackage.id, kind: "page-work-package", title: workPackage.title, workItemIds: workPackage.workItemIds, briefFile, contractFile, coreFactsFile, sourceContextFile, acceptanceFile };
6236
6975
  });
6976
+ const targets = plan.planningPhase === "resolution" ? resolutionTargets : pageWorkPackageTargets;
6237
6977
  const index = {
6238
6978
  taskPlanVersion: plan.taskPlanVersion,
6239
6979
  taskPlanSha256,
6240
6980
  baseline: plan.baseline,
6241
- graphFile: "graph.json",
6981
+ executionPlanVersion: plan.executionPlanVersion,
6982
+ planningPhase: plan.planningPhase,
6983
+ graphFile: "execution-graph.json",
6984
+ behaviorPlanFile: "behavior-plan.json",
6242
6985
  counts: {
6243
6986
  total: plan.tasks.length,
6244
6987
  resolution: plan.tasks.filter((task) => task.type === "resolution").length,
6245
6988
  bootstrap: plan.tasks.filter((task) => task.type === "bootstrap").length,
6246
- journey: plan.tasks.filter((task) => task.type === "journey").length
6989
+ journey: plan.tasks.filter((task) => task.type === "journey").length,
6990
+ workPackages: plan.pageWorkPackages.length,
6991
+ behaviors: plan.behaviorResponsibilities.length,
6992
+ executionTargets: targets.length
6247
6993
  },
6248
- tasks: descriptors
6994
+ targets,
6995
+ workItems: plan.planningPhase === "execution" ? plan.tasks.map((task) => ({ id: task.id, type: task.type, title: task.title, workPackageId: task.workPackageId, behaviorIds: task.behaviorIds })) : []
6249
6996
  };
6250
6997
  writeFile(path33.join(temporary, "index.json"), prettyStableJson(index));
6251
6998
  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 });
6999
+ if (fs27.existsSync(backup)) fs27.rmSync(backup, { recursive: true, force: true });
7000
+ if (fs27.existsSync(root)) fs27.renameSync(root, backup);
7001
+ fs27.renameSync(temporary, root);
7002
+ if (fs27.existsSync(backup)) fs27.rmSync(backup, { recursive: true, force: true });
6256
7003
  } 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 });
7004
+ if (!fs27.existsSync(root) && fs27.existsSync(backup)) fs27.renameSync(backup, root);
7005
+ if (fs27.existsSync(temporary)) fs27.rmSync(temporary, { recursive: true, force: true });
6259
7006
  throw error;
6260
7007
  }
6261
7008
  return {
6262
7009
  taskPlanSha256,
6263
- graph: path33.relative(path33.resolve(projectRoot), path33.join(root, "graph.json")).replaceAll("\\", "/"),
7010
+ graph: path33.relative(path33.resolve(projectRoot), path33.join(root, "execution-graph.json")).replaceAll("\\", "/"),
6264
7011
  index: path33.relative(path33.resolve(projectRoot), path33.join(root, "index.json")).replaceAll("\\", "/")
6265
7012
  };
6266
7013
  }
6267
7014
  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"));
7015
+ const root = taskPlanRoot(projectRoot);
7016
+ const file = fs27.existsSync(path33.join(root, "execution-graph.json")) ? path33.join(root, "execution-graph.json") : path33.join(root, "graph.json");
7017
+ if (!fs27.existsSync(file)) return null;
7018
+ return JSON.parse(fs27.readFileSync(file, "utf8"));
6271
7019
  }
6272
7020
  function hasTaskPlan(projectRoot) {
6273
- return fs26.existsSync(path33.join(taskPlanRoot(projectRoot), "graph.json"));
7021
+ return fs27.existsSync(path33.join(taskPlanRoot(projectRoot), "execution-graph.json")) || fs27.existsSync(path33.join(taskPlanRoot(projectRoot), "graph.json"));
6274
7022
  }
6275
7023
 
6276
7024
  // src/planning/materialize.ts
6277
- function cursorFor(taskId, taskPlanSha256, offset) {
6278
- return Buffer.from(JSON.stringify({ taskId, taskPlanSha256, offset }), "utf8").toString("base64url");
7025
+ function cursorFor(targetId, taskPlanSha256, offset) {
7026
+ return Buffer.from(JSON.stringify({ targetId, taskPlanSha256, offset }), "utf8").toString("base64url");
6279
7027
  }
6280
- function cursorOffset(cursor, taskId, taskPlanSha256) {
7028
+ function cursorOffset(cursor, targetId, taskPlanSha256) {
6281
7029
  if (!cursor) return 0;
6282
7030
  try {
6283
7031
  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");
7032
+ if (value.targetId !== targetId || value.taskPlanSha256 !== taskPlanSha256 || !Number.isInteger(value.offset) || value.offset < 0) throw new Error("mismatch");
6285
7033
  return value.offset;
6286
7034
  } catch {
6287
7035
  throw new Error("Invalid or stale task envelope cursor");
6288
7036
  }
6289
7037
  }
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}`);
7038
+ function materializeEnvelope(plan, facts, targetId, options = {}) {
6297
7039
  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
7040
  const limit = Math.max(1, Math.min(200, Math.floor(options.maxSourceContext ?? 25)));
6315
- const offset = cursorOffset(options.cursor, taskId, taskPlanSha256);
7041
+ const offset = cursorOffset(options.cursor, targetId, taskPlanSha256);
7042
+ const task = plan.tasks.find((item) => item.id === targetId);
7043
+ if (task) {
7044
+ if (plan.planningPhase !== "resolution" || task.type !== "resolution") throw new Error("Execution phase materializes a Page Work Package, not an internal Coding Work Item");
7045
+ const coreFacts2 = coreFactsForTask(task, facts);
7046
+ const envelopeTask = taskContractView(task);
7047
+ const sourceContext2 = sourceContextForTask(task);
7048
+ if (offset > sourceContext2.length) throw new Error("Task envelope cursor is past the available source context");
7049
+ const page2 = sourceContext2.slice(offset, offset + limit);
7050
+ const nextOffset2 = offset + page2.length;
7051
+ return {
7052
+ taskEnvelopeVersion: TASK_ENVELOPE_VERSION,
7053
+ taskPlanSha256,
7054
+ targetKind: "resolution-task",
7055
+ targetId,
7056
+ task: envelopeTask,
7057
+ pageWorkPackage: null,
7058
+ contract: { executionPlanSha256: taskPlanSha256, baseline: plan.baseline, task: envelopeTask },
7059
+ brief: renderResolutionTaskBrief(task, plan),
7060
+ coreFacts: coreFacts2,
7061
+ dependencyInterfaces: task.dependencyInterfaces,
7062
+ acceptance: task.acceptance,
7063
+ sourceContext: page2,
7064
+ continuation: {
7065
+ cursor: nextOffset2 < sourceContext2.length ? cursorFor(targetId, taskPlanSha256, nextOffset2) : null,
7066
+ remainingSourceContext: Math.max(0, sourceContext2.length - nextOffset2)
7067
+ }
7068
+ };
7069
+ }
7070
+ const workPackage = plan.pageWorkPackages.find((item) => item.id === targetId);
7071
+ if (!workPackage) throw new Error(`Execution target not found: ${targetId}`);
7072
+ if (plan.planningPhase !== "execution") throw new Error("Resolution phase materializes a Resolution Task, not a Page Work Package");
7073
+ const contract = pageWorkPackageContractView(workPackage, plan, facts);
7074
+ const coreFacts = coreFactsForPageWorkPackage(contract, facts);
7075
+ const sourceContext = sourceContextForPageWorkPackage(workPackage, plan);
6316
7076
  if (offset > sourceContext.length) throw new Error("Task envelope cursor is past the available source context");
6317
7077
  const page = sourceContext.slice(offset, offset + limit);
6318
7078
  const nextOffset = offset + page.length;
6319
7079
  return {
6320
7080
  taskEnvelopeVersion: TASK_ENVELOPE_VERSION,
6321
7081
  taskPlanSha256,
6322
- task: envelopeTask,
6323
- brief: renderTaskBrief(task),
7082
+ targetKind: "page-work-package",
7083
+ targetId,
7084
+ task: null,
7085
+ pageWorkPackage: contract,
7086
+ contract: { executionPlanSha256: taskPlanSha256, baseline: plan.baseline, pageWorkPackage: contract },
7087
+ brief: renderPageWorkPackageBrief(contract, plan, facts),
6324
7088
  coreFacts,
7089
+ dependencyInterfaces: contract.dependencyInterfaces,
7090
+ acceptance: contract.acceptance,
6325
7091
  sourceContext: page,
6326
7092
  continuation: {
6327
- cursor: nextOffset < sourceContext.length ? cursorFor(taskId, taskPlanSha256, nextOffset) : null,
7093
+ cursor: nextOffset < sourceContext.length ? cursorFor(targetId, taskPlanSha256, nextOffset) : null,
6328
7094
  remainingSourceContext: Math.max(0, sourceContext.length - nextOffset)
6329
7095
  }
6330
7096
  };
6331
7097
  }
6332
7098
 
6333
7099
  // src/planning/runtime.ts
6334
- import fs27 from "fs";
7100
+ import fs28 from "fs";
6335
7101
  import path34 from "path";
6336
7102
 
6337
7103
  // src/planning/validate.ts
6338
- function findCycle(plan) {
6339
- const dependencies = new Map(plan.tasks.map((task) => [task.id, task.dependsOnTaskIds]));
7104
+ function findDependencyCycle(dependencies) {
6340
7105
  const visiting = /* @__PURE__ */ new Set();
6341
7106
  const visited = /* @__PURE__ */ new Set();
6342
7107
  const stack = [];
@@ -6360,33 +7125,101 @@ function findCycle(plan) {
6360
7125
  }
6361
7126
  return null;
6362
7127
  }
7128
+ function findCycle(plan) {
7129
+ return findDependencyCycle(new Map(plan.tasks.map((task) => [task.id, task.dependsOnTaskIds])));
7130
+ }
7131
+ function hasDependencyPath(plan, from, to) {
7132
+ const byId = new Map(plan.tasks.map((task) => [task.id, task]));
7133
+ const queue = [...byId.get(from)?.dependsOnTaskIds ?? []];
7134
+ const seen = /* @__PURE__ */ new Set();
7135
+ while (queue.length) {
7136
+ const next = queue.shift();
7137
+ if (next === to) return true;
7138
+ if (seen.has(next)) continue;
7139
+ seen.add(next);
7140
+ queue.push(...byId.get(next)?.dependsOnTaskIds ?? []);
7141
+ }
7142
+ return false;
7143
+ }
6363
7144
  function validateTaskPlan(plan, facts, androidFingerprint) {
6364
7145
  const errors = [];
6365
7146
  const warnings = [];
6366
7147
  if (plan.taskPlanVersion !== TASK_PLAN_VERSION) errors.push(`taskPlanVersion must be ${TASK_PLAN_VERSION}`);
7148
+ if (plan.executionPlanVersion !== EXECUTION_PLAN_VERSION) errors.push(`executionPlanVersion must be ${EXECUTION_PLAN_VERSION}`);
6367
7149
  if (plan.planningModelVersion !== PLANNING_MODEL_VERSION) errors.push(`planningModelVersion must be ${PLANNING_MODEL_VERSION}`);
6368
7150
  if (plan.factsAdapterVersion !== FACTS_ADAPTER_VERSION) errors.push(`factsAdapterVersion must be ${FACTS_ADAPTER_VERSION}`);
6369
7151
  if (plan.plannerPolicyVersion !== PLANNER_POLICY_VERSION) errors.push(`plannerPolicyVersion must be ${PLANNER_POLICY_VERSION}`);
6370
7152
  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
7153
  if (!fresh) warnings.push("Task plan baseline does not match current Facts, planning inputs, or Android source fingerprint");
7154
+ const behaviorIds = /* @__PURE__ */ new Set();
7155
+ for (const behavior of plan.behaviorResponsibilities ?? []) {
7156
+ if (!/^behavior:[a-f0-9]{16}$/.test(behavior.id)) errors.push(`invalid behavior id ${behavior.id}`);
7157
+ if (behaviorIds.has(behavior.id)) errors.push(`duplicate behavior id ${behavior.id}`);
7158
+ else behaviorIds.add(behavior.id);
7159
+ if (!behavior.primaryFactRefs?.length) errors.push(`${behavior.id}: primaryFactRefs must not be empty`);
7160
+ if (!behavior.acceptance?.observableOutcome?.trim()) errors.push(`${behavior.id}: observableOutcome is required`);
7161
+ for (const ref of behavior.supportingFactRefs ?? []) if (behavior.primaryFactRefs.includes(ref)) errors.push(`${behavior.id}: ${ref} cannot be both primary and supporting`);
7162
+ }
7163
+ for (const behavior of plan.behaviorResponsibilities ?? []) for (const dependency of behavior.dependsOnBehaviorIds ?? []) {
7164
+ if (!behaviorIds.has(dependency)) errors.push(`${behavior.id}: unknown behavior dependency ${dependency}`);
7165
+ if (dependency === behavior.id) errors.push(`${behavior.id}: behavior cannot depend on itself`);
7166
+ }
7167
+ const expectedPlanningPhase = (plan.behaviorResponsibilities ?? []).some((behavior) => behavior.type === "resolution") ? "resolution" : "execution";
7168
+ if (plan.planningPhase !== expectedPlanningPhase) errors.push(`planningPhase must be ${expectedPlanningPhase}`);
7169
+ const activeBehaviors = (plan.behaviorResponsibilities ?? []).filter((behavior) => expectedPlanningPhase === "execution" || behavior.type === "resolution");
7170
+ const activeBehaviorIds = new Set(activeBehaviors.map((behavior) => behavior.id));
7171
+ if (expectedPlanningPhase === "resolution" && (plan.tasks ?? []).some((task) => task.type !== "resolution")) {
7172
+ errors.push("resolution phase must materialize only Resolution Tasks; Coding Work Items are assembled after Resolution is cleared");
7173
+ }
7174
+ if (expectedPlanningPhase === "execution" && (plan.tasks ?? []).some((task) => task.type === "resolution")) {
7175
+ errors.push("execution phase must not contain Resolution Tasks");
7176
+ }
7177
+ if (expectedPlanningPhase === "resolution" && (plan.pageWorkPackages ?? []).length > 0) {
7178
+ errors.push("resolution phase must not materialize Coding Work Packages");
7179
+ }
7180
+ const behaviorOwners = /* @__PURE__ */ new Map();
7181
+ const behaviorSupporters = /* @__PURE__ */ new Map();
7182
+ for (const behavior of plan.behaviorResponsibilities ?? []) for (const ref of behavior.primaryFactRefs ?? []) behaviorOwners.set(ref, [...behaviorOwners.get(ref) ?? [], behavior.id]);
7183
+ for (const behavior of plan.behaviorResponsibilities ?? []) for (const ref of behavior.supportingFactRefs ?? []) behaviorSupporters.set(ref, [...behaviorSupporters.get(ref) ?? [], behavior.id]);
6372
7184
  const taskIds = /* @__PURE__ */ new Set();
6373
7185
  for (const task of plan.tasks ?? []) {
6374
7186
  if (!/^task:[a-f0-9]{16}$/.test(task.id)) errors.push(`invalid task id ${task.id}`);
6375
7187
  if (taskIds.has(task.id)) errors.push(`duplicate task id ${task.id}`);
6376
7188
  else taskIds.add(task.id);
6377
7189
  if (!task.title?.trim()) errors.push(`${task.id}: title is required`);
7190
+ if (expectedPlanningPhase === "resolution" && task.workPackageId !== null) errors.push(`${task.id}: Resolution Task must not belong to a Work Package`);
7191
+ if (expectedPlanningPhase === "execution" && !task.workPackageId?.trim()) errors.push(`${task.id}: Coding Work Item requires workPackageId`);
7192
+ if (!task.primaryModule?.trim()) errors.push(`${task.id}: primaryModule is required`);
7193
+ if (!task.behaviorIds?.length) errors.push(`${task.id}: behaviorIds must not be empty`);
7194
+ if (expectedPlanningPhase === "resolution" && task.behaviorIds?.length !== 1) errors.push(`${task.id}: Resolution Task must project exactly one Resolution Behavior`);
7195
+ for (const behaviorId of task.behaviorIds ?? []) if (!behaviorIds.has(behaviorId)) errors.push(`${task.id}: unknown behavior ${behaviorId}`);
6378
7196
  if (!task.primaryFactRefs?.length) errors.push(`${task.id}: primaryFactRefs must not be empty`);
6379
7197
  if (!task.acceptance?.observableOutcome?.trim()) errors.push(`${task.id}: observableOutcome is required`);
6380
7198
  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`);
7199
+ 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;
7200
+ if (overBudget && (task.behaviorIds?.length ?? 0) > 1) errors.push(`${task.id}: merged Coding Work Item exceeds the execution budget`);
7201
+ 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`);
7202
+ const owned = task.writeOwnership?.ownedFiles ?? [];
7203
+ const readOnly = task.writeOwnership?.readOnlyFiles ?? [];
7204
+ const forbidden = task.writeOwnership?.forbiddenGlobalFiles ?? [];
7205
+ for (const file of owned) if (readOnly.includes(file)) errors.push(`${task.id}: ${file} cannot be both owned and read-only`);
7206
+ for (const file of owned) if (forbidden.includes(file)) errors.push(`${task.id}: ${file} cannot be both owned and forbidden`);
7207
+ if (!(task.writeOwnership?.exclusiveScopes?.length || owned.length)) errors.push(`${task.id}: write ownership requires ownedFiles or an exclusive scope`);
7208
+ if (task.readyGate?.writeOwnershipResolved !== Boolean(task.writeOwnership?.exclusiveScopes?.length || owned.length)) errors.push(`${task.id}: writeOwnershipResolved gate disagrees with the contract`);
7209
+ if (task.readyGate?.semanticsResolved !== (task.type === "resolution" || task.closure.unresolvedRefs.length === 0)) errors.push(`${task.id}: semanticsResolved gate disagrees with unresolved refs`);
7210
+ if (task.readyGate?.contextWithinBudget !== (!overBudget || (task.behaviorIds?.length ?? 0) === 1)) errors.push(`${task.id}: contextWithinBudget gate disagrees with execution estimate`);
6385
7211
  }
6386
7212
  for (const task of plan.tasks ?? []) for (const dependency of task.dependsOnTaskIds ?? []) {
6387
7213
  if (!taskIds.has(dependency)) errors.push(`${task.id}: unknown dependency ${dependency}`);
6388
7214
  if (dependency === task.id) errors.push(`${task.id}: task cannot depend on itself`);
6389
7215
  }
7216
+ for (const task of plan.tasks ?? []) {
7217
+ const interfaceProviders = (task.dependencyInterfaces ?? []).map((item) => item.providerTaskId).filter((id) => Boolean(id)).sort();
7218
+ if (JSON.stringify(interfaceProviders) !== JSON.stringify([...task.dependsOnTaskIds ?? []].sort())) errors.push(`${task.id}: dependencyInterfaces do not match dependsOnTaskIds`);
7219
+ for (const dependencyInterface of task.dependencyInterfaces ?? []) {
7220
+ if (dependencyInterface.providerTaskId && !taskIds.has(dependencyInterface.providerTaskId)) errors.push(`${task.id}: dependency Interface references unknown provider ${dependencyInterface.providerTaskId}`);
7221
+ }
7222
+ }
6390
7223
  const cycle = findCycle(plan);
6391
7224
  if (cycle) errors.push(`task dependency cycle: ${cycle.join(" -> ")}`);
6392
7225
  const owners = /* @__PURE__ */ new Map();
@@ -6398,14 +7231,20 @@ function validateTaskPlan(plan, facts, androidFingerprint) {
6398
7231
  supporters.set(ref, [...supporters.get(ref) ?? [], task.id]);
6399
7232
  }
6400
7233
  }
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}`);
7234
+ const activePrimaryFactRefs = uniqueSorted(activeBehaviors.flatMap((behavior) => behavior.primaryFactRefs));
7235
+ const behaviorOrphanFactRefs = facts.requiredFactRefs.filter((ref) => (behaviorOwners.get(ref) ?? []).length === 0);
7236
+ const activeTaskOrphanFactRefs = activePrimaryFactRefs.filter((ref) => (owners.get(ref) ?? []).length === 0);
7237
+ const orphanFactRefs = uniqueSorted([
7238
+ ...behaviorOrphanFactRefs,
7239
+ ...activeTaskOrphanFactRefs.filter((ref) => facts.requiredFactRefs.includes(ref))
7240
+ ]);
7241
+ for (const ref of activeTaskOrphanFactRefs) errors.push(`active Behavior fact has no primary Task owner: ${ref}`);
6403
7242
  for (const [ref, ownerIds] of owners) if (ownerIds.length > 1) errors.push(`fact has multiple primary owners: ${ref} -> ${ownerIds.join(", ")}`);
6404
7243
  for (const [ref, ownerId] of Object.entries(plan.ownership?.primaryOwnerByFactRef ?? {})) {
6405
7244
  if (!taskIds.has(ownerId)) errors.push(`ownership ${ref} references unknown task ${ownerId}`);
6406
7245
  if (!(owners.get(ref) ?? []).includes(ownerId)) errors.push(`ownership map disagrees with task primaryFactRefs for ${ref}`);
6407
7246
  }
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}`);
7247
+ 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
7248
  for (const [ref, taskIdsForRef] of Object.entries(plan.ownership?.supportingTaskIdsByFactRef ?? {})) {
6410
7249
  const expected = [...supporters.get(ref) ?? []].sort();
6411
7250
  const actual = [...taskIdsForRef ?? []].sort();
@@ -6414,6 +7253,125 @@ function validateTaskPlan(plan, facts, androidFingerprint) {
6414
7253
  for (const [ref, expectedIds] of supporters) {
6415
7254
  if (!(ref in (plan.ownership?.supportingTaskIdsByFactRef ?? {}))) errors.push(`supporting ownership map is missing for ${ref}: ${[...expectedIds].sort().join(", ")}`);
6416
7255
  }
7256
+ const behaviorMembership = /* @__PURE__ */ new Map();
7257
+ for (const task of plan.tasks ?? []) for (const behaviorId of task.behaviorIds ?? []) behaviorMembership.set(behaviorId, [...behaviorMembership.get(behaviorId) ?? [], task.id]);
7258
+ for (const behavior of plan.behaviorResponsibilities ?? []) {
7259
+ const ownersForBehavior = behaviorMembership.get(behavior.id) ?? [];
7260
+ const expectedMembership = activeBehaviorIds.has(behavior.id) ? 1 : 0;
7261
+ if (ownersForBehavior.length !== expectedMembership) errors.push(`${behavior.id}: must belong to ${expectedMembership} ${expectedPlanningPhase === "resolution" ? "Resolution Task" : "Coding Work Item"}, found ${ownersForBehavior.length}`);
7262
+ const task = plan.tasks.find((item) => item.id === ownersForBehavior[0]);
7263
+ if (!task) continue;
7264
+ for (const ref of behavior.primaryFactRefs) if (!task.primaryFactRefs.includes(ref)) errors.push(`${task.id}: missing primary fact ${ref} from ${behavior.id}`);
7265
+ for (const ref of behavior.closure.entryRefs) if (!task.closure.entryRefs.includes(ref)) errors.push(`${task.id}: missing entry ${ref} from ${behavior.id}`);
7266
+ for (const ref of behavior.closure.interactionRefs) if (!task.closure.interactionRefs.includes(ref)) errors.push(`${task.id}: missing interaction ${ref} from ${behavior.id}`);
7267
+ }
7268
+ const behaviorById = new Map((plan.behaviorResponsibilities ?? []).map((behavior) => [behavior.id, behavior]));
7269
+ for (const task of plan.tasks ?? []) {
7270
+ const members = (task.behaviorIds ?? []).map((id) => behaviorById.get(id)).filter((item) => Boolean(item));
7271
+ const expectedPrimary = uniqueSorted(members.flatMap((item) => item.primaryFactRefs));
7272
+ const expectedSupporting = uniqueSorted(members.flatMap((item) => item.supportingFactRefs).filter((ref) => !expectedPrimary.includes(ref)));
7273
+ if (JSON.stringify(expectedPrimary) !== JSON.stringify([...task.primaryFactRefs ?? []].sort())) errors.push(`${task.id}: primaryFactRefs disagree with member Behaviors`);
7274
+ if (JSON.stringify(expectedSupporting) !== JSON.stringify([...task.supportingFactRefs ?? []].sort())) errors.push(`${task.id}: supportingFactRefs disagree with member Behaviors`);
7275
+ for (const key of ["entryRefs", "surfaceRefs", "controlRefs", "interactionRefs", "unresolvedRefs", "warningRefs"]) {
7276
+ const expected = uniqueSorted(members.flatMap((item) => item.closure[key]));
7277
+ if (JSON.stringify(expected) !== JSON.stringify([...task.closure?.[key] ?? []].sort())) errors.push(`${task.id}: closure.${key} disagrees with member Behaviors`);
7278
+ }
7279
+ const expectedOutcomes = uniqueSorted(members.map((item) => item.acceptance.observableOutcome));
7280
+ const expectedRequired = uniqueSorted(members.flatMap((item) => item.acceptance.requiredFactRefs));
7281
+ const expectedReports = uniqueSorted(members.flatMap((item) => item.acceptance.reportRefs));
7282
+ if (JSON.stringify(expectedOutcomes) !== JSON.stringify([...task.acceptance?.observableOutcomes ?? []].sort())) errors.push(`${task.id}: observableOutcomes disagree with member Behaviors`);
7283
+ if (JSON.stringify(expectedRequired) !== JSON.stringify([...task.acceptance?.requiredFactRefs ?? []].sort())) errors.push(`${task.id}: requiredFactRefs disagree with member Behaviors`);
7284
+ if (JSON.stringify(expectedReports) !== JSON.stringify([...task.acceptance?.reportRefs ?? []].sort())) errors.push(`${task.id}: reportRefs disagree with member Behaviors`);
7285
+ if (members.some((member) => member.type !== task.type)) errors.push(`${task.id}: task type disagrees with a member Behavior`);
7286
+ }
7287
+ for (const ref of facts.requiredFactRefs) {
7288
+ const expectedBehavior = behaviorOwners.get(ref)?.[0];
7289
+ if (!expectedBehavior || behaviorOwners.get(ref)?.length !== 1) errors.push(`required fact must have exactly one Behavior owner: ${ref}`);
7290
+ if (plan.behaviorOwnership?.primaryOwnerByFactRef?.[ref] !== expectedBehavior) errors.push(`behavior ownership map is missing or incorrect for ${ref}`);
7291
+ }
7292
+ for (const [ref, behaviorId] of Object.entries(plan.behaviorOwnership?.primaryOwnerByFactRef ?? {})) {
7293
+ if (!behaviorIds.has(behaviorId)) errors.push(`behavior ownership ${ref} references unknown Behavior ${behaviorId}`);
7294
+ if (!(behaviorOwners.get(ref) ?? []).includes(behaviorId)) errors.push(`behavior ownership map disagrees with Behavior primaryFactRefs for ${ref}`);
7295
+ }
7296
+ for (const [ref, expectedIds] of behaviorSupporters) {
7297
+ const actual = [...plan.behaviorOwnership?.supportingBehaviorIdsByFactRef?.[ref] ?? []].sort();
7298
+ if (JSON.stringify(actual) !== JSON.stringify([...expectedIds].sort())) errors.push(`behavior supporting ownership map is missing or incorrect for ${ref}`);
7299
+ }
7300
+ for (const [ref, actualIds] of Object.entries(plan.behaviorOwnership?.supportingBehaviorIdsByFactRef ?? {})) {
7301
+ const expected = [...behaviorSupporters.get(ref) ?? []].sort();
7302
+ if (JSON.stringify([...actualIds].sort()) !== JSON.stringify(expected)) errors.push(`behavior supporting ownership map disagrees with Behaviors for ${ref}`);
7303
+ }
7304
+ const packageIds = /* @__PURE__ */ new Set();
7305
+ const knownPackageIds = new Set((plan.pageWorkPackages ?? []).map((workPackage) => workPackage.id));
7306
+ const packageMembership = /* @__PURE__ */ new Map();
7307
+ const canonicalBySurface = canonicalPageRefs(facts);
7308
+ for (const workPackage of plan.pageWorkPackages ?? []) {
7309
+ if (!/^package:[a-f0-9]{16}$/.test(workPackage.id)) errors.push(`invalid work package id ${workPackage.id}`);
7310
+ if (packageIds.has(workPackage.id)) errors.push(`duplicate work package id ${workPackage.id}`);
7311
+ else packageIds.add(workPackage.id);
7312
+ for (const dependency of workPackage.dependsOnPackageIds ?? []) {
7313
+ if (!knownPackageIds.has(dependency)) errors.push(`${workPackage.id}: unknown Page Work Package dependency ${dependency}`);
7314
+ if (dependency === workPackage.id) errors.push(`${workPackage.id}: Page Work Package cannot depend on itself`);
7315
+ }
7316
+ for (const taskId2 of workPackage.workItemIds ?? []) {
7317
+ if (!taskIds.has(taskId2)) errors.push(`${workPackage.id}: unknown Coding Work Item ${taskId2}`);
7318
+ packageMembership.set(taskId2, [...packageMembership.get(taskId2) ?? [], workPackage.id]);
7319
+ }
7320
+ 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`);
7321
+ if (workPackage.canonicalPageRef !== null && !facts.surfaces.some((surface) => surface.id === workPackage.canonicalPageRef)) errors.push(`${workPackage.id}: unknown canonical page ${workPackage.canonicalPageRef}`);
7322
+ for (const surfaceRef of workPackage.surfaceRefs ?? []) {
7323
+ if (!facts.surfaces.some((surface) => surface.id === surfaceRef)) errors.push(`${workPackage.id}: unknown surface ${surfaceRef}`);
7324
+ if ((canonicalBySurface[surfaceRef] ?? surfaceRef) !== workPackage.canonicalPageRef) errors.push(`${workPackage.id}: ${surfaceRef} does not belong to canonical page ${workPackage.canonicalPageRef}`);
7325
+ }
7326
+ if (workPackage.serialLane) for (let index = 1; index < workPackage.workItemIds.length; index++) {
7327
+ 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]}`);
7328
+ }
7329
+ const expectedScopes = [...new Set(plan.tasks.filter((task) => task.workPackageId === workPackage.id).flatMap((task) => task.writeOwnership?.exclusiveScopes ?? []))].sort();
7330
+ if (JSON.stringify([...workPackage.exclusiveScopes ?? []].sort()) !== JSON.stringify(expectedScopes)) errors.push(`${workPackage.id}: exclusiveScopes disagree with member Work Items`);
7331
+ 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)));
7332
+ if (JSON.stringify([...workPackage.dependsOnPackageIds ?? []].sort()) !== JSON.stringify(expectedDependencies)) errors.push(`${workPackage.id}: dependsOnPackageIds disagree with cross-package Work Item dependencies`);
7333
+ }
7334
+ const packageCycle = findDependencyCycle(new Map((plan.pageWorkPackages ?? []).map((workPackage) => [workPackage.id, workPackage.dependsOnPackageIds ?? []])));
7335
+ if (packageCycle) errors.push(`Page Work Package dependency cycle: ${packageCycle.join(" -> ")}`);
7336
+ for (const task of plan.tasks ?? []) {
7337
+ const memberships = packageMembership.get(task.id) ?? [];
7338
+ if (task.type === "resolution") {
7339
+ if (memberships.length > 0 || task.workPackageId !== null) errors.push(`${task.id}: Resolution Task must not have Work Package membership`);
7340
+ continue;
7341
+ }
7342
+ if (memberships.length !== 1 || memberships[0] !== task.workPackageId) errors.push(`${task.id}: work package membership is missing or inconsistent`);
7343
+ const workPackage = plan.pageWorkPackages?.find((item) => item.id === task.workPackageId);
7344
+ if (workPackage && workPackage.canonicalPageRef !== task.canonicalPageRef) errors.push(`${task.id}: canonicalPageRef disagrees with its Work Package`);
7345
+ }
7346
+ const conflictEdges = /* @__PURE__ */ new Map();
7347
+ for (const edge of plan.fileConflictEdges ?? []) {
7348
+ if (!taskIds.has(edge.leftTaskId) || !taskIds.has(edge.rightTaskId)) errors.push(`file conflict references an unknown task: ${edge.leftTaskId}, ${edge.rightTaskId}`);
7349
+ if (edge.leftTaskId === edge.rightTaskId) errors.push(`${edge.leftTaskId}: file conflict cannot reference itself`);
7350
+ if (!edge.reasons?.length) errors.push(`${edge.leftTaskId}/${edge.rightTaskId}: file conflict requires a reason`);
7351
+ const key = [edge.leftTaskId, edge.rightTaskId].sort().join(":");
7352
+ if (conflictEdges.has(key)) errors.push(`duplicate file conflict edge: ${key}`);
7353
+ else conflictEdges.set(key, edge);
7354
+ }
7355
+ for (let leftIndex = 0; leftIndex < (plan.tasks ?? []).length; leftIndex++) for (let rightIndex = leftIndex + 1; rightIndex < plan.tasks.length; rightIndex++) {
7356
+ const left = plan.tasks[leftIndex];
7357
+ const right = plan.tasks[rightIndex];
7358
+ const leftOwned = left.writeOwnership?.ownedFiles ?? [];
7359
+ const rightOwned = right.writeOwnership?.ownedFiles ?? [];
7360
+ const leftScopes = left.writeOwnership?.exclusiveScopes ?? [];
7361
+ const rightScopes = right.writeOwnership?.exclusiveScopes ?? [];
7362
+ const overlappingFiles = leftOwned.filter((file) => rightOwned.includes(file));
7363
+ const overlappingScopes = leftScopes.filter((scope) => rightScopes.includes(scope));
7364
+ const hasConflict = overlappingFiles.length > 0 || overlappingScopes.length > 0;
7365
+ const edge = conflictEdges.get([left.id, right.id].sort().join(":"));
7366
+ if (hasConflict && !edge) errors.push(`${left.id} and ${right.id}: overlapping write ownership requires a conflict edge`);
7367
+ if (!hasConflict && edge) errors.push(`${left.id} and ${right.id}: file conflict edge has no overlapping write ownership`);
7368
+ if (edge) {
7369
+ const expectedReasons = [...overlappingFiles.map((file) => `owned-file:${file}`), ...overlappingScopes.map((scope) => `exclusive-scope:${scope}`)].sort();
7370
+ if (JSON.stringify([...edge.reasons].sort()) !== JSON.stringify(expectedReasons)) errors.push(`${left.id} and ${right.id}: file conflict reasons disagree with write ownership`);
7371
+ }
7372
+ 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`);
7373
+ 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`);
7374
+ }
6417
7375
  const knownRefs = /* @__PURE__ */ new Set([...facts.requiredFactRefs, ...facts.issues.map((issue) => issue.id)]);
6418
7376
  const entryRefs = new Set(facts.entries.map((entry) => entry.id));
6419
7377
  const surfaceRefs = new Set(facts.surfaces.map((surface) => surface.id));
@@ -6447,16 +7405,16 @@ function validateTaskPlan(plan, facts, androidFingerprint) {
6447
7405
  }
6448
7406
 
6449
7407
  // src/planning/runtime.ts
6450
- function readJson6(file) {
7408
+ function readJson7(file) {
6451
7409
  try {
6452
- return JSON.parse(fs27.readFileSync(file, "utf8"));
7410
+ return JSON.parse(fs28.readFileSync(file, "utf8"));
6453
7411
  } catch {
6454
7412
  return null;
6455
7413
  }
6456
7414
  }
6457
7415
  function reportItems(projectRoot) {
6458
7416
  const root = path34.join(migrationDirectory(projectRoot), "report");
6459
- const index = readJson6(path34.join(root, "index.json"));
7417
+ const index = readJson7(path34.join(root, "index.json"));
6460
7418
  const out = /* @__PURE__ */ new Map();
6461
7419
  if (!index) return out;
6462
7420
  const files = [
@@ -6466,7 +7424,7 @@ function reportItems(projectRoot) {
6466
7424
  index.discoveriesFile
6467
7425
  ].filter((value) => typeof value === "string");
6468
7426
  for (const relative2 of files) {
6469
- const document = readJson6(path34.join(root, relative2));
7427
+ const document = readJson7(path34.join(root, relative2));
6470
7428
  if (!document) continue;
6471
7429
  const values = [document.page, ...document.controls ?? [], ...document.entries ?? [], ...document.interactions ?? [], ...document.items ?? []].filter(Boolean);
6472
7430
  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 +7435,7 @@ function managedReceipt(projectRoot, reference) {
6477
7435
  const managedRoot = path34.resolve(migrationDirectory(projectRoot), "evidence", "managed");
6478
7436
  const absolute = reference.replaceAll("\\", "/").startsWith(".migration/") ? path34.resolve(migrationWorkspaceRoot(projectRoot), reference) : path34.resolve(projectRoot, reference);
6479
7437
  if (absolute !== managedRoot && !absolute.startsWith(`${managedRoot}${path34.sep}`)) return null;
6480
- return readJson6(absolute);
7438
+ return readJson7(absolute);
6481
7439
  }
6482
7440
  function freshTaskEvidence(projectRoot, reference, factsSha256, harmonyFingerprint) {
6483
7441
  const receipt = managedReceipt(projectRoot, reference);
@@ -6503,7 +7461,24 @@ function stateReasons(task, state, dependencyStates) {
6503
7461
  })(),
6504
7462
  task.estimate.downstreamUnlockCount > 0 ? `\u5B8C\u6210\u540E\u53EF\u89E3\u9664 ${task.estimate.downstreamUnlockCount} \u4E2A\u4E0B\u6E38\u4EFB\u52A1` : null
6505
7463
  ]);
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"];
7464
+ if (state === "blocked") {
7465
+ const gate = task.readyGate ?? {
7466
+ semanticsResolved: false,
7467
+ dependencyInterfacesStable: false,
7468
+ writeOwnershipResolved: false,
7469
+ contextWithinBudget: false,
7470
+ acceptanceExecutable: false
7471
+ };
7472
+ return uniqueSorted([
7473
+ dependencyStates.length ? "\u4F9D\u8D56\u4EFB\u52A1\u5C1A\u672A\u53D6\u5F97\u65B0\u9C9C\u7684\u5B9A\u5411\u9A8C\u8BC1\u8BC1\u636E" : null,
7474
+ !gate.semanticsResolved ? "\u4ECD\u6709\u672A\u88C1\u51B3\u8BED\u4E49\u95EE\u9898" : null,
7475
+ !gate.dependencyInterfacesStable ? "\u4E0A\u6E38 Module Interface \u5C1A\u672A\u7A33\u5B9A" : null,
7476
+ !gate.writeOwnershipResolved ? "\u5199\u5165\u6587\u4EF6\u6216\u72EC\u5360\u8303\u56F4\u5C1A\u672A\u5206\u914D" : null,
7477
+ !gate.contextWithinBudget ? "\u5185\u90E8 Work Item \u95ED\u5305\u8D85\u51FA\u5F53\u524D\u6267\u884C\u9884\u7B97" : null,
7478
+ !gate.acceptanceExecutable ? "\u9A8C\u6536\u6761\u4EF6\u5F53\u524D\u4E0D\u53EF\u6267\u884C" : null,
7479
+ !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
7480
+ ]);
7481
+ }
6507
7482
  if (state === "implemented") return ["\u5B9E\u73B0\u6620\u5C04\u5DF2\u5B58\u5728\uFF0C\u4F46\u5B9A\u5411\u8BBE\u5907\u9A8C\u8BC1\u5C1A\u672A\u5B8C\u6210"];
6508
7483
  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
7484
  if (state === "locally-verified") return ["\u5F53\u524D\u4EFB\u52A1\u5DF2\u6709\u65B0\u9C9C\u5B9A\u5411\u8BBE\u5907\u8BC1\u636E"];
@@ -6512,25 +7487,72 @@ function stateReasons(task, state, dependencyStates) {
6512
7487
  }
6513
7488
  function rankReady(tasks, states) {
6514
7489
  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
- }
7490
+ if (task.type === "resolution") return 0;
6519
7491
  const order = { bootstrap: 1, journey: 2, capability: 3, assembly: 4, regression: 6 };
6520
7492
  return order[task.type] ?? 6;
6521
7493
  };
6522
7494
  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
7495
  }
7496
+ function completedState(state) {
7497
+ return ["locally-verified", "globally-verified"].includes(state);
7498
+ }
7499
+ function aggregatePackageOwnState(states) {
7500
+ if (states.length > 0 && states.every((state) => state === "globally-verified")) return "globally-verified";
7501
+ if (states.length > 0 && states.every(completedState)) return "locally-verified";
7502
+ if (states.some((state) => state === "blocked")) return "blocked";
7503
+ if (states.some((state) => state === "stale")) return "stale";
7504
+ if (states.length > 0 && states.every((state) => ["implemented", "locally-verified", "globally-verified"].includes(state))) return "implemented";
7505
+ return "pending";
7506
+ }
7507
+ function packageStateReasons(workPackage, state, dependencyStates, ownStates) {
7508
+ if (state === "ready") return [
7509
+ `\u9875\u9762\u5305\u5C06\u5728\u4E00\u6B21\u5B9E\u73B0 epoch \u5185\u6309\u51BB\u7ED3\u987A\u5E8F\u5B8C\u6210 ${workPackage.workItemIds.length} \u4E2A Work Item`,
7510
+ "\u8DE8\u9875\u9762\u4F9D\u8D56\u5DF2\u6EE1\u8DB3\uFF0C\u53EF\u52A0\u8F7D\u9875\u9762\u7EA7\u6A21\u578B\u8F93\u5165"
7511
+ ];
7512
+ if (state === "blocked") return uniqueSorted([
7513
+ dependencyStates.some((dependency) => !completedState(dependency)) ? "\u524D\u7F6E Page Work Package \u5C1A\u672A\u53D6\u5F97\u65B0\u9C9C\u7684\u5B9A\u5411\u9A8C\u8BC1\u8BC1\u636E" : null,
7514
+ ownStates.some((own) => own === "blocked") ? "\u9875\u9762\u5305\u5185\u5B58\u5728\u88AB\u62A5\u544A\u6807\u8BB0\u4E3A blocked \u7684\u884C\u4E3A" : null,
7515
+ !dependencyStates.length && !ownStates.some((own) => own === "blocked") ? "\u9875\u9762\u5305\u7684\u9759\u6001 Ready Gate \u672A\u5168\u90E8\u901A\u8FC7" : null
7516
+ ]);
7517
+ 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"];
7518
+ 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"];
7519
+ if (state === "locally-verified") return ["\u9875\u9762\u5305\u5185\u5168\u90E8\u884C\u4E3A\u5747\u6709\u65B0\u9C9C\u5B9A\u5411\u8BBE\u5907\u8BC1\u636E"];
7520
+ if (state === "globally-verified") return ["\u9875\u9762\u5305\u5185\u5168\u90E8\u884C\u4E3A\u53CA\u5168\u91CF\u56DE\u5F52\u8BC1\u636E\u5747\u65B0\u9C9C"];
7521
+ return [];
7522
+ }
7523
+ function packageDownstreamCounts(packages) {
7524
+ const children = /* @__PURE__ */ new Map();
7525
+ for (const workPackage of packages) for (const dependency of workPackage.dependsOnPackageIds) children.set(dependency, [...children.get(dependency) ?? [], workPackage.id]);
7526
+ const result = /* @__PURE__ */ new Map();
7527
+ for (const workPackage of packages) {
7528
+ const seen = /* @__PURE__ */ new Set();
7529
+ const queue = [...children.get(workPackage.id) ?? []];
7530
+ while (queue.length) {
7531
+ const next = queue.shift();
7532
+ if (seen.has(next)) continue;
7533
+ seen.add(next);
7534
+ queue.push(...children.get(next) ?? []);
7535
+ }
7536
+ result.set(workPackage.id, seen.size);
7537
+ }
7538
+ return result;
7539
+ }
6524
7540
  function emptyTaskPlanSnapshot(error) {
6525
7541
  return {
6526
7542
  present: false,
6527
7543
  valid: false,
6528
7544
  fresh: false,
6529
7545
  taskPlanSha256: null,
7546
+ planningPhase: null,
6530
7547
  totalTasks: 0,
7548
+ resolutionTasks: 0,
7549
+ totalTargets: 0,
7550
+ readyTargets: 0,
7551
+ completedTargets: 0,
6531
7552
  readyTasks: 0,
6532
7553
  blockedTasks: 0,
6533
7554
  completedTasks: 0,
7555
+ recommendedTargetId: null,
6534
7556
  recommendedTaskId: null,
6535
7557
  alternatives: [],
6536
7558
  reasons: {},
@@ -6539,7 +7561,8 @@ function emptyTaskPlanSnapshot(error) {
6539
7561
  orphanFactRefs: [],
6540
7562
  errors: error ? [error] : [],
6541
7563
  warnings: [],
6542
- tasks: []
7564
+ tasks: [],
7565
+ targets: []
6543
7566
  };
6544
7567
  }
6545
7568
  function inspectCompiledTaskPlan(projectRootArgument, facts, source) {
@@ -6548,6 +7571,7 @@ function inspectCompiledTaskPlan(projectRootArgument, facts, source) {
6548
7571
  if (!plan) return emptyTaskPlanSnapshot();
6549
7572
  const validation = validateTaskPlan(plan, facts, source.androidFingerprint);
6550
7573
  const reports = reportItems(projectRoot);
7574
+ const ownStates = new Map(plan.tasks.map((task) => [task.id, initialState(task, reports, projectRoot, plan, source)]));
6551
7575
  const states = /* @__PURE__ */ new Map();
6552
7576
  const resolving = /* @__PURE__ */ new Set();
6553
7577
  const byId = new Map(plan.tasks.map((task) => [task.id, task]));
@@ -6557,12 +7581,13 @@ function inspectCompiledTaskPlan(projectRootArgument, facts, source) {
6557
7581
  if (!validation.fresh) return "stale";
6558
7582
  if (resolving.has(task.id)) return "blocked";
6559
7583
  resolving.add(task.id);
6560
- const own = initialState(task, reports, projectRoot, plan, source);
7584
+ const own = ownStates.get(task.id);
6561
7585
  let state = own;
6562
7586
  if (own === "pending") {
6563
7587
  const dependencyStates = task.dependsOnTaskIds.map((id) => byId.get(id)).filter((item) => Boolean(item)).map(resolveState);
6564
7588
  const satisfied = dependencyStates.every((value) => ["locally-verified", "globally-verified"].includes(value));
6565
- state = satisfied ? "ready" : "blocked";
7589
+ const readyGatePassed = Object.values(task.readyGate).every(Boolean);
7590
+ state = satisfied && readyGatePassed ? "ready" : "blocked";
6566
7591
  }
6567
7592
  resolving.delete(task.id);
6568
7593
  states.set(task.id, state);
@@ -6575,8 +7600,59 @@ function inspectCompiledTaskPlan(projectRootArgument, facts, source) {
6575
7600
  const dependencyStates = task.dependsOnTaskIds.map((id) => states.get(id)).filter(Boolean);
6576
7601
  return { taskId: task.id, state, reasons: stateReasons(task, state, dependencyStates) };
6577
7602
  });
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;
7603
+ let targets;
7604
+ let orderedReadyTargetIds;
7605
+ if (plan.planningPhase === "resolution") {
7606
+ targets = runtimeItems.map((item) => ({ targetId: item.taskId, kind: "resolution-task", state: item.state, reasons: item.reasons, workItemIds: [item.taskId] }));
7607
+ orderedReadyTargetIds = ready.map((task) => task.id);
7608
+ } else {
7609
+ const packageById = new Map(plan.pageWorkPackages.map((workPackage) => [workPackage.id, workPackage]));
7610
+ const packageStates = /* @__PURE__ */ new Map();
7611
+ const packageResolving = /* @__PURE__ */ new Set();
7612
+ const resolvePackageState = (workPackage) => {
7613
+ if (packageStates.has(workPackage.id)) return packageStates.get(workPackage.id);
7614
+ if (!validation.valid) return "blocked";
7615
+ if (!validation.fresh) return "stale";
7616
+ if (packageResolving.has(workPackage.id)) return "blocked";
7617
+ packageResolving.add(workPackage.id);
7618
+ const memberTasks = workPackage.workItemIds.map((id) => byId.get(id)).filter((task) => Boolean(task));
7619
+ const memberOwnStates = memberTasks.map((task) => ownStates.get(task.id));
7620
+ const own = aggregatePackageOwnState(memberOwnStates);
7621
+ let state = own;
7622
+ if (own === "pending") {
7623
+ const dependencyStates = workPackage.dependsOnPackageIds.map((id) => packageById.get(id)).filter((item) => Boolean(item)).map(resolvePackageState);
7624
+ const dependenciesSatisfied = dependencyStates.every(completedState);
7625
+ const readyGatePassed = memberTasks.every((task) => Object.values(task.readyGate).every(Boolean));
7626
+ state = dependenciesSatisfied && readyGatePassed ? "ready" : "blocked";
7627
+ }
7628
+ packageResolving.delete(workPackage.id);
7629
+ packageStates.set(workPackage.id, state);
7630
+ return state;
7631
+ };
7632
+ for (const workPackage of plan.pageWorkPackages) resolvePackageState(workPackage);
7633
+ targets = plan.pageWorkPackages.map((workPackage) => {
7634
+ const state = packageStates.get(workPackage.id);
7635
+ const dependencyStates = workPackage.dependsOnPackageIds.map((id) => packageStates.get(id)).filter(Boolean);
7636
+ const memberOwnStates = workPackage.workItemIds.map((id) => ownStates.get(id)).filter(Boolean);
7637
+ return { targetId: workPackage.id, kind: "page-work-package", state, reasons: packageStateReasons(workPackage, state, dependencyStates, memberOwnStates), workItemIds: workPackage.workItemIds };
7638
+ });
7639
+ const downstream = packageDownstreamCounts(plan.pageWorkPackages);
7640
+ const targetById = new Map(targets.map((target) => [target.targetId, target]));
7641
+ orderedReadyTargetIds = plan.pageWorkPackages.filter((workPackage) => targetById.get(workPackage.id)?.state === "ready").sort((left, right) => {
7642
+ const leftTasks = left.workItemIds.map((id) => byId.get(id)).filter((task) => Boolean(task));
7643
+ const rightTasks = right.workItemIds.map((id) => byId.get(id)).filter((task) => Boolean(task));
7644
+ const leftBootstrap = leftTasks.some((task) => task.type === "bootstrap") ? 0 : 1;
7645
+ const rightBootstrap = rightTasks.some((task) => task.type === "bootstrap") ? 0 : 1;
7646
+ const leftTokens = leftTasks.reduce((total, task) => total + task.estimate.estimatedFactTokens, 0);
7647
+ const rightTokens = rightTasks.reduce((total, task) => total + task.estimate.estimatedFactTokens, 0);
7648
+ return leftBootstrap - rightBootstrap || (downstream.get(right.id) ?? 0) - (downstream.get(left.id) ?? 0) || leftTokens - rightTokens || left.id.localeCompare(right.id);
7649
+ }).map((workPackage) => workPackage.id);
7650
+ }
7651
+ const reasons = Object.fromEntries([
7652
+ ...runtimeItems.filter((item) => item.reasons.length).map((item) => [item.taskId, item.reasons]),
7653
+ ...targets.filter((item) => item.reasons.length).map((item) => [item.targetId, item.reasons])
7654
+ ]);
7655
+ const ownerCount = facts.requiredFactRefs.filter((ref) => plan.behaviorOwnership.primaryOwnerByFactRef[ref]).length;
6580
7656
  const behaviorTasks = plan.tasks.filter((task) => task.type !== "resolution");
6581
7657
  const closedTasks = behaviorTasks.filter((task) => !task.closure.requiresSourceReview && (task.closure.interactionRefs.length > 0 || task.type === "bootstrap" && task.closure.surfaceRefs.length > 0)).length;
6582
7658
  return {
@@ -6584,19 +7660,26 @@ function inspectCompiledTaskPlan(projectRootArgument, facts, source) {
6584
7660
  valid: validation.valid,
6585
7661
  fresh: validation.fresh,
6586
7662
  taskPlanSha256: validation.taskPlanSha256,
7663
+ planningPhase: plan.planningPhase,
6587
7664
  totalTasks: plan.tasks.length,
7665
+ resolutionTasks: plan.tasks.filter((task) => task.type === "resolution").length,
7666
+ totalTargets: targets.length,
7667
+ readyTargets: orderedReadyTargetIds.length,
7668
+ completedTargets: targets.filter((item) => completedState(item.state)).length,
6588
7669
  readyTasks: ready.length,
6589
7670
  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),
7671
+ completedTasks: runtimeItems.filter((item) => completedState(item.state)).length,
7672
+ recommendedTargetId: orderedReadyTargetIds[0] ?? null,
7673
+ recommendedTaskId: plan.planningPhase === "resolution" ? orderedReadyTargetIds[0] ?? null : null,
7674
+ alternatives: orderedReadyTargetIds.slice(1, 4),
6593
7675
  reasons,
6594
7676
  ownershipCoverage: facts.requiredFactRefs.length ? ownerCount / facts.requiredFactRefs.length : 1,
6595
- closureCoverage: behaviorTasks.length ? closedTasks / behaviorTasks.length : 1,
7677
+ closureCoverage: plan.planningPhase === "resolution" ? 0 : behaviorTasks.length ? closedTasks / behaviorTasks.length : 1,
6596
7678
  orphanFactRefs: validation.orphanFactRefs,
6597
7679
  errors: validation.errors,
6598
7680
  warnings: validation.warnings,
6599
- tasks: runtimeItems
7681
+ tasks: runtimeItems,
7682
+ targets
6600
7683
  };
6601
7684
  }
6602
7685
 
@@ -6605,10 +7688,10 @@ function createTaskPlanningModule(factsSource = v2PlanningFactsSource) {
6605
7688
  return {
6606
7689
  compile(projectRoot, options) {
6607
7690
  const facts = factsSource.load(projectRoot);
6608
- const plan = compileFromPlanningFacts(facts, options.androidFingerprint);
7691
+ const plan = compileFromPlanningFacts(facts, options.androidFingerprint, loadExecutionPlanningContext(projectRoot));
6609
7692
  const validation = validateTaskPlan(plan, facts, options.androidFingerprint);
6610
7693
  if (!validation.valid || !validation.fresh) throw new Error(`Task plan compilation failed: ${[...validation.errors, ...validation.warnings].join("; ")}`);
6611
- writeTaskPlan(projectRoot, plan);
7694
+ writeTaskPlan(projectRoot, plan, facts);
6612
7695
  return plan;
6613
7696
  },
6614
7697
  inspect(projectRoot, source) {
@@ -6620,19 +7703,19 @@ function createTaskPlanningModule(factsSource = v2PlanningFactsSource) {
6620
7703
  return { ...emptyTaskPlanSnapshot(error instanceof Error ? error.message : String(error)), present: hasTaskPlan(projectRoot) };
6621
7704
  }
6622
7705
  },
6623
- materialize(projectRoot, task, request) {
7706
+ materialize(projectRoot, target, request) {
6624
7707
  const facts = factsSource.load(projectRoot);
6625
7708
  const plan = readTaskPlan(projectRoot);
6626
7709
  if (!plan) throw new Error("Task plan is not present; run droid2hmos task plan first");
6627
7710
  const validation = validateTaskPlan(plan, facts, request.source.androidFingerprint);
6628
7711
  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") {
7712
+ let targetId = target;
7713
+ if (target === "next") {
6631
7714
  const snapshot = inspectCompiledTaskPlan(projectRoot, facts, request.source);
6632
- if (!snapshot.recommendedTaskId) throw new Error("No ready task is available");
6633
- taskId = snapshot.recommendedTaskId;
7715
+ if (!snapshot.recommendedTargetId) throw new Error("No ready execution target is available");
7716
+ targetId = snapshot.recommendedTargetId;
6634
7717
  }
6635
- return materializeEnvelope(plan, facts, taskId, request);
7718
+ return materializeEnvelope(plan, facts, targetId, request);
6636
7719
  }
6637
7720
  };
6638
7721
  }
@@ -6648,9 +7731,7 @@ var actions = [
6648
7731
  "init",
6649
7732
  "analyze:android",
6650
7733
  "analyze:harmony",
6651
- "build:android",
6652
7734
  "build:harmony",
6653
- "test:android",
6654
7735
  "test:harmony:targeted",
6655
7736
  "test:harmony:all",
6656
7737
  "report:init",
@@ -6672,9 +7753,9 @@ var actions = [
6672
7753
  "complete"
6673
7754
  ];
6674
7755
  var ignoredDirectories = /* @__PURE__ */ new Set([".git", ".gradle", ".hvigor", ".idea", ".migration", ".codegraph-appgraph", "build", "node_modules", "oh_modules"]);
6675
- function readJson7(file) {
7756
+ function readJson8(file) {
6676
7757
  try {
6677
- return JSON.parse(fs28.readFileSync(file, "utf8"));
7758
+ return JSON.parse(fs29.readFileSync(file, "utf8"));
6678
7759
  } catch {
6679
7760
  return null;
6680
7761
  }
@@ -6691,11 +7772,11 @@ function sha2563(value) {
6691
7772
  return createHash8("sha256").update(typeof value === "string" ? value : stableJson2(value)).digest("hex");
6692
7773
  }
6693
7774
  function hashDirectory(root, excludedRoots = []) {
6694
- if (!root || !fs28.existsSync(root) || !fs28.statSync(root).isDirectory()) return null;
7775
+ if (!root || !fs29.existsSync(root) || !fs29.statSync(root).isDirectory()) return null;
6695
7776
  const excluded = excludedRoots.map((value) => path35.resolve(value));
6696
7777
  const hash2 = createHash8("sha256");
6697
7778
  const visit = (directory) => {
6698
- const entries = fs28.readdirSync(directory, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
7779
+ const entries = fs29.readdirSync(directory, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
6699
7780
  for (const entry of entries) {
6700
7781
  const absolute = path35.join(directory, entry.name);
6701
7782
  if (entry.isDirectory() && (ignoredDirectories.has(entry.name) || excluded.some((value) => absolute === value || absolute.startsWith(`${value}${path35.sep}`)))) continue;
@@ -6705,10 +7786,10 @@ function hashDirectory(root, excludedRoots = []) {
6705
7786
  hash2.update(`d:${relative2}\0`);
6706
7787
  visit(absolute);
6707
7788
  } else if (entry.isSymbolicLink()) {
6708
- hash2.update(`l:${relative2}\0${fs28.readlinkSync(absolute)}\0`);
7789
+ hash2.update(`l:${relative2}\0${fs29.readlinkSync(absolute)}\0`);
6709
7790
  } else if (entry.isFile()) {
6710
7791
  hash2.update(`f:${relative2}\0`);
6711
- hash2.update(fs28.readFileSync(absolute));
7792
+ hash2.update(fs29.readFileSync(absolute));
6712
7793
  hash2.update("\0");
6713
7794
  }
6714
7795
  }
@@ -6732,21 +7813,21 @@ function androidProjectFor(projectRoot, config) {
6732
7813
  return path35.isAbsolute(configured) ? configured : path35.resolve(projectRoot, configured);
6733
7814
  }
6734
7815
  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)));
7816
+ if (!fs29.existsSync(root) || !fs29.statSync(root).isDirectory()) return false;
7817
+ const hasBuildRoot = ["settings.gradle", "settings.gradle.kts", "build.gradle", "build.gradle.kts", "gradlew", "gradlew.bat"].some((name) => fs29.existsSync(path35.join(root, name)));
6737
7818
  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")));
7819
+ if (fs29.existsSync(path35.join(root, "app", "src", "main", "AndroidManifest.xml"))) return true;
7820
+ 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
7821
  }
6741
7822
  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();
7823
+ if (!fs29.existsSync(root) || !fs29.statSync(root).isDirectory()) return [];
7824
+ 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
7825
  }
6745
7826
  function evidenceRoot(projectRoot) {
6746
7827
  return path35.join(migrationDirectory(projectRoot), "evidence");
6747
7828
  }
6748
7829
  function evidenceIndex(projectRoot) {
6749
- return readJson7(path35.join(evidenceRoot(projectRoot), "index.json")) ?? { evidenceIndexVersion: "1.0.0", latestByAction: {} };
7830
+ return readJson8(path35.join(evidenceRoot(projectRoot), "index.json")) ?? { evidenceIndexVersion: "1.0.0", latestByAction: {} };
6750
7831
  }
6751
7832
  function projectRelative(projectRoot, absolute) {
6752
7833
  return path35.relative(projectRoot, absolute).replaceAll("\\", "/");
@@ -6759,7 +7840,7 @@ function managedReceipt2(projectRoot, reference) {
6759
7840
  const root = path35.resolve(evidenceRoot(projectRoot), "managed");
6760
7841
  const absolute = path35.resolve(migrationWorkspaceRoot(projectRoot), reference);
6761
7842
  if (absolute !== root && !absolute.startsWith(`${root}${path35.sep}`)) return null;
6762
- return readJson7(absolute);
7843
+ return readJson8(absolute);
6763
7844
  }
6764
7845
  function reportClaims(projectRoot, reportIndex) {
6765
7846
  if (!reportIndex) return [];
@@ -6775,16 +7856,16 @@ function reportClaims(projectRoot, reportIndex) {
6775
7856
  });
6776
7857
  };
6777
7858
  for (const descriptor of reportIndex.pages ?? []) {
6778
- const shard = readJson7(path35.join(reportRoot, descriptor.file));
7859
+ const shard = readJson8(path35.join(reportRoot, descriptor.file));
6779
7860
  if (!shard) continue;
6780
7861
  addItems([shard.page], descriptor.file);
6781
7862
  addItems(shard.controls, descriptor.file);
6782
7863
  addItems(shard.entries, descriptor.file);
6783
7864
  }
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;
7865
+ if (reportIndex.unassignedEntriesFile) addItems(readJson8(path35.join(reportRoot, reportIndex.unassignedEntriesFile))?.entries, reportIndex.unassignedEntriesFile);
7866
+ if (reportIndex.interactionsFile) addItems(readJson8(path35.join(reportRoot, reportIndex.interactionsFile))?.interactions, reportIndex.interactionsFile);
7867
+ if (reportIndex.discoveriesFile) addItems(readJson8(path35.join(reportRoot, reportIndex.discoveriesFile))?.items, "discoveries", true);
7868
+ const acceptance = reportIndex.finalAcceptanceFile ? readJson8(path35.join(reportRoot, reportIndex.finalAcceptanceFile)) : null;
6788
7869
  for (const [index, test] of (acceptance?.tests ?? []).entries()) claims.push({
6789
7870
  ref: test?.ref ?? `final-acceptance:${test?.name ?? index + 1}`,
6790
7871
  source: reportIndex.finalAcceptanceFile,
@@ -6799,8 +7880,8 @@ function receiptArtifactsAreIntact(projectRoot, receipt) {
6799
7880
  return receipt.artifacts.every((artifact) => {
6800
7881
  if (!artifact || typeof artifact.path !== "string" || !/^[a-f0-9]{64}$/.test(artifact.sha256) || !Number.isInteger(artifact.size)) return false;
6801
7882
  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);
7883
+ if (!absolute.startsWith(`${blobsRoot}${path35.sep}`) || !fs29.existsSync(absolute) || !fs29.statSync(absolute).isFile()) return false;
7884
+ const content = fs29.readFileSync(absolute);
6804
7885
  return content.byteLength === artifact.size && createHash8("sha256").update(content).digest("hex") === artifact.sha256;
6805
7886
  });
6806
7887
  }
@@ -6862,7 +7943,7 @@ function actionRequirements(action, snapshot) {
6862
7943
  if (action === "config:init" && snapshot.workspace.configPresent) missing.push(unmet("config-present", ".migration/config.json \u5DF2\u5B58\u5728"));
6863
7944
  if (action === "config:validate") requireConfig();
6864
7945
  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();
7946
+ if (["analyze:android", "analyze:harmony", "build:harmony", "test:harmony:targeted", "test:harmony:all", "evidence:flow"].includes(action)) requireConfig();
6866
7947
  if (["analyze:harmony", "build:harmony", "test:harmony:targeted", "test:harmony:all"].includes(action)) requireHarmony();
6867
7948
  if (action === "report:init") {
6868
7949
  requireFacts();
@@ -6874,6 +7955,10 @@ function actionRequirements(action, snapshot) {
6874
7955
  requireFacts();
6875
7956
  requireReport();
6876
7957
  requireFreshAnalysis();
7958
+ if (snapshot.planning.present && snapshot.planning.valid && snapshot.planning.fresh) {
7959
+ 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";
7960
+ missing.push(unmet("task-plan-already-fresh", message));
7961
+ }
6877
7962
  }
6878
7963
  if (action === "task:validate" && !snapshot.planning.present) missing.push(unmet("task-plan-missing", "Task Plan \u5C1A\u672A\u751F\u6210"));
6879
7964
  if (action === "checkpoint:create") {
@@ -6897,14 +7982,14 @@ function derivePhase(snapshot) {
6897
7982
  function inspectMigration(project = ".") {
6898
7983
  const projectRoot = path35.resolve(project);
6899
7984
  const migrationRoot = migrationDirectory(projectRoot);
6900
- const config = readJson7(path35.join(migrationRoot, "config.json"));
7985
+ const config = readJson8(path35.join(migrationRoot, "config.json"));
6901
7986
  const harmonyProject = harmonyProjectFor(projectRoot, config);
6902
7987
  const androidProject = androidProjectFor(projectRoot, config);
6903
7988
  const projectPresent = isAndroidProject(androidProject);
6904
7989
  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"));
7990
+ const preflight = readJson8(path35.join(migrationRoot, "preflight-report.json"));
7991
+ const specIndex = readJson8(path35.join(migrationRoot, "spec", "index.json"));
7992
+ const reportIndex = readJson8(path35.join(migrationRoot, "report", "index.json"));
6908
7993
  let validation;
6909
7994
  try {
6910
7995
  validation = validateMigrationReport([projectRoot], { writeResult: false });
@@ -6928,7 +8013,7 @@ function inspectMigration(project = ".") {
6928
8013
  projectCandidates,
6929
8014
  configPresent: Boolean(config),
6930
8015
  harmonyProject,
6931
- harmonyProjectPresent: fs28.existsSync(harmonyProject) && fs28.statSync(harmonyProject).isDirectory(),
8016
+ harmonyProjectPresent: fs29.existsSync(harmonyProject) && fs29.statSync(harmonyProject).isDirectory(),
6932
8017
  gitHead: projectPresent ? gitHead(androidProject) : null
6933
8018
  },
6934
8019
  preflight: {
@@ -6960,10 +8045,16 @@ function inspectMigration(project = ".") {
6960
8045
  valid: false,
6961
8046
  fresh: false,
6962
8047
  taskPlanSha256: null,
8048
+ planningPhase: null,
6963
8049
  totalTasks: 0,
8050
+ resolutionTasks: 0,
8051
+ totalTargets: 0,
8052
+ readyTargets: 0,
8053
+ completedTargets: 0,
6964
8054
  readyTasks: 0,
6965
8055
  blockedTasks: 0,
6966
8056
  completedTasks: 0,
8057
+ recommendedTargetId: null,
6967
8058
  recommendedTaskId: null,
6968
8059
  alternatives: [],
6969
8060
  reasons: {},
@@ -6972,7 +8063,8 @@ function inspectMigration(project = ".") {
6972
8063
  orphanFactRefs: [],
6973
8064
  errors: [],
6974
8065
  warnings: [],
6975
- tasks: []
8066
+ tasks: [],
8067
+ targets: []
6976
8068
  },
6977
8069
  evidence: {
6978
8070
  androidAnalysis: { reference: null, present: false, passed: false, fresh: false },
@@ -7018,7 +8110,7 @@ function inspectMigration(project = ".") {
7018
8110
  else {
7019
8111
  if (!snapshot.planning.valid) completionUnmet.push(unmet("task-plan-invalid", "Task Plan \u5B8C\u6574\u6027\u6821\u9A8C\u5931\u8D25"));
7020
8112
  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`));
8113
+ 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
8114
  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
8115
  }
7024
8116
  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 +8196,13 @@ function archiveArtifacts(projectRoot, artifacts) {
7104
8196
  const archived = [];
7105
8197
  for (const value of artifacts) {
7106
8198
  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);
8199
+ if (!fs29.existsSync(absolute) || !fs29.statSync(absolute).isFile()) continue;
8200
+ const content = fs29.readFileSync(absolute);
7109
8201
  const digest = createHash8("sha256").update(content).digest("hex");
7110
8202
  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);
8203
+ fs29.mkdirSync(path35.dirname(destination), { recursive: true });
8204
+ const existingIsIntact = fs29.existsSync(destination) && fs29.statSync(destination).isFile() && fs29.statSync(destination).size === content.byteLength && createHash8("sha256").update(fs29.readFileSync(destination)).digest("hex") === digest;
8205
+ if (!existingIsIntact) fs29.copyFileSync(absolute, destination);
7114
8206
  archived.push({
7115
8207
  path: workspaceRelative(projectRoot, destination),
7116
8208
  source: projectRelative(projectRoot, absolute),
@@ -7135,11 +8227,11 @@ function receiptResult(action, result) {
7135
8227
  };
7136
8228
  }
7137
8229
  function writeJsonAtomic(file, value) {
7138
- fs28.mkdirSync(path35.dirname(file), { recursive: true });
8230
+ fs29.mkdirSync(path35.dirname(file), { recursive: true });
7139
8231
  const temporary = `${file}.${process.pid}.tmp`;
7140
- fs28.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}
8232
+ fs29.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}
7141
8233
  `, "utf8");
7142
- fs28.renameSync(temporary, file);
8234
+ fs29.renameSync(temporary, file);
7143
8235
  }
7144
8236
  function recordObservation(options) {
7145
8237
  const projectRoot = path35.resolve(options.project ?? ".");
@@ -7201,9 +8293,8 @@ Usage:
7201
8293
  droid2hmos complete [project]
7202
8294
  droid2hmos analyze <android|harmony|all> [project]
7203
8295
  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>]
8296
+ droid2hmos config <init|validate> [project] [--harmony-project <path>]
8297
+ droid2hmos build harmony [project] [--timeout-ms <ms>]
7207
8298
  droid2hmos test harmony [project] [--runner arkts] (--class <class> [--case <case>] | --all) [--target <serial>] [--case-timeout-ms <ms>] [--ref <report-ref>...] [--timeout-ms <ms>]
7208
8299
  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
8300
  droid2hmos report [project] [--require-complete]
@@ -7212,16 +8303,16 @@ Usage:
7212
8303
  droid2hmos task plan [project]
7213
8304
  droid2hmos task status [project]
7214
8305
  droid2hmos task next [project]
7215
- droid2hmos task show [project] <task-id> [--cursor <cursor>] [--max-source-context <count>]
8306
+ droid2hmos task show [project] <task-or-package-id> [--cursor <cursor>] [--max-source-context <count>]
7216
8307
  droid2hmos task validate [project]
7217
8308
  droid2hmos checkpoint status [project]
7218
8309
  droid2hmos checkpoint create [project] --message <message> (--include <path>...|--all)
7219
- droid2hmos device check <android|harmony> [--adb <path>|--hdc <path>]
8310
+ droid2hmos device check harmony [--hdc <path>]
7220
8311
  droid2hmos auth login [project] [--timeout-ms <ms>]
7221
8312
  droid2hmos sign [project] [--target <serial>] [--force] [--timeout-ms <ms>]
7222
- droid2hmos evidence capture <android|harmony> [project] --name <name> [--target <id>]
8313
+ droid2hmos evidence capture harmony [project] --name <name> [--target <id>]
7223
8314
  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>...]
8315
+ droid2hmos evidence flow harmony [project] --name <name> [--target <id>] [--class <class> [--case <case>]|--all] [--case-timeout-ms <ms>] [--ref <report-ref>...]
7225
8316
  droid2hmos mock init [project] --spec <protocol-spec.json> [--force]
7226
8317
  droid2hmos logs <list|show|failures|stats> [project] [value]
7227
8318
  droid2hmos upgrade
@@ -7274,13 +8365,13 @@ function observed(project, action, result, scope, guardResult) {
7274
8365
  }
7275
8366
  async function runCli(argv, runtime) {
7276
8367
  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`);
8368
+ let command2 = args.shift();
8369
+ if (command2 && ["-v", "--version"].includes(command2)) {
8370
+ if (args.length > 0) throw new CliError(`${command2} does not accept arguments`);
7280
8371
  console.log(readPackageMetadata(runtime.packageRoot).version);
7281
8372
  return 0;
7282
8373
  }
7283
- if (!command3 || ["help", "-h", "--help"].includes(command3)) {
8374
+ if (!command2 || ["help", "-h", "--help"].includes(command2)) {
7284
8375
  console.log(HELP);
7285
8376
  return 0;
7286
8377
  }
@@ -7288,13 +8379,13 @@ async function runCli(argv, runtime) {
7288
8379
  console.log(HELP);
7289
8380
  return 0;
7290
8381
  }
7291
- if (command3 === "status") {
8382
+ if (command2 === "status") {
7292
8383
  const projects = positionals(args);
7293
8384
  if (projects.length > 1) throw new CliError("status accepts at most one project directory");
7294
8385
  json(inspectMigration(projects[0] ?? "."));
7295
8386
  return 0;
7296
8387
  }
7297
- if (command3 === "complete") {
8388
+ if (command2 === "complete") {
7298
8389
  const projects = positionals(args);
7299
8390
  if (projects.length > 1) throw new CliError("complete accepts at most one project directory");
7300
8391
  const project = projects[0] ?? ".";
@@ -7309,7 +8400,7 @@ async function runCli(argv, runtime) {
7309
8400
  if (exitCode !== 0) return exitCode;
7310
8401
  return 0;
7311
8402
  }
7312
- if (command3 === "init") {
8403
+ if (command2 === "init") {
7313
8404
  const projects = positionals(args, ["--target"]);
7314
8405
  if (projects.length > 1) throw new Error("init accepts at most one Android project directory");
7315
8406
  if (!runtime.templatesRoot) throw new Error(`Cannot locate bundled HarmonyOS template (templates/ohos) from ${runtime.entryFile}`);
@@ -7335,28 +8426,26 @@ async function runCli(argv, runtime) {
7335
8426
  json(observed(project, "init", { ...result, signature }, { target: optionValue(args, "--target") ?? null }, guardResult));
7336
8427
  return 0;
7337
8428
  }
7338
- if (command3 === "upgrade") {
8429
+ if (command2 === "upgrade") {
7339
8430
  if (args.length > 0) throw new CliError("upgrade does not accept arguments");
7340
8431
  json(upgradeCli({ packageRoot: runtime.packageRoot }));
7341
8432
  return 0;
7342
8433
  }
7343
- if (command3 === "config") {
8434
+ if (command2 === "config") {
7344
8435
  const action = args.shift();
7345
8436
  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"]);
8437
+ const projects = positionals(args, ["--harmony-project"]);
7347
8438
  if (projects.length > 1) throw new CliError("config accepts at most one project directory");
7348
8439
  const project = projects[0] ?? ".";
7349
8440
  const guardResult = guard(`config:${action}`, project);
7350
8441
  const result = action === "init" ? initMigrationConfig({
7351
8442
  project,
7352
- harmonyProject: optionValue(args, "--harmony-project"),
7353
- androidBuild: optionValue(args, "--android-build"),
7354
- androidTest: optionValue(args, "--android-test")
8443
+ harmonyProject: optionValue(args, "--harmony-project")
7355
8444
  }) : validateMigrationConfig(project);
7356
8445
  json(observed(project, `config:${action}`, result, null, guardResult));
7357
8446
  return action === "validate" && !result.valid ? 1 : 0;
7358
8447
  }
7359
- if (command3 === "preflight") {
8448
+ if (command2 === "preflight") {
7360
8449
  const projects = positionals(args, ["--sdk-path", "--timeout-ms"]);
7361
8450
  if (projects.length > 1) throw new CliError("preflight accepts at most one project directory");
7362
8451
  if (!runtime.templatesRoot) throw new Error("Cannot locate bundled HarmonyOS template");
@@ -7368,7 +8457,7 @@ async function runCli(argv, runtime) {
7368
8457
  json(observed(project, "preflight", result, { initializeHarmony: !args.includes("--no-init-harmony") }, guardResult));
7369
8458
  return result.status === "passed" ? 0 : 1;
7370
8459
  }
7371
- if (command3 === "analyze") {
8460
+ if (command2 === "analyze") {
7372
8461
  const target = args.shift();
7373
8462
  if (!(/* @__PURE__ */ new Set(["android", "harmony", "all"])).has(target)) throw new CliError("analyze target must be android, harmony, or all");
7374
8463
  const projects = positionals(args);
@@ -7401,7 +8490,7 @@ async function runCli(argv, runtime) {
7401
8490
  }
7402
8491
  return 0;
7403
8492
  }
7404
- if (command3 === "report") {
8493
+ if (command2 === "report") {
7405
8494
  const explicitAction = ["init", "validate", "render", "update"].includes(args[0] ?? "") ? args.shift() : null;
7406
8495
  const action = explicitAction ?? "render";
7407
8496
  const projects = positionals(args, ["--ref", "--status", "--harmony-file", "--android-source", "--evidence", "--difference"]);
@@ -7464,19 +8553,19 @@ async function runCli(argv, runtime) {
7464
8553
  }
7465
8554
  throw new Error("report requires init, validate, or render");
7466
8555
  }
7467
- if (command3 === "task") {
8556
+ if (command2 === "task") {
7468
8557
  const action = args.shift();
7469
8558
  if (!(/* @__PURE__ */ new Set(["plan", "status", "next", "show", "validate"])).has(action)) throw new CliError("task requires plan, status, next, show, or validate");
7470
8559
  if (action === "show") {
7471
8560
  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>");
8561
+ if (values.length < 1 || values.length > 2) throw new CliError("task show requires [project] <task-or-package-id>");
7473
8562
  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");
8563
+ const targetId = values.at(-1);
8564
+ 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
8565
  const maxSourceContext = Number(optionValue(args, "--max-source-context") ?? 25);
7477
8566
  if (!Number.isInteger(maxSourceContext) || maxSourceContext <= 0 || maxSourceContext > 200) throw new CliError("--max-source-context must be an integer from 1 to 200");
7478
8567
  const snapshot2 = inspectMigration(project2);
7479
- json(taskPlanning.materialize(project2, taskId, {
8568
+ json(taskPlanning.materialize(project2, targetId, {
7480
8569
  source: {
7481
8570
  androidFingerprint: snapshot2.source.androidFingerprint,
7482
8571
  harmonyFingerprint: snapshot2.source.harmonyFingerprint,
@@ -7498,8 +8587,13 @@ async function runCli(argv, runtime) {
7498
8587
  const result = {
7499
8588
  status: planning.valid && planning.fresh ? "passed" : "failed",
7500
8589
  taskPlanVersion: plan.taskPlanVersion,
8590
+ executionPlanVersion: plan.executionPlanVersion,
8591
+ planningPhase: plan.planningPhase,
7501
8592
  taskPlanSha256: planning.taskPlanSha256,
7502
8593
  totalTasks: plan.tasks.length,
8594
+ behaviorResponsibilities: plan.behaviorResponsibilities.length,
8595
+ pageWorkPackages: plan.pageWorkPackages.length,
8596
+ executionTargets: plan.planningPhase === "resolution" ? plan.tasks.length : plan.pageWorkPackages.length,
7503
8597
  resolutionTasks: plan.tasks.filter((task) => task.type === "resolution").length,
7504
8598
  errors: planning.errors,
7505
8599
  warnings: planning.warnings
@@ -7525,19 +8619,19 @@ async function runCli(argv, runtime) {
7525
8619
  }
7526
8620
  const allComplete = snapshot.totalTasks > 0 && snapshot.completedTasks === snapshot.totalTasks;
7527
8621
  json({
7528
- status: snapshot.recommendedTaskId ? "ready" : (() => {
8622
+ status: snapshot.recommendedTargetId ? "ready" : (() => {
7529
8623
  if (allComplete) return "complete";
7530
8624
  return "blocked";
7531
8625
  })(),
7532
- recommended: snapshot.recommendedTaskId,
8626
+ recommended: snapshot.recommendedTargetId,
7533
8627
  alternatives: snapshot.alternatives,
7534
8628
  reasons: snapshot.reasons
7535
8629
  });
7536
- return snapshot.recommendedTaskId || allComplete ? 0 : 1;
8630
+ return snapshot.recommendedTargetId || allComplete ? 0 : 1;
7537
8631
  }
7538
- if (command3 === "build") {
8632
+ if (command2 === "build") {
7539
8633
  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");
8634
+ 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
8635
  const projects = positionals(args, ["--timeout-ms"]);
7542
8636
  if (projects.length > 1) throw new CliError("build accepts at most one project directory");
7543
8637
  const projectRoot = path36.resolve(projects[0] ?? ".");
@@ -7545,52 +8639,29 @@ async function runCli(argv, runtime) {
7545
8639
  if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) throw new CliError("--timeout-ms must be a positive number");
7546
8640
  const configPath = path36.join(migrationDirectory(projectRoot), "config.json");
7547
8641
  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;
8642
+ const guardResult = guard("build:harmony", projectRoot);
8643
+ const config = JSON.parse(readFileSync7(configPath, "utf8"));
8644
+ const configuredHarmony = config.harmonyProject ?? defaultHarmonyProjectPath(projectRoot);
8645
+ const harmonyProject = path36.isAbsolute(configuredHarmony) ? configuredHarmony : path36.resolve(projectRoot, configuredHarmony);
8646
+ let result;
8647
+ try {
8648
+ result = runHarmonyBuild({ projectRoot, harmonyProject, timeoutMs });
8649
+ } catch (error) {
8650
+ result = { status: "failed", key: "harmonyBuild", summary: error?.message ?? String(error) };
7570
8651
  }
7571
- return 0;
8652
+ result = observed(projectRoot, "build:harmony", result, null, guardResult);
8653
+ json(result);
8654
+ return result.status === "passed" ? 0 : 1;
7572
8655
  }
7573
- if (command3 === "test") {
8656
+ if (command2 === "test") {
7574
8657
  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"]);
8658
+ 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)");
8659
+ const projects = positionals(args, ["--timeout-ms", "--case-timeout-ms", "--class", "--case", "--target", "--ref", "--runner", "--hypium-project", "--test-list", "--python", "--report-path"]);
7577
8660
  if (projects.length > 1) throw new CliError("test accepts at most one project directory");
7578
8661
  const projectRoot = path36.resolve(projects[0] ?? ".");
7579
8662
  const timeoutMs = Number(optionValue(args, "--timeout-ms") ?? 30 * 60 * 1e3);
7580
8663
  const harmonyTarget = optionValue(args, "--target") ?? null;
7581
8664
  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
8665
  const runner = optionValue(args, "--runner") ?? "arkts";
7595
8666
  if (!(/* @__PURE__ */ new Set(["arkts", "python"])).has(runner)) throw new CliError("--runner must be arkts or python");
7596
8667
  const testClass = optionValue(args, "--class") ?? null;
@@ -7651,7 +8722,7 @@ async function runCli(argv, runtime) {
7651
8722
  json(result);
7652
8723
  return result.status === "passed" ? 0 : 1;
7653
8724
  }
7654
- if (command3 === "checkpoint") {
8725
+ if (command2 === "checkpoint") {
7655
8726
  const action = args.shift();
7656
8727
  if (!(/* @__PURE__ */ new Set(["status", "create"])).has(action)) throw new CliError("checkpoint requires status or create");
7657
8728
  const projects = positionals(args, ["--message", "--include"]);
@@ -7688,16 +8759,16 @@ async function runCli(argv, runtime) {
7688
8759
  json(createGitCheckpoint({ project, harmonyProject: roots.harmonyRoot, message: optionValue(args, "--message") ?? "", include: optionValues(args, "--include"), all: args.includes("--all"), provenanceReceipt, provenanceFiles }));
7689
8760
  return 0;
7690
8761
  }
7691
- if (command3 === "device") {
8762
+ if (command2 === "device") {
7692
8763
  if (args.shift() !== "check") throw new CliError("device requires check");
7693
8764
  const platform = args.shift();
7694
- if (!(/* @__PURE__ */ new Set(["android", "harmony"])).has(platform)) throw new CliError("device check requires android or harmony");
8765
+ if (platform !== "harmony") throw new CliError("device check requires harmony (Android \u8BBE\u5907\u68C0\u6D4B\u5DF2\u79FB\u9664)");
7695
8766
  guard("device:check", ".");
7696
- const result = platform === "android" ? checkAndroidEmulator({ adb: optionValue(args, "--adb") }) : checkHarmonyDevice({ hdc: optionValue(args, "--hdc") });
8767
+ const result = checkHarmonyDevice({ hdc: optionValue(args, "--hdc") });
7697
8768
  json(result);
7698
8769
  return result.status === "failed" ? 1 : 0;
7699
8770
  }
7700
- if (command3 === "auth") {
8771
+ if (command2 === "auth") {
7701
8772
  if (args.shift() !== "login") throw new CliError("auth requires login");
7702
8773
  const projects = positionals(args, ["--timeout-ms"]);
7703
8774
  if (projects.length > 1) throw new CliError("auth accepts at most one project directory");
@@ -7712,7 +8783,7 @@ async function runCli(argv, runtime) {
7712
8783
  json(result);
7713
8784
  return result.status === "passed" ? 0 : 1;
7714
8785
  }
7715
- if (command3 === "sign") {
8786
+ if (command2 === "sign") {
7716
8787
  const projects = positionals(args, ["--target", "--timeout-ms"]);
7717
8788
  if (projects.length > 1) throw new CliError("sign accepts at most one project directory");
7718
8789
  const project = projects[0] ?? ".";
@@ -7734,7 +8805,7 @@ async function runCli(argv, runtime) {
7734
8805
  json(result);
7735
8806
  return result.status === "passed" || result.status === "skipped" ? 0 : 1;
7736
8807
  }
7737
- if (command3 === "evidence") {
8808
+ if (command2 === "evidence") {
7738
8809
  const action = args.shift();
7739
8810
  if (!(/* @__PURE__ */ new Set(["capture", "compare", "flow"])).has(action)) throw new CliError("evidence requires capture, compare, or flow");
7740
8811
  if (action === "compare") {
@@ -7749,8 +8820,8 @@ async function runCli(argv, runtime) {
7749
8820
  return 0;
7750
8821
  }
7751
8822
  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"]);
8823
+ if (platform !== "harmony") throw new CliError(`evidence ${action} requires harmony (Android \u8BC1\u636E\u91C7\u96C6\u5DF2\u79FB\u9664)`);
8824
+ const projects = positionals(args, ["--name", "--target", "--class", "--case", "--timeout-ms", "--case-timeout-ms", "--ref"]);
7754
8825
  const project = projects[0] ?? ".";
7755
8826
  const name = optionValue(args, "--name");
7756
8827
  if (!name) throw new CliError(`evidence ${action} requires --name`);
@@ -7766,11 +8837,7 @@ async function runCli(argv, runtime) {
7766
8837
  const timeoutMs = Number(optionValue(args, "--timeout-ms") ?? 30 * 60 * 1e3);
7767
8838
  let execution;
7768
8839
  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 {
8840
+ else {
7774
8841
  const config = JSON.parse(readFileSync7(path36.join(migrationDirectory(projectRoot), "config.json"), "utf8"));
7775
8842
  const harmonyProject = path36.isAbsolute(config.harmonyProject) ? config.harmonyProject : path36.resolve(projectRoot, config.harmonyProject);
7776
8843
  const testClass = optionValue(args, "--class") ?? null;
@@ -7782,7 +8849,7 @@ async function runCli(argv, runtime) {
7782
8849
  }
7783
8850
  const after = captureDeviceEvidence({ project, platform, name: `${name}-after`, target: optionValue(args, "--target") });
7784
8851
  let result = writeFlowBundle({ project, name, platform, before, execution, after });
7785
- if (platform === "harmony") {
8852
+ {
7786
8853
  const testAction = args.includes("--all") ? "test:harmony:all" : "test:harmony:targeted";
7787
8854
  result = observed(project, testAction, result, {
7788
8855
  evidenceFlow: name,
@@ -7797,7 +8864,7 @@ async function runCli(argv, runtime) {
7797
8864
  json(result);
7798
8865
  return result.status === "passed" ? 0 : 1;
7799
8866
  }
7800
- if (command3 === "mock") {
8867
+ if (command2 === "mock") {
7801
8868
  if (args.shift() !== "init") throw new CliError("mock requires init");
7802
8869
  const projects = positionals(args, ["--spec"]);
7803
8870
  const specFile = optionValue(args, "--spec");
@@ -7807,14 +8874,14 @@ async function runCli(argv, runtime) {
7807
8874
  json(generateMockServer({ project, specFile, force: args.includes("--force") }));
7808
8875
  return 0;
7809
8876
  }
7810
- if (command3 === "logs") {
8877
+ if (command2 === "logs") {
7811
8878
  const action = args.shift();
7812
8879
  if (!(/* @__PURE__ */ new Set(["list", "show", "failures", "stats"])).has(action)) throw new CliError("logs requires list, show, failures, or stats");
7813
8880
  const project = args.shift() ?? ".";
7814
8881
  json(queryCommandLogs(project, action, args.shift()));
7815
8882
  return 0;
7816
8883
  }
7817
- if (command3 === "service") {
8884
+ if (command2 === "service") {
7818
8885
  const action = args.shift();
7819
8886
  if (!(/* @__PURE__ */ new Set(["start", "status", "stop"])).has(action)) throw new CliError("service action must be start, status, or stop");
7820
8887
  const name = args.shift();
@@ -7858,7 +8925,7 @@ async function runCli(argv, runtime) {
7858
8925
  }));
7859
8926
  return 0;
7860
8927
  }
7861
- if (command3 === "internal") {
8928
+ if (command2 === "internal") {
7862
8929
  const action = args.shift();
7863
8930
  if (action === "detect-harmony-sdk") {
7864
8931
  const projects = positionals(args, ["--sdk-path"]);
@@ -7890,14 +8957,14 @@ async function runCli(argv, runtime) {
7890
8957
  }
7891
8958
  throw new Error("unknown internal command");
7892
8959
  }
7893
- if (command3 === "dev") {
8960
+ if (command2 === "dev") {
7894
8961
  const action = args.shift();
7895
8962
  if (!runtime.packageRoot || action !== "build") throw new Error("dev requires build from a source checkout");
7896
8963
  const result = spawnCommandSync("npm", ["run", "build"], { cwd: runtime.packageRoot, stdio: "inherit" });
7897
8964
  if (result.error) throw result.error;
7898
8965
  return result.status ?? 1;
7899
8966
  }
7900
- throw new CliError(`Unknown command: ${command3}`);
8967
+ throw new CliError(`Unknown command: ${command2}`);
7901
8968
  }
7902
8969
  var entryHref = import.meta.url;
7903
8970
  var invokedHref = (() => {