@hunter-harness/workflow-harness 0.2.5 → 0.2.7

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 (270) hide show
  1. package/harness/bundles/general/claude-code/.harness-build.json +1 -1
  2. package/harness/bundles/general/claude-code/contracts/workflow-policy.json +169 -0
  3. package/harness/bundles/general/claude-code/harness-archive/SKILL.md +7 -2
  4. package/harness/bundles/general/claude-code/harness-archive/reference.md +1 -1
  5. package/harness/bundles/general/claude-code/harness-archive/templates/render-summary.mjs +78 -155
  6. package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +4 -2
  7. package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +5 -3
  8. package/harness/bundles/general/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +109 -7
  9. package/harness/bundles/general/claude-code/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +2 -0
  10. package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +6 -2
  11. package/harness/bundles/general/claude-code/harness-plan/SKILL.md +8 -3
  12. package/harness/bundles/general/claude-code/harness-review/SKILL.md +8 -3
  13. package/harness/bundles/general/claude-code/harness-run/SKILL.md +13 -7
  14. package/harness/bundles/general/claude-code/harness-run/checklist.md +2 -2
  15. package/harness/bundles/general/claude-code/harness-run/reference.md +6 -3
  16. package/harness/bundles/general/claude-code/harness-submit/SKILL.md +11 -6
  17. package/harness/bundles/general/claude-code/harness-sync/SKILL.md +5 -3
  18. package/harness/bundles/general/claude-code/harness-test/SKILL.md +12 -3
  19. package/harness/bundles/general/claude-code/harness-test/reference.md +1 -1
  20. package/harness/bundles/general/claude-code/protocols/ledger-protocol.md +1 -1
  21. package/harness/bundles/general/claude-code/protocols/report-pipeline-protocol.md +11 -4
  22. package/harness/bundles/general/claude-code/scripts/harness_apidoc.py +53 -0
  23. package/harness/bundles/general/claude-code/scripts/harness_archive.py +231 -48
  24. package/harness/bundles/general/claude-code/scripts/harness_change.py +854 -0
  25. package/harness/bundles/general/claude-code/scripts/harness_events.py +134 -40
  26. package/harness/bundles/general/claude-code/scripts/harness_gate.py +1038 -0
  27. package/harness/bundles/general/claude-code/scripts/harness_preflight.py +46 -3
  28. package/harness/bundles/general/claude-code/scripts/harness_profile.py +2 -2
  29. package/harness/bundles/general/claude-code/scripts/harness_service.py +34 -1
  30. package/harness/bundles/general/claude-code/scripts/harness_state.py +148 -0
  31. package/harness/bundles/general/claude-code/scripts/harness_test_guard.py +210 -14
  32. package/harness/bundles/general/claude-code/scripts/harness_workflow_policy.py +160 -0
  33. package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
  34. package/harness/bundles/general/codebuddy/contracts/workflow-policy.json +169 -0
  35. package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +7 -2
  36. package/harness/bundles/general/codebuddy/harness-archive/reference.md +1 -1
  37. package/harness/bundles/general/codebuddy/harness-archive/templates/render-summary.mjs +78 -155
  38. package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +4 -2
  39. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +5 -3
  40. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +109 -7
  41. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +2 -0
  42. package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +6 -2
  43. package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +8 -3
  44. package/harness/bundles/general/codebuddy/harness-review/SKILL.md +8 -3
  45. package/harness/bundles/general/codebuddy/harness-run/SKILL.md +13 -7
  46. package/harness/bundles/general/codebuddy/harness-run/checklist.md +2 -2
  47. package/harness/bundles/general/codebuddy/harness-run/reference.md +6 -3
  48. package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +11 -6
  49. package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +5 -3
  50. package/harness/bundles/general/codebuddy/harness-test/SKILL.md +12 -3
  51. package/harness/bundles/general/codebuddy/harness-test/reference.md +1 -1
  52. package/harness/bundles/general/codebuddy/protocols/ledger-protocol.md +1 -1
  53. package/harness/bundles/general/codebuddy/protocols/report-pipeline-protocol.md +11 -4
  54. package/harness/bundles/general/codebuddy/scripts/harness_apidoc.py +53 -0
  55. package/harness/bundles/general/codebuddy/scripts/harness_archive.py +231 -48
  56. package/harness/bundles/general/codebuddy/scripts/harness_change.py +854 -0
  57. package/harness/bundles/general/codebuddy/scripts/harness_events.py +134 -40
  58. package/harness/bundles/general/codebuddy/scripts/harness_gate.py +1038 -0
  59. package/harness/bundles/general/codebuddy/scripts/harness_preflight.py +46 -3
  60. package/harness/bundles/general/codebuddy/scripts/harness_profile.py +2 -2
  61. package/harness/bundles/general/codebuddy/scripts/harness_service.py +34 -1
  62. package/harness/bundles/general/codebuddy/scripts/harness_state.py +148 -0
  63. package/harness/bundles/general/codebuddy/scripts/harness_test_guard.py +210 -14
  64. package/harness/bundles/general/codebuddy/scripts/harness_workflow_policy.py +160 -0
  65. package/harness/bundles/general/codex/.harness-build.json +1 -1
  66. package/harness/bundles/general/codex/contracts/workflow-policy.json +169 -0
  67. package/harness/bundles/general/codex/harness-archive/SKILL.md +7 -2
  68. package/harness/bundles/general/codex/harness-archive/reference.md +1 -1
  69. package/harness/bundles/general/codex/harness-archive/templates/render-summary.mjs +78 -155
  70. package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +4 -2
  71. package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +5 -3
  72. package/harness/bundles/general/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +109 -7
  73. package/harness/bundles/general/codex/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +2 -0
  74. package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +6 -2
  75. package/harness/bundles/general/codex/harness-plan/SKILL.md +8 -3
  76. package/harness/bundles/general/codex/harness-review/SKILL.md +7 -2
  77. package/harness/bundles/general/codex/harness-run/SKILL.md +13 -7
  78. package/harness/bundles/general/codex/harness-run/checklist.md +2 -2
  79. package/harness/bundles/general/codex/harness-run/reference.md +6 -3
  80. package/harness/bundles/general/codex/harness-submit/SKILL.md +11 -6
  81. package/harness/bundles/general/codex/harness-sync/SKILL.md +5 -3
  82. package/harness/bundles/general/codex/harness-test/SKILL.md +12 -3
  83. package/harness/bundles/general/codex/harness-test/reference.md +1 -1
  84. package/harness/bundles/general/codex/protocols/ledger-protocol.md +1 -1
  85. package/harness/bundles/general/codex/protocols/report-pipeline-protocol.md +11 -4
  86. package/harness/bundles/general/codex/scripts/harness_apidoc.py +53 -0
  87. package/harness/bundles/general/codex/scripts/harness_archive.py +231 -48
  88. package/harness/bundles/general/codex/scripts/harness_change.py +854 -0
  89. package/harness/bundles/general/codex/scripts/harness_events.py +134 -40
  90. package/harness/bundles/general/codex/scripts/harness_gate.py +1038 -0
  91. package/harness/bundles/general/codex/scripts/harness_preflight.py +46 -3
  92. package/harness/bundles/general/codex/scripts/harness_profile.py +2 -2
  93. package/harness/bundles/general/codex/scripts/harness_service.py +34 -1
  94. package/harness/bundles/general/codex/scripts/harness_state.py +148 -0
  95. package/harness/bundles/general/codex/scripts/harness_test_guard.py +210 -14
  96. package/harness/bundles/general/codex/scripts/harness_workflow_policy.py +160 -0
  97. package/harness/bundles/general/cursor/.harness-build.json +1 -1
  98. package/harness/bundles/general/cursor/contracts/workflow-policy.json +169 -0
  99. package/harness/bundles/general/cursor/harness-archive/SKILL.md +7 -2
  100. package/harness/bundles/general/cursor/harness-archive/reference.md +1 -1
  101. package/harness/bundles/general/cursor/harness-archive/templates/render-summary.mjs +78 -155
  102. package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +4 -2
  103. package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +5 -3
  104. package/harness/bundles/general/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +109 -7
  105. package/harness/bundles/general/cursor/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +2 -0
  106. package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +6 -2
  107. package/harness/bundles/general/cursor/harness-plan/SKILL.md +8 -3
  108. package/harness/bundles/general/cursor/harness-review/SKILL.md +7 -2
  109. package/harness/bundles/general/cursor/harness-run/SKILL.md +13 -7
  110. package/harness/bundles/general/cursor/harness-run/checklist.md +2 -2
  111. package/harness/bundles/general/cursor/harness-run/reference.md +6 -3
  112. package/harness/bundles/general/cursor/harness-submit/SKILL.md +11 -6
  113. package/harness/bundles/general/cursor/harness-sync/SKILL.md +5 -3
  114. package/harness/bundles/general/cursor/harness-test/SKILL.md +12 -3
  115. package/harness/bundles/general/cursor/harness-test/reference.md +1 -1
  116. package/harness/bundles/general/cursor/protocols/ledger-protocol.md +1 -1
  117. package/harness/bundles/general/cursor/protocols/report-pipeline-protocol.md +11 -4
  118. package/harness/bundles/general/cursor/scripts/harness_apidoc.py +53 -0
  119. package/harness/bundles/general/cursor/scripts/harness_archive.py +231 -48
  120. package/harness/bundles/general/cursor/scripts/harness_change.py +854 -0
  121. package/harness/bundles/general/cursor/scripts/harness_events.py +134 -40
  122. package/harness/bundles/general/cursor/scripts/harness_gate.py +1038 -0
  123. package/harness/bundles/general/cursor/scripts/harness_preflight.py +46 -3
  124. package/harness/bundles/general/cursor/scripts/harness_profile.py +2 -2
  125. package/harness/bundles/general/cursor/scripts/harness_service.py +34 -1
  126. package/harness/bundles/general/cursor/scripts/harness_state.py +148 -0
  127. package/harness/bundles/general/cursor/scripts/harness_test_guard.py +210 -14
  128. package/harness/bundles/general/cursor/scripts/harness_workflow_policy.py +160 -0
  129. package/harness/bundles/java/claude-code/.harness-build.json +1 -1
  130. package/harness/bundles/java/claude-code/contracts/workflow-policy.json +169 -0
  131. package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +11 -2
  132. package/harness/bundles/java/claude-code/harness-archive/SKILL.md +7 -2
  133. package/harness/bundles/java/claude-code/harness-archive/reference.md +1 -1
  134. package/harness/bundles/java/claude-code/harness-archive/templates/render-summary.mjs +78 -155
  135. package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +4 -2
  136. package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +5 -3
  137. package/harness/bundles/java/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +109 -7
  138. package/harness/bundles/java/claude-code/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +2 -0
  139. package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +6 -2
  140. package/harness/bundles/java/claude-code/harness-package/SKILL.md +10 -4
  141. package/harness/bundles/java/claude-code/harness-plan/SKILL.md +8 -3
  142. package/harness/bundles/java/claude-code/harness-review/SKILL.md +8 -3
  143. package/harness/bundles/java/claude-code/harness-run/SKILL.md +13 -7
  144. package/harness/bundles/java/claude-code/harness-run/reference.md +1 -1
  145. package/harness/bundles/java/claude-code/harness-submit/SKILL.md +11 -6
  146. package/harness/bundles/java/claude-code/harness-sync/SKILL.md +5 -3
  147. package/harness/bundles/java/claude-code/harness-test/SKILL.md +12 -3
  148. package/harness/bundles/java/claude-code/harness-test/reference.md +1 -1
  149. package/harness/bundles/java/claude-code/protocols/ledger-protocol.md +1 -1
  150. package/harness/bundles/java/claude-code/protocols/report-pipeline-protocol.md +11 -4
  151. package/harness/bundles/java/claude-code/scripts/harness_apidoc.py +53 -0
  152. package/harness/bundles/java/claude-code/scripts/harness_archive.py +231 -48
  153. package/harness/bundles/java/claude-code/scripts/harness_change.py +854 -0
  154. package/harness/bundles/java/claude-code/scripts/harness_events.py +134 -40
  155. package/harness/bundles/java/claude-code/scripts/harness_gate.py +1038 -0
  156. package/harness/bundles/java/claude-code/scripts/harness_preflight.py +46 -3
  157. package/harness/bundles/java/claude-code/scripts/harness_profile.py +2 -2
  158. package/harness/bundles/java/claude-code/scripts/harness_service.py +34 -1
  159. package/harness/bundles/java/claude-code/scripts/harness_state.py +148 -0
  160. package/harness/bundles/java/claude-code/scripts/harness_test_guard.py +210 -14
  161. package/harness/bundles/java/claude-code/scripts/harness_workflow_policy.py +160 -0
  162. package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
  163. package/harness/bundles/java/codebuddy/contracts/workflow-policy.json +169 -0
  164. package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +11 -2
  165. package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +7 -2
  166. package/harness/bundles/java/codebuddy/harness-archive/reference.md +1 -1
  167. package/harness/bundles/java/codebuddy/harness-archive/templates/render-summary.mjs +78 -155
  168. package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +4 -2
  169. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +5 -3
  170. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +109 -7
  171. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +2 -0
  172. package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +6 -2
  173. package/harness/bundles/java/codebuddy/harness-package/SKILL.md +10 -4
  174. package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +8 -3
  175. package/harness/bundles/java/codebuddy/harness-review/SKILL.md +8 -3
  176. package/harness/bundles/java/codebuddy/harness-run/SKILL.md +13 -7
  177. package/harness/bundles/java/codebuddy/harness-run/reference.md +1 -1
  178. package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +11 -6
  179. package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +5 -3
  180. package/harness/bundles/java/codebuddy/harness-test/SKILL.md +12 -3
  181. package/harness/bundles/java/codebuddy/harness-test/reference.md +1 -1
  182. package/harness/bundles/java/codebuddy/protocols/ledger-protocol.md +1 -1
  183. package/harness/bundles/java/codebuddy/protocols/report-pipeline-protocol.md +11 -4
  184. package/harness/bundles/java/codebuddy/scripts/harness_apidoc.py +53 -0
  185. package/harness/bundles/java/codebuddy/scripts/harness_archive.py +231 -48
  186. package/harness/bundles/java/codebuddy/scripts/harness_change.py +854 -0
  187. package/harness/bundles/java/codebuddy/scripts/harness_events.py +134 -40
  188. package/harness/bundles/java/codebuddy/scripts/harness_gate.py +1038 -0
  189. package/harness/bundles/java/codebuddy/scripts/harness_preflight.py +46 -3
  190. package/harness/bundles/java/codebuddy/scripts/harness_profile.py +2 -2
  191. package/harness/bundles/java/codebuddy/scripts/harness_service.py +34 -1
  192. package/harness/bundles/java/codebuddy/scripts/harness_state.py +148 -0
  193. package/harness/bundles/java/codebuddy/scripts/harness_test_guard.py +210 -14
  194. package/harness/bundles/java/codebuddy/scripts/harness_workflow_policy.py +160 -0
  195. package/harness/bundles/java/codex/.harness-build.json +1 -1
  196. package/harness/bundles/java/codex/contracts/workflow-policy.json +169 -0
  197. package/harness/bundles/java/codex/harness-apidoc/SKILL.md +11 -2
  198. package/harness/bundles/java/codex/harness-archive/SKILL.md +7 -2
  199. package/harness/bundles/java/codex/harness-archive/reference.md +1 -1
  200. package/harness/bundles/java/codex/harness-archive/templates/render-summary.mjs +78 -155
  201. package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +4 -2
  202. package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +5 -3
  203. package/harness/bundles/java/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +109 -7
  204. package/harness/bundles/java/codex/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +2 -0
  205. package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +6 -2
  206. package/harness/bundles/java/codex/harness-package/SKILL.md +10 -4
  207. package/harness/bundles/java/codex/harness-plan/SKILL.md +8 -3
  208. package/harness/bundles/java/codex/harness-review/SKILL.md +7 -2
  209. package/harness/bundles/java/codex/harness-run/SKILL.md +13 -7
  210. package/harness/bundles/java/codex/harness-run/reference.md +1 -1
  211. package/harness/bundles/java/codex/harness-submit/SKILL.md +11 -6
  212. package/harness/bundles/java/codex/harness-sync/SKILL.md +5 -3
  213. package/harness/bundles/java/codex/harness-test/SKILL.md +12 -3
  214. package/harness/bundles/java/codex/harness-test/reference.md +1 -1
  215. package/harness/bundles/java/codex/protocols/ledger-protocol.md +1 -1
  216. package/harness/bundles/java/codex/protocols/report-pipeline-protocol.md +11 -4
  217. package/harness/bundles/java/codex/scripts/harness_apidoc.py +53 -0
  218. package/harness/bundles/java/codex/scripts/harness_archive.py +231 -48
  219. package/harness/bundles/java/codex/scripts/harness_change.py +854 -0
  220. package/harness/bundles/java/codex/scripts/harness_events.py +134 -40
  221. package/harness/bundles/java/codex/scripts/harness_gate.py +1038 -0
  222. package/harness/bundles/java/codex/scripts/harness_preflight.py +46 -3
  223. package/harness/bundles/java/codex/scripts/harness_profile.py +2 -2
  224. package/harness/bundles/java/codex/scripts/harness_service.py +34 -1
  225. package/harness/bundles/java/codex/scripts/harness_state.py +148 -0
  226. package/harness/bundles/java/codex/scripts/harness_test_guard.py +210 -14
  227. package/harness/bundles/java/codex/scripts/harness_workflow_policy.py +160 -0
  228. package/harness/bundles/java/cursor/.harness-build.json +1 -1
  229. package/harness/bundles/java/cursor/contracts/workflow-policy.json +169 -0
  230. package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +11 -2
  231. package/harness/bundles/java/cursor/harness-archive/SKILL.md +7 -2
  232. package/harness/bundles/java/cursor/harness-archive/reference.md +1 -1
  233. package/harness/bundles/java/cursor/harness-archive/templates/render-summary.mjs +78 -155
  234. package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +4 -2
  235. package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +5 -3
  236. package/harness/bundles/java/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +109 -7
  237. package/harness/bundles/java/cursor/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +2 -0
  238. package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +6 -2
  239. package/harness/bundles/java/cursor/harness-package/SKILL.md +10 -4
  240. package/harness/bundles/java/cursor/harness-plan/SKILL.md +8 -3
  241. package/harness/bundles/java/cursor/harness-review/SKILL.md +7 -2
  242. package/harness/bundles/java/cursor/harness-run/SKILL.md +13 -7
  243. package/harness/bundles/java/cursor/harness-run/reference.md +1 -1
  244. package/harness/bundles/java/cursor/harness-submit/SKILL.md +11 -6
  245. package/harness/bundles/java/cursor/harness-sync/SKILL.md +5 -3
  246. package/harness/bundles/java/cursor/harness-test/SKILL.md +12 -3
  247. package/harness/bundles/java/cursor/harness-test/reference.md +1 -1
  248. package/harness/bundles/java/cursor/protocols/ledger-protocol.md +1 -1
  249. package/harness/bundles/java/cursor/protocols/report-pipeline-protocol.md +11 -4
  250. package/harness/bundles/java/cursor/scripts/harness_apidoc.py +53 -0
  251. package/harness/bundles/java/cursor/scripts/harness_archive.py +231 -48
  252. package/harness/bundles/java/cursor/scripts/harness_change.py +854 -0
  253. package/harness/bundles/java/cursor/scripts/harness_events.py +134 -40
  254. package/harness/bundles/java/cursor/scripts/harness_gate.py +1038 -0
  255. package/harness/bundles/java/cursor/scripts/harness_preflight.py +46 -3
  256. package/harness/bundles/java/cursor/scripts/harness_profile.py +2 -2
  257. package/harness/bundles/java/cursor/scripts/harness_service.py +34 -1
  258. package/harness/bundles/java/cursor/scripts/harness_state.py +148 -0
  259. package/harness/bundles/java/cursor/scripts/harness_test_guard.py +210 -14
  260. package/harness/bundles/java/cursor/scripts/harness_workflow_policy.py +160 -0
  261. package/harness/manifests/general/claude-code.json +48 -28
  262. package/harness/manifests/general/codebuddy.json +48 -28
  263. package/harness/manifests/general/codex.json +48 -28
  264. package/harness/manifests/general/cursor.json +48 -28
  265. package/harness/manifests/java/claude-code.json +49 -29
  266. package/harness/manifests/java/codebuddy.json +49 -29
  267. package/harness/manifests/java/codex.json +49 -29
  268. package/harness/manifests/java/cursor.json +49 -29
  269. package/hunter-workflow-family.json +2 -2
  270. package/package.json +1 -1
