@hunter-harness/workflow-harness 0.2.33 → 0.2.35

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 (243) hide show
  1. package/README.md +25 -25
  2. package/harness/bundles/general/claude-code/.harness-build.json +1 -1
  3. package/harness/bundles/general/claude-code/contracts/workflow-policy.json +1 -0
  4. package/harness/bundles/general/claude-code/harness-archive/SKILL.md +1 -1
  5. package/harness/bundles/general/claude-code/harness-archive/templates/render-summary.mjs +215 -106
  6. package/harness/bundles/general/claude-code/harness-archive/templates/summary-data-template.json +8 -0
  7. package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +1 -1
  8. package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  9. package/harness/bundles/general/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  10. package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
  11. package/harness/bundles/general/claude-code/harness-plan/SKILL.md +3 -3
  12. package/harness/bundles/general/claude-code/harness-review/SKILL.md +2 -2
  13. package/harness/bundles/general/claude-code/harness-run/SKILL.md +4 -4
  14. package/harness/bundles/general/claude-code/harness-submit/SKILL.md +2 -2
  15. package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
  16. package/harness/bundles/general/claude-code/harness-test/SKILL.md +4 -4
  17. package/harness/bundles/general/claude-code/harness-test/checklist.md +1 -0
  18. package/harness/bundles/general/claude-code/harness-test/reference.md +2 -1
  19. package/harness/bundles/general/claude-code/protocols/ledger-protocol.md +2 -2
  20. package/harness/bundles/general/claude-code/scripts/harness_archive.py +415 -15
  21. package/harness/bundles/general/claude-code/scripts/harness_context.py +717 -0
  22. package/harness/bundles/general/claude-code/scripts/harness_gate.py +1 -0
  23. package/harness/bundles/general/claude-code/scripts/harness_ledger.py +77 -9
  24. package/harness/bundles/general/claude-code/scripts/harness_phase.py +1 -0
  25. package/harness/bundles/general/claude-code/scripts/harness_plan_aggregate.py +249 -0
  26. package/harness/bundles/general/claude-code/scripts/harness_profile.py +272 -15
  27. package/harness/bundles/general/claude-code/scripts/harness_report_model.py +420 -0
  28. package/harness/bundles/general/claude-code/scripts/harness_review.py +16 -1
  29. package/harness/bundles/general/claude-code/scripts/harness_verification.py +114 -0
  30. package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
  31. package/harness/bundles/general/codebuddy/contracts/workflow-policy.json +1 -0
  32. package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +1 -1
  33. package/harness/bundles/general/codebuddy/harness-archive/templates/render-summary.mjs +215 -106
  34. package/harness/bundles/general/codebuddy/harness-archive/templates/summary-data-template.json +8 -0
  35. package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
  36. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  37. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  38. package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  39. package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +3 -3
  40. package/harness/bundles/general/codebuddy/harness-review/SKILL.md +2 -2
  41. package/harness/bundles/general/codebuddy/harness-run/SKILL.md +4 -4
  42. package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +2 -2
  43. package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
  44. package/harness/bundles/general/codebuddy/harness-test/SKILL.md +4 -4
  45. package/harness/bundles/general/codebuddy/harness-test/checklist.md +1 -0
  46. package/harness/bundles/general/codebuddy/harness-test/reference.md +2 -1
  47. package/harness/bundles/general/codebuddy/protocols/ledger-protocol.md +2 -2
  48. package/harness/bundles/general/codebuddy/scripts/harness_archive.py +415 -15
  49. package/harness/bundles/general/codebuddy/scripts/harness_context.py +717 -0
  50. package/harness/bundles/general/codebuddy/scripts/harness_gate.py +1 -0
  51. package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +77 -9
  52. package/harness/bundles/general/codebuddy/scripts/harness_phase.py +1 -0
  53. package/harness/bundles/general/codebuddy/scripts/harness_plan_aggregate.py +249 -0
  54. package/harness/bundles/general/codebuddy/scripts/harness_profile.py +272 -15
  55. package/harness/bundles/general/codebuddy/scripts/harness_report_model.py +420 -0
  56. package/harness/bundles/general/codebuddy/scripts/harness_review.py +16 -1
  57. package/harness/bundles/general/codebuddy/scripts/harness_verification.py +114 -0
  58. package/harness/bundles/general/codex/.harness-build.json +1 -1
  59. package/harness/bundles/general/codex/contracts/workflow-policy.json +1 -0
  60. package/harness/bundles/general/codex/harness-archive/SKILL.md +1 -1
  61. package/harness/bundles/general/codex/harness-archive/templates/render-summary.mjs +215 -106
  62. package/harness/bundles/general/codex/harness-archive/templates/summary-data-template.json +8 -0
  63. package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
  64. package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
  65. package/harness/bundles/general/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  66. package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
  67. package/harness/bundles/general/codex/harness-plan/SKILL.md +3 -3
  68. package/harness/bundles/general/codex/harness-review/SKILL.md +2 -2
  69. package/harness/bundles/general/codex/harness-run/SKILL.md +4 -4
  70. package/harness/bundles/general/codex/harness-submit/SKILL.md +2 -2
  71. package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
  72. package/harness/bundles/general/codex/harness-test/SKILL.md +4 -4
  73. package/harness/bundles/general/codex/harness-test/checklist.md +1 -0
  74. package/harness/bundles/general/codex/harness-test/reference.md +2 -1
  75. package/harness/bundles/general/codex/protocols/ledger-protocol.md +2 -2
  76. package/harness/bundles/general/codex/scripts/harness_archive.py +415 -15
  77. package/harness/bundles/general/codex/scripts/harness_context.py +717 -0
  78. package/harness/bundles/general/codex/scripts/harness_gate.py +1 -0
  79. package/harness/bundles/general/codex/scripts/harness_ledger.py +77 -9
  80. package/harness/bundles/general/codex/scripts/harness_phase.py +1 -0
  81. package/harness/bundles/general/codex/scripts/harness_plan_aggregate.py +249 -0
  82. package/harness/bundles/general/codex/scripts/harness_profile.py +272 -15
  83. package/harness/bundles/general/codex/scripts/harness_report_model.py +420 -0
  84. package/harness/bundles/general/codex/scripts/harness_review.py +16 -1
  85. package/harness/bundles/general/codex/scripts/harness_verification.py +114 -0
  86. package/harness/bundles/general/cursor/.harness-build.json +1 -1
  87. package/harness/bundles/general/cursor/contracts/workflow-policy.json +1 -0
  88. package/harness/bundles/general/cursor/harness-archive/SKILL.md +1 -1
  89. package/harness/bundles/general/cursor/harness-archive/templates/render-summary.mjs +215 -106
  90. package/harness/bundles/general/cursor/harness-archive/templates/summary-data-template.json +8 -0
  91. package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
  92. package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  93. package/harness/bundles/general/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  94. package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
  95. package/harness/bundles/general/cursor/harness-plan/SKILL.md +3 -3
  96. package/harness/bundles/general/cursor/harness-review/SKILL.md +2 -2
  97. package/harness/bundles/general/cursor/harness-run/SKILL.md +4 -4
  98. package/harness/bundles/general/cursor/harness-submit/SKILL.md +2 -2
  99. package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
  100. package/harness/bundles/general/cursor/harness-test/SKILL.md +4 -4
  101. package/harness/bundles/general/cursor/harness-test/checklist.md +1 -0
  102. package/harness/bundles/general/cursor/harness-test/reference.md +2 -1
  103. package/harness/bundles/general/cursor/protocols/ledger-protocol.md +2 -2
  104. package/harness/bundles/general/cursor/scripts/harness_archive.py +415 -15
  105. package/harness/bundles/general/cursor/scripts/harness_context.py +717 -0
  106. package/harness/bundles/general/cursor/scripts/harness_gate.py +1 -0
  107. package/harness/bundles/general/cursor/scripts/harness_ledger.py +77 -9
  108. package/harness/bundles/general/cursor/scripts/harness_phase.py +1 -0
  109. package/harness/bundles/general/cursor/scripts/harness_plan_aggregate.py +249 -0
  110. package/harness/bundles/general/cursor/scripts/harness_profile.py +272 -15
  111. package/harness/bundles/general/cursor/scripts/harness_report_model.py +420 -0
  112. package/harness/bundles/general/cursor/scripts/harness_review.py +16 -1
  113. package/harness/bundles/general/cursor/scripts/harness_verification.py +114 -0
  114. package/harness/bundles/java/claude-code/.harness-build.json +1 -1
  115. package/harness/bundles/java/claude-code/contracts/workflow-policy.json +1 -0
  116. package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
  117. package/harness/bundles/java/claude-code/harness-archive/SKILL.md +1 -1
  118. package/harness/bundles/java/claude-code/harness-archive/templates/render-summary.mjs +215 -106
  119. package/harness/bundles/java/claude-code/harness-archive/templates/summary-data-template.json +8 -0
  120. package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
  121. package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  122. package/harness/bundles/java/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  123. package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
  124. package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
  125. package/harness/bundles/java/claude-code/harness-plan/SKILL.md +3 -3
  126. package/harness/bundles/java/claude-code/harness-review/SKILL.md +2 -2
  127. package/harness/bundles/java/claude-code/harness-run/SKILL.md +4 -4
  128. package/harness/bundles/java/claude-code/harness-submit/SKILL.md +2 -2
  129. package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
  130. package/harness/bundles/java/claude-code/harness-test/SKILL.md +4 -4
  131. package/harness/bundles/java/claude-code/harness-test/checklist.md +1 -0
  132. package/harness/bundles/java/claude-code/harness-test/reference.md +2 -0
  133. package/harness/bundles/java/claude-code/protocols/ledger-protocol.md +2 -2
  134. package/harness/bundles/java/claude-code/scripts/harness_archive.py +415 -15
  135. package/harness/bundles/java/claude-code/scripts/harness_context.py +717 -0
  136. package/harness/bundles/java/claude-code/scripts/harness_gate.py +1 -0
  137. package/harness/bundles/java/claude-code/scripts/harness_ledger.py +77 -9
  138. package/harness/bundles/java/claude-code/scripts/harness_phase.py +1 -0
  139. package/harness/bundles/java/claude-code/scripts/harness_plan_aggregate.py +249 -0
  140. package/harness/bundles/java/claude-code/scripts/harness_profile.py +272 -15
  141. package/harness/bundles/java/claude-code/scripts/harness_report_model.py +420 -0
  142. package/harness/bundles/java/claude-code/scripts/harness_review.py +16 -1
  143. package/harness/bundles/java/claude-code/scripts/harness_verification.py +114 -0
  144. package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
  145. package/harness/bundles/java/codebuddy/contracts/workflow-policy.json +1 -0
  146. package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
  147. package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +1 -1
  148. package/harness/bundles/java/codebuddy/harness-archive/templates/render-summary.mjs +215 -106
  149. package/harness/bundles/java/codebuddy/harness-archive/templates/summary-data-template.json +8 -0
  150. package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
  151. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  152. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  153. package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  154. package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
  155. package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +3 -3
  156. package/harness/bundles/java/codebuddy/harness-review/SKILL.md +2 -2
  157. package/harness/bundles/java/codebuddy/harness-run/SKILL.md +4 -4
  158. package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +2 -2
  159. package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
  160. package/harness/bundles/java/codebuddy/harness-test/SKILL.md +4 -4
  161. package/harness/bundles/java/codebuddy/harness-test/checklist.md +1 -0
  162. package/harness/bundles/java/codebuddy/harness-test/reference.md +2 -0
  163. package/harness/bundles/java/codebuddy/protocols/ledger-protocol.md +2 -2
  164. package/harness/bundles/java/codebuddy/scripts/harness_archive.py +415 -15
  165. package/harness/bundles/java/codebuddy/scripts/harness_context.py +717 -0
  166. package/harness/bundles/java/codebuddy/scripts/harness_gate.py +1 -0
  167. package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +77 -9
  168. package/harness/bundles/java/codebuddy/scripts/harness_phase.py +1 -0
  169. package/harness/bundles/java/codebuddy/scripts/harness_plan_aggregate.py +249 -0
  170. package/harness/bundles/java/codebuddy/scripts/harness_profile.py +272 -15
  171. package/harness/bundles/java/codebuddy/scripts/harness_report_model.py +420 -0
  172. package/harness/bundles/java/codebuddy/scripts/harness_review.py +16 -1
  173. package/harness/bundles/java/codebuddy/scripts/harness_verification.py +114 -0
  174. package/harness/bundles/java/codex/.harness-build.json +1 -1
  175. package/harness/bundles/java/codex/contracts/workflow-policy.json +1 -0
  176. package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
  177. package/harness/bundles/java/codex/harness-archive/SKILL.md +1 -1
  178. package/harness/bundles/java/codex/harness-archive/templates/render-summary.mjs +215 -106
  179. package/harness/bundles/java/codex/harness-archive/templates/summary-data-template.json +8 -0
  180. package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
  181. package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
  182. package/harness/bundles/java/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  183. package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
  184. package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
  185. package/harness/bundles/java/codex/harness-plan/SKILL.md +3 -3
  186. package/harness/bundles/java/codex/harness-review/SKILL.md +2 -2
  187. package/harness/bundles/java/codex/harness-run/SKILL.md +4 -4
  188. package/harness/bundles/java/codex/harness-submit/SKILL.md +2 -2
  189. package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
  190. package/harness/bundles/java/codex/harness-test/SKILL.md +4 -4
  191. package/harness/bundles/java/codex/harness-test/checklist.md +1 -0
  192. package/harness/bundles/java/codex/harness-test/reference.md +2 -0
  193. package/harness/bundles/java/codex/protocols/ledger-protocol.md +2 -2
  194. package/harness/bundles/java/codex/scripts/harness_archive.py +415 -15
  195. package/harness/bundles/java/codex/scripts/harness_context.py +717 -0
  196. package/harness/bundles/java/codex/scripts/harness_gate.py +1 -0
  197. package/harness/bundles/java/codex/scripts/harness_ledger.py +77 -9
  198. package/harness/bundles/java/codex/scripts/harness_phase.py +1 -0
  199. package/harness/bundles/java/codex/scripts/harness_plan_aggregate.py +249 -0
  200. package/harness/bundles/java/codex/scripts/harness_profile.py +272 -15
  201. package/harness/bundles/java/codex/scripts/harness_report_model.py +420 -0
  202. package/harness/bundles/java/codex/scripts/harness_review.py +16 -1
  203. package/harness/bundles/java/codex/scripts/harness_verification.py +114 -0
  204. package/harness/bundles/java/cursor/.harness-build.json +1 -1
  205. package/harness/bundles/java/cursor/contracts/workflow-policy.json +1 -0
  206. package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
  207. package/harness/bundles/java/cursor/harness-archive/SKILL.md +1 -1
  208. package/harness/bundles/java/cursor/harness-archive/templates/render-summary.mjs +215 -106
  209. package/harness/bundles/java/cursor/harness-archive/templates/summary-data-template.json +8 -0
  210. package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
  211. package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  212. package/harness/bundles/java/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  213. package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
  214. package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
  215. package/harness/bundles/java/cursor/harness-plan/SKILL.md +3 -3
  216. package/harness/bundles/java/cursor/harness-review/SKILL.md +2 -2
  217. package/harness/bundles/java/cursor/harness-run/SKILL.md +4 -4
  218. package/harness/bundles/java/cursor/harness-submit/SKILL.md +2 -2
  219. package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
  220. package/harness/bundles/java/cursor/harness-test/SKILL.md +4 -4
  221. package/harness/bundles/java/cursor/harness-test/checklist.md +1 -0
  222. package/harness/bundles/java/cursor/harness-test/reference.md +2 -0
  223. package/harness/bundles/java/cursor/protocols/ledger-protocol.md +2 -2
  224. package/harness/bundles/java/cursor/scripts/harness_archive.py +415 -15
  225. package/harness/bundles/java/cursor/scripts/harness_context.py +717 -0
  226. package/harness/bundles/java/cursor/scripts/harness_gate.py +1 -0
  227. package/harness/bundles/java/cursor/scripts/harness_ledger.py +77 -9
  228. package/harness/bundles/java/cursor/scripts/harness_phase.py +1 -0
  229. package/harness/bundles/java/cursor/scripts/harness_plan_aggregate.py +249 -0
  230. package/harness/bundles/java/cursor/scripts/harness_profile.py +272 -15
  231. package/harness/bundles/java/cursor/scripts/harness_report_model.py +420 -0
  232. package/harness/bundles/java/cursor/scripts/harness_review.py +16 -1
  233. package/harness/bundles/java/cursor/scripts/harness_verification.py +114 -0
  234. package/harness/manifests/general/claude-code.json +42 -26
  235. package/harness/manifests/general/codebuddy.json +42 -26
  236. package/harness/manifests/general/codex.json +42 -26
  237. package/harness/manifests/general/cursor.json +42 -26
  238. package/harness/manifests/java/claude-code.json +44 -28
  239. package/harness/manifests/java/codebuddy.json +44 -28
  240. package/harness/manifests/java/codex.json +44 -28
  241. package/harness/manifests/java/cursor.json +44 -28
  242. package/hunter-workflow-family.json +4 -4
  243. package/package.json +1 -1
