@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.
- package/.claude-plugin/plugin.json +1 -1
- package/AGENTS.md +2 -0
- package/README.md +43 -3
- package/agents/d2h.md +16 -14
- package/dist/bin/install-opencode.js +28 -23
- package/dist/src/install-codex.js +5 -126
- package/dist/src/install-skills.js +147 -0
- package/dist/src/install-vscode.js +2 -2
- package/dist/src/select-agent.js +207 -0
- package/docs/environment-check.md +3 -3
- package/package.json +6 -6
- package/skills/arkts-mvvm-architecture/SKILL.md +2 -0
- package/skills/d2h-architecture-plan/SKILL.md +19 -26
- package/skills/d2h-architecture-plan/references/architecture-plan.example.json +80 -315
- package/skills/d2h-architecture-plan/references/directory-creation.md +27 -34
- package/skills/d2h-architecture-plan/references/hybrid-design.md +13 -15
- package/skills/d2h-architecture-plan/references/json-contract.md +29 -91
- package/skills/d2h-architecture-plan/references/layout-reference.md +30 -27
- package/skills/d2h-architecture-plan/references/plan-contract.md +27 -34
- package/skills/d2h-architecture-plan/references/source-review.md +1 -1
- package/skills/d2h-architecture-plan/scripts/architecture_plan.py +29 -163
- package/skills/d2h-architecture-plan/scripts/test_architecture_plan.py +205 -342
- package/skills/d2h-spec-develop/SKILL.md +5 -4
- package/{install-opencode.sh → tooling/install-opencode.sh} +3 -3
- package/{opencode → tooling/opencode}/interactive-tools/advance-progress-demo.mjs +1 -1
- package/{opencode → tooling/opencode}/interactive-tools/demo.mjs +3 -3
- package/{opencode → tooling/opencode}/interactive-tools/prepare-demo.mjs +1 -1
- package/{scripts → tooling/scripts}/check-environment.mjs +3 -3
- package/skills/d2h-architecture-plan/references/selection-flow.md +0 -71
- /package/{opencode → tooling/opencode}/agents.json +0 -0
- /package/{opencode → tooling/opencode}/interactive-tools/browser-host.ts +0 -0
- /package/{opencode → tooling/opencode}/interactive-tools/core.ts +0 -0
- /package/{opencode → tooling/opencode}/interactive-tools/demo-workflow.ts +0 -0
- /package/{opencode → tooling/opencode}/interactive-tools/package-lock.json +0 -0
- /package/{opencode → tooling/opencode}/interactive-tools/package.json +0 -0
- /package/{opencode → tooling/opencode}/interactive-tools/panel.ts +0 -0
- /package/{opencode → tooling/opencode}/interactive-tools/prepare-progress-demo.mjs +0 -0
- /package/{opencode → tooling/opencode}/interactive-tools/progress.ts +0 -0
- /package/{opencode → tooling/opencode}/interactive-tools/server-legacy.mjs +0 -0
- /package/{opencode → tooling/opencode}/interactive-tools/server.mjs +0 -0
- /package/{opencode → tooling/opencode}/interactive-tools/tui.tsx +0 -0
- /package/{opencode → tooling/opencode}/vscode-d2h-package.json +0 -0
- /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.
|
|
4
|
-
"description": "Install Android-to-HarmonyOS migration agents and skills into
|
|
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
|
},
|
|
@@ -1,45 +1,38 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: d2h-architecture-plan
|
|
3
3
|
description: >-
|
|
4
|
-
用于 Android 转 HarmonyOS
|
|
5
|
-
|
|
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
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
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
|
|
28
|
-
3.
|
|
29
|
-
4.
|
|
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
|
-
|
|
28
|
+
校验命令:`python <本Skill目录>/scripts/architecture_plan.py validate <产物目录>/architecture-plan.json`。辅助脚本要求 Python 3.10+,只校验展示数据,不处理选择、写入文件或创建工程。
|
|
32
29
|
|
|
33
|
-
|
|
30
|
+
## 交付与修订
|
|
34
31
|
|
|
35
|
-
|
|
32
|
+
`architecture-plan.md` 保存项目理解、三套对照、推荐、用户回答与范围、最终归属/依赖及风险;`architecture-plan.json` 是确认后生成的最终展示和目录实施输入,两者应一致。
|
|
36
33
|
|
|
37
|
-
|
|
34
|
+
用户通过对话修改时同步两份产物。影响目录、依赖、目标或授权范围时,先将旧展示 JSON 移为不覆盖历史文件的备份,注明待修订并暂停受影响的实施,处理后重新校验发布;明确授权的调整不再重复确认。缺少当前有效确认的旧产物不作为最终结果继续展示。
|
|
38
35
|
|
|
39
|
-
|
|
36
|
+
恢复实施须重读 Markdown 的实际确认与最终 JSON,核对项目、目标和目录;文件存在或校验通过不代表用户授权。看板只展示最终 JSON,加载、刷新及修订期间的缓存处理由调用方适配。
|
|
40
37
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
用户通过对话调整 JSON,并同步 Markdown。仅修改说明或风险时保留有效确认;影响目录、目标位置或授权范围时,先清理当前确认和旧选择,再更新展示并按实际授权确认。技术未知由 Agent 在实施时核查;必要输入/授权缺失时只询问缺失项。
|
|
44
|
-
|
|
45
|
-
需要落地时读取 [directory-creation.md](references/directory-creation.md)。沿用 Preflight 目标与基础工程,普通文件工具补齐选定目录,确实无基础工程时才用本机 DevEco CLI 创建;不生成业务实现、不构建或运行应用。看板服务器、提交端点与会话通知由外部适配方提供,本 Skill 不提供监听器,也不把“文件已写入”当作 Agent 已被唤醒。
|
|
38
|
+
本 Skill 交付架构与转换交接设计。页面族是候选,目录壳子不是已实现能力;实际派发、开发和验收由调用方流程决定。
|