@opengsd/gsd-path 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (352) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/DOCS.md +4 -3
  3. package/FULL.md +64 -4
  4. package/HOOKS.md +63 -2
  5. package/MIGRATE.md +12 -0
  6. package/QUICK.md +4 -2
  7. package/README.md +67 -15
  8. package/RUNTIME.md +23 -5
  9. package/UPDATE.md +35 -0
  10. package/WORKFLOW.md +58 -3
  11. package/package.json +1 -1
  12. package/scripts/_common.py +107 -1
  13. package/scripts/build_recovery.py +62 -30
  14. package/scripts/build_state.py +195 -5
  15. package/scripts/check_handoffs.py +92 -30
  16. package/scripts/check_task_briefs.py +19 -14
  17. package/scripts/detect_project.py +17 -2
  18. package/scripts/discussion_records.py +9 -1
  19. package/scripts/discussion_validate.py +7 -6
  20. package/scripts/dispatch_driver.py +157 -31
  21. package/scripts/git_guard.py +24 -5
  22. package/scripts/guard_hook.py +1216 -47
  23. package/scripts/install.mjs +76 -3
  24. package/scripts/install.py +182 -37
  25. package/scripts/integration.py +98 -64
  26. package/scripts/isolation.py +148 -11
  27. package/scripts/members.py +68 -25
  28. package/scripts/migrate_core.py +2 -0
  29. package/scripts/model_policy.py +5 -1
  30. package/scripts/pipeline_diagnose.py +17 -2
  31. package/scripts/pipeline_git.py +68 -30
  32. package/scripts/pipeline_state.py +28 -6
  33. package/scripts/pipeline_undo.py +17 -8
  34. package/scripts/promote_lookahead.py +3 -3
  35. package/scripts/review_findings.py +180 -44
  36. package/scripts/roadmap.py +9 -1
  37. package/scripts/runtime_store.py +1 -0
  38. package/scripts/state_checkpoint.py +30 -9
  39. package/scripts/state_promote.py +15 -4
  40. package/scripts/task_context.py +2 -0
  41. package/scripts/workflow_run.py +9 -2
  42. package/skills/gsd-path/DEFINE.md +6 -2
  43. package/skills/gsd-path/PLAN.md +3 -0
  44. package/skills/gsd-path/ROADMAP.md +16 -6
  45. package/skills/gsd-path/SHIP.md +26 -4
  46. package/skills/gsd-path/SKILL.md +52 -24
  47. package/skills/gsd-path/references/build-native.md +96 -9
  48. package/skills/gsd-path/references/build-recovery.md +13 -0
  49. package/skills/gsd-path/references/coder.md +7 -3
  50. package/skills/gsd-path/references/operating-rules.md +11 -3
  51. package/skills/gsd-path/references/planner.md +13 -1
  52. package/skills/gsd-path/references/reviewer-final.md +5 -5
  53. package/skills/gsd-path/references/reviewer-wave.md +46 -13
  54. package/skills/gsd-path/references/runtime-selection.md +9 -0
  55. package/skills/gsd-path/scripts/_common.py +107 -1
  56. package/skills/gsd-path/scripts/build_recovery.py +62 -30
  57. package/skills/gsd-path/scripts/build_state.py +195 -5
  58. package/skills/gsd-path/scripts/check_handoffs.py +92 -30
  59. package/skills/gsd-path/scripts/check_task_briefs.py +19 -14
  60. package/skills/gsd-path/scripts/detect_project.py +17 -2
  61. package/skills/gsd-path/scripts/discussion_records.py +9 -1
  62. package/skills/gsd-path/scripts/discussion_validate.py +7 -6
  63. package/skills/gsd-path/scripts/dispatch_driver.py +157 -31
  64. package/skills/gsd-path/scripts/git_guard.py +24 -5
  65. package/skills/gsd-path/scripts/integration.py +98 -64
  66. package/skills/gsd-path/scripts/isolation.py +148 -11
  67. package/skills/gsd-path/scripts/members.py +68 -25
  68. package/skills/gsd-path/scripts/model_policy.py +5 -1
  69. package/skills/gsd-path/scripts/pipeline_git.py +68 -30
  70. package/skills/gsd-path/scripts/pipeline_state.py +28 -6
  71. package/skills/gsd-path/scripts/promote_lookahead.py +3 -3
  72. package/skills/gsd-path/scripts/review_findings.py +180 -44
  73. package/skills/gsd-path/scripts/roadmap.py +9 -1
  74. package/skills/gsd-path/scripts/state_checkpoint.py +30 -9
  75. package/skills/gsd-path/scripts/state_promote.py +15 -4
  76. package/skills/gsd-path/scripts/task_context.py +2 -0
  77. package/skills/gsd-path/scripts/workflow_run.py +9 -2
  78. package/skills/gsd-path/templates/final-review.md +5 -5
  79. package/skills/gsd-path/templates/state.md +1 -0
  80. package/skills/gsd-path/templates/task.md +1 -0
  81. package/skills/gsd-path/templates/wave-review.md +18 -9
  82. package/skills/gsd-path-build/references/build-native.md +96 -9
  83. package/skills/gsd-path-build/references/build-recovery.md +13 -0
  84. package/skills/gsd-path-build/references/coder.md +7 -3
  85. package/skills/gsd-path-build/references/operating-rules.md +11 -3
  86. package/skills/gsd-path-build/references/reviewer-final.md +5 -5
  87. package/skills/gsd-path-build/references/reviewer-wave.md +46 -13
  88. package/skills/gsd-path-build/references/runtime-selection.md +9 -0
  89. package/skills/gsd-path-build/scripts/_common.py +107 -1
  90. package/skills/gsd-path-build/scripts/build_recovery.py +62 -30
  91. package/skills/gsd-path-build/scripts/build_state.py +195 -5
  92. package/skills/gsd-path-build/scripts/check_handoffs.py +92 -30
  93. package/skills/gsd-path-build/scripts/check_task_briefs.py +19 -14
  94. package/skills/gsd-path-build/scripts/detect_project.py +17 -2
  95. package/skills/gsd-path-build/scripts/discussion_records.py +9 -1
  96. package/skills/gsd-path-build/scripts/discussion_validate.py +7 -6
  97. package/skills/gsd-path-build/scripts/dispatch_driver.py +157 -31
  98. package/skills/gsd-path-build/scripts/integration.py +98 -64
  99. package/skills/gsd-path-build/scripts/isolation.py +148 -11
  100. package/skills/gsd-path-build/scripts/members.py +68 -25
  101. package/skills/gsd-path-build/scripts/model_policy.py +5 -1
  102. package/skills/gsd-path-build/scripts/pipeline_git.py +68 -30
  103. package/skills/gsd-path-build/scripts/pipeline_state.py +28 -6
  104. package/skills/gsd-path-build/scripts/promote_lookahead.py +3 -3
  105. package/skills/gsd-path-build/scripts/review_findings.py +180 -44
  106. package/skills/gsd-path-build/scripts/roadmap.py +9 -1
  107. package/skills/gsd-path-build/scripts/state_checkpoint.py +30 -9
  108. package/skills/gsd-path-build/scripts/state_promote.py +15 -4
  109. package/skills/gsd-path-build/scripts/task_context.py +2 -0
  110. package/skills/gsd-path-build/scripts/workflow_run.py +9 -2
  111. package/skills/gsd-path-build/templates/task.md +1 -0
  112. package/skills/gsd-path-build/templates/wave-review.md +18 -9
  113. package/skills/gsd-path-decide/references/operating-rules.md +11 -3
  114. package/skills/gsd-path-decide/references/runtime-selection.md +9 -0
  115. package/skills/gsd-path-decide/scripts/_common.py +107 -1
  116. package/skills/gsd-path-decide/scripts/build_recovery.py +62 -30
  117. package/skills/gsd-path-decide/scripts/check_handoffs.py +92 -30
  118. package/skills/gsd-path-decide/scripts/check_task_briefs.py +19 -14
  119. package/skills/gsd-path-decide/scripts/detect_project.py +17 -2
  120. package/skills/gsd-path-decide/scripts/discussion_records.py +9 -1
  121. package/skills/gsd-path-decide/scripts/discussion_validate.py +7 -6
  122. package/skills/gsd-path-decide/scripts/integration.py +98 -64
  123. package/skills/gsd-path-decide/scripts/isolation.py +148 -11
  124. package/skills/gsd-path-decide/scripts/members.py +68 -25
  125. package/skills/gsd-path-decide/scripts/model_policy.py +5 -1
  126. package/skills/gsd-path-decide/scripts/pipeline_git.py +68 -30
  127. package/skills/gsd-path-decide/scripts/pipeline_state.py +28 -6
  128. package/skills/gsd-path-decide/scripts/promote_lookahead.py +3 -3
  129. package/skills/gsd-path-decide/scripts/roadmap.py +9 -1
  130. package/skills/gsd-path-decide/scripts/state_checkpoint.py +30 -9
  131. package/skills/gsd-path-decide/scripts/state_promote.py +15 -4
  132. package/skills/gsd-path-define/SKILL.md +6 -2
  133. package/skills/gsd-path-define/references/build-recovery.md +13 -0
  134. package/skills/gsd-path-define/references/operating-rules.md +11 -3
  135. package/skills/gsd-path-define/references/runtime-selection.md +9 -0
  136. package/skills/gsd-path-define/scripts/_common.py +107 -1
  137. package/skills/gsd-path-define/scripts/build_recovery.py +62 -30
  138. package/skills/gsd-path-define/scripts/check_handoffs.py +92 -30
  139. package/skills/gsd-path-define/scripts/check_task_briefs.py +19 -14
  140. package/skills/gsd-path-define/scripts/detect_project.py +17 -2
  141. package/skills/gsd-path-define/scripts/discussion_records.py +9 -1
  142. package/skills/gsd-path-define/scripts/discussion_validate.py +7 -6
  143. package/skills/gsd-path-define/scripts/integration.py +98 -64
  144. package/skills/gsd-path-define/scripts/isolation.py +148 -11
  145. package/skills/gsd-path-define/scripts/members.py +68 -25
  146. package/skills/gsd-path-define/scripts/model_policy.py +5 -1
  147. package/skills/gsd-path-define/scripts/pipeline_git.py +68 -30
  148. package/skills/gsd-path-define/scripts/pipeline_state.py +28 -6
  149. package/skills/gsd-path-define/scripts/promote_lookahead.py +3 -3
  150. package/skills/gsd-path-define/scripts/roadmap.py +9 -1
  151. package/skills/gsd-path-define/scripts/state_checkpoint.py +30 -9
  152. package/skills/gsd-path-define/scripts/state_promote.py +15 -4
  153. package/skills/gsd-path-define/templates/state.md +1 -0
  154. package/skills/gsd-path-discuss/references/operating-rules.md +11 -3
  155. package/skills/gsd-path-discuss/references/runtime-selection.md +9 -0
  156. package/skills/gsd-path-discuss/scripts/_common.py +107 -1
  157. package/skills/gsd-path-discuss/scripts/discussion_records.py +9 -1
  158. package/skills/gsd-path-discuss/scripts/model_policy.py +5 -1
  159. package/skills/gsd-path-docs-audit/references/operating-rules.md +11 -3
  160. package/skills/gsd-path-docs-audit/references/runtime-selection.md +9 -0
  161. package/skills/gsd-path-docs-audit/scripts/_common.py +107 -1
  162. package/skills/gsd-path-docs-audit/scripts/build_recovery.py +62 -30
  163. package/skills/gsd-path-docs-audit/scripts/check_handoffs.py +92 -30
  164. package/skills/gsd-path-docs-audit/scripts/check_task_briefs.py +19 -14
  165. package/skills/gsd-path-docs-audit/scripts/detect_project.py +17 -2
  166. package/skills/gsd-path-docs-audit/scripts/discussion_records.py +9 -1
  167. package/skills/gsd-path-docs-audit/scripts/discussion_validate.py +7 -6
  168. package/skills/gsd-path-docs-audit/scripts/integration.py +98 -64
  169. package/skills/gsd-path-docs-audit/scripts/isolation.py +148 -11
  170. package/skills/gsd-path-docs-audit/scripts/members.py +68 -25
  171. package/skills/gsd-path-docs-audit/scripts/model_policy.py +5 -1
  172. package/skills/gsd-path-docs-audit/scripts/pipeline_git.py +68 -30
  173. package/skills/gsd-path-docs-audit/scripts/pipeline_state.py +28 -6
  174. package/skills/gsd-path-docs-audit/scripts/promote_lookahead.py +3 -3
  175. package/skills/gsd-path-docs-audit/scripts/roadmap.py +9 -1
  176. package/skills/gsd-path-docs-audit/scripts/state_checkpoint.py +30 -9
  177. package/skills/gsd-path-docs-audit/scripts/state_promote.py +15 -4
  178. package/skills/gsd-path-forensics/references/operating-rules.md +11 -3
  179. package/skills/gsd-path-forensics/references/runtime-selection.md +9 -0
  180. package/skills/gsd-path-forensics/scripts/_common.py +107 -1
  181. package/skills/gsd-path-forensics/scripts/discussion_records.py +9 -1
  182. package/skills/gsd-path-forensics/scripts/model_policy.py +5 -1
  183. package/skills/gsd-path-forensics/scripts/pipeline_diagnose.py +17 -2
  184. package/skills/gsd-path-forensics/scripts/pipeline_undo.py +17 -8
  185. package/skills/gsd-path-inspect/references/operating-rules.md +11 -3
  186. package/skills/gsd-path-inspect/references/runtime-selection.md +9 -0
  187. package/skills/gsd-path-inspect/scripts/_common.py +107 -1
  188. package/skills/gsd-path-inspect/scripts/build_recovery.py +62 -30
  189. package/skills/gsd-path-inspect/scripts/check_handoffs.py +92 -30
  190. package/skills/gsd-path-inspect/scripts/check_task_briefs.py +19 -14
  191. package/skills/gsd-path-inspect/scripts/detect_project.py +17 -2
  192. package/skills/gsd-path-inspect/scripts/discussion_records.py +9 -1
  193. package/skills/gsd-path-inspect/scripts/discussion_validate.py +7 -6
  194. package/skills/gsd-path-inspect/scripts/integration.py +98 -64
  195. package/skills/gsd-path-inspect/scripts/isolation.py +148 -11
  196. package/skills/gsd-path-inspect/scripts/members.py +68 -25
  197. package/skills/gsd-path-inspect/scripts/model_policy.py +5 -1
  198. package/skills/gsd-path-inspect/scripts/pipeline_git.py +68 -30
  199. package/skills/gsd-path-inspect/scripts/pipeline_state.py +28 -6
  200. package/skills/gsd-path-inspect/scripts/promote_lookahead.py +3 -3
  201. package/skills/gsd-path-inspect/scripts/roadmap.py +9 -1
  202. package/skills/gsd-path-inspect/scripts/state_checkpoint.py +30 -9
  203. package/skills/gsd-path-inspect/scripts/state_promote.py +15 -4
  204. package/skills/gsd-path-inspect/scripts/workflow_run.py +9 -2
  205. package/skills/gsd-path-inspect/templates/state.md +1 -0
  206. package/skills/gsd-path-loop/references/operating-rules.md +11 -3
  207. package/skills/gsd-path-loop/references/runtime-selection.md +9 -0
  208. package/skills/gsd-path-loop/scripts/_common.py +107 -1
  209. package/skills/gsd-path-loop/scripts/discussion_records.py +9 -1
  210. package/skills/gsd-path-loop/scripts/model_policy.py +5 -1
  211. package/skills/gsd-path-migrate/SKILL.md +8 -0
  212. package/skills/gsd-path-migrate/references/operating-rules.md +11 -3
  213. package/skills/gsd-path-migrate/references/runtime-selection.md +9 -0
  214. package/skills/gsd-path-migrate/scripts/_common.py +107 -1
  215. package/skills/gsd-path-migrate/scripts/discussion_records.py +9 -1
  216. package/skills/gsd-path-migrate/scripts/migrate_core.py +2 -0
  217. package/skills/gsd-path-migrate/scripts/model_policy.py +5 -1
  218. package/skills/gsd-path-plan/SKILL.md +3 -0
  219. package/skills/gsd-path-plan/references/build-recovery.md +13 -0
  220. package/skills/gsd-path-plan/references/operating-rules.md +11 -3
  221. package/skills/gsd-path-plan/references/planner.md +13 -1
  222. package/skills/gsd-path-plan/references/runtime-selection.md +9 -0
  223. package/skills/gsd-path-plan/scripts/_common.py +107 -1
  224. package/skills/gsd-path-plan/scripts/build_recovery.py +62 -30
  225. package/skills/gsd-path-plan/scripts/check_handoffs.py +92 -30
  226. package/skills/gsd-path-plan/scripts/check_task_briefs.py +19 -14
  227. package/skills/gsd-path-plan/scripts/detect_project.py +17 -2
  228. package/skills/gsd-path-plan/scripts/discussion_records.py +9 -1
  229. package/skills/gsd-path-plan/scripts/discussion_validate.py +7 -6
  230. package/skills/gsd-path-plan/scripts/integration.py +98 -64
  231. package/skills/gsd-path-plan/scripts/isolation.py +148 -11
  232. package/skills/gsd-path-plan/scripts/members.py +68 -25
  233. package/skills/gsd-path-plan/scripts/model_policy.py +5 -1
  234. package/skills/gsd-path-plan/scripts/pipeline_git.py +68 -30
  235. package/skills/gsd-path-plan/scripts/pipeline_state.py +28 -6
  236. package/skills/gsd-path-plan/scripts/promote_lookahead.py +3 -3
  237. package/skills/gsd-path-plan/scripts/roadmap.py +9 -1
  238. package/skills/gsd-path-plan/scripts/state_checkpoint.py +30 -9
  239. package/skills/gsd-path-plan/scripts/state_promote.py +15 -4
  240. package/skills/gsd-path-plan/scripts/workflow_run.py +9 -2
  241. package/skills/gsd-path-plan/templates/task.md +1 -0
  242. package/skills/gsd-path-research/references/operating-rules.md +11 -3
  243. package/skills/gsd-path-research/references/runtime-selection.md +9 -0
  244. package/skills/gsd-path-research/scripts/_common.py +107 -1
  245. package/skills/gsd-path-research/scripts/build_recovery.py +62 -30
  246. package/skills/gsd-path-research/scripts/check_handoffs.py +92 -30
  247. package/skills/gsd-path-research/scripts/check_task_briefs.py +19 -14
  248. package/skills/gsd-path-research/scripts/detect_project.py +17 -2
  249. package/skills/gsd-path-research/scripts/discussion_records.py +9 -1
  250. package/skills/gsd-path-research/scripts/discussion_validate.py +7 -6
  251. package/skills/gsd-path-research/scripts/integration.py +98 -64
  252. package/skills/gsd-path-research/scripts/isolation.py +148 -11
  253. package/skills/gsd-path-research/scripts/members.py +68 -25
  254. package/skills/gsd-path-research/scripts/model_policy.py +5 -1
  255. package/skills/gsd-path-research/scripts/pipeline_git.py +68 -30
  256. package/skills/gsd-path-research/scripts/pipeline_state.py +28 -6
  257. package/skills/gsd-path-research/scripts/promote_lookahead.py +3 -3
  258. package/skills/gsd-path-research/scripts/roadmap.py +9 -1
  259. package/skills/gsd-path-research/scripts/state_checkpoint.py +30 -9
  260. package/skills/gsd-path-research/scripts/state_promote.py +15 -4
  261. package/skills/gsd-path-roadmap/SKILL.md +16 -6
  262. package/skills/gsd-path-roadmap/references/operating-rules.md +11 -3
  263. package/skills/gsd-path-roadmap/references/runtime-selection.md +9 -0
  264. package/skills/gsd-path-roadmap/scripts/_common.py +107 -1
  265. package/skills/gsd-path-roadmap/scripts/build_recovery.py +62 -30
  266. package/skills/gsd-path-roadmap/scripts/check_handoffs.py +92 -30
  267. package/skills/gsd-path-roadmap/scripts/check_task_briefs.py +19 -14
  268. package/skills/gsd-path-roadmap/scripts/detect_project.py +17 -2
  269. package/skills/gsd-path-roadmap/scripts/discussion_records.py +9 -1
  270. package/skills/gsd-path-roadmap/scripts/discussion_validate.py +7 -6
  271. package/skills/gsd-path-roadmap/scripts/integration.py +98 -64
  272. package/skills/gsd-path-roadmap/scripts/isolation.py +148 -11
  273. package/skills/gsd-path-roadmap/scripts/members.py +68 -25
  274. package/skills/gsd-path-roadmap/scripts/model_policy.py +5 -1
  275. package/skills/gsd-path-roadmap/scripts/pipeline_git.py +68 -30
  276. package/skills/gsd-path-roadmap/scripts/pipeline_state.py +28 -6
  277. package/skills/gsd-path-roadmap/scripts/promote_lookahead.py +3 -3
  278. package/skills/gsd-path-roadmap/scripts/roadmap.py +9 -1
  279. package/skills/gsd-path-roadmap/scripts/state_checkpoint.py +30 -9
  280. package/skills/gsd-path-roadmap/scripts/state_promote.py +15 -4
  281. package/skills/gsd-path-ship/SKILL.md +26 -4
  282. package/skills/gsd-path-ship/references/operating-rules.md +11 -3
  283. package/skills/gsd-path-ship/references/reviewer-final.md +5 -5
  284. package/skills/gsd-path-ship/references/reviewer-wave.md +46 -13
  285. package/skills/gsd-path-ship/references/runtime-selection.md +9 -0
  286. package/skills/gsd-path-ship/scripts/_common.py +107 -1
  287. package/skills/gsd-path-ship/scripts/build_recovery.py +62 -30
  288. package/skills/gsd-path-ship/scripts/build_state.py +195 -5
  289. package/skills/gsd-path-ship/scripts/check_handoffs.py +92 -30
  290. package/skills/gsd-path-ship/scripts/check_task_briefs.py +19 -14
  291. package/skills/gsd-path-ship/scripts/detect_project.py +17 -2
  292. package/skills/gsd-path-ship/scripts/discussion_records.py +9 -1
  293. package/skills/gsd-path-ship/scripts/discussion_validate.py +7 -6
  294. package/skills/gsd-path-ship/scripts/integration.py +98 -64
  295. package/skills/gsd-path-ship/scripts/isolation.py +148 -11
  296. package/skills/gsd-path-ship/scripts/members.py +68 -25
  297. package/skills/gsd-path-ship/scripts/model_policy.py +5 -1
  298. package/skills/gsd-path-ship/scripts/pipeline_git.py +68 -30
  299. package/skills/gsd-path-ship/scripts/pipeline_state.py +28 -6
  300. package/skills/gsd-path-ship/scripts/promote_lookahead.py +3 -3
  301. package/skills/gsd-path-ship/scripts/roadmap.py +9 -1
  302. package/skills/gsd-path-ship/scripts/state_checkpoint.py +30 -9
  303. package/skills/gsd-path-ship/scripts/state_promote.py +15 -4
  304. package/skills/gsd-path-ship/scripts/workflow_run.py +9 -2
  305. package/skills/gsd-path-ship/templates/final-review.md +5 -5
  306. package/skills/gsd-path-ship/templates/wave-review.md +18 -9
  307. package/skills/gsd-path-undo/references/operating-rules.md +11 -3
  308. package/skills/gsd-path-undo/references/runtime-selection.md +9 -0
  309. package/skills/gsd-path-undo/scripts/_common.py +107 -1
  310. package/skills/gsd-path-undo/scripts/discussion_records.py +9 -1
  311. package/skills/gsd-path-undo/scripts/model_policy.py +5 -1
  312. package/skills/gsd-path-undo/scripts/pipeline_undo.py +17 -8
  313. package/skills/path/DEFINE.md +6 -2
  314. package/skills/path/PLAN.md +3 -0
  315. package/skills/path/ROADMAP.md +16 -6
  316. package/skills/path/SHIP.md +26 -4
  317. package/skills/path/SKILL.md +52 -24
  318. package/skills/path/references/build-native.md +96 -9
  319. package/skills/path/references/build-recovery.md +13 -0
  320. package/skills/path/references/coder.md +7 -3
  321. package/skills/path/references/operating-rules.md +11 -3
  322. package/skills/path/references/planner.md +13 -1
  323. package/skills/path/references/reviewer-final.md +5 -5
  324. package/skills/path/references/reviewer-wave.md +46 -13
  325. package/skills/path/references/runtime-selection.md +9 -0
  326. package/skills/path/scripts/_common.py +107 -1
  327. package/skills/path/scripts/build_recovery.py +62 -30
  328. package/skills/path/scripts/build_state.py +195 -5
  329. package/skills/path/scripts/check_handoffs.py +92 -30
  330. package/skills/path/scripts/check_task_briefs.py +19 -14
  331. package/skills/path/scripts/detect_project.py +17 -2
  332. package/skills/path/scripts/discussion_records.py +9 -1
  333. package/skills/path/scripts/discussion_validate.py +7 -6
  334. package/skills/path/scripts/dispatch_driver.py +157 -31
  335. package/skills/path/scripts/git_guard.py +24 -5
  336. package/skills/path/scripts/integration.py +98 -64
  337. package/skills/path/scripts/isolation.py +148 -11
  338. package/skills/path/scripts/members.py +68 -25
  339. package/skills/path/scripts/model_policy.py +5 -1
  340. package/skills/path/scripts/pipeline_git.py +68 -30
  341. package/skills/path/scripts/pipeline_state.py +28 -6
  342. package/skills/path/scripts/promote_lookahead.py +3 -3
  343. package/skills/path/scripts/review_findings.py +180 -44
  344. package/skills/path/scripts/roadmap.py +9 -1
  345. package/skills/path/scripts/state_checkpoint.py +30 -9
  346. package/skills/path/scripts/state_promote.py +15 -4
  347. package/skills/path/scripts/task_context.py +2 -0
  348. package/skills/path/scripts/workflow_run.py +9 -2
  349. package/skills/path/templates/final-review.md +5 -5
  350. package/skills/path/templates/state.md +1 -0
  351. package/skills/path/templates/task.md +1 -0
  352. package/skills/path/templates/wave-review.md +18 -9
