@harmonyos-arkts/d2h 0.1.26 → 0.1.28

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 (52) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/AGENTS.md +2 -0
  3. package/README.md +34 -2
  4. package/agents/d2h.md +24 -23
  5. package/dist/bin/install-opencode.js +36 -24
  6. package/dist/src/install-codex.js +5 -126
  7. package/dist/src/install-skills.js +147 -0
  8. package/dist/src/install-tui-extension.js +133 -0
  9. package/dist/src/install-vscode.js +2 -2
  10. package/dist/src/toolchain.js +2 -2
  11. package/docs/environment-check.md +3 -3
  12. package/package.json +6 -6
  13. package/skills/arkts-mvvm-architecture/SKILL.md +2 -0
  14. package/skills/d2h-architecture-plan/SKILL.md +21 -19
  15. package/skills/d2h-architecture-plan/references/architecture-plan.example.json +122 -0
  16. package/skills/d2h-architecture-plan/references/directory-creation.md +27 -29
  17. package/skills/d2h-architecture-plan/references/hybrid-design.md +13 -15
  18. package/skills/d2h-architecture-plan/references/json-contract.md +49 -0
  19. package/skills/d2h-architecture-plan/references/layout-reference.md +30 -27
  20. package/skills/d2h-architecture-plan/references/plan-contract.md +27 -32
  21. package/skills/d2h-architecture-plan/references/source-review.md +1 -1
  22. package/skills/d2h-architecture-plan/scripts/architecture_plan.py +147 -0
  23. package/skills/d2h-architecture-plan/scripts/test_architecture_plan.py +241 -0
  24. package/skills/d2h-dashboard/SKILL.md +12 -1
  25. package/skills/d2h-spec-develop/SKILL.md +52 -42
  26. package/skills/d2h-spec-generate/SKILL.md +30 -29
  27. package/skills/d2h-spec-plan/SKILL.md +20 -16
  28. package/skills/d2h-spec-plan/agents/openai.yaml +3 -3
  29. package/skills/d2h-spec-plan/references/plan-contract.md +32 -7
  30. package/skills/d2h-spec-plan/references/planning-method.md +12 -62
  31. package/skills/d2h-spec-plan/references/planning-review.md +9 -21
  32. package/skills/d2h-spec-plan/references/round-scope-assessment.md +12 -39
  33. package/skills/d2h-spec-plan/references/work-packets.md +6 -28
  34. package/{install-opencode.sh → tooling/install-opencode.sh} +3 -3
  35. package/{opencode → tooling/opencode}/interactive-tools/advance-progress-demo.mjs +1 -1
  36. package/{opencode → tooling/opencode}/interactive-tools/demo.mjs +3 -3
  37. package/{opencode → tooling/opencode}/interactive-tools/prepare-demo.mjs +1 -1
  38. package/{scripts → tooling/scripts}/check-environment.mjs +3 -3
  39. /package/{opencode → tooling/opencode}/agents.json +0 -0
  40. /package/{opencode → tooling/opencode}/interactive-tools/browser-host.ts +0 -0
  41. /package/{opencode → tooling/opencode}/interactive-tools/core.ts +0 -0
  42. /package/{opencode → tooling/opencode}/interactive-tools/demo-workflow.ts +0 -0
  43. /package/{opencode → tooling/opencode}/interactive-tools/package-lock.json +0 -0
  44. /package/{opencode → tooling/opencode}/interactive-tools/package.json +0 -0
  45. /package/{opencode → tooling/opencode}/interactive-tools/panel.ts +0 -0
  46. /package/{opencode → tooling/opencode}/interactive-tools/prepare-progress-demo.mjs +0 -0
  47. /package/{opencode → tooling/opencode}/interactive-tools/progress.ts +0 -0
  48. /package/{opencode → tooling/opencode}/interactive-tools/server-legacy.mjs +0 -0
  49. /package/{opencode → tooling/opencode}/interactive-tools/server.mjs +0 -0
  50. /package/{opencode → tooling/opencode}/interactive-tools/tui.tsx +0 -0
  51. /package/{opencode → tooling/opencode}/vscode-d2h-package.json +0 -0
  52. /package/{opencode → tooling/opencode}/vscode-d2h-plugin.mjs +0 -0
