@opengsd/gsd-path 1.1.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 (577) hide show
  1. package/AGENTS.md +5 -2
  2. package/CHANGELOG.md +68 -0
  3. package/DOCS.md +69 -1
  4. package/FULL.md +3 -2
  5. package/HOOKS.md +18 -9
  6. package/QUICK.md +5 -3
  7. package/README.md +72 -5
  8. package/RUNTIME.md +9 -4
  9. package/UPDATE.md +30 -11
  10. package/WORKFLOW.md +5 -2
  11. package/package.json +9 -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 +6 -19
  21. package/scripts/_common.py +5 -0
  22. package/scripts/archive_milestone.py +20 -6
  23. package/scripts/bootstrap_repository.py +8 -1
  24. package/scripts/build_recovery.py +5 -0
  25. package/scripts/build_state.py +2 -0
  26. package/scripts/check_docs_audit.py +2 -0
  27. package/scripts/check_handoffs.py +6 -0
  28. package/scripts/check_task_briefs.py +3 -0
  29. package/scripts/detect_project.py +11 -1
  30. package/scripts/discussion_records.py +3 -0
  31. package/scripts/discussion_validate.py +8 -3
  32. package/scripts/dispatch_driver.py +230 -21
  33. package/scripts/git_guard.py +12 -4
  34. package/scripts/guard_hook.py +11 -1
  35. package/scripts/install.mjs +52 -1448
  36. package/scripts/install.py +155 -190
  37. package/scripts/integration.py +28 -8
  38. package/scripts/isolation.py +15 -5
  39. package/scripts/jev_review.py +138 -0
  40. package/scripts/lean_verification.py +2 -0
  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 +3 -0
  45. package/scripts/pipeline_git.py +3 -0
  46. package/scripts/pipeline_state.py +4 -1
  47. package/scripts/pipeline_undo.py +3 -0
  48. package/scripts/promote_lookahead.py +3 -0
  49. package/scripts/review_panel.py +11 -0
  50. package/scripts/roadmap.py +5 -0
  51. package/scripts/runtime_store.py +278 -0
  52. package/scripts/skill-resources.json +104 -4
  53. package/scripts/state_checkpoint.py +5 -0
  54. package/scripts/state_promote.py +5 -0
  55. package/scripts/status_runtime.py +92 -6
  56. package/scripts/workflow_run.py +12 -1
  57. package/scripts/worktree_paths.py +94 -0
  58. package/skills/gsd-path/BUILD.md +7 -0
  59. package/skills/gsd-path/DECIDE.md +2 -0
  60. package/skills/gsd-path/DEFINE.md +13 -7
  61. package/skills/gsd-path/DOCS-AUDIT.md +2 -0
  62. package/skills/gsd-path/INSPECT.md +2 -0
  63. package/skills/gsd-path/PLAN.md +4 -2
  64. package/skills/gsd-path/RESEARCH.md +2 -0
  65. package/skills/gsd-path/ROADMAP.md +2 -0
  66. package/skills/gsd-path/SHIP.md +24 -13
  67. package/skills/gsd-path/SKILL.md +23 -9
  68. package/skills/gsd-path/references/build-native.md +15 -7
  69. package/skills/gsd-path/references/config.md +57 -0
  70. package/skills/gsd-path/references/dispatch.md +6 -19
  71. package/skills/gsd-path/references/jev-review.md +82 -0
  72. package/skills/gsd-path/references/model-policy.md +139 -0
  73. package/skills/gsd-path/references/reviewer-panel.md +1 -1
  74. package/skills/gsd-path/references/reviewer.md +2 -0
  75. package/skills/gsd-path/references/runtime-selection.md +25 -0
  76. package/skills/gsd-path/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  77. package/skills/gsd-path/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
  78. package/skills/gsd-path/scripts/_common.py +5 -0
  79. package/skills/gsd-path/scripts/archive_milestone.py +20 -6
  80. package/skills/gsd-path/scripts/bootstrap_repository.py +8 -1
  81. package/skills/gsd-path/scripts/build_recovery.py +5 -0
  82. package/skills/gsd-path/scripts/build_state.py +2 -0
  83. package/skills/gsd-path/scripts/check_docs_audit.py +2 -0
  84. package/skills/gsd-path/scripts/check_handoffs.py +6 -0
  85. package/skills/gsd-path/scripts/check_task_briefs.py +3 -0
  86. package/skills/gsd-path/scripts/detect_project.py +11 -1
  87. package/skills/gsd-path/scripts/discussion_records.py +3 -0
  88. package/skills/gsd-path/scripts/discussion_validate.py +8 -3
  89. package/skills/gsd-path/scripts/dispatch_driver.py +230 -21
  90. package/skills/gsd-path/scripts/git_guard.py +768 -0
  91. package/skills/gsd-path/scripts/integration.py +28 -8
  92. package/skills/gsd-path/scripts/isolation.py +15 -5
  93. package/skills/gsd-path/scripts/jev_review.py +138 -0
  94. package/skills/gsd-path/scripts/lean_verification.py +2 -0
  95. package/skills/gsd-path/scripts/loop_run.py +2 -0
  96. package/skills/gsd-path/scripts/model_policy.py +279 -0
  97. package/skills/gsd-path/scripts/path_config.py +248 -0
  98. package/skills/gsd-path/scripts/pipeline_git.py +3 -0
  99. package/skills/gsd-path/scripts/pipeline_state.py +4 -1
  100. package/skills/gsd-path/scripts/promote_lookahead.py +3 -0
  101. package/skills/gsd-path/scripts/review_panel.py +11 -0
  102. package/skills/gsd-path/scripts/roadmap.py +5 -0
  103. package/skills/gsd-path/scripts/state_checkpoint.py +5 -0
  104. package/skills/gsd-path/scripts/state_promote.py +5 -0
  105. package/skills/gsd-path/scripts/status_runtime.py +208 -0
  106. package/skills/gsd-path/scripts/workflow_run.py +12 -1
  107. package/skills/gsd-path/scripts/worktree_paths.py +94 -0
  108. package/skills/gsd-path/templates/skeptic.md +1 -1
  109. package/skills/gsd-path/templates/task.md +2 -0
  110. package/skills/gsd-path/templates/wave-panel.md +1 -1
  111. package/skills/gsd-path-build/SKILL.md +7 -0
  112. package/skills/gsd-path-build/references/build-native.md +15 -7
  113. package/skills/gsd-path-build/references/dispatch.md +6 -19
  114. package/skills/gsd-path-build/references/jev-review.md +82 -0
  115. package/skills/gsd-path-build/references/model-policy.md +139 -0
  116. package/skills/gsd-path-build/references/reviewer-panel.md +1 -1
  117. package/skills/gsd-path-build/references/reviewer.md +2 -0
  118. package/skills/gsd-path-build/references/runtime-selection.md +25 -0
  119. package/skills/gsd-path-build/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  120. package/skills/gsd-path-build/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
  121. package/skills/gsd-path-build/scripts/_common.py +5 -0
  122. package/skills/gsd-path-build/scripts/archive_milestone.py +20 -6
  123. package/skills/gsd-path-build/scripts/build_recovery.py +5 -0
  124. package/skills/gsd-path-build/scripts/build_state.py +2 -0
  125. package/skills/gsd-path-build/scripts/check_handoffs.py +6 -0
  126. package/skills/gsd-path-build/scripts/check_task_briefs.py +3 -0
  127. package/skills/gsd-path-build/scripts/detect_project.py +11 -1
  128. package/skills/gsd-path-build/scripts/discussion_records.py +3 -0
  129. package/skills/gsd-path-build/scripts/discussion_validate.py +8 -3
  130. package/skills/gsd-path-build/scripts/dispatch_driver.py +230 -21
  131. package/skills/gsd-path-build/scripts/integration.py +28 -8
  132. package/skills/gsd-path-build/scripts/isolation.py +15 -5
  133. package/skills/gsd-path-build/scripts/jev_review.py +138 -0
  134. package/skills/gsd-path-build/scripts/loop_run.py +2 -0
  135. package/skills/gsd-path-build/scripts/model_policy.py +279 -0
  136. package/skills/gsd-path-build/scripts/path_config.py +248 -0
  137. package/skills/gsd-path-build/scripts/pipeline_git.py +3 -0
  138. package/skills/gsd-path-build/scripts/pipeline_state.py +4 -1
  139. package/skills/gsd-path-build/scripts/promote_lookahead.py +3 -0
  140. package/skills/gsd-path-build/scripts/review_panel.py +11 -0
  141. package/skills/gsd-path-build/scripts/roadmap.py +5 -0
  142. package/skills/gsd-path-build/scripts/state_checkpoint.py +5 -0
  143. package/skills/gsd-path-build/scripts/state_promote.py +5 -0
  144. package/skills/gsd-path-build/scripts/workflow_run.py +12 -1
  145. package/skills/gsd-path-build/scripts/worktree_paths.py +94 -0
  146. package/skills/gsd-path-build/templates/skeptic.md +1 -1
  147. package/skills/gsd-path-build/templates/task.md +2 -0
  148. package/skills/gsd-path-build/templates/wave-panel.md +1 -1
  149. package/skills/gsd-path-decide/SKILL.md +2 -0
  150. package/skills/gsd-path-decide/references/dispatch.md +6 -19
  151. package/skills/gsd-path-decide/references/model-policy.md +139 -0
  152. package/skills/gsd-path-decide/references/runtime-selection.md +25 -0
  153. package/skills/gsd-path-decide/scripts/_common.py +5 -0
  154. package/skills/gsd-path-decide/scripts/archive_milestone.py +20 -6
  155. package/skills/gsd-path-decide/scripts/build_recovery.py +5 -0
  156. package/skills/gsd-path-decide/scripts/check_handoffs.py +6 -0
  157. package/skills/gsd-path-decide/scripts/check_task_briefs.py +3 -0
  158. package/skills/gsd-path-decide/scripts/detect_project.py +11 -1
  159. package/skills/gsd-path-decide/scripts/discussion_records.py +3 -0
  160. package/skills/gsd-path-decide/scripts/discussion_validate.py +8 -3
  161. package/skills/gsd-path-decide/scripts/integration.py +28 -8
  162. package/skills/gsd-path-decide/scripts/isolation.py +15 -5
  163. package/skills/gsd-path-decide/scripts/loop_run.py +839 -0
  164. package/skills/gsd-path-decide/scripts/model_policy.py +279 -0
  165. package/skills/gsd-path-decide/scripts/path_config.py +248 -0
  166. package/skills/gsd-path-decide/scripts/pipeline_git.py +3 -0
  167. package/skills/gsd-path-decide/scripts/pipeline_state.py +4 -1
  168. package/skills/gsd-path-decide/scripts/promote_lookahead.py +3 -0
  169. package/skills/gsd-path-decide/scripts/review_panel.py +11 -0
  170. package/skills/gsd-path-decide/scripts/roadmap.py +5 -0
  171. package/skills/gsd-path-decide/scripts/state_checkpoint.py +5 -0
  172. package/skills/gsd-path-decide/scripts/state_promote.py +5 -0
  173. package/skills/gsd-path-decide/scripts/worktree_paths.py +94 -0
  174. package/skills/gsd-path-define/SKILL.md +13 -7
  175. package/skills/gsd-path-define/references/runtime-selection.md +25 -0
  176. package/skills/gsd-path-define/scripts/_common.py +5 -0
  177. package/skills/gsd-path-define/scripts/archive_milestone.py +20 -6
  178. package/skills/gsd-path-define/scripts/build_recovery.py +5 -0
  179. package/skills/gsd-path-define/scripts/check_handoffs.py +6 -0
  180. package/skills/gsd-path-define/scripts/check_task_briefs.py +3 -0
  181. package/skills/gsd-path-define/scripts/detect_project.py +11 -1
  182. package/skills/gsd-path-define/scripts/discussion_records.py +3 -0
  183. package/skills/gsd-path-define/scripts/discussion_validate.py +8 -3
  184. package/skills/gsd-path-define/scripts/integration.py +28 -8
  185. package/skills/gsd-path-define/scripts/isolation.py +15 -5
  186. package/skills/gsd-path-define/scripts/loop_run.py +839 -0
  187. package/skills/gsd-path-define/scripts/model_policy.py +279 -0
  188. package/skills/gsd-path-define/scripts/path_config.py +248 -0
  189. package/skills/gsd-path-define/scripts/pipeline_git.py +3 -0
  190. package/skills/gsd-path-define/scripts/pipeline_state.py +4 -1
  191. package/skills/gsd-path-define/scripts/promote_lookahead.py +3 -0
  192. package/skills/gsd-path-define/scripts/review_panel.py +11 -0
  193. package/skills/gsd-path-define/scripts/roadmap.py +5 -0
  194. package/skills/gsd-path-define/scripts/state_checkpoint.py +5 -0
  195. package/skills/gsd-path-define/scripts/state_promote.py +5 -0
  196. package/skills/gsd-path-define/scripts/worktree_paths.py +94 -0
  197. package/skills/gsd-path-discuss/SKILL.md +2 -0
  198. package/skills/gsd-path-discuss/references/runtime-selection.md +25 -0
  199. package/skills/gsd-path-discuss/scripts/_common.py +5 -0
  200. package/skills/gsd-path-discuss/scripts/discussion_records.py +3 -0
  201. package/skills/gsd-path-discuss/scripts/loop_run.py +839 -0
  202. package/skills/gsd-path-discuss/scripts/model_policy.py +279 -0
  203. package/skills/gsd-path-discuss/scripts/path_config.py +248 -0
  204. package/skills/gsd-path-discuss/scripts/review_panel.py +564 -0
  205. package/skills/gsd-path-discuss/scripts/worktree_paths.py +94 -0
  206. package/skills/gsd-path-docs-audit/SKILL.md +2 -0
  207. package/skills/gsd-path-docs-audit/references/dispatch.md +6 -19
  208. package/skills/gsd-path-docs-audit/references/model-policy.md +139 -0
  209. package/skills/gsd-path-docs-audit/references/runtime-selection.md +25 -0
  210. package/skills/gsd-path-docs-audit/scripts/_common.py +5 -0
  211. package/skills/gsd-path-docs-audit/scripts/archive_milestone.py +20 -6
  212. package/skills/gsd-path-docs-audit/scripts/build_recovery.py +5 -0
  213. package/skills/gsd-path-docs-audit/scripts/check_docs_audit.py +2 -0
  214. package/skills/gsd-path-docs-audit/scripts/check_handoffs.py +6 -0
  215. package/skills/gsd-path-docs-audit/scripts/check_task_briefs.py +3 -0
  216. package/skills/gsd-path-docs-audit/scripts/detect_project.py +11 -1
  217. package/skills/gsd-path-docs-audit/scripts/discussion_records.py +3 -0
  218. package/skills/gsd-path-docs-audit/scripts/discussion_validate.py +8 -3
  219. package/skills/gsd-path-docs-audit/scripts/integration.py +28 -8
  220. package/skills/gsd-path-docs-audit/scripts/isolation.py +15 -5
  221. package/skills/gsd-path-docs-audit/scripts/loop_run.py +839 -0
  222. package/skills/gsd-path-docs-audit/scripts/model_policy.py +279 -0
  223. package/skills/gsd-path-docs-audit/scripts/path_config.py +248 -0
  224. package/skills/gsd-path-docs-audit/scripts/pipeline_git.py +3 -0
  225. package/skills/gsd-path-docs-audit/scripts/pipeline_state.py +4 -1
  226. package/skills/gsd-path-docs-audit/scripts/promote_lookahead.py +3 -0
  227. package/skills/gsd-path-docs-audit/scripts/review_panel.py +11 -0
  228. package/skills/gsd-path-docs-audit/scripts/roadmap.py +5 -0
  229. package/skills/gsd-path-docs-audit/scripts/state_checkpoint.py +5 -0
  230. package/skills/gsd-path-docs-audit/scripts/state_promote.py +5 -0
  231. package/skills/gsd-path-docs-audit/scripts/worktree_paths.py +94 -0
  232. package/skills/gsd-path-forensics/SKILL.md +9 -5
  233. package/skills/gsd-path-forensics/references/runtime-selection.md +25 -0
  234. package/skills/gsd-path-forensics/scripts/_common.py +5 -0
  235. package/skills/gsd-path-forensics/scripts/discussion_records.py +3 -0
  236. package/skills/gsd-path-forensics/scripts/loop_run.py +839 -0
  237. package/skills/gsd-path-forensics/scripts/model_policy.py +279 -0
  238. package/skills/gsd-path-forensics/scripts/path_config.py +248 -0
  239. package/skills/gsd-path-forensics/scripts/pipeline_diagnose.py +3 -0
  240. package/skills/gsd-path-forensics/scripts/pipeline_undo.py +3 -0
  241. package/skills/gsd-path-forensics/scripts/review_panel.py +564 -0
  242. package/skills/gsd-path-forensics/scripts/worktree_paths.py +94 -0
  243. package/skills/gsd-path-inspect/SKILL.md +2 -0
  244. package/skills/gsd-path-inspect/references/dispatch.md +6 -19
  245. package/skills/gsd-path-inspect/references/model-policy.md +139 -0
  246. package/skills/gsd-path-inspect/references/runtime-selection.md +25 -0
  247. package/skills/gsd-path-inspect/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  248. package/skills/gsd-path-inspect/scripts/_common.py +5 -0
  249. package/skills/gsd-path-inspect/scripts/archive_milestone.py +20 -6
  250. package/skills/gsd-path-inspect/scripts/build_recovery.py +5 -0
  251. package/skills/gsd-path-inspect/scripts/check_docs_audit.py +2 -0
  252. package/skills/gsd-path-inspect/scripts/check_handoffs.py +6 -0
  253. package/skills/gsd-path-inspect/scripts/check_task_briefs.py +3 -0
  254. package/skills/gsd-path-inspect/scripts/detect_project.py +11 -1
  255. package/skills/gsd-path-inspect/scripts/discussion_records.py +3 -0
  256. package/skills/gsd-path-inspect/scripts/discussion_validate.py +8 -3
  257. package/skills/gsd-path-inspect/scripts/integration.py +28 -8
  258. package/skills/gsd-path-inspect/scripts/isolation.py +15 -5
  259. package/skills/gsd-path-inspect/scripts/loop_run.py +839 -0
  260. package/skills/gsd-path-inspect/scripts/model_policy.py +279 -0
  261. package/skills/gsd-path-inspect/scripts/path_config.py +248 -0
  262. package/skills/gsd-path-inspect/scripts/pipeline_git.py +3 -0
  263. package/skills/gsd-path-inspect/scripts/pipeline_state.py +4 -1
  264. package/skills/gsd-path-inspect/scripts/promote_lookahead.py +3 -0
  265. package/skills/gsd-path-inspect/scripts/review_panel.py +11 -0
  266. package/skills/gsd-path-inspect/scripts/roadmap.py +5 -0
  267. package/skills/gsd-path-inspect/scripts/state_checkpoint.py +5 -0
  268. package/skills/gsd-path-inspect/scripts/state_promote.py +5 -0
  269. package/skills/gsd-path-inspect/scripts/workflow_run.py +12 -1
  270. package/skills/gsd-path-inspect/scripts/worktree_paths.py +94 -0
  271. package/skills/gsd-path-loop/SKILL.md +2 -0
  272. package/skills/gsd-path-loop/references/runtime-selection.md +25 -0
  273. package/skills/gsd-path-loop/scripts/_common.py +5 -0
  274. package/skills/gsd-path-loop/scripts/discussion_records.py +3 -0
  275. package/skills/gsd-path-loop/scripts/loop_run.py +2 -0
  276. package/skills/gsd-path-loop/scripts/model_policy.py +279 -0
  277. package/skills/gsd-path-loop/scripts/path_config.py +248 -0
  278. package/skills/gsd-path-loop/scripts/review_panel.py +564 -0
  279. package/skills/gsd-path-loop/scripts/worktree_paths.py +94 -0
  280. package/skills/gsd-path-migrate/SKILL.md +2 -0
  281. package/skills/gsd-path-migrate/references/runtime-selection.md +25 -0
  282. package/skills/gsd-path-migrate/scripts/_common.py +185 -0
  283. package/skills/gsd-path-migrate/scripts/discussion_records.py +3 -0
  284. package/skills/gsd-path-migrate/scripts/loop_run.py +839 -0
  285. package/skills/gsd-path-migrate/scripts/model_policy.py +279 -0
  286. package/skills/gsd-path-migrate/scripts/path_config.py +248 -0
  287. package/skills/gsd-path-migrate/scripts/review_panel.py +564 -0
  288. package/skills/gsd-path-plan/SKILL.md +4 -2
  289. package/skills/gsd-path-plan/references/dispatch.md +6 -19
  290. package/skills/gsd-path-plan/references/model-policy.md +139 -0
  291. package/skills/gsd-path-plan/references/runtime-selection.md +25 -0
  292. package/skills/gsd-path-plan/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  293. package/skills/gsd-path-plan/scripts/_common.py +5 -0
  294. package/skills/gsd-path-plan/scripts/archive_milestone.py +20 -6
  295. package/skills/gsd-path-plan/scripts/build_recovery.py +5 -0
  296. package/skills/gsd-path-plan/scripts/check_handoffs.py +6 -0
  297. package/skills/gsd-path-plan/scripts/check_task_briefs.py +3 -0
  298. package/skills/gsd-path-plan/scripts/detect_project.py +11 -1
  299. package/skills/gsd-path-plan/scripts/discussion_records.py +3 -0
  300. package/skills/gsd-path-plan/scripts/discussion_validate.py +8 -3
  301. package/skills/gsd-path-plan/scripts/integration.py +28 -8
  302. package/skills/gsd-path-plan/scripts/isolation.py +15 -5
  303. package/skills/gsd-path-plan/scripts/loop_run.py +839 -0
  304. package/skills/gsd-path-plan/scripts/model_policy.py +279 -0
  305. package/skills/gsd-path-plan/scripts/path_config.py +248 -0
  306. package/skills/gsd-path-plan/scripts/pipeline_git.py +3 -0
  307. package/skills/gsd-path-plan/scripts/pipeline_state.py +4 -1
  308. package/skills/gsd-path-plan/scripts/promote_lookahead.py +3 -0
  309. package/skills/gsd-path-plan/scripts/review_panel.py +11 -0
  310. package/skills/gsd-path-plan/scripts/roadmap.py +5 -0
  311. package/skills/gsd-path-plan/scripts/state_checkpoint.py +5 -0
  312. package/skills/gsd-path-plan/scripts/state_promote.py +5 -0
  313. package/skills/gsd-path-plan/scripts/workflow_run.py +12 -1
  314. package/skills/gsd-path-plan/scripts/worktree_paths.py +94 -0
  315. package/skills/gsd-path-plan/templates/task.md +2 -0
  316. package/skills/gsd-path-research/SKILL.md +2 -0
  317. package/skills/gsd-path-research/references/dispatch.md +6 -19
  318. package/skills/gsd-path-research/references/model-policy.md +139 -0
  319. package/skills/gsd-path-research/references/runtime-selection.md +25 -0
  320. package/skills/gsd-path-research/scripts/_common.py +5 -0
  321. package/skills/gsd-path-research/scripts/archive_milestone.py +20 -6
  322. package/skills/gsd-path-research/scripts/build_recovery.py +5 -0
  323. package/skills/gsd-path-research/scripts/check_handoffs.py +6 -0
  324. package/skills/gsd-path-research/scripts/check_task_briefs.py +3 -0
  325. package/skills/gsd-path-research/scripts/detect_project.py +11 -1
  326. package/skills/gsd-path-research/scripts/discussion_records.py +3 -0
  327. package/skills/gsd-path-research/scripts/discussion_validate.py +8 -3
  328. package/skills/gsd-path-research/scripts/integration.py +28 -8
  329. package/skills/gsd-path-research/scripts/isolation.py +15 -5
  330. package/skills/gsd-path-research/scripts/loop_run.py +839 -0
  331. package/skills/gsd-path-research/scripts/model_policy.py +279 -0
  332. package/skills/gsd-path-research/scripts/path_config.py +248 -0
  333. package/skills/gsd-path-research/scripts/pipeline_git.py +3 -0
  334. package/skills/gsd-path-research/scripts/pipeline_state.py +4 -1
  335. package/skills/gsd-path-research/scripts/promote_lookahead.py +3 -0
  336. package/skills/gsd-path-research/scripts/review_panel.py +11 -0
  337. package/skills/gsd-path-research/scripts/roadmap.py +5 -0
  338. package/skills/gsd-path-research/scripts/state_checkpoint.py +5 -0
  339. package/skills/gsd-path-research/scripts/state_promote.py +5 -0
  340. package/skills/gsd-path-research/scripts/worktree_paths.py +94 -0
  341. package/skills/gsd-path-roadmap/SKILL.md +2 -0
  342. package/skills/gsd-path-roadmap/references/dispatch.md +6 -19
  343. package/skills/gsd-path-roadmap/references/model-policy.md +139 -0
  344. package/skills/gsd-path-roadmap/references/runtime-selection.md +25 -0
  345. package/skills/gsd-path-roadmap/scripts/_common.py +5 -0
  346. package/skills/gsd-path-roadmap/scripts/archive_milestone.py +20 -6
  347. package/skills/gsd-path-roadmap/scripts/build_recovery.py +5 -0
  348. package/skills/gsd-path-roadmap/scripts/check_handoffs.py +6 -0
  349. package/skills/gsd-path-roadmap/scripts/check_task_briefs.py +3 -0
  350. package/skills/gsd-path-roadmap/scripts/detect_project.py +11 -1
  351. package/skills/gsd-path-roadmap/scripts/discussion_records.py +3 -0
  352. package/skills/gsd-path-roadmap/scripts/discussion_validate.py +8 -3
  353. package/skills/gsd-path-roadmap/scripts/integration.py +28 -8
  354. package/skills/gsd-path-roadmap/scripts/isolation.py +15 -5
  355. package/skills/gsd-path-roadmap/scripts/loop_run.py +839 -0
  356. package/skills/gsd-path-roadmap/scripts/model_policy.py +279 -0
  357. package/skills/gsd-path-roadmap/scripts/path_config.py +248 -0
  358. package/skills/gsd-path-roadmap/scripts/pipeline_git.py +3 -0
  359. package/skills/gsd-path-roadmap/scripts/pipeline_state.py +4 -1
  360. package/skills/gsd-path-roadmap/scripts/promote_lookahead.py +3 -0
  361. package/skills/gsd-path-roadmap/scripts/review_panel.py +11 -0
  362. package/skills/gsd-path-roadmap/scripts/roadmap.py +5 -0
  363. package/skills/gsd-path-roadmap/scripts/state_checkpoint.py +5 -0
  364. package/skills/gsd-path-roadmap/scripts/state_promote.py +5 -0
  365. package/skills/gsd-path-roadmap/scripts/worktree_paths.py +94 -0
  366. package/skills/gsd-path-ship/SKILL.md +24 -13
  367. package/skills/gsd-path-ship/references/dispatch.md +6 -19
  368. package/skills/gsd-path-ship/references/jev-review.md +82 -0
  369. package/skills/gsd-path-ship/references/model-policy.md +139 -0
  370. package/skills/gsd-path-ship/references/reviewer-panel.md +1 -1
  371. package/skills/gsd-path-ship/references/reviewer.md +2 -0
  372. package/skills/gsd-path-ship/references/runtime-selection.md +25 -0
  373. package/skills/gsd-path-ship/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  374. package/skills/gsd-path-ship/scripts/_common.py +5 -0
  375. package/skills/gsd-path-ship/scripts/archive_milestone.py +20 -6
  376. package/skills/gsd-path-ship/scripts/build_recovery.py +5 -0
  377. package/skills/gsd-path-ship/scripts/build_state.py +2 -0
  378. package/skills/gsd-path-ship/scripts/check_handoffs.py +6 -0
  379. package/skills/gsd-path-ship/scripts/check_task_briefs.py +3 -0
  380. package/skills/gsd-path-ship/scripts/detect_project.py +11 -1
  381. package/skills/gsd-path-ship/scripts/discussion_records.py +3 -0
  382. package/skills/gsd-path-ship/scripts/discussion_validate.py +8 -3
  383. package/skills/gsd-path-ship/scripts/integration.py +28 -8
  384. package/skills/gsd-path-ship/scripts/isolation.py +15 -5
  385. package/skills/gsd-path-ship/scripts/jev_review.py +138 -0
  386. package/skills/gsd-path-ship/scripts/lean_verification.py +2 -0
  387. package/skills/gsd-path-ship/scripts/loop_run.py +839 -0
  388. package/skills/gsd-path-ship/scripts/model_policy.py +279 -0
  389. package/skills/gsd-path-ship/scripts/path_config.py +248 -0
  390. package/skills/gsd-path-ship/scripts/pipeline_git.py +3 -0
  391. package/skills/gsd-path-ship/scripts/pipeline_state.py +4 -1
  392. package/skills/gsd-path-ship/scripts/promote_lookahead.py +3 -0
  393. package/skills/gsd-path-ship/scripts/review_panel.py +11 -0
  394. package/skills/gsd-path-ship/scripts/roadmap.py +5 -0
  395. package/skills/gsd-path-ship/scripts/state_checkpoint.py +5 -0
  396. package/skills/gsd-path-ship/scripts/state_promote.py +5 -0
  397. package/skills/gsd-path-ship/scripts/workflow_run.py +12 -1
  398. package/skills/gsd-path-ship/scripts/worktree_paths.py +94 -0
  399. package/skills/gsd-path-undo/SKILL.md +2 -0
  400. package/skills/gsd-path-undo/references/runtime-selection.md +25 -0
  401. package/skills/gsd-path-undo/scripts/_common.py +5 -0
  402. package/skills/gsd-path-undo/scripts/discussion_records.py +3 -0
  403. package/skills/gsd-path-undo/scripts/loop_run.py +839 -0
  404. package/skills/gsd-path-undo/scripts/model_policy.py +279 -0
  405. package/skills/gsd-path-undo/scripts/path_config.py +248 -0
  406. package/skills/gsd-path-undo/scripts/pipeline_undo.py +3 -0
  407. package/skills/gsd-path-undo/scripts/review_panel.py +564 -0
  408. package/skills/gsd-path-undo/scripts/worktree_paths.py +94 -0
  409. package/skills/path/BUILD.md +7 -0
  410. package/skills/path/DECIDE.md +2 -0
  411. package/skills/path/DEFINE.md +13 -7
  412. package/skills/path/DOCS-AUDIT.md +2 -0
  413. package/skills/path/INSPECT.md +2 -0
  414. package/skills/path/PLAN.md +4 -2
  415. package/skills/path/RESEARCH.md +2 -0
  416. package/skills/path/ROADMAP.md +2 -0
  417. package/skills/path/SHIP.md +24 -13
  418. package/skills/path/SKILL.md +23 -9
  419. package/skills/path/references/build-native.md +15 -7
  420. package/skills/path/references/config.md +57 -0
  421. package/skills/path/references/dispatch.md +6 -19
  422. package/skills/path/references/jev-review.md +82 -0
  423. package/skills/path/references/model-policy.md +139 -0
  424. package/skills/path/references/reviewer-panel.md +1 -1
  425. package/skills/path/references/reviewer.md +2 -0
  426. package/skills/path/references/runtime-selection.md +25 -0
  427. package/skills/path/scripts/_common.py +5 -0
  428. package/skills/path/scripts/archive_milestone.py +20 -6
  429. package/skills/path/scripts/bootstrap_repository.py +8 -1
  430. package/skills/path/scripts/build_recovery.py +5 -0
  431. package/skills/path/scripts/build_state.py +2 -0
  432. package/skills/path/scripts/check_docs_audit.py +2 -0
  433. package/skills/path/scripts/check_handoffs.py +6 -0
  434. package/skills/path/scripts/check_task_briefs.py +3 -0
  435. package/skills/path/scripts/detect_project.py +11 -1
  436. package/skills/path/scripts/discussion_records.py +3 -0
  437. package/skills/path/scripts/discussion_validate.py +8 -3
  438. package/skills/path/scripts/dispatch_driver.py +230 -21
  439. package/skills/path/scripts/git_guard.py +768 -0
  440. package/skills/path/scripts/integration.py +28 -8
  441. package/skills/path/scripts/isolation.py +15 -5
  442. package/skills/path/scripts/jev_review.py +138 -0
  443. package/skills/path/scripts/lean_verification.py +2 -0
  444. package/skills/path/scripts/loop_run.py +2 -0
  445. package/skills/path/scripts/model_policy.py +279 -0
  446. package/skills/path/scripts/path_config.py +248 -0
  447. package/skills/path/scripts/pipeline_git.py +3 -0
  448. package/skills/path/scripts/pipeline_state.py +4 -1
  449. package/skills/path/scripts/promote_lookahead.py +3 -0
  450. package/skills/path/scripts/review_panel.py +11 -0
  451. package/skills/path/scripts/roadmap.py +5 -0
  452. package/skills/path/scripts/state_checkpoint.py +5 -0
  453. package/skills/path/scripts/state_promote.py +5 -0
  454. package/skills/path/scripts/status_runtime.py +208 -0
  455. package/skills/path/scripts/workflow_run.py +12 -1
  456. package/skills/path/scripts/worktree_paths.py +94 -0
  457. package/skills/path/templates/skeptic.md +1 -1
  458. package/skills/path/templates/task.md +2 -0
  459. package/skills/path/templates/wave-panel.md +1 -1
  460. package/skills/gsd-path/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
  461. package/skills/gsd-path/scripts/__pycache__/build_recovery.cpython-312.pyc +0 -0
  462. package/skills/gsd-path/scripts/__pycache__/build_state.cpython-312.pyc +0 -0
  463. package/skills/gsd-path/scripts/__pycache__/check_task_briefs.cpython-312.pyc +0 -0
  464. package/skills/gsd-path/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
  465. package/skills/gsd-path/scripts/__pycache__/discussion_records.cpython-312.pyc +0 -0
  466. package/skills/gsd-path/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
  467. package/skills/gsd-path/scripts/__pycache__/integration.cpython-312.pyc +0 -0
  468. package/skills/gsd-path/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
  469. package/skills/gsd-path/scripts/__pycache__/loop_run.cpython-312.pyc +0 -0
  470. package/skills/gsd-path/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
  471. package/skills/gsd-path/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
  472. package/skills/gsd-path/scripts/__pycache__/review_findings.cpython-312.pyc +0 -0
  473. package/skills/gsd-path/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
  474. package/skills/gsd-path/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
  475. package/skills/gsd-path/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
  476. package/skills/gsd-path/scripts/__pycache__/task_context.cpython-312.pyc +0 -0
  477. package/skills/gsd-path/scripts/__pycache__/workflow_run.cpython-312.pyc +0 -0
  478. package/skills/gsd-path-build/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
  479. package/skills/gsd-path-build/scripts/__pycache__/build_recovery.cpython-312.pyc +0 -0
  480. package/skills/gsd-path-build/scripts/__pycache__/build_state.cpython-312.pyc +0 -0
  481. package/skills/gsd-path-build/scripts/__pycache__/check_task_briefs.cpython-312.pyc +0 -0
  482. package/skills/gsd-path-build/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
  483. package/skills/gsd-path-build/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
  484. package/skills/gsd-path-build/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
  485. package/skills/gsd-path-build/scripts/__pycache__/loop_run.cpython-312.pyc +0 -0
  486. package/skills/gsd-path-build/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
  487. package/skills/gsd-path-build/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
  488. package/skills/gsd-path-build/scripts/__pycache__/review_findings.cpython-312.pyc +0 -0
  489. package/skills/gsd-path-build/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
  490. package/skills/gsd-path-build/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
  491. package/skills/gsd-path-build/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
  492. package/skills/gsd-path-build/scripts/__pycache__/task_context.cpython-312.pyc +0 -0
  493. package/skills/gsd-path-build/scripts/__pycache__/workflow_run.cpython-312.pyc +0 -0
  494. package/skills/gsd-path-decide/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  495. package/skills/gsd-path-decide/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
  496. package/skills/gsd-path-decide/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
  497. package/skills/gsd-path-decide/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
  498. package/skills/gsd-path-decide/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
  499. package/skills/gsd-path-decide/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
  500. package/skills/gsd-path-decide/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
  501. package/skills/gsd-path-decide/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
  502. package/skills/gsd-path-decide/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
  503. package/skills/gsd-path-decide/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
  504. package/skills/gsd-path-define/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  505. package/skills/gsd-path-define/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
  506. package/skills/gsd-path-define/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
  507. package/skills/gsd-path-define/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
  508. package/skills/gsd-path-define/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
  509. package/skills/gsd-path-define/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
  510. package/skills/gsd-path-define/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
  511. package/skills/gsd-path-define/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
  512. package/skills/gsd-path-define/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
  513. package/skills/gsd-path-define/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
  514. package/skills/gsd-path-docs-audit/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  515. package/skills/gsd-path-docs-audit/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
  516. package/skills/gsd-path-docs-audit/scripts/__pycache__/build_recovery.cpython-312.pyc +0 -0
  517. package/skills/gsd-path-docs-audit/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
  518. package/skills/gsd-path-docs-audit/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
  519. package/skills/gsd-path-docs-audit/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
  520. package/skills/gsd-path-docs-audit/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
  521. package/skills/gsd-path-docs-audit/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
  522. package/skills/gsd-path-docs-audit/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
  523. package/skills/gsd-path-docs-audit/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
  524. package/skills/gsd-path-docs-audit/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
  525. package/skills/gsd-path-forensics/scripts/__pycache__/pipeline_undo.cpython-312.pyc +0 -0
  526. package/skills/gsd-path-inspect/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
  527. package/skills/gsd-path-inspect/scripts/__pycache__/build_recovery.cpython-312.pyc +0 -0
  528. package/skills/gsd-path-inspect/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
  529. package/skills/gsd-path-inspect/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
  530. package/skills/gsd-path-inspect/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
  531. package/skills/gsd-path-inspect/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
  532. package/skills/gsd-path-inspect/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
  533. package/skills/gsd-path-inspect/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
  534. package/skills/gsd-path-inspect/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
  535. package/skills/gsd-path-inspect/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
  536. package/skills/gsd-path-plan/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
  537. package/skills/gsd-path-plan/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
  538. package/skills/gsd-path-plan/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
  539. package/skills/gsd-path-plan/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
  540. package/skills/gsd-path-plan/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
  541. package/skills/gsd-path-plan/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
  542. package/skills/gsd-path-plan/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
  543. package/skills/gsd-path-plan/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
  544. package/skills/gsd-path-plan/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
  545. package/skills/gsd-path-research/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  546. package/skills/gsd-path-research/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
  547. package/skills/gsd-path-research/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
  548. package/skills/gsd-path-research/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
  549. package/skills/gsd-path-research/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
  550. package/skills/gsd-path-research/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
  551. package/skills/gsd-path-research/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
  552. package/skills/gsd-path-research/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
  553. package/skills/gsd-path-research/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
  554. package/skills/gsd-path-research/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
  555. package/skills/gsd-path-roadmap/scripts/__pycache__/_common.cpython-312.pyc +0 -0
  556. package/skills/gsd-path-roadmap/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
  557. package/skills/gsd-path-roadmap/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
  558. package/skills/gsd-path-roadmap/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
  559. package/skills/gsd-path-roadmap/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
  560. package/skills/gsd-path-roadmap/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
  561. package/skills/gsd-path-roadmap/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
  562. package/skills/gsd-path-roadmap/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
  563. package/skills/gsd-path-roadmap/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
  564. package/skills/gsd-path-roadmap/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
  565. package/skills/gsd-path-ship/scripts/__pycache__/archive_milestone.cpython-312.pyc +0 -0
  566. package/skills/gsd-path-ship/scripts/__pycache__/build_recovery.cpython-312.pyc +0 -0
  567. package/skills/gsd-path-ship/scripts/__pycache__/build_state.cpython-312.pyc +0 -0
  568. package/skills/gsd-path-ship/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
  569. package/skills/gsd-path-ship/scripts/__pycache__/check_task_briefs.cpython-312.pyc +0 -0
  570. package/skills/gsd-path-ship/scripts/__pycache__/detect_project.cpython-312.pyc +0 -0
  571. package/skills/gsd-path-ship/scripts/__pycache__/discussion_validate.cpython-312.pyc +0 -0
  572. package/skills/gsd-path-ship/scripts/__pycache__/isolation.cpython-312.pyc +0 -0
  573. package/skills/gsd-path-ship/scripts/__pycache__/pipeline_git.cpython-312.pyc +0 -0
  574. package/skills/gsd-path-ship/scripts/__pycache__/pipeline_state.cpython-312.pyc +0 -0
  575. package/skills/gsd-path-ship/scripts/__pycache__/review_panel.cpython-312.pyc +0 -0
  576. package/skills/gsd-path-ship/scripts/__pycache__/roadmap.cpython-312.pyc +0 -0
  577. package/skills/gsd-path-ship/scripts/__pycache__/state_checkpoint.cpython-312.pyc +0 -0
