@opengsd/gsd-path 1.0.0 → 1.2.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 (498) hide show
  1. package/AGENTS.md +33 -13
  2. package/CHANGELOG.md +68 -0
  3. package/DOCS.md +75 -6
  4. package/FULL.md +12 -15
  5. package/HOOKS.md +18 -9
  6. package/QUICK.md +8 -6
  7. package/README.md +218 -14
  8. package/RUNTIME.md +16 -5
  9. package/UPDATE.md +30 -11
  10. package/WORKFLOW.md +35 -16
  11. package/package.json +23 -1
  12. package/platforms/claude/dispatch.md +4 -4
  13. package/platforms/copilot/dispatch.md +4 -4
  14. package/platforms/cursor/dispatch.md +5 -10
  15. package/platforms/grok/dispatch.md +4 -6
  16. package/platforms/kimi/dispatch.md +4 -4
  17. package/platforms/kiro/dispatch.md +4 -6
  18. package/platforms/opencode/dispatch.md +4 -4
  19. package/platforms/qwen/dispatch.md +4 -4
  20. package/platforms/shared-agents/dispatch.md +12 -19
  21. package/scripts/_common.py +5 -0
  22. package/scripts/archive_milestone.py +30 -124
  23. package/scripts/bootstrap_repository.py +33 -4
  24. package/scripts/build_recovery.py +292 -0
  25. package/scripts/build_state.py +2 -0
  26. package/scripts/check_docs_audit.py +7 -3
  27. package/scripts/check_handoffs.py +7 -1
  28. package/scripts/check_task_briefs.py +3 -0
  29. package/scripts/detect_project.py +22 -3
  30. package/scripts/discussion_records.py +3 -0
  31. package/scripts/discussion_validate.py +8 -3
  32. package/scripts/dispatch_driver.py +233 -22
  33. package/scripts/git_guard.py +14 -5
  34. package/scripts/guard_hook.py +11 -1
  35. package/scripts/install.mjs +54 -1448
  36. package/scripts/install.py +155 -188
  37. package/scripts/integration.py +54 -28
  38. package/scripts/isolation.py +24 -6
  39. package/scripts/jev_review.py +138 -0
  40. package/scripts/lean_verification.py +3 -1
  41. package/scripts/loop_run.py +2 -0
  42. package/scripts/model_policy.py +279 -0
  43. package/scripts/path_config.py +248 -0
  44. package/scripts/pipeline_diagnose.py +15 -3
  45. package/scripts/pipeline_git.py +3 -0
  46. package/scripts/pipeline_state.py +117 -186
  47. package/scripts/pipeline_undo.py +14 -7
  48. package/scripts/promote_lookahead.py +33 -162
  49. package/scripts/review_panel.py +11 -0
  50. package/scripts/roadmap.py +326 -0
  51. package/scripts/runtime_store.py +278 -0
  52. package/scripts/skill-resources.json +182 -4
  53. package/scripts/state_checkpoint.py +114 -72
  54. package/scripts/state_promote.py +47 -44
  55. package/scripts/status_runtime.py +92 -6
  56. package/scripts/task_context.py +73 -0
  57. package/scripts/token_budget.py +40 -1
  58. package/scripts/workflow_run.py +128 -8
  59. package/scripts/worktree_paths.py +94 -0
  60. package/skills/gsd-path/BUILD.md +54 -575
  61. package/skills/gsd-path/DECIDE.md +4 -6
  62. package/skills/gsd-path/DEFINE.md +23 -21
  63. package/skills/gsd-path/DOCS-AUDIT.md +2 -0
  64. package/skills/gsd-path/INSPECT.md +32 -8
  65. package/skills/gsd-path/PLAN.md +29 -19
  66. package/skills/gsd-path/RESEARCH.md +5 -6
  67. package/skills/gsd-path/ROADMAP.md +24 -58
  68. package/skills/gsd-path/SHIP.md +29 -17
  69. package/skills/gsd-path/SKILL.md +46 -22
  70. package/skills/gsd-path/references/build-abandon.md +33 -0
  71. package/skills/gsd-path/references/build-native.md +502 -0
  72. package/skills/gsd-path/references/build-recovery.md +78 -0
  73. package/skills/gsd-path/references/coder.md +8 -3
  74. package/skills/gsd-path/references/config.md +57 -0
  75. package/skills/gsd-path/references/dispatch.md +12 -19
  76. package/skills/gsd-path/references/docs-auditor.md +14 -0
  77. package/skills/gsd-path/references/jev-review.md +82 -0
  78. package/skills/gsd-path/references/model-policy.md +139 -0
  79. package/skills/gsd-path/references/reviewer-final.md +21 -0
  80. package/skills/gsd-path/references/reviewer-panel.md +17 -0
  81. package/skills/gsd-path/references/reviewer-skeptic.md +20 -0
  82. package/skills/gsd-path/references/reviewer-wave.md +89 -0
  83. package/skills/gsd-path/references/reviewer.md +12 -148
  84. package/skills/gsd-path/references/runtime-selection.md +25 -0
  85. package/skills/gsd-path/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  86. package/skills/gsd-path/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
  87. package/skills/gsd-path/scripts/_common.py +5 -0
  88. package/skills/gsd-path/scripts/archive_milestone.py +30 -124
  89. package/skills/gsd-path/scripts/bootstrap_repository.py +33 -4
  90. package/skills/gsd-path/scripts/build_recovery.py +292 -0
  91. package/skills/gsd-path/scripts/build_state.py +2 -0
  92. package/skills/gsd-path/scripts/check_docs_audit.py +7 -3
  93. package/skills/gsd-path/scripts/check_handoffs.py +7 -1
  94. package/skills/gsd-path/scripts/check_task_briefs.py +3 -0
  95. package/skills/gsd-path/scripts/detect_project.py +22 -3
  96. package/skills/gsd-path/scripts/discussion_records.py +3 -0
  97. package/skills/gsd-path/scripts/discussion_validate.py +8 -3
  98. package/skills/gsd-path/scripts/dispatch_driver.py +233 -22
  99. package/skills/gsd-path/scripts/git_guard.py +768 -0
  100. package/skills/gsd-path/scripts/integration.py +54 -28
  101. package/skills/gsd-path/scripts/isolation.py +24 -6
  102. package/skills/gsd-path/scripts/jev_review.py +138 -0
  103. package/skills/gsd-path/scripts/lean_verification.py +3 -1
  104. package/skills/gsd-path/scripts/loop_run.py +2 -0
  105. package/skills/gsd-path/scripts/model_policy.py +279 -0
  106. package/skills/gsd-path/scripts/path_config.py +248 -0
  107. package/skills/gsd-path/scripts/pipeline_git.py +3 -0
  108. package/skills/gsd-path/scripts/pipeline_state.py +117 -186
  109. package/skills/gsd-path/scripts/promote_lookahead.py +33 -162
  110. package/skills/gsd-path/scripts/review_panel.py +11 -0
  111. package/skills/gsd-path/scripts/roadmap.py +326 -0
  112. package/skills/gsd-path/scripts/state_checkpoint.py +114 -72
  113. package/skills/gsd-path/scripts/state_promote.py +47 -44
  114. package/skills/gsd-path/scripts/status_runtime.py +208 -0
  115. package/skills/gsd-path/scripts/task_context.py +73 -0
  116. package/skills/gsd-path/scripts/token_budget.py +40 -1
  117. package/skills/gsd-path/scripts/workflow_run.py +128 -8
  118. package/skills/gsd-path/scripts/worktree_paths.py +94 -0
  119. package/skills/gsd-path/templates/skeptic.md +1 -1
  120. package/skills/gsd-path/templates/task.md +2 -0
  121. package/skills/gsd-path/templates/wave-panel.md +1 -1
  122. package/skills/gsd-path/templates/wave-review.md +1 -1
  123. package/skills/gsd-path-build/SKILL.md +54 -575
  124. package/skills/gsd-path-build/references/build-abandon.md +33 -0
  125. package/skills/gsd-path-build/references/build-native.md +502 -0
  126. package/skills/gsd-path-build/references/build-recovery.md +78 -0
  127. package/skills/gsd-path-build/references/coder.md +8 -3
  128. package/skills/gsd-path-build/references/dispatch.md +12 -19
  129. package/skills/gsd-path-build/references/jev-review.md +82 -0
  130. package/skills/gsd-path-build/references/model-policy.md +139 -0
  131. package/skills/gsd-path-build/references/reviewer-final.md +21 -0
  132. package/skills/gsd-path-build/references/reviewer-panel.md +17 -0
  133. package/skills/gsd-path-build/references/reviewer-skeptic.md +20 -0
  134. package/skills/gsd-path-build/references/reviewer-wave.md +89 -0
  135. package/skills/gsd-path-build/references/reviewer.md +12 -148
  136. package/skills/gsd-path-build/references/runtime-selection.md +25 -0
  137. package/skills/gsd-path-build/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  138. package/skills/gsd-path-build/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
  139. package/skills/gsd-path-build/scripts/_common.py +5 -0
  140. package/skills/gsd-path-build/scripts/archive_milestone.py +30 -124
  141. package/skills/gsd-path-build/scripts/build_recovery.py +292 -0
  142. package/skills/gsd-path-build/scripts/build_state.py +2 -0
  143. package/skills/gsd-path-build/scripts/check_handoffs.py +7 -1
  144. package/skills/gsd-path-build/scripts/check_task_briefs.py +3 -0
  145. package/skills/gsd-path-build/scripts/detect_project.py +1536 -0
  146. package/skills/gsd-path-build/scripts/discussion_records.py +3 -0
  147. package/skills/gsd-path-build/scripts/discussion_validate.py +8 -3
  148. package/skills/gsd-path-build/scripts/dispatch_driver.py +233 -22
  149. package/skills/gsd-path-build/scripts/integration.py +54 -28
  150. package/skills/gsd-path-build/scripts/isolation.py +24 -6
  151. package/skills/gsd-path-build/scripts/jev_review.py +138 -0
  152. package/skills/gsd-path-build/scripts/loop_run.py +2 -0
  153. package/skills/gsd-path-build/scripts/model_policy.py +279 -0
  154. package/skills/gsd-path-build/scripts/path_config.py +248 -0
  155. package/skills/gsd-path-build/scripts/pipeline_git.py +3 -0
  156. package/skills/gsd-path-build/scripts/pipeline_state.py +117 -186
  157. package/skills/gsd-path-build/scripts/promote_lookahead.py +286 -0
  158. package/skills/gsd-path-build/scripts/review_panel.py +11 -0
  159. package/skills/gsd-path-build/scripts/roadmap.py +326 -0
  160. package/skills/gsd-path-build/scripts/state_checkpoint.py +114 -72
  161. package/skills/gsd-path-build/scripts/state_promote.py +47 -44
  162. package/skills/gsd-path-build/scripts/task_context.py +73 -0
  163. package/skills/gsd-path-build/scripts/token_budget.py +40 -1
  164. package/skills/gsd-path-build/scripts/workflow_run.py +128 -8
  165. package/skills/gsd-path-build/scripts/worktree_paths.py +94 -0
  166. package/skills/gsd-path-build/templates/skeptic.md +1 -1
  167. package/skills/gsd-path-build/templates/task.md +2 -0
  168. package/skills/gsd-path-build/templates/wave-panel.md +1 -1
  169. package/skills/gsd-path-build/templates/wave-review.md +1 -1
  170. package/skills/gsd-path-decide/SKILL.md +4 -6
  171. package/skills/gsd-path-decide/references/dispatch.md +12 -19
  172. package/skills/gsd-path-decide/references/model-policy.md +139 -0
  173. package/skills/gsd-path-decide/references/runtime-selection.md +25 -0
  174. package/skills/gsd-path-decide/scripts/_common.py +5 -0
  175. package/skills/gsd-path-decide/scripts/archive_milestone.py +2171 -0
  176. package/skills/gsd-path-decide/scripts/build_recovery.py +292 -0
  177. package/skills/gsd-path-decide/scripts/check_handoffs.py +7 -1
  178. package/skills/gsd-path-decide/scripts/check_task_briefs.py +3 -0
  179. package/skills/gsd-path-decide/scripts/detect_project.py +1536 -0
  180. package/skills/gsd-path-decide/scripts/discussion_records.py +3 -0
  181. package/skills/gsd-path-decide/scripts/discussion_validate.py +1326 -0
  182. package/skills/gsd-path-decide/scripts/integration.py +1459 -0
  183. package/skills/gsd-path-decide/scripts/isolation.py +24 -6
  184. package/skills/gsd-path-decide/scripts/loop_run.py +839 -0
  185. package/skills/gsd-path-decide/scripts/model_policy.py +279 -0
  186. package/skills/gsd-path-decide/scripts/path_config.py +248 -0
  187. package/skills/gsd-path-decide/scripts/pipeline_git.py +3 -0
  188. package/skills/gsd-path-decide/scripts/pipeline_state.py +117 -186
  189. package/skills/gsd-path-decide/scripts/promote_lookahead.py +286 -0
  190. package/skills/gsd-path-decide/scripts/review_panel.py +564 -0
  191. package/skills/gsd-path-decide/scripts/roadmap.py +326 -0
  192. package/skills/gsd-path-decide/scripts/state_checkpoint.py +114 -72
  193. package/skills/gsd-path-decide/scripts/state_promote.py +47 -44
  194. package/skills/gsd-path-decide/scripts/worktree_paths.py +94 -0
  195. package/skills/gsd-path-define/SKILL.md +23 -21
  196. package/skills/gsd-path-define/references/build-recovery.md +78 -0
  197. package/skills/gsd-path-define/references/runtime-selection.md +25 -0
  198. package/skills/gsd-path-define/scripts/_common.py +5 -0
  199. package/skills/gsd-path-define/scripts/archive_milestone.py +2171 -0
  200. package/skills/gsd-path-define/scripts/build_recovery.py +292 -0
  201. package/skills/gsd-path-define/scripts/check_handoffs.py +7 -1
  202. package/skills/gsd-path-define/scripts/check_task_briefs.py +3 -0
  203. package/skills/gsd-path-define/scripts/detect_project.py +22 -3
  204. package/skills/gsd-path-define/scripts/discussion_records.py +3 -0
  205. package/skills/gsd-path-define/scripts/discussion_validate.py +1326 -0
  206. package/skills/gsd-path-define/scripts/integration.py +1459 -0
  207. package/skills/gsd-path-define/scripts/isolation.py +24 -6
  208. package/skills/gsd-path-define/scripts/loop_run.py +839 -0
  209. package/skills/gsd-path-define/scripts/model_policy.py +279 -0
  210. package/skills/gsd-path-define/scripts/path_config.py +248 -0
  211. package/skills/gsd-path-define/scripts/pipeline_git.py +3 -0
  212. package/skills/gsd-path-define/scripts/pipeline_state.py +117 -186
  213. package/skills/gsd-path-define/scripts/promote_lookahead.py +286 -0
  214. package/skills/gsd-path-define/scripts/review_panel.py +564 -0
  215. package/skills/gsd-path-define/scripts/roadmap.py +326 -0
  216. package/skills/gsd-path-define/scripts/state_checkpoint.py +114 -72
  217. package/skills/gsd-path-define/scripts/state_promote.py +47 -44
  218. package/skills/gsd-path-define/scripts/worktree_paths.py +94 -0
  219. package/skills/gsd-path-discuss/SKILL.md +2 -0
  220. package/skills/gsd-path-discuss/references/runtime-selection.md +25 -0
  221. package/skills/gsd-path-discuss/scripts/_common.py +5 -0
  222. package/skills/gsd-path-discuss/scripts/discussion_records.py +3 -0
  223. package/skills/gsd-path-discuss/scripts/loop_run.py +839 -0
  224. package/skills/gsd-path-discuss/scripts/model_policy.py +279 -0
  225. package/skills/gsd-path-discuss/scripts/path_config.py +248 -0
  226. package/skills/gsd-path-discuss/scripts/review_panel.py +564 -0
  227. package/skills/gsd-path-discuss/scripts/worktree_paths.py +94 -0
  228. package/skills/gsd-path-docs-audit/SKILL.md +2 -0
  229. package/skills/gsd-path-docs-audit/references/dispatch.md +12 -19
  230. package/skills/gsd-path-docs-audit/references/docs-auditor.md +14 -0
  231. package/skills/gsd-path-docs-audit/references/model-policy.md +139 -0
  232. package/skills/gsd-path-docs-audit/references/runtime-selection.md +25 -0
  233. package/skills/gsd-path-docs-audit/scripts/_common.py +5 -0
  234. package/skills/gsd-path-docs-audit/scripts/archive_milestone.py +2171 -0
  235. package/skills/gsd-path-docs-audit/scripts/build_recovery.py +292 -0
  236. package/skills/gsd-path-docs-audit/scripts/check_docs_audit.py +7 -3
  237. package/skills/gsd-path-docs-audit/scripts/check_handoffs.py +7 -1
  238. package/skills/gsd-path-docs-audit/scripts/check_task_briefs.py +3 -0
  239. package/skills/gsd-path-docs-audit/scripts/detect_project.py +1536 -0
  240. package/skills/gsd-path-docs-audit/scripts/discussion_records.py +3 -0
  241. package/skills/gsd-path-docs-audit/scripts/discussion_validate.py +1326 -0
  242. package/skills/gsd-path-docs-audit/scripts/integration.py +1459 -0
  243. package/skills/gsd-path-docs-audit/scripts/isolation.py +24 -6
  244. package/skills/gsd-path-docs-audit/scripts/loop_run.py +839 -0
  245. package/skills/gsd-path-docs-audit/scripts/model_policy.py +279 -0
  246. package/skills/gsd-path-docs-audit/scripts/path_config.py +248 -0
  247. package/skills/gsd-path-docs-audit/scripts/pipeline_git.py +3 -0
  248. package/skills/gsd-path-docs-audit/scripts/pipeline_state.py +117 -186
  249. package/skills/gsd-path-docs-audit/scripts/promote_lookahead.py +286 -0
  250. package/skills/gsd-path-docs-audit/scripts/review_panel.py +564 -0
  251. package/skills/gsd-path-docs-audit/scripts/roadmap.py +326 -0
  252. package/skills/gsd-path-docs-audit/scripts/state_checkpoint.py +114 -72
  253. package/skills/gsd-path-docs-audit/scripts/state_promote.py +47 -44
  254. package/skills/gsd-path-docs-audit/scripts/worktree_paths.py +94 -0
  255. package/skills/gsd-path-forensics/SKILL.md +9 -5
  256. package/skills/gsd-path-forensics/references/runtime-selection.md +25 -0
  257. package/skills/gsd-path-forensics/scripts/_common.py +5 -0
  258. package/skills/gsd-path-forensics/scripts/discussion_records.py +3 -0
  259. package/skills/gsd-path-forensics/scripts/loop_run.py +839 -0
  260. package/skills/gsd-path-forensics/scripts/model_policy.py +279 -0
  261. package/skills/gsd-path-forensics/scripts/path_config.py +248 -0
  262. package/skills/gsd-path-forensics/scripts/pipeline_diagnose.py +15 -3
  263. package/skills/gsd-path-forensics/scripts/pipeline_undo.py +14 -7
  264. package/skills/gsd-path-forensics/scripts/review_panel.py +564 -0
  265. package/skills/gsd-path-forensics/scripts/worktree_paths.py +94 -0
  266. package/skills/gsd-path-inspect/SKILL.md +32 -8
  267. package/skills/gsd-path-inspect/references/dispatch.md +12 -19
  268. package/skills/gsd-path-inspect/references/docs-auditor.md +14 -0
  269. package/skills/gsd-path-inspect/references/model-policy.md +139 -0
  270. package/skills/gsd-path-inspect/references/runtime-selection.md +25 -0
  271. package/skills/gsd-path-inspect/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  272. package/skills/gsd-path-inspect/scripts/_common.py +5 -0
  273. package/skills/gsd-path-inspect/scripts/archive_milestone.py +2171 -0
  274. package/skills/gsd-path-inspect/scripts/build_recovery.py +292 -0
  275. package/skills/gsd-path-inspect/scripts/check_docs_audit.py +7 -3
  276. package/skills/gsd-path-inspect/scripts/check_handoffs.py +7 -1
  277. package/skills/gsd-path-inspect/scripts/check_task_briefs.py +3 -0
  278. package/skills/gsd-path-inspect/scripts/detect_project.py +22 -3
  279. package/skills/gsd-path-inspect/scripts/discussion_records.py +3 -0
  280. package/skills/gsd-path-inspect/scripts/discussion_validate.py +1326 -0
  281. package/skills/gsd-path-inspect/scripts/integration.py +1459 -0
  282. package/skills/gsd-path-inspect/scripts/isolation.py +24 -6
  283. package/skills/gsd-path-inspect/scripts/loop_run.py +839 -0
  284. package/skills/gsd-path-inspect/scripts/model_policy.py +279 -0
  285. package/skills/gsd-path-inspect/scripts/path_config.py +248 -0
  286. package/skills/gsd-path-inspect/scripts/pipeline_git.py +3 -0
  287. package/skills/gsd-path-inspect/scripts/pipeline_state.py +117 -186
  288. package/skills/gsd-path-inspect/scripts/promote_lookahead.py +286 -0
  289. package/skills/gsd-path-inspect/scripts/review_panel.py +564 -0
  290. package/skills/gsd-path-inspect/scripts/roadmap.py +326 -0
  291. package/skills/gsd-path-inspect/scripts/state_checkpoint.py +114 -72
  292. package/skills/gsd-path-inspect/scripts/state_promote.py +47 -44
  293. package/skills/gsd-path-inspect/scripts/workflow_run.py +237 -0
  294. package/skills/gsd-path-inspect/scripts/worktree_paths.py +94 -0
  295. package/skills/gsd-path-loop/SKILL.md +2 -0
  296. package/skills/gsd-path-loop/references/runtime-selection.md +25 -0
  297. package/skills/gsd-path-loop/scripts/_common.py +5 -0
  298. package/skills/gsd-path-loop/scripts/discussion_records.py +3 -0
  299. package/skills/gsd-path-loop/scripts/loop_run.py +2 -0
  300. package/skills/gsd-path-loop/scripts/model_policy.py +279 -0
  301. package/skills/gsd-path-loop/scripts/path_config.py +248 -0
  302. package/skills/gsd-path-loop/scripts/review_panel.py +564 -0
  303. package/skills/gsd-path-loop/scripts/worktree_paths.py +94 -0
  304. package/skills/gsd-path-migrate/SKILL.md +2 -0
  305. package/skills/gsd-path-migrate/references/runtime-selection.md +25 -0
  306. package/skills/gsd-path-migrate/scripts/_common.py +185 -0
  307. package/skills/gsd-path-migrate/scripts/discussion_records.py +3 -0
  308. package/skills/gsd-path-migrate/scripts/loop_run.py +839 -0
  309. package/skills/gsd-path-migrate/scripts/model_policy.py +279 -0
  310. package/skills/gsd-path-migrate/scripts/path_config.py +248 -0
  311. package/skills/gsd-path-migrate/scripts/review_panel.py +564 -0
  312. package/skills/gsd-path-plan/SKILL.md +29 -19
  313. package/skills/gsd-path-plan/references/build-recovery.md +78 -0
  314. package/skills/gsd-path-plan/references/dispatch.md +12 -19
  315. package/skills/gsd-path-plan/references/model-policy.md +139 -0
  316. package/skills/gsd-path-plan/references/runtime-selection.md +25 -0
  317. package/skills/gsd-path-plan/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  318. package/skills/gsd-path-plan/scripts/_common.py +5 -0
  319. package/skills/gsd-path-plan/scripts/archive_milestone.py +2171 -0
  320. package/skills/gsd-path-plan/scripts/build_recovery.py +292 -0
  321. package/skills/gsd-path-plan/scripts/check_handoffs.py +7 -1
  322. package/skills/gsd-path-plan/scripts/check_task_briefs.py +3 -0
  323. package/skills/gsd-path-plan/scripts/detect_project.py +1536 -0
  324. package/skills/gsd-path-plan/scripts/discussion_records.py +3 -0
  325. package/skills/gsd-path-plan/scripts/discussion_validate.py +1326 -0
  326. package/skills/gsd-path-plan/scripts/integration.py +1459 -0
  327. package/skills/gsd-path-plan/scripts/isolation.py +24 -6
  328. package/skills/gsd-path-plan/scripts/loop_run.py +839 -0
  329. package/skills/gsd-path-plan/scripts/model_policy.py +279 -0
  330. package/skills/gsd-path-plan/scripts/path_config.py +248 -0
  331. package/skills/gsd-path-plan/scripts/pipeline_git.py +3 -0
  332. package/skills/gsd-path-plan/scripts/pipeline_state.py +117 -186
  333. package/skills/gsd-path-plan/scripts/promote_lookahead.py +286 -0
  334. package/skills/gsd-path-plan/scripts/review_panel.py +11 -0
  335. package/skills/gsd-path-plan/scripts/roadmap.py +326 -0
  336. package/skills/gsd-path-plan/scripts/state_checkpoint.py +114 -72
  337. package/skills/gsd-path-plan/scripts/state_promote.py +47 -44
  338. package/skills/gsd-path-plan/scripts/workflow_run.py +128 -8
  339. package/skills/gsd-path-plan/scripts/worktree_paths.py +94 -0
  340. package/skills/gsd-path-plan/templates/task.md +2 -0
  341. package/skills/gsd-path-research/SKILL.md +5 -6
  342. package/skills/gsd-path-research/references/dispatch.md +12 -19
  343. package/skills/gsd-path-research/references/model-policy.md +139 -0
  344. package/skills/gsd-path-research/references/runtime-selection.md +25 -0
  345. package/skills/gsd-path-research/scripts/_common.py +5 -0
  346. package/skills/gsd-path-research/scripts/archive_milestone.py +2171 -0
  347. package/skills/gsd-path-research/scripts/build_recovery.py +292 -0
  348. package/skills/gsd-path-research/scripts/check_handoffs.py +7 -1
  349. package/skills/gsd-path-research/scripts/check_task_briefs.py +3 -0
  350. package/skills/gsd-path-research/scripts/detect_project.py +1536 -0
  351. package/skills/gsd-path-research/scripts/discussion_records.py +3 -0
  352. package/skills/gsd-path-research/scripts/discussion_validate.py +1326 -0
  353. package/skills/gsd-path-research/scripts/integration.py +1459 -0
  354. package/skills/gsd-path-research/scripts/isolation.py +24 -6
  355. package/skills/gsd-path-research/scripts/loop_run.py +839 -0
  356. package/skills/gsd-path-research/scripts/model_policy.py +279 -0
  357. package/skills/gsd-path-research/scripts/path_config.py +248 -0
  358. package/skills/gsd-path-research/scripts/pipeline_git.py +3 -0
  359. package/skills/gsd-path-research/scripts/pipeline_state.py +117 -186
  360. package/skills/gsd-path-research/scripts/promote_lookahead.py +286 -0
  361. package/skills/gsd-path-research/scripts/review_panel.py +564 -0
  362. package/skills/gsd-path-research/scripts/roadmap.py +326 -0
  363. package/skills/gsd-path-research/scripts/state_checkpoint.py +114 -72
  364. package/skills/gsd-path-research/scripts/state_promote.py +47 -44
  365. package/skills/gsd-path-research/scripts/worktree_paths.py +94 -0
  366. package/skills/gsd-path-roadmap/SKILL.md +24 -58
  367. package/skills/gsd-path-roadmap/references/dispatch.md +12 -19
  368. package/skills/gsd-path-roadmap/references/model-policy.md +139 -0
  369. package/skills/gsd-path-roadmap/references/runtime-selection.md +25 -0
  370. package/skills/gsd-path-roadmap/scripts/_common.py +5 -0
  371. package/skills/gsd-path-roadmap/scripts/archive_milestone.py +30 -124
  372. package/skills/gsd-path-roadmap/scripts/build_recovery.py +292 -0
  373. package/skills/gsd-path-roadmap/scripts/check_handoffs.py +7 -1
  374. package/skills/gsd-path-roadmap/scripts/check_task_briefs.py +3 -0
  375. package/skills/gsd-path-roadmap/scripts/detect_project.py +22 -3
  376. package/skills/gsd-path-roadmap/scripts/discussion_records.py +3 -0
  377. package/skills/gsd-path-roadmap/scripts/discussion_validate.py +8 -3
  378. package/skills/gsd-path-roadmap/scripts/integration.py +54 -28
  379. package/skills/gsd-path-roadmap/scripts/isolation.py +24 -6
  380. package/skills/gsd-path-roadmap/scripts/loop_run.py +839 -0
  381. package/skills/gsd-path-roadmap/scripts/model_policy.py +279 -0
  382. package/skills/gsd-path-roadmap/scripts/path_config.py +248 -0
  383. package/skills/gsd-path-roadmap/scripts/pipeline_git.py +3 -0
  384. package/skills/gsd-path-roadmap/scripts/pipeline_state.py +117 -186
  385. package/skills/gsd-path-roadmap/scripts/promote_lookahead.py +33 -162
  386. package/skills/gsd-path-roadmap/scripts/review_panel.py +11 -0
  387. package/skills/gsd-path-roadmap/scripts/roadmap.py +326 -0
  388. package/skills/gsd-path-roadmap/scripts/state_checkpoint.py +114 -72
  389. package/skills/gsd-path-roadmap/scripts/state_promote.py +47 -44
  390. package/skills/gsd-path-roadmap/scripts/worktree_paths.py +94 -0
  391. package/skills/gsd-path-ship/SKILL.md +29 -17
  392. package/skills/gsd-path-ship/references/dispatch.md +12 -19
  393. package/skills/gsd-path-ship/references/jev-review.md +82 -0
  394. package/skills/gsd-path-ship/references/model-policy.md +139 -0
  395. package/skills/gsd-path-ship/references/reviewer-final.md +21 -0
  396. package/skills/gsd-path-ship/references/reviewer-panel.md +17 -0
  397. package/skills/gsd-path-ship/references/reviewer-skeptic.md +20 -0
  398. package/skills/gsd-path-ship/references/reviewer-wave.md +89 -0
  399. package/skills/gsd-path-ship/references/reviewer.md +12 -148
  400. package/skills/gsd-path-ship/references/runtime-selection.md +25 -0
  401. package/skills/gsd-path-ship/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  402. package/skills/gsd-path-ship/scripts/_common.py +5 -0
  403. package/skills/gsd-path-ship/scripts/archive_milestone.py +30 -124
  404. package/skills/gsd-path-ship/scripts/build_recovery.py +292 -0
  405. package/skills/gsd-path-ship/scripts/build_state.py +2 -0
  406. package/skills/gsd-path-ship/scripts/check_handoffs.py +7 -1
  407. package/skills/gsd-path-ship/scripts/check_task_briefs.py +3 -0
  408. package/skills/gsd-path-ship/scripts/detect_project.py +1536 -0
  409. package/skills/gsd-path-ship/scripts/discussion_records.py +3 -0
  410. package/skills/gsd-path-ship/scripts/discussion_validate.py +8 -3
  411. package/skills/gsd-path-ship/scripts/integration.py +54 -28
  412. package/skills/gsd-path-ship/scripts/isolation.py +24 -6
  413. package/skills/gsd-path-ship/scripts/jev_review.py +138 -0
  414. package/skills/gsd-path-ship/scripts/lean_verification.py +3 -1
  415. package/skills/gsd-path-ship/scripts/loop_run.py +839 -0
  416. package/skills/gsd-path-ship/scripts/model_policy.py +279 -0
  417. package/skills/gsd-path-ship/scripts/path_config.py +248 -0
  418. package/skills/gsd-path-ship/scripts/pipeline_git.py +3 -0
  419. package/skills/gsd-path-ship/scripts/pipeline_state.py +117 -186
  420. package/skills/gsd-path-ship/scripts/promote_lookahead.py +286 -0
  421. package/skills/gsd-path-ship/scripts/review_panel.py +11 -0
  422. package/skills/gsd-path-ship/scripts/roadmap.py +326 -0
  423. package/skills/gsd-path-ship/scripts/state_checkpoint.py +114 -72
  424. package/skills/gsd-path-ship/scripts/state_promote.py +47 -44
  425. package/skills/gsd-path-ship/scripts/workflow_run.py +128 -8
  426. package/skills/gsd-path-ship/scripts/worktree_paths.py +94 -0
  427. package/skills/gsd-path-ship/templates/wave-review.md +1 -1
  428. package/skills/gsd-path-undo/SKILL.md +2 -0
  429. package/skills/gsd-path-undo/references/runtime-selection.md +25 -0
  430. package/skills/gsd-path-undo/scripts/_common.py +5 -0
  431. package/skills/gsd-path-undo/scripts/discussion_records.py +3 -0
  432. package/skills/gsd-path-undo/scripts/loop_run.py +839 -0
  433. package/skills/gsd-path-undo/scripts/model_policy.py +279 -0
  434. package/skills/gsd-path-undo/scripts/path_config.py +248 -0
  435. package/skills/gsd-path-undo/scripts/pipeline_undo.py +14 -7
  436. package/skills/gsd-path-undo/scripts/review_panel.py +564 -0
  437. package/skills/gsd-path-undo/scripts/worktree_paths.py +94 -0
  438. package/skills/path/BUILD.md +54 -575
  439. package/skills/path/DECIDE.md +4 -6
  440. package/skills/path/DEFINE.md +23 -21
  441. package/skills/path/DOCS-AUDIT.md +2 -0
  442. package/skills/path/INSPECT.md +32 -8
  443. package/skills/path/PLAN.md +29 -19
  444. package/skills/path/RESEARCH.md +5 -6
  445. package/skills/path/ROADMAP.md +24 -58
  446. package/skills/path/SHIP.md +29 -17
  447. package/skills/path/SKILL.md +46 -22
  448. package/skills/path/references/build-abandon.md +33 -0
  449. package/skills/path/references/build-native.md +502 -0
  450. package/skills/path/references/build-recovery.md +78 -0
  451. package/skills/path/references/coder.md +8 -3
  452. package/skills/path/references/config.md +57 -0
  453. package/skills/path/references/dispatch.md +12 -19
  454. package/skills/path/references/docs-auditor.md +14 -0
  455. package/skills/path/references/jev-review.md +82 -0
  456. package/skills/path/references/model-policy.md +139 -0
  457. package/skills/path/references/reviewer-final.md +21 -0
  458. package/skills/path/references/reviewer-panel.md +17 -0
  459. package/skills/path/references/reviewer-skeptic.md +20 -0
  460. package/skills/path/references/reviewer-wave.md +89 -0
  461. package/skills/path/references/reviewer.md +12 -148
  462. package/skills/path/references/runtime-selection.md +25 -0
  463. package/skills/path/scripts/_common.py +5 -0
  464. package/skills/path/scripts/archive_milestone.py +30 -124
  465. package/skills/path/scripts/bootstrap_repository.py +33 -4
  466. package/skills/path/scripts/build_recovery.py +292 -0
  467. package/skills/path/scripts/build_state.py +2 -0
  468. package/skills/path/scripts/check_docs_audit.py +7 -3
  469. package/skills/path/scripts/check_handoffs.py +7 -1
  470. package/skills/path/scripts/check_task_briefs.py +3 -0
  471. package/skills/path/scripts/detect_project.py +22 -3
  472. package/skills/path/scripts/discussion_records.py +3 -0
  473. package/skills/path/scripts/discussion_validate.py +8 -3
  474. package/skills/path/scripts/dispatch_driver.py +233 -22
  475. package/skills/path/scripts/git_guard.py +768 -0
  476. package/skills/path/scripts/integration.py +54 -28
  477. package/skills/path/scripts/isolation.py +24 -6
  478. package/skills/path/scripts/jev_review.py +138 -0
  479. package/skills/path/scripts/lean_verification.py +3 -1
  480. package/skills/path/scripts/loop_run.py +2 -0
  481. package/skills/path/scripts/model_policy.py +279 -0
  482. package/skills/path/scripts/path_config.py +248 -0
  483. package/skills/path/scripts/pipeline_git.py +3 -0
  484. package/skills/path/scripts/pipeline_state.py +117 -186
  485. package/skills/path/scripts/promote_lookahead.py +33 -162
  486. package/skills/path/scripts/review_panel.py +11 -0
  487. package/skills/path/scripts/roadmap.py +326 -0
  488. package/skills/path/scripts/state_checkpoint.py +114 -72
  489. package/skills/path/scripts/state_promote.py +47 -44
  490. package/skills/path/scripts/status_runtime.py +208 -0
  491. package/skills/path/scripts/task_context.py +73 -0
  492. package/skills/path/scripts/token_budget.py +40 -1
  493. package/skills/path/scripts/workflow_run.py +128 -8
  494. package/skills/path/scripts/worktree_paths.py +94 -0
  495. package/skills/path/templates/skeptic.md +1 -1
  496. package/skills/path/templates/task.md +2 -0
  497. package/skills/path/templates/wave-panel.md +1 -1
  498. package/skills/path/templates/wave-review.md +1 -1
