@hunter-harness/workflow-harness 0.2.86 → 0.3.1

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 (258) 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 -10
  9. package/harness/bundles/general/claude-code/harness-plan/checklist.md +2 -13
  10. package/harness/bundles/general/claude-code/harness-plan/reference.md +43 -81
  11. package/harness/bundles/general/claude-code/harness-pull/SKILL.md +1 -1
  12. package/harness/bundles/general/claude-code/harness-push/SKILL.md +1 -1
  13. package/harness/bundles/general/claude-code/harness-review/SKILL.md +3 -3
  14. package/harness/bundles/general/claude-code/harness-review/protocols.md +1 -1
  15. package/harness/bundles/general/claude-code/harness-run/SKILL.md +8 -2
  16. package/harness/bundles/general/claude-code/harness-submit/SKILL.md +2 -2
  17. package/harness/bundles/general/claude-code/harness-submit/checklist.md +1 -1
  18. package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
  19. package/harness/bundles/general/claude-code/harness-test/SKILL.md +11 -2
  20. package/harness/bundles/general/claude-code/scripts/harness_adoption_metrics.py +391 -0
  21. package/harness/bundles/general/claude-code/scripts/harness_archive.py +46 -15
  22. package/harness/bundles/general/claude-code/scripts/harness_context.py +94 -43
  23. package/harness/bundles/general/claude-code/scripts/harness_events.py +1 -1
  24. package/harness/bundles/general/claude-code/scripts/harness_events_sync.py +1 -0
  25. package/harness/bundles/general/claude-code/scripts/harness_fixback.py +10 -10
  26. package/harness/bundles/general/claude-code/scripts/harness_gate.py +92 -25
  27. package/harness/bundles/general/claude-code/scripts/harness_ledger.py +4 -2
  28. package/harness/bundles/general/claude-code/scripts/harness_paths.py +120 -5
  29. package/harness/bundles/general/claude-code/scripts/harness_phase.py +41 -17
  30. package/harness/bundles/general/claude-code/scripts/harness_plan_finalize.py +16 -873
  31. package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
  32. package/harness/bundles/general/codebuddy/contracts/workflow-policy.json +46 -17
  33. package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +2 -2
  34. package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +2 -2
  35. package/harness/bundles/general/codebuddy/harness-execute/SKILL.md +170 -0
  36. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +2 -2
  37. package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +2 -2
  38. package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +10 -10
  39. package/harness/bundles/general/codebuddy/harness-plan/checklist.md +2 -13
  40. package/harness/bundles/general/codebuddy/harness-plan/reference.md +43 -81
  41. package/harness/bundles/general/codebuddy/harness-pull/SKILL.md +1 -1
  42. package/harness/bundles/general/codebuddy/harness-push/SKILL.md +1 -1
  43. package/harness/bundles/general/codebuddy/harness-review/SKILL.md +3 -3
  44. package/harness/bundles/general/codebuddy/harness-review/protocols.md +1 -1
  45. package/harness/bundles/general/codebuddy/harness-run/SKILL.md +8 -2
  46. package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +2 -2
  47. package/harness/bundles/general/codebuddy/harness-submit/checklist.md +1 -1
  48. package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
  49. package/harness/bundles/general/codebuddy/harness-test/SKILL.md +11 -2
  50. package/harness/bundles/general/codebuddy/scripts/harness_adoption_metrics.py +391 -0
  51. package/harness/bundles/general/codebuddy/scripts/harness_archive.py +46 -15
  52. package/harness/bundles/general/codebuddy/scripts/harness_context.py +94 -43
  53. package/harness/bundles/general/codebuddy/scripts/harness_events.py +1 -1
  54. package/harness/bundles/general/codebuddy/scripts/harness_events_sync.py +1 -0
  55. package/harness/bundles/general/codebuddy/scripts/harness_fixback.py +10 -10
  56. package/harness/bundles/general/codebuddy/scripts/harness_gate.py +92 -25
  57. package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +4 -2
  58. package/harness/bundles/general/codebuddy/scripts/harness_paths.py +120 -5
  59. package/harness/bundles/general/codebuddy/scripts/harness_phase.py +41 -17
  60. package/harness/bundles/general/codebuddy/scripts/harness_plan_finalize.py +16 -873
  61. package/harness/bundles/general/codex/.harness-build.json +1 -1
  62. package/harness/bundles/general/codex/contracts/workflow-policy.json +46 -17
  63. package/harness/bundles/general/codex/harness-archive/SKILL.md +2 -2
  64. package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +2 -2
  65. package/harness/bundles/general/codex/harness-execute/SKILL.md +170 -0
  66. package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +2 -2
  67. package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +2 -2
  68. package/harness/bundles/general/codex/harness-plan/SKILL.md +10 -10
  69. package/harness/bundles/general/codex/harness-plan/checklist.md +2 -13
  70. package/harness/bundles/general/codex/harness-plan/reference.md +43 -81
  71. package/harness/bundles/general/codex/harness-pull/SKILL.md +1 -1
  72. package/harness/bundles/general/codex/harness-push/SKILL.md +1 -1
  73. package/harness/bundles/general/codex/harness-review/SKILL.md +3 -3
  74. package/harness/bundles/general/codex/harness-review/protocols.md +1 -1
  75. package/harness/bundles/general/codex/harness-run/SKILL.md +8 -2
  76. package/harness/bundles/general/codex/harness-submit/SKILL.md +2 -2
  77. package/harness/bundles/general/codex/harness-submit/checklist.md +1 -1
  78. package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
  79. package/harness/bundles/general/codex/harness-test/SKILL.md +11 -2
  80. package/harness/bundles/general/codex/scripts/harness_adoption_metrics.py +391 -0
  81. package/harness/bundles/general/codex/scripts/harness_archive.py +46 -15
  82. package/harness/bundles/general/codex/scripts/harness_context.py +94 -43
  83. package/harness/bundles/general/codex/scripts/harness_events.py +1 -1
  84. package/harness/bundles/general/codex/scripts/harness_events_sync.py +1 -0
  85. package/harness/bundles/general/codex/scripts/harness_fixback.py +10 -10
  86. package/harness/bundles/general/codex/scripts/harness_gate.py +92 -25
  87. package/harness/bundles/general/codex/scripts/harness_ledger.py +4 -2
  88. package/harness/bundles/general/codex/scripts/harness_paths.py +120 -5
  89. package/harness/bundles/general/codex/scripts/harness_phase.py +41 -17
  90. package/harness/bundles/general/codex/scripts/harness_plan_finalize.py +16 -873
  91. package/harness/bundles/general/cursor/.harness-build.json +1 -1
  92. package/harness/bundles/general/cursor/contracts/workflow-policy.json +46 -17
  93. package/harness/bundles/general/cursor/harness-archive/SKILL.md +2 -2
  94. package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +2 -2
  95. package/harness/bundles/general/cursor/harness-execute/SKILL.md +170 -0
  96. package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +2 -2
  97. package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +2 -2
  98. package/harness/bundles/general/cursor/harness-plan/SKILL.md +10 -10
  99. package/harness/bundles/general/cursor/harness-plan/checklist.md +2 -13
  100. package/harness/bundles/general/cursor/harness-plan/reference.md +43 -81
  101. package/harness/bundles/general/cursor/harness-pull/SKILL.md +1 -1
  102. package/harness/bundles/general/cursor/harness-push/SKILL.md +1 -1
  103. package/harness/bundles/general/cursor/harness-review/SKILL.md +3 -3
  104. package/harness/bundles/general/cursor/harness-review/protocols.md +1 -1
  105. package/harness/bundles/general/cursor/harness-run/SKILL.md +8 -2
  106. package/harness/bundles/general/cursor/harness-submit/SKILL.md +2 -2
  107. package/harness/bundles/general/cursor/harness-submit/checklist.md +1 -1
  108. package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
  109. package/harness/bundles/general/cursor/harness-test/SKILL.md +11 -2
  110. package/harness/bundles/general/cursor/scripts/harness_adoption_metrics.py +391 -0
  111. package/harness/bundles/general/cursor/scripts/harness_archive.py +46 -15
  112. package/harness/bundles/general/cursor/scripts/harness_context.py +94 -43
  113. package/harness/bundles/general/cursor/scripts/harness_events.py +1 -1
  114. package/harness/bundles/general/cursor/scripts/harness_events_sync.py +1 -0
  115. package/harness/bundles/general/cursor/scripts/harness_fixback.py +10 -10
  116. package/harness/bundles/general/cursor/scripts/harness_gate.py +92 -25
  117. package/harness/bundles/general/cursor/scripts/harness_ledger.py +4 -2
  118. package/harness/bundles/general/cursor/scripts/harness_paths.py +120 -5
  119. package/harness/bundles/general/cursor/scripts/harness_phase.py +41 -17
  120. package/harness/bundles/general/cursor/scripts/harness_plan_finalize.py +16 -873
  121. package/harness/bundles/java/claude-code/.harness-build.json +1 -1
  122. package/harness/bundles/java/claude-code/contracts/workflow-policy.json +46 -17
  123. package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +2 -2
  124. package/harness/bundles/java/claude-code/harness-archive/SKILL.md +2 -2
  125. package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +2 -2
  126. package/harness/bundles/java/claude-code/harness-execute/SKILL.md +189 -0
  127. package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +2 -2
  128. package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +2 -2
  129. package/harness/bundles/java/claude-code/harness-package/SKILL.md +2 -2
  130. package/harness/bundles/java/claude-code/harness-plan/SKILL.md +10 -10
  131. package/harness/bundles/java/claude-code/harness-plan/checklist.md +2 -13
  132. package/harness/bundles/java/claude-code/harness-plan/reference.md +43 -81
  133. package/harness/bundles/java/claude-code/harness-pull/SKILL.md +1 -1
  134. package/harness/bundles/java/claude-code/harness-push/SKILL.md +1 -1
  135. package/harness/bundles/java/claude-code/harness-review/SKILL.md +3 -3
  136. package/harness/bundles/java/claude-code/harness-review/protocols.md +1 -1
  137. package/harness/bundles/java/claude-code/harness-run/SKILL.md +8 -2
  138. package/harness/bundles/java/claude-code/harness-submit/SKILL.md +2 -2
  139. package/harness/bundles/java/claude-code/harness-submit/checklist.md +1 -1
  140. package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
  141. package/harness/bundles/java/claude-code/harness-test/SKILL.md +11 -2
  142. package/harness/bundles/java/claude-code/scripts/harness_adoption_metrics.py +391 -0
  143. package/harness/bundles/java/claude-code/scripts/harness_archive.py +46 -15
  144. package/harness/bundles/java/claude-code/scripts/harness_context.py +94 -43
  145. package/harness/bundles/java/claude-code/scripts/harness_events.py +1 -1
  146. package/harness/bundles/java/claude-code/scripts/harness_events_sync.py +1 -0
  147. package/harness/bundles/java/claude-code/scripts/harness_fixback.py +10 -10
  148. package/harness/bundles/java/claude-code/scripts/harness_gate.py +92 -25
  149. package/harness/bundles/java/claude-code/scripts/harness_ledger.py +4 -2
  150. package/harness/bundles/java/claude-code/scripts/harness_paths.py +120 -5
  151. package/harness/bundles/java/claude-code/scripts/harness_phase.py +41 -17
  152. package/harness/bundles/java/claude-code/scripts/harness_plan_finalize.py +16 -873
  153. package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
  154. package/harness/bundles/java/codebuddy/contracts/workflow-policy.json +46 -17
  155. package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +2 -2
  156. package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +2 -2
  157. package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +2 -2
  158. package/harness/bundles/java/codebuddy/harness-execute/SKILL.md +170 -0
  159. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +2 -2
  160. package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +2 -2
  161. package/harness/bundles/java/codebuddy/harness-package/SKILL.md +2 -2
  162. package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +10 -10
  163. package/harness/bundles/java/codebuddy/harness-plan/checklist.md +2 -13
  164. package/harness/bundles/java/codebuddy/harness-plan/reference.md +43 -81
  165. package/harness/bundles/java/codebuddy/harness-pull/SKILL.md +1 -1
  166. package/harness/bundles/java/codebuddy/harness-push/SKILL.md +1 -1
  167. package/harness/bundles/java/codebuddy/harness-review/SKILL.md +3 -3
  168. package/harness/bundles/java/codebuddy/harness-review/protocols.md +1 -1
  169. package/harness/bundles/java/codebuddy/harness-run/SKILL.md +8 -2
  170. package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +2 -2
  171. package/harness/bundles/java/codebuddy/harness-submit/checklist.md +1 -1
  172. package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
  173. package/harness/bundles/java/codebuddy/harness-test/SKILL.md +11 -2
  174. package/harness/bundles/java/codebuddy/scripts/harness_adoption_metrics.py +391 -0
  175. package/harness/bundles/java/codebuddy/scripts/harness_archive.py +46 -15
  176. package/harness/bundles/java/codebuddy/scripts/harness_context.py +94 -43
  177. package/harness/bundles/java/codebuddy/scripts/harness_events.py +1 -1
  178. package/harness/bundles/java/codebuddy/scripts/harness_events_sync.py +1 -0
  179. package/harness/bundles/java/codebuddy/scripts/harness_fixback.py +10 -10
  180. package/harness/bundles/java/codebuddy/scripts/harness_gate.py +92 -25
  181. package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +4 -2
  182. package/harness/bundles/java/codebuddy/scripts/harness_paths.py +120 -5
  183. package/harness/bundles/java/codebuddy/scripts/harness_phase.py +41 -17
  184. package/harness/bundles/java/codebuddy/scripts/harness_plan_finalize.py +16 -873
  185. package/harness/bundles/java/codex/.harness-build.json +1 -1
  186. package/harness/bundles/java/codex/contracts/workflow-policy.json +46 -17
  187. package/harness/bundles/java/codex/harness-apidoc/SKILL.md +2 -2
  188. package/harness/bundles/java/codex/harness-archive/SKILL.md +2 -2
  189. package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +2 -2
  190. package/harness/bundles/java/codex/harness-execute/SKILL.md +170 -0
  191. package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +2 -2
  192. package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +2 -2
  193. package/harness/bundles/java/codex/harness-package/SKILL.md +2 -2
  194. package/harness/bundles/java/codex/harness-plan/SKILL.md +10 -10
  195. package/harness/bundles/java/codex/harness-plan/checklist.md +2 -13
  196. package/harness/bundles/java/codex/harness-plan/reference.md +43 -81
  197. package/harness/bundles/java/codex/harness-pull/SKILL.md +1 -1
  198. package/harness/bundles/java/codex/harness-push/SKILL.md +1 -1
  199. package/harness/bundles/java/codex/harness-review/SKILL.md +3 -3
  200. package/harness/bundles/java/codex/harness-review/protocols.md +1 -1
  201. package/harness/bundles/java/codex/harness-run/SKILL.md +8 -2
  202. package/harness/bundles/java/codex/harness-submit/SKILL.md +2 -2
  203. package/harness/bundles/java/codex/harness-submit/checklist.md +1 -1
  204. package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
  205. package/harness/bundles/java/codex/harness-test/SKILL.md +11 -2
  206. package/harness/bundles/java/codex/scripts/harness_adoption_metrics.py +391 -0
  207. package/harness/bundles/java/codex/scripts/harness_archive.py +46 -15
  208. package/harness/bundles/java/codex/scripts/harness_context.py +94 -43
  209. package/harness/bundles/java/codex/scripts/harness_events.py +1 -1
  210. package/harness/bundles/java/codex/scripts/harness_events_sync.py +1 -0
  211. package/harness/bundles/java/codex/scripts/harness_fixback.py +10 -10
  212. package/harness/bundles/java/codex/scripts/harness_gate.py +92 -25
  213. package/harness/bundles/java/codex/scripts/harness_ledger.py +4 -2
  214. package/harness/bundles/java/codex/scripts/harness_paths.py +120 -5
  215. package/harness/bundles/java/codex/scripts/harness_phase.py +41 -17
  216. package/harness/bundles/java/codex/scripts/harness_plan_finalize.py +16 -873
  217. package/harness/bundles/java/cursor/.harness-build.json +1 -1
  218. package/harness/bundles/java/cursor/contracts/workflow-policy.json +46 -17
  219. package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +2 -2
  220. package/harness/bundles/java/cursor/harness-archive/SKILL.md +2 -2
  221. package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +2 -2
  222. package/harness/bundles/java/cursor/harness-execute/SKILL.md +170 -0
  223. package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +2 -2
  224. package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +2 -2
  225. package/harness/bundles/java/cursor/harness-package/SKILL.md +2 -2
  226. package/harness/bundles/java/cursor/harness-plan/SKILL.md +10 -10
  227. package/harness/bundles/java/cursor/harness-plan/checklist.md +2 -13
  228. package/harness/bundles/java/cursor/harness-plan/reference.md +43 -81
  229. package/harness/bundles/java/cursor/harness-pull/SKILL.md +1 -1
  230. package/harness/bundles/java/cursor/harness-push/SKILL.md +1 -1
  231. package/harness/bundles/java/cursor/harness-review/SKILL.md +3 -3
  232. package/harness/bundles/java/cursor/harness-review/protocols.md +1 -1
  233. package/harness/bundles/java/cursor/harness-run/SKILL.md +8 -2
  234. package/harness/bundles/java/cursor/harness-submit/SKILL.md +2 -2
  235. package/harness/bundles/java/cursor/harness-submit/checklist.md +1 -1
  236. package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
  237. package/harness/bundles/java/cursor/harness-test/SKILL.md +11 -2
  238. package/harness/bundles/java/cursor/scripts/harness_adoption_metrics.py +391 -0
  239. package/harness/bundles/java/cursor/scripts/harness_archive.py +46 -15
  240. package/harness/bundles/java/cursor/scripts/harness_context.py +94 -43
  241. package/harness/bundles/java/cursor/scripts/harness_events.py +1 -1
  242. package/harness/bundles/java/cursor/scripts/harness_events_sync.py +1 -0
  243. package/harness/bundles/java/cursor/scripts/harness_fixback.py +10 -10
  244. package/harness/bundles/java/cursor/scripts/harness_gate.py +92 -25
  245. package/harness/bundles/java/cursor/scripts/harness_ledger.py +4 -2
  246. package/harness/bundles/java/cursor/scripts/harness_paths.py +120 -5
  247. package/harness/bundles/java/cursor/scripts/harness_phase.py +41 -17
  248. package/harness/bundles/java/cursor/scripts/harness_plan_finalize.py +16 -873
  249. package/harness/manifests/general/claude-code.json +36 -28
  250. package/harness/manifests/general/codebuddy.json +36 -28
  251. package/harness/manifests/general/codex.json +36 -28
  252. package/harness/manifests/general/cursor.json +36 -28
  253. package/harness/manifests/java/claude-code.json +38 -30
  254. package/harness/manifests/java/codebuddy.json +38 -30
  255. package/harness/manifests/java/codex.json +38 -30
  256. package/harness/manifests/java/cursor.json +38 -30
  257. package/hunter-workflow-family.json +2 -2
  258. package/package.json +1 -1