@@ -27,7 +27,7 @@ import tempfile
27
27
  import time
28
28
  from datetime import datetime, timezone
29
29
  from pathlib import Path
30
- from typing import BinaryIO, Callable, Dict, List, Optional
30
+ from typing import BinaryIO, Callable, Dict, List, Optional, Tuple
31
31
 
32
32
  try:
33
33
  from scripts import (_common, archive_milestone, build_state, discussion_validate, isolation,
@@ -77,6 +77,9 @@ STOP_ERRORS = (DriverStop, isolation.IsolationError, build_state.BuildStateError
77
77
  pipeline_state.PipelineStateError, archive_milestone.ArchiveError, model_policy.PolicyError)
78
78
  _RUNTIME_ACTIVATION_ORDER = (
79
79
  "_common",
80
+ "roadmap",
81
+ "review_panel",
82
+ "loop_run",
80
83
  "pipeline_git",
81
84
  "worktree_paths",
82
85
  "build_recovery",
@@ -87,11 +90,16 @@ _RUNTIME_ACTIVATION_ORDER = (
87
90
  "build_state",
88
91
  "check_handoffs",
89
92
  "task_context",
93
+ # discussion_validate and archive_milestone import each other: the second
94
+ # reload binds the reloaded ArchiveError that STOP_ERRORS catches.
95
+ "discussion_validate",
90
96
  "archive_milestone",
97
+ "discussion_validate",
91
98
  "review_findings",
92
99
  "model_policy",
93
100
  )
94
101
  RECOVERY_BLOCKED = "recovery blocked"
102
+ LEFTOVER_ISOLATE = "task {} is landed; remove its leftover isolate: {}"
95
103
  # The stops the build contract answers with build/blocked when the round raises them (steps 1 and 2);
96
104
  # the same stops from review, panel, or skeptics stay with the parent.
97
105
  BLOCKING_STOPS = {"dependency-deadlock": "dependency deadlock", RECOVERY_BLOCKED: "blocked recovery"}
@@ -640,6 +648,15 @@ def run_verify(command: str, cwd: Path) -> Dict[str, object]:
640
648
  return {"exit_code": completed.returncode, "stdout": completed.stdout, "stderr": completed.stderr}
641
649
 
642
650
 
651
+ def landing_verify(command: str, regressions: List[Dict[str, object]],
652
+ cwd: Path) -> Tuple[Dict[str, object], List[Dict[str, object]]]:
653
+ """The task's own Verify, then every regression Verify when the own one passes."""
654
+ execution = run_verify(command, cwd)
655
+ if execution["exit_code"]:
656
+ return execution, []
657
+ return execution, [{**item, **run_verify(str(item["command"]), cwd)} for item in regressions]
658
+
659
+
643
660
  def state_from_task(primary: Path, task_id: str) -> Dict[str, object]:
644
661
  """A finish-able state derived from the task frontmatter alone, for coders dispatched by hand."""
645
662
  tasks_dir = primary / ".project/tasks"
@@ -693,6 +710,22 @@ def state_from_task(primary: Path, task_id: str) -> Dict[str, object]:
693
710
 
694
711
  def finish_task(primary: Path, state: Dict[str, object]) -> Dict[str, object]:
695
712
  """Verify in the isolate, land, record the ledger, retire. Returns the landing receipt."""
713
+ landing = land_task(primary, state)
714
+ try:
715
+ if state["mode"] == "member":
716
+ isolation.retire_member_task(primary, str(state["member"]), str(state["task_id"]))
717
+ else:
718
+ isolation.retire(primary, Path(str(state["worktree"])), state.get("task_branch") or None, False)
719
+ except isolation.IsolationError as error:
720
+ # The landing is proven, so a failed removal is a warning; Round.recover checks the isolate again.
721
+ landing["cleanup_warning"] = (f"{error}; the task is landed, but its isolate was not removed. The next "
722
+ "round or finish checks the isolate again and stops if the isolate is "
723
+ "still there")
724
+ return landing
725
+
726
+
727
+ def land_task(primary: Path, state: Dict[str, object]) -> Dict[str, object]:
728
+ """Verify in the isolate, land, record the ledger. Returns the landing receipt."""
696
729
  worktree = Path(str(state["worktree"]))
697
730
  task_id = str(state["task_id"])
698
731
  task_file = str(state["task_file"])
@@ -701,6 +734,11 @@ def finish_task(primary: Path, state: Dict[str, object]) -> Dict[str, object]:
701
734
  command = _common.task_verify_command(task_path.read_text(encoding="utf-8"))
702
735
  if not command:
703
736
  raise DriverStop("task has no Verify command", task=task_id)
737
+ # A later task can break an earlier task that shares a file; that task's Verify also decides this landing.
738
+ regressions = build_state.regression_verifies(primary, task_id, command)
739
+ # A plan-approved `supersedes` retires an earlier Verify; the receipt and the Log show each one.
740
+ retired = build_state.retired_verifies(primary, task_id)
741
+ superseded = {"retired": retired} if retired else {}
704
742
  if state["mode"] == "serial":
705
743
  sidecar = state["sidecar"]
706
744
  ensured = isolation.ensure_verify_sidecar(primary, base, str(sidecar["branch"]))
@@ -710,30 +748,45 @@ def finish_task(primary: Path, state: Dict[str, object]) -> Dict[str, object]:
710
748
  isolation.reproduce_verify_sidecar(sidecar_path, tree)
711
749
  except isolation.IsolationError as error:
712
750
  raise DriverStop(str(error), task=task_id) from error
713
- execution = run_verify(command, sidecar_path)
751
+ execution, reruns = landing_verify(command, regressions, sidecar_path)
714
752
  location = f"sidecar {sidecar['branch']}"
715
753
  isolation.clean_verify(primary, sidecar_path, base, str(sidecar["branch"]))
716
754
  isolation.retire(primary, sidecar_path, str(sidecar["branch"]), False)
717
755
  else:
718
- execution = run_verify(command, worktree)
756
+ execution, reruns = landing_verify(command, regressions, worktree)
719
757
  location = f"isolate {state['task_branch']}"
720
758
  passed = execution["exit_code"] == 0
759
+ broken = [item for item in reruns if item["exit_code"]]
721
760
  execution.update({"worktree": str(worktree), "location": location})
722
761
  evidence_dir = (Path(str(state["_path"])).parent if state.get("_path")
723
762
  else records_root(primary) / task_id / f"manual-{now().replace(':', '')}")
724
763
  evidence_dir.mkdir(parents=True, exist_ok=True)
725
764
  _common.atomic_write(evidence_dir / "verify.json",
726
- json.dumps(execution, indent=2, sort_keys=True) + "\n")
765
+ json.dumps({**execution, "regressions": reruns, **superseded},
766
+ indent=2, sort_keys=True) + "\n")
727
767
  execution["evidence"] = str(evidence_dir / "verify.json")
728
- output = "\n".join(f" {line}" for line in tail(execution["stdout"] + execution["stderr"]).splitlines())
729
- append_log(task_path, f"- {now()[:10]} — orchestrator Verify ({location}): "
730
- f"{'pass' if passed else 'fail'}, exit {execution['exit_code']}"
731
- + (f"; output tail:\n ```\n{output}\n ```" if output else ""))
732
- if not passed:
768
+ # The Verify of a superseding task runs in the place of the retired Verify: the label names both.
769
+ for label, run in [("Verify", execution),
770
+ *((f"regression Verify of {', '.join(item['tasks'])}"
771
+ + (f" (supersedes {', '.join(item['supersedes'])})" if item.get("supersedes") else ""),
772
+ item) for item in reruns)]:
773
+ output = "\n".join(f" {line}" for line in tail(run["stdout"] + run["stderr"]).splitlines())
774
+ append_log(task_path, f"- {now()[:10]} — orchestrator {label} ({location}): "
775
+ f"{'fail' if run['exit_code'] else 'pass'}, exit {run['exit_code']}"
776
+ + (f"; output tail:\n ```\n{output}\n ```" if output else ""))
777
+ for item in retired:
778
+ part = f": part of {item['part']}" if item.get("part") else ""
779
+ append_log(task_path, f"- {now()[:10]} — orchestrator regression Verify of {item['task']}{part} retired: "
780
+ f"superseded by {item['superseded_by']}")
781
+ if not passed or broken:
733
782
  if state["mode"] == "parallel":
734
783
  isolation.deactivate_task(worktree, task_id, str(state["task_branch"]))
735
784
  elif state["mode"] == "member":
736
785
  isolation.deactivate_member_task(primary, str(state["member"]), task_id)
786
+ if passed:
787
+ names = ", ".join(name for item in broken for name in item["tasks"])
788
+ raise DriverStop(f"regression Verify of {names} failed in the isolate", task=task_id,
789
+ execution=execution, regressions=broken)
737
790
  raise DriverStop("Verify failed in the isolate", task=task_id, execution=execution)
738
791
  if state["mode"] == "member":
739
792
  member = str(state["member"])
@@ -744,9 +797,9 @@ def finish_task(primary: Path, state: Dict[str, object]) -> Dict[str, object]:
744
797
  ledger = isolation.git_output(worktree, "rev-parse", f"{landing}^") == str(state["member_base"])
745
798
  if ledger:
746
799
  build_state.verify_record(str(primary), command, landing, "pass", execution=execution, member=member)
747
- isolation.retire_member_task(primary, member, task_id)
748
800
  return {"task": task_id, "commit": str(landed["commit"]), "mode": "member",
749
- "landing": landing, "verify": execution, "ledger": ledger}
801
+ "landing": landing, "verify": execution, "regressions": reruns, **superseded,
802
+ "ledger": ledger}
750
803
  landed = isolation.land(primary, worktree, base, task_id, str(state["title"]), task_file,
751
804
  list(state["files"]))