@@ -0,0 +1,49 @@
1
+ # 最终方案 JSON:只读展示契约
2
+
3
+ 用户完成方案确认、处理影响目录的修改后,才生成 `architecture-plan.json`。看板只展示结果和展开/折叠目录,不进行选型、确认或选择回传。完整示例见 [architecture-plan.example.json](architecture-plan.example.json),示例是小型邮件应用的已选单 HAP 结构。
4
+
5
+ ## 固定结构
6
+
7
+ 默认位置:`<Android工程同级>/<工程名>-migration/.migration/architecture-plan/architecture-plan.json`。使用 UTF-8、标准 JSON,不含注释、代码围栏或占位内容。顶层只有三个字段:
8
+
9
+ | 字段 | 类型 | 含义 |
10
+ |---|---|---|
11
+ | `projectName` | 非空字符串 | 当前规划的项目名称 |
12
+ | `directoryTree` | 目录节点对象 | 用户最终确认的一整套目录,根相对于实际鸿蒙目标根 |
13
+ | `decisions` | 非空对象数组 | 最终方案的主要架构决定及依赖说明,包含理由、职责边界与必要装配 |
14
+
15
+ 不增加协议版本、修订号、规划/请求 ID。选定方案身份、实际用户回答、工程绝对路径、详细证据保存在 `architecture-plan.md`。JSON 表示规划结构,不表示目录已经创建或应用已经实现。
16
+
17
+ ### 目录节点
18
+
19
+ | 字段 | 类型 | 规则 |
20
+ |---|---|---|
21
+ | `name` | 非空字符串 | 当前目录名;除根外等于 `path` 的最后一段 |
22
+ | `path` | 非空字符串 | 完整的目标根相对路径,统一 `/`;根为 `.` |
23
+ | `description` | 非空字符串 | 该路径的职责与边界,必要时说明保留或平台适配 |
24
+ | `children` | 节点数组 | 直接子目录,叶子固定为 `[]` |
25
+ | `kind` | 可选字符串 | 实际模块根填写 `HAP` / `HAR` / `HSP`;普通目录省略或为 `directory` |
26
+
27
+ 根节点 `name` 可用项目名,`kind` 为普通目录。每层必须真实嵌套,例如 `entry → src → main → ets`,不能把 `src/main/ets` 合并成一个子节点。模块源码树内的 feature、页面、共享目录均为普通目录,不嵌入另一个模块根;源码树外的模块分组路径不等于构建模块嵌套,实际注册与可用性仍需核对构建配置。
28
+
29
+ 树包含最终选定方案的实际模块和关键目录,至少有一个 HAP,并展开每个 HAP 的 `src/main` 生产目录及关键子目录,不能只标记一个空 HAP。实际源码根按目标工程类型确定,ArkTS 工程通常为 `src/main/ets`;结构校验不据此限制源码语言。按实际需要展示 `AppScope`、模块资源及其他职责目录,不要求列文件。路径必须唯一,不能有大小写冲突、绝对路径、上级跳转、反斜杠、通配符、占位符或 Windows 非法名称。路径顺序按阅读顺序组织。
30
+
31
+ ### 主要决定与依赖 `decisions[]`
32
+
33
+ 每项固定为 `title`、`description` 两个非空字符串。`title` 概括决定;`description` 简述保留/拆分/合并、模块类型、职责与共享所有权、依赖方向、装配或平台适配的最终安排及原因。只保留影响理解结构的主要决定,不罗列一般原则、全部风险或实现任务。
34
+
35
+ 已确认的范围决定可说明其对目录的影响;技术未知写清保留的边界与后续核验前置,不能借此声称已验证、擅自删功能或要求用户逐项答题。
36
+
37
+ 依赖说明写在相关决定的 `description` 中,也可单列“依赖方向与装配”这一决定:用实际目录名/路径说明谁使用谁的公开能力、谁创建和提供共享实例,区分源中保留与目标新增/替代的依赖。不再生成独立 `dependencies` 数组,也不向决定对象增加关系字段。修订已有四字段 JSON 时,先合并有效依赖说明,再移除旧数组,不丢弃当前仍适用的决定。
38
+
39
+ 提及的目标路径须与树一致,交付时人工复核;不机械展开全部 import、不以父子目录关系或任务先后充数、不用通配符和“products(entry)”别名。运行时回调与代码依赖方向分开解释,未核验的协作前置如实标明。
40
+
41
+ ## 校验与交接
42
+
43
+ ```text
44
+ python <本Skill目录>/scripts/architecture_plan.py validate <产物目录>/architecture-plan.json
45
+ ```
46
+
47
+ Python 3.10+,标准库。校验器只读文件,检查字段、类型、目录层级/路径/模块;返回退出码 0 和 `status: valid`,失败返回非零和错误说明。它不解析决定中的自然语言依赖,不判断用户是否已确认、实际目标目录是否存在、决定是否正确或代码是否实现。
48
+
49
+ 按主流程在用户回答后生成 JSON 和 Markdown;JSON 先在临时文件中生成、校验,再替换正式文件,失败不把临时数据交给看板。调用方交接 JSON 路径即可,看板的加载/刷新由外部适配实现。本 Skill 不提供选择端点、回传协议、监听服务或自动唤醒。
@@ -1,6 +1,6 @@
1
1
  # 架构选择与目录参考
2
2
 
3
- 先决定职责、状态所有权、协作和演进边界,再决定目录。以下给出判断方法及可选目录示例;名称、分层与模块数量由当前工程职责和维护需求决定。
3
+ 先决定职责、状态所有权、协作和演进边界,再决定目录。以下给出选型原则与目录职责参考;名称、分层与模块数量由当前工程职责和维护需求决定。
4
4
 
