@opengsd/gsd-path 1.4.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 (476) hide show
  1. package/CHANGELOG.md +460 -341
  2. package/DOCS.md +16 -5
  3. package/FULL.md +67 -6
  4. package/HOOKS.md +158 -10
  5. package/MIGRATE.md +12 -0
  6. package/QUICK.md +4 -2
  7. package/README.md +136 -116
  8. package/RUNTIME.md +24 -6
  9. package/UPDATE.md +39 -3
  10. package/WORKFLOW.md +80 -4
  11. package/package.json +5 -2
  12. package/platforms/shared-agents/dispatch.md +13 -0
  13. package/scripts/_common.py +256 -2
  14. package/scripts/archive_milestone.py +14 -13
  15. package/scripts/build_recovery.py +155 -58
  16. package/scripts/build_state.py +274 -8
  17. package/scripts/check_docs_audit.py +77 -4
  18. package/scripts/check_handoffs.py +635 -57
  19. package/scripts/check_task_briefs.py +293 -32
  20. package/scripts/detect_project.py +260 -20
  21. package/scripts/discussion_records.py +9 -1
  22. package/scripts/discussion_validate.py +93 -35
  23. package/scripts/dispatch_driver.py +549 -53
  24. package/scripts/git_guard.py +27 -8
  25. package/scripts/guard_hook.py +2630 -210
  26. package/scripts/install.mjs +99 -11
  27. package/scripts/install.py +231 -46
  28. package/scripts/integration.py +244 -65
  29. package/scripts/isolation.py +539 -70
  30. package/scripts/loop_run.py +16 -4
  31. package/scripts/members.py +77 -32
  32. package/scripts/migrate_core.py +2 -0
  33. package/scripts/model_policy.py +5 -1
  34. package/scripts/path_config.py +3 -3
  35. package/scripts/pipeline_diagnose.py +17 -2
  36. package/scripts/pipeline_git.py +131 -42
  37. package/scripts/pipeline_state.py +139 -39
  38. package/scripts/pipeline_undo.py +118 -16
  39. package/scripts/promote_lookahead.py +3 -3
  40. package/scripts/review_findings.py +180 -44
  41. package/scripts/roadmap.py +9 -1
  42. package/scripts/runtime_store.py +21 -2
  43. package/scripts/skill-resources.json +9 -2
  44. package/scripts/state_checkpoint.py +39 -36
  45. package/scripts/state_promote.py +15 -4
  46. package/scripts/status_runtime.py +22 -4
  47. package/scripts/task_context.py +14 -3
  48. package/scripts/wizard.mjs +1 -0
  49. package/scripts/workflow_run.py +14 -4
  50. package/scripts/worktree_paths.py +54 -2
  51. package/skills/gsd-path/DEFINE.md +34 -5
  52. package/skills/gsd-path/PLAN.md +14 -3
  53. package/skills/gsd-path/RESEARCH.md +11 -0
  54. package/skills/gsd-path/ROADMAP.md +16 -6
  55. package/skills/gsd-path/SHIP.md +32 -5
  56. package/skills/gsd-path/SKILL.md +52 -24
  57. package/skills/gsd-path/references/build-native.md +112 -10
  58. package/skills/gsd-path/references/build-recovery.md +13 -0
  59. package/skills/gsd-path/references/coder.md +7 -3
  60. package/skills/gsd-path/references/dispatch.md +13 -0
  61. package/skills/gsd-path/references/docs-auditor.md +5 -0
  62. package/skills/gsd-path/references/operating-rules.md +11 -3
  63. package/skills/gsd-path/references/planner.md +26 -4
  64. package/skills/gsd-path/references/reviewer-final.md +20 -2
  65. package/skills/gsd-path/references/reviewer-wave.md +56 -12
  66. package/skills/gsd-path/references/reviewer.md +3 -1
  67. package/skills/gsd-path/references/runtime-selection.md +9 -0
  68. package/skills/gsd-path/references/spec-probes.md +150 -0
  69. package/skills/gsd-path/scripts/_common.py +256 -2
  70. package/skills/gsd-path/scripts/archive_milestone.py +14 -13
  71. package/skills/gsd-path/scripts/build_recovery.py +155 -58
  72. package/skills/gsd-path/scripts/build_state.py +274 -8
  73. package/skills/gsd-path/scripts/check_docs_audit.py +77 -4
  74. package/skills/gsd-path/scripts/check_handoffs.py +635 -57
  75. package/skills/gsd-path/scripts/check_task_briefs.py +293 -32
  76. package/skills/gsd-path/scripts/detect_project.py +260 -20
  77. package/skills/gsd-path/scripts/discussion_records.py +9 -1
  78. package/skills/gsd-path/scripts/discussion_validate.py +93 -35
  79. package/skills/gsd-path/scripts/dispatch_driver.py +549 -53
  80. package/skills/gsd-path/scripts/git_guard.py +27 -8
  81. package/skills/gsd-path/scripts/integration.py +244 -65
  82. package/skills/gsd-path/scripts/isolation.py +539 -70
  83. package/skills/gsd-path/scripts/loop_run.py +16 -4
  84. package/skills/gsd-path/scripts/members.py +77 -32
  85. package/skills/gsd-path/scripts/model_policy.py +5 -1
  86. package/skills/gsd-path/scripts/path_config.py +3 -3
  87. package/skills/gsd-path/scripts/pipeline_git.py +131 -42
  88. package/skills/gsd-path/scripts/pipeline_state.py +139 -39
  89. package/skills/gsd-path/scripts/promote_lookahead.py +3 -3
  90. package/skills/gsd-path/scripts/review_findings.py +180 -44
  91. package/skills/gsd-path/scripts/roadmap.py +9 -1
  92. package/skills/gsd-path/scripts/state_checkpoint.py +39 -36
  93. package/skills/gsd-path/scripts/state_promote.py +15 -4
  94. package/skills/gsd-path/scripts/status_runtime.py +22 -4
  95. package/skills/gsd-path/scripts/task_context.py +14 -3
  96. package/skills/gsd-path/scripts/workflow_run.py +14 -4
  97. package/skills/gsd-path/scripts/worktree_paths.py +54 -2
  98. package/skills/gsd-path/templates/docs-audit.md +5 -1
  99. package/skills/gsd-path/templates/final-review.md +11 -2
  100. package/skills/gsd-path/templates/intent.md +30 -0
  101. package/skills/gsd-path/templates/patch-findings.md +3 -3
  102. package/skills/gsd-path/templates/plan.md +17 -2
  103. package/skills/gsd-path/templates/research-handoff.md +14 -3
  104. package/skills/gsd-path/templates/state.md +2 -1
  105. package/skills/gsd-path/templates/task.md +1 -0
  106. package/skills/gsd-path/templates/wave-review.md +28 -9
  107. package/skills/gsd-path-build/references/build-native.md +112 -10
  108. package/skills/gsd-path-build/references/build-recovery.md +13 -0
  109. package/skills/gsd-path-build/references/coder.md +7 -3
  110. package/skills/gsd-path-build/references/dispatch.md +13 -0
  111. package/skills/gsd-path-build/references/operating-rules.md +11 -3
  112. package/skills/gsd-path-build/references/reviewer-final.md +20 -2
  113. package/skills/gsd-path-build/references/reviewer-wave.md +56 -12
  114. package/skills/gsd-path-build/references/reviewer.md +3 -1
  115. package/skills/gsd-path-build/references/runtime-selection.md +9 -0
  116. package/skills/gsd-path-build/references/spec-probes.md +150 -0
  117. package/skills/gsd-path-build/scripts/_common.py +256 -2
  118. package/skills/gsd-path-build/scripts/archive_milestone.py +14 -13
  119. package/skills/gsd-path-build/scripts/build_recovery.py +155 -58
  120. package/skills/gsd-path-build/scripts/build_state.py +274 -8
  121. package/skills/gsd-path-build/scripts/check_handoffs.py +635 -57
  122. package/skills/gsd-path-build/scripts/check_task_briefs.py +293 -32
  123. package/skills/gsd-path-build/scripts/detect_project.py +260 -20
  124. package/skills/gsd-path-build/scripts/discussion_records.py +9 -1
  125. package/skills/gsd-path-build/scripts/discussion_validate.py +93 -35
  126. package/skills/gsd-path-build/scripts/dispatch_driver.py +549 -53
  127. package/skills/gsd-path-build/scripts/integration.py +244 -65
  128. package/skills/gsd-path-build/scripts/isolation.py +539 -70
  129. package/skills/gsd-path-build/scripts/loop_run.py +16 -4
  130. package/skills/gsd-path-build/scripts/members.py +77 -32
  131. package/skills/gsd-path-build/scripts/model_policy.py +5 -1
  132. package/skills/gsd-path-build/scripts/path_config.py +3 -3
  133. package/skills/gsd-path-build/scripts/pipeline_git.py +131 -42
  134. package/skills/gsd-path-build/scripts/pipeline_state.py +139 -39
  135. package/skills/gsd-path-build/scripts/promote_lookahead.py +3 -3
  136. package/skills/gsd-path-build/scripts/review_findings.py +180 -44
  137. package/skills/gsd-path-build/scripts/roadmap.py +9 -1
  138. package/skills/gsd-path-build/scripts/state_checkpoint.py +39 -36
  139. package/skills/gsd-path-build/scripts/state_promote.py +15 -4
  140. package/skills/gsd-path-build/scripts/task_context.py +14 -3
  141. package/skills/gsd-path-build/scripts/workflow_run.py +14 -4
  142. package/skills/gsd-path-build/scripts/worktree_paths.py +54 -2
  143. package/skills/gsd-path-build/templates/task.md +1 -0
  144. package/skills/gsd-path-build/templates/wave-review.md +28 -9
  145. package/skills/gsd-path-decide/references/dispatch.md +13 -0
  146. package/skills/gsd-path-decide/references/operating-rules.md +11 -3
  147. package/skills/gsd-path-decide/references/runtime-selection.md +9 -0
  148. package/skills/gsd-path-decide/scripts/_common.py +256 -2
  149. package/skills/gsd-path-decide/scripts/archive_milestone.py +14 -13
  150. package/skills/gsd-path-decide/scripts/build_recovery.py +155 -58
  151. package/skills/gsd-path-decide/scripts/check_handoffs.py +635 -57
  152. package/skills/gsd-path-decide/scripts/check_task_briefs.py +293 -32
  153. package/skills/gsd-path-decide/scripts/detect_project.py +260 -20
  154. package/skills/gsd-path-decide/scripts/discussion_records.py +9 -1
  155. package/skills/gsd-path-decide/scripts/discussion_validate.py +93 -35
  156. package/skills/gsd-path-decide/scripts/integration.py +244 -65
  157. package/skills/gsd-path-decide/scripts/isolation.py +539 -70
  158. package/skills/gsd-path-decide/scripts/loop_run.py +16 -4
  159. package/skills/gsd-path-decide/scripts/members.py +77 -32
  160. package/skills/gsd-path-decide/scripts/model_policy.py +5 -1
  161. package/skills/gsd-path-decide/scripts/path_config.py +3 -3
  162. package/skills/gsd-path-decide/scripts/pipeline_git.py +131 -42
  163. package/skills/gsd-path-decide/scripts/pipeline_state.py +139 -39
  164. package/skills/gsd-path-decide/scripts/promote_lookahead.py +3 -3
  165. package/skills/gsd-path-decide/scripts/roadmap.py +9 -1
  166. package/skills/gsd-path-decide/scripts/state_checkpoint.py +39 -36
  167. package/skills/gsd-path-decide/scripts/state_promote.py +15 -4
  168. package/skills/gsd-path-decide/scripts/worktree_paths.py +54 -2
  169. package/skills/gsd-path-define/SKILL.md +34 -5
  170. package/skills/gsd-path-define/references/build-recovery.md +13 -0
  171. package/skills/gsd-path-define/references/operating-rules.md +11 -3
  172. package/skills/gsd-path-define/references/runtime-selection.md +9 -0
  173. package/skills/gsd-path-define/references/spec-probes.md +150 -0
  174. package/skills/gsd-path-define/scripts/_common.py +256 -2
  175. package/skills/gsd-path-define/scripts/archive_milestone.py +14 -13
  176. package/skills/gsd-path-define/scripts/build_recovery.py +155 -58
  177. package/skills/gsd-path-define/scripts/check_handoffs.py +635 -57
  178. package/skills/gsd-path-define/scripts/check_task_briefs.py +293 -32
  179. package/skills/gsd-path-define/scripts/detect_project.py +260 -20
  180. package/skills/gsd-path-define/scripts/discussion_records.py +9 -1
  181. package/skills/gsd-path-define/scripts/discussion_validate.py +93 -35
  182. package/skills/gsd-path-define/scripts/integration.py +244 -65
  183. package/skills/gsd-path-define/scripts/isolation.py +539 -70
  184. package/skills/gsd-path-define/scripts/loop_run.py +16 -4
  185. package/skills/gsd-path-define/scripts/members.py +77 -32
  186. package/skills/gsd-path-define/scripts/model_policy.py +5 -1
  187. package/skills/gsd-path-define/scripts/path_config.py +3 -3
  188. package/skills/gsd-path-define/scripts/pipeline_git.py +131 -42
  189. package/skills/gsd-path-define/scripts/pipeline_state.py +139 -39
  190. package/skills/gsd-path-define/scripts/promote_lookahead.py +3 -3
  191. package/skills/gsd-path-define/scripts/roadmap.py +9 -1
  192. package/skills/gsd-path-define/scripts/state_checkpoint.py +39 -36
  193. package/skills/gsd-path-define/scripts/state_promote.py +15 -4
  194. package/skills/gsd-path-define/scripts/worktree_paths.py +54 -2
  195. package/skills/gsd-path-define/templates/intent.md +30 -0
  196. package/skills/gsd-path-define/templates/state.md +2 -1
  197. package/skills/gsd-path-discuss/references/operating-rules.md +11 -3
  198. package/skills/gsd-path-discuss/references/runtime-selection.md +9 -0
  199. package/skills/gsd-path-discuss/scripts/_common.py +256 -2
  200. package/skills/gsd-path-discuss/scripts/discussion_records.py +9 -1
  201. package/skills/gsd-path-discuss/scripts/loop_run.py +16 -4
  202. package/skills/gsd-path-discuss/scripts/model_policy.py +5 -1
  203. package/skills/gsd-path-discuss/scripts/path_config.py +3 -3
  204. package/skills/gsd-path-discuss/scripts/worktree_paths.py +54 -2
  205. package/skills/gsd-path-docs-audit/references/dispatch.md +13 -0
  206. package/skills/gsd-path-docs-audit/references/docs-auditor.md +5 -0
  207. package/skills/gsd-path-docs-audit/references/operating-rules.md +11 -3
  208. package/skills/gsd-path-docs-audit/references/runtime-selection.md +9 -0
  209. package/skills/gsd-path-docs-audit/scripts/_common.py +256 -2
  210. package/skills/gsd-path-docs-audit/scripts/archive_milestone.py +14 -13
  211. package/skills/gsd-path-docs-audit/scripts/build_recovery.py +155 -58
  212. package/skills/gsd-path-docs-audit/scripts/check_docs_audit.py +77 -4
  213. package/skills/gsd-path-docs-audit/scripts/check_handoffs.py +635 -57
  214. package/skills/gsd-path-docs-audit/scripts/check_task_briefs.py +293 -32
  215. package/skills/gsd-path-docs-audit/scripts/detect_project.py +260 -20
  216. package/skills/gsd-path-docs-audit/scripts/discussion_records.py +9 -1
  217. package/skills/gsd-path-docs-audit/scripts/discussion_validate.py +93 -35
  218. package/skills/gsd-path-docs-audit/scripts/integration.py +244 -65
  219. package/skills/gsd-path-docs-audit/scripts/isolation.py +539 -70
  220. package/skills/gsd-path-docs-audit/scripts/loop_run.py +16 -4
  221. package/skills/gsd-path-docs-audit/scripts/members.py +77 -32
  222. package/skills/gsd-path-docs-audit/scripts/model_policy.py +5 -1
  223. package/skills/gsd-path-docs-audit/scripts/path_config.py +3 -3
  224. package/skills/gsd-path-docs-audit/scripts/pipeline_git.py +131 -42
  225. package/skills/gsd-path-docs-audit/scripts/pipeline_state.py +139 -39
  226. package/skills/gsd-path-docs-audit/scripts/promote_lookahead.py +3 -3
  227. package/skills/gsd-path-docs-audit/scripts/roadmap.py +9 -1
  228. package/skills/gsd-path-docs-audit/scripts/state_checkpoint.py +39 -36
  229. package/skills/gsd-path-docs-audit/scripts/state_promote.py +15 -4
  230. package/skills/gsd-path-docs-audit/scripts/worktree_paths.py +54 -2
  231. package/skills/gsd-path-docs-audit/templates/docs-audit.md +5 -1
  232. package/skills/gsd-path-forensics/references/operating-rules.md +11 -3
  233. package/skills/gsd-path-forensics/references/runtime-selection.md +9 -0
  234. package/skills/gsd-path-forensics/scripts/_common.py +256 -2
  235. package/skills/gsd-path-forensics/scripts/discussion_records.py +9 -1
  236. package/skills/gsd-path-forensics/scripts/loop_run.py +16 -4
  237. package/skills/gsd-path-forensics/scripts/model_policy.py +5 -1
  238. package/skills/gsd-path-forensics/scripts/path_config.py +3 -3
  239. package/skills/gsd-path-forensics/scripts/pipeline_diagnose.py +17 -2
  240. package/skills/gsd-path-forensics/scripts/pipeline_undo.py +118 -16
  241. package/skills/gsd-path-forensics/scripts/worktree_paths.py +54 -2
  242. package/skills/gsd-path-inspect/references/dispatch.md +13 -0
  243. package/skills/gsd-path-inspect/references/docs-auditor.md +5 -0
  244. package/skills/gsd-path-inspect/references/operating-rules.md +11 -3
  245. package/skills/gsd-path-inspect/references/runtime-selection.md +9 -0
  246. package/skills/gsd-path-inspect/scripts/_common.py +256 -2
  247. package/skills/gsd-path-inspect/scripts/archive_milestone.py +14 -13
  248. package/skills/gsd-path-inspect/scripts/build_recovery.py +155 -58
  249. package/skills/gsd-path-inspect/scripts/check_docs_audit.py +77 -4
  250. package/skills/gsd-path-inspect/scripts/check_handoffs.py +635 -57
  251. package/skills/gsd-path-inspect/scripts/check_task_briefs.py +293 -32
  252. package/skills/gsd-path-inspect/scripts/detect_project.py +260 -20
  253. package/skills/gsd-path-inspect/scripts/discussion_records.py +9 -1
  254. package/skills/gsd-path-inspect/scripts/discussion_validate.py +93 -35
  255. package/skills/gsd-path-inspect/scripts/integration.py +244 -65
  256. package/skills/gsd-path-inspect/scripts/isolation.py +539 -70
  257. package/skills/gsd-path-inspect/scripts/loop_run.py +16 -4
  258. package/skills/gsd-path-inspect/scripts/members.py +77 -32
  259. package/skills/gsd-path-inspect/scripts/model_policy.py +5 -1
  260. package/skills/gsd-path-inspect/scripts/path_config.py +3 -3
  261. package/skills/gsd-path-inspect/scripts/pipeline_git.py +131 -42
  262. package/skills/gsd-path-inspect/scripts/pipeline_state.py +139 -39
  263. package/skills/gsd-path-inspect/scripts/promote_lookahead.py +3 -3
  264. package/skills/gsd-path-inspect/scripts/roadmap.py +9 -1
  265. package/skills/gsd-path-inspect/scripts/state_checkpoint.py +39 -36
  266. package/skills/gsd-path-inspect/scripts/state_promote.py +15 -4
  267. package/skills/gsd-path-inspect/scripts/workflow_run.py +14 -4
  268. package/skills/gsd-path-inspect/scripts/worktree_paths.py +54 -2
  269. package/skills/gsd-path-inspect/templates/docs-audit.md +5 -1
  270. package/skills/gsd-path-inspect/templates/state.md +2 -1
  271. package/skills/gsd-path-loop/references/operating-rules.md +11 -3
  272. package/skills/gsd-path-loop/references/runtime-selection.md +9 -0
  273. package/skills/gsd-path-loop/scripts/_common.py +256 -2
  274. package/skills/gsd-path-loop/scripts/discussion_records.py +9 -1
  275. package/skills/gsd-path-loop/scripts/loop_run.py +16 -4
  276. package/skills/gsd-path-loop/scripts/model_policy.py +5 -1
  277. package/skills/gsd-path-loop/scripts/path_config.py +3 -3
  278. package/skills/gsd-path-loop/scripts/worktree_paths.py +54 -2
  279. package/skills/gsd-path-migrate/SKILL.md +8 -0
  280. package/skills/gsd-path-migrate/references/operating-rules.md +11 -3
  281. package/skills/gsd-path-migrate/references/runtime-selection.md +9 -0
  282. package/skills/gsd-path-migrate/scripts/_common.py +256 -2
  283. package/skills/gsd-path-migrate/scripts/discussion_records.py +9 -1
  284. package/skills/gsd-path-migrate/scripts/loop_run.py +16 -4
  285. package/skills/gsd-path-migrate/scripts/migrate_core.py +2 -0
  286. package/skills/gsd-path-migrate/scripts/model_policy.py +5 -1
  287. package/skills/gsd-path-migrate/scripts/path_config.py +3 -3
  288. package/skills/gsd-path-plan/SKILL.md +14 -3
  289. package/skills/gsd-path-plan/references/build-recovery.md +13 -0
  290. package/skills/gsd-path-plan/references/dispatch.md +13 -0
  291. package/skills/gsd-path-plan/references/operating-rules.md +11 -3
  292. package/skills/gsd-path-plan/references/planner.md +26 -4
  293. package/skills/gsd-path-plan/references/runtime-selection.md +9 -0
  294. package/skills/gsd-path-plan/references/spec-probes.md +150 -0
  295. package/skills/gsd-path-plan/scripts/_common.py +256 -2
  296. package/skills/gsd-path-plan/scripts/archive_milestone.py +14 -13
  297. package/skills/gsd-path-plan/scripts/build_recovery.py +155 -58
  298. package/skills/gsd-path-plan/scripts/check_handoffs.py +635 -57
  299. package/skills/gsd-path-plan/scripts/check_task_briefs.py +293 -32
  300. package/skills/gsd-path-plan/scripts/detect_project.py +260 -20
  301. package/skills/gsd-path-plan/scripts/discussion_records.py +9 -1
  302. package/skills/gsd-path-plan/scripts/discussion_validate.py +93 -35
  303. package/skills/gsd-path-plan/scripts/integration.py +244 -65
  304. package/skills/gsd-path-plan/scripts/isolation.py +539 -70
  305. package/skills/gsd-path-plan/scripts/loop_run.py +16 -4
  306. package/skills/gsd-path-plan/scripts/members.py +77 -32
  307. package/skills/gsd-path-plan/scripts/model_policy.py +5 -1
  308. package/skills/gsd-path-plan/scripts/path_config.py +3 -3
  309. package/skills/gsd-path-plan/scripts/pipeline_git.py +131 -42
  310. package/skills/gsd-path-plan/scripts/pipeline_state.py +139 -39
  311. package/skills/gsd-path-plan/scripts/promote_lookahead.py +3 -3
  312. package/skills/gsd-path-plan/scripts/roadmap.py +9 -1
  313. package/skills/gsd-path-plan/scripts/state_checkpoint.py +39 -36
  314. package/skills/gsd-path-plan/scripts/state_promote.py +15 -4
  315. package/skills/gsd-path-plan/scripts/workflow_run.py +14 -4
  316. package/skills/gsd-path-plan/scripts/worktree_paths.py +54 -2
  317. package/skills/gsd-path-plan/templates/patch-findings.md +3 -3
  318. package/skills/gsd-path-plan/templates/plan.md +17 -2
  319. package/skills/gsd-path-plan/templates/task.md +1 -0
  320. package/skills/gsd-path-research/SKILL.md +11 -0
  321. package/skills/gsd-path-research/references/dispatch.md +13 -0
  322. package/skills/gsd-path-research/references/operating-rules.md +11 -3
  323. package/skills/gsd-path-research/references/runtime-selection.md +9 -0
  324. package/skills/gsd-path-research/scripts/_common.py +256 -2
  325. package/skills/gsd-path-research/scripts/archive_milestone.py +14 -13
  326. package/skills/gsd-path-research/scripts/build_recovery.py +155 -58
  327. package/skills/gsd-path-research/scripts/check_handoffs.py +635 -57
  328. package/skills/gsd-path-research/scripts/check_task_briefs.py +293 -32
  329. package/skills/gsd-path-research/scripts/detect_project.py +260 -20
  330. package/skills/gsd-path-research/scripts/discussion_records.py +9 -1
  331. package/skills/gsd-path-research/scripts/discussion_validate.py +93 -35
  332. package/skills/gsd-path-research/scripts/integration.py +244 -65
  333. package/skills/gsd-path-research/scripts/isolation.py +539 -70
  334. package/skills/gsd-path-research/scripts/loop_run.py +16 -4
  335. package/skills/gsd-path-research/scripts/members.py +77 -32
  336. package/skills/gsd-path-research/scripts/model_policy.py +5 -1
  337. package/skills/gsd-path-research/scripts/path_config.py +3 -3
  338. package/skills/gsd-path-research/scripts/pipeline_git.py +131 -42
  339. package/skills/gsd-path-research/scripts/pipeline_state.py +139 -39
  340. package/skills/gsd-path-research/scripts/promote_lookahead.py +3 -3
  341. package/skills/gsd-path-research/scripts/roadmap.py +9 -1
  342. package/skills/gsd-path-research/scripts/state_checkpoint.py +39 -36
  343. package/skills/gsd-path-research/scripts/state_promote.py +15 -4
  344. package/skills/gsd-path-research/scripts/worktree_paths.py +54 -2
  345. package/skills/gsd-path-research/templates/research-handoff.md +14 -3
  346. package/skills/gsd-path-roadmap/SKILL.md +16 -6
  347. package/skills/gsd-path-roadmap/references/dispatch.md +13 -0
  348. package/skills/gsd-path-roadmap/references/operating-rules.md +11 -3
  349. package/skills/gsd-path-roadmap/references/runtime-selection.md +9 -0
  350. package/skills/gsd-path-roadmap/scripts/_common.py +256 -2
  351. package/skills/gsd-path-roadmap/scripts/archive_milestone.py +14 -13
  352. package/skills/gsd-path-roadmap/scripts/build_recovery.py +155 -58
  353. package/skills/gsd-path-roadmap/scripts/check_handoffs.py +635 -57
  354. package/skills/gsd-path-roadmap/scripts/check_task_briefs.py +293 -32
  355. package/skills/gsd-path-roadmap/scripts/detect_project.py +260 -20
  356. package/skills/gsd-path-roadmap/scripts/discussion_records.py +9 -1
  357. package/skills/gsd-path-roadmap/scripts/discussion_validate.py +93 -35
  358. package/skills/gsd-path-roadmap/scripts/integration.py +244 -65
  359. package/skills/gsd-path-roadmap/scripts/isolation.py +539 -70
  360. package/skills/gsd-path-roadmap/scripts/loop_run.py +16 -4
  361. package/skills/gsd-path-roadmap/scripts/members.py +77 -32
  362. package/skills/gsd-path-roadmap/scripts/model_policy.py +5 -1
  363. package/skills/gsd-path-roadmap/scripts/path_config.py +3 -3
  364. package/skills/gsd-path-roadmap/scripts/pipeline_git.py +131 -42
  365. package/skills/gsd-path-roadmap/scripts/pipeline_state.py +139 -39
  366. package/skills/gsd-path-roadmap/scripts/promote_lookahead.py +3 -3
  367. package/skills/gsd-path-roadmap/scripts/roadmap.py +9 -1
  368. package/skills/gsd-path-roadmap/scripts/state_checkpoint.py +39 -36
  369. package/skills/gsd-path-roadmap/scripts/state_promote.py +15 -4
  370. package/skills/gsd-path-roadmap/scripts/worktree_paths.py +54 -2
  371. package/skills/gsd-path-ship/SKILL.md +32 -5
  372. package/skills/gsd-path-ship/references/dispatch.md +13 -0
  373. package/skills/gsd-path-ship/references/operating-rules.md +11 -3
  374. package/skills/gsd-path-ship/references/reviewer-final.md +20 -2
  375. package/skills/gsd-path-ship/references/reviewer-wave.md +56 -12
  376. package/skills/gsd-path-ship/references/reviewer.md +3 -1
  377. package/skills/gsd-path-ship/references/runtime-selection.md +9 -0
  378. package/skills/gsd-path-ship/references/spec-probes.md +150 -0
  379. package/skills/gsd-path-ship/scripts/_common.py +256 -2
  380. package/skills/gsd-path-ship/scripts/archive_milestone.py +14 -13
  381. package/skills/gsd-path-ship/scripts/build_recovery.py +155 -58
  382. package/skills/gsd-path-ship/scripts/build_state.py +274 -8
  383. package/skills/gsd-path-ship/scripts/check_handoffs.py +635 -57
  384. package/skills/gsd-path-ship/scripts/check_task_briefs.py +293 -32
  385. package/skills/gsd-path-ship/scripts/detect_project.py +260 -20
  386. package/skills/gsd-path-ship/scripts/discussion_records.py +9 -1
  387. package/skills/gsd-path-ship/scripts/discussion_validate.py +93 -35
  388. package/skills/gsd-path-ship/scripts/integration.py +244 -65
  389. package/skills/gsd-path-ship/scripts/isolation.py +539 -70
  390. package/skills/gsd-path-ship/scripts/loop_run.py +16 -4
  391. package/skills/gsd-path-ship/scripts/members.py +77 -32
  392. package/skills/gsd-path-ship/scripts/model_policy.py +5 -1
  393. package/skills/gsd-path-ship/scripts/path_config.py +3 -3
  394. package/skills/gsd-path-ship/scripts/pipeline_git.py +131 -42
  395. package/skills/gsd-path-ship/scripts/pipeline_state.py +139 -39
  396. package/skills/gsd-path-ship/scripts/promote_lookahead.py +3 -3
  397. package/skills/gsd-path-ship/scripts/roadmap.py +9 -1
  398. package/skills/gsd-path-ship/scripts/state_checkpoint.py +39 -36
  399. package/skills/gsd-path-ship/scripts/state_promote.py +15 -4
  400. package/skills/gsd-path-ship/scripts/workflow_run.py +14 -4
  401. package/skills/gsd-path-ship/scripts/worktree_paths.py +54 -2
  402. package/skills/gsd-path-ship/templates/final-review.md +11 -2
  403. package/skills/gsd-path-ship/templates/patch-findings.md +3 -3
  404. package/skills/gsd-path-ship/templates/wave-review.md +28 -9
  405. package/skills/gsd-path-undo/references/operating-rules.md +11 -3
  406. package/skills/gsd-path-undo/references/runtime-selection.md +9 -0
  407. package/skills/gsd-path-undo/scripts/_common.py +256 -2
  408. package/skills/gsd-path-undo/scripts/discussion_records.py +9 -1
  409. package/skills/gsd-path-undo/scripts/loop_run.py +16 -4
  410. package/skills/gsd-path-undo/scripts/model_policy.py +5 -1
  411. package/skills/gsd-path-undo/scripts/path_config.py +3 -3
  412. package/skills/gsd-path-undo/scripts/pipeline_undo.py +118 -16
  413. package/skills/gsd-path-undo/scripts/worktree_paths.py +54 -2
  414. package/skills/path/DEFINE.md +34 -5
  415. package/skills/path/PLAN.md +14 -3
  416. package/skills/path/RESEARCH.md +11 -0
  417. package/skills/path/ROADMAP.md +16 -6
  418. package/skills/path/SHIP.md +32 -5
  419. package/skills/path/SKILL.md +52 -24
  420. package/skills/path/references/build-native.md +112 -10
  421. package/skills/path/references/build-recovery.md +13 -0
  422. package/skills/path/references/coder.md +7 -3
  423. package/skills/path/references/dispatch.md +13 -0
  424. package/skills/path/references/docs-auditor.md +5 -0
  425. package/skills/path/references/operating-rules.md +11 -3
  426. package/skills/path/references/planner.md +26 -4
  427. package/skills/path/references/reviewer-final.md +20 -2
  428. package/skills/path/references/reviewer-wave.md +56 -12
  429. package/skills/path/references/reviewer.md +3 -1
  430. package/skills/path/references/runtime-selection.md +9 -0
  431. package/skills/path/references/spec-probes.md +150 -0
  432. package/skills/path/scripts/_common.py +256 -2
  433. package/skills/path/scripts/archive_milestone.py +14 -13
  434. package/skills/path/scripts/build_recovery.py +155 -58
  435. package/skills/path/scripts/build_state.py +274 -8
  436. package/skills/path/scripts/check_docs_audit.py +77 -4
  437. package/skills/path/scripts/check_handoffs.py +635 -57
  438. package/skills/path/scripts/check_task_briefs.py +293 -32
  439. package/skills/path/scripts/detect_project.py +260 -20
  440. package/skills/path/scripts/discussion_records.py +9 -1
  441. package/skills/path/scripts/discussion_validate.py +93 -35
  442. package/skills/path/scripts/dispatch_driver.py +549 -53
  443. package/skills/path/scripts/git_guard.py +27 -8
  444. package/skills/path/scripts/integration.py +244 -65
  445. package/skills/path/scripts/isolation.py +539 -70
  446. package/skills/path/scripts/loop_run.py +16 -4
  447. package/skills/path/scripts/members.py +77 -32
  448. package/skills/path/scripts/model_policy.py +5 -1
  449. package/skills/path/scripts/path_config.py +3 -3
  450. package/skills/path/scripts/pipeline_git.py +131 -42
  451. package/skills/path/scripts/pipeline_state.py +139 -39
  452. package/skills/path/scripts/promote_lookahead.py +3 -3
  453. package/skills/path/scripts/review_findings.py +180 -44
  454. package/skills/path/scripts/roadmap.py +9 -1
  455. package/skills/path/scripts/state_checkpoint.py +39 -36
  456. package/skills/path/scripts/state_promote.py +15 -4
  457. package/skills/path/scripts/status_runtime.py +22 -4
  458. package/skills/path/scripts/task_context.py +14 -3
  459. package/skills/path/scripts/workflow_run.py +14 -4
  460. package/skills/path/scripts/worktree_paths.py +54 -2
  461. package/skills/path/templates/docs-audit.md +5 -1
  462. package/skills/path/templates/final-review.md +11 -2
  463. package/skills/path/templates/intent.md +30 -0
  464. package/skills/path/templates/patch-findings.md +3 -3
  465. package/skills/path/templates/plan.md +17 -2
  466. package/skills/path/templates/research-handoff.md +14 -3
  467. package/skills/path/templates/state.md +2 -1
  468. package/skills/path/templates/task.md +1 -0
  469. package/skills/path/templates/wave-review.md +28 -9
  470. package/skills/gsd-path/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  471. package/skills/gsd-path/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
  472. package/skills/gsd-path-build/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  473. package/skills/gsd-path-build/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
  474. package/skills/gsd-path-inspect/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  475. package/skills/gsd-path-plan/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  476. package/skills/gsd-path-ship/scripts/__pycache__/_common.cpython-312.pyc +0 -0
