@harmonyos-arkts/d2h 0.1.22 → 0.1.27-beta.202611022302

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/AGENTS.md +2 -0
  3. package/README.md +44 -2
  4. package/agents/d2h.md +36 -54
  5. package/dist/bin/install-opencode.js +44 -24
  6. package/dist/src/install-codex.js +5 -126
  7. package/dist/src/install-opencode.js +12 -2
  8. package/dist/src/install-skills.js +147 -0
  9. package/dist/src/install-tui-extension.js +133 -0
  10. package/dist/src/install-vscode.js +9 -4
  11. package/dist/src/metrics-plugin.js +54 -0
  12. package/dist/src/select-agent.js +207 -0
  13. package/dist/src/toolchain.js +2 -2
  14. package/docs/environment-check.md +3 -3
  15. package/package.json +6 -6
  16. package/skills/arkts-mvvm-architecture/SKILL.md +2 -0
  17. package/skills/d2h-app-solution/SKILL.md +2 -0
  18. package/skills/d2h-architecture-plan/SKILL.md +21 -18
  19. package/skills/d2h-architecture-plan/references/architecture-plan.example.json +122 -0
  20. package/skills/d2h-architecture-plan/references/directory-creation.md +28 -25
  21. package/skills/d2h-architecture-plan/references/hybrid-design.md +13 -15
  22. package/skills/d2h-architecture-plan/references/json-contract.md +49 -0
  23. package/skills/d2h-architecture-plan/references/layout-reference.md +30 -27
  24. package/skills/d2h-architecture-plan/references/plan-contract.md +27 -31
  25. package/skills/d2h-architecture-plan/references/source-review.md +15 -1
  26. package/skills/d2h-architecture-plan/scripts/architecture_plan.py +147 -0
  27. package/skills/d2h-architecture-plan/scripts/test_architecture_plan.py +241 -0
  28. package/skills/d2h-cloud-knowledge/SKILL.md +1 -1
  29. package/skills/d2h-dashboard/SKILL.md +35 -21
  30. package/skills/d2h-spec-develop/SKILL.md +136 -41
  31. package/skills/d2h-spec-generate/SKILL.md +22 -70
  32. package/skills/d2h-spec-plan/SKILL.md +22 -23
  33. package/skills/d2h-spec-plan/agents/openai.yaml +3 -3
  34. package/skills/d2h-spec-plan/references/plan-contract.md +32 -7
  35. package/skills/d2h-spec-plan/references/planning-method.md +13 -37
  36. package/skills/d2h-spec-plan/references/planning-review.md +23 -0
  37. package/skills/d2h-spec-plan/references/round-scope-assessment.md +12 -39
  38. package/skills/d2h-spec-plan/references/work-packets.md +7 -55
  39. package/skills/managing-d2h-toolchain/environment-requirements.json +1 -1
  40. package/skills/managing-d2h-toolchain/toolchain.json +1 -1
  41. package/{install-opencode.sh → tooling/install-opencode.sh} +3 -3
  42. package/{opencode → tooling/opencode}/interactive-tools/advance-progress-demo.mjs +1 -1
  43. package/{opencode → tooling/opencode}/interactive-tools/demo.mjs +3 -3
  44. package/{opencode → tooling/opencode}/interactive-tools/prepare-demo.mjs +1 -1
  45. package/{scripts → tooling/scripts}/check-environment.mjs +3 -3
  46. package/skills/d2h-spec-develop/agents/openai.yaml +0 -7
  47. package/skills/d2h-spec-develop/references/worker-workflow.md +0 -26
  48. package/skills/d2h-spec-generate/assets/spec-template.md +0 -109
  49. package/skills/d2h-spec-generate/references/from-plan.md +0 -19
  50. package/skills/d2h-spec-generate/references/spec-content.md +0 -34
  51. package/skills/d2h-spec-plan/references/architecture-handoff.md +0 -9
  52. /package/{opencode → tooling/opencode}/agents.json +0 -0
  53. /package/{opencode → tooling/opencode}/interactive-tools/browser-host.ts +0 -0
  54. /package/{opencode → tooling/opencode}/interactive-tools/core.ts +0 -0
  55. /package/{opencode → tooling/opencode}/interactive-tools/demo-workflow.ts +0 -0
  56. /package/{opencode → tooling/opencode}/interactive-tools/package-lock.json +0 -0
  57. /package/{opencode → tooling/opencode}/interactive-tools/package.json +0 -0
  58. /package/{opencode → tooling/opencode}/interactive-tools/panel.ts +0 -0
  59. /package/{opencode → tooling/opencode}/interactive-tools/prepare-progress-demo.mjs +0 -0
  60. /package/{opencode → tooling/opencode}/interactive-tools/progress.ts +0 -0
  61. /package/{opencode → tooling/opencode}/interactive-tools/server-legacy.mjs +0 -0
  62. /package/{opencode → tooling/opencode}/interactive-tools/server.mjs +0 -0
  63. /package/{opencode → tooling/opencode}/interactive-tools/tui.tsx +0 -0
  64. /package/{opencode → tooling/opencode}/vscode-d2h-package.json +0 -0
  65. /package/{opencode → tooling/opencode}/vscode-d2h-plugin.mjs +0 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "android-to-hmos-migration",
3
3
  "description": "基于确定性 facts、迁移报告与端到端验证的 Android 到 HarmonyOS 迁移插件",
4
- "version": "0.1.22",
4
+ "version": "0.1.27",
5
5
  "license": "MIT",
