@harmonyos-arkts/d2h 0.1.11 → 0.1.12

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 (126) hide show
  1. package/.claude-plugin/marketplace.json +15 -13
  2. package/.claude-plugin/plugin.json +13 -7
  3. package/AGENTS.md +5 -5
  4. package/README.md +212 -212
  5. package/agents/android-to-hmos-00-orchestrator.md +318 -318
  6. package/agents/d2h.md +157 -157
  7. package/dist/bin/install-opencode.js +30 -30
  8. package/docs/environment-check.md +86 -86
  9. package/install-opencode.sh +160 -160
  10. package/opencode/agents.json +16 -16
  11. package/opencode/vscode-d2h-package.json +7 -7
  12. package/opencode/vscode-d2h-plugin.mjs +29 -29
  13. package/package.json +54 -54
  14. package/schemas/android-source-manifest.schema.json +25 -25
  15. package/schemas/checkpoint-provenance.schema.json +67 -67
  16. package/schemas/final-acceptance.schema.json +29 -29
  17. package/schemas/managed-evidence-index.schema.json +15 -15
  18. package/schemas/managed-evidence.schema.json +114 -114
  19. package/schemas/migration-config.schema.json +51 -51
  20. package/schemas/migration-report-index.schema.json +39 -39
  21. package/schemas/migration-report-item.schema.json +51 -51
  22. package/schemas/migration-report-summary.schema.json +25 -25
  23. package/schemas/migration-status.schema.json +202 -202
  24. package/schemas/preflight.schema.json +60 -60
  25. package/schemas/source-order-audit.schema.json +16 -16
  26. package/schemas/source-provenance-event.schema.json +19 -19
  27. package/schemas/spec-app-shard.schema.json +45 -45
  28. package/schemas/spec-fact-corrections-shard.schema.json +13 -13
  29. package/schemas/spec-features-shard.schema.json +43 -43
  30. package/schemas/spec-index.schema.json +26 -26
  31. package/schemas/spec-interactions-shard.schema.json +40 -40
  32. package/schemas/spec-page.schema.json +98 -98
  33. package/schemas/spec-pages-index.schema.json +1 -1
  34. package/schemas/spec-unresolved-shard.schema.json +1 -1
  35. package/schemas/task-envelope.schema.json +109 -109
  36. package/schemas/task-plan.schema.json +341 -341
  37. package/scripts/check-environment.mjs +320 -320
  38. package/skills/android2hmos_resources_convert/SKILL.md +180 -180
  39. package/skills/android2hmos_resources_convert/references/image-conversion-rules.md +230 -230
  40. package/skills/android2hmos_resources_convert/references/svg-fix-patterns.md +175 -175
  41. package/skills/android2hmos_resources_convert/references/xml-drawable-to-svg-rules.md +513 -513
  42. package/skills/arkts-app-identity/SKILL.md +238 -238
  43. package/skills/arkts-i18n/SKILL.md +496 -496
  44. package/skills/arkts-i18n/evals/evals.json +84 -84
  45. package/skills/arkts-i18n/references/code-examples.md +302 -302
  46. package/skills/arkts-i18n/references/common-pitfalls.md +391 -391
  47. package/skills/arkts-i18n/references/dynamic-language-switch.md +604 -604
  48. package/skills/arkts-i18n/references/hardcoded-string-scanner.md +348 -348
  49. package/skills/arkts-i18n/references/language-codes.md +104 -104
  50. package/skills/arkts-i18n/references/resource-file-structure.md +775 -775
  51. package/skills/arkts-i18n/references/static-vs-dynamic.md +242 -242
  52. package/skills/arkts-i18n/references/v1-compat.md +244 -244
  53. package/skills/arkts-i18n/scripts/audit_i18n_completeness.sh +174 -174
  54. package/skills/arkts-icon-sizing/SKILL.md +211 -211
  55. package/skills/arkts-icon-sizing/scripts/icon_audit.py +131 -131
  56. package/skills/arkts-icon-sizing/scripts/icon_autofix.py +88 -88
  57. package/skills/arkts-icon-sizing/scripts/icon_dims.py +179 -179
  58. package/skills/arkts-icon-sizing/scripts/icon_fix.py +119 -119
  59. package/skills/arkts-mvvm-architecture/SKILL.md +613 -613
  60. package/skills/d2h-hypium-forge/SKILL.md +309 -309
  61. package/skills/d2h-hypium-forge/references/functional-spec-template.md +140 -140
  62. package/skills/d2h-hypium-forge/references/official-api.md +135 -135
  63. package/skills/d2h-hypium-forge/references/pitfalls.md +98 -98
  64. package/skills/d2h-hypium-forge/references/testability-template.md +71 -71
  65. package/skills/d2h-hypium-forge/scripts/_common_template.py +659 -659
  66. package/skills/d2h-hypium-forge/scripts/analyze_results.py +165 -165
  67. package/skills/d2h-hypium-forge/scripts/assert_audit.py +107 -107
  68. package/skills/d2h-hypium-forge/scripts/collect_runtime_pages.py +169 -169
  69. package/skills/d2h-hypium-forge/scripts/forge_report.py +325 -325
  70. package/skills/d2h-hypium-forge/scripts/inject_resource_ids.py +114 -114
  71. package/skills/d2h-hypium-forge/scripts/lint_asserts.py +80 -80
  72. package/skills/d2h-hypium-forge/scripts/lint_locate.py +60 -60
  73. package/skills/d2h-hypium-forge/scripts/merge_appgraph.py +249 -249
  74. package/skills/d2h-hypium-forge/scripts/precondition.py +140 -140
  75. package/skills/d2h-hypium-forge/scripts/preinject_ids.py +232 -232
  76. package/skills/d2h-hypium-forge/scripts/probe.py +197 -197
  77. package/skills/d2h-hypium-forge/scripts/run_direct.py +823 -823
  78. package/skills/d2h-hypium-forge/scripts/run_parallel.py +121 -121
  79. package/skills/d2h-hypium-forge/scripts/scan_features.py +255 -255
  80. package/skills/d2h-hypium-forge/scripts/upgrade_asserts.py +66 -66
  81. package/skills/d2h-hypium-forge/scripts/validate_scenarios.py +84 -84
  82. package/skills/d2h-page-develop/SKILL.md +112 -112
  83. package/skills/d2h-page-develop/agents/openai.yaml +7 -7
  84. package/skills/d2h-page-spec-generate/SKILL.md +206 -206
  85. package/skills/d2h-page-spec-generate/scripts/build_batch_report.py +227 -227
  86. package/skills/d2h-utg-ai-generate/SKILL.md +128 -128
  87. package/skills/d2h-utg-ai-generate/references/click-paths-schema.json +76 -76
  88. package/skills/d2h-utg-ai-generate/references/extraction-guide.md +34 -34
  89. package/skills/d2h-utg-ai-generate/references/facts-contract.md +96 -96
  90. package/skills/d2h-utg-ai-generate/references/page-schema.json +61 -61
  91. package/skills/d2h-utg-ai-generate/references/utg-schema.json +89 -89
  92. package/skills/d2h-utg-ai-generate/scripts/build_page_json.py +314 -314
  93. package/skills/d2h-utg-ai-generate/scripts/build_utg.py +123 -123
  94. package/skills/d2h-utg-ai-generate/scripts/capture_from_page_json.ts +280 -280
  95. package/skills/d2h-utg-ai-generate/scripts/utg_common.py +524 -524
  96. package/skills/d2h-utg-ai-generate/scripts/utg_html.py +489 -489
  97. package/skills/d2h-utg-ai-generate/scripts/validate_utg.py +69 -69
  98. package/skills/d2h-utg-ai-generate/scripts/vendor/D3-LICENSE +13 -13
  99. package/skills/d2h-utg-ai-generate/scripts/vendor/d3-7.9.0.min.js +2 -2
  100. package/skills/d2h-utg-ai-generate/tests/test_pipeline.py +369 -369
  101. package/skills/d2h-utg-static-generate/SKILL.md +73 -73
  102. package/skills/d2h-utg-static-generate/references/discoveries.md +38 -38
  103. package/skills/d2h-utg-static-generate/scripts/generate.py +499 -499
  104. package/skills/d2h-utg-static-generate/tests/test_static.py +335 -335
  105. package/skills/d2h-utg-static-generate/tests/verify-offline-html.py +60 -60
  106. package/skills/d2h-utg-static-generate/tests/verify-produced-fixture.py +131 -131
  107. package/skills/harmonyos-migration-playbook/SKILL.md +83 -83
  108. package/skills/harmonyos-migration-playbook/agents/openai.yaml +7 -7
  109. package/skills/harmonyos-migration-playbook/references/arkts-compile.md +24 -24
  110. package/skills/harmonyos-migration-playbook/references/harmony-runtime.md +53 -53
  111. package/skills/harmonyos-migration-playbook/references/lesson-lifecycle.md +45 -45
  112. package/skills/harmonyos-migration-playbook/references/protocol-e2e.md +23 -23
  113. package/skills/harmonyos-migration-playbook/references/ui-automation.md +53 -53
  114. package/skills/harmonyos-migration-playbook/references/windows-environment.md +38 -38
  115. package/skills/maintaining-migration-report/SKILL.md +144 -144
  116. package/skills/maintaining-migration-report/references/final-acceptance.md +48 -48
  117. package/skills/maintaining-migration-report/references/source-provenance.md +66 -66
  118. package/skills/managing-d2h-toolchain/SKILL.md +24 -24
  119. package/skills/managing-d2h-toolchain/environment-requirements.json +16 -16
  120. package/skills/managing-d2h-toolchain/scripts/manage-toolchain.mjs +98 -98
  121. package/skills/managing-d2h-toolchain/toolchain.json +16 -16
  122. package/skills/native-library-substitution/SKILL.md +384 -384
  123. package/skills/native-library-substitution/references/native-library-substitution.json +56905 -56905
  124. package/skills/native-library-substitution/references/native-library-substitution.md +163 -163
  125. package/skills/preparing-migration-workspace/SKILL.md +131 -131
  126. package/skills/reviewing-migration-process/SKILL.md +62 -62