@@ -15,6 +15,9 @@ import argparse
15
15
  import json
16
16
  import re
17
17
  import sys
18
+
19
+ # Runtime helpers must not modify their immutable installation.
20
+ sys.dont_write_bytecode = True
18
21
  from pathlib import Path
19
22
  from typing import Iterable, Optional, Sequence
20
23
 
@@ -214,6 +217,7 @@ def resolve_panel(
214
217
  advertised: Sequence[str],
215
218
  parent_family: Optional[str] = None,
216
219
  parent_slug: Optional[str] = None,
220
+ excluded_families: Sequence[str] = (),
217
221
  ) -> dict:
218
222
  inferred_parent = parent_family or (
219
223
  family_of_slug(parent_slug) if parent_slug else None
@@ -244,7 +248,12 @@ def resolve_panel(
244
248
  "named review_panel families are not advertised: " + ", ".join(missing)
245
249
  )
246
250
 
251
+ if config["mode"] == "named" and set(wanted) & set(excluded_families):
252
+ raise ReviewPanelError("named review_panel must use an independent family")
247
253
  for family in wanted:
254
+ if family in excluded_families:
255
+ skipped.append({"family": family, "reason": "excluded reviewer family"})
256
+ continue
248
257
  if family == inferred_parent:
249
258
  skipped.append({"family": family, "reason": "parent family"})
250
259
  continue
@@ -488,6 +497,7 @@ def parser() -> argparse.ArgumentParser:
488
497
  result.add_argument("--advertised", default="")
489
498
  result.add_argument("--parent-family")
490
499
  result.add_argument("--parent-slug")
500
+ result.add_argument("--exclude-family", action="append", choices=KNOWN_FAMILIES, default=[])
491
501
  result.add_argument("--kind", choices=("plan", "wave"))
492
502
  result.add_argument("--inputs", default="")
493
503
  result.add_argument("--output", type=Path)
@@ -541,6 +551,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
541
551
  split_csv(arguments.advertised),
542
552
  arguments.parent_family,
543
553
  arguments.parent_slug,
554
+ arguments.exclude_family,
544
555
  )
