@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,619 @@
1
+ #!/usr/bin/env python3
2
+ # gsd-path project runtime
3
+ """Manage GSD Path discussion records as a recoverable paired transaction."""
4
+
5
+ import argparse
6
+ import json
7
+ import os
8
+ import shutil
9
+ import sys
10
+ from contextlib import contextmanager
11
+ from datetime import date
12
+ from pathlib import Path
13
+ from typing import Iterator, Optional, Sequence
14
+
15
+ def _load_pipeline_modules():
16
+ try:
17
+ import archive_milestone
18
+ import pipeline_state
19
+ import _common
20
+ return _common, archive_milestone, pipeline_state
21
+ except ModuleNotFoundError as error:
22
+ if error.name not in {"archive_milestone", "pipeline_state", "_common"}:
23
+ raise
24
+ try:
25
+ from scripts import archive_milestone, pipeline_state, _common
26
+ return _common, archive_milestone, pipeline_state
27
+ except ModuleNotFoundError as error:
28
+ if error.name not in {
29
+ "scripts",
30
+ "scripts.archive_milestone",
31
+ "scripts.pipeline_state",
32
+ "scripts._common",
33
+ }:
34
+ raise
35
+ shared_scripts = Path(__file__).resolve().parents[2] / "gsd-path" / "scripts"
36
+ sys.path.insert(0, str(shared_scripts))
37
+ import archive_milestone
38
+ import pipeline_state
39
+ import _common
40
+ return _common, archive_milestone, pipeline_state
41
+
42
+
43
+ _common, archive_milestone, pipeline_state = _load_pipeline_modules()
44
+
45
+
46
+ class DiscussionError(RuntimeError):
47
+ pass
48
+
49
+
50
+ APPEND_TRANSACTION = ".discussion-append-transaction.json"
51
+ FILES = ("DIALOGUE.md", "ANSWERS.md")
52
+ PHASES = {"inspect", "define", "research", "decide", "roadmap", "plan", "build", "ship"}
53
+
54
+
55
+ atomic_write = _common.atomic_write
56
+
57
+
58
+ def project_root(repo: Path) -> tuple[Path, Path, Path]:
59
+ root = repo.resolve()
60
+ project = root / ".project"
61
+ state = project / "STATE.md"
62
+ if project.is_symlink() or not project.is_dir():
63
+ raise DiscussionError(f".project must be a real directory: {project}")
64
+ if state.is_symlink() or not state.is_file():
65
+ raise DiscussionError(f"STATE.md must be a real file: {state}")
66
+ return root, project, state
67
+
68
+
69
+ @contextmanager
70
+ def state_lock(state: Path) -> Iterator[None]:
71
+ with archive_milestone.discussion_lock(state.parent):
72
+ yield
73
+
74
+
75
+ def state_context(state: Path) -> tuple[str, str, str]:
76
+ try:
77
+ current, _content, loaded = pipeline_state.load_state(state.parent.parent)
78
+ except pipeline_state.PipelineStateError as error:
79
+ raise DiscussionError(str(error)) from error
80
+ if loaded != state:
81
+ raise DiscussionError(f"canonical STATE.md path changed: {loaded}")
82
+ phase = current.phase
83
+ status = current.status
84
+ configured_archive = current.archive or "null"
85
+ if phase not in PHASES or status not in {"active", "blocked", "done"}:
86
+ raise DiscussionError(f"discussion is not available in state {phase}/{status}")
87
+ return phase, status, configured_archive
88
+
89
+
90
+ def template_header(path: Path, record_prefix: str) -> str:
91
+ if path.is_symlink() or not path.is_file() or not path.is_absolute():
92
+ raise DiscussionError(f"template must be an absolute real file: {path}")
93
+ content = path.read_text(encoding="utf-8")
94
+ marker = f"\n{record_prefix}"
95
+ if marker not in content:
96
+ raise DiscussionError(f"template lacks sample record: {path}")
97
+ return content.split(marker, 1)[0].rstrip() + "\n"
98
+
99
+
100
+ def record_count(directory: Path) -> int:
101
+ lines = (directory / "DIALOGUE.md").read_text(encoding="utf-8").splitlines()
102
+ return sum(
103
+ archive_milestone.DIALOGUE_HEADING_PATTERN.fullmatch(line) is not None
104
+ for line in lines
105
+ )
106
+
107
+
108
+ def validate_or_empty(directory: Path) -> None:
109
+ if directory.is_symlink() or not directory.is_dir():
110
+ raise DiscussionError("discussion records must be a real directory")
111
+ if sorted(path.name for path in directory.iterdir()) != sorted(FILES):
112
+ raise DiscussionError("discussion directory must contain exactly DIALOGUE.md and ANSWERS.md")
113
+ if record_count(directory) == 0:
114
+ if "### D" in (directory / "DIALOGUE.md").read_text(encoding="utf-8"):
115
+ raise DiscussionError("empty discussion has a malformed dialogue record")
116
+ if "## Answer A" in (directory / "ANSWERS.md").read_text(encoding="utf-8"):
117
+ raise DiscussionError("empty discussion has a malformed answer record")
118
+ return
119
+ try:
120
+ archive_milestone.validate_discussion_directory(
121
+ directory, require_dispositions=False
122
+ )
123
+ except archive_milestone.ArchiveError as error:
124
+ raise DiscussionError(str(error)) from error
125
+
126
+
127
+ def finish_append(project: Path, discussion: Path) -> None:
128
+ transaction = project / APPEND_TRANSACTION
129
+ if not transaction.exists():
130
+ return
131
+ if transaction.is_symlink() or not transaction.is_file():
132
+ raise DiscussionError("discussion append journal must be a real file")
133
+ try:
134
+ payload = json.loads(transaction.read_text(encoding="utf-8"))
135
+ except (OSError, json.JSONDecodeError) as error:
136
+ raise DiscussionError(f"discussion append journal is unreadable: {error}") from error
137
+ if payload.get("schema") != "gsd-path/discussion-append/v1":
138
+ raise DiscussionError("discussion append journal has the wrong schema")
139
+ files = payload.get("files")
140
+ if (
141
+ not isinstance(files, dict)
142
+ or set(files) != set(FILES)
143
+ or any(not isinstance(files[name], str) for name in FILES)
144
+ ):
145
+ raise DiscussionError("discussion append journal has invalid files")
146
+ if project.is_symlink() or not project.is_dir():
147
+ raise DiscussionError("discussion recovery requires a real .project directory")
148
+ if discussion.is_symlink() or (
149
+ discussion.exists() and not discussion.is_dir()
150
+ ):
151
+ raise DiscussionError("discussion recovery destination must be a real directory")
152
+ if discussion.resolve(strict=False).parent != project.resolve():
153
+ raise DiscussionError("discussion recovery destination escapes .project")
154
+ discussion.mkdir(exist_ok=True)
155
+ for name in FILES:
156
+ atomic_write(discussion / name, files[name])
157
+ validate_or_empty(discussion)
158
+ transaction.unlink()
159
+
160
+
161
+ def validate_contents(project: Path, files: dict) -> None:
162
+ staging = project / ".discussion-validation-tmp"
163
+ if staging.exists() or staging.is_symlink():
164
+ if staging.is_symlink() or not staging.is_dir():
165
+ raise DiscussionError(f"discussion validation path is invalid: {staging}")
166
+ shutil.rmtree(staging)
167
+ staging.mkdir()
168
+ try:
169
+ for name in FILES:
170
+ (staging / name).write_text(files[name], encoding="utf-8")
171
+ validate_or_empty(staging)
172
+ finally:
173
+ shutil.rmtree(staging)
174
+
175
+
176
+ def initialize_records(discussion: Path, dialogue_template: Path, answers_template: Path) -> None:
177
+ if discussion.exists() or discussion.is_symlink():
178
+ validate_or_empty(discussion)
179
+ return
180
+ staging = discussion.parent / ".discuss.gsd-path-tmp"
181
+ if staging.exists() or staging.is_symlink():
182
+ if staging.is_symlink() or not staging.is_dir():
183
+ raise DiscussionError(f"discussion staging path is invalid: {staging}")
184
+ shutil.rmtree(staging)
185
+ staging.mkdir()
186
+ try:
187
+ (staging / "DIALOGUE.md").write_text(
188
+ template_header(dialogue_template, "### D001"), encoding="utf-8"
189
+ )
190
+ (staging / "ANSWERS.md").write_text(
191
+ template_header(answers_template, "## Answer A001"), encoding="utf-8"
192
+ )
193
+ os.replace(staging, discussion)
194
+ finally:
195
+ if staging.exists():
196
+ shutil.rmtree(staging)
197
+
198
+
199
+ def prepare_locked(
200
+ root: Path,
201
+ project: Path,
202
+ state: Path,
203
+ dialogue_template: Path,
204
+ answers_template: Path,
205
+ ) -> tuple[Path, str, str]:
206
+ phase, status, configured = state_context(state)
207
+ discussion = project / "discuss"
208
+ finish_append(project, discussion)
209
+ if not archive_milestone.is_unset(configured):
210
+ if (phase, status) != ("ship", "active"):
211
+ raise DiscussionError("archive recovery is allowed only in ship/active")
212
+ try:
213
+ archive = archive_milestone.archive_from_state(root, configured)
214
+ except archive_milestone.ArchiveError as error:
215
+ raise DiscussionError(str(error)) from error
216
+ if archive_milestone.archive_is_committed(root, configured):
217
+ raise DiscussionError("committed discussion archives are immutable")
218
+ archived = archive / "discuss"
219
+ if not archived.is_dir():
220
+ raise DiscussionError("uncommitted archive has no discussion records to resume")
221
+ if not discussion.exists():
222
+ staging = project / ".discuss.gsd-path-tmp"
223
+ if staging.exists():
224
+ shutil.rmtree(staging)
225
+ shutil.copytree(archived, staging)
226
+ os.replace(staging, discussion)
227
+ else:
228
+ try:
229
+ archive_milestone.require_append_only_discussion(discussion, archived)
230
+ except archive_milestone.ArchiveError as error:
231
+ raise DiscussionError(str(error)) from error
232
+ else:
233
+ initialize_records(discussion, dialogue_template, answers_template)
234
+ validate_or_empty(discussion)
235
+ if state_context(state) != (phase, status, configured):
236
+ raise DiscussionError("STATE.md changed while discussion records were prepared")
237
+ return discussion, phase, status
238
+
239
+
240
+ def numbered_sections(lines: Sequence[str], pattern, boundaries) -> list[tuple]:
241
+ return list(archive_milestone.record_sections(lines, pattern, boundaries))
242
+
243
+
244
+ def field(lines: Sequence[str], name: str) -> str:
245
+ try:
246
+ return archive_milestone.completed_bullet_field(lines, name, "ANSWERS.md")
247
+ except archive_milestone.ArchiveError as error:
248
+ raise DiscussionError(str(error)) from error
249
+
250
+
251
+ def disposed_answers(answer_lines: Sequence[str]) -> set[str]:
252
+ boundaries = (
253
+ archive_milestone.ANSWER_HEADING_PATTERN,
254
+ archive_milestone.DISPOSITION_HEADING_PATTERN,
255
+ )
256
+ dispositions = numbered_sections(
257
+ answer_lines, archive_milestone.DISPOSITION_HEADING_PATTERN, boundaries
258
+ )
259
+ return {field(section, "Answer") for _, section in dispositions}
260
+
261
+
262
+ def dialogue_threads(dialogue_lines: Sequence[str]) -> dict[str, list[tuple]]:
263
+ sections = numbered_sections(
264
+ dialogue_lines,
265
+ archive_milestone.DIALOGUE_HEADING_PATTERN,
266
+ (archive_milestone.DIALOGUE_HEADING_PATTERN,),
267
+ )
268
+ threads: dict[str, list[tuple]] = {}
269
+ for match, section in sections:
270
+ threads.setdefault(field(section, "Thread"), []).append((match, section))
271
+ return threads
272
+
273
+
274
+ def thread_records(discussion: Path) -> list[dict]:
275
+ lines = (discussion / "DIALOGUE.md").read_text(encoding="utf-8").splitlines()
276
+ records = []
277
+ for thread, sections in dialogue_threads(lines).items():
278
+ match, section = sections[-1]
279
+ records.append(
280
+ {
281
+ "thread": thread,
282
+ "topic": match.group(5),
283
+ "last_turn": f"D{int(match.group(1)):03d}",
284
+ "status": field(section, "Thread status"),
285
+ }
286
+ )
287
+ return records
288
+
289
+
290
+ def pending_records(discussion: Path) -> list[dict]:
291
+ if record_count(discussion) == 0:
292
+ return []
293
+ lines = (discussion / "ANSWERS.md").read_text(encoding="utf-8").splitlines()
294
+ boundaries = (
295
+ archive_milestone.ANSWER_HEADING_PATTERN,
296
+ archive_milestone.DISPOSITION_HEADING_PATTERN,
297
+ )
298
+ answers = numbered_sections(lines, archive_milestone.ANSWER_HEADING_PATTERN, boundaries)
299
+ resolved = disposed_answers(lines)
300
+ pending = []
301
+ for match, section in answers:
302
+ identifier = f"A{int(match.group(1)):03d}"
303
+ if (
304
+ field(section, "Follow-up") == "required"
305
+ and field(section, "Status") in {"final", "NEEDS-USER"}
306
+ and identifier not in resolved
307
+ ):
308
+ pending.append(
309
+ {
310
+ "answer": identifier,
311
+ "owner": field(section, "Next owner"),
312
+ "target": field(section, "Target artifact"),
313
+ "status": field(section, "Status"),
314
+ }
315
+ )
316
+ return pending
317
+
318
+
319
+ def append_disposition(discussion: Path, project: Path, payload: dict) -> dict:
320
+ answer = required_text(payload, "answer")
321
+ pending = {record["answer"]: record for record in pending_records(discussion)}
322
+ if answer not in pending:
323
+ raise DiscussionError(f"answer is not pending disposition: {answer}")
324
+ disposition_status = required_text(payload, "status")
325
+ if disposition_status not in {
326
+ "applied",
327
+ "acknowledged-no-change",
328
+ "superseded",
329
+ "rejected-by-user",
330
+ }:
331
+ raise DiscussionError("disposition status is invalid")
332
+ expected = pending[answer]
333
+ owner = single_line(payload, "owner")
334
+ artifact = single_line(payload, "artifact")
335
+ expected_owner = (
336
+ "user" if disposition_status == "rejected-by-user" else expected["owner"]
337
+ )
338
+ if owner != expected_owner:
339
+ raise DiscussionError(f"disposition owner must be {expected_owner}")
340
+ if artifact != expected["target"]:
341
+ raise DiscussionError(f"disposition artifact must be {expected['target']}")
342
+ lines = (discussion / "ANSWERS.md").read_text(encoding="utf-8").splitlines()
343
+ boundaries = (
344
+ archive_milestone.ANSWER_HEADING_PATTERN,
345
+ archive_milestone.DISPOSITION_HEADING_PATTERN,
346
+ )
347
+ existing = numbered_sections(
348
+ lines, archive_milestone.DISPOSITION_HEADING_PATTERN, boundaries
349
+ )
350
+ number = len(existing) + 1
351
+ record_date = iso_date(payload)
352
+ disposition = f"""\n## Disposition X{number:03d} — {record_date}
353
+
354
+ - **Answer**: {answer}
355
+ - **Status**: {disposition_status}
356
+ - **Owner**: {owner}
357
+ - **Artifact**: {artifact}
358
+ - **Evidence**: {single_line(payload, "evidence")}
359
+ """
360
+ files = {
361
+ "DIALOGUE.md": (discussion / "DIALOGUE.md").read_text(encoding="utf-8"),
362
+ "ANSWERS.md": (discussion / "ANSWERS.md").read_text(encoding="utf-8").rstrip()
363
+ + "\n"
364
+ + disposition,
365
+ }
366
+ validate_contents(project, files)
367
+ atomic_write(
368
+ project / APPEND_TRANSACTION,
369
+ json.dumps(
370
+ {"schema": "gsd-path/discussion-append/v1", "files": files},
371
+ sort_keys=True,
372
+ )
373
+ + "\n",
374
+ )
375
+ finish_append(project, discussion)
376
+ return {"answer": answer, "disposition": f"X{number:03d}"}
377
+
378
+
379
+ MULTILINE_FIELDS = ("user", "assistant")
380
+ SINGLE_LINE_FIELDS = (
381
+ "topic",
382
+ "thread",
383
+ "question",
384
+ "status",
385
+ "thread_status",
386
+ "conclusion",
387
+ "reasoning",
388
+ "evidence",
389
+ "research",
390
+ "confidence",
391
+ "unresolved",
392
+ "next_owner",
393
+ "target_artifact",
394
+ "follow_up",
395
+ )
396
+
397
+
398
+ def validate_payload_fields(payload: dict) -> None:
399
+ problems = []
400
+ for name in MULTILINE_FIELDS + SINGLE_LINE_FIELDS:
401
+ value = payload.get(name)
402
+ if not isinstance(value, str) or not value.strip():
403
+ problems.append(f"{name} (missing or empty)")
404
+ elif name in SINGLE_LINE_FIELDS and ("\n" in value or "\r" in value):
405
+ problems.append(f"{name} (must be one line)")
406
+ if problems:
407
+ raise DiscussionError("append payload invalid: " + ", ".join(problems))
408
+
409
+
410
+ def required_text(payload: dict, name: str) -> str:
411
+ value = payload.get(name)
412
+ if not isinstance(value, str) or not value.strip():
413
+ raise DiscussionError(f"append payload requires non-empty {name}")
414
+ return value.strip()
415
+
416
+
417
+ def single_line(payload: dict, name: str) -> str:
418
+ value = required_text(payload, name)
419
+ if "\n" in value or "\r" in value:
420
+ raise DiscussionError(f"append payload {name} must be one line")
421
+ return value
422
+
423
+
424
+ def quote_block(value: str) -> str:
425
+ return "\n".join(f"> {line}" if line else ">" for line in value.splitlines())
426
+
427
+
428
+ def indent_block(value: str) -> str:
429
+ return "\n".join(f" {line}" if line else " " for line in value.splitlines())
430
+
431
+
432
+ def iso_date(payload: dict) -> str:
433
+ value = payload.get("date", date.today().isoformat())
434
+ if not isinstance(value, str):
435
+ raise DiscussionError("date must be YYYY-MM-DD")
436
+ try:
437
+ parsed = date.fromisoformat(value)
438
+ except ValueError as error:
439
+ raise DiscussionError("date must be YYYY-MM-DD") from error
440
+ if parsed.isoformat() != value:
441
+ raise DiscussionError("date must be YYYY-MM-DD")
442
+ return value
443
+
444
+
445
+ def append_record(discussion: Path, project: Path, phase: str, status: str, payload: dict) -> dict:
446
+ count = record_count(discussion)
447
+ number = count + 1
448
+ dialogue_id = f"D{number:03d}"
449
+ answer_id = f"A{number:03d}"
450
+ dialogue_text = (discussion / "DIALOGUE.md").read_text(encoding="utf-8")
451
+ dialogue_lines = dialogue_text.splitlines()
452
+ if count:
453
+ archive_milestone.validate_discussion_directory(
454
+ discussion, require_dispositions=False
455
+ )
456
+ validate_payload_fields(payload)
457
+ answers_text = (discussion / "ANSWERS.md").read_text(encoding="utf-8")
458
+ requested_thread = payload["thread"]
459
+ existing_threads = dialogue_threads(dialogue_lines) if count else {}
460
+ if requested_thread == "new":
461
+ thread_number = max((int(value[1:]) for value in existing_threads), default=0) + 1
462
+ thread = f"T{thread_number:03d}"
463
+ reply_to = supersedes = "none"
464
+ elif requested_thread in existing_threads:
465
+ thread = requested_thread
466
+ prior = int(existing_threads[thread][-1][0].group(1))
467
+ reply_to = f"D{prior:03d}"
468
+ prior_answer = f"A{prior:03d}"
469
+ # A disposed answer is settled; a continuation replies without superseding it.
470
+ supersedes = (
471
+ "none"
472
+ if prior_answer in disposed_answers(answers_text.splitlines())
473
+ else prior_answer
474
+ )
475
+ else:
476
+ raise DiscussionError("thread must be new or an existing T### id")
477
+ record_date = iso_date(payload)
478
+ thread_status = single_line(payload, "thread_status")
479
+ answer_status = single_line(payload, "status")
480
+ if thread_status not in {"working", "NEEDS-USER", "final"}:
481
+ raise DiscussionError("thread_status is invalid")
482
+ if answer_status not in {"working", "NEEDS-USER", "final"}:
483
+ raise DiscussionError("status is invalid")
484
+ topic = single_line(payload, "topic")
485
+ evidence = single_line(payload, "evidence")
486
+ research = single_line(payload, "research")
487
+ dialogue = f"""\n### {dialogue_id} — {record_date} — {phase}/{status} — {topic}
488
+
489
+ - **Thread**: {thread}
490
+ - **Reply to**: {reply_to}
491
+ - **User (verbatim)**:
492
+
493
+ {quote_block(required_text(payload, "user"))}
494
+
495
+ - **Assistant**:
496
+
497
+ {indent_block(required_text(payload, "assistant"))}
498
+
499
+ - **Evidence checked**: {evidence}
500
+ - **Research**: {research}
501
+ - **Thread status**: {thread_status}
502
+ """
503
+ answer = f"""\n## Answer {answer_id} — {record_date} — {topic}
504
+
505
+ - **Thread**: {thread}
506
+ - **Turn**: {dialogue_id}
507
+ - **Supersedes**: {supersedes}
508
+ - **Question**: {single_line(payload, "question")}
509
+ - **Status**: {answer_status}
510
+ - **Phase/status**: {phase}/{status}
511
+ - **Conclusion**: {single_line(payload, "conclusion")}
512
+ - **Reasoning / pushback**: {single_line(payload, "reasoning")}
513
+ - **Evidence**: {evidence}
514
+ - **Research**: {research}
515
+ - **Confidence**: {single_line(payload, "confidence")}
516
+ - **Unresolved**: {single_line(payload, "unresolved")}
517
+ - **Next owner**: {single_line(payload, "next_owner")}
518
+ - **Target artifact**: {single_line(payload, "target_artifact")}
519
+ - **Follow-up**: {single_line(payload, "follow_up")}
520
+ """
521
+ files = {
522
+ "DIALOGUE.md": dialogue_text.rstrip() + "\n" + dialogue,
523
+ "ANSWERS.md": answers_text.rstrip() + "\n" + answer,
524
+ }
525
+ validate_contents(project, files)
526
+ transaction = project / APPEND_TRANSACTION
527
+ atomic_write(
528
+ transaction,
529
+ json.dumps(
530
+ {"schema": "gsd-path/discussion-append/v1", "files": files},
531
+ sort_keys=True,
532
+ )
533
+ + "\n",
534
+ )
535
+ finish_append(project, discussion)
536
+ return {"answer": answer_id, "dialogue": dialogue_id, "thread": thread}
537
+
538
+
539
+ def load_payload(path: Path) -> dict:
540
+ if path.is_symlink() or not path.is_file():
541
+ raise DiscussionError(f"append input must be a real file: {path}")
542
+ try:
543
+ payload = json.loads(path.read_text(encoding="utf-8"))
544
+ except (OSError, json.JSONDecodeError) as error:
545
+ raise DiscussionError(f"append input is unreadable: {error}") from error
546
+ if not isinstance(payload, dict):
547
+ raise DiscussionError("append input must contain one JSON object")
548
+ return payload
549
+
550
+
551
+ def parser() -> argparse.ArgumentParser:
552
+ result = argparse.ArgumentParser(description=__doc__)
553
+ result.add_argument(
554
+ "command", choices=("prepare", "append", "pending", "dispose", "threads")
555
+ )
556
+ result.add_argument("--repo", type=Path, required=True)
557
+ result.add_argument("--dialogue-template", type=Path)
558
+ result.add_argument("--answers-template", type=Path)
559
+ result.add_argument("--input", type=Path)
560
+ return result
561
+
562
+
563
+ def main(argv: Optional[Sequence[str]] = None) -> int:
564
+ try:
565
+ arguments = parser().parse_args(argv)
566
+ root, project, state = project_root(arguments.repo)
567
+ with state_lock(state):
568
+ state_context(state)
569
+ if arguments.command in {"pending", "dispose", "threads"}:
570
+ discussion = project / "discuss"
571
+ finish_append(project, discussion)
572
+ if not discussion.exists():
573
+ if arguments.command == "dispose":
574
+ raise DiscussionError("discussion records do not exist")
575
+ result = {arguments.command: []}
576
+ else:
577
+ validate_or_empty(discussion)
578
+ if arguments.command == "pending":
579
+ result = {"pending": pending_records(discussion)}
580
+ elif arguments.command == "threads":
581
+ result = {"threads": thread_records(discussion)}
582
+ else:
583
+ if arguments.input is None:
584
+ raise DiscussionError("dispose requires --input")
585
+ result = append_disposition(
586
+ discussion, project, load_payload(arguments.input)
587
+ )
588
+ else:
589
+ if arguments.dialogue_template is None or arguments.answers_template is None:
590
+ raise DiscussionError("prepare and append require both bundled template paths")
591
+ discussion, phase, status = prepare_locked(
592
+ root,
593
+ project,
594
+ state,
595
+ arguments.dialogue_template.resolve(),
596
+ arguments.answers_template.resolve(),
597
+ )
598
+ if arguments.command == "prepare":
599
+ result = {
600
+ "dialogue": str((discussion / "DIALOGUE.md").resolve()),
601
+ "answers": str((discussion / "ANSWERS.md").resolve()),
602
+ "phase_status": f"{phase}/{status}",
603
+ "records": record_count(discussion),
604
+ }
605
+ else:
606
+ if arguments.input is None:
607
+ raise DiscussionError("append requires --input")
608
+ result = append_record(
609
+ discussion, project, phase, status, load_payload(arguments.input)
610
+ )
611
+ print(json.dumps(result, sort_keys=True))
612
+ return 0
613
+ except (DiscussionError, archive_milestone.ArchiveError) as error:
614
+ print(str(error), file=sys.stderr)
615
+ return 1
616
+
617
+
618
+ if __name__ == "__main__":
619
+ raise SystemExit(main())
@@ -0,0 +1,29 @@
1
+ # GSD Path Discussion — Answers
2
+
3
+ <!-- Written by $gsd-path-discuss. Append one record per turn; never rewrite prior answers. -->
4
+
5
+ ## Answer A001 — <YYYY-MM-DD> — <topic>
6
+
7
+ - **Thread**: T001
8
+ - **Turn**: D001
9
+ - **Supersedes**: none | A###
10
+ - **Question**: <the question being answered>
11
+ - **Status**: working | NEEDS-USER | final
12
+ - **Phase/status**: <phase>/<status>
13
+ - **Conclusion**: <answer first>
14
+ - **Reasoning / pushback**: <why, including rejected premise or safer alternative>
15
+ - **Evidence**: <exact repository paths and line references; commands only if run>
16
+ - **Research**: <not needed — reason, or query, checked sources, and confidence>
17
+ - **Confidence**: high | medium | low | unverifiable
18
+ - **Unresolved**: <none, or NEEDS-USER / RESEARCH question>
19
+ - **Next owner**: <none, user, or named GSD Path phase>
20
+ - **Target artifact**: <none or canonical .project path>
21
+ - **Follow-up**: none | required
22
+
23
+ ## Disposition X001 — <YYYY-MM-DD>
24
+
25
+ - **Answer**: A###
26
+ - **Status**: applied | acknowledged-no-change | superseded | rejected-by-user
27
+ - **Owner**: <phase or router>
28
+ - **Artifact**: <canonical path or none>
29
+ - **Evidence**: <what was changed, why no change was needed, or the user ruling>
@@ -0,0 +1,21 @@
1
+ # GSD Path Discussion — Dialogue
2
+
3
+ <!-- Written by $gsd-path-discuss. Append turns; never rewrite prior turns. -->
4
+
5
+ ## Turns
6
+
7
+ ### D001 — <YYYY-MM-DD> — <phase>/<status> — <topic>
8
+
9
+ - **Thread**: T001
10
+ - **Reply to**: none | D###
11
+ - **User (verbatim)**:
12
+
13
+ > <copy the user's message exactly>
14
+
15
+ - **Assistant**:
16
+
17
+ <copy the answer returned to the user>
18
+
19
+ - **Evidence checked**: <repository paths, line references, commands actually run, or none>
20
+ - **Research**: <not needed — reason, or query/sources/confidence>
21
+ - **Thread status**: working | NEEDS-USER | final