@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
@@ -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