@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,710 @@
1
+ #!/usr/bin/env python3
2
+ """Create or resume an approved GitHub repository and GSD Path worktree."""
3
+
4
+ import argparse
5
+ import json
6
+ import os
7
+ import re
8
+ import shutil
9
+ import subprocess
10
+ import sys
11
+ from dataclasses import asdict, dataclass
12
+ from pathlib import Path
13
+ from typing import Optional, Sequence
14
+
15
+ try:
16
+ import _common
17
+ except ImportError: # pragma: no cover - package import used by tests
18
+ from scripts import _common
19
+
20
+
21
+ class BootstrapError(RuntimeError):
22
+ pass
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class BootstrapRequest:
27
+ workspace: str
28
+ owner: str
29
+ repo: str
30
+ visibility: str
31
+ default_checkout: str
32
+ worktree: str
33
+ branch: str
34
+ description: Optional[str]
35
+
36
+ @property
37
+ def repository(self) -> str:
38
+ return f"{self.owner}/{self.repo}"
39
+
40
+ @property
41
+ def slug(self) -> str:
42
+ return normalized_slug(self.repo)
43
+
44
+ @property
45
+ def workspace_path(self) -> Path:
46
+ return Path(self.workspace)
47
+
48
+ @property
49
+ def checkout_path(self) -> Path:
50
+ return Path(self.default_checkout)
51
+
52
+ @property
53
+ def worktree_path(self) -> Path:
54
+ return Path(self.worktree)
55
+
56
+ @property
57
+ def journal_path(self) -> Path:
58
+ name = f"{self.owner}--{self.repo}.json"
59
+ return self.workspace_path / ".gsd-path-transactions" / name
60
+
61
+
62
+ def normalized_slug(value: str) -> str:
63
+ slug = re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-")
64
+ if not slug:
65
+ raise BootstrapError("repository name does not produce a project slug")
66
+ return slug
67
+
68
+
69
+ run = _common.run_command
70
+
71
+
72
+ def require_success(result: subprocess.CompletedProcess[str], action: str) -> str:
73
+ if result.returncode != 0:
74
+ detail = result.stderr.strip() or result.stdout.strip()
75
+ raise BootstrapError(f"{action} failed: {detail}")
76
+ return result.stdout.strip()
77
+
78
+
79
+ def require_real_directory(path: Path, label: str) -> None:
80
+ if path.is_symlink() or not path.is_dir():
81
+ raise BootstrapError(f"{label} must be a real directory: {path}")
82
+
83
+
84
+ def require_outside_worktree(path: Path, label: str) -> None:
85
+ result = run("git", "-C", str(path), "rev-parse", "--is-inside-work-tree")
86
+ if result.returncode == 0:
87
+ raise BootstrapError(f"{label} must not be inside a Git worktree: {path}")
88
+
89
+
90
+ def request_from_arguments(arguments: argparse.Namespace) -> BootstrapRequest:
91
+ if not re.fullmatch(r"[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})", arguments.owner):
92
+ raise BootstrapError("GitHub owner is invalid")
93
+ if not re.fullmatch(r"[A-Za-z0-9._-]+", arguments.repo):
94
+ raise BootstrapError("GitHub repository name is invalid")
95
+ if arguments.visibility not in {"public", "private", "internal"}:
96
+ raise BootstrapError("visibility must be public, private, or internal")
97
+
98
+ workspace = arguments.workspace.resolve()
99
+ checkout = arguments.default_checkout.resolve()
100
+ worktree = arguments.worktree.resolve()
101
+ require_real_directory(workspace, "workspace")
102
+ require_real_directory(checkout.parent, "default-checkout parent")
103
+ require_real_directory(worktree.parent, "worktree parent")
104
+ require_outside_worktree(checkout.parent, "default-checkout parent")
105
+ require_outside_worktree(worktree.parent, "worktree parent")
106
+ if checkout == worktree:
107
+ raise BootstrapError("default checkout and linked worktree must be distinct")
108
+
109
+ _ = normalized_slug(arguments.repo)
110
+ return BootstrapRequest(
111
+ workspace=str(workspace),
112
+ owner=arguments.owner,
113
+ repo=arguments.repo,
114
+ visibility=arguments.visibility,
115
+ default_checkout=str(checkout),
116
+ worktree=str(worktree),
117
+ branch="gsd-path/M001",
118
+ description=arguments.description,
119
+ )
120
+
121
+
122
+ def atomic_write(path: Path, content: str) -> None:
123
+ path.parent.mkdir(parents=True, exist_ok=True)
124
+ temporary = path.parent / f".{path.name}.gsd-path-tmp"
125
+ try:
126
+ if temporary.exists() or temporary.is_symlink():
127
+ temporary.unlink()
128
+ temporary.write_text(content, encoding="utf-8")
129
+ os.replace(temporary, path)
130
+ finally:
131
+ if temporary.exists() or temporary.is_symlink():
132
+ temporary.unlink()
133
+
134
+
135
+ def transaction_directory(request: BootstrapRequest, create: bool = False) -> Path:
136
+ directory = request.journal_path.parent
137
+ if directory.is_symlink():
138
+ raise BootstrapError(f"transaction directory must be real: {directory}")
139
+ if directory.exists() and not directory.is_dir():
140
+ raise BootstrapError(f"transaction directory must be real: {directory}")
141
+ if directory.exists():
142
+ expected_parent = request.workspace_path.resolve()
143
+ if directory.resolve().parent != expected_parent:
144
+ raise BootstrapError(f"transaction directory escapes workspace: {directory}")
145
+ elif create:
146
+ directory.mkdir()
147
+ return directory
148
+
149
+
150
+ def request_payload(request: BootstrapRequest) -> dict:
151
+ return {"schema": "gsd-path/new-github/v1", **asdict(request)}
152
+
153
+
154
+ def read_journal(request: BootstrapRequest) -> Optional[dict]:
155
+ transaction_directory(request)
156
+ journal = request.journal_path
157
+ if not journal.exists():
158
+ return None
159
+ if journal.is_symlink() or not journal.is_file():
160
+ raise BootstrapError(f"transaction journal must be a real file: {journal}")
161
+ try:
162
+ payload = json.loads(journal.read_text(encoding="utf-8"))
163
+ except (OSError, json.JSONDecodeError) as error:
164
+ raise BootstrapError(
165
+ f"transaction journal is unreadable: {journal}: {error}"
166
+ ) from error
167
+ if payload != request_payload(request):
168
+ raise BootstrapError(
169
+ "transaction journal does not match the approved repository targets"
170
+ )
171
+ return payload
172
+
173
+
174
+ def write_journal(request: BootstrapRequest) -> None:
175
+ transaction_directory(request, create=True)
176
+ atomic_write(
177
+ request.journal_path,
178
+ json.dumps(request_payload(request), indent=2, sort_keys=True) + "\n",
179
+ )
180
+
181
+
182
+ def gh_authentication() -> None:
183
+ require_success(run("gh", "auth", "status"), "verify GitHub authentication")
184
+
185
+
186
+ def remote_exists(request: BootstrapRequest) -> bool:
187
+ result = run("gh", "api", "--silent", f"repos/{request.repository}")
188
+ if result.returncode == 0:
189
+ return True
190
+ detail = f"{result.stdout}\n{result.stderr}"
191
+ if re.search(r"\b404\b", detail):
192
+ return False
193
+ raise BootstrapError(f"GitHub repository lookup was ambiguous: {detail.strip()}")
194
+
195
+
196
+ def verify_remote_visibility(request: BootstrapRequest) -> None:
197
+ visibility = require_success(
198
+ run(
199
+ "gh",
200
+ "api",
201
+ f"repos/{request.repository}",
202
+ "--jq",
203
+ ".visibility",
204
+ ),
205
+ "verify GitHub repository visibility",
206
+ ).casefold()
207
+ if visibility != request.visibility:
208
+ raise BootstrapError(
209
+ f"GitHub repository visibility is {visibility}, not {request.visibility}"
210
+ )
211
+
212
+
213
+ def binding_field(content: str, name: str) -> str:
214
+ values = [
215
+ line.removeprefix(f"{name}:").strip()
216
+ for line in content.splitlines()
217
+ if line.startswith(f"{name}:")
218
+ ]
219
+ if len(values) != 1 or not values[0]:
220
+ raise BootstrapError(f"REPOSITORY.md requires one {name} field")
221
+ return values[0]
222
+
223
+
224
+ def completed_binding(request: BootstrapRequest) -> Optional[tuple[str, str]]:
225
+ state = request.worktree_path / ".project" / "STATE.md"
226
+ binding = request.worktree_path / ".project" / "REPOSITORY.md"
227
+ if not state.exists() and not binding.exists():
228
+ return None
229
+ if (
230
+ not state.is_file()
231
+ or not binding.is_file()
232
+ or state.is_symlink()
233
+ or binding.is_symlink()
234
+ ):
235
+ return None
236
+ state_text = state.read_text(encoding="utf-8")
237
+ binding_text = binding.read_text(encoding="utf-8")
238
+ if not re.search(r"^pipeline:\s*gsd-path/v2\s*$", state_text, re.MULTILINE):
239
+ raise BootstrapError("completed bootstrap STATE.md has the wrong pipeline marker")
240
+ if not re.search(
241
+ rf"^branch:\s*{re.escape(request.branch)}\s*(?:#.*)?$",
242
+ state_text,
243
+ re.MULTILINE,
244
+ ):
245
+ raise BootstrapError("completed bootstrap STATE.md has the wrong branch binding")
246
+
247
+ expected = {
248
+ "Kind": "new-github",
249
+ "Remote": f"https://github.com/{request.repository}",
250
+ "Visibility": request.visibility,
251
+ "Default checkout": request.default_checkout,
252
+ "GSD Path branch": request.branch,
253
+ "Primary worktree": request.worktree,
254
+ }
255
+ for name, value in expected.items():
256
+ if binding_field(binding_text, name) != value:
257
+ raise BootstrapError(f"completed repository binding has the wrong {name}")
258
+ remote_default = binding_field(binding_text, "Remote default")
259
+ base = binding_field(binding_text, "Remote default SHA")
260
+ if not re.fullmatch(r"[0-9a-f]{40}", base):
261
+ raise BootstrapError("completed repository binding has an invalid base SHA")
262
+ return remote_default, base
263
+
264
+
265
+ def transaction_stages(request: BootstrapRequest) -> dict:
266
+ binding = completed_binding(request)
267
+ return {
268
+ "remote": remote_exists(request),
269
+ "default_checkout": request.checkout_path.exists(),
270
+ "worktree": request.worktree_path.exists(),
271
+ "pipeline": binding is not None,
272
+ }
273
+
274
+
275
+ def preview(request: BootstrapRequest) -> dict:
276
+ gh_authentication()
277
+ stages = transaction_stages(request)
278
+ if stages["pipeline"]:
279
+ verify_completed_binding(request)
280
+ return {**request_payload(request), "mode": "complete", "stages": stages}
281
+ if read_journal(request) is not None:
282
+ return {**request_payload(request), "mode": "resume", "stages": stages}
283
+ if stages["remote"]:
284
+ raise BootstrapError(
285
+ "GitHub repository already exists without this approved transaction"
286
+ )
287
+ for label, path in (
288
+ ("default checkout", request.checkout_path),
289
+ ("linked worktree", request.worktree_path),
290
+ ):
291
+ if path.exists() or path.is_symlink():
292
+ raise BootstrapError(f"{label} path already exists: {path}")
293
+ return {**request_payload(request), "mode": "create", "stages": stages}
294
+
295
+
296
+ def create_remote(request: BootstrapRequest) -> None:
297
+ command = [
298
+ "gh",
299
+ "repo",
300
+ "create",
301
+ request.repository,
302
+ f"--{request.visibility}",
303
+ "--add-readme",
304
+ ]
305
+ if request.description:
306
+ command.extend(("--description", request.description))
307
+ require_success(
308
+ run(*command, cwd=request.workspace_path), "create GitHub repository"
309
+ )
310
+
311
+
312
+ def git_output(repository: Path, *arguments: str) -> str:
313
+ return require_success(run("git", "-C", str(repository), *arguments), "run Git")
314
+
315
+
316
+ def verify_origin(request: BootstrapRequest) -> None:
317
+ origin = git_output(request.checkout_path, "remote", "get-url", "origin")
318
+ resolved = require_success(
319
+ run(
320
+ "gh",
321
+ "repo",
322
+ "view",
323
+ origin,
324
+ "--json",
325
+ "nameWithOwner",
326
+ "--jq",
327
+ ".nameWithOwner",
328
+ ),
329
+ "verify cloned repository origin",
330
+ )
331
+ if resolved.casefold() != request.repository.casefold():
332
+ raise BootstrapError(
333
+ f"default checkout origin belongs to {resolved!r}, not {request.repository!r}"
334
+ )
335
+
336
+
337
+ def verify_checkout_location(request: BootstrapRequest) -> Path:
338
+ checkout = request.checkout_path
339
+ require_real_directory(checkout, "default checkout")
340
+ top_level = Path(git_output(checkout, "rev-parse", "--show-toplevel")).resolve()
341
+ if top_level != checkout:
342
+ raise BootstrapError(f"default checkout is not its Git root: {checkout}")
343
+ verify_origin(request)
344
+ if git_output(checkout, "status", "--porcelain"):
345
+ raise BootstrapError("default checkout is not clean")
346
+ return checkout
347
+
348
+
349
+ def verify_checkout_branch(checkout: Path, remote_default: str) -> None:
350
+ if git_output(checkout, "branch", "--show-current") != remote_default:
351
+ raise BootstrapError("default checkout is not on the remote default branch")
352
+
353
+
354
+ def verify_checkout_revision(checkout: Path, remote_default: str, base: str) -> None:
355
+ verify_checkout_branch(checkout, remote_default)
356
+ if git_output(checkout, "rev-parse", "HEAD") != base:
357
+ raise BootstrapError("default checkout does not match the remote-default SHA")
358
+
359
+
360
+ def verify_completed_checkout_revision(
361
+ checkout: Path, remote_default: str, creation_sha: str
362
+ ) -> None:
363
+ verify_checkout_branch(checkout, remote_default)
364
+ verify_creation_ancestry(checkout, creation_sha, "completed default checkout")
365
+
366
+
367
+ def verify_checkout(request: BootstrapRequest) -> tuple[str, str]:
368
+ checkout = verify_checkout_location(request)
369
+
370
+ require_success(
371
+ run("git", "-C", str(checkout), "fetch", "--prune", "origin"),
372
+ "refresh remote default branch",
373
+ )
374
+ require_success(
375
+ run("git", "-C", str(checkout), "remote", "set-head", "origin", "--auto"),
376
+ "resolve remote default branch",
377
+ )
378
+ remote_head = run(
379
+ "git",
380
+ "-C",
381
+ str(checkout),
382
+ "symbolic-ref",
383
+ "--quiet",
384
+ "--short",
385
+ "refs/remotes/origin/HEAD",
386
+ )
387
+ remote_ref = require_success(remote_head, "resolve remote default branch")
388
+ if not remote_ref.startswith("origin/"):
389
+ raise BootstrapError(f"remote default ref is invalid: {remote_ref}")
390
+ remote_default = remote_ref.removeprefix("origin/")
391
+ if remote_default == request.branch:
392
+ raise BootstrapError(
393
+ f"remote default {remote_default!r} must stay a trunk; "
394
+ "the GSD Path branch cannot be the GitHub default"
395
+ )
396
+ if remote_default != "main":
397
+ raise BootstrapError(f"remote default must be main, got {remote_default!r}")
398
+ base = git_output(checkout, "rev-parse", remote_ref)
399
+ verify_checkout_revision(checkout, remote_default, base)
400
+ return remote_default, base
401
+
402
+
403
+ def clone_or_verify_checkout(request: BootstrapRequest) -> tuple[str, str]:
404
+ checkout = request.checkout_path
405
+ if not checkout.exists():
406
+ require_success(
407
+ run("gh", "repo", "clone", request.repository, str(checkout)),
408
+ "clone GitHub repository",
409
+ )
410
+ return verify_checkout(request)
411
+
412
+
413
+ def git_common_directory(repository: Path) -> Path:
414
+ value = git_output(repository, "rev-parse", "--git-common-dir")
415
+ path = Path(value)
416
+ if not path.is_absolute():
417
+ path = repository / path
418
+ return path.resolve()
419
+
420
+
421
+ def verify_creation_ancestry(
422
+ repository: Path, creation_sha: str, label: str
423
+ ) -> None:
424
+ ancestry = run(
425
+ "git",
426
+ "-C",
427
+ str(repository),
428
+ "merge-base",
429
+ "--is-ancestor",
430
+ creation_sha,
431
+ "HEAD",
432
+ )
433
+ if ancestry.returncode != 0:
434
+ raise BootstrapError(
435
+ f"{label} does not descend from the recorded creation SHA"
436
+ )
437
+
438
+
439
+ def allowed_partial_project_status(worktree: Path) -> bool:
440
+ status = git_output(worktree, "status", "--porcelain", "--untracked-files=all")
441
+ if not status:
442
+ return True
443
+ allowed = {".project/STATE.md", ".project/REPOSITORY.md"}
444
+ paths = {line[3:] for line in status.splitlines() if len(line) > 3}
445
+ return bool(paths) and paths <= allowed
446
+
447
+
448
+ def create_or_verify_worktree(request: BootstrapRequest, base: str) -> None:
449
+ checkout = request.checkout_path
450
+ worktree = request.worktree_path
451
+ if not worktree.exists():
452
+ branch_ref = f"refs/heads/{request.branch}"
453
+ branch = run("git", "-C", str(checkout), "rev-parse", "--verify", branch_ref)
454
+ if branch.returncode == 0:
455
+ if branch.stdout.strip() != base:
456
+ raise BootstrapError(
457
+ "existing GSD Path branch is not at the approved base SHA"
458
+ )
459
+ command = (
460
+ "git",
461
+ "-C",
462
+ str(checkout),
463
+ "worktree",
464
+ "add",
465
+ str(worktree),
466
+ request.branch,
467
+ )
468
+ else:
469
+ command = (
470
+ "git",
471
+ "-C",
472
+ str(checkout),
473
+ "worktree",
474
+ "add",
475
+ "-b",
476
+ request.branch,
477
+ str(worktree),
478
+ base,
479
+ )
480
+ require_success(run(*command), "create linked GSD Path worktree")
481
+
482
+ verify_worktree(request, base)
483
+
484
+
485
+ def verify_worktree(request: BootstrapRequest, base: str) -> None:
486
+ worktree = verify_worktree_location(request)
487
+ if git_output(worktree, "rev-parse", "HEAD") != base:
488
+ raise BootstrapError("linked worktree is not at the approved base SHA")
489
+ if not allowed_partial_project_status(worktree):
490
+ raise BootstrapError("linked worktree contains unexpected changes")
491
+
492
+
493
+ def verify_worktree_location(request: BootstrapRequest) -> Path:
494
+ worktree = request.worktree_path
495
+
496
+ require_real_directory(worktree, "linked GSD Path worktree")
497
+ top_level = Path(git_output(worktree, "rev-parse", "--show-toplevel")).resolve()
498
+ if top_level != worktree:
499
+ raise BootstrapError(f"linked worktree is not its Git root: {worktree}")
500
+ if git_common_directory(request.checkout_path) != git_common_directory(worktree):
501
+ raise BootstrapError("linked worktree belongs to another repository")
502
+ if git_output(worktree, "branch", "--show-current") != request.branch:
503
+ raise BootstrapError("linked worktree is on the wrong branch")
504
+ return worktree
505
+
506
+
507
+ def verify_completed_worktree(request: BootstrapRequest, creation_sha: str) -> None:
508
+ worktree = verify_worktree_location(request)
509
+ verify_creation_ancestry(worktree, creation_sha, "completed linked worktree")
510
+
511
+
512
+ def verify_completed_binding(request: BootstrapRequest) -> tuple[str, str]:
513
+ binding = completed_binding(request)
514
+ if binding is None:
515
+ raise BootstrapError("completed repository binding is missing")
516
+ expected_default, expected_base = binding
517
+ verify_remote_visibility(request)
518
+ checkout = verify_checkout_location(request)
519
+ try:
520
+ verify_completed_checkout_revision(
521
+ checkout, expected_default, expected_base
522
+ )
523
+ except BootstrapError as error:
524
+ raise BootstrapError(
525
+ f"completed repository binding does not match the checkout: {error}"
526
+ ) from error
527
+ verify_completed_worktree(request, expected_base)
528
+ return expected_default, expected_base
529
+
530
+
531
+ def render_state(template: Path, request: BootstrapRequest) -> str:
532
+ if template.is_symlink() or not template.is_file():
533
+ raise BootstrapError(f"state template must be a real file: {template}")
534
+ content = template.read_text(encoding="utf-8")
535
+ content = content.replace("project: <slug>", f"project: {request.slug}")
536
+ content = content.replace("branch: null", f"branch: {request.branch}")
537
+ content = content.replace(
538
+ "- YYYY-MM-DD — <phase> — project initialized",
539
+ "- repository initialized — binding: .project/REPOSITORY.md",
540
+ )
541
+ if "<slug>" in content or "branch: null" in content:
542
+ raise BootstrapError("state template placeholders were not fully replaced")
543
+ return content
544
+
545
+
546
+ def render_binding(
547
+ template: Path,
548
+ request: BootstrapRequest,
549
+ remote_default: str,
550
+ base: str,
551
+ ) -> str:
552
+ if template.is_symlink() or not template.is_file():
553
+ raise BootstrapError(f"repository template must be a real file: {template}")
554
+ replacements = {
555
+ "<kind>": "new-github",
556
+ "<remote>": f"https://github.com/{request.repository}",
557
+ "<visibility>": request.visibility,
558
+ "<remote-default>": remote_default,
559
+ "<remote-default-sha>": base,
560
+ "<default-checkout>": request.default_checkout,
561
+ "<branch>": request.branch,
562
+ "<primary-worktree>": request.worktree,
563
+ }
564
+ content = template.read_text(encoding="utf-8")
565
+ for placeholder, value in replacements.items():
566
+ content = content.replace(placeholder, value)
567
+ if any(placeholder in content for placeholder in replacements):
568
+ raise BootstrapError("repository template placeholders were not fully replaced")
569
+ return content
570
+
571
+
572
+ def write_exact(path: Path, content: str) -> None:
573
+ if path.exists() or path.is_symlink():
574
+ if path.is_symlink() or not path.is_file():
575
+ raise BootstrapError(f"bootstrap artifact must be a real file: {path}")
576
+ if path.read_text(encoding="utf-8") != content:
577
+ raise BootstrapError(f"bootstrap artifact differs from approved transaction: {path}")
578
+ return
579
+ atomic_write(path, content)
580
+
581
+
582
+ def initialize_pipeline(
583
+ request: BootstrapRequest,
584
+ remote_default: str,
585
+ base: str,
586
+ state_template: Path,
587
+ repository_template: Path,
588
+ ) -> None:
589
+ project = request.worktree_path / ".project"
590
+ staging = request.worktree_path / ".project.gsd-path-tmp"
591
+ if project.exists() or project.is_symlink():
592
+ if project.is_symlink() or not project.is_dir():
593
+ raise BootstrapError(f"pipeline directory must be a real directory: {project}")
594
+ write_exact(project / "STATE.md", render_state(state_template, request))
595
+ write_exact(
596
+ project / "REPOSITORY.md",
597
+ render_binding(repository_template, request, remote_default, base),
598
+ )
599
+ return
600
+ if staging.exists() or staging.is_symlink():
601
+ if staging.is_symlink() or not staging.is_dir():
602
+ raise BootstrapError(f"pipeline staging path is invalid: {staging}")
603
+ shutil.rmtree(staging)
604
+ staging.mkdir()
605
+ try:
606
+ atomic_write(staging / "STATE.md", render_state(state_template, request))
607
+ atomic_write(
608
+ staging / "REPOSITORY.md",
609
+ render_binding(repository_template, request, remote_default, base),
610
+ )
611
+ os.replace(staging, project)
612
+ finally:
613
+ if staging.exists():
614
+ shutil.rmtree(staging)
615
+
616
+
617
+ def remove_journal(request: BootstrapRequest) -> None:
618
+ transaction_directory(request)
619
+ request.journal_path.unlink()
620
+ directory = request.journal_path.parent
621
+ if not any(directory.iterdir()):
622
+ directory.rmdir()
623
+
624
+
625
+ def create(
626
+ request: BootstrapRequest,
627
+ state_template: Path,
628
+ repository_template: Path,
629
+ ) -> dict:
630
+ gh_authentication()
631
+ if completed_binding(request) is not None:
632
+ remote_default, base = verify_completed_binding(request)
633
+ if request.journal_path.exists():
634
+ read_journal(request)
635
+ remove_journal(request)
636
+ return {
637
+ **request_payload(request),
638
+ "status": "complete",
639
+ "remote_default": remote_default,
640
+ "remote_default_sha": base,
641
+ }
642
+
643
+ journal = read_journal(request)
644
+ if journal is None:
645
+ preview_result = preview(request)
646
+ if preview_result["mode"] != "create":
647
+ raise BootstrapError("repository transaction is not ready to create")
648
+ write_journal(request)
649
+
650
+ if not remote_exists(request):
651
+ create_remote(request)
652
+ verify_remote_visibility(request)
653
+ remote_default, base = clone_or_verify_checkout(request)
654
+ create_or_verify_worktree(request, base)
655
+ initialize_pipeline(
656
+ request,
657
+ remote_default,
658
+ base,
659
+ state_template.resolve(),
660
+ repository_template.resolve(),
661
+ )
662
+ remove_journal(request)
663
+ return {
664
+ **request_payload(request),
665
+ "status": "complete",
666
+ "remote_default": remote_default,
667
+ "remote_default_sha": base,
668
+ }
669
+
670
+
671
+ def parser() -> argparse.ArgumentParser:
672
+ argument_parser = argparse.ArgumentParser(description=__doc__)
673
+ argument_parser.add_argument("command", choices=("preview", "create"))
674
+ argument_parser.add_argument("--workspace", type=Path, required=True)
675
+ argument_parser.add_argument("--owner", required=True)
676
+ argument_parser.add_argument("--repo", required=True)
677
+ argument_parser.add_argument(
678
+ "--visibility", choices=("public", "private", "internal"), required=True
679
+ )
680
+ argument_parser.add_argument("--default-checkout", type=Path, required=True)
681
+ argument_parser.add_argument("--worktree", type=Path, required=True)
682
+ argument_parser.add_argument("--description")
683
+ argument_parser.add_argument("--state-template", type=Path)
684
+ argument_parser.add_argument("--repository-template", type=Path)
685
+ return argument_parser
686
+
687
+
688
+ def main(argv: Optional[Sequence[str]] = None) -> int:
689
+ try:
690
+ arguments = parser().parse_args(argv)
691
+ request = request_from_arguments(arguments)
692
+ if arguments.command == "preview":
693
+ result = preview(request)
694
+ else:
695
+ if arguments.state_template is None or arguments.repository_template is None:
696
+ raise BootstrapError("create requires both bundled template paths")
697
+ result = create(
698
+ request,
699
+ arguments.state_template,
700
+ arguments.repository_template,
701
+ )
702
+ print(json.dumps(result, sort_keys=True))
703
+ return 0
704
+ except BootstrapError as error:
705
+ print(str(error), file=sys.stderr)
706
+ return 1
707
+
708
+
709
+ if __name__ == "__main__":
710
+ raise SystemExit(main())