@@ -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,28 @@ 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
4204
+ # 服务端 CLI schema 2.3 要求全阶段键集 {plan, run, test, review, submit,
4205
+ # archive},而 2026-08 起事件侧 run+test 已合并为 execute。仍带 run/test
4206
+ # 原名的旧事件在折叠进 execute 之前单独留一份,供归档时拆分还原。
4207
+ raw_run_test: dict[str, str] = {}
4202
4208
  for event in projected_events:
4203
4209
  if event.get("type") != "phase.end":
4204
4210
  continue
4205
- phase = str(event.get("phase") or "").lower()
4211
+ # 旧事件的 run/test phase.end 折叠进 execute,按事件序后写胜。
4212
+ raw_phase = str(event.get("phase") or "").strip().lower()
4213
+ phase = hp.resolve_phase_name(raw_phase) or ""
4206
4214
  raw = str(event.get("status") or "").upper()
4207
4215
  if phase not in status or not raw:
4208
4216
  continue
4217
+ if phase == "execute":
4218
+ execute_phase_end_seen = True
4209
4219
  if raw in {"PASS", "PASSED", "SUCCESS"}:
4210
4220
  raw = "OK"
4211
4221
  elif raw in {"FAILED", "ERROR"}:
@@ -4213,6 +4223,8 @@ def _stage_status_from_sources(
4213
4223
  elif raw in {"SKIP", "SKIPPED"}:
4214
4224
  raw = "USER_SKIPPED"
4215
4225
  status[phase] = raw
4226
+ if raw_phase in {"run", "test"}:
4227
+ raw_run_test[raw_phase] = raw
4216
4228
  # Issues in events can downgrade. H-14: informational / hygiene / agent-preflight
4217
4229
  # notes must not downgrade an already-OK phase.end.
4218
4230
  _informational_markers = (
@@ -4238,7 +4250,7 @@ def _stage_status_from_sources(
4238
4250
  continue
4239
4251
  if sev in {"", "info", "note", "informational"}:
4240
4252
  continue
4241
- phase = str(e.get("phase") or "").lower()
4253
+ phase = hp.resolve_phase_name(str(e.get("phase") or "").lower()) or ""
4242
4254
  if phase in status and sev in {"error", "fail", "failed", "critical"}:
4243
4255
  status[phase] = "FAIL"
4244
4256
  elif phase in status and sev in {"warn", "warning"} and status[phase] == "OK":
@@ -4248,22 +4260,37 @@ def _stage_status_from_sources(
4248
4260
  db = _ledger_db_compat(ledger)
4249
4261
  api_status = str(api.get("status") or "").upper()
4250
4262
  if api_status in {"FAIL", "FAILED", "ERROR"} or int(api.get("failed") or 0) > 0:
4251
- status["test"] = "FAIL"
4263
+ status["execute"] = "FAIL"
4252
4264
  elif api_status in {"BLOCKED", "BLOCKED_BY_ENV", "BLOCKED_BY_DBA"}:
4253
- status["test"] = api_status
4265
+ status["execute"] = api_status
4254
4266
  elif api_status == "USER_SKIPPED":
4255
- status["test"] = "USER_SKIPPED"
4267
+ status["execute"] = "USER_SKIPPED"
4256
4268
  elif db == "BLOCKED_BY_DBA":
4257
- status["test"] = "BLOCKED_BY_DBA"
4269
+ status["execute"] = "BLOCKED_BY_DBA"
4258
4270
  elif api.get("status") == "NOT_RUN" and not find_test_reports(change_dir):
4259
4271
  unit = _ledger_unit_tests(ledger)
4260
4272
  if unit.get("source") == "not-run" and unit.get("run", 0) == 0:
4261
- status["test"] = "NOT_RUN"
4273
+ # "无证据按未运行"只在事件侧也没有终态信号时成立:execute 组已有
4274
+ # phase.end(含旧 run/test)说明阶段跑过,缺账本/报告不该把它抹成
4275
+ # NOT_RUN——FAIL/BLOCKED 等有据分支不受影响。
4276
+ if not execute_phase_end_seen:
4277
+ status["execute"] = "NOT_RUN"
4262
4278
 
4263
4279
  if not review_evidence_present(change_dir, projected_events):
4264
4280
  status["review"] = "ADVISORY"
4265
4281
 
4266
- return status
4282
+ # 服务端 schema 2.3 是 passthrough:execute 键保留供既有消费方使用,
4283
+ # 同时补上 run/test 满足必需键。旧事件带 run/test 原名时按原名拆分,
4284
+ # 否则镜像 execute 聚合值(合并事件流里没有更细的信号可拆)。
4285
+ return {
4286
+ "plan": status["plan"],
4287
+ "execute": status["execute"],
4288
+ "run": raw_run_test.get("run", status["execute"]),
4289
+ "test": raw_run_test.get("test", status["execute"]),
4290
+ "review": status["review"],
4291
+ "submit": status["submit"],
4292
+ "archive": status["archive"],
4293
+ }
4267
4294
 
4268
4295
 
4269
4296
  def _compute_final_status(
@@ -5656,7 +5683,11 @@ def collect_summary_data(
5656
5683
  scenario_risks, scenario_actions = _risks_from_test_results(change_dir)
5657
5684
  data["knownRisks"] = known_risks + scenario_risks
5658
5685
  data["manualActions"] = scenario_actions
5686
+ execute_status = (data.get("stageStatus") or {}).get("execute")
5659
5687
  for name, value in (data.get("stageStatus") or {}).items():
5688
+ # run/test 镜像 execute 聚合值时不重复生成同一条人工动作。
5689
+ if name in {"run", "test"} and value == execute_status:
5690
+ continue
5660
5691
  if value in {"BLOCKED", "BLOCKED_BY_ENV", "BLOCKED_BY_DBA", "NOT_RUN", "USER_SKIPPED"}:
5661
5692
  data["manualActions"].append({
5662
5693
  "stage": name,