@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
@@ -51,6 +51,21 @@ WHITELIST = {
51
51
  "duration_ms",
52
52
  "schemaVersion",
53
53
  "summary",
54
+ "executor_tool",
55
+ "executor_agent",
56
+ "executor_model",
57
+ "execution_mode",
58
+ "decision_reason_code",
59
+ "fallback_reason_code",
60
+ "trigger",
61
+ "from_phase",
62
+ "result_status",
63
+ "planned_phases",
64
+ "skipped_phases",
65
+ "next_phase",
66
+ "phase_plan_source",
67
+ "closure_disposition",
68
+ "closure_reason",
54
69
  }
55
70
 
56
71
  MAX_BATCH_SIZE = 100
@@ -70,6 +85,14 @@ PHASE_LABELS = {
70
85
  "archive": "归档",
71
86
  }
72
87
 
88
+ REVIEW_REASON_SUMMARIES = {
89
+ "REVIEW_DELEGATED": "已使用独立评审,主会话负责核验结果。",
90
+ "REVIEW_INLINE_UNAVAILABLE": "当前环境没有可用的隔离评审能力,已由主会话完成评审。",
91
+ "REVIEW_INLINE_SPAWN_FAILED": "独立评审启动失败,已由主会话完成评审。",
92
+ "REVIEW_INLINE_INVALID_RESULT": "独立评审未返回有效结果,已由主会话重新完成评审。",
93
+ "INLINE_BY_ADAPTER": "当前适配器未提供隔离评审,已由主会话完成评审。",
94
+ }
95
+
73
96
 
74
97
  def _yaml_scalar(text: str, key: str) -> str | None:
75
98
  match = re.search(rf"(?m)^[ \t]*{re.escape(key)}:\s*['\"]?([^'\"\n#]+)", text)
@@ -110,20 +133,56 @@ def _summary_text(value: Any) -> str:
110
133
  return text[:EVENT_SUMMARY_MAX_LENGTH].rstrip()
111
134
 
112
135
 
136
+ _MACHINE_DETAIL_RE = re.compile(
137
+ r"(?:\s*[;;,,]?\s*)"
138
+ r"(?:artifactsHash|artifactHash|receiptHash|content_sha256|contentSha256)"
139
+ r"\s*[:=]\s*(?:sha256:)?[0-9a-f]{32,64}",
140
+ re.IGNORECASE,
141
+ )
142
+
143
+
144
+ def _readable_summary_text(value: Any) -> str:
145
+ text = _summary_text(value)
146
+ if not text:
147
+ return ""
148
+ text = _MACHINE_DETAIL_RE.sub("", text)
149
+ text = re.sub(
150
+ r"\bfinalize\s+ok\b",
151
+ "规划产物已校验并发布",
152
+ text,
153
+ flags=re.IGNORECASE,
154
+ )
155
+ text = re.sub(r"\s*([;;,,])\s*([;;,,])", r"\1", text)
156
+ return _summary_text(text.strip(" ;;,,"))
157
+
158
+
113
159
  def build_event_summary(event: dict[str, Any]) -> str:
114
- explicit = _summary_text(event.get("summary"))
115
- if explicit:
160
+ reason_code = _summary_text(
161
+ event.get("fallback_reason_code") or event.get("decision_reason_code")
162
+ )
163
+ reason_summary = REVIEW_REASON_SUMMARIES.get(reason_code)
164
+ explicit = _readable_summary_text(event.get("summary"))
165
+ if reason_summary and (
166
+ not explicit
167
+ or explicit == reason_code
168
+ or re.search(r"REVIEW_[A-Z0-9_]+|INLINE_BY_ADAPTER", explicit)
169
+ ):
170
+ return reason_summary
171
+ if explicit and not re.search(
172
+ r"\b[A-Z][A-Z0-9_]{3,}\b|\b[a-z][\w.-]+\s*=\s*(?:true|false)\b",
173
+ explicit,
174
+ ):
116
175
  return explicit