@@ -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
@@ -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
 
@@ -34,6 +34,9 @@ import re
34
34
  import stat
35
35
  import subprocess
36
36
  import sys
37
+
38
+ # Runtime helpers must not modify their immutable installation.
39
+ sys.dont_write_bytecode = True
37
40
  from datetime import date
38
41
  from pathlib import Path, PurePosixPath, PureWindowsPath
39
42
  from typing import Iterable, NamedTuple, Optional, Sequence
@@ -1200,7 +1203,14 @@ def filled_state_template(template: str, slug: str, phase: str) -> str:
1200
1203
  text = text.replace("YYYY-MM-DD — <phase> — project initialized", stamp)
1201
1204
  if "<slug>" in text or "YYYY-MM-DD" in text:
1202
1205
  raise DetectError("state template placeholder remains")
1203
- return text
1206
+ try:
1207
+ from .path_config import initial_state
1208
+ except ImportError:
1209
+ from path_config import initial_state
1210
+ try:
1211
+ return initial_state(text)
1212
+ except (OSError, ValueError, RuntimeError) as error:
1213
+ raise DetectError(str(error)) from error
1204
1214
 
1205
1215
 
1206
1216
  def write_all(descriptor: int, payload: bytes) -> None:
@@ -7,6 +7,9 @@ import json
7
7
  import os