752
805
  commit = str(landed["commit"])
@@ -754,9 +807,8 @@ def finish_task(primary: Path, state: Dict[str, object]) -> Dict[str, object]:
754
807
  ledger = isolation.git_output(primary, "rev-parse", f"{commit}^") == base
755
808
  if ledger:
756
809
  build_state.verify_record(str(primary), command, commit, "pass", execution=execution)
757
- isolation.retire(primary, worktree, state.get("task_branch") or None, False)
758
810
  return {"task": task_id, "commit": commit, "mode": landed["mode"], "verify": execution,
759
- "ledger": ledger}
811
+ "regressions": reruns, **superseded, "ledger": ledger}
760
812
 
761
813
 
762
814
  # --- shared by round and review --------------------------------------------
@@ -772,6 +824,11 @@ def recover_report(primary: Path, project_dir: str, receipt: Dict[str, object])
772
824
  report = isolation.recover(primary, Path(project_dir) / "tasks")
773
825
  receipt["steps"].append({"script": "isolation.py recover", "result": report})
774
826
  if report["verdict"] == "block":
827
+ blocked = [task for task in report["tasks"] if task["verdict"] == "block"]
828
+ if blocked and all(task.get("leftover") for task in blocked):
829
+ # Every blocked task is landed: the stop names only the isolates to remove, and the build stays active.
830
+ raise DriverStop("; ".join(LEFTOVER_ISOLATE.format(task["task_id"], task["leftover"])
831
+ for task in blocked), recover=blocked)
775
832
  raise DriverStop(RECOVERY_BLOCKED, recover=report)