@@ -22,7 +22,7 @@ disallowed-tools:
22
22
  - Bash(node *)
23
23
  - Bash(codegraph *)
24
24
  ---
25
- <!-- generated by harness_deploy.py; core=5cadfa54663ecdbf; overlay=none; agent=claude-code; do not edit -->
25
+ <!-- generated by harness_deploy.py; core=89e664933ed69572; overlay=none; agent=claude-code; do not edit -->
26
26
  # harness-test — 测试执行
27
27
 
28
28
  ## Purpose
@@ -56,14 +56,21 @@ disallowed-tools:
56
56
  6. **implementation-detail** — 自适应执行参考;legacy 缺失 🟡WARN,不阻断
57
57
  7. **test-scenarios** — 测试真相源:`plans/<change>-test-scenarios.md`
58
58
  8. **禁止读取 `docs/superpowers/` 作为正式输入** — 旧草稿仅人工线索
59
+ 9. **每个 skill 边界只刷新一次状态快照** — 先运行 `python <skills-root>/scripts/harness_state.py capture --project . --change-dir ".harness/changes/<change-name>" --base <baseCommit> --json`;`changedSegments=[]` 时复用已有 profile/rules/map/knowledge/change/code 指纹,不再重复跑全量 `harness-sync`、知识重建或代码库扫描。只有对应 segment 变化或缺失时才定向刷新;快照不能替代源码读取和验证门禁。
59
60
 