545
556
  return emit(resolved)
546
557
  except ReviewPanelError as error:
@@ -0,0 +1,326 @@
1
+ #!/usr/bin/env python3
2
+ # gsd-path project runtime
3
+ """Interpret Roadmap text for selection, approval, promotion and milestone close."""
4
+ from __future__ import annotations
5
+
6
+ import sys
7
+
8
+ # Runtime helpers must not modify their immutable installation.
9
+ sys.dont_write_bytecode = True
10
+
11
+ import re
12
+ from typing import Optional
13
+
14
+
15
+ class RoadmapError(RuntimeError):
16
+ pass
17
+
18
+
19
+ class NoEligibleMilestone(RoadmapError):
20
+ pass
21
+
22
+
23
+ ROADMAP_HEADING_RE = re.compile(r"^### (M\d{3,}) — (.+)$")
24
+ STRICT_HEADING_RE = re.compile(r"^### (M\d{3,}) — ([a-z0-9][a-z0-9-]*)\s*$")
25
+ MUTABLE_ROADMAP_FIELDS_RE = re.compile(r"^(Status|Archive|Integrated):")
26
+
27
+ def normalized_slug(value: str) -> str:
28
+ slug = re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-")
29
+ return slug or "project"
30
+
31
+ def roadmap_blocks(content: str) -> list[dict]:
32
+ lines = content.splitlines(keepends=True)
33
+ headings = []
34
+ for index, line in enumerate(lines):
35
+ match = ROADMAP_HEADING_RE.fullmatch(line.rstrip("\r\n"))
36
+ if match:
37
+ headings.append((index, match.group(1), match.group(2).strip()))
38
+ blocks = []
39
+ for position, (start, milestone_id, title) in enumerate(headings):
40
+ end = headings[position + 1][0] if position + 1 < len(headings) else len(lines)
41
+ fields = {}
42
+ for index in range(start + 1, end):
43
+ match = re.match(
44
+ r"^(Status|Archive|Integrated|Depends on):\s*([^#]*?)\s*(?:#.*)?$",
45
+ lines[index].rstrip("\r\n"),
46
+ )
47
+ if match:
48
+ if match.group(1) in fields:
49
+ raise RoadmapError(
50
+ f"roadmap milestone {milestone_id} has duplicate "
51
+ f"{match.group(1)} field"
52
+ )
53
+ fields[match.group(1)] = (index, match.group(2).strip())
54
+ blocks.append(
55
+ {
56
+ "id": milestone_id,
57
+ "title": title,
58
+ "start": start,
59
+ "end": end,
60
+ "fields": fields,
61
+ }
62
+ )
63
+ return blocks
64
+
65
+
66
+ def milestone_block(content: str, milestone: str) -> dict:
67
+ normalized = normalized_slug(milestone)
68
+ matches = [
69
+ block
70
+ for block in roadmap_blocks(content)
71
+ if normalized_slug(block["title"]) == normalized
72
+ ]
73
+ if len(matches) != 1:
74
+ raise RoadmapError(f"ROADMAP.md must contain exactly one entry for {milestone}")
75
+ return matches[0]
76
+
77
+
78
+ def dependency_ids(block: dict) -> list[str]:
79
+ dependencies = block["fields"].get("Depends on")
80
+ if dependencies is None:
81
+ raise RoadmapError(f"roadmap milestone {block['id']} lacks dependencies")
82
+ dependency_list = re.fullmatch(
83
+ r"\[\s*(M\d{3,}(?:\s*,\s*M\d{3,})*)?\s*\]",
84
+ dependencies[1],
85
+ )
86
+ if dependency_list is None:
87
+ raise RoadmapError(f"roadmap milestone {block['id']} has invalid dependencies")
88
+ return re.findall(r"M\d{3,}", dependency_list.group(1) or "")
89
+
90
+
91
+ def next_eligible_pending(content: str, active_milestone: Optional[str] = None) -> dict:
92
+ blocks = roadmap_blocks(content)
93
+ blocks_by_id = {block["id"]: block for block in blocks}
94
+ if len(blocks_by_id) != len(blocks):
95
+ raise RoadmapError("ROADMAP.md milestone ids must be unique")
96
+ active_id = None
97
+ if active_milestone is not None:
98
+ active = milestone_block(content, active_milestone)
99
+ if active["fields"].get("Status", (-1, ""))[1] != "active":
100
+ raise RoadmapError(f"roadmap milestone {active_milestone} is not active")
101
+ active_id = active["id"]
102
+ for block in blocks:
103
+ if block["fields"].get("Status", (-1, ""))[1] != "pending":
104
+ continue
105
+ dependencies = dependency_ids(block)
106
+ if all(
107
+ dependency in blocks_by_id
108
+ and (
109
+ blocks_by_id[dependency]["fields"].get("Status", (-1, ""))[1]
110
+ == "shipped"
111
+ or dependency == active_id
112
+ )
113
+ for dependency in dependencies
114
+ ):
115
+ return block
116
+ raise NoEligibleMilestone("ROADMAP.md has no dependency-ready pending milestone")
117
+
118
+
119
+ def selection_payload(selected: dict) -> dict:
120
+ milestone = normalized_slug(selected["title"])
121
+ return {
122
+ "status": "selected",
123
+ "id": selected["id"],
124
+ "milestone": milestone,
125
+ "branch": f"gsd-path/{selected['id']}",
126
+ }
127
+
128
+
129
+ def select_lookahead(content: str, active_milestone: Optional[str] = None) -> dict:
130
+ try:
131
+ selected = next_eligible_pending(content, active_milestone)
132
+ except NoEligibleMilestone:
133
+ blocks = roadmap_blocks(content)
134
+ if blocks and all(
135
+ block["fields"].get("Status", (-1, ""))[1]
136
+ in {"shipped", "abandoned"}
137
+ for block in blocks
138
+ ):
139
+ return {"status": "complete"}
140
+ return {"status": "none"}
141
+ return selection_payload(selected)
142
+
143
+
144
+ def roadmap_contract(content: str, milestone: str) -> tuple[str, ...]:
145
+ block = milestone_block(content, milestone)
146
+ lines = content.splitlines()
147
+ return tuple(
148
+ line.rstrip()
149
+ for line in lines[block["start"] : block["end"]]
150
+ if not MUTABLE_ROADMAP_FIELDS_RE.match(line)
151
+ )
152
+
153
+
154
+ def compare_roadmap_entry(
155
+ before: str,
156
+ after: str,
157
+ milestone: str,
158
+ active_milestone: Optional[str] = None,
159
+ ) -> dict:
160
+ before_contract = roadmap_contract(before, milestone)
161
+ try:
162
+ after_contract = roadmap_contract(after, milestone)
163
+ selected = milestone_block(after, milestone)
164
+ eligible = next_eligible_pending(after, active_milestone)
165
+ except RoadmapError:
166
+ after_contract = ()
167
+ selected = None
168
+ eligible = None
169
+ status = (
170
+ "unchanged"
171
+ if before_contract == after_contract
172
+ and selected is not None
173
+ and eligible is not None
174
+ and selected["id"] == eligible["id"]
175
+ else "changed"
176
+ )
177
+ return {"status": status, "milestone": milestone}
178
+
179
+
180
+ def strict_sections(text: str) -> dict[str, tuple[str, int, int]]:
181
+ lines = text.splitlines(keepends=True)
182
+ headings: list[tuple[str, str, int]] = []
183
+ for index, line in enumerate(lines):
184
+ match = STRICT_HEADING_RE.fullmatch(line.rstrip("\r\n"))
185
+ if match:
186
+ if int(match.group(1).removeprefix("M")) < 1:
187
+ raise RoadmapError(f"ROADMAP.md has invalid milestone id: {match.group(1)}")
188
+ headings.append((match.group(1), match.group(2), index))
189
+ sections: dict[str, tuple[str, int, int]] = {}
190
+ for position, (milestone_id, slug, start) in enumerate(headings):
191
+ end = headings[position + 1][2] if position + 1 < len(headings) else len(lines)
192
+ if slug in sections:
193
+ raise RoadmapError(f"ROADMAP.md repeats milestone slug: {slug}")
194
+ sections[slug] = (milestone_id, start, end)
195
+ return sections
196
+
197
+
198
+ def replace_field(
199
+ lines: list[str],
200
+ start: int,
201
+ end: int,
202
+ field: str,
203
+ expected: set[str],
204
+ value: str,
205
+ ) -> None:
206
+ found: Optional[int] = None
207
+ for index in range(start, end):
208
+ raw = lines[index].rstrip("\r\n")
209
+ ending = lines[index][len(raw) :]
210
+ match = re.match(rf"^({re.escape(field)}:\s*)(\S+)(.*)$", raw)
211
+ if match is None:
212
+ continue
213
+ if found is not None:
214
+ raise RoadmapError(f"ROADMAP.md repeats {field} in one milestone")
215
+ if match.group(2) not in expected:
216
+ raise RoadmapError(
217
+ f"ROADMAP.md {field} is {match.group(2)}, expected {sorted(expected)}"
218
+ )
219
+ lines[index] = f"{match.group(1)}{value}{match.group(3)}{ending}"
220
+ found = index
221
+ if found is None:
222
+ raise RoadmapError(f"ROADMAP.md milestone is missing {field}")
223
+
224
+
225
+ def milestone_status(lines: list[str], start: int, end: int) -> str:
226
+ values = []
227
+ for line in lines[start:end]:
228
+ match = re.fullmatch(r"Status:\s*(\S+)\s*", line.rstrip("\r\n"))
229
+ if match:
230
+ values.append(match.group(1))
231
+ if len(values) != 1:
232
+ raise RoadmapError("ROADMAP.md milestone must have one Status field")
233
+ return values[0]
234
+
235
+
236
+ def activate_milestone(text: str, milestone: str) -> str:
237
+ sections = strict_sections(text)
238
+ if milestone not in sections:
239
+ raise RoadmapError(f"ROADMAP.md is missing selected milestone: {milestone}")
240
+ lines = text.splitlines(keepends=True)
241
+ active = [
242
+ slug
243
+ for slug, (_, start, end) in sections.items()
244
+ if milestone_status(lines, start, end) == "active"
245
+ ]
246
+ if active:
247
+ raise RoadmapError(
248
+ "ROADMAP.md already has an active milestone: " + ", ".join(active)
249
+ )
250
+ _, start, end = sections[milestone]
251
+ replace_field(lines, start, end, "Status", {"pending"}, "active")
252
+ return "".join(lines)
253
+
254
+
255
+ def reslice(before: str, candidate: str, phase: str, selected: str) -> str:
256
+ old_blocks = roadmap_blocks(before)
257
+ before_lines = before.splitlines(keepends=True)
258
+ candidate_lines = candidate.splitlines(keepends=True)
259
+ for block in old_blocks:
260
+ status = block["fields"].get("Status", (-1, ""))[1]
261
+ if status not in {"active", "shipped", "abandoned"}:
262
+ continue
263
+ slug = normalized_slug(block["title"])
264
+ new = milestone_block(candidate, slug)
265
+ if status == "shipped":
266
+ changed = roadmap_contract(before, slug) != roadmap_contract(candidate, slug)
267
+ else:
268
+ changed = before_lines[block["start"]:block["end"]] != candidate_lines[new["start"]:new["end"]]
269
+ if changed:
270
+ raise RoadmapError(f"{status} roadmap entry changed: {slug}")
271
+ if phase in {"inspect", "define"}:
272
+ active = [b for b in roadmap_blocks(candidate) if b["fields"].get("Status", (-1, ""))[1] == "active"]
273
+ if len(active) != 1 or selection_payload(active[0])["milestone"] != selected:
274
+ raise RoadmapError("roadmap re-slice must retain the active entry")
275
+ return candidate
276
+ if not any(b["fields"].get("Status", (-1, ""))[1] == "abandoned" for b in old_blocks):
277
+ raise RoadmapError("post-abandon re-slice requires an abandoned milestone")
278
+ selection = select_lookahead(candidate)
279
+ if selection.get("milestone") != selected:
280
+ raise RoadmapError("roadmap re-slice must use the selected dependency-ready milestone")
281
+ return activate_milestone(candidate, selected)
282
+
283
+
284
+ def has_questions(text: str, milestone: Optional[str] = None) -> bool:
285
+ """Report open questions for the active entry, or for ``milestone`` when given."""
286
+ lines = text.splitlines()
287
+ active_section: Optional[list[str]] = None
288
+ for index, line in enumerate(lines):
289
+ heading = STRICT_HEADING_RE.fullmatch(line)
290
+ if heading is None:
291
+ continue
292
+ if int(heading.group(1).removeprefix("M")) < 1:
293
+ raise RoadmapError(
294
+ f"ROADMAP.md has invalid milestone id: {heading.group(1)}"
295
+ )
296
+ end = next(
297
+ (
298
+ cursor
299
+ for cursor in range(index + 1, len(lines))
300
+ if STRICT_HEADING_RE.fullmatch(lines[cursor]) is not None
301
+ ),
302
+ len(lines),
303
+ )
304
+ section = lines[index:end]
305
+ if milestone is not None:
306
+ if heading.group(2) == milestone:
307
+ active_section = section
308
+ continue
309
+ if any(re.fullmatch(r"Status:\s*active(?:\s+#.*)?", item) for item in section):
310
+ if active_section is not None:
311
+ raise RoadmapError("ROADMAP.md has more than one active milestone")
312
+ active_section = section
313
+ if active_section is None:
314
+ if milestone is not None:
315
+ raise RoadmapError(f"ROADMAP.md is missing lookahead milestone: {milestone}")
316
+ raise RoadmapError("ROADMAP.md has no active milestone")
317
+ try:
318
+ start = active_section.index("Open questions") + 1
319
+ except ValueError as error:
320
+ raise RoadmapError("active roadmap milestone has no Open questions") from error
321
+ questions = [
322
+ line[2:].strip()
323
+ for line in active_section[start:]
324
+ if line.startswith("- ")
325
+ ]
326
+ return any(question.lower() != "none" for question in questions)
@@ -4,6 +4,11 @@
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
+ import sys
8
+
9
+ # Runtime helpers must not modify their immutable installation.
10
+ sys.dont_write_bytecode = True
11
+
7
12
  import hashlib