package/README.md CHANGED
@@ -1,25 +1,25 @@
1
- # @hunter-harness/workflow-harness
2
-
3
- Harness 工作流族的**纯数据 npm 包**(bundles + manifests + migrations),供瘦身后 `hunter-harness` CLI 在运行时解析安装。
4
-
5
- ## 版本约定
6
-
7
- - npm 包 `version` 与 Registry 中 **Workflow Family 发布版本** 1:1 对齐(例如 Family `1.0.0` → 本包 `1.0.0`)。
8
- - `hunter-workflow-family.json` 记录族 slug、所需 profile 与 bundle 元数据,以及 `content_sha256`
9
- ——对 `harness/` 下所有文件按路径排序后 `{path, content}` 规范化 JSON(`canonicalJson`)取 SHA-256,
10
- 与 `apps/server/src/npm/publisher.ts` 的 `buildWorkflowFamilyManifest` 使用同一算法,
11
- 供离线/本地校验本包内容完整性。
12
-
13
- ## 布局
14
-
15
- ```
16
- harness/
17
- bundles/{general|java}/{claude-code|codex|cursor|codebuddy}/...
18
- manifests/{general|java}/{agent}.json
19
- migrations/0.1.1/{general|java}.json
20
- hunter-workflow-family.json
21
- ```
22
-
23
- ## 生成
24
-
25
- 仓库根目录执行 `npm run sync:harness`,产物会同步写入本包的 `harness/` 目录。
1
+ # @hunter-harness/workflow-harness
2
+
3
+ Harness 工作流族的**纯数据 npm 包**(bundles + manifests + migrations),供瘦身后 `hunter-harness` CLI 在运行时解析安装。
4
+
5
+ ## 版本约定
6
+
7
+ - npm 包 `version` 与 Registry 中 **Workflow Family 发布版本** 1:1 对齐(例如 Family `1.0.0` → 本包 `1.0.0`)。
8
+ - `hunter-workflow-family.json` 记录族 slug、所需 profile 与 bundle 元数据,以及 `content_sha256`
9
+ ——对 `harness/` 下所有文件按路径排序后 `{path, content}` 规范化 JSON(`canonicalJson`)取 SHA-256,
10
+ 与 `apps/server/src/npm/publisher.ts` 的 `buildWorkflowFamilyManifest` 使用同一算法,
11
+ 供离线/本地校验本包内容完整性。
12
+
13
+ ## 布局
14
+
15
+ ```
16
+ harness/
17
+ bundles/{general|java}/{claude-code|codex|cursor|codebuddy}/...
18
+ manifests/{general|java}/{agent}.json
19
+ migrations/0.1.1/{general|java}.json
20
+ hunter-workflow-family.json
21
+ ```
22
+
23
+ ## 生成
24
+
25
+ 仓库根目录执行 `npm run sync:harness`,产物会同步写入本包的 `harness/` 目录。
@@ -2,5 +2,5 @@
2
2
  "schemaVersion": 1,
