@hunter-harness/workflow-harness 0.2.54 → 0.2.56

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 (262) hide show
  1. package/harness/bundles/general/claude-code/.harness-build.json +1 -1
  2. package/harness/bundles/general/claude-code/README.md +3 -3
  3. package/harness/bundles/general/claude-code/contracts/workflow-policy.json +1 -1
  4. package/harness/bundles/general/claude-code/harness-archive/SKILL.md +21 -33
  5. package/harness/bundles/general/claude-code/harness-archive/checklist.md +6 -10
  6. package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +1 -1
  7. package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  8. package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
  9. package/harness/bundles/general/claude-code/harness-plan/SKILL.md +7 -3
  10. package/harness/bundles/general/claude-code/harness-plan/checklist.md +2 -1
  11. package/harness/bundles/general/claude-code/harness-review/SKILL.md +8 -4
  12. package/harness/bundles/general/claude-code/harness-run/SKILL.md +9 -7
  13. package/harness/bundles/general/claude-code/harness-run/checklist.md +1 -1
  14. package/harness/bundles/general/claude-code/harness-run/reference.md +3 -3
  15. package/harness/bundles/general/claude-code/harness-submit/SKILL.md +6 -6
  16. package/harness/bundles/general/claude-code/harness-submit/checklist.md +1 -1
  17. package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
  18. package/harness/bundles/general/claude-code/harness-test/SKILL.md +8 -6
  19. package/harness/bundles/general/claude-code/harness-test/checklist.md +1 -1
  20. package/harness/bundles/general/claude-code/harness-test/reference.md +2 -2
  21. package/harness/bundles/general/claude-code/protocols/ledger-protocol.md +3 -3
  22. package/harness/bundles/general/claude-code/scripts/harness_archive.py +322 -74
  23. package/harness/bundles/general/claude-code/scripts/harness_context.py +236 -63
  24. package/harness/bundles/general/claude-code/scripts/harness_events.py +86 -5
  25. package/harness/bundles/general/claude-code/scripts/harness_events_sync.py +201 -17
  26. package/harness/bundles/general/claude-code/scripts/harness_fixback.py +189 -1
  27. package/harness/bundles/general/claude-code/scripts/harness_gate.py +353 -3
  28. package/harness/bundles/general/claude-code/scripts/harness_ledger.py +195 -11
  29. package/harness/bundles/general/claude-code/scripts/harness_plan_finalize.py +82 -0
  30. package/harness/bundles/general/claude-code/scripts/harness_test_guard.py +37 -44
  31. package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
  32. package/harness/bundles/general/codebuddy/README.md +3 -3
  33. package/harness/bundles/general/codebuddy/contracts/workflow-policy.json +1 -1
  34. package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +21 -33
  35. package/harness/bundles/general/codebuddy/harness-archive/checklist.md +6 -10
  36. package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
  37. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  38. package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  39. package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +7 -3
  40. package/harness/bundles/general/codebuddy/harness-plan/checklist.md +2 -1
  41. package/harness/bundles/general/codebuddy/harness-review/SKILL.md +8 -4
  42. package/harness/bundles/general/codebuddy/harness-run/SKILL.md +9 -7
  43. package/harness/bundles/general/codebuddy/harness-run/checklist.md +1 -1
  44. package/harness/bundles/general/codebuddy/harness-run/reference.md +3 -3
  45. package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +6 -6
  46. package/harness/bundles/general/codebuddy/harness-submit/checklist.md +1 -1
  47. package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
  48. package/harness/bundles/general/codebuddy/harness-test/SKILL.md +8 -6
  49. package/harness/bundles/general/codebuddy/harness-test/checklist.md +1 -1
  50. package/harness/bundles/general/codebuddy/harness-test/reference.md +2 -2
  51. package/harness/bundles/general/codebuddy/protocols/ledger-protocol.md +3 -3
  52. package/harness/bundles/general/codebuddy/scripts/harness_archive.py +322 -74
  53. package/harness/bundles/general/codebuddy/scripts/harness_context.py +236 -63
  54. package/harness/bundles/general/codebuddy/scripts/harness_events.py +86 -5
  55. package/harness/bundles/general/codebuddy/scripts/harness_events_sync.py +201 -17
  56. package/harness/bundles/general/codebuddy/scripts/harness_fixback.py +189 -1
  57. package/harness/bundles/general/codebuddy/scripts/harness_gate.py +353 -3
  58. package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +195 -11
  59. package/harness/bundles/general/codebuddy/scripts/harness_plan_finalize.py +82 -0
  60. package/harness/bundles/general/codebuddy/scripts/harness_test_guard.py +37 -44
  61. package/harness/bundles/general/codex/.harness-build.json +1 -1
  62. package/harness/bundles/general/codex/README.md +3 -3
  63. package/harness/bundles/general/codex/contracts/workflow-policy.json +1 -1
  64. package/harness/bundles/general/codex/harness-archive/SKILL.md +21 -33
  65. package/harness/bundles/general/codex/harness-archive/checklist.md +6 -10
  66. package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
  67. package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
  68. package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
  69. package/harness/bundles/general/codex/harness-plan/SKILL.md +7 -3
  70. package/harness/bundles/general/codex/harness-plan/checklist.md +2 -1
  71. package/harness/bundles/general/codex/harness-review/SKILL.md +4 -4
  72. package/harness/bundles/general/codex/harness-run/SKILL.md +9 -7
  73. package/harness/bundles/general/codex/harness-run/checklist.md +1 -1
  74. package/harness/bundles/general/codex/harness-run/reference.md +3 -3
  75. package/harness/bundles/general/codex/harness-submit/SKILL.md +6 -6
  76. package/harness/bundles/general/codex/harness-submit/checklist.md +1 -1
  77. package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
  78. package/harness/bundles/general/codex/harness-test/SKILL.md +8 -6
  79. package/harness/bundles/general/codex/harness-test/checklist.md +1 -1
  80. package/harness/bundles/general/codex/harness-test/reference.md +2 -2
  81. package/harness/bundles/general/codex/protocols/ledger-protocol.md +3 -3
  82. package/harness/bundles/general/codex/scripts/harness_archive.py +322 -74
  83. package/harness/bundles/general/codex/scripts/harness_context.py +236 -63
  84. package/harness/bundles/general/codex/scripts/harness_events.py +86 -5
  85. package/harness/bundles/general/codex/scripts/harness_events_sync.py +201 -17
  86. package/harness/bundles/general/codex/scripts/harness_fixback.py +189 -1
  87. package/harness/bundles/general/codex/scripts/harness_gate.py +353 -3
  88. package/harness/bundles/general/codex/scripts/harness_ledger.py +195 -11
  89. package/harness/bundles/general/codex/scripts/harness_plan_finalize.py +82 -0
  90. package/harness/bundles/general/codex/scripts/harness_test_guard.py +37 -44
  91. package/harness/bundles/general/cursor/.harness-build.json +1 -1
  92. package/harness/bundles/general/cursor/README.md +3 -3
  93. package/harness/bundles/general/cursor/contracts/workflow-policy.json +1 -1
  94. package/harness/bundles/general/cursor/harness-archive/SKILL.md +21 -33
  95. package/harness/bundles/general/cursor/harness-archive/checklist.md +6 -10
  96. package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
  97. package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  98. package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
  99. package/harness/bundles/general/cursor/harness-plan/SKILL.md +7 -3
  100. package/harness/bundles/general/cursor/harness-plan/checklist.md +2 -1
  101. package/harness/bundles/general/cursor/harness-review/SKILL.md +4 -4
  102. package/harness/bundles/general/cursor/harness-run/SKILL.md +9 -7
  103. package/harness/bundles/general/cursor/harness-run/checklist.md +1 -1
  104. package/harness/bundles/general/cursor/harness-run/reference.md +3 -3
  105. package/harness/bundles/general/cursor/harness-submit/SKILL.md +6 -6
  106. package/harness/bundles/general/cursor/harness-submit/checklist.md +1 -1
  107. package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
  108. package/harness/bundles/general/cursor/harness-test/SKILL.md +8 -6
  109. package/harness/bundles/general/cursor/harness-test/checklist.md +1 -1
  110. package/harness/bundles/general/cursor/harness-test/reference.md +2 -2
  111. package/harness/bundles/general/cursor/protocols/ledger-protocol.md +3 -3
  112. package/harness/bundles/general/cursor/scripts/harness_archive.py +322 -74
  113. package/harness/bundles/general/cursor/scripts/harness_context.py +236 -63
  114. package/harness/bundles/general/cursor/scripts/harness_events.py +86 -5
  115. package/harness/bundles/general/cursor/scripts/harness_events_sync.py +201 -17
  116. package/harness/bundles/general/cursor/scripts/harness_fixback.py +189 -1
  117. package/harness/bundles/general/cursor/scripts/harness_gate.py +353 -3
  118. package/harness/bundles/general/cursor/scripts/harness_ledger.py +195 -11
  119. package/harness/bundles/general/cursor/scripts/harness_plan_finalize.py +82 -0
  120. package/harness/bundles/general/cursor/scripts/harness_test_guard.py +37 -44
  121. package/harness/bundles/java/claude-code/.harness-build.json +1 -1
  122. package/harness/bundles/java/claude-code/README.md +3 -3
  123. package/harness/bundles/java/claude-code/contracts/workflow-policy.json +1 -1
  124. package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
  125. package/harness/bundles/java/claude-code/harness-archive/SKILL.md +21 -33
  126. package/harness/bundles/java/claude-code/harness-archive/checklist.md +6 -10
  127. package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
  128. package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  129. package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
  130. package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
  131. package/harness/bundles/java/claude-code/harness-package/checklist.md +1 -1
  132. package/harness/bundles/java/claude-code/harness-plan/SKILL.md +7 -3
  133. package/harness/bundles/java/claude-code/harness-plan/checklist.md +2 -1
  134. package/harness/bundles/java/claude-code/harness-review/SKILL.md +8 -4
  135. package/harness/bundles/java/claude-code/harness-run/SKILL.md +9 -7
  136. package/harness/bundles/java/claude-code/harness-run/checklist.md +1 -1
  137. package/harness/bundles/java/claude-code/harness-run/reference.md +2 -2
  138. package/harness/bundles/java/claude-code/harness-submit/SKILL.md +6 -6
  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 +8 -6
  142. package/harness/bundles/java/claude-code/harness-test/checklist.md +1 -1
  143. package/harness/bundles/java/claude-code/harness-test/reference.md +1 -1
  144. package/harness/bundles/java/claude-code/protocols/ledger-protocol.md +3 -3
  145. package/harness/bundles/java/claude-code/scripts/harness_archive.py +322 -74
  146. package/harness/bundles/java/claude-code/scripts/harness_context.py +236 -63
  147. package/harness/bundles/java/claude-code/scripts/harness_events.py +86 -5
  148. package/harness/bundles/java/claude-code/scripts/harness_events_sync.py +201 -17
  149. package/harness/bundles/java/claude-code/scripts/harness_fixback.py +189 -1
  150. package/harness/bundles/java/claude-code/scripts/harness_gate.py +353 -3
  151. package/harness/bundles/java/claude-code/scripts/harness_ledger.py +195 -11
  152. package/harness/bundles/java/claude-code/scripts/harness_plan_finalize.py +82 -0
  153. package/harness/bundles/java/claude-code/scripts/harness_test_guard.py +37 -44
  154. package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
  155. package/harness/bundles/java/codebuddy/README.md +3 -3
  156. package/harness/bundles/java/codebuddy/contracts/workflow-policy.json +1 -1
  157. package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
  158. package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +21 -33
  159. package/harness/bundles/java/codebuddy/harness-archive/checklist.md +6 -10
  160. package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
  161. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  162. package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  163. package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
  164. package/harness/bundles/java/codebuddy/harness-package/checklist.md +1 -1
  165. package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +7 -3
  166. package/harness/bundles/java/codebuddy/harness-plan/checklist.md +2 -1
  167. package/harness/bundles/java/codebuddy/harness-review/SKILL.md +8 -4
  168. package/harness/bundles/java/codebuddy/harness-run/SKILL.md +9 -7
  169. package/harness/bundles/java/codebuddy/harness-run/checklist.md +1 -1
  170. package/harness/bundles/java/codebuddy/harness-run/reference.md +2 -2
  171. package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +6 -6
  172. package/harness/bundles/java/codebuddy/harness-submit/checklist.md +1 -1
  173. package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
  174. package/harness/bundles/java/codebuddy/harness-test/SKILL.md +8 -6
  175. package/harness/bundles/java/codebuddy/harness-test/checklist.md +1 -1
  176. package/harness/bundles/java/codebuddy/harness-test/reference.md +1 -1
  177. package/harness/bundles/java/codebuddy/protocols/ledger-protocol.md +3 -3
  178. package/harness/bundles/java/codebuddy/scripts/harness_archive.py +322 -74
  179. package/harness/bundles/java/codebuddy/scripts/harness_context.py +236 -63
  180. package/harness/bundles/java/codebuddy/scripts/harness_events.py +86 -5
  181. package/harness/bundles/java/codebuddy/scripts/harness_events_sync.py +201 -17
  182. package/harness/bundles/java/codebuddy/scripts/harness_fixback.py +189 -1
  183. package/harness/bundles/java/codebuddy/scripts/harness_gate.py +353 -3
  184. package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +195 -11
  185. package/harness/bundles/java/codebuddy/scripts/harness_plan_finalize.py +82 -0
  186. package/harness/bundles/java/codebuddy/scripts/harness_test_guard.py +37 -44
  187. package/harness/bundles/java/codex/.harness-build.json +1 -1
  188. package/harness/bundles/java/codex/README.md +3 -3
  189. package/harness/bundles/java/codex/contracts/workflow-policy.json +1 -1
  190. package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
  191. package/harness/bundles/java/codex/harness-archive/SKILL.md +21 -33
  192. package/harness/bundles/java/codex/harness-archive/checklist.md +6 -10
  193. package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
  194. package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
  195. package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
  196. package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
  197. package/harness/bundles/java/codex/harness-package/checklist.md +1 -1
  198. package/harness/bundles/java/codex/harness-plan/SKILL.md +7 -3
  199. package/harness/bundles/java/codex/harness-plan/checklist.md +2 -1
  200. package/harness/bundles/java/codex/harness-review/SKILL.md +4 -4
  201. package/harness/bundles/java/codex/harness-run/SKILL.md +9 -7
  202. package/harness/bundles/java/codex/harness-run/checklist.md +1 -1
  203. package/harness/bundles/java/codex/harness-run/reference.md +2 -2
  204. package/harness/bundles/java/codex/harness-submit/SKILL.md +6 -6
  205. package/harness/bundles/java/codex/harness-submit/checklist.md +1 -1
  206. package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
  207. package/harness/bundles/java/codex/harness-test/SKILL.md +8 -6
  208. package/harness/bundles/java/codex/harness-test/checklist.md +1 -1
  209. package/harness/bundles/java/codex/harness-test/reference.md +1 -1
  210. package/harness/bundles/java/codex/protocols/ledger-protocol.md +3 -3
  211. package/harness/bundles/java/codex/scripts/harness_archive.py +322 -74
  212. package/harness/bundles/java/codex/scripts/harness_context.py +236 -63
  213. package/harness/bundles/java/codex/scripts/harness_events.py +86 -5
  214. package/harness/bundles/java/codex/scripts/harness_events_sync.py +201 -17
  215. package/harness/bundles/java/codex/scripts/harness_fixback.py +189 -1
  216. package/harness/bundles/java/codex/scripts/harness_gate.py +353 -3
  217. package/harness/bundles/java/codex/scripts/harness_ledger.py +195 -11
  218. package/harness/bundles/java/codex/scripts/harness_plan_finalize.py +82 -0
  219. package/harness/bundles/java/codex/scripts/harness_test_guard.py +37 -44
  220. package/harness/bundles/java/cursor/.harness-build.json +1 -1
  221. package/harness/bundles/java/cursor/README.md +3 -3
  222. package/harness/bundles/java/cursor/contracts/workflow-policy.json +1 -1
  223. package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
  224. package/harness/bundles/java/cursor/harness-archive/SKILL.md +21 -33
  225. package/harness/bundles/java/cursor/harness-archive/checklist.md +6 -10
  226. package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
  227. package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  228. package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
  229. package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
  230. package/harness/bundles/java/cursor/harness-package/checklist.md +1 -1
  231. package/harness/bundles/java/cursor/harness-plan/SKILL.md +7 -3
  232. package/harness/bundles/java/cursor/harness-plan/checklist.md +2 -1
  233. package/harness/bundles/java/cursor/harness-review/SKILL.md +4 -4
  234. package/harness/bundles/java/cursor/harness-run/SKILL.md +9 -7
  235. package/harness/bundles/java/cursor/harness-run/checklist.md +1 -1
  236. package/harness/bundles/java/cursor/harness-run/reference.md +2 -2
  237. package/harness/bundles/java/cursor/harness-submit/SKILL.md +6 -6
  238. package/harness/bundles/java/cursor/harness-submit/checklist.md +1 -1
  239. package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
  240. package/harness/bundles/java/cursor/harness-test/SKILL.md +8 -6
  241. package/harness/bundles/java/cursor/harness-test/checklist.md +1 -1
  242. package/harness/bundles/java/cursor/harness-test/reference.md +1 -1
  243. package/harness/bundles/java/cursor/protocols/ledger-protocol.md +3 -3
  244. package/harness/bundles/java/cursor/scripts/harness_archive.py +322 -74
  245. package/harness/bundles/java/cursor/scripts/harness_context.py +236 -63
  246. package/harness/bundles/java/cursor/scripts/harness_events.py +86 -5
  247. package/harness/bundles/java/cursor/scripts/harness_events_sync.py +201 -17
  248. package/harness/bundles/java/cursor/scripts/harness_fixback.py +189 -1
  249. package/harness/bundles/java/cursor/scripts/harness_gate.py +353 -3
  250. package/harness/bundles/java/cursor/scripts/harness_ledger.py +195 -11
  251. package/harness/bundles/java/cursor/scripts/harness_plan_finalize.py +82 -0
  252. package/harness/bundles/java/cursor/scripts/harness_test_guard.py +37 -44
  253. package/harness/manifests/general/claude-code.json +32 -32
  254. package/harness/manifests/general/codebuddy.json +32 -32
  255. package/harness/manifests/general/codex.json +32 -32
  256. package/harness/manifests/general/cursor.json +32 -32
  257. package/harness/manifests/java/claude-code.json +35 -35
  258. package/harness/manifests/java/codebuddy.json +35 -35
  259. package/harness/manifests/java/codex.json +35 -35
  260. package/harness/manifests/java/cursor.json +35 -35
  261. package/hunter-workflow-family.json +5 -4
  262. package/package.json +1 -1