5
5
  这些决定使用结构地图与必要的边界证据即可,不要求先验证全部业务行为。共享结论在三套方案间复用;本参考用于选择结构,阅读深度与停止条件见 [source-review.md](source-review.md)。
6
6
 
@@ -17,15 +17,38 @@
17
17
 
18
18
  新工程也评价原作者的维护便利性:哪些业务名和包族能沿用、变化后从哪里找页面/状态/数据、常见修改需跨多少模块、故障怎样回溯源码。可读性来自职责与一致命名,不来自把所有Java包原样堆进ArkTS,也不来自一律改成陌生名字。
19
19
 
20
+ ### HAP、HAR、HSP 怎么选
21
+
22
+ 先确定业务职责,再决定是否需要独立构建模块。这里的 `feature` 指业务目录,不等于 `type: feature` 的 HAP;`features`、`commons` 等目录名也不决定包类型。
23
+
24
+ | 组织方式 | 采用条件 | 对目录设计的影响 |
25
+ |---|---|---|
26
+ | HAP 内的普通目录 | 页面/业务只服务当前产品,单独拆库没有明显收益 | 在产品 HAP 的源码根内按业务分组,不额外创建模块 |
27
+ | HAP | 需要应用安装/部署边界:entry 承载产品主入口,feature HAP 承载有独立部署或按需安装要求的功能 | 明确入口、设备和安装要求;不能让产品 HAP 直接 import 另一个 HAP 的接口/组件,跨 HAP 通过 Ability 等机制协作 |
28
+ | HAR | 需要封装供 HAP/HSP 引用的代码、页面、组件或资源,或者发布可复用库 | 编译时合入引用模块,不独立安装;多个 HAP/HSP 引用同一 HAR 可能重复打包,单个产品引用业务 HAR 也可以成立 |
29
+ | HSP | 同一应用内多个 HAP/HSP 需要在运行时共享代码/资源,或有明确的按需加载/元服务分包需求,收益足以抵偿部署与配置成本 | 单独产出共享包,核对真实消费者、加载需求、包大小和部署约束;不能仅因多个页面共用状态就建 HSP |
30
+
31
+ 以下边界会影响选型,按当前目标 SDK 核验,不照搬旧对照表:
32
+
33
+ - HAR 可以包含页面,由宿主通过 Navigation 等方式打开,但不能在 HAR 的配置中声明 `pages`;HAR 自身资源不引用 `AppScope`。因此“包含业务页面”不是改为 HAP 的理由。
34
+ - Ability 声明有版本条件:当前官方资料中,HAR/HSP 从 API 14 支持声明 UIAbility、从 API 18 支持声明部分 ExtensionAbility,仍有入口能力等限制。不能把“库模块一律不能声明 Ability”写成永久规则。
35
+ - HSP 的运行时共享以宿主应用为边界;集成态 HSP 可以供不同应用工程复用,但会按各宿主重新集成和签名,不是不同应用共用一个运行实例。多个品牌/产品若产出不同应用,不能因为同处一个工程就当成应用内多 HAP 共享状态。
36
+ - HAR/HSP 之间不支持循环依赖,也不自动传递依赖。引用下层库的能力时核对实际依赖和公开导出,不靠目录层级或 Gradle 的传递规则推断目标可编译。
37
+ - HAP/HSP 的安装需满足签名、应用身份与版本等一致性规则;不要把这些规则误写成 HAR 库版本必须等于宿主版本。普通应用内 HSP 可与依赖它的 HAP 一起部署,不把“手工先装 HSP 再装 HAP”定为所有项目的固定流程。
38
+
39
+ 在主要决定中简记实际采用的模块类型及理由,并说明引用或跨 HAP 协作方式。代码/资源共享不保证业务状态只有一个实例,仍需说明由谁创建和提供;跨进程状态另按通信与存储方式设计。
40
+
20
41
  ## 2. 三套方案如何区别
21
42
 
22
43
  | 方案 | 目录/边界约束 | 必须解释 |
23
44
  |---|---|---|
24
45
  | android-mirror | 运行时源模块一对一对照,保留源模块名、相对布局、关键包族与内部模块声明依赖;构建辅助项目不变成目标业务库 | 外部SDK/API保留能力与消费者对照,不照搬AndroidX等包坐标;Ability/资源/语言及运行时必要适配;源技术债、环或不可行的边不能伪装成鸿蒙可实施结构 |
25
46
  | harmony-practice | 依据目标职责重组,按消费者、数据所有权与平台边界选择目录和模块粒度;允许单HAP或适当模块化 | 具体收益、消费者和数据流;新增命名、拆合及平台适配成本,不以“最佳实践”替代论证 |
26
- | hybrid | 基础依赖层优先保留原模块名、包族及合理依赖;业务层按页面或紧密页面组组织 feature,应用壳负责装配 | 基础保留与适配例外、页面边界及共享业务所有者;详细展开时按 [hybrid-design.md](hybrid-design.md) 设计,不能仅改变第二套顶层名字 |
47
+ | hybrid | 基础依赖层优先保留原模块名、包族及合理依赖;业务层按页面或紧密页面组组织 feature,应用壳负责装配 | 基础保留与适配例外、页面边界及共享业务所有者;按 [hybrid-design.md](hybrid-design.md) 设计,不能仅改变第二套顶层名字 |
48
+
49
+ 依据实际收益和维护代价判断推荐哪一套。
27
50
 
