@opengsd/gsd-path 1.0.0 → 1.1.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 (395) hide show
  1. package/AGENTS.md +28 -11
  2. package/DOCS.md +6 -5
  3. package/FULL.md +9 -13
  4. package/QUICK.md +3 -3
  5. package/README.md +151 -14
  6. package/RUNTIME.md +7 -1
  7. package/WORKFLOW.md +30 -14
  8. package/package.json +15 -1
  9. package/platforms/shared-agents/dispatch.md +6 -0
  10. package/scripts/archive_milestone.py +10 -118
  11. package/scripts/bootstrap_repository.py +25 -3
  12. package/scripts/build_recovery.py +287 -0
  13. package/scripts/check_docs_audit.py +5 -3
  14. package/scripts/check_handoffs.py +1 -1
  15. package/scripts/detect_project.py +11 -2
  16. package/scripts/dispatch_driver.py +4 -2
  17. package/scripts/git_guard.py +2 -1
  18. package/scripts/install.mjs +2 -0
  19. package/scripts/install.py +2 -0
  20. package/scripts/integration.py +28 -22
  21. package/scripts/isolation.py +9 -1
  22. package/scripts/lean_verification.py +1 -1
  23. package/scripts/pipeline_diagnose.py +12 -3
  24. package/scripts/pipeline_state.py +114 -186
  25. package/scripts/pipeline_undo.py +11 -7
  26. package/scripts/promote_lookahead.py +30 -162
  27. package/scripts/roadmap.py +321 -0
  28. package/scripts/skill-resources.json +79 -1
  29. package/scripts/state_checkpoint.py +109 -72
  30. package/scripts/state_promote.py +42 -44
  31. package/scripts/task_context.py +73 -0
  32. package/scripts/token_budget.py +40 -1
  33. package/scripts/workflow_run.py +116 -7
  34. package/skills/gsd-path/BUILD.md +47 -575
  35. package/skills/gsd-path/DECIDE.md +2 -6
  36. package/skills/gsd-path/DEFINE.md +10 -14
  37. package/skills/gsd-path/INSPECT.md +30 -8
  38. package/skills/gsd-path/PLAN.md +25 -17
  39. package/skills/gsd-path/RESEARCH.md +3 -6
  40. package/skills/gsd-path/ROADMAP.md +22 -58
  41. package/skills/gsd-path/SHIP.md +5 -4
  42. package/skills/gsd-path/SKILL.md +24 -14
  43. package/skills/gsd-path/references/build-abandon.md +33 -0
  44. package/skills/gsd-path/references/build-native.md +494 -0
  45. package/skills/gsd-path/references/build-recovery.md +78 -0
  46. package/skills/gsd-path/references/coder.md +8 -3
  47. package/skills/gsd-path/references/dispatch.md +6 -0
  48. package/skills/gsd-path/references/docs-auditor.md +14 -0
  49. package/skills/gsd-path/references/reviewer-final.md +21 -0
  50. package/skills/gsd-path/references/reviewer-panel.md +17 -0
  51. package/skills/gsd-path/references/reviewer-skeptic.md +20 -0
  52. package/skills/gsd-path/references/reviewer-wave.md +89 -0
  53. package/skills/gsd-path/references/reviewer.md +12 -150
  54. package/skills/gsd-path/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  55. package/skills/gsd-path/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
  56. package/skills/gsd-path/scripts/__pycache__/build_recovery.cpython-312.pyc +0 -0
  57. package/skills/gsd-path/scripts/__pycache__/build_state.cpython-312.pyc +0 -0
  58. package/skills/gsd-path/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
  59. package/skills/gsd-path/scripts/__pycache__/check_task_briefs.cpython-312.pyc +0 -0
  60. package/skills/gsd-path/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
  61. package/skills/gsd-path/scripts/__pycache__/discussion_records.cpython-312.pyc +0 -0
  62. package/skills/gsd-path/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
  63. package/skills/gsd-path/scripts/__pycache__/integration.cpython-312.pyc +0 -0
  64. package/skills/gsd-path/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
  65. package/skills/gsd-path/scripts/__pycache__/loop_run.cpython-312.pyc +0 -0
  66. package/skills/gsd-path/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
  67. package/skills/gsd-path/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
  68. package/skills/gsd-path/scripts/__pycache__/review_findings.cpython-312.pyc +0 -0
  69. package/skills/gsd-path/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
  70. package/skills/gsd-path/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
  71. package/skills/gsd-path/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
  72. package/skills/gsd-path/scripts/__pycache__/task_context.cpython-312.pyc +0 -0
  73. package/skills/gsd-path/scripts/__pycache__/workflow_run.cpython-312.pyc +0 -0
  74. package/skills/gsd-path/scripts/archive_milestone.py +10 -118
  75. package/skills/gsd-path/scripts/bootstrap_repository.py +25 -3
  76. package/skills/gsd-path/scripts/build_recovery.py +287 -0
  77. package/skills/gsd-path/scripts/check_docs_audit.py +5 -3
  78. package/skills/gsd-path/scripts/check_handoffs.py +1 -1
  79. package/skills/gsd-path/scripts/detect_project.py +11 -2
  80. package/skills/gsd-path/scripts/dispatch_driver.py +4 -2
  81. package/skills/gsd-path/scripts/integration.py +28 -22
  82. package/skills/gsd-path/scripts/isolation.py +9 -1
  83. package/skills/gsd-path/scripts/lean_verification.py +1 -1
  84. package/skills/gsd-path/scripts/pipeline_state.py +114 -186
  85. package/skills/gsd-path/scripts/promote_lookahead.py +30 -162
  86. package/skills/gsd-path/scripts/roadmap.py +321 -0
  87. package/skills/gsd-path/scripts/state_checkpoint.py +109 -72
  88. package/skills/gsd-path/scripts/state_promote.py +42 -44
  89. package/skills/gsd-path/scripts/task_context.py +73 -0
  90. package/skills/gsd-path/scripts/token_budget.py +40 -1
  91. package/skills/gsd-path/scripts/workflow_run.py +116 -7
  92. package/skills/gsd-path/templates/wave-review.md +1 -1
  93. package/skills/gsd-path-build/SKILL.md +47 -575
  94. package/skills/gsd-path-build/references/build-abandon.md +33 -0
  95. package/skills/gsd-path-build/references/build-native.md +494 -0
  96. package/skills/gsd-path-build/references/build-recovery.md +78 -0
  97. package/skills/gsd-path-build/references/coder.md +8 -3
  98. package/skills/gsd-path-build/references/dispatch.md +6 -0
  99. package/skills/gsd-path-build/references/reviewer-final.md +21 -0
  100. package/skills/gsd-path-build/references/reviewer-panel.md +17 -0
  101. package/skills/gsd-path-build/references/reviewer-skeptic.md +20 -0
  102. package/skills/gsd-path-build/references/reviewer-wave.md +89 -0
  103. package/skills/gsd-path-build/references/reviewer.md +12 -150
  104. package/skills/gsd-path-build/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  105. package/skills/gsd-path-build/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
  106. package/skills/gsd-path-build/scripts/__pycache__/build_recovery.cpython-312.pyc +0 -0
  107. package/skills/gsd-path-build/scripts/__pycache__/build_state.cpython-312.pyc +0 -0
  108. package/skills/gsd-path-build/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
  109. package/skills/gsd-path-build/scripts/__pycache__/check_task_briefs.cpython-312.pyc +0 -0
  110. package/skills/gsd-path-build/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
  111. package/skills/gsd-path-build/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
  112. package/skills/gsd-path-build/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
  113. package/skills/gsd-path-build/scripts/__pycache__/loop_run.cpython-312.pyc +0 -0
  114. package/skills/gsd-path-build/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
  115. package/skills/gsd-path-build/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
  116. package/skills/gsd-path-build/scripts/__pycache__/review_findings.cpython-312.pyc +0 -0
  117. package/skills/gsd-path-build/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
  118. package/skills/gsd-path-build/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
  119. package/skills/gsd-path-build/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
  120. package/skills/gsd-path-build/scripts/__pycache__/task_context.cpython-312.pyc +0 -0
  121. package/skills/gsd-path-build/scripts/__pycache__/workflow_run.cpython-312.pyc +0 -0
  122. package/skills/gsd-path-build/scripts/archive_milestone.py +10 -118
  123. package/skills/gsd-path-build/scripts/build_recovery.py +287 -0
  124. package/skills/gsd-path-build/scripts/check_handoffs.py +1 -1
  125. package/skills/gsd-path-build/scripts/detect_project.py +1526 -0
  126. package/skills/gsd-path-build/scripts/dispatch_driver.py +4 -2
  127. package/skills/gsd-path-build/scripts/integration.py +28 -22
  128. package/skills/gsd-path-build/scripts/isolation.py +9 -1
  129. package/skills/gsd-path-build/scripts/pipeline_state.py +114 -186
  130. package/skills/gsd-path-build/scripts/promote_lookahead.py +283 -0
  131. package/skills/gsd-path-build/scripts/roadmap.py +321 -0
  132. package/skills/gsd-path-build/scripts/state_checkpoint.py +109 -72
  133. package/skills/gsd-path-build/scripts/state_promote.py +42 -44
  134. package/skills/gsd-path-build/scripts/task_context.py +73 -0
  135. package/skills/gsd-path-build/scripts/token_budget.py +40 -1
  136. package/skills/gsd-path-build/scripts/workflow_run.py +116 -7
  137. package/skills/gsd-path-build/templates/wave-review.md +1 -1
  138. package/skills/gsd-path-decide/SKILL.md +2 -6
  139. package/skills/gsd-path-decide/references/dispatch.md +6 -0
  140. package/skills/gsd-path-decide/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  141. package/skills/gsd-path-decide/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
  142. package/skills/gsd-path-decide/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
  143. package/skills/gsd-path-decide/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
  144. package/skills/gsd-path-decide/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
  145. package/skills/gsd-path-decide/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
  146. package/skills/gsd-path-decide/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
  147. package/skills/gsd-path-decide/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
  148. package/skills/gsd-path-decide/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
  149. package/skills/gsd-path-decide/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
  150. package/skills/gsd-path-decide/scripts/archive_milestone.py +2157 -0
  151. package/skills/gsd-path-decide/scripts/build_recovery.py +287 -0
  152. package/skills/gsd-path-decide/scripts/check_handoffs.py +1 -1
  153. package/skills/gsd-path-decide/scripts/detect_project.py +1526 -0
  154. package/skills/gsd-path-decide/scripts/discussion_validate.py +1321 -0
  155. package/skills/gsd-path-decide/scripts/integration.py +1439 -0
  156. package/skills/gsd-path-decide/scripts/isolation.py +9 -1
  157. package/skills/gsd-path-decide/scripts/pipeline_state.py +114 -186
  158. package/skills/gsd-path-decide/scripts/promote_lookahead.py +283 -0
  159. package/skills/gsd-path-decide/scripts/review_panel.py +553 -0
  160. package/skills/gsd-path-decide/scripts/roadmap.py +321 -0
  161. package/skills/gsd-path-decide/scripts/state_checkpoint.py +109 -72
  162. package/skills/gsd-path-decide/scripts/state_promote.py +42 -44
  163. package/skills/gsd-path-define/SKILL.md +10 -14
  164. package/skills/gsd-path-define/references/build-recovery.md +78 -0
  165. package/skills/gsd-path-define/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  166. package/skills/gsd-path-define/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
  167. package/skills/gsd-path-define/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
  168. package/skills/gsd-path-define/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
  169. package/skills/gsd-path-define/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
  170. package/skills/gsd-path-define/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
  171. package/skills/gsd-path-define/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
  172. package/skills/gsd-path-define/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
  173. package/skills/gsd-path-define/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
  174. package/skills/gsd-path-define/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
  175. package/skills/gsd-path-define/scripts/archive_milestone.py +2157 -0
  176. package/skills/gsd-path-define/scripts/build_recovery.py +287 -0
  177. package/skills/gsd-path-define/scripts/check_handoffs.py +1 -1
  178. package/skills/gsd-path-define/scripts/detect_project.py +11 -2
  179. package/skills/gsd-path-define/scripts/discussion_validate.py +1321 -0
  180. package/skills/gsd-path-define/scripts/integration.py +1439 -0
  181. package/skills/gsd-path-define/scripts/isolation.py +9 -1
  182. package/skills/gsd-path-define/scripts/pipeline_state.py +114 -186
  183. package/skills/gsd-path-define/scripts/promote_lookahead.py +283 -0
  184. package/skills/gsd-path-define/scripts/review_panel.py +553 -0
  185. package/skills/gsd-path-define/scripts/roadmap.py +321 -0
  186. package/skills/gsd-path-define/scripts/state_checkpoint.py +109 -72
  187. package/skills/gsd-path-define/scripts/state_promote.py +42 -44
  188. package/skills/gsd-path-docs-audit/references/dispatch.md +6 -0
  189. package/skills/gsd-path-docs-audit/references/docs-auditor.md +14 -0
  190. package/skills/gsd-path-docs-audit/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  191. package/skills/gsd-path-docs-audit/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
  192. package/skills/gsd-path-docs-audit/scripts/__pycache__/build_recovery.cpython-312.pyc +0 -0
  193. package/skills/gsd-path-docs-audit/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
  194. package/skills/gsd-path-docs-audit/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
  195. package/skills/gsd-path-docs-audit/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
  196. package/skills/gsd-path-docs-audit/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
  197. package/skills/gsd-path-docs-audit/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
  198. package/skills/gsd-path-docs-audit/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
  199. package/skills/gsd-path-docs-audit/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
  200. package/skills/gsd-path-docs-audit/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
  201. package/skills/gsd-path-docs-audit/scripts/archive_milestone.py +2157 -0
  202. package/skills/gsd-path-docs-audit/scripts/build_recovery.py +287 -0
  203. package/skills/gsd-path-docs-audit/scripts/check_docs_audit.py +5 -3
  204. package/skills/gsd-path-docs-audit/scripts/check_handoffs.py +1 -1
  205. package/skills/gsd-path-docs-audit/scripts/detect_project.py +1526 -0
  206. package/skills/gsd-path-docs-audit/scripts/discussion_validate.py +1321 -0
  207. package/skills/gsd-path-docs-audit/scripts/integration.py +1439 -0
  208. package/skills/gsd-path-docs-audit/scripts/isolation.py +9 -1
  209. package/skills/gsd-path-docs-audit/scripts/pipeline_state.py +114 -186
  210. package/skills/gsd-path-docs-audit/scripts/promote_lookahead.py +283 -0
  211. package/skills/gsd-path-docs-audit/scripts/review_panel.py +553 -0
  212. package/skills/gsd-path-docs-audit/scripts/roadmap.py +321 -0
  213. package/skills/gsd-path-docs-audit/scripts/state_checkpoint.py +109 -72
  214. package/skills/gsd-path-docs-audit/scripts/state_promote.py +42 -44
  215. package/skills/gsd-path-forensics/scripts/__pycache__/pipeline_undo.cpython-312.pyc +0 -0
  216. package/skills/gsd-path-forensics/scripts/pipeline_diagnose.py +12 -3
  217. package/skills/gsd-path-forensics/scripts/pipeline_undo.py +11 -7
  218. package/skills/gsd-path-inspect/SKILL.md +30 -8
  219. package/skills/gsd-path-inspect/references/dispatch.md +6 -0
  220. package/skills/gsd-path-inspect/references/docs-auditor.md +14 -0
  221. package/skills/gsd-path-inspect/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  222. package/skills/gsd-path-inspect/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
  223. package/skills/gsd-path-inspect/scripts/__pycache__/build_recovery.cpython-312.pyc +0 -0
  224. package/skills/gsd-path-inspect/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
  225. package/skills/gsd-path-inspect/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
  226. package/skills/gsd-path-inspect/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
  227. package/skills/gsd-path-inspect/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
  228. package/skills/gsd-path-inspect/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
  229. package/skills/gsd-path-inspect/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
  230. package/skills/gsd-path-inspect/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
  231. package/skills/gsd-path-inspect/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
  232. package/skills/gsd-path-inspect/scripts/archive_milestone.py +2157 -0
  233. package/skills/gsd-path-inspect/scripts/build_recovery.py +287 -0
  234. package/skills/gsd-path-inspect/scripts/check_docs_audit.py +5 -3
  235. package/skills/gsd-path-inspect/scripts/check_handoffs.py +1 -1
  236. package/skills/gsd-path-inspect/scripts/detect_project.py +11 -2
  237. package/skills/gsd-path-inspect/scripts/discussion_validate.py +1321 -0
  238. package/skills/gsd-path-inspect/scripts/integration.py +1439 -0
  239. package/skills/gsd-path-inspect/scripts/isolation.py +9 -1
  240. package/skills/gsd-path-inspect/scripts/pipeline_state.py +114 -186
  241. package/skills/gsd-path-inspect/scripts/promote_lookahead.py +283 -0
  242. package/skills/gsd-path-inspect/scripts/review_panel.py +553 -0
  243. package/skills/gsd-path-inspect/scripts/roadmap.py +321 -0
  244. package/skills/gsd-path-inspect/scripts/state_checkpoint.py +109 -72
  245. package/skills/gsd-path-inspect/scripts/state_promote.py +42 -44
  246. package/skills/gsd-path-inspect/scripts/workflow_run.py +226 -0
  247. package/skills/gsd-path-plan/SKILL.md +25 -17
  248. package/skills/gsd-path-plan/references/build-recovery.md +78 -0
  249. package/skills/gsd-path-plan/references/dispatch.md +6 -0
  250. package/skills/gsd-path-plan/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  251. package/skills/gsd-path-plan/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
  252. package/skills/gsd-path-plan/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
  253. package/skills/gsd-path-plan/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
  254. package/skills/gsd-path-plan/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
  255. package/skills/gsd-path-plan/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
  256. package/skills/gsd-path-plan/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
  257. package/skills/gsd-path-plan/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
  258. package/skills/gsd-path-plan/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
  259. package/skills/gsd-path-plan/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
  260. package/skills/gsd-path-plan/scripts/archive_milestone.py +2157 -0
  261. package/skills/gsd-path-plan/scripts/build_recovery.py +287 -0
  262. package/skills/gsd-path-plan/scripts/check_handoffs.py +1 -1
  263. package/skills/gsd-path-plan/scripts/detect_project.py +1526 -0
  264. package/skills/gsd-path-plan/scripts/discussion_validate.py +1321 -0
  265. package/skills/gsd-path-plan/scripts/integration.py +1439 -0
  266. package/skills/gsd-path-plan/scripts/isolation.py +9 -1
  267. package/skills/gsd-path-plan/scripts/pipeline_state.py +114 -186
  268. package/skills/gsd-path-plan/scripts/promote_lookahead.py +283 -0
  269. package/skills/gsd-path-plan/scripts/roadmap.py +321 -0
  270. package/skills/gsd-path-plan/scripts/state_checkpoint.py +109 -72
  271. package/skills/gsd-path-plan/scripts/state_promote.py +42 -44
  272. package/skills/gsd-path-plan/scripts/workflow_run.py +116 -7
  273. package/skills/gsd-path-research/SKILL.md +3 -6
  274. package/skills/gsd-path-research/references/dispatch.md +6 -0
  275. package/skills/gsd-path-research/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  276. package/skills/gsd-path-research/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
  277. package/skills/gsd-path-research/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
  278. package/skills/gsd-path-research/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
  279. package/skills/gsd-path-research/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
  280. package/skills/gsd-path-research/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
  281. package/skills/gsd-path-research/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
  282. package/skills/gsd-path-research/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
  283. package/skills/gsd-path-research/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
  284. package/skills/gsd-path-research/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
  285. package/skills/gsd-path-research/scripts/archive_milestone.py +2157 -0
  286. package/skills/gsd-path-research/scripts/build_recovery.py +287 -0
  287. package/skills/gsd-path-research/scripts/check_handoffs.py +1 -1
  288. package/skills/gsd-path-research/scripts/detect_project.py +1526 -0
  289. package/skills/gsd-path-research/scripts/discussion_validate.py +1321 -0
  290. package/skills/gsd-path-research/scripts/integration.py +1439 -0
  291. package/skills/gsd-path-research/scripts/isolation.py +9 -1
  292. package/skills/gsd-path-research/scripts/pipeline_state.py +114 -186
  293. package/skills/gsd-path-research/scripts/promote_lookahead.py +283 -0
  294. package/skills/gsd-path-research/scripts/review_panel.py +553 -0
  295. package/skills/gsd-path-research/scripts/roadmap.py +321 -0
  296. package/skills/gsd-path-research/scripts/state_checkpoint.py +109 -72
  297. package/skills/gsd-path-research/scripts/state_promote.py +42 -44
  298. package/skills/gsd-path-roadmap/SKILL.md +22 -58
  299. package/skills/gsd-path-roadmap/references/dispatch.md +6 -0
  300. package/skills/gsd-path-roadmap/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  301. package/skills/gsd-path-roadmap/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
  302. package/skills/gsd-path-roadmap/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
  303. package/skills/gsd-path-roadmap/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
  304. package/skills/gsd-path-roadmap/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
  305. package/skills/gsd-path-roadmap/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
  306. package/skills/gsd-path-roadmap/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
  307. package/skills/gsd-path-roadmap/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
  308. package/skills/gsd-path-roadmap/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
  309. package/skills/gsd-path-roadmap/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
  310. package/skills/gsd-path-roadmap/scripts/archive_milestone.py +10 -118
  311. package/skills/gsd-path-roadmap/scripts/build_recovery.py +287 -0
  312. package/skills/gsd-path-roadmap/scripts/check_handoffs.py +1 -1
  313. package/skills/gsd-path-roadmap/scripts/detect_project.py +11 -2
  314. package/skills/gsd-path-roadmap/scripts/integration.py +28 -22
  315. package/skills/gsd-path-roadmap/scripts/isolation.py +9 -1
  316. package/skills/gsd-path-roadmap/scripts/pipeline_state.py +114 -186
  317. package/skills/gsd-path-roadmap/scripts/promote_lookahead.py +30 -162
  318. package/skills/gsd-path-roadmap/scripts/roadmap.py +321 -0
  319. package/skills/gsd-path-roadmap/scripts/state_checkpoint.py +109 -72
  320. package/skills/gsd-path-roadmap/scripts/state_promote.py +42 -44
  321. package/skills/gsd-path-ship/SKILL.md +5 -4
  322. package/skills/gsd-path-ship/references/dispatch.md +6 -0
  323. package/skills/gsd-path-ship/references/reviewer-final.md +21 -0
  324. package/skills/gsd-path-ship/references/reviewer-panel.md +17 -0
  325. package/skills/gsd-path-ship/references/reviewer-skeptic.md +20 -0
  326. package/skills/gsd-path-ship/references/reviewer-wave.md +89 -0
  327. package/skills/gsd-path-ship/references/reviewer.md +12 -150
  328. package/skills/gsd-path-ship/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  329. package/skills/gsd-path-ship/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
  330. package/skills/gsd-path-ship/scripts/__pycache__/build_recovery.cpython-312.pyc +0 -0
  331. package/skills/gsd-path-ship/scripts/__pycache__/build_state.cpython-312.pyc +0 -0
  332. package/skills/gsd-path-ship/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
  333. package/skills/gsd-path-ship/scripts/__pycache__/check_task_briefs.cpython-312.pyc +0 -0
  334. package/skills/gsd-path-ship/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
  335. package/skills/gsd-path-ship/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
  336. package/skills/gsd-path-ship/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
  337. package/skills/gsd-path-ship/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
  338. package/skills/gsd-path-ship/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
  339. package/skills/gsd-path-ship/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
  340. package/skills/gsd-path-ship/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
  341. package/skills/gsd-path-ship/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
  342. package/skills/gsd-path-ship/scripts/archive_milestone.py +10 -118
  343. package/skills/gsd-path-ship/scripts/build_recovery.py +287 -0
  344. package/skills/gsd-path-ship/scripts/check_handoffs.py +1 -1
  345. package/skills/gsd-path-ship/scripts/detect_project.py +1526 -0
  346. package/skills/gsd-path-ship/scripts/integration.py +28 -22
  347. package/skills/gsd-path-ship/scripts/isolation.py +9 -1
  348. package/skills/gsd-path-ship/scripts/lean_verification.py +1 -1
  349. package/skills/gsd-path-ship/scripts/pipeline_state.py +114 -186
  350. package/skills/gsd-path-ship/scripts/promote_lookahead.py +283 -0
  351. package/skills/gsd-path-ship/scripts/roadmap.py +321 -0
  352. package/skills/gsd-path-ship/scripts/state_checkpoint.py +109 -72
  353. package/skills/gsd-path-ship/scripts/state_promote.py +42 -44
  354. package/skills/gsd-path-ship/scripts/workflow_run.py +116 -7
  355. package/skills/gsd-path-ship/templates/wave-review.md +1 -1
  356. package/skills/gsd-path-undo/scripts/pipeline_undo.py +11 -7
  357. package/skills/path/BUILD.md +47 -575
  358. package/skills/path/DECIDE.md +2 -6
  359. package/skills/path/DEFINE.md +10 -14
  360. package/skills/path/INSPECT.md +30 -8
  361. package/skills/path/PLAN.md +25 -17
  362. package/skills/path/RESEARCH.md +3 -6
  363. package/skills/path/ROADMAP.md +22 -58
  364. package/skills/path/SHIP.md +5 -4
  365. package/skills/path/SKILL.md +24 -14
  366. package/skills/path/references/build-abandon.md +33 -0
  367. package/skills/path/references/build-native.md +494 -0
  368. package/skills/path/references/build-recovery.md +78 -0
  369. package/skills/path/references/coder.md +8 -3
  370. package/skills/path/references/dispatch.md +6 -0
  371. package/skills/path/references/docs-auditor.md +14 -0
  372. package/skills/path/references/reviewer-final.md +21 -0
  373. package/skills/path/references/reviewer-panel.md +17 -0
  374. package/skills/path/references/reviewer-skeptic.md +20 -0
  375. package/skills/path/references/reviewer-wave.md +89 -0
  376. package/skills/path/references/reviewer.md +12 -150
  377. package/skills/path/scripts/archive_milestone.py +10 -118
  378. package/skills/path/scripts/bootstrap_repository.py +25 -3
  379. package/skills/path/scripts/build_recovery.py +287 -0
  380. package/skills/path/scripts/check_docs_audit.py +5 -3
  381. package/skills/path/scripts/check_handoffs.py +1 -1
  382. package/skills/path/scripts/detect_project.py +11 -2
  383. package/skills/path/scripts/dispatch_driver.py +4 -2
  384. package/skills/path/scripts/integration.py +28 -22
  385. package/skills/path/scripts/isolation.py +9 -1
  386. package/skills/path/scripts/lean_verification.py +1 -1
  387. package/skills/path/scripts/pipeline_state.py +114 -186
  388. package/skills/path/scripts/promote_lookahead.py +30 -162
  389. package/skills/path/scripts/roadmap.py +321 -0
  390. package/skills/path/scripts/state_checkpoint.py +109 -72
  391. package/skills/path/scripts/state_promote.py +42 -44
  392. package/skills/path/scripts/task_context.py +73 -0
  393. package/skills/path/scripts/token_budget.py +40 -1
  394. package/skills/path/scripts/workflow_run.py +116 -7
  395. package/skills/path/templates/wave-review.md +1 -1
