@harmonyos-arkts/d2h 0.1.20 → 0.1.22

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 (152) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +40 -0
  3. package/agents/android-to-hmos-00-orchestrator.md +3 -315
  4. package/agents/d2h.md +53 -38
  5. package/contracts/workflow/artifacts.md +22 -0
  6. package/contracts/workflow/delivery.md +54 -0
  7. package/contracts/workflow/dispatch.md +26 -0
  8. package/contracts/workflow/execution.md +125 -0
  9. package/contracts/workflow/generation.md +40 -0
  10. package/contracts/workflow/orchestration.md +49 -0
  11. package/contracts/workflow/overview.md +58 -0
  12. package/contracts/workflow/plan.md +119 -0
  13. package/contracts/workflow/scope.md +151 -0
  14. package/dist/bin/install-opencode.js +34 -15
  15. package/dist/src/install-codex.js +131 -0
  16. package/dist/src/install-opencode.js +5 -5
  17. package/dist/src/workflow.js +54 -0
  18. package/docs/architecture-plan-guide.zh-CN.md +70 -0
  19. package/docs/incremental-spec-evaluation-design.md +670 -0
  20. package/docs/spec-plan-guide.md +226 -0
  21. package/docs/workflow-decoupling.md +59 -0
  22. package/opencode/interactive-tools/advance-progress-demo.mjs +21 -0
  23. package/opencode/interactive-tools/browser-host.ts +110 -0
  24. package/opencode/interactive-tools/core.ts +242 -0
  25. package/opencode/interactive-tools/demo-workflow.ts +55 -0
  26. package/opencode/interactive-tools/demo.mjs +37 -0
  27. package/opencode/interactive-tools/package-lock.json +1876 -0
  28. package/opencode/interactive-tools/package.json +23 -0
  29. package/opencode/interactive-tools/panel.ts +40 -0
  30. package/opencode/interactive-tools/prepare-demo.mjs +17 -0
  31. package/opencode/interactive-tools/prepare-progress-demo.mjs +13 -0
  32. package/opencode/interactive-tools/progress.ts +118 -0
  33. package/opencode/interactive-tools/server-legacy.mjs +2 -0
  34. package/opencode/interactive-tools/server.mjs +40 -0
  35. package/opencode/interactive-tools/tui.tsx +230 -0
  36. package/package.json +9 -1
  37. package/schemas/native-libs-analysis-report.schema.json +695 -0
  38. package/schemas/native-libs-verify-report.schema.json +566 -0
  39. package/schemas/spec-native-libs-shard.schema.json +110 -0
  40. package/skills/arkts-icon-sizing/SKILL.md +0 -10
  41. package/skills/cpp/d2h-native-library-analyse/SKILL.md +91 -0
  42. package/skills/cpp/d2h-native-library-analyse/scripts/analyse-android-so.py +630 -0
  43. package/skills/cpp/d2h-native-library-build/SKILL.md +403 -0
  44. package/skills/cpp/d2h-native-library-build/references/build-config-mapping.md +180 -0
  45. package/skills/cpp/d2h-native-library-build/references/jni-to-napi-mapping.md +74 -0
  46. package/skills/cpp/d2h-native-library-build/scripts/check-android-so.py +580 -0
  47. package/skills/cpp/d2h-native-library-verify/SKILL.md +111 -0
  48. package/skills/cpp/d2h-native-library-verify/references/demo-build-sign-run.md +67 -0
  49. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/AppScope/app.json5 +1 -0
  50. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/AppScope/resources/base/element/string.json +1 -0
  51. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/README.md +25 -0
  52. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/build-profile.json5 +24 -0
  53. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/build-profile.json5 +1 -0
  54. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/hvigorfile.ts +2 -0
  55. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/oh-package.json5 +1 -0
  56. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/ets/entryability/EntryAbility.ets +5 -0
  57. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/ets/pages/Index.ets +3 -0
  58. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/ets/pages/NativeVerifyDemo.ets +276 -0
  59. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/module.json5 +1 -0
  60. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/resources/base/element/color.json +1 -0
  61. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/resources/base/element/string.json +1 -0
  62. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/resources/base/profile/main_pages.json +1 -0
  63. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/ohosTest/ets/test/FFmpegVerify.test.ets +176 -0
  64. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/ohosTest/ets/test/List.test.ets +2 -0
  65. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/ohosTest/module.json5 +1 -0
  66. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/hvigor/hvigor-config.json5 +1 -0
  67. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/hvigorfile.ts +2 -0
  68. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/oh-package.json5 +9 -0
  69. package/skills/cpp/d2h-native-library-verify/references/demo-template.md +176 -0
  70. package/skills/cpp/d2h-native-library-verify/references/hypium-test-template.md +163 -0
  71. package/skills/cpp/d2h-native-library-verify/scripts/__pycache__/native_common.cpython-313.pyc +0 -0
  72. package/skills/cpp/d2h-native-library-verify/scripts/device-verify.py +304 -0
  73. package/skills/cpp/d2h-native-library-verify/scripts/native_common.py +341 -0
  74. package/skills/cpp/d2h-native-library-verify/scripts/verify-ohos-native.py +1099 -0
  75. package/skills/d2h-app-solution/SKILL.md +198 -0
  76. package/skills/d2h-app-solution/agents/openai.yaml +4 -0
  77. package/skills/d2h-app-solution/references/framework-evidence.md +117 -0
  78. package/skills/d2h-app-solution/references/hierarchy-rules.md +88 -0
  79. package/skills/d2h-app-solution/references/inventory-schema.md +305 -0
  80. package/skills/d2h-app-solution/references/technical-analysis.md +101 -0
  81. package/skills/d2h-app-solution/scripts/scan_repositories.py +350 -0
  82. package/skills/d2h-app-solution/scripts/validate_inventory.py +337 -0
  83. package/skills/d2h-architecture-plan/SKILL.md +35 -0
  84. package/skills/d2h-architecture-plan/references/directory-creation.md +43 -0
  85. package/skills/d2h-architecture-plan/references/hybrid-design.md +84 -0
  86. package/skills/d2h-architecture-plan/references/layout-reference.md +83 -0
  87. package/skills/d2h-architecture-plan/references/plan-contract.md +51 -0
  88. package/skills/d2h-architecture-plan/references/source-review.md +50 -0
  89. package/skills/d2h-cloud-knowledge/SKILL.md +28 -7
  90. package/skills/d2h-cloud-knowledge/kb_query.mjs +8 -0
  91. package/skills/d2h-dashboard/SKILL.md +56 -0
  92. package/skills/d2h-product-family-migration/SKILL.md +103 -0
  93. package/skills/d2h-product-family-migration/agents/openai.yaml +3 -0
  94. package/skills/d2h-product-family-migration/assets/incremental-plan.template.md +55 -0
  95. package/skills/d2h-product-family-migration/references/advanced/behavioral-verification.md +59 -0
  96. package/skills/d2h-product-family-migration/references/advanced/existing-skill-integration.md +30 -0
  97. package/skills/d2h-product-family-migration/references/advanced/packaging-and-verification.md +43 -0
  98. package/skills/d2h-product-family-migration/references/advanced/platform-probes.md +26 -0
  99. package/skills/d2h-product-family-migration/references/advanced/product-line-design.md +40 -0
  100. package/skills/d2h-product-family-migration/references/baseline-and-delta.md +50 -0
  101. package/skills/d2h-product-family-migration/tests/markdown-cases.md +76 -0
  102. package/skills/d2h-spec-develop/SKILL.md +41 -0
  103. package/skills/d2h-spec-develop/agents/openai.yaml +7 -0
  104. package/skills/d2h-spec-develop/references/worker-workflow.md +26 -0
  105. package/skills/{d2h-page-spec-generate → d2h-spec-generate}/SKILL.md +24 -13
  106. package/skills/d2h-spec-generate/assets/spec-template.md +109 -0
  107. package/skills/d2h-spec-generate/references/from-plan.md +19 -0
  108. package/skills/d2h-spec-generate/references/spec-content.md +34 -0
  109. package/skills/{d2h-page-spec-generate → d2h-spec-generate}/scripts/build_batch_report.py +35 -84
  110. package/skills/d2h-spec-plan/SKILL.md +38 -0
  111. package/skills/d2h-spec-plan/agents/openai.yaml +4 -0
  112. package/skills/d2h-spec-plan/references/architecture-handoff.md +9 -0
  113. package/skills/d2h-spec-plan/references/plan-contract.md +13 -0
  114. package/skills/d2h-spec-plan/references/planning-method.md +46 -0
  115. package/skills/d2h-spec-plan/references/round-scope-assessment.md +49 -0
  116. package/skills/d2h-spec-plan/references/work-packets.md +61 -0
  117. package/skills/d2h-utg-ai-generate/references/click-paths-schema.json +2 -2
  118. package/skills/d2h-utg-ai-generate/references/page-schema.json +2 -2
  119. package/skills/d2h-utg-ai-generate/references/utg-schema.json +2 -2
  120. package/skills/d2h-utg-static-generate/SKILL.md +2 -2
  121. package/skills/managing-d2h-toolchain/environment-requirements.json +1 -1
  122. package/skills/managing-d2h-toolchain/toolchain.json +1 -1
  123. package/skills/preparing-migration-workspace/SKILL.md +32 -2
  124. package/workflow/__pycache__/artifact_bindings.cpython-313.pyc +0 -0
  125. package/workflow/__pycache__/spec_runtime.cpython-313.pyc +0 -0
  126. package/workflow/artifact_bindings.py +224 -0
  127. package/workflow/assets/dashboard.html +88 -0
  128. package/workflow/build_batch_report.py +8 -0
  129. package/workflow/collect_code_metrics.py +75 -0
  130. package/workflow/dashboard_overview.py +109 -0
  131. package/workflow/delivery_view.py +82 -0
  132. package/workflow/feature_plan.py +234 -0
  133. package/workflow/page_batches.py +223 -0
  134. package/workflow/spec_generation.py +120 -0
  135. package/workflow/spec_runtime.py +890 -0
  136. package/workflow/work_packets.py +85 -0
  137. package/skills/d2h-page-develop/SKILL.md +0 -126
  138. package/skills/d2h-page-develop/agents/openai.yaml +0 -7
  139. package/skills/harmonyos-migration-playbook/SKILL.md +0 -83
  140. package/skills/harmonyos-migration-playbook/agents/openai.yaml +0 -7
  141. package/skills/harmonyos-migration-playbook/references/arkts-compile.md +0 -24
  142. package/skills/harmonyos-migration-playbook/references/harmony-runtime.md +0 -53
  143. package/skills/harmonyos-migration-playbook/references/lesson-lifecycle.md +0 -45
  144. package/skills/harmonyos-migration-playbook/references/protocol-e2e.md +0 -23
  145. package/skills/harmonyos-migration-playbook/references/ui-automation.md +0 -53
  146. package/skills/harmonyos-migration-playbook/references/windows-environment.md +0 -38
  147. package/skills/native-library-substitution/SKILL.md +0 -385
  148. package/skills/native-library-substitution/references/native-library-substitution.json +0 -56906
  149. package/skills/native-library-substitution/references/native-library-substitution.md +0 -163
  150. package/skills/porting-cmake-native-libraries/SKILL.md +0 -57
  151. package/skills/porting-cmake-native-libraries/references/platform-adaptation.md +0 -49
  152. package/skills/porting-cmake-native-libraries/references/report-and-build.md +0 -60