117
176
 
118
177
  event_type = _summary_text(event.get("type"))
119
178
  phase = _summary_text(event.get("phase"))
120
179
  phase_label = PHASE_LABELS.get(phase, phase or "当前")
121
- note = _summary_text(event.get("note"))
122
- message = _summary_text(event.get("message"))
123
- decision = _summary_text(event.get("decision"))
124
- reason = _summary_text(event.get("reason"))
125
- name = _summary_text(event.get("name"))
126
- code = _summary_text(event.get("code"))
180
+ note = _readable_summary_text(event.get("note"))
181
+ message = _readable_summary_text(event.get("message"))
182
+ decision = _readable_summary_text(event.get("decision"))
183
+ reason = _readable_summary_text(event.get("reason"))
184
+ name = _readable_summary_text(event.get("name"))
185
+ code = _readable_summary_text(event.get("code"))
127
186
  status = _summary_text(event.get("status"))
128
187
 
129
188
  if event_type == "decision" and decision:
@@ -145,6 +204,10 @@ def build_event_summary(event: dict[str, Any]) -> str:
145
204
  return _summary_text(
146
205
  f"{phase_label}阶段已结束:{detail}" if detail else f"{phase_label}阶段已结束。"
147
206
  )
207
+ if event_type == "gate.blocked":
208
+ return note or message or "阶段门禁暂未通过,现场已保留,可按提示恢复。"
209
+ if event_type == "gate.recovered":
210
+ return note or message or "阶段门禁已恢复,流程可以继续。"
148
211
  if event_type == "command":
149
212
  detail = name or note
150
213
  return _summary_text(f"执行步骤:{detail}" if detail else "执行了一项工作步骤。")
@@ -173,6 +236,48 @@ def sanitize(event: dict[str, Any]) -> dict[str, Any]:
173
236
  return payload
174
237
 
175
238
 
239
+ def _workflow_plan(change_dir: Path) -> dict[str, Any] | None:
240
+ policy_path = change_dir / "meta" / "gate-policy.json"
241
+ if not policy_path.is_file():
242
+ return None
243
+ try:
244
+ policy = json.loads(policy_path.read_text(encoding="utf-8-sig"))
245
+ except (OSError, json.JSONDecodeError):
246
+ return None
247
+ if not isinstance(policy, dict):
248
+ return None
249
+ raw = policy.get("plannedPhases")
250
+ source = "change"
251
+ if not isinstance(raw, list) or not raw:
252
+ raw = policy.get("defaultPhases")
253
+ source = "policy-default"
254
+ if not isinstance(raw, list):
255
+ return None
256
+ phases = [str(item).strip() for item in raw if str(item).strip()]
257
+ if not phases:
258
+ return None
259
+ skipped = policy.get("skippedPhases")
260
+ return {
261
+ "planned_phases": list(dict.fromkeys(phases)),
262
+ "skipped_phases": skipped if isinstance(skipped, list) else [],
263
+ "phase_plan_source": source,
264
+ }
265
+
266
+
267
+ def _event_payload(event: dict[str, Any], phase_plan: dict[str, Any] | None) -> dict[str, Any]:
268
+ payload = sanitize(event)
269
+ if phase_plan is None:
270
+ return payload
271
+ payload.update(phase_plan)
272
+ if str(event.get("type") or "") in {"phase.end", "phase.auto_sealed"}:
273
+ phase = str(event.get("phase") or "").strip()
274
+ planned = phase_plan["planned_phases"]
275
+ if phase in planned:
276
+ index = planned.index(phase)
277
+ payload["next_phase"] = planned[index + 1] if index + 1 < len(planned) else None
278
+ return payload
279
+
280
+
176
281
  def _valid_display_title(value: Any) -> str | None:
177
282
  if not isinstance(value, str):
178
283
  return None
@@ -437,6 +542,7 @@ def sync_change(
437
542
  repair=True,
438
543
  )
439
544
  pending_raw = lines[acked:]
