@harmonyos-arkts/d2h 0.1.27 → 0.1.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/AGENTS.md +2 -0
  3. package/README.md +43 -3
  4. package/agents/d2h.md +16 -14
  5. package/dist/bin/install-opencode.js +28 -23
  6. package/dist/src/install-codex.js +5 -126
  7. package/dist/src/install-skills.js +147 -0
  8. package/dist/src/install-vscode.js +2 -2
  9. package/dist/src/select-agent.js +207 -0
  10. package/docs/environment-check.md +3 -3
  11. package/package.json +6 -6
  12. package/skills/arkts-mvvm-architecture/SKILL.md +2 -0
  13. package/skills/d2h-architecture-plan/SKILL.md +19 -26
  14. package/skills/d2h-architecture-plan/references/architecture-plan.example.json +80 -315
  15. package/skills/d2h-architecture-plan/references/directory-creation.md +27 -34
  16. package/skills/d2h-architecture-plan/references/hybrid-design.md +13 -15
  17. package/skills/d2h-architecture-plan/references/json-contract.md +29 -91
  18. package/skills/d2h-architecture-plan/references/layout-reference.md +30 -27
  19. package/skills/d2h-architecture-plan/references/plan-contract.md +27 -34
  20. package/skills/d2h-architecture-plan/references/source-review.md +1 -1
  21. package/skills/d2h-architecture-plan/scripts/architecture_plan.py +29 -163
  22. package/skills/d2h-architecture-plan/scripts/test_architecture_plan.py +205 -342
  23. package/skills/d2h-spec-develop/SKILL.md +5 -4
  24. package/{install-opencode.sh → tooling/install-opencode.sh} +3 -3
  25. package/{opencode → tooling/opencode}/interactive-tools/advance-progress-demo.mjs +1 -1
  26. package/{opencode → tooling/opencode}/interactive-tools/demo.mjs +3 -3
  27. package/{opencode → tooling/opencode}/interactive-tools/prepare-demo.mjs +1 -1
  28. package/{scripts → tooling/scripts}/check-environment.mjs +3 -3
  29. package/skills/d2h-architecture-plan/references/selection-flow.md +0 -71
  30. /package/{opencode → tooling/opencode}/agents.json +0 -0
  31. /package/{opencode → tooling/opencode}/interactive-tools/browser-host.ts +0 -0
  32. /package/{opencode → tooling/opencode}/interactive-tools/core.ts +0 -0
  33. /package/{opencode → tooling/opencode}/interactive-tools/demo-workflow.ts +0 -0
  34. /package/{opencode → tooling/opencode}/interactive-tools/package-lock.json +0 -0
  35. /package/{opencode → tooling/opencode}/interactive-tools/package.json +0 -0
  36. /package/{opencode → tooling/opencode}/interactive-tools/panel.ts +0 -0
  37. /package/{opencode → tooling/opencode}/interactive-tools/prepare-progress-demo.mjs +0 -0
  38. /package/{opencode → tooling/opencode}/interactive-tools/progress.ts +0 -0
  39. /package/{opencode → tooling/opencode}/interactive-tools/server-legacy.mjs +0 -0
  40. /package/{opencode → tooling/opencode}/interactive-tools/server.mjs +0 -0
  41. /package/{opencode → tooling/opencode}/interactive-tools/tui.tsx +0 -0
  42. /package/{opencode → tooling/opencode}/vscode-d2h-package.json +0 -0
  43. /package/{opencode → tooling/opencode}/vscode-d2h-plugin.mjs +0 -0
