@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
@@ -31,19 +31,21 @@ 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)
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
38
38
  import archive_milestone
39
39
  import build_state
40
40
  import check_handoffs as contracts
41
+ import task_context
41
42
  import discussion_validate
42
43
  import isolation
43
44
  import pipeline_state
44
45
  import review_findings
45
46
  import review_panel
46
47
  import workflow_run
48
+ import model_policy
47
49
 
48
50
  if os.name == "nt":
49
51
  import msvcrt
@@ -70,7 +72,7 @@ class DriverStop(RuntimeError):
70
72
 
71
73
  STOP_ERRORS = (DriverStop, isolation.IsolationError, build_state.BuildStateError,
72
74
  contracts.HandoffError, review_findings.ReviewFindingsError,
73
- pipeline_state.PipelineStateError, archive_milestone.ArchiveError)
75
+ pipeline_state.PipelineStateError, archive_milestone.ArchiveError, model_policy.PolicyError)
74
76
  RECOVERY_BLOCKED = "recovery blocked"
75
77
  # The stops the build contract answers with build/blocked when the round raises them (steps 1 and 2);
76
78
  # the same stops from review, panel, or skeptics stay with the parent.
@@ -94,7 +96,7 @@ def milestone_slug(primary: Path) -> str:
94
96
 
95
97
 
96
98
  def records_root(primary: Path) -> Path:
97
- return isolation.common_git_dir(primary) / "gsd-path" / "dispatch" / milestone_slug(primary)
99
+ return pipeline_state._build_recovery().records_root(primary)
98
100
 
99
101
 
100
102
  def budget_ledger(primary: Path) -> Path:
@@ -246,13 +248,85 @@ def brief_text(state: Dict[str, object], role_brief: Path, task_template: Path)
246
248
  if state.get("answered"):
247
249
  lines.append(f"The task Log now records an `{ANSWER_MARK}` to your earlier question; "
248
250
  "continue from it.")
251
+ lines.append(task_context.render(worktree, worktree / str(state['task_file'])))
249
252
  return "\n".join(lines) + "\n"
250
253
 
251
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
+
252
322
  def spawn(root: Path, state: Dict[str, object], options: argparse.Namespace,
253
323
  brief: Callable[[Dict[str, object]], str], command: Optional[str] = None) -> Dict[str, object]:
254
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)
255
328
  task_dir = root / str(state["task_id"])
329
+ argv, selection = selected_command(state, options, command or options.child_command)
256
330
  attempt = max((attempt_number(path) for path in task_dir.glob("attempt-*")), default=0) + 1
257
331
  attempt_dir = task_dir / f"attempt-{attempt}"
258
332
  attempt_dir.mkdir(parents=True)
@@ -260,7 +334,9 @@ def spawn(root: Path, state: Dict[str, object], options: argparse.Namespace,
260
334
  "usage_recorded")
261
335
  state = {key: value for key, value in state.items()
262
336
  if not key.startswith("_") and key not in stale}
263
- 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,
264
340
  "child_timeout": options.child_timeout, "outcome": None,
265
341
  "dispatched_at": now(), "origin": "question" if state.get("answered") else "dispatch"})
266
342
  state_path = attempt_dir / "state.json"
@@ -395,7 +471,8 @@ def finish_task(primary: Path, state: Dict[str, object]) -> Dict[str, object]:
395
471
 
396
472
  def summary(state: Dict[str, object]) -> Dict[str, object]:
397
473
  return {key: state.get(key) for key in
398
- ("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")}
399
476
 
400
477
 
401
478
  def recover_report(primary: Path, project_dir: str, receipt: Dict[str, object]) -> Dict[str, object]:
@@ -556,14 +633,23 @@ class Round:
556
633
  def settle(self) -> bool:
557
634
  """Classify every exited child. Returns True when a landing or redispatch changed the round."""
558
635
  changed = False
636
+ rejected = {item['task_id'] for item in self.receipt['blocked']
637
+ if item.get('code') == 'model-selection'}
559
638
  for state in latest_states(self.root):
560
639
  if state.get("wave") != self.receipt["wave"]:
561
640
  continue
562
641
  outcome = state.get("outcome")
563
642
  if outcome == "question":
564
643
  if state.get("answered"):
644
+ if state['task_id'] in rejected:
645
+ continue
565
646
  self.admit(str(state["task_id"]))
566
- 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
567
653
  update_state(state, outcome="redispatched")
568
654
  changed = True
569
655
  else:
@@ -688,23 +774,33 @@ class Round:
688
774
  heavy_busy = any(state.get("verify_heavy") for state in in_flight)