28
- 简单应用三套可以有相同模块数甚至相同树;说明相同决定与缺少额外拆分收益的原因。推荐可为任一套,依据实际收益和维护代价判断。
51
+ 对于简单应用,三套目录可以有相同模块数甚至相同树;说明相同决定与缺少额外拆分收益的原因。
29
52
 
30
53
  融合方案的两个层次是迁移职责划分,不强制增加两个顶层目录或独立构建模块。下节是通用目录字典;融合方案的原包族保留、页面粒度与共享边界以 [hybrid-design.md](hybrid-design.md) 为准。
31
54
 
@@ -35,7 +58,7 @@
35
58
 
36
59
  多技术栈对照应同时保留Android宿主与实际业务源码的定位线索;不把Dart/JS业务当作新的Gradle模块,也不声称照搬源目录就保留了运行时。用户明确目标设备或范围优先;未知设备支持列为前提,不通过删功能制造可行性。
37
60
 
38
- ## 3. 可选目录组织示例
61
+ ## 3. 通用目录职责
39
62
 
40
63
  项目可按应用装配、业务职责与共享能力组织目录,也可保留已有且合理的结构。以下名称用于示意,按实际职责选取:
41
64
 
@@ -45,30 +68,11 @@
45
68
  | 业务职责 | features / 业务域 | 该域实际存在的页面/状态、规则、数据访问及私有组件 | 保持域内所有权,独立模块按实际收益拆分 |
46
69
  | 共享能力 | commons / core | 有真实跨域消费者的展示、数据/契约或平台设施 | 明确所有权和依赖方向,按职责决定合并或拆分 |
47
70
 
48
- 一种模块化组织示意,按实际需要选取其中的职责和目录;单模块或其他合理结构无需改成此树:
49
-
50
- ```text
51
- <HarmonyOS>/
52
- ├── AppScope/ # 工程基础配置
53
- ├── products/<产品>/src/main/
54
- │ ├── ets/{entryability,navigation,composition}/
55
- │ └── resources/
56
- ├── features/<业务>/src/main/
57
- │ ├── ets/pages/
58
- │ ├── ets/state/ # 或viewmodel,依所选状态模式
59
- │ ├── ets/data/ # 专属Repository/数据源
60
- │ ├── ets/components/ # 业务私有UI,有需要才设
61
- │ ├── ets/domain/ # 仅规则复杂/复用时
62
- │ └── resources/
63
- └── commons/<实际共享能力>/src/main/
64
- ├── ets/ # 如ui、会话数据、网络或协议
65
- ├── cpp/ # 有实际Native构建需求时
66
- └── resources/
67
- ```
71
+ 融合方案的具体目录示例见 [hybrid-design.md](hybrid-design.md),展示基础包族保留、页面拆分与共享业务归属。
68
72
 
69
73
  单模块同样可在 `entry/src/main/ets/<业务>/` 内组织页面、状态和数据;选择products目录也不强制独立features库。业务代码通过清晰接口与平台能力协作,不将Android Activity/Service逐个等同为UIAbility/ExtensionAbility。
70
74
 
71
- 组件按职责与消费者确定归属:域内私有组件留本域,跨域通用展示可放共享UI或独立组件目录;顶层 `components` 与其他命名均按当前结构和维护收益选择。完整业务能力的复用仍需说明其状态、数据与协作边界。数据/协议模块不反向依赖共享UI,应用装配保持独立职责。
75
+ 组件按职责与消费者确定归属:域内私有组件留本域,跨域通用展示可放共享UI或独立组件目录;完整业务能力的复用仍需说明其状态、数据与协作边界。数据/协议模块不反向依赖共享UI,应用装配保持独立职责。
72
76
 
73
77
  对工程实际存在的资源、配置、持久化结构、本地能力或跨运行时依赖,分别说明目标归属;配置差异仅在影响本次迁移时展开。共享能力是否合库,按消费者、上游升级、构建和生命周期决定。
74
78
 