@@ -0,0 +1,207 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { createInterface } from 'node:readline/promises';
5
+ import { emitKeypressEvents, moveCursor, cursorTo, clearScreenDown } from 'node:readline';
6
+ import { skillHosts } from './install-skills.js';
7
+ import { locateVSCodeExtension } from './install-vscode.js';
8
+ function executable(name, env, platform) {
9
+ const extensions = platform === 'win32' ? (env.PATHEXT || '.EXE;.CMD;.BAT').split(';') : [''];
10
+ for (const directory of (env.PATH || '').split(platform === 'win32' ? ';' : ':').filter(Boolean)) {
11
+ for (const extension of extensions) {
12
+ const file = path.join(directory, name + extension);
13
+ try {
14
+ if (!fs.statSync(file).isFile())
15
+ continue;
16
+ fs.accessSync(file, platform === 'win32' ? fs.constants.F_OK : fs.constants.X_OK);
17
+ return file;
18
+ }
19
+ catch { /* Missing or inaccessible commands are not detection evidence. */ }
20
+ }
21
+ }
22
+ return undefined;
23
+ }
24
+ export function detectAgents({ home = os.homedir(), env = process.env, platform = process.platform, extensionDir } = {}) {
25
+ const configHome = env.XDG_CONFIG_HOME || path.join(home, '.config');
26
+ const hosts = [
27
+ { agent: 'opencode', label: 'OpenCode', root: env.OPENCODE_CONFIG_DIR || path.join(configHome, 'opencode'), commands: ['opencode'] },
28
+ { agent: 'vscode', label: 'VS Code (HarmonyOS Dev Assistant)', root: path.join(home, '.vscode'), commands: ['code'] },
29
+ { agent: 'deveco', label: 'DevEco Code', root: env.DEVECO_CONFIG_DIR || path.join(configHome, 'deveco'), commands: ['deveco'] },
30
+ ...Object.entries(skillHosts).map(([agent, host]) => ({
31
+ agent, label: host.label,
32
+ root: ('environment' in host && env[host.environment]) || path.join(home, host.directory),
33
+ commands: agent === 'qoder' ? ['qodercli', 'qoder'] : agent === 'trae-cn' ? ['trae-cn'] : [agent]
34
+ }))
35
+ ];
36
+ return hosts.map(host => {
37
+ const detected = [];
38
+ for (const command of host.commands) {
39
+ const file = executable(command, env, platform);
40
+ if (file) {
41
+ detected.push(`命令 ${file}`);
42
+ break;
43
+ }
44
+ }
45
+ try {
46
+ if (fs.statSync(host.root).isDirectory())
47
+ detected.push(`配置目录 ${host.root}`);
48
+ }
49
+ catch { /* Not present. */ }
50
+ if (host.agent === 'vscode') {
51
+ try {
52
+ const extension = locateVSCodeExtension(extensionDir, home);
53
+ const metadata = JSON.parse(fs.readFileSync(path.join(extension, 'package.json'), 'utf8'));
54
+ if (String(metadata.name).toLowerCase() === 'harmonyosdevassistantforvscode' &&
55
+ String(metadata.publisher).toLowerCase() === 'huaweideveloperserviceplatform') {
56
+ detected.push(`鸿蒙插件 ${extension}`);
57
+ }
58
+ }
59
+ catch { /* VS Code without the required extension remains selectable. */ }
60
+ }
61
+ return { agent: host.agent, label: host.label, detected };
62
+ });
63
+ }
64
+ export function formatAgentChoice(choice) {
65
+ const detected = choice.agent === 'vscode'
66
+ ? choice.detected.some(item => item.startsWith('鸿蒙插件 '))
67
+ : choice.detected.length > 0;
68
+ const status = detected ? '可配置' : '未检测到';
69
+ const label = ['opencode', 'vscode'].includes(choice.agent) ? choice.label : `${choice.label} (${choice.agent})`;
70
+ return `${label} — ${status}`;
71
+ }
72
+ export async function chooseAgent(choices, ask, write) {
73
+ write('选择安装目标 / Select agent(检测结果仅供参考):\n');
74
+ choices.forEach((choice, index) => write(` ${index + 1}. ${formatAgentChoice(choice)}\n`));
75
+ for (;;) {
76
+ const answer = (await ask('输入序号或 agent 名称,q 取消:')).trim().toLowerCase();
77
+ if (['q', 'quit', 'exit'].includes(answer))
78
+ throw new Error('Installation cancelled before making changes');
79
+ const choice = /^\d+$/.test(answer) ? choices[Number(answer) - 1] : choices.find(item => item.agent === answer);
80
+ if (choice)
81
+ return choice.agent;
82
+ write('请选择列表中的序号或 agent 名称。\n');
83
+ }
84
+ }
85
+ export function chooseAgentWithKeys(choices, input = process.stdin, output = process.stderr) {
86
+ return new Promise((resolve, reject) => {
87
+ let selected = 0;
88
+ let renderedLines = 0;
89
+ let answer = '';
90
+ const colorDepth = process.env.NO_COLOR === undefined ? (output.getColorDepth?.() || 1) : 1;
91
+ const wasRaw = Boolean(input.isRaw);
92
+ const wasFlowing = input.readableFlowing === true;
93
+ const rows = Math.max(1, Math.min(choices.length, (output.rows || 24) - 4));
94
+ function render() {
95
+ if (renderedLines) {
96
+ moveCursor(output, 0, -renderedLines);
97
+ cursorTo(output, 0);
98
+ clearScreenDown(output);
99
+ }
100
+ const start = Math.max(0, Math.min(selected - rows + 1, choices.length - rows));
101
+ const lines = ['选择安装目标 / Select agent(检测结果仅供参考)'];
102
+ for (let i = start; i < start + rows; i++) {
103
+ lines.push(`${i === selected ? '>' : ' '} ${i + 1}. ${formatAgentChoice(choices[i])}`);
104
+ }
105
+ lines.push(`↑↓ 选择,回车确认;也可输入序号或名称,q 取消:${answer}`);
106
+ // Keep every row within the terminal width so cursor movement stays aligned.
107
+ const width = Math.max(1, (output.columns || 80) - 1);
108
+ const clipped = lines.map(line => {
109
+ let cells = 0;
110
+ let result = '';
111
+ for (const char of line) {
112
+ const size = char.codePointAt(0) >= 0x1100 ? 2 : 1;
113
+ if (cells + size > width)
114
+ break;
115
+ result += char;
116
+ cells += size;
117
+ }
118
+ return result;
119
+ });
120
+ const highlighted = clipped.map((line, index) => {
121
+ if (index !== selected - start + 1 || colorDepth < 4)
122
+ return line;
123
+ // HarmonyOS-style blue (#0A59F7), with an ANSI blue fallback.
124
+ const color = colorDepth >= 24 ? '\x1b[1;38;2;255;255;255;48;2;10;89;247m' : '\x1b[1;97;44m';
125
+ return color + line + '\x1b[0m';
126
+ });
127
+ output.write(highlighted.join('\n') + '\n');
128
+ renderedLines = clipped.length;
129
+ }
130
+ function finish(agent) {
131
+ input.removeListener('keypress', onKey);
132
+ input.removeListener('end', onEnd);
133
+ input.removeListener('error', onError);
134
+ input.setRawMode(wasRaw);
135
+ if (!wasFlowing)
136
+ input.pause();
137
+ if (agent) {
138
+ output.write(`已选择:${choices.find(item => item.agent === agent).label}\n`);
139
+ resolve(agent);
140
+ }
141
+ else
142
+ reject(new Error('Installation cancelled before making changes'));
143
+ }
144
+ function onEnd() { finish(); }
145
+ function onError(error) { finish(); }
146
+ function onKey(text, key = {}) {
147
+ if ((key.ctrl && ['c', 'd'].includes(key.name || '')) || key.name === 'escape') {
148
+ finish();
149
+ return;
150
+ }
151
+ if (key.name === 'up' || key.name === 'down') {
152
+ selected = (selected + (key.name === 'up' ? -1 : 1) + choices.length) % choices.length;
153
+ answer = '';
154
+ }
155
+ else if (key.name === 'return' || key.name === 'enter') {
156
+ const value = answer.trim().toLowerCase();
157
+ if (['q', 'quit', 'exit'].includes(value)) {
158
+ finish();
159
+ return;
160
+ }
161
+ const choice = !value ? choices[selected] : /^\d+$/.test(value) ? choices[Number(value) - 1] : choices.find(item => item.agent === value);
162
+ if (choice) {
163
+ finish(choice.agent);
164
+ return;
165
+ }
166
+ answer = '';
167
+ }
168
+ else if (key.name === 'backspace')
169
+ answer = answer.slice(0, -1);
170
+ else if (text && !key.ctrl && !key.meta && /^[a-zA-Z0-9-]+$/.test(text))
171
+ answer = (answer + text).slice(0, 30);
172
+ else
173
+ return;
174
+ render();
175
+ }
176
+ emitKeypressEvents(input);
177
+ input.setRawMode(true);
178
+ input.on('keypress', onKey);
179
+ input.once('end', onEnd);
180
+ input.once('error', onError);
181
+ input.resume();
182
+ render();
183
+ });
184
+ }
185
+ export async function selectAgent(extensionDir) {
186
+ if (!process.stdin.isTTY || !process.stderr.isTTY) {
187
+ throw new Error('Agent selection requires an interactive terminal; pass --agent <name> (for example --agent opencode)');
188
+ }
189
+ const choices = detectAgents({ extensionDir });
190
+ if (process.env.TERM !== 'dumb')
191
+ return chooseAgentWithKeys(choices);
192
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
193
+ const controller = new AbortController();
194
+ rl.once('SIGINT', () => controller.abort());
195
+ rl.once('close', () => controller.abort());
196
+ try {
197
+ return await chooseAgent(choices, prompt => rl.question(prompt, { signal: controller.signal }), text => process.stderr.write(text));
198
+ }
199
+ catch (error) {
200
+ if (controller.signal.aborted)
201
+ throw new Error('Installation cancelled before making changes');
202
+ throw error;
203
+ }
204
+ finally {
205
+ rl.close();
206
+ }
207
+ }
@@ -11,11 +11,11 @@ npx --yes @harmonyos-arkts/d2h@0.1.5 --check-env --output-dir ./environment-repo
11
11
  本地构建后:`node dist/bin/install-opencode.js --check-env`。检查参数在 --check-env 后传入;使用 --check-env --help 查看。检查模式不执行 Agent/Skill 安装;不能与 --force、--agent、--config-dir 等安装参数混用,误用时返回参数错误。npx 自身可能下载并缓存 npm 包。