545
+ phase_plan = _workflow_plan(change_dir)
440
546
  uploaded = 0
441
547
  quarantined = 0
442
548
  last_result: dict[str, Any] | None = None
@@ -468,7 +574,7 @@ def sync_change(
468
574
  "producer_seq": line_number,
469
575
  "event_type": str(event.get("type") or "unknown"),
470
576
  "occurred_at": str(event.get("timestamp") or event.get("ts") or client_time),
471
- "payload": sanitize(event),
577
+ "payload": _event_payload(event, phase_plan),
472
578
  }
473
579
  if event.get("phase") is not None:
474
580
  item["phase"] = event.get("phase")
@@ -695,6 +801,31 @@ class _CrossProcessLock:
695
801
  self._stream = None
696
802
 
697
803
 
804
+ def _windowless_python_executable(
805
+ executable: str, base_executable: str | None
806
+ ) -> str:
807
+ """Return a Windows interpreter that cannot allocate a console window.
808
+
809
+ Virtual-environment ``python.exe`` files may be forwarding launchers. Even
810
+ when the first process is created with ``CREATE_NO_WINDOW``, such a launcher
811
+ can start the real console interpreter and make Windows Terminal flash. A
812
+ sibling ``pythonw.exe`` preserves the windowless subsystem across that hop.
813
+ """
814
+ executable_path = Path(executable)
815
+ candidates = [executable_path.with_name("pythonw.exe")]
816
+ if base_executable:
817
+ base_path = Path(base_executable)
818
+ base_windowless = base_path.with_name("pythonw.exe")
819
+ if base_windowless not in candidates:
820
+ candidates.append(base_windowless)
821
+ for candidate in candidates:
822
+ if candidate.is_file():
823
+ return str(candidate)
824
+ if base_executable and Path(base_executable).is_file():
825
+ return str(Path(base_executable))
826
+ return str(executable_path)
827
+
828
+
698
829
  def schedule_events_sync(project_root: Path, change_dir: Path) -> bool:
699
830
  """Wake a finite, project-local sync worker after an event is durable.
700
831
 
@@ -716,8 +847,14 @@ def schedule_events_sync(project_root: Path, change_dir: Path) -> bool:
716
847
  temporary = nudge.with_name(f".{nudge.name}.{uuid.uuid4().hex}.tmp")
717
848
  temporary.write_text(json.dumps(payload, separators=(",", ":")) + "\n", encoding="utf-8")
718
849
  os.replace(temporary, nudge)
850
+ interpreter = sys.executable
851
+ if os.name == "nt":
852
+ interpreter = _windowless_python_executable(
853
+ sys.executable,
854
+ getattr(sys, "_base_executable", None),
855
+ )
719
856
  command = [
720
- sys.executable,
857
+ interpreter,
721
858
  str(Path(__file__).resolve()),
722
859
  "--project",
723
860
  str(project_root),
@@ -754,6 +891,39 @@ def _discover_changes(project_root: Path) -> list[Path]:
754
891
  return sorted(path for path in root.glob("*") if path.is_dir()) if root.is_dir() else []
755
892
 
756
893
 
894
+ def _change_has_open_phase(change_dir: Path) -> bool:
895
+ """Project the append-only stream into open/closed phase state."""
896
+ try:
897
+ path = Path(harness_paths.resolve_state_dir_for_contract(change_dir)) / "events.ndjson"
898
+ except (OSError, ValueError):
899
+ return False
900
+ if not path.is_file():
901
+ return False
902
+ open_phases: dict[str, bool] = {}
903
+ try:
904
+ for line in path.read_text(encoding="utf-8-sig").splitlines():
905
+ if not line.strip():
906
+ continue
907
+ event = json.loads(line)
908
+ if not isinstance(event, dict):
909
+ continue
910
+ phase = event.get("phase")
911
+ event_type = event.get("type")
912
+ if not isinstance(phase, str) or not phase:
913
+ continue
914
+ if event_type == "phase.start":
915
+ open_phases[phase] = True
916
+ elif event_type in {"phase.end", "phase.auto_sealed", "workflow.end", "run.end"}:
917
+ open_phases[phase] = False
918
+ except (OSError, json.JSONDecodeError):
919
+ return False
920
+ return any(open_phases.values())
921
+
922
+
923
+ def _project_has_open_phase(project_root: Path) -> bool:
924
+ return any(_change_has_open_phase(path) for path in _discover_changes(project_root))
925
+
926
+
757
927
  def _run_agent_session(project_root: Path) -> dict[str, Any]:
758
928
  attempts = 0
759
929
  uploaded = 0
@@ -766,14 +936,28 @@ def _run_agent_session(project_root: Path) -> dict[str, Any]:
766
936
  if all(item.get("ok") for item in results):
767
937
  time.sleep(0.6)
768
938
  if generation == _read_nudge_generation(project_root):
769
- return {
770
- "ok": True,
771
- "uploaded": uploaded,
772
- "quarantined": quarantined,
773
- "attempts": attempts + 1,
774
- }
939
+ if not _project_has_open_phase(project_root):
940
+ return {
941
+ "ok": True,
942
+ "uploaded": uploaded,
943
+ "quarantined": quarantined,
944
+ "attempts": attempts + 1,
945
+ }
946
+ # Keep a finite workflow lease alive while a phase is open.
947
+ # A phase.end nudge is observed on the next pass and releases
948
+ # the worker naturally; no terminal window is created.
949
+ time.sleep(10.0)
950
+ heartbeat_results = [
951
+ sync_change(project_root, path, heartbeat_only=True)
952
+ for path in _discover_changes(project_root)
953
+ ]
954
+ if all(item.get("ok") for item in heartbeat_results):
955
+ attempts = 0
956
+ continue
957
+ results = heartbeat_results
775
958
  attempts = 0
776
- continue
959
+ if generation != _read_nudge_generation(project_root):
960
+ continue
777
961
  time.sleep(min(16.0, float(2 ** attempts)))
778
962
  attempts += 1
779
963
  return {
@@ -11,6 +11,7 @@ import os
11
11
  import re
12
12
  import tempfile
13
13
  import sys
14
+ import uuid
14
15
  from pathlib import Path
15
16
  from typing import Any
16
17
 
@@ -19,6 +20,7 @@ if str(SCRIPTS_DIR) not in sys.path:
19
20
  sys.path.insert(0, str(SCRIPTS_DIR))
20
21
 
21
22
  from harness_paths import resolve_state_dir_for_contract
23
+ import harness_events as he
22
24
 
23
25
 
24
26
  SCHEMA_VERSION = 1
@@ -96,6 +98,171 @@ def _all_batches(change_dir: Path) -> list[dict[str, Any]]:
96
98
  return batches
97
99
 
98
100
 
101
+ def _session_path(change_dir: Path) -> Path:
102
+ return _state_root(change_dir) / "runtime" / "fixback-session.json"
103
+
104
+
105
+ def _next_step(batch: dict[str, Any]) -> str:
106
+ issues = [item for item in batch.get("issues", []) if isinstance(item, dict)]
107
+ if not issues:
108
+ return "add-issues"
109
+ if any(item.get("status") != "RESOLVED" for item in issues):
110
+ return "resolve-issues"
111
+ receipts = batch.get("receipts") if isinstance(batch.get("receipts"), dict) else {}
112
+ if not receipts.get("affected"):
113
+ return "verify-affected"
114
+ if not receipts.get("review"):
115
+ return "review"
116
+ return "close"
117
+
118
+
119
+ def resume_batch(
120
+ change_dir: Path,
121
+ *,
122
+ batch_id: str | None,
123
+ product_identity: str | None,
124
+ root_cause: str | None,
125
+ run_id: str | None,
126
+ attempt: int | None,
127
+ ) -> dict[str, Any]:
128
+ """Open once or resume the active Fixback session with a stable run identity."""
129
+ opened = [item for item in _all_batches(change_dir) if item.get("status") == "OPEN"]
130
+ resumed = bool(opened)
131
+ if opened:
132
+ batch = sorted(opened, key=lambda item: str(item.get("openedAt") or ""))[0]
133
+ else:
134
+ if not batch_id or not product_identity or not root_cause:
135
+ raise ValueError(
136
+ "FIXBACK_RESUME_INPUT_REQUIRED: new sessions require batch-id, "
137
+ "product-identity and root-cause"
138
+ )
139
+ batch = open_batch(
140
+ change_dir,
141
+ batch_id=batch_id,
142
+ product_identity=product_identity,
143
+ root_cause=root_cause,
144
+ )
145
+ session_path = _session_path(change_dir)
146
+ existing: dict[str, Any] = {}
147
+ if session_path.is_file():
148
+ try:
149
+ loaded = json.loads(session_path.read_text(encoding="utf-8-sig"))
150
+ existing = loaded if isinstance(loaded, dict) else {}
151
+ except (OSError, json.JSONDecodeError):
152
+ existing = {}
153
+ same_open_session = (
154
+ existing.get("status") == "ACTIVE"
155
+ and existing.get("batchId") == batch.get("batchId")
156
+ )
157
+ effective_run_id = (
158
+ str(existing.get("runId"))
159
+ if same_open_session and existing.get("runId")
160
+ else str(run_id or "fixback-" + uuid.uuid4().hex)
161
+ )
162
+ effective_attempt = (
163
+ int(existing.get("attempt"))
164
+ if same_open_session and isinstance(existing.get("attempt"), int)
165
+ else max(1, int(attempt or 1))
166
+ )
167
+ next_step = _next_step(batch)
168
+ session = {
169
+ "schemaVersion": 1,
170
+ "status": "ACTIVE",
171
+ "batchId": batch.get("batchId"),
172
+ "runId": effective_run_id,
173
+ "attempt": effective_attempt,
174
+ "nextStep": next_step,
175
+ "updatedAt": now_iso(),
176
+ }
177
+ _write_json(session_path, session)
178
+ he.append_event(
179
+ change_dir,
180
+ phase="run",
181
+ type_="decision",
182
+ note=(
183
+ "已恢复现有修复批次,将从未完成步骤继续。"
184
+ if resumed
185
+ else "已创建修复批次,将按问题逐项执行 RED/GREEN。"
186
+ ),
187
+ run_id=effective_run_id,
188
+ )
189
+ return {
190
+ "ok": True,
191
+ "code": "FIXBACK_RESUMED" if resumed else "FIXBACK_OPENED",
192
+ "resumed": resumed,
193
+ "batchId": batch.get("batchId"),
194
+ "runId": effective_run_id,
195
+ "attempt": effective_attempt,
196
+ "nextStep": next_step,
197
+ "requiredVerifications": batch.get("requiredVerifications", []),
198
+ "changedFiles": batch.get("changedFiles", []),
199
+ "sessionPath": str(session_path),
200
+ }
201
+
202
+
203
+ def invalidate_affected_evidence(
204
+ change_dir: Path,
205
+ *,
206
+ changed_files: list[str],
207
+ batch_id: str,
208
+ ) -> dict[str, Any]:
209
+ """Invalidate only verification targets whose declared inputs were changed."""
210
+ normalized = {
211
+ str(path).replace("\\", "/").lstrip("./")
212
+ for path in changed_files
213
+ if str(path).strip()
214
+ }
215
+ ledger_path = _state_root(change_dir) / "evidence" / "verification-ledger.json"
216
+ if not normalized or not ledger_path.is_file():
217
+ return {"ok": True, "targetIds": [], "validations": []}
218
+ try:
219
+ ledger = json.loads(ledger_path.read_text(encoding="utf-8-sig"))
220
+ except (OSError, json.JSONDecodeError):
221
+ return {"ok": False, "code": "FIXBACK_LEDGER_INVALID"}
222
+ if not isinstance(ledger, dict):
223
+ return {"ok": False, "code": "FIXBACK_LEDGER_INVALID"}
224
+
225
+ def affected(entry: dict[str, Any]) -> bool:
226
+ inputs = entry.get("inputsFiles")
227
+ if not isinstance(inputs, list):
228
+ return False
229
+ input_set = {
230
+ str(path).replace("\\", "/").lstrip("./")
231
+ for path in inputs
232
+ if str(path).strip()
233
+ }
234
+ return bool(normalized & input_set)
235
+
236
+ invalidation = {
237
+ "code": "FIXBACK_AFFECTED_INPUT_CHANGED",
238
+ "batchId": batch_id,
239
+ "changedFiles": sorted(normalized),
240
+ "invalidatedAt": now_iso(),
241
+ }
242
+ target_ids: list[str] = []
243
+ targets = ledger.get("verificationTargets")
244
+ if isinstance(targets, dict):
245
+ for target_id, target in targets.items():
246
+ if isinstance(target, dict) and affected(target):
247
+ target["reusable"] = False
248
+ target["invalidation"] = dict(invalidation)
249
+ target_ids.append(str(target_id))
250
+ validation_names: list[str] = []
251
+ validations = ledger.get("validations")
252
+ if isinstance(validations, dict):
253
+ for name, entry in validations.items():
254
+ if isinstance(entry, dict) and affected(entry):
255
+ entry["reusable"] = False
256
+ entry["invalidation"] = dict(invalidation)
257
+ validation_names.append(str(name))
258
+ _write_json(ledger_path, ledger)
259
+ return {
260
+ "ok": True,
261
+ "targetIds": sorted(target_ids),
262
+ "validations": sorted(validation_names),
263
+ }
264
+
265
+
99
266
  def _required_verifications(issues: list[dict[str, Any]]) -> list[str]:
100
267
  required = {"affected", "review"}
101
268
  for issue in issues:
@@ -506,6 +673,11 @@ def resolve_issue(
506
673
  }
507
674
  )
508
675
  batch["updatedAt"] = resolved_at
676
+ batch["invalidation"] = invalidate_affected_evidence(
677
+ change_dir,
678
+ changed_files=[record["path"] for record in changed_records],
679
+ batch_id=batch_id,
680
+ )
509
681
  _write_json(_batch_path(change_dir, batch_id), batch)
510
682
  return issue
511
683
 
@@ -652,6 +824,13 @@ def _emit(value: dict[str, Any]) -> int:
652
824
  def build_parser() -> argparse.ArgumentParser:
653
825
  parser = argparse.ArgumentParser(prog="harness_fixback.py")
654
826
  sub = parser.add_subparsers(dest="command", required=True)
827
+ resume = sub.add_parser("resume")
828
+ resume.add_argument("--change-dir", required=True)
829
+ resume.add_argument("--batch-id")
830
+ resume.add_argument("--product-identity")
831
+ resume.add_argument("--root-cause")
832
+ resume.add_argument("--run-id")
833
+ resume.add_argument("--attempt", type=int)
655
834
  opened = sub.add_parser("open")
656
835
  opened.add_argument("--change-dir", required=True)
657
836
  opened.add_argument("--batch-id", required=True)
@@ -692,7 +871,16 @@ def main(argv: list[str] | None = None) -> int:
692
871
  args = build_parser().parse_args(argv)
693
872
  change_dir = Path(args.change_dir)
694
873
  try:
695
- if args.command == "open":
874
+ if args.command == "resume":
875
+ value = resume_batch(
876
+ change_dir,
877
+ batch_id=args.batch_id,
878
+ product_identity=args.product_identity,
879
+ root_cause=args.root_cause,
880
+ run_id=args.run_id,
881
+ attempt=args.attempt,
882
+ )
883
+ elif args.command == "open":
696
884
  value = open_batch(
697
885
  change_dir,
698
886
  batch_id=args.batch_id,