@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
@@ -17,6 +17,9 @@ import os
17
17
  import re
18
18
  import shutil
19
19
  import sys
20
+
21
+ # Runtime helpers must not modify their immutable installation.
22
+ sys.dont_write_bytecode = True
20
23
  import tempfile
21
24
  from pathlib import Path, PurePosixPath
22
25
  from typing import Optional, Sequence
@@ -27,18 +30,20 @@ def _load_pipeline_modules():
27
30
  import isolation
28
31
  import pipeline_git
29
32
  import pipeline_state
30
- return archive_milestone, isolation, pipeline_git, pipeline_state
33
+ import state_checkpoint
34
+ return archive_milestone, isolation, pipeline_git, pipeline_state, state_checkpoint
31
35
  except ModuleNotFoundError as error:
32
36
  if error.name not in {
33
37
  "archive_milestone",
34
38
  "isolation",
35
39
  "pipeline_git",
36
40
  "pipeline_state",
41
+ "state_checkpoint",
37
42
  }:
38
43
  raise
39
44
  try:
40
- from scripts import archive_milestone, isolation, pipeline_git, pipeline_state
41
- return archive_milestone, isolation, pipeline_git, pipeline_state
45
+ from scripts import archive_milestone, isolation, pipeline_git, pipeline_state, state_checkpoint
46
+ return archive_milestone, isolation, pipeline_git, pipeline_state, state_checkpoint
42
47
  except ModuleNotFoundError as error:
43
48
  if error.name not in {
44
49
  "scripts",
@@ -46,6 +51,7 @@ def _load_pipeline_modules():
46
51
  "scripts.isolation",
47
52
  "scripts.pipeline_git",
48
53
  "scripts.pipeline_state",
54
+ "scripts.state_checkpoint",
49
55
  }:
50
56
  raise
51
57
  shared = Path(__file__).resolve().parents[2] / "gsd-path" / "scripts"
@@ -54,16 +60,17 @@ def _load_pipeline_modules():
54
60
  import isolation
55
61
  import pipeline_git
56
62
  import pipeline_state
57
- return archive_milestone, isolation, pipeline_git, pipeline_state
63
+ import state_checkpoint
64
+ return archive_milestone, isolation, pipeline_git, pipeline_state, state_checkpoint
58
65
 
59
66
 
60
- archive_milestone, isolation, pipeline_git, pipeline_state = _load_pipeline_modules()
67
+ archive_milestone, isolation, pipeline_git, pipeline_state, state_checkpoint = _load_pipeline_modules()
61
68
  ArchiveError = archive_milestone.ArchiveError
62
69
  IsolationError = isolation.IsolationError
63
70
  PLAN_APPROVAL_SUBJECT = pipeline_state.PLAN_APPROVAL_SUBJECT
64
71
  PipelineStateError = pipeline_state.PipelineStateError
65
- _activate_roadmap_milestone = pipeline_state._activate_roadmap_milestone
66
- _approval_details = pipeline_state._approval_details
72
+ _activate_roadmap_milestone = state_checkpoint._activate_roadmap_milestone
73
+ _approval_details = state_checkpoint._approval_details
67
74
  _commit_subject_body = pipeline_state._commit_subject_body
68
75
  _optional_rev = pipeline_state._optional_rev
69
76
  _render_transition = pipeline_state._render_transition