3
3
  "overlay": "none",
4
4
  "agent": "claude-code",
5
- "coreHash": "33c3c012031041b2"
5
+ "coreHash": "5830124f26774287"
6
6
  }
@@ -174,6 +174,7 @@
174
174
  "unitTest": "run",
175
175
  "unitTestFull": "test",
176
176
  "apiTest": "test",
177
+ "browserTest": "test",
177
178
  "dbCompatibility": "test",
178
179
  "package": "package"
179
180
  },
@@ -20,7 +20,7 @@ disallowed-tools:
20
20
  - Bash(awk *)
21
21
  - Bash(curl *)
22
22
  ---
23
- <!-- generated by harness_deploy.py; core=33c3c012031041b2; overlay=none; agent=claude-code; do not edit -->
23
+ <!-- generated by harness_deploy.py; core=5830124f26774287; overlay=none; agent=claude-code; do not edit -->
24
24
  # harness-archive — 变更归档
25
25
 
26
26
  ## Purpose
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // Compact deterministic renderer for Harness archive summary-data.json.
2
+ // Deterministic executive renderer for normalized Harness archive reports.
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
 
@@ -16,124 +16,233 @@ if (!summaryPath) {
16
16
  }
17
17
 
18
18
  const data = JSON.parse(fs.readFileSync(summaryPath, "utf8"));
19
- const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => ({
19
+ const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (character) => ({
20
20
  "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;"
21
- })[char]);
21
+ })[character]);
22
22
  const list = (value) => Array.isArray(value) ? value : [];
23
23
  const record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
