@opengsd/gsd-path 1.0.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 (398) hide show
  1. package/AGENTS.md +397 -0
  2. package/DOCS.md +341 -0
  3. package/FULL.md +475 -0
  4. package/GUIDE.md +23 -0
  5. package/HOOKS.md +273 -0
  6. package/LICENSE +21 -0
  7. package/MIGRATE.md +100 -0
  8. package/QUICK.md +146 -0
  9. package/README.md +280 -0
  10. package/RUNTIME.md +229 -0
  11. package/UPDATE.md +196 -0
  12. package/WORKFLOW.md +683 -0
  13. package/package.json +80 -0
  14. package/platforms/claude/dispatch.md +63 -0
  15. package/platforms/copilot/dispatch.md +61 -0
  16. package/platforms/cursor/agent.md +17 -0
  17. package/platforms/cursor/dispatch.md +63 -0
  18. package/platforms/grok/dispatch.md +61 -0
  19. package/platforms/kimi/dispatch.md +64 -0
  20. package/platforms/kiro/dispatch.md +58 -0
  21. package/platforms/opencode/dispatch.md +62 -0
  22. package/platforms/qwen/dispatch.md +60 -0
  23. package/platforms/shared-agents/dispatch.md +131 -0
  24. package/plugin.json +14 -0
  25. package/scripts/_common.py +180 -0
  26. package/scripts/archive_milestone.py +2265 -0
  27. package/scripts/bootstrap_repository.py +710 -0
  28. package/scripts/build_state.py +854 -0
  29. package/scripts/check_docs_audit.py +463 -0
  30. package/scripts/check_handoffs.py +1855 -0
  31. package/scripts/check_task_briefs.py +376 -0
  32. package/scripts/check_update.py +93 -0
  33. package/scripts/core_hook_gate.py +34 -0
  34. package/scripts/core_hook_settings.py +119 -0
  35. package/scripts/detect_project.py +1517 -0
  36. package/scripts/discussion_records.py +619 -0
  37. package/scripts/discussion_validate.py +1321 -0
  38. package/scripts/dispatch_driver.py +1812 -0
  39. package/scripts/git_guard.py +759 -0
  40. package/scripts/guard_hook.py +2124 -0
  41. package/scripts/install.mjs +3239 -0
  42. package/scripts/install.py +3008 -0
  43. package/scripts/integration.py +1433 -0
  44. package/scripts/isolation.py +3538 -0
  45. package/scripts/lean_verification.py +256 -0
  46. package/scripts/loop_run.py +837 -0
  47. package/scripts/migrate_core.py +169 -0
  48. package/scripts/pipeline_diagnose.py +715 -0
  49. package/scripts/pipeline_git.py +940 -0
  50. package/scripts/pipeline_state.py +2291 -0
  51. package/scripts/pipeline_undo.py +1039 -0
  52. package/scripts/promote_lookahead.py +415 -0
  53. package/scripts/review_findings.py +714 -0
  54. package/scripts/review_panel.py +553 -0
  55. package/scripts/skill-resources.json +340 -0
  56. package/scripts/state_checkpoint.py +1039 -0
  57. package/scripts/state_promote.py +743 -0
  58. package/scripts/status_runtime.py +122 -0
  59. package/scripts/sync_skill_resources.py +259 -0
  60. package/scripts/token_budget.py +146 -0
  61. package/scripts/wizard.mjs +179 -0
  62. package/scripts/workflow_run.py +117 -0
  63. package/skills/gsd-path/BUILD.md +700 -0
  64. package/skills/gsd-path/DECIDE.md +115 -0
  65. package/skills/gsd-path/DEFINE.md +297 -0
  66. package/skills/gsd-path/DOCS-AUDIT.md +190 -0
  67. package/skills/gsd-path/INSPECT.md +167 -0
  68. package/skills/gsd-path/PLAN.md +411 -0
  69. package/skills/gsd-path/RESEARCH.md +159 -0
  70. package/skills/gsd-path/ROADMAP.md +215 -0
  71. package/skills/gsd-path/SHIP.md +412 -0
  72. package/skills/gsd-path/SKILL.md +471 -0
  73. package/skills/gsd-path/agents/openai.yaml +6 -0
  74. package/skills/gsd-path/references/codebase-mapper.md +52 -0
  75. package/skills/gsd-path/references/coder.md +90 -0
  76. package/skills/gsd-path/references/decider.md +36 -0
  77. package/skills/gsd-path/references/dispatch.md +131 -0
  78. package/skills/gsd-path/references/docs-auditor.md +60 -0
  79. package/skills/gsd-path/references/planner.md +104 -0
  80. package/skills/gsd-path/references/researcher.md +42 -0
  81. package/skills/gsd-path/references/reviewer.md +188 -0
  82. package/skills/gsd-path/references/roadmapper.md +48 -0
  83. package/skills/gsd-path/scripts/_common.py +180 -0
  84. package/skills/gsd-path/scripts/archive_milestone.py +2265 -0
  85. package/skills/gsd-path/scripts/bootstrap_repository.py +710 -0
  86. package/skills/gsd-path/scripts/build_state.py +854 -0
  87. package/skills/gsd-path/scripts/check_docs_audit.py +463 -0
  88. package/skills/gsd-path/scripts/check_handoffs.py +1855 -0
  89. package/skills/gsd-path/scripts/check_task_briefs.py +376 -0
  90. package/skills/gsd-path/scripts/check_update.py +93 -0
  91. package/skills/gsd-path/scripts/detect_project.py +1517 -0
  92. package/skills/gsd-path/scripts/discussion_records.py +619 -0
  93. package/skills/gsd-path/scripts/discussion_validate.py +1321 -0
  94. package/skills/gsd-path/scripts/dispatch_driver.py +1812 -0
  95. package/skills/gsd-path/scripts/integration.py +1433 -0
  96. package/skills/gsd-path/scripts/isolation.py +3538 -0
  97. package/skills/gsd-path/scripts/lean_verification.py +256 -0
  98. package/skills/gsd-path/scripts/loop_run.py +837 -0
  99. package/skills/gsd-path/scripts/pipeline_git.py +940 -0
  100. package/skills/gsd-path/scripts/pipeline_state.py +2291 -0
  101. package/skills/gsd-path/scripts/promote_lookahead.py +415 -0
  102. package/skills/gsd-path/scripts/review_findings.py +714 -0
  103. package/skills/gsd-path/scripts/review_panel.py +553 -0
  104. package/skills/gsd-path/scripts/state_checkpoint.py +1039 -0
  105. package/skills/gsd-path/scripts/state_promote.py +743 -0
  106. package/skills/gsd-path/scripts/token_budget.py +146 -0
  107. package/skills/gsd-path/scripts/workflow_run.py +117 -0
  108. package/skills/gsd-path/templates/answers.md +29 -0
  109. package/skills/gsd-path/templates/archive-manifest.md +37 -0
  110. package/skills/gsd-path/templates/charter.md +50 -0
  111. package/skills/gsd-path/templates/codebase.md +46 -0
  112. package/skills/gsd-path/templates/dialogue.md +21 -0
  113. package/skills/gsd-path/templates/docs-audit.md +69 -0
  114. package/skills/gsd-path/templates/evidence.md +24 -0
  115. package/skills/gsd-path/templates/final-review.md +27 -0
  116. package/skills/gsd-path/templates/gap-review.md +20 -0
  117. package/skills/gsd-path/templates/intent.md +98 -0
  118. package/skills/gsd-path/templates/loop.md +59 -0
  119. package/skills/gsd-path/templates/patch-findings.md +16 -0
  120. package/skills/gsd-path/templates/plan-panel.md +25 -0
  121. package/skills/gsd-path/templates/plan.md +89 -0
  122. package/skills/gsd-path/templates/repository.md +16 -0
  123. package/skills/gsd-path/templates/research-handoff.md +25 -0
  124. package/skills/gsd-path/templates/roadmap.md +38 -0
  125. package/skills/gsd-path/templates/skeptic.md +36 -0
  126. package/skills/gsd-path/templates/state.md +27 -0
  127. package/skills/gsd-path/templates/synthesis.md +36 -0
  128. package/skills/gsd-path/templates/task.md +74 -0
  129. package/skills/gsd-path/templates/wave-panel.md +25 -0
  130. package/skills/gsd-path/templates/wave-review.md +64 -0
  131. package/skills/gsd-path-build/SKILL.md +700 -0
  132. package/skills/gsd-path-build/agents/openai.yaml +6 -0
  133. package/skills/gsd-path-build/references/coder.md +90 -0
  134. package/skills/gsd-path-build/references/dispatch.md +131 -0
  135. package/skills/gsd-path-build/references/reviewer.md +188 -0
  136. package/skills/gsd-path-build/scripts/_common.py +180 -0
  137. package/skills/gsd-path-build/scripts/archive_milestone.py +2265 -0
  138. package/skills/gsd-path-build/scripts/build_state.py +854 -0
  139. package/skills/gsd-path-build/scripts/check_handoffs.py +1855 -0
  140. package/skills/gsd-path-build/scripts/check_task_briefs.py +376 -0
  141. package/skills/gsd-path-build/scripts/discussion_records.py +619 -0
  142. package/skills/gsd-path-build/scripts/discussion_validate.py +1321 -0
  143. package/skills/gsd-path-build/scripts/dispatch_driver.py +1812 -0
  144. package/skills/gsd-path-build/scripts/integration.py +1433 -0
  145. package/skills/gsd-path-build/scripts/isolation.py +3538 -0
  146. package/skills/gsd-path-build/scripts/loop_run.py +837 -0
  147. package/skills/gsd-path-build/scripts/pipeline_git.py +940 -0
  148. package/skills/gsd-path-build/scripts/pipeline_state.py +2291 -0
  149. package/skills/gsd-path-build/scripts/review_findings.py +714 -0
  150. package/skills/gsd-path-build/scripts/review_panel.py +553 -0
  151. package/skills/gsd-path-build/scripts/state_checkpoint.py +1039 -0
  152. package/skills/gsd-path-build/scripts/state_promote.py +743 -0
  153. package/skills/gsd-path-build/scripts/token_budget.py +146 -0
  154. package/skills/gsd-path-build/scripts/workflow_run.py +117 -0
  155. package/skills/gsd-path-build/templates/skeptic.md +36 -0
  156. package/skills/gsd-path-build/templates/task.md +74 -0
  157. package/skills/gsd-path-build/templates/wave-panel.md +25 -0
  158. package/skills/gsd-path-build/templates/wave-review.md +64 -0
  159. package/skills/gsd-path-decide/SKILL.md +115 -0
  160. package/skills/gsd-path-decide/agents/openai.yaml +6 -0
  161. package/skills/gsd-path-decide/references/decider.md +36 -0
  162. package/skills/gsd-path-decide/references/dispatch.md +131 -0
  163. package/skills/gsd-path-decide/scripts/_common.py +180 -0
  164. package/skills/gsd-path-decide/scripts/check_handoffs.py +1855 -0
  165. package/skills/gsd-path-decide/scripts/check_task_briefs.py +376 -0
  166. package/skills/gsd-path-decide/scripts/discussion_records.py +619 -0
  167. package/skills/gsd-path-decide/scripts/isolation.py +3538 -0
  168. package/skills/gsd-path-decide/scripts/pipeline_git.py +940 -0
  169. package/skills/gsd-path-decide/scripts/pipeline_state.py +2291 -0
  170. package/skills/gsd-path-decide/scripts/state_checkpoint.py +1039 -0
  171. package/skills/gsd-path-decide/scripts/state_promote.py +743 -0
  172. package/skills/gsd-path-decide/templates/synthesis.md +36 -0
  173. package/skills/gsd-path-define/SKILL.md +297 -0
  174. package/skills/gsd-path-define/agents/openai.yaml +6 -0
  175. package/skills/gsd-path-define/scripts/_common.py +180 -0
  176. package/skills/gsd-path-define/scripts/check_handoffs.py +1855 -0
  177. package/skills/gsd-path-define/scripts/check_task_briefs.py +376 -0
  178. package/skills/gsd-path-define/scripts/detect_project.py +1517 -0
  179. package/skills/gsd-path-define/scripts/discussion_records.py +619 -0
  180. package/skills/gsd-path-define/scripts/isolation.py +3538 -0
  181. package/skills/gsd-path-define/scripts/pipeline_git.py +940 -0
  182. package/skills/gsd-path-define/scripts/pipeline_state.py +2291 -0
  183. package/skills/gsd-path-define/scripts/state_checkpoint.py +1039 -0
  184. package/skills/gsd-path-define/scripts/state_promote.py +743 -0
  185. package/skills/gsd-path-define/templates/charter.md +50 -0
  186. package/skills/gsd-path-define/templates/intent.md +98 -0
  187. package/skills/gsd-path-define/templates/state.md +27 -0
  188. package/skills/gsd-path-discuss/SKILL.md +162 -0
  189. package/skills/gsd-path-discuss/agents/openai.yaml +6 -0
  190. package/skills/gsd-path-discuss/scripts/_common.py +180 -0
  191. package/skills/gsd-path-discuss/scripts/discussion_records.py +619 -0
  192. package/skills/gsd-path-discuss/templates/answers.md +29 -0
  193. package/skills/gsd-path-discuss/templates/dialogue.md +21 -0
  194. package/skills/gsd-path-docs-audit/SKILL.md +190 -0
  195. package/skills/gsd-path-docs-audit/agents/openai.yaml +6 -0
  196. package/skills/gsd-path-docs-audit/references/dispatch.md +131 -0
  197. package/skills/gsd-path-docs-audit/references/docs-auditor.md +60 -0
  198. package/skills/gsd-path-docs-audit/scripts/_common.py +180 -0
  199. package/skills/gsd-path-docs-audit/scripts/check_docs_audit.py +463 -0
  200. package/skills/gsd-path-docs-audit/scripts/check_handoffs.py +1855 -0
  201. package/skills/gsd-path-docs-audit/scripts/check_task_briefs.py +376 -0
  202. package/skills/gsd-path-docs-audit/scripts/discussion_records.py +619 -0
  203. package/skills/gsd-path-docs-audit/scripts/isolation.py +3538 -0
  204. package/skills/gsd-path-docs-audit/scripts/pipeline_git.py +940 -0
  205. package/skills/gsd-path-docs-audit/scripts/pipeline_state.py +2291 -0
  206. package/skills/gsd-path-docs-audit/scripts/state_checkpoint.py +1039 -0
  207. package/skills/gsd-path-docs-audit/scripts/state_promote.py +743 -0
  208. package/skills/gsd-path-docs-audit/templates/docs-audit.md +69 -0
  209. package/skills/gsd-path-forensics/SKILL.md +39 -0
  210. package/skills/gsd-path-forensics/agents/openai.yaml +6 -0
  211. package/skills/gsd-path-forensics/scripts/_common.py +180 -0
  212. package/skills/gsd-path-forensics/scripts/discussion_records.py +619 -0
  213. package/skills/gsd-path-forensics/scripts/pipeline_diagnose.py +715 -0
  214. package/skills/gsd-path-forensics/scripts/pipeline_undo.py +1039 -0
  215. package/skills/gsd-path-inspect/SKILL.md +167 -0
  216. package/skills/gsd-path-inspect/agents/openai.yaml +6 -0
  217. package/skills/gsd-path-inspect/references/codebase-mapper.md +52 -0
  218. package/skills/gsd-path-inspect/references/dispatch.md +131 -0
  219. package/skills/gsd-path-inspect/references/docs-auditor.md +60 -0
  220. package/skills/gsd-path-inspect/scripts/_common.py +180 -0
  221. package/skills/gsd-path-inspect/scripts/check_docs_audit.py +463 -0
  222. package/skills/gsd-path-inspect/scripts/check_handoffs.py +1855 -0
  223. package/skills/gsd-path-inspect/scripts/check_task_briefs.py +376 -0
  224. package/skills/gsd-path-inspect/scripts/detect_project.py +1517 -0
  225. package/skills/gsd-path-inspect/scripts/discussion_records.py +619 -0
  226. package/skills/gsd-path-inspect/scripts/isolation.py +3538 -0
  227. package/skills/gsd-path-inspect/scripts/pipeline_git.py +940 -0
  228. package/skills/gsd-path-inspect/scripts/pipeline_state.py +2291 -0
  229. package/skills/gsd-path-inspect/scripts/state_checkpoint.py +1039 -0
  230. package/skills/gsd-path-inspect/scripts/state_promote.py +743 -0
  231. package/skills/gsd-path-inspect/templates/codebase.md +46 -0
  232. package/skills/gsd-path-inspect/templates/docs-audit.md +69 -0
  233. package/skills/gsd-path-inspect/templates/state.md +27 -0
  234. package/skills/gsd-path-loop/SKILL.md +98 -0
  235. package/skills/gsd-path-loop/agents/openai.yaml +6 -0
  236. package/skills/gsd-path-loop/examples/ci-repair.LOOP.md +61 -0
  237. package/skills/gsd-path-loop/scripts/_common.py +180 -0
  238. package/skills/gsd-path-loop/scripts/discussion_records.py +619 -0
  239. package/skills/gsd-path-loop/scripts/loop_run.py +837 -0
  240. package/skills/gsd-path-loop/templates/loop.md +59 -0
  241. package/skills/gsd-path-migrate/SKILL.md +134 -0
  242. package/skills/gsd-path-migrate/agents/openai.yaml +6 -0
  243. package/skills/gsd-path-migrate/scripts/core_hook_gate.py +34 -0
  244. package/skills/gsd-path-migrate/scripts/core_hook_settings.py +119 -0
  245. package/skills/gsd-path-migrate/scripts/discussion_records.py +619 -0
  246. package/skills/gsd-path-migrate/scripts/migrate_core.py +169 -0
  247. package/skills/gsd-path-plan/SKILL.md +411 -0
  248. package/skills/gsd-path-plan/agents/openai.yaml +6 -0
  249. package/skills/gsd-path-plan/references/dispatch.md +131 -0
  250. package/skills/gsd-path-plan/references/planner.md +104 -0
  251. package/skills/gsd-path-plan/scripts/_common.py +180 -0
  252. package/skills/gsd-path-plan/scripts/check_handoffs.py +1855 -0
  253. package/skills/gsd-path-plan/scripts/check_task_briefs.py +376 -0
  254. package/skills/gsd-path-plan/scripts/discussion_records.py +619 -0
  255. package/skills/gsd-path-plan/scripts/isolation.py +3538 -0
  256. package/skills/gsd-path-plan/scripts/pipeline_git.py +940 -0
  257. package/skills/gsd-path-plan/scripts/pipeline_state.py +2291 -0
  258. package/skills/gsd-path-plan/scripts/review_panel.py +553 -0
  259. package/skills/gsd-path-plan/scripts/state_checkpoint.py +1039 -0
  260. package/skills/gsd-path-plan/scripts/state_promote.py +743 -0
  261. package/skills/gsd-path-plan/scripts/workflow_run.py +117 -0
  262. package/skills/gsd-path-plan/templates/patch-findings.md +16 -0
  263. package/skills/gsd-path-plan/templates/plan-panel.md +25 -0
  264. package/skills/gsd-path-plan/templates/plan.md +89 -0
  265. package/skills/gsd-path-plan/templates/task.md +74 -0
  266. package/skills/gsd-path-research/SKILL.md +159 -0
  267. package/skills/gsd-path-research/agents/openai.yaml +6 -0
  268. package/skills/gsd-path-research/references/dispatch.md +131 -0
  269. package/skills/gsd-path-research/references/researcher.md +42 -0
  270. package/skills/gsd-path-research/scripts/_common.py +180 -0
  271. package/skills/gsd-path-research/scripts/check_handoffs.py +1855 -0
  272. package/skills/gsd-path-research/scripts/check_task_briefs.py +376 -0
  273. package/skills/gsd-path-research/scripts/discussion_records.py +619 -0
  274. package/skills/gsd-path-research/scripts/isolation.py +3538 -0
  275. package/skills/gsd-path-research/scripts/pipeline_git.py +940 -0
  276. package/skills/gsd-path-research/scripts/pipeline_state.py +2291 -0
  277. package/skills/gsd-path-research/scripts/state_checkpoint.py +1039 -0
  278. package/skills/gsd-path-research/scripts/state_promote.py +743 -0
  279. package/skills/gsd-path-research/templates/evidence.md +24 -0
  280. package/skills/gsd-path-research/templates/research-handoff.md +25 -0
  281. package/skills/gsd-path-roadmap/SKILL.md +215 -0
  282. package/skills/gsd-path-roadmap/agents/openai.yaml +6 -0
  283. package/skills/gsd-path-roadmap/references/dispatch.md +131 -0
  284. package/skills/gsd-path-roadmap/references/roadmapper.md +48 -0
  285. package/skills/gsd-path-roadmap/scripts/_common.py +180 -0
  286. package/skills/gsd-path-roadmap/scripts/archive_milestone.py +2265 -0
  287. package/skills/gsd-path-roadmap/scripts/check_handoffs.py +1855 -0
  288. package/skills/gsd-path-roadmap/scripts/check_task_briefs.py +376 -0
  289. package/skills/gsd-path-roadmap/scripts/detect_project.py +1517 -0
  290. package/skills/gsd-path-roadmap/scripts/discussion_records.py +619 -0
  291. package/skills/gsd-path-roadmap/scripts/discussion_validate.py +1321 -0
  292. package/skills/gsd-path-roadmap/scripts/integration.py +1433 -0
  293. package/skills/gsd-path-roadmap/scripts/isolation.py +3538 -0
  294. package/skills/gsd-path-roadmap/scripts/pipeline_git.py +940 -0
  295. package/skills/gsd-path-roadmap/scripts/pipeline_state.py +2291 -0
  296. package/skills/gsd-path-roadmap/scripts/promote_lookahead.py +415 -0
  297. package/skills/gsd-path-roadmap/scripts/review_panel.py +553 -0
  298. package/skills/gsd-path-roadmap/scripts/state_checkpoint.py +1039 -0
  299. package/skills/gsd-path-roadmap/scripts/state_promote.py +743 -0
  300. package/skills/gsd-path-roadmap/templates/roadmap.md +38 -0
  301. package/skills/gsd-path-ship/SKILL.md +412 -0
  302. package/skills/gsd-path-ship/agents/openai.yaml +6 -0
  303. package/skills/gsd-path-ship/references/dispatch.md +131 -0
  304. package/skills/gsd-path-ship/references/reviewer.md +188 -0
  305. package/skills/gsd-path-ship/scripts/_common.py +180 -0
  306. package/skills/gsd-path-ship/scripts/archive_milestone.py +2265 -0
  307. package/skills/gsd-path-ship/scripts/build_state.py +854 -0
  308. package/skills/gsd-path-ship/scripts/check_handoffs.py +1855 -0
  309. package/skills/gsd-path-ship/scripts/check_task_briefs.py +376 -0
  310. package/skills/gsd-path-ship/scripts/discussion_records.py +619 -0
  311. package/skills/gsd-path-ship/scripts/discussion_validate.py +1321 -0
  312. package/skills/gsd-path-ship/scripts/integration.py +1433 -0
  313. package/skills/gsd-path-ship/scripts/isolation.py +3538 -0
  314. package/skills/gsd-path-ship/scripts/lean_verification.py +256 -0
  315. package/skills/gsd-path-ship/scripts/pipeline_git.py +940 -0
  316. package/skills/gsd-path-ship/scripts/pipeline_state.py +2291 -0
  317. package/skills/gsd-path-ship/scripts/review_panel.py +553 -0
  318. package/skills/gsd-path-ship/scripts/state_checkpoint.py +1039 -0
  319. package/skills/gsd-path-ship/scripts/state_promote.py +743 -0
  320. package/skills/gsd-path-ship/scripts/workflow_run.py +117 -0
  321. package/skills/gsd-path-ship/templates/archive-manifest.md +37 -0
  322. package/skills/gsd-path-ship/templates/final-review.md +27 -0
  323. package/skills/gsd-path-ship/templates/gap-review.md +20 -0
  324. package/skills/gsd-path-ship/templates/patch-findings.md +16 -0
  325. package/skills/gsd-path-ship/templates/wave-review.md +64 -0
  326. package/skills/gsd-path-undo/SKILL.md +45 -0
  327. package/skills/gsd-path-undo/agents/openai.yaml +6 -0
  328. package/skills/gsd-path-undo/scripts/_common.py +180 -0
  329. package/skills/gsd-path-undo/scripts/discussion_records.py +619 -0
  330. package/skills/gsd-path-undo/scripts/pipeline_undo.py +1039 -0
  331. package/skills/path/BUILD.md +700 -0
  332. package/skills/path/DECIDE.md +115 -0
  333. package/skills/path/DEFINE.md +297 -0
  334. package/skills/path/DOCS-AUDIT.md +190 -0
  335. package/skills/path/INSPECT.md +167 -0
  336. package/skills/path/PLAN.md +411 -0
  337. package/skills/path/RESEARCH.md +159 -0
  338. package/skills/path/ROADMAP.md +215 -0
  339. package/skills/path/SHIP.md +412 -0
  340. package/skills/path/SKILL.md +471 -0
  341. package/skills/path/agents/openai.yaml +6 -0
  342. package/skills/path/references/codebase-mapper.md +52 -0
  343. package/skills/path/references/coder.md +90 -0
  344. package/skills/path/references/decider.md +36 -0
  345. package/skills/path/references/dispatch.md +131 -0
  346. package/skills/path/references/docs-auditor.md +60 -0
  347. package/skills/path/references/planner.md +104 -0
  348. package/skills/path/references/researcher.md +42 -0
  349. package/skills/path/references/reviewer.md +188 -0
  350. package/skills/path/references/roadmapper.md +48 -0
  351. package/skills/path/scripts/_common.py +180 -0
  352. package/skills/path/scripts/archive_milestone.py +2265 -0
  353. package/skills/path/scripts/bootstrap_repository.py +710 -0
  354. package/skills/path/scripts/build_state.py +854 -0
  355. package/skills/path/scripts/check_docs_audit.py +463 -0
  356. package/skills/path/scripts/check_handoffs.py +1855 -0
  357. package/skills/path/scripts/check_task_briefs.py +376 -0
  358. package/skills/path/scripts/check_update.py +93 -0
  359. package/skills/path/scripts/detect_project.py +1517 -0
  360. package/skills/path/scripts/discussion_records.py +619 -0
  361. package/skills/path/scripts/discussion_validate.py +1321 -0
  362. package/skills/path/scripts/dispatch_driver.py +1812 -0
  363. package/skills/path/scripts/integration.py +1433 -0
  364. package/skills/path/scripts/isolation.py +3538 -0
  365. package/skills/path/scripts/lean_verification.py +256 -0
  366. package/skills/path/scripts/loop_run.py +837 -0
  367. package/skills/path/scripts/pipeline_git.py +940 -0
  368. package/skills/path/scripts/pipeline_state.py +2291 -0
  369. package/skills/path/scripts/promote_lookahead.py +415 -0
  370. package/skills/path/scripts/review_findings.py +714 -0
  371. package/skills/path/scripts/review_panel.py +553 -0
  372. package/skills/path/scripts/state_checkpoint.py +1039 -0
  373. package/skills/path/scripts/state_promote.py +743 -0
  374. package/skills/path/scripts/token_budget.py +146 -0
  375. package/skills/path/scripts/workflow_run.py +117 -0
  376. package/skills/path/templates/answers.md +29 -0
  377. package/skills/path/templates/archive-manifest.md +37 -0
  378. package/skills/path/templates/charter.md +50 -0
  379. package/skills/path/templates/codebase.md +46 -0
  380. package/skills/path/templates/dialogue.md +21 -0
  381. package/skills/path/templates/docs-audit.md +69 -0
  382. package/skills/path/templates/evidence.md +24 -0
  383. package/skills/path/templates/final-review.md +27 -0
  384. package/skills/path/templates/gap-review.md +20 -0
  385. package/skills/path/templates/intent.md +98 -0
  386. package/skills/path/templates/loop.md +59 -0
  387. package/skills/path/templates/patch-findings.md +16 -0
  388. package/skills/path/templates/plan-panel.md +25 -0
  389. package/skills/path/templates/plan.md +89 -0
  390. package/skills/path/templates/repository.md +16 -0
  391. package/skills/path/templates/research-handoff.md +25 -0
  392. package/skills/path/templates/roadmap.md +38 -0
  393. package/skills/path/templates/skeptic.md +36 -0
  394. package/skills/path/templates/state.md +27 -0
  395. package/skills/path/templates/synthesis.md +36 -0
  396. package/skills/path/templates/task.md +74 -0
  397. package/skills/path/templates/wave-panel.md +25 -0
  398. package/skills/path/templates/wave-review.md +64 -0
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env python3
2
+ """Launch the project status runtime across an atomic refresh handoff."""
3
+
4
+ import errno
5
+ import json
6
+ import os
7
+ import subprocess
8
+ import sys
9
+ import time
10
+ from pathlib import Path
11
+ from typing import Sequence
12
+
13
+ PROJECT_STATUS_MARKER = "gsd-path project status launcher"
14
+ INSTALL_LOCK_OWNER = "owner.json"
15
+ INSTALL_LOCK_SCHEMA = "gsd-path/install-lock/v2"
16
+
17
+
18
+ def process_identity(pid: int):
19
+ if os.name == "nt":
20
+ command = [
21
+ "powershell.exe",
22
+ "-NoProfile",
23
+ "-NonInteractive",
24
+ "-Command",
25
+ f"(Get-Process -Id {pid} -ErrorAction Stop).StartTime.ToUniversalTime().Ticks",
26
+ ]
27
+ else:
28
+ command = ["ps", "-o", "lstart=", "-p", str(pid)]
29
+ try:
30
+ result = subprocess.run(command, capture_output=True, text=True, check=False)
31
+ except OSError:
32
+ return None
33
+ value = result.stdout.strip()
34
+ return f"{os.name}:{value}" if result.returncode == 0 and value else None
35
+
36
+
37
+ def runtime_identity(runtime: Path):
38
+ try:
39
+ status = runtime.stat()
40
+ except FileNotFoundError:
41
+ return None
42
+ return (status.st_dev, status.st_ino, status.st_mtime_ns, status.st_size)
43
+
44
+
45
+ def process_alive(pid: int) -> bool:
46
+ try:
47
+ os.kill(pid, 0)
48
+ except OSError as error:
49
+ return error.errno == errno.EPERM
50
+ return True
51
+
52
+
53
+ def install_lock_active(lock: Path) -> bool:
54
+ if lock.is_symlink() or not lock.is_dir():
55
+ return False
56
+ try:
57
+ owner = json.loads((lock / INSTALL_LOCK_OWNER).read_text(encoding="utf-8"))
58
+ except (OSError, UnicodeError, json.JSONDecodeError):
59
+ return False
60
+ pid = owner.get("pid")
61
+ valid_pid = isinstance(pid, int) and not isinstance(pid, bool) and pid > 0
62
+ identity = owner.get("identity")
63
+ if (
64
+ owner.get("schema") != INSTALL_LOCK_SCHEMA
65
+ or not valid_pid
66
+ or not isinstance(identity, str)
67
+ ):
68
+ return False
69
+ current_identity = process_identity(pid)
70
+ if current_identity is None:
71
+ return process_alive(pid)
72
+ return current_identity == identity
73
+
74
+
75
+ def launch(repo: Path) -> int:
76
+ parent = repo / ".gsd-path"
77
+ runtime = parent / "runtime" / "pipeline_state.py"
78
+ lock = repo / ".gsd-path-install-lock"
79
+ command = [sys.executable, "-B", str(runtime), "status", "--repo", str(repo)]
80
+ environment = os.environ.copy()
81
+ environment["GIT_OPTIONAL_LOCKS"] = "0"
82
+ while True:
83
+ if install_lock_active(lock):
84
+ time.sleep(0.05)
85
+ continue
86
+ before = runtime_identity(runtime)
87
+ if before is None:
88
+ if install_lock_active(lock):
89
+ continue
90
+ if runtime_identity(runtime) is not None:
91
+ continue
92
+ if install_lock_active(lock):
93
+ continue
94
+ print(f"GSD Path status runtime is unavailable: {runtime}", file=sys.stderr)
95
+ return 2
96
+ try:
97
+ result = subprocess.run(
98
+ command, capture_output=True, check=False, env=environment
99
+ )
100
+ except OSError as error:
101
+ print(f"GSD Path status runtime failed: {error}", file=sys.stderr)
102
+ return 2
103
+ if install_lock_active(lock) or runtime_identity(runtime) != before:
104
+ continue
105
+ sys.stdout.write(result.stdout.decode())
106
+ sys.stderr.write(result.stderr.decode())
107
+ return result.returncode
108
+
109
+
110
+ def main(argv: Sequence[str] = sys.argv[1:]) -> int:
111
+ if len(argv) != 2 or argv[0] != "--repo":
112
+ print("usage: status_runtime.py --repo <absolute-root>", file=sys.stderr)
113
+ return 2
114
+ repo = Path(argv[1])
115
+ if not repo.is_absolute():
116
+ print("GSD Path status repository must be absolute", file=sys.stderr)
117
+ return 2
118
+ return launch(repo.resolve())
119
+
120
+
121
+ if __name__ == "__main__":
122
+ raise SystemExit(main())
@@ -0,0 +1,259 @@
1
+ #!/usr/bin/env python3
2
+ """Synchronize canonical GSD Path resources into standalone phase skills."""
3
+
4
+ import argparse
5
+ import json
6
+ import os
7
+ import shutil
8
+ import sys
9
+ from pathlib import Path
10
+ from typing import Iterable, Optional, Sequence, Tuple
11
+
12
+
13
+ # Single source of truth for the resource tables, shared with install.mjs.
14
+ RESOURCE_MANIFEST = json.loads(
15
+ (Path(__file__).resolve().parent / "skill-resources.json").read_text(
16
+ encoding="utf-8"
17
+ )
18
+ )
19
+ PHASE_RESOURCES = {
20
+ skill: tuple(resources)
21
+ for skill, resources in RESOURCE_MANIFEST["phase_resources"].items()
22
+ }
23
+ SCRIPT_TARGETS = tuple(tuple(pair) for pair in RESOURCE_MANIFEST["script_targets"])
24
+ PHASE_CONTRACT_TARGETS = tuple(
25
+ tuple(pair) for pair in RESOURCE_MANIFEST["phase_contract_targets"]
26
+ )
27
+ SHARED_DISPATCH_TARGETS = tuple(
28
+ tuple(pair) for pair in RESOURCE_MANIFEST["shared_dispatch_targets"]
29
+ )
30
+ SKILL_NAMES = tuple(RESOURCE_MANIFEST["skills"])
31
+ SKILL_ALIASES = dict(RESOURCE_MANIFEST["skill_aliases"])
32
+ ROUTER_ALIASES = dict(RESOURCE_MANIFEST["router_aliases"])
33
+ PACKAGE_FILES = tuple(RESOURCE_MANIFEST["package_files"])
34
+
35
+
36
+ def resource_pairs(root: Path) -> Iterable[Tuple[Path, Path]]:
37
+ for source, destination in SHARED_DISPATCH_TARGETS:
38
+ yield root / source, root / destination
39
+ canonical = root / "skills" / "gsd-path"
40
+ for skill, resources in PHASE_RESOURCES.items():
41
+ for relative in resources:
42
+ yield canonical / relative, root / "skills" / skill / relative
43
+ for source, destination in SCRIPT_TARGETS:
44
+ yield root / source, root / destination
45
+ for source, destination in PHASE_CONTRACT_TARGETS:
46
+ yield root / source, root / destination
47
+ for legacy, canonical_skill in SKILL_ALIASES.items():
48
+ canonical_directory = root / "skills" / canonical_skill
49
+ legacy_directory = root / "skills" / legacy
50
+ yield canonical_directory / "SKILL.md", legacy_directory / "CANONICAL.md"
51
+ for relative in PHASE_RESOURCES.get(canonical_skill, ()):
52
+ yield canonical / relative, legacy_directory / relative
53
+ canonical_prefix = f"skills/{canonical_skill}/"
54
+ for source, destination in SCRIPT_TARGETS:
55
+ if destination.startswith(canonical_prefix):
56
+ relative = destination.removeprefix(canonical_prefix)
57
+ yield root / source, legacy_directory / relative
58
+ for alias, canonical_skill in ROUTER_ALIASES.items():
59
+ canonical_directory = root / "skills" / canonical_skill
60
+ alias_directory = root / "skills" / alias
61
+ for source in _skill_files(canonical_directory):
62
+ yield source, alias_directory / source.relative_to(canonical_directory)
63
+
64
+
65
+ def _skill_files(skill_root: Path) -> Iterable[Path]:
66
+ for directory, names, files in os.walk(skill_root):
67
+ names[:] = [name for name in names if name not in (".git", "node_modules", "__pycache__")]
68
+ for name in files:
69
+ if name == ".DS_Store":
70
+ continue
71
+ yield Path(directory) / name
72
+
73
+
74
+ def rewrite_router_alias_skill(text: str, alias: str, canonical: str) -> str:
75
+ """Keep the router contract; change only the slash/catalog name."""
76
+ if not text.startswith("---\n"):
77
+ raise ValueError("SKILL.md is missing YAML frontmatter")
78
+ end = text.find("\n---\n", 4)
79
+ if end == -1:
80
+ raise ValueError("SKILL.md has unterminated YAML frontmatter")
81
+ header = text[4:end]
82
+ rest = text[end:]
83
+ expected = f"name: {canonical}"
84
+ replacement = f"name: {alias}"
85
+ lines = header.split("\n")
86
+ if expected not in lines:
87
+ raise ValueError(f"router alias {alias} is missing {expected}")
88
+ rewritten = [replacement if line == expected else line for line in lines]
89
+ if rewritten.count(replacement) != 1:
90
+ raise ValueError(f"router alias {alias} name rewrite was not unique")
91
+ invoke = f"invokes ${canonical}."
92
+ invoke_with_alias = f"invokes ${alias} or ${canonical}."
93
+ header = "\n".join(rewritten)
94
+ if invoke not in header:
95
+ raise ValueError(f"router alias {alias} is missing {invoke}")
96
+ header = header.replace(invoke, invoke_with_alias, 1)
97
+ return "---\n" + header + rest
98
+
99
+
100
+ def materialized_resource_bytes(source: Path, destination: Path) -> bytes:
101
+ data = source.read_bytes()
102
+ if destination.name != "SKILL.md":
103
+ return data
104
+ alias = destination.parent.name
105
+ canonical = ROUTER_ALIASES.get(alias)
106
+ if canonical is None:
107
+ return data
108
+ return rewrite_router_alias_skill(
109
+ data.decode("utf-8"), alias, canonical
110
+ ).encode("utf-8")
111
+
112
+
113
+ def package_metadata(root: Path) -> Iterable[Path]:
114
+ for directory, names, files in os.walk(root):
115
+ names[:] = [name for name in names if name not in (".git", "node_modules")]
116
+ for name in names + files:
117
+ if name == ".DS_Store":
118
+ yield Path(directory) / name
119
+
120
+
121
+ def divergence_warning(source: Path, destination: Path, root: Path) -> Optional[str]:
122
+ """Flag the wrong-direction-edit signature: a generated copy that differs
123
+ from canonical and is NEWER than it (sync preserves canonical mtimes via
124
+ copy2, so a just-edited copy is the only newer-and-different case)."""
125
+ if not destination.is_file():
126
+ return None
127
+ if destination.read_bytes() == materialized_resource_bytes(source, destination):
128
+ return None
129
+ if destination.stat().st_mtime > source.stat().st_mtime:
130
+ return (
131
+ f"generated resource diverges from canonical and is newer "
132
+ f"(possible wrong-direction edit; edit {source.relative_to(root)} "
133
+ f"instead): {destination.relative_to(root)}"
134
+ )
135
+ return None
136
+
137
+
138
+ def mismatches(root: Path) -> Sequence[str]:
139
+ problems = []
140
+ expected_skills = (
141
+ "gsd-path",
142
+ *sorted((*PHASE_RESOURCES, *SKILL_ALIASES, *ROUTER_ALIASES)),
143
+ )
144
+ if SKILL_NAMES != expected_skills:
145
+ problems.append(
146
+ "manifest skills must be root gsd-path plus every canonical skill and alias"
147
+ )
148
+ if set(SKILL_ALIASES) & set(PHASE_RESOURCES):
149
+ problems.append("skill aliases cannot also own phase resources")
150
+ if set(ROUTER_ALIASES) & set(PHASE_RESOURCES):
151
+ problems.append("router aliases cannot also own phase resources")
152
+ if set(ROUTER_ALIASES) & set(SKILL_ALIASES):
153
+ problems.append("router aliases cannot also be skill aliases")
154
+ for legacy, canonical in SKILL_ALIASES.items():
155
+ if canonical not in PHASE_RESOURCES:
156
+ problems.append(f"skill alias target is not canonical: {legacy} -> {canonical}")
157
+ for alias, canonical in ROUTER_ALIASES.items():
158
+ if canonical != "gsd-path":
159
+ problems.append(f"router alias target is not the router: {alias} -> {canonical}")
160
+ package_path = root / "package.json"
161
+ try:
162
+ package_files = tuple(json.loads(package_path.read_text(encoding="utf-8"))["files"])
163
+ except (OSError, json.JSONDecodeError, KeyError, TypeError):
164
+ package_files = ()
165
+ if package_files != PACKAGE_FILES:
166
+ problems.append("package.json files do not match manifest package_files")
167
+ for source, destination in resource_pairs(root):
168
+ if not source.is_file():
169
+ problems.append(f"missing canonical resource: {source.relative_to(root)}")
170
+ elif not destination.is_file():
171
+ problems.append(f"missing generated resource: {destination.relative_to(root)}")
172
+ else:
173
+ try:
174
+ expected = materialized_resource_bytes(source, destination)
175
+ except ValueError as error:
176
+ problems.append(str(error))
177
+ continue
178
+ if expected != destination.read_bytes():
179
+ warning = divergence_warning(source, destination, root)
180
+ if warning:
181
+ problems.append(warning)
182
+ else:
183
+ problems.append(
184
+ f"stale generated resource: {destination.relative_to(root)}"
185
+ )
186
+ for path in package_metadata(root):
187
+ problems.append(f"unexpected package metadata: {path.relative_to(root)}")
188
+ return problems
189
+
190
+
191
+ def synchronize(root: Path) -> int:
192
+ copied = 0
193
+ warnings = 0
194
+ for source, destination in resource_pairs(root):
195
+ if not source.is_file():
196
+ print(f"missing canonical resource: {source}", file=sys.stderr)
197
+ return 1
198
+ warning = divergence_warning(source, destination, root)
199
+ if warning:
200
+ print(f"warning: {warning}", file=sys.stderr)
201
+ warnings += 1
202
+ destination.parent.mkdir(parents=True, exist_ok=True)
203
+ expected = materialized_resource_bytes(source, destination)
204
+ if expected != source.read_bytes():
205
+ destination.write_bytes(expected)
206
+ else:
207
+ shutil.copy2(source, destination)
208
+ copied += 1
209
+ removed_metadata = 0
210
+ for path in package_metadata(root):
211
+ if not path.is_file() and not path.is_symlink():
212
+ print(f"unexpected non-file package metadata: {path}", file=sys.stderr)
213
+ return 1
214
+ path.unlink()
215
+ removed_metadata += 1
216
+ package_path = root / "package.json"
217
+ package = json.loads(package_path.read_text(encoding="utf-8"))
218
+ package["files"] = list(PACKAGE_FILES)
219
+ package_path.write_text(json.dumps(package, indent=2) + "\n", encoding="utf-8")
220
+ print(
221
+ json.dumps(
222
+ {
223
+ "removed_metadata": removed_metadata,
224
+ "resources": copied,
225
+ "warnings": warnings,
226
+ },
227
+ sort_keys=True,
228
+ )
229
+ )
230
+ return 0
231
+
232
+
233
+ def parser() -> argparse.ArgumentParser:
234
+ argument_parser = argparse.ArgumentParser(description=__doc__)
235
+ argument_parser.add_argument(
236
+ "--root",
237
+ type=Path,
238
+ default=Path(__file__).resolve().parents[1],
239
+ help="repository root",
240
+ )
241
+ argument_parser.add_argument("--check", action="store_true")
242
+ return argument_parser
243
+
244
+
245
+ def main(argv: Optional[Sequence[str]] = None) -> int:
246
+ arguments = parser().parse_args(argv)
247
+ root = arguments.root.resolve()
248
+ if arguments.check:
249
+ problems = mismatches(root)
250
+ if problems:
251
+ print("\n".join(problems), file=sys.stderr)
252
+ return 1
253
+ print(json.dumps({"resources": sum(1 for _ in resource_pairs(root))}, sort_keys=True))
254
+ return 0
255
+ return synchronize(root)
256
+
257
+
258
+ if __name__ == "__main__":
259
+ raise SystemExit(main())
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env python3
2
+ """Persist observed Codex output usage and block admission after exhaustion.
3
+
4
+ This is an admission gate, not an in-flight generation cap. Codex CLI does not
5
+ expose a hard output-token cap. Unobserved calls are not budget evidence.
6
+ """
7
+
8
+ import argparse
9
+ import json
10
+ import sys
11
+ from pathlib import Path
12
+
13
+ try:
14
+ from _common import atomic_write
15
+ from loop_run import log_lock
16
+ except ModuleNotFoundError: # pragma: no cover - package imports
17
+ from scripts._common import atomic_write
18
+ from scripts.loop_run import log_lock
19
+
20
+
21
+ SCHEMA = "gsd-path/token-budget/v1"
22
+
23
+
24
+ def positive(value):
25
+ result = int(value)
26
+ if result <= 0:
27
+ raise argparse.ArgumentTypeError("budget must be positive")
28
+ return result
29
+
30
+
31
+ def output_usage(path: Path) -> int:
32
+ usages = []
33
+ child_usage = None
34
+ child = False
35
+ starts = 0
36
+ complete = False
37
+ text = path.read_text(encoding="utf-8")
38
+ try:
39
+ document = json.loads(text)
40
+ except ValueError:
41
+ document = None
42
+ if isinstance(document, dict) and isinstance(document.get("usage"), dict):
43
+ # Claude Code `-p --output-format json`: one document with the run's usage.
44
+ usages.append(document["usage"].get("output_tokens"))
45
+ text = ""
46
+ for line in text.splitlines():
47
+ event = json.loads(line)
48
+ if "raw" in event:
49
+ event = json.loads(event["raw"])
50
+ if event.get("type") == "turn.completed":
51
+ usages.append(event["usage"]["output_tokens"])
52
+ payload = event.get("payload", {})
53
+ if event.get("type") == "session_meta":
54
+ source = payload.get("source", {})
55
+ child = isinstance(source, dict) and "subagent" in source
56
+ if event.get("type") == "event_msg":
57
+ if payload.get("type") == "task_started":
58
+ starts += 1
59
+ complete = False
60
+ elif payload.get("type") == "token_count" and payload.get("info"):
61
+ child_usage = payload["info"]["total_token_usage"]["output_tokens"]
62
+ elif payload.get("type") == "task_complete":
63
+ complete = True
64
+ if not usages and child and starts == 1 and complete and child_usage is not None:
65
+ # Only a single native invocation has an unambiguous cumulative counter.
66
+ usages = [child_usage]
67
+ if not usages or any(type(value) is not int or value < 0 for value in usages):
68
+ raise ValueError("complete supported output usage is unavailable; budget is unproven")
69
+ return sum(usages)
70
+
71
+
72
+ def operate(args):
73
+ ledger = args.ledger.absolute()
74
+ if ledger.is_symlink():
75
+ raise ValueError("ledger must be a real file")
76
+ with log_lock({"log": str(ledger)}):
77
+ if args.action == "configure":
78
+ policy = {"schema": SCHEMA, "host": "codex-cli", "metric": "output_tokens",
79
+ "task_limit": args.task_limit, "session_limit": args.session_limit,
80
+ "authority": args.authority}
81
+ if ledger.exists():
82
+ data = json.loads(ledger.read_text())
83
+ if any(data.get(key) != value for key, value in policy.items()):
84
+ raise ValueError("existing session policy differs; configuration cannot reset usage")
85
+ else:
86
+ data = {**policy, "observations": {}}
87
+ atomic_write(ledger, json.dumps(data, indent=2) + "\n")
88
+ return {"status": "configured", "hard_cap_supported": False}
89
+ data = json.loads(ledger.read_text())
90
+ if data.get("schema") != SCHEMA or data.get("host") != "codex-cli":
91
+ raise ValueError("unsupported budget ledger")
92
+ if args.action == "record":
93
+ source = str(args.events.resolve())
94
+ observation = {"task": args.task, "output_tokens": output_usage(args.events)}
95
+ previous = data["observations"].get(source)
96
+ if previous and (previous["task"] != args.task or
97
+ previous["output_tokens"] > observation["output_tokens"]):
98
+ raise ValueError("observation cannot change task or reduce recorded usage")
99
+ data["observations"][source] = observation
100
+ atomic_write(ledger, json.dumps(data, indent=2) + "\n")
101
+ return {"status": "recorded", **observation, "hard_cap_supported": False}
102
+ observations = data["observations"].values()
103
+ task_used = sum(row["output_tokens"] for row in observations if row["task"] == args.task)
104
+ session_used = sum(row["output_tokens"] for row in observations)
105
+ task_remaining = max(0, data["task_limit"] - task_used)
106
+ session_remaining = max(0, data["session_limit"] - session_used)
107
+ reason = "observed usage is within budget; unobserved and in-flight usage is unproven"
108
+ blocked = not task_remaining or not session_remaining
109
+ if blocked:
110
+ reason = "observed output budget exhausted"
111
+ if args.require_hard_cap:
112
+ blocked = True
113
+ reason = "hard generation cap unsupported by Codex CLI"
114
+ return {"decision": "blocked" if blocked else "admit", "reason": reason,
115
+ "task_output_tokens": task_used, "session_output_tokens": session_used,
116
+ "task_remaining": task_remaining, "session_remaining": session_remaining,
117
+ "hard_cap_supported": False, "enforcement": "observed-admission-only"}
118
+
119
+
120
+ def main(argv=None):
121
+ parser = argparse.ArgumentParser(description=__doc__)
122
+ subparsers = parser.add_subparsers(dest="action", required=True)
123
+ for name in ("configure", "record", "admit"):
124
+ command = subparsers.add_parser(name)
125
+ command.add_argument("--ledger", type=Path, required=True)
126
+ if name == "configure":
127
+ command.add_argument("--task-limit", type=positive, required=True)
128
+ command.add_argument("--session-limit", type=positive, required=True)
129
+ command.add_argument("--authority", required=True)
130
+ else:
131
+ command.add_argument("--task", required=True)
132
+ if name == "record":
133
+ command.add_argument("--events", type=Path, required=True)
134
+ if name == "admit":
135
+ command.add_argument("--require-hard-cap", action="store_true")
136
+ try:
137
+ result = operate(parser.parse_args(argv))
138
+ except (ValueError, OSError, KeyError, TypeError) as error:
139
+ print(json.dumps({"decision": "blocked", "reason": str(error)}))
140
+ return 1
141
+ print(json.dumps(result, sort_keys=True))
142
+ return 1 if result.get("decision") == "blocked" else 0
143
+
144
+
145
+ if __name__ == "__main__":
146
+ raise SystemExit(main())