@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
@@ -0,0 +1,286 @@
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
+
16
+ # Runtime helpers must not modify their immutable installation.
17
+ sys.dont_write_bytecode = True
18
+ from pathlib import Path
19
+ from typing import Optional, Sequence
20
+
21
+ if __package__:
22
+ from . import _common, archive_milestone, detect_project
23
+ from .roadmap import (
24
+ RoadmapError as LookaheadError,
25
+ NoEligibleMilestone as NoEligibleLookahead,
26
+ roadmap_blocks,
27
+ milestone_block,
28
+ dependency_ids,
29
+ next_eligible_pending,
30
+ selection_payload,
31
+ select_lookahead,
32
+ roadmap_contract,
33
+ compare_roadmap_entry,
34
+ )
35
+ else:
36
+ from roadmap import (
37
+ RoadmapError as LookaheadError,
38
+ NoEligibleMilestone as NoEligibleLookahead,
39
+ roadmap_blocks,
40
+ milestone_block,
41
+ dependency_ids,
42
+ next_eligible_pending,
43
+ selection_payload,
44
+ select_lookahead,
45
+ roadmap_contract,
46
+ compare_roadmap_entry,
47
+ )
48
+ import _common
49
+ import archive_milestone
50
+ import detect_project
51
+
52
+
53
+ PHASES = {"inspect", "define", "research", "decide", "plan"}
54
+ STATUSES = {"active", "blocked", "done"}
55
+ SHA_RE = re.compile(r"^[0-9a-f]{40}$")
56
+
57
+
58
+ def read_text(path: Path, description: str) -> str:
59
+ try:
60
+ return path.read_text(encoding="utf-8")
61
+ except (OSError, UnicodeDecodeError) as error:
62
+ raise LookaheadError(f"cannot read {description} {path}: {error}") from error
63
+
64
+
65
+ def path_mode(path: Path) -> Optional[int]:
66
+ try:
67
+ return path.lstat().st_mode
68
+ except (FileNotFoundError, NotADirectoryError):
69
+ return None
70
+ except OSError as error:
71
+ raise LookaheadError(f"cannot inspect path {path}: {error}") from error
72
+
73
+
74
+ def require_directory(path: Path, description: str) -> None:
75
+ mode = path_mode(path)
76
+ if mode is None or not stat.S_ISDIR(mode):
77
+ raise LookaheadError(f"{description} must be a real directory: {path}")
78
+
79
+
80
+ def require_file(path: Path, description: str) -> None:
81
+ mode = path_mode(path)
82
+ if mode is None or not stat.S_ISREG(mode):
83
+ raise LookaheadError(f"{description} must be a real file: {path}")
84
+
85
+
86
+ atomic_write = _common.atomic_write
87
+
88
+
89
+ def state_fields(content: str) -> dict[str, str]:
90
+ try:
91
+ fields = detect_project.state_frontmatter(content)
92
+ except detect_project.DetectError as error:
93
+ raise LookaheadError(str(error)) from error
94
+ if fields is None:
95
+ raise LookaheadError("STATE.md is missing YAML frontmatter")
96
+ return fields
97
+
98
+
99
+ def select_track_branch(content: str, track_state: str) -> dict:
100
+ fields = state_fields(track_state)
101
+ if fields.get("pipeline", "") != archive_milestone.PIPELINE_MARKER:
102
+ raise LookaheadError("lookahead STATE.md is not owned by gsd-path/v2")
103
+ phase = fields.get("phase", "")
104
+ status = fields.get("status", "")
105
+ milestone = fields.get("milestone", "")
106
+ if phase not in PHASES or status not in STATUSES:
107
+ raise LookaheadError(f"invalid lookahead state: {phase}/{status}")
108
+ if archive_milestone.is_unset(milestone):
109
+ raise LookaheadError("lookahead STATE.md does not name a milestone")
110
+ if not archive_milestone.is_unset(fields.get("branch")):
111
+ raise LookaheadError("lookahead STATE.md must not bind a branch")
112
+ if not archive_milestone.is_unset(fields.get("archive")):
113
+ raise LookaheadError("lookahead STATE.md must not own an archive")
114
+ selected = milestone_block(content, milestone)
115
+ if selected["fields"].get("Status", (-1, ""))[1] != "pending":
116
+ raise LookaheadError(f"lookahead milestone {milestone} is not pending")
117
+ eligible = next_eligible_pending(content)
118
+ if selected["id"] != eligible["id"]:
119
+ raise LookaheadError(
120
+ f"lookahead milestone {milestone} is not the next eligible milestone "
121
+ f"{eligible['id']}"
122
+ )
123
+ return selection_payload(selected)
124
+
125
+
126
+ def snapshot_roadmap(source: Path, destination: Path) -> dict:
127
+ require_file(source, "ROADMAP.md")
128
+ require_directory(destination.parent, "roadmap snapshot parent")
129
+ existing_mode = path_mode(destination)
130
+ if existing_mode is not None:
131
+ require_file(destination, "roadmap baseline snapshot")
132
+ content = read_text(destination, "roadmap baseline snapshot")
133
+ return {
134
+ "status": "existing",
135
+ "sha256": hashlib.sha256(content.encode("utf-8")).hexdigest(),
136
+ }
137
+ content = read_text(source, "ROADMAP.md")
138
+ temporary = destination.parent / f".{destination.name}.gsd-path-tmp"
139
+ try:
140
+ atomic_write(temporary, content)
141
+ try:
142
+ os.link(temporary, destination)
143
+ status = "created"
144
+ except FileExistsError:
145
+ require_file(destination, "roadmap baseline snapshot")
146
+ content = read_text(destination, "roadmap baseline snapshot")
147
+ status = "existing"
148
+ finally:
149
+ if path_mode(temporary) is not None:
150
+ temporary.unlink()
151
+ return {
152
+ "status": status,
153
+ "sha256": hashlib.sha256(content.encode("utf-8")).hexdigest(),
154
+ }
155
+
156
+
157
+ def git_commit_file(root: Path, commit: str, relative: str) -> str:
158
+ require_directory(root, "repository")
159
+ if not SHA_RE.fullmatch(commit):
160
+ raise LookaheadError("base must be a full lowercase commit SHA")
161
+ entry = subprocess.run(
162
+ ["git", "-C", str(root), "ls-tree", "-z", commit, "--", relative],
163
+ capture_output=True,
164
+ check=False,
165
+ )
166
+ metadata, separator, path = entry.stdout.rstrip(b"\0").partition(b"\t")
167
+ fields = metadata.split()
168
+ if (
169
+ entry.returncode != 0
170
+ or not separator
171
+ or path.decode("utf-8", "replace") != relative
172
+ or len(fields) != 3
173
+ or fields[0] not in {b"100644", b"100755"}
174
+ or fields[1] != b"blob"
175
+ ):
176
+ raise LookaheadError(f"{relative} must be a regular file at base {commit}")
177
+ result = subprocess.run(
178
+ ["git", "-C", str(root), "show", f"{commit}:{relative}"],
179
+ capture_output=True,
180
+ check=False,
181
+ )
182
+ if result.returncode != 0:
183
+ detail = result.stderr.decode("utf-8", "replace").strip()
184
+ suffix = f": {detail}" if detail else ""
185
+ raise LookaheadError(f"cannot read {relative} at base {commit}{suffix}")
186
+ try:
187
+ return result.stdout.decode("utf-8")
188
+ except UnicodeDecodeError as error:
189
+ raise LookaheadError(
190
+ f"cannot decode {relative} at base {commit}: {error}"
191
+ ) from error
192
+
193
+
194
+ def select_fetched_base(
195
+ repo: Path,
196
+ base: str,
197
+ remote_default: str,
198
+ use_lookahead: bool,
199
+ ) -> dict:
200
+ root = repo.resolve()
201
+ if not re.fullmatch(r"origin/[A-Za-z0-9][A-Za-z0-9._/-]*", remote_default):
202
+ raise LookaheadError("remote default must be an origin branch")
203
+ if ".." in remote_default or "//" in remote_default:
204
+ raise LookaheadError("remote default is invalid")
205
+ resolved = subprocess.run(
206
+ ["git", "-C", str(root), "rev-parse", "--verify", f"{remote_default}^{{commit}}"],
207
+ capture_output=True,
208
+ text=True,
209
+ check=False,
210
+ )
211
+ if resolved.returncode != 0 or resolved.stdout.strip() != base:
212
+ raise LookaheadError("base does not match the fetched remote default")
213
+ roadmap = git_commit_file(root, base, ".project/ROADMAP.md")
214
+ if use_lookahead:
215
+ track_state = git_commit_file(root, base, ".project/next/STATE.md")
216
+ result = select_track_branch(roadmap, track_state)
217
+ else:
218
+ result = select_lookahead(roadmap)
219
+ return {**result, "base": base}
220
+
221
+
222
+
223
+ def parser() -> argparse.ArgumentParser:
224
+ result = argparse.ArgumentParser(description=__doc__)
225
+ subparsers = result.add_subparsers(dest="command", required=True)
226
+ compare_parser = subparsers.add_parser("compare-entry")
227
+ compare_parser.add_argument("--before", required=True, type=Path)
228
+ compare_parser.add_argument("--after", required=True, type=Path)
229
+ compare_parser.add_argument("--milestone", required=True)
230
+ compare_parser.add_argument("--active-milestone")
231
+ select_parser = subparsers.add_parser("select-next")
232
+ select_parser.add_argument("--roadmap", required=True, type=Path)
233
+ select_parser.add_argument("--active-milestone")
234
+ branch_parser = subparsers.add_parser("select-branch")
235
+ branch_parser.add_argument("--roadmap", required=True, type=Path)
236
+ branch_parser.add_argument("--state", required=True, type=Path)
237
+ base_parser = subparsers.add_parser("select-base")
238
+ base_parser.add_argument("--repo", required=True, type=Path)
239
+ base_parser.add_argument("--base", required=True)
240
+ base_parser.add_argument("--remote-default", required=True)
241
+ base_parser.add_argument("--lookahead", action="store_true")
242
+ snapshot_parser = subparsers.add_parser("snapshot-roadmap")
243
+ snapshot_parser.add_argument("--roadmap", required=True, type=Path)
244
+ snapshot_parser.add_argument("--snapshot", required=True, type=Path)
245
+ return result
246
+
247
+
248
+ def main(argv: Optional[Sequence[str]] = None) -> int:
249
+ arguments = parser().parse_args(argv)
250
+ try:
251
+ if arguments.command == "compare-entry":
252
+ result = compare_roadmap_entry(
253
+ read_text(arguments.before, "previous ROADMAP.md"),
254
+ read_text(arguments.after, "proposed ROADMAP.md"),
255
+ arguments.milestone,
256
+ arguments.active_milestone,
257
+ )
258
+ elif arguments.command == "select-next":
259
+ result = select_lookahead(
260
+ read_text(arguments.roadmap, "ROADMAP.md"),
261
+ arguments.active_milestone,
262
+ )
263
+ elif arguments.command == "select-branch":
264
+ require_file(arguments.state, "lookahead STATE.md")
265
+ result = select_track_branch(
266
+ read_text(arguments.roadmap, "ROADMAP.md"),
267
+ read_text(arguments.state, "lookahead STATE.md"),
268
+ )
269
+ elif arguments.command == "select-base":
270
+ result = select_fetched_base(
271
+ arguments.repo,
272
+ arguments.base,
273
+ arguments.remote_default,
274
+ arguments.lookahead,
275
+ )
276
+ elif arguments.command == "snapshot-roadmap":
277
+ result = snapshot_roadmap(arguments.roadmap, arguments.snapshot)
278
+ except (LookaheadError, OSError) as error:
279
+ print(f"error: {error}", file=sys.stderr)
280
+ return 1
281
+ print(json.dumps(result, sort_keys=True))
282
+ return 0
283
+
284
+
285
+ if __name__ == "__main__":
286
+ raise SystemExit(main())
@@ -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)