@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
@@ -16,6 +16,9 @@ import re
16
16
  import shutil
17
17
  import subprocess
18
18
  import sys
19
+
20
+ # Runtime helpers must not modify their immutable installation.
21
+ sys.dont_write_bytecode = True
19
22
  from contextlib import contextmanager
20
23
  from datetime import date
21
24
  from pathlib import Path, PurePosixPath
@@ -239,15 +242,10 @@ class ArchiveError(RuntimeError):
239
242
 
240
243
 
241
244
  PIPELINE_MARKER = _common.PIPELINE_MARKER
242
- PR_CREDIT_LINE = (
243
- "PR prepared with [GSD Path](https://github.com/open-gsd/gsd-path)."
244
- )
245
- GITHUB_HOST = "github.com"
246
245
 
247
246
 
248
- # discussion_validate and integration call back into this module through a
249
- # module reference and import the constants and ArchiveError above, so this
250
- # import must stay below those definitions.
247
+ # discussion_validate calls back into this module and imports its constants
248
+ # and ArchiveError, so this import must stay below those definitions.
251
249
  if __package__: # imported as scripts.archive_milestone
252
250
  from .discussion_validate import (
253
251
  record_sections,
@@ -280,42 +278,6 @@ if __package__: # imported as scripts.archive_milestone
280
278
  render_manifest,
281
279
  validate_manifest,
282
280
  )
283
- from .integration import (
284
- resolve_remote_default,
285
- live_remote_ref,
286
- publish_bound_branch,
287
- refresh_origin,
288
- optional_ref,
289
- integration_names,
290
- registered_worktree,
291
- remove_registered_worktree,
292
- delete_integration_branch,
293
- require_generated_integration_commit,
294
- discard_unpublished_stale_integration,
295
- create_integration_merge,
296
- require_annotated_tag,
297
- ensure_integration_tag,
298
- github_repository,
299
- require_github_authentication,
300
- github_api_json,
301
- require_pull_request_shape,
302
- require_pull_request_pages,
303
- pull_request_head_matches_repository,
304
- require_pull_request_identity,
305
- find_pull_request,
306
- pull_request_body,
307
- update_pull_request_body,
308
- create_pull_request,
309
- pull_request_tag_message,
310
- pull_request_tag_metadata,
311
- require_pull_request_merge,
312
- require_pull_request_merge_provenance,
313
- integrate_pull_request,
314
- integrate,
315
- find_integrate_commit,
316
- validate_integrated,
317
- require_published_integration,
318
- )
319
281
  else: # standalone script or sibling import
320
282
  if __name__ == "__main__":
321
283
  sys.modules.setdefault("archive_milestone", sys.modules[__name__])
@@ -351,42 +313,6 @@ else: # standalone script or sibling import
351
313
  render_manifest,
352
314
  validate_manifest,
353
315
  )
354
- from integration import (
355
- resolve_remote_default,
356
- live_remote_ref,
357
- publish_bound_branch,
358
- refresh_origin,
359
- optional_ref,
360
- integration_names,
361
- registered_worktree,
362
- remove_registered_worktree,
363
- delete_integration_branch,
364
- require_generated_integration_commit,
365
- discard_unpublished_stale_integration,
366
- create_integration_merge,
367
- require_annotated_tag,
368
- ensure_integration_tag,
369
- github_repository,
370
- require_github_authentication,
371
- github_api_json,
372
- require_pull_request_shape,
373
- require_pull_request_pages,
374
- pull_request_head_matches_repository,
375
- require_pull_request_identity,
376
- find_pull_request,
377
- pull_request_body,
378
- update_pull_request_body,
379
- create_pull_request,
380
- pull_request_tag_message,
381
- pull_request_tag_metadata,
382
- require_pull_request_merge,
383
- require_pull_request_merge_provenance,
384
- integrate_pull_request,
385
- integrate,
386
- find_integrate_commit,
387
- validate_integrated,
388
- require_published_integration,
389
- )
390
316
  except ImportError: # pragma: no cover - package import used by tests
