@hunter-harness/workflow-harness 0.2.85 → 0.3.0

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 (282) 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 +46 -17
  3. package/harness/bundles/general/claude-code/harness-archive/SKILL.md +2 -2
  4. package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +2 -2
  5. package/harness/bundles/general/claude-code/harness-execute/SKILL.md +189 -0
  6. package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +2 -2
  7. package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +2 -2
  8. package/harness/bundles/general/claude-code/harness-plan/SKILL.md +10 -9
  9. package/harness/bundles/general/claude-code/harness-plan/checklist.md +19 -91
  10. package/harness/bundles/general/claude-code/harness-plan/protocols.md +3 -1
  11. package/harness/bundles/general/claude-code/harness-plan/reference.md +74 -83
  12. package/harness/bundles/general/claude-code/harness-pull/SKILL.md +1 -1
  13. package/harness/bundles/general/claude-code/harness-push/SKILL.md +1 -1
  14. package/harness/bundles/general/claude-code/harness-review/SKILL.md +3 -3
  15. package/harness/bundles/general/claude-code/harness-review/protocols.md +1 -1
  16. package/harness/bundles/general/claude-code/harness-run/SKILL.md +13 -5
  17. package/harness/bundles/general/claude-code/harness-submit/SKILL.md +2 -2
  18. package/harness/bundles/general/claude-code/harness-submit/checklist.md +1 -1
  19. package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
  20. package/harness/bundles/general/claude-code/harness-test/SKILL.md +11 -2
  21. package/harness/bundles/general/claude-code/scripts/harness_adoption_metrics.py +391 -0
  22. package/harness/bundles/general/claude-code/scripts/harness_archive.py +23 -14
  23. package/harness/bundles/general/claude-code/scripts/harness_change.py +25 -7
  24. package/harness/bundles/general/claude-code/scripts/harness_context.py +154 -60
  25. package/harness/bundles/general/claude-code/scripts/harness_events.py +1 -1
  26. package/harness/bundles/general/claude-code/scripts/harness_events_sync.py +1 -0
  27. package/harness/bundles/general/claude-code/scripts/harness_fixback.py +14 -10
  28. package/harness/bundles/general/claude-code/scripts/harness_gate.py +293 -90
  29. package/harness/bundles/general/claude-code/scripts/harness_ledger.py +47 -2
  30. package/harness/bundles/general/claude-code/scripts/harness_paths.py +175 -0
  31. package/harness/bundles/general/claude-code/scripts/harness_phase.py +65 -23
  32. package/harness/bundles/general/claude-code/scripts/harness_plan_finalize.py +140 -874
  33. package/harness/bundles/general/claude-code/scripts/harness_review.py +25 -4
  34. package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
  35. package/harness/bundles/general/codebuddy/contracts/workflow-policy.json +46 -17
  36. package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +2 -2
  37. package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +2 -2
  38. package/harness/bundles/general/codebuddy/harness-execute/SKILL.md +170 -0
  39. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +2 -2
  40. package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +2 -2
  41. package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +10 -9
  42. package/harness/bundles/general/codebuddy/harness-plan/checklist.md +19 -91
  43. package/harness/bundles/general/codebuddy/harness-plan/protocols.md +3 -1
  44. package/harness/bundles/general/codebuddy/harness-plan/reference.md +74 -83
  45. package/harness/bundles/general/codebuddy/harness-pull/SKILL.md +1 -1
  46. package/harness/bundles/general/codebuddy/harness-push/SKILL.md +1 -1
  47. package/harness/bundles/general/codebuddy/harness-review/SKILL.md +3 -3
  48. package/harness/bundles/general/codebuddy/harness-review/protocols.md +1 -1
  49. package/harness/bundles/general/codebuddy/harness-run/SKILL.md +13 -5
  50. package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +2 -2
  51. package/harness/bundles/general/codebuddy/harness-submit/checklist.md +1 -1
  52. package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
  53. package/harness/bundles/general/codebuddy/harness-test/SKILL.md +11 -2
  54. package/harness/bundles/general/codebuddy/scripts/harness_adoption_metrics.py +391 -0
  55. package/harness/bundles/general/codebuddy/scripts/harness_archive.py +23 -14
  56. package/harness/bundles/general/codebuddy/scripts/harness_change.py +25 -7
  57. package/harness/bundles/general/codebuddy/scripts/harness_context.py +154 -60
  58. package/harness/bundles/general/codebuddy/scripts/harness_events.py +1 -1
  59. package/harness/bundles/general/codebuddy/scripts/harness_events_sync.py +1 -0
  60. package/harness/bundles/general/codebuddy/scripts/harness_fixback.py +14 -10
  61. package/harness/bundles/general/codebuddy/scripts/harness_gate.py +293 -90
  62. package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +47 -2
  63. package/harness/bundles/general/codebuddy/scripts/harness_paths.py +175 -0
  64. package/harness/bundles/general/codebuddy/scripts/harness_phase.py +65 -23
  65. package/harness/bundles/general/codebuddy/scripts/harness_plan_finalize.py +140 -874
  66. package/harness/bundles/general/codebuddy/scripts/harness_review.py +25 -4
  67. package/harness/bundles/general/codex/.harness-build.json +1 -1
  68. package/harness/bundles/general/codex/contracts/workflow-policy.json +46 -17
  69. package/harness/bundles/general/codex/harness-archive/SKILL.md +2 -2
  70. package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +2 -2
  71. package/harness/bundles/general/codex/harness-execute/SKILL.md +170 -0
  72. package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +2 -2
  73. package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +2 -2
  74. package/harness/bundles/general/codex/harness-plan/SKILL.md +10 -9
  75. package/harness/bundles/general/codex/harness-plan/checklist.md +19 -91
  76. package/harness/bundles/general/codex/harness-plan/protocols.md +3 -1
  77. package/harness/bundles/general/codex/harness-plan/reference.md +74 -83
  78. package/harness/bundles/general/codex/harness-pull/SKILL.md +1 -1
  79. package/harness/bundles/general/codex/harness-push/SKILL.md +1 -1
  80. package/harness/bundles/general/codex/harness-review/SKILL.md +3 -3
  81. package/harness/bundles/general/codex/harness-review/protocols.md +1 -1
  82. package/harness/bundles/general/codex/harness-run/SKILL.md +13 -5
  83. package/harness/bundles/general/codex/harness-submit/SKILL.md +2 -2
  84. package/harness/bundles/general/codex/harness-submit/checklist.md +1 -1
  85. package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
  86. package/harness/bundles/general/codex/harness-test/SKILL.md +11 -2
  87. package/harness/bundles/general/codex/scripts/harness_adoption_metrics.py +391 -0
  88. package/harness/bundles/general/codex/scripts/harness_archive.py +23 -14
  89. package/harness/bundles/general/codex/scripts/harness_change.py +25 -7
  90. package/harness/bundles/general/codex/scripts/harness_context.py +154 -60
  91. package/harness/bundles/general/codex/scripts/harness_events.py +1 -1
  92. package/harness/bundles/general/codex/scripts/harness_events_sync.py +1 -0
  93. package/harness/bundles/general/codex/scripts/harness_fixback.py +14 -10
  94. package/harness/bundles/general/codex/scripts/harness_gate.py +293 -90
  95. package/harness/bundles/general/codex/scripts/harness_ledger.py +47 -2
  96. package/harness/bundles/general/codex/scripts/harness_paths.py +175 -0
  97. package/harness/bundles/general/codex/scripts/harness_phase.py +65 -23
  98. package/harness/bundles/general/codex/scripts/harness_plan_finalize.py +140 -874
  99. package/harness/bundles/general/codex/scripts/harness_review.py +25 -4
  100. package/harness/bundles/general/cursor/.harness-build.json +1 -1
  101. package/harness/bundles/general/cursor/contracts/workflow-policy.json +46 -17
  102. package/harness/bundles/general/cursor/harness-archive/SKILL.md +2 -2
  103. package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +2 -2
  104. package/harness/bundles/general/cursor/harness-execute/SKILL.md +170 -0
  105. package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +2 -2
  106. package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +2 -2
  107. package/harness/bundles/general/cursor/harness-plan/SKILL.md +10 -9
  108. package/harness/bundles/general/cursor/harness-plan/checklist.md +19 -91
  109. package/harness/bundles/general/cursor/harness-plan/protocols.md +3 -1
  110. package/harness/bundles/general/cursor/harness-plan/reference.md +74 -83
  111. package/harness/bundles/general/cursor/harness-pull/SKILL.md +1 -1
  112. package/harness/bundles/general/cursor/harness-push/SKILL.md +1 -1
  113. package/harness/bundles/general/cursor/harness-review/SKILL.md +3 -3
  114. package/harness/bundles/general/cursor/harness-review/protocols.md +1 -1
  115. package/harness/bundles/general/cursor/harness-run/SKILL.md +13 -5
  116. package/harness/bundles/general/cursor/harness-submit/SKILL.md +2 -2
  117. package/harness/bundles/general/cursor/harness-submit/checklist.md +1 -1
  118. package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
  119. package/harness/bundles/general/cursor/harness-test/SKILL.md +11 -2
  120. package/harness/bundles/general/cursor/scripts/harness_adoption_metrics.py +391 -0
  121. package/harness/bundles/general/cursor/scripts/harness_archive.py +23 -14
  122. package/harness/bundles/general/cursor/scripts/harness_change.py +25 -7
  123. package/harness/bundles/general/cursor/scripts/harness_context.py +154 -60
  124. package/harness/bundles/general/cursor/scripts/harness_events.py +1 -1
  125. package/harness/bundles/general/cursor/scripts/harness_events_sync.py +1 -0
  126. package/harness/bundles/general/cursor/scripts/harness_fixback.py +14 -10
  127. package/harness/bundles/general/cursor/scripts/harness_gate.py +293 -90
  128. package/harness/bundles/general/cursor/scripts/harness_ledger.py +47 -2
  129. package/harness/bundles/general/cursor/scripts/harness_paths.py +175 -0
  130. package/harness/bundles/general/cursor/scripts/harness_phase.py +65 -23
  131. package/harness/bundles/general/cursor/scripts/harness_plan_finalize.py +140 -874
  132. package/harness/bundles/general/cursor/scripts/harness_review.py +25 -4
  133. package/harness/bundles/java/claude-code/.harness-build.json +1 -1
  134. package/harness/bundles/java/claude-code/contracts/workflow-policy.json +46 -17
  135. package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +2 -2
  136. package/harness/bundles/java/claude-code/harness-archive/SKILL.md +2 -2
  137. package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +2 -2
  138. package/harness/bundles/java/claude-code/harness-execute/SKILL.md +189 -0
  139. package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +2 -2
  140. package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +2 -2
  141. package/harness/bundles/java/claude-code/harness-package/SKILL.md +2 -2
  142. package/harness/bundles/java/claude-code/harness-plan/SKILL.md +10 -9
  143. package/harness/bundles/java/claude-code/harness-plan/checklist.md +19 -91
  144. package/harness/bundles/java/claude-code/harness-plan/protocols.md +3 -1
  145. package/harness/bundles/java/claude-code/harness-plan/reference.md +74 -83
  146. package/harness/bundles/java/claude-code/harness-pull/SKILL.md +1 -1
  147. package/harness/bundles/java/claude-code/harness-push/SKILL.md +1 -1
  148. package/harness/bundles/java/claude-code/harness-review/SKILL.md +3 -3
  149. package/harness/bundles/java/claude-code/harness-review/protocols.md +1 -1
  150. package/harness/bundles/java/claude-code/harness-run/SKILL.md +13 -5
  151. package/harness/bundles/java/claude-code/harness-submit/SKILL.md +2 -2
  152. package/harness/bundles/java/claude-code/harness-submit/checklist.md +1 -1
  153. package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
  154. package/harness/bundles/java/claude-code/harness-test/SKILL.md +11 -2
  155. package/harness/bundles/java/claude-code/scripts/harness_adoption_metrics.py +391 -0
  156. package/harness/bundles/java/claude-code/scripts/harness_archive.py +23 -14
  157. package/harness/bundles/java/claude-code/scripts/harness_change.py +25 -7
  158. package/harness/bundles/java/claude-code/scripts/harness_context.py +154 -60
  159. package/harness/bundles/java/claude-code/scripts/harness_events.py +1 -1
  160. package/harness/bundles/java/claude-code/scripts/harness_events_sync.py +1 -0
  161. package/harness/bundles/java/claude-code/scripts/harness_fixback.py +14 -10
  162. package/harness/bundles/java/claude-code/scripts/harness_gate.py +293 -90
  163. package/harness/bundles/java/claude-code/scripts/harness_ledger.py +47 -2
  164. package/harness/bundles/java/claude-code/scripts/harness_paths.py +175 -0
  165. package/harness/bundles/java/claude-code/scripts/harness_phase.py +65 -23
  166. package/harness/bundles/java/claude-code/scripts/harness_plan_finalize.py +140 -874
  167. package/harness/bundles/java/claude-code/scripts/harness_review.py +25 -4
  168. package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
  169. package/harness/bundles/java/codebuddy/contracts/workflow-policy.json +46 -17
  170. package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +2 -2
  171. package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +2 -2
  172. package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +2 -2
  173. package/harness/bundles/java/codebuddy/harness-execute/SKILL.md +170 -0
  174. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +2 -2
  175. package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +2 -2
  176. package/harness/bundles/java/codebuddy/harness-package/SKILL.md +2 -2
  177. package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +10 -9
  178. package/harness/bundles/java/codebuddy/harness-plan/checklist.md +19 -91
  179. package/harness/bundles/java/codebuddy/harness-plan/protocols.md +3 -1
  180. package/harness/bundles/java/codebuddy/harness-plan/reference.md +74 -83
  181. package/harness/bundles/java/codebuddy/harness-pull/SKILL.md +1 -1
  182. package/harness/bundles/java/codebuddy/harness-push/SKILL.md +1 -1
  183. package/harness/bundles/java/codebuddy/harness-review/SKILL.md +3 -3
  184. package/harness/bundles/java/codebuddy/harness-review/protocols.md +1 -1
  185. package/harness/bundles/java/codebuddy/harness-run/SKILL.md +13 -5
  186. package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +2 -2
  187. package/harness/bundles/java/codebuddy/harness-submit/checklist.md +1 -1
  188. package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
  189. package/harness/bundles/java/codebuddy/harness-test/SKILL.md +11 -2
  190. package/harness/bundles/java/codebuddy/scripts/harness_adoption_metrics.py +391 -0
  191. package/harness/bundles/java/codebuddy/scripts/harness_archive.py +23 -14
  192. package/harness/bundles/java/codebuddy/scripts/harness_change.py +25 -7
  193. package/harness/bundles/java/codebuddy/scripts/harness_context.py +154 -60
  194. package/harness/bundles/java/codebuddy/scripts/harness_events.py +1 -1
  195. package/harness/bundles/java/codebuddy/scripts/harness_events_sync.py +1 -0
  196. package/harness/bundles/java/codebuddy/scripts/harness_fixback.py +14 -10
  197. package/harness/bundles/java/codebuddy/scripts/harness_gate.py +293 -90
  198. package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +47 -2
  199. package/harness/bundles/java/codebuddy/scripts/harness_paths.py +175 -0
  200. package/harness/bundles/java/codebuddy/scripts/harness_phase.py +65 -23
  201. package/harness/bundles/java/codebuddy/scripts/harness_plan_finalize.py +140 -874
  202. package/harness/bundles/java/codebuddy/scripts/harness_review.py +25 -4
  203. package/harness/bundles/java/codex/.harness-build.json +1 -1
  204. package/harness/bundles/java/codex/contracts/workflow-policy.json +46 -17
  205. package/harness/bundles/java/codex/harness-apidoc/SKILL.md +2 -2
  206. package/harness/bundles/java/codex/harness-archive/SKILL.md +2 -2
  207. package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +2 -2
  208. package/harness/bundles/java/codex/harness-execute/SKILL.md +170 -0
  209. package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +2 -2
  210. package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +2 -2
  211. package/harness/bundles/java/codex/harness-package/SKILL.md +2 -2
  212. package/harness/bundles/java/codex/harness-plan/SKILL.md +10 -9
  213. package/harness/bundles/java/codex/harness-plan/checklist.md +19 -91
  214. package/harness/bundles/java/codex/harness-plan/protocols.md +3 -1
  215. package/harness/bundles/java/codex/harness-plan/reference.md +74 -83
  216. package/harness/bundles/java/codex/harness-pull/SKILL.md +1 -1
  217. package/harness/bundles/java/codex/harness-push/SKILL.md +1 -1
  218. package/harness/bundles/java/codex/harness-review/SKILL.md +3 -3
  219. package/harness/bundles/java/codex/harness-review/protocols.md +1 -1
  220. package/harness/bundles/java/codex/harness-run/SKILL.md +13 -5
  221. package/harness/bundles/java/codex/harness-submit/SKILL.md +2 -2
  222. package/harness/bundles/java/codex/harness-submit/checklist.md +1 -1
  223. package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
  224. package/harness/bundles/java/codex/harness-test/SKILL.md +11 -2
  225. package/harness/bundles/java/codex/scripts/harness_adoption_metrics.py +391 -0
  226. package/harness/bundles/java/codex/scripts/harness_archive.py +23 -14
  227. package/harness/bundles/java/codex/scripts/harness_change.py +25 -7
  228. package/harness/bundles/java/codex/scripts/harness_context.py +154 -60
  229. package/harness/bundles/java/codex/scripts/harness_events.py +1 -1
  230. package/harness/bundles/java/codex/scripts/harness_events_sync.py +1 -0
  231. package/harness/bundles/java/codex/scripts/harness_fixback.py +14 -10
  232. package/harness/bundles/java/codex/scripts/harness_gate.py +293 -90
  233. package/harness/bundles/java/codex/scripts/harness_ledger.py +47 -2
  234. package/harness/bundles/java/codex/scripts/harness_paths.py +175 -0
  235. package/harness/bundles/java/codex/scripts/harness_phase.py +65 -23
  236. package/harness/bundles/java/codex/scripts/harness_plan_finalize.py +140 -874
  237. package/harness/bundles/java/codex/scripts/harness_review.py +25 -4
  238. package/harness/bundles/java/cursor/.harness-build.json +1 -1
  239. package/harness/bundles/java/cursor/contracts/workflow-policy.json +46 -17
  240. package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +2 -2
  241. package/harness/bundles/java/cursor/harness-archive/SKILL.md +2 -2
  242. package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +2 -2
  243. package/harness/bundles/java/cursor/harness-execute/SKILL.md +170 -0
  244. package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +2 -2
  245. package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +2 -2
  246. package/harness/bundles/java/cursor/harness-package/SKILL.md +2 -2
  247. package/harness/bundles/java/cursor/harness-plan/SKILL.md +10 -9
  248. package/harness/bundles/java/cursor/harness-plan/checklist.md +19 -91
  249. package/harness/bundles/java/cursor/harness-plan/protocols.md +3 -1
  250. package/harness/bundles/java/cursor/harness-plan/reference.md +74 -83
  251. package/harness/bundles/java/cursor/harness-pull/SKILL.md +1 -1
  252. package/harness/bundles/java/cursor/harness-push/SKILL.md +1 -1
  253. package/harness/bundles/java/cursor/harness-review/SKILL.md +3 -3
  254. package/harness/bundles/java/cursor/harness-review/protocols.md +1 -1
  255. package/harness/bundles/java/cursor/harness-run/SKILL.md +13 -5
  256. package/harness/bundles/java/cursor/harness-submit/SKILL.md +2 -2
  257. package/harness/bundles/java/cursor/harness-submit/checklist.md +1 -1
  258. package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
  259. package/harness/bundles/java/cursor/harness-test/SKILL.md +11 -2
  260. package/harness/bundles/java/cursor/scripts/harness_adoption_metrics.py +391 -0
  261. package/harness/bundles/java/cursor/scripts/harness_archive.py +23 -14
  262. package/harness/bundles/java/cursor/scripts/harness_change.py +25 -7
  263. package/harness/bundles/java/cursor/scripts/harness_context.py +154 -60
  264. package/harness/bundles/java/cursor/scripts/harness_events.py +1 -1
  265. package/harness/bundles/java/cursor/scripts/harness_events_sync.py +1 -0
  266. package/harness/bundles/java/cursor/scripts/harness_fixback.py +14 -10
  267. package/harness/bundles/java/cursor/scripts/harness_gate.py +293 -90
  268. package/harness/bundles/java/cursor/scripts/harness_ledger.py +47 -2
  269. package/harness/bundles/java/cursor/scripts/harness_paths.py +175 -0
  270. package/harness/bundles/java/cursor/scripts/harness_phase.py +65 -23
  271. package/harness/bundles/java/cursor/scripts/harness_plan_finalize.py +140 -874
  272. package/harness/bundles/java/cursor/scripts/harness_review.py +25 -4
  273. package/harness/manifests/general/claude-code.json +40 -32
  274. package/harness/manifests/general/codebuddy.json +40 -32
  275. package/harness/manifests/general/codex.json +40 -32
  276. package/harness/manifests/general/cursor.json +40 -32
  277. package/harness/manifests/java/claude-code.json +42 -34
  278. package/harness/manifests/java/codebuddy.json +42 -34
  279. package/harness/manifests/java/codex.json +42 -34
  280. package/harness/manifests/java/cursor.json +42 -34
  281. package/hunter-workflow-family.json +4 -4
  282. package/package.json +1 -1
