@harmonyos-arkts/d2h 0.1.16 → 0.1.18

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.18",
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 协议,不在此列。
package/README.md CHANGED
@@ -29,6 +29,8 @@ npx --yes @harmonyos-arkts/d2h@latest install --agent deveco --force
29
29
  npx --yes @harmonyos-arkts/d2h@latest install --agent vscode --force
30
30
  ```
31
31
 
32
+ 安装完成后会清除 `~/.harmonybot/opencode-bundle/vscode` 缓存。请重启 VS Code,让扩展重新解压资源并加载最新的 D2H 和指标插件配置;安装前无需先启动扩展。`--dry-run` 不会清除缓存。
33
+
32
34
  默认使用本地轻量指标插件 `opencode-metrics-plugin-lite`。需要远端指标能力时增加 `--remote`:
33
35
 
34
36
  ```bash
@@ -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
  ### 一、启动或恢复
@@ -85,6 +85,9 @@ function configFileFor(root, agent) {
85
85
  function updateAgentConfig(config, remote) {
86
86
  updateMetricsPlugin(config, remote);
87
87
  config.agent ??= {};
88
+ for (const name of ['general', 'explore']) {
89
+ config.agent[name] = { ...config.agent[name], permission: 'allow' };
90
+ }
88
91
  const previous = config.agent.d2h ?? {};
89
92
  config.agent.d2h = {
90
93
  ...previous,
@@ -110,7 +113,8 @@ export function inspectInstallation(configDir) {
110
113
  try {
111
114
  const config = parseJsonConfig(fs.readFileSync(configFile, 'utf8'));
112
115
  const d2h = config.agent?.d2h;
113
- if (!d2h || !['read', 'write', 'edit', 'bash'].every(tool => d2h.tools?.[tool] === true) ||
116
+ if (!['general', 'explore'].every(name => config.agent?.[name]?.permission === 'allow') ||
117
+ !d2h || !['read', 'write', 'edit', 'bash'].every(tool => d2h.tools?.[tool] === true) ||
114
118
  !['edit', 'bash', 'external_directory'].every(permission => d2h.permission?.[permission] === 'allow')) {
115
119
  changed.push(path.basename(configFile));
116
120
  }
@@ -76,6 +76,9 @@ export function updateD2HModeLabel(source) {
76
76
  function updateConfig(source, remote) {
77
77
  const value = JSON.parse(source);
78
78
  value.agent ??= {};
79
+ for (const name of ['general', 'explore']) {
80
+ value.agent[name] = { ...value.agent[name], permission: 'allow' };
81
+ }
79
82
  value.agent.d2h = { ...value.agent.d2h, description: modeLabel,
80
83
  tools: { ...value.agent.d2h?.tools, read: true, write: true, edit: true, bash: true },
81
84
  permission: { ...value.agent.d2h?.permission, read: 'allow', edit: 'allow',
@@ -118,10 +121,9 @@ function copyWithBackup(source, destination, backupRoot, root) {
118
121
  export function installVSCode({ sourceDir, extensionDir, bundleCacheRoot, remote = false, dryRun = false, log = console.log }) {
119
122
  const sourceRoot = fs.realpathSync(sourceDir);
120
123
  const root = validatedRoot(locateVSCodeExtension(extensionDir));
121
- const bundle = activeBundle(root, bundleCacheRoot);
124
+ const cacheRoot = path.resolve(bundleCacheRoot ?? path.join(os.homedir(), '.harmonybot', 'opencode-bundle', 'vscode'));
122
125
  const pluginRoot = path.join(root, 'resources', 'opencode', 'plugin', 'd2h');
123
126
  const configFile = path.join(root, 'resources', 'opencode', 'opencode.json');
124
- const bundleConfigFile = path.join(bundle, 'opencode.json');
125
127
  const distFiles = ['dist/extension.js', 'dist/webview/main.js'];
126
128
  const patches = [
127
129
  { file: configFile, content: updateConfig(fs.readFileSync(configFile, 'utf8'), remote) },
@@ -134,24 +136,17 @@ export function installVSCode({ sourceDir, extensionDir, bundleCacheRoot, remote
134
136
  { source: path.join(sourceRoot, 'opencode', 'vscode-d2h-plugin.mjs'), destination: path.join(pluginRoot, 'src', 'index.js') },
135
137
  { source: path.join(sourceRoot, 'opencode', 'vscode-d2h-package.json'), destination: path.join(pluginRoot, 'package.json') },
136
138
  ];
137
- const bundleCopies = copies.map(entry => ({ source: entry.source,
138
- destination: path.join(bundle, 'plugin', 'd2h', path.relative(pluginRoot, entry.destination)) }));
139
- const bundleConfig = updateConfig(fs.readFileSync(bundleConfigFile, 'utf8'));
140
139
  for (const entry of copies)
141
140
  if (!fs.existsSync(entry.source))
142
141
  throw new Error(`missing D2H source: ${entry.source}`);
143
142
  for (const target of [...copies.map(entry => entry.destination), ...patches.map(entry => entry.file)])
144
143
  assertSafeDestination(root, target);
145
- for (const target of [...bundleCopies.map(entry => entry.destination), bundleConfigFile])
146
- assertSafeDestination(bundle, target);
147
144
  if (dryRun) {
148
145
  for (const entry of copies)
149
146
  log(`would copy ${entry.source} -> ${entry.destination}`);
150
147
  for (const entry of patches)
151
148
  log(`would update ${entry.file}`);
152
- for (const entry of bundleCopies)
153
- log(`would copy ${entry.source} -> ${entry.destination}`);
154
- log(`would update ${bundleConfigFile}`);
149
+ log(`would remove VS Code bundle cache ${cacheRoot}`);
155
150
  return { extensionRoot: root, changed: false };
156
151
  }
157
152
  if (!/^name: d2h\r?$/m.test(fs.readFileSync(path.join(sourceRoot, 'agents', 'd2h.md'), 'utf8'))) {
@@ -174,26 +169,14 @@ export function installVSCode({ sourceDir, extensionDir, bundleCacheRoot, remote
174
169
  fs.renameSync(temporary, entry.file);
175
170
  changed = true;
176
171
  }
177
- const bundleBackupRoot = fs.mkdtempSync(path.join(bundle, '.d2h-backup-'));
178
- for (const entry of bundleCopies)
179
- changed = copyWithBackup(entry.source, entry.destination, bundleBackupRoot, bundle) || changed;
180
- if (fs.readFileSync(bundleConfigFile, 'utf8') !== bundleConfig) {
181
- const backup = path.join(bundleBackupRoot, 'opencode.json');
182
- fs.copyFileSync(bundleConfigFile, backup);
183
- const temporary = `${bundleConfigFile}.d2h-${process.pid}.tmp`;
184
- fs.writeFileSync(temporary, bundleConfig);
185
- fs.renameSync(temporary, bundleConfigFile);
186
- changed = true;
187
- }
188
- if (fs.readdirSync(bundleBackupRoot).length === 0)
189
- fs.rmdirSync(bundleBackupRoot);
190
172
  const tracked = [...copies.map(entry => entry.destination), ...patches.map(entry => entry.file)];
191
173
  const receipt = { schemaVersion: 1, packageVersion: JSON.parse(fs.readFileSync(path.join(sourceRoot, 'package.json'), 'utf8')).version,
192
174
  files: tracked.map(file => ({ path: path.relative(root, file).replaceAll('\\', '/'), digest: digest(file) })) };
193
175
  fs.writeFileSync(path.join(pluginRoot, '.d2h-installation.json'), JSON.stringify(receipt, null, 2) + '\n');
194
176
  if (!changed)
195
177
  fs.rmdirSync(backupRoot);
196
- log(`VS Code D2H ${changed ? 'installed' : 'unchanged'} at ${root}; active bundle: ${bundle}${changed ? `; backup: ${backupRoot}` : ''}`);
178
+ fs.rmSync(cacheRoot, { recursive: true, force: true });
179
+ log(`VS Code D2H ${changed ? 'installed' : 'unchanged'} at ${root}${changed ? `; backup: ${backupRoot}` : ''}; cleared bundle cache: ${cacheRoot}; restart VS Code to rebuild it`);
197
180
  return { extensionRoot: root, changed, backupRoot: changed ? backupRoot : undefined };
198
181
  }
199
182
  export function inspectVSCodeInstallation(extensionDir, bundleCacheRoot) {
@@ -208,6 +191,11 @@ export function inspectVSCodeInstallation(extensionDir, bundleCacheRoot) {
208
191
  const target = path.resolve(root, entry.path);
209
192
  return !inside(root, target) || !fs.existsSync(target) || digest(target) !== entry.digest;
210
193
  }).map(entry => entry.path);
194
+ const cacheRoot = bundleCacheRoot ?? path.join(os.homedir(), '.harmonybot', 'opencode-bundle', 'vscode');
195
+ if (!fs.existsSync(cacheRoot) && !changed.length) {
196
+ return { status: 'passed', packageVersion: receipt.packageVersion,
197
+ summary: `${receipt.files.length} VS Code resources verified; bundle cache cleared, restart VS Code to rebuild it` };
198
+ }
211
199
  try {
212
200
  const bundle = activeBundle(root, bundleCacheRoot);
213
201
  const pluginRoot = path.join(root, 'resources', 'opencode', 'plugin', 'd2h');
@@ -218,7 +206,8 @@ export function inspectVSCodeInstallation(extensionDir, bundleCacheRoot) {
218
206
  changed.push(`bundle/plugin/d2h/${name}`);
219
207
  }
220
208
  const config = JSON.parse(fs.readFileSync(path.join(bundle, 'opencode.json'), 'utf8'));
221
- if (!config.plugin?.includes('./plugin/d2h') || config.agent?.d2h?.description !== modeLabel) {
209
+ if (!['general', 'explore'].every(name => config.agent?.[name]?.permission === 'allow') ||
210
+ !config.plugin?.includes('./plugin/d2h') || config.agent?.d2h?.description !== modeLabel) {
222
211
  changed.push('bundle/opencode.json');
223
212
  }
224
213
  }
@@ -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.18",
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
 
@@ -0,0 +1,57 @@
1
+ ---
2
+ name: porting-cmake-native-libraries
3
+ description: 将 Android 工程中基于 CMakeLists.txt 的 C/C++ 库模块迁移并交叉编译为 HarmonyOS 的 .so/.a;分析平台条件分支和 Android Native API,输出报告,适配宏、Native 实现与 CMake,并验证产物。适用于有源码的原生库移植;不负责 N-API/JNI 绑定层重写或仅查询 ohpm 替代包。
4
+ ---
5
+
6
+ # CMake 原生库迁移
7
+
8
+ 交付可追溯的分析报告、源码与 CMake 修改、可复现的交叉编译命令及验证过的库文件。保持 Android 构建路径可用,优先复用平台无关核心。
9
+
10
+ 本技能可独立处理库项目。由迁移主 Agent 调用时,消费其提供的项目路径、工作区和任务边界,将结果返回主 Agent;不修改 AppGraph facts、Kernel 状态或自行推进应用迁移阶段。
11
+
12
+ ## 1. 确定编译范围
13
+
14
+ 读取项目及父目录适用的 `AGENTS.md`,检查现有改动。定位顶层及子目录 `CMakeLists.txt`、`.cmake`、Gradle `externalNativeBuild`、源文件、生成头文件、依赖库和导出头文件,梳理目标及传递依赖。
15
+
16
+ 从用户输入和项目配置确认:源码目录、目标库名、输出类型(SHARED/STATIC)、目标 ABI、SDK native 目录及版本、最低 API、构建配置、产物和报告目录。已有配置优先;无法推断的目标参数先询问,同时继续静态分析。`.so/.a` 表示按所需类型构建,不意味着擅自将所有目标同时生成两种类型。缺少 SDK 时仍交付分析与可执行的构建准备,明确记录未编译。
17
+
18
+ 构建目录按平台、ABI、配置隔离,不能复用 Android/宿主机 CMake cache。报告放在用户指定目录;否则放在源码外的专用迁移输出目录。已有迁移工作区时使用主 Agent 提供的产物位置,不自建 Kernel 状态文件。
19
+
20
+ ## 2. 分析并先输出报告
21
+
22
+ 按 [平台扫描与能力适配](references/platform-adaptation.md) 检查平台宏、CMake 条件、头文件、符号、链接库、预编译依赖及运行时平台假设。文本搜索只产生候选,结合条件上下文、目标依赖和实际编译命令判定;不能把未命中 `__ANDROID__` 当作无 Android 依赖。
23
+
24
+ 按 [报告与验收](references/report-and-build.md) 输出初始报告,再修改代码。每项记录文件和行号、所属目标、条件表达式、实际用途、处理方式、证据及未解决项。区分核心、平台适配层、JNI/N-API 绑定层。
25
+
26
+ **N-API 层 C++ 代码暂不适配,标记为需要重写。** JNI/Java 桥接也不能通过改宏变成鸿蒙绑定。记录绑定接口、参数/所有权、线程与生命周期约束,留给独立重写任务。可将混合文件中的平台无关算法抽为核心,但不在本技能中实现新绑定。
27
+
28
+ 若绑定目标能与核心分离,仅在鸿蒙构建中排除绑定目标/源文件,并明确核心库的能力边界;若核心仍依赖绑定符号或 Java 服务,将相关目标标为阻塞。禁止用空函数、伪成功返回或忽略链接错误掩盖缺失功能。
29
+
30
+ ## 3. 适配宏与 Native 能力
31
+
32
+ 用实际 SDK 编译器和目标参数探测预定义宏,并检查 toolchain 设置的 CMake 平台变量。不要假设 `__OHOS__` 在所有 SDK 中都存在;不要手动定义 `__ANDROID__` 或把 `ANDROID` 设置为真来复用 Android 路径。
33
+
34
+ 对每个条件分支分别决策:纯 C/C++/可验证的 POSIX 逻辑可共享;Android 特有实现保留,另加鸿蒙实现;不支持的能力明确阻塞。注意 `__linux__` 等宽泛分支可能先命中,不能只在末尾追加鸿蒙分支。需要项目宏时,通过目标级 `target_compile_definitions` 定义,并与编译器保留宏区分。
35
+
36
+ Native 替换以实际 SDK 头文件、库、官方文档及最小编译/链接验证为依据。记录最低 API、SysCap/设备约束、权限、线程、资源所有权、错误语义和行为差异。ArkTS API 或 ohpm 包不能直接当成 C/C++ 链接接口;只有 ArkTS 方案的能力留作上层/绑定重写依赖。
37
+
38
+ ## 4. 修改 CMake 并交叉编译
39
+
40
+ 按 [构建与验证流程](references/report-and-build.md) 操作:
41
+
42
+ - 使用目标 SDK 提供的 `ohos.toolchain.cmake`,不手工拼接一套猜测的 sysroot/compiler 配置。
43
+ - 以平台条件选择源文件、头文件和链接库;使用目标级配置,保留已有 Android 路径。只有确认配置有效时才用 `CMAKE_SYSTEM_NAME STREQUAL "OHOS"` 分流。
44
+ - 清理鸿蒙路径中的 NDK、Gradle、Android sysroot、`log/android/jnigraphics` 等链接项,按已验证的能力映射替换。不能全局删除其他平台所需依赖。
45
+ - 所有参与链接的第三方库必须具有匹配的鸿蒙 ABI、API 与 C++ 运行时;Android ELF 即使 CPU 相同也不能直接复用。无源码或无兼容二进制则记录阻塞。
46
+ - 明确 C++ 标准、异常/RTTI、可见性、导出接口、PIC 和运行时依赖;静态库进入共享库的对象需要 PIC。不要把 `BUILD_SHARED_LIBS` 当成覆盖显式 STATIC/SHARED 目标的开关。
47
+ - 检查 version script、导出列表和符号可见性是否绑定 Android/JNI 入口;鸿蒙核心库应导出其实际消费接口,不能直接沿用仅导出 `JNI_OnLoad` 的规则。
48
+
49
+ 逐目标、逐 ABI 配置和构建,保留完整命令、退出码和日志。修复有证据的源码、配置或依赖问题后重试;缺 SDK、不可获得的依赖或仅绑定层重写才能解决的问题应报告阻塞,而非反复试编或静默禁用功能。
50
+
51
+ ## 5. 验证并交付
52
+
53
+ 检查本次构建产物、架构、符号、动态依赖、静态归档成员和最小消费端链接。构建成功、链接验证成功、设备运行成功是不同状态;无法在鸿蒙设备运行时明确标注未验证。只交付通过对应检查的产物,失败目标单独列出。
54
+
55
+ 拆分算法核心时,按参考文档进行原实现与迁移实现的固定输入对照。记录接口契约与行为差异,不能为让样例通过而静默收窄输入范围或改变资源生命周期。
56
+
57
+ 更新初始报告为最终报告,给出修改摘要、产物绝对路径、ABI/API/SDK、重现命令、测试结果、运行时依赖与绑定重写清单。不要将“核心库编译通过”描述为“应用或 N-API 迁移完成”。
@@ -0,0 +1,49 @@
1
+ # 平台扫描与能力适配
2
+
3
+ ## 搜索候选
4
+
5
+ 在项目目录使用 `rg --files` 发现源码与构建文件,再搜索以下候选;检查依赖源码、生成配置与宏别名。忽略构建缓存,无法访问的依赖明确标为扫描缺口。
6
+
7
+ ```sh
8
+ rg -n '__ANDROID__|__ANDROID_API__|ANDROID_NDK|ANDROID_ABI|CMAKE_SYSTEM_NAME|__linux__|__OHOS__' .
9
+ rg -n 'jni\.h|JNIEnv|JavaVM|JNI_OnLoad|Java_|RegisterNatives|napi_|node_api|NAPI_MODULE' .
10
+ rg -n 'android/|sys/system_properties|__android_|AAsset|ANativeWindow|ALooper|AHardwareBuffer|AAudio|AMedia|OpenSL|GLES|EGL' .
11
+ rg -n 'find_package|find_library|target_link_libraries|IMPORTED|add_subdirectory|FetchContent|ExternalProject|try_run|pkg_check_modules' -g CMakeLists.txt -g '*.cmake' .
12
+ ```
13
+
14
+ 补查 `#ifdef ANDROID`、自定义 `PLATFORM_*`、`#if !defined(...)`、嵌套/组合条件、CMake `if(ANDROID)`、生成器表达式和 Gradle 传入的 defines。动态加载字符串、硬编码 Android 路径、Bionic 扩展、汇编/CPU 优化、Java 回调及传递依赖也可能形成平台耦合。
15
+
16
+ 扫描还需覆盖 `AndroidBitmap_*`、`AndroidBitmapInfo`、`android/bitmap.h` 等直接 API/类型依赖。平台专用源文件可能完全没有条件宏;报告应分别记录“平台宏命中情况”和“直接平台依赖”,不能相互替代。
17
+
18
+ 不能机械替换 `__ANDROID__` 为鸿蒙宏,或给每个条件都追加 `|| defined(__OHOS__)`。对当前目标以编译器预处理输出和 `compile_commands.json` 验证命中的源文件与分支;对其他配置记录覆盖范围。
19
+
20
+ ## Native 能力候选表
21
+
22
+ 以下是检索方向,不是已验证的一对一替换承诺。按用户目标 SDK/API 查询官方 C API,并在本地头文件和库中核对符号;报告中保存查证链接和 SDK 版本。
23
+
24
+ | Android 依赖 | 鸿蒙检索方向 | 必须检查的差异 |
25
+ | --- | --- | --- |
26
+ | `__android_log_*` / `android/log.h` | HiLog Native API | 格式、隐私标记、domain/tag、链接库 |
27
+ | `AAssetManager` | Native ResourceManager / RawFile | 资源包语义、管理器初始化、句柄释放;初始化可能依赖上层 |
28
+ | `ANativeWindow` / Surface | NativeWindow / NativeBuffer | Surface 来源、像素格式、buffer 生命周期、线程 |
29
+ | AAudio / OpenSL ES | OHAudio | 回调模型、低延迟支持、流状态和设备能力 |
30
+ | MediaCodec / `AMedia*` | Native AVCodec / AVFormat 等 | 编解码格式、异步回调、缓冲区所有权、最低 API |
31
+ | Bitmap / `jnigraphics` | Native Image / PixelMap | 数据布局、stride、alpha、对象来源 |
32
+ | EGL / GLES | SDK 提供的 EGL / GLES | 扩展、窗口接口、上下文线程与链接依赖 |
33
+ | 属性、Looper、Binder、Bionic 扩展 | 公共 C API / 已支持的 POSIX / 平台抽象 | 可能无公开等价能力,不能依赖内部系统接口 |
34
+ | JNI / Java 服务 / N-API | 核心接口拆分与绑定重写清单 | 不在本技能中重写绑定,不提供伪实现 |
35
+
36
+ 优先保持行为与公开 C/C++ 接口。能使用目标 SDK 已支持的标准接口时,不必引入平台专有能力。需要上层提供资源管理器、窗口或上下文的接口,在报告中明确初始化契约;不能凭空构造句柄。
37
+
38
+ ## 图像缓冲区适配
39
+
40
+ 涉及 Bitmap/PixelMap 时,分别核对接口要求的**输入缓冲区格式**与**目标对象格式**,以及 stride、通道顺序、位深、alpha 和字节序;不能从目标格式选项反推输入格式。例如,若选定 SDK 的创建接口要求 BGRA8888 输入,原 RGB565 数据必须先显式转换,不能只设置格式枚举后直接传入。
41
+
42
+ 确认输入缓冲区是否复制、何时可释放、输出句柄由谁释放,以及是否需要可编辑对象。原位锁定/写入改为创建新对象会改变对象身份、分配次数和拷贝成本,应作为行为差异报告并验证。锁定或创建失败后停止使用该资源,传播真实错误;资源转换不等于完成 JNI/N-API 绑定重写。
43
+
44
+ 资料入口:
45
+
46
+ - [OpenHarmony NDK 构建概述](https://gitcode.com/openharmony/docs/blob/master/zh-cn/application-dev/napi/build-with-ndk-overview.md)
47
+ - [华为三方库适配指南](https://developer.huawei.com/consumer/cn/doc/games-guides/games-2dx-adapt-third-library-0000002290527381)
48
+
49
+ OpenHarmony 文档可用于理解工具链,但不代表用户目标 HarmonyOS SDK 的每个 API 都可用;最终以选定发行版的文档与 SDK 为准。
@@ -0,0 +1,60 @@
1
+ # 报告与构建验收
2
+
3
+ ## 报告内容
4
+
5
+ 初始分析与最终结果使用同一份报告,保留初始发现及最终处置。建议结构:
6
+
7
+ 1. **范围**:源码版本/本地改动、CMake 入口、目标、输出类型、依赖图摘要、扫描缺口。
8
+ 2. **环境**:SDK 路径/版本、API、ABI、工具链、CMake/编译器版本、构建配置、预定义平台宏证据。
9
+ 3. **问题明细表**:编号、文件:行号、目标、条件/符号、用途、迁移策略、证据、验证结果、状态。
10
+ 4. **绑定重写清单**:N-API/JNI 文件、导出接口、核心依赖、生命周期与线程要求、排除的目标和影响。
11
+ 5. **修改及构建**:源码/CMake 修改摘要、每个目标和 ABI 的命令、退出码、日志路径。
12
+ 6. **产物表**:绝对路径、目标、类型、ABI、API、SDK、配置、哈希、公共头文件、动态/静态传递依赖、验证级别。
13
+ 7. **剩余工作**:阻塞项、行为差异、未执行检查、设备验证与绑定重写。
14
+
15
+ 问题状态可用 `pending / adapted / verified / blocked / rewrite-required`。产物验证分别记录 `configured / built / link-verified / runtime-verified`,失败记录具体阶段,不用单一“成功”覆盖所有层次。
16
+
17
+ ## 配置与构建
18
+
19
+ 先读取实际 SDK 的 `native/build/cmake/ohos.toolchain.cmake` 及其包含文件,确认 ABI、API、运行时和平台变量的支持方式。下面仅为命令形状;将变量设置为已确认的路径和目标,API/运行时选项按该 SDK 定义补充,不盲目照抄不存在的变量。
20
+
21
+ ```sh
22
+ cmake -S "$source_dir" -B "$build_dir" \
23
+ -DCMAKE_TOOLCHAIN_FILE="$sdk_native/build/cmake/ohos.toolchain.cmake" \
24
+ -DOHOS_ARCH="$target_abi" \
25
+ -DCMAKE_BUILD_TYPE=Release \
26
+ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
27
+ cmake --build "$build_dir" --target "$library_target" --parallel
28
+ ```
29
+
30
+ 多配置生成器应使用对应的 `--config`。从 cache、配置输出和实际编译命令确认 compiler、target triple、sysroot、架构及 API;配置参数被忽略的警告必须处理。用同一编译器及编译参数执行预定义宏探测(如 `-dM -E -x c++` 读取空输入),不要用宿主机 clang 的结果推断目标宏。
31
+
32
+ 检查 `find_package`、pkg-config、IMPORTED targets 和嵌套构建是否误取宿主机/Android 库。`ExternalProject` 等独立配置的依赖要显式传递正确工具链;宿主机代码生成工具与目标库分开构建。交叉编译的 `try_run` 不能直接在宿主机执行目标程序,也不能伪造通过结果。
33
+
34
+ 对项目现有显式 SHARED/STATIC 目标保留类型。用户确需两类产物时,建立独立目标或使用项目已支持的选项,处理输出命名、公共宏与 PIC;不要为无关目标改变全局行为。
35
+
36
+ 检查原项目的 version script(如 `.map.txt`)、导出列表、`-fvisibility` 和 `extern "C"`。只导出 `JNI_OnLoad` 的共享库规则不能用于新的核心 C 接口;按平台保留原规则,为鸿蒙目标声明所需接口,并核验最终动态符号表。库能生成但消费端无法链接,仍属失败。
37
+
38
+ 若 SDK 注入的参数触发 `-Werror`,保留首次失败日志,定位参数来源并确认其不影响目标架构、sysroot 或 ABI。优先修正配置;确属无效的冗余参数警告时,可仅对受影响目标使用对应的 `-Wno-error=<具体警告>`,保留警告可见。不预置通用抑制开关,不全局关闭 `-Werror`,不忽略 API 可用性、源码错误或未解析符号。
39
+
40
+ ## 核心行为对照
41
+
42
+ 拆分可独立运行的算法时,以固定源码版本的原实现作为对照,使用相同输入比较输出。可以仅提取原算法及其必要数据结构作为宿主机基线,但要说明提取范围;不要用伪造的平台 API 成功结果代替基线。图像算法适合覆盖奇偶尺寸、带 padding 的 stride、合法对齐、固定时间/参数和缓冲区边界,按算法特性选择逐字节或有依据的误差比较。必要时启用地址/未定义行为检查。
43
+
44
+ 宿主机对照证明核心拆分的一致性,不证明鸿蒙 PixelMap、日志、绑定层或设备行为正确。验证记录区分核心对照、目标链接和目标运行;没有真实平台宏组合或 STATIC 目标的样例不能证明这些场景已覆盖。
45
+
46
+ 不要将验证样例的临时时间/尺寸上限引入迁移接口作为默认限制。发现原算法数值范围或资源约束时,应明确原有契约、修复策略及新增限制的影响;未解决的行为差异不能归入完整兼容。
47
+
48
+ ## 产物验收
49
+
50
+ - 确认文件由本次构建产生,记录 SHA-256,避免把旧库作为成功结果。
51
+ - 使用 SDK 的 `llvm-readelf`、`llvm-nm`、`llvm-ar` 等可用工具;记录命令和结果。
52
+ - `.so`:检查 ELF machine/class、SONAME(适用时)、导出符号、DT_NEEDED、RPATH/RUNPATH 和未定义符号。动态库正常可有未定义导入,逐项确认提供者;不能要求全部符号为空,也不能忽略无法解析的 Android/JNI 依赖。
53
+ - `.a`:列出归档并验证各对象架构;LLVM bitcode/LTO 成员使用兼容工具链验证。归档创建成功不等于链接成功,必须用目标工具链构建最小消费端,调用预期公共入口并链接所需传递依赖。确认所有需交付对象被覆盖;适用时加 whole-archive 链接检查,避免仅验证未拉取任何成员的空程序。
54
+ - 检查产物架构只是必要条件:同 CPU 的 Android ELF 不能据此判定兼容。还要验证工具链来源、运行时、目标 API、动态依赖和消费端链接。
55
+ - 共享库也执行最小消费端链接检查;只有显式授权并具有设备条件时执行设备加载/功能测试,不在宿主机运行目标 ELF。
56
+ - 公共头文件、依赖库及 C++ 运行时部署要求随产物清单交付;报告未覆盖的 ABI、配置与平台分支。
57
+
58
+ 解析动态导入时,按实际链接搜索路径查找提供者:C++ 运行时可能位于 SDK 的 LLVM 库目录,而非 sysroot。区分系统库、应用需打包的运行时和仅供链接的 SDK stub;找到符号提供者仅证明链接条件,不允许将 stub 当运行时交付。排查交付物中的宿主机构建路径;需要相对加载路径时,按部署布局设置并验证。
59
+
60
+ 有 Android 编译环境时对受影响目标执行回归;没有时明确写“Android 构建未验证”,仍检查条件分流及原路径是否完整。SDK/设备缺失属于未执行验证,不能标记通过。