@harmonyos-arkts/d2h 0.1.16 → 0.1.17

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.16",
4
+ "version": "0.1.17",
5
5
  "license": "MIT",
6
6
  "keywords": [
7
7
  "android",
package/AGENTS.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # Android to HarmonyOS Migration
2
2
 
3
- 处理迁移请求时,由当前正式主 Agent `agents/d2h_v2.md` 统一编排专项 Skill。`agents/android-to-hmos-00-orchestrator.md` 和 `agents/d2h.md` 仅保留旧名称兼容,不是规则真源。
3
+ 处理迁移请求时,由当前正式主 Agent `agents/d2h.md` 统一编排专项 Skill。`agents/android-to-hmos-00-orchestrator.md`仅保留旧名称兼容,不是规则真源。
4
4
 
5
5
  以真实 HarmonyOS 应用质量为目标,以 AppGraph facts 和迁移报告降低遗漏风险。不要恢复已废弃的旧 SPEC/PLAN/TASK 多 Agent 文档流程或静态转译器;当前使用的 Kernel Task Plan(`droid2hmos task plan/validate/next/show` 派生计划)属于 Migration Kernel 协议,不在此列。
@@ -72,6 +72,7 @@ description: Android 应用到 HarmonyOS 的结果驱动迁移主 Agent,以完
72
72
 
73
73
  - 图片/图形资源转换 skill:`android2hmos_resources_convert`(res/ 图片资产 → `media/`、XML drawable → SVG、自适应图标 → layered-image)。需要 Preflight 已创建或复用的 HarmonyOS 工程
74
74
  - 安卓/鸿蒙三方库映射关系 skill:`native-library-substitution`
75
+ - 云侧权威知识查询 skill:`d2h-cloud-knowledge`。当当前实现的功能涉及不确定的 @kit.* Kit / @ohos.* 模块 API、ArkUI 组件行为或平台能力(相机/定位/文件/通知/蓝牙/分享/Web/后台任务等)、构建或测试失败指向 API 签名与行为、或需核验已选型 ohpm 库用法时加载。先查 `.migration/knowledge/kb_cache.json` 缓存,命中不重查;结论以文档 `title`/`uri` 引用回 spec 与迁移报告。本地文档、`harmonyos-migration-playbook` 已验证经验或其他专项 skill 能解决时不加载。
75
76
 
76
77
  Skill 是当前工作的操作说明,不替代本 Agent 的目标、判断和持续执行责任。读取一个 Skill 后只使用与当前阶段直接相关的内容。
77
78
 
@@ -109,7 +110,8 @@ Skill 是当前工作的操作说明,不替代本 Agent 的目标、判断和
109
110
  - App 名称、图标、版本号、包名等身份信息迁移 → `arkts-app-identity`;
110
111
  - 用户可见文案与多语言资源迁移 → `arkts-i18n`;
111
112
  - 图标/图片尺寸失真校准 → `arkts-icon-sizing`;
112
- - Android 三方库依赖的鸿蒙替代映射 → `native-library-substitution`。
113
+ - Android 三方库依赖的鸿蒙替代映射 → `native-library-substitution`;
114
+ - 平台能力实现、不确定的 @kit.* Kit / @ohos.* 模块 API、ArkUI 组件行为,或构建/测试失败分诊需核对 API 权威签名、权限、错误码 → `d2h-cloud-knowledge`(先查 `.migration/knowledge/kb_cache.json`,结论回写缓存并在 spec/报告引用文档 uri)。
113
115
  7. 小步运行构建和应用验证,按以下分组规则建立与使用测试:
114
116
 
115
117
  **测试政策**
package/agents/d2h.md CHANGED
@@ -40,6 +40,7 @@ description: Android 应用到 HarmonyOS 的 AI 驱动迁移主 Agent;按迁
40
40
  | 转换 Android 图片、XML drawable 和自适应图标 | `android2hmos_resources_convert` |
41
41
  | 修复 HarmonyOS 图片或图标尺寸、比例失真 | `arkts-icon-sizing` |
42
42
  | 诊断 ArkTS 构建、运行时、UI 自动化或迁移验证故障 | `harmonyos-migration-playbook` |
43
+ | 实现涉及不确定的 @kit.* Kit / @ohos.* 模块 API、ArkUI 组件行为或平台能力(相机/定位/文件/通知/蓝牙/分享/Web/后台任务等),构建/测试失败需核对 API 签名、权限、错误码,或核验已选型 ohpm 库用法 | `d2h-cloud-knowledge` |
43
44
  | 迁移完整结束或本次运行明确中止后复盘流程 | `reviewing-migration-process` |
44
45
  | 首次安装、工具缺失、版本/能力不符或主动升级 | `managing-d2h-toolchain` |
45
46
 
@@ -47,6 +48,8 @@ description: Android 应用到 HarmonyOS 的 AI 驱动迁移主 Agent;按迁
47
48
 
48
49
  本流程的测试执行统一由 `d2h-hypium-forge` 负责,`batch_report.md` 记录其实际结果和证据路径,主 Agent 对照 Spec 和设备行为决定验收。
49
50
 
51
+ 加载 `d2h-cloud-knowledge` 时先查 `.migration/knowledge/kb_cache.json` 缓存,命中不重查;结论以文档 `title`/`uri` 引用回页面 Spec 与 `batch_report.md`。本地文档、`harmonyos-migration-playbook` 已验证经验或其他专项 Skill 能解决时不加载该 Skill。
52
+
50
53
  ## 迁移流程
51
54
 
52
55
  ### 一、启动或恢复
@@ -20,21 +20,38 @@ node scripts/check-environment.mjs --output-dir ~/reports/hmos-environment
20
20
 
21
21
  ## 当前检查项
22
22
 
23
+ 检查结果分为运行环境、鸿蒙工具链和迁移场景依赖三组。JSON 中保留原有检查 ID,并为每项增加 `category`;DevEco 项还记录安装类型、安装根目录和识别来源。
24
+
25
+ ### 运行环境
26
+
23
27
  | 项目 | 条件 |
24
28
  | --- | --- |
25
- | Node.js | 当前运行时为 24.x |
29
+ | Node.js | 当前运行时满足配置的版本范围 |
26
30
  | Git | git --version 成功 |
27
31
  | npm | npm --version 成功 |
28
- | Java/JDK | java -version 成功,不固定 JDK 主版本 |
29
32
  | Python | 找到 Python 3.10+,优先 PYTHON_BINARY |
30
- | AppGraph | appgraph --version 成功且匹配 D2H 锁定的 0.0.11 |
31
- | droid2hmos | droid2hmos --version 成功且匹配 D2H 锁定的 0.0.12 |
32
- | DevEco 工具链目录 | 安装目录存在 |
33
+
34
+ ### 鸿蒙工具链
35
+
36
+ DevEco Studio 或 HarmonyOS Command Line Tools 任一存在,即认为 DevEco 父项已安装。报告会区分 `studio`、`command-line-tools`、`studio-and-command-line-tools` 和 `missing`,并继续显示可用子工具的版本。
37
+
38
+ | 项目 | 条件 |
39
+ | --- | --- |
40
+ | DevEco 工具链 | DevEco Studio 或 HarmonyOS Command Line Tools 安装目录存在 |
41
+ | Java/JDK | 优先 JAVA_HOME 和 PATH,再检查 DevEco 内置 JBR/JDK;java -version 成功 |
33
42
  | Hvigor | 版本命令成功 |
34
43
  | ohpm | 版本命令成功 |
35
- | DevEco CLI | devecocli --version 成功 |
36
- | 开发者账号 | DevEco CLI 登录状态成功,报告不保留账号输出 |
37
44
  | HarmonyOS SDK 目录 | 辅助诊断,不作为必需门禁 |
45
+ | DevEco CLI | devecocli --version 成功 |
46
+ | 开发者账号 | DevEco CLI 登录状态成功;CLI 缺失时跳过,报告不保留账号输出 |
47
+
48
+ ### 迁移场景依赖
49
+
50
+ | 项目 | 条件 |
51
+ | --- | --- |
52
+ | AppGraph | appgraph --version 成功且匹配 D2H 锁定的 0.0.11 |
53
+ | droid2hmos | droid2hmos --version 成功且匹配 D2H 锁定的 0.0.12 |
54
+ | Android 源码根目录 | 传入 --android-project 时包含 Gradle 配置标记 |
38
55
 
39
56
  按需检查:指定插件生产脚本导入 json5 时检查相同 Python 的 json5 模块;传入 --android-project 时检查源码 Gradle 配置标记;传入 --harmony-project 时枚举工程可用 SDK 版本。
40
57
 
@@ -65,11 +82,11 @@ npx --yes @harmonyos-arkts/d2h@0.1.5 --check-env --requirements ./environment-re
65
82
 
66
83
  不再提供 --host、--host-binary、--skip-devices 参数。
67
84
 
68
- 工具环境变量:DEVECO_STUDIO_HOME、DEVECO_HOME、DROID2HMOS_HVIGOR、DROID2HMOS_OHPM、DEVECO_CLI_BINARY、PYTHON_BINARY、HARMONYOS_SDK_HOME、OHOS_SDK_HOME、DEVECO_SDK_HOME。macOS 包含 DevEco 默认 Contents 目录和常见 Python 安装路径。
85
+ 工具环境变量:DEVECO_STUDIO_HOME、DEVECO_HOME、JAVA_HOME、DROID2HMOS_HVIGOR、DROID2HMOS_OHPM、DEVECO_CLI_BINARY、PYTHON_BINARY、HARMONYOS_SDK_HOME、OHOS_SDK_HOME、DEVECO_SDK_HOME。macOS 包含 DevEco 默认 Contents 目录和常见 Python 安装路径。
69
86
 
70
87
  ## 输出
71
88
 
72
- 命令行先显示总体结果,然后逐项显示 `[通过]`、`[未通过]`、`[提醒]` 或 `[未检查]` 及版本/原因。未通过、提醒和未检查项下方直接显示修复指导,最后显示报告路径。完整明细、证据和修复指导保存在 environment.md/environment.json,重复执行覆盖。脚本只提供指导,不自动安装或修改环境。
89
+ 命令行先显示总体结果,再按“运行环境”“鸿蒙工具链”“迁移场景依赖”分组显示 `[通过]`、`[未通过]`、`[提醒]` 或 `[未检查]` 及版本/原因。未通过、提醒和未检查项下方直接显示修复指导,最后显示报告路径。完整明细、证据、分类、DevEco 安装信息和修复指导保存在 environment.md/environment.json,重复执行覆盖。脚本只提供指导,不自动安装或修改环境。
73
90
 
74
91
  退出码:0=必需检查通过;1=必需失败或执行错误;2=必需项未检查或参数错误。SDK 辅助诊断 warning 不阻断通过。
75
92
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harmonyos-arkts/d2h",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "Install Android-to-HarmonyOS migration agents and skills into OpenCode or DevEco Code",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,6 +8,18 @@ import { parseArgs } from 'node:util';
8
8
 
9
9
  const DEFAULT_REQUIREMENTS = fileURLToPath(new URL('../skills/managing-d2h-toolchain/environment-requirements.json', import.meta.url));
10
10
  const VERSION_TOOLS = ['node', 'python', 'git', 'npm', 'java', 'python-json5', 'appgraph', 'droid2hmos', 'hvigor', 'ohpm', 'devecocli'];
11
+ const CATEGORY_LABELS = {
12
+ runtime: '运行环境',
13
+ 'harmony-toolchain': '鸿蒙工具链',
14
+ 'migration-capabilities': '迁移场景依赖',
15
+ };
16
+ const CHECK_CATEGORIES = {
17
+ node: 'runtime', npm: 'runtime', git: 'runtime', python: 'runtime', 'python-json5': 'runtime',
18
+ deveco: 'harmony-toolchain', java: 'harmony-toolchain', hvigor: 'harmony-toolchain', ohpm: 'harmony-toolchain',
19
+ 'harmony-sdk-location': 'harmony-toolchain', 'harmony-sdk-compatibility': 'harmony-toolchain',
20
+ devecocli: 'harmony-toolchain', 'devecocli-auth': 'harmony-toolchain',
21
+ appgraph: 'migration-capabilities', droid2hmos: 'migration-capabilities', 'android-source-root': 'migration-capabilities',
22
+ };
11
23
 
12
24
  function versionParts(value) {
13
25
  if (typeof value !== 'string' || !/^\d+(?:\.\d+){0,3}$/.test(value)) throw new Error(`无效版本:${value}`);
@@ -169,9 +181,27 @@ export function pythonJson5Required(source) {
169
181
  }
170
182
 
171
183
  export function parseDevEcoJson(output) {
172
- // DevEco CLI 1.3.3 appends its updater notice to stdout even with --format json.
173
- // Strip only that known trailing line; unrelated malformed output still fails.
174
- return JSON.parse(output.replace(/\r?\n\s*New version [\w.+-]+ available, run `devecocli update` to upgrade\s*$/, '').trim());
184
+ // DevEco CLI 会在 --format json 输出前后追加非 JSON 文本(如升级提示
185
+ // "New version 1.3.5 available, run `devecocli update` to upgrade",措辞随版本变化)。
186
+ // 整体解析失败时回退提取最外层 JSON 值再解析;完全不含 JSON 才失败。
187
+ // 与 droid2hmos src/commands/deveco-cli.ts parseJson 保持同一策略。
188
+ const trimmed = String(output).trim();
189
+ try {
190
+ return JSON.parse(trimmed);
191
+ } catch (error) {
192
+ const start = trimmed.search(/[[{]/);
193
+ if (start >= 0) {
194
+ const close = trimmed[start] === '[' ? ']' : '}';
195
+ const end = trimmed.lastIndexOf(close);
196
+ if (end > start) {
197
+ try {
198
+ return JSON.parse(trimmed.slice(start, end + 1));
199
+ } catch { /* fall through */
200
+ }
201
+ }
202
+ }
203
+ throw error;
204
+ }
175
205
  }
176
206
 
177
207
  export async function collect(options, { runner = probe, env = process.env, platform = process.platform, home = os.homedir(), directoryFinder = firstDirectory, nodeVersion = process.version } = {}) {
@@ -179,24 +209,27 @@ export async function collect(options, { runner = probe, env = process.env, plat
179
209
  const requirements = loadRequirements(requirementsFile);
180
210
  const ruleFor = id => requirements.tools[id];
181
211
  const checks = [];
182
- const add = (id, required, passed, summary, remediation = '', evidence = [], skipped = false) => {
183
- checks.push({ id, required, status: skipped ? 'skipped' : passed ? 'passed' : required ? 'failed' : 'warning', summary,
184
- remediation: passed ? '' : remediation, evidence });
212
+ const add = (id, required, passed, summary, remediation = '', evidence = [], skipped = false, details = {}) => {
213
+ checks.push({ id, category: CHECK_CATEGORIES[id] || 'migration-capabilities', required,
214
+ status: skipped ? 'skipped' : passed ? 'passed' : required ? 'failed' : 'warning', summary,
215
+ remediation: passed ? '' : remediation, evidence, ...details });
185
216
  };
186
217
  const tool = async (id, executable, params, remediation, predicate = () => true) => {
187
218
  const result = await runner(executable, params, options.timeout);
188
219
  const rule = ruleFor(id);
189
220
  const passed = result.ok && predicate(result.output) && meetsVersionRule(result.output, rule, id);
190
- const summary = result.output.split(/\r?\n/)[0] || result.error || '命令未成功';
221
+ const missing = /(?:ENOENT|not found|无法找到|找不到)/i.test(`${result.error || ''}\n${result.output || ''}`);
222
+ const summary = missing ? `未找到 ${id}` : result.output.split(/\r?\n/)[0] || result.error || '命令未成功';
223
+ const version = result.output.match(/\d+\.\d+(?:\.\d+){0,2}/)?.[0];
191
224
  add(id, true, passed, `${summary}${rule ? `(要求 ${constraintLabel(rule)})` : ''}`,
192
- `${remediation}${rule ? ` 版本须满足 ${constraintLabel(rule)}。` : ''}`, [result]);
225
+ `${remediation}${rule ? ` 版本须满足 ${constraintLabel(rule)}。` : ''}`, [result], false,
226
+ { executable, ...(version ? { version } : {}) });
193
227
  return result;
194
228
  };
195
229
  add('node', true, meetsVersionRule(nodeVersion, ruleFor('node')), `${nodeVersion} (${process.execPath})(要求 ${constraintLabel(ruleFor('node'))})`,
196
230
  `请安装满足 ${constraintLabel(ruleFor('node'))} 的 Node.js,然后重新运行脚本。`);
197
231
  await tool('git', 'git', ['--version'], '安装 Git 并将 git 加入 PATH。');
198
232
  await tool('npm', 'npm', ['--version'], '使用 Node.js 自带的 npm,检查 PATH。');
199
- await tool('java', 'java', ['-version'], '安装项目 Gradle/DevEco 所需 JDK,设置 JAVA_HOME 和 PATH;本检查不限定 JDK 主版本。');
200
233
  // Python is a migration dependency, no longer a prerequisite to run this doctor.
201
234
  const candidates = [...new Set([env.PYTHON_BINARY, platform === 'win32' ? 'python' : 'python3', 'python',
202
235
  platform === 'darwin' ? '/opt/homebrew/bin/python3' : null, platform === 'darwin' ? '/usr/local/bin/python3' : null,
@@ -221,26 +254,66 @@ export async function collect(options, { runner = probe, env = process.env, plat
221
254
  for (const name of ['appgraph', 'droid2hmos']) {
222
255
  await tool(name, name, ['--version'], '运行 managing-d2h-toolchain Skill 中的 node scripts/manage-toolchain.mjs install 安装精确版本;本地 npm link 需由开发者显式管理。');
223
256
  }
224
- const deveco = directoryFinder([env.DEVECO_STUDIO_HOME, env.DEVECO_HOME,
257
+ const studio = directoryFinder([env.DEVECO_STUDIO_HOME,
225
258
  platform === 'darwin' ? '/Applications/DevEco-Studio.app/Contents' : null,
226
259
  ...(platform === 'win32' ? ['C:/Program Files/Huawei/DevEco Studio', 'D:/Program Files/Huawei/DevEco Studio'] : [])]);
227
- add('deveco', true, Boolean(deveco), deveco || '未找到 DevEco Studio', '安装 DevEco Studio,设置 DEVECO_STUDIO_HOME 为安装根目录(macOS 为 Contents)。');
228
- for (const [id, variable, relative, fallback] of [
229
- ['hvigor', 'DROID2HMOS_HVIGOR', 'tools/hvigor/bin/hvigorw.js', 'hvigorw'],
230
- ['ohpm', 'DROID2HMOS_OHPM', 'tools/ohpm/bin/pm-cli.js', 'ohpm'],
231
- ]) {
232
- const bundled = deveco && path.join(deveco, relative);
233
- await tool(id, env[variable] || (isFile(bundled) ? bundled : fallback), ['--version'], `安装 DevEco 配套工具;设置 ${variable} 为可执行文件或 JS 入口路径。`);
260
+ const commandLineTools = directoryFinder([env.DEVECO_HOME,
261
+ ...(platform === 'win32' ? ['C:/Huawei/command-line-tools', 'D:/HarmonyOS/command-line-tools'] : [])]);
262
+ const deveco = studio || commandLineTools;
263
+ const installationType = studio && commandLineTools ? 'studio-and-command-line-tools' : studio ? 'studio' : commandLineTools ? 'command-line-tools' : 'missing';
264
+ const installationLabel = { studio: 'DevEco Studio', 'command-line-tools': 'HarmonyOS Command Line Tools',
265
+ 'studio-and-command-line-tools': 'DevEco Studio 和 HarmonyOS Command Line Tools' }[installationType];
266
+ add('deveco', true, Boolean(deveco), deveco ? `${installationLabel}:${[studio, commandLineTools].filter(Boolean).join(';')}` : '未找到 DevEco Studio 或 HarmonyOS Command Line Tools',
267
+ '安装 DevEco Studio 或 HarmonyOS Command Line Tools,并设置 DEVECO_STUDIO_HOME 或 DEVECO_HOME(macOS 的 DevEco Studio 根目录为 Contents)。',
268
+ [], false, { installation: { type: installationType, studioRoot: studio, commandLineToolsRoot: commandLineTools,
269
+ detectedBy: studio === env.DEVECO_STUDIO_HOME ? 'DEVECO_STUDIO_HOME' : commandLineTools === env.DEVECO_HOME ? 'DEVECO_HOME' : deveco ? 'default-location' : null } });
270
+ if (deveco) {
271
+ const javaCandidates = [...new Set([
272
+ env.JAVA_HOME && path.join(env.JAVA_HOME, 'bin', platform === 'win32' ? 'java.exe' : 'java'),
273
+ 'java',
274
+ ...[studio, commandLineTools].filter(Boolean).flatMap(root => [
275
+ path.join(root, 'jbr', 'bin', platform === 'win32' ? 'java.exe' : 'java'),
276
+ path.join(root, 'jbr', 'Contents', 'Home', 'bin', 'java'),
277
+ ]).filter(isFile),
278
+ ].filter(Boolean))];
279
+ let javaResult = null;
280
+ let javaExecutable = null;
281
+ for (const candidate of javaCandidates) {
282
+ const result = await runner(candidate, ['-version'], options.timeout);
283
+ if (result.ok && meetsVersionRule(result.output, ruleFor('java'), 'java')) { javaResult = result; javaExecutable = candidate; break; }
284
+ javaResult ??= result;
285
+ }
286
+ const javaRule = ruleFor('java');
287
+ const javaVersion = javaResult?.output.match(/\d+\.\d+(?:\.\d+){0,2}/)?.[0];
288
+ add('java', true, Boolean(javaExecutable), javaExecutable ? `${javaResult.output.split(/\r?\n/)[0]} (${javaExecutable})` : '未找到可用的 Java/JDK',
289
+ `配置 JAVA_HOME,或检查 DevEco 内置 JBR/JDK${javaRule ? `;版本须满足 ${constraintLabel(javaRule)}` : ''}。`, javaResult ? [javaResult] : [], false,
290
+ { relatedTo: ['deveco', 'hvigor'], ...(javaExecutable ? { executable: javaExecutable } : {}), ...(javaVersion ? { version: javaVersion } : {}) });
291
+ for (const [id, variable, relative, fallback] of [
292
+ ['hvigor', 'DROID2HMOS_HVIGOR', 'tools/hvigor/bin/hvigorw.js', 'hvigorw'],
293
+ ['ohpm', 'DROID2HMOS_OHPM', 'tools/ohpm/bin/pm-cli.js', 'ohpm'],
294
+ ]) {
295
+ const bundled = [studio, commandLineTools].filter(Boolean).map(root => path.join(root, relative)).find(isFile);
296
+ await tool(id, env[variable] || bundled || fallback, ['--version'], `安装 DevEco 配套工具;设置 ${variable} 为可执行文件或 JS 入口路径。`);
297
+ }
298
+ const sdk = directoryFinder([options.sdkPath, env.HARMONYOS_SDK_HOME, env.OHOS_SDK_HOME, env.DEVECO_SDK_HOME, path.join(deveco, 'sdk')]);
299
+ add('harmony-sdk-location', false, Boolean(sdk), sdk || '未发现声明 SDK 根目录', '设置 HARMONYOS_SDK_HOME 或 --sdk-path;实际声明有效性由 droid2hmos preflight 检查。');
300
+ } else {
301
+ for (const id of ['java', 'hvigor', 'ohpm', 'harmony-sdk-location']) {
302
+ add(id, id !== 'harmony-sdk-location', false, '等待鸿蒙基础工具链配置', '', [], true,
303
+ id === 'java' ? { relatedTo: ['deveco', 'hvigor'] } : {});
304
+ }
234
305
  }
235
- const sdk = directoryFinder([options.sdkPath, env.HARMONYOS_SDK_HOME, env.OHOS_SDK_HOME, env.DEVECO_SDK_HOME, deveco && path.join(deveco, 'sdk')]);
236
- add('harmony-sdk-location', false, Boolean(sdk), sdk || '未发现声明 SDK 根目录', '设置 HARMONYOS_SDK_HOME 或 --sdk-path;实际声明有效性由 droid2hmos preflight 检查。');
237
306
  const cli = env.DEVECO_CLI_BINARY || 'devecocli';
238
- await tool('devecocli', cli, ['--version'], '安装 npm install -g @deveco/deveco-cli。');
239
- const auth = await runner(cli, ['auth', 'status'], options.timeout);
240
- const loggedIn = auth.ok && /Current user:\s*[^\r\n]+/i.test(auth.output) && !/Not logged in/i.test(auth.output);
241
- add('devecocli-auth', true, loggedIn, loggedIn ? '已登录' : '未登录或无法确认登录状态',
242
- '运行 droid2hmos auth login <android-project>,在浏览器完成授权;不要并发登录。',
243
- [{ command: auth.command, exitCode: auth.exitCode, error: auth.error }]);
307
+ const cliProbe = await tool('devecocli', cli, ['--version'], '安装 npm install -g @deveco/deveco-cli。');
308
+ if (cliProbe.ok) {
309
+ const auth = await runner(cli, ['auth', 'status'], options.timeout);
310
+ const loggedIn = auth.ok && /Current user:\s*[^\r\n]+/i.test(auth.output) && !/Not logged in/i.test(auth.output);
311
+ add('devecocli-auth', true, loggedIn, loggedIn ? '已登录' : '未登录或无法确认登录状态',
312
+ '运行 droid2hmos auth login <android-project>,在浏览器完成授权;不要并发登录。',
313
+ [{ command: auth.command, exitCode: auth.exitCode, error: auth.error }]);
314
+ } else {
315
+ add('devecocli-auth', true, false, '未检查:devecocli 不可用', '先安装 devecocli,再重新运行环境检查。', [], true);
316
+ }
244
317
  if (options.harmonyProject) {
245
318
  const project = path.resolve(options.harmonyProject);
246
319
  const versions = await runner(cli, ['check', 'compat', 'versions', '--format', 'json'], options.timeout, { cwd: project });
@@ -264,9 +337,11 @@ export async function collect(options, { runner = probe, env = process.env, plat
264
337
 
265
338
  export function markdown(report) {
266
339
  const escape = (value) => String(value).replaceAll('|', '\\|').replaceAll('\r', '').replaceAll('\n', '<br>');
267
- return ['# Android → HarmonyOS 环境检查', '', `结果:**${report.result}**`, '', report.scope, '', `版本配置:${report.requirementsFile || ''}`, '',
340
+ const sections = Object.entries(CATEGORY_LABELS).flatMap(([category, label]) => ['', `## ${label}`, '',
268
341
  '| 检查 | 必需 | 状态 | 说明 |', '| --- | --- | --- | --- |',
269
- ...report.checks.map((c) => `| ${c.id} | ${c.required ? '是' : '否'} | ${c.status} | ${escape(c.summary)} |`),
342
+ ...report.checks.filter(c => c.category === category).map((c) => `| ${c.id} | ${c.required ? '是' : '否'} | ${c.status} | ${escape(c.summary)} |`)]);
343
+ return ['# Android → HarmonyOS 环境检查', '', `结果:**${report.result}**`, '', report.scope, '', `版本配置:${report.requirementsFile || ''}`,
344
+ ...sections,
270
345
  '', '## 修复指导', '', ...report.checks.filter((c) => c.remediation).map((c) => `- **${c.id}**:${c.remediation}`), '',
271
346
  '修复后重新运行本脚本。主机检查通过后,运行 `droid2hmos preflight <android-project>` 验证真实项目门禁;该命令可能初始化工程并执行构建。', ''].join('\n');
272
347
  }
@@ -305,10 +380,13 @@ export async function main(argv = process.argv.slice(2)) {
305
380
  const resultLabel = { passed: '通过', failed: '未通过', incomplete: '未完成' }[report.result];
306
381
  console.log(`环境检查:${resultLabel}(通过 ${passed},失败 ${failures.length}${warnings ? `,提醒 ${warnings}` : ''}${skipped ? `,未检查 ${skipped}` : ''})`);
307
382
  const statusLabels = { passed: '通过', failed: '未通过', warning: '提醒', skipped: '未检查' };
308
- for (const check of report.checks) {
309
- console.log(`[${statusLabels[check.status] || check.status}] ${check.id}:${check.summary}`);
310
- if (check.status !== 'passed' && check.remediation) {
311
- console.log(` 修复:${check.remediation}`);
383
+ for (const [category, label] of Object.entries(CATEGORY_LABELS)) {
384
+ console.log(`\n${label}`);
385
+ for (const check of report.checks.filter(item => item.category === category)) {
386
+ console.log(`[${statusLabels[check.status] || check.status}] ${check.id}:${check.summary}`);
387
+ if (check.status !== 'passed' && check.remediation) {
388
+ console.log(` 修复:${check.remediation}`);
389
+ }
312
390
  }
313
391
  }
314
392
  console.log(`详细报告:${path.join(options.outputDir, 'environment.md')}`);
@@ -0,0 +1,129 @@
1
+ ---
2
+ name: d2h-cloud-knowledge
3
+ description: >-
4
+ D2h 迁移过程中的华为开发者云侧知识库查询技能。在 Android 转 HarmonyOS 迁移的各环节,
5
+ 根据当前正在实现的功能(平台能力、HarmonyOS API(@kit.* Kit 导入或 @ohos.* 模块)、ArkUI 组件行为、
6
+ 三方库用法)主动查询权威依据,
7
+ 提升转换质量与效率。适用于:页面 spec 生成时的能力可行性核对、页面实现前的 API 签名/参数/权限/
8
+ 错误码核对、构建与测试失败分诊中涉及的 API 行为确认、三方库(ohpm)用法与注意事项核验、
9
+ 系统能力支持范围确认。查询词从当前 Android 功能语义(类名/能力/行为)构造。
10
+ 无需注册 MCP 服务或初始化握手;先查本地缓存,未命中先搜索摘要,信息不足再取全文;
11
+ 结论以文档 title/uri 引用回 spec 与迁移报告。本地文档或其他专项 skill 能解决时不使用本技能。
12
+ version: 1.3.0
13
+ ---
14
+ # 云侧知识库查询(HarmonyOS Developer Knowledge)
15
+
16
+ 在 D2h 迁移过程中,当当前实现的功能涉及不确定的 HarmonyOS API、平台能力或三方库用法时,通过 Shell 用 Node.js `fetch` 直接发送 HTTP POST 查询华为开发者知识库,**无需注册 MCP 服务或执行初始化握手**。
17
+
18
+ ## 目标
19
+
20
+ 1. 把云侧查询绑定到**当前正在迁移的功能**:从 Android 源码证据(类名、权限、行为)构造查询词,获取权威实现依据,而不是凭印象写 API。
21
+ 2. 控制成本:先查本地缓存(`kb_cache.json`),未命中先搜摘要(`content`),确实相关再取全文;单功能点预算 ≤2 次 search、≤3 篇全文。
22
+ 3. 结论可追溯:以文档 `title`/`uri` 引用回页面 spec、迁移报告或失败分诊记录;同一主题查询一次、跨页面复用。
23
+
24
+ ## 迁移流程中的触发时机
25
+
26
+ 在 D2h 主流程的以下环节,命中即按需加载本技能(同一功能不重复查询):
27
+
28
+ | 环节 | 触发条件 | 查什么 |
29
+ |------|----------|--------|
30
+ | 页面 spec 生成(`d2h-page-spec-generate`) | spec 标记了平台能力项(相机/定位/文件/通知/蓝牙/分享/Web/后台任务等) | 该能力对应 Kit/API 是否可用、是否受系统能力限制,写入 spec 依据 |
31
+ | 页面实现前(`d2h-page-develop` 步骤 1-2) | 功能清单(F-xxx)中存在平台能力或不确定的 @kit.* Kit / @ohos.* 模块 API | API 签名、参数/返回值、所需权限、错误码、推荐用法与指南 |
32
+ | 页面实现中 | 不确定 ArkUI 组件/属性行为(如 Tabs、Swiper、手势、懒加载) | 开发指南与 FAQ 中的权威行为描述 |
33
+ | 构建失败分诊 | 编译错误指向 @kit.* / @ohos.* API 签名、类型、import 模块名 | 核对该 API 的真实签名与起始版本,避免臆造签名反复试错 |
34
+ | 测试失败分诊(`d2h-hypium-forge`) | 运行时行为与预期不符、错误码/权限弹窗原因不明 | API 行为约束、错误码含义、权限申请时序 |
35
+ | 三方库替换后核验(`native-library-substitution` 已选型) | 已确定替代库,需要确认用法 | 所选 ohpm 库的开发指南、已知限制、实践样例 |
36
+
37
+ 优先级与边界(先走更合适的通道,本技能兜底权威依据):
38
+
39
+ - 本地工程文档能确定答案 → `deveco-cli docs`,不发起云侧查询。
40
+ - 已验证的迁移失败教训 → `harmonyos-migration-playbook` 命中即用,不重查云侧。
41
+ - 三方库**选型**(Android 库 → 鸿蒙库映射)→ `native-library-substitution`;本技能只负责所选库的**用法核验**。
42
+
43
+ ## 从当前功能构造查询词
44
+
45
+ 查询词 = **Android 侧证据 + 期望行为 + HarmonyOS 术语**,两步走:
46
+
47
+ 1. 探索(尚不知对应 API):用「开发场景 + 想实现的行为」,可带 Android 类名提高召回,例如 `选择文件并读取内容 ContentResolver`、`后台下载续传 WorkManager`。
48
+ 2. 精查(已知 Kit/模块/API/权限/错误码):把它直接放进 query;Kit 名与 @ohos 模块名均可作精查词,官方文档两种写法都会出现,组合使用召回更好,例如 `FilePicker DocumentViewPicker`、`CameraKit @ohos.multimedia.camera photoAccessHelper 拍照`。
49
+
50
+ 常用 Android → HarmonyOS 术语对照(用于构造查询词和对照检索结果,**不作为实现结论,结论以检索到的文档为准**):
51
+
52
+ | Android 侧 | HarmonyOS 检索词 |
53
+ |------------|------------------|
54
+ | Activity / Fragment | UIAbility / Navigation |
55
+ | SharedPreferences | @ohos.data.preferences |
56
+ | ContentProvider / ContentResolver | DataShare / FilePicker |
57
+ | CameraX / Camera2 | CameraKit @ohos.multimedia.camera |
58
+ | FusedLocationProvider | LocationKit geoLocationManager |
59
+ | Notification / NotificationChannel | NotificationKit @ohos.notification |
60
+ | WebView | Web 组件 @ohos.web.webview |
61
+ | Bitmap / Canvas | PixelMap / Canvas @ohos.graphics.drawing |
62
+ | OkHttp / Retrofit | @ohos.net.http / Remote Communication Kit rcp |
63
+ | Glide / Coil | ImageKnife(ohpm) |
64
+ | WorkManager / JobScheduler | BackgroundTasksKit 后台任务 |
65
+ | RecyclerView / DiffUtil | List / Grid / LazyForEach |
66
+
67
+ ## 知识缓存(跨页面复用)
68
+
69
+ 维护 `.migration/knowledge/kb_cache.json`(工作缓存,非受管证据;建议随页面 ledger 一并提交以便跨会话复用)。顶层为 `topics` 对象,按功能 slug 作 key(如 `file-picker`、`camera-capture`),每个条目包含字段:
70
+
71
+ - `query`:产生该结论的查询词;`date`:查询日期。
72
+ - `conclusion`:结论摘要(API/Kit、支持范围、权限、关键约束)。
73
+ - `docs[]`:引用文档列表,每项含 `parent`、`title`、`uri`。
74
+ - `usedBy[]`:使用该结论的页面/任务 ID 列表。
75
+
76
+ 规则:
77
+
78
+ - 实现前先按主题查缓存(key 用功能 slug,如 `file-picker`、`camera-capture`);命中直接复用结论与文档引用,不重查。
79
+ - 未命中才发起云侧查询;拿到结论后立即写入缓存,并在 `usedBy` 登记当前页面/任务。
80
+ - 缓存中的结论仅是摘要;实现时仍以 `docs` 里的全文为准,摘要与全文冲突时更新缓存。
81
+
82
+ ## 请求基础
83
+
84
+ - 请求地址:`https://connect-api.cloud.huawei.com/api/developerknowledge/mcp`(本技能唯一域名)
85
+ - 每次请求超时:**30 秒**。
86
+ - 固定请求头:`x-gray-flag: 1`、`Content-Type: application/json`、`Accept: application/json, text/event-stream`、`sceneName: app2harmony`(D2h 应用转换场景专用标识,脚本内已内置)。
87
+
88
+ ## 通用请求脚本
89
+
90
+ 脚本已随本技能打包为 `skills/d2h-cloud-knowledge/kb_query.mjs`(OpenCode 安装后位于 `~/.config/opencode/skills/d2h-cloud-knowledge/kb_query.mjs`)。首次使用时复制到 `<MIGRATION_ROOT>/.migration/knowledge/` 后一直复用,不要手抄重写,也不要绕过脚本手写 curl/JSON-RPC:
91
+
92
+ - `node kb_query.mjs search "<query>"`:摘要检索(searchDocuments),输出每条结果的 `parent`(文档标识)与 `content`(摘要)。
93
+ - `node kb_query.mjs doc "<parent1,parent2>"`:读取全文(getDocumentsById),输出全文条目的 `name`/`title`/`uri`/`content`,多个 `parent` 用英文逗号分隔。
94
+ - 脚本固定 30 秒超时,自动完成请求、SSE 处理、JSON-RPC 解析、业务码校验与结构化输出;失败时输出错误摘要并以非零码退出(判定标准见「失败判定」)。
95
+
96
+ ## 搜索文档 searchDocuments
97
+
98
+ 通过脚本 `search` 模式调用,query 按「从当前功能构造查询词」组织。单个功能点最多 2 次 search(探索 1 次 + 精查 1 次);两次仍无相关结果视为「知识库未覆盖」,改用本地文档或指南链接,不要继续换词重试。
99
+
100
+ ## 读取全文 getDocumentsById
101
+
102
+ 根据摘要筛选真正相关的结果,按 `parent` 去重后通过脚本 `doc` 模式获取全文(单次 ≤3 篇)。全文条目包含 `name`、`title`、`uri`、`content`,返回顺序可能与传入顺序不同,**必须按 `name` 识别对应文档,不能依赖数组下标**。
103
+
104
+ ## 解析响应与回流
105
+
106
+ 响应解析(SSE 提取、JSON-RPC 解包、业务码校验)已封装在 `kb_query.mjs` 中,脚本输出即结构化结果,直接使用即可,无需再手动解析。拿到结果后按以下方式回流(让查询真正改善转换效果):
107
+
108
+ - 实现依据:按文档核对 API 签名、权限、版本后落地 ArkTS 代码。
109
+ - spec / 迁移报告:在依据、差异(differences)或发现(discoveries)处引用文档 `title` + `uri`,让结论可复核。
110
+ - 失败分诊:在分诊记录中注明「依据 <uri>」,避免同一 API 问题反复查询。
111
+ - 更新 `kb_cache.json`(见「知识缓存」)。
112
+
113
+ ## 失败判定
114
+
115
+ 以下任一情况均视为调用失败,不得当作空结果继续:
116
+
117
+ - HTTP 状态非成功(非 2xx)。
118
+ - 外层 JSON-RPC 带顶层 `error` 字段。
119
+ - `result.isError: true`。
120
+ - 业务结果 `code` 非 `0` / `"0"`。
121
+
122
+ 失败时输出原始错误摘要与 HTTP 状态,检查 query / names 是否合法后最多重试一次;不要循环重试同一请求。知识库持续不可用时,退回本地文档通道并在分诊/报告记录「云侧知识库不可用」,不得阻塞页面主流程。
123
+
124
+ ## 注意事项
125
+
126
+ - 只查询**当前功能点**所需资料,不批量拉取无关文档。
127
+ - 全文请求按 `parent` 去重,避免重复请求同一文档;缓存命中时不发起请求。
128
+ - 响应中的 `content` 为 Markdown 正文,直接引用时保留关键代码与注意事项,不要臆造摘要之外的内容。
129
+ - 本技能是只读查询,不修改任何工程文件(`.migration/knowledge/` 下的查询脚本与缓存除外)。
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+ // 云侧知识库查询脚本(d2h-cloud-knowledge 技能配套)。
3
+ // 用法:
4
+ // node kb_query.mjs search "FilePicker DocumentViewPicker"
5
+ // node kb_query.mjs doc "alliance/document/cn/harmonyos-guides/select-user-file,alliance/document/cn/other"
6
+ // sceneName 固定为 app2harmony(D2h 应用转换场景)
7
+ const KB_URL = 'https://connect-api.cloud.huawei.com/api/developerknowledge/mcp';
8
+ const HEADERS = {
9
+ 'x-gray-flag': '1',
10
+ 'Content-Type': 'application/json',
11
+ 'Accept': 'application/json, text/event-stream',
12
+ 'sceneName': 'app2harmony',
13
+ };
14
+
15
+ async function callKb(name, args) {
16
+ const resp = await fetch(KB_URL, {
17
+ method: 'POST',
18
+ headers: HEADERS,
19
+ body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name, arguments: args } }),
20
+ signal: AbortSignal.timeout(30000),
21
+ });
22
+ if (!resp.ok) throw new Error(`HTTP ${resp.status} ${resp.statusText}`);
23
+ const raw = await resp.text();
24
+ // 若返回 SSE,提取 data: 内容;否则按 JSON 解析
25
+ const dataLines = raw.split('\n').map((l) => l.trim()).filter((l) => l.startsWith('data:'));
26
+ const payloads = dataLines.length ? dataLines.map((l) => JSON.parse(l.slice(5).trim())) : [JSON.parse(raw)];
27
+ const outer = payloads.find((p) => p && (p.result || p.error)) || payloads[0];
28
+ if (outer.error) throw new Error(`JSON-RPC error: ${JSON.stringify(outer.error)}`);
29
+ if (outer.result && outer.result.isError) throw new Error(`result.isError: ${JSON.stringify(outer.result)}`);
30
+ const text = (outer.result && outer.result.content || []).find((c) => c.type === 'text');
31
+ if (!text || !text.text) throw new Error('响应中缺少 type=text 内容');
32
+ const biz = JSON.parse(text.text);
33
+ if (biz.code !== 0 && String(biz.code) !== '0') throw new Error(`业务错误 code=${biz.code} message=${biz.message}`);
34
+ return biz.resultList || [];
35
+ }
36
+
37
+ const [mode, arg] = process.argv.slice(2);
38
+ (async () => {
39
+ if (mode === 'search') {
40
+ const list = await callKb('searchDocuments', { SearchDocumentsReq: { query: arg } });
41
+ for (const item of list) {
42
+ console.log(JSON.stringify({ parent: item.parent, content: item.content }, null, 2));
43
+ console.log('---');
44
+ }
45
+ } else if (mode === 'doc') {
46
+ const names = arg.split(',').map((s) => s.trim()).filter(Boolean);
47
+ const list = await callKb('getDocumentsById', { GetDocumentsByIdRequest: { names } });
48
+ for (const doc of list) {
49
+ console.log(JSON.stringify({ name: doc.name, title: doc.title, uri: doc.uri, content: doc.content }, null, 2));
50
+ console.log('---');
51
+ }
52
+ } else {
53
+ throw new Error('用法: node kb_query.mjs search "<query>" | doc "<parent1,parent2>"');
54
+ }
55
+ })().catch((e) => { console.error('调用失败:', e.message); process.exit(1); });
@@ -66,7 +66,7 @@ If independent page-worker execution is unavailable, return that the required mo
66
66
  - Implement all page-scoped UI, navigation, overlays, state transitions, persistence/data effects, error paths, resources, and required shared integration.
67
67
  - Use the Android source and observable behavior for functional truth. Use HarmonyOS-native architecture and components for the target implementation.
68
68
  - Give tested interactive elements stable, unique semantic locators. Do not add controls solely to make testing easier.
69
- - Load the applicable domain Skill for identity, i18n, drawable conversion, icon sizing, or third-party substitution when the page touches that domain.
69
+ - Load the applicable domain Skill for identity, i18n, drawable conversion, icon sizing, or third-party substitution when the page touches that domain. Before implementing platform capabilities or uncertain HarmonyOS APIs (`@kit.*` Kit imports or `@ohos.*` modules), and when build or test failures point to API signatures or behavior, load `d2h-cloud-knowledge`: check `.migration/knowledge/kb_cache.json` first, query only on cache miss, and record conclusions with doc `title`/`uri` back into the cache, the page spec evidence, and the migration report.
70
70
  - Keep shared changes limited to capabilities this page actually needs and record downstream consumers.
71
71
  - Do not leave stubs, fixed success values, swallowed errors, unreachable pages, placeholder controls, or TODO implementations.
72
72
 
@@ -92,6 +92,7 @@ Use schema `d2h-page-plan/v1`. Every `pages[]` item must contain:
92
92
  3. Use `page.json` navigation steps, selectors, guards, expected-arrival rules, required state, and depth to reconstruct the semantic arrival path. Source-derived reachability is planning evidence, not runtime confirmation.
93
93
  4. Translate selectors into semantic control names only when Facts, resources, or Android source support the mapping. Keep missing or ambiguous selectors explicitly unresolved.
94
94
  5. Verify every listed Android path exists under `android_root`. Verify every HarmonyOS path marked `existing` exists under `harmony_root`; otherwise mark it `planned`.
95
+ 6. When page features touch platform capabilities (camera, location, files, notifications, Bluetooth, share, Web, background tasks, etc.) or uncertain HarmonyOS APIs (`@kit.*` Kit imports or `@ohos.*` modules), load `d2h-cloud-knowledge` to confirm the corresponding Kit/API availability and system-capability limits before writing: check `.migration/knowledge/kb_cache.json` first, query only on cache miss, cite the doc `title`/`uri` in「未决项与平台差异」or the supporting feature item, and record the topic in the cache with this page as `usedBy`.
95
96
 
96
97
  ### 5. Write only `spec.md`
97
98