@@ -23,9 +23,18 @@ disallowed-tools:
23
23
  - Bash(node *)
24
24
  - Bash(codegraph *)
25
25
  ---
26
- <!-- generated by harness_deploy.py; core=123134be5333129c; overlay=java; agent=claude-code; do not edit -->
26
+ <!-- generated by harness_deploy.py; core=5710908ba16c20a7; overlay=java; agent=claude-code; do not edit -->
27
27
  # harness-test — 测试执行
28
28
 
29
+ > **2026-08 阶段合并**:原 `run`(编码)与 `test`(验证)合并为单一 `execute` 阶段,
30
+ > 统一入口为 **`/harness-execute`**(`harness/harness-execute/SKILL.md`)。本 skill 作为
31
+ > 别名保留,frontmatter `name` 不变、内容照旧可用;门禁入参 `--phase test` 经
32
+ > `LEGACY_PHASE_ALIASES` 归一为 execute。别名至少保留一个 minor 版本,退役时间见
33
+ > `docs/harness-improvement-roadmap/`。下文出现的阶段名 `run`/`test` 均按别名解析。
34
+ >
35
+ > 合并语义注意:execute 关门时 C9 场景覆盖要求全部 `ownerPhase=execute` 场景有通过
36
+ > receipt(包括本属 test 的接口/兼容场景);`ownerPhase=review`/`submit` 仍按计划顺延。
37
+
29
38
  ## Purpose