12
12
 
13
13
  ```powershell
14
- node scripts/check-environment.mjs --output-dir D:/reports/hmos-environment
14
+ node tooling/scripts/check-environment.mjs --output-dir D:/reports/hmos-environment
15
15
  ```
16
16
 
17
17
  ```sh
18
- node scripts/check-environment.mjs --output-dir ~/reports/hmos-environment
18
+ node tooling/scripts/check-environment.mjs --output-dir ~/reports/hmos-environment
19
19
  ```
20
20
 
21
21
  ## 当前检查项
@@ -99,5 +99,5 @@ npx --yes @harmonyos-arkts/d2h@0.1.5 --check-env --requirements ./environment-re
99
99
  测试:
100
100
 
101
101
  ```sh
102
- node --test --test-concurrency=1 scripts/tests/environment.test.mjs
102
+ node --test --test-concurrency=1 tooling/scripts/tests/environment.test.mjs
103
103
  ```
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@harmonyos-arkts/d2h",
3
- "version": "0.1.27",
4
- "description": "Install Android-to-HarmonyOS migration agents and skills into OpenCode or DevEco Code",
3
+ "version": "0.1.29",
4
+ "description": "Install Android-to-HarmonyOS migration agents and skills into AI coding tools",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "d2h": "dist/bin/install-opencode.js"
@@ -15,10 +15,10 @@
15
15
  "schemas/",