@@ -1,227 +1,227 @@
1
- #!/usr/bin/env python3
2
- """Create the initial batch_report.md from a validated pages.json plan."""
3
-
4
- from __future__ import annotations
5
-
6
- import argparse
7
- import hashlib
8
- import json
9
- import os
10
- import sys
11
- import tempfile
12
- from datetime import datetime
13
- from pathlib import Path
14
-
15
-
16
- def sha256(path: Path) -> str:
17
- digest = hashlib.sha256()
18
- with path.open("rb") as stream:
19
- for chunk in iter(lambda: stream.read(1024 * 1024), b""):
20
- digest.update(chunk)
21
- return digest.hexdigest()
22
-
23
-
24
- def markdown_text(value: object) -> str:
25
- return str(value).replace("|", "\\|").replace("\n", " ")
26
-
27
-
28
- def require_int(value: object, where: str) -> int:
29
- if not isinstance(value, int) or isinstance(value, bool):
30
- raise ValueError(f"{where} must be an integer")
31
- return value
32
-
33
-
34
- def main() -> int:
35
- parser = argparse.ArgumentParser(description=__doc__)
36
- parser.add_argument("--pages-plan", required=True, type=Path)
37
- parser.add_argument("--output-root", required=True, type=Path)
38
- args = parser.parse_args()
39
-
40
- pages_plan = args.pages_plan.resolve()
41
- output_root = args.output_root.resolve()
42
- report_path = output_root / "batch_report.md"
43
- if not pages_plan.is_file():
44
- raise FileNotFoundError(f"missing page plan: {pages_plan}")
45
- if report_path.exists():
46
- raise FileExistsError(
47
- f"refusing to overwrite existing execution history: {report_path}; update it in place"
48
- )
49
-
50
- payload = json.loads(pages_plan.read_text(encoding="utf-8"))
51
- if not isinstance(payload, dict) or payload.get("schema_version") != "d2h-page-plan/v1":
52
- raise ValueError("pages plan must use schema d2h-page-plan/v1")
53
- pages = payload.get("pages")
54
- if not isinstance(pages, list) or not pages:
55
- raise ValueError("pages plan must contain a non-empty pages array")
56
-
57
- normalized: list[dict[str, object]] = []
58
- seen_packages: set[str] = set()
59
- seen_page_ids: set[str] = set()
60
- for index, page in enumerate(pages):
61
- if not isinstance(page, dict):
62
- raise ValueError(f"pages[{index}] must be an object")
63
- order = require_int(page.get("order"), f"pages[{index}].order")
64
- if order != index + 1:
65
- raise ValueError(f"pages[{index}].order must be {index + 1}")
66
-
67
- package = page.get("package")
68
- page_id = page.get("page_id")
69
- if not isinstance(package, str) or not package:
70
- raise ValueError(f"pages[{index}].package must be a non-empty string")
71
- if Path(package).name != package or not package.startswith(f"page_{order:04d}_"):
72
- raise ValueError(
73
- f"pages[{index}].package must be a single directory named page_{order:04d}_<name>-<id>"
74
- )
75
- if package in seen_packages:
76
- raise ValueError(f"duplicate package: {package}")
77
- seen_packages.add(package)
78
- if not isinstance(page_id, str) or not page_id:
79
- raise ValueError(f"pages[{index}].page_id must be a non-empty string")
80
- if page_id in seen_page_ids:
81
- raise ValueError(f"duplicate page_id: {page_id}")
82
- seen_page_ids.add(page_id)
83
-
84
- spec_path = output_root / package / "spec.md"
85
- if not spec_path.is_file():
86
- raise FileNotFoundError(
87
- f"all page specs must exist before batch report generation; missing {spec_path}"
88
- )
89
- package_files = sorted(path.name for path in spec_path.parent.iterdir() if path.is_file())
90
- if package_files != ["spec.md"]:
91
- raise ValueError(
92
- f"page package must contain only spec.md: {spec_path.parent} contains {package_files}"
93
- )
94
-
95
- ledger = page.get("decision_ledger")
96
- if not isinstance(ledger, dict):
97
- raise ValueError(f"pages[{index}].decision_ledger must be an object")
98
- dimensions: dict[str, int] = {}
99
- for dimension in ("enablement", "ordinary_reachability", "shallowness"):
100
- item = ledger.get(dimension)
101
- if not isinstance(item, dict):
102
- raise ValueError(f"pages[{index}].decision_ledger.{dimension} must be an object")
103
- dimensions[dimension] = require_int(
104
- item.get("rating"), f"pages[{index}].decision_ledger.{dimension}.rating"
105
- )
106
-
107
- normalized.append(
108
- {
109
- "order": order,
110
- "task_id": f"PAGE-{order:04d}",
111
- "page_id": page_id,
112
- "kind": page.get("kind", "page"),
113
- "page_score": require_int(page.get("page_score"), f"pages[{index}].page_score"),
114
- "package": package,
115
- "page_path": page.get("page_path", []),
116
- **dimensions,
117
- }
118
- )
119
-
120
- created_at = datetime.now().astimezone().isoformat(timespec="seconds")
121
- plan_link = Path(os.path.relpath(pages_plan, output_root)).as_posix()
122
- lines = [
123
- "# 页面开发批次执行记录",
124
- "",
125
- f"- 页面计划:[pages.json]({plan_link})",
126
- f"- 页面计划 SHA-256:`{sha256(pages_plan)}`",
127
- f"- 记录创建时间:`{created_at}`",
128
- "- 更新规则:后续开发直接更新本文件;不要重新生成或覆盖已有执行历史。",
129
- "",
130
- "## 状态约定",
131
- "",
132
- "- 开发状态:`pending | in_progress | implemented | blocked`",
133
- "- 构建状态:`not_run | passed | failed | blocked`",
134
- "- 验证状态:`not_run | passed | failed | blocked`",
135
- "- 完成条件:开发为 `implemented`,且构建、验证均为 `passed`。",
136
- "",
137
- "## 批次汇总",
138
- "",
139
- f"- 总任务:{len(normalized)}",
140
- f"- 待开始:{len(normalized)}",
141
- "- 进行中:0",
142
- "- 已完成:0",
143
- "- 阻塞:0",
144
- "",
145
- "## 执行队列",
146
- "",
147
- "| 任务 | 顺序 | 页面 | 类型 | 分数 | Spec | 开发 | 构建 | 验证 |",
148
- "|---|---:|---|---|---:|---|---|---|---|",
149
- ]
150
- for page in normalized:
151
- spec_link = f"./{page['package']}/spec.md"
152
- lines.append(
153
- "| {task} | {order} | {page_id} | {kind} | {score} | "
154
- "[spec.md]({spec}) | `pending` | `not_run` | `not_run` |".format(
155
- task=page["task_id"],
156
- order=page["order"],
157
- page_id=markdown_text(page["page_id"]),
158
- kind=markdown_text(page["kind"]),
159
- score=page["page_score"],
160
- spec=spec_link,
161
- )
162
- )
163
-
164
- lines.extend(["", "## 任务记录"])
165
- for page in normalized:
166
- page_path = page["page_path"]
167
- path_text = (
168
- " → ".join(markdown_text(value) for value in page_path)
169
- if isinstance(page_path, list)
170
- else "—"
171
- )
172
- spec_link = f"./{page['package']}/spec.md"
173
- lines.extend(
174
- [
175
- "",
176
- f"### {page['task_id']} · {markdown_text(page['page_id'])}",
177
- "",
178
- f"- 执行顺序:{page['order']}",
179
- f"- 页面路径:{path_text or '—'}",
180
- f"- 页面分数:{page['page_score']}(enablement={page['enablement']},ordinary_reachability={page['ordinary_reachability']},shallowness={page['shallowness']})",
181
- f"- 任务 Spec:[spec.md]({spec_link})",
182
- "- 开发状态:`pending`",
183
- "- 构建状态:`not_run`",
184
- "- 验证状态:`not_run`",
185
- "- 实现文件:—",
186
- "- 测试文件:—",
187
- "- 代码提交 / 源码快照:—",
188
- "- 构建证据:—",
189
- "- 测试证据:—",
190
- "- 最后更新:—",
191
- "- 阻塞原因:—",
192
- "",
193
- "#### 执行记录",
194
- "",
195
- "- 尚未开始。",
196
- ]
197
- )
198
-
199
- output_root.mkdir(parents=True, exist_ok=True)
200
- report_text = "\n".join(lines) + "\n"
201
- with tempfile.NamedTemporaryFile(
202
- mode="w", encoding="utf-8", dir=output_root, prefix=".batch-report-", delete=False
203
- ) as stream:
204
- stream.write(report_text)
205
- temporary_path = Path(stream.name)
206
- os.replace(temporary_path, report_path)
207
- print(
208
- json.dumps(
209
- {
210
- "ok": True,
211
- "task_count": len(normalized),
212
- "pages_plan": str(pages_plan),
213
- "batch_report": str(report_path),
214
- },
215
- ensure_ascii=False,
216
- indent=2,
217
- )
218
- )
219
- return 0
220
-
221
-
222
- if __name__ == "__main__":
223
- try:
224
- raise SystemExit(main())
225
- except (FileNotFoundError, FileExistsError, ValueError, KeyError, json.JSONDecodeError) as error:
226
- print(f"error: {error}", file=sys.stderr)
227
- raise SystemExit(2)
1
+ #!/usr/bin/env python3
2
+ """Create the initial batch_report.md from a validated pages.json plan."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import hashlib
8
+ import json
9
+ import os
10
+ import sys
11
+ import tempfile
12
+ from datetime import datetime
13
+ from pathlib import Path
14
+
15
+
16
+ def sha256(path: Path) -> str:
17
+ digest = hashlib.sha256()
18
+ with path.open("rb") as stream:
19
+ for chunk in iter(lambda: stream.read(1024 * 1024), b""):
20
+ digest.update(chunk)
21
+ return digest.hexdigest()
22
+
23
+
24
+ def markdown_text(value: object) -> str:
25
+ return str(value).replace("|", "\\|").replace("\n", " ")
26
+
27
+
28
+ def require_int(value: object, where: str) -> int:
29
+ if not isinstance(value, int) or isinstance(value, bool):
30
+ raise ValueError(f"{where} must be an integer")
31
+ return value
32
+
33
+
34
+ def main() -> int:
35
+ parser = argparse.ArgumentParser(description=__doc__)
36
+ parser.add_argument("--pages-plan", required=True, type=Path)
37
+ parser.add_argument("--output-root", required=True, type=Path)
38
+ args = parser.parse_args()
39
+
40
+ pages_plan = args.pages_plan.resolve()
41
+ output_root = args.output_root.resolve()
42
+ report_path = output_root / "batch_report.md"
43
+ if not pages_plan.is_file():
44
+ raise FileNotFoundError(f"missing page plan: {pages_plan}")
45
+ if report_path.exists():
46
+ raise FileExistsError(
47
+ f"refusing to overwrite existing execution history: {report_path}; update it in place"
48
+ )
49
+
50
+ payload = json.loads(pages_plan.read_text(encoding="utf-8"))
51
+ if not isinstance(payload, dict) or payload.get("schema_version") != "d2h-page-plan/v1":
52
+ raise ValueError("pages plan must use schema d2h-page-plan/v1")
53
+ pages = payload.get("pages")
54
+ if not isinstance(pages, list) or not pages:
55
+ raise ValueError("pages plan must contain a non-empty pages array")
56
+
57
+ normalized: list[dict[str, object]] = []
58
+ seen_packages: set[str] = set()
59
+ seen_page_ids: set[str] = set()
60
+ for index, page in enumerate(pages):
61
+ if not isinstance(page, dict):
62
+ raise ValueError(f"pages[{index}] must be an object")
63
+ order = require_int(page.get("order"), f"pages[{index}].order")
64
+ if order != index + 1:
65
+ raise ValueError(f"pages[{index}].order must be {index + 1}")
66
+
67
+ package = page.get("package")
68
+ page_id = page.get("page_id")
69
+ if not isinstance(package, str) or not package:
70
+ raise ValueError(f"pages[{index}].package must be a non-empty string")
71
+ if Path(package).name != package or not package.startswith(f"page_{order:04d}_"):
72
+ raise ValueError(
73
+ f"pages[{index}].package must be a single directory named page_{order:04d}_<name>-<id>"
74
+ )
75
+ if package in seen_packages:
76
+ raise ValueError(f"duplicate package: {package}")
77
+ seen_packages.add(package)
78
+ if not isinstance(page_id, str) or not page_id:
79
+ raise ValueError(f"pages[{index}].page_id must be a non-empty string")
80
+ if page_id in seen_page_ids:
81
+ raise ValueError(f"duplicate page_id: {page_id}")
82
+ seen_page_ids.add(page_id)
83
+
84
+ spec_path = output_root / package / "spec.md"
85
+ if not spec_path.is_file():
86
+ raise FileNotFoundError(
87
+ f"all page specs must exist before batch report generation; missing {spec_path}"
88
+ )
89
+ package_files = sorted(path.name for path in spec_path.parent.iterdir() if path.is_file())
90
+ if package_files != ["spec.md"]:
91
+ raise ValueError(
92
+ f"page package must contain only spec.md: {spec_path.parent} contains {package_files}"
93
+ )
94
+
95
+ ledger = page.get("decision_ledger")
96
+ if not isinstance(ledger, dict):
97
+ raise ValueError(f"pages[{index}].decision_ledger must be an object")
98
+ dimensions: dict[str, int] = {}
99
+ for dimension in ("enablement", "ordinary_reachability", "shallowness"):
100
+ item = ledger.get(dimension)
101
+ if not isinstance(item, dict):
102
+ raise ValueError(f"pages[{index}].decision_ledger.{dimension} must be an object")
103
+ dimensions[dimension] = require_int(
104
+ item.get("rating"), f"pages[{index}].decision_ledger.{dimension}.rating"
105
+ )
106
+
107
+ normalized.append(
108
+ {
109
+ "order": order,
110
+ "task_id": f"PAGE-{order:04d}",
111
+ "page_id": page_id,
112
+ "kind": page.get("kind", "page"),
113
+ "page_score": require_int(page.get("page_score"), f"pages[{index}].page_score"),
114
+ "package": package,
115
+ "page_path": page.get("page_path", []),
116
+ **dimensions,
117
+ }
118
+ )
119
+
120
+ created_at = datetime.now().astimezone().isoformat(timespec="seconds")
121
+ plan_link = Path(os.path.relpath(pages_plan, output_root)).as_posix()
122
+ lines = [
123
+ "# 页面开发批次执行记录",
124
+ "",
125
+ f"- 页面计划:[pages.json]({plan_link})",
126
+ f"- 页面计划 SHA-256:`{sha256(pages_plan)}`",
127
+ f"- 记录创建时间:`{created_at}`",
128
+ "- 更新规则:后续开发直接更新本文件;不要重新生成或覆盖已有执行历史。",
129
+ "",
130
+ "## 状态约定",
131
+ "",
132
+ "- 开发状态:`pending | in_progress | implemented | blocked`",
133
+ "- 构建状态:`not_run | passed | failed | blocked`",
134
+ "- 验证状态:`not_run | passed | failed | blocked`",
135
+ "- 完成条件:开发为 `implemented`,且构建、验证均为 `passed`。",
136
+ "",
137
+ "## 批次汇总",
138
+ "",
139
+ f"- 总任务:{len(normalized)}",
140
+ f"- 待开始:{len(normalized)}",
141
+ "- 进行中:0",
142
+ "- 已完成:0",
143
+ "- 阻塞:0",
144
+ "",
145
+ "## 执行队列",
146
+ "",
147
+ "| 任务 | 顺序 | 页面 | 类型 | 分数 | Spec | 开发 | 构建 | 验证 |",
148
+ "|---|---:|---|---|---:|---|---|---|---|",
149
+ ]
150
+ for page in normalized:
151
+ spec_link = f"./{page['package']}/spec.md"
152
+ lines.append(
153
+ "| {task} | {order} | {page_id} | {kind} | {score} | "
154
+ "[spec.md]({spec}) | `pending` | `not_run` | `not_run` |".format(
155
+ task=page["task_id"],
156
+ order=page["order"],
157
+ page_id=markdown_text(page["page_id"]),
158
+ kind=markdown_text(page["kind"]),
159
+ score=page["page_score"],
160
+ spec=spec_link,
161
+ )
162
+ )
163
+
164
+ lines.extend(["", "## 任务记录"])
165
+ for page in normalized:
166
+ page_path = page["page_path"]
167
+ path_text = (
168
+ " → ".join(markdown_text(value) for value in page_path)
169
+ if isinstance(page_path, list)
170
+ else "—"
171
+ )
172
+ spec_link = f"./{page['package']}/spec.md"
173
+ lines.extend(
174
+ [
175
+ "",
176
+ f"### {page['task_id']} · {markdown_text(page['page_id'])}",
177
+ "",
178
+ f"- 执行顺序:{page['order']}",
179
+ f"- 页面路径:{path_text or '—'}",
180
+ f"- 页面分数:{page['page_score']}(enablement={page['enablement']},ordinary_reachability={page['ordinary_reachability']},shallowness={page['shallowness']})",
181
+ f"- 任务 Spec:[spec.md]({spec_link})",
182
+ "- 开发状态:`pending`",
183
+ "- 构建状态:`not_run`",
184
+ "- 验证状态:`not_run`",
185
+ "- 实现文件:—",
186
+ "- 测试文件:—",
187
+ "- 代码提交 / 源码快照:—",
188
+ "- 构建证据:—",
189
+ "- 测试证据:—",
190
+ "- 最后更新:—",
191
+ "- 阻塞原因:—",
192
+ "",
193
+ "#### 执行记录",
194
+ "",
195
+ "- 尚未开始。",
196
+ ]
197
+ )
198
+
199
+ output_root.mkdir(parents=True, exist_ok=True)
200
+ report_text = "\n".join(lines) + "\n"
201
+ with tempfile.NamedTemporaryFile(
202
+ mode="w", encoding="utf-8", dir=output_root, prefix=".batch-report-", delete=False
203
+ ) as stream:
204
+ stream.write(report_text)
205
+ temporary_path = Path(stream.name)
206
+ os.replace(temporary_path, report_path)
207
+ print(
208
+ json.dumps(
209
+ {
210
+ "ok": True,
211
+ "task_count": len(normalized),
212
+ "pages_plan": str(pages_plan),
213
+ "batch_report": str(report_path),
214
+ },
215
+ ensure_ascii=False,
216
+ indent=2,
217
+ )
218
+ )
219
+ return 0
220
+
221
+
222
+ if __name__ == "__main__":
223
+ try:
224
+ raise SystemExit(main())
225
+ except (FileNotFoundError, FileExistsError, ValueError, KeyError, json.JSONDecodeError) as error:
226
+ print(f"error: {error}", file=sys.stderr)
227
+ raise SystemExit(2)