30
39
 
31
40
  读取测试场景表,逐条执行单元测试和接口测试,验证代码变更的正确性,输出测试报告。
@@ -269,7 +278,7 @@ python <skills-root>/scripts/harness_events.py append --change-dir ".harness/cha
269
278
 
270
279
  > **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 边界保持最新。
271
280
 
272
- 每个阶段的 `phase.start` 与对应 `phase.end` 必须复用同一 `--run-id` / `--attempt`;阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`。重试同一阶段时生成新的 run-id 并增加 `--attempt <n>`,不得覆盖或伪装成一次执行。**`attempt` 按 phase 全局递增,不是按 run-id**:一个 run-id 只绑定一个 attempt,重试必须「新 run-id + 下一个 attempt」两者同时换,只换其一会撞 `EVENT_ATTEMPT_CONFLICT` 或 `PHASE_ALREADY_CLOSED`。已发布的 plan `harness_plan_finalize.py republish` 自动分配两者。
281
+ 每个阶段的 `phase.start` 与对应 `phase.end` 必须复用同一 `--run-id` / `--attempt`;阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`。重试同一阶段时生成新的 run-id 并增加 `--attempt <n>`,不得覆盖或伪装成一次执行。**`attempt` 按 phase 全局递增,不是按 run-id**:一个 run-id 只绑定一个 attempt,重试必须「新 run-id + 下一个 attempt」两者同时换,只换其一会撞 `EVENT_ATTEMPT_CONFLICT` 或 `PHASE_ALREADY_CLOSED`。已发布 plan 的修订通过重跑 `plan evidence-pack` + `plan finalize` 分配新 attempt(`harness_plan_finalize.py republish` 已于 0.3.0 移除)。
273
282
 