16
16
  "workflow/",
17
17
  "contracts/",
18
- "opencode/",
18
+ "tooling/opencode/",
19
19
  ".claude-plugin/",
20
- "install-opencode.sh",
21
- "scripts/check-environment.mjs",
20
+ "tooling/install-opencode.sh",
21
+ "tooling/scripts/check-environment.mjs",
22
22
  "docs/environment-check.md",
23
23
  "docs/incremental-spec-evaluation-design.md",
24
24
  "docs/spec-plan-guide.md",
@@ -32,7 +32,7 @@
32
32
  "scripts": {
33
33
  "build": "tsc -p tsconfig.json",
34
34
  "check": "tsc --noEmit && npm run build && node dist/scripts/check-skill-refs.js && node dist/scripts/check-toolchain.js",
35
- "test": "npm run build && node --test dist/test/*.test.js scripts/tests/*.test.mjs",
35
+ "test": "npm run build && node --test dist/test/*.test.js tooling/scripts/tests/*.test.mjs",
36
36
  "prepack": "npm run build",
37
37
  "prepublishOnly": "npm run check && npm test"
38
38
  },
@@ -20,6 +20,8 @@ tags: [arkts, mvvm, state-management-v2, navigation, architecture, harmonyos]
20
20
 
21
21
  ## 1. 目录结构规范