8
8
  import shutil
9
9
  import sys
10
+
11
+ # Runtime helpers must not modify their immutable installation.
12
+ sys.dont_write_bytecode = True
10
13
  from contextlib import contextmanager
11
14
  from datetime import date
12
15
  from pathlib import Path
@@ -2,6 +2,11 @@
2
2
  # gsd-path project runtime
3
3
  """Validate discussion records, reviews and the manifest for archive_milestone."""
4
4
 
5
+ import sys
6
+
7
+ # Runtime helpers must not modify their immutable installation.
8
+ sys.dont_write_bytecode = True
9
+
5
10
  import json
6
11
  import re
7
12
  import shutil
@@ -409,7 +414,7 @@ def remove_interrupted_carry_copy(active_root: Path, archive: Path) -> None:
409
414
 
410
415
 
411
416
  def require_transaction_context(
412
- project: Path, state: PipelineState, phases: Sequence[str]
417
+ project: Path, state: PipelineState, phases: Sequence[str], *, historical: bool = False
413
418
  ) -> None:
414
419
  phase = state.phase
415
420
  if phase not in phases:
@@ -429,7 +434,7 @@ def require_transaction_context(
429
434
  )
430
435
  if archive_milestone.is_unset(expected_branch):
431
436
  raise ArchiveError("STATE.md does not name a bound build branch")
