@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
@@ -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,
@@ -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:
@@ -18,14 +18,14 @@ from pathlib import Path, PurePosixPath
18
18
  from typing import Mapping, Optional, Sequence
19
19
 
20
20
  if __package__:
21
- from .isolation import IsolationError, checkpoint as isolation_checkpoint, require_commit, require_full_sha, PROJECT_ENTRIES
21
+ from .isolation import IsolationError, checkpoint as isolation_checkpoint, PROJECT_ENTRIES
22
22
  else:
23
23
  try:
24
- from isolation import IsolationError, checkpoint as isolation_checkpoint, require_commit, require_full_sha, PROJECT_ENTRIES
24
+ from isolation import IsolationError, checkpoint as isolation_checkpoint, PROJECT_ENTRIES
25
25
  except ModuleNotFoundError as error: # pragma: no cover - package imports used by tests
26
26
  if error.name != "isolation":
27
27
  raise
28
- from scripts.isolation import IsolationError, checkpoint as isolation_checkpoint, require_commit, require_full_sha, PROJECT_ENTRIES
28
+ from scripts.isolation import IsolationError, checkpoint as isolation_checkpoint, PROJECT_ENTRIES
29
29
 
30
30
 
31
31
  if __package__:
@@ -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:
@@ -444,14 +459,14 @@ def _validate_plan_briefs(repo: Path, kind: str, project_dir: str) -> None:
444
459
  # Pre-Git approval defers base-dependent checks to build.
445
460
  return
446
461
  if __package__:
447
- from .check_task_briefs import BriefError, _member_bases, validate_task_briefs
462
+ from .check_task_briefs import BriefError, _member_bases, plan_validation_bases, validate_task_briefs
448
463
  else:
449
464
  try:
450
- from check_task_briefs import BriefError, _member_bases, validate_task_briefs
465
+ from check_task_briefs import BriefError, _member_bases, plan_validation_bases, validate_task_briefs
451
466
  except ModuleNotFoundError as error: # pragma: no cover - package imports
452
467
  if error.name != "check_task_briefs":
453
468
  raise
454
- from scripts.check_task_briefs import BriefError, _member_bases, validate_task_briefs
469
+ from scripts.check_task_briefs import BriefError, _member_bases, plan_validation_bases, validate_task_briefs
455
470
  if __package__:
456
471
  from . import check_handoffs
457
472
  else:
@@ -462,28 +477,10 @@ def _validate_plan_briefs(repo: Path, kind: str, project_dir: str) -> None:
462
477
  raise
463
478
  from scripts import check_handoffs
464
479
  try:
465
- tasks, dependency_files = check_handoffs.plan_brief_inputs(repo, project_dir)
466
- landed_bases = {}
467
480
  member_base = _member_bases(repo)
468
- for task_id, text in tasks.items():
469
- if check_handoffs._task_scalar(text, task_id, "status") != "done":
470
- continue
471
- agent = check_handoffs._task_scalar(text, task_id, "agent")
472
- if agent in {"", "null"}:
473
- raise BriefError(f"{task_id} landed task has no recorded agent")
474
- member = check_handoffs._strict_frontmatter(text, task_id).get("repo")
475
- base_repo = repo
476
- if member is not None:
477
- located = member_base(member) if isinstance(member, str) and member else None
478
- if located is None:
479
- raise BriefError(f"repo: names no member in MEMBERS.md: {member}")
480
- base_repo = located[0]
481
- recorded_base = check_handoffs._task_scalar(text, task_id, "base")
482
- try:
483
- landed_bases[task_id] = require_commit(base_repo, require_full_sha(recorded_base))
484
- except IsolationError as error:
485
- raise BriefError(f"{task_id} landed task has invalid historical base: {error}") from error
486
- dependency_files[task_id] = set()
481
+ tasks, dependency_files, landed_bases = plan_validation_bases(
482
+ repo, project_dir, member_base, initial=True
483
+ )
487
484
  validate_task_briefs(
488
485
  repo, head.stdout.strip(), f"{project_dir}/tasks",
489
486
  dependency_files=dependency_files, landed_bases=landed_bases,
@@ -581,7 +578,10 @@ def checkpoint_approval(
581
578
  f"{expected_head}^{{commit}}",
582
579
  ).stdout.strip()
583
580
  if resolved_head != expected_head:
584
- 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
+ )
585
585
  if pipeline_state._run_git(resolved, "rev-parse", "HEAD").stdout.strip() != expected_head:
586
586
  raise PipelineStateError("approval must start at expected-head")
587
587
  active_state, _, _ = pipeline_state.load_state(resolved)