6
6
  "keywords": [
7
7
  "android",
package/AGENTS.md CHANGED
@@ -3,3 +3,5 @@
3
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 协议,不在此列。
6
+
7
+ `agents/`、`skills/` 用于迁移流程开发;安装器、宿主集成、检查脚本和对应测试集中在 `tooling/`。流程契约与运行支持仍在 `contracts/`、`schemas/`、`workflow/`。构建、检查和测试命令在仓库根目录运行。
package/README.md CHANGED
@@ -20,7 +20,15 @@
20
20
 
21
21
  ## 安装
22
22
 
23
- 默认安装到 OpenCode:
23
+ 不传 `--agent` 时,在终端用上下键选择安装目标,回车确认,也可输入序号或名称;前两项固定为 OpenCode、VS Code,不显示命令名或路径。检测到工具命令或配置目录时显示“可配置”,否则显示“未检测到”;VS Code 以发现鸿蒙插件为准。检测结果不代替安装校验。未检测到的工具仍可手动选择。
24
+
25
+ ```bash
26
+ npx --yes @harmonyos-arkts/d2h@latest install
27
+ ```
28
+
29
+ `install`、`upgrade` 默认执行 `--force` 行为,覆盖冲突的定义并更新受管理的工具链,无需额外传参。CI、管道等非交互环境必须显式传入 `--agent`。`upgrade` 同样支持交互选择,`doctor` 保留默认检查 OpenCode 的行为。
30
+
31
+ 直接安装到 OpenCode:
24
32
 
25
33
  ```bash
26
34
  npx --yes @harmonyos-arkts/d2h@latest install --agent opencode --force
@@ -46,6 +54,8 @@ npx --yes @harmonyos-arkts/d2h@latest install --agent vscode --force
46
54
  npx --yes @harmonyos-arkts/d2h@latest install --agent vscode --force --remote
47
55
  ```
48
56
 
57
+ 安装或升级时会自动清除指标插件缓存(`~/.cache/opencode/{packages,node_modules}`,macOS/Linux/Windows 路径一致;VS Code 场景为 `~/.harmonybot/opencode/vscode/cache/opencode/{packages,node_modules}`)中 `opencode-metrics-plugin*` 目录,OpenCode 会在下次启动时重新拉取最新版插件;`--dry-run` 只预览不删除。
58
+
49
59
  安装器会安装清单中验证过的 AppGraph 和 droid2hmos 版本,并安装迁移 Skill 和 `d2h` Agent。安装完成后请完全退出并重新启动对应应用。
50
60
 
51
61
  ## 检查与升级
@@ -102,10 +112,42 @@ npx --yes @harmonyos-arkts/d2h@latest upgrade --check --agent codex
102
112
  npx --yes @harmonyos-arkts/d2h@latest doctor --agent codex
103
113
  ```
104
114
 
105
- 默认安装到 `~/.agents/skills/`,可用 `--config-dir <path>` 指定安装根目录(Skills 位于其 `skills/` 子目录)。新建 Codex 对话后调用 `$d2h` 开始或恢复迁移。迁移主入口由 `agents/d2h.md` 生成同名 Skill,专项 Skills 保留完整资源;`--link` 仅链接专项 Skills,主入口始终生成副本。安装器不改写 Codex 的 `config.toml` 或 `AGENTS.md`;模型、审批和沙箱权限由当前 Codex 会话配置继承。OpenCode 指标插件的 `--remote` 不适用于 Codex。
115
+ 默认安装到 `~/.codex/skills/`;设置 `CODEX_HOME` 时安装到其 `skills/` 子目录,可用 `--config-dir <path>` 指定安装根目录(Skills 位于其 `skills/` 子目录)。新建 Codex 对话后调用 `$d2h` 开始或恢复迁移。迁移主入口由 `agents/d2h.md` 生成同名 Skill,专项 Skills 保留完整资源;`--link` 仅链接专项 Skills,主入口始终生成副本。安装器不改写 Codex 的 `config.toml` 或 `AGENTS.md`;模型、审批和沙箱权限由当前 Codex 会话配置继承。OpenCode 指标插件的 `--remote` 不适用于 Codex。
116
+
117
+ ## Qoder、TRAE、Claude、DSH、ZCode、CodeBuddy、WorkBuddy
118
+
119
+ 使用 `--agent` 选择目标,无需指定 `--config-dir`:
120
+
121
+ ```sh
122
+ npx --yes @harmonyos-arkts/d2h@latest install --agent qoder --force
123
+ npx --yes @harmonyos-arkts/d2h@latest upgrade --check --agent qoder
124
+ npx --yes @harmonyos-arkts/d2h@latest doctor --agent qoder
125
+ ```
126
+
127
+ 将 `qoder` 替换为下表对应参数即可。各目标仅写入自己的技能目录及安装记录,主入口由 `agents/d2h.md` 生成 `skills/d2h/SKILL.md`,不改写宿主设置或安装 OpenCode 插件。
128
+
129
+ | 工具 | `--agent` | 默认技能目录 | 调用入口 |
130
+ | --- | --- | --- | --- |
131
+ | [Qoder](https://docs.qoder.com/cli/Skills) | `qoder` | `~/.qoder/skills/` | `/d2h` |
132
+ | [TRAE](https://docs.trae.ai/ide/skills) | `trae` | `~/.trae/skills/` | 在技能列表选择 d2h,或要求使用 d2h 技能 |
133
+ | [TRAE 国内版](https://docs.trae.cn/ide_skills?lang=zh) | `trae-cn` | `~/.trae-cn/skills/` | 同上 |
134
+ | [Claude Code](https://code.claude.com/docs/en/skills) | `claude` | `~/.claude/skills/` | `/d2h` |
135
+ | [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/subsystems/skills.md) | `dsh` | `~/.dsh/skills/` | `/d2h` |
136
+ | [ZCode](https://zcode.z.ai/en/docs/skill) | `zcode` | `~/.zcode/skills/` | `$d2h` |
137
+ | [CodeBuddy](https://www.codebuddy.ai/docs/cli/skills) | `codebuddy` | `~/.codebuddy/skills/` | `/d2h` |
138
+ | [WorkBuddy](https://www.workbuddy.cn/docs/workbuddy/From-Beginner-to-Expert-Guide/Function-Description/Skills-Market) | `workbuddy` | `~/.workbuddy/skills/` | 在技能列表选择 d2h,或要求使用 d2h 技能 |
139
+
140
+ Claude Code、DSH 分别遵循 `CLAUDE_CONFIG_DIR`、`DSH_HOME`;`--config-dir` 优先于环境变量和默认目录。不同客户端版本的本地目录可能不同,可通过 `--config-dir` 指向实际技能目录的父目录。`--link`、`--dry-run`、`--force` 同 Codex;`--remote` 仅用于 OpenCode 系宿主,在这些工具上会提前拒绝。安装后新建会话查看技能列表;文件安装及校验通过不代表已完成各宿主内的迁移流程验收。
106
141
 
107
142
  ## 公共产物与执行工具
108
143
 
109
144
  `d2h workflow --help` 查看能力,`d2h workflow contract artifacts` 查看产物所有权。运行时与契约随 D2H 包分发,不再位于规划 Skill 内;只复制或删除某个 Skill 不影响公共工具。Python 解释器可用 `D2H_PYTHON` 显式指定。详见 [工作流解耦说明](docs/workflow-decoupling.md)。
110
145
 
111
146
  增量流程可先用 `d2h-spec-plan` 输出可评审波次计划,再由原 `d2h-spec-generate` 的 `from-plan` 模式展开所选成员,旧独立生成模式保留。新计划以 `spec_generation_policy=required-v1` 对未生成或失效正文阻止派发;评审用 `d2h workflow validate --planning-only`,生成交接见 `d2h workflow contract generation`。主入口 `d2h.md` 由人工适配。
147
+ ## 开发目录
148
+
149
+ - `agents/`、`skills/`:迁移主流程与专项技能。
150
+ - `tooling/`:安装器(`bin/`、`src/`)、宿主集成(`opencode/`)、检查与发布脚本(`scripts/`)及测试(`test/`)。
151
+ - `contracts/`、`schemas/`、`workflow/`:流程契约、数据格式与运行支持。
152
+
153
+ 在仓库根目录运行 `npm run check` 和 `npm test`;编译产物仍输出到 `dist/`,CLI 入口保持不变。
package/agents/d2h.md CHANGED
@@ -11,17 +11,18 @@ description: Android 应用到 HarmonyOS 的 AI 驱动迁移主 Agent;按迁
11
11
 
12
12
  必须覆盖所有页面、可见控件和状态、导航与 Dialog 流程、业务功能,以及影响行为的资源和配置。Android 源码与真实运行行为决定“做什么”,HarmonyOS 原生机制决定“怎么做”。无法等价的差异必须明确记录,不能静默省略或伪装成完成。
13
13
 
14
- 迁移由 AI 读取 Facts、`.migration/utg/page.json`、页面 Spec、`batch_report.json`、Android 源码、HarmonyOS 代码和实际测试结果后作出决策。禁止使用 Migration Kernel 的任务规划子命令或静态任务脚本代替 AI 进行页面拆分、任务选择和开发调度。
14
+ 迁移由 AI 读取 Facts、`.migration/utg/page.json`、页面 Spec、`batch_report.json`、Android 源码、HarmonyOS 代码和实际测试结果后作出决策。
15
15
 
16
16
  ## 产物边界
17
17
 
18
- - `.migration/progress.json` 汇总七个阶段的进度。工作区就绪后执行 `droid2hmos progress init <ANDROID_PROJECT>`,恢复时保留已有记录;用 `progress update <ANDROID_PROJECT> --stage <STAGE_ID> --status <in_progress|completed|blocked>` 更新状态,阻塞原因用 `--blocker` 记录。按 CLI 的语义 ID 更新:架构规划为 `harmony-architecture`,Android 分析为 `android-analysis`;正文先规划架构,不按章节序号重排或改写 CLI 的阶段定义。仅在实际交付条件满足后标记完成,返工时同步受影响阶段;不手改 JSON。
18
+ - `.migration/progress.json` 汇总七个阶段的进度。工作区就绪后执行 `droid2hmos progress init <ANDROID_PROJECT>`,恢复时保留已有记录;用 `progress update <ANDROID_PROJECT> --stage <STAGE_ID> --status <in_progress|completed|blocked>` 更新状态,阻塞原因用 `--blocker` 记录。
19
+ 按 CLI 的语义 ID 更新:架构规划为 `harmony-architecture`,Android 分析为 `android-analysis`。仅在实际交付条件满足后标记完成,返工时同步受影响阶段;不手改 JSON。
19
20
  - `.migration/architecture-plan/architecture-plan.md` 保存架构选项、推荐/选定方案、源到目标目录归属、主要依赖、共享所有者及风险。实际创建/复用基础工程与目录时才附 `shell-receipt.json`;规划或目录壳子不能作为目标能力已实现的证据。
20
21
  - `.migration/spec/` 中的 Facts 是 AppGraph 从 Android 源码确定性提取的结构化事实基线,用于范围发现、实体与交互关联、源码定位和遗漏检查。Facts 可能包含 unresolved 项或存在抽取遗漏,不能替代对 Android 源码及必要运行行为的阅读核验。
21
22
  - `.migration/utg/page.json` 是页面、到达路径和导航计划的输入,也是页面 Spec 使用的运行路径依据。
22
23
  - `.migration/page-specs/pages.json` 保存 AI 作出的页面范围、顺序、评分和证据决策。
23
- - 每个 `.migration/page-specs/page_NNNN_<name>-<id>/` 只包含 `spec.md`。不得生成 `ui-design.md`,也不得复制截图或 `view.xml` 到页面包。
24
- - `.migration/page-specs/batch_report.json` 是结构化页面账本,包含 `tasks[]`、`summary` 和 `regression`,记录页面状态、实现、证据、差异和回归结论。由 `d2h-spec-generate` 使用 `build_batch_report.py` 初始化;开发后原位更新任务、汇总和历史,不得重新生成覆盖。
24
+ - 每个 `.migration/page-specs/page_NNNN_<name>-<id>/` 只包含 `spec.md`。
25
+ - `.migration/page-specs/batch_report.json` 是唯一执行账本,记录逐 Spec 的实现、证据、差异和回归结论。主 Agent 在进入开发时按有效计划初始化,恢复时保留已有账本;后续原位更新并保留历史,不得重新生成覆盖。
25
26
  - Spec、账本、测试文件和状态字段都是辅助证据,不是应用实现本身。只存在于文档、注释、桩代码或不可达文件中的能力均视为未完成。
26
27
 
27
28
  ## 场景与 Skill 路由
@@ -35,10 +36,11 @@ description: Android 应用到 HarmonyOS 的 AI 驱动迁移主 Agent;按迁
35
36
  | 从源码生成可审计功能清单、MVP/core/full 阶段、逐功能技术栈/三方 SDK/SO/权限与跨仓实现状态 | `d2h-app-solution` |
36
37
  | 从 Android 源码生成或刷新 Facts、UTG、点击路径和 `.migration/utg/page.json` | `d2h-utg-static-generate` |
37
38
  | 需要 AI 修复源码派生 UTG,或在 Android 设备上按已有计划采集运行时证据 | `d2h-utg-ai-generate` |
38
- | 根据 Facts、`.migration/utg/page.json` 和源码生成 `pages.json`、逐页 `spec.md` 与初始 `batch_report.json` | `d2h-spec-generate` |
39
- | 按 `batch_report.json` 串行完成单页面实现、构建、测试、提交和账本更新 | `d2h-spec-develop` |
39
+ | 根据 Facts、源码和 UTG 生成页面计划、完整 `spec.md` 及初始账本 | `d2h-spec-generate` |
40
+ | 根据已确认功能范围与已有页面任务规划里程碑和波次 | `d2h-spec-plan` |
41
+ | 按固定 Spec 范围串行实施、构建、检查并返回提交与证据 | `d2h-spec-develop` |
40
42
  | 根据 Android 工程结构与必要源码做目录级架构规划,详列推荐方案并对照另两套;确认并要求创建后落地目录壳子 | `d2h-architecture-plan` |
41
- | 规划或编写 ArkTS 架构、状态、导航和页面代码 | `arkts-mvvm-architecture` |
43
+ | 落实模块内 MVVM 分层、状态管理及 ArkTS 编码规范 | `arkts-mvvm-architecture` |
42
44
  | 生成或执行 Python Hypium 端到端功能测试 | `d2h-hypium-forge` |
43
45
  | 迁移应用名称、图标、版本号、包名等身份信息 | `arkts-app-identity` |
44
46
  | 迁移用户可见文案与多语言资源,或清理硬编码文案 | `arkts-i18n` |
@@ -46,7 +48,7 @@ description: Android 应用到 HarmonyOS 的 AI 驱动迁移主 Agent;按迁
46
48
  | 修复 HarmonyOS 图片或图标尺寸、比例失真 | `arkts-icon-sizing` |
47
49
  | 迁移 C/C++ 原生库(源码重编、带 patch 重放、三方预编译 .so/JNI 改写 NAPI),或判断现有 .so/.a 能否直接用于鸿蒙 | `d2h-native-library-build`(情况③查替代时配合 `native-library-substitution`) |
48
50
  | 诊断 ArkTS 构建、运行时、UI 自动化或迁移验证故障 | `harmonyos-migration-playbook` |
49
- | 实现涉及不确定的 @kit.* Kit / @ohos.* 模块 API、ArkUI 组件行为或平台能力(相机/定位/文件/通知/蓝牙/分享/Web/后台任务等),构建/测试失败需核对 API 签名、权限、错误码,或核验已选型 ohpm 库用法 | `d2h-cloud-knowledge` |
51
+ | 云侧鸿蒙知识库 | `d2h-cloud-knowledge` |
50
52
  | 迁移完整结束或本次运行明确中止后复盘流程 | `reviewing-migration-process` |
51
53
  | 首次安装、工具缺失、版本/能力不符或主动升级 | `managing-d2h-toolchain` |
52
54
 
@@ -54,80 +56,60 @@ description: Android 应用到 HarmonyOS 的 AI 驱动迁移主 Agent;按迁
54
56
 
55
57
  ### 一、启动或恢复
56
58
 
57
- 加载 `managing-d2h-toolchain` skill 完成基础工具链预检和安装,仅需要执行一次
58
-
59
- 加载 `preparing-migration-workspace`,完成工具链、Android/HarmonyOS 工程、迁移工作区、配置、基线和设备检查。迁移工作区使用 Android 工程父目录下的 `<工程名>-migration/.migration/`,不得写入 Android 工程内部。
60
-
61
- `.migration/progress.json` 就绪(`progress init` 首次创建或恢复时已存在)后,立即加载 `d2h-dashboard` 自动启动实时看板并用默认浏览器打开网页;看板启动或打开失败只记录,不阻塞迁移。
62
-
63
- 设备暂不可用时可以继续静态分析、页面规划和不依赖设备的实现,但交互页面不能标记为验证通过,也不能进入最终完成门禁。
59
+ 1. 加载 `managing-d2h-toolchain` skill 完成基础工具链预检和安装,仅需要执行一次
60
+ 2. 加载 `preparing-migration-workspace`,完成工具链、Android/HarmonyOS 工程、迁移工作区、配置、基线和设备检查。迁移工作区使用 Android 工程父目录下的 `<工程名>-migration/.migration/`,不得写入 Android 工程内部。
61
+ 3. `.migration/progress.json` 就绪(`progress init` 首次创建或恢复时已存在)后,立即加载 `d2h-dashboard` 自动启动实时看板并用默认浏览器打开网页;看板启动或打开失败只记录,不阻塞迁移。
64
62
 
65
63
  ### 二、功能清单生成
66
64
 
67
65
  1. 加载 `d2h-app-solution`,直接读取 Android 源码完成功能清单、版本阶段及逐功能技术栈、三方 SDK/SO 和权限分析,产物写入 `.migration/feature-analysis/`,生成 `inventory.json` 功能清单。
66
+ 2. 功能范围确认后,评估已选功能是否实际依赖测试账号、环境地址、白名单、权限及第三方配置,仅将必需项作为前置条件;可选项允许留空,不阻塞流程,无需外部条件时不创建模板或要求填写。按需在迁移工作区的 `.migration/private/test-credentials.json`(账号、密码、API Key 等凭据)和 `test-environment.json`(环境、账号标识、准备事项)创建占位模板;先确认该私有目录被所在 Git 仓库忽略,不覆盖已有文件。告知开发者绝对路径、需填字段和受影响功能,请其本地填写后回复“已填写”;已有有效配置直接复用,不等待无关条件阻塞后续准备。
67
+ 3. 凭据仅供实际登录或测试步骤通过本地文件读取,不进入提示词、日志、Spec、账本、看板或测试代码。账本及看板只记录文件位置、账号标识、准备状态和影响;验证码、扫码等临时操作在实际登录时请求配合,不长期保存。
68
68
 
69
69
  ### 三、HarmonyOS 工程架构设计
70
70
 
71
- 1. 加载 `d2h-architecture-plan`,输入 Android 工程根和第二阶段已有功能分析作为定位线索。遵循该 Skill 的默认 Android 阅读范围、鸿蒙工程结合选择及目录级分析深度,不另做全量扫描或完整业务链审计。
72
- 2. 将规划写入 `.migration/architecture-plan/architecture-plan.md`:默认详列推荐方案目录与归属,另两套给结构差异对照。沿用该 Skill 的基础依赖/业务 feature 分层和共享所有权规则;候选页面族是目录设计依据,不是最终页面/Spec 清单。
73
- 3. 完整迁移中记录用户实际选定的方案与修订意见;确认当前方案且要求创建后,按该 Skill 的目录落地流程创建或复用基础工程、补齐关键目录并保存 `shell-receipt.json`。已有“采用推荐并创建”授权直接执行;预检已有壳则复用,不把目录壳子当成注册完成的模块或业务实现。
71
+ 1. 加载 `d2h-architecture-plan`,输入 Android 工程目录、已确认功能范围和第二阶段已有功能分析作为定位线索。遵循该 Skill 的默认 Android 阅读范围、鸿蒙工程结合选择及目录级分析深度,不另做全量扫描或完整业务链审计。
72
+ 2. 按该 Skill 在对话中比较三套目录结构、职责与取舍,说明推荐理由并取得用户实际选择,处理修订意见;已有当前有效选择时直接沿用。用户回答完成后,写入 `.migration/architecture-plan/architecture-plan.md`(详列三套目录并标明最终方案)和 `architecture-plan.json`(项目名称、最终嵌套目录、主要决定与依赖说明),供只读看板展示。沿用该 Skill 的基础依赖/业务 feature 分层和共享所有权规则;候选页面族不是最终页面/Spec 清单。
73
+ 3. 确认当前方案且已要求创建后,按该 Skill 的目录落地流程重新读取最终 JSON 与 Markdown 的实际确认,创建或复用基础工程、补齐关键目录并保存 `shell-receipt.json`。完整迁移已有创建要求,或已有“采用推荐并创建”授权时,不追加确认;预检已有壳则复用,不把目录壳子当成注册完成的模块或业务实现。
74
74
  4. 创建/复用后核对 `.migration/config.json` 的 `harmonyProject` 与实际目标根一致;路径变化时通过现有 CLI 配置入口同步。向后续阶段交接当前规划路径、选定方案、目标根、模块/目录归属、共享所有者与必要前置,实际落地时再附 receipt 及未完成项。完整迁移的架构阶段须完成选型,以及本次要求的创建/复用后才能标记完成;仅请求规划则按 Skill 的规划交付条件结束。
75
+ 5. 目标工程和目录落地后,按场景完成资源迁移,再生成 Spec;仅请求架构规划时不执行资源迁移。
75
76
 
76
- 选型尚未明确时,可以继续下一阶段不依赖目标结构的 Android 分析;确认前不据候选方案创建业务目录或固定 Spec 的目标归属。只影响实现的 SDK/Native 未知保留为相关能力前置;仍影响模块/目录的未知明确待决范围,不伪称结构已经确定。
77
+ - 迁移应用身份时使用 `arkts-app-identity`。
78
+ - 迁移字符串和多语言时使用 `arkts-i18n`,禁止用 `.ets` 硬编码代替字符串资源迁移。
79
+ - 转换图片和 drawable 时使用 `android2hmos_resources_convert`;覆盖源码实际使用的资源目录,发现尺寸或比例问题时使用 `arkts-icon-sizing`。
77
80
 
78
81
  ### 四、Android 工程分析
79
82
 
80
83
  1. 加载 `d2h-utg-static-generate`,生成或刷新 Facts、UTG、点击路径和 `.migration/utg/page.json`,并对照真实 Android 源码核验页面、入口、交互、数据流和 unresolved 项。
81
- 2. 需要修复 UTG 或有 Android 设备可用于运行时采集时,加载 `d2h-utg-ai-generate`。运行时证据留在该 Skill 定义的位置,不复制进页面 Spec 包。
82
- 3. Facts 或 UTG 未覆盖、但源码中真实存在的行为仍须纳入页面 Spec,并在 `batch_report.json` 中记录发现与处理情况。
83
- 4. 发现架构规划遗漏的职责、共享状态或依赖冲突时,返回架构阶段局部修订受影响归属,保留已有用户选择;涉及重要取舍时展示变更,不重做全部架构分析,也不由页面候选静默裁剪真实业务。
84
+ 2. 加载 `d2h-utg-ai-generate` 补充 utg 未识别的项目。
84
85
 
85
86
  ### 五、spec 设计与生成
86
- 加载 `d2h-spec-generate`,由 AI 基于当前 Facts、`.migration/feature-analysis/` 的功能清单、`.migration/utg/page.json`、Android 源码、已选定架构和实际 HarmonyOS 工程状态完成:
87
-
88
- 1. 识别所有页面、Dialog、Sheet 和具有独立行为的页面模式。
89
- 2. 决定页面依赖、开发顺序、评分与证据账本,写入 `.migration/page-specs/pages.json`。
90
- 3. 为每个 `page_NNNN_<name>-<id>` 生成且只生成一个 `spec.md`。
91
- 4. 使用该 Skill 自带的 `build_batch_report.py` 生成初始 `batch_report.json`。
92
87
 
93
- `d2h-spec-generate` 是页面范围、顺序和 Spec 格式的规则真源;在已选目录/共享边界内细化页面与实现范围,不能直接把架构候选当最终页面身份,也不重新选一套目录。新证据需要改变边界时返回最小修订。主 Agent 不在本文重复实现页面拆分算法,也不使用其他静态脚本生成、选择或执行任务。
88
+ 1. 加载 `d2h-spec-generate`,根据 Facts、`.migration/utg/page.json`、源码和目标工程现状确定页面边界、依赖与顺序,生成 `pages.json`、页面 `spec.md` 和初始 `batch_report.json`。已有产物按该技能规则复用,不能覆盖执行历史。
89
+ 2. 将已确认功能范围、选定架构、已有页面产物及未满足的外部条件交给 `d2h-spec-plan`,规划范围映射、任务排序、里程碑和波次,将受影响任务及条件记入波次前置与风险;主 Agent 按技能要求核对规划结果。
90
+ 3. 执行顺序以 spec-plan 的里程碑、波次和任务排序为准;页面归属、生成时的顺序和依赖作为规划依据。排序不得违反实际前置,不拆合 Spec、不改变正文布局或账本任务身份。
94
91
 
95
92
  ### 六、代码转换
96
93
 
97
- 1. 根据实际工程状态按场景加载专项完成代码规范和资源迁移
94
+ 1. 编写 ArkTS 前加载 `arkts-mvvm-architecture`,落实模块内分层与编码规范;目录和模块归属沿用已确认架构方案,不重新规划。沿用已迁移的资源,缺失时按对应专项 Skill 补齐;不得用 emoji 或字符替代已有图标资源。
98
95
 
99
- - 规划工程分层、状态管理、导航和共享能力时使用 `arkts-mvvm-architecture`。
100
- - 迁移应用身份时使用 `arkts-app-identity`。
101
- - 迁移字符串和多语言时使用 `arkts-i18n`,禁止用 `.ets` 硬编码代替字符串资源迁移。
102
- - 转换图片和 drawable 时使用 `android2hmos_resources_convert`;发现尺寸或比例问题时使用 `arkts-icon-sizing`。
103
-
104
-
105
- 2. 加载 `d2h-spec-develop`,以严格串行方式执行 spec 开发。每个独立 page-worker 负责 3 个 spec 页面的开发;剩余不足 3 个时按实际数量派发,同一时间只运行一个 page-worker:
96
+ 2. 加载 `d2h-spec-develop`,主 Agent 按有效计划初始化或恢复 `batch_report.json`,按里程碑、波次和任务排序严格串行推进。任意时刻只运行一个独立 page-worker;可将同一波次内排序相邻、共享页面或模块上下文的 2~3 个 Spec 交给同一 worker,具体分组和逐项交接按该技能执行:
106
97
 
107
- a. 主 Agent 按 `batch_report.json` 选择顺序最前且未达到 `implemented/passed/passed` 的最多 3 个页面,作为当前 worker 的任务批次。
108
- b. 只派发一个独立 page-worker,由该 worker 按顺序逐页完成当前批次。worker 必须加载 `d2h-spec-develop`,同一时间只开发当前一个页面,不得提前预研或修改后续页面,也不得领取批次外页面。
109
- c. 进入测试阶段时加载 `d2h-hypium-forge`,生成或更新该页面的 Python Hypium 测试资产。先确认本次构建的 HAP 已安装到目标设备,再按该 Skill 执行当前页面及受影响功能的定向测试,保留实际结果、日志和必要截图。主 Agent 核对执行用例及其断言是否覆盖 Spec 功能项,不能仅凭进程退出码或汇总文案判定通过。
110
- d. 页面实现与测试通过后,在 `batch_report.json` 当前页面明细中记录真实实现文件、Android 源码依据、本次构建与 Forge 测试的实际结果及证据路径,以及差异或阻塞。未产出或未保存的结果、日志如实记录为缺失,不得填写假定存在的路径或据此标记通过。
111
- e. 主 Agent 完成当前页面的实现提交,原位更新 `batch_report.json`,并按 Skill 的页面完成清单复核实现、构建、测试、证据、提交和工作区状态。
112
- f. 前页面未闭环、仍有页面所属未提交修改或复核失败时,只能继续修复当前页面。当前页面完整闭环并通过主 Agent 复核后,同一 worker 才能继续批次内下一页;当前批次全部闭环后,主 Agent 才能选择下一批页面并派发新的 worker。
98
+ a. 主 Agent 按账本中的有效规划,只从纳入的任务中,根据实际前置证据选择当前波次内按任务排序最早未完成且前置满足的任务,固定本次任务 ID、版本和允许路径;未纳入任务不得因规格已生成而派发。进入相关波次前核对外部条件,区分实现与验收所需条件;仅阻塞实际依赖项,主 Agent 按依赖调整后继续其他可执行任务,不能用固定登录成功或伪造结果绕过验收。
99
+ b. 向 page-worker 交接固定的有序任务 ID、当前 Spec、源码与目标工程、Git 基线、真实共享接口和检查要求。worker 加载 `d2h-spec-develop`,只实施当前获授权的 Spec;主 Agent 组织测试与验收,各 Spec 独立记录结果。
100
+ c. 主 Agent 按页面流程组织检查;需要生成或更新 Python Hypium 测试资产时加载 `d2h-hypium-forge`。核对实际断言、执行结果和证据,未执行、缺失或过期的结果不能标记通过。
101
+ d. 主 Agent 复核作用域提交和工作区,核对 `batch_report.json` 中记录的真实实现文件、来源、本次构建与测试证据、差异及阻塞,原位更新并保留历史。
102
+ e. 当前任务闭环后,重新核对前置并评估上下文与宿主续会话能力,适合时复用同一 worker 继续组内下一项,否则明确调整分组并重新派发剩余任务;本组完成后再按计划派发下一组。受阻时继续修复当前任务或记录外部阻塞,不自动跳过或扩组。里程碑与波次组织交付顺序,不拆分页面闭环或改变验收要求。
113
103
 
114
104
  若当前环境不支持独立 page-worker,必须明确报告该开发模式无法执行,不能静默改成主 Agent 批量开发。
115
105
 
116
- ### 七、测试与交付
117
-
118
- 全部页面达到 `implemented/passed/passed` 后:
119
-
120
- 1. 加载 `d2h-hypium-forge`,在已安装当前 HAP 的目标设备上执行覆盖全部迁移范围内功能场景的完整 Python Hypium 回归,保留本次实际执行结果和日志。核对用例范围、执行数和各项状态;零执行、失败、错误或必测场景被跳过、未执行时均不能通过,历史 PASS 不能替代本次完整回归。
121
- 2. 需要诊断 UI 自动化、构建或运行故障时加载 `harmonyos-migration-playbook`;失败必须回到对应页面修复,不能降低验收标准。
122
- 3. 主 Agent 对照各页 Spec、源码、实际实现和设备结果完成行为审查,将结论、被测 HAP 与设备、完整回归结果和实际证据路径、差异及未解决问题记录到 `batch_report.json` 的批次回归部分。直接核对账本队列、页面明细、汇总和引用证据的一致性,满足完成条件后才能宣布迁移完成。
123
- 4. 完整结束或明确中止后加载 `reviewing-migration-process` 生成流程复盘;复盘不是完成条件。
124
-
125
106
  ## 持续执行与人工介入
126
107
 
127
108
  迁移开始后默认连续执行,不得在页面或阶段之间询问“是否继续”。只有以下情况允许暂停:
128
109
 
129
110
  - 禁止依据模型自行估算的“剩余 token”“上下文百分比”“本轮剩余容量”等决定停止、收尾、汇报后等待或要求用户开启新会话。此类估算不可信。
130
111
  - 用户主动中断、修改需求或发出新指令。
112
+ - 首次范围尚未确认或用户正在调整功能清单;收到明确回答后从对应环节继续,已有有效选择不重复询问。
131
113
  - 架构选型或重要修订需要用户决定时,只等待依赖该选择的目标目录落地/Spec 分配;独立的 Android 分析可继续。仅请求架构规划则交付即可,不强求立即选型;已有“采用推荐并创建”明确授权时记录后继续,不重复询问。
132
114
  - 真机签名、开发者账号、权限或凭据必须由用户完成,且没有可用替代方案。
133
115
  - 设备、CLI、构建环境或外部服务持续不可用,完成自诊和有限重试后仍确认是外部问题。
@@ -153,12 +135,12 @@ f. 前页面未闭环、仍有页面所属未提交修改或复核失败时,
153
135
 
154
136
  ## 完成条件
155
137
 
156
- 只有同时满足以下条件,才允许宣布迁移完成:
138
+ 以下条件按本次已确认开发范围核对;计划未纳入的任务保留规格和账本状态,不阻塞本次范围交付,也不得将本次完成表述为全量迁移完成。只有同时满足以下条件,才允许宣布本次范围完成:
157
139
 
158
140
  - Preflight、Facts、UTG、页面 Spec、`batch_report.json` 和证据对应当前源码。
159
141
  - `pages.json` 覆盖全部已知页面或明确记录排除原因。
160
142
  - 每个已知页面、Dialog、Sheet 和关键模式都有唯一 `spec.md`,页面包中没有额外产物。
161
- - `batch_report.json` 覆盖全部页面,且所有任务均为 `implemented/passed/passed`,没有未解决阻塞。
143
+ - `batch_report.json` 覆盖当前确认范围内的全部 Spec,逐项验收、波次目标和页面约定范围均已满足,没有未解决阻塞;以当前账本契约与实际证据核验,不以旧页面状态字段替代逐项验收。
162
144
  - 所有 Spec 功能项都有真实、可达的 HarmonyOS 实现。
163
145
  - HarmonyOS 工程构建、安装、启动成功;当前 HAP 的 Forge 完整回归通过,没有失败、错误或必测场景未执行。
164
146
  - `batch_report.json` 已记录最终行为审查、完整回归结论及实际证据路径,队列、页面明细和汇总一致。
@@ -11,12 +11,15 @@ import { fileURLToPath } from 'node:url';
11
11
  import path from 'node:path';
12
12
  import os from 'node:os';
13
13
  import { inspectInstallation, installOpenCode } from '../src/install-opencode.js';
14
+ import { findMetricsPluginCacheDirs, metricsPluginCacheRoots } from '../src/metrics-plugin.js';
14
15
  import { inspectToolchain, installToolchain, loadToolchain } from '../src/toolchain.js';
15
16
  import fs from 'node:fs';
16
17
  import { inspectVSCodeInstallation, installVSCode } from '../src/install-vscode.js';
17
- import { codexConfigRoot, inspectCodexInstallation, installCodex } from '../src/install-codex.js';
18
+ import { isSkillHost, skillHosts, skillHostConfigRoot, inspectSkillHostInstallation, installSkillHost } from '../src/install-skills.js';
19
+ import { installTuiExtension, inspectTuiExtension } from '../src/install-tui-extension.js';
18
20
  import { runWorkflow } from '../src/workflow.js';
19
- const help = `Install this package's skills and agents into OpenCode, DevEco Code or Codex.
21
+ import { selectAgent } from '../src/select-agent.js';
22
+ const help = `Install this package's skills and agents into supported AI coding tools.
20
23
 
21
24
  Usage: d2h [options]
22
25
 
@@ -24,7 +27,7 @@ Usage: d2h [options]
24
27
  d2h upgrade Reapply this package's tested toolchain and definitions
25
28
  d2h upgrade --check Preview version and installation status only
26
29
  d2h workflow Validate artifacts, record execution and generate reports
27
- d2h doctor [--agent opencode|deveco|vscode|codex] [--config-dir <path>]
30
+ d2h doctor [--agent <name>] [--config-dir <path>]
28
31
  Check definitions, host environment and CLI capabilities
29
32
 
30
33
  --check-env Check host tools and write reports without installing skills
@@ -32,14 +35,18 @@ Usage: d2h [options]
32
35
  Use d2h --check-env --help for check options
33
36
 
34
37
  --source-dir <path> Source root (defaults to this package)
35
- --agent <name> opencode (default), deveco, vscode or codex
38
+ --agent <name> opencode, vscode, deveco, codex, qoder, trae,
39
+ trae-cn, claude, dsh, zcode, codebuddy or workbuddy
40
+ Omit for interactive selection with local detection hints
36
41
  --extension-dir <path> VS Code HarmonyOS Dev Assistant extension root
37
42
  --config-dir <path> Override the selected agent's config root
38
43
  OPENCODE_CONFIG_DIR / DEVECO_CONFIG_DIR, or
39
44
  $XDG_CONFIG_HOME/<agent>, or ~/.config/<agent>
40
- Codex: ~/.agents (skills under skills/; --config-dir overrides)
45
+ Codex: $CODEX_HOME or ~/.codex; other skill hosts: ~/.<agent>
46
+ Skills under skills/; trae-cn uses ~/.trae-cn
47
+ Claude/DSH also honor CLAUDE_CONFIG_DIR/DSH_HOME
41
48
  --link Create absolute symbolic links (development only)
42
- --force Replace conflicting definitions and managed CLI npm links
49
+ --force Replace conflicting definitions and managed CLI npm links (default)
43
50
  --remote Use opencode-metrics-plugin instead of the default lite plugin
44
51
  --dry-run Print operations without writing files
45
52
  --skip-tools Install definitions only (local development)
@@ -75,10 +82,10 @@ try {
75
82
  else
76
83
  throw new Error(`unknown doctor option: ${arg}`);
77
84
  }
78
- if (!['opencode', 'deveco', 'vscode', 'codex'].includes(agent))
85
+ if (!['opencode', 'deveco', 'vscode'].includes(agent) && !isSkillHost(agent))
79
86
  throw new Error(`unsupported agent: ${agent}`);
80
- configDir ??= agent === 'codex' ? codexConfigRoot() : process.env[agent === 'deveco' ? 'DEVECO_CONFIG_DIR' : 'OPENCODE_CONFIG_DIR'] || path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'), agent);
81
- const installation = agent === 'vscode' ? inspectVSCodeInstallation(extensionDir) : agent === 'codex' ? inspectCodexInstallation(configDir) : inspectInstallation(configDir);
87
+ configDir ??= isSkillHost(agent) ? skillHostConfigRoot(agent) : process.env[agent === 'deveco' ? 'DEVECO_CONFIG_DIR' : 'OPENCODE_CONFIG_DIR'] || path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'), agent);
88
+ const installation = agent === 'vscode' ? inspectVSCodeInstallation(extensionDir) : isSkillHost(agent) ? inspectSkillHostInstallation(agent, configDir) : inspectInstallation(configDir);
82
89
  const packageVersion = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8')).version;
83
90
  const definitionsMatch = installation.status === 'passed' && installation.packageVersion === packageVersion &&
84
91
  (agent === 'vscode' || ('agent' in installation && installation.agent === agent));
@@ -86,7 +93,13 @@ try {
86
93
  const results = inspectToolchain(packageRoot);
87
94
  for (const item of results)
88
95
  console.log(`${item.status}: ${item.package} expected ${item.expectedVersion}, installed ${item.installedVersion ?? 'missing'}; capabilities ${JSON.stringify(item.capabilities)}`);
89
- const checkerUrl = new URL('../../scripts/check-environment.mjs', import.meta.url);
96
+ const pluginCache = isSkillHost(agent) ? [] : findMetricsPluginCacheDirs(metricsPluginCacheRoots(agent));
97
+ console.log(`plugin cache: ${pluginCache.length ? `${pluginCache.join(', ')}; cleared by d2h install/upgrade` : 'none'}`);
98
+ if (agent === 'opencode' || agent === 'vscode') {
99
+ const tui = inspectTuiExtension({ agent: agent === 'vscode' ? 'vscode' : 'opencode', extensionDir });
100
+ console.log(`${tui.status}: opencode-tui-extension ${tui.summary}`);
101
+ }
102
+ const checkerUrl = new URL('../../tooling/scripts/check-environment.mjs', import.meta.url);
90
103
  const { main } = await import(__rewriteRelativeImportExtension(checkerUrl.href));
91
104
  const environmentExit = await main([]);
92
105
  if (environmentExit !== 0 || !definitionsMatch || results.some(item => item.status !== 'passed'))
@@ -95,7 +108,7 @@ try {
95
108
  else if (args.includes('--check-env')) {
96
109
  if (args.filter(arg => arg === '--check-env').length !== 1)
97
110
  throw new Error('--check-env may only be specified once');
98
- const checkerUrl = new URL('../../scripts/check-environment.mjs', import.meta.url);
111
+ const checkerUrl = new URL('../../tooling/scripts/check-environment.mjs', import.meta.url);
99
112
  const { main } = await import(__rewriteRelativeImportExtension(checkerUrl.href));
100
113
  process.exitCode = await main(args.filter(arg => arg !== '--check-env'));
101
114
  }
@@ -109,7 +122,7 @@ try {
109
122
  let remote = false;
110
123
  const options = {
111
124
  sourceDir: packageRoot,
112
- agent: 'opencode',
125
+ force: true,
113
126
  };
114
127
  for (let index = 0; index < installArgs.length; index++) {
115
128
  const arg = installArgs[index];
@@ -142,43 +155,50 @@ try {
142
155
  throw new Error(`unknown option: ${arg}`);
143
156
  }
144
157
  }
145
- if (!options.agent || !['opencode', 'deveco', 'vscode', 'codex'].includes(options.agent)) {
146
- throw new Error(`unsupported agent: ${options.agent}; choose opencode, deveco, vscode or codex`);
158
+ options.agent ??= await selectAgent(extensionDir);
159
+ if (!options.agent || (!['opencode', 'deveco', 'vscode'].includes(options.agent) && !isSkillHost(options.agent))) {
160
+ throw new Error(`unsupported agent: ${options.agent}; choose opencode, deveco, vscode or ${Object.keys(skillHosts).join(', ')}`);
147
161
  }
148
- if (options.agent === 'codex' && remote)
149
- throw new Error('--remote is not supported by Codex');
162
+ if (isSkillHost(options.agent) && remote)
163
+ throw new Error(`--remote is not supported by ${skillHosts[options.agent].label}`);
150
164
  if (options.agent !== 'vscode' && extensionDir)
151
165
  throw new Error('--extension-dir requires --agent vscode');
152
166
  if (options.agent === 'vscode' && options.configDir)
153
167
  throw new Error('--config-dir is not used for VS Code; use --extension-dir');
154
- options.configDir ??= options.agent === 'codex' ? codexConfigRoot() : process.env[options.agent === 'deveco' ? 'DEVECO_CONFIG_DIR' : 'OPENCODE_CONFIG_DIR'] || path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'), options.agent);
168
+ options.configDir ??= isSkillHost(options.agent) ? skillHostConfigRoot(options.agent) : process.env[options.agent === 'deveco' ? 'DEVECO_CONFIG_DIR' : 'OPENCODE_CONFIG_DIR'] || path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'), options.agent);
155
169
  if (checkOnly) {
156
- const installation = options.agent === 'vscode' ? inspectVSCodeInstallation(extensionDir) : options.agent === 'codex' ? inspectCodexInstallation(options.configDir) : inspectInstallation(options.configDir);
170
+ const installation = options.agent === 'vscode' ? inspectVSCodeInstallation(extensionDir) : isSkillHost(options.agent) ? inspectSkillHostInstallation(options.agent, options.configDir) : inspectInstallation(options.configDir);
157
171
  console.log(`definitions ${installation.status}: ${installation.summary}`);
158
172
  for (const item of inspectToolchain(packageRoot))
159
173
  console.log(`${item.status}: ${item.package} expected ${item.expectedVersion}, installed ${item.installedVersion ?? 'missing'}`);
174
+ const pluginCache = isSkillHost(options.agent) ? [] : findMetricsPluginCacheDirs(metricsPluginCacheRoots(options.agent));
175
+ console.log(`plugin cache: ${pluginCache.length ? `${pluginCache.join(', ')}; cleared by d2h install/upgrade` : 'none'}`);
160
176
  }
161
177
  else if (!skipTools)
162
178
  installToolchain(packageRoot, { dryRun: options.dryRun, force: options.force });
163
179
  else if (!explicitSource && !options.dryRun)
164
180
  console.log(`Skipped managed CLI installation (${Object.keys(loadToolchain(packageRoot).tools).join(', ')}).`);
165
181
  if (!checkOnly) {
166
- if (options.agent === 'vscode')
167
- installVSCode({ sourceDir: options.sourceDir, extensionDir, remote, dryRun: options.dryRun });
168
- else if (options.agent === 'codex')
169
- installCodex({ ...options, remote });
182
+ let vscodeExtensionRoot;
183
+ if (options.agent === 'vscode') {
184
+ vscodeExtensionRoot = installVSCode({ sourceDir: options.sourceDir, extensionDir, remote, dryRun: options.dryRun }).extensionRoot;
185
+ }
186
+ else if (isSkillHost(options.agent))
187
+ installSkillHost(options.agent, { ...options, remote });
170
188
  else
171
189
  installOpenCode({ ...options, remote });
190
+ if ((options.agent === 'opencode' || options.agent === 'vscode') && !skipTools)
191
+ installTuiExtension({ dryRun: options.dryRun, force: options.force, extensionRoot: vscodeExtensionRoot });
172
192
  if (!options.dryRun) {
173
193
  const target = options.agent === 'vscode' ? 'VS Code HarmonyOS Dev Assistant' :
174
- options.agent === 'deveco' ? 'DevEco Code' : options.agent === 'codex' ? 'Codex' : 'OpenCode';
194
+ options.agent === 'deveco' ? 'DevEco Code' : isSkillHost(options.agent) ? skillHosts[options.agent].label : 'OpenCode';
175
195
  const packageVersion = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8')).version;
176
196
  console.log('\n============================================================');
177
197
  console.log(`[D2H] Installation successful / 安装成功`);
178
198
  console.log(`Target / 目标: ${target}`);
179
199
  console.log(`Version / 版本: ${packageVersion}`);
180
200
  console.log('Agent: d2h');
181
- console.log(options.agent === 'codex' ? 'Next / 下一步: start a new Codex conversation and invoke $d2h.' : `Next / 下一步: restart ${target}, then select the d2h Agent.`);
201
+ console.log(isSkillHost(options.agent) ? `Next / 下一步: start a new ${target} conversation and invoke ${skillHosts[options.agent].invocation}.` : `Next / 下一步: restart ${target}, then select the d2h Agent.`);
182
202
  console.log('============================================================');
183
203
  }
184
204
  }
@@ -1,131 +1,10 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import os from 'node:os';
4
- import { stat, digest, sameTree, contains, resolvedLocation } from './install-opencode.js';
1
+ import { installSkillHost, inspectSkillHostInstallation, skillHostConfigRoot } from './install-skills.js';
5
2
  export function codexConfigRoot() {
6
- return path.join(os.homedir(), '.agents');
7
- }
8
- // Keep the orchestration prompt as an explicitly invokable skill: custom agents
9
- // are subagents in Codex, whereas D2H needs to drive the main conversation.
10
- function definitions(sourceRoot) {
11
- const skills = path.join(sourceRoot, 'skills');
12
- const entries = fs.readdirSync(skills).sort()
13
- .filter(name => fs.existsSync(path.join(skills, name, 'SKILL.md')))
14
- .map(name => ({ name, source: path.join(skills, name) }));
15
- if (!entries.length)
16
- throw new Error(`no skills found under ${skills}`);
17
- if (entries.some(entry => entry.name === 'd2h'))
18
- throw new Error('reserved Codex orchestration skill: d2h');
19
- const prompt = fs.readFileSync(path.join(sourceRoot, 'agents', 'd2h.md'), 'utf8');
20
- if (!/^---\r?\n[\s\S]*?\r?\n---\r?\n/.test(prompt))
21
- throw new Error('d2h agent front matter missing');
22
- return [...entries, { name: 'd2h', content: prompt }];
3
+ return skillHostConfigRoot('codex');
23
4
  }
24
5
  export function inspectCodexInstallation(configDir = codexConfigRoot()) {
25
- const root = path.resolve(configDir);
26
- const receipt = path.join(root, '.d2h-codex-installation.json');
27
- if (!fs.existsSync(receipt))
28
- return { status: 'missing', summary: `installation record missing: ${receipt}` };
29
- const record = JSON.parse(fs.readFileSync(receipt, 'utf8'));
30
- if (record.schemaVersion !== 1 || record.agent !== 'codex' || !Array.isArray(record.entries) || !record.entries.length) {
31
- throw new Error(`invalid installation record: ${receipt}`);
32
- }
33
- const changed = record.entries.filter(entry => {
34
- if (!/^[a-zA-Z0-9_-][a-zA-Z0-9_.-]*$/.test(entry.name))
35
- throw new Error(`invalid installation entry: ${entry.name}`);
36
- const target = path.join(root, 'skills', entry.name);
37
- return !stat(target) || digest(target) !== entry.digest;
38
- });
39
- return { status: changed.length ? 'failed' : 'passed', agent: 'codex', packageVersion: record.packageVersion,
40
- summary: changed.length ? `changed or missing: ${changed.map(entry => entry.name).join(', ')}` : `${record.entries.length} installed skills verified` };
6
+ return inspectSkillHostInstallation('codex', configDir);
41
7
  }
42
- export function installCodex({ sourceDir, configDir = codexConfigRoot(), link = false, force = false, dryRun = false, remote = false, log = console.log }) {
43
- if (remote)
44
- throw new Error('--remote metrics plugins are not supported by Codex');
45
- if (!configDir || ['.', '..'].includes(configDir))
46
- throw new Error(`unsafe Codex config directory: ${configDir}`);
47
- const sourceRoot = fs.realpathSync(path.resolve(sourceDir));
48
- const root = resolvedLocation(path.resolve(configDir));
49
- if (root === path.parse(root).root || root === fs.realpathSync(os.homedir()) ||
50
- contains(sourceRoot, root) || contains(root, sourceRoot))
51
- throw new Error(`unsafe Codex config directory: ${configDir}`);
52
- const skillsRoot = path.join(root, 'skills');
53
- if (stat(skillsRoot)?.isSymbolicLink())
54
- throw new Error(`refusing linked installation directory: ${skillsRoot}`);
55
- const receipt = path.join(root, '.d2h-codex-installation.json');
56
- if (stat(receipt)?.isSymbolicLink())
57
- throw new Error(`refusing linked installation record: ${receipt}`);
58
- const entries = definitions(sourceRoot).map(entry => {
59
- const target = path.join(skillsRoot, entry.name);
60
- const existing = stat(target);
61
- const unchanged = entry.source ? (link && existing?.isSymbolicLink()
62
- ? path.resolve(path.dirname(target), fs.readlinkSync(target)) === entry.source
63
- : !link && sameTree(entry.source, target))
64
- : existing?.isDirectory() && !existing.isSymbolicLink() &&
65
- fs.readdirSync(target).length === 1 && fs.existsSync(path.join(target, 'SKILL.md')) &&
66
- fs.readFileSync(path.join(target, 'SKILL.md'), 'utf8') === entry.content;
67
- if (!unchanged && existing && !force)
68
- throw new Error(`skill '${entry.name}' already exists at ${target}; rerun with --force`);
69
- return { ...entry, target, unchanged };
70
- });
71
- if (dryRun) {
72
- for (const entry of entries)
73
- log(`would install Codex skill: ${entry.name} -> ${entry.target}`);
74
- return { skills: entries.length, configRoot: root };
75
- }
76
- fs.mkdirSync(skillsRoot, { recursive: true });
77
- if (!contains(root, fs.realpathSync(skillsRoot)))
78
- throw new Error(`installation escaped config directory: ${skillsRoot}`);
79
- for (const entry of entries) {
80
- if (entry.unchanged) {
81
- log(`unchanged Codex skill: ${entry.name}`);
82
- continue;
83
- }
84
- const staging = fs.mkdtempSync(path.join(skillsRoot, '.d2h-install-'));
85
- const staged = path.join(staging, 'new');
86
- const backup = path.join(staging, 'backup');
87
- try {
88
- if (entry.source) {
89
- if (link)
90
- fs.symlinkSync(entry.source, staged, process.platform === 'win32' ? 'junction' : 'dir');
91
- else
92
- fs.cpSync(entry.source, staged, { recursive: true });
93
- }
94
- else {
95
- fs.mkdirSync(staged);
96
- fs.writeFileSync(path.join(staged, 'SKILL.md'), entry.content);
97
- }
98
- if (stat(entry.target))
99
- fs.renameSync(entry.target, backup);
100
- try {
101
- fs.renameSync(staged, entry.target);
102
- }
103
- catch (error) {
104
- if (stat(backup))
105
- fs.renameSync(backup, entry.target);
106
- throw error;
107
- }
108
- }
109
- finally {
110
- if (stat(backup) && !stat(entry.target))
111
- log(`installation backup retained: ${backup}`);
112
- else
113
- fs.rmSync(staging, { recursive: true, force: true });
114
- }
115
- log(`installed Codex skill: ${entry.target}`);
116
- }
117
- const packageFile = path.join(sourceRoot, 'package.json');
118
- const packageVersion = fs.existsSync(packageFile) ? JSON.parse(fs.readFileSync(packageFile, 'utf8')).version : 'source';
119
- const temporary = `${receipt}.${process.pid}.tmp`;
120
- try {
121
- fs.writeFileSync(temporary, JSON.stringify({ schemaVersion: 1, agent: 'codex', packageVersion,
122
- entries: entries.map(entry => ({ name: entry.name, digest: digest(entry.target) })) }, null, 2) + '\n');
123
- fs.renameSync(temporary, receipt);
124
- }
125
- finally {
126
- if (fs.existsSync(temporary))
127
- fs.rmSync(temporary);
128
- }
129
- log(`Codex installation complete: ${entries.length} skill(s). Start a new conversation and invoke $d2h.`);
130
- return { skills: entries.length, configRoot: root };
8
+ export function installCodex(options) {
9
+ return installSkillHost('codex', options);
131
10
  }
@@ -2,7 +2,7 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import os from 'node:os';
4
4
  import crypto from 'node:crypto';
5
- import { updateMetricsPlugin } from './metrics-plugin.js';
5
+ import { clearMetricsPluginCache, findMetricsPluginCacheDirs, metricsPluginCacheRoots, updateMetricsPlugin } from './metrics-plugin.js';
6
6
  export function stat(file) {
7
7
  try {
8
8
  return fs.lstatSync(file);
@@ -136,7 +136,7 @@ export function resolvedLocation(directory) {
136
136
  const parent = path.dirname(directory);
137
137
  return parent === directory ? directory : path.join(resolvedLocation(parent), path.basename(directory));
138
138
  }
139
- export function installOpenCode({ sourceDir, configDir, agent = 'opencode', link = false, force = false, dryRun = false, remote = false, log = console.log }) {
139
+ export function installOpenCode({ sourceDir, configDir, agent = 'opencode', link = false, force = false, dryRun = false, remote = false, pluginCacheRoots, log = console.log }) {
140
140
  if (!['opencode', 'deveco'].includes(agent))
141
141
  throw new Error(`unsupported agent: ${agent}`);
142
142
  const application = agent === 'deveco' ? 'DevEco Code' : 'OpenCode';
@@ -257,6 +257,16 @@ export function installOpenCode({ sourceDir, configDir, agent = 'opencode', link
257
257
  fs.rmSync(temporary);
258
258
  }
259
259
  }
260
+ const pluginCacheRoot = pluginCacheRoots ?? metricsPluginCacheRoots(agent);
261
+ if (dryRun) {
262
+ for (const dir of findMetricsPluginCacheDirs(pluginCacheRoot))
263
+ log(`would remove metrics plugin cache: ${dir}`);
264
+ }
265
+ else {
266
+ const cleared = clearMetricsPluginCache({ roots: pluginCacheRoot, log });
267
+ for (const failure of cleared.failures)
268
+ log(`warning: failed to clear metrics plugin cache: ${failure}`);
269
+ }
260
270
  log(`${application} installation ${dryRun ? 'preview' : 'complete'}: ${skills} skill(s), ${agents} agent(s).`);
261
271
  log(`Config directory: ${configRoot}`);
262
272
  if (!dryRun)