@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
@@ -2,8 +2,9 @@
2
2
  # gsd-path project runtime
3
3
  """Deterministic GSD Path branch names and pipeline commit messages.
4
4
 
5
- Bound work lives on `gsd-path/M00N` for that milestone. The required remote
6
- default `main` stays a separate trunk. Ship merges the bound branch onto main;
5
+ Bound work lives on `gsd-path/M00N` for that milestone. The remote default
6
+ that STATE.md records (`main` when unrecorded) stays a separate trunk. Ship
7
+ merges the bound branch onto it;
7
8
  the next milestone binds a new unused `gsd-path/M00N` and retires the
8
9
  integrated previous branch, locally and on origin.
9
10
  """
@@ -98,7 +99,8 @@ def ship_subject(archive_name: str) -> str:
98
99
 
99
100
 
100
101
  def integrate_subject(archive_name: str, default_branch: str) -> str:
101
- if not default_branch or default_branch.startswith("origin/"):
102
+ # The bare name, under the rule that records it; `origin/trunk` is a legal bare name.
103
+ if not _common.valid_default_branch(default_branch):
102
104
  raise PipelineGitError(f"default branch is invalid: {default_branch}")
103
105
  number = milestone_number(archive_name)
104
106
  return (
@@ -363,7 +365,7 @@ def _remote_branch_exists(repo: Path, branch: str) -> bool:
363
365
  return _remote_ref_sha(repo, f"refs/heads/{branch}") is not None
364
366
 
365
367
 
366
- def _pull_request_tag_fields(repo: Path, ref: str) -> dict[str, str]:
368
+ def _milestone_tag_fields(repo: Path, ref: str) -> dict[str, str]:
367
369
  tag_type = _run_git(repo, "cat-file", "-t", ref, check=False)
368
370
  if tag_type.returncode != 0 or tag_type.stdout.strip() != "tag":
369
371
  return {}
@@ -373,7 +375,7 @@ def _pull_request_tag_fields(repo: Path, ref: str) -> dict[str, str]:
373
375
  match = re.fullmatch(r"(Mode|Pull-Request|Ship|Landing): (.+)", line)
374
376
  if match:
375
377
  if match.group(1) in fields:
376
- raise PipelineGitError("pull-request milestone tag repeats metadata")
378
+ raise PipelineGitError("milestone tag repeats metadata")
377
379
  fields[match.group(1)] = match.group(2)
378
380
  return fields
379
381
 
@@ -404,7 +406,15 @@ def _validated_shipped_state(repo: Path) -> dict[str, object]:
404
406
  return state
405
407
 
406
408
 
407
- def _require_pull_request_integration_proof(
409
+ # Metadata a published milestone tag must carry, per integration mode, before
410
+ # bind-next accepts a previous bound branch that the forge deleted on merge.
411
+ TAG_PROOF_FIELDS = {
412
+ "pull-request": {"Mode", "Pull-Request", "Ship", "Landing"},
413
+ "external-landing": {"Mode", "Ship", "Landing"},
414
+ }
415
+
416
+
417
+ def _require_tag_integration_proof(
408
418
  repo: Path,
409
419
  ship: str,
410
420
  base: str,
@@ -415,8 +425,11 @@ def _require_pull_request_integration_proof(
415
425
  raise PipelineGitError("bind-next requires shipped/done state")
416
426
  if state.get("branch") != previous_branch:
417
427
  raise PipelineGitError("shipped state does not name the previous branch")
418
- if state.get("integration") != "pull-request":
419
- raise PipelineGitError("shipped integration mode is not pull-request")
428
+ mode = state.get("integration")
429
+ if mode not in TAG_PROOF_FIELDS:
430
+ raise PipelineGitError(
431
+ "shipped integration mode is not pull-request or external-landing"
432
+ )
420
433
  archive = state.get("archive")
421
434
  if not isinstance(archive, str):
422
435
  raise PipelineGitError("shipped state does not name an archive")
@@ -427,24 +440,23 @@ def _require_pull_request_integration_proof(
427
440
  ):
428
441
  raise PipelineGitError("shipped archive does not match the previous branch")
429
442
  ref = f"refs/tags/milestone/{match.group(1)}"
430
- fields = _pull_request_tag_fields(repo, ref)
431
- if set(fields) != {"Mode", "Pull-Request", "Ship", "Landing"}:
432
- raise PipelineGitError("pull-request integration proof is incomplete")
433
- if fields["Mode"] != "pull-request" or fields["Ship"] != ship:
434
- raise PipelineGitError("pull-request integration proof does not match shipped state")
435
- pull_request = fields["Pull-Request"]
436
- if re.fullmatch(
443
+ fields = _milestone_tag_fields(repo, ref)
444
+ if set(fields) != TAG_PROOF_FIELDS[mode]:
445
+ raise PipelineGitError(f"{mode} integration proof is incomplete")
446
+ if fields["Mode"] != mode or fields["Ship"] != ship:
447
+ raise PipelineGitError(f"{mode} integration proof does not match shipped state")
448
+ if mode == "pull-request" and re.fullmatch(
437
449
  r"https://github\.com/[^/]+/[^/]+/pull/[1-9][0-9]*",
438
- pull_request,
450
+ fields["Pull-Request"],
439
451
  ) is None:
440
452
  raise PipelineGitError("pull-request integration proof has an invalid PR URL")
441
453
  landing = fields["Landing"]
442
454
  if re.fullmatch(r"[0-9a-f]{40}", landing) is None:
443
- raise PipelineGitError("pull-request integration proof has an invalid landing")
455
+ raise PipelineGitError(f"{mode} integration proof has an invalid landing")
444
456
  local_object = _run_git(repo, "rev-parse", ref).stdout.strip()
445
457
  local_target = _run_git(repo, "rev-parse", f"{ref}^{{commit}}").stdout.strip()
446
458
  if local_target != landing:
447
- raise PipelineGitError("pull-request integration tag does not point at landing")
459
+ raise PipelineGitError(f"{mode} integration tag does not point at landing")
448
460
  remote = _run_git(
449
461
  repo,
450
462
  "ls-remote",
@@ -456,14 +468,14 @@ def _require_pull_request_integration_proof(
456
468
  check=False,
457
469
  )
458
470
  if remote.returncode != 0:
459
- raise PipelineGitError("pull-request integration tag is not published")
471
+ raise PipelineGitError(f"{mode} integration tag is not published")
460
472
  rows = dict(
461
473
  line.split("\t", 1)[::-1]
462
474
  for line in remote.stdout.splitlines()
463
475
  if "\t" in line
464
476
  )
465
477
  if rows != {ref: local_object, f"{ref}^{{}}": landing}:
466
- raise PipelineGitError("published pull-request integration tag differs")
478
+ raise PipelineGitError(f"published {mode} integration tag differs")
467
479
  parents = _run_git(
468
480
  repo,
469
481
  "rev-list",
@@ -473,11 +485,11 @@ def _require_pull_request_integration_proof(
473
485
  landing,
474
486
  ).stdout.split()
475
487
  if len(parents) != 3 or parents[2] != ship:
476
- raise PipelineGitError("pull-request integration proof has invalid merge topology")
488
+ raise PipelineGitError(f"{mode} integration proof has invalid merge topology")
477
489
  first_parent = _run_git(repo, "rev-list", "--first-parent", base).stdout.splitlines()
478
490
  if landing not in first_parent:
479
491
  raise PipelineGitError(
480
- "pull-request integration landing is not on main first-parent history"
492
+ f"{mode} integration landing is not on the default branch first-parent history"
481
493
  )
482
494
  return landing
483
495
 
@@ -533,6 +545,34 @@ def _write_bind_next_journal(path: Path, value: dict[str, object]) -> None:
533
545
  temporary.unlink()
534
546
 
535
547
 
548
+ def _require_state_remote_default(repo: Path, remote_default: str) -> str:
549
+ """Return the default branch STATE.md records; `remote_default` must name it.
550
+
551
+ No STATE.md yet (bind-initial before initialize) or no field means main.
552
+ An unreadable STATE.md also means main, as before the field existed: the
553
+ callers that need a valid state check it themselves.
554
+ """
555
+ if __package__:
556
+ from . import pipeline_state
557
+ else:
558
+ try:
559
+ import pipeline_state
560
+ except ImportError: # pragma: no cover - package import used by tests
561
+ from scripts import pipeline_state
562
+
563
+ default_name, unreadable = "main", ""
564
+ if (repo / ".project" / "STATE.md").exists():
565
+ try:
566
+ default_name = pipeline_state.load_state(repo)[0].default_branch
567
+ except (OSError, pipeline_state.PipelineStateError) as error:
568
+ unreadable = f" (STATE.md is unreadable: {error})"
569
+ if remote_default != f"origin/{default_name}":
570
+ raise PipelineGitError(
571
+ f"remote default must be origin/{default_name}, got {remote_default}{unreadable}"
572
+ )
573
+ return default_name
574
+
575
+
536
576
  def _initial_binding_snapshot(repo: Path) -> Optional[tuple]:
537
577
  """Admit only clean work or the validated, untracked initializer state."""
538
578
  dirty = _run_git(repo, "status", "--porcelain=v1", "-z", "--untracked-files=all").stdout
@@ -579,8 +619,7 @@ def bind_initial_milestone_branch(
579
619
  resolved = _require_worktree_root(repo)
580
620
  if not is_bound_branch(branch):
581
621
  raise PipelineGitError(f"invalid bound branch: {branch}")
582
- if remote_default != "origin/main":
583
- raise PipelineGitError(f"remote default must be origin/main, got {remote_default}")
622
+ default_name = _require_state_remote_default(resolved, remote_default)
584
623
 
585
624
  default_sha = _run_git(
586
625
  resolved,
@@ -600,7 +639,7 @@ def bind_initial_milestone_branch(
600
639
  raise PipelineGitError(
601
640
  f"validated base is stale: {base_sha} != {remote_default} {default_sha}"
602
641
  )
603
- remote_sha = _remote_ref_sha(resolved, "refs/heads/main")
642
+ remote_sha = _remote_ref_sha(resolved, f"refs/heads/{default_name}")
604
643
  if remote_sha != default_sha:
605
644
  raise PipelineGitError(
606
645
  f"fetched {remote_default} is stale relative to origin"
@@ -742,8 +781,7 @@ def bind_next_milestone_branch(
742
781
  raise PipelineGitError(f"invalid previous bound branch: {previous_branch}")
743
782
  if milestone_number(branch) <= milestone_number(previous_branch):
744
783
  raise PipelineGitError(f"next branch {branch} must follow {previous_branch}")
745
- if remote_default != "origin/main":
746
- raise PipelineGitError(f"remote default must be origin/main, got {remote_default}")
784
+ default_name = _require_state_remote_default(repo, remote_default)
747
785
 
748
786
  default_sha = _run_git(
749
787
  repo,
@@ -763,7 +801,7 @@ def bind_next_milestone_branch(
763
801
  raise PipelineGitError(
764
802
  f"validated base is stale: {base_sha} != {remote_default} {default_sha}"
765
803
  )
766
- live_default_sha = _remote_ref_sha(repo, "refs/heads/main")
804
+ live_default_sha = _remote_ref_sha(repo, f"refs/heads/{default_name}")
767
805
  if live_default_sha != default_sha:
768
806
  raise PipelineGitError(f"fetched {remote_default} is stale relative to origin")
769
807
  ship_sha = _run_git(
@@ -821,7 +859,7 @@ def bind_next_milestone_branch(
821
859
  detail = landing_integrated.stderr.strip() or landing_integrated.stdout.strip()
822
860
  raise PipelineGitError(f"could not verify integrated branch: {detail}")
823
861
  if allow_remote_absent:
824
- proven_landing = _require_pull_request_integration_proof(
862
+ proven_landing = _require_tag_integration_proof(
825
863
  repo,
826
864
  ship_sha,
827
865
  base_sha,
@@ -829,7 +867,7 @@ def bind_next_milestone_branch(
829
867
  )
830
868
  if proven_landing != landing_sha:
831
869
  raise PipelineGitError(
832
- "validated landing does not match pull-request integration proof"
870
+ "validated landing does not match the milestone tag integration proof"
833
871
  )
834
872
 
835
873
  symbolic_branch = _run_git(
@@ -173,9 +173,14 @@ class PipelineState:
173
173
  integration_default: str
174
174
  integration: str
175
175
  integration_source: str
176
+ default_branch: str = "main"
176
177
 
177
178
  def json(self) -> dict[str, Optional[str]]:
178
- return asdict(self)
179
+ # ponytail: default_branch stays out of this payload; guard_hook, install
180
+ # and the daemon probe accept only the closed STATE_FIELDS set.
181
+ payload = asdict(self)
182
+ del payload["default_branch"]
183
+ return payload
179
184
 
180
185
 
181
186
  @dataclass(frozen=True)
@@ -290,7 +295,7 @@ def _bound_branch_number(value: str) -> Optional[int]:
290
295
  def _state_from_text(text: str, label: str = "STATE.md") -> PipelineState:
291
296
  values = _parse_frontmatter(text, label)
292
297
  missing = [field for field in LEGACY_STATE_FIELDS if field not in values]
293
- extra = sorted(set(values) - set(STATE_FIELDS))
298
+ extra = sorted(set(values) - set(STATE_FIELDS) - {"default_branch"})
294
299
  if missing:
295
300
  raise PipelineStateError(f"{label} is missing fields: {', '.join(missing)}")
296
301
  if extra:
@@ -322,6 +327,7 @@ def _state_from_text(text: str, label: str = "STATE.md") -> PipelineState:
322
327
  integration_default=integration_default,
323
328
  integration=integration,
324
329
  integration_source=integration_source,
330
+ default_branch=values.get("default_branch", "main"),
325
331
  )
326
332
  _validate_state_values(state, label)
327
333
  return state
@@ -355,6 +361,10 @@ def _validate_state_values(state: PipelineState, label: str) -> None:
355
361
  raise PipelineStateError(
356
362
  f"{label} default-sourced integration must match integration_default"
357
363
  )
364
+ if not _common.valid_default_branch(state.default_branch):
365
+ raise PipelineStateError(
366
+ f"{label} has invalid default_branch: {state.default_branch}"
367
+ )
358
368
  if state.branch is not None and _bound_branch_number(state.branch) is None:
359
369
  raise PipelineStateError(f"{label} has invalid branch: {state.branch}")
360
370
  archive_match = ARCHIVE_RE.fullmatch(state.archive or "")
@@ -416,6 +426,7 @@ def validate_state(repo: Path, project_dir: str = ".project") -> dict[str, objec
416
426
  "status": "valid",
417
427
  "path": str(path),
418
428
  "state": state.json(),
429
+ "default_branch": state.default_branch,
419
430
  }
420
431
 
421
432
 
@@ -698,8 +709,10 @@ def status_state(repo: Path, project_dir: str = ".project") -> dict[str, object]
698
709
  if branch
699
710
  else None
700
711
  )
712
+ # The key keeps its name for status consumers; the ref follows STATE.
713
+ default_branch = load_state(resolved, project_dir)[0].default_branch
701
714
  origin_main = (
702
- _optional_rev(resolved, "refs/remotes/origin/main") if has_git else None
715
+ _optional_rev(resolved, f"refs/remotes/origin/{default_branch}") if has_git else None
703
716
  )
704
717
  pending, pending_error = _pending_answers(resolved)
705
718
  lookahead = resolved / ".project" / "next"
@@ -722,6 +735,7 @@ def status_state(repo: Path, project_dir: str = ".project") -> dict[str, object]
722
735
  "advance": False,
723
736
  "completion": completion,
724
737
  "state": state,
738
+ "default_branch": default_branch,
725
739
  "route": route,
726
740
  "path": str(_track_root(resolved, project_dir) / "STATE.md"),
727
741
  "git": {
@@ -881,7 +895,12 @@ def _route_result(
881
895
  route["phase"] = phase
882
896
  if mode is not None:
883
897
  route["mode"] = mode
884
- return {"schema": ROUTE_SCHEMA, "state": state.json(), "route": route}
898
+ return {
899
+ "schema": ROUTE_SCHEMA,
900
+ "state": state.json(),
901
+ "default_branch": state.default_branch,
902
+ "route": route,
903
+ }
885
904
 
886
905
 
887
906
  def _legacy_bind_next_landing(repo: Path, state: PipelineState) -> str:
@@ -1009,7 +1028,7 @@ def _bind_next_journal_recovery(
1009
1028
  raise PipelineStateError("bind-next journal landing does not contain ship")
1010
1029
  if not _is_ancestor(repo, landing, base):
1011
1030
  raise PipelineStateError("bind-next journal landing is not an ancestor of base")
1012
- if transaction["remote_default"] != "origin/main":
1031
+ if transaction["remote_default"] != f"origin/{state.default_branch}":
1013
1032
  raise PipelineStateError("bind-next journal has invalid remote default")
1014
1033
  if stage not in {"prepared", "switched", "retired"}:
1015
1034
  raise PipelineStateError("bind-next journal has invalid stage")
@@ -1758,7 +1777,10 @@ def transition_state(
1758
1777
  if unsupported:
1759
1778
  raise PipelineStateError(f"unsupported state fields: {', '.join(sorted(unsupported))}")
1760
1779
  if not changes:
1761
- raise PipelineStateError("transition requires at least one changed field")
1780
+ raise PipelineStateError(
1781
+ "transition requires at least one changed field; "
1782
+ "to record only a Log event, pass --set-status with the current status"
1783
+ )
1762
1784
  required_expected = {"phase", "status", "branch", "archive"} | set(changes)
1763
1785
  missing_expected = sorted(required_expected - set(expected))
1764
1786
  if missing_expected:
@@ -198,10 +198,10 @@ def select_fetched_base(
198
198
  use_lookahead: bool,
199
199
  ) -> dict:
200
200
  root = repo.resolve()
201
- if not re.fullmatch(r"origin/[A-Za-z0-9][A-Za-z0-9._/-]*", remote_default):
201
+ if not remote_default.startswith("origin/") or not _common.valid_default_branch(
202
+ remote_default.removeprefix("origin/")
203
+ ):
202
204
  raise LookaheadError("remote default must be an origin branch")
203
- if ".." in remote_default or "//" in remote_default:
204
- raise LookaheadError("remote default is invalid")
205
205
  resolved = subprocess.run(
206
206
  ["git", "-C", str(root), "rev-parse", "--verify", f"{remote_default}^{{commit}}"],
207
207
  capture_output=True,
@@ -252,7 +252,12 @@ def activate_milestone(text: str, milestone: str) -> str:
252
252
  return "".join(lines)
253
253
 
254
254
 
255
- def reslice(before: str, candidate: str, phase: str, selected: str) -> str:
255
+ def _entry_identity(block: dict) -> tuple:
256
+ """Heading, Status, Depends on, Archive and Integrated of one entry."""
257
+ return block["id"], block["title"], {name: value for name, (_, value) in block["fields"].items()}
258
+
259
+
260
+ def reslice(before: str, candidate: str, phase: str, selected: str, amend_active: bool = False) -> str:
256
261
  old_blocks = roadmap_blocks(before)
257
262
  before_lines = before.splitlines(keepends=True)
258
263
  candidate_lines = candidate.splitlines(keepends=True)
@@ -264,6 +269,9 @@ def reslice(before: str, candidate: str, phase: str, selected: str) -> str:
264
269
  new = milestone_block(candidate, slug)
265
270
  if status == "shipped":
266
271
  changed = roadmap_contract(before, slug) != roadmap_contract(candidate, slug)
272
+ elif status == "active" and amend_active:
273
+ # Intent is not approved: the scope may change, the identity may not.
274
+ changed = _entry_identity(block) != _entry_identity(new)
267
275
  else:
268
276
  changed = before_lines[block["start"]:block["end"]] != candidate_lines[new["start"]:new["end"]]
269
277
  if changed:
@@ -164,9 +164,16 @@ def _approval_details(
164
164
  raise PipelineStateError(f"unsupported approval kind: {kind}")
165
165
 
166
166
 
167
- def _reslice_roadmap(before: str, candidate: str, state: PipelineState, selected: str) -> str:
167
+ def _reslice_roadmap(
168
+ repo: Path, before: str, candidate: str, state: PipelineState, state_text: str, selected: str,
169
+ ) -> str:
170
+ # Define may amend its active entry until intent approval. A return to
171
+ # define after that approval always leaves an active recovery record.
172
+ amend_active = state.phase == "define" and not (
173
+ pipeline_state._build_recovery().context(repo, state_text) or {}
174
+ ).get("active")
168
175
  try:
169
- return roadmap.reslice(before, candidate, state.phase, selected)
176
+ return roadmap.reslice(before, candidate, state.phase, selected, amend_active)
170
177
  except roadmap.RoadmapError as error:
171
178
  raise PipelineStateError(str(error)) from error
172
179
 
@@ -223,6 +230,10 @@ def _approval_journal(repo: Path, journal: Mapping[str, object]) -> dict[str, ob
223
230
  expected_keys.add("baseline_before")
224
231
  if not isinstance(journal.get("baseline_before"), str):
225
232
  raise PipelineStateError("checkpoint journal has an invalid re-slice baseline")
233
+ if "recovery_reviews" in journal:
234
+ expected_keys.add("recovery_reviews")
235
+ if journal.get("kind") != "plan" or not isinstance(journal["recovery_reviews"], dict):
236
+ raise PipelineStateError("checkpoint journal has an invalid recovery review report")
226
237
  if set(journal) != expected_keys:
227
238
  raise PipelineStateError("checkpoint journal has invalid fields")
228
239
  if journal.get("schema") != CHECKPOINT_SCHEMA or journal.get("repo") != str(repo):
@@ -307,7 +318,8 @@ def _approval_journal(repo: Path, journal: Mapping[str, object]) -> dict[str, ob
307
318
  if journal["roadmap_path"] != ".project/ROADMAP.md":
308
319
  raise PipelineStateError("checkpoint journal has an invalid ROADMAP path")
309
320
  expected_roadmap = _reslice_roadmap(
310
- str(journal["baseline_before"]), str(journal["roadmap_before"]), before, str(selected),
321
+ repo, str(journal["baseline_before"]), str(journal["roadmap_before"]), before,
322
+ str(journal["state_before"]), str(selected),
311
323
  ) if journal["kind"] == "roadmap-reslice" else _activate_roadmap_milestone(
312
324
  str(journal["roadmap_before"]),
313
325
  str(selected),
@@ -423,7 +435,7 @@ def _resume_checkpoint_locked(
423
435
  raise PipelineStateError("approval checkpoint returned the wrong commit")
424
436
  _unlink_checkpoint_journal(journal_path)
425
437
  state = pipeline_state._state_from_text(str(transaction["state_after"]))
426
- return {
438
+ approved: dict[str, object] = {
427
439
  "schema": CHECKPOINT_SCHEMA,
428
440
  "status": "approved",
429
441
  "kind": transaction["kind"],
@@ -432,6 +444,9 @@ def _resume_checkpoint_locked(
432
444
  "paths": committed.get("paths"),
433
445
  "state": state.json(),
434
446
  }
447
+ if "recovery_reviews" in transaction:
448
+ approved["recovery_reviews"] = transaction["recovery_reviews"]
449
+ return approved
435
450
 
436
451
 
437
452
  def _validate_plan_briefs(repo: Path, kind: str, project_dir: str) -> None:
@@ -563,7 +578,10 @@ def checkpoint_approval(
563
578
  f"{expected_head}^{{commit}}",
564
579
  ).stdout.strip()
565
580
  if resolved_head != expected_head:
566
- raise PipelineStateError("expected-head must be an exact full commit SHA")
581
+ raise PipelineStateError(
582
+ "expected-head must be an exact full commit SHA; "
583
+ "resolve a ref or a short SHA with git rev-parse first"
584
+ )
567
585
  if pipeline_state._run_git(resolved, "rev-parse", "HEAD").stdout.strip() != expected_head:
568
586
  raise PipelineStateError("approval must start at expected-head")
569
587
  active_state, _, _ = pipeline_state.load_state(resolved)
@@ -578,8 +596,9 @@ def checkpoint_approval(
578
596
  state, state_before, state_path = pipeline_state.load_state(resolved, project_dir)
579
597
  changes, event, subject, body = _approval_details(kind, state, selected_milestone)
580
598
  _validate_plan_briefs(resolved, kind, project_dir)
599
+ recovery_reviews = None
581
600
  if kind == "plan" and project_dir == ".project":
582
- pipeline_state._build_recovery().restore_unchanged_reviews(resolved)
601
+ recovery_reviews = pipeline_state._build_recovery().restore_unchanged_reviews(resolved)
583
602
  expected = {
584
603
  "phase": state.phase,
585
604
  "status": state.status,
@@ -611,7 +630,7 @@ def checkpoint_approval(
611
630
  if kind == "roadmap-reslice":
612
631
  baseline_before = pipeline_state._read_real_file(project / "ROADMAP.before-reslice.md", "re-slice baseline")
613
632
  roadmap_after = _reslice_roadmap(
614
- baseline_before, roadmap_before, state, selected_milestone,
633
+ resolved, baseline_before, roadmap_before, state, state_before, selected_milestone,
615
634
  ) if kind == "roadmap-reslice" else _activate_roadmap_milestone(
616
635
  roadmap_before,
617
636
  selected_milestone,
@@ -641,6 +660,8 @@ def checkpoint_approval(
641
660
  }
642
661
  if baseline_before is not None:
643
662
  journal["baseline_before"] = baseline_before
663
+ if recovery_reviews is not None:
664
+ journal["recovery_reviews"] = recovery_reviews
644
665
  pipeline_state._write_json(journal_path, journal)
645
666
  return _resume_checkpoint_locked(resolved, project, journal_path, journal)
646
667
 
@@ -722,8 +743,8 @@ def defer_approval(
722
743
  if kind == "roadmap-reslice":
723
744
  baseline = project / "ROADMAP.before-reslice.md"
724
745
  roadmap_after = _reslice_roadmap(
725
- pipeline_state._read_real_file(baseline, "re-slice baseline"),
726
- roadmap_before, state, selected_milestone,
746
+ resolved, pipeline_state._read_real_file(baseline, "re-slice baseline"),
747
+ roadmap_before, state, state_before, selected_milestone,
727
748
  )
728
749
  else:
729
750
  roadmap_after = _activate_roadmap_milestone(roadmap_before, selected_milestone)
@@ -377,17 +377,22 @@ def _prepare_promotion(
377
377
  head = pipeline_state._run_git(repo, "rev-parse", "HEAD").stdout.strip()
378
378
  if head != base:
379
379
  raise PipelineStateError(f"promotion must start at base SHA: {head} != {base}")
380
- remote = pipeline_state._run_git(repo, "rev-parse", "--verify", "origin/main^{commit}").stdout.strip()
380
+ active_state, active_text, _ = pipeline_state.load_state(repo)
381
+ remote_default = f"origin/{active_state.default_branch}"
382
+ remote = pipeline_state._run_git(
383
+ repo, "rev-parse", "--verify", f"{remote_default}^{{commit}}"
384
+ ).stdout.strip()
381
385
  if remote != base:
382
386
  raise PipelineStateError(
383
- f"base SHA is not current origin/main: {base} != {remote}"
387
+ f"base SHA is not current {remote_default}: {base} != {remote}"
384
388
  )
385
389
  if not pipeline_state._is_ancestor(repo, landing, base):
386
- raise PipelineStateError("landing is not an ancestor of the current main base")
390
+ raise PipelineStateError(
391
+ f"landing is not an ancestor of the current {active_state.default_branch} base"
392
+ )
387
393
  if pipeline_state._run_git(repo, "status", "--porcelain", "--untracked-files=all").stdout:
388
394
  raise PipelineStateError("promotion must start from a clean worktree")
389
395
 
390
- active_state, active_text, _ = pipeline_state.load_state(repo)
391
396
  if active_state.phase != "shipped" or active_state.status != "done":
392
397
  raise PipelineStateError("promotion requires active STATE shipped/done")
393
398
  if active_state.milestone is None:
@@ -456,6 +461,12 @@ def _prepare_promotion(
456
461
  raise PipelineStateError(
457
462
  "lookahead integration_default does not match the active project"
458
463
  )
464
+ if next_state.default_branch != active_state.default_branch:
465
+ raise PipelineStateError(
466
+ "lookahead default_branch does not match the active project: copy "
467
+ f"`default_branch: {active_state.default_branch}` from .project/STATE.md "
468
+ "into .project/next/STATE.md"
469
+ )
459
470
  if next_state.milestone != milestone:
460
471
  raise PipelineStateError(
461
472
  f"requested milestone {milestone} != next STATE milestone {next_state.milestone}"
@@ -27,7 +27,8 @@ return to `$gsd-path` for ownership checking. Legal entry is
27
27
  yet, `roadmap/active|blocked`, or a
28
28
  milestone-boundary re-slice: `inspect/active` or `define/active` with no
29
29
  approved INTENT.md for the next milestone and a user request to re-scope the
30
- remaining `pending` entries,
30
+ remaining `pending` entries or, at `define/active` only, to change the scope
31
+ of the `active` entry,
31
32
  or `roadmap/active` with STATE.milestone null and an `abandoned` entry in
32
33
  ROADMAP.md (the post-abandon re-slice the build orchestrator hands off).
33
34
  Enter from `decide/done` only with `pipeline_state.py transition`, exact
@@ -35,6 +36,8 @@ expected phase/status/milestone/branch/archive fields, event `roadmap started`,
35
36
  and `--set-phase roadmap --set-status active`; never edit STATE directly. In a
36
37
  milestone-boundary re-slice, preserve the entering `inspect/active` or
37
38
  `define/active` state throughout the re-slice.
39
+ A `define/active` state that the router reports in `corrections` mode already
40
+ has an approved INTENT.md; it blocks.
38
41
  Any later phase blocks; mid-milestone
39
42
  re-scope is never legal — finish or ship the active milestone first. A
40
43
  `decide/done` state with ROADMAP.md already present is milestone-scope decide;
@@ -71,8 +74,11 @@ precondition fails.
71
74
  give `.project/ROADMAP.before-reslice.md` with the instruction to preserve
72
75
  `shipped` entries byte-for-byte except Status/Archive/Integrated fields and
73
76
  to never modify `abandoned` entries at all. For a milestone-boundary
74
- re-slice, also preserve the existing `active` entry byte-for-byte; only
75
- `pending` entries are in scope.
77
+ re-slice entered at `inspect/active`, also preserve the existing `active`
78
+ entry byte-for-byte; only `pending` entries are in scope. Entered at
79
+ `define/active`, the `active` entry may also change as the user ruled, but
80
+ its heading (id and slug) and its Status, Depends on, Archive, and
81
+ Integrated fields stay unchanged.
76
82
  The output is exactly `.project/ROADMAP.md` — no other location is
77
83
  canonical.
78
84
  3. Gate ROADMAP.md:
@@ -96,8 +102,11 @@ precondition fails.
96
102
  - On a re-slice, `shipped` entries differ from
97
103
  `.project/ROADMAP.before-reslice.md` only in Status/Archive/Integrated;
98
104
  `abandoned` entries are byte-for-byte identical.
99
- - On a milestone-boundary re-slice, the prior `active` entry is byte-for-byte
100
- identical to `.project/ROADMAP.before-reslice.md`.
105
+ - On a milestone-boundary re-slice entered at `inspect/active`, the prior
106
+ `active` entry is byte-for-byte identical to
107
+ `.project/ROADMAP.before-reslice.md`. Entered at `define/active`, it
108
+ stays the only `active` entry and keeps its heading and its Status,
109
+ Depends on, Archive, and Integrated fields.
101
110
  4. Redispatch one complete corrected brief under logical task name `roadmap`,
102
111
  following the runtime dispatch contract and including all gate failures.
103
112
  Allow one revision round. If it still fails, keep the entering
@@ -160,7 +169,8 @@ precondition fails.
160
169
 
161
170
  The helper owns roadmap selection, expected state, baseline cleanup and
162
171
  the approval checkpoint. It journals before mutation, retains the baseline
163
- bytes for recovery, and refuses changes to settled or active entries.
172
+ bytes for recovery, and refuses changes to settled entries and every
173
+ `active` entry change that step 2 does not permit.
164
174
  A milestone-boundary re-slice retains Inspect or Define; a post-abandon
165
175
  re-slice enters Inspect. Require its typed result to report `status:
166
176
  approved`, the requested kind, milestone and returned commit. After an
@@ -38,8 +38,8 @@ bundles this file; spawned agents follow their role brief instead.
38
38
  the lookahead planning track for the next milestone; it follows the same
39
39
  marker rule, owns no task frontmatter, and never binds a branch.
40
40
  - STATE.integration_default is the project closeout choice and
41
- STATE.integration is the current milestone choice. Both are `direct` or
42
- `pull-request`; STATE.integration_source is `default` or `milestone` and
41
+ STATE.integration is the current milestone choice. Both are `direct`,
42
+ `pull-request`, or `external-landing`; STATE.integration_source is `default` or `milestone` and
43
43
  preserves explicit override provenance. Older v2 state defaults to `direct`,
44
44
  and modes may be changed only through `pipeline_state.py
45
45
  configure-integration` before build. A next milestone resets its current
@@ -82,7 +82,9 @@ bundles this file; spawned agents follow their role brief instead.
82
82
  `isolation.py attest` may stand in for it: one `.project/`-only commit that
83
83
  names the base, the attested HEAD, the declared files that changed, the
84
84
  task Verify recorded as passing at that HEAD, and the ruling verbatim.
85
- `recover` reports it as `attested`, never as a proven landing.
85
+ `recover` reports it as `attested`, never as a proven landing. Attestation
86
+ matches the declared files literally: work that is only below a declared
87
+ directory can land but cannot be attested.
86
88
  - All pipeline work for a milestone lives on `gsd-path/M00N` (M001, M002,
87
89
  …) bound in STATE.branch. The next milestone binds a new unused
88
90
  `gsd-path/M00N` at the remote default after the previous ship integrates.
@@ -90,6 +92,12 @@ bundles this file; spawned agents follow their role brief instead.
90
92
  branch — either Path's direct merge or a Path-owned PR merged by the user.
91
93
  The bound branch never receives merges or back-merges and is never the
92
94
  GitHub default.
95
+ - The default branch is the one that STATE `default_branch` records at
96
+ initialization; a STATE file without the field means `main`. `main` and
97
+ `origin/main` in these rules mean that branch. Never change the record and
98
+ never take the name from a later `origin/HEAD` lookup. For a member
99
+ repository, the branch is the one that its `.project/MEMBERS.md` section
100
+ records as `Default branch`; a section without the field means `main`.
93
101
  - After initialization writes STATE.md, and before entering the first pipeline
94
102
  phase in an existing Git repository, the router calls
95
103
  `scripts/pipeline_git.py bind-initial` with the selected M00N and exact
@@ -20,6 +20,15 @@ references to bundled copies of those helpers. Other skill helpers, templates,
20
20
  and role briefs remain in the installed skill bundle. Pass `-B` to Python helper
21
21
  invocations so execution does not create bytecode in the runtime store.
22
22
 
23
+ The selected runtime is authoritative for each helper in its `manifest.json`;
24
+ this includes `review_findings.py` and `task_context.py`. A selected runtime
25
+ that was published before a helper became a runtime file does not list that
26
+ helper: run the bundled copy until the project runtime is upgraded.
27
+ `dispatch_driver.py` and `workflow_run.py` always run from the installed skill
28
+ bundle, because they read role briefs and templates from it; a skill update
29
+ replaces them, a runtime upgrade does not. The driver reloads the helper modules
30
+ that it imports from the selected runtime.
31
+
23
32
  With no declaration, retain the legacy helper lookup. An existing
24
33
  `.gsd-path/runtime/` requires the explicit `--runtime-migrate --project PATH`
25
34
  operation before adopting external storage; review the resulting Git diff.