432
- if current_branch != expected_branch:
437
+ if current_branch != expected_branch and not historical:
433
438
  raise ArchiveError(
434
439
  f"current branch {current_branch!r} does not match STATE.branch {expected_branch!r}"
435
440
  )
@@ -446,7 +451,7 @@ def require_transaction_context(
446
451
  ),
447
452
  "inspect non-project worktree status",
448
453
  )
449
- if non_project_status:
454
+ if non_project_status and not historical:
450
455
  raise ArchiveError("non-.project worktree changes block the archive transaction")
451
456
 
452
457
 
@@ -31,7 +31,7 @@ from typing import BinaryIO, Callable, Dict, List, Optional
31
31
 
32
32
  try:
33
33
  from scripts import (_common, archive_milestone, build_state, discussion_validate, isolation,
34
- pipeline_state, review_findings, review_panel, workflow_run, task_context)
34
+ pipeline_state, review_findings, review_panel, workflow_run, task_context, model_policy)
35
35
  from scripts import check_handoffs as contracts
36
36
  except ImportError: # bundled copy inside a skill's scripts directory
37
37
  import _common
@@ -45,6 +45,7 @@ except ImportError: # bundled copy inside a skill's scripts directory
45
45
  import review_findings
46
46
  import review_panel
47
47
  import workflow_run
