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.
- package/LICENSE +21 -0
- package/README.md +939 -0
- package/README.zh-CN.md +876 -0
- package/agents/gsd-advisor-researcher.md +127 -0
- package/agents/gsd-ai-researcher.md +133 -0
- package/agents/gsd-assumptions-analyzer.md +105 -0
- package/agents/gsd-code-fixer.md +517 -0
- package/agents/gsd-code-reviewer.md +371 -0
- package/agents/gsd-codebase-mapper.md +781 -0
- package/agents/gsd-debug-session-manager.md +314 -0
- package/agents/gsd-debugger.md +1452 -0
- package/agents/gsd-doc-classifier.md +168 -0
- package/agents/gsd-doc-synthesizer.md +204 -0
- package/agents/gsd-doc-verifier.md +217 -0
- package/agents/gsd-doc-writer.md +615 -0
- package/agents/gsd-domain-researcher.md +153 -0
- package/agents/gsd-eval-auditor.md +191 -0
- package/agents/gsd-eval-planner.md +154 -0
- package/agents/gsd-executor.md +603 -0
- package/agents/gsd-framework-selector.md +160 -0
- package/agents/gsd-integration-checker.md +470 -0
- package/agents/gsd-intel-updater.md +334 -0
- package/agents/gsd-nyquist-auditor.md +203 -0
- package/agents/gsd-pattern-mapper.md +335 -0
- package/agents/gsd-phase-researcher.md +841 -0
- package/agents/gsd-plan-checker.md +978 -0
- package/agents/gsd-planner.md +1251 -0
- package/agents/gsd-project-researcher.md +677 -0
- package/agents/gsd-research-synthesizer.md +247 -0
- package/agents/gsd-roadmapper.md +688 -0
- package/agents/gsd-security-auditor.md +155 -0
- package/agents/gsd-ui-auditor.md +495 -0
- package/agents/gsd-ui-checker.md +309 -0
- package/agents/gsd-ui-researcher.md +380 -0
- package/agents/gsd-user-profiler.md +171 -0
- package/agents/gsd-verifier.md +830 -0
- package/bin/install.js +7062 -0
- package/commands/gsd/add-backlog.md +79 -0
- package/commands/gsd/add-phase.md +43 -0
- package/commands/gsd/add-tests.md +41 -0
- package/commands/gsd/add-todo.md +47 -0
- package/commands/gsd/ai-integration-phase.md +36 -0
- package/commands/gsd/analyze-dependencies.md +34 -0
- package/commands/gsd/audit-fix.md +33 -0
- package/commands/gsd/audit-milestone.md +36 -0
- package/commands/gsd/audit-uat.md +24 -0
- package/commands/gsd/autonomous.md +46 -0
- package/commands/gsd/check-todos.md +45 -0
- package/commands/gsd/cleanup.md +23 -0
- package/commands/gsd/code-review-fix.md +52 -0
- package/commands/gsd/code-review.md +55 -0
- package/commands/gsd/complete-milestone.md +136 -0
- package/commands/gsd/debug.md +263 -0
- package/commands/gsd/discuss-phase.md +69 -0
- package/commands/gsd/do.md +30 -0
- package/commands/gsd/docs-update.md +48 -0
- package/commands/gsd/eval-review.md +32 -0
- package/commands/gsd/execute-phase.md +63 -0
- package/commands/gsd/explore.md +27 -0
- package/commands/gsd/extract_learnings.md +22 -0
- package/commands/gsd/fast.md +30 -0
- package/commands/gsd/forensics.md +56 -0
- package/commands/gsd/from-gsd2.md +47 -0
- package/commands/gsd/graphify.md +201 -0
- package/commands/gsd/health.md +22 -0
- package/commands/gsd/help.md +24 -0
- package/commands/gsd/import.md +37 -0
- package/commands/gsd/inbox.md +38 -0
- package/commands/gsd/ingest-docs.md +42 -0
- package/commands/gsd/insert-phase.md +32 -0
- package/commands/gsd/intel.md +179 -0
- package/commands/gsd/join-discord.md +19 -0
- package/commands/gsd/list-phase-assumptions.md +46 -0
- package/commands/gsd/list-workspaces.md +19 -0
- package/commands/gsd/manager.md +40 -0
- package/commands/gsd/map-codebase.md +71 -0
- package/commands/gsd/milestone-summary.md +51 -0
- package/commands/gsd/new-milestone.md +44 -0
- package/commands/gsd/new-project.md +46 -0
- package/commands/gsd/new-workspace.md +44 -0
- package/commands/gsd/next.md +28 -0
- package/commands/gsd/note.md +34 -0
- package/commands/gsd/pause-work.md +38 -0
- package/commands/gsd/plan-milestone-gaps.md +34 -0
- package/commands/gsd/plan-phase.md +52 -0
- package/commands/gsd/plan-review-convergence.md +52 -0
- package/commands/gsd/plant-seed.md +28 -0
- package/commands/gsd/pr-branch.md +25 -0
- package/commands/gsd/profile-user.md +46 -0
- package/commands/gsd/progress.md +25 -0
- package/commands/gsd/quick.md +173 -0
- package/commands/gsd/reapply-patches.md +331 -0
- package/commands/gsd/remove-phase.md +31 -0
- package/commands/gsd/remove-workspace.md +26 -0
- package/commands/gsd/research-phase.md +195 -0
- package/commands/gsd/resume-work.md +40 -0
- package/commands/gsd/review-backlog.md +62 -0
- package/commands/gsd/review.md +40 -0
- package/commands/gsd/scan.md +26 -0
- package/commands/gsd/secure-phase.md +35 -0
- package/commands/gsd/session-report.md +19 -0
- package/commands/gsd/set-profile.md +12 -0
- package/commands/gsd/settings.md +36 -0
- package/commands/gsd/ship.md +23 -0
- package/commands/gsd/sketch-wrap-up.md +31 -0
- package/commands/gsd/sketch.md +49 -0
- package/commands/gsd/spec-phase.md +62 -0
- package/commands/gsd/spike-wrap-up.md +31 -0
- package/commands/gsd/spike.md +46 -0
- package/commands/gsd/stats.md +18 -0
- package/commands/gsd/sync-skills.md +19 -0
- package/commands/gsd/thread.md +227 -0
- package/commands/gsd/ui-phase.md +34 -0
- package/commands/gsd/ui-review.md +32 -0
- package/commands/gsd/ultraplan-phase.md +33 -0
- package/commands/gsd/undo.md +34 -0
- package/commands/gsd/update.md +37 -0
- package/commands/gsd/validate-phase.md +35 -0
- package/commands/gsd/verify-work.md +38 -0
- package/commands/gsd/workstreams.md +69 -0
- package/get-shit-done/bin/gsd-tools.cjs +1263 -0
- package/get-shit-done/bin/lib/artifacts.cjs +52 -0
- package/get-shit-done/bin/lib/audit.cjs +757 -0
- package/get-shit-done/bin/lib/commands.cjs +1023 -0
- package/get-shit-done/bin/lib/config-schema.cjs +79 -0
- package/get-shit-done/bin/lib/config.cjs +463 -0
- package/get-shit-done/bin/lib/core.cjs +1794 -0
- package/get-shit-done/bin/lib/docs.cjs +267 -0
- package/get-shit-done/bin/lib/frontmatter.cjs +379 -0
- package/get-shit-done/bin/lib/graphify.cjs +494 -0
- package/get-shit-done/bin/lib/gsd2-import.cjs +511 -0
- package/get-shit-done/bin/lib/init.cjs +1878 -0
- package/get-shit-done/bin/lib/intel.cjs +639 -0
- package/get-shit-done/bin/lib/learnings.cjs +378 -0
- package/get-shit-done/bin/lib/milestone.cjs +283 -0
- package/get-shit-done/bin/lib/model-profiles.cjs +71 -0
- package/get-shit-done/bin/lib/phase.cjs +1058 -0
- package/get-shit-done/bin/lib/profile-output.cjs +1080 -0
- package/get-shit-done/bin/lib/profile-pipeline.cjs +539 -0
- package/get-shit-done/bin/lib/roadmap.cjs +523 -0
- package/get-shit-done/bin/lib/schema-detect.cjs +238 -0
- package/get-shit-done/bin/lib/security.cjs +504 -0
- package/get-shit-done/bin/lib/state.cjs +1649 -0
- package/get-shit-done/bin/lib/template.cjs +226 -0
- package/get-shit-done/bin/lib/uat.cjs +288 -0
- package/get-shit-done/bin/lib/verify.cjs +1184 -0
- package/get-shit-done/bin/lib/workstream.cjs +495 -0
- package/get-shit-done/bin/repair-sdk.cjs +177 -0
- package/get-shit-done/contexts/dev.md +21 -0
- package/get-shit-done/contexts/research.md +22 -0
- package/get-shit-done/contexts/review.md +22 -0
- package/get-shit-done/references/agent-contracts.md +79 -0
- package/get-shit-done/references/ai-evals.md +156 -0
- package/get-shit-done/references/ai-frameworks.md +186 -0
- package/get-shit-done/references/artifact-types.md +131 -0
- package/get-shit-done/references/autonomous-smart-discuss.md +277 -0
- package/get-shit-done/references/checkpoints.md +808 -0
- package/get-shit-done/references/common-bug-patterns.md +114 -0
- package/get-shit-done/references/context-budget.md +49 -0
- package/get-shit-done/references/continuation-format.md +253 -0
- package/get-shit-done/references/debugger-philosophy.md +76 -0
- package/get-shit-done/references/decimal-phase-calculation.md +64 -0
- package/get-shit-done/references/doc-conflict-engine.md +91 -0
- package/get-shit-done/references/domain-probes.md +125 -0
- package/get-shit-done/references/executor-examples.md +110 -0
- package/get-shit-done/references/few-shot-examples/plan-checker.md +73 -0
- package/get-shit-done/references/few-shot-examples/verifier.md +109 -0
- package/get-shit-done/references/gate-prompts.md +100 -0
- package/get-shit-done/references/gates.md +70 -0
- package/get-shit-done/references/git-integration.md +295 -0
- package/get-shit-done/references/git-planning-commit.md +40 -0
- package/get-shit-done/references/ios-scaffold.md +123 -0
- package/get-shit-done/references/mandatory-initial-read.md +2 -0
- package/get-shit-done/references/model-profile-resolution.md +38 -0
- package/get-shit-done/references/model-profiles.md +145 -0
- package/get-shit-done/references/phase-argument-parsing.md +61 -0
- package/get-shit-done/references/planner-antipatterns.md +89 -0
- package/get-shit-done/references/planner-gap-closure.md +62 -0
- package/get-shit-done/references/planner-reviews.md +39 -0
- package/get-shit-done/references/planner-revision.md +87 -0
- package/get-shit-done/references/planner-source-audit.md +73 -0
- package/get-shit-done/references/planning-config.md +460 -0
- package/get-shit-done/references/project-skills-discovery.md +19 -0
- package/get-shit-done/references/questioning.md +162 -0
- package/get-shit-done/references/revision-loop.md +97 -0
- package/get-shit-done/references/sketch-interactivity.md +41 -0
- package/get-shit-done/references/sketch-theme-system.md +94 -0
- package/get-shit-done/references/sketch-tooling.md +45 -0
- package/get-shit-done/references/sketch-variant-patterns.md +81 -0
- package/get-shit-done/references/tdd.md +330 -0
- package/get-shit-done/references/thinking-models-debug.md +44 -0
- package/get-shit-done/references/thinking-models-execution.md +50 -0
- package/get-shit-done/references/thinking-models-planning.md +62 -0
- package/get-shit-done/references/thinking-models-research.md +50 -0
- package/get-shit-done/references/thinking-models-verification.md +55 -0
- package/get-shit-done/references/thinking-partner.md +96 -0
- package/get-shit-done/references/ui-brand.md +160 -0
- package/get-shit-done/references/universal-anti-patterns.md +63 -0
- package/get-shit-done/references/user-profiling.md +681 -0
- package/get-shit-done/references/verification-overrides.md +227 -0
- package/get-shit-done/references/verification-patterns.md +612 -0
- package/get-shit-done/references/workstream-flag.md +111 -0
- package/get-shit-done/templates/AI-SPEC.md +246 -0
- package/get-shit-done/templates/DEBUG.md +169 -0
- package/get-shit-done/templates/README.md +76 -0
- package/get-shit-done/templates/SECURITY.md +61 -0
- package/get-shit-done/templates/UAT.md +265 -0
- package/get-shit-done/templates/UI-SPEC.md +100 -0
- package/get-shit-done/templates/VALIDATION.md +76 -0
- package/get-shit-done/templates/claude-md.md +145 -0
- package/get-shit-done/templates/codebase/architecture.md +255 -0
- package/get-shit-done/templates/codebase/concerns.md +310 -0
- package/get-shit-done/templates/codebase/conventions.md +307 -0
- package/get-shit-done/templates/codebase/integrations.md +280 -0
- package/get-shit-done/templates/codebase/stack.md +186 -0
- package/get-shit-done/templates/codebase/structure.md +285 -0
- package/get-shit-done/templates/codebase/testing.md +480 -0
- package/get-shit-done/templates/config.json +56 -0
- package/get-shit-done/templates/context.md +352 -0
- package/get-shit-done/templates/continue-here.md +78 -0
- package/get-shit-done/templates/copilot-instructions.md +7 -0
- package/get-shit-done/templates/debug-subagent-prompt.md +91 -0
- package/get-shit-done/templates/dev-preferences.md +21 -0
- package/get-shit-done/templates/discovery.md +146 -0
- package/get-shit-done/templates/discussion-log.md +63 -0
- package/get-shit-done/templates/milestone-archive.md +123 -0
- package/get-shit-done/templates/milestone.md +115 -0
- package/get-shit-done/templates/phase-prompt.md +610 -0
- package/get-shit-done/templates/planner-subagent-prompt.md +117 -0
- package/get-shit-done/templates/project.md +186 -0
- package/get-shit-done/templates/requirements.md +231 -0
- package/get-shit-done/templates/research-project/ARCHITECTURE.md +204 -0
- package/get-shit-done/templates/research-project/FEATURES.md +147 -0
- package/get-shit-done/templates/research-project/PITFALLS.md +200 -0
- package/get-shit-done/templates/research-project/STACK.md +120 -0
- package/get-shit-done/templates/research-project/SUMMARY.md +170 -0
- package/get-shit-done/templates/research.md +592 -0
- package/get-shit-done/templates/retrospective.md +54 -0
- package/get-shit-done/templates/roadmap.md +202 -0
- package/get-shit-done/templates/spec.md +307 -0
- package/get-shit-done/templates/state.md +184 -0
- package/get-shit-done/templates/summary-complex.md +59 -0
- package/get-shit-done/templates/summary-minimal.md +41 -0
- package/get-shit-done/templates/summary-standard.md +48 -0
- package/get-shit-done/templates/summary.md +248 -0
- package/get-shit-done/templates/user-profile.md +146 -0
- package/get-shit-done/templates/user-setup.md +311 -0
- package/get-shit-done/templates/verification-report.md +322 -0
- package/get-shit-done/workflows/add-phase.md +112 -0
- package/get-shit-done/workflows/add-tests.md +354 -0
- package/get-shit-done/workflows/add-todo.md +160 -0
- package/get-shit-done/workflows/ai-integration-phase.md +284 -0
- package/get-shit-done/workflows/analyze-dependencies.md +96 -0
- package/get-shit-done/workflows/audit-fix.md +175 -0
- package/get-shit-done/workflows/audit-milestone.md +340 -0
- package/get-shit-done/workflows/audit-uat.md +109 -0
- package/get-shit-done/workflows/autonomous.md +789 -0
- package/get-shit-done/workflows/check-todos.md +179 -0
- package/get-shit-done/workflows/cleanup.md +154 -0
- package/get-shit-done/workflows/code-review-fix.md +497 -0
- package/get-shit-done/workflows/code-review.md +515 -0
- package/get-shit-done/workflows/complete-milestone.md +847 -0
- package/get-shit-done/workflows/diagnose-issues.md +238 -0
- package/get-shit-done/workflows/discovery-phase.md +291 -0
- package/get-shit-done/workflows/discuss-phase-assumptions.md +670 -0
- package/get-shit-done/workflows/discuss-phase-power.md +308 -0
- package/get-shit-done/workflows/discuss-phase.md +1378 -0
- package/get-shit-done/workflows/do.md +110 -0
- package/get-shit-done/workflows/docs-update.md +1155 -0
- package/get-shit-done/workflows/eval-review.md +155 -0
- package/get-shit-done/workflows/execute-phase.md +1677 -0
- package/get-shit-done/workflows/execute-plan.md +533 -0
- package/get-shit-done/workflows/explore.md +141 -0
- package/get-shit-done/workflows/extract_learnings.md +242 -0
- package/get-shit-done/workflows/fast.md +105 -0
- package/get-shit-done/workflows/forensics.md +265 -0
- package/get-shit-done/workflows/graduation.md +195 -0
- package/get-shit-done/workflows/health.md +314 -0
- package/get-shit-done/workflows/help.md +667 -0
- package/get-shit-done/workflows/import.md +246 -0
- package/get-shit-done/workflows/inbox.md +387 -0
- package/get-shit-done/workflows/ingest-docs.md +328 -0
- package/get-shit-done/workflows/insert-phase.md +130 -0
- package/get-shit-done/workflows/list-phase-assumptions.md +178 -0
- package/get-shit-done/workflows/list-workspaces.md +56 -0
- package/get-shit-done/workflows/manager.md +365 -0
- package/get-shit-done/workflows/map-codebase.md +393 -0
- package/get-shit-done/workflows/milestone-summary.md +223 -0
- package/get-shit-done/workflows/new-milestone.md +611 -0
- package/get-shit-done/workflows/new-project.md +1391 -0
- package/get-shit-done/workflows/new-workspace.md +239 -0
- package/get-shit-done/workflows/next.md +220 -0
- package/get-shit-done/workflows/node-repair.md +92 -0
- package/get-shit-done/workflows/note.md +158 -0
- package/get-shit-done/workflows/pause-work.md +243 -0
- package/get-shit-done/workflows/plan-milestone-gaps.md +273 -0
- package/get-shit-done/workflows/plan-phase.md +1349 -0
- package/get-shit-done/workflows/plan-review-convergence.md +254 -0
- package/get-shit-done/workflows/plant-seed.md +172 -0
- package/get-shit-done/workflows/pr-branch.md +157 -0
- package/get-shit-done/workflows/profile-user.md +452 -0
- package/get-shit-done/workflows/progress.md +619 -0
- package/get-shit-done/workflows/quick.md +970 -0
- package/get-shit-done/workflows/remove-phase.md +155 -0
- package/get-shit-done/workflows/remove-workspace.md +92 -0
- package/get-shit-done/workflows/research-phase.md +89 -0
- package/get-shit-done/workflows/resume-project.md +326 -0
- package/get-shit-done/workflows/review.md +344 -0
- package/get-shit-done/workflows/scan.md +102 -0
- package/get-shit-done/workflows/secure-phase.md +166 -0
- package/get-shit-done/workflows/session-report.md +146 -0
- package/get-shit-done/workflows/settings.md +319 -0
- package/get-shit-done/workflows/ship.md +302 -0
- package/get-shit-done/workflows/sketch-wrap-up.md +283 -0
- package/get-shit-done/workflows/sketch.md +286 -0
- package/get-shit-done/workflows/spec-phase.md +262 -0
- package/get-shit-done/workflows/spike-wrap-up.md +281 -0
- package/get-shit-done/workflows/spike.md +362 -0
- package/get-shit-done/workflows/stats.md +60 -0
- package/get-shit-done/workflows/sync-skills.md +182 -0
- package/get-shit-done/workflows/transition.md +693 -0
- package/get-shit-done/workflows/ui-phase.md +323 -0
- package/get-shit-done/workflows/ui-review.md +190 -0
- package/get-shit-done/workflows/ultraplan-phase.md +189 -0
- package/get-shit-done/workflows/undo.md +314 -0
- package/get-shit-done/workflows/update.md +587 -0
- package/get-shit-done/workflows/validate-phase.md +176 -0
- package/get-shit-done/workflows/verify-phase.md +465 -0
- package/get-shit-done/workflows/verify-work.md +740 -0
- package/hooks/dist/gsd-check-update-worker.js +108 -0
- package/hooks/dist/gsd-check-update.js +64 -0
- package/hooks/dist/gsd-context-monitor.js +192 -0
- package/hooks/dist/gsd-phase-boundary.sh +28 -0
- package/hooks/dist/gsd-prompt-guard.js +97 -0
- package/hooks/dist/gsd-read-guard.js +82 -0
- package/hooks/dist/gsd-read-injection-scanner.js +152 -0
- package/hooks/dist/gsd-session-state.sh +34 -0
- package/hooks/dist/gsd-statusline.js +293 -0
- package/hooks/dist/gsd-validate-commit.sh +48 -0
- package/hooks/dist/gsd-workflow-guard.js +94 -0
- package/hooks/gsd-check-update-worker.js +108 -0
- package/hooks/gsd-check-update.js +64 -0
- package/hooks/gsd-context-monitor.js +192 -0
- package/hooks/gsd-phase-boundary.sh +28 -0
- package/hooks/gsd-prompt-guard.js +97 -0
- package/hooks/gsd-read-guard.js +82 -0
- package/hooks/gsd-read-injection-scanner.js +152 -0
- package/hooks/gsd-session-state.sh +34 -0
- package/hooks/gsd-statusline.js +293 -0
- package/hooks/gsd-validate-commit.sh +48 -0
- package/hooks/gsd-workflow-guard.js +94 -0
- package/package.json +59 -0
- package/scripts/base64-scan.sh +262 -0
- package/scripts/build-hooks.js +95 -0
- package/scripts/gen-inventory-manifest.cjs +109 -0
- package/scripts/prompt-injection-scan.sh +201 -0
- package/scripts/run-tests.cjs +33 -0
- package/scripts/secret-scan.sh +227 -0
- package/sdk/package-lock.json +1998 -0
- package/sdk/package.json +52 -0
- package/sdk/prompts/agents/gsd-executor.md +110 -0
- package/sdk/prompts/agents/gsd-phase-researcher.md +158 -0
- package/sdk/prompts/agents/gsd-plan-checker.md +160 -0
- package/sdk/prompts/agents/gsd-planner.md +214 -0
- package/sdk/prompts/agents/gsd-project-researcher.md +323 -0
- package/sdk/prompts/agents/gsd-research-synthesizer.md +237 -0
- package/sdk/prompts/agents/gsd-roadmapper.md +670 -0
- package/sdk/prompts/agents/gsd-verifier.md +159 -0
- package/sdk/prompts/templates/project.md +186 -0
- package/sdk/prompts/templates/requirements.md +231 -0
- package/sdk/prompts/templates/research-project/ARCHITECTURE.md +204 -0
- package/sdk/prompts/templates/research-project/FEATURES.md +147 -0
- package/sdk/prompts/templates/research-project/PITFALLS.md +200 -0
- package/sdk/prompts/templates/research-project/STACK.md +120 -0
- package/sdk/prompts/templates/research-project/SUMMARY.md +170 -0
- package/sdk/prompts/templates/roadmap.md +202 -0
- package/sdk/prompts/templates/state.md +175 -0
- package/sdk/prompts/workflows/discuss-phase.md +126 -0
- package/sdk/prompts/workflows/execute-plan.md +106 -0
- package/sdk/prompts/workflows/plan-phase.md +84 -0
- package/sdk/prompts/workflows/research-phase.md +45 -0
- package/sdk/prompts/workflows/verify-phase.md +142 -0
- package/sdk/src/assembled-prompts.test.ts +349 -0
- package/sdk/src/cli-transport.test.ts +388 -0
- package/sdk/src/cli-transport.ts +130 -0
- package/sdk/src/cli.test.ts +383 -0
- package/sdk/src/cli.ts +670 -0
- package/sdk/src/config.test.ts +168 -0
- package/sdk/src/config.ts +177 -0
- package/sdk/src/context-engine.test.ts +295 -0
- package/sdk/src/context-engine.ts +170 -0
- package/sdk/src/context-truncation.test.ts +163 -0
- package/sdk/src/context-truncation.ts +233 -0
- package/sdk/src/e2e.integration.test.ts +178 -0
- package/sdk/src/errors.ts +72 -0
- package/sdk/src/event-stream.test.ts +661 -0
- package/sdk/src/event-stream.ts +441 -0
- package/sdk/src/failure-memory.test.ts +457 -0
- package/sdk/src/failure-memory.ts +1324 -0
- package/sdk/src/golden/capture.ts +95 -0
- package/sdk/src/golden/fixtures/generate-slug.golden.json +1 -0
- package/sdk/src/golden/fixtures/profile-sample-sessions/demo-project/sample.jsonl +3 -0
- package/sdk/src/golden/fixtures/summary-extract-sample.md +26 -0
- package/sdk/src/golden/fixtures/uat-render-checkpoint-sample.md +15 -0
- package/sdk/src/golden/golden-integration-covered.ts +30 -0
- package/sdk/src/golden/golden-mutation-covered.ts +7 -0
- package/sdk/src/golden/golden-policy.test.ts +8 -0
- package/sdk/src/golden/golden-policy.ts +112 -0
- package/sdk/src/golden/golden.integration.test.ts +373 -0
- package/sdk/src/golden/init-golden-normalize.ts +15 -0
- package/sdk/src/golden/read-only-golden-rows.ts +77 -0
- package/sdk/src/golden/read-only-parity.integration.test.ts +125 -0
- package/sdk/src/golden/registry-canonical-commands.ts +31 -0
- package/sdk/src/gsd-tools.test.ts +409 -0
- package/sdk/src/gsd-tools.ts +595 -0
- package/sdk/src/headless-prompts.test.ts +159 -0
- package/sdk/src/index.ts +333 -0
- package/sdk/src/init-e2e.integration.test.ts +136 -0
- package/sdk/src/init-runner.test.ts +783 -0
- package/sdk/src/init-runner.ts +735 -0
- package/sdk/src/lifecycle-e2e.integration.test.ts +258 -0
- package/sdk/src/logger.test.ts +149 -0
- package/sdk/src/logger.ts +113 -0
- package/sdk/src/milestone-runner.test.ts +421 -0
- package/sdk/src/phase-prompt.test.ts +538 -0
- package/sdk/src/phase-prompt.ts +264 -0
- package/sdk/src/phase-runner-types.test.ts +421 -0
- package/sdk/src/phase-runner.integration.test.ts +377 -0
- package/sdk/src/phase-runner.test.ts +2333 -0
- package/sdk/src/phase-runner.ts +1203 -0
- package/sdk/src/plan-parser.test.ts +528 -0
- package/sdk/src/plan-parser.ts +427 -0
- package/sdk/src/prompt-builder.test.ts +306 -0
- package/sdk/src/prompt-builder.ts +193 -0
- package/sdk/src/prompt-sanitizer.test.ts +260 -0
- package/sdk/src/prompt-sanitizer.ts +71 -0
- package/sdk/src/query/QUERY-HANDLERS.md +317 -0
- package/sdk/src/query/audit-open.ts +722 -0
- package/sdk/src/query/check-auto-mode.test.ts +77 -0
- package/sdk/src/query/check-auto-mode.ts +50 -0
- package/sdk/src/query/check-completion.test.ts +113 -0
- package/sdk/src/query/check-completion.ts +182 -0
- package/sdk/src/query/check-gates.test.ts +103 -0
- package/sdk/src/query/check-gates.ts +112 -0
- package/sdk/src/query/check-ship-ready.test.ts +77 -0
- package/sdk/src/query/check-ship-ready.ts +103 -0
- package/sdk/src/query/check-verification-status.test.ts +143 -0
- package/sdk/src/query/check-verification-status.ts +160 -0
- package/sdk/src/query/commit.test.ts +202 -0
- package/sdk/src/query/commit.ts +301 -0
- package/sdk/src/query/config-gates.test.ts +89 -0
- package/sdk/src/query/config-gates.ts +69 -0
- package/sdk/src/query/config-mutation.test.ts +365 -0
- package/sdk/src/query/config-mutation.ts +497 -0
- package/sdk/src/query/config-query.test.ts +161 -0
- package/sdk/src/query/config-query.ts +190 -0
- package/sdk/src/query/context-history.test.ts +165 -0
- package/sdk/src/query/context-history.ts +467 -0
- package/sdk/src/query/decomposed-handlers.test.ts +365 -0
- package/sdk/src/query/detect-custom-files.ts +97 -0
- package/sdk/src/query/detect-phase-type.test.ts +105 -0
- package/sdk/src/query/detect-phase-type.ts +141 -0
- package/sdk/src/query/docs-init.ts +257 -0
- package/sdk/src/query/failure-capture.ts +58 -0
- package/sdk/src/query/frontmatter-array.test.ts +14 -0
- package/sdk/src/query/frontmatter-mutation.test.ts +259 -0
- package/sdk/src/query/frontmatter-mutation.ts +343 -0
- package/sdk/src/query/frontmatter.test.ts +281 -0
- package/sdk/src/query/frontmatter.ts +397 -0
- package/sdk/src/query/helpers.test.ts +426 -0
- package/sdk/src/query/helpers.ts +482 -0
- package/sdk/src/query/index.ts +586 -0
- package/sdk/src/query/init-complex.test.ts +232 -0
- package/sdk/src/query/init-complex.ts +578 -0
- package/sdk/src/query/init.test.ts +522 -0
- package/sdk/src/query/init.ts +1046 -0
- package/sdk/src/query/intel.test.ts +90 -0
- package/sdk/src/query/intel.ts +404 -0
- package/sdk/src/query/normalize-query-command.test.ts +50 -0
- package/sdk/src/query/normalize-query-command.ts +56 -0
- package/sdk/src/query/phase-lifecycle.test.ts +1126 -0
- package/sdk/src/query/phase-lifecycle.ts +1799 -0
- package/sdk/src/query/phase-list-queries.test.ts +88 -0
- package/sdk/src/query/phase-list-queries.ts +152 -0
- package/sdk/src/query/phase-ready.test.ts +65 -0
- package/sdk/src/query/phase-ready.ts +158 -0
- package/sdk/src/query/phase.test.ts +307 -0
- package/sdk/src/query/phase.ts +340 -0
- package/sdk/src/query/pipeline.test.ts +169 -0
- package/sdk/src/query/pipeline.ts +243 -0
- package/sdk/src/query/plan-execution-route.test.ts +166 -0
- package/sdk/src/query/plan-execution-route.ts +209 -0
- package/sdk/src/query/plan-task-structure.test.ts +65 -0
- package/sdk/src/query/plan-task-structure.ts +63 -0
- package/sdk/src/query/profile-extract-messages.ts +247 -0
- package/sdk/src/query/profile-output.ts +908 -0
- package/sdk/src/query/profile-questionnaire-data.ts +181 -0
- package/sdk/src/query/profile-sample.ts +184 -0
- package/sdk/src/query/profile-scan-sessions.ts +174 -0
- package/sdk/src/query/profile.test.ts +74 -0
- package/sdk/src/query/profile.ts +337 -0
- package/sdk/src/query/progress.test.ts +156 -0
- package/sdk/src/query/progress.ts +566 -0
- package/sdk/src/query/registry.test.ts +216 -0
- package/sdk/src/query/registry.ts +174 -0
- package/sdk/src/query/requirements-extract-from-plans.test.ts +58 -0
- package/sdk/src/query/requirements-extract-from-plans.ts +86 -0
- package/sdk/src/query/roadmap-update-plan-progress.ts +132 -0
- package/sdk/src/query/roadmap.test.ts +359 -0
- package/sdk/src/query/roadmap.ts +591 -0
- package/sdk/src/query/route-next-action.test.ts +61 -0
- package/sdk/src/query/route-next-action.ts +345 -0
- package/sdk/src/query/runtime-health.ts +7 -0
- package/sdk/src/query/schema-detect.ts +189 -0
- package/sdk/src/query/skill-manifest.ts +214 -0
- package/sdk/src/query/skills.test.ts +80 -0
- package/sdk/src/query/skills.ts +62 -0
- package/sdk/src/query/state-mutation.test.ts +450 -0
- package/sdk/src/query/state-mutation.ts +1444 -0
- package/sdk/src/query/state-project-load.ts +109 -0
- package/sdk/src/query/state.test.ts +347 -0
- package/sdk/src/query/state.ts +397 -0
- package/sdk/src/query/summary.test.ts +95 -0
- package/sdk/src/query/summary.ts +296 -0
- package/sdk/src/query/template.test.ts +180 -0
- package/sdk/src/query/template.ts +242 -0
- package/sdk/src/query/uat.test.ts +77 -0
- package/sdk/src/query/uat.ts +314 -0
- package/sdk/src/query/utils.test.ts +82 -0
- package/sdk/src/query/utils.ts +92 -0
- package/sdk/src/query/validate.test.ts +656 -0
- package/sdk/src/query/validate.ts +807 -0
- package/sdk/src/query/verify.test.ts +414 -0
- package/sdk/src/query/verify.ts +645 -0
- package/sdk/src/query/websearch.test.ts +31 -0
- package/sdk/src/query/websearch.ts +82 -0
- package/sdk/src/query/workspace.test.ts +119 -0
- package/sdk/src/query/workspace.ts +131 -0
- package/sdk/src/query/workstream.test.ts +51 -0
- package/sdk/src/query/workstream.ts +434 -0
- package/sdk/src/research-gate.test.ts +190 -0
- package/sdk/src/research-gate.ts +94 -0
- package/sdk/src/runtime-health.test.ts +176 -0
- package/sdk/src/runtime-health.ts +387 -0
- package/sdk/src/session-runner.test.ts +98 -0
- package/sdk/src/session-runner.ts +299 -0
- package/sdk/src/tool-scoping.test.ts +160 -0
- package/sdk/src/tool-scoping.ts +61 -0
- package/sdk/src/types.ts +917 -0
- package/sdk/src/workstream-utils.ts +33 -0
- package/sdk/src/ws-flag.test.ts +285 -0
- package/sdk/src/ws-transport.test.ts +161 -0
- package/sdk/src/ws-transport.ts +93 -0
- 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
|
+
});
|