689
775
  capacity = self.options.capacity
690
776
  selected = []
777
+ rejected = {item["task_id"] for item in self.receipt["blocked"]
778
+ if item.get("code") == "model-selection"}
691
779
  for task in ready["ready"]:
692
780
  if capacity is not None and len(in_flight) + len(selected) >= capacity:
693
781
  break
694
782
  if task["wave"] != self.receipt["wave"]:
695
783
  continue
696
- 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):
697
785
  continue
698
- heavy_busy = heavy_busy or task["verify_heavy"]
699
- selected.append(task)
700
- if not selected:
701
- return False
702
- for task in selected:
703
786
  used = attempts_used(self.root, task["id"])
704
787
  if used >= self.options.max_attempts:
705
788
  raise DriverStop(f"task {task['id']} reached the attempt limit ({self.options.max_attempts}) "
706
789
  "for this milestone; a person must rule", task=task["id"], attempts=used)
707
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
708
804
  head = isolation.current_sha(self.primary)
709
805
  self.runner("lint-round", head)
710
806
  round_size = len(selected) + len(in_flight)
@@ -719,7 +815,8 @@ class Round:
719
815
  "files": task["files"], "wave": task["wave"],
720
816
  "verify_heavy": task["verify_heavy"], "base": head, "worktree": str(worktree),
721
817
  "task_branch": isolate["task_branch"], "mode": isolate["mode"],
722
- "sidecar": sidecar, "answered": False})
818
+ "sidecar": sidecar, "answered": False,
819
+ "model_selection": task['model_selection']})
723
820
  return False
724
821
 
725
822
  def runner(self, action: str, head: str, task_id: Optional[str] = None,
@@ -736,7 +833,8 @@ class Round:
736
833
  while True:
737
834
  self.receipt["in_flight"] = []
738
835
  changed = self.settle() or changed
739
- 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"]):
740
838
  self.receipt["status"] = "question" if self.receipt["questions"] else "blocked"
741
839
  return self.receipt
742
840
  # ready is expensive and its inputs only move when a landing or redispatch happened
@@ -746,7 +844,8 @@ class Round:
746
844
  changed = False
747
845
  live = self.in_flight_states()
748
846
  if deadline is None or time.monotonic() >= deadline or not live:
749
- 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")
750
849
  return self.receipt
751
850
  time.sleep(POLL_SECONDS)
752
851
  except STOP_ERRORS as error:
@@ -801,6 +900,15 @@ def spawn_in_sidecar(root: Path, primary: Path, receipt: Dict[str, object], opti
801
900
  name: str, state: Dict[str, object], brief: Callable[[Dict[str, object]], str],
802
901
  command: Optional[str] = None) -> None:
803
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)
804
912
  sidecar = isolation.isolate_verify(primary, str(state["base"]), name)
805
913
  receipt["steps"].append({"script": "isolation.py isolate-verify", "result": sidecar})
806
914
  spawn(root, dict(state, worktree=str(sidecar["worktree"]), branch=str(sidecar["branch"])), options, brief,
@@ -872,9 +980,21 @@ class Review:
872
980
  return wave_tasks(self.primary, self.project_dir, self.wave, self.receipt)
873
981
 
874
982
  def dispatch(self, lenses: Dict[str, str], depth: str, base: Optional[str] = None) -> None:
875
- dirty = sorted(isolation.uncommitted_paths(self.primary))
876
- if dirty: # the review base must include every bookkeeping change
877
- 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))
878
998
  tasks = self.wave_tasks()
879
999
  plan_text = (self.primary / self.project_dir / "plan/PLAN.md").read_text(encoding="utf-8")
880
1000
  intent = (self.primary / self.project_dir / "intent/INTENT.md").read_text(encoding="utf-8")
@@ -955,7 +1075,8 @@ class Review:
955
1075
 
956
1076
  def settle(self, states: Dict[str, Dict[str, object]]) -> None:
957
1077
  """Advance every lens and render the receipt from the records; settle is their only writer."""
958
- 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, {}
959
1080
  for key, state in states.items():
960
1081
  if not advance(state, self.collect):
961
1082
  self.receipt["in_flight"].append(summary(state))
@@ -1035,7 +1156,10 @@ class Review:
1035
1156
  states = self.current_states(list(lenses))
1036
1157
  while True:
1037
1158
  self.settle(states)
1038
- 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(
1039
1163
  state.get("outcome") in ("collected", "blocked") for state in states.values()):
1040
1164
  self.conclude()
1041
1165
  return self.receipt