22
22
 
23
+ 优先遵循工程已有的目录、模块归属和共享边界,在其内部落实 MVVM 分层。以下目录树和模块拆分建议供新建工程或尚无明确约定的部分参考,不要求现有工程按示例重组。
24
+
23
25
  按「功能 + 分层」组织。`core/` 放跨模块共享资产,业务按 feature 内聚。
24
26
 
25
27
  ```
@@ -1,45 +1,38 @@
1
1
  ---
2
2
  name: d2h-architecture-plan
3
3
  description: >-
4
- 用于 Android 转 HarmonyOS 的架构评估、目录规划和方案选型。读取 Android 源码,以 JSON 交付项目简介、原工程分析和三套嵌套目录,供看板展示推荐与用户选择;
5
- 看板确认或 question 工具回答统一落盘,目录实施读取 JSON 后复用或创建基础工程、补齐所选目录。
4
+ 用于 Android 转 HarmonyOS 的目录级架构评估、三方案比较与选型。确认后交付规划 Markdown 和最终目录 JSON;
5
+ 按授权创建或复用鸿蒙基础工程、补齐关键目录。
6
6
  ---
7
7
 
8
8
  # Android 源码 → 鸿蒙架构与目录规划
9
9
 
10
- 基于 Android 工程结构与必要源码,为迁移确定职责归属、依赖方向和可落地的鸿蒙目录,兼顾原开发者维护便利性。默认做目录级架构分析,不开展完整业务理解、逐页行为分析或实现级迁移设计。
10
+ 基于 Android 工程结构与必要源码,确定职责归属、依赖方向和可落地的鸿蒙目录,兼顾原开发者维护便利性。默认做目录级架构分析,不开展完整业务理解、逐页行为分析或实现级迁移设计。
11
11
 
12
12
  ## 输入与边界
13
13
 
14
14
  - 必需输入只有 Android 源码根目录,可独立执行。按用户范围识别实际应用、运行时模块、技术栈和目标设备;源码缺失或目标能力未知时标明影响。
15
- - 默认读取 Android 工程源码及其相关资源、构建声明;不自动读取鸿蒙业务源码,也不为寻找鸿蒙参考实现扩大扫描范围。上下文已指向鸿蒙目标时,按 [source-review.md](references/source-review.md) 做最小辨别,区分自动生成的模板壳、已有业务工程和性质未明的工程。
16
- - 已确认仍为自动生成模板壳的工程(包括 Preflight 创建的工程)作为后续落地基础,不作为业务架构依据,不触发“是否结合现有鸿蒙工程”的询问。职责与目录由 Android 事实及目标要求决定,不能被空壳的默认结构限制。
17
- - 确认鸿蒙工程已存在,且含业务实现或最小辨别后性质仍不明确,用户又尚未决定是否结合时,询问一次:“是否结合现有鸿蒙工程的业务实现进行架构规划?默认仅分析 Android 工程。”仅提供输出路径或配置中的目标路径不单独证明已有业务工程或用户选择结合。
18
- - 用户明确要求结合/复用鸿蒙业务实现时,按约定范围读取;明确选择不结合时,只分析 Android。同一工程、同一任务范围内沿用已有明确选择,不重复询问;用户改变选择时据此调整。尚未答复期间可继续 Android 侧分析,不扩读鸿蒙业务源码,也不把沉默记为用户拒绝。复用基础工程、核对配置或创建目录的授权不自动扩大为分析已有业务实现。
19
- - 已有功能清单、Facts、规划可作导航线索,用当前源码核验。
20
- - 默认交付 `architecture-plan.json` 作为看板决策事实源,同时保留 `architecture-plan.md` 作为现有主流程使用的阅读视图;推荐、选择和目录以 JSON 为准,两者同步。不生成持久化的全量文件清单、源码快照、import/调用图或扫描基线。已有大型扫描产物按问题查询,不整体读入或为更新规划重新生成。
21
- - 源工程只读;默认在同级 `<工程名>-migration/.migration/architecture-plan/` 交付。已有文件先阅读,保留用户批注,修订时同步受影响部分。
22
- - 分析分支和目录组织依据本次读取范围内的工程事实与迁移目标确定,参考示例按实际需要选用。基础配置核对可用于工程复用判断;未读取的鸿蒙业务实现不作已核验的能力复用依据。
15
+ - 默认读取 Android 源码及相关资源、构建声明。上下文已指向鸿蒙目标时,按 [source-review.md](references/source-review.md) 辨别其性质:模板壳仅作落地基础,无需询问是否结合业务;已有业务或性质未明且用户尚未决定时,询问是否结合其业务实现,默认仅分析 Android。
16
+ - 沿用同工程、同范围的明确选择;等待答复时可继续 Android 分析,不扩读鸿蒙业务。配置核对与目录创建不自动扩大为业务分析;模板的默认目录不限制架构设计。
17
+ - 已有功能清单、Facts、规划可作定位线索,用当前源码核验。不生成全量文件清单、源码快照、import/调用图或扫描基线;已有大型产物按问题查询。
18
+ - 源工程只读;默认在同级 `<工程名>-migration/.migration/architecture-plan/` 交付。已有文件先阅读,保留用户批注,修订受影响部分。
23
19
 
24
- ## 架构规划
20
+ ## 规划 → 确认 → 展示
25
21
 
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) 和 [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` 和待决项;三套方案的选择单位是整套目录方案,展开/折叠及点击单个节点不改变选择。
22
+ 1. 按 [source-review.md](references/source-review.md) 从构建声明、入口/导航和主要源码目录建立结构地图,识别基础依赖、页面业务、共享业务及装配。仅当证据不足会改变拆合、归属或依赖时扩读;边界可解释后停止。
23
+ 2. 按 [layout-reference.md](references/layout-reference.md) 设计原结构对照 `android-mirror`、鸿蒙优选 `harmony-practice`、融合 `hybrid`,共用范围地图。在对话中展示三套目录结构、职责、取舍及推荐理由,供用户判断,暂不生成交付文件。融合方案设计时读 [hybrid-design.md](references/hybrid-design.md)。已有有效选择时围绕它修订,不重新强制选型。
24
+ 3. 用可用的 question 工具或对话询问方案选择,三个选项对应上述方案并标识推荐,说明本次只选型还是确认后落地。只接受实际回答,预选项、取消或超时不算选择;先处理修改意见,仅询问仍缺失的用户决定。技术核验由 Agent 完成。已有当前有效选择和授权直接沿用,不重复询问;仅比较、尚未选型时在对话中交付分析,不生成最终 JSON/Markdown。
25
+ 4. 用户回答完成后,按 [plan-contract.md](references/plan-contract.md) 生成 `architecture-plan.md`,保存三套目录及实际选型/授权;按 [json-contract.md](references/json-contract.md) 生成最终 `architecture-plan.json`,只含 `projectName`、`directoryTree`、`decisions`。两份产物使用同一组已确认的路径与决定;JSON 先在临时文件中生成,校验通过后替换正式文件,再交给只读看板。任一产物保存失败或不一致时,先修复,不进入实施。
26
+ 5. 有创建/复用授权时,读取 [directory-creation.md](references/directory-creation.md),重新读取最终 JSON 和 Markdown 确认记录后实施。完整迁移已有创建要求并确认方案时直接继续;仅选型不扩大为创建授权。
30
27
 