48
+ import model_policy
48
49
 
49
50
  if os.name == "nt":
50
51
  import msvcrt
@@ -71,7 +72,7 @@ class DriverStop(RuntimeError):
71
72
 
72
73
  STOP_ERRORS = (DriverStop, isolation.IsolationError, build_state.BuildStateError,
73
74
  contracts.HandoffError, review_findings.ReviewFindingsError,
74
- pipeline_state.PipelineStateError, archive_milestone.ArchiveError)
75
+ pipeline_state.PipelineStateError, archive_milestone.ArchiveError, model_policy.PolicyError)
75
76
  RECOVERY_BLOCKED = "recovery blocked"
76
77
  # The stops the build contract answers with build/blocked when the round raises them (steps 1 and 2);
77
78
  # the same stops from review, panel, or skeptics stay with the parent.
@@ -251,10 +252,81 @@ def brief_text(state: Dict[str, object], role_brief: Path, task_template: Path)
251
252
  return "\n".join(lines) + "\n"
252
253
 
253
254
 
255
+ def selected_command(state: Dict[str, object], options: argparse.Namespace, command: str):
256
+ """Shared selection for every CLI child; legacy unconfigured commands remain unchanged."""
257
+ primary = Path(getattr(options, 'repo', state['worktree']))
258
+ project_dir = getattr(options, 'project_dir', '.project')
259
+ overrides = (model_policy.task_settings(Path(str(state['worktree'])) / str(state['task_file']))
260
+ if state.get('task_file') else {})
261
+ for field in model_policy.FIELDS:
262
+ value = getattr(options, field, None)
263
+ if value is not None:
264
+ if field in overrides and overrides[field] != value:
265
+ raise model_policy.PolicyError('dispatch choice conflicts with the task contract')
266
+ overrides[field] = value
267
+ overrides_path = getattr(options, 'model_overrides', None)
268
+ if overrides_path:
269
+ assignments = model_policy.read_json(overrides_path)
270
+ for assignment, values in assignments.items():
271
+ model_policy.settings(values, f'assignment {assignment}')
272
+ for field, value in assignments.get(str(state['task_id']), {}).items():
273
+ if field in overrides and overrides[field] != value:
274
+ raise model_policy.PolicyError('assignment overrides conflict with explicit task/dispatch settings')
275
+ overrides[field] = value
276
+ caps_path = getattr(options, 'model_capabilities', None)
277
+ previous = state.get('model_selection')
278
+ if previous is None and state.get('command'):
279
+ if overrides:
280
+ raise model_policy.PolicyError('legacy assignment requires a new explicit assignment for model controls')
281
+ return list(state['command']), None
282
+ if caps_path is None:
283
+ if overrides or previous or model_policy.policy_path(primary, project_dir).exists():
284
+ raise model_policy.PolicyError('model policy requires --model-capabilities for this dispatch')
285
+ return shlex.split(command), None
286
+ caps = model_policy.read_json(caps_path)
287
+ action = getattr(options, 'action', 'round')
288
+ role = {'round': 'coder', 'review': 'reviewer', 'panel': 'review_panel',
289
+ 'skeptics': 'skeptic'}.get(action, action)
290
+ if state.get('family'):
291
+ role = 'review_panel'
292
+ selection = model_policy.resolve(primary, project_dir, role, caps, overrides, previous)
293
+ if state.get('family'):
294
+ if selection['selected']['model'] == 'inherit' and selection['sources']['model'] == 'default':
295
+ # The panel roster owns the default model, not parent inheritance.
296
+ selection = model_policy.resolve(primary, project_dir, role, caps,
297
+ dict(overrides, model=state['slug']), previous)
298
+ model_policy.validate_panel(selection, state['family'], state.get('excluded_families', []))
299
+ state['slug'] = selection['selected']['model']
300
+ return model_policy.command_args(shlex.split(command), selection), selection
301
+
302
+
303
+ def retain_selection(root: Path, state: Dict[str, object]) -> Dict[str, object]:
304
+ state = dict(state)
305
+ task_dir = root / str(state['task_id'])
306
+ if not state.get('model_selection'):
307
+ attempts = sorted(task_dir.glob('attempt-*/state.json'), key=lambda path: attempt_number(path.parent))
308
+ if attempts:
309
+ prior = read_attempt(attempts[-1])
310
+ if child_running(prior):
311
+ raise DriverStop('cannot redispatch while the previous child is active')
312
+ if prior.get('model_selection'):
313
+ state['model_selection'] = prior['model_selection']
314
+ else:
315
+ state['command'] = prior['command']
316
+ reassignment = task_dir / 'model-reassignment.json'
317
+ if reassignment.exists():
318
+ state['model_selection'] = load_state(reassignment)['selection']
319
+ return state
320
+
321
+
254
322
  def spawn(root: Path, state: Dict[str, object], options: argparse.Namespace,
255
323
  brief: Callable[[Dict[str, object]], str], command: Optional[str] = None) -> Dict[str, object]:
256
324
  """Create the next attempt record, write the brief the callback builds, and start the wrapper."""
325
+ if child_running(state):
326
+ raise DriverStop('cannot redispatch while the previous child is active')
327
+ state = retain_selection(root, state)
257
328
  task_dir = root / str(state["task_id"])
329
+ argv, selection = selected_command(state, options, command or options.child_command)
258
330
  attempt = max((attempt_number(path) for path in task_dir.glob("attempt-*")), default=0) + 1
259
331
  attempt_dir = task_dir / f"attempt-{attempt}"
260
332
  attempt_dir.mkdir(parents=True)
@@ -262,7 +334,9 @@ def spawn(root: Path, state: Dict[str, object], options: argparse.Namespace,
262
334
  "usage_recorded")
263
335
  state = {key: value for key, value in state.items()
264
336
  if not key.startswith("_") and key not in stale}
265
- state.update({"attempt": attempt, "command": shlex.split(command or options.child_command),
337
+ if selection is not None:
338
+ state['model_selection'] = selection
339
+ state.update({"attempt": attempt, "command": argv,
266
340
  "child_timeout": options.child_timeout, "outcome": None,
267
341
  "dispatched_at": now(), "origin": "question" if state.get("answered") else "dispatch"})
268
342
  state_path = attempt_dir / "state.json"
@@ -397,7 +471,8 @@ def finish_task(primary: Path, state: Dict[str, object]) -> Dict[str, object]:
397
471
 
398
472
  def summary(state: Dict[str, object]) -> Dict[str, object]:
399
473
  return {key: state.get(key) for key in
400
- ("task_id", "attempt", "mode", "worktree", "wave", "cycle", "verify_heavy", "dispatched_at")}
474
+ ("task_id", "attempt", "mode", "worktree", "wave", "cycle", "verify_heavy", "dispatched_at",
475
+ "model_selection")}
401
476
 
402
477
 
403
478
  def recover_report(primary: Path, project_dir: str, receipt: Dict[str, object]) -> Dict[str, object]:
@@ -558,14 +633,23 @@ class Round:
558
633
  def settle(self) -> bool:
559
634
  """Classify every exited child. Returns True when a landing or redispatch changed the round."""
560
635
  changed = False
636
+ rejected = {item['task_id'] for item in self.receipt['blocked']
637
+ if item.get('code') == 'model-selection'}
561
638
  for state in latest_states(self.root):
562
639
  if state.get("wave") != self.receipt["wave"]:
563
640
  continue
564
641
  outcome = state.get("outcome")
565
642
  if outcome == "question":
566
643
  if state.get("answered"):
644
+ if state['task_id'] in rejected:
645
+ continue
567
646
  self.admit(str(state["task_id"]))
568
- self.launch(dict(state, answered=True))
647
+ try:
648
+ self.launch(dict(state, answered=True))
649
+ except model_policy.PolicyError as error:
650
+ self.receipt['blocked'].append({'task_id': state['task_id'], 'reason': str(error),
651
+ 'code': 'model-selection'})
652
+ continue
569
653
  update_state(state, outcome="redispatched")
570
654
  changed = True
571
655
  else:
@@ -690,23 +774,33 @@ class Round:
690
774
  heavy_busy = any(state.get("verify_heavy") for state in in_flight)
691
775
  capacity = self.options.capacity
692
776
  selected = []
777
+ rejected = {item["task_id"] for item in self.receipt["blocked"]
778
+ if item.get("code") == "model-selection"}
693
779
  for task in ready["ready"]:
694
780
  if capacity is not None and len(in_flight) + len(selected) >= capacity:
695
781
  break
696
782
  if task["wave"] != self.receipt["wave"]:
697
783
  continue
698
- if task["id"] in active_ids or (task["verify_heavy"] and heavy_busy):
784
+ if task["id"] in active_ids | rejected or (task["verify_heavy"] and heavy_busy):
699
785
  continue
700
- heavy_busy = heavy_busy or task["verify_heavy"]
701
- selected.append(task)
702
- if not selected:
703
- return False
704
- for task in selected:
705
786
  used = attempts_used(self.root, task["id"])
706
787
  if used >= self.options.max_attempts:
707
788
  raise DriverStop(f"task {task['id']} reached the attempt limit ({self.options.max_attempts}) "
708
789
  "for this milestone; a person must rule", task=task["id"], attempts=used)
709
790
  self.admit(task["id"])
791
+ candidate = retain_selection(self.root, {'task_id': task['id'], 'task_file': task['task_file'],
792
+ 'worktree': str(self.primary)})
793
+ try:
794
+ _, selection = selected_command(candidate, self.options, self.options.child_command)
795
+ except model_policy.PolicyError as error:
796
+ self.receipt['blocked'].append({'task_id': task['id'], 'reason': str(error),
797
+ 'code': 'model-selection'})
798
+ continue
799
+ task['model_selection'] = selection
800
+ heavy_busy = heavy_busy or task['verify_heavy']
801
+ selected.append(task)
802
+ if not selected:
803
+ return False
710
804
  head = isolation.current_sha(self.primary)
711
805
  self.runner("lint-round", head)
712
806
  round_size = len(selected) + len(in_flight)
@@ -721,7 +815,8 @@ class Round:
721
815
  "files": task["files"], "wave": task["wave"],
722
816
  "verify_heavy": task["verify_heavy"], "base": head, "worktree": str(worktree),
723
817
  "task_branch": isolate["task_branch"], "mode": isolate["mode"],
724
- "sidecar": sidecar, "answered": False})
818
+ "sidecar": sidecar, "answered": False,
819
+ "model_selection": task['model_selection']})
725
820
  return False
