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,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for frontmatter mutation handlers.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
6
|
+
import { mkdtemp, writeFile, readFile, rm } from 'node:fs/promises';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { tmpdir } from 'node:os';
|
|
9
|
+
import {
|
|
10
|
+
reconstructFrontmatter,
|
|
11
|
+
spliceFrontmatter,
|
|
12
|
+
frontmatterSet,
|
|
13
|
+
frontmatterMerge,
|
|
14
|
+
frontmatterValidate,
|
|
15
|
+
FRONTMATTER_SCHEMAS,
|
|
16
|
+
} from './frontmatter-mutation.js';
|
|
17
|
+
import { extractFrontmatter } from './frontmatter.js';
|
|
18
|
+
|
|
19
|
+
// ─── reconstructFrontmatter ─────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
describe('reconstructFrontmatter', () => {
|
|
22
|
+
it('serializes flat key-value pairs', () => {
|
|
23
|
+
const result = reconstructFrontmatter({ phase: '10', plan: '01' });
|
|
24
|
+
expect(result).toContain('phase: 10');
|
|
25
|
+
expect(result).toContain('plan: 01');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('serializes short arrays inline', () => {
|
|
29
|
+
const result = reconstructFrontmatter({ tags: ['a', 'b', 'c'] });
|
|
30
|
+
expect(result).toBe('tags: [a, b, c]');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('serializes long arrays as dash items', () => {
|
|
34
|
+
const result = reconstructFrontmatter({
|
|
35
|
+
items: ['alpha', 'bravo', 'charlie', 'delta'],
|
|
36
|
+
});
|
|
37
|
+
expect(result).toContain('items:');
|
|
38
|
+
expect(result).toContain(' - alpha');
|
|
39
|
+
expect(result).toContain(' - delta');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('serializes empty arrays as []', () => {
|
|
43
|
+
const result = reconstructFrontmatter({ depends_on: [] });
|
|
44
|
+
expect(result).toBe('depends_on: []');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('serializes nested objects with 2-space indent', () => {
|
|
48
|
+
const result = reconstructFrontmatter({ progress: { total: 5, done: 3 } });
|
|
49
|
+
expect(result).toContain('progress:');
|
|
50
|
+
expect(result).toContain(' total: 5');
|
|
51
|
+
expect(result).toContain(' done: 3');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('skips null and undefined values', () => {
|
|
55
|
+
const result = reconstructFrontmatter({ a: 'yes', b: null, c: undefined });
|
|
56
|
+
expect(result).toBe('a: yes');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('quotes strings containing colons', () => {
|
|
60
|
+
const result = reconstructFrontmatter({ label: 'key: value' });
|
|
61
|
+
expect(result).toContain('"key: value"');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('quotes strings containing hash', () => {
|
|
65
|
+
const result = reconstructFrontmatter({ label: 'color #red' });
|
|
66
|
+
expect(result).toContain('"color #red"');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('quotes strings starting with [ or {', () => {
|
|
70
|
+
const result = reconstructFrontmatter({ data: '[1,2,3]' });
|
|
71
|
+
expect(result).toContain('"[1,2,3]"');
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// ─── spliceFrontmatter ──────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
describe('spliceFrontmatter', () => {
|
|
78
|
+
it('replaces existing frontmatter block', () => {
|
|
79
|
+
const content = '---\nphase: 10\n---\n\n# Body';
|
|
80
|
+
const result = spliceFrontmatter(content, { phase: '11', plan: '01' });
|
|
81
|
+
expect(result).toMatch(/^---\nphase: 11\nplan: 01\n---/);
|
|
82
|
+
expect(result).toContain('# Body');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('prepends frontmatter when none exists', () => {
|
|
86
|
+
const content = '# Just a body';
|
|
87
|
+
const result = spliceFrontmatter(content, { phase: '10' });
|
|
88
|
+
expect(result).toMatch(/^---\nphase: 10\n---\n\n# Just a body/);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// ─── frontmatterSet ─────────────────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
describe('frontmatterSet', () => {
|
|
95
|
+
let tmpDir: string;
|
|
96
|
+
|
|
97
|
+
beforeEach(async () => {
|
|
98
|
+
tmpDir = await mkdtemp(join(tmpdir(), 'gsd-fm-set-'));
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
afterEach(async () => {
|
|
102
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('writes a single field and round-trips through extractFrontmatter', async () => {
|
|
106
|
+
const filePath = join(tmpDir, 'test.md');
|
|
107
|
+
await writeFile(filePath, '---\nphase: 10\nplan: 01\n---\n\n# Body\n');
|
|
108
|
+
|
|
109
|
+
await frontmatterSet([filePath, 'status', 'executing'], tmpDir);
|
|
110
|
+
|
|
111
|
+
const content = await readFile(filePath, 'utf-8');
|
|
112
|
+
const fm = extractFrontmatter(content);
|
|
113
|
+
expect(fm.status).toBe('executing');
|
|
114
|
+
expect(fm.phase).toBe('10');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('converts boolean string values', async () => {
|
|
118
|
+
const filePath = join(tmpDir, 'test.md');
|
|
119
|
+
await writeFile(filePath, '---\nphase: 10\n---\n\n# Body\n');
|
|
120
|
+
|
|
121
|
+
await frontmatterSet([filePath, 'autonomous', 'true'], tmpDir);
|
|
122
|
+
|
|
123
|
+
const content = await readFile(filePath, 'utf-8');
|
|
124
|
+
const fm = extractFrontmatter(content);
|
|
125
|
+
expect(fm.autonomous).toBe('true');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('handles numeric string values', async () => {
|
|
129
|
+
const filePath = join(tmpDir, 'test.md');
|
|
130
|
+
await writeFile(filePath, '---\nphase: 10\n---\n\n# Body\n');
|
|
131
|
+
|
|
132
|
+
await frontmatterSet([filePath, 'wave', '3'], tmpDir);
|
|
133
|
+
|
|
134
|
+
const content = await readFile(filePath, 'utf-8');
|
|
135
|
+
const fm = extractFrontmatter(content);
|
|
136
|
+
// reconstructFrontmatter outputs the number, extractFrontmatter reads it back as string
|
|
137
|
+
expect(String(fm.wave)).toBe('3');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('rejects null bytes in file path', async () => {
|
|
141
|
+
await expect(
|
|
142
|
+
frontmatterSet(['/path/with\0null', 'key', 'val'], tmpDir)
|
|
143
|
+
).rejects.toThrow(/null bytes/);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// ─── frontmatterMerge ───────────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
describe('frontmatterMerge', () => {
|
|
150
|
+
let tmpDir: string;
|
|
151
|
+
|
|
152
|
+
beforeEach(async () => {
|
|
153
|
+
tmpDir = await mkdtemp(join(tmpdir(), 'gsd-fm-merge-'));
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
afterEach(async () => {
|
|
157
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('deep merges JSON into existing frontmatter', async () => {
|
|
161
|
+
const filePath = join(tmpDir, 'test.md');
|
|
162
|
+
await writeFile(filePath, '---\nphase: 10\nplan: 01\n---\n\n# Body\n');
|
|
163
|
+
|
|
164
|
+
const result = await frontmatterMerge(
|
|
165
|
+
[filePath, JSON.stringify({ status: 'done', wave: 2 })],
|
|
166
|
+
tmpDir
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const content = await readFile(filePath, 'utf-8');
|
|
170
|
+
const fm = extractFrontmatter(content);
|
|
171
|
+
expect(fm.phase).toBe('10');
|
|
172
|
+
expect(fm.status).toBe('done');
|
|
173
|
+
expect((result.data as Record<string, unknown>).merged).toBe(true);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('rejects invalid JSON', async () => {
|
|
177
|
+
const filePath = join(tmpDir, 'test.md');
|
|
178
|
+
await writeFile(filePath, '---\nphase: 10\n---\n\n# Body\n');
|
|
179
|
+
|
|
180
|
+
await expect(
|
|
181
|
+
frontmatterMerge([filePath, 'not-json'], tmpDir)
|
|
182
|
+
).rejects.toThrow();
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// ─── frontmatterValidate ────────────────────────────────────────────────────
|
|
187
|
+
|
|
188
|
+
describe('frontmatterValidate', () => {
|
|
189
|
+
let tmpDir: string;
|
|
190
|
+
|
|
191
|
+
beforeEach(async () => {
|
|
192
|
+
tmpDir = await mkdtemp(join(tmpdir(), 'gsd-fm-validate-'));
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
afterEach(async () => {
|
|
196
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('validates a valid plan file', async () => {
|
|
200
|
+
const filePath = join(tmpDir, 'plan.md');
|
|
201
|
+
const fm = '---\nphase: 10\nplan: 01\ntype: execute\nwave: 1\ndepends_on: []\nfiles_modified: []\nautonomous: true\nmust_haves:\n truths:\n - foo\n---\n\n# Plan\n';
|
|
202
|
+
await writeFile(filePath, fm);
|
|
203
|
+
|
|
204
|
+
const result = await frontmatterValidate([filePath, '--schema', 'plan'], tmpDir);
|
|
205
|
+
const data = result.data as Record<string, unknown>;
|
|
206
|
+
expect(data.valid).toBe(true);
|
|
207
|
+
expect((data.missing as string[]).length).toBe(0);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('detects missing fields', async () => {
|
|
211
|
+
const filePath = join(tmpDir, 'plan.md');
|
|
212
|
+
await writeFile(filePath, '---\nphase: 10\n---\n\n# Plan\n');
|
|
213
|
+
|
|
214
|
+
const result = await frontmatterValidate([filePath, '--schema', 'plan'], tmpDir);
|
|
215
|
+
const data = result.data as Record<string, unknown>;
|
|
216
|
+
expect(data.valid).toBe(false);
|
|
217
|
+
expect((data.missing as string[]).length).toBeGreaterThan(0);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('rejects unknown schema', async () => {
|
|
221
|
+
const filePath = join(tmpDir, 'test.md');
|
|
222
|
+
await writeFile(filePath, '---\nphase: 10\n---\n\n# Body\n');
|
|
223
|
+
|
|
224
|
+
await expect(
|
|
225
|
+
frontmatterValidate([filePath, '--schema', 'unknown'], tmpDir)
|
|
226
|
+
).rejects.toThrow(/Unknown schema/);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('has plan, summary, and verification schemas', () => {
|
|
230
|
+
expect(FRONTMATTER_SCHEMAS).toHaveProperty('plan');
|
|
231
|
+
expect(FRONTMATTER_SCHEMAS).toHaveProperty('summary');
|
|
232
|
+
expect(FRONTMATTER_SCHEMAS).toHaveProperty('verification');
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// ─── Round-trip (extract → reconstruct → splice) ───────────────────────────
|
|
237
|
+
|
|
238
|
+
describe('frontmatter round-trip', () => {
|
|
239
|
+
it('preserves scalar and list fields through extract + splice', () => {
|
|
240
|
+
const original = `---
|
|
241
|
+
phase: "01"
|
|
242
|
+
plan: "02"
|
|
243
|
+
type: execute
|
|
244
|
+
wave: 1
|
|
245
|
+
depends_on: []
|
|
246
|
+
tags: [a, b]
|
|
247
|
+
---
|
|
248
|
+
# Title
|
|
249
|
+
`;
|
|
250
|
+
const fm = extractFrontmatter(original) as Record<string, unknown>;
|
|
251
|
+
const spliced = spliceFrontmatter('# Title\n', fm);
|
|
252
|
+
expect(spliced.startsWith('---\n')).toBe(true);
|
|
253
|
+
const round = extractFrontmatter(spliced) as Record<string, unknown>;
|
|
254
|
+
expect(String(round.phase)).toBe('01');
|
|
255
|
+
// YAML may round-trip wave as number or string depending on parser output
|
|
256
|
+
expect(Number(round.wave)).toBe(1);
|
|
257
|
+
expect(Array.isArray(round.tags)).toBe(true);
|
|
258
|
+
});
|
|
259
|
+
});
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frontmatter mutation handlers — write operations for YAML frontmatter.
|
|
3
|
+
*
|
|
4
|
+
* Ported from get-shit-done/bin/lib/frontmatter.cjs.
|
|
5
|
+
* Provides reconstructFrontmatter (serialization), spliceFrontmatter (replacement),
|
|
6
|
+
* and query handlers for frontmatter.set, frontmatter.merge, frontmatter.validate.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { reconstructFrontmatter, spliceFrontmatter } from './frontmatter-mutation.js';
|
|
11
|
+
*
|
|
12
|
+
* const yaml = reconstructFrontmatter({ phase: '10', tags: ['a', 'b'] });
|
|
13
|
+
* // 'phase: 10\ntags: [a, b]'
|
|
14
|
+
*
|
|
15
|
+
* const updated = spliceFrontmatter('---\nold: val\n---\nbody', { new: 'val' });
|
|
16
|
+
* // '---\nnew: val\n---\nbody'
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
21
|
+
import { GSDError, ErrorClassification } from '../errors.js';
|
|
22
|
+
import { extractFrontmatter } from './frontmatter.js';
|
|
23
|
+
import { normalizeMd, resolvePathUnderProject } from './helpers.js';
|
|
24
|
+
import type { QueryHandler } from './utils.js';
|
|
25
|
+
|
|
26
|
+
// ─── FRONTMATTER_SCHEMAS ──────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
/** Schema definitions for frontmatter validation. */
|
|
29
|
+
export const FRONTMATTER_SCHEMAS: Record<string, { required: string[] }> = {
|
|
30
|
+
plan: { required: ['phase', 'plan', 'type', 'wave', 'depends_on', 'files_modified', 'autonomous', 'must_haves'] },
|
|
31
|
+
summary: { required: ['phase', 'plan', 'subsystem', 'tags', 'duration', 'completed'] },
|
|
32
|
+
verification: { required: ['phase', 'verified', 'status', 'score'] },
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// ─── reconstructFrontmatter ────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Serialize a flat/nested object into YAML frontmatter lines.
|
|
39
|
+
*
|
|
40
|
+
* Port of `reconstructFrontmatter` from frontmatter.cjs lines 122-183.
|
|
41
|
+
* Handles arrays (inline/dash), nested objects (2 levels), and quoting.
|
|
42
|
+
*
|
|
43
|
+
* @param obj - Object to serialize
|
|
44
|
+
* @returns YAML string (without --- delimiters)
|
|
45
|
+
*/
|
|
46
|
+
export function reconstructFrontmatter(obj: Record<string, unknown>): string {
|
|
47
|
+
const lines: string[] = [];
|
|
48
|
+
|
|
49
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
50
|
+
if (value === null || value === undefined) continue;
|
|
51
|
+
|
|
52
|
+
if (Array.isArray(value)) {
|
|
53
|
+
serializeArray(lines, key, value, '');
|
|
54
|
+
} else if (typeof value === 'object') {
|
|
55
|
+
lines.push(`${key}:`);
|
|
56
|
+
for (const [subkey, subval] of Object.entries(value as Record<string, unknown>)) {
|
|
57
|
+
if (subval === null || subval === undefined) continue;
|
|
58
|
+
if (Array.isArray(subval)) {
|
|
59
|
+
serializeArray(lines, subkey, subval, ' ');
|
|
60
|
+
} else if (typeof subval === 'object') {
|
|
61
|
+
lines.push(` ${subkey}:`);
|
|
62
|
+
for (const [subsubkey, subsubval] of Object.entries(subval as Record<string, unknown>)) {
|
|
63
|
+
if (subsubval === null || subsubval === undefined) continue;
|
|
64
|
+
if (Array.isArray(subsubval)) {
|
|
65
|
+
if (subsubval.length === 0) {
|
|
66
|
+
lines.push(` ${subsubkey}: []`);
|
|
67
|
+
} else {
|
|
68
|
+
lines.push(` ${subsubkey}:`);
|
|
69
|
+
for (const item of subsubval) {
|
|
70
|
+
lines.push(` - ${item}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
lines.push(` ${subsubkey}: ${subsubval}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
const sv = String(subval);
|
|
79
|
+
lines.push(` ${subkey}: ${needsQuoting(sv) ? `"${sv}"` : sv}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
const sv = String(value);
|
|
84
|
+
if (sv.includes(':') || sv.includes('#') || sv.startsWith('[') || sv.startsWith('{')) {
|
|
85
|
+
lines.push(`${key}: "${sv}"`);
|
|
86
|
+
} else {
|
|
87
|
+
lines.push(`${key}: ${sv}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return lines.join('\n');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Serialize an array at the given indent level. */
|
|
96
|
+
function serializeArray(lines: string[], key: string, arr: unknown[], indent: string): void {
|
|
97
|
+
if (arr.length === 0) {
|
|
98
|
+
lines.push(`${indent}${key}: []`);
|
|
99
|
+
} else if (
|
|
100
|
+
arr.every(v => typeof v === 'string') &&
|
|
101
|
+
arr.length <= 3 &&
|
|
102
|
+
(arr as string[]).join(', ').length < 60
|
|
103
|
+
) {
|
|
104
|
+
lines.push(`${indent}${key}: [${(arr as string[]).join(', ')}]`);
|
|
105
|
+
} else {
|
|
106
|
+
lines.push(`${indent}${key}:`);
|
|
107
|
+
for (const item of arr) {
|
|
108
|
+
const s = String(item);
|
|
109
|
+
lines.push(`${indent} - ${typeof item === 'string' && needsQuoting(s) ? `"${s}"` : s}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Check if a string value needs quoting in YAML. */
|
|
115
|
+
function needsQuoting(s: string): boolean {
|
|
116
|
+
return s.includes(':') || s.includes('#');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ─── spliceFrontmatter ─────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Replace or prepend frontmatter in content.
|
|
123
|
+
*
|
|
124
|
+
* Port of `spliceFrontmatter` from frontmatter.cjs lines 186-193.
|
|
125
|
+
*
|
|
126
|
+
* @param content - File content with potential existing frontmatter
|
|
127
|
+
* @param newObj - New frontmatter object to serialize
|
|
128
|
+
* @returns Content with updated frontmatter
|
|
129
|
+
*/
|
|
130
|
+
export function spliceFrontmatter(content: string, newObj: Record<string, unknown>): string {
|
|
131
|
+
const yamlStr = reconstructFrontmatter(newObj);
|
|
132
|
+
const match = content.match(/^---\r?\n[\s\S]+?\r?\n---/);
|
|
133
|
+
if (match) {
|
|
134
|
+
return `---\n${yamlStr}\n---` + content.slice(match[0].length);
|
|
135
|
+
}
|
|
136
|
+
return `---\n${yamlStr}\n---\n\n` + content;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ─── parseSimpleValue ──────────────────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Parse a simple CLI value string into a typed value.
|
|
143
|
+
* Tries JSON.parse first (handles booleans, numbers, arrays, objects).
|
|
144
|
+
* Falls back to raw string.
|
|
145
|
+
*/
|
|
146
|
+
function parseSimpleValue(value: string): unknown {
|
|
147
|
+
try {
|
|
148
|
+
return JSON.parse(value);
|
|
149
|
+
} catch {
|
|
150
|
+
return value;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ─── frontmatterSet ────────────────────────────────────────────────────────
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Query handler for frontmatter.set command.
|
|
158
|
+
*
|
|
159
|
+
* Reads a file, sets a single frontmatter field, writes back with normalization.
|
|
160
|
+
* Port of `cmdFrontmatterSet` from frontmatter.cjs lines 328-342.
|
|
161
|
+
*
|
|
162
|
+
* @param args - args[0]: file path, args[1]: field name, args[2]: value
|
|
163
|
+
* @param projectDir - Project root directory
|
|
164
|
+
* @returns QueryResult with { updated: true, field, value }
|
|
165
|
+
*/
|
|
166
|
+
export const frontmatterSet: QueryHandler = async (args, projectDir) => {
|
|
167
|
+
let filePath: string;
|
|
168
|
+
let field: string;
|
|
169
|
+
let value: string;
|
|
170
|
+
|
|
171
|
+
const fi = args.indexOf('--field');
|
|
172
|
+
const vi = args.indexOf('--value');
|
|
173
|
+
const hasNamedArgs = fi !== -1 || vi !== -1;
|
|
174
|
+
if (hasNamedArgs) {
|
|
175
|
+
if (fi === -1 || vi === -1 || !args[fi + 1] || args[vi + 1] === undefined) {
|
|
176
|
+
throw new GSDError('file, --field, and --value required together', ErrorClassification.Validation);
|
|
177
|
+
}
|
|
178
|
+
filePath = args[0];
|
|
179
|
+
field = args[fi + 1];
|
|
180
|
+
value = args[vi + 1];
|
|
181
|
+
} else {
|
|
182
|
+
filePath = args[0];
|
|
183
|
+
field = args[1];
|
|
184
|
+
value = args[2];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!filePath || !field || value === undefined) {
|
|
188
|
+
throw new GSDError('file, field, and value required', ErrorClassification.Validation);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Path traversal guard: reject null bytes
|
|
192
|
+
if (filePath.includes('\0')) {
|
|
193
|
+
throw new GSDError('file path contains null bytes', ErrorClassification.Validation);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let fullPath: string;
|
|
197
|
+
try {
|
|
198
|
+
fullPath = await resolvePathUnderProject(projectDir, filePath);
|
|
199
|
+
} catch (err) {
|
|
200
|
+
if (err instanceof GSDError) {
|
|
201
|
+
return { data: { error: err.message, path: filePath } };
|
|
202
|
+
}
|
|
203
|
+
throw err;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
let content: string;
|
|
207
|
+
try {
|
|
208
|
+
content = await readFile(fullPath, 'utf-8');
|
|
209
|
+
} catch {
|
|
210
|
+
return { data: { error: 'File not found', path: filePath } };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const fm = extractFrontmatter(content);
|
|
214
|
+
const parsedValue = parseSimpleValue(value);
|
|
215
|
+
fm[field] = parsedValue;
|
|
216
|
+
const newContent = spliceFrontmatter(content, fm);
|
|
217
|
+
await writeFile(fullPath, normalizeMd(newContent), 'utf-8');
|
|
218
|
+
|
|
219
|
+
return { data: { updated: true, field, value: parsedValue } };
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// ─── frontmatterMerge ──────────────────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Query handler for frontmatter.merge command.
|
|
226
|
+
*
|
|
227
|
+
* Reads a file, merges JSON object into existing frontmatter, writes back.
|
|
228
|
+
* Port of `cmdFrontmatterMerge` from frontmatter.cjs lines 344-356.
|
|
229
|
+
*
|
|
230
|
+
* @param args - `file --data <json>` (gsd-tools) or `[file, jsonString]` (SDK)
|
|
231
|
+
* @param projectDir - Project root directory
|
|
232
|
+
* @returns QueryResult with { merged: true, fields: [...] }
|
|
233
|
+
*/
|
|
234
|
+
export const frontmatterMerge: QueryHandler = async (args, projectDir) => {
|
|
235
|
+
const filePath = args[0];
|
|
236
|
+
const dataIdx = args.indexOf('--data');
|
|
237
|
+
const jsonString = dataIdx !== -1 ? args[dataIdx + 1] : args[1];
|
|
238
|
+
|
|
239
|
+
if (!filePath || !jsonString) {
|
|
240
|
+
throw new GSDError('file and data required', ErrorClassification.Validation);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Path traversal guard: reject null bytes (consistent with frontmatterSet)
|
|
244
|
+
if (filePath.includes('\0')) {
|
|
245
|
+
throw new GSDError('file path contains null bytes', ErrorClassification.Validation);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
let fullPath: string;
|
|
249
|
+
try {
|
|
250
|
+
fullPath = await resolvePathUnderProject(projectDir, filePath);
|
|
251
|
+
} catch (err) {
|
|
252
|
+
if (err instanceof GSDError) {
|
|
253
|
+
return { data: { error: err.message, path: filePath } };
|
|
254
|
+
}
|
|
255
|
+
throw err;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
let content: string;
|
|
259
|
+
try {
|
|
260
|
+
content = await readFile(fullPath, 'utf-8');
|
|
261
|
+
} catch {
|
|
262
|
+
return { data: { error: 'File not found', path: filePath } };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
let mergeData: Record<string, unknown>;
|
|
266
|
+
try {
|
|
267
|
+
mergeData = JSON.parse(jsonString) as Record<string, unknown>;
|
|
268
|
+
} catch {
|
|
269
|
+
throw new GSDError('Invalid JSON for merge data', ErrorClassification.Validation);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const fm = extractFrontmatter(content);
|
|
273
|
+
Object.assign(fm, mergeData);
|
|
274
|
+
const newContent = spliceFrontmatter(content, fm);
|
|
275
|
+
await writeFile(fullPath, normalizeMd(newContent), 'utf-8');
|
|
276
|
+
|
|
277
|
+
return { data: { merged: true, fields: Object.keys(mergeData) } };
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// ─── frontmatterValidate ───────────────────────────────────────────────────
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Query handler for frontmatter.validate command.
|
|
284
|
+
*
|
|
285
|
+
* Reads a file and checks its frontmatter against a known schema.
|
|
286
|
+
* Port of `cmdFrontmatterValidate` from frontmatter.cjs lines 358-369.
|
|
287
|
+
*
|
|
288
|
+
* @param args - args[0]: file path, args[1]: '--schema', args[2]: schema name
|
|
289
|
+
* @param projectDir - Project root directory
|
|
290
|
+
* @returns QueryResult with { valid, missing, present, schema }
|
|
291
|
+
*/
|
|
292
|
+
export const frontmatterValidate: QueryHandler = async (args, projectDir) => {
|
|
293
|
+
const filePath = args[0];
|
|
294
|
+
|
|
295
|
+
// Parse --schema flag from args
|
|
296
|
+
let schemaName: string | undefined;
|
|
297
|
+
for (let i = 1; i < args.length; i++) {
|
|
298
|
+
if (args[i] === '--schema' && args[i + 1]) {
|
|
299
|
+
schemaName = args[i + 1];
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (!filePath || !schemaName) {
|
|
305
|
+
throw new GSDError('file and schema required', ErrorClassification.Validation);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Path traversal guard: reject null bytes (consistent with frontmatterSet)
|
|
309
|
+
if (filePath.includes('\0')) {
|
|
310
|
+
throw new GSDError('file path contains null bytes', ErrorClassification.Validation);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const schema = FRONTMATTER_SCHEMAS[schemaName];
|
|
314
|
+
if (!schema) {
|
|
315
|
+
throw new GSDError(
|
|
316
|
+
`Unknown schema: ${schemaName}. Available: ${Object.keys(FRONTMATTER_SCHEMAS).join(', ')}`,
|
|
317
|
+
ErrorClassification.Validation
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
let fullPath: string;
|
|
322
|
+
try {
|
|
323
|
+
fullPath = await resolvePathUnderProject(projectDir, filePath);
|
|
324
|
+
} catch (err) {
|
|
325
|
+
if (err instanceof GSDError) {
|
|
326
|
+
return { data: { error: err.message, path: filePath } };
|
|
327
|
+
}
|
|
328
|
+
throw err;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
let content: string;
|
|
332
|
+
try {
|
|
333
|
+
content = await readFile(fullPath, 'utf-8');
|
|
334
|
+
} catch {
|
|
335
|
+
return { data: { error: 'File not found', path: filePath } };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const fm = extractFrontmatter(content);
|
|
339
|
+
const missing = schema.required.filter(f => fm[f] === undefined);
|
|
340
|
+
const present = schema.required.filter(f => fm[f] !== undefined);
|
|
341
|
+
|
|
342
|
+
return { data: { valid: missing.length === 0, missing, present, schema: schemaName } };
|
|
343
|
+
};
|