274
283
  阶段跑得久(plan/run 常见)时用 `harness_context.py renew --project . --change <cn> --executor <tool>` 续租;租约到期本身不再阻断 `close`(同一 owner 的过期租约不构成冲突,收据里记 `leaseLapsed`),但续租能让 `view` 的状态如实反映在跑。跨工具继续执行时写 `--executor-tool <codex|claude-code|codebuddy|cursor>`,并在接棒事件写 `--handoff-from-tool` / `--handoff-reason`;也可由 `HUNTER_HARNESS_TOOL/AGENT/MODEL/RUN_ID` 环境变量统一注入。
275
284
  > 片段:logging · phase=`test` · 事件:phase/command/verification/decision/issue/artifact
@@ -0,0 +1,391 @@
1
+ #!/usr/bin/env python3
2
+ """Legacy plan finalize 退役前的采纳度度量(roadmap 14,只读)。
3
+
4
+ 对一组项目根聚合三条判据的读数,输出 ``pass`` / ``fail`` / ``indeterminate``。
5
+ 本脚本只读:不写任何文件、不改判据阈值、不宣告"达标可删"。样本不足时输出
6
+ ``indeterminate``,由人决定是否继续积累 dogfood 样本。
7
+
8
+ 三条判据(docs/harness-improvement-roadmap/14-migration-rollout-and-acceptance.md):
9
+
10
+ 1. ``finalize_first_try``:v2 finalize 首次成功率。连续 10 次 finalize 中
11
+ 首次 attempt 即 ``publication_committed_event_complete`` 的比例 >= 8/10。
12
+ 操作化定义:磁盘布局是一 operation 一文件
13
+ (``meta/plan-finalization-transactions/<operation_id>.json``),同 operation
14
+ 的重试覆盖同一文件,可观测的重试信号 = 同一 change 的后续 operation 文件数。
15
+ "首次成功" = 该 change 最早创建的 operation 文件 status 为
16
+ ``publication_committed_event_complete``。
17
+ 2. ``evidence_closure``:证据闭环。连续 5 个 v2 change 的
18
+ ``evidence/verification-ledger.json`` 覆盖全部 ``requiredEvidenceKind=ledger``
19
+ 场景,且 events 未出现 ``SCENARIO_MANIFEST_`` 阻断码。
20
+ 覆盖判定:场景声明的 ``executableTestId`` 出现在 ledger receipt 的
21
+ ``declared`` 或 ``selected`` 列表,或其三元 identity(testId/file/title)
22
+ 出现在 ``collected``。任何一项判不出都记 uncovered 并带 reason——
23
+ 准入门槛宁缺毋滥。
24
+ 3. ``legacy_fallback``:legacy 回退率。最近 10 个新 change 中写出
25
+ ``meta/plan-finalization.json``(legacy finalize 收据)的比例 <= 1/10。
26
+
27
+ 多项目样本合并成单一证据池(dogfood 车队是一个总体)。窗口内排序键:
28
+ payload ``created_at``(ISO 字典序)→ 缺则文件 mtime → 再并列按 change_key。
29
+
30
+ 用法::
31
+
32
+ python harness/scripts/harness_adoption_metrics.py \
33
+ --project <root> [--project <root2> ...] [--json] [--strict]
34
+
35
+ 默认 exit 0(度量工具不卡 CI);``--strict`` 时任何 fail 或 indeterminate → exit 1。
36
+
37
+ Python 3.10+,stdlib only,不 import 任何 harness_*(零耦合)。
38
+ """
39
+
40
+ from __future__ import annotations
41
+
42
+ import argparse
43
+ import json
44
+ import sys
45
+ from pathlib import Path
46
+ from typing import Any
47
+
48
+ SCRIPTS_DIR = Path(__file__).resolve().parent
49
+ if str(SCRIPTS_DIR) not in sys.path:
50
+ sys.path.insert(0, str(SCRIPTS_DIR))
51
+
52
+ if hasattr(sys.stdout, "reconfigure"):
53
+ sys.stdout.reconfigure(encoding="utf-8")
54
+ if hasattr(sys.stderr, "reconfigure"):
55
+ sys.stderr.reconfigure(encoding="utf-8")
56
+
57
+ FIRST_TRY_WINDOW = 10
58
+ FIRST_TRY_THRESHOLD = 8
59
+ EVIDENCE_WINDOW = 5
60
+ LEGACY_WINDOW = 10
61
+ LEGACY_THRESHOLD = 1
62
+
63
+ FINAL_STATUS = "publication_committed_event_complete"
64
+
65
+
66
+ def _read_json(path: Path) -> dict[str, Any] | None:
67
+ try:
68
+ payload = json.loads(path.read_text(encoding="utf-8-sig"))
69
+ except (OSError, json.JSONDecodeError):
70
+ return None
71
+ return payload if isinstance(payload, dict) else None
72
+
73
+
74
+ def _change_dirs(project: Path) -> list[Path]:
75
+ root = project / ".harness" / "changes"
76
+ if not root.is_dir():
77
+ return []
78
+ return sorted((p for p in root.iterdir() if p.is_dir()), key=lambda p: p.name)
79
+
80
+
81
+ def _sort_key(created_at: str, mtime: float, change_key: str) -> tuple[str, float, str]:
82
+ # created_at 是 ISO 字符串,字典序即时间序;缺失时退到 mtime。
83
+ return (created_at, mtime, change_key)
84
+
85
+
86
+ def _operation_sort_key(record: dict[str, Any], path: Path) -> tuple[str, float, str]:
87
+ return _sort_key(
88
+ str(record.get("created_at") or ""),
89
+ path.stat().st_mtime,
90
+ str(record.get("operation_id") or path.name),
91
+ )
92
+
93
+
94
+ # ---------------------------------------------------------------------------
95
+ # 判据 1:v2 finalize 首次成功率
96
+ # ---------------------------------------------------------------------------
97
+
98
+
99
+ def _collect_first_try(projects: list[Path]) -> dict[str, Any]:
100
+ per_change: list[dict[str, Any]] = []
101
+ for project in projects:
102
+ for change in _change_dirs(project):
103
+ transactions_dir = change / "meta" / "plan-finalization-transactions"
104
+ if not transactions_dir.is_dir():
105
+ continue
106
+ records: list[tuple[dict[str, Any], Path]] = []
107
+ for path in transactions_dir.glob("*.json"):
108
+ payload = _read_json(path)
109
+ if payload is not None:
110
+ records.append((payload, path))
111
+ if not records:
112
+ continue
113
+ records.sort(key=lambda item: _operation_sort_key(item[0], item[1]))
114
+ first_payload, _first_path = records[0]
115
+ per_change.append(
116
+ {
117
+ "project": str(project),
118
+ "change": change.name,
119
+ "first_status": str(first_payload.get("status") or ""),
120
+ "first_try_ok": first_payload.get("status") == FINAL_STATUS,
121
+ "retry_count": len(records) - 1,
122
+ "sort": _operation_sort_key(first_payload, records[0][1]),
123
+ }
124
+ )
125
+ per_change.sort(key=lambda item: item["sort"])
126
+ window = per_change[-FIRST_TRY_WINDOW:]
127
+ sample = len(window)
128
+ ok_count = sum(1 for item in window if item["first_try_ok"])
129
+ if sample < FIRST_TRY_WINDOW:
130
+ status = "indeterminate"
131
+ detail = f"样本 {sample}/{FIRST_TRY_WINDOW},判据不可判定"
132
+ else:
133
+ status = "pass" if ok_count >= FIRST_TRY_THRESHOLD else "fail"
134
+ detail = (
135
+ f"最近 {sample} 次 finalize 中首次即 {FINAL_STATUS} 的有 "
136
+ f"{ok_count} 次(阈值 >= {FIRST_TRY_THRESHOLD})"
137
+ )
138
+ return {
139
+ "id": "finalize_first_try",
140
+ "reading": f"{ok_count}/{sample}",
141
+ "threshold": f">= {FIRST_TRY_THRESHOLD}/{FIRST_TRY_WINDOW}",
142
+ "sample": sample,
143
+ "status": status,
144
+ "detail": detail,
145
+ "changes": [
146
+ {key: value for key, value in item.items() if key != "sort"}
147
+ for item in window
148
+ ],
149
+ }
150
+
151
+
152
+ # ---------------------------------------------------------------------------
153
+ # 判据 2:证据闭环
154
+ # ---------------------------------------------------------------------------
155
+
156
+
157
+ def _evidence_closure_for_change(change: Path) -> dict[str, Any]:
158
+ manifest = _read_json(change / "meta" / "scenario-manifest.json")
159
+ if manifest is None:
160
+ return {"change": change.name, "clean": False,
161
+ "reason": "scenario-manifest.json 缺失或不可解析"}
162
+ scenarios = manifest.get("scenarios")
163
+ if not isinstance(scenarios, list):
164
+ return {"change": change.name, "clean": False,
165
+ "reason": "scenario-manifest.json 无 scenarios 数组"}
166
+ ledger_scenarios = [
167
+ s for s in scenarios
168
+ if isinstance(s, dict) and s.get("requiredEvidenceKind") == "ledger"
169
+ ]
170
+
171
+ uncovered: list[str] = []
172
+ if ledger_scenarios:
173
+ ledger_payload = None
174
+ for candidate in (
175
+ change / ".harness" / "state" / "changes" / change.name
176
+ / "evidence" / "verification-ledger.json",
177
+ change / "evidence" / "verification-ledger.json",
178
+ ):
179
+ ledger_payload = _read_json(candidate)
180
+ if ledger_payload is not None:
181
+ break
182
+ if ledger_payload is None:
183
+ uncovered = [
184
+ f"{s.get('id')}:verification-ledger.json 缺失或不可解析"
185
+ for s in ledger_scenarios
186
+ ]
187
+ else:
188
+ declared = set(ledger_payload.get("declared") or [])
189
+ selected = set(ledger_payload.get("selected") or [])
190
+ collected_keys = {
191
+ (
192
+ str(item.get("testId") or ""),
193
+ str(item.get("file") or ""),
194
+ str(item.get("title") or ""),
195
+ )
196
+ for item in (ledger_payload.get("collected") or [])
197
+ if isinstance(item, dict)
198
+ }
199
+ for scenario in ledger_scenarios:
200
+ scenario_id = str(scenario.get("id") or "?")
201
+ test_id = str(scenario.get("executableTestId") or "").strip()
202
+ identity = (
203
+ test_id,
204
+ str(scenario.get("testFile") or "").strip(),
205
+ str(scenario.get("testTitle") or "").strip(),
206
+ )
207
+ if not test_id:
208
+ uncovered.append(f"{scenario_id}:缺 executableTestId,无法判定")
209
+ continue
210
+ if (
211
+ test_id not in declared
212
+ and test_id not in selected
213
+ and identity not in collected_keys
214
+ ):
215
+ uncovered.append(f"{scenario_id}:ledger 未覆盖")
216
+
217
+ blocked = False
218
+ for events_path in (
219
+ change / "events.ndjson",
220
+ change / ".harness" / "state" / "changes" / change.name / "events.ndjson",
221
+ change / "meta" / "events.ndjson",
222
+ ):
223
+ try:
224
+ text = events_path.read_text(encoding="utf-8-sig", errors="replace")
225
+ except OSError:
226
+ continue
227
+ if "SCENARIO_MANIFEST_" in text:
228
+ blocked = True
229
+ break
230
+
231
+ clean = not uncovered and not blocked
232
+ result: dict[str, Any] = {
233
+ "change": change.name,
234
+ "clean": clean,
235
+ "ledger_scenarios": len(ledger_scenarios),
236
+ }
237
+ if uncovered:
238
+ result["uncovered"] = uncovered
239
+ if blocked:
240
+ result["blocked_by"] = "SCENARIO_MANIFEST_*"
241
+ if clean:
242
+ result["reason"] = "ok"
243
+ return result
244
+
245
+
246
+ def _collect_evidence_closure(projects: list[Path]) -> dict[str, Any]:
247
+ v2_changes: list[tuple[tuple[str, float, str], Path]] = []
248
+ for project in projects:
249
+ for change in _change_dirs(project):
250
+ transactions_dir = change / "meta" / "plan-finalization-transactions"
251
+ if not transactions_dir.is_dir() or not any(
252
+ transactions_dir.glob("*.json")
253
+ ):
254
+ continue
255
+ # 窗口排序键与判据 1 一致:最早事务的 created_at。
256
+ earliest: tuple[str, float, str] | None = None
257
+ for path in transactions_dir.glob("*.json"):
258
+ payload = _read_json(path)
259
+ if payload is None:
260
+ continue
261
+ key = _operation_sort_key(payload, path)
262
+ if earliest is None or key < earliest:
263
+ earliest = key
264
+ v2_changes.append((earliest or _sort_key("", change.stat().st_mtime, change.name), change))
265
+ v2_changes.sort(key=lambda item: item[0])
266
+ window = [change for _key, change in v2_changes[-EVIDENCE_WINDOW:]]
267
+ sample = len(window)
268
+ results = [_evidence_closure_for_change(change) for change in window]
269
+ clean_count = sum(1 for item in results if item["clean"])
270
+ if sample < EVIDENCE_WINDOW:
271
+ status = "indeterminate"
272
+ detail = f"样本 {sample}/{EVIDENCE_WINDOW},判据不可判定"
273
+ else:
274
+ status = "pass" if clean_count == sample else "fail"
275
+ detail = (
276
+ f"最近 {sample} 个 v2 change 中证据闭环干净的有 {clean_count} 个"
277
+ f"(阈值 = {EVIDENCE_WINDOW}/{EVIDENCE_WINDOW})"
278
+ )
279
+ return {
280
+ "id": "evidence_closure",
281
+ "reading": f"{clean_count}/{sample}",
282
+ "threshold": f"{EVIDENCE_WINDOW}/{EVIDENCE_WINDOW} 干净",
283
+ "sample": sample,
284
+ "status": status,
285
+ "detail": detail,
286
+ "changes": results,
287
+ }
288
+
289
+
290
+ # ---------------------------------------------------------------------------
291
+ # 判据 3:legacy 回退率
292
+ # ---------------------------------------------------------------------------
293
+
294
+
295
+ def _collect_legacy_fallback(projects: list[Path]) -> dict[str, Any]:
296
+ all_changes: list[tuple[float, str, Path]] = []
297
+ for project in projects:
298
+ for change in _change_dirs(project):
299
+ all_changes.append((change.stat().st_mtime, change.name, change))
300
+ all_changes.sort(key=lambda item: (item[0], item[1]))
301
+ window = [change for _mtime, _name, change in all_changes[-LEGACY_WINDOW:]]
302
+ sample = len(window)
303
+ legacy = [
304
+ change.name
305
+ for change in window
306
+ if (change / "meta" / "plan-finalization.json").is_file()
307
+ ]
308
+ if sample < LEGACY_WINDOW:
309
+ status = "indeterminate"
310
+ detail = f"样本 {sample}/{LEGACY_WINDOW},判据不可判定"
311
+ else:
312
+ status = "pass" if len(legacy) <= LEGACY_THRESHOLD else "fail"
313
+ detail = (
314
+ f"最近 {sample} 个 change 中含 legacy plan-finalization.json 的有 "
315
+ f"{len(legacy)} 个(阈值 <= {LEGACY_THRESHOLD})"
316
+ )
317
+ return {
318
+ "id": "legacy_fallback",
319
+ "reading": f"{len(legacy)}/{sample}",
320
+ "threshold": f"<= {LEGACY_THRESHOLD}/{LEGACY_WINDOW}",
321
+ "sample": sample,
322
+ "status": status,
323
+ "detail": detail,
324
+ "legacy_changes": legacy,
325
+ }
326
+
327
+
328
+ # ---------------------------------------------------------------------------
329
+ # CLI
330
+ # ---------------------------------------------------------------------------
331
+
332
+
333
+ def collect_metrics(projects: list[Path]) -> dict[str, Any]:
334
+ criteria = [
335
+ _collect_first_try(projects),
336
+ _collect_evidence_closure(projects),
337
+ _collect_legacy_fallback(projects),
338
+ ]
339
+ overall = "pass"
340
+ if any(item["status"] == "fail" for item in criteria):
341
+ overall = "fail"
342
+ elif any(item["status"] == "indeterminate" for item in criteria):
343
+ overall = "indeterminate"
344
+ return {
345
+ "projects": [str(project) for project in projects],
346
+ "overall": overall,
347
+ "criteria": criteria,
348
+ }
349
+
350
+
351
+ def _print_text(report: dict[str, Any]) -> None:
352
+ print(f"projects: {', '.join(report['projects']) or '(none)'}")
353
+ print(f"overall: {report['overall']}")
354
+ for criterion in report["criteria"]:
355
+ print(
356
+ f"[{criterion['status']:>13}] {criterion['id']}: "
357
+ f"{criterion['reading']}({criterion['detail']})"
358
+ )
359
+
360
+
361
+ def main(argv: list[str] | None = None) -> int:
362
+ parser = argparse.ArgumentParser(
363
+ description="legacy plan finalize 采纳度度量(只读)",
364
+ )
365
+ parser.add_argument(
366
+ "--project",
367
+ action="append",
368
+ default=[],
369
+ help="项目根(含 .harness/changes/),可重复",
370
+ )
371
+ parser.add_argument("--json", action="store_true", help="JSON 输出")
372
+ parser.add_argument(
373
+ "--strict",
374
+ action="store_true",
375
+ help="任何 fail 或 indeterminate 时 exit 1",
376
+ )
377
+ args = parser.parse_args(argv)
378
+
379
+ projects = [Path(item).resolve() for item in args.project]
380
+ report = collect_metrics(projects)
381
+ if args.json:
382
+ print(json.dumps(report, ensure_ascii=False, indent=2))
383
+ else:
384
+ _print_text(report)
385
+ if args.strict and report["overall"] != "pass":
386
+ return 1
387
+ return 0
388
+
389
+
390
+ if __name__ == "__main__":
391
+ raise SystemExit(main())
@@ -3762,7 +3762,7 @@ def _risks_from_test_results(change_dir: Path) -> tuple[list[dict[str, Any]], li
3762
3762
  note = str(item.get("note") or item.get("message") or "").strip()
3763
3763
  risks.append(
3764
3764
  {
3765
- "phase": "test",
3765
+ "phase": "execute",
3766
3766
  "severity": "medium",
3767
3767
  "scenarioId": scenario_id,
3768
3768
  "status": status,
@@ -3771,7 +3771,7 @@ def _risks_from_test_results(change_dir: Path) -> tuple[list[dict[str, Any]], li
3771
3771
  )
3772
3772
  actions.append(
3773
3773
  {
3774
- "stage": "test",
3774
+ "stage": "execute",
3775
3775
  "status": status,
3776
3776
  "scenarioId": scenario_id,
3777
3777
  "action": "补齐该场景的完整验证或显式接受风险",
@@ -4028,15 +4028,17 @@ def _skill_calls_from_stages(stages: list[dict[str, Any]]) -> list[dict[str, Any
4028
4028
 
4029
4029
  def _phases_from_events_summary(summary: dict[str, Any]) -> dict[str, Any]:
4030
4030
  phases_out: dict[str, Any] = {}
4031
- for name in ("plan", "run", "test", "review", "submit", "archive"):
4031
+ for name in ("plan", "execute", "review", "submit", "archive"):
4032
4032
  phases_out[name] = {"duration_ms": None, "event_count": 0}
4033
4033
  for name, info in (summary.get("phases") or {}).items():
4034
- key = str(name).lower()
4034
+ # 旧事件里的 run/test 折叠进 execute(event_count 求和、duration 取后写值)。
4035
+ key = hp.resolve_phase_name(str(name).lower()) or str(name).lower()
4035
4036
  if key not in phases_out:
4036
4037
  phases_out[key] = {"duration_ms": None, "event_count": 0}
4037
4038
  phases_out[key] = {
4038
4039
  "duration_ms": info.get("duration_ms"),
4039
- "event_count": int(info.get("event_count") or 0),
4040
+ "event_count": int(info.get("event_count") or 0)
4041
+ + int(phases_out[key].get("event_count") or 0),
4040
4042
  }
4041
4043
  return phases_out
4042
4044
 
@@ -4192,20 +4194,23 @@ def _stage_status_from_sources(
4192
4194
  ) -> dict[str, str]:
4193
4195
  status = {
4194
4196
  "plan": "OK",
4195
- "run": "OK",
4196
- "test": "OK",
4197
+ "execute": "OK",
4197
4198
  "review": "ADVISORY",
4198
4199
  "submit": "OK",
4199
4200
  "archive": "OK",
4200
4201
  }
4201
4202
  projected_events = he.apply_event_corrections(events)
4203
+ execute_phase_end_seen = False
4202
4204
  for event in projected_events:
4203
4205
  if event.get("type") != "phase.end":
4204
4206
  continue
4205
- phase = str(event.get("phase") or "").lower()
4207
+ # 旧事件的 run/test phase.end 折叠进 execute,按事件序后写胜。
4208
+ phase = hp.resolve_phase_name(str(event.get("phase") or "").lower()) or ""
4206
4209
  raw = str(event.get("status") or "").upper()
4207
4210
  if phase not in status or not raw:
4208
4211
  continue
4212
+ if phase == "execute":
4213
+ execute_phase_end_seen = True
4209
4214
  if raw in {"PASS", "PASSED", "SUCCESS"}:
4210
4215
  raw = "OK"
4211
4216
  elif raw in {"FAILED", "ERROR"}:
@@ -4238,7 +4243,7 @@ def _stage_status_from_sources(
4238
4243
  continue
4239
4244
  if sev in {"", "info", "note", "informational"}:
4240
4245
  continue
4241
- phase = str(e.get("phase") or "").lower()
4246
+ phase = hp.resolve_phase_name(str(e.get("phase") or "").lower()) or ""
4242
4247
  if phase in status and sev in {"error", "fail", "failed", "critical"}:
4243
4248
  status[phase] = "FAIL"
4244
4249
  elif phase in status and sev in {"warn", "warning"} and status[phase] == "OK":
@@ -4248,17 +4253,21 @@ def _stage_status_from_sources(
4248
4253
  db = _ledger_db_compat(ledger)
4249
4254
  api_status = str(api.get("status") or "").upper()
4250
4255
  if api_status in {"FAIL", "FAILED", "ERROR"} or int(api.get("failed") or 0) > 0:
4251
- status["test"] = "FAIL"
4256
+ status["execute"] = "FAIL"
4252
4257
  elif api_status in {"BLOCKED", "BLOCKED_BY_ENV", "BLOCKED_BY_DBA"}:
4253
- status["test"] = api_status
4258
+ status["execute"] = api_status
4254
4259
  elif api_status == "USER_SKIPPED":
4255
- status["test"] = "USER_SKIPPED"
4260
+ status["execute"] = "USER_SKIPPED"
4256
4261
  elif db == "BLOCKED_BY_DBA":
4257
- status["test"] = "BLOCKED_BY_DBA"
4262
+ status["execute"] = "BLOCKED_BY_DBA"
4258
4263
  elif api.get("status") == "NOT_RUN" and not find_test_reports(change_dir):
4259
4264
  unit = _ledger_unit_tests(ledger)
4260
4265
  if unit.get("source") == "not-run" and unit.get("run", 0) == 0:
4261
- status["test"] = "NOT_RUN"
4266
+ # "无证据按未运行"只在事件侧也没有终态信号时成立:execute 组已有
4267
+ # phase.end(含旧 run/test)说明阶段跑过,缺账本/报告不该把它抹成
4268
+ # NOT_RUN——FAIL/BLOCKED 等有据分支不受影响。
4269
+ if not execute_phase_end_seen:
4270
+ status["execute"] = "NOT_RUN"
4262
4271
 
4263
4272
  if not review_evidence_present(change_dir, projected_events):
4264
4273
  status["review"] = "ADVISORY"
@@ -564,18 +564,36 @@ def _lease_expired(lease: dict[str, Any]) -> bool:
564
564
  return dt.datetime.now().astimezone() >= exp_dt
565
565
 
566
566
 
567
- def inspect_lease(project_root: Path, change_id: str) -> dict[str, Any] | None:
568
- """Return the current non-expired lease without mutating it."""
567
+ def inspect_lease_state(project_root: Path, change_id: str) -> dict[str, Any]:
568
+ """Read the lease without mutating it, keeping the three failure modes apart.
569
+
570
+ ``inspect_lease`` collapses absent / expired / corrupt into ``None``, so a
571
+ caller cannot tell "nobody ever held this" from "the phase outlived its
572
+ TTL". Those need different answers: an expired lease still carries the
573
+ runId that proves who owns the phase, a corrupt one proves nothing, and an
574
+ absent one may just mean the phase already closed.
575
+
576
+ state is one of ``active`` | ``expired`` | ``absent`` | ``corrupt``. The
577
+ lease dict is returned for ``active`` and ``expired``; ``None`` otherwise.
578
+ """
569
579
  path = _lease_path(project_root, change_id)
570
580
  if not path.is_file():
571
- return None
581
+ return {"state": "absent", "lease": None}
572
582
  try:
573
583
  lease = _read_json(path)
574
584
  except (OSError, json.JSONDecodeError):
575
- return None
576
- if not isinstance(lease, dict) or _lease_expired(lease):
577
- return None
578
- return lease
585
+ return {"state": "corrupt", "lease": None}
586
+ if not isinstance(lease, dict):
587
+ return {"state": "corrupt", "lease": None}
588
+ if _lease_expired(lease):
589
+ return {"state": "expired", "lease": lease}
590
+ return {"state": "active", "lease": lease}
591
+
592
+
593
+ def inspect_lease(project_root: Path, change_id: str) -> dict[str, Any] | None:
594
+ """Return the current non-expired lease without mutating it."""
595
+ state = inspect_lease_state(project_root, change_id)
596
+ return state["lease"] if state["state"] == "active" else None
579
597
 
580
598
 
581
599
  def _claim_lease_locked(