60
61
  状态目录分层:新路径优先,旧路径兼容 → [[../protocols/state-layout-protocol.md|state-layout-protocol]]
61
62
 
62
63
  ## Workflow
63
64
 
65
+ 测试跟踪的确定性边界为 `harness_test_guard.py begin` 和 `harness_test_guard.py close`;任一失败都必须传递给 `harness_gate.py close`,不得以自然语言覆盖失败状态。
66
+
67
+ 并行服务测试先运行 `harness_change.py lease-port --change <id> --run-id <run-id> --range <start-end> --json`,再把返回端口传给 `harness_service.py ensure --leased-port <port> --lease-owner <run-id>`。`serviceStart` 的 command/health/overlay 可用 `{leasedPort}` 占位符;用户自启进程仍只进入 Service Gate,禁止 kill。测试清理的 `finally` 中运行 `harness_change.py release-port --change <id> --run-id <run-id> --json`,避免租约池耗尽。
68
+
64
69
  ### Phase 0:环境准备(主会话执行,需要交互确认)
65
70
 
66
- 执行 各项强制环境检查 + **命令执行模式 preflight (0.1)**;只有首选执行器不可用时,才执行 fallback 执行器探测。
71
+ `harness_change.py resolve [--change] --json` 解析 change-id;再 **`harness_gate.py begin --phase test --change <id>`**(禁止手工 phase.start / 手写 ledger)。执行各项强制环境检查 + **命令执行模式 preflight (0.1)**;只有首选执行器不可用时,才执行 fallback 执行器探测。
72
+
73
+ 验证写入**仅**允许 `harness_ledger.py record` / `can-reuse`;禁止 Write/Edit `verification-ledger.json`。测试跟踪:`harness_test_guard.py begin` → 执行 → `close`(可选 `mark` stale-test-repair)。阶段结束必须 `harness_gate.py close --phase test --status ...`。
67
74
 
