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,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for `check.ship-ready` (decision-routing audit §3.9).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
6
|
+
import { mkdir, writeFile, rm } from 'node:fs/promises';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { tmpdir } from 'node:os';
|
|
9
|
+
import { checkShipReady } from './check-ship-ready.js';
|
|
10
|
+
|
|
11
|
+
describe('checkShipReady', () => {
|
|
12
|
+
let projectDir: string;
|
|
13
|
+
|
|
14
|
+
beforeEach(async () => {
|
|
15
|
+
projectDir = join(tmpdir(), `gsd-check-ship-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
16
|
+
await mkdir(join(projectDir, '.planning', 'phases'), { recursive: true });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(async () => {
|
|
20
|
+
await rm(projectDir, { recursive: true, force: true });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('throws when phase arg is missing', async () => {
|
|
24
|
+
await expect(checkShipReady([], projectDir)).rejects.toThrow();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('returns all expected shape keys', async () => {
|
|
28
|
+
await mkdir(join(projectDir, '.planning', 'phases', '01-foundation'), { recursive: true });
|
|
29
|
+
|
|
30
|
+
const { data } = await checkShipReady(['1'], projectDir);
|
|
31
|
+
const d = data as Record<string, unknown>;
|
|
32
|
+
|
|
33
|
+
expect(typeof d.ready).toBe('boolean');
|
|
34
|
+
expect(typeof d.verification_passed).toBe('boolean');
|
|
35
|
+
expect(typeof d.clean_tree).toBe('boolean');
|
|
36
|
+
expect(typeof d.on_feature_branch).toBe('boolean');
|
|
37
|
+
expect(typeof d.remote_configured).toBe('boolean');
|
|
38
|
+
expect(typeof d.gh_available).toBe('boolean');
|
|
39
|
+
expect(typeof d.gh_authenticated).toBe('boolean');
|
|
40
|
+
expect(Array.isArray(d.blockers)).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('returns current_branch and base_branch fields', async () => {
|
|
44
|
+
await mkdir(join(projectDir, '.planning', 'phases', '01-foundation'), { recursive: true });
|
|
45
|
+
|
|
46
|
+
const { data } = await checkShipReady(['1'], projectDir);
|
|
47
|
+
const d = data as Record<string, unknown>;
|
|
48
|
+
|
|
49
|
+
// current_branch is either a string (when in a git repo) or null (temp dir not a repo)
|
|
50
|
+
expect(d.current_branch === null || typeof d.current_branch === 'string').toBe(true);
|
|
51
|
+
expect(d.base_branch === null || typeof d.base_branch === 'string').toBe(true);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('never throws — returns false fields on git errors', async () => {
|
|
55
|
+
// Use a directory that is not a git repo
|
|
56
|
+
const nonGitDir = join(tmpdir(), `gsd-non-git-${Date.now()}`);
|
|
57
|
+
await mkdir(join(nonGitDir, '.planning', 'phases', '01-test'), { recursive: true });
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const { data } = await checkShipReady(['1'], nonGitDir);
|
|
61
|
+
const d = data as Record<string, unknown>;
|
|
62
|
+
// All git-based fields should be false/null when not a git repo
|
|
63
|
+
expect(d.ready).toBe(false);
|
|
64
|
+
} finally {
|
|
65
|
+
await rm(nonGitDir, { recursive: true, force: true });
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('gh_authenticated is always false (advisory — no network call)', async () => {
|
|
70
|
+
await mkdir(join(projectDir, '.planning', 'phases', '01-foundation'), { recursive: true });
|
|
71
|
+
|
|
72
|
+
const { data } = await checkShipReady(['1'], projectDir);
|
|
73
|
+
const d = data as Record<string, unknown>;
|
|
74
|
+
// Per spec: gh_authenticated is advisory — skip actual auth check to avoid slow network call
|
|
75
|
+
expect(d.gh_authenticated).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ship preflight checks (`check.ship-ready`).
|
|
3
|
+
*
|
|
4
|
+
* Consolidates git/gh checks from `ship.md` into a single structured query.
|
|
5
|
+
* All subprocess calls are wrapped in try/catch — never throws on git/gh failures.
|
|
6
|
+
* See `.planning/research/decision-routing-audit.md` §3.9.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { execSync } from 'node:child_process';
|
|
10
|
+
import { GSDError, ErrorClassification } from '../errors.js';
|
|
11
|
+
import { normalizePhaseName } from './helpers.js';
|
|
12
|
+
import { checkVerificationStatus } from './check-verification-status.js';
|
|
13
|
+
import type { QueryHandler } from './utils.js';
|
|
14
|
+
|
|
15
|
+
function runSyncSafe(cmd: string, cwd: string): string | null {
|
|
16
|
+
try {
|
|
17
|
+
return execSync(cmd, { cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function boolSyncSafe(cmd: string, cwd: string): boolean {
|
|
24
|
+
return runSyncSafe(cmd, cwd) !== null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const checkShipReady: QueryHandler = async (args, projectDir) => {
|
|
28
|
+
const raw = args[0];
|
|
29
|
+
if (!raw) {
|
|
30
|
+
throw new GSDError('phase number required for check ship-ready', ErrorClassification.Validation);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
normalizePhaseName(raw); // validate format
|
|
34
|
+
|
|
35
|
+
const blockers: string[] = [];
|
|
36
|
+
|
|
37
|
+
// git checks — all wrapped in try/catch via helpers
|
|
38
|
+
const porcelain = runSyncSafe('git status --porcelain', projectDir);
|
|
39
|
+
const clean_tree = porcelain !== null && porcelain === '';
|
|
40
|
+
|
|
41
|
+
const current_branch = runSyncSafe('git rev-parse --abbrev-ref HEAD', projectDir);
|
|
42
|
+
const on_feature_branch =
|
|
43
|
+
current_branch !== null &&
|
|
44
|
+
current_branch !== 'main' &&
|
|
45
|
+
current_branch !== 'master';
|
|
46
|
+
|
|
47
|
+
// Determine base branch
|
|
48
|
+
let base_branch: string | null = null;
|
|
49
|
+
if (current_branch) {
|
|
50
|
+
const mergeRef = runSyncSafe(`git config --get branch.${current_branch}.merge`, projectDir);
|
|
51
|
+
if (mergeRef) {
|
|
52
|
+
base_branch = mergeRef.replace('refs/heads/', '');
|
|
53
|
+
} else {
|
|
54
|
+
// Fallback: check if 'main' branch exists, else 'master'
|
|
55
|
+
const mainExists = boolSyncSafe('git rev-parse --verify main', projectDir);
|
|
56
|
+
base_branch = mainExists ? 'main' : 'master';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const remoteOut = runSyncSafe('git remote', projectDir);
|
|
61
|
+
const remote_configured = remoteOut !== null && remoteOut.trim().length > 0;
|
|
62
|
+
|
|
63
|
+
// gh availability
|
|
64
|
+
const gh_available =
|
|
65
|
+
boolSyncSafe('gh --version', projectDir) ||
|
|
66
|
+
boolSyncSafe('which gh', projectDir);
|
|
67
|
+
|
|
68
|
+
// gh_authenticated: advisory — skip actual auth check to avoid slow network call
|
|
69
|
+
const gh_authenticated = false;
|
|
70
|
+
|
|
71
|
+
// Verification status
|
|
72
|
+
let verification_passed = false;
|
|
73
|
+
try {
|
|
74
|
+
const verRes = await checkVerificationStatus([raw], projectDir);
|
|
75
|
+
const vdata = verRes.data as Record<string, unknown>;
|
|
76
|
+
verification_passed = vdata.status !== 'fail';
|
|
77
|
+
} catch {
|
|
78
|
+
verification_passed = false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Collect blockers
|
|
82
|
+
if (!verification_passed) blockers.push('verification status is fail or missing');
|
|
83
|
+
if (!clean_tree) blockers.push('working tree is not clean (uncommitted changes)');
|
|
84
|
+
if (!on_feature_branch) blockers.push('not on a feature branch (currently on main/master or unknown)');
|
|
85
|
+
if (!remote_configured) blockers.push('no git remote configured');
|
|
86
|
+
|
|
87
|
+
const ready = verification_passed && clean_tree && on_feature_branch && remote_configured;
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
data: {
|
|
91
|
+
ready,
|
|
92
|
+
verification_passed,
|
|
93
|
+
clean_tree,
|
|
94
|
+
on_feature_branch,
|
|
95
|
+
current_branch,
|
|
96
|
+
base_branch,
|
|
97
|
+
remote_configured,
|
|
98
|
+
gh_available,
|
|
99
|
+
gh_authenticated,
|
|
100
|
+
blockers,
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for `check.verification-status` (decision-routing audit §3.8).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
6
|
+
import { mkdir, writeFile, rm } from 'node:fs/promises';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { tmpdir } from 'node:os';
|
|
9
|
+
import { checkVerificationStatus } from './check-verification-status.js';
|
|
10
|
+
|
|
11
|
+
describe('checkVerificationStatus', () => {
|
|
12
|
+
let projectDir: string;
|
|
13
|
+
|
|
14
|
+
beforeEach(async () => {
|
|
15
|
+
projectDir = join(tmpdir(), `gsd-check-ver-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
16
|
+
await mkdir(join(projectDir, '.planning', 'phases'), { recursive: true });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(async () => {
|
|
20
|
+
await rm(projectDir, { recursive: true, force: true });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('throws when phase arg is missing', async () => {
|
|
24
|
+
await expect(checkVerificationStatus([], projectDir)).rejects.toThrow();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('returns status missing when VERIFICATION.md does not exist', async () => {
|
|
28
|
+
await mkdir(join(projectDir, '.planning', 'phases', '01-foundation'), { recursive: true });
|
|
29
|
+
|
|
30
|
+
const { data } = await checkVerificationStatus(['1'], projectDir);
|
|
31
|
+
const d = data as Record<string, unknown>;
|
|
32
|
+
expect(d.status).toBe('missing');
|
|
33
|
+
expect(d.score).toBeNull();
|
|
34
|
+
expect(d.gaps).toEqual([]);
|
|
35
|
+
expect(d.human_items).toEqual([]);
|
|
36
|
+
expect(d.deferred).toEqual([]);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('returns status pass when all rows are PASS', async () => {
|
|
40
|
+
const phaseDir = join(projectDir, '.planning', 'phases', '02-core');
|
|
41
|
+
await mkdir(phaseDir, { recursive: true });
|
|
42
|
+
await writeFile(
|
|
43
|
+
join(phaseDir, 'VERIFICATION.md'),
|
|
44
|
+
[
|
|
45
|
+
'---',
|
|
46
|
+
'status: passed',
|
|
47
|
+
'---',
|
|
48
|
+
'',
|
|
49
|
+
'| ID | Description | Status | Notes |',
|
|
50
|
+
'|---|---|---|---|',
|
|
51
|
+
'| T-01 | Auth works | PASS | |',
|
|
52
|
+
'| T-02 | User model | PASS | |',
|
|
53
|
+
'| T-03 | API endpoint | PASS | |',
|
|
54
|
+
].join('\n'),
|
|
55
|
+
'utf-8',
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const { data } = await checkVerificationStatus(['2'], projectDir);
|
|
59
|
+
const d = data as Record<string, unknown>;
|
|
60
|
+
expect(d.status).toBe('pass');
|
|
61
|
+
expect(d.gaps).toEqual([]);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('returns status fail with gaps when FAIL rows present', async () => {
|
|
65
|
+
const phaseDir = join(projectDir, '.planning', 'phases', '03-api');
|
|
66
|
+
await mkdir(phaseDir, { recursive: true });
|
|
67
|
+
await writeFile(
|
|
68
|
+
join(phaseDir, 'VERIFICATION.md'),
|
|
69
|
+
[
|
|
70
|
+
'| ID | Description | Status | Notes |',
|
|
71
|
+
'|---|---|---|---|',
|
|
72
|
+
'| T-01 | Auth works | PASS | |',
|
|
73
|
+
'| T-02 | Error handling | FAIL | Missing 500 handler |',
|
|
74
|
+
'| T-03 | API endpoint | PASS | |',
|
|
75
|
+
].join('\n'),
|
|
76
|
+
'utf-8',
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const { data } = await checkVerificationStatus(['3'], projectDir);
|
|
80
|
+
const d = data as Record<string, unknown>;
|
|
81
|
+
expect(d.status).toBe('fail');
|
|
82
|
+
expect((d.gaps as string[]).length).toBeGreaterThan(0);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('returns score as fraction string', async () => {
|
|
86
|
+
const phaseDir = join(projectDir, '.planning', 'phases', '04-ui');
|
|
87
|
+
await mkdir(phaseDir, { recursive: true });
|
|
88
|
+
await writeFile(
|
|
89
|
+
join(phaseDir, 'VERIFICATION.md'),
|
|
90
|
+
[
|
|
91
|
+
'| ID | Description | Status | Notes |',
|
|
92
|
+
'|---|---|---|---|',
|
|
93
|
+
'| T-01 | Feature A | PASS | |',
|
|
94
|
+
'| T-02 | Feature B | PASS | |',
|
|
95
|
+
'| T-03 | Feature C | FAIL | |',
|
|
96
|
+
'| T-04 | Feature D | PASS | |',
|
|
97
|
+
].join('\n'),
|
|
98
|
+
'utf-8',
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const { data } = await checkVerificationStatus(['4'], projectDir);
|
|
102
|
+
const d = data as Record<string, unknown>;
|
|
103
|
+
expect(d.score).toBe('3/4');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('collects human_items when type column contains human', async () => {
|
|
107
|
+
const phaseDir = join(projectDir, '.planning', 'phases', '05-test');
|
|
108
|
+
await mkdir(phaseDir, { recursive: true });
|
|
109
|
+
await writeFile(
|
|
110
|
+
join(phaseDir, 'VERIFICATION.md'),
|
|
111
|
+
[
|
|
112
|
+
'| ID | Description | Type | Status | Notes |',
|
|
113
|
+
'|---|---|---|---|---|',
|
|
114
|
+
'| T-01 | API returns 200 | truth | PASS | |',
|
|
115
|
+
'| T-02 | UI looks correct | human | PASS | Manual check |',
|
|
116
|
+
].join('\n'),
|
|
117
|
+
'utf-8',
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const { data } = await checkVerificationStatus(['5'], projectDir);
|
|
121
|
+
const d = data as Record<string, unknown>;
|
|
122
|
+
expect((d.human_items as string[]).length).toBeGreaterThan(0);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('collects deferred items when notes column contains deferred', async () => {
|
|
126
|
+
const phaseDir = join(projectDir, '.planning', 'phases', '06-misc');
|
|
127
|
+
await mkdir(phaseDir, { recursive: true });
|
|
128
|
+
await writeFile(
|
|
129
|
+
join(phaseDir, 'VERIFICATION.md'),
|
|
130
|
+
[
|
|
131
|
+
'| ID | Description | Status | Notes |',
|
|
132
|
+
'|---|---|---|---|',
|
|
133
|
+
'| T-01 | Feature A | PASS | |',
|
|
134
|
+
'| T-02 | Perf test | PASS | deferred to phase 8 |',
|
|
135
|
+
].join('\n'),
|
|
136
|
+
'utf-8',
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const { data } = await checkVerificationStatus(['6'], projectDir);
|
|
140
|
+
const d = data as Record<string, unknown>;
|
|
141
|
+
expect((d.deferred as string[]).length).toBeGreaterThan(0);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VERIFICATION.md parser (`check.verification-status`).
|
|
3
|
+
*
|
|
4
|
+
* Replaces VERIFICATION.md grep/parse branches in `execute-phase.md`,
|
|
5
|
+
* `autonomous.md`, `progress.md` with a structured query.
|
|
6
|
+
* See `.planning/research/decision-routing-audit.md` §3.8.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { readFile } from 'node:fs/promises';
|
|
10
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { GSDError, ErrorClassification } from '../errors.js';
|
|
13
|
+
import { normalizePhaseName } from './helpers.js';
|
|
14
|
+
import { findPhase } from './phase.js';
|
|
15
|
+
import type { QueryHandler } from './utils.js';
|
|
16
|
+
|
|
17
|
+
const NOT_FOUND_RESULT = {
|
|
18
|
+
status: 'missing' as const,
|
|
19
|
+
score: null,
|
|
20
|
+
gaps: [] as string[],
|
|
21
|
+
human_items: [] as string[],
|
|
22
|
+
deferred: [] as string[],
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// ─── Markdown table parser ─────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
interface TableRow {
|
|
28
|
+
cells: string[];
|
|
29
|
+
raw: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function parseTableRows(content: string): TableRow[] {
|
|
33
|
+
return content
|
|
34
|
+
.split('\n')
|
|
35
|
+
.filter(line => {
|
|
36
|
+
const trimmed = line.trim();
|
|
37
|
+
return trimmed.startsWith('|') && trimmed.endsWith('|') && !/^\|[-: |]+\|$/.test(trimmed);
|
|
38
|
+
})
|
|
39
|
+
.map(line => ({
|
|
40
|
+
cells: line
|
|
41
|
+
.split('|')
|
|
42
|
+
.slice(1, -1)
|
|
43
|
+
.map(c => c.trim()),
|
|
44
|
+
raw: line.trim(),
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Find the column index that matches a header predicate, falling back to -1.
|
|
50
|
+
*/
|
|
51
|
+
function findColIndex(headerRow: TableRow, predicate: (cell: string) => boolean): number {
|
|
52
|
+
return headerRow.cells.findIndex(c => predicate(c));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const checkVerificationStatus: QueryHandler = async (args, projectDir) => {
|
|
56
|
+
const raw = args[0];
|
|
57
|
+
if (!raw) {
|
|
58
|
+
throw new GSDError('phase number required for check verification-status', ErrorClassification.Validation);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
normalizePhaseName(raw); // validate format
|
|
62
|
+
|
|
63
|
+
const phaseRes = await findPhase([raw], projectDir);
|
|
64
|
+
const pdata = phaseRes.data as Record<string, unknown>;
|
|
65
|
+
|
|
66
|
+
if (!pdata.found || !pdata.directory) {
|
|
67
|
+
return { data: NOT_FOUND_RESULT };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const phaseDirFull = join(projectDir, pdata.directory as string);
|
|
71
|
+
|
|
72
|
+
// Locate VERIFICATION.md — may be prefixed
|
|
73
|
+
let verPath: string | null = null;
|
|
74
|
+
if (existsSync(phaseDirFull)) {
|
|
75
|
+
try {
|
|
76
|
+
const files = readdirSync(phaseDirFull) as string[];
|
|
77
|
+
const verFile = files.find(f => f.endsWith('-VERIFICATION.md') || f === 'VERIFICATION.md');
|
|
78
|
+
if (verFile) verPath = join(phaseDirFull, verFile);
|
|
79
|
+
} catch {
|
|
80
|
+
return { data: NOT_FOUND_RESULT };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!verPath) return { data: NOT_FOUND_RESULT };
|
|
85
|
+
|
|
86
|
+
let content: string;
|
|
87
|
+
try {
|
|
88
|
+
content = await readFile(verPath, 'utf-8');
|
|
89
|
+
} catch {
|
|
90
|
+
return { data: NOT_FOUND_RESULT };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const rows = parseTableRows(content);
|
|
94
|
+
if (rows.length === 0) {
|
|
95
|
+
// No table rows — check frontmatter status field only
|
|
96
|
+
const statusMatch = content.match(/^status:\s*(\S+)/im);
|
|
97
|
+
const status = statusMatch ? statusMatch[1].toLowerCase() : 'missing';
|
|
98
|
+
return { data: { ...NOT_FOUND_RESULT, status: status === 'missing' ? 'missing' : status } };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Detect header row — heuristic: first row typically has column names
|
|
102
|
+
const firstRow = rows[0];
|
|
103
|
+
const isHeader = firstRow.cells.some(c =>
|
|
104
|
+
/^(id|status|description|type|notes)$/i.test(c),
|
|
105
|
+
);
|
|
106
|
+
const dataRows = isHeader ? rows.slice(1) : rows;
|
|
107
|
+
const headerRow = isHeader ? firstRow : null;
|
|
108
|
+
|
|
109
|
+
// Determine column indices
|
|
110
|
+
let statusCol = headerRow ? findColIndex(headerRow, c => /^status$/i.test(c)) : -1;
|
|
111
|
+
let typeCol = headerRow ? findColIndex(headerRow, c => /^type$/i.test(c)) : -1;
|
|
112
|
+
let notesCol = headerRow ? findColIndex(headerRow, c => /^notes$/i.test(c)) : -1;
|
|
113
|
+
let descCol = headerRow ? findColIndex(headerRow, c => /^description$/i.test(c)) : -1;
|
|
114
|
+
|
|
115
|
+
// Fallbacks for tables without headers or unusual column orders
|
|
116
|
+
if (statusCol === -1) statusCol = 2; // typical: | ID | Description | Status |
|
|
117
|
+
if (descCol === -1) descCol = 1;
|
|
118
|
+
|
|
119
|
+
let passCount = 0;
|
|
120
|
+
let totalCount = 0;
|
|
121
|
+
const gaps: string[] = [];
|
|
122
|
+
const human_items: string[] = [];
|
|
123
|
+
const deferred: string[] = [];
|
|
124
|
+
|
|
125
|
+
for (const row of dataRows) {
|
|
126
|
+
const statusVal = (row.cells[statusCol] ?? '').toUpperCase();
|
|
127
|
+
const typeVal = typeCol >= 0 ? (row.cells[typeCol] ?? '').toLowerCase() : '';
|
|
128
|
+
const notesVal = notesCol >= 0 ? (row.cells[notesCol] ?? '').toLowerCase() : '';
|
|
129
|
+
const descVal = row.cells[descCol] ?? row.cells[0] ?? row.raw;
|
|
130
|
+
|
|
131
|
+
if (statusVal === 'PASS' || statusVal === 'FAIL') totalCount++;
|
|
132
|
+
if (statusVal === 'PASS') passCount++;
|
|
133
|
+
if (statusVal === 'FAIL') gaps.push(descVal);
|
|
134
|
+
if (typeVal.includes('human')) human_items.push(descVal);
|
|
135
|
+
if (notesVal.includes('deferred')) deferred.push(descVal);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const score = totalCount > 0 ? `${passCount}/${totalCount}` : null;
|
|
139
|
+
|
|
140
|
+
let status: string;
|
|
141
|
+
if (gaps.length > 0) {
|
|
142
|
+
status = 'fail';
|
|
143
|
+
} else if (passCount === totalCount && totalCount > 0) {
|
|
144
|
+
status = 'pass';
|
|
145
|
+
} else {
|
|
146
|
+
// Check frontmatter status as tiebreaker
|
|
147
|
+
const statusMatch = content.match(/^status:\s*(\S+)/im);
|
|
148
|
+
status = statusMatch ? statusMatch[1].toLowerCase() : 'partial';
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
data: {
|
|
153
|
+
status,
|
|
154
|
+
score,
|
|
155
|
+
gaps,
|
|
156
|
+
human_items,
|
|
157
|
+
deferred,
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for git commit and check-commit query handlers.
|
|
3
|
+
*
|
|
4
|
+
* Tests: execGit, sanitizeCommitMessage, commit, checkCommit.
|
|
5
|
+
* Uses real git repos in temp directories.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, beforeEach, afterEach } 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 { execSync } from 'node:child_process';
|
|
13
|
+
|
|
14
|
+
// ─── Test setup ─────────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
let tmpDir: string;
|
|
17
|
+
|
|
18
|
+
beforeEach(async () => {
|
|
19
|
+
tmpDir = await mkdtemp(join(tmpdir(), 'gsd-commit-'));
|
|
20
|
+
// Initialize a git repo
|
|
21
|
+
execSync('git init', { cwd: tmpDir, stdio: 'pipe' });
|
|
22
|
+
execSync('git config user.email "test@test.com"', { cwd: tmpDir, stdio: 'pipe' });
|
|
23
|
+
execSync('git config user.name "Test User"', { cwd: tmpDir, stdio: 'pipe' });
|
|
24
|
+
// Create .planning directory
|
|
25
|
+
await mkdir(join(tmpDir, '.planning'), { recursive: true });
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
afterEach(async () => {
|
|
29
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// ─── execGit ───────────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
describe('execGit', () => {
|
|
35
|
+
it('returns exitCode 0 for successful command', async () => {
|
|
36
|
+
const { execGit } = await import('./commit.js');
|
|
37
|
+
const result = execGit(tmpDir, ['status']);
|
|
38
|
+
expect(result.exitCode).toBe(0);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('returns non-zero exitCode for failed command', async () => {
|
|
42
|
+
const { execGit } = await import('./commit.js');
|
|
43
|
+
const result = execGit(tmpDir, ['log', '--oneline']);
|
|
44
|
+
// git log fails in empty repo with no commits
|
|
45
|
+
expect(result.exitCode).not.toBe(0);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('captures stdout from git command', async () => {
|
|
49
|
+
const { execGit } = await import('./commit.js');
|
|
50
|
+
const result = execGit(tmpDir, ['rev-parse', '--git-dir']);
|
|
51
|
+
expect(result.stdout).toBe('.git');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// ─── sanitizeCommitMessage ─────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
describe('sanitizeCommitMessage', () => {
|
|
58
|
+
it('strips null bytes and zero-width characters', async () => {
|
|
59
|
+
const { sanitizeCommitMessage } = await import('./commit.js');
|
|
60
|
+
const result = sanitizeCommitMessage('hello\u0000\u200Bworld');
|
|
61
|
+
expect(result).toBe('helloworld');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('neutralizes injection markers', async () => {
|
|
65
|
+
const { sanitizeCommitMessage } = await import('./commit.js');
|
|
66
|
+
const result = sanitizeCommitMessage('fix: update <system> prompt [SYSTEM] test');
|
|
67
|
+
expect(result).not.toContain('<system>');
|
|
68
|
+
expect(result).not.toContain('[SYSTEM]');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('preserves normal commit messages', async () => {
|
|
72
|
+
const { sanitizeCommitMessage } = await import('./commit.js');
|
|
73
|
+
const result = sanitizeCommitMessage('feat(auth): add login endpoint');
|
|
74
|
+
expect(result).toBe('feat(auth): add login endpoint');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('returns input unchanged for non-string', async () => {
|
|
78
|
+
const { sanitizeCommitMessage } = await import('./commit.js');
|
|
79
|
+
expect(sanitizeCommitMessage('')).toBe('');
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// ─── commit ────────────────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
describe('commit', () => {
|
|
86
|
+
it('returns committed:false when commit_docs is false and no --force', async () => {
|
|
87
|
+
const { commit } = await import('./commit.js');
|
|
88
|
+
await writeFile(
|
|
89
|
+
join(tmpDir, '.planning', 'config.json'),
|
|
90
|
+
JSON.stringify({ commit_docs: false }),
|
|
91
|
+
);
|
|
92
|
+
const result = await commit(['test commit message'], tmpDir);
|
|
93
|
+
expect((result.data as { committed: boolean }).committed).toBe(false);
|
|
94
|
+
expect((result.data as { reason: string }).reason).toContain('commit_docs');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('creates commit with --force even when commit_docs is false', async () => {
|
|
98
|
+
const { commit } = await import('./commit.js');
|
|
99
|
+
await writeFile(
|
|
100
|
+
join(tmpDir, '.planning', 'config.json'),
|
|
101
|
+
JSON.stringify({ commit_docs: false }),
|
|
102
|
+
);
|
|
103
|
+
await writeFile(join(tmpDir, '.planning', 'STATE.md'), '# State\n');
|
|
104
|
+
const result = await commit(['test commit', '--force'], tmpDir);
|
|
105
|
+
expect((result.data as { committed: boolean }).committed).toBe(true);
|
|
106
|
+
expect((result.data as { hash: string }).hash).toBeTruthy();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('stages files and creates commit with correct message', async () => {
|
|
110
|
+
const { commit } = await import('./commit.js');
|
|
111
|
+
await writeFile(
|
|
112
|
+
join(tmpDir, '.planning', 'config.json'),
|
|
113
|
+
JSON.stringify({ commit_docs: true }),
|
|
114
|
+
);
|
|
115
|
+
await writeFile(join(tmpDir, '.planning', 'STATE.md'), '# State\n');
|
|
116
|
+
const result = await commit(['docs: update state'], tmpDir);
|
|
117
|
+
expect((result.data as { committed: boolean }).committed).toBe(true);
|
|
118
|
+
expect((result.data as { hash: string }).hash).toBeTruthy();
|
|
119
|
+
|
|
120
|
+
// Verify commit message in git log
|
|
121
|
+
const log = execSync('git log -1 --format=%s', { cwd: tmpDir, encoding: 'utf-8' }).trim();
|
|
122
|
+
expect(log).toBe('docs: update state');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('returns nothing staged when no files match', async () => {
|
|
126
|
+
const { commit } = await import('./commit.js');
|
|
127
|
+
await writeFile(
|
|
128
|
+
join(tmpDir, '.planning', 'config.json'),
|
|
129
|
+
JSON.stringify({ commit_docs: true }),
|
|
130
|
+
);
|
|
131
|
+
// Stage config.json first then commit it so .planning/ has no unstaged changes
|
|
132
|
+
execSync('git add .planning/config.json', { cwd: tmpDir, stdio: 'pipe' });
|
|
133
|
+
execSync('git commit -m "init"', { cwd: tmpDir, stdio: 'pipe' });
|
|
134
|
+
// Now commit with specific nonexistent file (--files separates message from paths, matching CJS argv)
|
|
135
|
+
const result = await commit(['test msg', '--files', 'nonexistent-file.txt'], tmpDir);
|
|
136
|
+
expect((result.data as { committed: boolean }).committed).toBe(false);
|
|
137
|
+
expect((result.data as { reason: string }).reason).toContain('nonexistent-file.txt');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('commits specific files when provided', async () => {
|
|
141
|
+
const { commit } = await import('./commit.js');
|
|
142
|
+
await writeFile(
|
|
143
|
+
join(tmpDir, '.planning', 'config.json'),
|
|
144
|
+
JSON.stringify({ commit_docs: true }),
|
|
145
|
+
);
|
|
146
|
+
await writeFile(join(tmpDir, '.planning', 'STATE.md'), '# State\n');
|
|
147
|
+
await writeFile(join(tmpDir, '.planning', 'ROADMAP.md'), '# Roadmap\n');
|
|
148
|
+
const result = await commit(['docs: state only', '--files', '.planning/STATE.md'], tmpDir);
|
|
149
|
+
expect((result.data as { committed: boolean }).committed).toBe(true);
|
|
150
|
+
|
|
151
|
+
// Verify only STATE.md was committed
|
|
152
|
+
const files = execSync('git show --name-only --format=', { cwd: tmpDir, encoding: 'utf-8' }).trim();
|
|
153
|
+
expect(files).toContain('STATE.md');
|
|
154
|
+
expect(files).not.toContain('ROADMAP.md');
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// ─── checkCommit ───────────────────────────────────────────────────────────
|
|
159
|
+
|
|
160
|
+
describe('checkCommit', () => {
|
|
161
|
+
it('returns can_commit:true when commit_docs is enabled', async () => {
|
|
162
|
+
const { checkCommit } = await import('./commit.js');
|
|
163
|
+
await writeFile(
|
|
164
|
+
join(tmpDir, '.planning', 'config.json'),
|
|
165
|
+
JSON.stringify({ commit_docs: true }),
|
|
166
|
+
);
|
|
167
|
+
const result = await checkCommit([], tmpDir);
|
|
168
|
+
expect((result.data as { can_commit: boolean }).can_commit).toBe(true);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('returns can_commit:true when commit_docs is not set', async () => {
|
|
172
|
+
const { checkCommit } = await import('./commit.js');
|
|
173
|
+
await writeFile(
|
|
174
|
+
join(tmpDir, '.planning', 'config.json'),
|
|
175
|
+
JSON.stringify({}),
|
|
176
|
+
);
|
|
177
|
+
const result = await checkCommit([], tmpDir);
|
|
178
|
+
expect((result.data as { can_commit: boolean }).can_commit).toBe(true);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('returns can_commit:false when commit_docs is false and planning files staged', async () => {
|
|
182
|
+
const { checkCommit } = await import('./commit.js');
|
|
183
|
+
await writeFile(
|
|
184
|
+
join(tmpDir, '.planning', 'config.json'),
|
|
185
|
+
JSON.stringify({ commit_docs: false }),
|
|
186
|
+
);
|
|
187
|
+
await writeFile(join(tmpDir, '.planning', 'STATE.md'), '# State\n');
|
|
188
|
+
execSync('git add .planning/STATE.md', { cwd: tmpDir, stdio: 'pipe' });
|
|
189
|
+
const result = await checkCommit([], tmpDir);
|
|
190
|
+
expect((result.data as { can_commit: boolean }).can_commit).toBe(false);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('returns can_commit:true when commit_docs is false but no planning files staged', async () => {
|
|
194
|
+
const { checkCommit } = await import('./commit.js');
|
|
195
|
+
await writeFile(
|
|
196
|
+
join(tmpDir, '.planning', 'config.json'),
|
|
197
|
+
JSON.stringify({ commit_docs: false }),
|
|
198
|
+
);
|
|
199
|
+
const result = await checkCommit([], tmpDir);
|
|
200
|
+
expect((result.data as { can_commit: boolean }).can_commit).toBe(true);
|
|
201
|
+
});
|
|
202
|
+
});
|