24
- const number = (value) => Number(value) || 0;
25
- const shortHash = (value) => String(value || "N/A").slice(0, 10);
26
- const statusClass = (value) => {
27
- const status = String(value || "UNKNOWN").toUpperCase();
28
- if (/FAIL|ERROR/.test(status)) return "danger";
29
- if (/WARN|BLOCK|SKIP|CONDITIONAL|PARTIAL|NOT_RUN|UNKNOWN/.test(status)) return "warning";
30
- if (/ADVISORY|REUSED/.test(status)) return "neutral";
24
+ const numeric = (value) => Number.isFinite(Number(value)) ? Number(value) : null;
25
+ const normalized = record(data.normalizedReport);
26
+ const outcomes = record(normalized.outcomes);
27
+ const current = Object.keys(record(outcomes.current)).length
28
+ ? record(outcomes.current)
29
+ : {
30
+ status: data.finalStatus || "UNKNOWN",
31
+ reasons: list(data.finalStatusReasons),
32
+ stages: record(data.stageStatus),
33
+ knownRisks: list(data.knownRisks),
34
+ findings: list(data.reviewFindings)
35
+ };
36
+ const history = record(outcomes.history);
37
+ const release = Object.keys(record(outcomes.release)).length
38
+ ? record(outcomes.release)
39
+ : {
40
+ decision: data.archiveIntent === "record-only" ? "NOT_REQUESTED" : record(data.releaseDecision).code,
41
+ eligible: Boolean(record(data.releaseDecision).releaseEligible),
42
+ candidate: record(data.candidateVerification),
43
+ intent: data.archiveIntent || ""
44
+ };
45
+ const identity = Object.keys(record(normalized.identity)).length
46
+ ? record(normalized.identity)
47
+ : record(data.changeIdentity);
48
+ const verification = Object.keys(record(normalized.verification)).length
49
+ ? record(normalized.verification)
50
+ : record(data.verification);
51
+ const timing = Object.keys(record(normalized.timing)).length
52
+ ? record(normalized.timing)
53
+ : record(data.timing);
54
+ const measurements = record(normalized.measurements);
55
+ const recordOnly = release.intent === "record-only" || data.archiveIntent === "record-only";
56
+ const files = list(data.changedFiles);
57
+ const commands = list(record(data.reportPipeline).commands);
58
+ const timeline = list(history.timeline).length ? list(history.timeline) : list(data.timeline);
59
+ const attempts = list(history.attempts).length
60
+ ? list(history.attempts)
61
+ : list(timing.attempts);
62
+ const actions = list(data.manualActions);
63
+ const risks = list(current.findings).length
64
+ ? list(current.findings)
65
+ : list(current.knownRisks).length
66
+ ? list(current.knownRisks)
67
+ : list(data.knownRisks);
68
+
69
+ const statusTone = (raw) => {
70
+ const status = String(raw || "UNKNOWN").toUpperCase();
71
+ if (/FAIL|ERROR|BLOCKED/.test(status)) return "danger";
72
+ if (/WARN|CONDITIONAL|PARTIAL|NOT_RUN|UNKNOWN|SKIP/.test(status)) return "warning";
73
+ if (/ADVISORY|REUSED|NOT_APPLICABLE/.test(status)) return "neutral";
31
74
  return "success";
32
75
  };
33
- const pill = (value) => `<span class="pill ${statusClass(value)}">${esc(value || "UNKNOWN")}</span>`;
76
+ const STATUS_LABELS = {
77
+ OK: "通过", PASS: "通过", PASSED: "通过", CONDITIONAL_OK: "有条件通过",
78
+ WARN: "警告", ADVISORY: "建议", FAIL: "失败", FAILED: "失败", ERROR: "错误",
79
+ BLOCKED: "阻塞", NOT_RUN: "未运行", SKIPPED: "已跳过",
80
+ NOT_APPLICABLE: "不适用", UNKNOWN: "未知"
81
+ };
82
+ const pill = (raw) => {
83
+ const status = String(raw || "UNKNOWN").toUpperCase();
84
+ return `<span class="pill ${statusTone(status)}" title="${esc(status)}">${esc(STATUS_LABELS[status] || status)}</span>`;
85
+ };
34
86
  const describe = (value) => {
35
87
  if (typeof value === "string") return value;
36
- if (!value || typeof value !== "object") return String(value ?? "");
37
- return value.message || value.action || value.note || value.summary || JSON.stringify(value);
88
+ const item = record(value);
89
+ return item.title || item.message || item.summary || item.action || item.remediation || item.note || JSON.stringify(item);
38
90
  };
39
- const duration = (minutes) => {
40
- const value = number(minutes);
41
- if (value < 1) return `${Math.round(value * 60)} 秒`;
42
- if (value < 60) return `${Math.round(value * 10) / 10} 分钟`;
43
- return `${Math.floor(value / 60)} 小时 ${Math.round(value % 60)} 分钟`;
91
+ const shortHash = (value) => String(value || "N/A").slice(0, 10);
92
+ const duration = (raw) => {
93
+ const value = numeric(raw);
94
+ if (value === null) return "N/A";
95
+ if (value < 1000) return `${Math.round(value)}ms`;
96
+ if (value < 60000) return `${Math.round(value / 100) / 10} 秒`;
97
+ if (value < 3600000) return `${Math.round(value / 6000) / 10} 分钟`;
98
+ return `${Math.floor(value / 3600000)} 小时 ${Math.round(value % 3600000 / 60000)} 分钟`;
44
99
  };
45
- const durationMs = (ms) => {
46
- const value = number(ms);
47
- if (value <= 0) return "0 秒";
48
- if (value < 1000) return `${value}ms`;
49
- const seconds = value / 1000;
50
- if (seconds < 60) return `${Math.round(seconds * 10) / 10} 秒`;
51
- const minutes = seconds / 60;
52
- if (minutes < 60) return `${Math.round(minutes * 10) / 10} 分钟`;
53
- return `${Math.floor(minutes / 60)} 小时 ${Math.round(minutes % 60)} 分钟`;
100
+ const measurement = (value, unit = "") => {
101
+ const item = record(value);
102
+ if (item.state === "unknown" || item.state === "not_applicable") return "N/A";
103
+ if (item.state === "zero") return `0${unit}`;
104
+ if (item.state === "known") return `${esc(item.value)}${unit}`;
105
+ if (value === null || value === undefined || value === "") return "N/A";
106
+ return `${esc(value)}${unit}`;
54
107
  };
108
+ const verificationStatus = (value) => {
109
+ if (typeof value === "string") return value;
110
+ const item = record(value);
111
+ if (item.status) return item.status;
112
+ if ((numeric(item.failures) || 0) + (numeric(item.errors) || 0) > 0) return "FAIL";
113
+ if ((numeric(item.run) || numeric(item.total) || 0) > 0) return "OK";
114
+ return "NOT_RUN";
115
+ };
116
+ const verificationNote = (key, raw) => {
117
+ const value = record(raw);
118
+ if (key === "unitTests") return `${value.passRate || `${value.run ?? 0} 项`} · 失败 ${value.failures ?? 0}`;
119
+ if (key === "apiTests") return `${value.passRate || `${value.passed ?? 0}/${value.total ?? 0}`} · 阻塞 ${value.blocked ?? 0}`;
120
+ if (key === "browserE2E") return `${value.passed ?? 0}/${value.total ?? 0} 通过 · ${value.failed ?? 0} 失败 · 重试 ${value.retries ?? 0}`;
121
+ if (value.checks !== undefined) return `${value.checks} 项检查`;
122
+ if (value.coverageDisplay) return value.coverageDisplay;
123
+ return Object.keys(value).filter((name) => name !== "status").slice(0, 3)
124
+ .map((name) => `${name}=${value[name]}`).join(" · ") || "无额外指标";
125
+ };
126
+ const groups = [
127
+ ["后端", ["unitTests", "dbCompatibility"]],
128
+ ["Geo", ["geo"]],
129
+ ["前端", ["frontend"]],
130
+ ["浏览器", ["browserE2E"]],
131
+ ["API", ["apiTests"]]
132
+ ].map(([label, keys]) => {
133
+ const present = keys.filter((key) => verification[key] !== undefined);
134
+ const values = present.map((key) => verification[key]);
135
+ const statuses = values.map(verificationStatus);
136
+ const groupStatus = statuses.some((status) => /FAIL|ERROR|BLOCKED/.test(String(status)))
137
+ ? "FAIL"
138
+ : statuses.some((status) => /WARN|CONDITIONAL|PARTIAL/.test(String(status)))
139
+ ? "WARN"
140
+ : statuses.length && statuses.every((status) => /NOT_APPLICABLE/.test(String(status)))
141
+ ? "NOT_APPLICABLE"
142
+ : statuses.length && statuses.every((status) => /OK|PASS|NOT_APPLICABLE/.test(String(status)))
143
+ ? "OK"
144
+ : "NOT_RUN";
145
+ const note = present.map((key) => verificationNote(key, verification[key])).join(" · ") || "未配置该组验证";
146
+ return { label, status: groupStatus, note, present };
147
+ });
148
+ const passedGroups = groups.filter((group) => group.status === "OK").length;
149
+ const productCommit = identity.productCommit || data.productCommit || data.finalCommit;
150
+ const wallClock = timing.workflowWallClockMs ?? record(data.durations).totalMs;
55
151
 
56
- const stages = Object.entries(record(data.stageStatus));
57
- const verification = record(data.verification);
58
- const unit = record(verification.unitTests);
59
- const api = record(verification.apiTests);
60
- const performance = record(verification.performance);
61
- const durations = record(data.durations);
62
- const durationStages = list(durations.stages);
63
- const timing = record(data.timing);
64
- const attempts = list(timing.attempts);
65
- const sessions = list(timing.sessions);
66
- const candidate = record(data.candidateVerification);
67
- const archiveIntegrity = record(data.archiveIntegrity);
68
- const releaseDecision = record(data.releaseDecision);
69
- const releaseChecks = Object.entries(record(releaseDecision.checks));
70
- const artifactStorage = record(data.artifactStorage);
71
- const remoteCost = record(record(data.remoteCost).totals);
152
+ const groupHtml = groups.map((group) => `
153
+ <article class="verify-card" title="${esc(group.present.map((key) => {
154
+ const value = record(verification[key]);
155
+ return `${key} · status=${verificationStatus(verification[key])}${value.failed === undefined ? "" : ` · failed=${value.failed}`}`;
156
+ }).join(" / "))}">
157
+ <div><span class="verify-name">${esc(group.label)}</span>${pill(group.status)}</div>
158
+ <p>${esc(group.note)}</p>
159
+ </article>`).join("");
160
+ const risksHtml = risks.map((item) => `<li>${esc(describe(item))}</li>`).join("") || "<li>当前没有未处置风险</li>";
161
+ const actionsHtml = actions.map((item) => `<li>${esc(describe(item))}</li>`).join("") || "<li>无需人工后续动作</li>";
162
+ const stageHtml = Object.entries(record(current.stages)).map(([name, status]) =>
163
+ `<tr><td>${esc(name)}</td><td>${pill(status)}</td></tr>`
164
+ ).join("") || '<tr><td colspan="2">没有阶段状态记录</td></tr>';
165
+ const fileHtml = files.map((item) =>
166
+ `<tr><td><code>${esc(item.path || item.file)}</code></td><td class="positive">+${esc(item.insertions ?? 0)}</td><td class="negative">-${esc(item.deletions ?? 0)}</td></tr>`
167
+ ).join("") || '<tr><td colspan="3">没有变更文件证据</td></tr>';
168
+ const timelineHtml = timeline.map((item) =>
169
+ `<tr><td>${esc(item.phase || item.stage || "-")}</td><td>${esc(item.attempt || "-")}</td><td>${pill(item.status || item.result || item.type)}</td><td>${esc(item.executorTool || item.executor_tool || item.summary || "-")}</td></tr>`
170
+ ).join("") || '<tr><td colspan="4">没有时间线记录</td></tr>';
171
+ const commandHtml = commands.map((item) =>
172
+ `<tr><td>${esc(item.phase || "-")}</td><td><code>${esc(item.command || "")}</code></td><td>${pill(Number(item.exit_code ?? item.exitCode) === 0 ? "OK" : "FAIL")}</td></tr>`
173
+ ).join("") || '<tr><td colspan="3">没有命令证据</td></tr>';
174
+ const candidate = record(release.candidate);
175
+ const releaseHtml = recordOnly ? "" : `
176
+ <article class="card"><h2>发布与候选</h2>
177
+ <div class="fact"><span>候选证明</span>${pill(candidate.ok ? "OK" : candidate.code || "NOT_RUN")}</div>
178
+ <div class="fact"><span>发布资格</span>${pill(release.eligible ? "OK" : release.decision || "BLOCKED")}</div>
179
+ </article>`;
180
+ const remoteCost = record(measurements.remoteCost);
181
+ const remoteCostTotals = record(remoteCost.totals);
182
+ const storage = record(measurements.artifactStorage);
72
183
  const projection = record(data.projection);
73
- const wallLabel = timing.workflowWallClockMs != null
74
- ? durationMs(timing.workflowWallClockMs)
75
- : (durations.totalLabel || duration(durations.totalMinutes));
76
- const timingColumnsHtml = `
77
- <article class="card" id="timingColumns"><h2>时间守恒 / Timing Conservation</h2>
78
- <div class="row"><div><strong>全流程墙钟</strong><small>workflowWallClock</small></div><span>${esc(durationMs(timing.workflowWallClockMs))}</span></div>
79
- <div class="row"><div><strong>归因阶段并集</strong><small>attributedStageUnion</small></div><span>${esc(durationMs(timing.attributedStageUnionMs))}</span></div>
80
- <div class="row"><div><strong>远端等待</strong><small>externalWait</small></div><span>${esc(durationMs(timing.externalWaitMs))}</span></div>
81
- <div class="row"><div><strong>暂停</strong><small>paused</small></div><span>${esc(durationMs(timing.pausedMs))}</span></div>
82
- <div class="row"><div><strong>未归因</strong><small>agentOrToolUnattributed</small></div><span>${esc(durationMs(timing.agentOrToolUnattributedMs))}</span></div>
83
- <p><small>conservationDeltaMs=<code>${esc(timing.conservationDeltaMs ?? "N/A")}</code> · reportCutoffAt=<code id="reportCutoffAt">${esc(timing.reportCutoffAt || "N/A")}</code> · active-only 不冒充墙钟</small></p>
84
- </article>`;
85
- const maxMinutes = Math.max(1, ...durationStages.map((item) => number(item.minutes)));
86
- const diff = record(data.diffStat);
87
- const risks = list(data.knownRisks);
88
- const actions = list(data.manualActions);
89
- const files = list(data.changedFiles);
90
- const commands = list(record(data.reportPipeline).commands);
91
- const timeline = list(data.timeline);
92
- const statusReasons = list(data.finalStatusReasons);
93
- const reasonHtml = statusReasons.length
94
- ? `<small>${statusReasons.map((item) => esc(item)).join(" · ")}</small>`
95
- : "";
96
-
97
- const stageHtml = stages.map(([name, status]) => `<div class="row"><span>${esc(name)}</span>${pill(status)}</div>`).join("") || '<p class="empty">没有阶段状态记录</p>';
98
- const verificationHtml = [
99
- ["单元测试", unit.status || ((number(unit.failures) + number(unit.errors)) > 0 ? "FAIL" : (number(unit.run) > 0 ? "OK" : "NOT_RUN")), `${number(unit.run)} 个 · ${number(unit.failures)} 失败 · ${number(unit.errors)} 错误 · ${number(unit.skipped)} 跳过 · ${number(unit.deselected)} 未选择`],
100
- ["API 测试", api.status || "NOT_RUN", `${number(api.executed)}/${number(api.total)} 已执行 · ${number(api.passed)} 通过 · ${number(api.failed)} 失败 · ${number(api.blocked)} 阻塞 · 通过率 ${api.passRate || "N/A"} · 执行率 ${api.executionRate || "N/A"}`],
101
- ["数据库兼容", verification.dbCompatibility || "NOT_RUN", verification.coverageDisplay || "未记录覆盖率"],
102
- ["性能验证", performance.status || "NOT_RUN", Object.keys(record(performance.metrics)).length ? JSON.stringify(performance.metrics) : "未记录性能指标"]
103
- ].map(([name, status, note]) => `<div class="row"><div><strong>${esc(name)}</strong><small>${esc(note)}</small></div>${pill(status)}</div>`).join("");
104
- const durationHtml = durationStages.map((item) => {
105
- const width = Math.max(2, Math.round(number(item.minutes) / maxMinutes * 100));
106
- const attempts = list(item.attempts).length;
107
- return `<div class="duration"><div><span>${esc(item.skill || item.stage)}</span><span>${duration(item.minutes)}${attempts > 1 ? ` · ${attempts} 次尝试` : ""}</span></div><i><b style="width:${width}%"></b></i></div>`;
108
- }).join("") || '<p class="empty">没有可计算的阶段耗时</p>';
109
- const riskHtml = risks.map((item) => `<li>${esc(describe(item))}</li>`).join("") || "<li>未记录已知风险</li>";
110
- const actionHtml = actions.map((item) => `<li>${esc(describe(item))}</li>`).join("") || "<li>无需人工后续动作</li>";
111
- const fileRows = files.map((item) => `<tr><td><code>${esc(item.path || item.file)}</code></td><td class="plus">+${number(item.insertions)}</td><td class="minus">-${number(item.deletions)}</td></tr>`).join("") || '<tr><td colspan="3">没有变更文件证据</td></tr>';
112
- const timelineRows = timeline.map((item) => `<tr><td>${esc(item.phase || item.stage || "-")}</td><td>${esc(item.attempt || "-")}</td><td>${pill(item.status || item.result || item.type)}</td><td>${esc(item.executorTool || item.executor_tool || item.summary || "-")}</td></tr>`).join("") || '<tr><td colspan="4">没有时间线记录</td></tr>';
113
- const commandRows = commands.map((item) => `<tr><td>${esc(item.phase || "-")}</td><td><code>${esc(item.command)}</code></td><td>${pill(number(item.exit_code) === 0 ? "OK" : "FAIL")}</td></tr>`).join("") || '<tr><td colspan="3">没有命令证据</td></tr>';
114
- const releaseCheckHtml = releaseChecks.map(([name, checkValue]) => {
115
- const check = record(checkValue);
116
- return `<div class="row"><div><strong>${esc(name)}</strong><small>${esc(check.message || check.code || "")}</small></div>${pill(check.ok ? "OK" : "BLOCKED")}</div>`;
117
- }).join("") || '<p class="empty">没有 release check 证据</p>';
118
- const attemptRows = attempts.map((item) => `<tr><td>${esc(item.phase || "-")}</td><td>${esc(item.attempt || "-")}</td><td>${pill(item.terminalStatus || item.status || "UNKNOWN")}</td><td>${esc(durationMs(item.durationMs))}</td></tr>`).join("") || '<tr><td colspan="4">没有 typed attempt 记录</td></tr>';
119
- const sessionRows = sessions.map((item) => `<tr><td>${esc(item.sessionId || item.id || "-")}</td><td>${esc(item.startedAt || "-")}</td><td>${esc(item.endedAt || "-")}</td><td>${esc(durationMs(item.wallClockMs || item.durationMs))}</td></tr>`).join("") || '<tr><td colspan="4">没有 session 记录</td></tr>';
120
184
 
121
- const html = `<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
122
- <title>Harness 最终报告 · ${esc(data.changeName)}</title><style>
123
- :root{color-scheme:light dark;--bg:#f3f6fa;--card:#fff;--soft:#f7f9fc;--text:#182132;--muted:#667085;--line:#dce3ed;--blue:#2563eb;--good:#087443;--warn:#9b5a00;--bad:#b42318;--shadow:0 8px 24px rgba(18,35,70,.07)}
124
- @media(prefers-color-scheme:dark){:root{--bg:#0b1018;--card:#121925;--soft:#182130;--text:#eef3fb;--muted:#9aa8bc;--line:#29364a;--blue:#7aa9ff;--good:#55d99d;--warn:#f0bc63;--bad:#ff8b83;--shadow:0 12px 30px rgba(0,0,0,.25)}}
125
- *{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);font:14px/1.5 Inter,"Segoe UI","Microsoft YaHei",sans-serif}main{width:min(1140px,calc(100% - 28px));margin:22px auto 44px}.hero,.card,.metric,details{background:var(--card);border:1px solid var(--line);border-radius:14px;box-shadow:var(--shadow)}.hero{padding:23px 25px;border-top:3px solid var(--blue)}.eyebrow{color:var(--blue);font-weight:750;letter-spacing:.08em}h1{font-size:27px;line-height:1.2;margin:5px 0 7px;overflow-wrap:anywhere}.goal,.empty,small{color:var(--muted)}.status{display:flex;align-items:center;gap:10px;margin-top:16px}.metrics{display:grid;grid-template-columns:repeat(4,1fr);gap:11px;margin:11px 0}.metric{padding:14px 15px}.metric strong{display:block;font-size:19px;margin:4px 0}.grid{display:grid;grid-template-columns:1.12fr .88fr;gap:11px}.card{padding:17px;margin-bottom:11px}h2{font-size:16px;margin:0 0 10px}.row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 0;border-bottom:1px solid var(--line)}.row:last-child{border:0}.row small{display:block;margin-top:2px}.pill{display:inline-flex;border-radius:999px;padding:3px 9px;font-size:11px;font-weight:750;white-space:nowrap}.pill.success{color:var(--good);background:color-mix(in srgb,var(--good) 13%,transparent)}.pill.warning{color:var(--warn);background:color-mix(in srgb,var(--warn) 14%,transparent)}.pill.danger{color:var(--bad);background:color-mix(in srgb,var(--bad) 13%,transparent)}.pill.neutral{color:var(--blue);background:color-mix(in srgb,var(--blue) 12%,transparent)}.duration{margin:11px 0}.duration>div{display:flex;justify-content:space-between;color:var(--muted);font-size:12px}.duration>div span:first-child{color:var(--text);font-weight:650}.duration i{display:block;height:7px;background:var(--soft);border-radius:99px;overflow:hidden;margin-top:6px}.duration b{display:block;height:100%;background:linear-gradient(90deg,var(--blue),#7c70ff)}.risk{display:grid;grid-template-columns:1fr 1fr;gap:10px}.risk>div{background:var(--soft);border-radius:10px;padding:11px}.risk h3{font-size:13px;margin:0 0 5px}.risk ul{padding-left:18px;margin:0;color:var(--muted)}details{margin:9px 0}summary{cursor:pointer;padding:13px 15px;font-weight:650}details>div{padding:0 15px 15px;overflow:auto}table{width:100%;border-collapse:collapse}th,td{text-align:left;padding:8px 9px;border-bottom:1px solid var(--line);vertical-align:top}th{color:var(--muted);font-size:11px;letter-spacing:.04em}code{font-family:"Cascadia Code",Consolas,monospace;color:var(--blue);overflow-wrap:anywhere}.plus{color:var(--good)}.minus{color:var(--bad)}dl{display:grid;grid-template-columns:145px 1fr;gap:7px 11px}dt{color:var(--muted)}dd{margin:0;overflow-wrap:anywhere}
126
- @media(max-width:800px){.metrics{grid-template-columns:1fr 1fr}.grid,.risk{grid-template-columns:1fr}}@media(max-width:480px){main{width:calc(100% - 18px);margin-top:9px}.metrics{grid-template-columns:1fr}.hero{padding:19px}h1{font-size:23px}dl{grid-template-columns:1fr}}
127
- </style></head><body><main>
128
- <section class="hero"><div class="eyebrow">HARNESS EXECUTION REPORT · CURRENT OUTCOME / 当前结果</div><h1>${esc(data.changeName || "未命名变更")}</h1><p class="goal">${esc(data.businessGoal || "未记录业务目标")}</p><div class="status">${pill(data.finalStatus)}<span>当前结果基于事件、验证账本与 Git 证据${data.riskTier ? ` · riskTier=${esc(data.riskTier)}` : ""}</span></div>${reasonHtml ? `<div class="status">${reasonHtml}</div>` : ""}</section>
129
- <section class="metrics"><article class="metric"><small>产品提交</small><strong><code title="${esc(data.productCommit || data.finalCommit)}">${esc(shortHash(data.productCommit || data.finalCommit))}</code></strong><small>archive=${esc(shortHash(data.archiveCommit || data.finalCommit))}</small></article><article class="metric"><small>代码范围</small><strong>${number(diff.filesChanged)} 个文件</strong><small><span class="plus">+${number(diff.insertions)}</span> · <span class="minus">-${number(diff.deletions)}</span></small></article><article class="metric"><small>全流程墙钟</small><strong>${esc(wallLabel)}</strong><small>活动=${esc(durationMs(timing.stageActiveExecutionMs))} · ${durationStages.length} 阶段</small></article><article class="metric"><small>归档完整性</small><strong>${esc(record(data.archiveManifest).checksumStatus || "UNKNOWN")}</strong><small>${number(record(data.archiveManifest).totalArchiveFiles)} 个归档文件</small></article></section>
130
- <section class="grid"><div><article class="card"><h2>Candidate Claim / Attestation</h2><div class="row"><div><strong>${esc(candidate.assurance || "none")}</strong><small>${esc(candidate.code || candidate.message || "没有候选证明")}</small></div>${pill(candidate.ok ? "VERIFIED" : "UNVERIFIED")}</div></article><article class="card"><h2>Release Eligibility</h2><div class="row"><div><strong>${esc(releaseDecision.code || "NOT_EVALUATED")}</strong><small>唯一发布公式的汇总结论</small></div>${pill(releaseDecision.releaseEligible ? "ELIGIBLE" : "BLOCKED")}</div>${releaseCheckHtml}</article><article class="card"><h2>验证结论</h2>${verificationHtml}</article><article class="card"><h2>阶段耗时(活动)</h2>${durationHtml}</article>${timingColumnsHtml}</div><div><article class="card"><h2>Archive Integrity</h2><div class="row"><div><strong>${esc(archiveIntegrity.code || "NOT_EVALUATED")}</strong><small>${esc(archiveIntegrity.message || archiveIntegrity.checksumStatus || "")}</small></div>${pill(archiveIntegrity.ok ? "OK" : "FAIL")}</div></article><article class="card"><h2>History Quality</h2><div class="row"><div><strong>${attempts.length} 次 typed attempts</strong><small>${sessions.length} 个 sessions · unclosed=${number(timing.unclosedAttemptCount)}</small></div>${pill(number(timing.unclosedAttemptCount) === 0 ? "CLOSED" : "INCOMPLETE")}</div></article><article class="card"><h2>Remote Cost</h2><div class="row"><div><strong>${number(remoteCost.runnerMinutes)} runner 分钟</strong><small>queue/wait=${durationMs(remoteCost.queueWaitMs)} · artifacts=${number(remoteCost.artifactBytes)} bytes</small></div>${pill(number(remoteCost.duplicateRunCount) === 0 ? "NO_DUPLICATES" : `${number(remoteCost.duplicateRunCount)} DUPLICATES`)}</div></article><article class="card"><h2>Artifact Storage</h2><div class="row"><div><strong>${number(artifactStorage.bytesAdded)} bytes 新增</strong><small>复用=${number(artifactStorage.bytesReused)} · 清理=${number(artifactStorage.bytesPruned)} · ${number(artifactStorage.artifactCount)} 项</small></div>${pill("OBSERVED")}</div></article><article class="card"><h2>Projection / Fallback</h2><div class="row"><div><strong>${esc(projection.mode || projection.status || "not-configured")}</strong><small>${esc(projection.remediation || projection.message || projection.code || "")}</small></div>${pill(projection.ok === false ? "BLOCKED" : (projection.mode === "fallback" ? "FALLBACK" : "OK"))}</div></article><article class="card"><h2>阶段状态</h2>${stageHtml}</article><article class="card"><h2>风险与后续</h2><div class="risk"><div><h3>已知风险</h3><ul>${riskHtml}</ul></div><div><h3>人工动作</h3><ul>${actionHtml}</ul></div></div></article></div></section>
131
- <details><summary>变更文件(${files.length})</summary><div><table><thead><tr><th>文件</th><th>新增</th><th>删除</th></tr></thead><tbody>${fileRows}</tbody></table></div></details>
132
- <details><summary>执行时间线与工具交接(${timeline.length})</summary><div><table><thead><tr><th>阶段</th><th>尝试</th><th>状态</th><th>来源 / 摘要</th></tr></thead><tbody>${timelineRows}</tbody></table></div></details>
133
- <details><summary>Typed Attempts(${attempts.length})</summary><div><table><thead><tr><th>阶段</th><th>尝试</th><th>终态</th><th>耗时</th></tr></thead><tbody>${attemptRows}</tbody></table></div></details>
134
- <details><summary>Sessions(${sessions.length})</summary><div><table><thead><tr><th>Session</th><th>开始</th><th>结束</th><th>墙钟</th></tr></thead><tbody>${sessionRows}</tbody></table></div></details>
135
- <details><summary>命令证据(${commands.length})</summary><div><table><thead><tr><th>阶段</th><th>命令</th><th>结果</th></tr></thead><tbody>${commandRows}</tbody></table></div></details>
136
- <details><summary>技术元数据</summary><div><dl><dt>功能合并提交</dt><dd><code>${esc(data.featureMergeHash || data.productCommit || data.finalCommit || "N/A")}</code></dd><dt>发布线尖端</dt><dd><code>${esc(data.releaseTipHash || data.archiveCommit || "N/A")}</code></dd><dt>产品提交</dt><dd><code>${esc(data.productCommit || data.finalCommit || "N/A")}</code></dd><dt>产品树哈希</dt><dd><code>${esc(data.productTreeHash || "N/A")}</code></dd><dt>归档提交</dt><dd><code>${esc(data.archiveCommit || data.finalCommit || "N/A")}</code></dd><dt>基线提交</dt><dd><code>${esc(data.baseCommit || "N/A")}</code></dd><dt>Git 范围</dt><dd><code>${esc(diff.range || "N/A")}</code></dd><dt>报告数据版本</dt><dd>${esc(data.schemaVersion || "N/A")}</dd><dt>事实来源</dt><dd>${esc(list(record(data.reportPipeline).sources).join(" · ") || "N/A")}</dd></dl></div></details>
185
+ const html = `<!doctype html>
186
+ <html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
187
+ <title>Harness 最终报告 · ${esc(data.changeName || "未命名变更")}</title>
188
+ <style>
189
+ :root{color-scheme:light dark;--canvas:#f4f6f8;--surface:#fff;--surface-2:#f8fafc;--ink:#172033;--muted:#667085;--line:#dfe5ec;--accent:#2457d6;--good:#087443;--warn:#9a5b00;--bad:#b42318;--shadow:0 8px 26px rgba(22,34,58,.07)}
190
+ @media(prefers-color-scheme:dark){:root{--canvas:#0b1119;--surface:#121a26;--surface-2:#172231;--ink:#eef3f9;--muted:#9aa8ba;--line:#2a384a;--accent:#86abff;--good:#58d59b;--warn:#efbd68;--bad:#ff8b83;--shadow:0 12px 30px rgba(0,0,0,.28)}}
191
+ *{box-sizing:border-box}html,body{max-width:100%;overflow-x:hidden}body{margin:0;background:var(--canvas);color:var(--ink);font:14px/1.55 Inter,"Segoe UI","Microsoft YaHei",sans-serif}main{width:min(1180px,calc(100% - 32px));margin:24px auto 48px}.hero,.card,.metric,details,.verify-card{background:var(--surface);border:1px solid var(--line);border-radius:14px;box-shadow:var(--shadow)}.hero{padding:24px 26px;border-top:4px solid var(--accent)}.kicker{color:var(--accent);font-size:12px;font-weight:800;letter-spacing:.1em}.hero h1{font-size:28px;line-height:1.25;margin:5px 0 7px;overflow-wrap:anywhere}.goal,.muted,small{color:var(--muted)}.outcome{display:flex;align-items:center;gap:10px;margin-top:14px}.record-only{display:inline-flex;margin-top:12px;padding:6px 10px;border-radius:8px;background:var(--surface-2);color:var(--muted)}.metrics{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:10px;margin:11px 0}.metric{min-width:0;padding:13px 14px}.metric strong{display:block;font-size:18px;margin-top:3px;overflow-wrap:anywhere}.card{padding:17px;margin-bottom:11px}.card h2,details summary{font-size:16px;font-weight:760}.card h2{margin:0 0 11px}.verification-grid{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:9px}.verify-card{padding:13px;box-shadow:none;background:var(--surface-2)}.verify-card>div,.fact{display:flex;justify-content:space-between;gap:9px;align-items:center}.verify-name{font-weight:760}.verify-card p{margin:8px 0 0;color:var(--muted);font-size:12px}.two-column{display:grid;grid-template-columns:1.3fr .7fr;gap:11px}.risk-actions{display:grid;grid-template-columns:1fr 1fr;gap:10px}.risk-actions section{padding:12px;border-radius:10px;background:var(--surface-2)}.risk-actions h3{font-size:13px;margin:0 0 6px}.risk-actions ul{margin:0;padding-left:18px;color:var(--muted)}.fact{padding:8px 0;border-bottom:1px solid var(--line)}.fact:last-child{border:0}.pill{display:inline-flex;flex:none;border-radius:999px;padding:3px 9px;font-size:11px;font-weight:800;white-space:nowrap}.pill.success{color:var(--good);background:color-mix(in srgb,var(--good) 13%,transparent)}.pill.warning{color:var(--warn);background:color-mix(in srgb,var(--warn) 14%,transparent)}.pill.danger{color:var(--bad);background:color-mix(in srgb,var(--bad) 13%,transparent)}.pill.neutral{color:var(--accent);background:color-mix(in srgb,var(--accent) 12%,transparent)}details{margin:9px 0}summary{cursor:pointer;padding:13px 16px}details>div{padding:0 16px 16px;overflow:auto}.table-wrap{max-width:100%;overflow:auto}table{width:100%;border-collapse:collapse}th,td{text-align:left;padding:8px 9px;border-bottom:1px solid var(--line);vertical-align:top}th{color:var(--muted);font-size:11px}code{font-family:"Cascadia Code",Consolas,monospace;color:var(--accent);overflow-wrap:anywhere}.positive{color:var(--good)}.negative{color:var(--bad)}dl{display:grid;grid-template-columns:155px minmax(0,1fr);gap:7px 12px}dt{color:var(--muted)}dd{margin:0;overflow-wrap:anywhere}
192
+ @media(max-width:900px){.metrics{grid-template-columns:repeat(2,minmax(0,1fr))}.verification-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.two-column{grid-template-columns:1fr}}
193
+ @media(max-width:600px){main{width:calc(100% - 18px);margin:9px auto 30px}.hero{padding:18px}.hero h1{font-size:23px}.metrics,.verification-grid,.risk-actions{grid-template-columns:1fr}.outcome{align-items:flex-start;flex-direction:column}.card{padding:14px}dl{grid-template-columns:1fr}.metric strong{font-size:17px}}
194
+ </style></head>
195
+ <body><main>
196
+ <section class="hero">
197
+ <div class="kicker">HARNESS · 管理结论</div>
198
+ <h1>${esc(data.changeName || "未命名变更")}</h1>
199
+ <p class="goal">${esc(data.businessGoal || "未记录业务目标")}</p>
200
+ <div class="outcome">${pill(current.status)}<span>${esc(list(current.reasons).join(" · ") || "结论基于事件、验证账本与 Git 事实")}</span></div>
201
+ ${recordOnly ? '<div class="record-only">归档意图:仅记录 · 未请求发布</div>' : ""}
202
+ </section>
203
+ <section class="metrics">
204
+ <article class="metric"><small>产品提交</small><strong><code title="${esc(productCommit || "N/A")}">${esc(shortHash(productCommit))}</code></strong></article>
205
+ <article class="metric"><small>验证概览</small><strong>${passedGroups}/${groups.length} 组通过</strong><small>按后端 / Geo / 前端 / 浏览器 / API</small></article>
206
+ <article class="metric"><small>风险与动作</small><strong>${risks.length} / ${actions.length}</strong><small>当前风险 / 人工动作</small></article>
207
+ <article class="metric"><small>全流程耗时</small><strong>${esc(duration(wallClock))}</strong><small>活动 ${esc(duration(timing.stageActiveExecutionMs))}</small></article>
208
+ <article class="metric"><small>代码范围</small><strong>${esc(record(data.diffStat).filesChanged ?? files.length)} 个文件</strong><small class="positive">+${esc(record(data.diffStat).insertions ?? 0)} · <span class="negative">-${esc(record(data.diffStat).deletions ?? 0)}</span></small></article>
209
+ </section>
210
+ <article class="card"><h2>验证概览</h2><div class="verification-grid">${groupHtml}</div></article>
211
+ <section class="two-column">
212
+ <article class="card"><h2>风险与动作</h2><div class="risk-actions"><section><h3>当前风险</h3><ul>${risksHtml}</ul></section><section><h3>人工动作</h3><ul>${actionsHtml}</ul></section></div></article>
213
+ <div>
214
+ ${releaseHtml}
215
+ <article class="card"><h2>事实完整性</h2>
216
+ <div class="fact"><span>归档完整性</span>${pill(record(data.archiveIntegrity).ok === false ? "FAIL" : record(data.archiveManifest).checksumStatus || "UNKNOWN")}</div>
217
+ <div class="fact"><span>时间守恒</span>${pill(numeric(timing.conservationDeltaMs) === 0 ? "OK" : "WARN")}</div>
218
+ </article>
219
+ </div>
220
+ </section>
221
+ <details><summary>技术证据 · 验证与时间守恒</summary><div>
222
+ <div class="fact"><span>全流程墙钟 <code title="workflowWallClockMs">workflowWallClock</code></span><strong>${esc(duration(timing.workflowWallClockMs))}</strong></div>
223
+ <div class="fact"><span>活动执行 <code title="stageActiveExecutionMs">stageActiveExecution</code></span><strong>${esc(duration(timing.stageActiveExecutionMs))}</strong></div>
224
+ <div class="fact"><span>远端等待 <code title="externalWaitMs">externalWait</code></span><strong>${esc(duration(timing.externalWaitMs))}</strong></div>
225
+ <div class="fact"><span>暂停 <code title="pausedMs">paused</code></span><strong>${esc(duration(timing.pausedMs))}</strong></div>
226
+ <div class="fact"><span>未归因 <code title="agentOrToolUnattributedMs">agentOrToolUnattributed</code></span><strong>${esc(duration(timing.agentOrToolUnattributedMs))}</strong></div>
227
+ <div class="fact"><span>远端 runner 成本</span><strong>${measurement(remoteCostTotals.runnerMinutes ?? remoteCost.runnerMinutes, " 分钟")}</strong></div>
228
+ <div class="fact"><span>新增制品字节</span><strong>${measurement(storage.bytesAdded, " bytes")}</strong></div>
229
+ <div class="fact"><span>守恒差值 <code>conservationDeltaMs</code></span><strong>${esc(timing.conservationDeltaMs ?? "N/A")}</strong></div>
230
+ </div></details>
231
+ <details><summary>阶段状态</summary><div class="table-wrap"><table><thead><tr><th>阶段</th><th>结果</th></tr></thead><tbody>${stageHtml}</tbody></table></div></details>
232
+ <details><summary>变更文件(${files.length})</summary><div class="table-wrap"><table><thead><tr><th>文件</th><th>新增</th><th>删除</th></tr></thead><tbody>${fileHtml}</tbody></table></div></details>
233
+ <details><summary>执行时间线与工具交接(${timeline.length})</summary><div class="table-wrap"><table><thead><tr><th>阶段</th><th>尝试</th><th>状态</th><th>来源 / 摘要</th></tr></thead><tbody>${timelineHtml}</tbody></table></div></details>
234
+ <details><summary>命令证据(${commands.length})</summary><div class="table-wrap"><table><thead><tr><th>阶段</th><th>命令</th><th>结果</th></tr></thead><tbody>${commandHtml}</tbody></table></div></details>
235
+ <details><summary>技术元数据</summary><div><dl>
236
+ <dt>产品提交</dt><dd><code>${esc(productCommit || "N/A")}</code></dd>
237
+ <dt>产品树哈希</dt><dd><code>${esc(identity.productTreeHash || data.productTreeHash || "N/A")}</code></dd>
238
+ <dt>环境哈希</dt><dd><code>${esc(identity.environmentHash || data.environmentHash || "N/A")}</code></dd>
239
+ <dt>基线提交</dt><dd><code>${esc(identity.baseCommit || data.baseCommit || "N/A")}</code></dd>
240
+ <dt>报告数据版本</dt><dd>${esc(data.schemaVersion || "N/A")}</dd>
241
+ <dt>归档意图</dt><dd title="${esc(release.intent)}">${recordOnly ? "仅记录(未请求发布)" : esc(release.intent || "未声明")}</dd>
242
+ <dt>尝试记录</dt><dd>${attempts.length}</dd>
243
+ <dt>历史质量</dt><dd>${numeric(timing.unclosedAttemptCount) === 0 ? "无未闭合尝试" : `${esc(timing.unclosedAttemptCount ?? "N/A")} 个未闭合尝试`}</dd>
244
+ <dt>投影状态</dt><dd title="Projection / Fallback"><code>${esc(projection.code || projection.mode || "N/A")}</code></dd>
245
+ </dl></div></details>
137
246
  </main></body></html>`;
138
247
 
139
248
  fs.mkdirSync(path.dirname(outPath), { recursive: true });
@@ -82,6 +82,14 @@
82
82
  "blocked": 0,
83
83
  "passRate": "34/35"
84
84
  },
85
+ "browserE2E": {
86
+ "status": "OK | FAIL | PARTIAL | USER_SKIPPED | NOT_RUN",
87
+ "total": 0,
88
+ "passed": 0,
89
+ "failed": 0,
90
+ "skipped": 0,
91
+ "retries": 0
92
+ },
85
93
  "dbCompatibility": "OK | BLOCKED_BY_DBA | NOT_RUN",
86
94
  "coverageDisplay": "例如 29/29 或 USER_SKIPPED"
87
95
  },
@@ -20,7 +20,7 @@ disallowed-tools:
20
20
  - Bash(awk *)
21
21
  - Bash(curl *)
22
22
  ---
23
- <!-- generated by harness_deploy.py; core=33c3c012031041b2; overlay=none; agent=claude-code; do not edit -->
23
+ <!-- generated by harness_deploy.py; core=5830124f26774287; overlay=none; agent=claude-code; do not edit -->
24
24
  # harness-codebase-map — 代码库地图
25
25
 
26
26
  ## Purpose
@@ -20,7 +20,7 @@ disallowed-tools:
20
20
  - Bash(awk *)
21
21
  - Bash(curl *)
22
22
  ---
23
- <!-- generated by harness_deploy.py; core=33c3c012031041b2; overlay=none; agent=claude-code; do not edit -->
23
+ <!-- generated by harness_deploy.py; core=5830124f26774287; overlay=none; agent=claude-code; do not edit -->
24
24
  # harness-knowledge-ingest
25
25
 
26
26
  从 `.harness/archive/**/reports/final/summary-data.json` 抽取项目知识,生成并维护本地 `.harness/knowledge/` 索引。