@@ -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 (
@@ -146,6 +148,29 @@ def ship_commit_body(archive_path: str, reviewed_head: str, members: Sequence[di
146
148
  )
147
149
 
148
150
 
151
+ _TRAILER_LINE = re.compile(r"^[A-Za-z-]+:\s")
152
+
153
+
154
+ def ship_body_matches(body: str, expected_body: str) -> bool:
155
+ """True when body equals the contract or adds only optional git trailers after it."""
156
+ normalized = body.rstrip("\n")
157
+ expected = expected_body.rstrip("\n")
158
+ if normalized == expected:
159
+ return True
160
+ prefix = expected + "\n"
161
+ if not normalized.startswith(prefix):
162
+ return False
163
+ remainder = normalized[len(prefix):]
164
+ if not remainder:
165
+ return True
166
+ for line in remainder.splitlines():
167
+ if not line.strip():
168
+ return False
169
+ if not _TRAILER_LINE.match(line):
170
+ return False
171
+ return True
172
+
173
+
149
174
  def integrate_commit_body(
150
175
  archive_path: str,
151
176
  ship_commit: str,
@@ -270,22 +295,50 @@ def _symbolic_branch(repo: Path) -> str:
270
295
  return result.stdout.strip()
271
296
 
272
297
 
273
- def _branch_worktrees(repo: Path) -> dict[str, Path]:
274
- """Return every checked-out local branch from Git's worktree registry."""
275
- output = _run_git(repo, "worktree", "list", "--porcelain", "-z").stdout
298
+ def worktree_list_porcelain(repo: Path) -> tuple[str, bool]:
299
+ """Return worktree list output and whether it is NUL-separated."""
300
+ result = _run_git(repo, "worktree", "list", "--porcelain", "-z", check=False)
301
+ if result.returncode == 0:
302
+ return result.stdout, True
303
+ result = _run_git(repo, "worktree", "list", "--porcelain", check=False)
304
+ if result.returncode != 0:
305
+ message = (result.stderr or result.stdout).strip() or "could not list worktrees"
306
+ raise PipelineGitError(message)
307
+ return result.stdout, False
308
+
309
+
310
+ def branch_worktrees_from_porcelain(output: str, *, nul_separated: bool) -> dict[str, Path]:
276
311
  worktrees: dict[str, Path] = {}
277
- current_path: Optional[Path] = None
278
- for field in output.split("\0"):
279
- if field.startswith("worktree "):
280
- current_path = Path(field.removeprefix("worktree ")).resolve()
281
- elif field.startswith("branch ") and current_path is not None:
282
- ref = field.removeprefix("branch ")
283
- prefix = "refs/heads/"
284
- if ref.startswith(prefix):
285
- worktrees[ref.removeprefix(prefix)] = current_path
312
+ if nul_separated:
313
+ current_path: Optional[Path] = None
314
+ for field in output.split("\0"):
315
+ if not field:
316
+ continue
317
+ if field.startswith("worktree "):
318
+ current_path = Path(field.removeprefix("worktree ")).resolve()
319
+ elif field.startswith("branch ") and current_path is not None:
320
+ ref = field.removeprefix("branch ")
321
+ prefix = "refs/heads/"
322
+ if ref.startswith(prefix):
323
+ worktrees[ref.removeprefix(prefix)] = current_path
324
+ return worktrees
325
+ for block in output.strip().split("\n\n"):
326
+ fields = block.splitlines()
327
+ if not fields or not fields[0].startswith("worktree "):
328
+ continue
329
+ current_path = Path(fields[0].removeprefix("worktree ")).resolve()
330
+ for line in fields[1:]:
331
+ if line.startswith("branch refs/heads/"):
332
+ worktrees[line.removeprefix("branch refs/heads/")] = current_path
286
333
  return worktrees
287
334
 
288
335
 
336
+ def _branch_worktrees(repo: Path) -> dict[str, Path]:
337
+ """Return every checked-out local branch from Git's worktree registry."""
338
+ output, nul_separated = worktree_list_porcelain(repo)
339
+ return branch_worktrees_from_porcelain(output, nul_separated=nul_separated)
340
+
341
+
289
342
  def _remote_ref_sha(repo: Path, ref: str) -> Optional[str]:
290
343
  if not _origin_remote_exists(repo):
291
344
  raise PipelineGitError("origin remote is not configured")
@@ -312,7 +365,7 @@ def _remote_branch_exists(repo: Path, branch: str) -> bool:
312
365
  return _remote_ref_sha(repo, f"refs/heads/{branch}") is not None
313
366
 
314
367
 
315
- def _pull_request_tag_fields(repo: Path, ref: str) -> dict[str, str]:
368
+ def _milestone_tag_fields(repo: Path, ref: str) -> dict[str, str]:
316
369
  tag_type = _run_git(repo, "cat-file", "-t", ref, check=False)
317
370
  if tag_type.returncode != 0 or tag_type.stdout.strip() != "tag":
318
371
  return {}
@@ -322,7 +375,7 @@ def _pull_request_tag_fields(repo: Path, ref: str) -> dict[str, str]:
322
375
  match = re.fullmatch(r"(Mode|Pull-Request|Ship|Landing): (.+)", line)
323
376
  if match:
324
377
  if match.group(1) in fields:
325
- raise PipelineGitError("pull-request milestone tag repeats metadata")
378
+ raise PipelineGitError("milestone tag repeats metadata")
326
379
  fields[match.group(1)] = match.group(2)
327
380
  return fields
328
381
 
@@ -353,7 +406,15 @@ def _validated_shipped_state(repo: Path) -> dict[str, object]:
353
406
  return state
354
407
 
355
408
 
356
- 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(
357
418
  repo: Path,
358
419
  ship: str,
359
420
  base: str,
@@ -364,8 +425,11 @@ def _require_pull_request_integration_proof(
364
425
  raise PipelineGitError("bind-next requires shipped/done state")
365
426
  if state.get("branch") != previous_branch:
366
427
  raise PipelineGitError("shipped state does not name the previous branch")
367
- if state.get("integration") != "pull-request":
368
- 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
+ )
369
433
  archive = state.get("archive")
370
434
  if not isinstance(archive, str):
371
435
  raise PipelineGitError("shipped state does not name an archive")
@@ -376,24 +440,23 @@ def _require_pull_request_integration_proof(
376
440
  ):
377
441
  raise PipelineGitError("shipped archive does not match the previous branch")
378
442
  ref = f"refs/tags/milestone/{match.group(1)}"
379
- fields = _pull_request_tag_fields(repo, ref)
380
- if set(fields) != {"Mode", "Pull-Request", "Ship", "Landing"}:
381
- raise PipelineGitError("pull-request integration proof is incomplete")
382
- if fields["Mode"] != "pull-request" or fields["Ship"] != ship:
383
- raise PipelineGitError("pull-request integration proof does not match shipped state")
384
- pull_request = fields["Pull-Request"]
385
- 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(
386
449
  r"https://github\.com/[^/]+/[^/]+/pull/[1-9][0-9]*",
387
- pull_request,
450
+ fields["Pull-Request"],
388
451
  ) is None:
389
452
  raise PipelineGitError("pull-request integration proof has an invalid PR URL")
390
453
  landing = fields["Landing"]
391
454
  if re.fullmatch(r"[0-9a-f]{40}", landing) is None:
392
- raise PipelineGitError("pull-request integration proof has an invalid landing")
455
+ raise PipelineGitError(f"{mode} integration proof has an invalid landing")
393
456
  local_object = _run_git(repo, "rev-parse", ref).stdout.strip()
394
457
  local_target = _run_git(repo, "rev-parse", f"{ref}^{{commit}}").stdout.strip()
395
458
  if local_target != landing:
396
- raise PipelineGitError("pull-request integration tag does not point at landing")
459
+ raise PipelineGitError(f"{mode} integration tag does not point at landing")
397
460
  remote = _run_git(
398
461
  repo,
399
462
  "ls-remote",
@@ -405,14 +468,14 @@ def _require_pull_request_integration_proof(
405
468
  check=False,
406
469
  )
407
470
  if remote.returncode != 0:
408
- raise PipelineGitError("pull-request integration tag is not published")
471
+ raise PipelineGitError(f"{mode} integration tag is not published")
409
472
  rows = dict(
410
473
  line.split("\t", 1)[::-1]
411
474
  for line in remote.stdout.splitlines()
412
475
  if "\t" in line
413
476
  )
414
477
  if rows != {ref: local_object, f"{ref}^{{}}": landing}:
415
- raise PipelineGitError("published pull-request integration tag differs")
478
+ raise PipelineGitError(f"published {mode} integration tag differs")
416
479
  parents = _run_git(
417
480
  repo,
418
481
  "rev-list",
@@ -422,11 +485,11 @@ def _require_pull_request_integration_proof(
422
485
  landing,
423
486
  ).stdout.split()
424
487
  if len(parents) != 3 or parents[2] != ship:
425
- raise PipelineGitError("pull-request integration proof has invalid merge topology")
488
+ raise PipelineGitError(f"{mode} integration proof has invalid merge topology")
426
489
  first_parent = _run_git(repo, "rev-list", "--first-parent", base).stdout.splitlines()
427
490
  if landing not in first_parent:
428
491
  raise PipelineGitError(
429
- "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"
430
493
  )
431
494
  return landing
432
495
 
@@ -482,6 +545,34 @@ def _write_bind_next_journal(path: Path, value: dict[str, object]) -> None:
482
545
  temporary.unlink()
483
546
 
484
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
+
485
576
  def _initial_binding_snapshot(repo: Path) -> Optional[tuple]:
486
577
  """Admit only clean work or the validated, untracked initializer state."""
487
578
  dirty = _run_git(repo, "status", "--porcelain=v1", "-z", "--untracked-files=all").stdout
@@ -528,8 +619,7 @@ def bind_initial_milestone_branch(
528
619
  resolved = _require_worktree_root(repo)
529
620
  if not is_bound_branch(branch):
530
621
  raise PipelineGitError(f"invalid bound branch: {branch}")
531
- if remote_default != "origin/main":
532
- raise PipelineGitError(f"remote default must be origin/main, got {remote_default}")
622
+ default_name = _require_state_remote_default(resolved, remote_default)
533
623
 
534
624
  default_sha = _run_git(
535
625
  resolved,
@@ -549,7 +639,7 @@ def bind_initial_milestone_branch(
549
639
  raise PipelineGitError(
550
640
  f"validated base is stale: {base_sha} != {remote_default} {default_sha}"
551
641
  )
552
- remote_sha = _remote_ref_sha(resolved, "refs/heads/main")
642
+ remote_sha = _remote_ref_sha(resolved, f"refs/heads/{default_name}")
553
643
  if remote_sha != default_sha:
554
644
  raise PipelineGitError(
555
645
  f"fetched {remote_default} is stale relative to origin"
@@ -691,8 +781,7 @@ def bind_next_milestone_branch(
691
781
  raise PipelineGitError(f"invalid previous bound branch: {previous_branch}")
692
782
  if milestone_number(branch) <= milestone_number(previous_branch):
693
783
  raise PipelineGitError(f"next branch {branch} must follow {previous_branch}")
694
- if remote_default != "origin/main":
695
- raise PipelineGitError(f"remote default must be origin/main, got {remote_default}")
784
+ default_name = _require_state_remote_default(repo, remote_default)
696
785
 
697
786
  default_sha = _run_git(
698
787
  repo,
@@ -712,7 +801,7 @@ def bind_next_milestone_branch(
712
801
  raise PipelineGitError(
713
802
  f"validated base is stale: {base_sha} != {remote_default} {default_sha}"
714
803
  )
715
- live_default_sha = _remote_ref_sha(repo, "refs/heads/main")
804
+ live_default_sha = _remote_ref_sha(repo, f"refs/heads/{default_name}")
716
805
  if live_default_sha != default_sha:
717
806
  raise PipelineGitError(f"fetched {remote_default} is stale relative to origin")
718
807
  ship_sha = _run_git(
@@ -770,7 +859,7 @@ def bind_next_milestone_branch(
770
859
  detail = landing_integrated.stderr.strip() or landing_integrated.stdout.strip()
771
860
  raise PipelineGitError(f"could not verify integrated branch: {detail}")
772
861
  if allow_remote_absent:
773
- proven_landing = _require_pull_request_integration_proof(
862
+ proven_landing = _require_tag_integration_proof(
774
863
  repo,
775
864
  ship_sha,
776
865
  base_sha,
@@ -778,7 +867,7 @@ def bind_next_milestone_branch(
778
867
  )
779
868
  if proven_landing != landing_sha:
780
869
  raise PipelineGitError(
781
- "validated landing does not match pull-request integration proof"
870
+ "validated landing does not match the milestone tag integration proof"
782
871
  )
783
872
 
784
873
  symbolic_branch = _run_git(
@@ -91,7 +91,7 @@ STATE_FIELDS = (
91
91
  "integration_source",
92
92
  )
93
93
  LEGACY_STATE_FIELDS = STATE_FIELDS[:7]
94
- INTEGRATION_MODES = ("direct", "pull-request")
94
+ INTEGRATION_MODES = ("direct", "pull-request", "external-landing")
95
95
  INTEGRATION_SOURCES = ("default", "milestone")
96
96
  NULL = "null"
97
97
  SLUG_RE = re.compile(r"^[a-z0-9][a-z0-9-]*$")
@@ -141,6 +141,8 @@ CROSS_PHASE_TRANSITIONS = {
141
141
  ("decide", "done", "plan", "active"),
142
142
  ("roadmap", "done", "define", "active"),
143
143
  ("plan", "done", "build", "active"),
144
+ ("plan", "active", "define", "active"),
145
+ ("plan", "blocked", "define", "active"),
144
146
  ("build", "blocked", "define", "active"),
145
147
  ("build", "blocked", "plan", "active"),
146
148
  ("build", "active", "ship", "active"),
@@ -171,9 +173,14 @@ class PipelineState:
171
173
  integration_default: str
172
174
  integration: str
173
175
  integration_source: str
176
+ default_branch: str = "main"
174
177
 
175
178
  def json(self) -> dict[str, Optional[str]]:
176
- 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
177
184
 
178
185
 
179
186
  @dataclass(frozen=True)
@@ -193,11 +200,24 @@ def _run_git(
193
200
  *arguments: str,
194
201
  check: bool = True,
195
202
  ) -> subprocess.CompletedProcess[str]:
203
+ command = _common.git_command(repo, *arguments)
196
204
  result = subprocess.run(
197
- ["git", "-C", str(repo), *arguments],
205
+ command,
198
206
  capture_output=True,
199
207
  encoding="utf-8", errors="replace",
200
208
  )
209
+ if (
210
+ check
211
+ and result.returncode != 0
212
+ and arguments
213
+ and arguments[0] == "commit"
214
+ and _common.git_index_lock_retryable(result.stderr)
215
+ ):
216
+ result = subprocess.run(
217
+ command,
218
+ capture_output=True,
219
+ encoding="utf-8", errors="replace",
220
+ )
201
221
  if check and result.returncode != 0:
202
222
  detail = result.stderr.strip() or result.stdout.strip()
203
223
  raise PipelineStateError(f"git {' '.join(arguments)} failed: {detail}")
@@ -275,7 +295,7 @@ def _bound_branch_number(value: str) -> Optional[int]:
275
295
  def _state_from_text(text: str, label: str = "STATE.md") -> PipelineState:
276
296
  values = _parse_frontmatter(text, label)
277
297
  missing = [field for field in LEGACY_STATE_FIELDS if field not in values]
278
- extra = sorted(set(values) - set(STATE_FIELDS))
298
+ extra = sorted(set(values) - set(STATE_FIELDS) - {"default_branch"})
279
299
  if missing:
280
300
  raise PipelineStateError(f"{label} is missing fields: {', '.join(missing)}")
281
301
  if extra:
@@ -307,6 +327,7 @@ def _state_from_text(text: str, label: str = "STATE.md") -> PipelineState:
307
327
  integration_default=integration_default,
308
328
  integration=integration,
309
329
  integration_source=integration_source,
330
+ default_branch=values.get("default_branch", "main"),
310
331
  )
311
332
  _validate_state_values(state, label)
312
333
  return state
@@ -340,6 +361,10 @@ def _validate_state_values(state: PipelineState, label: str) -> None:
340
361
  raise PipelineStateError(
341
362
  f"{label} default-sourced integration must match integration_default"
342
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
+ )
343
368
  if state.branch is not None and _bound_branch_number(state.branch) is None:
344
369
  raise PipelineStateError(f"{label} has invalid branch: {state.branch}")
345
370
  archive_match = ARCHIVE_RE.fullmatch(state.archive or "")
@@ -401,6 +426,7 @@ def validate_state(repo: Path, project_dir: str = ".project") -> dict[str, objec
401
426
  "status": "valid",
402
427
  "path": str(path),
403
428
  "state": state.json(),
429
+ "default_branch": state.default_branch,
404
430
  }
405
431
 
406
432
 
@@ -683,8 +709,10 @@ def status_state(repo: Path, project_dir: str = ".project") -> dict[str, object]
683
709
  if branch
684
710
  else None
685
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
686
714
  origin_main = (
687
- _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
688
716
  )
689
717
  pending, pending_error = _pending_answers(resolved)
690
718
  lookahead = resolved / ".project" / "next"
@@ -707,6 +735,7 @@ def status_state(repo: Path, project_dir: str = ".project") -> dict[str, object]
707
735
  "advance": False,
708
736
  "completion": completion,
709
737
  "state": state,
738
+ "default_branch": default_branch,
710
739
  "route": route,
711
740
  "path": str(_track_root(resolved, project_dir) / "STATE.md"),
712
741
  "git": {
@@ -866,7 +895,12 @@ def _route_result(
866
895
  route["phase"] = phase
867
896
  if mode is not None:
868
897
  route["mode"] = mode
869
- 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
+ }
870
904
 
871
905
 
872
906
  def _legacy_bind_next_landing(repo: Path, state: PipelineState) -> str:
@@ -911,36 +945,13 @@ def _legacy_bind_next_landing(repo: Path, state: PipelineState) -> str:
911
945
  return landing
912
946
 
913
947
 
914
- def _bind_next_recovery(
948
+ def _bind_next_journal_recovery(
915
949
  repo: Path,
916
950
  state: PipelineState,
951
+ path: Path,
952
+ transaction: dict[str, object],
953
+ current: Optional[str],
917
954
  ) -> Optional[dict[str, object]]:
918
- journal_root = _git_path(repo, BIND_NEXT_JOURNAL_DIR)
919
- if not journal_root.exists() and not journal_root.is_symlink():
920
- return None
921
- if journal_root.is_symlink() or not journal_root.is_dir():
922
- raise PipelineStateError(f"bind-next journal root must be a real directory: {journal_root}")
923
-
924
- matches: list[tuple[Path, dict[str, object]]] = []
925
- for path in sorted(journal_root.glob("*.json")):
926
- transaction = _read_json(path)
927
- if transaction.get("schema") not in {
928
- LEGACY_BIND_NEXT_JOURNAL_SCHEMA,
929
- BIND_NEXT_JOURNAL_SCHEMA,
930
- }:
931
- raise PipelineStateError(f"bind-next journal has invalid schema: {path}")
932
- if transaction.get("repo") != str(repo):
933
- raise PipelineStateError(f"bind-next journal belongs to another worktree: {path}")
934
- if transaction.get("previous_branch") == state.branch:
935
- matches.append((path, transaction))
936
- if not matches:
937
- return None
938
- if len(matches) != 1:
939
- raise PipelineStateError("multiple bind-next journals claim the current STATE branch")
940
- if state.phase != "shipped" or state.status != "done":
941
- raise PipelineStateError("bind-next journal requires STATE shipped/done")
942
-
943
- path, transaction = matches[0]
944
955
  required = (
945
956
  "branch",
946
957
  "previous_branch",
@@ -1017,12 +1028,37 @@ def _bind_next_recovery(
1017
1028
  raise PipelineStateError("bind-next journal landing does not contain ship")
1018
1029
  if not _is_ancestor(repo, landing, base):
1019
1030
  raise PipelineStateError("bind-next journal landing is not an ancestor of base")
1020
- if transaction["remote_default"] != "origin/main":
1031
+ if transaction["remote_default"] != f"origin/{state.default_branch}":
1021
1032
  raise PipelineStateError("bind-next journal has invalid remote default")
1022
1033
  if stage not in {"prepared", "switched", "retired"}:
1023
1034
  raise PipelineStateError("bind-next journal has invalid stage")
1024
1035
 
1025
- current = _current_branch(repo)
1036
+ stale_retired = False
1037
+ if stage == "retired":
1038
+ target_ref = _run_git(
1039
+ repo,
1040
+ "show-ref",
1041
+ "--verify",
1042
+ "--quiet",
1043
+ f"refs/heads/{branch}",
1044
+ check=False,
1045
+ )
1046
+ if target_ref.returncode not in {0, 1}:
1047
+ detail = (target_ref.stderr or target_ref.stdout).strip()
1048
+ raise PipelineStateError(
1049
+ f"could not check bind-next target branch {branch}: {detail}"
1050
+ )
1051
+ target_exists = target_ref.returncode == 0
1052
+ stale_retired = current not in {previous, branch} or not target_exists
1053
+ if stale_retired:
1054
+ # If the completed target is gone while the previous branch is checked
1055
+ # out, still prove that branch has not moved away from the recorded ship.
1056
+ if current == previous:
1057
+ head = _run_git(repo, "rev-parse", "HEAD").stdout.strip()
1058
+ if head != ship:
1059
+ raise PipelineStateError("bind-next previous branch is not at journal ship")
1060
+ return None
1061
+
1026
1062
  if current not in {previous, branch}:
1027
1063
  raise PipelineStateError("bind-next journal does not own the current branch")
1028
1064
  if current == previous and stage != "prepared":
@@ -1057,6 +1093,52 @@ def _bind_next_recovery(
1057
1093
  return result
1058
1094
 
1059
1095
 
1096
+ def _bind_next_recovery(
1097
+ repo: Path,
1098
+ state: PipelineState,
1099
+ ) -> Optional[dict[str, object]]:
1100
+ journal_root = _git_path(repo, BIND_NEXT_JOURNAL_DIR)
1101
+ if not journal_root.exists() and not journal_root.is_symlink():
1102
+ return None
1103
+ if journal_root.is_symlink() or not journal_root.is_dir():
1104
+ raise PipelineStateError(f"bind-next journal root must be a real directory: {journal_root}")
1105
+
1106
+ matches: list[tuple[Path, dict[str, object]]] = []
1107
+ for path in sorted(journal_root.glob("*.json")):
1108
+ transaction = _read_json(path)
1109
+ if transaction.get("schema") not in {
1110
+ LEGACY_BIND_NEXT_JOURNAL_SCHEMA,
1111
+ BIND_NEXT_JOURNAL_SCHEMA,
1112
+ }:
1113
+ raise PipelineStateError(f"bind-next journal has invalid schema: {path}")
1114
+ if transaction.get("repo") != str(repo):
1115
+ raise PipelineStateError(f"bind-next journal belongs to another worktree: {path}")
1116
+ if transaction.get("previous_branch") == state.branch:
1117
+ matches.append((path, transaction))
1118
+ if not matches:
1119
+ return None
1120
+ if state.phase != "shipped" or state.status != "done":
1121
+ if len(matches) != 1:
1122
+ raise PipelineStateError("multiple bind-next journals claim the current STATE branch")
1123
+ raise PipelineStateError("bind-next journal requires STATE shipped/done")
1124
+
1125
+ current = _current_branch(repo)
1126
+ recoveries: list[dict[str, object]] = []
1127
+ for path, transaction in matches:
1128
+ recovery = _bind_next_journal_recovery(
1129
+ repo,
1130
+ state,
1131
+ path,
1132
+ transaction,
1133
+ current,
1134
+ )
1135
+ if recovery is not None:
1136
+ recoveries.append(recovery)
1137
+ if len(recoveries) > 1:
1138
+ raise PipelineStateError("multiple bind-next journals claim the current STATE branch")
1139
+ return recoveries[0] if recoveries else None
1140
+
1141
+
1060
1142
  def _pending_discussion_block(repo: Path, phase: Optional[str]) -> Optional[str]:
1061
1143
  pending, error = _pending_answers(repo)
1062
1144
  if error:
@@ -1077,7 +1159,7 @@ def route_state(repo: Path, project_dir: str = ".project") -> dict[str, object]:
1077
1159
  result["recovery"] = recovery
1078
1160
  if route.get("phase") == "define":
1079
1161
  route["mode"] = "corrections"
1080
- elif route.get("phase") == "plan":
1162
+ elif route.get("phase") == "plan" and recovery.get("source", "build") == "build":
1081
1163
  route["mode"] = "build-repair"
1082
1164
  if route["action"] == "run-phase" and result["state"]["archive"] is None:
1083
1165
  reason = _pending_discussion_block(_repo_root(repo), route.get("phase"))
@@ -1668,6 +1750,8 @@ def _validate_transition(
1668
1750
  },
1669
1751
  ("define", "done", "plan", "active"): {"planning started"},
1670
1752
  ("decide", "done", "plan", "active"): {"planning started"},
1753
+ ("plan", "active", "define", "active"): {"plan intent corrections requested"},
1754
+ ("plan", "blocked", "define", "active"): {"plan intent corrections requested"},
1671
1755
  ("ship", "blocked", "plan", "active"): {"patch plan reopened"},
1672
1756
  ("ship", "active", "shipped", "done"): {
1673
1757
  "archive preflight passed; shipment recorded",
@@ -1693,7 +1777,10 @@ def transition_state(
1693
1777
  if unsupported:
1694
1778
  raise PipelineStateError(f"unsupported state fields: {', '.join(sorted(unsupported))}")
1695
1779
  if not changes:
1696
- 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
+ )
1697
1784
  required_expected = {"phase", "status", "branch", "archive"} | set(changes)
1698
1785
  missing_expected = sorted(required_expected - set(expected))
1699
1786
  if missing_expected:
@@ -1720,9 +1807,10 @@ def transition_state(
1720
1807
  )
1721
1808
  if reason:
1722
1809
  raise PipelineStateError(reason)
1723
- if state.phase in {"research", "decide"} and (
1810
+ leaving_define = (state.phase, after.phase, after.status) == ("define", "define", "done")
1811
+ if leaving_define or (state.phase in {"research", "decide"} and (
1724
1812
  after.phase != state.phase or after.status == "done"
1725
- ):
1813
+ )):
1726
1814
  if __package__:
1727
1815
  from . import check_handoffs
1728
1816
  else:
@@ -1731,6 +1819,8 @@ def transition_state(
1731
1819
  except ImportError: # pragma: no cover - package imports used by tests
1732
1820
  from scripts import check_handoffs
1733
1821
  validator = {
1822
+ # Approved intent carries its spec-reach probe tables complete.
1823
+ "define": check_handoffs.validate_intent_probes,
1734
1824
  "research": check_handoffs.validate_research_artifacts,
1735
1825
  "decide": check_handoffs.validate_decide_artifacts,
1736
1826
  }[state.phase]
@@ -1747,6 +1837,16 @@ def transition_state(
1747
1837
  rendered, after.phase,
1748
1838
  _build_recovery().MARKER + json.dumps(recovery, sort_keys=True),
1749
1839
  )
1840
+ elif (
1841
+ state.phase == "plan"
1842
+ and state.status in {"active", "blocked"}
1843
+ and (after.phase, after.status) == ("define", "active")
1844
+ ):
1845
+ recovery = _build_recovery().begin(resolved, state, after, event)
1846
+ rendered = _append_event(
1847
+ rendered, after.phase,
1848
+ _build_recovery().MARKER + json.dumps(recovery, sort_keys=True),
1849
+ )
1750
1850
  _atomic_write(path, rendered)
1751
1851
  return {
1752
1852
  "schema": STATE_SCHEMA,