8
13
  import json
9
14
  import os
@@ -20,6 +25,43 @@ except ModuleNotFoundError as error: # pragma: no cover - package imports used
20
25
  from scripts.isolation import IsolationError, checkpoint as isolation_checkpoint, require_commit, require_full_sha, PROJECT_ENTRIES
21
26
 
22
27
 
28
+ if __package__:
29
+ from . import roadmap
30
+ else:
31
+ import roadmap
32
+
33
+ if __package__: # imported as scripts.state_checkpoint
34
+ from . import pipeline_state
35
+ from .pipeline_state import (
36
+ CHECKPOINT_JOURNAL_NAME,
37
+ CHECKPOINT_KINDS,
38
+ CHECKPOINT_SCHEMA,
39
+ PLAN_APPROVAL_SUBJECT,
40
+ PROMOTION_TRACKS,
41
+ SLUG_RE,
42
+ TASK_FILE_RE,
43
+ TASK_ID_RE,
44
+ ApprovedTaskContract,
45
+ PipelineState,
46
+ PipelineStateError,
47
+ )
48
+ else: # standalone script or sibling import
49
+ import pipeline_state
50
+ from pipeline_state import (
51
+ CHECKPOINT_JOURNAL_NAME,
52
+ CHECKPOINT_KINDS,
53
+ CHECKPOINT_SCHEMA,
54
+ PLAN_APPROVAL_SUBJECT,
55
+ PROMOTION_TRACKS,
56
+ SLUG_RE,
57
+ TASK_FILE_RE,
58
+ TASK_ID_RE,
59
+ ApprovedTaskContract,
60
+ PipelineState,
61
+ PipelineStateError,
62
+ )
63
+
64
+
23
65
  def _sha256(content: str) -> str:
24
66
  return hashlib.sha256(content.encode("utf-8")).hexdigest()
25
67
 
@@ -65,41 +107,29 @@ def _checkpoint_artifact_digest(project: Path, mutable_paths: set[str]) -> str:
65
107
  return digest.hexdigest()
66
108
 
67
109
 
68
- def _roadmap_status(lines: list[str], start: int, end: int) -> str:
69
- values = []
70
- for line in lines[start:end]:
71
- match = re.fullmatch(r"Status:\s*(\S+)\s*", line.rstrip("\r\n"))
72
- if match:
73
- values.append(match.group(1))
74
- if len(values) != 1:
75
- raise PipelineStateError("ROADMAP.md milestone must have one Status field")
76
- return values[0]
77
-
78
-
79
110
  def _activate_roadmap_milestone(text: str, milestone: str) -> str:
80
- sections = pipeline_state._roadmap_sections(text)
81
- if milestone not in sections:
82
- raise PipelineStateError(f"ROADMAP.md is missing selected milestone: {milestone}")
83
- lines = text.splitlines(keepends=True)
84
- active = [
85
- slug
86
- for slug, (_, start, end) in sections.items()
87
- if _roadmap_status(lines, start, end) == "active"
88
- ]
89
- if active:
90
- raise PipelineStateError(
91
- "ROADMAP.md already has an active milestone: " + ", ".join(active)
92
- )
93
- _, start, end = sections[milestone]
94
- pipeline_state._replace_roadmap_field(lines, start, end, "Status", {"pending"}, "active")
95
- return "".join(lines)
96
-
111
+ try:
112
+ return roadmap.activate_milestone(text, milestone)
113
+ except roadmap.RoadmapError as error:
114
+ raise PipelineStateError(str(error)) from error
97
115
 