726
821
 
727
822
  def runner(self, action: str, head: str, task_id: Optional[str] = None,
@@ -738,7 +833,8 @@ class Round:
738
833
  while True:
739
834
  self.receipt["in_flight"] = []
740
835
  changed = self.settle() or changed
741
- if self.receipt["questions"] or self.receipt["blocked"]:
836
+ if self.receipt["questions"] or any(
837
+ item.get("code") != "model-selection" for item in self.receipt["blocked"]):
742
838
  self.receipt["status"] = "question" if self.receipt["questions"] else "blocked"
743
839
  return self.receipt
744
840
  # ready is expensive and its inputs only move when a landing or redispatch happened
@@ -748,7 +844,8 @@ class Round:
748
844
  changed = False
749
845
  live = self.in_flight_states()
750
846
  if deadline is None or time.monotonic() >= deadline or not live:
751
- self.receipt["status"] = "in-flight" if live else "done"
847
+ self.receipt["status"] = ("blocked" if self.receipt["blocked"]
848
+ else "in-flight" if live else "done")
752
849
  return self.receipt
753
850
  time.sleep(POLL_SECONDS)
754
851
  except STOP_ERRORS as error:
@@ -803,6 +900,15 @@ def spawn_in_sidecar(root: Path, primary: Path, receipt: Dict[str, object], opti
803
900
  name: str, state: Dict[str, object], brief: Callable[[Dict[str, object]], str],
804
901
  command: Optional[str] = None) -> None:
805
902
  """Cut a verify sidecar at the state's base and start one child in it."""
903
+ candidate = retain_selection(root, dict(state, worktree=str(primary)))
904
+ try:
905
+ _, selection = selected_command(candidate, options, command or options.child_command)
906
+ except model_policy.PolicyError as error:
907
+ receipt['blocked'].append({'task_id': state['task_id'], 'reason': str(error),
908
+ 'code': 'model-selection'})
909
+ return
910
+ if selection is not None:
911
+ state = dict(state, model_selection=selection)
806
912
  sidecar = isolation.isolate_verify(primary, str(state["base"]), name)
807
913
  receipt["steps"].append({"script": "isolation.py isolate-verify", "result": sidecar})
808
914
  spawn(root, dict(state, worktree=str(sidecar["worktree"]), branch=str(sidecar["branch"])), options, brief,
@@ -874,9 +980,21 @@ class Review:
874
980
  return wave_tasks(self.primary, self.project_dir, self.wave, self.receipt)
875
981
 
876
982
  def dispatch(self, lenses: Dict[str, str], depth: str, base: Optional[str] = None) -> None:
877
- dirty = sorted(isolation.uncommitted_paths(self.primary))
878
- if dirty: # the review base must include every bookkeeping change
879
- raise DriverStop("review collection base requires a clean primary", paths=dirty)
983
+ dirty = isolation.uncommitted_paths(self.primary)
984
+ if base is not None:
985
+ paths = review_findings.review_paths(self.primary / self.project_dir, self.wave, self.cycle, depth)
986
+ for key, state in self.current_states(list(paths)).items():
987
+ if state.get('outcome') != 'collected':
988
+ continue
989
+ path = paths[key]
990
+ relative = path.relative_to(self.primary).as_posix()
991
+ if (state.get('base') != base or state.get('relative') != relative
992
+ or not path.is_file()
993
+ or hashlib.sha256(path.read_bytes()).hexdigest() != state.get('validated_sha256')):
994
+ raise DriverStop('canonical review artifact changed after collection', path=relative)
995
+ dirty.discard(relative)
996
+ if dirty:
997
+ raise DriverStop("review collection base requires a clean primary", paths=sorted(dirty))
880
998
  tasks = self.wave_tasks()
881
999
  plan_text = (self.primary / self.project_dir / "plan/PLAN.md").read_text(encoding="utf-8")
882
1000
  intent = (self.primary / self.project_dir / "intent/INTENT.md").read_text(encoding="utf-8")
@@ -957,7 +1075,8 @@ class Review:
957
1075
 
958
1076
  def settle(self, states: Dict[str, Dict[str, object]]) -> None:
959
1077
  """Advance every lens and render the receipt from the records; settle is their only writer."""
960
- self.receipt["in_flight"], self.receipt["blocked"], self.receipt["lenses"] = [], [], {}
1078
+ rejected = [item for item in self.receipt['blocked'] if item.get('code') == 'model-selection']
1079
+ self.receipt["in_flight"], self.receipt["blocked"], self.receipt["lenses"] = [], rejected, {}
961
1080
  for key, state in states.items():
962
1081
  if not advance(state, self.collect):
963
1082
  self.receipt["in_flight"].append(summary(state))
@@ -1037,7 +1156,10 @@ class Review:
1037
1156
  states = self.current_states(list(lenses))
1038
1157
  while True:
1039
1158
  self.settle(states)
1040
- if set(states) == set(lenses) and all(
1159
+ settled = {state['task_id'] for state in states.values()}
1160
+ settled.update(item['task_id'] for item in self.receipt['blocked']
1161
+ if item.get('code') == 'model-selection')
1162
+ if settled == {self.lens_name(key) for key in lenses} and all(
1041
1163
  state.get("outcome") in ("collected", "blocked") for state in states.values()):
1042
1164
  self.conclude()
1043
1165
  return self.receipt
@@ -1102,7 +1224,8 @@ class Children:
1102
1224
  def poll(self, states: Dict[str, Dict[str, object]], deadline: Optional[float]) -> bool:
1103
1225
  """Advance every child; True once all are terminal, False when the deadline passes first."""
1104
1226
  while True:
1105
- self.receipt["in_flight"], self.receipt["blocked"], self.receipt[self.results] = [], [], {}
1227
+ rejected = [item for item in self.receipt['blocked'] if item.get('code') == 'model-selection']
1228
+ self.receipt["in_flight"], self.receipt["blocked"], self.receipt[self.results] = [], rejected, {}
1106
1229
  for key, state in states.items():
1107
1230
  if not advance(state, self.collect):
1108
1231
  self.receipt["in_flight"].append(summary(state))
@@ -1220,6 +1343,12 @@ class Panel(Children):
1220
1343
  else:
1221
1344
  if states:
1222
1345
  raise DriverStop("panel records exist without a persisted roster")
1346
+ excluded = {review_panel.family_of_slug(self.options.parent_slug or '')}
1347
+ for canonical in latest_states(records_root(self.primary) / 'reviews'):
1348
+ if canonical.get('wave') == self.wave and canonical.get('cycle') == self.cycle:
1349
+ model = canonical.get('model_selection', {}).get('effective_model')
1350
+ if model:
1351
+ excluded.add(review_panel.family_of_slug(model))
1223
1352
  arguments = ["resolve", "--plan", str(project / "plan/PLAN.md"),
1224
1353
  "--intent", str(project / "intent/INTENT.md"),
1225
1354
  "--advertised", self.options.advertised]
@@ -1227,6 +1356,8 @@ class Panel(Children):
1227
1356
  arguments += ["--parent-slug", self.options.parent_slug]
1228
1357
  if (project / "CHARTER.md").exists():
1229
1358
  arguments += ["--charter", str(project / "CHARTER.md")]
1359
+ for family in sorted(family for family in excluded if family):
1360
+ arguments += ['--exclude-family', family]
1230
1361
  resolved = helper_json(self.receipt, "review_panel.py", *arguments, cwd=self.primary)
1231
1362
  self.receipt["resolve"] = resolved
1232
1363
  if resolved["status"] == "off":
@@ -1259,6 +1390,21 @@ class Panel(Children):
1259
1390
  roster = {"families": [item["family"] for item in resolved["selected"]],
1260
1391
  "slugs": {item["family"]: item["slug"] for item in resolved["selected"]},
1261
1392
  "mode": resolved["mode"], "base": isolation.current_sha(self.primary)}
1393
+ roster['excluded_families'] = sorted(family for family in excluded if family)
1394
+ roster['selections'] = {}
1395
+ for family in roster['families']:
1396
+ candidate = {'task_id': self.family_name(family), 'family': family,
1397
+ 'slug': roster['slugs'][family], 'worktree': str(self.primary),
1398
+ 'excluded_families': roster['excluded_families']}
1399
+ command = self.options.child_command
1400
+ if not getattr(self.options, 'model_capabilities', None):
1401
+ command = command.replace('{model}', candidate['slug'])
1402
+ _, selection = selected_command(candidate, self.options, command)
1403
+ model_policy.validate_panel(selection or {'effective_model': candidate['slug']},
1404
+ family, roster['excluded_families'])
1405
+ if selection is not None:
1406
+ roster['selections'][family] = selection
1407
+ roster['slugs'][family] = candidate['slug']
1262
1408
  save_state(roster_path, roster)
1263
1409
  self.receipt["mode"] = roster["mode"]
1264
1410
  if set(states) - set(roster["families"]) or any(
@@ -1275,10 +1421,16 @@ class Panel(Children):
1275
1421
  state = {"task_id": self.family_name(family), "family": family, "slug": slug,
1276
1422
  "wave": self.wave, "cycle": self.cycle, "base": base, "mode": roster["mode"],
1277
1423
  "relative": self.relative(family)}
1424
+ if roster.get('selections', {}).get(family):
1425
+ state['model_selection'] = roster['selections'][family]
1426
+ state['excluded_families'] = roster.get('excluded_families', [])
1427
+ command = self.options.child_command
1428
+ if not getattr(self.options, 'model_capabilities', None):
1429
+ command = command.replace('{model}', slug)
1278
1430
  spawn_in_sidecar(self.root, self.primary, self.receipt, self.options,
1279
1431
  f"wave-{self.wave}-cycle-{self.cycle}-panel-{family}", state,
1280
1432
  lambda fresh: self.brief(fresh, tasks, depth, review),
1281
- command=self.options.child_command.replace("{model}", slug))
1433
+ command=command)
1282
1434
  states = self.states()
1283
1435
  if not self.poll(states, deadline):
1284
1436
  return self.receipt
@@ -1656,6 +1808,43 @@ def answer(primary: Path, task_id: str, text: str) -> Dict[str, object]:
1656
1808
  "next": "run `round` to redispatch the retained isolate"}
1657
1809
 
1658
1810
 
1811
+ def reassign_model(root: Path, task_id: str, options: argparse.Namespace) -> Dict[str, object]:
1812
+ states = [state for state in latest_states(root) if state['task_id'] == task_id]
1813
+ if not states:
1814
+ raise DriverStop('assignment has no recorded attempt')
1815
+ state = states[0]
1816
+ if child_running(state) or state.get('finished_at') is None:
1817
+ raise DriverStop('cannot reassign an active or unproven child; reconcile its lifecycle first')
1818
+ if state.get('family') or state.get('outcome') in ('landed', 'collected'):
1819
+ raise DriverStop('completed work or a pinned panel roster requires a new legal review/task assignment')
1820
+ if not options.ruling or not options.ruling.strip() or not options.model_capabilities:
1821
+ raise DriverStop('reassignment requires an owner ruling and advertised capabilities')
1822
+ old = state.get('model_selection')
1823
+ if old is None:
1824
+ raise DriverStop('legacy assignment has no proven model selection to reassign')
1825
+ path = root / task_id / 'model-reassignment.json'
1826
+ history = load_state(path).get('history', []) if path.exists() else []
1827
+ if path.exists():
1828
+ old = load_state(path)['selection']
1829
+ changes = {field: getattr(options, field) for field in model_policy.FIELDS
1830
+ if getattr(options, field) is not None}
1831
+ if not changes:
1832
+ raise DriverStop('reassignment requires an explicit model or effort')
1833
+ task = (model_policy.task_settings(Path(state['worktree']) / state['task_file'])
1834
+ if state.get('task_file') else {})
1835
+ if any(field in task and task[field] != value for field, value in changes.items()):
1836
+ raise DriverStop('reassignment conflicts with the approved task contract')
1837
+ selection = model_policy.resolve(options.repo, options.project_dir, old['role'],
1838
+ model_policy.read_json(options.model_capabilities),
1839
+ dict(old['selected'], **changes))
1840
+ for field in model_policy.FIELDS:
1841
+ if field not in changes:
1842
+ selection['sources'][field] = old['sources'][field]
1843
+ save_state(path, {'selection': selection, 'history': [*history, {
1844
+ 'selection': old, 'ruling': options.ruling, 'attempt': state['attempt']} ]})
1845
+ return {'status': 'reassigned', 'task': task_id, 'selection': selection, 'record': str(path)}
1846
+
1847
+
1659
1848
  def require_files(parser: argparse.ArgumentParser, arguments: argparse.Namespace, *names: str) -> None:
1660
1849
  for name in names:
1661
1850
  value = getattr(arguments, name)
@@ -1708,6 +1897,13 @@ def main(argv=None) -> int:
1708
1897
  cycle_child.add_argument("--wait", type=float, help="seconds to keep polling before returning")
1709
1898
  cycle_child.add_argument("--child-timeout", type=float, help="per-child wall clock in seconds")
1710
1899
  cycle_child.add_argument("--role-brief", type=Path, default=default_resource("references/reviewer.md"))
1900
+ for child in (round_parser, cycle_child):
1901
+ child.add_argument('--model-capabilities', type=Path,
1902
+ help='advertised model/effort controls; required with project model policy')
1903
+ cycle_child.add_argument('--model', help='explicit model override for this review assignment')
1904
+ cycle_child.add_argument('--effort', help='explicit effort override for this review assignment')
1905
+ cycle_child.add_argument('--model-overrides', type=Path,
1906
+ help='JSON mapping logical assignment names to explicit model/effort overrides')
1711
1907
  review_parser = commands.add_parser("review", parents=[cycle_child],
1712
1908
  help="run one wave review cycle at full or deep depth")
1713
1909
  review_parser.add_argument("--repair-evidence", type=Path,
@@ -1741,6 +1937,14 @@ def main(argv=None) -> int:
1741
1937
  answer_parser.add_argument("--answer", required=True)
1742
1938
  status_parser = commands.add_parser("status", help="list dispatch records")
1743
1939
  status_parser.add_argument("--repo", type=Path, required=True)
1940
+ model_parser = commands.add_parser('reassign-model', help='record an explicit model reassignment')
1941
+ model_parser.add_argument('--repo', type=Path, required=True)
1942
+ model_parser.add_argument('--project-dir', default='.project')
1943
+ model_parser.add_argument('--task-id', required=True)
1944
+ model_parser.add_argument('--model-capabilities', type=Path, required=True)
1945
+ model_parser.add_argument('--model')
1946
+ model_parser.add_argument('--effort')
1947
+ model_parser.add_argument('--ruling', required=True)
1744
1948
  child_parser = commands.add_parser("_child", help=argparse.SUPPRESS)
1745
1949
  child_parser.add_argument("--state", type=Path, required=True)
1746
1950
  arguments = parser.parse_args(argv)
@@ -1797,6 +2001,11 @@ def main(argv=None) -> int:
1797
2001
  result = {**current.receipt, "status": "landed" if current.receipt["landed"] else "blocked"}
1798
2002
  elif arguments.action == "answer":
1799
2003
  result = answer(primary, arguments.task_id, arguments.answer)
2004
+ elif arguments.action == 'reassign-model':
2005
+ roots = [records_root(primary), *(records_root(primary) / name
2006
+ for name in ('reviews', 'panels', 'skeptics'))]
2007
+ root = next((root for root in roots if (root / arguments.task_id).is_dir()), roots[0])
2008
+ result = reassign_model(root, arguments.task_id, arguments)
1800
2009
  else:
1801
2010
  result = {"status": "ok", "dispatches": [
1802
2011
  {key: value for key, value in state.items() if key not in ("_path", "command")}