776
833
  return report
777
834
 
@@ -907,7 +964,7 @@ class Round:
907
964
  self.receipt["steps"].append({"script": "isolation.recover_member_landing", "task": journal.stem,
908
965
  "result": result})
909
966
  if result["state"] == "landed":
910
- isolation.retire_member_task(self.primary, str(result["member"]), journal.stem)
967
+ self.retire_member(str(result["member"]), journal.stem, str(result["landing"]))
911
968
  retired_members.add(journal.stem)
912
969
  report = recover_report(self.primary, self.project_dir, self.receipt)
913
970
  for task in report["tasks"]:
@@ -936,16 +993,36 @@ class Round:
936
993
  recover=task,
937
994
  )
938
995
  if verdict == "recovered" and task.get("member") and task.get("task_id") not in retired_members:
939
- isolation.retire_member_task(self.primary, str(task["member"]), str(task["task_id"]))
996
+ self.retire_member(str(task["member"]), str(task["task_id"]), str(task["landing"]))
940
997
  if verdict == "recovered" and worktree and worktree["clean"] and task.get("task_branch"):
941
998
  isolation.retire(self.primary, Path(str(worktree["path"])),
942
999
  str(task["task_branch"]), False)
943
- elif (verdict == "recovered" and worktree is None and task.get("task_branch")) or (
944
- verdict == "resume" and (task.get("landing_retry") or task.get("dispatch_retry"))):
945
- # ponytail: interrupted retirements and retries stay with the parent's documented
1000
+ elif verdict == "recovered" and worktree is None and task.get("task_branch"):
1001
+ # The isolate is gone and the landing is proven: retire proves the branch, then deletes it.
1002
+ isolation.retire(self.primary, None, str(task["task_branch"]), True, str(task["commit"]))
1003
+ elif verdict == "resume" and (task.get("landing_retry") or task.get("dispatch_retry")):
1004
+ # ponytail: interrupted retries stay with the parent's documented
946
1005
  # procedure; failed/blocked reconciliation surfaces through ready's typed error.