@@ -0,0 +1,337 @@
1
+ #!/usr/bin/env python3
2
+ """Validate hierarchy, repository coverage, confidence, and source citations."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import re
9
+ from pathlib import Path
10
+
11
+
12
+ STATUSES = {"implemented", "partial", "mock", "unreachable", "absent", "uncertain", "not-applicable"}
13
+ CONFIDENCES = {"high", "medium", "low"}
14
+ KINDS = {"screen", "surface", "tab", "menu", "dialog", "action", "setting", "flow-step", "external"}
15
+ EVIDENCE_TYPES = {"entry", "route_declaration", "navigation_call", "ui_label", "ui_interaction", "menu_item", "visibility_condition", "effect", "test", "doc"}
16
+ EVIDENCE_TYPES |= {"tech_stack", "dependency_declaration", "native_library", "permission_declaration", "permission_request"}
17
+ VISIBLE_TYPES = {"ui_label", "menu_item"}
18
+ ACTION_TYPES = {"navigation_call", "ui_interaction", "route_declaration", "effect"}
19
+ RELEASE_STAGES = {"mvp", "core", "full", "uncertain"}
20
+ TECHNOLOGIES = {"java", "kotlin", "cpp", "react-native", "flutter", "h5", "swift", "objective-c", "arkts", "javascript", "typescript", "other", "unknown"}
21
+ TECHNICAL_STATUSES = {"present", "none", "uncertain", "not-applicable"}
22
+ DEPENDENCY_KINDS = {"sdk", "so"}
23
+ PROVENANCE = {"open-source", "closed-source", "mixed", "unknown"}
24
+ LINKAGES = {"direct", "transitive", "dynamic", "unknown"}
25
+ PERMISSION_REQUIREMENTS = {"install-time", "runtime", "special", "conditional", "unknown"}
26
+
27
+
28
+ def validate_citations(items: object, label: str, repo_id: str, repo_map: dict[str, Path], line_counts: dict[Path, int], errors: list[str]) -> set[str]:
29
+ if not isinstance(items, list):
30
+ errors.append(f"{label} must be an array")
31
+ return set()
32
+ types: set[str] = set()
33
+ for evidence_index, citation in enumerate(items):
34
+ citation_label = f"{label}[{evidence_index}]"
35
+ if not isinstance(citation, dict):
36
+ errors.append(f"{citation_label} must be an object")
37
+ continue
38
+ evidence_type = citation.get("type")
39
+ if evidence_type not in EVIDENCE_TYPES:
40
+ errors.append(f"{citation_label}.type invalid: {evidence_type}")
41
+ else:
42
+ types.add(evidence_type)
43
+ rel = citation.get("path")
44
+ line_start = citation.get("line_start")
45
+ if not isinstance(rel, str) or not rel or Path(rel).is_absolute() or ".." in Path(rel).parts:
46
+ errors.append(f"{citation_label}.path must be repository-relative")
47
+ continue
48
+ repo_root = repo_map.get(repo_id)
49
+ source = repo_root / rel if repo_root else Path(rel)
50
+ if not source.is_file():
51
+ errors.append(f"{citation_label} source not found: {source}")
52
+ continue
53
+ if not isinstance(line_start, int) or line_start < 1:
54
+ errors.append(f"{citation_label}.line_start must be >= 1")
55
+ else:
56
+ if source not in line_counts:
57
+ try:
58
+ line_counts[source] = sum(1 for _ in source.open("rb"))
59
+ except OSError:
60
+ line_counts[source] = 0
61
+ if line_start > line_counts[source]:
62
+ errors.append(f"{citation_label}.line_start exceeds file length")
63
+ line_end = citation.get("line_end", line_start)
64
+ if not isinstance(line_end, int) or isinstance(line_start, int) and line_end < line_start:
65
+ errors.append(f"{citation_label}.line_end must be >= line_start")
66
+ if not isinstance(citation.get("summary"), str) or not citation["summary"].strip():
67
+ errors.append(f"{citation_label}.summary is required")
68
+ return types
69
+
70
+
71
+ def validate_negative_searches(value: object, label: str, errors: list[str]) -> list[str]:
72
+ if not isinstance(value, list):
73
+ errors.append(f"{label} must be an array")
74
+ return []
75
+ searches = [item.strip() for item in value if isinstance(item, str) and item.strip()]
76
+ if len(searches) < 2:
77
+ errors.append(f"{label} requires at least two concrete searches")
78
+ return searches
79
+
80
+
81
+ def main() -> int:
82
+ parser = argparse.ArgumentParser(description=__doc__)
83
+ parser.add_argument("inventory", type=Path)
84
+ parser.add_argument("--strict", action="store_true", help="fail when warnings exist")
85
+ args = parser.parse_args()
86
+ data = json.loads(args.inventory.read_text(encoding="utf-8"))
87
+ errors: list[str] = []
88
+ warnings: list[str] = []
89
+
90
+ if data.get("schema_version") != "1.2":
91
+ errors.append("schema_version must be '1.2'")
92
+ max_depth = data.get("max_depth")
93
+ if max_depth != 4:
94
+ errors.append("max_depth must be 4")
95
+
96
+ repos = data.get("repositories")
97
+ if not isinstance(repos, list) or not repos:
98
+ errors.append("repositories must be a non-empty array")
99
+ repos = []
100
+ repo_map: dict[str, Path] = {}
101
+ repo_platforms: dict[str, str] = {}
102
+ for index, repo in enumerate(repos):
103
+ label = f"repositories[{index}]"
104
+ repo_id = repo.get("id") if isinstance(repo, dict) else None
105
+ if not isinstance(repo_id, str) or not re.fullmatch(r"[a-z][a-z0-9_]*", repo_id):
106
+ errors.append(f"{label}.id must be stable snake_case")
107
+ continue
108
+ if repo_id in repo_map:
109
+ errors.append(f"duplicate repository id: {repo_id}")
110
+ path = Path(str(repo.get("path", ""))).expanduser()
111
+ if not path.is_absolute() or not path.is_dir():
112
+ errors.append(f"{label}.path must be an existing absolute directory: {path}")
113
+ repo_map[repo_id] = path
114
+ repo_platforms[repo_id] = str(repo.get("platform", "")).lower()
115
+
116
+ features = data.get("features")
117
+ if not isinstance(features, list):
118
+ errors.append("features must be an array")
119
+ features = []
120
+ seen: set[tuple[str, tuple[str, ...]]] = set()
121
+ line_counts: dict[Path, int] = {}
122
+
123
+ for index, feature in enumerate(features):
124
+ label = f"features[{index}]"
125
+ if not isinstance(feature, dict):
126
+ errors.append(f"{label} must be an object")
127
+ continue
128
+ context = feature.get("entry_context")
129
+ if not isinstance(context, str) or not context.strip():
130
+ errors.append(f"{label}.entry_context is required")
131
+ path_parts = feature.get("path")
132
+ if not isinstance(path_parts, list) or not 1 <= len(path_parts) <= 4 or any(not isinstance(part, str) or not part.strip() for part in path_parts):
133
+ errors.append(f"{label}.path must contain 1-4 non-empty strings")
134
+ continue
135
+ dedupe_key = (str(context), tuple(part.strip() for part in path_parts))
136
+ if dedupe_key in seen:
137
+ errors.append(f"duplicate path in context {context}: {' > '.join(path_parts)}")
138
+ seen.add(dedupe_key)
139
+ if feature.get("kind") not in KINDS:
140
+ errors.append(f"{label}.kind must be one of {sorted(KINDS)}")
141
+ if not isinstance(feature.get("trigger"), str) or not feature["trigger"].strip():
142
+ errors.append(f"{label}.trigger is required")
143
+ if not isinstance(feature.get("effect"), str) or not feature["effect"].strip():
144
+ warnings.append(f"{label}.effect is empty")
145
+ if not isinstance(feature.get("should_develop"), bool):
146
+ errors.append(f"{label}.should_develop must be boolean")
147
+ elif feature["should_develop"] is False and (not isinstance(feature.get("notes"), str) or not feature["notes"].strip()):
148
+ errors.append(f"{label}.notes must explain why should_develop is false")
149
+ if feature.get("release_stage") not in RELEASE_STAGES:
150
+ errors.append(f"{label}.release_stage must be one of {sorted(RELEASE_STAGES)}")
151
+ if not isinstance(feature.get("release_rationale"), str) or not feature["release_rationale"].strip():
152
+ errors.append(f"{label}.release_rationale is required")
153
+
154
+ implementations = feature.get("implementations")
155
+ if not isinstance(implementations, dict):
156
+ errors.append(f"{label}.implementations must be an object")
157
+ continue
158
+ missing = sorted(set(repo_map) - set(implementations))
159
+ extra = sorted(set(implementations) - set(repo_map))
160
+ if missing:
161
+ errors.append(f"{label} missing repository statuses: {missing}")
162
+ if extra:
163
+ errors.append(f"{label} has unknown repository statuses: {extra}")
164
+
165
+ for repo_id, impl in implementations.items():
166
+ impl_label = f"{label}.implementations.{repo_id}"
167
+ if not isinstance(impl, dict):
168
+ errors.append(f"{impl_label} must be an object")
169
+ continue
170
+ status = impl.get("status")
171
+ confidence = impl.get("confidence")
172
+ if status not in STATUSES:
173
+ errors.append(f"{impl_label}.status invalid: {status}")
174
+ if confidence not in CONFIDENCES:
175
+ errors.append(f"{impl_label}.confidence invalid: {confidence}")
176
+ technology_stack = impl.get("technology_stack")
177
+ if not isinstance(technology_stack, list) or any(item not in TECHNOLOGIES for item in technology_stack):
178
+ errors.append(f"{impl_label}.technology_stack must contain only {sorted(TECHNOLOGIES)}")
179
+ technology_stack = []
180
+ elif len(technology_stack) != len(set(technology_stack)):
181
+ errors.append(f"{impl_label}.technology_stack must not contain duplicates")
182
+ if status in {"implemented", "partial", "mock", "unreachable"} and not technology_stack:
183
+ errors.append(f"{impl_label}.technology_stack is required for status {status}")
184
+ technology_types = validate_citations(impl.get("technology_evidence", []), f"{impl_label}.technology_evidence", repo_id, repo_map, line_counts, errors)
185
+ if status in {"implemented", "partial", "mock", "unreachable"} and "tech_stack" not in technology_types:
186
+ errors.append(f"{impl_label}.technology_evidence needs tech_stack evidence")
187
+
188
+ external = impl.get("external_dependencies")
189
+ if not isinstance(external, dict):
190
+ errors.append(f"{impl_label}.external_dependencies must be an object")
191
+ else:
192
+ external_status = external.get("status")
193
+ if external_status not in TECHNICAL_STATUSES:
194
+ errors.append(f"{impl_label}.external_dependencies.status invalid: {external_status}")
195
+ if status in {"implemented", "partial", "mock", "unreachable"} and external_status == "not-applicable":
196
+ errors.append(f"{impl_label}.external_dependencies cannot be not-applicable for an existing implementation")
197
+ items = external.get("items", [])
198
+ if not isinstance(items, list):
199
+ errors.append(f"{impl_label}.external_dependencies.items must be an array")
200
+ items = []
201
+ if external_status == "present" and not items:
202
+ errors.append(f"{impl_label}.external_dependencies present requires items")
203
+ if external_status in {"none", "not-applicable"} and items:
204
+ errors.append(f"{impl_label}.external_dependencies {external_status} must not contain items")
205
+ if external_status == "none":
206
+ validate_negative_searches(external.get("negative_searches"), f"{impl_label}.external_dependencies.negative_searches", errors)
207
+ for item_index, item in enumerate(items):
208
+ item_label = f"{impl_label}.external_dependencies.items[{item_index}]"
209
+ if not isinstance(item, dict):
210
+ errors.append(f"{item_label} must be an object")
211
+ continue
212
+ if not isinstance(item.get("name"), str) or not item["name"].strip():
213
+ errors.append(f"{item_label}.name is required")
214
+ if item.get("kind") not in DEPENDENCY_KINDS:
215
+ errors.append(f"{item_label}.kind invalid: {item.get('kind')}")
216
+ if item.get("provenance") not in PROVENANCE:
217
+ errors.append(f"{item_label}.provenance invalid: {item.get('provenance')}")
218
+ if item.get("linkage") not in LINKAGES:
219
+ errors.append(f"{item_label}.linkage invalid: {item.get('linkage')}")
220
+ if not isinstance(item.get("required"), bool):
221
+ errors.append(f"{item_label}.required must be boolean")
222
+ if not isinstance(item.get("purpose"), str) or not item["purpose"].strip():
223
+ errors.append(f"{item_label}.purpose is required")
224
+ dependency_types = validate_citations(item.get("evidence", []), f"{item_label}.evidence", repo_id, repo_map, line_counts, errors)
225
+ if not dependency_types & {"dependency_declaration", "native_library"}:
226
+ errors.append(f"{item_label}.evidence needs dependency declaration or native-library evidence")
227
+ if not dependency_types & {"effect", "native_library"}:
228
+ errors.append(f"{item_label}.evidence needs feature usage, bridge, call, or load evidence")
229
+
230
+ permissions = impl.get("android_permissions")
231
+ if not isinstance(permissions, dict):
232
+ errors.append(f"{impl_label}.android_permissions must be an object")
233
+ else:
234
+ permission_status = permissions.get("status")
235
+ if permission_status not in TECHNICAL_STATUSES:
236
+ errors.append(f"{impl_label}.android_permissions.status invalid: {permission_status}")
237
+ if repo_platforms.get(repo_id) != "android" and permission_status != "not-applicable":
238
+ errors.append(f"{impl_label}.android_permissions must be not-applicable for non-Android repositories")
239
+ if repo_platforms.get(repo_id) == "android" and status in {"implemented", "partial", "mock", "unreachable"} and permission_status == "not-applicable":
240
+ errors.append(f"{impl_label}.android_permissions cannot be not-applicable for an Android implementation")
241
+ permission_items = permissions.get("items", [])
242
+ if not isinstance(permission_items, list):
243
+ errors.append(f"{impl_label}.android_permissions.items must be an array")
244
+ permission_items = []
245
+ if permission_status == "present" and not permission_items:
246
+ errors.append(f"{impl_label}.android_permissions present requires items")
247
+ if permission_status in {"none", "not-applicable"} and permission_items:
248
+ errors.append(f"{impl_label}.android_permissions {permission_status} must not contain items")
249
+ if permission_status == "none":
250
+ validate_negative_searches(permissions.get("negative_searches"), f"{impl_label}.android_permissions.negative_searches", errors)
251
+ for item_index, item in enumerate(permission_items):
252
+ item_label = f"{impl_label}.android_permissions.items[{item_index}]"
253
+ if not isinstance(item, dict):
254
+ errors.append(f"{item_label} must be an object")
255
+ continue
256
+ if not isinstance(item.get("name"), str) or not item["name"].strip():
257
+ errors.append(f"{item_label}.name is required")
258
+ if item.get("requirement") not in PERMISSION_REQUIREMENTS:
259
+ errors.append(f"{item_label}.requirement invalid: {item.get('requirement')}")
260
+ if not isinstance(item.get("required_when"), str) or not item["required_when"].strip():
261
+ errors.append(f"{item_label}.required_when is required")
262
+ if not isinstance(item.get("purpose"), str) or not item["purpose"].strip():
263
+ errors.append(f"{item_label}.purpose is required")
264
+ permission_types = validate_citations(item.get("evidence", []), f"{item_label}.evidence", repo_id, repo_map, line_counts, errors)
265
+ if not permission_types & {"permission_request", "effect"}:
266
+ errors.append(f"{item_label}.evidence needs a permission request/check or protected API usage")
267
+ evidence = impl.get("evidence", [])
268
+ if not isinstance(evidence, list):
269
+ errors.append(f"{impl_label}.evidence must be an array")
270
+ evidence = []
271
+ types: set[str] = set()
272
+ production_types: set[str] = set()
273
+ for evidence_index, citation in enumerate(evidence):
274
+ citation_label = f"{impl_label}.evidence[{evidence_index}]"
275
+ if not isinstance(citation, dict):
276
+ errors.append(f"{citation_label} must be an object")
277
+ continue
278
+ evidence_type = citation.get("type")
279
+ if evidence_type not in EVIDENCE_TYPES:
280
+ errors.append(f"{citation_label}.type invalid: {evidence_type}")
281
+ else:
282
+ types.add(evidence_type)
283
+ if evidence_type not in {"test", "doc"}:
284
+ production_types.add(evidence_type)
285
+ rel = citation.get("path")
286
+ line_start = citation.get("line_start")
287
+ if not isinstance(rel, str) or not rel or Path(rel).is_absolute() or ".." in Path(rel).parts:
288
+ errors.append(f"{citation_label}.path must be repository-relative")
289
+ continue
290
+ repo_root = repo_map.get(repo_id)
291
+ source = repo_root / rel if repo_root else Path(rel)
292
+ if not source.is_file():
293
+ errors.append(f"{citation_label} source not found: {source}")
294
+ continue
295
+ if not isinstance(line_start, int) or line_start < 1:
296
+ errors.append(f"{citation_label}.line_start must be >= 1")
297
+ else:
298
+ if source not in line_counts:
299
+ try:
300
+ line_counts[source] = sum(1 for _ in source.open("rb"))
301
+ except OSError:
302
+ line_counts[source] = 0
303
+ if line_start > line_counts[source]:
304
+ errors.append(f"{citation_label}.line_start exceeds file length")
305
+ line_end = citation.get("line_end", line_start)
306
+ if not isinstance(line_end, int) or isinstance(line_start, int) and line_end < line_start:
307
+ errors.append(f"{citation_label}.line_end must be >= line_start")
308
+ if not isinstance(citation.get("summary"), str) or not citation["summary"].strip():
309
+ errors.append(f"{citation_label}.summary is required")
310
+
311
+ if status in {"implemented", "partial", "mock"} and not (types & ACTION_TYPES):
312
+ errors.append(f"{impl_label} needs interaction, route, navigation, or effect evidence")
313
+ if status in {"implemented", "partial", "mock", "unreachable"} and not production_types:
314
+ errors.append(f"{impl_label} cannot rely only on test/doc evidence")
315
+ if status == "implemented" and confidence == "high":
316
+ if not (types & VISIBLE_TYPES):
317
+ warnings.append(f"{impl_label} high confidence lacks visible label/menu evidence")
318
+ if len(production_types & ACTION_TYPES) < 1 or len(production_types) < 2:
319
+ warnings.append(f"{impl_label} high confidence has fewer than two production evidence classes")
320
+ if status == "absent":
321
+ searches = impl.get("negative_searches")
322
+ if not isinstance(searches, list) or len([item for item in searches if isinstance(item, str) and item.strip()]) < 2:
323
+ errors.append(f"{impl_label} absent requires at least two concrete negative_searches")
324
+ if confidence == "high" and len(searches or []) < 4:
325
+ warnings.append(f"{impl_label} high-confidence absence should document broader negative searches")
326
+
327
+ for message in errors:
328
+ print(f"ERROR: {message}")
329
+ for message in warnings:
330
+ print(f"WARNING: {message}")
331
+ print(json.dumps({"features": len(features), "repositories": len(repo_map), "errors": len(errors), "warnings": len(warnings)}, ensure_ascii=False))
332
+ return 1 if errors or args.strict and warnings else 0
333
+
334
+
335
+ if __name__ == "__main__":
336
+ raise SystemExit(main())
337
+
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: d2h-architecture-plan
3
+ description: >-
4
+ 用于 Android 转 HarmonyOS 的架构评估、目录规划和方案选型。默认读取 Android 源码,交付原结构对照、鸿蒙优选、融合三套结构选项,详列推荐方案目录;
5
+ 用户确认方案并要求创建时,可落地基础工程与关键目录。
6
+ ---
7
+
8
+ # Android 源码 → 鸿蒙架构与目录规划
9
+
10
+ 基于 Android 工程结构与必要源码,为迁移确定职责归属、依赖方向和可落地的鸿蒙目录,兼顾原开发者维护便利性。默认做目录级架构分析,不开展完整业务理解、逐页行为分析或实现级迁移设计。
11
+
12
+ ## 输入与边界
13
+
14
+ - 必需输入只有 Android 源码根目录,可独立执行。按用户范围识别实际应用、运行时模块、技术栈和目标设备;源码缺失或目标能力未知时标明影响。
15
+ - 默认只读取 Android 工程源码及其相关资源、构建声明;不自动读取同级、嵌套或配置中指向的鸿蒙工程源码,也不为寻找鸿蒙参考实现扩大扫描范围。
16
+ - 仅当已有上下文明确表明存在鸿蒙工程,且用户尚未决定是否结合时,询问一次:“是否结合现有鸿蒙工程进行架构规划?默认仅分析 Android 工程。”仅提供新工程的输出路径不触发询问,配置中的目标路径也不能单独证明已有鸿蒙工程或用户选择结合。
17
+ - 用户明确要求结合/复用指定鸿蒙工程时,按约定范围读取;明确选择不结合时,只分析 Android。同一工程、同一任务范围内沿用已有明确选择,不重复询问;用户改变选择时据此调整。尚未答复期间可继续 Android 侧分析,不读取鸿蒙源码,也不把沉默记为用户拒绝。
18
+ - 已有功能清单、Facts、规划可作导航线索,用当前源码核验。
19
+ - 默认只交付架构 Markdown;不生成持久化的全量文件清单、源码快照、import/调用图或扫描基线。已有大型扫描产物按问题查询,不整体读入或为更新规划重新生成。
20
+ - 源工程只读;默认在同级 `<工程名>-migration/.migration/architecture-plan/` 交付。已有文件先阅读,保留用户批注,修订时同步受影响部分。
21
+ - 分析分支和目录组织依据本次读取范围内的工程事实与迁移目标确定,参考示例按实际需要选用。未读取的鸿蒙工程不作已核验的复用或实现依据。
22
+
23
+ ## 架构规划
24
+
25
+ 1. 按 [source-review.md](references/source-review.md) 从构建声明、入口/导航和主要源码目录建立结构地图,识别基础依赖、页面业务、共享业务及装配。仅当证据不足会改变拆合、归属或依赖方向时,定点读取相关源码;边界可解释后停止扩读。
26
+ 2. 按 [layout-reference.md](references/layout-reference.md) 设计原结构对照 `android-mirror`、鸿蒙优选 `harmony-practice`、融合 `hybrid`,共用范围地图;已有选定方案时只修订它及受影响的对照。融合结构以基础依赖保留原模块/包族、业务按 feature 承载页面为原则,需要详细展开时读 [hybrid-design.md](references/hybrid-design.md),不展开逐页实施计划。
27
+ 3. 按 [plan-contract.md](references/plan-contract.md) 详细展开推荐方案的关键目录、归属与主要依赖,另两套给结构差异对照;已有选定方案则围绕它展开。用户明确要求三套完整目录时再全部展开,共享事实仍只写一次。复核目录/归属/依赖一致性,不重复业务链或完整分析报告。
28
+
29
+ 本 Skill 交付架构与转换交接设计;并行候选表示具备独立转换的架构条件,实际派发、开发和验收由调用方的执行流程决定。
30
+
31
+ ## 选型与可选落地
32
+
33
+ 只请求分析/规划时,交付推荐和可选方案即完成本次规划,不为结束任务强求用户立即选型。用户选定后按意见修订、复核并记录最终方案。**创建或搬迁前须有对当前方案的实际确认及创建要求**;已有“采用推荐并创建”的明确授权则直接按授权执行。
34
+
35
+ 需要落地时才读 [directory-creation.md](references/directory-creation.md),使用本机 DevEco CLI 和普通文件工具创建选中方案的基础工程与目录。不生成业务实现,不构建或运行应用。
@@ -0,0 +1,43 @@
1
+ # 用户确认后的目录落地
2
+
3
+ 仅在用户要求创建/搬迁且已确认当前方案时读取。本阶段使用普通文件工具与本机 DevEco CLI。只创建基础工程和关键目录,不实现业务、不构建或运行应用。
4
+
5
+ ## 1. 固定实际选择与目标
6
+
7
+ - 记录用户选择的方案ID、当前规划文件、修改意见和创建要求;明确推荐委托同样有效,不能自动把模型推荐当用户授权。
8
+ - 从选中方案推导目录清单,核对路径位于目标根内,无绝对子路径、上级跳转、大小写冲突或模块嵌套。不得跟随链接写入源/参考工程或目标根之外。
9
+ - 先检查目标实际状态和已有工程;保留用户文件。新增目录通常可直接执行;重用、移动或覆盖存在冲突的内容先查清,未授权覆盖则停止该动作并说明具体冲突。
10
+ - 目标设备/SDK与方案一致;未知设备能力不以默认phone掩盖,也不把所有Android设备名直接写入鸿蒙配置。
11
+
12
+ ## 2. 创建或复用基础工程
13
+
14
+ 新工程先定位本机DevEco CLI,阅读其帮助核对当前create命令、应用名、bundle、设备和输出路径等参数;不把Android构建文件复制成鸿蒙配置。需要用户身份信息时只询问缺少的实际值。
15
+
16
+ 已有工程先核对根构建配置、注册模块及本地依赖,确认可承载所选目录。保留CLI模板和用户文件。需要把模板entry放到选中产品路径时,先核对目标无冲突,再同步根注册srcPath及相关本地路径;无法确认安全时不移动,报告差异与必要改法。
17
+
18
+ 创建/复用授权所需的目标配置核对、目录冲突检查与创建结果验证,不等于选择结合鸿蒙源码进行架构分析。若还需阅读现有业务实现来判断复用或调整架构,先按 [SKILL.md](../SKILL.md) 询问是否结合,已有明确选择则按其范围执行。
19
+
20
+ CLI不可用或不支持目标设备时,保留已完成规划,给出实际错误及未完成创建项;普通空目录不能冒充可用鸿蒙基础工程。不要为本阶段无限安装/重试工具或索要与创建无关的凭据。
21
+
22
+ ## 3. 补齐选中目录
23
+
24
+ 只创建选中方案的实际模块/关键子目录。模板之外不生成业务文件、空实现、额外模块配置或提前注册所有HAR/HSP;这些目录须明确标注为待下游配置的目录壳子。
25
+
26
+ 选中原本仅作差异对照的方案时,先据已确认的结构差异补齐它的目录树。规划使用共用 feature 子目录模式时,从关键树与模式展开本次实际清单,按实际职责选取子目录即可;不为展开或创建壳子重做业务分析。若发现归属冲突或影响实际布局的待决,局部澄清该边界后再创建受影响目录。
27
+
28
+ 选中融合方案时从已确认映射创建基础包族与页面 feature 目录,保留逻辑目录和实际模块的区别;单 HAP 的 feature 目录落在实际源码根内,资源服从所属模块规则,不套用独立库的 `src/main`。共享契约和集成所有权随规划交接,不以生成接口桩或复制共享状态充当完成前置。
29
+
30
+ 命令成功后检查目标根基础配置、注册产品路径及关键目录确实存在,设备配置与选择一致,已有文件没有被覆盖;若执行中用户修改规划,先核对影响,不能按旧目录继续宣称完整。
31
+
32
+ ## 4. 保存实际记录并交接
33
+
34
+ 在规划产物目录写 `shell-receipt.json`,记录可核验的实际创建结果:
35
+
36
+ - `status`:complete / partial / blocked,按真实创建结果填写。
37
+ - `planFile`、`proposalId`、`target`:当前规划、选中方案和实际目标根。
38
+ - `confirmation`:实际用户选择/推荐委托及对应修订,未确认不得执行创建。
39
+ - `baseProject`:新建或复用、实际注册产品路径、设备类型及使用的CLI/命令/日志位置。
40
+ - `createdDirectories`、`existingDirectories`、`directoryOnlyModules`:分别记录新增、已存在和未注册的模块目录。
41
+ - `verification`、`remainingIssues`:实际检查结果、路径/配置差异和未完成项,不编造日志或检查。
42
+
43
+ 创建完成只表示基础工程和所选目录壳子已落地。交接最终规划、receipt与未解决风险;其他两套保留为对照。模型生成的记录不能替代目标文件/目录和用户授权证据。
@@ -0,0 +1,84 @@
1
+ # 融合方案:基础保留结构,业务按页面组织
2
+
3
+ 设计或修订 `hybrid` 时读取。目标是让原开发者沿原模块/包族定位基础能力,让后续转换能按页面分工。先分类与核对主要依赖,再画目录;两个层次是迁移职责,不等同于两个物理模块。阅读深度按 [source-review.md](source-review.md) 控制,以下约束用于确定边界,不要求在此完成实现设计。
4
+
5
+ ## 1. 按职责划分两层
6
+
7
+ | 归属 | 判断依据 | 组织原则 |
8
+ |---|---|---|
9
+ | 基础依赖层 | 网络传输、通用存储、日志、基础协议、平台/Native/三方包装等支撑能力;有实际消费者,不决定页面流程 | 优先保留原模块名、包族的相对层级与合理依赖;保留业务无关的通用 UI 时仍需替换 Android 展示实现,数据/协议不依赖 UI |
10
+ | 业务层的页面 feature | 页面/紧密页面组的展示、状态、交互规则、私有数据访问和资源 | 按目标职责内聚,采用 ArkUI 页面、ViewModel/等效状态管理及数据访问分离;不强制为简单页面增设 Domain/UseCase |
11
+ | 业务层的共享业务 | 多页共同使用的业务规则、Repository、会话或任务状态,有唯一业务所有者 | 放在所属业务域的共享区域,提供稳定公开能力;不能仅因无 UI 或多页复用就归入基础依赖 |
12
+ | 业务侧应用装配 | Ability、启动、导航容器、依赖注入与设备/产品配置 | 产品壳装配基础实现、共享业务与页面;不承接全应用业务状态,不新增一套与两层竞争的业务归属 |
13
+
14
+ 按真实职责分类,不按 Gradle 模块、Activity 类名或 `common/lib` 名字整包贴标签。原依赖库中含页面则把页面抽入 feature;原 app 包中的通用网络/存储可归基础层;源码中的领域模型/业务 Repository 按语义与所有者归业务域。后台、外部入口及非页面业务也要保留所有者与生命周期,不能因按页面组织而遗漏。
15
+
16
+ ## 2. 基础依赖:保留定位,显式适配
17
+
18
+ 以实际源码根为锚点,把原模块/包族映射到目标。默认保留有维护价值的模块名与源码根以下的相对目录;已有多层模块路径保留必要的定位关系。语言根从 `java/kotlin` 变为 `ets` 等目标变化单列,不在目标中嵌套一份 Android 工程。
19
+
20
+ 例如 `infra/src/main/java/com/acme/infra/net/` 可映射到 `commons/infra/src/main/ets/com/acme/infra/net/`。源文件的主要职责只认领一次;混合文件按符号/职责说明页面、共享业务或适配内容的去向,不让同一粗路径同时代表“整体保留”和“全部抽走”。
21
+
22
+ 保留的是可追溯结构与合理协作,不是 Android 实现机制:Android API、资源格式、JNI/运行时和 SDK 坐标按目标承接方式替换。原结构中反向依赖页面、应用单例、环或业务混杂的部分需要最小拆分/改向,逐项记录原路径、保留部分、调整部分、目标所有者、消费者与理由。未确认的 SDK/Native/运行时能力记录受影响边界和验证前置,不能凭保留目录认定可直接转换。
23
+
24
+ 不要为了套 `core/network/storage/model` 模板再次打散已清晰的原包族;也不要为了保留路径把对页面/应用单例的依赖、展示状态或业务策略留在基础层。平台适配所需上下文由装配按生命周期提供,避免基础能力持有页面实例。跨层拆分成本明显超过收益时说明替代边界及仍受耦合限制的页面。
25
+
26
+ ## 3. 业务层:feature 是转换边界
27
+
28
+ 依据主要页面族与代表入口规划 feature 候选,不要求先穷举全部页面。优先让可独立转换的页面有明确目录;强共享私有状态或不可拆生命周期的页面可组成紧密页面组,说明原因。多页共用领域会话可通过共享所有者承接,不因此把整个业务域合成不可并行的一组。Activity/Fragment 个数及追求并行度不单独决定拆分;Dialog/Sheet/页面模式按已知职责归属,具体页面清单留待后续细化。
29
+
30
+ 每个 feature 内聚页面、ViewModel/等效状态、私有数据访问、组件和资源;按真实复杂度选目录,保留原业务名及源码映射便于定位。共享会话、缓存、任务队列等由一个领域所有者维护,页面只保留展示投影;导航传必要标识/值或约定结果,不传其他页面的 ViewModel、组件实例或可变状态。
31
+
32
+ `feature` 是职责与目录边界,不是“一页一个 HAR/HSP”。先确定页面归属,再按部署、运行时共享和构建成本决定哪些目录构成 HAP/HAR/HSP。不同 HAR 中重复打包或初始化同一状态不能当作进程级单例;需共享状态时说明实际实例如何由装配创建、提供、持有与释放。
33
+
34
+ 下面仅示意已存在 `infra` 基础包、商品列表/详情共享业务及独立设置页的工程。树只列目录,名字和包粒度从实际源码确定;`commons`、`features` 分别可承载两层,`products` 承载业务侧装配。
35
+
36
+ ```text
37
+ <HarmonyOS>/
38
+ ├── AppScope/
39
+ ├── products/<产品>/src/main/ # HAP 示例
40
+ │ ├── ets/{entryability,navigation,composition}/
41
+ │ └── resources/
42
+ ├── commons/infra/src/main/ # 基础 HAR 示例
43
+ │ ├── ets/com/acme/infra/ # 原基础包族相对层级
44
+ │ │ ├── net/
45
+ │ │ └── store/
46
+ │ └── resources/ # 原基础能力实际拥有的资源
47
+ └── features/
48
+ ├── catalog/src/main/ # 业务 HAR 示例,含两个页面 feature
49
+ │ ├── ets/
50
+ │ │ ├── list/{pages,viewmodel,data,components}/
51
+ │ │ ├── detail/{pages,viewmodel,data}/
52
+ │ │ └── shared/{model,repository,state}/
53
+ │ └── resources/ # 同模块资源,划分页面命名与集成归属
54
+ └── settings/src/main/ # 另一业务 HAR 示例
55
+ ├── ets/{pages,viewmodel}/
56
+ └── resources/
57
+ ```
58
+
59
+ 上例列表/详情共享一个业务 HAR,页面 feature 是其内部独立目录。实际工程可以合入产品 HAP,也可在独立演进收益足够时拆库;目录树需标注实际模块及源码根,不把业务域分组自动变成构建模块。共享状态实例仍按装配契约提供,不由 HAR 名称保证唯一性。
60
+
61
+ 单 HAP 可在 `entry/src/main/ets/` 下使用 `foundation/<原包族>/`、`features/<业务>/<页面>/`、必要的 `features/<业务>/shared/` 和装配目录;资源按目标模块规则放置并划分命名/写入所有权。模块内目录也能定义页面转换边界,不能虚构每页独立的资源根或构建能力。
62
+
63
+ ## 4. 依赖与共享契约
64
+
65
+ 说明代码依赖与运行时调用各自如何闭合:
66
+
67
+ - 产品壳装配页面、共享业务与基础实现;页面使用本域共享业务的公开能力和基础能力,基础能力之间保留已核验的无环依赖,基础层不反向依赖 feature 或产品壳。
68
+ - feature 不直接导入其他页面的内部页面、状态、组件或数据实现。跨 feature 导航使用约定路由/参数/结果;数据、事件与回调另给实际契约及所有者,不能用“走路由”解释全部协作。
69
+ - 共享业务按消费者与领域归属放置,必要时形成独立共享业务能力,其依赖保持无环;跨域协作不因缺少目录就全部放进全局 `commons`。
70
+ - 平台实现需要业务回调时,在合适的消费者/契约边界定义所需能力,由产品装配注入实现;说明谁定义、谁实现、谁装配及如何取得实例。只有真实跨边界交互才引入契约,不为每个类造接口。
71
+
72
+ 在架构稿中简记关键共享能力、目标所有者、主要消费者及协作/装配方式,未明确的契约列为后续细化前置。只有输入输出、错误或生命周期约束会改变拆合与所有权时才展开;不要求完整契约设计,不预定接口文件,也不把规划能力写成已实现。
73
+
74
+ 导航可参考 [HarmonyOS 应用规划](https://developer.huawei.com/consumer/cn/app/planning/) 中的分层、模块化和 Navigation 设计,以及 [Navigation 跨包路由](https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/arkts-navigation-cross-package) 的模块解耦机制(核查日期:2026-10-29)。两层融合、原包族保留及按页面分工是本 Skill 的迁移设计选择;不将一页一模块作为平台要求。具体路由与打包机制按实际 SDK/设备核实,不仅凭目录选定。
75
+
76
+ ## 5. 从分层到独立页面转换的交接
77
+
78
+ 本阶段交接“源入口/页面族 → feature 目录 → 共享依赖/所有者”及会影响分工的已知前置,复用归属表即可。转换建议按消费者说明最小基础/共享前置,再列可独立转换的页面候选和集成区域;不要求全部基础库先迁完,无关页面不被不相关的 Native/SDK 风险阻塞。
79
+
80
+ 路由、资源聚合、公共导出、构建配置与装配等已知共同修改区域指定集成归属;页面私有目录与共享区域分开。共享修改先回所有者,精确的独占文件、测试资产、完整契约与验收条件由后续页面规划/实现确定,不作为架构稿交付前置。
81
+
82
+ 并行建议附条件:契约明确、实现前置满足、写入范围不冲突且共同修改有集成归属;仅导航关联不自动形成实现顺序。条件未核验时写候选或待细化,强耦合已证实才要求合组/串行;规划契约与空目录不能证明已可开工。集成仍需验证真实能力和跨页协作。
83
+
84
+ 已有稳定 ID 可沿用,没有则以源入口关联候选,不造页面/Spec 身份、账本或任务协议。实际派发、工作区隔离、构建/设备资源协调与验收由执行方决定,架构目录不自动开放并发。
@@ -0,0 +1,83 @@
1
+ # 架构选择与目录参考
2
+
3
+ 先决定职责、状态所有权、协作和演进边界,再决定目录。以下给出判断方法及可选目录示例;名称、分层与模块数量由当前工程职责和维护需求决定。
4
+
5
+ 这些决定使用结构地图与必要的边界证据即可,不要求先验证全部业务行为。共享结论在三套方案间复用;本参考用于选择结构,阅读深度与停止条件见 [source-review.md](source-review.md)。
6
+
7
+ ## 1. 选择适合工程的方法
8
+
9
+ | 判断 | 决策原则 |
10
+ |---|---|
11
+ | 原架构是否健康 | 有清晰边界与状态来源时保留其有效思想;MVC/MVP/MVVM/MVI名称本身不决定质量 |
12
+ | UI、规则与数据如何协作 | 通常先分离展示/状态和数据访问,评估单向数据流、MVVM或等效状态模式;复杂/复用规则需要时加Domain/UseCase,不强制完整Clean分层 |
13
+ | 什么时候拆模块 | 职责内聚、真实跨域复用、独立演进/上游升级、平台边界或隔离收益足以抵偿契约、配置和定位成本时再拆 |
14
+ | 什么时候共享 | 先确认实际消费者与稳定契约;域内复用留本域,跨域共享分别界定展示、数据/契约与平台设施的所有权 |
15
+ | 如何控制依赖 | 应用入口负责装配,业务通过公开契约协作,基础设施不感知页面;消除目标环和反向UI依赖,接口方向按所选架构解释 |
16
+ | 如何打包 | HAP/HAR/HSP按实际部署、共享及运行需求选择;目录分层不等于独立库,不因采用规范目录就增加HAP或每层建HAR |
17
+
18
+ 新工程也评价原作者的维护便利性:哪些业务名和包族能沿用、变化后从哪里找页面/状态/数据、常见修改需跨多少模块、故障怎样回溯源码。可读性来自职责与一致命名,不来自把所有Java包原样堆进ArkTS,也不来自一律改成陌生名字。
19
+
20
+ ## 2. 三套方案如何区别
21
+
22
+ | 方案 | 目录/边界约束 | 必须解释 |
23
+ |---|---|---|
24
+ | android-mirror | 运行时源模块一对一对照,保留源模块名、相对布局、关键包族与内部模块声明依赖;构建辅助项目不变成目标业务库 | 外部SDK/API保留能力与消费者对照,不照搬AndroidX等包坐标;Ability/资源/语言及运行时必要适配;源技术债、环或不可行的边不能伪装成鸿蒙可实施结构 |
25
+ | harmony-practice | 依据目标职责重组,按消费者、数据所有权与平台边界选择目录和模块粒度;允许单HAP或适当模块化 | 具体收益、消费者和数据流;新增命名、拆合及平台适配成本,不以“最佳实践”替代论证 |
26
+ | hybrid | 基础依赖层优先保留原模块名、包族及合理依赖;业务层按页面或紧密页面组组织 feature,应用壳负责装配 | 基础保留与适配例外、页面边界及共享业务所有者;详细展开时按 [hybrid-design.md](hybrid-design.md) 设计,不能仅改变第二套顶层名字 |
27
+
28
+ 简单应用三套可以有相同模块数甚至相同树;说明相同决定与缺少额外拆分收益的原因。推荐可为任一套,依据实际收益和维护代价判断。
29
+
30
+ 融合方案的两个层次是迁移职责划分,不强制增加两个顶层目录或独立构建模块。下节是通用目录字典;融合方案的原包族保留、页面粒度与共享边界以 [hybrid-design.md](hybrid-design.md) 为准。
31
+
32
+ 对照方案也保留模块内部主要目录/包族的相对层级,参照各实际源码根(如Java/Kotlin根、Dart/JS业务根),包括原有扁平组织;不主动新增pages/state/data等分组来重构源包。鸿蒙必需的入口、资源格式或Native桥接新增目录逐项标明适配理由;模板目录命名惯例不等于平台强制结构。纯职责重分层留给另外两套。未知实现只在已有职责归属中标待证,不为未知类细分一串目录。
33
+
34
+ 源声明依赖与实际调用不一致时,两者分开记录。对照方案保留源模块及原有边,目标必须补边或替代的地方显式标为适配/修复,不能冒充源已声明的事实或偷偷拆合;证据不足时保留待决,不以“严格对照”宣布不可实施的图已就绪。
35
+
36
+ 多技术栈对照应同时保留Android宿主与实际业务源码的定位线索;不把Dart/JS业务当作新的Gradle模块,也不声称照搬源目录就保留了运行时。用户明确目标设备或范围优先;未知设备支持列为前提,不通过删功能制造可行性。
37
+
38
+ ## 3. 可选目录组织示例
39
+
40
+ 项目可按应用装配、业务职责与共享能力组织目录,也可保留已有且合理的结构。以下名称用于示意,按实际职责选取:
41
+
42
+ | 职责 | 名称示例 | 放什么 | 边界 |
43
+ |---|---|---|---|
44
+ | 应用装配 | products / app / entry | Ability、启动与依赖装配、应用级导航、当前目标的运行配置 | 业务状态和共享数据各有所有者 |
45
+ | 业务职责 | features / 业务域 | 该域实际存在的页面/状态、规则、数据访问及私有组件 | 保持域内所有权,独立模块按实际收益拆分 |
46
+ | 共享能力 | commons / core | 有真实跨域消费者的展示、数据/契约或平台设施 | 明确所有权和依赖方向,按职责决定合并或拆分 |
47
+
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
+ ```
68
+
69
+ 单模块同样可在 `entry/src/main/ets/<业务>/` 内组织页面、状态和数据;选择products目录也不强制独立features库。业务代码通过清晰接口与平台能力协作,不将Android Activity/Service逐个等同为UIAbility/ExtensionAbility。
70
+
71
+ 组件按职责与消费者确定归属:域内私有组件留本域,跨域通用展示可放共享UI或独立组件目录;顶层 `components` 与其他命名均按当前结构和维护收益选择。完整业务能力的复用仍需说明其状态、数据与协作边界。数据/协议模块不反向依赖共享UI,应用装配保持独立职责。
72
+
73
+ 对工程实际存在的资源、配置、持久化结构、本地能力或跨运行时依赖,分别说明目标归属;配置差异仅在影响本次迁移时展开。共享能力是否合库,按消费者、上游升级、构建和生命周期决定。
74
+
75
+ ## 4. 依据与适用边界
76
+
77
+ 以下一手资料提供原则,具体SDK/API/设备能力在当前决策需要时查最新文档;不将Android打包/API机制直接复制成鸿蒙方案。
78
+
79
+ - [Android架构指南](https://developer.android.com/topic/architecture):关注职责分离、数据驱动和状态来源,UI/数据及可选领域层是职责划分。
80
+ - [Android架构建议](https://developer.android.com/topic/architecture/recommendations):数据通过Repository访问,Domain/UseCase按复杂度及复用需求引入。
81
+ - [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约束。