@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,715 @@
1
+ #!/usr/bin/env python3
2
+ # gsd-path project runtime
3
+ """Read-only diagnosis of a stuck GSD Path pipeline.
4
+
5
+ Runs existing helpers and git probes. Never mutates the worktree, state, or
6
+ refs. Findings name the exact helper command to retry; they never invent git.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ import json
13
+ import shlex
14
+ import sys
15
+ from pathlib import Path
16
+ from typing import Callable, Optional, Sequence
17
+
18
+
19
+ def _load_diagnose_modules():
20
+ try:
21
+ import archive_milestone
22
+ import detect_project
23
+ import discussion_records
24
+ import isolation
25
+ import pipeline_git
26
+ import pipeline_state
27
+ return (
28
+ archive_milestone,
29
+ detect_project,
30
+ discussion_records,
31
+ isolation,
32
+ pipeline_git,
33
+ pipeline_state,
34
+ )
35
+ except ModuleNotFoundError as error:
36
+ if error.name not in {
37
+ "archive_milestone",
38
+ "detect_project",
39
+ "discussion_records",
40
+ "isolation",
41
+ "pipeline_git",
42
+ "pipeline_state",
43
+ }:
44
+ raise
45
+ try:
46
+ from scripts import (
47
+ archive_milestone,
48
+ detect_project,
49
+ discussion_records,
50
+ isolation,
51
+ pipeline_git,
52
+ pipeline_state,
53
+ )
54
+ return (
55
+ archive_milestone,
56
+ detect_project,
57
+ discussion_records,
58
+ isolation,
59
+ pipeline_git,
60
+ pipeline_state,
61
+ )
62
+ except ModuleNotFoundError as error:
63
+ if error.name not in {
64
+ "scripts",
65
+ "scripts.archive_milestone",
66
+ "scripts.detect_project",
67
+ "scripts.discussion_records",
68
+ "scripts.isolation",
69
+ "scripts.pipeline_git",
70
+ "scripts.pipeline_state",
71
+ }:
72
+ raise
73
+ shared = Path(__file__).resolve().parents[2] / "gsd-path" / "scripts"
74
+ sys.path.insert(0, str(shared))
75
+ import archive_milestone
76
+ import detect_project
77
+ import discussion_records
78
+ import isolation
79
+ import pipeline_git
80
+ import pipeline_state
81
+ return (
82
+ archive_milestone,
83
+ detect_project,
84
+ discussion_records,
85
+ isolation,
86
+ pipeline_git,
87
+ pipeline_state,
88
+ )
89
+
90
+
91
+ (
92
+ archive_milestone,
93
+ detect_project,
94
+ discussion_records,
95
+ isolation,
96
+ pipeline_git,
97
+ pipeline_state,
98
+ ) = _load_diagnose_modules()
99
+ DetectError = detect_project.DetectError
100
+ classify = detect_project.classify
101
+ IsolationError = isolation.IsolationError
102
+ _registered_worktrees = isolation._registered_worktrees
103
+ _worktree_report = isolation._worktree_report
104
+ PipelineStateError = pipeline_state.PipelineStateError
105
+ status_state = pipeline_state.status_state
106
+ validate_state = pipeline_state.validate_state
107
+
108
+ try:
109
+ import pipeline_undo
110
+ except ModuleNotFoundError as error:
111
+ if error.name != "pipeline_undo":
112
+ raise
113
+ try:
114
+ from scripts import pipeline_undo
115
+ except ModuleNotFoundError as error:
116
+ if error.name not in {"scripts", "scripts.pipeline_undo"}:
117
+ raise
118
+ shared = Path(__file__).resolve().parents[2] / "gsd-path" / "scripts"
119
+ if str(shared) not in sys.path:
120
+ sys.path.insert(0, str(shared))
121
+ import pipeline_undo
122
+
123
+ UndoError = pipeline_undo.UndoError
124
+ undo_preview = pipeline_undo.preview
125
+
126
+
127
+ DIAGNOSE_SCHEMA = "gsd-path/diagnose/v1"
128
+ ISOLATION_WORKTREE_PREFIXES = ("gsd-path-task/", "gsd-path-verify/")
129
+ INTEGRATION_WORKTREE_PREFIX = "gsd-path-integrate/"
130
+
131
+
132
+ class DiagnoseError(RuntimeError):
133
+ """Raised only when the repository path itself cannot be diagnosed."""
134
+
135
+
136
+ def _probe(
137
+ name: str,
138
+ runner: Callable[[], object],
139
+ ) -> dict[str, object]:
140
+ try:
141
+ value = runner()
142
+ except (
143
+ DetectError,
144
+ PipelineStateError,
145
+ UndoError,
146
+ IsolationError,
147
+ OSError,
148
+ ValueError,
149
+ ) as error:
150
+ return {"name": name, "ok": False, "error": str(error), "result": None}
151
+ return {"name": name, "ok": True, "error": None, "result": value}
152
+
153
+
154
+ def _finding(
155
+ identifier: str,
156
+ severity: str,
157
+ evidence: str,
158
+ retry: str,
159
+ ) -> dict[str, str]:
160
+ return {
161
+ "id": identifier,
162
+ "severity": severity,
163
+ "evidence": evidence,
164
+ "retry": retry,
165
+ }
166
+
167
+
168
+ def _needs_user(action: str) -> str:
169
+ return f"NEEDS-USER: {action}"
170
+
171
+
172
+ def _state_template() -> Path:
173
+ here = Path(__file__).resolve()
174
+ for candidate in (
175
+ here.parents[2] / "gsd-path" / "templates" / "state.md", # skills/<skill>/scripts
176
+ here.parents[1] / "skills" / "gsd-path" / "templates" / "state.md", # scripts/
177
+ ):
178
+ if candidate.is_file():
179
+ return candidate
180
+ return Path("<path-to-gsd-path-state-template>")
181
+
182
+
183
+ def _command(module: object, *arguments: object) -> str:
184
+ script = Path(str(getattr(module, "__file__"))).resolve()
185
+ return shlex.join([sys.executable, str(script), *map(str, arguments)])
186
+
187
+
188
+ def _route_retry(
189
+ repo: Path,
190
+ route: dict[str, object],
191
+ undo_result: Optional[dict[str, object]],
192
+ ) -> Optional[str]:
193
+ action = route.get("action")
194
+ if action == "resume-checkpoint":
195
+ return _command(pipeline_state, "resume-checkpoint", "--repo", repo)
196
+ if action == "resume-shipment":
197
+ return _command(
198
+ pipeline_state,
199
+ "record-shipment",
200
+ "--repo",
201
+ repo,
202
+ "--archive",
203
+ route["archive"],
204
+ "--event",
205
+ route["event"],
206
+ )
207
+ if action == "resume-promotion":
208
+ return _command(
209
+ pipeline_state,
210
+ "promote-next",
211
+ "--repo",
212
+ repo,
213
+ "--milestone",
214
+ route["milestone"],
215
+ "--branch",
216
+ route["branch"],
217
+ "--base",
218
+ route["base"],
219
+ "--landing",
220
+ route["landing"],
221
+ )
222
+ if action == "resume-next-handoff":
223
+ arguments: list[object] = [
224
+ "bind-next",
225
+ "--repo",
226
+ repo,
227
+ "--branch",
228
+ route["branch"],
229
+ "--previous-branch",
230
+ route["previous_branch"],
231
+ "--ship",
232
+ route["ship"],
233
+ "--remote-default",
234
+ route["remote_default"],
235
+ "--base",
236
+ route["base"],
237
+ "--landing",
238
+ route["landing"],
239
+ ]
240
+ if route.get("allow_remote_absent") is True:
241
+ arguments.append("--allow-missing-previous")
242
+ return _command(pipeline_git, *arguments)
243
+ if action == "resume-undo":
244
+ apply = (undo_result or {}).get("apply") or {}
245
+ if (
246
+ apply.get("kind") != route.get("kind")
247
+ or apply.get("expected_head") != route.get("expected_head")
248
+ ):
249
+ blocked = (
250
+ ((undo_result or {}).get("target") or {}).get("blocked") or []
251
+ )
252
+ reason = "; ".join(map(str, blocked)) or "undo recovery is not resumable"
253
+ return _needs_user(reason)
254
+ return _command(
255
+ pipeline_undo,
256
+ "apply",
257
+ "--repo",
258
+ repo,
259
+ "--kind",
260
+ route["kind"],
261
+ "--expected-head",
262
+ route["expected_head"],
263
+ )
264
+ return None
265
+
266
+
267
+ def _leftover_worktrees(repo: Path) -> list[dict[str, object]]:
268
+ leftovers: list[dict[str, object]] = []
269
+ records = _registered_worktrees(repo)
270
+ primary = repo.resolve()
271
+ for path, branch in records.items():
272
+ if path == primary:
273
+ continue
274
+ ref = branch or ""
275
+ short = ref.removeprefix("refs/heads/")
276
+ if short.startswith(ISOLATION_WORKTREE_PREFIXES):
277
+ report, error = _worktree_report(repo, path, short)
278
+ if error or report is None:
279
+ recovery = "needs-user"
280
+ reason = error or "sidecar ownership could not be proven"
281
+ elif report["clean"]:
282
+ recovery = "retire"
283
+ reason = None
284
+ else:
285
+ recovery = "needs-user"
286
+ reason = "dirty sidecar requires its owning retry-retirement workflow"
287
+ leftovers.append(
288
+ {
289
+ "worktree": str(path),
290
+ "branch": short,
291
+ "recovery": recovery,
292
+ "reason": reason,
293
+ }
294
+ )
295
+ elif short.startswith(INTEGRATION_WORKTREE_PREFIX):
296
+ leftovers.append(
297
+ {"worktree": str(path), "branch": short, "recovery": "integrate"}
298
+ )
299
+ return leftovers
300
+
301
+
302
+ def diagnose(repo: Path) -> dict[str, object]:
303
+ resolved = repo.resolve()
304
+ if not resolved.is_dir():
305
+ raise DiagnoseError(f"repo is not a directory: {resolved}")
306
+
307
+ probes = [
308
+ _probe("classify", lambda: classify(resolved)),
309
+ _probe("validate", lambda: validate_state(resolved)),
310
+ _probe("status", lambda: status_state(resolved)),
311
+ _probe("undo-preview", lambda: undo_preview(resolved)),
312
+ _probe("undo-transaction", lambda: pipeline_undo.pending_transaction(resolved)),
313
+ _probe("collect-artifacts", lambda: isolation.collect_artifact_recoveries(resolved)),
314
+ _probe("worktrees", lambda: _leftover_worktrees(resolved)),
315
+ ]
316
+ by_name = {probe["name"]: probe for probe in probes}
317
+ findings: list[dict[str, str]] = []
318
+
319
+ classified = by_name["classify"]
320
+ if classified["ok"]:
321
+ verdict = (classified["result"] or {}).get("verdict")
322
+ if verdict == "orphan":
323
+ paths = (classified["result"] or {}).get("orphan_paths") or []
324
+ findings.append(
325
+ _finding(
326
+ "orphan",
327
+ "stuck",
328
+ "detect_project classify returned orphan: " + ", ".join(map(str, paths)),
329
+ _needs_user("choose orphan recovery, migration, or a new location"),
330
+ )
331
+ )
332
+ else:
333
+ findings.append(
334
+ _finding(
335
+ "classify",
336
+ "stuck",
337
+ classified["error"] or "classify failed",
338
+ _needs_user("repair or relocate the unowned project metadata"),
339
+ )
340
+ )
341
+
342
+ status_probe = by_name["status"]
343
+ no_project = (
344
+ classified["ok"]
345
+ and (classified["result"] or {}).get("verdict") in {"brownfield", "greenfield"}
346
+ and not (resolved / ".project" / "STATE.md").exists()
347
+ )
348
+ if no_project:
349
+ findings.append(
350
+ _finding(
351
+ "no-project",
352
+ "stuck",
353
+ "no owned pipeline: .project/STATE.md is missing",
354
+ _command(
355
+ detect_project,
356
+ "initialize",
357
+ "--repo",
358
+ resolved,
359
+ "--template",
360
+ _state_template(),
361
+ ),
362
+ )
363
+ )
364
+ elif not status_probe["ok"]:
365
+ findings.append(
366
+ _finding(
367
+ "status",
368
+ "stuck",
369
+ status_probe["error"] or "status failed",
370
+ _needs_user("repair the invalid pipeline state or helper journal"),
371
+ )
372
+ )
373
+ else:
374
+ status = status_probe["result"] or {}
375
+ route = status.get("route") or {}
376
+ git = status.get("git") or {}
377
+ journals = {
378
+ key: path
379
+ for key, path in (status.get("journals") or {}).items()
380
+ if path
381
+ }
382
+ undo_probe = by_name["undo-preview"]
383
+ undo_result = undo_probe["result"] if undo_probe["ok"] else None
384
+ route_retry = _route_retry(resolved, route, undo_result)
385
+ recorded_branch = status.get("state", {}).get("branch")
386
+ if (
387
+ not route_retry
388
+ and git.get("branch")
389
+ and recorded_branch not in {None, git.get("branch")}
390
+ ):
391
+ findings.append(
392
+ _finding(
393
+ "branch-mismatch",
394
+ "stuck",
395
+ str(route.get("reason") or "current branch does not match STATE.branch"),
396
+ _needs_user("restore the worktree to the branch recorded in STATE.md"),
397
+ )
398
+ )
399
+ if route_retry:
400
+ active = [f"{name}: {path}" for name, path in sorted(journals.items())]
401
+ findings.append(
402
+ _finding(
403
+ f"journal-{route.get('action')}",
404
+ "stuck",
405
+ "; ".join(active),
406
+ route_retry,
407
+ )
408
+ )
409
+ if route.get("action") == "block":
410
+ undo_result = by_name["undo-preview"]
411
+ undo_target = (
412
+ (undo_result["result"] or {}).get("target") or {}
413
+ if undo_result["ok"]
414
+ else {}
415
+ )
416
+ retry = (
417
+ "$gsd-path-undo"
418
+ if undo_target.get("kind")
419
+ else _needs_user(str(route.get("reason") or "resolve the route block"))
420
+ )
421
+ findings.append(
422
+ _finding(
423
+ "route-block",
424
+ "stuck",
425
+ str(route.get("reason") or "route returned block"),
426
+ retry,
427
+ )
428
+ )
429
+ pending = status.get("pending_answers") or []
430
+ if pending:
431
+ paths = ", ".join(
432
+ f"{item.get('answer')} -> {item.get('owner')} -> {item.get('target')}"
433
+ for item in pending
434
+ )
435
+ findings.append(
436
+ _finding(
437
+ "pending-answers",
438
+ "stuck",
439
+ f"{len(pending)} required discussion disposition(s): {paths}",
440
+ _needs_user("resolve and dispose the pending discussion answers"),
441
+ )
442
+ )
443
+ if status.get("pending_error"):
444
+ findings.append(
445
+ _finding(
446
+ "pending-error",
447
+ "stuck",
448
+ str(status["pending_error"]),
449
+ _needs_user("repair the discussion records before routing"),
450
+ )
451
+ )
452
+ dirty = git.get("dirty") or []
453
+ if dirty:
454
+ findings.append(
455
+ _finding(
456
+ "dirty-worktree",
457
+ "info",
458
+ "uncommitted paths: " + ", ".join(dirty),
459
+ _command(pipeline_state, "status", "--repo", resolved),
460
+ )
461
+ )
462
+
463
+ state = status.get("state") or {}
464
+ archive = state.get("archive")
465
+ if archive and state.get("phase") in {"ship", "shipped"}:
466
+ try:
467
+ archive_milestone.validate(resolved)
468
+ except archive_milestone.ArchiveError as error:
469
+ findings.append(
470
+ _finding(
471
+ "archive-validate",
472
+ "stuck",
473
+ str(error),
474
+ "$gsd-path-ship",
475
+ )
476
+ )
477
+ else:
478
+ if state.get("phase") == "shipped" and state.get("milestone"):
479
+ try:
480
+ archive_milestone.validate_integrated(
481
+ resolved, str(state["milestone"])
482
+ )
483
+ except archive_milestone.ArchiveError as error:
484
+ findings.append(
485
+ _finding(
486
+ "integration",
487
+ "stuck",
488
+ str(error),
489
+ "$gsd-path-ship",
490
+ )
491
+ )
492
+
493
+ undo_transaction = by_name["undo-transaction"]
494
+ route_action = (
495
+ ((status_probe["result"] or {}).get("route") or {}).get("action")
496
+ if status_probe["ok"]
497
+ else None
498
+ )
499
+ if (
500
+ undo_transaction["ok"]
501
+ and undo_transaction["result"]
502
+ and route_action != "resume-undo"
503
+ ):
504
+ transaction = undo_transaction["result"]
505
+ findings.append(
506
+ _finding(
507
+ "undo-transaction",
508
+ "stuck",
509
+ f"{transaction['kind']} undo transaction is incomplete",
510
+ _command(
511
+ pipeline_undo,
512
+ "apply",
513
+ "--repo",
514
+ resolved,
515
+ "--kind",
516
+ transaction["kind"],
517
+ "--expected-head",
518
+ transaction["expected_head"],
519
+ ),
520
+ )
521
+ )
522
+ elif not undo_transaction["ok"]:
523
+ findings.append(
524
+ _finding(
525
+ "undo-transaction",
526
+ "stuck",
527
+ undo_transaction["error"] or "undo transaction is unreadable",
528
+ "$gsd-path-undo",
529
+ )
530
+ )
531
+
532
+ collect_probe = by_name["collect-artifacts"]
533
+ if collect_probe["ok"]:
534
+ for transaction in collect_probe["result"] or []:
535
+ arguments: list[object] = [
536
+ "collect-artifact",
537
+ "--repo",
538
+ transaction["primary_worktree"],
539
+ "--source",
540
+ transaction["source_worktree"],
541
+ "--base",
542
+ transaction["base"],
543
+ "--branch",
544
+ transaction["branch"],
545
+ "--source-path",
546
+ transaction["source"],
547
+ "--destination-path",
548
+ transaction["destination"],
549
+ ]
550
+ if transaction["expected_destination"] is not None:
551
+ arguments.extend(
552
+ ["--expected-destination", transaction["expected_destination"]]
553
+ )
554
+ findings.append(
555
+ _finding(
556
+ "collect-artifact",
557
+ "stuck",
558
+ f"{transaction['stage']} journal: {transaction['journal']}",
559
+ _command(isolation, *arguments),
560
+ )
561
+ )
562
+ else:
563
+ findings.append(
564
+ _finding(
565
+ "collect-artifact",
566
+ "stuck",
567
+ collect_probe["error"] or "artifact collection journal is unreadable",
568
+ _needs_user("repair the artifact collection journal"),
569
+ )
570
+ )
571
+
572
+ worktrees = by_name["worktrees"]
573
+ if worktrees["ok"]:
574
+ leftovers = worktrees["result"] or []
575
+ for item in leftovers:
576
+ if item["recovery"] == "integrate":
577
+ state = (
578
+ (status_probe["result"] or {}).get("state") or {}
579
+ if status_probe["ok"]
580
+ else {}
581
+ )
582
+ slug = state.get("milestone")
583
+ archive = str(state.get("archive") or "").rstrip("/")
584
+ expected_branch = None
585
+ expected_worktree = None
586
+ if archive:
587
+ expected_branch, expected_worktree = (
588
+ archive_milestone.integration_names(
589
+ resolved,
590
+ Path(archive).name,
591
+ )
592
+ )
593
+ retry = (
594
+ _command(
595
+ archive_milestone,
596
+ "integrate",
597
+ "--repo",
598
+ resolved,
599
+ "--slug",
600
+ slug,
601
+ )
602
+ if (
603
+ isinstance(slug, str)
604
+ and slug
605
+ and state.get("phase") == "shipped"
606
+ and state.get("status") == "done"
607
+ and item["branch"] == expected_branch
608
+ and expected_worktree is not None
609
+ and Path(str(item["worktree"])).resolve()
610
+ == expected_worktree.resolve()
611
+ )
612
+ else _needs_user(
613
+ "reconcile the integration worktree with the shipped milestone "
614
+ "and canonical helper path"
615
+ )
616
+ )
617
+ elif item["recovery"] == "retire":
618
+ retry = _command(
619
+ isolation,
620
+ "retire",
621
+ "--repo",
622
+ resolved,
623
+ "--worktree",
624
+ item["worktree"],
625
+ "--branch",
626
+ item["branch"],
627
+ )
628
+ else:
629
+ retry = _needs_user(str(item["reason"]))
630
+ findings.append(
631
+ _finding(
632
+ "leftover-worktrees",
633
+ "info",
634
+ f"{item['branch']} @ {item['worktree']}",
635
+ retry,
636
+ )
637
+ )
638
+ else:
639
+ findings.append(
640
+ _finding(
641
+ "worktrees",
642
+ "info",
643
+ worktrees["error"] or "could not list worktrees",
644
+ _needs_user("repair the worktree registry"),
645
+ )
646
+ )
647
+
648
+ undo_probe = by_name["undo-preview"]
649
+ if undo_probe["ok"]:
650
+ target = (undo_probe["result"] or {}).get("target") or {}
651
+ if target.get("kind"):
652
+ findings.append(
653
+ _finding(
654
+ "undo-available",
655
+ "info",
656
+ f"preview kind {target['kind']} at {target.get('head')}",
657
+ _command(pipeline_undo, "preview", "--repo", resolved),
658
+ )
659
+ )
660
+ else:
661
+ findings.append(
662
+ _finding(
663
+ "undo-preview",
664
+ "info",
665
+ undo_probe["error"] or "undo preview failed",
666
+ _command(pipeline_undo, "preview", "--repo", resolved),
667
+ )
668
+ )
669
+
670
+ stuck = [item for item in findings if item["severity"] == "stuck"]
671
+ if any(item["id"] == "orphan" for item in stuck):
672
+ verdict = "unowned"
673
+ elif stuck:
674
+ verdict = "stuck"
675
+ else:
676
+ verdict = "ok"
677
+
678
+ return {
679
+ "schema": DIAGNOSE_SCHEMA,
680
+ "status": verdict,
681
+ "repo": str(resolved),
682
+ "probes": [
683
+ {
684
+ "name": probe["name"],
685
+ "ok": probe["ok"],
686
+ "error": probe["error"],
687
+ }
688
+ for probe in probes
689
+ ],
690
+ "findings": findings,
691
+ "undo": None if not undo_probe["ok"] else undo_probe["result"],
692
+ "status_snapshot": None if not status_probe["ok"] else status_probe["result"],
693
+ }
694
+
695
+
696
+ def parse_args(argv: Optional[Sequence[str]] = None) -> argparse.Namespace:
697
+ parser = argparse.ArgumentParser(description=__doc__)
698
+ parser.add_argument("command", choices=("diagnose",))
699
+ parser.add_argument("--repo", required=True, type=Path)
700
+ return parser.parse_args(argv)
701
+
702
+
703
+ def main(argv: Optional[Sequence[str]] = None) -> int:
704
+ args = parse_args(argv)
705
+ try:
706
+ result = diagnose(args.repo)
707
+ except DiagnoseError as error:
708
+ print(f"error: {error}", file=sys.stderr)
709
+ return 1
710
+ print(json.dumps(result, sort_keys=True))
711
+ return 0
712
+
713
+
714
+ if __name__ == "__main__":
715
+ raise SystemExit(main())