@@ -30,6 +30,7 @@ if str(SCRIPTS_DIR) not in sys.path:
30
30
  sys.path.insert(0, str(SCRIPTS_DIR))
31
31
 
32
32
  import harness_change as hc # noqa: E402
33
+ import harness_context as hctx # noqa: E402
33
34
  import harness_events as he # noqa: E402
34
35
  import harness_events_sync as hes # noqa: E402
35
36
  import harness_ledger as hl # noqa: E402
@@ -692,11 +693,12 @@ def validate_ledger_for_phase_close(
692
693
  try:
693
694
  current_repository = hp.repository_identity(root)
694
695
  current_ownership = hl.ownership_hash(contract)
695
- current_diff = hl.compute_ownership_diff(
696
+ current_diff_detail = hl.compute_ownership_diff(
696
697
  root,
697
698
  base=str(ledger["baseCommit"]),
698
699
  change_dir=change_dir,
699
- )["diffHash"]
700
+ )
701
+ current_diff = current_diff_detail["diffHash"]
700
702
  current_head = _git_text(root, "rev-parse", "--verify", "HEAD")
701
703
  except (OSError, ValueError, RuntimeError) as exc:
702
704
  return {
@@ -726,6 +728,18 @@ def validate_ledger_for_phase_close(
726
728
  "storedHead": ledger.get("currentHead"),
727
729
  "currentHead": current_head,
728
730
  "ledgerPath": str(ledger_path) if ledger_path else None,
731
+ "pathSummary": {
732
+ "ownedFiles": list(current_diff_detail.get("files") or [])[:20],
733
+ "foreignPaths": list(current_diff_detail.get("foreignPaths") or [])[:20],
734
+ "ownedFileCount": current_diff_detail.get("ownedFileCount", 0),
735
+ "excludedRuntimeCount": current_diff_detail.get(
736
+ "excludedRuntimeCount", 0
737
+ ),
738
+ },
739
+ "recoveryAction": (
740
+ "先确认新增、删除或越界路径是否属于当前变更;更新所有权后,"
741
+ "只重新执行受影响的验证,再原样重试 close。"
742
+ ),
729
743
  }
730
744
 
731
745
  problems: list[dict[str, Any]] = []
@@ -1602,6 +1616,18 @@ def append_phase_event(
1602
1616
  auto_sealed: list[dict[str, Any]] = []
1603
1617
  with he.event_file_lock(lock_path):
1604
1618
  existing = he.load_events(events_file)
1619
+ phase_attempts = he.split_phase_attempts(
1620
+ [event for event in existing if event.get("phase") == phase]
1621
+ )
1622
+ latest_attempt = max(
1623
+ (
1624
+ int(item.get("attempt"))
1625
+ for item in phase_attempts
1626
+ if isinstance(item.get("attempt"), int)
1627
+ ),
1628
+ default=0,
1629
+ )
1630
+ event_attempt = latest_attempt + 1 if type_ == "phase.start" else latest_attempt or 1
1605
1631
  args = argparse.Namespace(
1606
1632
  phase=phase,
1607
1633
  type=type_,
@@ -1619,12 +1645,18 @@ def append_phase_event(
1619
1645
  decision=None,
1620
1646
  reason=None,
1621
1647
  run_id=run_id,
1622
- attempt=None,
1648
+ attempt=event_attempt,
1623
1649
  executor_tool=executor_tool,
1624
1650
  executor_agent=executor_agent,
1625
1651
  executor_model=executor_model,
1626
1652
  handoff_from_tool=None,
1627
1653
  handoff_reason=None,
1654
+ execution_mode=None,
1655
+ decision_reason_code=None,
1656
+ fallback_reason_code=None,
1657
+ trigger=("fixback" if "fixback" in note.lower() else None),
1658
+ from_phase=("review" if phase == "run" and "fixback" in note.lower() else None),
1659
+ result_status=None,
1628
1660
  )
1629
1661
  event = he.build_event(args, existing)
1630
1662
  if identity:
@@ -1655,6 +1687,7 @@ def append_phase_event(
1655
1687
  content = he.render_execution_log(events)
1656
1688
  log_path = he.write_execution_log(change_dir, content)
1657
1689
  rendered = True
1690
+ he.nudge_remote_sync(change_dir)
1658
1691
  return {
1659
1692
  "ok": True,
1660
1693
  "eventId": event.get("id"),
@@ -1693,6 +1726,98 @@ def record_blocked_attempt(
1693
1726
  )
1694
1727
 
1695
1728
 
1729
+ def record_gate_blocked(
1730
+ change_dir: Path,
1731
+ *,
1732
+ phase: str,
1733
+ code: str,
1734
+ message: str,
1735
+ run_id: str | None = None,
1736
+ ) -> dict[str, Any]:
1737
+ """Persist a recoverable gate rejection without manufacturing a phase run."""
1738
+ return append_phase_event(
1739
+ change_dir,
1740
+ phase=phase,
1741
+ type_="gate.blocked",
1742
+ status="BLOCKED",
1743
+ note=message,
1744
+ run_id=run_id or "blocked-" + uuid.uuid4().hex[:12],
1745
+ code=code,
1746
+ )
1747
+
1748
+
1749
+ def record_gate_recovered(
1750
+ change_dir: Path,
1751
+ *,
1752
+ phase: str,
1753
+ run_id: str,
1754
+ ) -> dict[str, Any]:
1755
+ """Close the latest unresolved gate.blocked record for this phase."""
1756
+ blocked: dict[str, Any] | None = None
1757
+ for event in reversed(he.load_events(he.events_path(change_dir))):
1758
+ if event.get("phase") != phase:
1759
+ continue
1760
+ if event.get("type") == "gate.recovered":
1761
+ break
1762
+ if event.get("type") == "gate.blocked":
1763
+ blocked = event
1764
+ break
1765
+ if blocked is None:
1766
+ return {"ok": True, "skipped": True, "reason": "no-unresolved-block"}
1767
+ return append_phase_event(
1768
+ change_dir,
1769
+ phase=phase,
1770
+ type_="gate.recovered",
1771
+ status="OK",
1772
+ note="阶段交接已恢复,门禁现已成功启动。",
1773
+ run_id=run_id,
1774
+ code=str(blocked.get("code") or "GATE_BLOCKED"),
1775
+ )
1776
+
1777
+
1778
+ def validate_context_for_gate_begin(
1779
+ project: Path,
1780
+ change_id: str,
1781
+ phase: str,
1782
+ ) -> dict[str, Any]:
1783
+ """Require a completed Context handoff whenever modern Context state exists."""
1784
+ view = hctx.context_view(project, change_id)
1785
+ if not isinstance(view, dict) or not view.get("ok"):
1786
+ return {"ok": True, "code": "CONTEXT_LEGACY_COMPATIBLE"}
1787
+ current = view.get("current")
1788
+ transitions = view.get("transitions")
1789
+ begins = (view.get("attemptHistory") or {}).get("begins")
1790
+ if not isinstance(current, dict):
1791
+ return {"ok": True, "code": "CONTEXT_LEGACY_COMPATIBLE"}
1792
+ current_phase = str(current.get("phase") or view.get("currentPhase") or "")
1793
+ if current_phase != phase:
1794
+ return {
1795
+ "ok": False,
1796
+ "code": "CONTEXT_HANDOFF_REQUIRED",
1797
+ "message": "阶段交接尚未完成,门禁未启动;请先完成上下文交接。",
1798
+ "expectedPhase": current_phase or None,
1799
+ "requestedPhase": phase,
1800
+ }
1801
+ if isinstance(transitions, list) and transitions:
1802
+ latest = transitions[-1] if isinstance(transitions[-1], dict) else {}
1803
+ receipt_hash = latest.get("receiptHash")
1804
+ begin_items = begins if isinstance(begins, list) else []
1805
+ acknowledged = any(
1806
+ isinstance(item, dict)
1807
+ and item.get("phase") == phase
1808
+ and item.get("receiptHash") == receipt_hash
1809
+ for item in begin_items
1810
+ )
1811
+ if latest.get("toPhase") != phase or not acknowledged:
1812
+ return {
1813
+ "ok": False,
1814
+ "code": "CONTEXT_BEGIN_REQUIRED",
1815
+ "message": "阶段交接收据尚未确认,门禁未启动;请先执行上下文 begin。",
1816
+ "requestedPhase": phase,
1817
+ }
1818
+ return {"ok": True, "code": "CONTEXT_READY"}
1819
+
1820
+
1696
1821
  def _phase_event_exists(change_dir: Path, phase: str, type_: str, run_id: str) -> bool:
1697
1822
  return any(
1698
1823
  event.get("phase") == phase
@@ -1702,6 +1827,80 @@ def _phase_event_exists(change_dir: Path, phase: str, type_: str, run_id: str) -
1702
1827
  )
1703
1828
 
1704
1829
 
1830
+ def _latest_phase_end(
1831
+ change_dir: Path,
1832
+ phase: str,
1833
+ run_id: str | None = None,
1834
+ ) -> dict[str, Any] | None:
1835
+ for event in reversed(he.load_events(he.events_path(change_dir))):
1836
+ if event.get("phase") != phase or event.get("type") != "phase.end":
1837
+ continue
1838
+ if run_id and event.get("run_id") != run_id:
1839
+ continue
1840
+ return event
1841
+ return None
1842
+
1843
+
1844
+ def _terminal_matches_context_session(
1845
+ project: Path,
1846
+ change_id: str,
1847
+ phase: str,
1848
+ terminal: dict[str, Any],
1849
+ ) -> bool:
1850
+ view = hctx.context_view(project, change_id)
1851
+ current = view.get("current") if isinstance(view, dict) else None
1852
+ if not isinstance(current, dict) or current.get("phase") != phase:
1853
+ return False
1854
+ prepared_at = he.parse_timestamp(current.get("preparedAt"))
1855
+ terminal_at = he.parse_timestamp(
1856
+ terminal.get("timestamp") or terminal.get("occurred_at")
1857
+ )
1858
+ return (
1859
+ prepared_at is not None
1860
+ and terminal_at is not None
1861
+ and terminal_at >= prepared_at
1862
+ )
1863
+
1864
+
1865
+ def _close_context_handoff(
1866
+ project: Path,
1867
+ change_id: str,
1868
+ args: argparse.Namespace,
1869
+ *,
1870
+ status: str,
1871
+ ) -> dict[str, Any] | None:
1872
+ to_phase = getattr(args, "to_phase", None)
1873
+ if not to_phase:
1874
+ return None
1875
+ try:
1876
+ return hctx.close_transition(
1877
+ project,
1878
+ change_id,
1879
+ from_phase=args.phase,
1880
+ to_phase=str(to_phase),
1881
+ executor=getattr(args, "executor", None),
1882
+ artifacts=list(getattr(args, "artifact", None) or []),
1883
+ status=status,
1884
+ )
1885
+ except Exception as exc: # noqa: BLE001 — phase close remains locally durable
1886
+ return {
1887
+ "ok": False,
1888
+ "code": "CONTEXT_HANDOFF_FAILED",
1889
+ "message": str(exc),
1890
+ }
1891
+
1892
+
1893
+ def _sync_after_phase_close(project: Path, change_dir: Path) -> dict[str, Any]:
1894
+ try:
1895
+ return hes.auto_events_sync(project, change_dir)
1896
+ except Exception as exc: # noqa: BLE001 — remote sync is recoverable
1897
+ return {
1898
+ "ok": False,
1899
+ "code": "PLATFORM_EVENTS_SYNC_WARN",
1900
+ "warning": f"events-sync hook failed: {exc}",
1901
+ }
1902
+
1903
+
1705
1904
  def _validate_scenario_coverage(change_dir: Path) -> dict[str, Any]:
1706
1905
  """C9: validate all ledger-required scenarios are covered by ledger entries.
1707
1906
 
@@ -1971,6 +2170,32 @@ def cmd_begin(args: argparse.Namespace) -> int:
1971
2170
  extra={k: v for k, v in resolved.items() if k not in {"ok", "message"}},
1972
2171
  )
1973
2172
  change_dir = Path(resolved["changeDir"])
2173
+ context_check = validate_context_for_gate_begin(
2174
+ project, str(resolved["changeId"]), args.phase
2175
+ )
2176
+ if not context_check.get("ok"):
2177
+ code = str(context_check.get("code") or "CONTEXT_HANDOFF_REQUIRED")
2178
+ message = str(
2179
+ context_check.get("message")
2180
+ or "阶段交接尚未完成,门禁未启动。"
2181
+ )
2182
+ record_gate_blocked(
2183
+ change_dir,
2184
+ phase=args.phase,
2185
+ code=code,
2186
+ message=message,
2187
+ run_id=args.run_id or os.environ.get("HUNTER_HARNESS_RUN_ID"),
2188
+ )
2189
+ return emit_error(
2190
+ code,
2191
+ message,
2192
+ as_json=as_json,
2193
+ extra={
2194
+ key: value
2195
+ for key, value in context_check.items()
2196
+ if key not in {"ok", "code", "message"}
2197
+ },
2198
+ )
1974
2199
  severity_mode = gate_severity_mode(project, change_dir)
1975
2200
  gate_warnings: list[dict[str, Any]] = []
1976
2201
  plan_verification: dict[str, Any] | None = None
@@ -2142,6 +2367,16 @@ def cmd_begin(args: argparse.Namespace) -> int:
2142
2367
  message="test guard begin failed (downgraded to WARN)",
2143
2368
  ))
2144
2369
  else:
2370
+ record_gate_blocked(
2371
+ change_dir,
2372
+ phase=args.phase,
2373
+ code=str(
2374
+ guard_result.get("code")
2375
+ or "TEST_GUARD_BEGIN_FAILED"
2376
+ ),
2377
+ message="测试保护快照建立失败,已停止阶段启动。",
2378
+ run_id=run_id,
2379
+ )
2145
2380
  hc.release_lease(
2146
2381
  project,
2147
2382
  change_id=resolved["changeId"],
@@ -2158,6 +2393,11 @@ def cmd_begin(args: argparse.Namespace) -> int:
2158
2393
  current_head = _git_text(execution_root, "rev-parse", "--verify", "HEAD")
2159
2394
  ledger, _ = hl.load_ledger(change_dir)
2160
2395
  base_commit = ledger.get("baseCommit") if isinstance(ledger, dict) else None
2396
+ try:
2397
+ begin_contract = hp.load_change_contract(change_dir)
2398
+ begin_ownership_hash = hl.ownership_hash(begin_contract)
2399
+ except (OSError, ValueError, json.JSONDecodeError):
2400
+ begin_ownership_hash = None
2161
2401
  capsule = {
2162
2402
  "schemaVersion": 1,
2163
2403
  "changeId": resolved["changeId"],
@@ -2170,6 +2410,7 @@ def cmd_begin(args: argparse.Namespace) -> int:
2170
2410
  "repositoryId": hp.repository_identity(execution_root),
2171
2411
  "baseCommit": base_commit or current_head,
2172
2412
  "currentHead": current_head,
2413
+ "ownershipHash": begin_ownership_hash,
2173
2414
  "createdAt": he.now_iso(),
2174
2415
  "projectionReceipt": projection,
2175
2416
  }
@@ -2187,6 +2428,11 @@ def cmd_begin(args: argparse.Namespace) -> int:
2187
2428
  executor_agent=args.executor_agent or os.environ.get("HUNTER_HARNESS_AGENT"),
2188
2429
  executor_model=args.executor_model or os.environ.get("HUNTER_HARNESS_MODEL"),
2189
2430
  )
2431
+ recovery_result = record_gate_recovered(
2432
+ change_dir,
2433
+ phase=args.phase,
2434
+ run_id=run_id,
2435
+ )
2190
2436
  except BaseException:
2191
2437
  hc.release_lease(
2192
2438
  project, change_id=resolved["changeId"], phase=args.phase, run_id=run_id
@@ -2206,6 +2452,7 @@ def cmd_begin(args: argparse.Namespace) -> int:
2206
2452
  "lease": claim.get("lease"),
2207
2453
  "identity": identity,
2208
2454
  "event": event_result,
2455
+ "gateRecovery": recovery_result,
2209
2456
  "testGuard": guard_result,
2210
2457
  "policySchemaVersion": policy.get("schemaVersion"),
2211
2458
  "planVerification": plan_verification,
@@ -2266,6 +2513,48 @@ def cmd_close(args: argparse.Namespace) -> int:
2266
2513
  explicit_run_id = args.run_id or os.environ.get("HUNTER_HARNESS_RUN_ID")
2267
2514
  current_lease = hc.inspect_lease(project, resolved["changeId"])
2268
2515
  if current_lease is None:
2516
+ terminal = _latest_phase_end(change_dir, args.phase, explicit_run_id)
2517
+ if (
2518
+ getattr(args, "to_phase", None)
2519
+ and terminal is not None
2520
+ and _terminal_matches_context_session(
2521
+ project, str(resolved["changeId"]), args.phase, terminal
2522
+ )
2523
+ ):
2524
+ terminal_status = str(terminal.get("status") or args.status)
2525
+ handoff = _close_context_handoff(
2526
+ project,
2527
+ str(resolved["changeId"]),
2528
+ args,
2529
+ status=terminal_status,
2530
+ )
2531
+ monitor = _sync_after_phase_close(project, change_dir)
2532
+ if not isinstance(handoff, dict) or not handoff.get("ok"):
2533
+ return emit_error(
2534
+ "PHASE_HANDOFF_PENDING",
2535
+ "phase gate is closed, but context handoff still needs attention",
2536
+ as_json=as_json,
2537
+ extra={
2538
+ "localCloseComplete": True,
2539
+ "retryable": True,
2540
+ "contextHandoff": handoff,
2541
+ "platformMonitor": monitor,
2542
+ },
2543
+ )
2544
+ emit(
2545
+ {
2546
+ "ok": True,
2547
+ "code": "PHASE_CLOSE_RESUMED",
2548
+ "phase": args.phase,
2549
+ "status": terminal_status,
2550
+ "changeId": resolved["changeId"],
2551
+ "localCloseComplete": True,
2552
+ "contextHandoff": handoff,
2553
+ "platformMonitor": monitor,
2554
+ },
2555
+ as_json=as_json,
2556
+ )
2557
+ return 0
2269
2558
  return emit_error("LEASE_ABSENT", "no active lease for phase close", as_json=as_json)
2270
2559
  run_id = explicit_run_id or str(current_lease.get("runId") or "")
2271
2560
  if str(current_lease.get("runId")) != run_id or str(current_lease.get("phase")) != args.phase:
@@ -2432,6 +2721,16 @@ def cmd_close(args: argparse.Namespace) -> int:
2432
2721
  status="LEDGER_VALIDATION_FAILED",
2433
2722
  error=ledger_result,
2434
2723
  )
2724
+ record_gate_blocked(
2725
+ change_dir,
2726
+ phase=args.phase,
2727
+ code=str(ledger_result.get("code") or "LEDGER_INVALID"),
2728
+ message=(
2729
+ "验证记录与当前变更身份不一致,已保留现场;"
2730
+ "请按返回的路径摘要重新验证受影响范围。"
2731
+ ),
2732
+ run_id=run_id,
2733
+ )
2435
2734
  return emit_error(
2436
2735
  str(ledger_result.get("code", "LEDGER_INVALID")),
2437
2736
  str(ledger_result.get("message", "ledger validation failed")),
@@ -2520,6 +2819,16 @@ def cmd_close(args: argparse.Namespace) -> int:
2520
2819
  "updatedAt": he.now_iso(),
2521
2820
  })
2522
2821
  write_phase_capsule(change_dir, args.phase, run_id, capsule)
2822
+ record_gate_blocked(
2823
+ change_dir,
2824
+ phase=args.phase,
2825
+ code=str(
2826
+ guard_result.get("code")
2827
+ or "TEST_GUARD_CLOSE_FAILED"
2828
+ ),
2829
+ message="测试保护关门失败,已保留现场并等待同一命令恢复。",
2830
+ run_id=run_id,
2831
+ )
2523
2832
  return emit_error(
2524
2833
  str(guard_result.get("code", "TEST_GUARD_CLOSE_FAILED")),
2525
2834
  "test guard close failed",
@@ -2597,6 +2906,41 @@ def cmd_close(args: argparse.Namespace) -> int:
2597
2906
  capsule["closeStatus"] = close_status
2598
2907
  write_phase_capsule(change_dir, args.phase, run_id, capsule)
2599
2908
 
2909
+ context_handoff = _close_context_handoff(
2910
+ project,
2911
+ str(resolved["changeId"]),
2912
+ args,
2913
+ status=close_status,
2914
+ )
2915
+ platform_monitor = _sync_after_phase_close(project, change_dir)
2916
+ if isinstance(context_handoff, dict) and not context_handoff.get("ok"):
2917
+ record_gate_blocked(
2918
+ change_dir,
2919
+ phase=args.phase,
2920
+ code=str(context_handoff.get("code") or "PHASE_HANDOFF_PENDING"),
2921
+ message="阶段本地关门已完成,但上下文交接尚待恢复。",
2922
+ run_id=run_id,
2923
+ )
2924
+ return emit_error(
2925
+ "PHASE_HANDOFF_PENDING",
2926
+ "phase gate closed locally, but context handoff failed; retry the same close command",
2927
+ as_json=as_json,
2928
+ extra={
2929
+ "localCloseComplete": True,
2930
+ "retryable": True,
2931
+ "phase": args.phase,
2932
+ "status": close_status,
2933
+ "changeId": resolved["changeId"],
2934
+ "contextHandoff": context_handoff,
2935
+ "platformMonitor": platform_monitor,
2936
+ },
2937
+ )
2938
+
2939
+ recovery_result = record_gate_recovered(
2940
+ change_dir,
2941
+ phase=args.phase,
2942
+ run_id=run_id,
2943
+ )
2600
2944
  payload = {
2601
2945
  "ok": True,
2602
2946
  "code": close_code,
@@ -2612,6 +2956,9 @@ def cmd_close(args: argparse.Namespace) -> int:
2612
2956
  "lease": release,
2613
2957
  "gateSeverityMode": severity_mode,
2614
2958
  "gateWarnings": gate_warnings,
2959
+ "contextHandoff": context_handoff,
2960
+ "platformMonitor": platform_monitor,
2961
+ "gateRecovery": recovery_result,
2615
2962
  }
2616
2963
  emit(payload, as_json=as_json)
2617
2964
  return 0
@@ -2806,6 +3153,9 @@ def build_parser() -> argparse.ArgumentParser:
2806
3153
  p_close.add_argument("--run-id", default=None)
2807
3154
  p_close.add_argument("--task", type=int, default=None)
2808
3155
  p_close.add_argument("--note", default="")
3156
+ p_close.add_argument("--to-phase", default=None)
3157
+ p_close.add_argument("--executor", default=None)
3158
+ p_close.add_argument("--artifact", action="append", default=[])
2809
3159
  p_close.set_defaults(func=cmd_close)
2810
3160
 
2811
3161
  p_classify = sub.add_parser("classify", parents=[shared])