947
1006
  raise DriverStop(f"task {task.get('task_id')} needs recovery: {verdict}", recover=task)
948
1007
 
1008
+ def retire_member(self, member: str, task_id: str, landing: str) -> None:
1009
+ """Retire a landed member task's sidecar; a proven leftover of it is a stop that names only its path."""
1010
+ checkout, project, _ = isolation._member_context(self.primary, member)
1011
+ sidecar = isolation.sidecar_root(checkout, "task", f"{project}-{task_id}")
1012
+ branch = f"refs/heads/{isolation.TASK_BRANCH_PREFIX}{project}-{task_id}"
1013
+ tip = isolation.run_git(checkout, "rev-parse", "--verify", "--quiet", branch)
1014
+ commits = [landing, *tip.stdout.split()]
1015
+ try:
1016
+ isolation.retire_member_task(self.primary, member, task_id)
1017
+ except isolation.RemovalError as error:
1018
+ # Missing files are a removal that stopped part way; every other state keeps the refusal.
1019
+ if not (isolation.only_missing_files(sidecar)
1020
+ or isolation.holds_only_landed_files(checkout, sidecar, commits)):
1021
+ raise
1022
+ raise DriverStop(LEFTOVER_ISOLATE.format(task_id, sidecar), cleanup_error=str(error))
1023
+ if isolation.holds_only_landed_files(checkout, sidecar, commits):
1024
+ raise DriverStop(LEFTOVER_ISOLATE.format(task_id, sidecar))
1025
+
949
1026
  # settle exited children ---------------------------------------------