31
- `androidAnalysis` 固定为简短的 `summary`、按语言/界面/存储/网络四类归纳的 `techStack`、最多五项的 `risks`。风险只含名称与迁移影响;字段与类型见 JSON 契约。用用户能理解的文字说明关键事实,不堆叠工具和架构缩写,不输出源码目录清单或独立未决清单;详细核验线索放 Markdown。
28
+ 校验命令:`python <本Skill目录>/scripts/architecture_plan.py validate <产物目录>/architecture-plan.json`。辅助脚本要求 Python 3.10+,只校验展示数据,不处理选择、写入文件或创建工程。
32
29
 
33
- 本 Skill 交付架构与转换交接设计;并行候选表示具备独立转换的架构条件,实际派发、开发和验收由调用方的执行流程决定。
30
+ ## 交付与修订
34
31
 
35
- ## 选型与可选落地
32
+ `architecture-plan.md` 保存项目理解、三套对照、推荐、用户回答与范围、最终归属/依赖及风险;`architecture-plan.json` 是确认后生成的最终展示和目录实施输入,两者应一致。
36
33
 
37
- 选型、接收回答和恢复实施时读取 [selection-flow.md](references/selection-flow.md)。仅规划时交付即可;需要选型时,模型填写推荐,`selectedProposalId` 初始为 `null`。有效选择不重复询问,风险仅展示、不要求逐项答复,也不因风险非空而阻塞选型或目录创建。
34
+ 用户通过对话修改时同步两份产物。影响目录、依赖、目标或授权范围时,先将旧展示 JSON 移为不覆盖历史文件的备份,注明待修订并暂停受影响的实施,处理后重新校验发布;明确授权的调整不再重复确认。缺少当前有效确认的旧产物不作为最终结果继续展示。
38
35
 