98
116
  def _approval_details(
99
117
  kind: str,
100
118
  state: PipelineState,
101
119
  selected_milestone: Optional[str],
102
120
  ) -> tuple[dict[str, Optional[str]], str, str, str]:
121
+ if kind == "roadmap-reslice":
122
+ if state.status != "active" or state.archive is not None:
123
+ raise PipelineStateError("roadmap re-slice requires active, unarchived state")
124
+ if state.phase in {"inspect", "define"} and selected_milestone == state.milestone and state.milestone:
125
+ changes = {}
126
+ event = "program roadmap re-slice approved"
127
+ elif state.phase == "roadmap" and state.milestone is None and selected_milestone and SLUG_RE.fullmatch(selected_milestone):
128
+ changes = {"phase": "inspect", "milestone": selected_milestone}
129
+ event = "roadmap approved after abandoned milestone"
130
+ else:
131
+ raise PipelineStateError("roadmap re-slice must preserve the active milestone or select after abandon")
132
+ return changes, event, "roadmap: program roadmap approved", "Why: approved roadmap checkpoint"
103
133
  if kind == "plan":
104
134
  if selected_milestone is not None:
105
135
  raise PipelineStateError("plan approval does not accept a selected milestone")
@@ -126,6 +156,12 @@ def _approval_details(
126
156
  raise PipelineStateError(f"unsupported approval kind: {kind}")
127
157
 
128
158
 
159
+ def _reslice_roadmap(before: str, candidate: str, state: PipelineState, selected: str) -> str:
160
+ try:
161
+ return roadmap.reslice(before, candidate, state.phase, selected)
162
+ except roadmap.RoadmapError as error:
163
+ raise PipelineStateError(str(error)) from error
164
+
129
165
  def _checkpoint_request_fields(
130
166
  kind: str,
131
167
  project_dir: str,
@@ -175,6 +211,10 @@ def _approval_journal(repo: Path, journal: Mapping[str, object]) -> dict[str, ob
175
211
  "roadmap_before",
176
212
  "roadmap_after",
177
213
  }
214
+ if journal.get("kind") == "roadmap-reslice":
215
+ expected_keys.add("baseline_before")
216
+ if not isinstance(journal.get("baseline_before"), str):
217
+ raise PipelineStateError("checkpoint journal has an invalid re-slice baseline")
178
218
  if set(journal) != expected_keys:
179
219
  raise PipelineStateError("checkpoint journal has invalid fields")
180
220
  if journal.get("schema") != CHECKPOINT_SCHEMA or journal.get("repo") != str(repo):
@@ -253,12 +293,14 @@ def _approval_journal(repo: Path, journal: Mapping[str, object]) -> dict[str, ob
253
293
  or journal["state_after"] != rendered
254
294
  ):
255
295
  raise PipelineStateError("checkpoint journal approval contract is invalid")
256
- if str(journal["kind"]) == "roadmap":
296
+ if journal["kind"] in {"roadmap", "roadmap-reslice"}:
257
297
  if project_dir != ".project":
258
298
  raise PipelineStateError("roadmap approval is legal only on the active track")
259
299
  if journal["roadmap_path"] != ".project/ROADMAP.md":
260
300
  raise PipelineStateError("checkpoint journal has an invalid ROADMAP path")
261
- expected_roadmap = _activate_roadmap_milestone(
301
+ expected_roadmap = _reslice_roadmap(
302
+ str(journal["baseline_before"]), str(journal["roadmap_before"]), before, str(selected),
303
+ ) if journal["kind"] == "roadmap-reslice" else _activate_roadmap_milestone(
262
304
  str(journal["roadmap_before"]),
263
305
  str(selected),
264
306
  )
@@ -307,6 +349,12 @@ def _resume_checkpoint_locked(
307
349
  transaction = _approval_journal(repo, journal)
308
350
  state_path = _checkpoint_file(repo, str(transaction["state_path"]), "STATE.md")
309
351
  mutable = {state_path.relative_to(project).as_posix()}
352
+ baseline = project / "ROADMAP.before-reslice.md" if transaction["kind"] == "roadmap-reslice" else None
353
+ if baseline is not None:
354
+ mutable.add(baseline.name)
355
+ if baseline.exists() or baseline.is_symlink():
356
+ if pipeline_state._read_real_file(baseline, "re-slice baseline") != transaction["baseline_before"]:
357
+ raise PipelineStateError("re-slice baseline drifted during checkpoint recovery")
310
358
  roadmap_path: Optional[Path] = None
311
359
  if transaction["roadmap_path"] is not None:
312
360
  roadmap_path = _checkpoint_file(
@@ -354,6 +402,8 @@ def _resume_checkpoint_locked(
354
402
  raise PipelineStateError("completed checkpoint has the wrong ROADMAP.md")
355
403
 
356
404
  try:
405
+ if baseline is not None and baseline.exists():
406
+ baseline.unlink()
357
407
  committed = isolation_checkpoint(
358
408
  repo,
359
409
  expected_head,
@@ -362,6 +412,8 @@ def _resume_checkpoint_locked(
362
412
  [".project"],
363
413
  )
364
414
  except IsolationError as error:
415
+ if baseline is not None and pipeline_state._run_git(repo, "rev-parse", "HEAD").stdout.strip() == expected_head:
416
+ pipeline_state._atomic_write(baseline, str(transaction["baseline_before"]))
365
417
  raise PipelineStateError(f"approval checkpoint failed: {error}") from error
366
418
  if committed.get("commit") != pipeline_state._run_git(repo, "rev-parse", "HEAD").stdout.strip():
367
419
  raise PipelineStateError("approval checkpoint returned the wrong commit")
@@ -381,6 +433,8 @@ def _resume_checkpoint_locked(
381
433
  def _validate_plan_briefs(repo: Path, kind: str, project_dir: str) -> None:
382
434
  if kind != "plan":
383
435
  return
436
+ if project_dir == ".project":
437
+ pipeline_state._build_recovery().validate_plan(repo)
384
438
  head = pipeline_state._run_git(repo, "rev-parse", "--verify", "HEAD", check=False)
385
439
  if head.returncode != 0:
386
440
  # Pre-Git approval defers base-dependent checks to build.
@@ -476,6 +530,8 @@ def checkpoint_approval(
476
530
  state, state_before, state_path = pipeline_state.load_state(resolved, project_dir)
477
531
  changes, event, subject, body = _approval_details(kind, state, selected_milestone)
478
532
  _validate_plan_briefs(resolved, kind, project_dir)
533
+ if kind == "plan" and project_dir == ".project":
534
+ pipeline_state._build_recovery().restore_unchanged_reviews(resolved)
479
535
  expected = {
480
536
  "phase": state.phase,
481
537
  "status": state.status,
@@ -497,13 +553,18 @@ def checkpoint_approval(
497
553
  roadmap_path: Optional[Path] = None
498
554
  roadmap_before: Optional[str] = None
499
555
  roadmap_after: Optional[str] = None
500
- if kind == "roadmap":
556
+ baseline_before = None
557
+ if kind in {"roadmap", "roadmap-reslice"}:
501
558
  if project_dir != ".project":
502
559
  raise PipelineStateError("roadmap approval is legal only on the active track")
503
560
  assert selected_milestone is not None
504
561
  roadmap_path = project / "ROADMAP.md"
505
562
  roadmap_before = pipeline_state._read_real_file(roadmap_path, "ROADMAP.md")
506
- roadmap_after = _activate_roadmap_milestone(
563
+ if kind == "roadmap-reslice":
564
+ baseline_before = pipeline_state._read_real_file(project / "ROADMAP.before-reslice.md", "re-slice baseline")
565
+ roadmap_after = _reslice_roadmap(
566
+ baseline_before, roadmap_before, state, selected_milestone,
567
+ ) if kind == "roadmap-reslice" else _activate_roadmap_milestone(
507
568
  roadmap_before,
508
569
  selected_milestone,
509
570
  )
@@ -512,6 +573,8 @@ def checkpoint_approval(
512
573
  mutable = {state_relative}
513
574
  if roadmap_path is not None:
514
575
  mutable.add(roadmap_path.relative_to(project).as_posix())
576
+ if baseline_before is not None:
577
+ mutable.add("ROADMAP.before-reslice.md")
515
578
  journal: dict[str, object] = {
516
579
  "schema": CHECKPOINT_SCHEMA,
517
580
  "repo": str(resolved),
@@ -528,6 +591,8 @@ def checkpoint_approval(
528
591
  "roadmap_before": roadmap_before,
529
592
  "roadmap_after": roadmap_after,
530
593
  }
594
+ if baseline_before is not None:
595
+ journal["baseline_before"] = baseline_before
531
596
  pipeline_state._write_json(journal_path, journal)
532
597
  return _resume_checkpoint_locked(resolved, project, journal_path, journal)
533
598
 
@@ -576,6 +641,10 @@ def defer_approval(
576
641
  with pipeline_state._state_lock(project):
577
642
  state, state_before, state_path = pipeline_state.load_state(resolved, project_dir)
578
643
  changes, event, _, _ = _approval_details(kind, state, selected_milestone)
644
+ if project_dir == ".project":
645
+ recovery = pipeline_state._build_recovery().context(resolved, state_before)
646
+ if recovery and recovery["active"]:
647
+ raise PipelineStateError("build recovery requires a plan checkpoint")
579
648
  _validate_plan_briefs(resolved, kind, project_dir)
580
649
  if patch:
581
650
  event = "patch plan approved"
@@ -595,17 +664,25 @@ def defer_approval(
595
664
  project_dir,
596
665
  approval_kind=kind,
597
666
  )
598
- if kind == "roadmap":
667
+ baseline = None
668
+ if kind in {"roadmap", "roadmap-reslice"}:
599
669
  if project_dir != ".project":
600
670
  raise PipelineStateError("roadmap approval is legal only on the active track")
601
671
  assert selected_milestone is not None
602
672
  roadmap_path = project / "ROADMAP.md"
603
- roadmap_after = _activate_roadmap_milestone(
604
- pipeline_state._read_real_file(roadmap_path, "ROADMAP.md"),
605
- selected_milestone,
606
- )
673
+ roadmap_before = pipeline_state._read_real_file(roadmap_path, "ROADMAP.md")
674
+ if kind == "roadmap-reslice":
675
+ baseline = project / "ROADMAP.before-reslice.md"
676
+ roadmap_after = _reslice_roadmap(
677
+ pipeline_state._read_real_file(baseline, "re-slice baseline"),
678
+ roadmap_before, state, selected_milestone,
679
+ )
680
+ else:
681
+ roadmap_after = _activate_roadmap_milestone(roadmap_before, selected_milestone)
607
682
  pipeline_state._atomic_write(roadmap_path, roadmap_after)
608
683
  pipeline_state._atomic_write(state_path, state_after)
684
+ if baseline is not None:
685
+ baseline.unlink()
609
686
  return {
610
687
  "schema": CHECKPOINT_SCHEMA,
611
688
  "status": "approved",
@@ -1002,38 +1079,3 @@ def _classify_plan_drift(
1002
1079
  "contract_paths": sorted(contract_paths),
1003
1080
  "reason": "; ".join(reasons) if reasons else "approval contracts and declared paths are unchanged",
1004
1081
  }
1005
-
1006
-
1007
- # pipeline_state imports this module, so the parent is resolved after the
1008
- # definitions above. Parent functions are looked up on the module at call time
1009
- # so patches applied to pipeline_state stay visible here.
1010
- if __package__: # imported as scripts.state_checkpoint
1011
- from . import pipeline_state
1012
- from .pipeline_state import (
1013
- CHECKPOINT_JOURNAL_NAME,
1014
- CHECKPOINT_KINDS,
1015
- CHECKPOINT_SCHEMA,
1016
- PLAN_APPROVAL_SUBJECT,
1017
- PROMOTION_TRACKS,
1018
- SLUG_RE,
1019
- TASK_FILE_RE,
1020
- TASK_ID_RE,
1021
- ApprovedTaskContract,
1022
- PipelineState,
1023
- PipelineStateError,
1024
- )
1025
- else: # standalone script or sibling import
1026
- import pipeline_state
1027
- from pipeline_state import (
1028
- CHECKPOINT_JOURNAL_NAME,
1029
- CHECKPOINT_KINDS,
1030
- CHECKPOINT_SCHEMA,
1031
- PLAN_APPROVAL_SUBJECT,
1032
- PROMOTION_TRACKS,
1033
- SLUG_RE,
1034
- TASK_FILE_RE,
1035
- TASK_ID_RE,
1036
- ApprovedTaskContract,
1037
- PipelineState,
1038
- PipelineStateError,
1039
- )