@hunter-harness/workflow-harness 0.2.33 → 0.2.34

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 +404 -12
  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 +404 -12
  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 +404 -12
  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 +404 -12
  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 +404 -12
  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 +404 -12
  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 +404 -12
  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 +404 -12
  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
@@ -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=5e077a99a90502cb; overlay=java; agent=claude-code; do not edit -->
23
+ <!-- generated by harness_deploy.py; core=7eb7481f06ed7f41; overlay=java; 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=5e077a99a90502cb; overlay=java; agent=claude-code; do not edit -->
23
+ <!-- generated by harness_deploy.py; core=7eb7481f06ed7f41; overlay=java; agent=claude-code; do not edit -->
24
24
  # harness-knowledge-ingest
25
25
 
26
26
  从 `.harness/archive/**/reports/final/summary-data.json` 抽取项目知识,生成并维护本地 `.harness/knowledge/` 索引。