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,497 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config mutation handlers — write operations for .planning/config.json.
|
|
3
|
+
*
|
|
4
|
+
* Ported from get-shit-done/bin/lib/config.cjs.
|
|
5
|
+
* Provides config-set (with key validation and value coercion),
|
|
6
|
+
* config-set-model-profile, config-new-project, and config-ensure-section.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { configSet, configNewProject } from './config-mutation.js';
|
|
11
|
+
*
|
|
12
|
+
* await configSet(['model_profile', 'quality'], '/project');
|
|
13
|
+
* // { data: { updated: true, key: 'model_profile', value: 'quality', previousValue: 'balanced' } }
|
|
14
|
+
*
|
|
15
|
+
* await configNewProject([], '/project');
|
|
16
|
+
* // { data: { created: true, path: '.planning/config.json' } }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { readFile, writeFile, mkdir, rename, unlink } from 'node:fs/promises';
|
|
21
|
+
import { existsSync } from 'node:fs';
|
|
22
|
+
import { homedir } from 'node:os';
|
|
23
|
+
import { join } from 'node:path';
|
|
24
|
+
import { GSDError, ErrorClassification } from '../errors.js';
|
|
25
|
+
import { VALID_PROFILES, getAgentToModelMapForProfile } from './config-query.js';
|
|
26
|
+
import { planningPaths } from './helpers.js';
|
|
27
|
+
import { acquireStateLock, releaseStateLock } from './state-mutation.js';
|
|
28
|
+
import type { QueryHandler } from './utils.js';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Write config JSON atomically via temp file + rename to prevent
|
|
32
|
+
* partial writes on process interruption.
|
|
33
|
+
*/
|
|
34
|
+
async function atomicWriteConfig(configPath: string, config: Record<string, unknown>): Promise<void> {
|
|
35
|
+
const tmpPath = configPath + '.tmp.' + process.pid;
|
|
36
|
+
const content = JSON.stringify(config, null, 2) + '\n';
|
|
37
|
+
try {
|
|
38
|
+
await writeFile(tmpPath, content, 'utf-8');
|
|
39
|
+
await rename(tmpPath, configPath);
|
|
40
|
+
} catch {
|
|
41
|
+
// D5: Rename-failure fallback — clean up temp, fall back to direct write
|
|
42
|
+
try { await unlink(tmpPath); } catch { /* already gone */ }
|
|
43
|
+
await writeFile(configPath, content, 'utf-8');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ─── VALID_CONFIG_KEYS ────────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Allowlist of valid config key paths.
|
|
51
|
+
*
|
|
52
|
+
* Ported from config.cjs lines 14-37.
|
|
53
|
+
* Dynamic patterns (agent_skills.*, features.*) are handled
|
|
54
|
+
* separately in isValidConfigKey.
|
|
55
|
+
*/
|
|
56
|
+
const VALID_CONFIG_KEYS = new Set([
|
|
57
|
+
'mode', 'granularity', 'parallelization', 'commit_docs', 'model_profile',
|
|
58
|
+
'search_gitignored', 'brave_search', 'firecrawl', 'exa_search',
|
|
59
|
+
'workflow.research', 'workflow.plan_check', 'workflow.verifier',
|
|
60
|
+
'workflow.nyquist_validation', 'workflow.ui_phase', 'workflow.ui_safety_gate',
|
|
61
|
+
'workflow.auto_advance', 'workflow.node_repair', 'workflow.node_repair_budget',
|
|
62
|
+
'workflow.text_mode',
|
|
63
|
+
'workflow.research_before_questions',
|
|
64
|
+
'workflow.discuss_mode',
|
|
65
|
+
'workflow.skip_discuss',
|
|
66
|
+
'workflow._auto_chain_active',
|
|
67
|
+
'workflow.use_worktrees',
|
|
68
|
+
'workflow.code_review',
|
|
69
|
+
'workflow.code_review_depth',
|
|
70
|
+
'git.branching_strategy', 'git.base_branch', 'git.phase_branch_template',
|
|
71
|
+
'git.milestone_branch_template', 'git.quick_branch_template',
|
|
72
|
+
'planning.commit_docs', 'planning.search_gitignored',
|
|
73
|
+
'workflow.subagent_timeout',
|
|
74
|
+
'hooks.context_warnings',
|
|
75
|
+
'features.thinking_partner',
|
|
76
|
+
'features.global_learnings',
|
|
77
|
+
'learnings.max_inject',
|
|
78
|
+
'context',
|
|
79
|
+
'project_code', 'phase_naming',
|
|
80
|
+
'manager.flags.discuss', 'manager.flags.plan', 'manager.flags.execute',
|
|
81
|
+
'response_language',
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
// ─── CONFIG_KEY_SUGGESTIONS (D9 — match CJS config.cjs:57-67) ────────────
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Curated typo correction map for known config key mistakes.
|
|
88
|
+
* Checked before the general LCP fallback for more precise suggestions.
|
|
89
|
+
*/
|
|
90
|
+
const CONFIG_KEY_SUGGESTIONS: Record<string, string> = {
|
|
91
|
+
'workflow.nyquist_validation_enabled': 'workflow.nyquist_validation',
|
|
92
|
+
'agents.nyquist_validation_enabled': 'workflow.nyquist_validation',
|
|
93
|
+
'nyquist.validation_enabled': 'workflow.nyquist_validation',
|
|
94
|
+
'hooks.research_questions': 'workflow.research_before_questions',
|
|
95
|
+
'workflow.research_questions': 'workflow.research_before_questions',
|
|
96
|
+
'workflow.codereview': 'workflow.code_review',
|
|
97
|
+
'workflow.review': 'workflow.code_review',
|
|
98
|
+
'workflow.code_review_level': 'workflow.code_review_depth',
|
|
99
|
+
'workflow.review_depth': 'workflow.code_review_depth',
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// ─── isValidConfigKey ─────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Check whether a config key path is valid.
|
|
106
|
+
*
|
|
107
|
+
* Supports exact matches from VALID_CONFIG_KEYS plus dynamic patterns
|
|
108
|
+
* like `agent_skills.<agent-type>` and `features.<feature_name>`.
|
|
109
|
+
* Uses curated CONFIG_KEY_SUGGESTIONS before LCP fallback for typo correction.
|
|
110
|
+
*
|
|
111
|
+
* @param keyPath - Dot-notation config key path
|
|
112
|
+
* @returns Object with valid flag and optional suggestion for typos
|
|
113
|
+
*/
|
|
114
|
+
export function isValidConfigKey(keyPath: string): { valid: boolean; suggestion?: string } {
|
|
115
|
+
if (VALID_CONFIG_KEYS.has(keyPath)) return { valid: true };
|
|
116
|
+
|
|
117
|
+
// Dynamic patterns: agent_skills.<agent-type>
|
|
118
|
+
if (/^agent_skills\.[a-zA-Z0-9_-]+$/.test(keyPath)) return { valid: true };
|
|
119
|
+
|
|
120
|
+
// Dynamic patterns: features.<feature_name>
|
|
121
|
+
if (/^features\.[a-zA-Z0-9_]+$/.test(keyPath)) return { valid: true };
|
|
122
|
+
|
|
123
|
+
// D9: Check curated suggestions before LCP fallback
|
|
124
|
+
if (CONFIG_KEY_SUGGESTIONS[keyPath]) {
|
|
125
|
+
return { valid: false, suggestion: CONFIG_KEY_SUGGESTIONS[keyPath] };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Find closest suggestion using longest common prefix
|
|
129
|
+
const keys = [...VALID_CONFIG_KEYS];
|
|
130
|
+
let bestMatch = '';
|
|
131
|
+
let bestScore = 0;
|
|
132
|
+
|
|
133
|
+
for (const candidate of keys) {
|
|
134
|
+
let shared = 0;
|
|
135
|
+
const maxLen = Math.min(keyPath.length, candidate.length);
|
|
136
|
+
for (let i = 0; i < maxLen; i++) {
|
|
137
|
+
if (keyPath[i] === candidate[i]) shared++;
|
|
138
|
+
else break;
|
|
139
|
+
}
|
|
140
|
+
if (shared > bestScore) {
|
|
141
|
+
bestScore = shared;
|
|
142
|
+
bestMatch = candidate;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return { valid: false, suggestion: bestScore > 2 ? bestMatch : undefined };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ─── parseConfigValue ─────────────────────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Coerce a CLI string value to its native type.
|
|
153
|
+
*
|
|
154
|
+
* Ported from config.cjs lines 344-351.
|
|
155
|
+
*
|
|
156
|
+
* @param value - String value from CLI
|
|
157
|
+
* @returns Coerced value: boolean, number, parsed JSON, or original string
|
|
158
|
+
*/
|
|
159
|
+
export function parseConfigValue(value: string): unknown {
|
|
160
|
+
if (value === 'true') return true;
|
|
161
|
+
if (value === 'false') return false;
|
|
162
|
+
if (value !== '' && !isNaN(Number(value))) return Number(value);
|
|
163
|
+
if (typeof value === 'string' && (value.startsWith('[') || value.startsWith('{'))) {
|
|
164
|
+
try { return JSON.parse(value); } catch { /* keep as string */ }
|
|
165
|
+
}
|
|
166
|
+
return value;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ─── setConfigValue ───────────────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Set a value at a dot-notation path in a config object.
|
|
173
|
+
*
|
|
174
|
+
* Creates nested objects as needed along the path.
|
|
175
|
+
*
|
|
176
|
+
* @param obj - Config object to mutate
|
|
177
|
+
* @param dotPath - Dot-notation key path (e.g., 'workflow.auto_advance')
|
|
178
|
+
* @param value - Value to set
|
|
179
|
+
*/
|
|
180
|
+
function getValueAtPath(obj: Record<string, unknown>, dotPath: string): unknown {
|
|
181
|
+
const keys = dotPath.split('.');
|
|
182
|
+
let current: unknown = obj;
|
|
183
|
+
for (const key of keys) {
|
|
184
|
+
if (current === undefined || current === null || typeof current !== 'object') {
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
current = (current as Record<string, unknown>)[key];
|
|
188
|
+
}
|
|
189
|
+
return current;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function setConfigValue(obj: Record<string, unknown>, dotPath: string, value: unknown): void {
|
|
193
|
+
const keys = dotPath.split('.');
|
|
194
|
+
let current: Record<string, unknown> = obj;
|
|
195
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
196
|
+
const key = keys[i];
|
|
197
|
+
if (current[key] === undefined || typeof current[key] !== 'object' || current[key] === null) {
|
|
198
|
+
current[key] = {};
|
|
199
|
+
}
|
|
200
|
+
current = current[key] as Record<string, unknown>;
|
|
201
|
+
}
|
|
202
|
+
current[keys[keys.length - 1]] = value;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ─── configSet ────────────────────────────────────────────────────────────
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Write a validated key-value pair to config.json.
|
|
209
|
+
*
|
|
210
|
+
* Validates key against VALID_CONFIG_KEYS allowlist, coerces value
|
|
211
|
+
* from CLI string to native type, and writes config.json.
|
|
212
|
+
*
|
|
213
|
+
* @param args - args[0]=key, args[1]=value
|
|
214
|
+
* @param projectDir - Project root directory
|
|
215
|
+
* @returns QueryResult matching gsd-tools `config-set` JSON: `{ updated, key, value, previousValue }`
|
|
216
|
+
* @throws GSDError with Validation if key is invalid or args missing
|
|
217
|
+
*/
|
|
218
|
+
export const configSet: QueryHandler = async (args, projectDir) => {
|
|
219
|
+
const keyPath = args[0];
|
|
220
|
+
const rawValue = args[1];
|
|
221
|
+
if (!keyPath) {
|
|
222
|
+
throw new GSDError('Usage: config-set <key.path> <value>', ErrorClassification.Validation);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const validation = isValidConfigKey(keyPath);
|
|
226
|
+
if (!validation.valid) {
|
|
227
|
+
const suggestion = validation.suggestion ? `. Did you mean: ${validation.suggestion}?` : '';
|
|
228
|
+
throw new GSDError(
|
|
229
|
+
`Unknown config key: "${keyPath}"${suggestion}`,
|
|
230
|
+
ErrorClassification.Validation,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const parsedValue = rawValue !== undefined ? parseConfigValue(rawValue) : rawValue;
|
|
235
|
+
|
|
236
|
+
// D8: Context value validation (match CJS config.cjs:357-359)
|
|
237
|
+
const VALID_CONTEXT_VALUES = ['dev', 'research', 'review'];
|
|
238
|
+
if (keyPath === 'context' && !VALID_CONTEXT_VALUES.includes(String(parsedValue))) {
|
|
239
|
+
throw new GSDError(
|
|
240
|
+
`Invalid context value '${rawValue}'. Valid values: ${VALID_CONTEXT_VALUES.join(', ')}`,
|
|
241
|
+
ErrorClassification.Validation,
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// D6: Lock protection for read-modify-write (match CJS config.cjs:296)
|
|
246
|
+
const paths = planningPaths(projectDir);
|
|
247
|
+
const lockPath = await acquireStateLock(paths.config);
|
|
248
|
+
let previousValue: unknown;
|
|
249
|
+
try {
|
|
250
|
+
let config: Record<string, unknown> = {};
|
|
251
|
+
try {
|
|
252
|
+
const raw = await readFile(paths.config, 'utf-8');
|
|
253
|
+
config = JSON.parse(raw) as Record<string, unknown>;
|
|
254
|
+
} catch {
|
|
255
|
+
// Start with empty config if file doesn't exist or is malformed
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
previousValue = getValueAtPath(config, keyPath);
|
|
259
|
+
setConfigValue(config, keyPath, parsedValue);
|
|
260
|
+
await atomicWriteConfig(paths.config, config);
|
|
261
|
+
} finally {
|
|
262
|
+
await releaseStateLock(lockPath);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Match CJS JSON: `JSON.stringify` omits keys whose value is `undefined`
|
|
266
|
+
const data: Record<string, unknown> = {
|
|
267
|
+
updated: true,
|
|
268
|
+
key: keyPath,
|
|
269
|
+
value: parsedValue,
|
|
270
|
+
};
|
|
271
|
+
if (previousValue !== undefined) {
|
|
272
|
+
data.previousValue = previousValue;
|
|
273
|
+
}
|
|
274
|
+
return { data };
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// ─── configSetModelProfile ────────────────────────────────────────────────
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Validate and set the model profile in config.json.
|
|
281
|
+
*
|
|
282
|
+
* @param args - args[0]=profileName
|
|
283
|
+
* @param projectDir - Project root directory
|
|
284
|
+
* @returns QueryResult with { set: true, profile, agents }
|
|
285
|
+
* @throws GSDError with Validation if profile is invalid
|
|
286
|
+
*/
|
|
287
|
+
export const configSetModelProfile: QueryHandler = async (args, projectDir) => {
|
|
288
|
+
const profileName = args[0];
|
|
289
|
+
if (!profileName) {
|
|
290
|
+
throw new GSDError(
|
|
291
|
+
`Usage: config-set-model-profile <${VALID_PROFILES.join('|')}>`,
|
|
292
|
+
ErrorClassification.Validation,
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const normalized = profileName.toLowerCase().trim();
|
|
297
|
+
if (!VALID_PROFILES.includes(normalized)) {
|
|
298
|
+
throw new GSDError(
|
|
299
|
+
`Invalid profile '${profileName}'. Valid profiles: ${VALID_PROFILES.join(', ')}`,
|
|
300
|
+
ErrorClassification.Validation,
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// D6: Lock protection for read-modify-write
|
|
305
|
+
const paths = planningPaths(projectDir);
|
|
306
|
+
const lockPath = await acquireStateLock(paths.config);
|
|
307
|
+
let previousProfile = 'balanced';
|
|
308
|
+
try {
|
|
309
|
+
let config: Record<string, unknown> = {};
|
|
310
|
+
try {
|
|
311
|
+
const raw = await readFile(paths.config, 'utf-8');
|
|
312
|
+
config = JSON.parse(raw) as Record<string, unknown>;
|
|
313
|
+
} catch {
|
|
314
|
+
// Start with empty config
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const prev =
|
|
318
|
+
typeof config.model_profile === 'string' ? config.model_profile.toLowerCase().trim() : '';
|
|
319
|
+
previousProfile = VALID_PROFILES.includes(prev) ? prev : 'balanced';
|
|
320
|
+
config.model_profile = normalized;
|
|
321
|
+
await atomicWriteConfig(paths.config, config);
|
|
322
|
+
} finally {
|
|
323
|
+
await releaseStateLock(lockPath);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const agentToModelMap = getAgentToModelMapForProfile(normalized);
|
|
327
|
+
return {
|
|
328
|
+
data: {
|
|
329
|
+
updated: true,
|
|
330
|
+
profile: normalized,
|
|
331
|
+
previousProfile,
|
|
332
|
+
agentToModelMap,
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
// ─── configNewProject ─────────────────────────────────────────────────────
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Create config.json with defaults and optional user choices.
|
|
341
|
+
*
|
|
342
|
+
* Idempotent: if config.json already exists, returns { created: false }.
|
|
343
|
+
* Detects API key availability from environment variables.
|
|
344
|
+
*
|
|
345
|
+
* @param args - args[0]=optional JSON string of user choices
|
|
346
|
+
* @param projectDir - Project root directory
|
|
347
|
+
* @returns QueryResult with { created: true, path } or { created: false, reason }
|
|
348
|
+
*/
|
|
349
|
+
export const configNewProject: QueryHandler = async (args, projectDir) => {
|
|
350
|
+
const paths = planningPaths(projectDir);
|
|
351
|
+
|
|
352
|
+
// Idempotent: don't overwrite existing config
|
|
353
|
+
if (existsSync(paths.config)) {
|
|
354
|
+
return { data: { created: false, reason: 'already_exists' } };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Parse user choices
|
|
358
|
+
let userChoices: Record<string, unknown> = {};
|
|
359
|
+
if (args[0] && args[0].trim() !== '') {
|
|
360
|
+
try {
|
|
361
|
+
userChoices = JSON.parse(args[0]) as Record<string, unknown>;
|
|
362
|
+
} catch (err: unknown) {
|
|
363
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
364
|
+
throw new GSDError(`Invalid JSON for config-new-project: ${msg}`, ErrorClassification.Validation);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Ensure .planning directory exists
|
|
369
|
+
const planningDir = paths.planning;
|
|
370
|
+
if (!existsSync(planningDir)) {
|
|
371
|
+
await mkdir(planningDir, { recursive: true });
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// D11: Load global defaults from ~/.gsd/defaults.json if present
|
|
375
|
+
const homeDir = homedir();
|
|
376
|
+
let globalDefaults: Record<string, unknown> = {};
|
|
377
|
+
try {
|
|
378
|
+
const defaultsPath = join(homeDir, '.gsd', 'defaults.json');
|
|
379
|
+
const defaultsRaw = await readFile(defaultsPath, 'utf-8');
|
|
380
|
+
globalDefaults = JSON.parse(defaultsRaw) as Record<string, unknown>;
|
|
381
|
+
} catch {
|
|
382
|
+
// No global defaults — continue with hardcoded defaults only
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Detect API key availability (boolean only, never store keys)
|
|
386
|
+
const hasBraveSearch = !!(process.env.BRAVE_API_KEY || existsSync(join(homeDir, '.gsd', 'brave_api_key')));
|
|
387
|
+
const hasFirecrawl = !!(process.env.FIRECRAWL_API_KEY || existsSync(join(homeDir, '.gsd', 'firecrawl_api_key')));
|
|
388
|
+
const hasExaSearch = !!(process.env.EXA_API_KEY || existsSync(join(homeDir, '.gsd', 'exa_api_key')));
|
|
389
|
+
|
|
390
|
+
// Build default config
|
|
391
|
+
const defaults: Record<string, unknown> = {
|
|
392
|
+
model_profile: 'balanced',
|
|
393
|
+
commit_docs: false,
|
|
394
|
+
parallelization: 1,
|
|
395
|
+
search_gitignored: false,
|
|
396
|
+
brave_search: hasBraveSearch,
|
|
397
|
+
firecrawl: hasFirecrawl,
|
|
398
|
+
exa_search: hasExaSearch,
|
|
399
|
+
git: {
|
|
400
|
+
branching_strategy: 'none',
|
|
401
|
+
phase_branch_template: 'gsd/phase-{phase}-{slug}',
|
|
402
|
+
milestone_branch_template: 'gsd/{milestone}-{slug}',
|
|
403
|
+
quick_branch_template: null,
|
|
404
|
+
},
|
|
405
|
+
workflow: {
|
|
406
|
+
research: true,
|
|
407
|
+
plan_check: true,
|
|
408
|
+
verifier: true,
|
|
409
|
+
nyquist_validation: true,
|
|
410
|
+
auto_advance: false,
|
|
411
|
+
node_repair: true,
|
|
412
|
+
node_repair_budget: 2,
|
|
413
|
+
ui_phase: true,
|
|
414
|
+
ui_safety_gate: true,
|
|
415
|
+
text_mode: false,
|
|
416
|
+
research_before_questions: false,
|
|
417
|
+
discuss_mode: 'discuss',
|
|
418
|
+
skip_discuss: false,
|
|
419
|
+
code_review: true,
|
|
420
|
+
code_review_depth: 'standard',
|
|
421
|
+
},
|
|
422
|
+
hooks: {
|
|
423
|
+
context_warnings: true,
|
|
424
|
+
},
|
|
425
|
+
project_code: null,
|
|
426
|
+
phase_naming: 'sequential',
|
|
427
|
+
agent_skills: {},
|
|
428
|
+
features: {},
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
// Deep merge: hardcoded <- globalDefaults <- userChoices (D11)
|
|
432
|
+
const config: Record<string, unknown> = {
|
|
433
|
+
...defaults,
|
|
434
|
+
...globalDefaults,
|
|
435
|
+
...userChoices,
|
|
436
|
+
git: {
|
|
437
|
+
...(defaults.git as Record<string, unknown>),
|
|
438
|
+
...((userChoices.git as Record<string, unknown>) || {}),
|
|
439
|
+
},
|
|
440
|
+
workflow: {
|
|
441
|
+
...(defaults.workflow as Record<string, unknown>),
|
|
442
|
+
...((userChoices.workflow as Record<string, unknown>) || {}),
|
|
443
|
+
},
|
|
444
|
+
hooks: {
|
|
445
|
+
...(defaults.hooks as Record<string, unknown>),
|
|
446
|
+
...((userChoices.hooks as Record<string, unknown>) || {}),
|
|
447
|
+
},
|
|
448
|
+
agent_skills: {
|
|
449
|
+
...((defaults.agent_skills as Record<string, unknown>) || {}),
|
|
450
|
+
...((userChoices.agent_skills as Record<string, unknown>) || {}),
|
|
451
|
+
},
|
|
452
|
+
features: {
|
|
453
|
+
...((defaults.features as Record<string, unknown>) || {}),
|
|
454
|
+
...((userChoices.features as Record<string, unknown>) || {}),
|
|
455
|
+
},
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
await atomicWriteConfig(paths.config, config);
|
|
459
|
+
|
|
460
|
+
return { data: { created: true, path: paths.config } };
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
// ─── configEnsureSection ──────────────────────────────────────────────────
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Idempotently ensure a top-level section exists in config.json.
|
|
467
|
+
*
|
|
468
|
+
* If the section key doesn't exist, creates it as an empty object.
|
|
469
|
+
* If it already exists, preserves its contents.
|
|
470
|
+
*
|
|
471
|
+
* @param args - args[0]=sectionName
|
|
472
|
+
* @param projectDir - Project root directory
|
|
473
|
+
* @returns QueryResult with { ensured: true, section }
|
|
474
|
+
*/
|
|
475
|
+
export const configEnsureSection: QueryHandler = async (args, projectDir) => {
|
|
476
|
+
const sectionName = args[0];
|
|
477
|
+
if (!sectionName) {
|
|
478
|
+
throw new GSDError('Usage: config-ensure-section <section>', ErrorClassification.Validation);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const paths = planningPaths(projectDir);
|
|
482
|
+
let config: Record<string, unknown> = {};
|
|
483
|
+
try {
|
|
484
|
+
const raw = await readFile(paths.config, 'utf-8');
|
|
485
|
+
config = JSON.parse(raw) as Record<string, unknown>;
|
|
486
|
+
} catch {
|
|
487
|
+
// Start with empty config
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (!(sectionName in config)) {
|
|
491
|
+
config[sectionName] = {};
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
await atomicWriteConfig(paths.config, config);
|
|
495
|
+
|
|
496
|
+
return { data: { ensured: true, section: sectionName } };
|
|
497
|
+
};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for config-get and resolve-model query handlers.
|
|
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 { GSDError } from '../errors.js';
|
|
10
|
+
|
|
11
|
+
// ─── Test setup ─────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
let tmpDir: string;
|
|
14
|
+
|
|
15
|
+
beforeEach(async () => {
|
|
16
|
+
tmpDir = await mkdtemp(join(tmpdir(), 'gsd-cfg-'));
|
|
17
|
+
await mkdir(join(tmpDir, '.planning'), { recursive: true });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterEach(async () => {
|
|
21
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// ─── configGet ──────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
describe('configGet', () => {
|
|
27
|
+
it('returns raw config value for top-level key', async () => {
|
|
28
|
+
const { configGet } = await import('./config-query.js');
|
|
29
|
+
await writeFile(
|
|
30
|
+
join(tmpDir, '.planning', 'config.json'),
|
|
31
|
+
JSON.stringify({ model_profile: 'quality' }),
|
|
32
|
+
);
|
|
33
|
+
const result = await configGet(['model_profile'], tmpDir);
|
|
34
|
+
expect(result.data).toBe('quality');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('traverses dot-notation for nested keys', async () => {
|
|
38
|
+
const { configGet } = await import('./config-query.js');
|
|
39
|
+
await writeFile(
|
|
40
|
+
join(tmpDir, '.planning', 'config.json'),
|
|
41
|
+
JSON.stringify({ workflow: { auto_advance: true } }),
|
|
42
|
+
);
|
|
43
|
+
const result = await configGet(['workflow.auto_advance'], tmpDir);
|
|
44
|
+
expect(result.data).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('throws GSDError when no key provided', async () => {
|
|
48
|
+
const { configGet } = await import('./config-query.js');
|
|
49
|
+
await expect(configGet([], tmpDir)).rejects.toThrow(GSDError);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('throws GSDError for nonexistent key', async () => {
|
|
53
|
+
const { configGet } = await import('./config-query.js');
|
|
54
|
+
await writeFile(
|
|
55
|
+
join(tmpDir, '.planning', 'config.json'),
|
|
56
|
+
JSON.stringify({ model_profile: 'quality' }),
|
|
57
|
+
);
|
|
58
|
+
await expect(configGet(['nonexistent.key'], tmpDir)).rejects.toThrow(GSDError);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('reads raw config without merging defaults', async () => {
|
|
62
|
+
const { configGet } = await import('./config-query.js');
|
|
63
|
+
// Write config with only model_profile -- no workflow section
|
|
64
|
+
await writeFile(
|
|
65
|
+
join(tmpDir, '.planning', 'config.json'),
|
|
66
|
+
JSON.stringify({ model_profile: 'balanced' }),
|
|
67
|
+
);
|
|
68
|
+
// Accessing workflow should fail (not merged with defaults)
|
|
69
|
+
await expect(configGet(['workflow.auto_advance'], tmpDir)).rejects.toThrow(GSDError);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// ─── resolveModel ───────────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
describe('resolveModel', () => {
|
|
76
|
+
it('returns model and profile for known agent', async () => {
|
|
77
|
+
const { resolveModel } = await import('./config-query.js');
|
|
78
|
+
await writeFile(
|
|
79
|
+
join(tmpDir, '.planning', 'config.json'),
|
|
80
|
+
JSON.stringify({ model_profile: 'balanced' }),
|
|
81
|
+
);
|
|
82
|
+
const result = await resolveModel(['gsd-planner'], tmpDir);
|
|
83
|
+
const data = result.data as Record<string, unknown>;
|
|
84
|
+
expect(data).toHaveProperty('model');
|
|
85
|
+
expect(data).toHaveProperty('profile', 'balanced');
|
|
86
|
+
expect(data).not.toHaveProperty('unknown_agent');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('returns unknown_agent flag for unknown agent', async () => {
|
|
90
|
+
const { resolveModel } = await import('./config-query.js');
|
|
91
|
+
await writeFile(
|
|
92
|
+
join(tmpDir, '.planning', 'config.json'),
|
|
93
|
+
JSON.stringify({ model_profile: 'balanced' }),
|
|
94
|
+
);
|
|
95
|
+
const result = await resolveModel(['unknown-agent'], tmpDir);
|
|
96
|
+
const data = result.data as Record<string, unknown>;
|
|
97
|
+
expect(data).toHaveProperty('model', 'sonnet');
|
|
98
|
+
expect(data).toHaveProperty('unknown_agent', true);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('throws GSDError when no agent type provided', async () => {
|
|
102
|
+
const { resolveModel } = await import('./config-query.js');
|
|
103
|
+
await expect(resolveModel([], tmpDir)).rejects.toThrow(GSDError);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('respects model_overrides from config', async () => {
|
|
107
|
+
const { resolveModel } = await import('./config-query.js');
|
|
108
|
+
await writeFile(
|
|
109
|
+
join(tmpDir, '.planning', 'config.json'),
|
|
110
|
+
JSON.stringify({
|
|
111
|
+
model_profile: 'balanced',
|
|
112
|
+
model_overrides: { 'gsd-planner': 'openai/gpt-5.4' },
|
|
113
|
+
}),
|
|
114
|
+
);
|
|
115
|
+
const result = await resolveModel(['gsd-planner'], tmpDir);
|
|
116
|
+
const data = result.data as Record<string, unknown>;
|
|
117
|
+
expect(data).toHaveProperty('model', 'openai/gpt-5.4');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('returns empty model when resolve_model_ids is omit', async () => {
|
|
121
|
+
const { resolveModel } = await import('./config-query.js');
|
|
122
|
+
await writeFile(
|
|
123
|
+
join(tmpDir, '.planning', 'config.json'),
|
|
124
|
+
JSON.stringify({
|
|
125
|
+
model_profile: 'balanced',
|
|
126
|
+
resolve_model_ids: 'omit',
|
|
127
|
+
}),
|
|
128
|
+
);
|
|
129
|
+
const result = await resolveModel(['gsd-planner'], tmpDir);
|
|
130
|
+
const data = result.data as Record<string, unknown>;
|
|
131
|
+
expect(data).toHaveProperty('model', '');
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// ─── MODEL_PROFILES ─────────────────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
describe('MODEL_PROFILES', () => {
|
|
138
|
+
it('contains all 18 agent entries (sync with model-profiles.cjs)', async () => {
|
|
139
|
+
const { MODEL_PROFILES } = await import('./config-query.js');
|
|
140
|
+
expect(Object.keys(MODEL_PROFILES)).toHaveLength(18);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('has quality/balanced/budget/adaptive for each agent', async () => {
|
|
144
|
+
const { MODEL_PROFILES } = await import('./config-query.js');
|
|
145
|
+
for (const agent of Object.keys(MODEL_PROFILES)) {
|
|
146
|
+
expect(MODEL_PROFILES[agent]).toHaveProperty('quality');
|
|
147
|
+
expect(MODEL_PROFILES[agent]).toHaveProperty('balanced');
|
|
148
|
+
expect(MODEL_PROFILES[agent]).toHaveProperty('budget');
|
|
149
|
+
expect(MODEL_PROFILES[agent]).toHaveProperty('adaptive');
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// ─── VALID_PROFILES ─────────────────────────────────────────────────────────
|
|
155
|
+
|
|
156
|
+
describe('VALID_PROFILES', () => {
|
|
157
|
+
it('contains the four profile names', async () => {
|
|
158
|
+
const { VALID_PROFILES } = await import('./config-query.js');
|
|
159
|
+
expect(VALID_PROFILES).toEqual(['quality', 'balanced', 'budget', 'adaptive']);
|
|
160
|
+
});
|
|
161
|
+
});
|