950
1027
 
951
1028
  def isolate_live(self, state: Dict[str, object]) -> bool:
@@ -1231,7 +1308,7 @@ class Round:
1231
1308
  continue
1232
1309
  prepared = self.runner("prepare-task", head, task["id"], round_size)
1233
1310
  isolate = prepared[0]["result"]
1234
- sidecar = prepared[1]["result"] if len(prepared) > 1 else None
1311
+ sidecar = prepared[-1]["result"] if len(prepared) > 1 else None # the isolate-verify step is last
1235
1312
  worktree = Path(str(isolate["worktree"]))
1236
1313
  isolation.activate_task(worktree, head, task["id"], f"build_{task['id'].lower()}",
1237
1314
  task["task_file"], isolate["task_branch"])
@@ -2007,6 +2084,23 @@ def next_task_id(tasks_dir: Path) -> str:
2007
2084
  return f"T{max(numbers, default=0) + 1:03d}"
2008
2085
 
2009
2086
 
2087
+ def replaced_review_base(primary: Path, project_dir: str, wave: int, cycle: int) -> Optional[str]:
2088
+ """The recovery base when this cycle's review replaces the one a build recovery set aside, else None."""
2089
+ recovery = pipeline_state._build_recovery().context(primary)
2090
+ if not recovery:
2091
+ return None
2092
+ relative = f"{project_dir}/review/wave-{wave}.cycle{cycle}.md"
2093
+ before = isolation.run_git(primary, "show", f"{recovery['base']}:{relative}")
2094
+ current = primary / relative
2095
+ # Plan approval restores the review of an unchanged wave before its checkpoint, so no commit
2096
+ # since the base touches that file. A set-aside review is deleted by that checkpoint.
2097
+ rewritten = isolation.run_git(primary, "log", "-1", "--format=%H", f"{recovery['base']}..HEAD", "--", relative)
2098
+ if (not rewritten.stdout.strip() and before.returncode == 0 and current.is_file()
2099
+ and current.read_text(encoding="utf-8") == before.stdout):
2100
+ return None # the restored review keeps its repairs
2101
+ return recovery["base"]
2102
+
2103
+
2010
2104
  def fix_tasks(primary: Path, options: argparse.Namespace) -> Dict[str, object]:
2011
2105
  """Step 7: one fix task per findings batch, carrying failed criteria and observations verbatim."""
2012
2106
  receipt: Dict[str, object] = {"status": None, "wave": options.wave, "cycle": options.cycle,
@@ -2015,14 +2109,32 @@ def fix_tasks(primary: Path, options: argparse.Namespace) -> Dict[str, object]:
2015
2109
  project = primary / options.project_dir
2016
2110
  tasks_dir = project / "tasks"
2017
2111
  repairs = []
2112
+ pending = []
2018
2113
  for path in sorted(tasks_dir.glob("*-fix-wave-*-cycle-*.md")):
2019
2114
  text = path.read_text(encoding="utf-8")
2020
2115
  fields, _ = isolation.task_frontmatter(text)
2021
2116
  repairs.append({"task": fields["id"], "path": path.relative_to(primary).as_posix(),
2022
- "wave": int(fields["wave"]), "locators": re.findall(r"(?m)^### (.+)$", _common.section_body(text, "Review findings") or "")})
2117
+ "wave": int(fields["wave"]), "locators": re.findall(r"(?m)^### (.+)$", _common.section_body(text, "Review findings") or ""),
2118
+ **({"repo": fields["repo"]} if fields.get("repo") else {})})
2119
+ if fields["status"] != "done":
2120
+ pending.append(repairs[-1])
2023
2121
  suffix = f"-fix-wave-{options.wave}-cycle-{options.cycle}.md"
2024
- existing = [item for item in repairs if item["path"].endswith(suffix)]
2025
- carried = {locator for item in repairs if item not in existing for locator in item["locators"]}
2122
+ # A build recovery restarts a changed wave at cycle 1, so the repairs and the PLAN heading
2123
+ # of the review it set aside carry the same names. They belong to that review, not this one.
2124
+ replaced = replaced_review_base(primary, options.project_dir, options.wave, options.cycle)
2125
+
2126
+ def of_replaced_review(relative: str) -> bool:
2127
+ if not replaced or isolation.run_git(primary, "show", f"{replaced}:{relative}").returncode != 0:
2128
+ return False
2129
+ return not isolation.run_git(primary, "log", "--diff-filter=AD", "--format=%H",
2130
+ f"{replaced}..HEAD", "--", relative).stdout.strip()
2131
+
2132
+ stale = [item for item in repairs if item["path"].endswith(suffix) and of_replaced_review(item["path"])]
2133
+ existing = [item for item in repairs if item["path"].endswith(suffix) and item not in stale]
2134
+ # A landed repair no longer carries its finding: a repeat proves that fix failed and needs the next one.
2135
+ # Two repos can carry the same locator, so a repair carries a finding only in its own repo.
2136
+ carried = {(item.get("repo"), locator) for item in pending if not item["path"].endswith(suffix)
2137
+ for locator in item["locators"]}
2026
2138
  findings = review_findings.compute(primary, options.project_dir, options.wave, options.cycle)
2027
2139
  receipt["findings"] = findings
