@harmonyos-arkts/d2h 0.1.19 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +40 -0
- package/agents/android-to-hmos-00-orchestrator.md +3 -315
- package/agents/d2h.md +53 -38
- package/contracts/workflow/artifacts.md +22 -0
- package/contracts/workflow/delivery.md +54 -0
- package/contracts/workflow/dispatch.md +26 -0
- package/contracts/workflow/execution.md +125 -0
- package/contracts/workflow/generation.md +40 -0
- package/contracts/workflow/orchestration.md +49 -0
- package/contracts/workflow/overview.md +58 -0
- package/contracts/workflow/plan.md +119 -0
- package/contracts/workflow/scope.md +151 -0
- package/dist/bin/install-opencode.js +34 -15
- package/dist/src/install-codex.js +131 -0
- package/dist/src/install-opencode.js +5 -5
- package/dist/src/workflow.js +54 -0
- package/docs/architecture-plan-guide.zh-CN.md +70 -0
- package/docs/incremental-spec-evaluation-design.md +670 -0
- package/docs/spec-plan-guide.md +226 -0
- package/docs/workflow-decoupling.md +59 -0
- package/opencode/interactive-tools/advance-progress-demo.mjs +21 -0
- package/opencode/interactive-tools/browser-host.ts +110 -0
- package/opencode/interactive-tools/core.ts +242 -0
- package/opencode/interactive-tools/demo-workflow.ts +55 -0
- package/opencode/interactive-tools/demo.mjs +37 -0
- package/opencode/interactive-tools/package-lock.json +1876 -0
- package/opencode/interactive-tools/package.json +23 -0
- package/opencode/interactive-tools/panel.ts +40 -0
- package/opencode/interactive-tools/prepare-demo.mjs +17 -0
- package/opencode/interactive-tools/prepare-progress-demo.mjs +13 -0
- package/opencode/interactive-tools/progress.ts +118 -0
- package/opencode/interactive-tools/server-legacy.mjs +2 -0
- package/opencode/interactive-tools/server.mjs +40 -0
- package/opencode/interactive-tools/tui.tsx +230 -0
- package/package.json +9 -1
- package/schemas/native-libs-analysis-report.schema.json +695 -0
- package/schemas/native-libs-verify-report.schema.json +566 -0
- package/schemas/spec-native-libs-shard.schema.json +110 -0
- package/skills/arkts-icon-sizing/SKILL.md +0 -10
- package/skills/cpp/d2h-native-library-analyse/SKILL.md +91 -0
- package/skills/cpp/d2h-native-library-analyse/scripts/analyse-android-so.py +630 -0
- package/skills/cpp/d2h-native-library-build/SKILL.md +403 -0
- package/skills/cpp/d2h-native-library-build/references/build-config-mapping.md +180 -0
- package/skills/cpp/d2h-native-library-build/references/jni-to-napi-mapping.md +74 -0
- package/skills/cpp/d2h-native-library-build/scripts/check-android-so.py +580 -0
- package/skills/cpp/d2h-native-library-verify/SKILL.md +111 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-build-sign-run.md +67 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/AppScope/app.json5 +1 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/AppScope/resources/base/element/string.json +1 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/README.md +25 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/build-profile.json5 +24 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/build-profile.json5 +1 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/hvigorfile.ts +2 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/oh-package.json5 +1 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/ets/entryability/EntryAbility.ets +5 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/ets/pages/Index.ets +3 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/ets/pages/NativeVerifyDemo.ets +276 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/module.json5 +1 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/resources/base/element/color.json +1 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/resources/base/element/string.json +1 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/resources/base/profile/main_pages.json +1 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/ohosTest/ets/test/FFmpegVerify.test.ets +176 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/ohosTest/ets/test/List.test.ets +2 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/ohosTest/module.json5 +1 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/hvigor/hvigor-config.json5 +1 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/hvigorfile.ts +2 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-project-template/oh-package.json5 +9 -0
- package/skills/cpp/d2h-native-library-verify/references/demo-template.md +176 -0
- package/skills/cpp/d2h-native-library-verify/references/hypium-test-template.md +163 -0
- package/skills/cpp/d2h-native-library-verify/scripts/__pycache__/native_common.cpython-313.pyc +0 -0
- package/skills/cpp/d2h-native-library-verify/scripts/device-verify.py +304 -0
- package/skills/cpp/d2h-native-library-verify/scripts/native_common.py +341 -0
- package/skills/cpp/d2h-native-library-verify/scripts/verify-ohos-native.py +1099 -0
- package/skills/d2h-app-solution/SKILL.md +198 -0
- package/skills/d2h-app-solution/agents/openai.yaml +4 -0
- package/skills/d2h-app-solution/references/framework-evidence.md +117 -0
- package/skills/d2h-app-solution/references/hierarchy-rules.md +88 -0
- package/skills/d2h-app-solution/references/inventory-schema.md +305 -0
- package/skills/d2h-app-solution/references/technical-analysis.md +101 -0
- package/skills/d2h-app-solution/scripts/scan_repositories.py +350 -0
- package/skills/d2h-app-solution/scripts/validate_inventory.py +337 -0
- package/skills/d2h-architecture-plan/SKILL.md +35 -0
- package/skills/d2h-architecture-plan/references/directory-creation.md +43 -0
- package/skills/d2h-architecture-plan/references/hybrid-design.md +84 -0
- package/skills/d2h-architecture-plan/references/layout-reference.md +83 -0
- package/skills/d2h-architecture-plan/references/plan-contract.md +51 -0
- package/skills/d2h-architecture-plan/references/source-review.md +50 -0
- package/skills/d2h-cloud-knowledge/SKILL.md +28 -7
- package/skills/d2h-cloud-knowledge/kb_query.mjs +8 -0
- package/skills/d2h-dashboard/SKILL.md +56 -0
- package/skills/d2h-product-family-migration/SKILL.md +103 -0
- package/skills/d2h-product-family-migration/agents/openai.yaml +3 -0
- package/skills/d2h-product-family-migration/assets/incremental-plan.template.md +55 -0
- package/skills/d2h-product-family-migration/references/advanced/behavioral-verification.md +59 -0
- package/skills/d2h-product-family-migration/references/advanced/existing-skill-integration.md +30 -0
- package/skills/d2h-product-family-migration/references/advanced/packaging-and-verification.md +43 -0
- package/skills/d2h-product-family-migration/references/advanced/platform-probes.md +26 -0
- package/skills/d2h-product-family-migration/references/advanced/product-line-design.md +40 -0
- package/skills/d2h-product-family-migration/references/baseline-and-delta.md +50 -0
- package/skills/d2h-product-family-migration/tests/markdown-cases.md +76 -0
- package/skills/{d2h-page-develop → d2h-spec-develop}/SKILL.md +143 -132
- package/skills/d2h-spec-develop/agents/openai.yaml +7 -0
- package/skills/d2h-spec-develop/references/worker-workflow.md +26 -0
- package/skills/{d2h-page-spec-generate → d2h-spec-generate}/SKILL.md +24 -13
- package/skills/d2h-spec-generate/assets/spec-template.md +109 -0
- package/skills/d2h-spec-generate/references/from-plan.md +19 -0
- package/skills/d2h-spec-generate/references/spec-content.md +34 -0
- package/skills/{d2h-page-spec-generate → d2h-spec-generate}/scripts/build_batch_report.py +35 -84
- package/skills/d2h-spec-plan/SKILL.md +38 -0
- package/skills/d2h-spec-plan/agents/openai.yaml +4 -0
- package/skills/d2h-spec-plan/references/architecture-handoff.md +9 -0
- package/skills/d2h-spec-plan/references/plan-contract.md +13 -0
- package/skills/d2h-spec-plan/references/planning-method.md +46 -0
- package/skills/d2h-spec-plan/references/round-scope-assessment.md +49 -0
- package/skills/d2h-spec-plan/references/work-packets.md +61 -0
- package/skills/d2h-utg-ai-generate/references/click-paths-schema.json +2 -2
- package/skills/d2h-utg-ai-generate/references/page-schema.json +2 -2
- package/skills/d2h-utg-ai-generate/references/utg-schema.json +2 -2
- package/skills/d2h-utg-static-generate/SKILL.md +2 -2
- package/skills/managing-d2h-toolchain/environment-requirements.json +1 -1
- package/skills/managing-d2h-toolchain/toolchain.json +1 -1
- package/skills/preparing-migration-workspace/SKILL.md +32 -2
- package/workflow/__pycache__/artifact_bindings.cpython-313.pyc +0 -0
- package/workflow/__pycache__/spec_runtime.cpython-313.pyc +0 -0
- package/workflow/artifact_bindings.py +224 -0
- package/workflow/assets/dashboard.html +88 -0
- package/workflow/build_batch_report.py +8 -0
- package/workflow/collect_code_metrics.py +75 -0
- package/workflow/dashboard_overview.py +109 -0
- package/workflow/delivery_view.py +82 -0
- package/workflow/feature_plan.py +234 -0
- package/workflow/page_batches.py +223 -0
- package/workflow/spec_generation.py +120 -0
- package/workflow/spec_runtime.py +890 -0
- package/workflow/work_packets.py +85 -0
- package/skills/d2h-page-develop/agents/openai.yaml +0 -7
- package/skills/harmonyos-migration-playbook/SKILL.md +0 -83
- package/skills/harmonyos-migration-playbook/agents/openai.yaml +0 -7
- package/skills/harmonyos-migration-playbook/references/arkts-compile.md +0 -24
- package/skills/harmonyos-migration-playbook/references/harmony-runtime.md +0 -53
- package/skills/harmonyos-migration-playbook/references/lesson-lifecycle.md +0 -45
- package/skills/harmonyos-migration-playbook/references/protocol-e2e.md +0 -23
- package/skills/harmonyos-migration-playbook/references/ui-automation.md +0 -53
- package/skills/harmonyos-migration-playbook/references/windows-environment.md +0 -38
- package/skills/native-library-substitution/SKILL.md +0 -385
- package/skills/native-library-substitution/references/native-library-substitution.json +0 -56906
- package/skills/native-library-substitution/references/native-library-substitution.md +0 -163
- package/skills/porting-cmake-native-libraries/SKILL.md +0 -57
- package/skills/porting-cmake-native-libraries/references/platform-adaptation.md +0 -49
- package/skills/porting-cmake-native-libraries/references/report-and-build.md +0 -60
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# 自足工作包、共享状态与实际接口
|
|
2
|
+
|
|
3
|
+
创建或细化当前轮次、处理开工缺口、设计跨 Spec 状态流时读取。计划字段归 pages.json;接口回执归 execution.json。context 裁剪输出,不另建可编辑工单真源。
|
|
4
|
+
|
|
5
|
+
## 开工就绪
|
|
6
|
+
|
|
7
|
+
新计划使用 `work_packet_policy: "required-v1"`。每个 Spec 提供:
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"implementation_brief": {
|
|
12
|
+
"behavior_notes": ["输入、状态转移、结果与必要失败路径"],
|
|
13
|
+
"invariants": ["本次行为不能破坏的约束"],
|
|
14
|
+
"known_pitfalls": ["从源码或实际失败确认的风险"],
|
|
15
|
+
"open_questions": []
|
|
16
|
+
},
|
|
17
|
+
"testing": {
|
|
18
|
+
"layers": [{"check_id": "login/basic", "layer": "device", "reason": "真实鉴权与导航"}]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
behavior_notes 非空;其余三个数组必须存在,无适用内容经核对后写 []。open_questions 只放阻止可靠实现的未决问题,非阻塞背景放 behavior_notes。testing.layers 唯一且完整覆盖本 Spec 的 checks;layer 为 static/logic/integration/device,reason 非空。可暂存不完整计划,但 derive.readiness_gaps 阻止相关成员进入窗口,不能夹带进同页批次。
|
|
24
|
+
|
|
25
|
+
工作包复用 scope、evidence_refs、owned_paths、requires/provides 和 checks。AI 按任务补齐 UI 入口与路由参数、数据表/键/副作用、逻辑模块及目标文件;纯后台/算法能力不虚构页面。源码仍是行为细节依据,摘要用于定位与交接。远期延期需求不提前写大工单。
|
|
26
|
+
|
|
27
|
+
## 跨 Spec 共享状态
|
|
28
|
+
|
|
29
|
+
仅在多个 Spec 实际共享状态时写顶层 shared_contracts,不为每个功能强建基础设施。
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"state_id": "session",
|
|
34
|
+
"capability": "session.basic",
|
|
35
|
+
"producer_spec_id": "login-basic",
|
|
36
|
+
"consumer_spec_ids": ["home-basic"],
|
|
37
|
+
"channel": "SessionStore 单一状态通道",
|
|
38
|
+
"payload": "令牌、用户标识及匿名/有效/过期状态",
|
|
39
|
+
"initialization": "登录成功持久化;首页订阅并读取快照",
|
|
40
|
+
"lifecycle": "注销或过期清空并通知消费者",
|
|
41
|
+
"checks": ["login/persist-session", "home/observe-session"]
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
producer 提供指定 capability,每个 consumer 必须 requires 它;checks 包含每个参与 Spec 的实际检查,不得越界。channel/payload/initialization/lifecycle 缺失时参与者不能开工。登录和首页 UI 都存在不能证明状态已传递;必须明确谁写、谁读、何时生效及失效,并用消费者断言覆盖状态变化。
|
|
46
|
+
|
|
47
|
+
## 真实接口交接
|
|
48
|
+
|
|
49
|
+
提供者实现并形成当前候选后,主 Agent 阅读代码核对实际导出符号、参数/返回及错误语义,以 interface 事件记录代码文件及哈希、symbols、summary、reviewer。运行时确认由调用方按公共 `d2h workflow contract execution` 执行;本 Skill 不记录事件。这不是预先编造的签名,也不是运行证据。提供者只有接口回执有效、当前构建和即时契约测试有效,才解锁消费者。
|
|
50
|
+
|
|
51
|
+
candidate 或接口文件变化、failed/invalidate/repair 会撤销接口有效性。context 不交接过期接口;共享契约或工作包语义通过 reconcile 修订并重置相关成员,保留历史。程序检查文件身份,不解析符号真实性或证明实际调用关系,主 Agent 负责语义核验。
|
|
52
|
+
|
|
53
|
+
## 分层验证与定向修复
|
|
54
|
+
|
|
55
|
+
分层是选择验证成本的计划,不改变通过标准。当前正式 test evidence 仍只支持受管 Python 设备执行;静态检查、算法向量和服务探针可前置发现问题,但尚无 CLI 证据适配,不能用于正式 PASS。鉴权、启动、共享契约和其他高风险行为仍即时验证,低风险成员按窗口合批。
|
|
56
|
+
|
|
57
|
+
只修复并重跑失败的准备步骤,避免重新提取已完成需求;同一候选可共享构建安装和真实结果。主 Agent 对高风险且缺证据的行为优先检查反例,如失效令牌、重复提交、返回后状态丢失,不为每项重复启动多轮主观评审。候选变化仍保守撤销旧验证,影响范围复用需后续 CLI 协同。
|
|
58
|
+
|
|
59
|
+
## 与完整 Spec 的关系
|
|
60
|
+
|
|
61
|
+
implementation_brief 是结构化计划/派发摘要,不替代详细行为、操作、证据与逐 check 断言。规划先提供已核验的概要及缺口;生成方按公共 `d2h workflow contract generation` 展开正文,缺少实施字段时提出修订建议。新策略额外核对逐成员生成绑定,但 readiness_gaps 仍不验证 Markdown 语义;AI/人工须审查。worker 根据固定 spec_id 补读详细文档,不根据摘要猜测。
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://
|
|
4
|
-
"title": "
|
|
3
|
+
"$id": "https://d2h.local/schemas/click-paths-v2.json",
|
|
4
|
+
"title": "d2h deterministic click paths",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"required": ["root", "paths", "unreachable"],
|
|
7
7
|
"properties": {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://
|
|
4
|
-
"title": "
|
|
3
|
+
"$id": "https://d2h.local/schemas/page-plan-v1.json",
|
|
4
|
+
"title": "d2h UTG-directed runtime capture plan",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"required": ["schema_version", "source", "summary", "pages"],
|
|
7
7
|
"properties": {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://
|
|
4
|
-
"title": "
|
|
3
|
+
"$id": "https://d2h.local/schemas/utg-v2.json",
|
|
4
|
+
"title": "d2h source-derived UTG",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"required": ["directed", "multigraph", "graph", "nodes", "links"],
|
|
7
7
|
"properties": {
|
|
@@ -19,14 +19,14 @@ description: 迁移主流程的 Android 静态分析入口,在 Skill 内调用
|
|
|
19
19
|
droid2hmos analyze android <android-source>
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
2. 确认本次生成的 `<migration-workspace>/spec/index.json`、`spec/facts
|
|
22
|
+
2. 确认本次生成的 `<migration-workspace>/spec/index.json`、`spec/facts/`(含 `facts/native-libs.json` 原生库分片)和 `appgraph/app-graph.json` 存在,核对源码与分析结果。复用这份 AppGraph,不再执行第二次 `appgraph build`:
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
25
|
python <skill>/scripts/generate.py --project-root <android-source> --workspace <migration-workspace>/utg --appgraph <migration-workspace>/appgraph/app-graph.json
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
3. 按下文完成源码核验、独立 AI 修补和统一重建。已有 UTG 工作区需显式使用 `--force`;源码或 AppGraph 变化时重新核验旧修补,不能沿用过期指纹。
|
|
29
|
-
4. 将 Facts 位置、受管分析结果、UTG 产物及未解析项交回主 Agent,由主 Agent 接续报告初始化与 Task Plan
|
|
29
|
+
4. 将 Facts 位置、受管分析结果、UTG 产物及未解析项交回主 Agent,由主 Agent 接续报告初始化与 Task Plan。`facts/native-libs.json` 的 `present`/`usageCase`/`libraries`/`loadCalls` 原样转交(其处置归 `d2h-native-library-build`,本 Skill 不做 ELF/符号深检)。UTG discoveries 不替代 Kernel 的 correction/review,也不替代受管分析证据。
|
|
30
30
|
|
|
31
31
|
Android 分析不运行 Gradle、不启用 `--resolve-dependencies`,不需要 APK 或 Android 设备。已有图缺失时报告分析产物不完整,不在主流程模式中静默切换到独立建图。
|
|
32
32
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"java": null,
|
|
9
9
|
"python-json5": null,
|
|
10
10
|
"appgraph": { "minimum": "0.0.11", "maximumExclusive": "0.0.12" },
|
|
11
|
-
"droid2hmos": { "minimum": "0.0.
|
|
11
|
+
"droid2hmos": { "minimum": "0.0.17", "maximumExclusive": "0.0.18" },
|
|
12
12
|
"hvigor": null,
|
|
13
13
|
"ohpm": null,
|
|
14
14
|
"devecocli": null
|
|
@@ -9,10 +9,38 @@ description: 在 Android 转 HarmonyOS 迁移开始或恢复时,通过 droid2h
|
|
|
9
9
|
|
|
10
10
|
工具安装、精确版本和升级由 `managing-d2h-toolchain` 负责。本 Skill 只消费已检查的 CLI;发现版本或能力不符时返回该 Skill 处理,不运行 npm。
|
|
11
11
|
|
|
12
|
-
只调用 `droid2hmos` 的 `status`、`preflight`、`config init/validate` 和按需的 `device check`。不要直接调用 Node 脚本、Gradle、Hvigor、Hypium、ADB、HDC 或 AppGraph 分析命令,也不要在 `ohos/` 尚不存在时猜测 HarmonyOS 构建配置。
|
|
12
|
+
只调用 `droid2hmos` 的 `status`、`preflight`、`config init/validate`、`progress init/update` 和按需的 `device check`。不要直接调用 Node/Python 脚本、Gradle、Hvigor、Hypium、ADB、HDC 或 AppGraph 分析命令,也不要在 `ohos/` 尚不存在时猜测 HarmonyOS 构建配置。
|
|
13
13
|
|
|
14
14
|
迁移产物(`.migration/`)统一位于 Android 工程父目录下的 `<工程名>-migration/.migration/`,由 CLI 创建和维护,不写入 Android 工程内部。下文所有 `.migration/...` 路径均相对该迁移工作区根目录。
|
|
15
15
|
|
|
16
|
+
## 阶段进度文件
|
|
17
|
+
|
|
18
|
+
CLI 确认迁移工作区且 `.migration/` 已存在后,立即初始化阶段进度文件:
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
droid2hmos progress init <ANDROID_PROJECT>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
该命令原子创建 `.migration/progress.json`;恢复迁移时若文件已存在,只校验结构并保留全部已有时间和状态,禁止覆盖重置。文件固定包含以下七个有序阶段:
|
|
25
|
+
|
|
26
|
+
1. 初始化工程(`workspace-initialization`)
|
|
27
|
+
2. 功能清单生成(`feature-inventory`)
|
|
28
|
+
3. Android 工程分析(`android-analysis`)
|
|
29
|
+
4. HarmonyOS 工程架构设计(`harmony-architecture`)
|
|
30
|
+
5. spec 设计与生成(`spec-design-generation`)
|
|
31
|
+
6. 代码转换(`code-conversion`)
|
|
32
|
+
7. 测试与交付(`testing-delivery`)
|
|
33
|
+
|
|
34
|
+
每个阶段记录 `status`、`startedAt`、`endedAt`、`completed`、`lastUpdatedAt`、`details` 和 `blockers`;顶层 `summary` 汇总总数及 `pending/in_progress/blocked/completed` 数量和当前阶段。所有时间使用带时区偏移的 ISO 8601。初始化时“初始化工程”为 `in_progress`,其余阶段为 `pending`。
|
|
35
|
+
|
|
36
|
+
本 Skill 只更新“初始化工程”阶段。Preflight 和配置准备达到 `baselineReady=true` 时执行:
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
droid2hmos progress update <ANDROID_PROJECT> --stage workspace-initialization --status completed --details "workspace, configuration, and non-device baseline are ready"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
若工程定位、配置或必要工具链使 `allowedNonDeviceWork=false`,将该阶段更新为 `blocked` 并用 `--blocker` 原样记录 CLI 返回的阻塞原因;设备单独不可用不阻止初始化阶段完成。后续编排者在对应阶段开始、完成或受阻时用同一 `update` 命令更新 `progress.json`,不得直接手工改写、删除阶段或伪造时间。
|
|
43
|
+
|
|
16
44
|
## 输入与事实来源
|
|
17
45
|
|
|
18
46
|
输入必须包含 Android Gradle 工程根目录。根目录应有 Gradle 标记,应用模块中应有 `AndroidManifest.xml`。
|
|
@@ -112,7 +140,7 @@ droid2hmos config validate <ANDROID_PROJECT>
|
|
|
112
140
|
],
|
|
113
141
|
"allowedNonDeviceWork": true,
|
|
114
142
|
"allowedActions": [],
|
|
115
|
-
"evidence": [".migration/preflight-report.json"],
|
|
143
|
+
"evidence": [".migration/preflight-report.json", ".migration/progress.json"],
|
|
116
144
|
"suggestedRemediation": []
|
|
117
145
|
}
|
|
118
146
|
```
|
|
@@ -128,4 +156,6 @@ droid2hmos config validate <ANDROID_PROJECT>
|
|
|
128
156
|
- `evidence`:本次读取或生成的状态、配置和 Preflight 报告;
|
|
129
157
|
- `suggestedRemediation`:CLI 给出的可执行补救,不臆造成功结论。
|
|
130
158
|
|
|
159
|
+
结构化结果的 `evidence` 必须包含本次读取或初始化的 `.migration/progress.json`。
|
|
160
|
+
|
|
131
161
|
本 Skill 不推进外部状态机、不更新迁移报告、不创建 checkpoint、不调用 `complete`,也不决定定向测试或全量回归。调用方根据上述结果决定下一步。
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"""Read-only bindings between analysis reports, versioned requirements and dashboard metadata."""
|
|
2
|
+
import hashlib
|
|
3
|
+
import json
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
FEATURE_LIST_REQUIRED = ["trigger", "result", "priority", "evidence"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def validate_legacy_report(data):
|
|
12
|
+
errors = []
|
|
13
|
+
for field in ("project", "feature_list", "summary"):
|
|
14
|
+
if field not in data:
|
|
15
|
+
errors.append("缺少必填字段: {}".format(field))
|
|
16
|
+
if errors:
|
|
17
|
+
return errors
|
|
18
|
+
if not isinstance(data["project"], str) or not data["project"].strip():
|
|
19
|
+
errors.append("project 必须是非空字符串")
|
|
20
|
+
if not isinstance(data["feature_list"], list) or not data["feature_list"]:
|
|
21
|
+
errors.append("feature_list 必须是非空数组")
|
|
22
|
+
else:
|
|
23
|
+
for idx, row in enumerate(data["feature_list"], 1):
|
|
24
|
+
if not isinstance(row, dict):
|
|
25
|
+
errors.append("feature_list 第 {} 行不是对象".format(idx))
|
|
26
|
+
continue
|
|
27
|
+
for field in FEATURE_LIST_REQUIRED:
|
|
28
|
+
value = str(row.get(field, "") or "").strip()
|
|
29
|
+
if not value:
|
|
30
|
+
errors.append("feature_list 第 {} 行缺少必填字段 {}".format(idx, field))
|
|
31
|
+
if str(row.get("priority", "")).strip() not in ("P0", "P1", "P2"):
|
|
32
|
+
errors.append("feature_list 第 {} 行 priority 必须是 P0/P1/P2".format(idx))
|
|
33
|
+
if not isinstance(data["summary"], list) or not data["summary"]:
|
|
34
|
+
errors.append("summary 必须是非空数组")
|
|
35
|
+
return errors
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def validate_inventory_contract(data):
|
|
39
|
+
"""Validate the exchanged artifact, not a producer's source-analysis workflow."""
|
|
40
|
+
errors = []
|
|
41
|
+
def require(value, message):
|
|
42
|
+
if not value:
|
|
43
|
+
errors.append(message)
|
|
44
|
+
require(data.get('schema_version') == '1.1', 'schema_version must be 1.1')
|
|
45
|
+
require(data.get('max_depth') == 4, 'max_depth must be 4')
|
|
46
|
+
repositories = data.get('repositories')
|
|
47
|
+
require(isinstance(repositories, list) and bool(repositories), 'repositories must be non-empty')
|
|
48
|
+
ids = []
|
|
49
|
+
for repo in repositories if isinstance(repositories, list) else []:
|
|
50
|
+
if not isinstance(repo, dict):
|
|
51
|
+
errors.append('repository must be an object')
|
|
52
|
+
continue
|
|
53
|
+
identity = repo.get('id')
|
|
54
|
+
require(isinstance(identity, str) and bool(identity), 'repository id required')
|
|
55
|
+
if isinstance(identity, str):
|
|
56
|
+
ids.append(identity)
|
|
57
|
+
require(isinstance(repo.get('path'), str) and Path(repo['path']).is_absolute(), 'repository path must be absolute')
|
|
58
|
+
require(len(set(ids)) == len(ids), 'duplicate repository id')
|
|
59
|
+
rows = data.get('features')
|
|
60
|
+
require(isinstance(rows, list) and bool(rows), 'features must be non-empty')
|
|
61
|
+
seen = set()
|
|
62
|
+
for i, row in enumerate(rows if isinstance(rows, list) else []):
|
|
63
|
+
label = f'features[{i}]'
|
|
64
|
+
if not isinstance(row, dict):
|
|
65
|
+
errors.append(label + ' must be an object')
|
|
66
|
+
continue
|
|
67
|
+
path = row.get('path')
|
|
68
|
+
valid_path = isinstance(path, list) and 1 <= len(path) <= 4 and all(isinstance(p, str) and bool(p.strip()) for p in path)
|
|
69
|
+
require(valid_path, label + '.path must contain 1-4 non-empty names')
|
|
70
|
+
context = row.get('entry_context')
|
|
71
|
+
require(isinstance(context, str) and bool(context.strip()), label + '.entry_context required')
|
|
72
|
+
if valid_path and isinstance(context, str):
|
|
73
|
+
key = (context, tuple(path))
|
|
74
|
+
require(key not in seen, label + ': duplicate path/context')
|
|
75
|
+
seen.add(key)
|
|
76
|
+
require(row.get('release_stage') in ('mvp', 'core', 'full', 'uncertain'), label + '.release_stage invalid')
|
|
77
|
+
require(isinstance(row.get('release_rationale'), str) and bool(row['release_rationale'].strip()), label + '.release_rationale required')
|
|
78
|
+
implementations = row.get('implementations')
|
|
79
|
+
require(isinstance(implementations, dict) and set(implementations) == set(ids), label + '.implementations must match repositories')
|
|
80
|
+
for repo_id, impl in implementations.items() if isinstance(implementations, dict) else []:
|
|
81
|
+
if not isinstance(impl, dict):
|
|
82
|
+
errors.append(label + ': implementation must be an object')
|
|
83
|
+
continue
|
|
84
|
+
require(impl.get('status') in ('implemented', 'partial', 'mock', 'unreachable', 'absent', 'uncertain'), label + ': status invalid')
|
|
85
|
+
require(impl.get('confidence') in ('high', 'medium', 'low'), label + ': confidence invalid')
|
|
86
|
+
technology = impl.get('technology_stack')
|
|
87
|
+
require(isinstance(technology, list) and all(isinstance(t, str) for t in technology), label + ': technology_stack must be strings')
|
|
88
|
+
require(isinstance(impl.get('evidence', []), list), label + ': evidence must be an array')
|
|
89
|
+
for field in ('external_dependencies', 'android_permissions'):
|
|
90
|
+
section = impl.get(field)
|
|
91
|
+
require(isinstance(section, dict), label + ': ' + field + ' must be an object')
|
|
92
|
+
if not isinstance(section, dict):
|
|
93
|
+
continue
|
|
94
|
+
require(section.get('status') in ('present', 'none', 'uncertain', 'not-applicable'), label + ': technical status invalid')
|
|
95
|
+
items = section.get('items', [])
|
|
96
|
+
require(isinstance(items, list) and all(isinstance(item, dict) and isinstance(item.get('name'), str) and bool(item['name']) for item in items), label + ': technical items invalid')
|
|
97
|
+
if field == 'external_dependencies':
|
|
98
|
+
require(isinstance(items, list) and all(isinstance(item, dict) and item.get('kind') in ('sdk', 'so') for item in items), label + ': dependency kind invalid')
|
|
99
|
+
return errors
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def inventory_view(data):
|
|
103
|
+
"""Read-only presentation projection; source references keep /features/N."""
|
|
104
|
+
result = {'project': data.get('title', ''), 'feature_list': [], 'summary': [],
|
|
105
|
+
'tech_stack': [], 'sdk_dependencies': [], 'permissions': [], 'user_inputs': [],
|
|
106
|
+
'uncovered': [{'component': 'inventory question', 'reason': str(q), 'evidence': ''}
|
|
107
|
+
for q in data.get('open_questions', [])]}
|
|
108
|
+
for i, feature in enumerate(data['features']):
|
|
109
|
+
evidence = []
|
|
110
|
+
implementations = feature.get('implementations', {})
|
|
111
|
+
for repo_id, impl in implementations.items():
|
|
112
|
+
evidence.extend(f"{repo_id}:{e.get('path', '')}:{e.get('line_start', '')}" for e in impl.get('evidence', []))
|
|
113
|
+
for tech in impl.get('technology_stack', []):
|
|
114
|
+
result['tech_stack'].append({'item': tech, 'conclusion': repo_id, 'evidence': '; '.join(evidence), 'impact': ' / '.join(feature['path'])})
|
|
115
|
+
for dep in impl.get('external_dependencies', {}).get('items', []):
|
|
116
|
+
result['sdk_dependencies'].append({'sdk': dep['name'], 'category': dep['kind'],
|
|
117
|
+
'harmony_support': '待确认', 'alternative': dep.get('purpose', ''),
|
|
118
|
+
'evidence': f"/features/{i}/implementations/{repo_id}/external_dependencies", 'provenance': dep.get('provenance', 'unknown')})
|
|
119
|
+
result['feature_list'].append({'trigger': feature.get('trigger', ''), 'result': feature.get('effect', ''),
|
|
120
|
+
'condition': ' / '.join(feature.get('conditions', [])), 'carrier': ' / '.join(feature['path']),
|
|
121
|
+
'priority': feature.get('release_stage', 'uncertain'), 'note': feature.get('release_rationale', ''),
|
|
122
|
+
'evidence': '; '.join(evidence), 'implementations': implementations})
|
|
123
|
+
return result
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def load_report(path):
|
|
127
|
+
path = Path(path).resolve()
|
|
128
|
+
raw = path.read_bytes()
|
|
129
|
+
data = json.loads(raw)
|
|
130
|
+
if not isinstance(data, dict):
|
|
131
|
+
raise ValueError('feature-analysis report must be an object')
|
|
132
|
+
if data.get('schema_version') == '1.1':
|
|
133
|
+
errors = validate_inventory_contract(data)
|
|
134
|
+
if errors:
|
|
135
|
+
raise ValueError('inventory validation failed: ' + '; '.join(errors))
|
|
136
|
+
if not data.get('features'):
|
|
137
|
+
raise ValueError('inventory requires at least one feature for migration planning')
|
|
138
|
+
return {'path': str(path), 'sha256': hashlib.sha256(raw).hexdigest(), 'pointer_prefix': '/features/',
|
|
139
|
+
'source_schema': 'inventory/1.1', 'inventory': data, 'data': inventory_view(data)}
|
|
140
|
+
errors = validate_legacy_report(data)
|
|
141
|
+
if errors:
|
|
142
|
+
raise ValueError('; '.join(errors))
|
|
143
|
+
fingerprint = data.get('fingerprint')
|
|
144
|
+
if not isinstance(fingerprint, dict) or not isinstance(fingerprint.get('value'), str) or not fingerprint['value'].strip() or fingerprint.get('algorithm') == 'none':
|
|
145
|
+
raise ValueError('feature-analysis source fingerprint required; rerun analysis')
|
|
146
|
+
for key in ('tech_stack', 'sdk_dependencies', 'user_inputs', 'permissions', 'uncovered'):
|
|
147
|
+
if not isinstance(data.get(key, []), list) or any(not isinstance(row, dict) for row in data.get(key, [])):
|
|
148
|
+
raise ValueError('feature-analysis ' + key + ' must be an object array')
|
|
149
|
+
return {'path': str(path), 'sha256': hashlib.sha256(raw).hexdigest(), 'pointer_prefix': '/feature_list/', 'data': data}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def validate_links(features):
|
|
154
|
+
errors = []
|
|
155
|
+
for source in features.get('sources', []):
|
|
156
|
+
if not isinstance(source, dict) or source.get('kind') not in ('inventory', 'source') or not isinstance(source.get('locator'), str) or Path(source['locator']).name not in ('feature-analysis.json', 'inventory.json'):
|
|
157
|
+
continue
|
|
158
|
+
try:
|
|
159
|
+
if not Path(source['locator']).is_absolute():
|
|
160
|
+
raise ValueError('feature-analysis locator must be absolute')
|
|
161
|
+
report = load_report(source['locator'])
|
|
162
|
+
if source.get('revision') != 'sha256:' + report['sha256']:
|
|
163
|
+
raise ValueError('feature-analysis report hash changed; revise upstream plan before Spec dispatch')
|
|
164
|
+
expected = {report['pointer_prefix'] + str(i) for i in range(len(report['data']['feature_list']))}
|
|
165
|
+
seen = set()
|
|
166
|
+
for feature in features.get('features', []):
|
|
167
|
+
for ref in feature.get('source_refs', []):
|
|
168
|
+
if ref.get('source_id') == source['source_id']:
|
|
169
|
+
pointer = ref.get('ref')
|
|
170
|
+
if pointer not in expected:
|
|
171
|
+
raise ValueError('feature-analysis ref must identify an existing ' + report['pointer_prefix'] + 'N row')
|
|
172
|
+
seen.add(pointer)
|
|
173
|
+
if seen != expected:
|
|
174
|
+
raise ValueError('every analysis feature row needs explicit planning coverage, including deferred/excluded/unresolved')
|
|
175
|
+
except (OSError, ValueError, KeyError, TypeError) as error:
|
|
176
|
+
errors.append(str(error))
|
|
177
|
+
return errors
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def dashboard_report(path, features):
|
|
181
|
+
report = load_report(path)
|
|
182
|
+
sources = [s for s in features['sources'] if s.get('kind') in ('inventory', 'source') and Path(s.get('locator', '')).resolve() == Path(report['path']) and s.get('revision') == 'sha256:' + report['sha256']]
|
|
183
|
+
if not sources:
|
|
184
|
+
raise ValueError('--feature-analysis must be bound by the current features.json source path and report hash')
|
|
185
|
+
errors = validate_links(features)
|
|
186
|
+
if errors:
|
|
187
|
+
raise ValueError('; '.join(errors))
|
|
188
|
+
data = report['data']
|
|
189
|
+
overview = {'schema': 'd2h-overview/v1', 'generated_at': datetime.now(timezone.utc).isoformat(),
|
|
190
|
+
'source_revision': 'analysis-report-sha256:' + report['sha256'],
|
|
191
|
+
'sources': [{'source_id': 'feature-analysis', 'path': report['path'], 'sha256': report['sha256']}],
|
|
192
|
+
'tech_stack': [], 'dependencies': [], 'tools': [], 'blockers': []}
|
|
193
|
+
refs = ['feature-analysis']
|
|
194
|
+
stack_path = Path(report['path']).with_name('stack.json')
|
|
195
|
+
if report.get('source_schema') != 'inventory/1.1' and stack_path.exists():
|
|
196
|
+
stack_raw = stack_path.read_bytes()
|
|
197
|
+
stack = json.loads(stack_raw)
|
|
198
|
+
fp = data['fingerprint']
|
|
199
|
+
if not isinstance(stack, dict) or any(stack.get('fingerprint', {}).get(k) != fp.get(k) for k in ('algorithm', 'value')):
|
|
200
|
+
raise ValueError('stack.json fingerprint differs from feature-analysis; rerun consistent analysis')
|
|
201
|
+
languages = stack.get('languages')
|
|
202
|
+
if not isinstance(languages, dict) or any(not isinstance(row, dict) or any(type(row.get(k)) is not int or row[k] < 0 for k in ('files', 'lines')) for row in languages.values()):
|
|
203
|
+
raise ValueError('stack.json language counts invalid')
|
|
204
|
+
overview['sources'].append({'source_id': 'analysis-stack', 'path': str(stack_path), 'sha256': hashlib.sha256(stack_raw).hexdigest()})
|
|
205
|
+
overview['code'] = {'files': sum(row['files'] for row in languages.values()), 'lines': sum(row['lines'] for row in languages.values()),
|
|
206
|
+
'languages': [dict(name=name, files=row['files'], lines=row['lines']) for name, row in languages.items()],
|
|
207
|
+
'method': 'legacy stack report source extensions and physical lines; completeness not independently verified',
|
|
208
|
+
'source_refs': ['analysis-stack']}
|
|
209
|
+
|
|
210
|
+
for row in data.get('tech_stack', []):
|
|
211
|
+
overview['tech_stack'].append({'name': row.get('item') or '分析项未命名', 'scope': str(row.get('conclusion', '')), 'evidence': row.get('evidence', ''), 'impact': row.get('impact', ''), 'source_refs': refs})
|
|
212
|
+
for i, row in enumerate(data.get('sdk_dependencies', [])):
|
|
213
|
+
name = str(row.get('sdk') or '未命名依赖')
|
|
214
|
+
category = str(row.get('category', '')).lower()
|
|
215
|
+
kind = 'so' if 'so' in category or name.lower().endswith('.so') else 'sdk' if 'sdk' in category or 'sdk' in name.lower() else 'library'
|
|
216
|
+
overview['dependencies'].append({'name': name, 'kind': kind, 'platform': 'Android → HarmonyOS', 'provision': 'unknown',
|
|
217
|
+
'status': '报告适配结论:' + str(row.get('harmony_support') or '待确认'),
|
|
218
|
+
'reason': '尚未确认自编译或供应商产物处置;' + str(row.get('alternative', '')),
|
|
219
|
+
'evidence': row.get('evidence', ''), 'analysis_ref': '/sdk_dependencies/' + str(i), 'source_refs': refs})
|
|
220
|
+
for row in data.get('user_inputs', []):
|
|
221
|
+
overview['blockers'].append({'name': row.get('item') or '用户待提供项', 'category': row.get('kind') or '接入准备',
|
|
222
|
+
'status': 'open', 'reason': row.get('reason') or '报告记录待提供,实际准备状态待复核',
|
|
223
|
+
'action': row.get('usage') or '核对报告并补齐所需配置', 'evidence': row.get('evidence', ''), 'source_refs': refs})
|
|
224
|
+
return report, overview
|