@@ -1100,7 +1224,8 @@ class Children:
1100
1224
  def poll(self, states: Dict[str, Dict[str, object]], deadline: Optional[float]) -> bool:
1101
1225
  """Advance every child; True once all are terminal, False when the deadline passes first."""
1102
1226
  while True:
1103
- 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, {}
1104
1229
  for key, state in states.items():
1105
1230
  if not advance(state, self.collect):
1106
1231
  self.receipt["in_flight"].append(summary(state))
@@ -1218,6 +1343,12 @@ class Panel(Children):
1218
1343
  else:
1219
1344
  if states:
1220
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))
1221
1352
  arguments = ["resolve", "--plan", str(project / "plan/PLAN.md"),
1222
1353
  "--intent", str(project / "intent/INTENT.md"),
1223
1354
  "--advertised", self.options.advertised]
@@ -1225,6 +1356,8 @@ class Panel(Children):
1225
1356
  arguments += ["--parent-slug", self.options.parent_slug]
1226
1357
  if (project / "CHARTER.md").exists():
1227
1358
  arguments += ["--charter", str(project / "CHARTER.md")]
1359
+ for family in sorted(family for family in excluded if family):
1360
+ arguments += ['--exclude-family', family]
1228
1361
  resolved = helper_json(self.receipt, "review_panel.py", *arguments, cwd=self.primary)
1229
1362
  self.receipt["resolve"] = resolved
1230
1363
  if resolved["status"] == "off":
@@ -1257,6 +1390,21 @@ class Panel(Children):
1257
1390
  roster = {"families": [item["family"] for item in resolved["selected"]],
1258
1391
  "slugs": {item["family"]: item["slug"] for item in resolved["selected"]},
1259
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']
1260
1408
  save_state(roster_path, roster)
1261
1409
  self.receipt["mode"] = roster["mode"]
1262
1410
  if set(states) - set(roster["families"]) or any(
@@ -1273,10 +1421,16 @@ class Panel(Children):
1273
1421
  state = {"task_id": self.family_name(family), "family": family, "slug": slug,
1274
1422
  "wave": self.wave, "cycle": self.cycle, "base": base, "mode": roster["mode"],
1275
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)
1276
1430
  spawn_in_sidecar(self.root, self.primary, self.receipt, self.options,
1277
1431
  f"wave-{self.wave}-cycle-{self.cycle}-panel-{family}", state,
1278
1432
  lambda fresh: self.brief(fresh, tasks, depth, review),
1279
- command=self.options.child_command.replace("{model}", slug))
1433
+ command=command)
1280
1434
  states = self.states()
1281
1435
  if not self.poll(states, deadline):
1282
1436
  return self.receipt
@@ -1654,6 +1808,43 @@ def answer(primary: Path, task_id: str, text: str) -> Dict[str, object]:
1654
1808
  "next": "run `round` to redispatch the retained isolate"}
1655
1809
 
1656
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
+
1657
1848
  def require_files(parser: argparse.ArgumentParser, arguments: argparse.Namespace, *names: str) -> None:
1658
1849
  for name in names:
1659
1850
  value = getattr(arguments, name)
@@ -1706,6 +1897,13 @@ def main(argv=None) -> int:
1706
1897
  cycle_child.add_argument("--wait", type=float, help="seconds to keep polling before returning")
1707
1898
  cycle_child.add_argument("--child-timeout", type=float, help="per-child wall clock in seconds")
1708
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')
1709
1907
  review_parser = commands.add_parser("review", parents=[cycle_child],
1710
1908
  help="run one wave review cycle at full or deep depth")
1711
1909
  review_parser.add_argument("--repair-evidence", type=Path,
@@ -1739,6 +1937,14 @@ def main(argv=None) -> int:
1739
1937
  answer_parser.add_argument("--answer", required=True)
1740
1938
  status_parser = commands.add_parser("status", help="list dispatch records")
1741
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)
1742
1948
  child_parser = commands.add_parser("_child", help=argparse.SUPPRESS)
1743
1949
  child_parser.add_argument("--state", type=Path, required=True)
1744
1950
  arguments = parser.parse_args(argv)
@@ -1795,6 +2001,11 @@ def main(argv=None) -> int:
1795
2001
  result = {**current.receipt, "status": "landed" if current.receipt["landed"] else "blocked"}
1796
2002
  elif arguments.action == "answer":
1797
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)
1798
2009
  else:
1799
2010
  result = {"status": "ok", "dispatches": [
1800
2011
  {key: value for key, value in state.items() if key not in ("_path", "command")}