@@ -79,5 +83,4 @@
79
83
  - [Android架构指南](https://developer.android.com/topic/architecture):关注职责分离、数据驱动和状态来源,UI/数据及可选领域层是职责划分。
80
84
  - [Android架构建议](https://developer.android.com/topic/architecture/recommendations):数据通过Repository访问,Domain/UseCase按复杂度及复用需求引入。
81
85
  - [Android模块化模式](https://developer.android.com/topic/modularization/patterns):按业务和共享数据职责组织,模块数量与粒度适应项目。
82
- - [HarmonyOS官方应用规划](https://developer.huawei.com/consumer/cn/app/planning/):结合产品、基础特性与公共能力组织应用。
83
- - [HarmonyOS开发路径](https://developer.huawei.com/consumer/en/app/knowledge-map):模块化及HAR/HSP等目标平台机制;选用时核对实际设备与SDK约束。
86
+ - 包类型及限制:[官方 HAP 文档源码](https://developer.huawei.com/consumer/cn/doc/doccenter-getting-started/hap-package)、[HAR](https://developer.huawei.com/consumer/cn/doc/doccenter-getting-started/har-package)、[HSP](https://developer.huawei.com/consumer/cn/doc/doccenter-getting-started/in-app-hsp)。
@@ -1,52 +1,47 @@
1
- # 规划交付:目录、归属与决策
1
+ # 规划产物:三套对照与最终方案
2
2
 
3
- 默认只写一份 `architecture-plan.md`,保留已有用户批注。目标是让开发者能判断采用哪套结构,并据此创建目录;不为呈现分析过程堆叠报告。
3
+ 用户按 [SKILL.md](../SKILL.md) 完成选型后,`architecture-plan.md` 留存分析、三套对照及真实回答,`architecture-plan.json` 展示最终方案。本文件规定交付内容;JSON 字段见 [json-contract.md](json-contract.md),交互顺序只按主流程执行。
4
4
 
5
- ## 1. 共用一次项目理解
5
+ ## 1. 项目理解与共用范围
6
6
 
7
- 简述应用用途、原结构、目标范围及实际读取范围。已定位鸿蒙目标时,简记实际目标根、模板壳/已有业务/性质未明的判断及证据;模板壳仅作为落地基础。已有明确的结合/不结合鸿蒙业务实现选择一并简记,后续同范围修订沿用;未答复只记默认 Android,不记为拒绝。基础工程可复用与业务能力已实现分开表述,未读取的目标业务不作为已核验的能力复用依据。
7
+ 在 Markdown 简述项目用途、原结构、迁移范围和实际读取范围。用必要的源路径、代表符号或行号支撑影响拆合的结论;声明依赖与实际调用、已核验事实与推断分开,不伪称运行验证。
8
8
 
9
- 把项目理解并入后面的归属表:源路径、职责/层次及必要证据。资源、配置、后台/外部入口和 Native/混合栈边界归入对应区域;事实、候选和未决分开。不另写长篇源码导读、完整阅读清单、文件统计或业务链报告。
9
+ 已定位鸿蒙目标时,记录实际目标根、模板壳/已有业务/性质未明的判断及依据,以及用户是否选择结合业务实现。模板壳仅作落地基础;未读取的目标业务不作已实现能力依据。路径与配置已有值直接引用,不重复维护。
10
10
 
11
- ## 2. 推荐目录详列,另外两套对照
11
+ 原工程分析只保留解释目录决定所需的信息:主要职责、装配、共享数据/状态所有权、非 UI 入口和平台边界。风险合并同类,只写对迁移的影响与核验前置;不堆叠技术名词或工具库清单。这些分析留在 Markdown,不导入最终展示 JSON。
12
12
 
13
- 共用上述源范围,提供 `android-mirror`、`harmony-practice`、`hybrid` 三套结构选项。默认详细展开推荐方案;用户已有选择时展开所选方案,不重新选型。详细方案保留:
13
+ ## 2. 留存三套目录及比较
14
14
 
15
- - **关键目录树**:展开全部拟议实际模块及主要职责/feature 目录,注明 HAP/HAR/HSP 或模块内目录。基础保留方案体现原模块/关键包族的相对层级;业务方案体现页面/页面组及共享区域。只列目录,不逐类造目录;同构 feature 的内部子目录模式可说明一次,无需在每处重复展开。选中创建时再从树和模式展开实际清单。
16
- - **主要决定与依赖**:说明本套保留/拆合/改名、装配与主要依赖方向,必要时用短表或依赖示意。保留的源边、目标新增/替代的边分别表述;目录不等于独立构建模块。
17
- - **收益、成本与风险**:围绕原作者定位、维护、平台边界及拆合成本简要说明,路径定位例子复用归属表。风险只写影响的边界/前置,不为此展开完整替代方案和接口设计。
15
+ 共用同一范围,比较 `android-mirror`、`harmony-practice`、`hybrid`。最终 Markdown 详列三套目录结构与职责、主要差异和取舍,并标明推荐和实际选择;JSON 只保留已选方案的一棵树。同构方案允许结构相同,不制造无依据差异。
18
16
 
19
- 用一张源到详细方案目标的归属表覆盖主要模块/职责区域,合并职责、必要共享所有权及适配说明;混合区域列出保留与移出部分,避免粗目录双重认领。不另加相同粒度的模块表、状态表和页面交接表重复这些事实。已知职责有去向才算目录覆盖,不以文档长度证明完整。
17
+ 推荐须解释维护定位、边界、平台适配和迁移成本,以及会改变推荐的条件。不能仅凭模块数量、命名或“最佳实践”标签判断。融合设计按 [hybrid-design.md](hybrid-design.md):基础依赖保留可追溯包族,业务按页面/页面组及共享所有者组织。
20
18
 
21
- 另外两套用紧凑对照说明模块/包族保留或重组、主要依赖变化、收益/代价;相同部分引用详细方案,只有结构差异难以表达时补局部树,不默认生成三份完整目录树和三列全量映射。用户明确要求三套完整目录时全部展开,但仍共用项目理解、映射和风险。选择另一套后依据差异展开其目录即可,除非边界出现新问题,不重新扫描源码。
19
+ 用关键目录与必要归属说明核对已知职责有去向;源区域混合职责时说明保留和移出部分,共享数据/状态只认一个所有者。简记必要的依赖方向、装配、导航和共同修改归属,不生成全量调用图、机器映射表或逐页实施计划。页面候选不等于最终 Spec,也不代表当前可以并发开发。
22
20
 
23
- ## 3. 融合方案的最小交接
21
+ ## 3. 确认后的最终方案
24
22
 
25
- 融合方案采用 [hybrid-design.md](hybrid-design.md) 的两层原则。详细展开 hybrid 时,把以下信息并入目录/归属表,足以支持后续细化,不额外生成逐页交接表或实施计划;仅作备选时概述与详细方案的差异即可:
23
+ 在 Markdown 记录实际选定方案、回答来源(question 或对话)与回答要点、用户修订与范围决定、实际目标根以及是否授权创建/复用。合并已回答的问题,不能让前文旧待决、旧树与后文确认矛盾。技术未知仍可保留为实施前置,不伪造用户结论。
26
24
 
27
- | 内容 | 本阶段粒度 |
28
- |---|---|
29
- | 基础保留与适配 | 原模块/包族→目标路径、保留部分、必须调整的边界与理由 |
30
- | 页面/页面组候选 | 源入口或页面族→目标 feature 与所属模块,已知共享依赖;不机械把 Activity/Fragment 个数当 feature 数量 |
31
- | 共享业务与协作 | 唯一目标所有者、主要消费者、装配/导航/数据协作方式;未明确的契约列为细化前置 |
32
- | 独立转换条件 | 已知共同修改区域及集成归属、最小能力前置,哪些候选可在条件满足后独立转换,哪些确需合组 |
25
+ Markdown 中明确标记最终方案及其目录归属;选中非推荐方案时也以实际选择生成 JSON,不让推荐树充当最终树。只规划且目标未定位时注明未知,不因此阻止目录方案交付或额外询问目标位置。
33
26
 
34
- 共享状态不复制;共享一个领域会话不自动证明页面应合组。不要求现在确定全部页面 ID、独占测试/资源文件、完整输入输出/错误契约或验收序列;这些细节在选定方案后的页面规划/实现中补齐。候选不是最终 Spec 身份,目录分开也不能证明当前已可并发执行。
27
+ 生成 JSON 前,形成唯一、可落地的最终结构:
35
28
 
36
- ## 4. 推荐、复核与选型
29
+ - 将职责示意路径统一为确认后的实际路径,例如复用 `entry` 就以它作为产品模块路径;不把二选一、别名或未采用的替代路径一起输出。不再采用的方案在 Markdown 中标明对照或历史。
30
+ - 模块类型、模块数量与实际树一致,按 [layout-reference.md](layout-reference.md) 说明 HAP/HAR/HSP 的实际选型理由。普通职责目录不自动成为独立模块;必要的模板适配应在主要决定中说明,不能为了套用模板改变业务范围。
31
+ - 已确认的功能保留、拆分、排除及共享归属反映在树和说明中;不因静态无引用、平台未知或示例中的取舍自动裁剪当前项目。
32
+ - 完整列出选定方案的实际模块、源码根和关键职责/feature 目录。每层独立嵌套,每条路径有简介;不用 `{pages,state}`、省略号、“同上”或不确定路径替代。无需生成逐类目录或文件清单。
37
33
 
38
- 比较三套的维护定位、边界清晰度、平台适配与迁移成本,给基于当前证据的推荐及会改变推荐的条件。模块数量、目录命名和“最佳实践”标签不能替代论证;同构方案不为制造差异而拆库。
34
+ JSON 的 `decisions` 摘录最终方案的主要决定,例如保留包族、拆分巨石 UI、模块类型、共享状态唯一所有者、产品壳与装配、公开契约及平台适配边界。少量关键代码依赖也在决定的说明中表达,指出谁使用谁的能力、如何装配;提及的目标目录须与树一致,不把目录包含关系或实施先后当代码依赖。具体字段只在 JSON 契约定义。
39
35
 
40
- 交付前对照同一张范围/归属表检查:
36
+ ## 4. 复核与交接
41
37
 
42
- - 已知运行时模块、主要职责与资源/非 UI 入口都有去向;未排除用户业务,三套范围一致。
43
- - 树、映射、模块类型及主要依赖互相吻合,原包族保留承诺可定位,共享所有权唯一,无未解释的目标环或反向页面依赖。
44
- - 方案不夹带未验证的平台替代/进程合并/功能裁剪结论;目录待决与实现前置分开,未读取鸿蒙源码时没有伪称目标已实现。
45
- - 空模板的目录、模块数量和默认参数未被当成架构约束;已建基础工程的复用安排与所选目录一致,必须调整的配置或路径假设有交接说明。
46
- - 融合分层与页面候选有证据,已知共同写入点有归属;没有将未细化的契约或目录壳子记为已可开工。
38
+ 交付前复核:
47
39
 
48
- 有矛盾时局部修订并补查相关证据,不重跑全仓分析。架构选择可以在保留实现风险的情况下完成;未知仍影响具体目录时明确条件分支或草案范围。
40
+ - 本次已知运行时职责、资源/配置和外部/后台入口都有去向;三套比较范围一致,没有未经授权删减业务。
41
+ - 最终树、Markdown 归属、模块类型/数量、主要决定和依赖一致;源包族保留承诺可定位,共享所有权唯一,实际 HAR/HSP 模块依赖无环,基础能力不反向依赖页面。
42
+ - 未把未验证的平台替代、进程合并、功能裁剪或目录壳子写成已经实现;不将旧 Preflight 结果当作新布局已构建通过。
43
+ - 当前用户回答已处理,选型和创建授权各有实际依据;不以 JSON 校验替代确认,不把已回答项继续当待决询问。
49
44
 
50
- 默认交付 Markdown 即可,不生成 `source-inventory.json`、`source-baseline.json`、`plan-input.json` 等扫描/中转文件。映射确实过长才单独放链接的 `source-map.md`;用户指定其他产物或下游确需结构化契约时,只补实际需要的字段,不空置字段后宣称已校验。
45
+ 校验最终 JSON 后,向调用方交接 JSON/Markdown 路径、实际目标根和必要前置。有创建授权时按 [directory-creation.md](directory-creation.md) 落地并附 `shell-receipt.json`;否则交付已选方案即可。看板读取最终 JSON 展示,不增加选择或确认步骤。
51
46
 
52
- 只请求规划时交付即可,不强求立即选型。用户选定后记录方案与修改意见,下游沿用实际目录及共享边界;创建时按 [directory-creation.md](directory-creation.md) 执行并记录实际结果,不同时落实三套,不把目录落地写成应用迁移完成。
47
+ 已有规划转为 JSON 时先阅读真实回答与当前结论,不凭“推荐”标题推断用户选择;格式转换规则见 JSON 契约。
@@ -49,7 +49,7 @@
49
49
  | 配置/资源差异改变产品与模块组织 | 生效选择规则、资源族与消费者 | 品牌×渠道的逐项内容比较、精确资源转换 |
50
50
  | 用户已选择结合现有鸿蒙工程 | 所涉模块注册、公开入口和对应职责,确定复用影响 | 无关目标实现的全面审查 |
51
51
 
52
- 平台可得性确实影响模块/运行时选择时才查对应官方资料,记录适用前提;不影响目录的 API 签名、Kit、资源格式与实施细节留到转换阶段。无法取得材料时说明未知与影响,不用连续检索猜结论;仅仓内缺少鸿蒙依赖不能证明没有鸿蒙版。
52
+ 当某项能力、第三方库或运行环境在鸿蒙上的支持情况会影响目录或模块设计时,查阅对应官方资料;其他实现细节留到迁移开发阶段核验。
53
53
 
54
54
  不要凭静态搜索无引用就删除疑似旧页面、flavor 或后台能力;默认保留到相应区域并标记待核实,排除用户业务需有依据。Native/SDK 风险也不能自动变成裁剪或降级决定。默认不运行构建、设备采集或下载依赖。
55
55
 
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env python3
2
+ """Read-only validation of the confirmed architecture shown on the dashboard.
3
+
4
+ Standard library only. User answers and authorization stay in architecture-plan.md;
5
+ valid JSON is neither proof of confirmation nor permission to create a project.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ import json
12
+ from pathlib import Path, PurePosixPath
13
+ import re
14
+ import sys
15
+
16
+
17
+ PLAN_FIELDS = {"projectName", "directoryTree", "decisions"}
18
+
19
+
20
+ class ContractError(ValueError):
21
+ pass
22
+
23
+
24
+ def require(condition: bool, message: str) -> None:
25
+ if not condition:
26
+ raise ContractError(message)
27
+
28
+
29
+ def record(value: object, fields: set[str], label: str, *, optional: set[str] | None = None) -> dict:
30
+ require(isinstance(value, dict), f"{label} must be an object")
31
+ allowed = fields | (optional or set())
32
+ require(fields <= set(value) <= allowed,
33
+ f"{label}: missing fields {sorted(fields - set(value))}; "
34
+ f"unknown fields {sorted(set(value) - allowed)}")
35
+ return value
36
+
37
+
38
+ def string(value: object, label: str) -> str:
39
+ require(isinstance(value, str) and bool(value.strip()), f"{label} must be a non-empty string")
40
+ return value
41
+
42
+
43
+ def array(value: object, label: str, *, nonempty: bool = False) -> list:
44
+ require(isinstance(value, list) and (not nonempty or bool(value)),
45
+ f"{label} must be {'a non-empty' if nonempty else 'an'} array")
46
+ return value
47
+
48
+
49
+ def relative_path(value: object, label: str) -> str:
50
+ value = string(value, label)
51
+ require(value == "." or all(
52
+ part not in ("", ".", "..", "…") and
53
+ not re.search(r'[<>:"\\|?*{}\x00-\x1f]', part) and
54
+ not part.endswith((".", " ")) and
55
+ not re.fullmatch(r"(?i:CON|PRN|AUX|NUL|COM[1-9¹²³]|LPT[1-9¹²³])(?:\..*)?", part)
56
+ for part in value.split("/")
57
+ ), f"{label} must be a normalized, safe root-relative directory path")
58
+ return value
59
+
60
+
61
+ def directory_tree(value: object, label: str = "directoryTree") -> set[str]:
62
+ paths: set[str] = set()
63
+ folded: set[str] = set()
64
+ hap_paths: list[str] = []
65
+
66
+ def visit(node: object, parent: str | None, module_roots: tuple[str, ...]) -> None:
67
+ node = record(node, {"name", "path", "description", "children"}, label, optional={"kind"})
68
+ name = string(node["name"], f"{label}.name")
69
+ path = relative_path(node["path"], f"{label}.path")
70
+ string(node["description"], f"{label}[{path}].description")
71
+ kind = string(node.get("kind", "directory"), f"{label}[{path}].kind")
72
+ require(kind in {"directory", "HAP", "HAR", "HSP"}, f"{label}[{path}].kind invalid")
73
+ if parent is None:
74
+ require(path == "." and kind == "directory", f"{label} root must be '.' / directory")
75
+ else:
76
+ require(path != "." and PurePosixPath(path).name == name and
77
+ str(PurePosixPath(path).parent) == parent,
78
+ f"{label}[{path}] must be a direct child of {parent}, named by its last segment")
79
+ require(path.casefold() not in folded, f"{label}: duplicate/case-conflicting path {path}")
80
+ require(kind == "directory" or all(
81
+ path != f"{root}/src" and not path.startswith(f"{root}/src/") for root in module_roots
82
+ ), f"{label}[{path}]: module inside another module's source tree")
83
+ if kind == "HAP":
84
+ hap_paths.append(path)
85
+ paths.add(path)
86
+ folded.add(path.casefold())
87
+ child_roots = module_roots + (path,) if kind != "directory" else module_roots
88
+ for child in array(node["children"], f"{label}[{path}].children"):
89
+ visit(child, path, child_roots)
90
+
91
+ visit(value, None, ())
92
+ require(bool(hap_paths), f"{label} must include an application HAP")
93
+ for path in hap_paths:
94
+ production_root = f"{path}/src/main"
95
+ require(production_root in paths and any(p.startswith(f"{production_root}/") for p in paths),
96
+ f"{label}[{path}] must include an expanded src/main production root with key directories")
97
+ return paths
98
+
99
+
100
+ def validate_plan(value: object) -> dict:
101
+ plan = record(value, PLAN_FIELDS, "plan")
102
+ string(plan["projectName"], "projectName")
103
+ directory_tree(plan["directoryTree"])
104
+ for decision in array(plan["decisions"], "decisions", nonempty=True):
105
+ decision = record(decision, {"title", "description"}, "decisions[]")
106
+ for key in ("title", "description"):
107
+ string(decision[key], f"decisions[].{key}")
108
+
109
+ return plan
110
+
111
+
112
+ def unique_keys(pairs: list[tuple[str, object]]) -> dict:
113
+ result: dict = {}
114
+ for key, value in pairs:
115
+ require(key not in result, f"duplicate JSON key: {key}")
116
+ result[key] = value
117
+ return result
118
+
119
+
120
+ def reject_constant(value: str) -> None:
121
+ raise ContractError(f"invalid JSON constant: {value}")
122
+
123
+
124
+ def load_json(path: Path) -> object:
125
+ return json.loads(path.read_text(encoding="utf-8-sig"), object_pairs_hook=unique_keys,
126
+ parse_constant=reject_constant)
127
+
128
+
129
+ def main() -> int:
130
+ if hasattr(sys.stdout, "reconfigure"):
131
+ sys.stdout.reconfigure(encoding="utf-8")
132
+ parser = argparse.ArgumentParser(description=__doc__)
133
+ subparsers = parser.add_subparsers(dest="command", required=True)
134
+ check = subparsers.add_parser("validate", help="read-only final-plan validation")
135
+ check.add_argument("plan", type=Path)
136
+ args = parser.parse_args()
137
+ try:
138
+ plan = validate_plan(load_json(args.plan))
139
+ print(json.dumps({"status": "valid", "projectName": plan["projectName"]}, ensure_ascii=False))
140
+ return 0
141
+ except (OSError, ValueError, RecursionError) as error:
142
+ print(json.dumps({"status": "error", "error": str(error)}, ensure_ascii=False))
143
+ return 1
144
+
145
+
146
+ if __name__ == "__main__":
147
+ raise SystemExit(main())