@@ -0,0 +1,283 @@
1
+ #!/usr/bin/env python3
2
+ # gsd-path project runtime
3
+ """Select and compare GSD Path lookahead tracks."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import argparse
8
+ import hashlib
9
+ import json
10
+ import os
11
+ import re
12
+ import stat
13
+ import subprocess
14
+ import sys
15
+ from pathlib import Path
16
+ from typing import Optional, Sequence
17
+
18
+ if __package__:
19
+ from . import _common, archive_milestone, detect_project
20
+ from .roadmap import (
21
+ RoadmapError as LookaheadError,
22
+ NoEligibleMilestone as NoEligibleLookahead,
23
+ roadmap_blocks,
24
+ milestone_block,
25
+ dependency_ids,
26
+ next_eligible_pending,
27
+ selection_payload,
28
+ select_lookahead,
29
+ roadmap_contract,
30
+ compare_roadmap_entry,
31
+ )
32
+ else:
33
+ from roadmap import (
34
+ RoadmapError as LookaheadError,
35
+ NoEligibleMilestone as NoEligibleLookahead,
36
+ roadmap_blocks,
37
+ milestone_block,
38
+ dependency_ids,
39
+ next_eligible_pending,
40
+ selection_payload,
41
+ select_lookahead,
42
+ roadmap_contract,
43
+ compare_roadmap_entry,
44
+ )
45
+ import _common
46
+ import archive_milestone
47
+ import detect_project
48
+
49
+
50
+ PHASES = {"inspect", "define", "research", "decide", "plan"}
51
+ STATUSES = {"active", "blocked", "done"}
52
+ SHA_RE = re.compile(r"^[0-9a-f]{40}$")
53
+
54
+
55
+ def read_text(path: Path, description: str) -> str:
56
+ try:
57
+ return path.read_text(encoding="utf-8")
58
+ except (OSError, UnicodeDecodeError) as error:
59
+ raise LookaheadError(f"cannot read {description} {path}: {error}") from error
60
+
61
+
62
+ def path_mode(path: Path) -> Optional[int]:
63
+ try:
64
+ return path.lstat().st_mode
65
+ except (FileNotFoundError, NotADirectoryError):
66
+ return None
67
+ except OSError as error:
68
+ raise LookaheadError(f"cannot inspect path {path}: {error}") from error
69
+
70
+
71
+ def require_directory(path: Path, description: str) -> None:
72
+ mode = path_mode(path)
73
+ if mode is None or not stat.S_ISDIR(mode):
74
+ raise LookaheadError(f"{description} must be a real directory: {path}")
75
+
76
+
77
+ def require_file(path: Path, description: str) -> None:
78
+ mode = path_mode(path)
79
+ if mode is None or not stat.S_ISREG(mode):
80
+ raise LookaheadError(f"{description} must be a real file: {path}")
81
+
82
+
83
+ atomic_write = _common.atomic_write
84
+
85
+
86
+ def state_fields(content: str) -> dict[str, str]:
87
+ try:
88
+ fields = detect_project.state_frontmatter(content)
89
+ except detect_project.DetectError as error:
90
+ raise LookaheadError(str(error)) from error
91
+ if fields is None:
92
+ raise LookaheadError("STATE.md is missing YAML frontmatter")
93
+ return fields
94
+
95
+
96
+ def select_track_branch(content: str, track_state: str) -> dict:
97
+ fields = state_fields(track_state)
98
+ if fields.get("pipeline", "") != archive_milestone.PIPELINE_MARKER:
99
+ raise LookaheadError("lookahead STATE.md is not owned by gsd-path/v2")
100
+ phase = fields.get("phase", "")
101
+ status = fields.get("status", "")
102
+ milestone = fields.get("milestone", "")
103
+ if phase not in PHASES or status not in STATUSES:
104
+ raise LookaheadError(f"invalid lookahead state: {phase}/{status}")
105
+ if archive_milestone.is_unset(milestone):
106
+ raise LookaheadError("lookahead STATE.md does not name a milestone")
107
+ if not archive_milestone.is_unset(fields.get("branch")):
108
+ raise LookaheadError("lookahead STATE.md must not bind a branch")
109
+ if not archive_milestone.is_unset(fields.get("archive")):
110
+ raise LookaheadError("lookahead STATE.md must not own an archive")
111
+ selected = milestone_block(content, milestone)
112
+ if selected["fields"].get("Status", (-1, ""))[1] != "pending":
113
+ raise LookaheadError(f"lookahead milestone {milestone} is not pending")
114
+ eligible = next_eligible_pending(content)
115
+ if selected["id"] != eligible["id"]:
116
+ raise LookaheadError(
117
+ f"lookahead milestone {milestone} is not the next eligible milestone "
118
+ f"{eligible['id']}"
119
+ )
120
+ return selection_payload(selected)
121
+
122
+
123
+ def snapshot_roadmap(source: Path, destination: Path) -> dict:
124
+ require_file(source, "ROADMAP.md")
125
+ require_directory(destination.parent, "roadmap snapshot parent")
126
+ existing_mode = path_mode(destination)
127
+ if existing_mode is not None:
128
+ require_file(destination, "roadmap baseline snapshot")
129
+ content = read_text(destination, "roadmap baseline snapshot")
130
+ return {
131
+ "status": "existing",
132
+ "sha256": hashlib.sha256(content.encode("utf-8")).hexdigest(),
133
+ }
134
+ content = read_text(source, "ROADMAP.md")
135
+ temporary = destination.parent / f".{destination.name}.gsd-path-tmp"
136
+ try:
137
+ atomic_write(temporary, content)
138
+ try:
139
+ os.link(temporary, destination)
140
+ status = "created"
141
+ except FileExistsError:
142
+ require_file(destination, "roadmap baseline snapshot")
143
+ content = read_text(destination, "roadmap baseline snapshot")
144
+ status = "existing"
145
+ finally:
146
+ if path_mode(temporary) is not None:
147
+ temporary.unlink()
148
+ return {
149
+ "status": status,
150
+ "sha256": hashlib.sha256(content.encode("utf-8")).hexdigest(),
151
+ }
152
+
153
+
154
+ def git_commit_file(root: Path, commit: str, relative: str) -> str:
155
+ require_directory(root, "repository")
156
+ if not SHA_RE.fullmatch(commit):
157
+ raise LookaheadError("base must be a full lowercase commit SHA")
158
+ entry = subprocess.run(
159
+ ["git", "-C", str(root), "ls-tree", "-z", commit, "--", relative],
160
+ capture_output=True,
161
+ check=False,
162
+ )
163
+ metadata, separator, path = entry.stdout.rstrip(b"\0").partition(b"\t")
164
+ fields = metadata.split()
165
+ if (
166
+ entry.returncode != 0
167
+ or not separator
168
+ or path.decode("utf-8", "replace") != relative
169
+ or len(fields) != 3
170
+ or fields[0] not in {b"100644", b"100755"}
171
+ or fields[1] != b"blob"
172
+ ):
173
+ raise LookaheadError(f"{relative} must be a regular file at base {commit}")
174
+ result = subprocess.run(
175
+ ["git", "-C", str(root), "show", f"{commit}:{relative}"],
176
+ capture_output=True,
177
+ check=False,
178
+ )
179
+ if result.returncode != 0:
180
+ detail = result.stderr.decode("utf-8", "replace").strip()
181
+ suffix = f": {detail}" if detail else ""
182
+ raise LookaheadError(f"cannot read {relative} at base {commit}{suffix}")
183
+ try:
184
+ return result.stdout.decode("utf-8")
185
+ except UnicodeDecodeError as error:
186
+ raise LookaheadError(
187
+ f"cannot decode {relative} at base {commit}: {error}"
188
+ ) from error
189
+
190
+
191
+ def select_fetched_base(
192
+ repo: Path,
193
+ base: str,
194
+ remote_default: str,
195
+ use_lookahead: bool,
196
+ ) -> dict:
197
+ root = repo.resolve()
198
+ if not re.fullmatch(r"origin/[A-Za-z0-9][A-Za-z0-9._/-]*", remote_default):
199
+ raise LookaheadError("remote default must be an origin branch")
200
+ if ".." in remote_default or "//" in remote_default:
201
+ raise LookaheadError("remote default is invalid")
202
+ resolved = subprocess.run(
203
+ ["git", "-C", str(root), "rev-parse", "--verify", f"{remote_default}^{{commit}}"],
204
+ capture_output=True,
205
+ text=True,
206
+ check=False,
207
+ )
208
+ if resolved.returncode != 0 or resolved.stdout.strip() != base:
209
+ raise LookaheadError("base does not match the fetched remote default")
210
+ roadmap = git_commit_file(root, base, ".project/ROADMAP.md")
211
+ if use_lookahead:
212
+ track_state = git_commit_file(root, base, ".project/next/STATE.md")
213
+ result = select_track_branch(roadmap, track_state)
214
+ else:
215
+ result = select_lookahead(roadmap)
216
+ return {**result, "base": base}
217
+
218
+
219
+
220
+ def parser() -> argparse.ArgumentParser:
221
+ result = argparse.ArgumentParser(description=__doc__)
222
+ subparsers = result.add_subparsers(dest="command", required=True)
223
+ compare_parser = subparsers.add_parser("compare-entry")
224
+ compare_parser.add_argument("--before", required=True, type=Path)
225
+ compare_parser.add_argument("--after", required=True, type=Path)
226
+ compare_parser.add_argument("--milestone", required=True)
227
+ compare_parser.add_argument("--active-milestone")
228
+ select_parser = subparsers.add_parser("select-next")
229
+ select_parser.add_argument("--roadmap", required=True, type=Path)
230
+ select_parser.add_argument("--active-milestone")
231
+ branch_parser = subparsers.add_parser("select-branch")
232
+ branch_parser.add_argument("--roadmap", required=True, type=Path)
233
+ branch_parser.add_argument("--state", required=True, type=Path)
234
+ base_parser = subparsers.add_parser("select-base")
235
+ base_parser.add_argument("--repo", required=True, type=Path)
236
+ base_parser.add_argument("--base", required=True)
237
+ base_parser.add_argument("--remote-default", required=True)
238
+ base_parser.add_argument("--lookahead", action="store_true")
239
+ snapshot_parser = subparsers.add_parser("snapshot-roadmap")
240
+ snapshot_parser.add_argument("--roadmap", required=True, type=Path)
241
+ snapshot_parser.add_argument("--snapshot", required=True, type=Path)
242
+ return result
243
+
244
+
245
+ def main(argv: Optional[Sequence[str]] = None) -> int:
246
+ arguments = parser().parse_args(argv)
247
+ try:
248
+ if arguments.command == "compare-entry":
249
+ result = compare_roadmap_entry(
250
+ read_text(arguments.before, "previous ROADMAP.md"),
251
+ read_text(arguments.after, "proposed ROADMAP.md"),
252
+ arguments.milestone,
253
+ arguments.active_milestone,
254
+ )
255
+ elif arguments.command == "select-next":
256
+ result = select_lookahead(
257
+ read_text(arguments.roadmap, "ROADMAP.md"),
258
+ arguments.active_milestone,
259
+ )
260
+ elif arguments.command == "select-branch":
261
+ require_file(arguments.state, "lookahead STATE.md")
262
+ result = select_track_branch(
263
+ read_text(arguments.roadmap, "ROADMAP.md"),
264
+ read_text(arguments.state, "lookahead STATE.md"),
265
+ )
266
+ elif arguments.command == "select-base":
267
+ result = select_fetched_base(
268
+ arguments.repo,
269
+ arguments.base,
270
+ arguments.remote_default,
271
+ arguments.lookahead,
272
+ )
273
+ elif arguments.command == "snapshot-roadmap":
274
+ result = snapshot_roadmap(arguments.roadmap, arguments.snapshot)
275
+ except (LookaheadError, OSError) as error:
276
+ print(f"error: {error}", file=sys.stderr)
277
+ return 1
278
+ print(json.dumps(result, sort_keys=True))
279
+ return 0
280
+
281
+
282
+ if __name__ == "__main__":
283
+ raise SystemExit(main())
@@ -0,0 +1,321 @@
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 re
7
+ from typing import Optional
8
+
9
+
10
+ class RoadmapError(RuntimeError):
11
+ pass
12
+
13
+
14
+ class NoEligibleMilestone(RoadmapError):
15
+ pass
16
+
17
+
18
+ ROADMAP_HEADING_RE = re.compile(r"^### (M\d{3,}) — (.+)$")
19
+ STRICT_HEADING_RE = re.compile(r"^### (M\d{3,}) — ([a-z0-9][a-z0-9-]*)\s*$")
20
+ MUTABLE_ROADMAP_FIELDS_RE = re.compile(r"^(Status|Archive|Integrated):")
21
+
22
+ def normalized_slug(value: str) -> str:
23
+ slug = re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-")
24
+ return slug or "project"
25
+
26
+ def roadmap_blocks(content: str) -> list[dict]:
27
+ lines = content.splitlines(keepends=True)
28
+ headings = []
29
+ for index, line in enumerate(lines):
30
+ match = ROADMAP_HEADING_RE.fullmatch(line.rstrip("\r\n"))
31
+ if match:
32
+ headings.append((index, match.group(1), match.group(2).strip()))
33
+ blocks = []
34
+ for position, (start, milestone_id, title) in enumerate(headings):
35
+ end = headings[position + 1][0] if position + 1 < len(headings) else len(lines)
36
+ fields = {}
37
+ for index in range(start + 1, end):
38
+ match = re.match(
39
+ r"^(Status|Archive|Integrated|Depends on):\s*([^#]*?)\s*(?:#.*)?$",
40
+ lines[index].rstrip("\r\n"),
41
+ )
42
+ if match:
43
+ if match.group(1) in fields:
44
+ raise RoadmapError(
45
+ f"roadmap milestone {milestone_id} has duplicate "
46
+ f"{match.group(1)} field"
47
+ )
48
+ fields[match.group(1)] = (index, match.group(2).strip())
49
+ blocks.append(
50
+ {
51
+ "id": milestone_id,
52
+ "title": title,
53
+ "start": start,
54
+ "end": end,
55
+ "fields": fields,
56
+ }
57
+ )
58
+ return blocks
59
+
60
+
61
+ def milestone_block(content: str, milestone: str) -> dict:
62
+ normalized = normalized_slug(milestone)
63
+ matches = [
64
+ block
65
+ for block in roadmap_blocks(content)
66
+ if normalized_slug(block["title"]) == normalized
67
+ ]
68
+ if len(matches) != 1:
69
+ raise RoadmapError(f"ROADMAP.md must contain exactly one entry for {milestone}")
70
+ return matches[0]
71
+
72
+
73
+ def dependency_ids(block: dict) -> list[str]:
74
+ dependencies = block["fields"].get("Depends on")
75
+ if dependencies is None:
76
+ raise RoadmapError(f"roadmap milestone {block['id']} lacks dependencies")
77
+ dependency_list = re.fullmatch(
78
+ r"\[\s*(M\d{3,}(?:\s*,\s*M\d{3,})*)?\s*\]",
79
+ dependencies[1],
80
+ )
81
+ if dependency_list is None:
82
+ raise RoadmapError(f"roadmap milestone {block['id']} has invalid dependencies")
83
+ return re.findall(r"M\d{3,}", dependency_list.group(1) or "")
84
+
85
+
86
+ def next_eligible_pending(content: str, active_milestone: Optional[str] = None) -> dict:
87
+ blocks = roadmap_blocks(content)
88
+ blocks_by_id = {block["id"]: block for block in blocks}
89
+ if len(blocks_by_id) != len(blocks):
90
+ raise RoadmapError("ROADMAP.md milestone ids must be unique")
91
+ active_id = None
92
+ if active_milestone is not None:
93
+ active = milestone_block(content, active_milestone)
94
+ if active["fields"].get("Status", (-1, ""))[1] != "active":
95
+ raise RoadmapError(f"roadmap milestone {active_milestone} is not active")
96
+ active_id = active["id"]
97
+ for block in blocks:
98
+ if block["fields"].get("Status", (-1, ""))[1] != "pending":
99
+ continue
100
+ dependencies = dependency_ids(block)
101
+ if all(
102
+ dependency in blocks_by_id
103
+ and (
104
+ blocks_by_id[dependency]["fields"].get("Status", (-1, ""))[1]
105
+ == "shipped"
106
+ or dependency == active_id
107
+ )
108
+ for dependency in dependencies
109
+ ):
110
+ return block
111
+ raise NoEligibleMilestone("ROADMAP.md has no dependency-ready pending milestone")
112
+
113
+
114
+ def selection_payload(selected: dict) -> dict:
115
+ milestone = normalized_slug(selected["title"])
116
+ return {
117
+ "status": "selected",
118
+ "id": selected["id"],
119
+ "milestone": milestone,
120
+ "branch": f"gsd-path/{selected['id']}",
121
+ }
122
+
123
+
124
+ def select_lookahead(content: str, active_milestone: Optional[str] = None) -> dict:
125
+ try:
126
+ selected = next_eligible_pending(content, active_milestone)
127
+ except NoEligibleMilestone:
128
+ blocks = roadmap_blocks(content)
129
+ if blocks and all(
130
+ block["fields"].get("Status", (-1, ""))[1]
131
+ in {"shipped", "abandoned"}
132
+ for block in blocks
133
+ ):
134
+ return {"status": "complete"}
135
+ return {"status": "none"}
136
+ return selection_payload(selected)
137
+
138
+
139
+ def roadmap_contract(content: str, milestone: str) -> tuple[str, ...]:
140
+ block = milestone_block(content, milestone)
141
+ lines = content.splitlines()
142
+ return tuple(
143
+ line.rstrip()
144
+ for line in lines[block["start"] : block["end"]]
145
+ if not MUTABLE_ROADMAP_FIELDS_RE.match(line)
146
+ )
147
+
148
+
149
+ def compare_roadmap_entry(
150
+ before: str,
151
+ after: str,
152
+ milestone: str,
153
+ active_milestone: Optional[str] = None,
154
+ ) -> dict:
155
+ before_contract = roadmap_contract(before, milestone)
156
+ try:
157
+ after_contract = roadmap_contract(after, milestone)
158
+ selected = milestone_block(after, milestone)
159
+ eligible = next_eligible_pending(after, active_milestone)
160
+ except RoadmapError:
161
+ after_contract = ()
162
+ selected = None
163
+ eligible = None
164
+ status = (
165
+ "unchanged"
166
+ if before_contract == after_contract
167
+ and selected is not None
168
+ and eligible is not None
169
+ and selected["id"] == eligible["id"]
170
+ else "changed"
171
+ )
172
+ return {"status": status, "milestone": milestone}
173
+
174
+
175
+ def strict_sections(text: str) -> dict[str, tuple[str, int, int]]:
176
+ lines = text.splitlines(keepends=True)
177
+ headings: list[tuple[str, str, int]] = []
178
+ for index, line in enumerate(lines):
179
+ match = STRICT_HEADING_RE.fullmatch(line.rstrip("\r\n"))
180
+ if match:
181
+ if int(match.group(1).removeprefix("M")) < 1:
182
+ raise RoadmapError(f"ROADMAP.md has invalid milestone id: {match.group(1)}")
183
+ headings.append((match.group(1), match.group(2), index))
184
+ sections: dict[str, tuple[str, int, int]] = {}
185
+ for position, (milestone_id, slug, start) in enumerate(headings):
186
+ end = headings[position + 1][2] if position + 1 < len(headings) else len(lines)
187
+ if slug in sections:
188
+ raise RoadmapError(f"ROADMAP.md repeats milestone slug: {slug}")
189
+ sections[slug] = (milestone_id, start, end)
190
+ return sections
191
+
192
+
193
+ def replace_field(
194
+ lines: list[str],
195
+ start: int,
196
+ end: int,
197
+ field: str,
198
+ expected: set[str],
199
+ value: str,
200
+ ) -> None:
201
+ found: Optional[int] = None
202
+ for index in range(start, end):
203
+ raw = lines[index].rstrip("\r\n")
204
+ ending = lines[index][len(raw) :]
205
+ match = re.match(rf"^({re.escape(field)}:\s*)(\S+)(.*)$", raw)
206
+ if match is None:
207
+ continue
208
+ if found is not None:
209
+ raise RoadmapError(f"ROADMAP.md repeats {field} in one milestone")
210
+ if match.group(2) not in expected:
211
+ raise RoadmapError(
212
+ f"ROADMAP.md {field} is {match.group(2)}, expected {sorted(expected)}"
213
+ )
214
+ lines[index] = f"{match.group(1)}{value}{match.group(3)}{ending}"
215
+ found = index
216
+ if found is None:
217
+ raise RoadmapError(f"ROADMAP.md milestone is missing {field}")
218
+
219
+
220
+ def milestone_status(lines: list[str], start: int, end: int) -> str:
221
+ values = []
222
+ for line in lines[start:end]:
223
+ match = re.fullmatch(r"Status:\s*(\S+)\s*", line.rstrip("\r\n"))
224
+ if match:
225
+ values.append(match.group(1))
226
+ if len(values) != 1:
227
+ raise RoadmapError("ROADMAP.md milestone must have one Status field")
228
+ return values[0]
229
+
230
+
231
+ def activate_milestone(text: str, milestone: str) -> str:
232
+ sections = strict_sections(text)
233
+ if milestone not in sections:
234
+ raise RoadmapError(f"ROADMAP.md is missing selected milestone: {milestone}")
235
+ lines = text.splitlines(keepends=True)
236
+ active = [
237
+ slug
238
+ for slug, (_, start, end) in sections.items()
239
+ if milestone_status(lines, start, end) == "active"
240
+ ]
241
+ if active:
242
+ raise RoadmapError(
243
+ "ROADMAP.md already has an active milestone: " + ", ".join(active)
244
+ )
245
+ _, start, end = sections[milestone]
246
+ replace_field(lines, start, end, "Status", {"pending"}, "active")
247
+ return "".join(lines)
248
+
249
+
250
+ def reslice(before: str, candidate: str, phase: str, selected: str) -> str:
251
+ old_blocks = roadmap_blocks(before)
252
+ before_lines = before.splitlines(keepends=True)
253
+ candidate_lines = candidate.splitlines(keepends=True)
254
+ for block in old_blocks:
255
+ status = block["fields"].get("Status", (-1, ""))[1]
256
+ if status not in {"active", "shipped", "abandoned"}:
257
+ continue
258
+ slug = normalized_slug(block["title"])
259
+ new = milestone_block(candidate, slug)
260
+ if status == "shipped":
261
+ changed = roadmap_contract(before, slug) != roadmap_contract(candidate, slug)
262
+ else:
263
+ changed = before_lines[block["start"]:block["end"]] != candidate_lines[new["start"]:new["end"]]
264
+ if changed:
265
+ raise RoadmapError(f"{status} roadmap entry changed: {slug}")
266
+ if phase in {"inspect", "define"}:
267
+ active = [b for b in roadmap_blocks(candidate) if b["fields"].get("Status", (-1, ""))[1] == "active"]
268
+ if len(active) != 1 or selection_payload(active[0])["milestone"] != selected:
269
+ raise RoadmapError("roadmap re-slice must retain the active entry")
270
+ return candidate
271
+ if not any(b["fields"].get("Status", (-1, ""))[1] == "abandoned" for b in old_blocks):
272
+ raise RoadmapError("post-abandon re-slice requires an abandoned milestone")
273
+ selection = select_lookahead(candidate)
274
+ if selection.get("milestone") != selected:
275
+ raise RoadmapError("roadmap re-slice must use the selected dependency-ready milestone")
276
+ return activate_milestone(candidate, selected)
277
+
278
+
279
+ def has_questions(text: str, milestone: Optional[str] = None) -> bool:
280
+ """Report open questions for the active entry, or for ``milestone`` when given."""
281
+ lines = text.splitlines()
282
+ active_section: Optional[list[str]] = None
283
+ for index, line in enumerate(lines):
284
+ heading = STRICT_HEADING_RE.fullmatch(line)
285
+ if heading is None:
286
+ continue
287
+ if int(heading.group(1).removeprefix("M")) < 1:
288
+ raise RoadmapError(
289
+ f"ROADMAP.md has invalid milestone id: {heading.group(1)}"
290
+ )
291
+ end = next(
292
+ (
293
+ cursor
294
+ for cursor in range(index + 1, len(lines))
295
+ if STRICT_HEADING_RE.fullmatch(lines[cursor]) is not None
296
+ ),
297
+ len(lines),
298
+ )
299
+ section = lines[index:end]
300
+ if milestone is not None:
301
+ if heading.group(2) == milestone:
302
+ active_section = section
303
+ continue
304
+ if any(re.fullmatch(r"Status:\s*active(?:\s+#.*)?", item) for item in section):
305
+ if active_section is not None:
306
+ raise RoadmapError("ROADMAP.md has more than one active milestone")
307
+ active_section = section
308
+ if active_section is None:
309
+ if milestone is not None:
310
+ raise RoadmapError(f"ROADMAP.md is missing lookahead milestone: {milestone}")
311
+ raise RoadmapError("ROADMAP.md has no active milestone")
312
+ try:
313
+ start = active_section.index("Open questions") + 1
314
+ except ValueError as error:
315
+ raise RoadmapError("active roadmap milestone has no Open questions") from error
316
+ questions = [
317
+ line[2:].strip()
318
+ for line in active_section[start:]
319
+ if line.startswith("- ")
320
+ ]
321
+ return any(question.lower() != "none" for question in questions)