68
75
  - **Read `checklist.md`** — 各项检查详情 + 0.1 preflight + Playwright 探测 + 避坑规则指引
69
76
  - **失败处理**:任一项检查失败 → 终止流程并报告原因,用户确认修复后才能继续
@@ -181,7 +188,7 @@ python <skills-root>/scripts/harness_test_guard.py record --project . --change-d
181
188
 
182
189
  ## 执行日志
183
190
 
184
- `events.ndjson` 为唯一事实源(schema_version 2,`note` 承载人类可读摘要);`logs/execution-log.md` 由 `harness_events.py` 渲染,**禁止用 Write/Edit 直接维护**。直接修改的内容会在 `phase.end` 或 finalize 时被完整重建覆盖,属于数据丢失;需要保留的详情必须进入事件 `note`。结构 → [[../protocols/report-pipeline-protocol.md|report-pipeline-protocol]]
191
+ `events.ndjson` 为唯一事实源(schema_version 3,兼容读取 v1/v2;`note` 承载人类可读摘要);`logs/execution-log.md` 由 `harness_events.py` 渲染,**禁止用 Write/Edit 直接维护**。直接修改的内容会在 `phase.end` 或 finalize 时被完整重建覆盖,属于数据丢失;需要保留的详情必须进入事件 `note`。结构 → [[../protocols/report-pipeline-protocol.md|report-pipeline-protocol]]
185
192
 
186
193
  ```powershell
187
194
  python <skills-root>/scripts/harness_events.py append --change-dir ".harness/changes/<change-name>" --phase <phase> --type phase.start --note "<触发指令>"
@@ -190,3 +197,5 @@ python <skills-root>/scripts/harness_events.py append --change-dir ".harness/cha
190
197
  > **脚本接线**:`harness_events.py append`;`harness_archive.py finalize`;`harness_preflight.py check`;`harness_ledger.py can-reuse`;`harness_service.py ensure/stop`(须 `--files`/`serviceStart.inputFiles`)。JSON 输出按 D13 护栏解读。
191
198
 
192
199
  > **Task 4 §6.1 写入契约**:普通 `append` = 加锁 -> 追加一行 -> fsync -> 解锁,**不 load 历史、不渲染**(O(1),跨进程锁 `events.ndjson.lock`,UUID 用完整 `uuid4().hex` 无需去重扫描)。仅 `--type phase.end` append 在追加成功后渲染一次 `execution-log.md`;显式 `harness_events.py render` 随时从完整 events 重建;`harness_archive.py finalize` 在 collect 前强制 render 一次。高频 command append 期间 log 可能滞后,phase 边界保持最新。
200
+
201
+ 每次阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`;重试同一阶段时增加 `--attempt <n>`,不得覆盖或伪装成一次执行。跨工具继续执行时写 `--executor-tool <codex|claude-code|codebuddy|cursor>`,并在接棒事件写 `--handoff-from-tool` / `--handoff-reason`;也可由 `HUNTER_HARNESS_TOOL/AGENT/MODEL/RUN_ID` 环境变量统一注入。
@@ -786,6 +786,6 @@ python <skills-root>/scripts/harness_ledger.py diff-hash --repo . --base <baseCo
786
786
 
787
787
  ## 执行日志记录
788
788
 
789
- `/harness-test` 只向 `events.ndjson` 追加事件(schema_version 2);`logs/execution-log.md` 由 `harness_events.py append` 自动渲染。Phase 0 之前 append `phase.start`;各阶段写入 `command` / `verification` / `decision` / `issue` / `artifact`,人类可读摘要放 `note`。事件类型与脚本用法见 [[../protocols/report-pipeline-protocol.md|report-pipeline-protocol]] 与 SKILL.md `## 执行日志`。
789
+ `/harness-test` 只向 `events.ndjson` 追加事件(schema_version 3,兼容读取 v1/v2);`logs/execution-log.md` 由 `harness_events.py append` 自动渲染。Phase 0 之前 append `phase.start`;各阶段写入 `command` / `verification` / `decision` / `issue` / `artifact`,人类可读摘要放 `note`。事件类型与脚本用法见 [[../protocols/report-pipeline-protocol.md|report-pipeline-protocol]] 与 SKILL.md `## 执行日志`。
790
790
 