2028
2140
  escalation = [key for key in ("structural_blockers", "skeptic_groups", "cap_reached", "all_refuted")
@@ -2043,8 +2155,10 @@ def fix_tasks(primary: Path, options: argparse.Namespace) -> Dict[str, object]:
2043
2155
  repair_waves = {item["wave"] for item in existing}
2044
2156
  if len(repair_waves) > 1:
2045
2157
  raise DriverStop("repair tasks for this cycle disagree on their wave")
2046
- repair_heading = re.search(
2158
+ stale_waves = {item["wave"] for item in stale}
2159
+ repair_heading = next((heading for heading in re.finditer(
2047
2160
  rf"(?m)^## Wave (\d+) — fix wave {options.wave} cycle {options.cycle} review findings$", plan_text)
2161
+ if int(heading.group(1)) not in stale_waves), None)
2048
2162
  if repair_waves:
2049
2163
  fix_wave = next(iter(repair_waves))
2050
2164
  elif repair_heading:
@@ -2055,9 +2169,10 @@ def fix_tasks(primary: Path, options: argparse.Namespace) -> Dict[str, object]:
2055
2169
  raise DriverStop("repair tasks disagree with the PLAN repair wave")
2056
2170
  receipt["existing"] = existing
2057
2171
  for batch in findings["fix_batches"]:
2058
- if any(set(batch["locators"]) <= set(item["locators"]) for item in existing):
2172
+ repo = batch.get("repo") # a member name, or None for the coordinator
2173
+ if any(item.get("repo") == repo and set(batch["locators"]) <= set(item["locators"]) for item in existing):
2059
2174
  continue
2060
- if all(locator in carried for locator in batch["locators"]):
2175
+ if all((repo, locator) in carried for locator in batch["locators"]):
2061
2176
  receipt["carried"].append(batch)
2062
2177
  continue
2063
2178
  batch_groups = [groups[locator] for locator in batch["locators"]]
@@ -2065,9 +2180,15 @@ def fix_tasks(primary: Path, options: argparse.Namespace) -> Dict[str, object]:
2065
2180
  for source in sources:
2066
2181
  if source not in texts:
2067
2182
  raise DriverStop(f"fix batch names unknown task {source}")
2183
+ # One task changes one repo: the fix depends on, and runs the Verify of, the source tasks of its repo.
2184
+ sources = [source for source in sources if isolation.task_frontmatter(texts[source])[0].get("repo") == repo]
2185
+ # A repeat builds on the landed repairs of its finding: repair-evidence links them by this dependency.
2186
+ deps = sources + [item["task"] for item in repairs
2187
+ if item not in pending and item.get("repo") == repo
2188
+ and f"-fix-wave-{options.wave}-cycle-" in item["path"] and not item["path"].endswith(suffix)
2189
+ and set(item["locators"]) & set(batch["locators"])]
2068
2190
  task_id = next_task_id(tasks_dir)
2069
- verify = "set -e\n" + "\n".join( # each source Verify in its own subshell
2070
- f"(\n{command}\n)" for command in dict.fromkeys(_common.task_verify_command(texts[source]) for source in sources))
2191
+ verify = _common.fix_verify(_common.task_verify_command(texts[source]) for source in sources)
2071
2192
  heavy = any(re.search(r"(?m)^Heavy:\s*yes", texts[source]) for source in sources)
2072
2193
  criteria = "\n".join(f"{index}. {criterion}" for index, criterion in
2073
2194
  enumerate(dict.fromkeys(group["criterion"] for group in batch_groups), 1))
@@ -2075,18 +2196,19 @@ def fix_tasks(primary: Path, options: argparse.Namespace) -> Dict[str, object]:
2075
2196
  f"### {group['locator']}\nCriterion: {group['criterion']}\n"
2076
2197
  + "\n".join(f"- {item['text']}" for item in group["observations"]) for group in batch_groups)
2077
2198
  files = "\n".join(f" - {path}" for path in batch["files"])
2199
+ member = f"repo: {repo}\n" if repo else "" # the task template puts `repo:` before `files:`
2078
2200
  title = f"Fix wave {options.wave} cycle {options.cycle} review findings in {', '.join(sources)}"
2079
2201
  text = f"""---
2080
2202
  id: {task_id}
2081
2203
  title: {title}
2082
2204
  wave: {fix_wave}
2083
- deps: [{', '.join(sources)}]
2205
+ deps: [{', '.join(deps)}]
2084
2206
  status: pending
2085
2207
  agent: null
2086
2208
  base: null
2087
2209
  worktree: null
2088
2210
  task_branch: null
2089
- files:
2211
+ {member}files:
2090
2212
  {files}
2091
2213
  ---
2092
2214
 
@@ -2135,8 +2257,8 @@ Heavy: {'yes' if heavy else 'no'}
2135
2257
  path = tasks_dir / f"{task_id}-fix-wave-{options.wave}-cycle-{options.cycle}.md"
2136
2258
  _common.atomic_write(path, text)
2137
2259
  receipt["created"].append({"task": task_id, "path": path.relative_to(primary).as_posix(),
2138
- "wave": fix_wave, "deps": sources, "files": batch["files"],
2139
- "locators": batch["locators"]})
2260
+ "wave": fix_wave, "deps": deps, "files": batch["files"],
2261
+ "locators": batch["locators"], **({"repo": repo} if repo else {})})
2140
2262
  rows = []
2141
2263
  for item in [*existing, *receipt["created"]]:
2142
2264
  fields, _ = isolation.task_frontmatter((primary / item["path"]).read_text(encoding="utf-8"))
@@ -2421,6 +2543,10 @@ def main(argv=None) -> int:
2421
2543
  if native and native["base"] != records[0].get("base"):
2422
2544
  records = []
2423
2545
  else:
2546
+ if (native and records[0]["outcome"] == "blocked" and records[0].get("native")
2547
+ and not records[0].get("command")):
2548
+ # A failed finish leaves a host-dispatched task active at its base: finish may run again.
2549
+ update_state(records[0], outcome=None, reason=None)
2424
2550
  native = None
2425
2551
  if arguments.task_id in current.proven:
2426
2552
  commit = current.proven[arguments.task_id]
@@ -19,9 +19,10 @@ _HERE = Path(__file__).resolve().parent
19
19
  sys.path.insert(0, str(_HERE / "runtime" if (_HERE / "runtime" / "isolation.py").is_file() else _HERE))
20
20
  sys.dont_write_bytecode = True # a hook must not leave __pycache__ in the worktree
21
21
  try:
22
- from isolation import BOOKKEEPING_PREFIXES, NULL_SHA, _landing_state, task_frontmatter
22
+ from isolation import BOOKKEEPING_PREFIXES, NULL_SHA, _landing_state, task_frontmatter, undeclared_paths
23
23
  from pipeline_git import is_ship_subject, ship_body_matches, ship_commit_body, task_commit_body
24
24
  from pipeline_state import _completion_status
25
+ from _common import valid_default_branch
25
26
  import members
26
27
  except ImportError as error: # pragma: no cover - broken install
27
28
  print(
@@ -240,7 +241,10 @@ def frontmatter_of(content, label):
240
241
 
241
242
 
242
243
  def strict_ship_state(state, archive):
243
- fields = frozenset(state)
244
+ # default_branch is optional beside each accepted field set.
245
+ if "default_branch" in state and not valid_default_branch(state["default_branch"]):
246
+ return False
247
+ fields = frozenset(state) - {"default_branch"}
244
248
  if fields not in {
245
249
  frozenset(LEGACY_STATE_FIELDS),
246
250
  frozenset(INTEGRATION_STATE_FIELDS),
@@ -509,7 +513,7 @@ def product_commit_violations(entries, subject, body):
509
513
  if error:
510
514
  problems.append(error)
511
515
  if contract_paths is not None:
512
- stray = sorted(set(staged) - contract_paths)
516
+ stray = undeclared_paths(repo_root(), base, set(staged), contract_paths)
513
517
  if stray:
514
518
  problems.append("undeclared paths: " + ", ".join(stray))
515
519
  if body != task_commit_body(task_file, staged, base).strip():
@@ -559,8 +563,10 @@ def member_pre_push_violations(lines, role):
559
563
  """Member mode: the member's own STATE is ignored; coordinator refs need authorization."""
560
564
  checkout, project = repo_root(), role["project"]
561
565
  reserved = members.member_ref_prefixes(project)
562
- members.require_origin_main(checkout)
563
- exclude = ["--not", "refs/remotes/origin/main"]
566
+ # The coordinator's MEMBERS.md records this member's default branch.
567
+ default = members.member_default_branch(role["coordinator"], role["name"])
568
+ members.require_origin_branch(checkout, default)
569
+ exclude = ["--not", f"refs/remotes/origin/{default}"]
564
570
  member_work = None
565
571
  found = []
566
572
  for _, local_sha, remote_ref, remote_sha in _push_updates(lines):
@@ -641,7 +647,20 @@ def abandon_contract_violations(subject, body, abandon):
641
647
  return []
642
648
 
643
649
 
650
+ def recorded_default_branch():
651
+ """STATE default_branch in the worktree: main without the field; None without a readable STATE."""
652
+ try:
653
+ content = (repo_root() / ".project" / "STATE.md").read_text(encoding="utf-8")
654
+ name = frontmatter_of(content, "STATE.md").get("default_branch", "main")
655
+ except (OSError, ValueError, subprocess.CalledProcessError):
656
+ return None
657
+ return name if valid_default_branch(name) else None
658
+
659
+
644
660
  def default_branch():
661
+ recorded = recorded_default_branch()
662
+ if recorded:
663
+ return recorded
645
664
  origin_head = subprocess.run(
646
665
  ["git", "symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"],
647
666
  capture_output=True,