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