791
791
  关键 `note` / 事件须覆盖:0.1 命令执行模式、fallback 执行器、serviceState、凭证策略、单元测试复用、批量执行器、verification-ledger 写入、关门检查、API 状态(不得把 PARTIAL 写成 NOT_RUN)。
@@ -86,7 +86,7 @@ verification-ledger 把每次验证(compile / unit test / api test / package
86
86
  },
87
87
  "package": {
88
88
  "status": "OK",
89
- "command": "<打包命令(按技术栈:Java=mvn package -pl <module> -am -DskipTests;前端=npm run build;Python=python -m build)>",
89
+ "command": "<打包命令(按技术栈:Java=mvn clean package -pl <module> -am -DskipTests;前端=npm run build;Python=python -m build)>",
90
90
  "baseCommit": "<final pushed hash>",
91
91
  "deployArtifact": "<构建产物路径(Java=<module>/target/<artifact>.jar;前端=<module>/dist/;Python=<module>/dist/)>",
92
92
  "sha256": "<artifact sha256>",
@@ -43,11 +43,11 @@ python <skills-root>/scripts/harness_events.py append --change-dir ".harness/cha
43
43
 
44
44
  `append` 写入契约(Task 4 §6.1):普通 append = 加锁 -> 追加一行 -> fsync -> 解锁,**不 load 历史、不渲染**(O(1),跨进程锁 `events.ndjson.lock`,UUID 用完整 `uuid4().hex`);仅 `--type phase.end` append 成功后渲染一次 `logs/execution-log.md`;显式 `harness_events.py render` 随时从完整 events 重建;`harness_archive.py finalize` 在 collect 前强制 render 一次。人类可读摘要(触发指令、降级原因、阶段结论)写入事件的 `note` 字段;阶段开始/结束、命令、验证、artifact、问题、决策各写对应 `type` 事件。旧 archive 缺少 events 可回放兼容;新变更不得以 execution-log 已存在为理由跳过 events。
45
45
 
46
- 基础事件结构(schema_version 2):
46
+ 基础事件结构(schema_version 3;兼容读取 v1/v2):
47
47
 
48
48
  ```json
49
49
  {
50
- "schema_version": 2,
50
+ "schema_version": 3,
51
51
  "id": "evt-...",
52
52
  "timestamp": "2026-07-02T00:00:00.000Z",
53
53
  "phase": "run",
@@ -55,6 +55,13 @@ python <skills-root>/scripts/harness_events.py append --change-dir ".harness/cha
55
55
  "command": "npm test",
56
56
  "exit_code": 0,
57
57
  "duration_ms": 42100,
58
+ "run_id": "run-...",
59
+ "attempt": 1,
60
+ "executor_tool": "codex",
61
+ "executor_agent": "main",
62
+ "executor_model": "gpt-5",
63
+ "handoff_from_tool": "claude-code",
64
+ "handoff_reason": "continue implementation",
58
65
  "note": "可选:触发指令、降级原因、阶段摘要"
59
66
  }
60
67
  ```
@@ -64,7 +71,7 @@ python <skills-root>/scripts/harness_events.py append --change-dir ".harness/cha
64
71
  | type | 必填/常用字段 | 用途 |
65
72
  |---|---|---|
66
73
  | `phase.start` | phase, timestamp | 阶段开始 |
67
- | `phase.end` | phase, timestamp | 阶段结束,计算耗时 |
74
+ | `phase.end` | phase, timestamp, status | 阶段结束并记录 `OK/WARN/FAIL/BLOCKED`;计算当前 attempt 耗时 |
68
75
  | `command` | command, exit_code, duration_ms | 命令事实 |
69
76
  | `verification` | name, status, command | 验证事实 |
70
77
  | `artifact` | path, kind | 报告、包、manifest 等产物 |
@@ -79,7 +86,7 @@ summary-data 必须保留原 archive final report 维度,并增加事件层摘
79
86
  {
80
87
  "schemaVersion": "2.2",
81
88
  "reportPipeline": {
82
- "schema_version": 2,
89
+ "schema_version": 1,
83
90
  "generated_at": "2026-07-02T00:00:00.000Z",
84
91
  "event_count": 0,
85
92
  "sources": ["events.ndjson", "evidence/verification-ledger.json"],
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env python3
2
+ """Small deterministic helpers for compact API contract reports."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import datetime as dt
8
+ import json
9
+ import re
10
+ import sys
11
+
12
+
13
+ COMMIT_ONLY = re.compile(r"^[0-9a-f]{7,64}$", re.IGNORECASE)
14
+ INVALID_FILENAME = re.compile(r"[<>:\"/\\|?*\x00-\x1f]+")
15
+ SEPARATORS = re.compile(r"[\s_-]+")
16
+
17
+
18
+ def build_filename(description: str, *, date: str | None = None) -> str:
19
+ raw = str(description or "").strip()
20
+ if not raw or COMMIT_ONLY.fullmatch(raw):
21
+ raise ValueError("a human-readable API change description is required")
22
+ slug = INVALID_FILENAME.sub("-", raw)
23
+ slug = SEPARATORS.sub("-", slug).strip(" .-")
24
+ if not slug:
25
+ raise ValueError("description does not contain filename-safe text")
26
+ day = date or dt.date.today().isoformat()
27
+ slug = slug[:86].rstrip(" .-")
28
+ return f"{day}-{slug}.md"
29
+
30
+
31
+ def main(argv: list[str] | None = None) -> int:
32
+ parser = argparse.ArgumentParser(description=__doc__)
33
+ sub = parser.add_subparsers(dest="command", required=True)
34
+ filename = sub.add_parser("filename", help="build a safe dated report filename")
35
+ filename.add_argument("--description", required=True)
36
+ filename.add_argument("--date", default=None)
37
+ filename.add_argument("--json", action="store_true")
38
+ args = parser.parse_args(argv)
39
+ try:
40
+ value = build_filename(args.description, date=args.date)
41
+ except ValueError as exc:
42
+ sys.stderr.write(json.dumps({"ok": False, "error": str(exc)}) + "\n")
43
+ return 1
44
+ payload = {"ok": True, "filename": value}
45
+ if args.json:
46
+ sys.stdout.write(json.dumps(payload, ensure_ascii=False) + "\n")
47
+ else:
48
+ sys.stdout.write(value + "\n")
49
+ return 0
50
+
51
+
52
+ if __name__ == "__main__":
53
+ raise SystemExit(main())
@@ -359,6 +359,28 @@ def find_review_reports(change_dir: Path) -> list[Path]:
359
359
  return found
360
360
 
361
361
 
362
+ def review_phase_completed(events: list[dict[str, Any]]) -> bool:
363
+ """True only when structured events record a review phase.end (UT-042)."""
364
+ for event in events:
365
+ if event.get("type") != "phase.end":
366
+ continue
367
+ if str(event.get("phase") or "").lower() == "review":
368
+ return True
369
+ return False
370
+
371
+
372
+ def review_evidence_present(
373
+ change_dir: Path,
374
+ events: list[dict[str, Any]] | None = None,
375
+ ) -> bool:
376
+ """Review ran only when report files or review phase.end events exist."""
377
+ if find_review_reports(change_dir):
378
+ return True
379
+ if events is None:
380
+ events = he.load_events(change_dir / "events.ndjson")
381
+ return review_phase_completed(events)
382
+
383
+
362
384
  def git_run(project: Path, *args: str) -> tuple[int, str, str]:
363
385
  try:
364
386
  proc = subprocess.run(
@@ -531,10 +553,9 @@ def check_status(change_dir: Path) -> dict[str, Any]:
531
553
  # --- test / review reports ---
532
554
  test_reports = find_test_reports(change_dir)
533
555
  review_reports = find_review_reports(change_dir)
534
- log_text = load_execution_log(change_dir)
535
- has_review_section = bool(
536
- re.search(r"harness-review|##\s*review", log_text, re.IGNORECASE)
537
- )
556
+ events_path = change_dir / "events.ndjson"
557
+ events = he.load_events(events_path) if events_path.is_file() else []
558
+ review_ran = review_evidence_present(change_dir, events)
538
559
 
539
560
  checks["test_reports"] = [str(p.relative_to(change_dir)) for p in test_reports]
540
561
  checks["review_reports"] = [str(p.relative_to(change_dir)) for p in review_reports]
@@ -555,13 +576,13 @@ def check_status(change_dir: Path) -> dict[str, Any]:
555
576
 
556
577
  if review_reports:
557
578
  checks["review_report_status"] = "present"
558
- elif has_review_section:
579
+ elif review_phase_completed(events):
559
580
  checks["review_report_status"] = "ran-but-not-persisted"
560
581
  warnings.append(
561
582
  {
562
583
  "code": "review-not-persisted",
563
584
  "message": (
564
- "execution-log has review section but no review-report file; "
585
+ "review phase.end recorded but no review-report file; "
565
586
  "prefer persisting report before archive"
566
587
  ),
567
588
  }
@@ -622,13 +643,16 @@ def _ledger_unit_tests(ledger: dict[str, Any] | None) -> dict[str, Any]:
622
643
  status = str(unit.get("status") or "").upper()
623
644
  evidence = unit.get("evidence") or {}
624
645
  if isinstance(evidence, dict):
625
- run = evidence.get("run", unit.get("run", 0))
646
+ run = evidence.get(
647
+ "run",
648
+ evidence.get("testsRun", unit.get("run", unit.get("testsRun", 0))),
649
+ )
626
650
  failures = evidence.get("failures", unit.get("failures", 0))
627
651
  errors = evidence.get("errors", unit.get("errors", 0))
628
652
  skipped = evidence.get("skipped", unit.get("skipped", 0))
629
653
  pass_rate = evidence.get("passRate") or unit.get("passRate")
630
654
  else:
631
- run = unit.get("run", 0)
655
+ run = unit.get("run", unit.get("testsRun", 0))
632
656
  failures = unit.get("failures", 0)
633
657
  errors = unit.get("errors", 0)
634
658
  skipped = unit.get("skipped", 0)
@@ -886,16 +910,21 @@ def _durations_from_event_phases(event_summary: dict[str, Any]) -> dict[str, Any
886
910
  minutes = round((dur or 0) / 60000, 2) if dur is not None else 0
887
911
  if dur:
888
912
  total_ms += int(dur)
889
- stages.append(
890
- {
891
- "stage": str(name),
892
- "skill": f"harness-{name}",
893
- "startedAt": info.get("started_at") or NOT_AVAILABLE,
894
- "endedAt": info.get("ended_at") or NOT_AVAILABLE,
895
- "minutes": minutes,
896
- "result": "OK",
897
- }
898
- )
913
+ attempts = info.get("attempts") if isinstance(info.get("attempts"), list) else []
914
+ result = str(info.get("status") or "UNKNOWN").upper()
915
+ if result in {"PASS", "PASSED", "SUCCESS"}:
916
+ result = "OK"
917
+ elif result in {"FAILED", "ERROR"}:
918
+ result = "FAIL"
919
+ stages.append({
920
+ "stage": str(name),
921
+ "skill": f"harness-{name}",
922
+ "startedAt": info.get("started_at") or NOT_AVAILABLE,
923
+ "endedAt": info.get("ended_at") or NOT_AVAILABLE,
924
+ "minutes": minutes,
925
+ "result": result,
926
+ "attempts": attempts,
927
+ })
899
928
  total_min = round(total_ms / 60000, 2)
900
929
  return {
901
930
  "totalLabel": f"约 {int(round(total_min))} 分",
@@ -917,11 +946,31 @@ def _stage_status_from_sources(
917
946
  "submit": "OK",
918
947
  "archive": "OK",
919
948
  }
949
+ for event in events:
950
+ if event.get("type") != "phase.end":
951
+ continue
952
+ phase = str(event.get("phase") or "").lower()
953
+ raw = str(event.get("status") or "").upper()
954
+ if phase not in status or not raw:
955
+ continue
956
+ if raw in {"PASS", "PASSED", "SUCCESS"}:
957
+ raw = "OK"
958
+ elif raw in {"FAILED", "ERROR"}:
959
+ raw = "FAIL"
960
+ elif raw in {"SKIP", "SKIPPED"}:
961
+ raw = "USER_SKIPPED"
962
+ status[phase] = raw
920
963
  # Issues in events can downgrade
921
964
  for e in events:
922
965
  if e.get("type") != "issue":
923
966
  continue
924
967
  sev = str(e.get("severity") or "").lower()
968
+ issue_text = " ".join(str(e.get(key) or "") for key in ("message", "note", "code")).lower()
969
+ if not sev:
970
+ if any(token in issue_text for token in ("fail", "error", "blocked", "失败", "阻塞")):
971
+ sev = "error"
972
+ elif any(token in issue_text for token in ("warn", "skip", "风险", "警告")):
973
+ sev = "warn"
925
974
  phase = str(e.get("phase") or "").lower()
926
975
  if phase in status and sev in {"error", "fail", "failed", "critical"}:
927
976
  status[phase] = "FAIL"
@@ -930,19 +979,22 @@ def _stage_status_from_sources(
930
979
 
931
980
  api = _ledger_api_tests(ledger)
932
981
  db = _ledger_db_compat(ledger)
933
- if api.get("status") == "USER_SKIPPED":
982
+ api_status = str(api.get("status") or "").upper()
983
+ if api_status in {"FAIL", "FAILED", "ERROR"} or int(api.get("failed") or 0) > 0:
984
+ status["test"] = "FAIL"
985
+ elif api_status in {"BLOCKED", "BLOCKED_BY_ENV", "BLOCKED_BY_DBA"}:
986
+ status["test"] = api_status
987
+ elif api_status == "USER_SKIPPED":
934
988
  status["test"] = "USER_SKIPPED"
935
989
  elif db == "BLOCKED_BY_DBA":
936
990
  status["test"] = "BLOCKED_BY_DBA"
937
991
  elif api.get("status") == "NOT_RUN" and not find_test_reports(change_dir):
938
992
  unit = _ledger_unit_tests(ledger)
939
993
  if unit.get("source") == "not-run" and unit.get("run", 0) == 0:
940
- status["test"] = "USER_SKIPPED"
994
+ status["test"] = "NOT_RUN"
941
995
 
942
- if not find_review_reports(change_dir):
943
- log_text = load_execution_log(change_dir)
944
- if not re.search(r"harness-review|##\s*review", log_text, re.IGNORECASE):
945
- status["review"] = "ADVISORY"
996
+ if not review_evidence_present(change_dir, events):
997
+ status["review"] = "ADVISORY"
946
998
 
947
999
  return status
948
1000
 
@@ -954,8 +1006,6 @@ def _compute_final_status(
954
1006
  api = verification.get("apiTests") or {}
955
1007
  db = str(verification.get("dbCompatibility") or "")
956
1008
  api_status = str(api.get("status") or "")
957
- if api_status == "USER_SKIPPED" or db == "BLOCKED_BY_DBA":
958
- return "CONDITIONAL_OK"
959
1009
  for v in stage_status.values():
960
1010
  if v == "FAIL":
961
1011
  return "FAIL"
@@ -964,8 +1014,14 @@ def _compute_final_status(
964
1014
  return "FAIL"
965
1015
  if int(api.get("failed") or 0) > 0:
966
1016
  return "FAIL"
1017
+ conditional = {
1018
+ "USER_SKIPPED", "BLOCKED", "BLOCKED_BY_ENV", "BLOCKED_BY_DBA",
1019
+ "NOT_RUN", "PARTIAL",
1020
+ }
1021
+ if api_status in conditional or db in conditional:
1022
+ return "CONDITIONAL_OK"
967
1023
  for v in stage_status.values():
968
- if v in {"WARN", "PARTIAL", "USER_SKIPPED", "BLOCKED_BY_DBA"}:
1024
+ if v in {"WARN", *conditional}:
969
1025
  return "WARN" if v == "WARN" else "CONDITIONAL_OK"
970
1026
  return "OK"
971
1027
 
@@ -984,6 +1040,8 @@ def _changed_files_from_git(
984
1040
  changed: list[dict[str, Any]] = []
985
1041
  if not base or not head:
986
1042
  return diff_stat, changed
1043
+ # The ledger bounds the complete task. Using only ``head^..head`` would
1044
+ # silently omit earlier checkpoint commits from the same change.
987
1045
  rng = f"{base}..{head}"
988
1046
  code, out, _ = git_run(project, "diff", "--numstat", rng)
989
1047
  if code != 0 or not out:
@@ -1018,7 +1076,11 @@ def _changed_files_from_git(
1018
1076
  return diff_stat, changed
1019
1077
 
1020
1078
 
1021
- def _review_summary(change_dir: Path, existing: dict[str, Any] | None) -> dict[str, Any]:
1079
+ def _review_summary(
1080
+ change_dir: Path,
1081
+ existing: dict[str, Any] | None,
1082
+ events: list[dict[str, Any]] | None = None,
1083
+ ) -> dict[str, Any]:
1022
1084
  base = {
1023
1085
  "status": "ADVISORY",
1024
1086
  "red": 0,
@@ -1033,13 +1095,102 @@ def _review_summary(change_dir: Path, existing: dict[str, Any] | None) -> dict[s
1033
1095
  merged = dict(base)
1034
1096
  merged.update(existing["reviewSummary"])
1035
1097
  return merged
1036
- if not find_review_reports(change_dir):
1037
- log_text = load_execution_log(change_dir)
1038
- if not re.search(r"harness-review|##\s*review", log_text, re.IGNORECASE):
1039
- base["status"] = "ADVISORY_NOT_RUN"
1098
+ if not review_evidence_present(change_dir, events):
1099
+ base["status"] = "ADVISORY_NOT_RUN"
1040
1100
  return base
1041
1101
 
1042
1102
 
1103
+ def _final_commit_from_sources(
1104
+ ledger: dict[str, Any] | None,
1105
+ events: list[dict[str, Any]],
1106
+ existing: dict[str, Any] | None,
1107
+ project: Path,
1108
+ ) -> str:
1109
+ if ledger:
1110
+ for key in (
1111
+ "mergeFinalHash", "finalCommit", "finalHash", "changeCommit", "headCommit",
1112
+ ):
1113
+ value = str(ledger.get(key) or "").strip()
1114
+ if value:
1115
+ return value
1116
+ commit_pattern = re.compile(r"\b[0-9a-f]{7,40}\b", re.IGNORECASE)
1117
+ for event in reversed(events):
1118
+ if str(event.get("phase") or "").lower() not in {"submit", "merge", "archive"}:
1119
+ continue
1120
+ text = " ".join(str(event.get(key) or "") for key in ("note", "message", "command"))
1121
+ match = commit_pattern.search(text)
1122
+ if match:
1123
+ return match.group(0)
1124
+ if existing and existing.get("finalCommit"):
1125
+ return str(existing["finalCommit"])
1126
+ code, head, _ = git_run(project, "rev-parse", "HEAD")
1127
+ return head if code == 0 and head else ""
1128
+
1129
+
1130
+ def _business_goal_from_sources(change_dir: Path, events: list[dict[str, Any]]) -> str:
1131
+ plans_root = change_dir / "plans"
1132
+ primary = sorted(plans_root.glob("*-plan.md"))
1133
+ secondary = [
1134
+ path for path in sorted(plans_root.glob("*.md"))
1135
+ if path not in primary
1136
+ and "implementation-detail" not in path.name
1137
+ and "test-scenarios" not in path.name
1138
+ ]
1139
+ for plan in [*primary, *secondary]:
1140
+ try:
1141
+ text = plan.read_text(encoding="utf-8-sig")
1142
+ except OSError:
1143
+ continue
1144
+ body = re.sub(r"\A---\s*\n.*?\n---\s*\n", "", text, count=1, flags=re.DOTALL)
1145
+ goal = re.search(r"(?im)^\s*(?:goal|目标|业务目标|需求)\s*[::]\s*(.+)$", body)
1146
+ if goal:
1147
+ return goal.group(1).strip()
1148
+ scope = re.search(r"(?im)^\s*>?\s*(?:变更范围|目标)\s*[::]\s*(.+)$", body)
1149
+ if scope:
1150
+ return scope.group(1).strip()
1151
+ first_task = re.search(r"(?m)^\s*\|\s*1\s*\|\s*([^|]+?)\s*\|", body)
1152
+ if first_task:
1153
+ return first_task.group(1).strip()
1154
+ for line in body.splitlines():
1155
+ clean = line.strip().lstrip("#").strip()
1156
+ if clean and not clean.startswith(("---", ">", "|")) and len(clean) > 8:
1157
+ return clean
1158
+ for event in events:
1159
+ if event.get("type") == "decision":
1160
+ value = str(event.get("decision") or event.get("note") or "").strip()
1161
+ if value:
1162
+ return re.sub(r"^(?:需求收敛|目标)\s*[::]\s*", "", value)
1163
+ return ""
1164
+
1165
+
1166
+ def _timeline_from_events(event_summary: dict[str, Any], events: list[dict[str, Any]]) -> list[dict[str, Any]]:
1167
+ timeline: list[dict[str, Any]] = []
1168
+ for phase, info in (event_summary.get("phases") or {}).items():
1169
+ attempts = info.get("attempts") if isinstance(info.get("attempts"), list) else []
1170
+ for attempt in attempts:
1171
+ timeline.append({
1172
+ "phase": phase,
1173
+ "attempt": attempt.get("attempt"),
1174
+ "startedAt": attempt.get("started_at"),
1175
+ "endedAt": attempt.get("ended_at"),
1176
+ "durationMs": attempt.get("duration_ms"),
1177
+ "status": attempt.get("status") or "UNKNOWN",
1178
+ "executorTool": attempt.get("executor_tool"),
1179
+ "executorAgent": attempt.get("executor_agent"),
1180
+ "handoffFromTool": attempt.get("handoff_from_tool"),
1181
+ })
1182
+ for event in events:
1183
+ if event.get("type") not in {"decision", "issue"}:
1184
+ continue
1185
+ timeline.append({
1186
+ "phase": event.get("phase"),
1187
+ "timestamp": event.get("timestamp"),
1188
+ "type": event.get("type"),
1189
+ "summary": event.get("decision") or event.get("message") or event.get("note") or "",
1190
+ })
1191
+ return timeline
1192
+
1193
+
1043
1194
  def collect_summary_data(
1044
1195
  change_dir: Path,
1045
1196
  *,
@@ -1118,18 +1269,14 @@ def collect_summary_data(
1118
1269
  if existing and existing.get("businessGoal"):
1119
1270
  data["businessGoal"] = existing["businessGoal"]
1120
1271
  else:
1121
- data["businessGoal"] = NOT_AVAILABLE if for_replay else ""
1272
+ inferred_goal = _business_goal_from_sources(change_dir, events)
1273
+ data["businessGoal"] = inferred_goal or (NOT_AVAILABLE if for_replay else "")
1122
1274
 
1123
1275
  # commits
1124
1276
  project = find_project_root(change_dir)
1125
1277
  if not data.get("finalCommit") or str(data.get("finalCommit")).startswith("<"):
1126
- code, head, _ = git_run(project, "rev-parse", "HEAD")
1127
- if code == 0 and head:
1128
- data["finalCommit"] = head
1129
- elif existing and existing.get("finalCommit"):
1130
- data["finalCommit"] = existing["finalCommit"]
1131
- else:
1132
- data["finalCommit"] = NOT_AVAILABLE if for_replay else ""
1278
+ final_commit = _final_commit_from_sources(ledger, events, existing, project)
1279
+ data["finalCommit"] = final_commit or (NOT_AVAILABLE if for_replay else "")
1133
1280
 
1134
1281
  if not data.get("baseCommit") or str(data.get("baseCommit")).startswith("<"):
1135
1282
  if ledger and ledger.get("baseCommit"):
@@ -1235,18 +1382,47 @@ def collect_summary_data(
1235
1382
  if not isinstance(data.get("artifacts"), list):
1236
1383
  data["artifacts"] = []
1237
1384
  if not for_replay:
1238
- # Keep empty placeholder for build artifacts; event artifacts go to reportPipeline
1239
- pass
1385
+ data["artifacts"] = _artifacts_from_events(events)
1240
1386
 
1241
- data["reviewSummary"] = _review_summary(change_dir, existing if for_replay else None)
1242
- data.setdefault("timeline", [])
1387
+ data["reviewSummary"] = _review_summary(
1388
+ change_dir,
1389
+ existing if for_replay else None,
1390
+ events,
1391
+ )
1392
+ if not for_replay:
1393
+ data["timeline"] = _timeline_from_events(event_summary, events)
1394
+ else:
1395
+ data.setdefault("timeline", [])
1243
1396
  data.setdefault("uncommittedTestEvidence", [])
1244
1397
 
1245
- # Model-owned fields: leave empty placeholders on finalize; preserve on replay
1398
+ # Derive risks/actions from evidence. These fields are facts, not model prose.
1246
1399
  if not for_replay:
1247
- data["maintenanceNotes"] = []
1248
- data["knownRisks"] = []
1400
+ data["maintenanceNotes"] = [
1401
+ str(event.get("note") or event.get("message") or "")
1402
+ for event in events
1403
+ if event.get("type") == "decision" and (event.get("note") or event.get("message"))
1404
+ ]
1405
+ data["knownRisks"] = [
1406
+ {
1407
+ "phase": event.get("phase"),
1408
+ "severity": event.get("severity") or "unknown",
1409
+ "message": event.get("message") or event.get("note") or event.get("code") or "",
1410
+ }
1411
+ for event in events
1412
+ if event.get("type") == "issue"
1413
+ and not (
1414
+ event.get("phase") == "archive"
1415
+ and event.get("code") == "missing-command"
1416
+ )
1417
+ ]
1249
1418
  data["manualActions"] = []
1419
+ for name, value in (data.get("stageStatus") or {}).items():
1420
+ if value in {"BLOCKED", "BLOCKED_BY_ENV", "BLOCKED_BY_DBA", "NOT_RUN", "USER_SKIPPED"}:
1421
+ data["manualActions"].append({
1422
+ "stage": name,
1423
+ "status": value,
1424
+ "action": "补充或确认该阶段的真实验证证据",
1425
+ })
1250
1426
  else:
1251
1427
  data.setdefault("maintenanceNotes", [])
1252
1428
  data.setdefault("knownRisks", [])
@@ -1978,6 +2154,7 @@ def cmd_finalize(
1978
2154
  _restore_target,
1979
2155
  phase="archive",
1980
2156
  type_="phase.end",
2157
+ status="FAIL",
1981
2158
  note="finalize aborted; render failed; original preserved",
1982
2159
  )
1983
2160
  except OSError:
@@ -2117,6 +2294,7 @@ def cmd_finalize(
2117
2294
  _safe_append_restored,
2118
2295
  phase="archive",
2119
2296
  type_="phase.end",
2297
+ status="FAIL",
2120
2298
  note="finalize aborted; original preserved",
2121
2299
  )
2122
2300
  except OSError:
@@ -2168,7 +2346,12 @@ def cmd_finalize(
2168
2346
  if service_result.get("warning"):
2169
2347
  warnings.append(str(service_result["warning"]))
2170
2348
 
2171
- _safe_append(phase="archive", type_="phase.end", note="finalize complete")
2349
+ _safe_append(
2350
+ phase="archive",
2351
+ type_="phase.end",
2352
+ status="WARN" if warnings else "OK",
2353
+ note="finalize complete",
2354
+ )
2172
2355
 
2173
2356
  payload["ok"] = True
2174
2357
  payload["warnings"] = warnings