@@ -0,0 +1,564 @@
1
+ #!/usr/bin/env python3
2
+ # gsd-path project runtime
3
+ """Resolve optional GSD Path cross-model review panels without model reasoning.
4
+
5
+ The orchestrator inspects the host's advertised child-model slugs and passes
6
+ them here. This helper parses PLAN.md / INTENT.md / CHARTER.md (PLAN wins,
7
+ then INTENT, then CHARTER, else off), groups slugs into families, skips the
8
+ parent family, caps the panel at three, and merges family artifacts. It never
9
+ probes a host or overrides the canonical inherit reviewer.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import argparse
15
+ import json
16
+ import re
17
+ import sys
18
+
19
+ # Runtime helpers must not modify their immutable installation.
20
+ sys.dont_write_bytecode = True
21
+ from pathlib import Path
22
+ from typing import Iterable, Optional, Sequence
23
+
24
+
25
+ KNOWN_FAMILIES = (
26
+ "claude",
27
+ "gpt",
28
+ "grok",
29
+ "composer",
30
+ "gemini",
31
+ "deepseek",
32
+ "kimi",
33
+ "qwen",
34
+ )
35
+ FAMILY_ALIASES = {
36
+ "claude": "claude",
37
+ "opus": "claude",
38
+ "sonnet": "claude",
39
+ "haiku": "claude",
40
+ "gpt": "gpt",
41
+ "openai": "gpt",
42
+ "grok": "grok",
43
+ "composer": "composer",
44
+ "gemini": "gemini",
45
+ "deepseek": "deepseek",
46
+ "kimi": "kimi",
47
+ "moonshot": "kimi",
48
+ "qwen": "qwen",
49
+ }
50
+ MAX_FAMILIES = 3
51
+ PLAN_LINE = re.compile(
52
+ r"^-\s*review_panel:\s*(\S.*?)\s*(?:<!--.*-->)?\s*$", re.MULTILINE
53
+ )
54
+ INTENT_LINE = re.compile(
55
+ r"^Review panel:\s*(\S.*?)\s*(?:<!--.*-->)?\s*$", re.MULTILINE
56
+ )
57
+ VERSION_NUMBERS = re.compile(r"\d+")
58
+ TOKEN_SPLIT = re.compile(r"[^a-z0-9]+")
59
+ FINDING_BLOCK = re.compile(
60
+ r"^###\s+(?P<id>F\d+)\s*$"
61
+ r"(?P<body>.*?)(?=^###\s+F\d+\s*$|\Z)",
62
+ re.MULTILINE | re.DOTALL,
63
+ )
64
+ FIELD_LINE = re.compile(r"^-\s*(?P<key>[A-Za-z][A-Za-z ]*):\s*(?P<value>.*)\s*$")
65
+
66
+
67
+ class ReviewPanelError(RuntimeError):
68
+ pass
69
+
70
+
71
+ def _first_token(value: str) -> str:
72
+ return value.strip().split()[0].strip().strip("`")
73
+
74
+
75
+ def _split_families(value: str) -> tuple[str, ...]:
76
+ tokens = [token.strip().casefold() for token in value.split(",") if token.strip()]
77
+ if not tokens:
78
+ raise ReviewPanelError("review_panel named list is empty")
79
+ unknown = [token for token in tokens if token not in KNOWN_FAMILIES]
80
+ if unknown:
81
+ raise ReviewPanelError(
82
+ f"unknown review_panel families: {', '.join(unknown)}"
83
+ )
84
+ seen = []
85
+ for token in tokens:
86
+ if token not in seen:
87
+ seen.append(token)
88
+ if len(seen) > MAX_FAMILIES:
89
+ raise ReviewPanelError(
90
+ f"review_panel names at most {MAX_FAMILIES} families"
91
+ )
92
+ return tuple(seen)
93
+
94
+
95
+ def parse_review_panel_value(raw: str) -> dict:
96
+ value = raw.strip()
97
+ if not value:
98
+ raise ReviewPanelError("review_panel value is empty")
99
+ token = _first_token(value).casefold()
100
+ if token == "off":
101
+ return {"mode": "off", "families": ()}
102
+ if token == "detected":
103
+ return {"mode": "detected", "families": ()}
104
+ families = _split_families(value)
105
+ return {"mode": "named", "families": families}
106
+
107
+
108
+ def extract_config_value(text: str, pattern: re.Pattern[str], source: str) -> Optional[str]:
109
+ matches = pattern.findall(text)
110
+ if not matches:
111
+ return None
112
+ if len(matches) > 1:
113
+ raise ReviewPanelError(f"{source} has multiple review_panel values")
114
+ return matches[0]
115
+
116
+
117
+ def parse_sources(
118
+ plan_text: Optional[str],
119
+ intent_text: Optional[str],
120
+ charter_text: Optional[str] = None,
121
+ ) -> dict:
122
+ if plan_text is not None:
123
+ plan_value = extract_config_value(plan_text, PLAN_LINE, "PLAN.md")
124
+ if plan_value is not None:
125
+ parsed = parse_review_panel_value(plan_value)
126
+ parsed["source"] = "plan"
127
+ return parsed
128
+ if intent_text is not None:
129
+ intent_value = extract_config_value(intent_text, INTENT_LINE, "INTENT.md")
130
+ if intent_value is not None:
131
+ parsed = parse_review_panel_value(intent_value)
132
+ parsed["source"] = "intent"
133
+ return parsed
134
+ if charter_text is not None:
135
+ charter_value = extract_config_value(charter_text, INTENT_LINE, "CHARTER.md")
136
+ if charter_value is not None:
137
+ parsed = parse_review_panel_value(charter_value)
138
+ parsed["source"] = "charter"
139
+ return parsed
140
+ return {"mode": "off", "families": (), "source": "default"}
141
+
142
+
143
+ def read_optional(path: Optional[Path]) -> Optional[str]:
144
+ if path is None:
145
+ return None
146
+ resolved = path.resolve()
147
+ if resolved.is_symlink() or not resolved.is_file():
148
+ raise ReviewPanelError(f"path must be a real file: {resolved}")
149
+ return resolved.read_text(encoding="utf-8")
150
+
151
+
152
+ def parse_files(
153
+ plan: Optional[Path],
154
+ intent: Optional[Path],
155
+ charter: Optional[Path] = None,
156
+ ) -> dict:
157
+ return parse_sources(
158
+ read_optional(plan), read_optional(intent), read_optional(charter)
159
+ )
160
+
161
+
162
+ def plan_review_panel_enabled(plan_text: str) -> bool:
163
+ """Archive-safe enabled check: anything other than absent/off is on."""
164
+ try:
165
+ parsed = parse_sources(plan_text, None)
166
+ except ReviewPanelError:
167
+ return True
168
+ return parsed["mode"] != "off"
169
+
170
+
171
+ def family_of_slug(slug: str) -> Optional[str]:
172
+ normalized = slug.strip().casefold()
173
+ if not normalized or normalized == "inherit":
174
+ return None
175
+ tokens = TOKEN_SPLIT.split(normalized)
176
+ for token in tokens:
177
+ if token in FAMILY_ALIASES:
178
+ return FAMILY_ALIASES[token]
179
+ for token in tokens:
180
+ stem = token.rstrip("0123456789")
181
+ if stem and stem in FAMILY_ALIASES:
182
+ return FAMILY_ALIASES[stem]
183
+ return None
184
+
185
+
186
+ def version_key(slug: str) -> tuple[int, ...]:
187
+ numbers = tuple(int(part) for part in VERSION_NUMBERS.findall(slug))
188
+ return numbers if numbers else (0,)
189
+
190
+
191
+ def latest_slug(slugs: Sequence[str]) -> str:
192
+ return max(slugs, key=lambda slug: (version_key(slug), len(slug), slug))
193
+
194
+
195
+ def group_advertised(slugs: Iterable[str]) -> dict[str, list[str]]:
196
+ grouped: dict[str, list[str]] = {family: [] for family in KNOWN_FAMILIES}
197
+ for slug in slugs:
198
+ cleaned = slug.strip()
199
+ if not cleaned:
200
+ continue
201
+ family = family_of_slug(cleaned)
202
+ if family is None:
203
+ continue
204
+ if cleaned not in grouped[family]:
205
+ grouped[family].append(cleaned)
206
+ return {family: values for family, values in grouped.items() if values}
207
+
208
+
209
+ def split_csv(raw: Optional[str]) -> tuple[str, ...]:
210
+ if not raw:
211
+ return ()
212
+ return tuple(part.strip() for part in raw.split(",") if part.strip())
213
+
214
+
215
+ def resolve_panel(
216
+ config: dict,
217
+ advertised: Sequence[str],
218
+ parent_family: Optional[str] = None,
219
+ parent_slug: Optional[str] = None,
220
+ excluded_families: Sequence[str] = (),
221
+ ) -> dict:
222
+ inferred_parent = parent_family or (
223
+ family_of_slug(parent_slug) if parent_slug else None
224
+ )
225
+ grouped = group_advertised(advertised)
226
+ skipped = []
227
+ selected = []
228
+
229
+ if config["mode"] == "off":
230
+ return {
231
+ "mode": "off",
232
+ "status": "off",
233
+ "reason": "review_panel is off",
234
+ "parent_family": inferred_parent,
235
+ "selected": [],
236
+ "skipped": [],
237
+ "missing": [],
238
+ }
239
+
240
+ wanted = (
241
+ list(config["families"])
242
+ if config["mode"] == "named"
243
+ else [family for family in KNOWN_FAMILIES if family in grouped]
244
+ )
245
+ missing = [family for family in wanted if family not in grouped]
246
+ if config["mode"] == "named" and missing:
247
+ raise ReviewPanelError(
248
+ "named review_panel families are not advertised: " + ", ".join(missing)
249
+ )
250
+
251
+ if config["mode"] == "named" and set(wanted) & set(excluded_families):
252
+ raise ReviewPanelError("named review_panel must use an independent family")
253
+ for family in wanted:
254
+ if family in excluded_families:
255
+ skipped.append({"family": family, "reason": "excluded reviewer family"})
256
+ continue
257
+ if family == inferred_parent:
258
+ skipped.append({"family": family, "reason": "parent family"})
259
+ continue
260
+ slugs = grouped.get(family)
261
+ if not slugs:
262
+ skipped.append({"family": family, "reason": "not advertised"})
263
+ continue
264
+ selected.append({"family": family, "slug": latest_slug(slugs)})
265
+ if len(selected) >= MAX_FAMILIES:
266
+ overflow = wanted[wanted.index(family) + 1 :]
267
+ for extra in overflow:
268
+ if extra != inferred_parent and extra in grouped:
269
+ skipped.append({"family": extra, "reason": "cap"})
270
+ break
271
+
272
+ if selected:
273
+ return {
274
+ "mode": config["mode"],
275
+ "status": "ready",
276
+ "reason": "panel resolved",
277
+ "parent_family": inferred_parent,
278
+ "selected": selected,
279
+ "skipped": skipped,
280
+ "missing": missing if config["mode"] == "detected" else [],
281
+ }
282
+
283
+ if config["mode"] == "named":
284
+ raise ReviewPanelError(
285
+ "named review_panel would only repeat the parent family or is empty"
286
+ )
287
+ return {
288
+ "mode": config["mode"],
289
+ "status": "skipped",
290
+ "reason": "no advertised cross-model families",
291
+ "parent_family": inferred_parent,
292
+ "selected": [],
293
+ "skipped": skipped,
294
+ "missing": missing,
295
+ }
296
+
297
+
298
+ def _require_field(body: str, key: str, source: str) -> str:
299
+ for line in body.splitlines():
300
+ match = FIELD_LINE.fullmatch(line.strip())
301
+ if match and match.group("key").casefold() == key.casefold():
302
+ value = match.group("value").strip()
303
+ if not value:
304
+ raise ReviewPanelError(f"{source} has an empty {key} field")
305
+ return value
306
+ raise ReviewPanelError(f"{source} is missing the {key} field")
307
+
308
+
309
+ def parse_findings(text: str, source: str) -> list[dict]:
310
+ findings = []
311
+ for match in FINDING_BLOCK.finditer(text):
312
+ body = match.group("body")
313
+ severity = _require_field(body, "Severity", f"{source} {match.group('id')}").casefold()
314
+ kind = _require_field(body, "Kind", f"{source} {match.group('id')}").casefold()
315
+ criterion = _require_field(body, "Criterion", f"{source} {match.group('id')}")
316
+ evidence = _require_field(body, "Evidence", f"{source} {match.group('id')}")
317
+ found = _require_field(body, "Found", f"{source} {match.group('id')}")
318
+ fix = _require_field(body, "Fix direction", f"{source} {match.group('id')}")
319
+ if severity not in {"high", "medium", "low"}:
320
+ raise ReviewPanelError(f"{source} {match.group('id')} has an invalid Severity")
321
+ if kind not in {"criterion", "preference"}:
322
+ raise ReviewPanelError(f"{source} {match.group('id')} has an invalid Kind")
323
+ if kind == "criterion" and criterion.casefold() == "none":
324
+ raise ReviewPanelError(
325
+ f"{source} {match.group('id')} criterion findings must name a criterion"
326
+ )
327
+ findings.append(
328
+ {
329
+ "id": match.group("id"),
330
+ "severity": severity,
331
+ "kind": kind,
332
+ "criterion": criterion,
333
+ "evidence": evidence,
334
+ "found": found,
335
+ "fix": fix,
336
+ "source": source,
337
+ }
338
+ )
339
+ if not findings:
340
+ findings_heading = re.search(r"^## Findings\s*$", text, re.MULTILINE)
341
+ if findings_heading:
342
+ rest = text[findings_heading.end() :]
343
+ next_heading = re.search(r"^## ", rest, re.MULTILINE)
344
+ section = rest[: next_heading.start()] if next_heading else rest
345
+ if re.search(r"^-\s*none\s*$", section, re.MULTILINE):
346
+ return []
347
+ raise ReviewPanelError(f"{source} has no findings")
348
+ return findings
349
+
350
+
351
+ def parse_family_artifact(path: Path, expected_kind: str) -> dict:
352
+ if path.is_symlink() or not path.is_file():
353
+ raise ReviewPanelError(f"panel artifact must be a real file: {path}")
354
+ text = path.read_text(encoding="utf-8")
355
+ lines = text.splitlines()
356
+ if expected_kind == "plan":
357
+ if not lines or not lines[0].startswith("# Plan panel — "):
358
+ raise ReviewPanelError(f"{path.name} heading must start with '# Plan panel — '")
359
+ elif expected_kind == "wave":
360
+ if not lines or not lines[0].startswith("# Panel — wave "):
361
+ raise ReviewPanelError(f"{path.name} heading must start with '# Panel — wave '")
362
+ else:
363
+ raise ReviewPanelError(f"unknown panel kind: {expected_kind}")
364
+ family = _require_field(text, "Family", path.name).casefold()
365
+ model = _require_field(text, "Model", path.name)
366
+ if family not in KNOWN_FAMILIES:
367
+ raise ReviewPanelError(f"{path.name} has an unknown Family")
368
+ if "## Findings" not in text:
369
+ raise ReviewPanelError(f"{path.name} is missing ## Findings")
370
+ findings = parse_findings(text, path.name)
371
+ return {
372
+ "path": str(path),
373
+ "family": family,
374
+ "model": model,
375
+ "text": text.rstrip() + "\n",
376
+ "findings": findings,
377
+ }
378
+
379
+
380
+ def actionable(finding: dict) -> bool:
381
+ return finding["kind"] == "criterion" and finding["severity"] == "high"
382
+
383
+
384
+ def detect_conflicts(findings: Sequence[dict]) -> list[dict]:
385
+ by_criterion: dict[str, list[dict]] = {}
386
+ for finding in findings:
387
+ key = finding["criterion"].strip().casefold()
388
+ if key in {"", "none"}:
389
+ continue
390
+ by_criterion.setdefault(key, []).append(finding)
391
+ conflicts = []
392
+ for criterion, group in by_criterion.items():
393
+ severities = {item["severity"] for item in group}
394
+ # A conflict is families disagreeing on whether the criterion needs a
395
+ # fix; wording differences in Found are not conflicts.
396
+ if len({actionable(item) for item in group}) > 1:
397
+ conflicts.append(
398
+ {
399
+ "criterion": group[0]["criterion"],
400
+ "families": [item["source"] for item in group],
401
+ "severities": sorted(severities),
402
+ }
403
+ )
404
+ return conflicts
405
+
406
+
407
+ def render_finding(finding: dict) -> str:
408
+ return (
409
+ f"- {finding['severity']} / {finding['kind']} — {finding['criterion']}\n"
410
+ f" evidence: {finding['evidence']}\n"
411
+ f" found: {finding['found']}\n"
412
+ f" fix: {finding['fix']}\n"
413
+ f" source: {finding['source']}\n"
414
+ )
415
+
416
+
417
+ def merge_artifacts(
418
+ kind: str,
419
+ inputs: Sequence[Path],
420
+ output: Path,
421
+ header_fields: Sequence[tuple[str, str]],
422
+ ) -> dict:
423
+ artifacts = [parse_family_artifact(path, kind) for path in inputs]
424
+ families = [artifact["family"] for artifact in artifacts]
425
+ if len(families) != len(set(families)):
426
+ raise ReviewPanelError("panel merge repeats a family")
427
+ findings = [finding for artifact in artifacts for finding in artifact["findings"]]
428
+ action = [finding for finding in findings if actionable(finding)]
429
+ warnings = [finding for finding in findings if not actionable(finding)]
430
+ conflicts = detect_conflicts(findings)
431
+ title = "# Plan panel" if kind == "plan" else "# Wave panel"
432
+ lines = [title, ""]
433
+ for key, value in header_fields:
434
+ lines.append(f"{key}: {value}")
435
+ lines.extend(
436
+ [
437
+ f"Families: {', '.join(families)}",
438
+ f"Actionable: {len(action)}",
439
+ f"Warnings: {len(warnings)}",
440
+ f"Conflicts: {len(conflicts)}",
441
+ "",
442
+ "## Actionable",
443
+ "",
444
+ ]
445
+ )
446
+ if action:
447
+ lines.extend(render_finding(finding) for finding in action)
448
+ else:
449
+ lines.append("- none")
450
+ lines.append("")
451
+ lines.extend(["## Warnings", ""])
452
+ if warnings:
453
+ lines.extend(render_finding(finding) for finding in warnings)
454
+ else:
455
+ lines.append("- none")
456
+ lines.append("")
457
+ lines.extend(["## Conflicts", ""])
458
+ if conflicts:
459
+ for conflict in conflicts:
460
+ lines.append(
461
+ f"- {conflict['criterion']} — families {', '.join(conflict['families'])}; "
462
+ f"severities {', '.join(conflict['severities'])}"
463
+ )
464
+ lines.append("")
465
+ else:
466
+ lines.append("- none")
467
+ lines.append("")
468
+ lines.extend(["## By family", ""])
469
+ for artifact in artifacts:
470
+ lines.append(artifact["text"].rstrip())
471
+ lines.append("")
472
+ output.parent.mkdir(parents=True, exist_ok=True)
473
+ output.write_text("\n".join(lines).rstrip() + "\n", encoding="utf-8")
474
+ return {
475
+ "output": str(output.resolve()),
476
+ "families": families,
477
+ "actionable": len(action),
478
+ "warnings": len(warnings),
479
+ "conflicts": len(conflicts),
480
+ }
481
+
482
+
483
+ def emit(payload: dict) -> int:
484
+ json.dump(payload, sys.stdout, indent=2, sort_keys=True)
485
+ sys.stdout.write("\n")
486
+ return 0
487
+
488
+
489
+ def parser() -> argparse.ArgumentParser:
490
+ result = argparse.ArgumentParser(description=__doc__)
491
+ result.add_argument(
492
+ "command", choices=("parse", "resolve", "families", "merge", "validate-plan")
493
+ )
494
+ result.add_argument("--plan", type=Path)
495
+ result.add_argument("--intent", type=Path)
496
+ result.add_argument("--charter", type=Path)
497
+ result.add_argument("--advertised", default="")
498
+ result.add_argument("--parent-family")
499
+ result.add_argument("--parent-slug")
500
+ result.add_argument("--exclude-family", action="append", choices=KNOWN_FAMILIES, default=[])
501
+ result.add_argument("--kind", choices=("plan", "wave"))
502
+ result.add_argument("--inputs", default="")
503
+ result.add_argument("--output", type=Path)
504
+ result.add_argument("--wave")
505
+ result.add_argument("--cycle")
506
+ result.add_argument("--mode")
507
+ return result
508
+
509
+
510
+ def main(argv: Optional[Sequence[str]] = None) -> int:
511
+ arguments = parser().parse_args(argv)
512
+ try:
513
+ if arguments.command == "families":
514
+ grouped = group_advertised(split_csv(arguments.advertised))
515
+ return emit(
516
+ {
517
+ "families": {
518
+ family: {"slugs": slugs, "latest": latest_slug(slugs)}
519
+ for family, slugs in grouped.items()
520
+ }
521
+ }
522
+ )
523
+ if arguments.command == "merge":
524
+ if arguments.kind is None or arguments.output is None:
525
+ raise ReviewPanelError("merge requires --kind and --output")
526
+ inputs = [Path(part) for part in split_csv(arguments.inputs)]
527
+ if not inputs:
528
+ raise ReviewPanelError("merge requires --inputs")
529
+ header = []
530
+ if arguments.mode:
531
+ header.append(("Mode", arguments.mode))
532
+ if arguments.kind == "wave":
533
+ if not arguments.wave or not arguments.cycle:
534
+ raise ReviewPanelError("wave merge requires --wave and --cycle")
535
+ header.extend(
536
+ (("Wave", arguments.wave), ("Cycle", arguments.cycle))
537
+ )
538
+ return emit(merge_artifacts(arguments.kind, inputs, arguments.output, header))
539
+ if arguments.plan is None and arguments.command in {"parse", "resolve", "validate-plan"}:
540
+ if arguments.command != "parse" or (
541
+ arguments.intent is None and arguments.charter is None
542
+ ):
543
+ raise ReviewPanelError(
544
+ f"{arguments.command} requires --plan, --intent, or --charter"
545
+ )
546
+ config = parse_files(arguments.plan, arguments.intent, arguments.charter)
547
+ if arguments.command in {"parse", "validate-plan"}:
548
+ return emit(config)
549
+ resolved = resolve_panel(
550
+ config,
551
+ split_csv(arguments.advertised),
552
+ arguments.parent_family,
553
+ arguments.parent_slug,
554
+ arguments.exclude_family,
555
+ )
556
+ return emit(resolved)
557
+ except ReviewPanelError as error:
558
+ json.dump({"status": "error", "error": str(error)}, sys.stdout, indent=2)
559
+ sys.stdout.write("\n")
560
+ return 2
561
+
562
+
563
+ if __name__ == "__main__":
564
+ sys.exit(main())