gsd-remix 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 (554) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +939 -0
  3. package/README.zh-CN.md +876 -0
  4. package/agents/gsd-advisor-researcher.md +127 -0
  5. package/agents/gsd-ai-researcher.md +133 -0
  6. package/agents/gsd-assumptions-analyzer.md +105 -0
  7. package/agents/gsd-code-fixer.md +517 -0
  8. package/agents/gsd-code-reviewer.md +371 -0
  9. package/agents/gsd-codebase-mapper.md +781 -0
  10. package/agents/gsd-debug-session-manager.md +314 -0
  11. package/agents/gsd-debugger.md +1452 -0
  12. package/agents/gsd-doc-classifier.md +168 -0
  13. package/agents/gsd-doc-synthesizer.md +204 -0
  14. package/agents/gsd-doc-verifier.md +217 -0
  15. package/agents/gsd-doc-writer.md +615 -0
  16. package/agents/gsd-domain-researcher.md +153 -0
  17. package/agents/gsd-eval-auditor.md +191 -0
  18. package/agents/gsd-eval-planner.md +154 -0
  19. package/agents/gsd-executor.md +603 -0
  20. package/agents/gsd-framework-selector.md +160 -0
  21. package/agents/gsd-integration-checker.md +470 -0
  22. package/agents/gsd-intel-updater.md +334 -0
  23. package/agents/gsd-nyquist-auditor.md +203 -0
  24. package/agents/gsd-pattern-mapper.md +335 -0
  25. package/agents/gsd-phase-researcher.md +841 -0
  26. package/agents/gsd-plan-checker.md +978 -0
  27. package/agents/gsd-planner.md +1251 -0
  28. package/agents/gsd-project-researcher.md +677 -0
  29. package/agents/gsd-research-synthesizer.md +247 -0
  30. package/agents/gsd-roadmapper.md +688 -0
  31. package/agents/gsd-security-auditor.md +155 -0
  32. package/agents/gsd-ui-auditor.md +495 -0
  33. package/agents/gsd-ui-checker.md +309 -0
  34. package/agents/gsd-ui-researcher.md +380 -0
  35. package/agents/gsd-user-profiler.md +171 -0
  36. package/agents/gsd-verifier.md +830 -0
  37. package/bin/install.js +7062 -0
  38. package/commands/gsd/add-backlog.md +79 -0
  39. package/commands/gsd/add-phase.md +43 -0
  40. package/commands/gsd/add-tests.md +41 -0
  41. package/commands/gsd/add-todo.md +47 -0
  42. package/commands/gsd/ai-integration-phase.md +36 -0
  43. package/commands/gsd/analyze-dependencies.md +34 -0
  44. package/commands/gsd/audit-fix.md +33 -0
  45. package/commands/gsd/audit-milestone.md +36 -0
  46. package/commands/gsd/audit-uat.md +24 -0
  47. package/commands/gsd/autonomous.md +46 -0
  48. package/commands/gsd/check-todos.md +45 -0
  49. package/commands/gsd/cleanup.md +23 -0
  50. package/commands/gsd/code-review-fix.md +52 -0
  51. package/commands/gsd/code-review.md +55 -0
  52. package/commands/gsd/complete-milestone.md +136 -0
  53. package/commands/gsd/debug.md +263 -0
  54. package/commands/gsd/discuss-phase.md +69 -0
  55. package/commands/gsd/do.md +30 -0
  56. package/commands/gsd/docs-update.md +48 -0
  57. package/commands/gsd/eval-review.md +32 -0
  58. package/commands/gsd/execute-phase.md +63 -0
  59. package/commands/gsd/explore.md +27 -0
  60. package/commands/gsd/extract_learnings.md +22 -0
  61. package/commands/gsd/fast.md +30 -0
  62. package/commands/gsd/forensics.md +56 -0
  63. package/commands/gsd/from-gsd2.md +47 -0
  64. package/commands/gsd/graphify.md +201 -0
  65. package/commands/gsd/health.md +22 -0
  66. package/commands/gsd/help.md +24 -0
  67. package/commands/gsd/import.md +37 -0
  68. package/commands/gsd/inbox.md +38 -0
  69. package/commands/gsd/ingest-docs.md +42 -0
  70. package/commands/gsd/insert-phase.md +32 -0
  71. package/commands/gsd/intel.md +179 -0
  72. package/commands/gsd/join-discord.md +19 -0
  73. package/commands/gsd/list-phase-assumptions.md +46 -0
  74. package/commands/gsd/list-workspaces.md +19 -0
  75. package/commands/gsd/manager.md +40 -0
  76. package/commands/gsd/map-codebase.md +71 -0
  77. package/commands/gsd/milestone-summary.md +51 -0
  78. package/commands/gsd/new-milestone.md +44 -0
  79. package/commands/gsd/new-project.md +46 -0
  80. package/commands/gsd/new-workspace.md +44 -0
  81. package/commands/gsd/next.md +28 -0
  82. package/commands/gsd/note.md +34 -0
  83. package/commands/gsd/pause-work.md +38 -0
  84. package/commands/gsd/plan-milestone-gaps.md +34 -0
  85. package/commands/gsd/plan-phase.md +52 -0
  86. package/commands/gsd/plan-review-convergence.md +52 -0
  87. package/commands/gsd/plant-seed.md +28 -0
  88. package/commands/gsd/pr-branch.md +25 -0
  89. package/commands/gsd/profile-user.md +46 -0
  90. package/commands/gsd/progress.md +25 -0
  91. package/commands/gsd/quick.md +173 -0
  92. package/commands/gsd/reapply-patches.md +331 -0
  93. package/commands/gsd/remove-phase.md +31 -0
  94. package/commands/gsd/remove-workspace.md +26 -0
  95. package/commands/gsd/research-phase.md +195 -0
  96. package/commands/gsd/resume-work.md +40 -0
  97. package/commands/gsd/review-backlog.md +62 -0
  98. package/commands/gsd/review.md +40 -0
  99. package/commands/gsd/scan.md +26 -0
  100. package/commands/gsd/secure-phase.md +35 -0
  101. package/commands/gsd/session-report.md +19 -0
  102. package/commands/gsd/set-profile.md +12 -0
  103. package/commands/gsd/settings.md +36 -0
  104. package/commands/gsd/ship.md +23 -0
  105. package/commands/gsd/sketch-wrap-up.md +31 -0
  106. package/commands/gsd/sketch.md +49 -0
  107. package/commands/gsd/spec-phase.md +62 -0
  108. package/commands/gsd/spike-wrap-up.md +31 -0
  109. package/commands/gsd/spike.md +46 -0
  110. package/commands/gsd/stats.md +18 -0
  111. package/commands/gsd/sync-skills.md +19 -0
  112. package/commands/gsd/thread.md +227 -0
  113. package/commands/gsd/ui-phase.md +34 -0
  114. package/commands/gsd/ui-review.md +32 -0
  115. package/commands/gsd/ultraplan-phase.md +33 -0
  116. package/commands/gsd/undo.md +34 -0
  117. package/commands/gsd/update.md +37 -0
  118. package/commands/gsd/validate-phase.md +35 -0
  119. package/commands/gsd/verify-work.md +38 -0
  120. package/commands/gsd/workstreams.md +69 -0
  121. package/get-shit-done/bin/gsd-tools.cjs +1263 -0
  122. package/get-shit-done/bin/lib/artifacts.cjs +52 -0
  123. package/get-shit-done/bin/lib/audit.cjs +757 -0
  124. package/get-shit-done/bin/lib/commands.cjs +1023 -0
  125. package/get-shit-done/bin/lib/config-schema.cjs +79 -0
  126. package/get-shit-done/bin/lib/config.cjs +463 -0
  127. package/get-shit-done/bin/lib/core.cjs +1794 -0
  128. package/get-shit-done/bin/lib/docs.cjs +267 -0
  129. package/get-shit-done/bin/lib/frontmatter.cjs +379 -0
  130. package/get-shit-done/bin/lib/graphify.cjs +494 -0
  131. package/get-shit-done/bin/lib/gsd2-import.cjs +511 -0
  132. package/get-shit-done/bin/lib/init.cjs +1878 -0
  133. package/get-shit-done/bin/lib/intel.cjs +639 -0
  134. package/get-shit-done/bin/lib/learnings.cjs +378 -0
  135. package/get-shit-done/bin/lib/milestone.cjs +283 -0
  136. package/get-shit-done/bin/lib/model-profiles.cjs +71 -0
  137. package/get-shit-done/bin/lib/phase.cjs +1058 -0
  138. package/get-shit-done/bin/lib/profile-output.cjs +1080 -0
  139. package/get-shit-done/bin/lib/profile-pipeline.cjs +539 -0
  140. package/get-shit-done/bin/lib/roadmap.cjs +523 -0
  141. package/get-shit-done/bin/lib/schema-detect.cjs +238 -0
  142. package/get-shit-done/bin/lib/security.cjs +504 -0
  143. package/get-shit-done/bin/lib/state.cjs +1649 -0
  144. package/get-shit-done/bin/lib/template.cjs +226 -0
  145. package/get-shit-done/bin/lib/uat.cjs +288 -0
  146. package/get-shit-done/bin/lib/verify.cjs +1184 -0
  147. package/get-shit-done/bin/lib/workstream.cjs +495 -0
  148. package/get-shit-done/bin/repair-sdk.cjs +177 -0
  149. package/get-shit-done/contexts/dev.md +21 -0
  150. package/get-shit-done/contexts/research.md +22 -0
  151. package/get-shit-done/contexts/review.md +22 -0
  152. package/get-shit-done/references/agent-contracts.md +79 -0
  153. package/get-shit-done/references/ai-evals.md +156 -0
  154. package/get-shit-done/references/ai-frameworks.md +186 -0
  155. package/get-shit-done/references/artifact-types.md +131 -0
  156. package/get-shit-done/references/autonomous-smart-discuss.md +277 -0
  157. package/get-shit-done/references/checkpoints.md +808 -0
  158. package/get-shit-done/references/common-bug-patterns.md +114 -0
  159. package/get-shit-done/references/context-budget.md +49 -0
  160. package/get-shit-done/references/continuation-format.md +253 -0
  161. package/get-shit-done/references/debugger-philosophy.md +76 -0
  162. package/get-shit-done/references/decimal-phase-calculation.md +64 -0
  163. package/get-shit-done/references/doc-conflict-engine.md +91 -0
  164. package/get-shit-done/references/domain-probes.md +125 -0
  165. package/get-shit-done/references/executor-examples.md +110 -0
  166. package/get-shit-done/references/few-shot-examples/plan-checker.md +73 -0
  167. package/get-shit-done/references/few-shot-examples/verifier.md +109 -0
  168. package/get-shit-done/references/gate-prompts.md +100 -0
  169. package/get-shit-done/references/gates.md +70 -0
  170. package/get-shit-done/references/git-integration.md +295 -0
  171. package/get-shit-done/references/git-planning-commit.md +40 -0
  172. package/get-shit-done/references/ios-scaffold.md +123 -0
  173. package/get-shit-done/references/mandatory-initial-read.md +2 -0
  174. package/get-shit-done/references/model-profile-resolution.md +38 -0
  175. package/get-shit-done/references/model-profiles.md +145 -0
  176. package/get-shit-done/references/phase-argument-parsing.md +61 -0
  177. package/get-shit-done/references/planner-antipatterns.md +89 -0
  178. package/get-shit-done/references/planner-gap-closure.md +62 -0
  179. package/get-shit-done/references/planner-reviews.md +39 -0
  180. package/get-shit-done/references/planner-revision.md +87 -0
  181. package/get-shit-done/references/planner-source-audit.md +73 -0
  182. package/get-shit-done/references/planning-config.md +460 -0
  183. package/get-shit-done/references/project-skills-discovery.md +19 -0
  184. package/get-shit-done/references/questioning.md +162 -0
  185. package/get-shit-done/references/revision-loop.md +97 -0
  186. package/get-shit-done/references/sketch-interactivity.md +41 -0
  187. package/get-shit-done/references/sketch-theme-system.md +94 -0
  188. package/get-shit-done/references/sketch-tooling.md +45 -0
  189. package/get-shit-done/references/sketch-variant-patterns.md +81 -0
  190. package/get-shit-done/references/tdd.md +330 -0
  191. package/get-shit-done/references/thinking-models-debug.md +44 -0
  192. package/get-shit-done/references/thinking-models-execution.md +50 -0
  193. package/get-shit-done/references/thinking-models-planning.md +62 -0
  194. package/get-shit-done/references/thinking-models-research.md +50 -0
  195. package/get-shit-done/references/thinking-models-verification.md +55 -0
  196. package/get-shit-done/references/thinking-partner.md +96 -0
  197. package/get-shit-done/references/ui-brand.md +160 -0
  198. package/get-shit-done/references/universal-anti-patterns.md +63 -0
  199. package/get-shit-done/references/user-profiling.md +681 -0
  200. package/get-shit-done/references/verification-overrides.md +227 -0
  201. package/get-shit-done/references/verification-patterns.md +612 -0
  202. package/get-shit-done/references/workstream-flag.md +111 -0
  203. package/get-shit-done/templates/AI-SPEC.md +246 -0
  204. package/get-shit-done/templates/DEBUG.md +169 -0
  205. package/get-shit-done/templates/README.md +76 -0
  206. package/get-shit-done/templates/SECURITY.md +61 -0
  207. package/get-shit-done/templates/UAT.md +265 -0
  208. package/get-shit-done/templates/UI-SPEC.md +100 -0
  209. package/get-shit-done/templates/VALIDATION.md +76 -0
  210. package/get-shit-done/templates/claude-md.md +145 -0
  211. package/get-shit-done/templates/codebase/architecture.md +255 -0
  212. package/get-shit-done/templates/codebase/concerns.md +310 -0
  213. package/get-shit-done/templates/codebase/conventions.md +307 -0
  214. package/get-shit-done/templates/codebase/integrations.md +280 -0
  215. package/get-shit-done/templates/codebase/stack.md +186 -0
  216. package/get-shit-done/templates/codebase/structure.md +285 -0
  217. package/get-shit-done/templates/codebase/testing.md +480 -0
  218. package/get-shit-done/templates/config.json +56 -0
  219. package/get-shit-done/templates/context.md +352 -0
  220. package/get-shit-done/templates/continue-here.md +78 -0
  221. package/get-shit-done/templates/copilot-instructions.md +7 -0
  222. package/get-shit-done/templates/debug-subagent-prompt.md +91 -0
  223. package/get-shit-done/templates/dev-preferences.md +21 -0
  224. package/get-shit-done/templates/discovery.md +146 -0
  225. package/get-shit-done/templates/discussion-log.md +63 -0
  226. package/get-shit-done/templates/milestone-archive.md +123 -0
  227. package/get-shit-done/templates/milestone.md +115 -0
  228. package/get-shit-done/templates/phase-prompt.md +610 -0
  229. package/get-shit-done/templates/planner-subagent-prompt.md +117 -0
  230. package/get-shit-done/templates/project.md +186 -0
  231. package/get-shit-done/templates/requirements.md +231 -0
  232. package/get-shit-done/templates/research-project/ARCHITECTURE.md +204 -0
  233. package/get-shit-done/templates/research-project/FEATURES.md +147 -0
  234. package/get-shit-done/templates/research-project/PITFALLS.md +200 -0
  235. package/get-shit-done/templates/research-project/STACK.md +120 -0
  236. package/get-shit-done/templates/research-project/SUMMARY.md +170 -0
  237. package/get-shit-done/templates/research.md +592 -0
  238. package/get-shit-done/templates/retrospective.md +54 -0
  239. package/get-shit-done/templates/roadmap.md +202 -0
  240. package/get-shit-done/templates/spec.md +307 -0
  241. package/get-shit-done/templates/state.md +184 -0
  242. package/get-shit-done/templates/summary-complex.md +59 -0
  243. package/get-shit-done/templates/summary-minimal.md +41 -0
  244. package/get-shit-done/templates/summary-standard.md +48 -0
  245. package/get-shit-done/templates/summary.md +248 -0
  246. package/get-shit-done/templates/user-profile.md +146 -0
  247. package/get-shit-done/templates/user-setup.md +311 -0
  248. package/get-shit-done/templates/verification-report.md +322 -0
  249. package/get-shit-done/workflows/add-phase.md +112 -0
  250. package/get-shit-done/workflows/add-tests.md +354 -0
  251. package/get-shit-done/workflows/add-todo.md +160 -0
  252. package/get-shit-done/workflows/ai-integration-phase.md +284 -0
  253. package/get-shit-done/workflows/analyze-dependencies.md +96 -0
  254. package/get-shit-done/workflows/audit-fix.md +175 -0
  255. package/get-shit-done/workflows/audit-milestone.md +340 -0
  256. package/get-shit-done/workflows/audit-uat.md +109 -0
  257. package/get-shit-done/workflows/autonomous.md +789 -0
  258. package/get-shit-done/workflows/check-todos.md +179 -0
  259. package/get-shit-done/workflows/cleanup.md +154 -0
  260. package/get-shit-done/workflows/code-review-fix.md +497 -0
  261. package/get-shit-done/workflows/code-review.md +515 -0
  262. package/get-shit-done/workflows/complete-milestone.md +847 -0
  263. package/get-shit-done/workflows/diagnose-issues.md +238 -0
  264. package/get-shit-done/workflows/discovery-phase.md +291 -0
  265. package/get-shit-done/workflows/discuss-phase-assumptions.md +670 -0
  266. package/get-shit-done/workflows/discuss-phase-power.md +308 -0
  267. package/get-shit-done/workflows/discuss-phase.md +1378 -0
  268. package/get-shit-done/workflows/do.md +110 -0
  269. package/get-shit-done/workflows/docs-update.md +1155 -0
  270. package/get-shit-done/workflows/eval-review.md +155 -0
  271. package/get-shit-done/workflows/execute-phase.md +1677 -0
  272. package/get-shit-done/workflows/execute-plan.md +533 -0
  273. package/get-shit-done/workflows/explore.md +141 -0
  274. package/get-shit-done/workflows/extract_learnings.md +242 -0
  275. package/get-shit-done/workflows/fast.md +105 -0
  276. package/get-shit-done/workflows/forensics.md +265 -0
  277. package/get-shit-done/workflows/graduation.md +195 -0
  278. package/get-shit-done/workflows/health.md +314 -0
  279. package/get-shit-done/workflows/help.md +667 -0
  280. package/get-shit-done/workflows/import.md +246 -0
  281. package/get-shit-done/workflows/inbox.md +387 -0
  282. package/get-shit-done/workflows/ingest-docs.md +328 -0
  283. package/get-shit-done/workflows/insert-phase.md +130 -0
  284. package/get-shit-done/workflows/list-phase-assumptions.md +178 -0
  285. package/get-shit-done/workflows/list-workspaces.md +56 -0
  286. package/get-shit-done/workflows/manager.md +365 -0
  287. package/get-shit-done/workflows/map-codebase.md +393 -0
  288. package/get-shit-done/workflows/milestone-summary.md +223 -0
  289. package/get-shit-done/workflows/new-milestone.md +611 -0
  290. package/get-shit-done/workflows/new-project.md +1391 -0
  291. package/get-shit-done/workflows/new-workspace.md +239 -0
  292. package/get-shit-done/workflows/next.md +220 -0
  293. package/get-shit-done/workflows/node-repair.md +92 -0
  294. package/get-shit-done/workflows/note.md +158 -0
  295. package/get-shit-done/workflows/pause-work.md +243 -0
  296. package/get-shit-done/workflows/plan-milestone-gaps.md +273 -0
  297. package/get-shit-done/workflows/plan-phase.md +1349 -0
  298. package/get-shit-done/workflows/plan-review-convergence.md +254 -0
  299. package/get-shit-done/workflows/plant-seed.md +172 -0
  300. package/get-shit-done/workflows/pr-branch.md +157 -0
  301. package/get-shit-done/workflows/profile-user.md +452 -0
  302. package/get-shit-done/workflows/progress.md +619 -0
  303. package/get-shit-done/workflows/quick.md +970 -0
  304. package/get-shit-done/workflows/remove-phase.md +155 -0
  305. package/get-shit-done/workflows/remove-workspace.md +92 -0
  306. package/get-shit-done/workflows/research-phase.md +89 -0
  307. package/get-shit-done/workflows/resume-project.md +326 -0
  308. package/get-shit-done/workflows/review.md +344 -0
  309. package/get-shit-done/workflows/scan.md +102 -0
  310. package/get-shit-done/workflows/secure-phase.md +166 -0
  311. package/get-shit-done/workflows/session-report.md +146 -0
  312. package/get-shit-done/workflows/settings.md +319 -0
  313. package/get-shit-done/workflows/ship.md +302 -0
  314. package/get-shit-done/workflows/sketch-wrap-up.md +283 -0
  315. package/get-shit-done/workflows/sketch.md +286 -0
  316. package/get-shit-done/workflows/spec-phase.md +262 -0
  317. package/get-shit-done/workflows/spike-wrap-up.md +281 -0
  318. package/get-shit-done/workflows/spike.md +362 -0
  319. package/get-shit-done/workflows/stats.md +60 -0
  320. package/get-shit-done/workflows/sync-skills.md +182 -0
  321. package/get-shit-done/workflows/transition.md +693 -0
  322. package/get-shit-done/workflows/ui-phase.md +323 -0
  323. package/get-shit-done/workflows/ui-review.md +190 -0
  324. package/get-shit-done/workflows/ultraplan-phase.md +189 -0
  325. package/get-shit-done/workflows/undo.md +314 -0
  326. package/get-shit-done/workflows/update.md +587 -0
  327. package/get-shit-done/workflows/validate-phase.md +176 -0
  328. package/get-shit-done/workflows/verify-phase.md +465 -0
  329. package/get-shit-done/workflows/verify-work.md +740 -0
  330. package/hooks/dist/gsd-check-update-worker.js +108 -0
  331. package/hooks/dist/gsd-check-update.js +64 -0
  332. package/hooks/dist/gsd-context-monitor.js +192 -0
  333. package/hooks/dist/gsd-phase-boundary.sh +28 -0
  334. package/hooks/dist/gsd-prompt-guard.js +97 -0
  335. package/hooks/dist/gsd-read-guard.js +82 -0
  336. package/hooks/dist/gsd-read-injection-scanner.js +152 -0
  337. package/hooks/dist/gsd-session-state.sh +34 -0
  338. package/hooks/dist/gsd-statusline.js +293 -0
  339. package/hooks/dist/gsd-validate-commit.sh +48 -0
  340. package/hooks/dist/gsd-workflow-guard.js +94 -0
  341. package/hooks/gsd-check-update-worker.js +108 -0
  342. package/hooks/gsd-check-update.js +64 -0
  343. package/hooks/gsd-context-monitor.js +192 -0
  344. package/hooks/gsd-phase-boundary.sh +28 -0
  345. package/hooks/gsd-prompt-guard.js +97 -0
  346. package/hooks/gsd-read-guard.js +82 -0
  347. package/hooks/gsd-read-injection-scanner.js +152 -0
  348. package/hooks/gsd-session-state.sh +34 -0
  349. package/hooks/gsd-statusline.js +293 -0
  350. package/hooks/gsd-validate-commit.sh +48 -0
  351. package/hooks/gsd-workflow-guard.js +94 -0
  352. package/package.json +59 -0
  353. package/scripts/base64-scan.sh +262 -0
  354. package/scripts/build-hooks.js +95 -0
  355. package/scripts/gen-inventory-manifest.cjs +109 -0
  356. package/scripts/prompt-injection-scan.sh +201 -0
  357. package/scripts/run-tests.cjs +33 -0
  358. package/scripts/secret-scan.sh +227 -0
  359. package/sdk/package-lock.json +1998 -0
  360. package/sdk/package.json +52 -0
  361. package/sdk/prompts/agents/gsd-executor.md +110 -0
  362. package/sdk/prompts/agents/gsd-phase-researcher.md +158 -0
  363. package/sdk/prompts/agents/gsd-plan-checker.md +160 -0
  364. package/sdk/prompts/agents/gsd-planner.md +214 -0
  365. package/sdk/prompts/agents/gsd-project-researcher.md +323 -0
  366. package/sdk/prompts/agents/gsd-research-synthesizer.md +237 -0
  367. package/sdk/prompts/agents/gsd-roadmapper.md +670 -0
  368. package/sdk/prompts/agents/gsd-verifier.md +159 -0
  369. package/sdk/prompts/templates/project.md +186 -0
  370. package/sdk/prompts/templates/requirements.md +231 -0
  371. package/sdk/prompts/templates/research-project/ARCHITECTURE.md +204 -0
  372. package/sdk/prompts/templates/research-project/FEATURES.md +147 -0
  373. package/sdk/prompts/templates/research-project/PITFALLS.md +200 -0
  374. package/sdk/prompts/templates/research-project/STACK.md +120 -0
  375. package/sdk/prompts/templates/research-project/SUMMARY.md +170 -0
  376. package/sdk/prompts/templates/roadmap.md +202 -0
  377. package/sdk/prompts/templates/state.md +175 -0
  378. package/sdk/prompts/workflows/discuss-phase.md +126 -0
  379. package/sdk/prompts/workflows/execute-plan.md +106 -0
  380. package/sdk/prompts/workflows/plan-phase.md +84 -0
  381. package/sdk/prompts/workflows/research-phase.md +45 -0
  382. package/sdk/prompts/workflows/verify-phase.md +142 -0
  383. package/sdk/src/assembled-prompts.test.ts +349 -0
  384. package/sdk/src/cli-transport.test.ts +388 -0
  385. package/sdk/src/cli-transport.ts +130 -0
  386. package/sdk/src/cli.test.ts +383 -0
  387. package/sdk/src/cli.ts +670 -0
  388. package/sdk/src/config.test.ts +168 -0
  389. package/sdk/src/config.ts +177 -0
  390. package/sdk/src/context-engine.test.ts +295 -0
  391. package/sdk/src/context-engine.ts +170 -0
  392. package/sdk/src/context-truncation.test.ts +163 -0
  393. package/sdk/src/context-truncation.ts +233 -0
  394. package/sdk/src/e2e.integration.test.ts +178 -0
  395. package/sdk/src/errors.ts +72 -0
  396. package/sdk/src/event-stream.test.ts +661 -0
  397. package/sdk/src/event-stream.ts +441 -0
  398. package/sdk/src/failure-memory.test.ts +457 -0
  399. package/sdk/src/failure-memory.ts +1324 -0
  400. package/sdk/src/golden/capture.ts +95 -0
  401. package/sdk/src/golden/fixtures/generate-slug.golden.json +1 -0
  402. package/sdk/src/golden/fixtures/profile-sample-sessions/demo-project/sample.jsonl +3 -0
  403. package/sdk/src/golden/fixtures/summary-extract-sample.md +26 -0
  404. package/sdk/src/golden/fixtures/uat-render-checkpoint-sample.md +15 -0
  405. package/sdk/src/golden/golden-integration-covered.ts +30 -0
  406. package/sdk/src/golden/golden-mutation-covered.ts +7 -0
  407. package/sdk/src/golden/golden-policy.test.ts +8 -0
  408. package/sdk/src/golden/golden-policy.ts +112 -0
  409. package/sdk/src/golden/golden.integration.test.ts +373 -0
  410. package/sdk/src/golden/init-golden-normalize.ts +15 -0
  411. package/sdk/src/golden/read-only-golden-rows.ts +77 -0
  412. package/sdk/src/golden/read-only-parity.integration.test.ts +125 -0
  413. package/sdk/src/golden/registry-canonical-commands.ts +31 -0
  414. package/sdk/src/gsd-tools.test.ts +409 -0
  415. package/sdk/src/gsd-tools.ts +595 -0
  416. package/sdk/src/headless-prompts.test.ts +159 -0
  417. package/sdk/src/index.ts +333 -0
  418. package/sdk/src/init-e2e.integration.test.ts +136 -0
  419. package/sdk/src/init-runner.test.ts +783 -0
  420. package/sdk/src/init-runner.ts +735 -0
  421. package/sdk/src/lifecycle-e2e.integration.test.ts +258 -0
  422. package/sdk/src/logger.test.ts +149 -0
  423. package/sdk/src/logger.ts +113 -0
  424. package/sdk/src/milestone-runner.test.ts +421 -0
  425. package/sdk/src/phase-prompt.test.ts +538 -0
  426. package/sdk/src/phase-prompt.ts +264 -0
  427. package/sdk/src/phase-runner-types.test.ts +421 -0
  428. package/sdk/src/phase-runner.integration.test.ts +377 -0
  429. package/sdk/src/phase-runner.test.ts +2333 -0
  430. package/sdk/src/phase-runner.ts +1203 -0
  431. package/sdk/src/plan-parser.test.ts +528 -0
  432. package/sdk/src/plan-parser.ts +427 -0
  433. package/sdk/src/prompt-builder.test.ts +306 -0
  434. package/sdk/src/prompt-builder.ts +193 -0
  435. package/sdk/src/prompt-sanitizer.test.ts +260 -0
  436. package/sdk/src/prompt-sanitizer.ts +71 -0
  437. package/sdk/src/query/QUERY-HANDLERS.md +317 -0
  438. package/sdk/src/query/audit-open.ts +722 -0
  439. package/sdk/src/query/check-auto-mode.test.ts +77 -0
  440. package/sdk/src/query/check-auto-mode.ts +50 -0
  441. package/sdk/src/query/check-completion.test.ts +113 -0
  442. package/sdk/src/query/check-completion.ts +182 -0
  443. package/sdk/src/query/check-gates.test.ts +103 -0
  444. package/sdk/src/query/check-gates.ts +112 -0
  445. package/sdk/src/query/check-ship-ready.test.ts +77 -0
  446. package/sdk/src/query/check-ship-ready.ts +103 -0
  447. package/sdk/src/query/check-verification-status.test.ts +143 -0
  448. package/sdk/src/query/check-verification-status.ts +160 -0
  449. package/sdk/src/query/commit.test.ts +202 -0
  450. package/sdk/src/query/commit.ts +301 -0
  451. package/sdk/src/query/config-gates.test.ts +89 -0
  452. package/sdk/src/query/config-gates.ts +69 -0
  453. package/sdk/src/query/config-mutation.test.ts +365 -0
  454. package/sdk/src/query/config-mutation.ts +497 -0
  455. package/sdk/src/query/config-query.test.ts +161 -0
  456. package/sdk/src/query/config-query.ts +190 -0
  457. package/sdk/src/query/context-history.test.ts +165 -0
  458. package/sdk/src/query/context-history.ts +467 -0
  459. package/sdk/src/query/decomposed-handlers.test.ts +365 -0
  460. package/sdk/src/query/detect-custom-files.ts +97 -0
  461. package/sdk/src/query/detect-phase-type.test.ts +105 -0
  462. package/sdk/src/query/detect-phase-type.ts +141 -0
  463. package/sdk/src/query/docs-init.ts +257 -0
  464. package/sdk/src/query/failure-capture.ts +58 -0
  465. package/sdk/src/query/frontmatter-array.test.ts +14 -0
  466. package/sdk/src/query/frontmatter-mutation.test.ts +259 -0
  467. package/sdk/src/query/frontmatter-mutation.ts +343 -0
  468. package/sdk/src/query/frontmatter.test.ts +281 -0
  469. package/sdk/src/query/frontmatter.ts +397 -0
  470. package/sdk/src/query/helpers.test.ts +426 -0
  471. package/sdk/src/query/helpers.ts +482 -0
  472. package/sdk/src/query/index.ts +586 -0
  473. package/sdk/src/query/init-complex.test.ts +232 -0
  474. package/sdk/src/query/init-complex.ts +578 -0
  475. package/sdk/src/query/init.test.ts +522 -0
  476. package/sdk/src/query/init.ts +1046 -0
  477. package/sdk/src/query/intel.test.ts +90 -0
  478. package/sdk/src/query/intel.ts +404 -0
  479. package/sdk/src/query/normalize-query-command.test.ts +50 -0
  480. package/sdk/src/query/normalize-query-command.ts +56 -0
  481. package/sdk/src/query/phase-lifecycle.test.ts +1126 -0
  482. package/sdk/src/query/phase-lifecycle.ts +1799 -0
  483. package/sdk/src/query/phase-list-queries.test.ts +88 -0
  484. package/sdk/src/query/phase-list-queries.ts +152 -0
  485. package/sdk/src/query/phase-ready.test.ts +65 -0
  486. package/sdk/src/query/phase-ready.ts +158 -0
  487. package/sdk/src/query/phase.test.ts +307 -0
  488. package/sdk/src/query/phase.ts +340 -0
  489. package/sdk/src/query/pipeline.test.ts +169 -0
  490. package/sdk/src/query/pipeline.ts +243 -0
  491. package/sdk/src/query/plan-execution-route.test.ts +166 -0
  492. package/sdk/src/query/plan-execution-route.ts +209 -0
  493. package/sdk/src/query/plan-task-structure.test.ts +65 -0
  494. package/sdk/src/query/plan-task-structure.ts +63 -0
  495. package/sdk/src/query/profile-extract-messages.ts +247 -0
  496. package/sdk/src/query/profile-output.ts +908 -0
  497. package/sdk/src/query/profile-questionnaire-data.ts +181 -0
  498. package/sdk/src/query/profile-sample.ts +184 -0
  499. package/sdk/src/query/profile-scan-sessions.ts +174 -0
  500. package/sdk/src/query/profile.test.ts +74 -0
  501. package/sdk/src/query/profile.ts +337 -0
  502. package/sdk/src/query/progress.test.ts +156 -0
  503. package/sdk/src/query/progress.ts +566 -0
  504. package/sdk/src/query/registry.test.ts +216 -0
  505. package/sdk/src/query/registry.ts +174 -0
  506. package/sdk/src/query/requirements-extract-from-plans.test.ts +58 -0
  507. package/sdk/src/query/requirements-extract-from-plans.ts +86 -0
  508. package/sdk/src/query/roadmap-update-plan-progress.ts +132 -0
  509. package/sdk/src/query/roadmap.test.ts +359 -0
  510. package/sdk/src/query/roadmap.ts +591 -0
  511. package/sdk/src/query/route-next-action.test.ts +61 -0
  512. package/sdk/src/query/route-next-action.ts +345 -0
  513. package/sdk/src/query/runtime-health.ts +7 -0
  514. package/sdk/src/query/schema-detect.ts +189 -0
  515. package/sdk/src/query/skill-manifest.ts +214 -0
  516. package/sdk/src/query/skills.test.ts +80 -0
  517. package/sdk/src/query/skills.ts +62 -0
  518. package/sdk/src/query/state-mutation.test.ts +450 -0
  519. package/sdk/src/query/state-mutation.ts +1444 -0
  520. package/sdk/src/query/state-project-load.ts +109 -0
  521. package/sdk/src/query/state.test.ts +347 -0
  522. package/sdk/src/query/state.ts +397 -0
  523. package/sdk/src/query/summary.test.ts +95 -0
  524. package/sdk/src/query/summary.ts +296 -0
  525. package/sdk/src/query/template.test.ts +180 -0
  526. package/sdk/src/query/template.ts +242 -0
  527. package/sdk/src/query/uat.test.ts +77 -0
  528. package/sdk/src/query/uat.ts +314 -0
  529. package/sdk/src/query/utils.test.ts +82 -0
  530. package/sdk/src/query/utils.ts +92 -0
  531. package/sdk/src/query/validate.test.ts +656 -0
  532. package/sdk/src/query/validate.ts +807 -0
  533. package/sdk/src/query/verify.test.ts +414 -0
  534. package/sdk/src/query/verify.ts +645 -0
  535. package/sdk/src/query/websearch.test.ts +31 -0
  536. package/sdk/src/query/websearch.ts +82 -0
  537. package/sdk/src/query/workspace.test.ts +119 -0
  538. package/sdk/src/query/workspace.ts +131 -0
  539. package/sdk/src/query/workstream.test.ts +51 -0
  540. package/sdk/src/query/workstream.ts +434 -0
  541. package/sdk/src/research-gate.test.ts +190 -0
  542. package/sdk/src/research-gate.ts +94 -0
  543. package/sdk/src/runtime-health.test.ts +176 -0
  544. package/sdk/src/runtime-health.ts +387 -0
  545. package/sdk/src/session-runner.test.ts +98 -0
  546. package/sdk/src/session-runner.ts +299 -0
  547. package/sdk/src/tool-scoping.test.ts +160 -0
  548. package/sdk/src/tool-scoping.ts +61 -0
  549. package/sdk/src/types.ts +917 -0
  550. package/sdk/src/workstream-utils.ts +33 -0
  551. package/sdk/src/ws-flag.test.ts +285 -0
  552. package/sdk/src/ws-transport.test.ts +161 -0
  553. package/sdk/src/ws-transport.ts +93 -0
  554. package/sdk/tsconfig.json +20 -0
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Unit tests for pipeline middleware.
3
+ *
4
+ * Tests wrapWithPipeline with dry-run mode, prepare/finalize callbacks,
5
+ * and normal execution passthrough.
6
+ */
7
+
8
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
9
+ import { mkdtemp, writeFile, mkdir, rm } from 'node:fs/promises';
10
+ import { join } from 'node:path';
11
+ import { tmpdir } from 'node:os';
12
+ import { QueryRegistry } from './registry.js';
13
+ import { wrapWithPipeline } from './pipeline.js';
14
+ import type { QueryResult } from './utils.js';
15
+
16
+ let tmpDir: string;
17
+
18
+ beforeEach(async () => {
19
+ tmpDir = await mkdtemp(join(tmpdir(), 'gsd-pipeline-'));
20
+ await mkdir(join(tmpDir, '.planning'), { recursive: true });
21
+ await writeFile(join(tmpDir, '.planning', 'STATE.md'), '# State\nstatus: idle\n');
22
+ });
23
+
24
+ afterEach(async () => {
25
+ await rm(tmpDir, { recursive: true, force: true });
26
+ });
27
+
28
+ // ─── Helper ───────────────────────────────────────────────────────────────
29
+
30
+ function makeRegistry(): QueryRegistry {
31
+ const registry = new QueryRegistry();
32
+ registry.register('read-cmd', async (_args, _dir) => ({ data: { read: true } }));
33
+ registry.register('mut-cmd', async (_args, dir) => {
34
+ // Simulate a mutation: write a file to the project dir
35
+ const { writeFile: wf } = await import('node:fs/promises');
36
+ await wf(join(dir, '.planning', 'MUTATED.md'), '# mutated');
37
+ return { data: { mutated: true } };
38
+ });
39
+ return registry;
40
+ }
41
+
42
+ const MUTATION_SET = new Set(['mut-cmd']);
43
+
44
+ // ─── Tests ─────────────────────────────────────────────────────────────────
45
+
46
+ describe('wrapWithPipeline — passthrough (no options)', () => {
47
+ it('read command passes through normally', async () => {
48
+ const registry = makeRegistry();
49
+ wrapWithPipeline(registry, MUTATION_SET, {});
50
+ const result = await registry.dispatch('read-cmd', [], tmpDir);
51
+ expect((result.data as Record<string, unknown>).read).toBe(true);
52
+ });
53
+
54
+ it('mutation command executes and writes to disk when dryRun=false', async () => {
55
+ const registry = makeRegistry();
56
+ wrapWithPipeline(registry, MUTATION_SET, { dryRun: false });
57
+ const result = await registry.dispatch('mut-cmd', [], tmpDir);
58
+ expect((result.data as Record<string, unknown>).mutated).toBe(true);
59
+ // File should have been written to the real dir
60
+ const { existsSync } = await import('node:fs');
61
+ expect(existsSync(join(tmpDir, '.planning', 'MUTATED.md'))).toBe(true);
62
+ });
63
+ });
64
+
65
+ describe('wrapWithPipeline — dry-run mode', () => {
66
+ it('dry-run mutation returns diff without writing to disk', async () => {
67
+ const registry = makeRegistry();
68
+ wrapWithPipeline(registry, MUTATION_SET, { dryRun: true });
69
+ const result = await registry.dispatch('mut-cmd', [], tmpDir);
70
+ const data = result.data as Record<string, unknown>;
71
+
72
+ // Should be a dry-run result
73
+ expect(data.dry_run).toBe(true);
74
+ expect(data.command).toBe('mut-cmd');
75
+ expect(data.diff).toBeDefined();
76
+ expect(typeof data.changes_summary).toBe('string');
77
+
78
+ // Real project should NOT have been written to
79
+ const { existsSync } = await import('node:fs');
80
+ expect(existsSync(join(tmpDir, '.planning', 'MUTATED.md'))).toBe(false);
81
+ });
82
+
83
+ it('dry-run diff contains before/after for changed files', async () => {
84
+ const registry = makeRegistry();
85
+ wrapWithPipeline(registry, MUTATION_SET, { dryRun: true });
86
+ const result = await registry.dispatch('mut-cmd', [], tmpDir);
87
+ const data = result.data as Record<string, unknown>;
88
+ const diff = data.diff as Record<string, { before: string | null; after: string | null }>;
89
+
90
+ // MUTATED.md is a new file — before should be null
91
+ const mutatedKey = Object.keys(diff).find(k => k.includes('MUTATED'));
92
+ expect(mutatedKey).toBeDefined();
93
+ expect(diff[mutatedKey!].before).toBeNull();
94
+ expect(diff[mutatedKey!].after).toBe('# mutated');
95
+ });
96
+
97
+ it('dry-run read command executes normally (side-effect-free)', async () => {
98
+ const registry = makeRegistry();
99
+ wrapWithPipeline(registry, MUTATION_SET, { dryRun: true });
100
+ // read-cmd is NOT in MUTATION_SET, so it's not wrapped at all
101
+ const result = await registry.dispatch('read-cmd', [], tmpDir);
102
+ expect((result.data as Record<string, unknown>).read).toBe(true);
103
+ });
104
+
105
+ it('dry-run changes_summary reflects number of changed files', async () => {
106
+ const registry = makeRegistry();
107
+ wrapWithPipeline(registry, MUTATION_SET, { dryRun: true });
108
+ const result = await registry.dispatch('mut-cmd', [], tmpDir);
109
+ const data = result.data as Record<string, unknown>;
110
+ expect(data.changes_summary).toContain('1 file');
111
+ });
112
+ });
113
+
114
+ describe('wrapWithPipeline — prepare/finalize callbacks', () => {
115
+ it('onPrepare fires before mutation execution', async () => {
116
+ const registry = makeRegistry();
117
+ const preparedCommands: string[] = [];
118
+ wrapWithPipeline(registry, MUTATION_SET, {
119
+ onPrepare: async (cmd) => { preparedCommands.push(cmd); },
120
+ });
121
+ await registry.dispatch('mut-cmd', ['arg1'], tmpDir);
122
+ expect(preparedCommands).toContain('mut-cmd');
123
+ });
124
+
125
+ it('onFinalize fires after mutation with result', async () => {
126
+ const registry = makeRegistry();
127
+ let capturedResult: QueryResult | null = null;
128
+ wrapWithPipeline(registry, MUTATION_SET, {
129
+ onFinalize: async (_cmd, _args, result) => { capturedResult = result; },
130
+ });
131
+ await registry.dispatch('mut-cmd', [], tmpDir);
132
+ expect(capturedResult).not.toBeNull();
133
+ });
134
+
135
+ it('onPrepare receives correct args', async () => {
136
+ const registry = makeRegistry();
137
+ let capturedArgs: string[] = [];
138
+ wrapWithPipeline(registry, MUTATION_SET, {
139
+ onPrepare: async (_cmd, args) => { capturedArgs = args; },
140
+ });
141
+ await registry.dispatch('mut-cmd', ['foo', 'bar'], tmpDir);
142
+ expect(capturedArgs).toEqual(['foo', 'bar']);
143
+ });
144
+
145
+ it('onFinalize fires even in dry-run mode', async () => {
146
+ const registry = makeRegistry();
147
+ let finalizeCalled = false;
148
+ wrapWithPipeline(registry, MUTATION_SET, {
149
+ dryRun: true,
150
+ onFinalize: async () => { finalizeCalled = true; },
151
+ });
152
+ await registry.dispatch('mut-cmd', [], tmpDir);
153
+ expect(finalizeCalled).toBe(true);
154
+ });
155
+ });
156
+
157
+ describe('wrapWithPipeline — unregistered command passthrough', () => {
158
+ it('commands not in mutation set are not wrapped', async () => {
159
+ const registry = makeRegistry();
160
+ const spy = vi.fn(async (_args: string[], _dir: string): Promise<QueryResult> => ({ data: { value: 42 } }));
161
+ registry.register('other-cmd', spy);
162
+ wrapWithPipeline(registry, MUTATION_SET, {
163
+ onPrepare: async () => { /* should not fire for non-mutation */ },
164
+ });
165
+ const result = await registry.dispatch('other-cmd', [], tmpDir);
166
+ // Since other-cmd is not in MUTATION_SET, it's not wrapped
167
+ expect((result.data as Record<string, unknown>).value).toBe(42);
168
+ });
169
+ });
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Staged execution pipeline — registry-level middleware for pre/post hooks
3
+ * and full in-memory dry-run support.
4
+ *
5
+ * Wraps all registry handlers with prepare/execute/finalize stages.
6
+ * When dryRun=true and the command is a mutation, the mutation executes
7
+ * against a temporary directory clone of .planning/ instead of the real
8
+ * project, and the before/after diff is returned without writing to disk.
9
+ *
10
+ * Read commands are always executed normally — they are side-effect-free.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * import { createRegistry } from './index.js';
15
+ * import { wrapWithPipeline } from './pipeline.js';
16
+ *
17
+ * const registry = createRegistry();
18
+ * wrapWithPipeline(registry, MUTATION_COMMANDS, { dryRun: true });
19
+ * // mutations now return { data: { dry_run: true, diff: { ... } } }
20
+ * ```
21
+ */
22
+
23
+ import { mkdtemp, mkdir, writeFile, readFile, rm } from 'node:fs/promises';
24
+ import { existsSync, readdirSync } from 'node:fs';
25
+ import { join, relative, dirname } from 'node:path';
26
+ import { tmpdir } from 'node:os';
27
+ import type { QueryResult } from './utils.js';
28
+ import type { QueryRegistry } from './registry.js';
29
+
30
+ // ─── Types ─────────────────────────────────────────────────────────────────
31
+
32
+ /**
33
+ * Configuration for the pipeline middleware.
34
+ */
35
+ export interface PipelineOptions {
36
+ /** When true, mutations execute against a temp clone and return a diff */
37
+ dryRun?: boolean;
38
+ /** Called before each handler invocation */
39
+ onPrepare?: (command: string, args: string[], projectDir: string) => Promise<void>;
40
+ /** Called after each handler invocation */
41
+ onFinalize?: (command: string, args: string[], result: QueryResult) => Promise<void>;
42
+ }
43
+
44
+ /**
45
+ * A single stage in the execution pipeline.
46
+ */
47
+ export type PipelineStage = 'prepare' | 'execute' | 'finalize';
48
+
49
+ // ─── Internal helpers ──────────────────────────────────────────────────────
50
+
51
+ /**
52
+ * Recursively collect all files under a directory.
53
+ * Returns paths relative to the base directory.
54
+ */
55
+ function collectFiles(dir: string, base: string): string[] {
56
+ const results: string[] = [];
57
+ if (!existsSync(dir)) return results;
58
+ const entries = readdirSync(dir, { withFileTypes: true });
59
+ for (const entry of entries) {
60
+ const fullPath = join(dir, entry.name);
61
+ const relPath = relative(base, fullPath);
62
+ if (entry.isFile()) {
63
+ results.push(relPath);
64
+ } else if (entry.isDirectory()) {
65
+ results.push(...collectFiles(fullPath, base));
66
+ }
67
+ }
68
+ return results;
69
+ }
70
+
71
+ /**
72
+ * Copy .planning/ subtree from sourceDir to destDir.
73
+ * Only copies text files relevant to GSD state (skips binaries and logs).
74
+ */
75
+ async function copyPlanningTree(sourceDir: string, destDir: string): Promise<void> {
76
+ const planningSource = join(sourceDir, '.planning');
77
+ if (!existsSync(planningSource)) return;
78
+
79
+ const files = collectFiles(planningSource, planningSource);
80
+ for (const relFile of files) {
81
+ // Skip large or binary-ish files (> 1MB) — only relevant for text state
82
+ const sourcePath = join(planningSource, relFile);
83
+ const destPath = join(destDir, '.planning', relFile);
84
+ await mkdir(dirname(destPath), { recursive: true });
85
+ try {
86
+ const content = await readFile(sourcePath, 'utf-8');
87
+ await writeFile(destPath, content, 'utf-8');
88
+ } catch {
89
+ // Skip unreadable files (binary, permission issues, etc.)
90
+ }
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Read all files from .planning/ in a directory into a map of relPath → content.
96
+ */
97
+ async function readPlanningState(projectDir: string): Promise<Map<string, string>> {
98
+ const planningDir = join(projectDir, '.planning');
99
+ const result = new Map<string, string>();
100
+ if (!existsSync(planningDir)) return result;
101
+
102
+ const files = collectFiles(planningDir, planningDir);
103
+ for (const relFile of files) {
104
+ try {
105
+ const content = await readFile(join(planningDir, relFile), 'utf-8');
106
+ result.set(relFile, content);
107
+ } catch { /* skip unreadable */ }
108
+ }
109
+ return result;
110
+ }
111
+
112
+ /**
113
+ * Diff two file maps, returning files that changed (with before/after content).
114
+ */
115
+ function diffPlanningState(
116
+ before: Map<string, string>,
117
+ after: Map<string, string>,
118
+ ): Record<string, { before: string | null; after: string | null }> {
119
+ const diff: Record<string, { before: string | null; after: string | null }> = {};
120
+ const allKeys = new Set([...before.keys(), ...after.keys()]);
121
+ for (const key of allKeys) {
122
+ const b = before.get(key) ?? null;
123
+ const a = after.get(key) ?? null;
124
+ if (b !== a) {
125
+ diff[`.planning/${key}`] = { before: b, after: a };
126
+ }
127
+ }
128
+ return diff;
129
+ }
130
+
131
+ // ─── wrapWithPipeline ──────────────────────────────────────────────────────
132
+
133
+ /**
134
+ * Wrap all registered handlers with prepare/execute/finalize pipeline stages.
135
+ *
136
+ * When dryRun=true and a mutation command is dispatched, the real projectDir
137
+ * is cloned (only .planning/ subtree) into a temp directory. The mutation
138
+ * runs against the clone, a before/after diff is computed, and the temp
139
+ * directory is cleaned up in a finally block. The real project is never
140
+ * touched during a dry run.
141
+ *
142
+ * @param registry - The registry whose handlers to wrap
143
+ * @param mutationCommands - Set of command names that perform mutations
144
+ * @param options - Pipeline configuration
145
+ */
146
+ export function wrapWithPipeline(
147
+ registry: QueryRegistry,
148
+ mutationCommands: Set<string>,
149
+ options: PipelineOptions,
150
+ ): void {
151
+ const { dryRun = false, onPrepare, onFinalize } = options;
152
+
153
+ // Collect all currently registered commands by iterating known handlers
154
+ // We wrap by re-registering with the same name using the same technique
155
+ // as event emission wiring in index.ts
156
+ const commandsToWrap: string[] = [];
157
+
158
+ // Enumerate mutation commands via the caller-provided set. QueryRegistry also
159
+ // exposes commands() for full command lists when needed by tooling.
160
+ // We wrap the register method temporarily to collect known commands,
161
+ // then restore. Instead, we use the mutation commands set + a marker approach:
162
+ // wrap mutation commands for dry-run, and wrap all via onPrepare/onFinalize.
163
+ //
164
+ // For pipeline wrapping we use a two-pass approach:
165
+ // Pass 1: wrap mutation commands (for dry-run + hooks)
166
+ // Pass 2: wrap non-mutation commands (for hooks only, if hooks provided)
167
+
168
+ const wrapHandler = (cmd: string, isMutation: boolean): void => {
169
+ const original = registry.getHandler(cmd);
170
+ if (!original) return;
171
+
172
+ registry.register(cmd, async (args: string[], projectDir: string) => {
173
+ // ─── Prepare stage ───────────────────────────────────────────────
174
+ if (onPrepare) {
175
+ await onPrepare(cmd, args, projectDir);
176
+ }
177
+
178
+ let result: QueryResult;
179
+
180
+ if (dryRun && isMutation) {
181
+ // ─── Dry-run: clone → mutate → diff ──────────────────────────
182
+ let tempDir: string | null = null;
183
+ try {
184
+ tempDir = await mkdtemp(join(tmpdir(), 'gsd-dryrun-'));
185
+
186
+ // Snapshot state before mutation
187
+ const beforeState = await readPlanningState(projectDir);
188
+
189
+ // Copy .planning/ to temp dir
190
+ await copyPlanningTree(projectDir, tempDir);
191
+
192
+ // Execute mutation against temp dir clone
193
+ await original(args, tempDir);
194
+
195
+ // Snapshot state after mutation (from temp dir)
196
+ const afterState = await readPlanningState(tempDir);
197
+
198
+ // Compute diff
199
+ const diff = diffPlanningState(beforeState, afterState);
200
+ const changedFiles = Object.keys(diff);
201
+
202
+ result = {
203
+ data: {
204
+ dry_run: true,
205
+ command: cmd,
206
+ args,
207
+ diff,
208
+ changes_summary: changedFiles.length > 0
209
+ ? `${changedFiles.length} file(s) would be modified: ${changedFiles.join(', ')}`
210
+ : 'No files would be modified',
211
+ },
212
+ };
213
+ } finally {
214
+ // T-14-06: Always clean up temp dir, even on error
215
+ if (tempDir) {
216
+ await rm(tempDir, { recursive: true, force: true }).catch(() => undefined);
217
+ }
218
+ }
219
+ } else {
220
+ // ─── Normal execution ─────────────────────────────────────────
221
+ result = await original(args, projectDir);
222
+ }
223
+
224
+ // ─── Finalize stage ───────────────────────────────────────────────
225
+ if (onFinalize) {
226
+ await onFinalize(cmd, args, result);
227
+ }
228
+
229
+ return result;
230
+ });
231
+
232
+ commandsToWrap.push(cmd);
233
+ };
234
+
235
+ // Wrap mutation commands (dry-run eligible + hooks)
236
+ for (const cmd of mutationCommands) {
237
+ wrapHandler(cmd, true);
238
+ }
239
+
240
+ // Note: non-mutation commands are NOT wrapped here for performance — callers
241
+ // can provide onPrepare/onFinalize for mutations only. If full wrapping of
242
+ // read commands is needed, callers should pass their command set explicitly.
243
+ }
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Unit tests for plan.execution-route.
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
6
+ import { mkdtemp, writeFile, mkdir, rm } from 'node:fs/promises';
7
+ import { join } from 'node:path';
8
+ import { tmpdir } from 'node:os';
9
+ import { planExecutionRoute } from './plan-execution-route.js';
10
+
11
+ let tmpDir: string;
12
+ let phaseDir: string;
13
+
14
+ function makeTask(index: number, options?: {
15
+ type?: string;
16
+ files?: string[];
17
+ readFirst?: string[];
18
+ acceptance?: string[];
19
+ }): string {
20
+ const type = options?.type ?? 'auto';
21
+ const files = (options?.files ?? [`src/file-${index}.ts`]).join(', ');
22
+ const readFirst = (options?.readFirst ?? []).join(', ');
23
+ const acceptance = options?.acceptance ?? [];
24
+ return `<task type="${type}">
25
+ <name>Task ${index}</name>
26
+ <files>${files}</files>
27
+ <read_first>${readFirst}</read_first>
28
+ <action>Do task ${index}</action>
29
+ <verify>echo ok</verify>
30
+ <acceptance_criteria>
31
+ ${acceptance.map((criterion) => ` - ${criterion}`).join('\n')}
32
+ </acceptance_criteria>
33
+ <done>done</done>
34
+ </task>`;
35
+ }
36
+
37
+ function makePlan(options: {
38
+ autonomous?: boolean;
39
+ filesModified?: string[];
40
+ tasks: string[];
41
+ }): string {
42
+ return `---
43
+ phase: 09-foundation
44
+ plan: "01"
45
+ wave: 1
46
+ depends_on: []
47
+ files_modified: [${(options.filesModified ?? []).join(', ')}]
48
+ autonomous: ${options.autonomous ?? true}
49
+ ---
50
+
51
+ <objective>
52
+ Route this plan
53
+ </objective>
54
+
55
+ <tasks>
56
+ ${options.tasks.join('\n')}
57
+ </tasks>
58
+ `;
59
+ }
60
+
61
+ async function writePlan(content: string): Promise<string> {
62
+ const rel = join('.planning', 'phases', '09-x', '09-01-PLAN.md');
63
+ await writeFile(join(tmpDir, rel), content, 'utf-8');
64
+ return rel;
65
+ }
66
+
67
+ beforeEach(async () => {
68
+ tmpDir = await mkdtemp(join(tmpdir(), 'gsd-per-'));
69
+ phaseDir = join(tmpDir, '.planning', 'phases', '09-x');
70
+ await mkdir(phaseDir, { recursive: true });
71
+ });
72
+
73
+ afterEach(async () => {
74
+ await rm(tmpDir, { recursive: true, force: true });
75
+ });
76
+
77
+ describe('planExecutionRoute', () => {
78
+ it('uses threshold inline routing for plans at or below the configured threshold', async () => {
79
+ const rel = await writePlan(makePlan({
80
+ filesModified: ['src/a.ts', 'src/b.ts'],
81
+ tasks: [
82
+ makeTask(1, { acceptance: ['criterion 1'] }),
83
+ makeTask(2, { acceptance: ['criterion 2'] }),
84
+ ],
85
+ }));
86
+
87
+ const r = await planExecutionRoute([rel, '--inline-threshold', '2'], tmpDir);
88
+ const d = r.data as Record<string, unknown>;
89
+
90
+ expect(d.recommended_pattern).toBe('C');
91
+ expect(d.recommended_execution).toBe('main-inline');
92
+ expect(d.reason).toBe('task_count_threshold');
93
+ });
94
+
95
+ it('applies low-complexity override for simple 4-task plans above the threshold', async () => {
96
+ const rel = await writePlan(makePlan({
97
+ filesModified: ['src/a.ts', 'src/b.ts', 'src/c.ts'],
98
+ tasks: [
99
+ makeTask(1, { files: ['src/a.ts'], readFirst: ['src/base.ts'], acceptance: ['a1'] }),
100
+ makeTask(2, { files: ['src/b.ts'], acceptance: ['b1'] }),
101
+ makeTask(3, { files: ['src/c.ts'], acceptance: ['c1'] }),
102
+ makeTask(4, { files: ['src/c.ts'], acceptance: ['c2'] }),
103
+ ],
104
+ }));
105
+
106
+ const r = await planExecutionRoute([rel, '--inline-threshold', '2'], tmpDir);
107
+ const d = r.data as Record<string, unknown>;
108
+
109
+ expect(d.low_complexity_inline).toBe(true);
110
+ expect(d.recommended_pattern).toBe('C');
111
+ expect(d.reason).toBe('low_complexity_override');
112
+ });
113
+
114
+ it('keeps complex 4-task plans on the subagent route', async () => {
115
+ const rel = await writePlan(makePlan({
116
+ filesModified: ['src/a.ts', 'src/b.ts', 'src/c.ts', 'src/d.ts', 'src/e.ts'],
117
+ tasks: [
118
+ makeTask(1, { files: ['src/a.ts'], readFirst: ['src/r1.ts', 'src/r2.ts'], acceptance: ['a1', 'a2'] }),
119
+ makeTask(2, { files: ['src/b.ts'], readFirst: ['src/r3.ts', 'src/r4.ts'], acceptance: ['b1', 'b2'] }),
120
+ makeTask(3, { files: ['src/c.ts'], readFirst: ['src/r5.ts'], acceptance: ['c1'] }),
121
+ makeTask(4, { files: ['src/d.ts', 'src/e.ts'], acceptance: ['d1'] }),
122
+ ],
123
+ }));
124
+
125
+ const r = await planExecutionRoute([rel, '--inline-threshold', '2'], tmpDir);
126
+ const d = r.data as Record<string, unknown>;
127
+
128
+ expect(d.low_complexity_inline).toBe(false);
129
+ expect(d.recommended_pattern).toBe('A');
130
+ expect(d.reason).toBe('autonomous_default');
131
+ });
132
+
133
+ it('routes verify-only checkpoints to segmented execution', async () => {
134
+ const rel = await writePlan(makePlan({
135
+ tasks: [
136
+ makeTask(1),
137
+ makeTask(2, { type: 'checkpoint:human-verify' }),
138
+ makeTask(3),
139
+ ],
140
+ }));
141
+
142
+ const r = await planExecutionRoute([rel, '--inline-threshold', '2'], tmpDir);
143
+ const d = r.data as Record<string, unknown>;
144
+
145
+ expect(d.checkpoint_mode).toBe('verify_only');
146
+ expect(d.recommended_pattern).toBe('B');
147
+ expect(d.reason).toBe('verify_checkpoints');
148
+ });
149
+
150
+ it('routes decision checkpoints to main inline execution', async () => {
151
+ const rel = await writePlan(makePlan({
152
+ tasks: [
153
+ makeTask(1),
154
+ makeTask(2, { type: 'checkpoint:decision' }),
155
+ makeTask(3),
156
+ ],
157
+ }));
158
+
159
+ const r = await planExecutionRoute([rel, '--inline-threshold', '2'], tmpDir);
160
+ const d = r.data as Record<string, unknown>;
161
+
162
+ expect(d.checkpoint_mode).toBe('decision_or_action');
163
+ expect(d.recommended_pattern).toBe('C');
164
+ expect(d.reason).toBe('decision_checkpoint');
165
+ });
166
+ });