391
317
  from scripts.discussion_validate import (
392
318
  record_sections,
@@ -419,42 +345,6 @@ else: # standalone script or sibling import
419
345
  render_manifest,
420
346
  validate_manifest,
421
347
  )
422
- from scripts.integration import (
423
- resolve_remote_default,
424
- live_remote_ref,
425
- publish_bound_branch,
426
- refresh_origin,
427
- optional_ref,
428
- integration_names,
429
- registered_worktree,
430
- remove_registered_worktree,
431
- delete_integration_branch,
432
- require_generated_integration_commit,
433
- discard_unpublished_stale_integration,
434
- create_integration_merge,
435
- require_annotated_tag,
436
- ensure_integration_tag,
437
- github_repository,
438
- require_github_authentication,
439
- github_api_json,
440
- require_pull_request_shape,
441
- require_pull_request_pages,
442
- pull_request_head_matches_repository,
443
- require_pull_request_identity,
444
- find_pull_request,
445
- pull_request_body,
446
- update_pull_request_body,
447
- create_pull_request,
448
- pull_request_tag_message,
449
- pull_request_tag_metadata,
450
- require_pull_request_merge,
451
- require_pull_request_merge_provenance,
452
- integrate_pull_request,
453
- integrate,
454
- find_integrate_commit,
455
- validate_integrated,
456
- require_published_integration,
457
- )
458
348
 
459
349
 
460
350
  @contextmanager
@@ -1856,6 +1746,12 @@ def require_clean_active_root(active_root: Path, archive: Path) -> None:
1856
1746
  carried_forward = pending_ruling_count(archived_audit)
1857
1747
  # Persistent project metadata ships but never archives with a milestone.
1858
1748
  allowed = {"STATE.md", "REPOSITORY.md", "archive", "LESSONS.md", "next"}
1749
+ for name in ("config.json", "model-policy.json"):
1750
+ path = active_root / name
1751
+ if path.exists() or path.is_symlink():
1752
+ if path.is_symlink() or not path.is_file() or path.stat().st_nlink != 1:
1753
+ raise ArchiveError(f"project settings must be a regular unlinked file: {path}")
1754
+ allowed.add(name)
1859
1755
  if (active_root / "CHARTER.md").is_file():
1860
1756
  # Program flow: these persist at the .project/ top level.
1861
1757
  allowed |= {"CHARTER.md", "ROADMAP.md", "SYNTHESIS.md"}
@@ -1921,6 +1817,7 @@ def prepared_transaction(
1921
1817
  repo: Path,
1922
1818
  phases: Sequence[str],
1923
1819
  must_be_uncommitted: bool = False,
1820
+ *, historical: bool = False,
1924
1821
  ) -> tuple:
1925
1822
  project = repo.resolve()
1926
1823
  active_root = require_project_layout(project)
