@harmonyos-arkts/d2h 0.1.26 → 0.1.27

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "android-to-hmos-migration",
3
3
  "description": "基于确定性 facts、迁移报告与端到端验证的 Android 到 HarmonyOS 迁移插件",
4
- "version": "0.1.26",
4
+ "version": "0.1.27",
5
5
  "license": "MIT",
6
6
  "keywords": [
7
7
  "android",
package/agents/d2h.md CHANGED
@@ -36,8 +36,8 @@ description: Android 应用到 HarmonyOS 的 AI 驱动迁移主 Agent;按迁
36
36
  | 从源码生成可审计功能清单、MVP/core/full 阶段、逐功能技术栈/三方 SDK/SO/权限与跨仓实现状态 | `d2h-app-solution` |
37
37
  | 从 Android 源码生成或刷新 Facts、UTG、点击路径和 `.migration/utg/page.json` | `d2h-utg-static-generate` |
38
38
  | 需要 AI 修复源码派生 UTG,或在 Android 设备上按已有计划采集运行时证据 | `d2h-utg-ai-generate` |
39
- | 根据已确认功能范围、架构和分析结果生成或修订 Spec 波次计划 | `d2h-spec-plan` |
40
- | 基于已确认计划生成逐页 `spec.md` 与生成记录 | `d2h-spec-generate` |
39
+ | 根据 Facts、源码和 UTG 生成页面计划、完整 `spec.md` 及初始账本 | `d2h-spec-generate` |
40
+ | 根据已确认功能范围与已有页面任务规划里程碑和波次 | `d2h-spec-plan` |
41
41
  | 按固定 Spec 范围串行实施、构建、检查并返回提交与证据 | `d2h-spec-develop` |
42
42
  | 根据 Android 工程结构与必要源码做目录级架构规划,详列推荐方案并对照另两套;确认并要求创建后落地目录壳子 | `d2h-architecture-plan` |
43
43
  | 规划或编写 ArkTS 架构、状态、导航和页面代码 | `arkts-mvvm-architecture` |
@@ -74,14 +74,13 @@ description: Android 应用到 HarmonyOS 的 AI 驱动迁移主 Agent;按迁
74
74
  ### 四、Android 工程分析
75
75
 
76
76
  1. 加载 `d2h-utg-static-generate`,生成或刷新 Facts、UTG、点击路径和 `.migration/utg/page.json`,并对照真实 Android 源码核验页面、入口、交互、数据流和 unresolved 项。
77
- 2. 加载 `d2h-utg-ai-generate` 补充 utg 为识别的项目。
77
+ 2. 加载 `d2h-utg-ai-generate` 补充 utg 未识别的项目。
78
78
 
79
79
  ### 五、spec 设计与生成
80
80
 
81
- 1. 将已确认的功能清单与范围、Facts、`.migration/utg/page.json`、源码证据和选定架构交给 `d2h-spec-plan`,明确执行正式规划,保存 `features.json` 与 `pages.json`。功能范围项映射到独立 Spec,页面保存归属;按交付目标组织波次,再将同轮同页的 Spec 组织为开发批次,保留逐成员分组依据与独立验收身份。
82
- 2. 加载 `d2h-spec-generate`,按确认计划及选定 Spec 生成详细 `spec.md` 和生成记录;沿用已验证的 Spec 模板与内容要求,发现计划问题时返回修订建议。
83
- 3. 将详细规格与生成结果交回 `d2h-spec-plan`,复核分组和依赖,交接开发前的计划版本、范围版本及选定批次/Spec;无实质变化时保留原版本。发生修订时核对受影响的生成绑定,规格缺失或失效的成员不能进入开发。
84
-
81
+ 1. 加载 `d2h-spec-generate`,根据 Facts、`.migration/utg/page.json`、源码和目标工程现状确定页面边界、依赖与顺序,生成 `pages.json`、页面 `spec.md` 和初始 `batch_report.json`。已有产物按该技能规则复用,不能覆盖执行历史。
82
+ 2. 将已确认功能范围、选定架构及已有页面产物交给 `d2h-spec-plan`,规划范围映射、任务排序、里程碑和波次;主 Agent 按技能要求核对规划结果。
83
+ 3. 执行顺序以 spec-plan 的里程碑、波次和任务排序为准;页面归属、生成时的顺序和依赖作为规划依据。排序不得违反实际前置,不拆合 Spec、不改变正文布局或账本任务身份。
85
84
 
86
85
  ### 六、代码转换
87
86
 
@@ -92,13 +91,13 @@ description: Android 应用到 HarmonyOS 的 AI 驱动迁移主 Agent;按迁
92
91
  - 迁移字符串和多语言时使用 `arkts-i18n`,禁止用 `.ets` 硬编码代替字符串资源迁移。
93
92
  - 转换图片和 drawable 时使用 `android2hmos_resources_convert`;发现尺寸或比例问题时使用 `arkts-icon-sizing`。
94
93
 
95
- 2. 加载 `d2h-spec-develop`,主 Agent 按有效计划初始化或恢复 `batch_report.json`,以严格串行方式推进页面批次。任意时刻只派发一个同页批次,由一个独立 page-worker 在固定窗口内逐 Spec 实施:
94
+ 2. 加载 `d2h-spec-develop`,主 Agent 按有效计划初始化或恢复 `batch_report.json`,按里程碑、波次和任务排序严格串行推进。任意时刻只派发一个页面,由一个独立 page-worker 完成当前页面闭环:
96
95
 
97
- a. 主 Agent 根据计划、账本及实际前置证据选择当前波次内就绪的页面批次,固定本次 `batch_id/spec_ids`、版本和允许路径。
98
- b. 向 page-worker 交接详细 Spec、源码与目标工程、Git 基线、真实共享接口和检查要求。worker 加载 `d2h-spec-develop`,只实施固定成员,返回逐 Spec 的变更、提交、证据和缺口;不自行扩组或更新账本。
99
- c. 主 Agent 按计划的验证安排组织检查;需要生成或更新 Python Hypium 测试资产时加载 `d2h-hypium-forge`。核对实际断言、执行结果和证据,未执行、缺失或过期的结果不能标记通过。
100
- d. 主 Agent 复核作用域提交和工作区,逐 Spec 更新 `batch_report.json`,记录真实实现文件、来源、本次构建与测试证据、差异及阻塞;分组不得合并验收状态。
101
- e. 当前窗口成员闭环后再选择下一批次;受阻时显式暂停并保存进度,按依赖协调后续工作。同一页面可跨波次再次开发,当前批次完成只表示本次增量完成,全部约定范围解决后才能标记整页完成。
96
+ a. 主 Agent 按账本中的有效规划,只从纳入的任务中,根据实际前置证据选择当前波次内按任务排序最早未完成且前置满足的任务,固定本次任务 ID、版本和允许路径;未纳入任务不得因规格已生成而派发。
97
+ b. 向 page-worker 交接详细 Spec、源码与目标工程、Git 基线、真实共享接口和检查要求。worker 加载 `d2h-spec-develop`,只实施当前页面,按技能完成构建、测试、提交及账本更新,返回变更、证据和缺口。
98
+ c. 主 Agent 按页面流程组织检查;需要生成或更新 Python Hypium 测试资产时加载 `d2h-hypium-forge`。核对实际断言、执行结果和证据,未执行、缺失或过期的结果不能标记通过。
99
+ d. 主 Agent 复核作用域提交和工作区,核对 `batch_report.json` 中记录的真实实现文件、来源、本次构建与测试证据、差异及阻塞,原位更新并保留历史。
100
+ e. 当前任务闭环后再按计划选择下一任务;受阻时继续修复当前任务或记录外部阻塞。里程碑与波次组织交付顺序,不拆分页面闭环或改变验收要求。
102
101
 
103
102
  若当前环境不支持独立 page-worker,必须明确报告该开发模式无法执行,不能静默改成主 Agent 批量开发。
104
103
 
@@ -134,7 +133,7 @@ e. 当前窗口成员闭环后再选择下一批次;受阻时显式暂停并
134
133
 
135
134
  ## 完成条件
136
135
 
137
- 只有同时满足以下条件,才允许宣布迁移完成:
136
+ 以下条件按本次已确认开发范围核对;计划未纳入的任务保留规格和账本状态,不阻塞本次范围交付,也不得将本次完成表述为全量迁移完成。只有同时满足以下条件,才允许宣布本次范围完成:
138
137
 
139
138
  - Preflight、Facts、UTG、页面 Spec、`batch_report.json` 和证据对应当前源码。
140
139
  - `pages.json` 覆盖全部已知页面或明确记录排除原因。
@@ -16,6 +16,7 @@ import { inspectToolchain, installToolchain, loadToolchain } from '../src/toolch
16
16
  import fs from 'node:fs';
17
17
  import { inspectVSCodeInstallation, installVSCode } from '../src/install-vscode.js';
18
18
  import { codexConfigRoot, inspectCodexInstallation, installCodex } from '../src/install-codex.js';
19
+ import { installTuiExtension, inspectTuiExtension } from '../src/install-tui-extension.js';
19
20
  import { runWorkflow } from '../src/workflow.js';
20
21
  const help = `Install this package's skills and agents into OpenCode, DevEco Code or Codex.
21
22
 
@@ -89,6 +90,10 @@ try {
89
90
  console.log(`${item.status}: ${item.package} expected ${item.expectedVersion}, installed ${item.installedVersion ?? 'missing'}; capabilities ${JSON.stringify(item.capabilities)}`);
90
91
  const pluginCache = agent === 'codex' ? [] : findMetricsPluginCacheDirs(metricsPluginCacheRoots(agent));
91
92
  console.log(`plugin cache: ${pluginCache.length ? `${pluginCache.join(', ')}; cleared by d2h install/upgrade` : 'none'}`);
93
+ if (agent === 'opencode' || agent === 'vscode') {
94
+ const tui = inspectTuiExtension({ agent: agent === 'vscode' ? 'vscode' : 'opencode', extensionDir });
95
+ console.log(`${tui.status}: opencode-tui-extension ${tui.summary}`);
96
+ }
92
97
  const checkerUrl = new URL('../../scripts/check-environment.mjs', import.meta.url);
93
98
  const { main } = await import(__rewriteRelativeImportExtension(checkerUrl.href));
94
99
  const environmentExit = await main([]);
@@ -168,12 +173,16 @@ try {
168
173
  else if (!explicitSource && !options.dryRun)
169
174
  console.log(`Skipped managed CLI installation (${Object.keys(loadToolchain(packageRoot).tools).join(', ')}).`);
170
175
  if (!checkOnly) {
171
- if (options.agent === 'vscode')
172
- installVSCode({ sourceDir: options.sourceDir, extensionDir, remote, dryRun: options.dryRun });
176
+ let vscodeExtensionRoot;
177
+ if (options.agent === 'vscode') {
178
+ vscodeExtensionRoot = installVSCode({ sourceDir: options.sourceDir, extensionDir, remote, dryRun: options.dryRun }).extensionRoot;
179
+ }
173
180
  else if (options.agent === 'codex')
174
181
  installCodex({ ...options, remote });
175
182
  else
176
183
  installOpenCode({ ...options, remote });
184
+ if ((options.agent === 'opencode' || options.agent === 'vscode') && !skipTools)
185
+ installTuiExtension({ dryRun: options.dryRun, force: options.force, extensionRoot: vscodeExtensionRoot });
177
186
  if (!options.dryRun) {
178
187
  const target = options.agent === 'vscode' ? 'VS Code HarmonyOS Dev Assistant' :
179
188
  options.agent === 'deveco' ? 'DevEco Code' : options.agent === 'codex' ? 'Codex' : 'OpenCode';
@@ -0,0 +1,133 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { run } from './toolchain.js';
5
+ import { locateVSCodeExtension } from './install-vscode.js';
6
+ export const tuiExtension = { name: 'opencode-tui-extension', version: '0.1.1' };
7
+ function bundledOpenCodeBinary(extensionRoot) {
8
+ const executable = process.platform === 'win32' ? 'opencode.exe' : 'opencode';
9
+ return path.join(extensionRoot, 'resources', 'opencode', 'bin', process.platform, process.arch, executable);
10
+ }
11
+ function majorVersion(output) {
12
+ const major = Number(output.match(/(\d+)\.\d+\.\d+/)?.[1]);
13
+ return Number.isInteger(major) && major > 0 ? major : null;
14
+ }
15
+ function bundledMajorVersion(binary, runner) {
16
+ const version = runner(binary, ['--version']);
17
+ if (version.status === 0) {
18
+ const major = majorVersion(`${version.stdout}\n${version.stderr}`);
19
+ if (major === 1 || major === 2)
20
+ return major;
21
+ }
22
+ const help = runner(binary, ['--help']);
23
+ const text = `${help.stdout}\n${help.stderr}`;
24
+ if (help.status !== 0 || !/Commands:/.test(text))
25
+ return null;
26
+ return /^\s*USAGE\b/m.test(text) ? 2 : 1;
27
+ }
28
+ export function installTuiExtension({ dryRun = false, force = false, extensionRoot, runner = run, log = console.log } = {}) {
29
+ const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
30
+ const globalRoot = runner(npm, ['root', '--global']);
31
+ if (globalRoot.status !== 0) {
32
+ log(`warning: cannot inspect global npm root; skipped ${tuiExtension.name}`);
33
+ return;
34
+ }
35
+ const linkedPath = path.join(globalRoot.stdout.trim(), tuiExtension.name);
36
+ const linked = fs.existsSync(linkedPath) && fs.lstatSync(linkedPath).isSymbolicLink();
37
+ if (linked && !force) {
38
+ log(`preserved npm link ${tuiExtension.name} at ${linkedPath}`);
39
+ }
40
+ else {
41
+ const coordinate = `${tuiExtension.name}@${tuiExtension.version}`;
42
+ if (dryRun) {
43
+ log(`would install ${coordinate}`);
44
+ return;
45
+ }
46
+ const install = runner(npm, ['install', '--global', coordinate]);
47
+ if (install.status !== 0) {
48
+ log(`warning: failed to install ${coordinate}: ${install.stderr || install.error?.message || 'unknown error'}`);
49
+ return;
50
+ }
51
+ log(`installed ${coordinate}`);
52
+ }
53
+ let setupArgs = ['setup'];
54
+ let env;
55
+ if (extensionRoot) {
56
+ const configDir = path.join(extensionRoot, 'resources', 'opencode');
57
+ const binary = bundledOpenCodeBinary(extensionRoot);
58
+ if (!fs.existsSync(binary)) {
59
+ log(`warning: bundled opencode binary not found: ${binary}; skipped ${tuiExtension.name} registration`);
60
+ return;
61
+ }
62
+ const major = bundledMajorVersion(binary, runner);
63
+ if (major !== 1 && major !== 2) {
64
+ log(`warning: cannot detect bundled opencode version at ${binary}; skipped ${tuiExtension.name} registration`);
65
+ return;
66
+ }
67
+ setupArgs = ['setup', major === 1 ? '--v1' : '--v2'];
68
+ env = { ...process.env, OPENCODE_CONFIG_DIR: configDir };
69
+ if (dryRun) {
70
+ log(`would register ${tuiExtension.name} for bundled opencode v${major} into ${configDir}`);
71
+ return;
72
+ }
73
+ }
74
+ else if (dryRun) {
75
+ log(`would register ${tuiExtension.name} (setup)`);
76
+ return;
77
+ }
78
+ const bin = process.platform === 'win32' ? `${tuiExtension.name}.cmd` : tuiExtension.name;
79
+ const setup = runner(bin, setupArgs, env ? { env } : undefined);
80
+ if (setup.status !== 0) {
81
+ log(`warning: ${tuiExtension.name} setup failed: ${setup.stderr || 'unknown error'}`);
82
+ return;
83
+ }
84
+ log(`registered ${tuiExtension.name} (TUI status banner)${extensionRoot ? ` into ${path.join(extensionRoot, 'resources', 'opencode')}` : ''}; restart to activate`);
85
+ }
86
+ function readJsonIfPresent(file) {
87
+ try {
88
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
89
+ }
90
+ catch {
91
+ return undefined;
92
+ }
93
+ }
94
+ function registeredIn(config, key) {
95
+ return Array.isArray(config?.[key]) && config[key].some(entry => String(entry).includes(tuiExtension.name));
96
+ }
97
+ export function inspectTuiExtension({ agent, extensionDir, runner = run }) {
98
+ const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
99
+ const globalRoot = runner(npm, ['root', '--global']);
100
+ const installedDir = globalRoot.status === 0 ? path.join(globalRoot.stdout.trim(), tuiExtension.name) : undefined;
101
+ const manifest = installedDir ? readJsonIfPresent(path.join(installedDir, 'package.json')) : undefined;
102
+ const installedVersion = typeof manifest?.version === 'string' ? manifest.version : undefined;
103
+ if (!installedVersion)
104
+ return { status: 'warning', summary: 'not installed globally; rerun d2h install' };
105
+ if (agent === 'opencode') {
106
+ const configDir = process.env.OPENCODE_CONFIG_DIR || path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'), 'opencode');
107
+ const where = [
108
+ registeredIn(readJsonIfPresent(path.join(configDir, 'tui.json')), 'plugin') ? 'tui.json' : undefined,
109
+ registeredIn(readJsonIfPresent(path.join(configDir, 'cli.json')), 'plugins') ? 'cli.json' : undefined,
110
+ ].filter(Boolean);
111
+ if (!where.length)
112
+ return { status: 'warning', summary: `${installedVersion} installed but not registered under ${configDir}; rerun d2h install` };
113
+ return { status: 'passed', summary: `${installedVersion} registered (${where.join(', ')})` };
114
+ }
115
+ try {
116
+ const root = locateVSCodeExtension(extensionDir);
117
+ const binary = bundledOpenCodeBinary(root);
118
+ if (!fs.existsSync(binary))
119
+ return { status: 'warning', summary: `${installedVersion} installed but bundled opencode binary missing: ${binary}` };
120
+ const major = bundledMajorVersion(binary, runner);
121
+ if (major !== 1 && major !== 2)
122
+ return { status: 'warning', summary: `${installedVersion} installed but bundled opencode version undetected` };
123
+ const file = major === 1 ? 'tui.json' : 'cli.json';
124
+ const configDir = path.join(root, 'resources', 'opencode');
125
+ if (!registeredIn(readJsonIfPresent(path.join(configDir, file)), major === 1 ? 'plugin' : 'plugins')) {
126
+ return { status: 'warning', summary: `${installedVersion} installed but not registered in ${path.join(configDir, file)}; rerun d2h install` };
127
+ }
128
+ return { status: 'passed', summary: `${installedVersion} registered for bundled opencode v${major} (${file})` };
129
+ }
130
+ catch (error) {
131
+ return { status: 'warning', summary: `${installedVersion} installed but VS Code extension not found: ${error instanceof Error ? error.message : String(error)}` };
132
+ }
133
+ }
@@ -18,7 +18,7 @@ export function loadToolchain(root) {
18
18
  }
19
19
  return value;
20
20
  }
21
- export const run = (command, args) => {
21
+ export const run = (command, args, options) => {
22
22
  let executable = command;
23
23
  let parameters = args;
24
24
  if (process.platform === 'win32') {
@@ -28,7 +28,7 @@ export const run = (command, args) => {
28
28
  executable = 'powershell.exe';
29
29
  parameters = ['-NoProfile', '-NonInteractive', '-EncodedCommand', Buffer.from(script, 'utf16le').toString('base64')];
30
30
  }
31
- const result = spawnSync(executable, parameters, { encoding: 'utf8', windowsHide: true, timeout: 300000 });
31
+ const result = spawnSync(executable, parameters, { encoding: 'utf8', windowsHide: true, timeout: 300000, env: options?.env, cwd: options?.cwd });
32
32
  return { status: result.status, stdout: result.stdout ?? '', stderr: result.stderr ?? '', error: result.error };
33
33
  };
34
34
  function version(output) { return output.match(/\b\d+\.\d+\.\d+\b/)?.[0] ?? null; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harmonyos-arkts/d2h",
3
- "version": "0.1.26",
3
+ "version": "0.1.27",
4
4
  "description": "Install Android-to-HarmonyOS migration agents and skills into OpenCode or DevEco Code",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  name: d2h-architecture-plan
3
3
  description: >-
4
- 用于 Android 转 HarmonyOS 的架构评估、目录规划和方案选型。默认读取 Android 源码,交付原结构对照、鸿蒙优选、融合三套结构选项,详列推荐方案目录;
5
- 用户确认方案并要求落地时,优先复用 Preflight 已建基础工程、补齐所选目录;无基础工程时可创建。
4
+ 用于 Android 转 HarmonyOS 的架构评估、目录规划和方案选型。读取 Android 源码,以 JSON 交付项目简介、原工程分析和三套嵌套目录,供看板展示推荐与用户选择;
5
+ 看板确认或 question 工具回答统一落盘,目录实施读取 JSON 后复用或创建基础工程、补齐所选目录。
6
6
  ---
7
7
 
8
8
  # Android 源码 → 鸿蒙架构与目录规划
@@ -17,20 +17,29 @@ description: >-
17
17
  - 确认鸿蒙工程已存在,且含业务实现或最小辨别后性质仍不明确,用户又尚未决定是否结合时,询问一次:“是否结合现有鸿蒙工程的业务实现进行架构规划?默认仅分析 Android 工程。”仅提供输出路径或配置中的目标路径不单独证明已有业务工程或用户选择结合。
18
18
  - 用户明确要求结合/复用鸿蒙业务实现时,按约定范围读取;明确选择不结合时,只分析 Android。同一工程、同一任务范围内沿用已有明确选择,不重复询问;用户改变选择时据此调整。尚未答复期间可继续 Android 侧分析,不扩读鸿蒙业务源码,也不把沉默记为用户拒绝。复用基础工程、核对配置或创建目录的授权不自动扩大为分析已有业务实现。
19
19
  - 已有功能清单、Facts、规划可作导航线索,用当前源码核验。
20
- - 默认只交付架构 Markdown;不生成持久化的全量文件清单、源码快照、import/调用图或扫描基线。已有大型扫描产物按问题查询,不整体读入或为更新规划重新生成。
20
+ - 默认交付 `architecture-plan.json` 作为看板决策事实源,同时保留 `architecture-plan.md` 作为现有主流程使用的阅读视图;推荐、选择和目录以 JSON 为准,两者同步。不生成持久化的全量文件清单、源码快照、import/调用图或扫描基线。已有大型扫描产物按问题查询,不整体读入或为更新规划重新生成。
21
21
  - 源工程只读;默认在同级 `<工程名>-migration/.migration/architecture-plan/` 交付。已有文件先阅读,保留用户批注,修订时同步受影响部分。
22
22
  - 分析分支和目录组织依据本次读取范围内的工程事实与迁移目标确定,参考示例按实际需要选用。基础配置核对可用于工程复用判断;未读取的鸿蒙业务实现不作已核验的能力复用依据。
23
23
 
24
24
  ## 架构规划
25
25
 
26
26
  1. 按 [source-review.md](references/source-review.md) 从构建声明、入口/导航和主要源码目录建立结构地图,识别基础依赖、页面业务、共享业务及装配。仅当证据不足会改变拆合、归属或依赖方向时,定点读取相关源码;边界可解释后停止扩读。
27
- 2. 按 [layout-reference.md](references/layout-reference.md) 设计原结构对照 `android-mirror`、鸿蒙优选 `harmony-practice`、融合 `hybrid`,共用范围地图;已有选定方案时只修订它及受影响的对照。融合结构以基础依赖保留原模块/包族、业务按 feature 承载页面为原则,需要详细展开时读 [hybrid-design.md](references/hybrid-design.md),不展开逐页实施计划。
28
- 3. 按 [plan-contract.md](references/plan-contract.md) 详细展开推荐方案的关键目录、归属与主要依赖,另两套给结构差异对照;已有选定方案则围绕它展开。用户明确要求三套完整目录时再全部展开,共享事实仍只写一次。复核目录/归属/依赖一致性,不重复业务链或完整分析报告。
27
+ 2. 按 [layout-reference.md](references/layout-reference.md) 设计原结构对照 `android-mirror`、鸿蒙优选 `harmony-practice`、融合 `hybrid`,共用范围地图;已有选定方案时围绕它修订并同步受影响的对照。融合结构以基础依赖保留原模块/包族、业务按 feature 承载页面为原则,需要详细展开时读 [hybrid-design.md](references/hybrid-design.md),不展开逐页实施计划。
28
+ 3. 读取 [plan-contract.md](references/plan-contract.md) 和 [json-contract.md](references/json-contract.md),JSON 聚焦三部分:项目简介、Android 工程分析与风险、三套可独立展开的目录树;推荐/选择及最小回传元数据沿用契约。每套仅 `id/name/summary/directoryTree`,不生成机器映射表、依赖图、共享所有者或逐方案收益成本数组。每个目录都有相对路径与简介,三套均展开到可选型、可创建的关键目录粒度,不用文字差异、通配符或引用另一套目录代替树。必要的归属/依赖/共享边界在 Markdown 简记,复核与目录一致。
29
+ 4. 运行 `python <本Skill目录>/scripts/architecture_plan.py validate <产物目录>/architecture-plan.json`,再同步 Markdown 阅读视图。向调用方交接 JSON/Markdown 路径、`planId` 和待决项;三套方案的选择单位是整套目录方案,展开/折叠及点击单个节点不改变选择。
30
+
31
+ `androidAnalysis` 固定为简短的 `summary`、按语言/界面/存储/网络四类归纳的 `techStack`、最多五项的 `risks`。风险只含名称与迁移影响;字段与类型见 JSON 契约。用用户能理解的文字说明关键事实,不堆叠工具和架构缩写,不输出源码目录清单或独立未决清单;详细核验线索放 Markdown。
29
32
 
30
33
  本 Skill 交付架构与转换交接设计;并行候选表示具备独立转换的架构条件,实际派发、开发和验收由调用方的执行流程决定。
31
34
 
32
35
  ## 选型与可选落地
33
36
 
34
- 只请求分析/规划时,交付推荐和可选方案即完成本次规划,不为结束任务强求用户立即选型。用户选定后按意见修订、复核并记录最终方案。**落地或搬迁前须有对当前方案的实际确认及落地要求**;已有“采用推荐并创建”的明确授权则直接按授权执行,已有基础工程时在其上补齐目录,无须再次要求新建工程授权。
37
+ 选型、接收回答和恢复实施时读取 [selection-flow.md](references/selection-flow.md)。仅规划时交付即可;需要选型时,模型填写推荐,`selectedProposalId` 初始为 `null`。有效选择不重复询问,风险仅展示、不要求逐项答复,也不因风险非空而阻塞选型或目录创建。
38
+
39
+ 看板“确定”和 OpenCode `question` 回答是同一个确认步骤的两个入口;已有明确对话授权也可沿用。真实提交先写独立请求文件,脚本校验后保存 `architecture-selection.json` 并更新规划选择。不得先覆盖已确认文件,不把默认项、未提交/取消或模型推荐当用户答案。主规划只保留 `selectedProposalId`,不嵌套完整确认。
40
+
41
+ 创建流程的提示需明确“确定后创建或复用基础工程并补齐目录”,一次确认记为 `select-and-create`,不追加“是否继续”;只选型则记为 `select`。question 回答也必须落盘,不只留在上下文。实施前运行 `read-selection` 重新核对规划、确认及原始意见,再按选定 ID 读取目录树;写入失败、不一致或有未处理的布局修改时不执行旧树。记录恢复使用统一流程的显式恢复入口。
42
+
43
+ 用户通过对话调整 JSON,并同步 Markdown。仅修改说明或风险时保留有效确认;影响目录、目标位置或授权范围时,先清理当前确认和旧选择,再更新展示并按实际授权确认。技术未知由 Agent 在实施时核查;必要输入/授权缺失时只询问缺失项。
35
44
 
36
- 需要落地时才读 [directory-creation.md](references/directory-creation.md)。完整迁移优先沿用 Preflight 的目标根与基础工程,使用普通文件工具落地所选目录;确实没有基础工程时才使用本机 DevEco CLI 创建。不生成业务实现,不构建或运行应用。
45
+ 需要落地时读取 [directory-creation.md](references/directory-creation.md)。沿用 Preflight 目标与基础工程,普通文件工具补齐选定目录,确实无基础工程时才用本机 DevEco CLI 创建;不生成业务实现、不构建或运行应用。看板服务器、提交端点与会话通知由外部适配方提供,本 Skill 不提供监听器,也不把“文件已写入”当作 Agent 已被唤醒。