@@ -596,8 +596,9 @@ def checkpoint_approval(
596
596
  state, state_before, state_path = pipeline_state.load_state(resolved, project_dir)
597
597
  changes, event, subject, body = _approval_details(kind, state, selected_milestone)
598
598
  _validate_plan_briefs(resolved, kind, project_dir)
599
+ recovery_reviews = None
599
600
  if kind == "plan" and project_dir == ".project":
600
- pipeline_state._build_recovery().restore_unchanged_reviews(resolved)
601
+ recovery_reviews = pipeline_state._build_recovery().restore_unchanged_reviews(resolved)
601
602
  expected = {
602
603
  "phase": state.phase,
603
604
  "status": state.status,
@@ -629,7 +630,7 @@ def checkpoint_approval(
629
630
  if kind == "roadmap-reslice":
630
631
  baseline_before = pipeline_state._read_real_file(project / "ROADMAP.before-reslice.md", "re-slice baseline")
631
632
  roadmap_after = _reslice_roadmap(
632
- baseline_before, roadmap_before, state, selected_milestone,
633
+ resolved, baseline_before, roadmap_before, state, state_before, selected_milestone,
633
634
  ) if kind == "roadmap-reslice" else _activate_roadmap_milestone(
634
635
  roadmap_before,
635
636
  selected_milestone,
@@ -659,6 +660,8 @@ def checkpoint_approval(
659
660
  }
660
661
  if baseline_before is not None:
661
662
  journal["baseline_before"] = baseline_before
663
+ if recovery_reviews is not None:
664
+ journal["recovery_reviews"] = recovery_reviews
662
665
  pipeline_state._write_json(journal_path, journal)
663
666
  return _resume_checkpoint_locked(resolved, project, journal_path, journal)
664
667
 
@@ -740,8 +743,8 @@ def defer_approval(
740
743
  if kind == "roadmap-reslice":
741
744
  baseline = project / "ROADMAP.before-reslice.md"
742
745
  roadmap_after = _reslice_roadmap(
743
- pipeline_state._read_real_file(baseline, "re-slice baseline"),
744
- roadmap_before, state, selected_milestone,
746
+ resolved, pipeline_state._read_real_file(baseline, "re-slice baseline"),
747
+ roadmap_before, state, state_before, selected_milestone,
745
748
  )
746
749
  else:
747
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}"
@@ -77,7 +77,7 @@ def run_workflow(repo: Path, action: str, project_dir: str, expected_head: str =
77
77
  head = subprocess.run(["git", "rev-parse", "--verify", "HEAD"],
78
78
  cwd=repo, capture_output=True, encoding="utf-8", errors="replace", check=True).stdout.strip()
79
79
  step("check_task_briefs.py", "--repo", str(repo), "--base", head,
80
- "--tasks-dir", f"{project_dir}/tasks")
80
+ "--tasks-dir", f"{project_dir}/tasks", "--project-dir", project_dir)
81
81
  panel = ["validate-plan", "--plan", str(repo / project_dir / "plan/PLAN.md"),
82
82
  "--intent", str(repo / project_dir / "intent/INTENT.md")]
83
83
  charter = repo / ".project/CHARTER.md"
@@ -199,7 +199,7 @@ def run_workflow(repo: Path, action: str, project_dir: str, expected_head: str =
199
199
  head = subprocess.run(["git", "rev-parse", "--verify", "HEAD"],
200
200
  cwd=repo, capture_output=True, encoding="utf-8", errors="replace", check=True).stdout.strip()
201
201
  step("check_task_briefs.py", "--repo", str(repo), "--base", head,
202
- "--tasks-dir", f"{project_dir}/tasks")
202
+ "--tasks-dir", f"{project_dir}/tasks", "--project-dir", project_dir)
203
203
  step("check_handoffs.py", "plan", *common)
204
204
  elif action == "prepare-task" and _task_member(repo, project_dir, task_id):
205
205
  # A member task works in a sidecar of its member; its Verify runs there at finish.
@@ -210,8 +210,14 @@ def run_workflow(repo: Path, action: str, project_dir: str, expected_head: str =
210
210
  "--base", expected_head, "--task-id", task_id,
211
211
  "--round-size", str(round_size))
212
212
  if task["mode"] == "serial":
213
+ name = f"task-{task_id.lower()}-verify"
214
+ stale = isolation.sidecar_root(repo, "verify", name)
215
+ if os.path.lexists(stale / ".git"):
216
+ # An earlier prepare-task left this sidecar. Only one that is proven untouched is replaced.
217
+ steps.append({"script": "isolation.py", "result": isolation.retire_stale_verify(
218
+ repo, stale, isolation.verify_branch_name(name))})
213
219
  step("isolation.py", "isolate-verify", "--repo", str(repo),
214
- "--base", expected_head, "--name", f"task-{task_id.lower()}-verify")
220
+ "--base", expected_head, "--name", name)
215
221
  elif action == "build-evidence":
216
222
  proof = step("build_state.py", "verify-landed", *common, "--head", expected_head)
217
223
  # The landing proof has one canonical home; lean final-review reuse allows only this path.
@@ -257,6 +263,9 @@ def run_workflow(repo: Path, action: str, project_dir: str, expected_head: str =
257
263
  ruled.add(row[0])
258
264
  if any(len(row) == 6 and row[4] == "NEEDS-USER" and row[0] not in ruled for row in queue):
259
265
  raise StepFailed("DOCS-AUDIT.md has NEEDS-USER rows without a user ruling")
266
+ if kind == "intent":
267
+ # Malformed or incomplete spec-probe tables are not a pre-approvable draft.
268
+ step("check_handoffs.py", "intent", *common)
260
269
  if kind == "plan":
261
270
  if gate_plan()["mode"] != "off":
262
271
  raise StepFailed("pre-approval requires review_panel off")
@@ -275,7 +284,8 @@ def run_workflow(repo: Path, action: str, project_dir: str, expected_head: str =
275
284
  if action == "approve-plan":
276
285
  step("pipeline_state.py", "approve", *common, "--kind", "plan",
277
286
  "--expected-head", expected_head)
278
- except (StepFailed, OSError, subprocess.CalledProcessError, ValueError, KeyError, TypeError) as error:
287
+ except (StepFailed, isolation.IsolationError, OSError, subprocess.CalledProcessError,
288
+ ValueError, KeyError, TypeError) as error:
279
289
  return {"status": "blocked", "reason": str(error), "steps": steps,
280
290
  "next": next_step}
281
291
  return {"status": "complete", "steps": steps, **outputs}