@@ -1929,7 +1826,7 @@ def prepared_transaction(
1929
1826
  state, _, loaded_state_path = strict_state(project)
1930
1827
  if loaded_state_path.resolve() != state_path.resolve():
1931
1828
  raise ArchiveError("strict STATE.md loader returned an unexpected path")
1932
- require_transaction_context(project, state, phases)
1829
+ require_transaction_context(project, state, phases, historical=historical)
1933
1830
 
1934
1831
  configured = state.archive
1935
1832
  if configured is None:
@@ -1993,10 +1890,11 @@ def require_canonical_commit_body(
1993
1890
  raise ArchiveError(f"{label} commit body does not match required fields")
1994
1891
 
1995
1892
 
1996
- def find_ship_commit(project: Path, archive_name: str) -> str:
1893
+ def find_ship_commit(project: Path, archive_name: str, *, historical: bool = False) -> str:
1997
1894
  expected_subject = ship_subject(archive_name)
1998
1895
  log = require_git_success(
1999
- run_git(project, "log", "--first-parent", "--format=%H%x00%s", "HEAD"),
1896
+ run_git(project, "log", *([] if historical else ["--first-parent"]),
1897
+ "--format=%H%x00%s", "HEAD"),
2000
1898
  "inspect HEAD history for the ship commit",
2001
1899
  )
2002
1900
  matches = []
@@ -2039,19 +1937,22 @@ def validate_shipped_roadmap(text: str, state: PipelineState, archive: str) -> N
2039
1937
  raise ArchiveError("ROADMAP.md must record Status: shipped and the exact Archive")
2040
1938
 
2041
1939
 
2042
- def validate(repo: Path) -> dict:
1940
+ def validate(repo: Path, *, historical: bool = False) -> dict:
2043
1941
  project, _, state, configured, archive, archived_files, reviewed_head = prepared_transaction(
2044
1942
  repo,
2045
1943
  ("shipped",),
1944
+ historical=historical,
2046
1945
  )
2047
1946
  if state.status != "done":
2048
1947
  raise ArchiveError("STATE.md is not shipped/done")
2049
1948
 
2050
- project_status = require_git_success(run_git(project, "status", "--porcelain"), "inspect worktree status")
1949
+ project_status = require_git_success(
1950
+ run_git(project, "status", "--porcelain", *(["--", ".project"] if historical else [])),
1951
+ "inspect worktree status")
2051
1952
  if project_status:
2052
1953
  raise ArchiveError("ship transaction worktree is not clean")
2053
1954
 
2054
- ship_commit = find_ship_commit(project, archive.name)
1955
+ ship_commit = find_ship_commit(project, archive.name, historical=historical)
2055
1956
 
2056
1957
  parents = require_git_success(
2057
1958
  run_git(project, "rev-list", "--parents", "-n", "1", ship_commit),
@@ -2238,6 +2139,11 @@ def parser() -> argparse.ArgumentParser:
2238
2139
  def main(argv: Optional[Sequence[str]] = None) -> int:
2239
2140
  arguments = parser().parse_args(argv)
2240
2141
  try:
2142
+ if arguments.command in {"validate-integrated", "refresh-origin", "integrate"}:
2143
+ if __package__:
2144
+ from . import integration
2145
+ else:
2146
+ import integration
2241
2147
  if arguments.command == "prepare":
2242
2148
  result = prepare(arguments.repo, arguments.slug)
2243
2149
  elif arguments.command == "render-manifest":
@@ -2247,11 +2153,11 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
2247
2153
  elif arguments.command == "abandon":
2248
2154
  result = abandon(arguments.repo, arguments.slug, arguments.reason)
2249
2155
  elif arguments.command == "validate-integrated":
2250
- result = validate_integrated(arguments.repo, arguments.slug)
2156
+ result = integration.validate_integrated(arguments.repo, arguments.slug)
2251
2157
  elif arguments.command == "refresh-origin":
2252
- result = refresh_origin(arguments.repo)
2158
+ result = integration.refresh_origin(arguments.repo)
2253
2159
  elif arguments.command == "integrate":
2254
- result = integrate(arguments.repo, arguments.slug)
2160
+ result = integration.integrate(arguments.repo, arguments.slug)
2255
2161
  else:
2256
2162
  result = validate(arguments.repo)
2257
2163
  except (ArchiveError, OSError) as error:
@@ -0,0 +1,292 @@
1
+ # gsd-path project runtime
2
+ """Guard a blocked build's return to Define or Plan using its committed base."""
3
+
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 json
12
+ import re
13
+ from pathlib import Path
14
+
15
+
16
+ EVENTS = {
17
+ "define": "build intent corrections requested",
18
+ "plan": "build plan repair requested",
19
+ }
20
+ MARKER = "build recovery: "
21
+
22
+
23
+ def runtime():
24
+ if __package__:
25
+ from scripts import pipeline_state
26
+ else:
27
+ import pipeline_state
28
+ return pipeline_state
29
+
30
+
31
+ def checkpoint_runtime():
32
+ if __package__:
33
+ from scripts import state_checkpoint
34
+ else:
35
+ import state_checkpoint
36
+ return state_checkpoint
37
+
38
+
39
+ def context(repo: Path, text: str | None = None) -> dict | None:
40
+ state = runtime()
41
+ current, loaded, _ = state.load_state(repo)
42
+ text = loaded if text is None else text
43
+ recovery = None
44
+ for line in text.splitlines():
45
+ if " — " not in line:
46
+ continue
47
+ event = line.split(" — ", 2)[-1]
48
+ if event.startswith(MARKER):
49
+ try:
50
+ value = json.loads(event[len(MARKER):])
51
+ except ValueError as error:
52
+ raise state.PipelineStateError("invalid build recovery record") from error
53
+ if (not isinstance(value, dict) or set(value) != {"base", "branch", "kind"}
54
+ or value["kind"] not in EVENTS
55
+ or not isinstance(value["base"], str)
56
+ or not re.fullmatch(r"[0-9a-f]{40}", value["base"])):
57
+ raise state.PipelineStateError("invalid build recovery record")
58
+ recovery = {**value, "active": True} if value["branch"] == current.branch else None
59
+ elif event == "build started" and recovery:
60
+ recovery["active"] = False
61
+ if recovery:
62
+ if not state._is_ancestor(repo, recovery["base"], "HEAD"):
63
+ raise state.PipelineStateError("build recovery base is not an ancestor of HEAD")
64
+ source = checkpoint_runtime()._git_text_at(repo, recovery["base"], ".project/STATE.md")
65
+ if source is None:
66
+ raise state.PipelineStateError("build recovery base has no STATE.md")
67
+ before = state._state_from_text(source)
68
+ if ((before.phase, before.status) != ("build", "blocked")
69
+ or before.branch != current.branch or before.milestone != current.milestone):
70
+ raise state.PipelineStateError("build recovery base does not prove this blocked milestone")
71
+ return recovery
72
+
73
+
74
+ def task_fields(text: str, label: str) -> dict:
75
+ if __package__:
76
+ from scripts.isolation import task_frontmatter
77
+ else:
78
+ from isolation import task_frontmatter
79
+ fields, error = task_frontmatter(text)
80
+ if fields is None:
81
+ raise runtime().PipelineStateError(f"{label}: {error}")
82
+ return fields
83
+
84
+
85
+ def settled_tasks(repo: Path) -> dict[str, tuple[str, dict]]:
86
+ """Read task ownership without requiring the graph that Plan must repair."""
87
+ state = runtime()
88
+ directory = repo / ".project/tasks"
89
+ if directory.is_symlink() or not directory.is_dir():
90
+ raise state.PipelineStateError("build recovery requires real task artifacts")
91
+ tasks = {}
92
+ for path in sorted(directory.iterdir()):
93
+ if not re.fullmatch(r"T[0-9]{3}-[a-z0-9][a-z0-9-]*\.md", path.name):
94
+ raise state.PipelineStateError(f"invalid recovery task path: {path.name}")
95
+ text = state._read_real_file(path, path.name)
96
+ fields = task_fields(text, path.name)
97
+ if fields.get("status") not in {"pending", "done"}:
98
+ raise state.PipelineStateError(f"settle task ownership before recovery: {path.name}")
99
+ if fields["status"] == "pending" and any(
100
+ fields.get(key) != "null" for key in ("agent", "base", "worktree", "task_branch")
101
+ ):
102
+ raise state.PipelineStateError(f"pending task retains dispatch metadata: {path.name}")
103
+ tasks[path.name] = (text, fields)
104
+ if not tasks:
105
+ raise state.PipelineStateError("build recovery requires task artifacts")
106
+ return tasks
107
+
108
+
109
+ def begin(repo: Path, before, after, event: str) -> dict:
110
+ state = runtime()
111
+ if (before.phase, before.status) != ("build", "blocked") or after.phase not in EVENTS:
112
+ raise state.PipelineStateError("build recovery requires build/blocked")
113
+ if event != EVENTS[after.phase]:
114
+ raise state.PipelineStateError(f"state transition requires event: {EVENTS[after.phase]}")
115
+ if before.archive is not None or before.branch != state._current_branch(repo):
116
+ raise state.PipelineStateError("build recovery requires the active bound branch")
117
+ if state._worktree_changes(repo):
118
+ raise state.PipelineStateError("checkpoint the blocked build before recovery")
119
+ if any(state.transaction_journals(repo).values()):
120
+ raise state.PipelineStateError("finish pending transactions before build recovery")
121
+ records = records_root(repo)
122
+ for directory in {path.parent.parent for path in records.rglob("attempt-*/state.json")}:
123
+ latest = max(directory.glob("attempt-*/state.json"), key=lambda path: int(path.parent.name.split("-")[1]))
124
+ record = state._read_json(latest)
125
+ if (record.get("cleanup_pending")
126
+ or (record.get("worktree") and record.get("branch") and not record.get("cleanup_complete"))
127
+ or record.get("outcome") not in {"landed", "collected", "blocked", "resolved", "redispatched"}):
128
+ raise state.PipelineStateError(f"settle dispatch records before recovery: {latest}")
129
+ tasks = settled_tasks(repo)
130
+ state._read_real_file(repo / ".project/plan/PLAN.md", "PLAN.md")
131
+ state._read_real_file(repo / ".project/intent/INTENT.md", "INTENT.md")
132
+ base = state._run_git(repo, "rev-parse", "HEAD").stdout.strip()
133
+ done = [repo / ".project/tasks" / name for name, (_, fields) in tasks.items()
134
+ if fields["status"] == "done"]
135
+ if done:
136
+ if __package__:
137
+ from scripts.isolation import verify_landed_task_files
138
+ else:
139
+ from isolation import verify_landed_task_files
140
+ verify_landed_task_files(repo, done, ".project/tasks", base)
141
+ return {"kind": after.phase, "base": base, "branch": before.branch}
142
+
143
+
144
+ def records_root(repo: Path) -> Path:
145
+ if __package__:
146
+ from scripts import isolation
147
+ else:
148
+ import isolation
149
+ root = isolation.common_git_dir(repo) / "gsd-path/dispatch" / isolation.require_bound(repo).replace("/", "-")
150
+ recovery = context(repo)
151
+ return root / recovery["base"] if recovery else root
152
+
153
+
154
+ def review_backup(repo: Path, recovery: dict) -> Path:
155
+ return repo / ".project/plan" / f"build-recovery-{recovery['base']}" / "review"
156
+
157
+
158
+ def validate_review_backup(repo: Path, recovery: dict, directory: Path) -> None:
159
+ state = runtime()
160
+ if directory.exists():
161
+ checkpoint_runtime()._tree_digest(directory) # Reject symlinks and special files before reading.
162
+ expected = {}
163
+ for path in state._run_git(repo, "ls-tree", "-r", "--name-only", "-z",
164
+ recovery["base"], "--", ".project/review").stdout.split("\0"):
165
+ if path:
166
+ expected[str(Path(path).relative_to(".project/review"))] = checkpoint_runtime()._git_text_at(repo, recovery["base"], path)
167
+ actual = {str(path.relative_to(directory)): path.read_text(encoding="utf-8")
168
+ for path in directory.rglob("*") if path.is_file()} if directory.exists() else {}
169
+ if actual != expected:
170
+ raise state.PipelineStateError("review backup differs from the recovery base")
171
+
172
+
173
+ def prepare(repo: Path) -> dict:
174
+ """Move old reviews once; the durable STATE record owns interruption recovery."""
175
+ state = runtime()
176
+ repo = state._repo_root(repo)
177
+ with state._state_lock(repo / ".project"):
178
+ recovery = context(repo)
179
+ if not recovery or not recovery["active"]:
180
+ raise state.PipelineStateError("no active build recovery")
181
+ target = review_backup(repo, recovery)
182
+ if (repo / ".project/plan").is_symlink() or target.parent.is_symlink() or target.is_symlink():
183
+ raise state.PipelineStateError("unsafe recovery review backup")
184
+ source = repo / ".project/review"
185
+ if source.is_symlink():
186
+ raise state.PipelineStateError("unsafe recovery review directory")
187
+ if not target.exists():
188
+ validate_review_backup(repo, recovery, source)
189
+ target.parent.mkdir(exist_ok=True)
190
+ if source.exists():
191
+ source.rename(target)
192
+ else:
193
+ target.mkdir()
194
+ validate_review_backup(repo, recovery, target)
195
+ source.mkdir(exist_ok=True)
196
+ return {"status": "prepared", "recovery": recovery, "path": str(target)}
197
+
198
+
199
+ def validate_plan(repo: Path) -> None:
200
+ state = runtime()
201
+ recovery = context(repo)
202
+ if not recovery or not recovery["active"]:
203
+ return
204
+ if not review_backup(repo, recovery).is_dir():
205
+ raise state.PipelineStateError("run prepare-build-recovery before planning")
206
+ validate_review_backup(repo, recovery, review_backup(repo, recovery))
207
+ source = checkpoint_runtime()._task_contracts_at(repo, recovery["base"], ".project/tasks")
208
+ current = settled_tasks(repo)
209
+ done = set()
210
+ for _, (path, text) in source.items():
211
+ fields = task_fields(text, path)
212
+ if fields.get("status") == "done":
213
+ name = Path(path).name
214
+ done.add(name)
215
+ if name not in current or current[name][0] != text:
216
+ raise state.PipelineStateError(f"build recovery must preserve landed task: {name}")
217
+ if any(fields["status"] == "done" and name not in done
218
+ for name, (_, fields) in current.items()):
219
+ raise state.PipelineStateError("build recovery cannot manufacture landed tasks")
220
+ if recovery["kind"] == "plan":
221
+ intent = checkpoint_runtime()._git_text_at(repo, recovery["base"], ".project/intent/INTENT.md")
222
+ if state._read_real_file(repo / ".project/intent/INTENT.md", "INTENT.md") != intent:
223
+ raise state.PipelineStateError("plan repair cannot change approved intent")
224
+ unchanged = unchanged_waves(repo, recovery)
225
+ for path in (repo / ".project/review").iterdir():
226
+ match = re.match(r"wave-([1-9]\d*)[.]", path.name)
227
+ if match and int(match[1]) not in unchanged:
228
+ raise state.PipelineStateError("changed wave must be reviewed after build resumes")
229
+
230
+
231
+ def unchanged_waves(repo: Path, recovery: dict) -> set[int]:
232
+ """Reuse evidence only when its intent, wave contract, and task bytes match."""
233
+ state = runtime()
234
+ project = repo / ".project"
235
+ old_intent = checkpoint_runtime()._git_text_at(repo, recovery["base"], ".project/intent/INTENT.md")
236
+ if state._read_real_file(project / "intent/INTENT.md", "INTENT.md") != old_intent:
237
+ return set()
238
+ old_plan = checkpoint_runtime()._git_text_at(repo, recovery["base"], ".project/plan/PLAN.md") or ""
239
+ new_plan = state._read_real_file(project / "plan/PLAN.md", "PLAN.md")
240
+
241
+ def sections(text):
242
+ return {match[1]: match[2] for match in re.finditer(
243
+ r"(?ms)^## ([^\n]+)\n(.*?)(?=^## |\Z)", text,
244
+ )}
245
+
246
+ old_sections, new_sections = sections(old_plan), sections(new_plan)
247
+ # Non-wave settings govern every wave, including coverage and surfaces.
248
+ if ({k: v for k, v in old_sections.items() if not k.startswith("Wave ")}
249
+ != {k: v for k, v in new_sections.items() if not k.startswith("Wave ")}):
250
+ return set()
251
+ old_tasks = checkpoint_runtime()._task_contracts_at(repo, recovery["base"], ".project/tasks")
252
+ current = settled_tasks(repo)
253
+ unchanged = set()
254
+ for title, body in new_sections.items():
255
+ match = re.match(r"Wave ([1-9]\d*)\b", title)
256
+ if not match or old_sections.get(title) != body:
257
+ continue
258
+ wave = match[1]
259
+ before = {Path(path).name: text for path, text in old_tasks.values()
260
+ if task_fields(text, path).get("wave") == wave}
261
+ after = {name: text for name, (text, fields) in current.items() if fields.get("wave") == wave}
262
+ if before and before == after:
263
+ unchanged.add(int(wave))
264
+ return unchanged
265
+
266
+
267
+ def restore_unchanged_reviews(repo: Path) -> None:
268
+ recovery = context(repo)
269
+ if not recovery or not recovery["active"]:
270
+ return
271
+ unchanged = unchanged_waves(repo, recovery)
272
+ for path in review_backup(repo, recovery).iterdir():
273
+ match = re.match(r"wave-([1-9]\d*)[.]", path.name)
274
+ if match and int(match[1]) in unchanged:
275
+ destination = repo / ".project/review" / path.name
276
+ content = path.read_text(encoding="utf-8")
277
+ if not destination.exists() or runtime()._read_real_file(destination, destination.name) != content:
278
+ runtime()._atomic_write(destination, content)
279
+
280
+
281
+ def inventory_checkpoint(repo: Path, head: str) -> bool:
282
+ state = runtime()
283
+ if state._run_git(repo, "show", "-s", "--format=%s", head).stdout.strip() != state.PLAN_APPROVAL_SUBJECT:
284
+ return False
285
+ text = checkpoint_runtime()._git_text_at(repo, head, ".project/STATE.md")
286
+ if text is None:
287
+ return False
288
+ approved = state._state_from_text(text)
289
+ if (approved.phase, approved.status) != ("plan", "done"):
290
+ return False
291
+ recovery = context(repo, text)
292
+ return bool(recovery and recovery["active"])
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env python3
2
+ # gsd-path project runtime
2
3
  """Build task readiness, landing reconciliation, and the verify ledger."""
3
4
 
4
5
  from __future__ import annotations
@@ -7,6 +8,7 @@ import argparse
7
8
  import json
8
9
  import re
9
10
  import sys
11
+ sys.dont_write_bytecode = True
10
12
  from dataclasses import dataclass
11
13
  from datetime import datetime, timezone
12
14
  from pathlib import Path, PurePosixPath
@@ -7,6 +7,9 @@ import json
7
7
  import re
8
8
  import subprocess
9
9
  import sys
10
+
11
+ # Runtime helpers must not modify their immutable installation.
12
+ sys.dont_write_bytecode = True
10
13
  from pathlib import Path, PurePosixPath
11
14
  from typing import Dict, List, Optional, Sequence, Set, Tuple
12
15
 
@@ -131,6 +134,9 @@ def _strict_frontmatter(text: str, label: str) -> Dict[str, object]:
131
134
  while index < len(lines):
132
135
  line = lines[index]
133
136
  if line == "---":
137
+ for field in ('model', 'effort'):
138
+ if field in values and (not isinstance(values[field], str) or not values[field].strip()):
139
+ raise HandoffError(f'{label}: {field} must be a nonempty scalar or inherit')
134
140
  return values
135
141
  if not line.strip() or line.lstrip().startswith("#"):
136
142
  index += 1
@@ -1462,7 +1468,7 @@ def validate_wave_evidence(
1462
1468
  raise HandoffError(f"{name} Cycle field does not match its filename")
1463
1469
  if _line_value(text, "Depth:") != expected_depth:
1464
1470
  raise HandoffError(f"{name} Depth does not match PLAN.md")
1465
- lens_fields = re.findall(r"(?m)^Lens:\s*(\S.*?)\s*$", text)
1471
+ lens_fields = re.findall(r"(?m)^Lens:[ \t]*(\S.*?)[ \t]*$", text)
1466
1472
  if lens is None and lens_fields:
1467
1473
  raise HandoffError(f"{name} must not declare a review lens")
1468
1474
  if lens is not None and lens_fields != [lens]:
@@ -6,6 +6,9 @@ import argparse
6
6
  import json
7
7
  import re
8
8
  import sys
9
+
10
+ # Runtime helpers must not modify their immutable installation.
11
+ sys.dont_write_bytecode = True
9
12
  from pathlib import Path, PurePosixPath
10
13
  from typing import Dict, List, Optional, Sequence, Set, Tuple
11
14