39
- 看板“确定”和 OpenCode `question` 回答是同一个确认步骤的两个入口;已有明确对话授权也可沿用。真实提交先写独立请求文件,脚本校验后保存 `architecture-selection.json` 并更新规划选择。不得先覆盖已确认文件,不把默认项、未提交/取消或模型推荐当用户答案。主规划只保留 `selectedProposalId`,不嵌套完整确认。
36
+ 恢复实施须重读 Markdown 的实际确认与最终 JSON,核对项目、目标和目录;文件存在或校验通过不代表用户授权。看板只展示最终 JSON,加载、刷新及修订期间的缓存处理由调用方适配。
40
37
 
41
- 创建流程的提示需明确“确定后创建或复用基础工程并补齐目录”,一次确认记为 `select-and-create`,不追加“是否继续”;只选型则记为 `select`。question 回答也必须落盘,不只留在上下文。实施前运行 `read-selection` 重新核对规划、确认及原始意见,再按选定 ID 读取目录树;写入失败、不一致或有未处理的布局修改时不执行旧树。记录恢复使用统一流程的显式恢复入口。
42
-
43
- 用户通过对话调整 JSON,并同步 Markdown。仅修改说明或风险时保留有效确认;影响目录、目标位置或授权范围时,先清理当前确认和旧选择,再更新展示并按实际授权确认。技术未知由 Agent 在实施时核查;必要输入/授权缺失时只询问缺失项。
44
-
45
- 需要落地时读取 [directory-creation.md](references/directory-creation.md)。沿用 Preflight 目标与基础工程,普通文件工具补齐选定目录,确实无基础工程时才用本机 DevEco CLI 创建;不生成业务实现、不构建或运行应用。看板服务器、提交端点与会话通知由外部适配方提供,本 Skill 不提供监听器,也不把“文件已写入”当作 Agent 已被唤醒。
38
+ 本 Skill 交付架构与转换交接设计。页面族是候选,目录壳子不是已实现能力;实际派发、开发和验收由调用方流程决定。