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,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for frontmatter parser and query handler.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
6
|
+
import { mkdtemp, writeFile, rm } from 'node:fs/promises';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { tmpdir } from 'node:os';
|
|
9
|
+
import {
|
|
10
|
+
splitInlineArray,
|
|
11
|
+
extractFrontmatter,
|
|
12
|
+
extractFrontmatterLeading,
|
|
13
|
+
stripFrontmatter,
|
|
14
|
+
frontmatterGet,
|
|
15
|
+
parseMustHavesBlock,
|
|
16
|
+
} from './frontmatter.js';
|
|
17
|
+
|
|
18
|
+
// ─── splitInlineArray ───────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
describe('splitInlineArray', () => {
|
|
21
|
+
it('splits simple CSV', () => {
|
|
22
|
+
expect(splitInlineArray('a, b, c')).toEqual(['a', 'b', 'c']);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('handles quoted strings with commas', () => {
|
|
26
|
+
expect(splitInlineArray('"a, b", c')).toEqual(['a, b', 'c']);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('handles single-quoted strings', () => {
|
|
30
|
+
expect(splitInlineArray("'a, b', c")).toEqual(['a, b', 'c']);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('trims whitespace', () => {
|
|
34
|
+
expect(splitInlineArray(' a , b ')).toEqual(['a', 'b']);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('returns empty array for empty string', () => {
|
|
38
|
+
expect(splitInlineArray('')).toEqual([]);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// ─── extractFrontmatter ─────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
describe('extractFrontmatter', () => {
|
|
45
|
+
it('parses simple key-value pairs', () => {
|
|
46
|
+
const content = '---\nkey: value\n---\nbody';
|
|
47
|
+
const result = extractFrontmatter(content);
|
|
48
|
+
expect(result).toEqual({ key: 'value' });
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('parses nested objects', () => {
|
|
52
|
+
const content = '---\nparent:\n child: value\n---\n';
|
|
53
|
+
const result = extractFrontmatter(content);
|
|
54
|
+
expect(result).toEqual({ parent: { child: 'value' } });
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('parses inline arrays', () => {
|
|
58
|
+
const content = '---\ntags: [a, b, c]\n---\n';
|
|
59
|
+
const result = extractFrontmatter(content);
|
|
60
|
+
expect(result).toEqual({ tags: ['a', 'b', 'c'] });
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('parses dash arrays', () => {
|
|
64
|
+
const content = '---\nitems:\n - one\n - two\n---\n';
|
|
65
|
+
const result = extractFrontmatter(content);
|
|
66
|
+
expect(result).toEqual({ items: ['one', 'two'] });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('uses the LAST block when multiple stacked blocks exist', () => {
|
|
70
|
+
const content = '---\nold: data\n---\n---\nnew: data\n---\nbody';
|
|
71
|
+
const result = extractFrontmatter(content);
|
|
72
|
+
expect(result).toEqual({ new: 'data' });
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('handles empty-object-to-array conversion', () => {
|
|
76
|
+
const content = '---\nlist:\n - item1\n - item2\n---\n';
|
|
77
|
+
const result = extractFrontmatter(content);
|
|
78
|
+
expect(result).toEqual({ list: ['item1', 'item2'] });
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('returns empty object when no frontmatter', () => {
|
|
82
|
+
const result = extractFrontmatter('no frontmatter here');
|
|
83
|
+
expect(result).toEqual({});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('strips surrounding quotes from values', () => {
|
|
87
|
+
const content = '---\nkey: "quoted"\n---\n';
|
|
88
|
+
const result = extractFrontmatter(content);
|
|
89
|
+
expect(result).toEqual({ key: 'quoted' });
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('handles CRLF line endings', () => {
|
|
93
|
+
const content = '---\r\nkey: value\r\n---\r\nbody';
|
|
94
|
+
const result = extractFrontmatter(content);
|
|
95
|
+
expect(result).toEqual({ key: 'value' });
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// ─── extractFrontmatterLeading ─────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
describe('extractFrontmatterLeading', () => {
|
|
102
|
+
it('parses only the first leading block (gsd-tools.cjs / frontmatter.cjs parity)', () => {
|
|
103
|
+
const content = '---\nfirst: 1\n---\n---\nsecond: 2\n---\nbody';
|
|
104
|
+
expect(extractFrontmatterLeading(content)).toEqual({ first: '1' });
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('matches extractFrontmatter when a single block starts the file', () => {
|
|
108
|
+
const content = '---\na: b\n---\n';
|
|
109
|
+
expect(extractFrontmatterLeading(content)).toEqual(extractFrontmatter(content));
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// ─── stripFrontmatter ───────────────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
describe('stripFrontmatter', () => {
|
|
116
|
+
it('strips single frontmatter block', () => {
|
|
117
|
+
const result = stripFrontmatter('---\nk: v\n---\nbody');
|
|
118
|
+
expect(result).toBe('body');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('strips multiple stacked blocks', () => {
|
|
122
|
+
const result = stripFrontmatter('---\na: 1\n---\n---\nb: 2\n---\nbody');
|
|
123
|
+
expect(result).toBe('body');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('returns content unchanged when no frontmatter', () => {
|
|
127
|
+
expect(stripFrontmatter('just body')).toBe('just body');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('handles leading whitespace after strip', () => {
|
|
131
|
+
const result = stripFrontmatter('---\nk: v\n---\n\nbody');
|
|
132
|
+
// After stripping, leading whitespace/newlines may remain
|
|
133
|
+
expect(result.trim()).toBe('body');
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// ─── frontmatterGet ─────────────────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
describe('frontmatterGet', () => {
|
|
140
|
+
let tmpDir: string;
|
|
141
|
+
|
|
142
|
+
beforeEach(async () => {
|
|
143
|
+
tmpDir = await mkdtemp(join(tmpdir(), 'gsd-fm-'));
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
afterEach(async () => {
|
|
147
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('returns parsed frontmatter from a file', async () => {
|
|
151
|
+
await writeFile(join(tmpDir, 'test.md'), '---\nkey: value\n---\nbody');
|
|
152
|
+
const result = await frontmatterGet(['test.md'], tmpDir);
|
|
153
|
+
expect(result.data).toEqual({ key: 'value' });
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('returns single field when field arg provided', async () => {
|
|
157
|
+
await writeFile(join(tmpDir, 'test.md'), '---\nkey: value\n---\nbody');
|
|
158
|
+
const result = await frontmatterGet(['test.md', 'key'], tmpDir);
|
|
159
|
+
expect(result.data).toEqual({ key: 'value' });
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('returns error for missing file', async () => {
|
|
163
|
+
const result = await frontmatterGet(['missing.md'], tmpDir);
|
|
164
|
+
expect(result.data).toEqual({ error: 'File not found', path: 'missing.md' });
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('throws GSDError for null bytes in path', async () => {
|
|
168
|
+
const { GSDError } = await import('../errors.js');
|
|
169
|
+
await expect(frontmatterGet(['bad\0path.md'], tmpDir)).rejects.toThrow(GSDError);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// ─── parseMustHavesBlock ───────────────────────────────────────────────────
|
|
174
|
+
|
|
175
|
+
describe('parseMustHavesBlock', () => {
|
|
176
|
+
it('parses artifacts block with path, provides, min_lines, contains, exports', () => {
|
|
177
|
+
const content = `---
|
|
178
|
+
phase: 12
|
|
179
|
+
must_haves:
|
|
180
|
+
artifacts:
|
|
181
|
+
- path: sdk/src/foo.ts
|
|
182
|
+
provides: Foo handler
|
|
183
|
+
min_lines: 50
|
|
184
|
+
contains: export function foo
|
|
185
|
+
exports:
|
|
186
|
+
- foo
|
|
187
|
+
- bar
|
|
188
|
+
---
|
|
189
|
+
body`;
|
|
190
|
+
const result = parseMustHavesBlock(content, 'artifacts');
|
|
191
|
+
expect(result.items).toHaveLength(1);
|
|
192
|
+
expect(result.items[0]).toEqual({
|
|
193
|
+
path: 'sdk/src/foo.ts',
|
|
194
|
+
provides: 'Foo handler',
|
|
195
|
+
min_lines: 50,
|
|
196
|
+
contains: 'export function foo',
|
|
197
|
+
exports: ['foo', 'bar'],
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('parses key_links block with from, to, via, pattern', () => {
|
|
202
|
+
const content = `---
|
|
203
|
+
phase: 12
|
|
204
|
+
must_haves:
|
|
205
|
+
key_links:
|
|
206
|
+
- from: src/a.ts
|
|
207
|
+
to: src/b.ts
|
|
208
|
+
via: import something
|
|
209
|
+
pattern: import.*something.*from.*b
|
|
210
|
+
---
|
|
211
|
+
body`;
|
|
212
|
+
const result = parseMustHavesBlock(content, 'key_links');
|
|
213
|
+
expect(result.items).toHaveLength(1);
|
|
214
|
+
expect(result.items[0]).toEqual({
|
|
215
|
+
from: 'src/a.ts',
|
|
216
|
+
to: 'src/b.ts',
|
|
217
|
+
via: 'import something',
|
|
218
|
+
pattern: 'import.*something.*from.*b',
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('parses simple string items (truths)', () => {
|
|
223
|
+
const content = `---
|
|
224
|
+
phase: 12
|
|
225
|
+
must_haves:
|
|
226
|
+
truths:
|
|
227
|
+
- Running verify returns valid
|
|
228
|
+
- Running check returns true
|
|
229
|
+
---
|
|
230
|
+
body`;
|
|
231
|
+
const result = parseMustHavesBlock(content, 'truths');
|
|
232
|
+
expect(result.items).toHaveLength(2);
|
|
233
|
+
expect(result.items[0]).toBe('Running verify returns valid');
|
|
234
|
+
expect(result.items[1]).toBe('Running check returns true');
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('preserves nested array values (exports: [a, b])', () => {
|
|
238
|
+
const content = `---
|
|
239
|
+
must_haves:
|
|
240
|
+
artifacts:
|
|
241
|
+
- path: foo.ts
|
|
242
|
+
exports:
|
|
243
|
+
- alpha
|
|
244
|
+
- beta
|
|
245
|
+
---
|
|
246
|
+
`;
|
|
247
|
+
const result = parseMustHavesBlock(content, 'artifacts');
|
|
248
|
+
expect(result.items[0]).toMatchObject({ exports: ['alpha', 'beta'] });
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('returns empty items for missing block', () => {
|
|
252
|
+
const content = `---
|
|
253
|
+
must_haves:
|
|
254
|
+
truths:
|
|
255
|
+
- something
|
|
256
|
+
---
|
|
257
|
+
`;
|
|
258
|
+
const result = parseMustHavesBlock(content, 'artifacts');
|
|
259
|
+
expect(result.items).toEqual([]);
|
|
260
|
+
expect(result.warnings).toEqual([]);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('returns empty items for no frontmatter', () => {
|
|
264
|
+
const result = parseMustHavesBlock('no frontmatter here', 'artifacts');
|
|
265
|
+
expect(result.items).toEqual([]);
|
|
266
|
+
expect(result.warnings).toEqual([]);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('emits diagnostic warning when content lines exist but 0 items parsed', () => {
|
|
270
|
+
const content = `---
|
|
271
|
+
must_haves:
|
|
272
|
+
artifacts:
|
|
273
|
+
some badly formatted content
|
|
274
|
+
---
|
|
275
|
+
`;
|
|
276
|
+
const result = parseMustHavesBlock(content, 'artifacts');
|
|
277
|
+
expect(result.items).toEqual([]);
|
|
278
|
+
expect(result.warnings.length).toBeGreaterThan(0);
|
|
279
|
+
expect(result.warnings[0]).toContain('artifacts');
|
|
280
|
+
});
|
|
281
|
+
});
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frontmatter parser and query handler.
|
|
3
|
+
*
|
|
4
|
+
* Ported from get-shit-done/bin/lib/frontmatter.cjs and state.cjs.
|
|
5
|
+
* Provides YAML frontmatter extraction from .planning/ artifacts.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { extractFrontmatter, frontmatterGet } from './frontmatter.js';
|
|
10
|
+
*
|
|
11
|
+
* const fm = extractFrontmatter('---\nphase: 10\nplan: 01\n---\nbody');
|
|
12
|
+
* // { phase: '10', plan: '01' }
|
|
13
|
+
*
|
|
14
|
+
* const result = await frontmatterGet(['STATE.md'], '/project');
|
|
15
|
+
* // { data: { gsd_state_version: '1.0', milestone: 'v3.0', ... } }
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { readFile } from 'node:fs/promises';
|
|
20
|
+
import { GSDError, ErrorClassification } from '../errors.js';
|
|
21
|
+
import type { QueryHandler } from './utils.js';
|
|
22
|
+
import { escapeRegex, resolvePathUnderProject } from './helpers.js';
|
|
23
|
+
|
|
24
|
+
// ─── splitInlineArray ───────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Quote-aware CSV splitting for inline YAML arrays.
|
|
28
|
+
*
|
|
29
|
+
* Handles both single and double quotes, preserving commas inside quotes.
|
|
30
|
+
*
|
|
31
|
+
* @param body - The content inside brackets, e.g. 'a, "b, c", d'
|
|
32
|
+
* @returns Array of trimmed values
|
|
33
|
+
*/
|
|
34
|
+
export function splitInlineArray(body: string): string[] {
|
|
35
|
+
const items: string[] = [];
|
|
36
|
+
let current = '';
|
|
37
|
+
let inQuote: string | null = null;
|
|
38
|
+
|
|
39
|
+
for (let i = 0; i < body.length; i++) {
|
|
40
|
+
const ch = body[i];
|
|
41
|
+
if (inQuote) {
|
|
42
|
+
if (ch === inQuote) {
|
|
43
|
+
inQuote = null;
|
|
44
|
+
} else {
|
|
45
|
+
current += ch;
|
|
46
|
+
}
|
|
47
|
+
} else if (ch === '"' || ch === "'") {
|
|
48
|
+
inQuote = ch;
|
|
49
|
+
} else if (ch === ',') {
|
|
50
|
+
const trimmed = current.trim();
|
|
51
|
+
if (trimmed) items.push(trimmed);
|
|
52
|
+
current = '';
|
|
53
|
+
} else {
|
|
54
|
+
current += ch;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const trimmed = current.trim();
|
|
58
|
+
if (trimmed) items.push(trimmed);
|
|
59
|
+
return items;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ─── parseFrontmatterYamlLines ───────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Parse YAML frontmatter body (between `---` fences) using the GSD stack parser.
|
|
66
|
+
* Shared by {@link extractFrontmatterLeading} and {@link extractFrontmatter}.
|
|
67
|
+
*/
|
|
68
|
+
function parseFrontmatterYamlLines(yaml: string): Record<string, unknown> {
|
|
69
|
+
const frontmatter: Record<string, unknown> = {};
|
|
70
|
+
const lines = yaml.split(/\r?\n/);
|
|
71
|
+
|
|
72
|
+
// Stack to track nested objects: [{obj, key, indent}]
|
|
73
|
+
const stack: Array<{ obj: Record<string, unknown> | unknown[]; key: string | null; indent: number }> = [
|
|
74
|
+
{ obj: frontmatter, key: null, indent: -1 },
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
for (const line of lines) {
|
|
78
|
+
// Skip empty lines
|
|
79
|
+
if (line.trim() === '') continue;
|
|
80
|
+
|
|
81
|
+
// Calculate indentation (number of leading spaces)
|
|
82
|
+
const indentMatch = line.match(/^(\s*)/);
|
|
83
|
+
const indent = indentMatch ? indentMatch[1].length : 0;
|
|
84
|
+
|
|
85
|
+
// Pop stack back to appropriate level
|
|
86
|
+
while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
|
|
87
|
+
stack.pop();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const current = stack[stack.length - 1];
|
|
91
|
+
|
|
92
|
+
// Check for key: value pattern
|
|
93
|
+
const keyMatch = line.match(/^(\s*)([a-zA-Z0-9_-]+):\s*(.*)/);
|
|
94
|
+
if (keyMatch) {
|
|
95
|
+
const key = keyMatch[2];
|
|
96
|
+
const value = keyMatch[3].trim();
|
|
97
|
+
|
|
98
|
+
if (value === '' || value === '[') {
|
|
99
|
+
// Key with no value or opening bracket -- could be nested object or array
|
|
100
|
+
(current.obj as Record<string, unknown>)[key] = value === '[' ? [] : {};
|
|
101
|
+
current.key = null;
|
|
102
|
+
// Push new context for potential nested content
|
|
103
|
+
stack.push({ obj: (current.obj as Record<string, unknown>)[key] as Record<string, unknown>, key: null, indent });
|
|
104
|
+
} else if (value.startsWith('[') && value.endsWith(']')) {
|
|
105
|
+
// Inline array: key: [a, b, c]
|
|
106
|
+
(current.obj as Record<string, unknown>)[key] = splitInlineArray(value.slice(1, -1));
|
|
107
|
+
current.key = null;
|
|
108
|
+
} else {
|
|
109
|
+
// Simple key: value -- strip surrounding quotes
|
|
110
|
+
(current.obj as Record<string, unknown>)[key] = value.replace(/^["']|["']$/g, '');
|
|
111
|
+
current.key = null;
|
|
112
|
+
}
|
|
113
|
+
} else if (line.trim().startsWith('- ')) {
|
|
114
|
+
// Array item
|
|
115
|
+
const afterDash = line.trim().slice(2).trim();
|
|
116
|
+
let itemValue: unknown = afterDash.replace(/^["']|["']$/g, '');
|
|
117
|
+
let isObjItem = false;
|
|
118
|
+
|
|
119
|
+
// Extract key: value within the array item if present
|
|
120
|
+
const kvMatch = afterDash.match(/^([a-zA-Z0-9_-]+):\s*(.*)/);
|
|
121
|
+
if (kvMatch) {
|
|
122
|
+
isObjItem = true;
|
|
123
|
+
const k = kvMatch[1];
|
|
124
|
+
const v = kvMatch[2].trim().replace(/^["']|["']$/g, '');
|
|
125
|
+
itemValue = { [k]: v };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// If current context is an empty object, convert to array
|
|
129
|
+
if (typeof current.obj === 'object' && !Array.isArray(current.obj) && Object.keys(current.obj).length === 0) {
|
|
130
|
+
// Find the key in parent that points to this object and convert it
|
|
131
|
+
const parent = stack.length > 1 ? stack[stack.length - 2] : null;
|
|
132
|
+
if (parent && !Array.isArray(parent.obj)) {
|
|
133
|
+
for (const k of Object.keys(parent.obj as Record<string, unknown>)) {
|
|
134
|
+
if ((parent.obj as Record<string, unknown>)[k] === current.obj) {
|
|
135
|
+
(parent.obj as Record<string, unknown>)[k] = [itemValue];
|
|
136
|
+
current.obj = (parent.obj as Record<string, unknown>)[k] as unknown[];
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} else if (Array.isArray(current.obj)) {
|
|
142
|
+
current.obj.push(itemValue);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Push object context onto stack so subsequent indented properties map to this object
|
|
146
|
+
if (isObjItem && Array.isArray(current.obj)) {
|
|
147
|
+
stack.push({ obj: itemValue as Record<string, unknown>, key: null, indent });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return frontmatter;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ─── extractFrontmatterLeading ──────────────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* First leading frontmatter block only — parity with `get-shit-done/bin/lib/frontmatter.cjs`
|
|
159
|
+
* `extractFrontmatter` (used by `summary-extract` and `history-digest` in gsd-tools.cjs).
|
|
160
|
+
*/
|
|
161
|
+
export function extractFrontmatterLeading(content: string): Record<string, unknown> {
|
|
162
|
+
const match = content.match(/^---\r?\n([\s\S]+?)\r?\n---/);
|
|
163
|
+
if (!match) return {};
|
|
164
|
+
return parseFrontmatterYamlLines(match[1]);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ─── extractFrontmatter ─────────────────────────────────────────────────────
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Parse YAML frontmatter from file content.
|
|
171
|
+
*
|
|
172
|
+
* Full stack-based parser supporting:
|
|
173
|
+
* - Simple key: value pairs
|
|
174
|
+
* - Nested objects via indentation
|
|
175
|
+
* - Inline arrays: key: [a, b, c]
|
|
176
|
+
* - Dash arrays with auto-conversion from empty objects
|
|
177
|
+
* - Multiple stacked blocks (uses the LAST match)
|
|
178
|
+
* - CRLF line endings
|
|
179
|
+
* - Quoted value stripping
|
|
180
|
+
*
|
|
181
|
+
* @param content - File content potentially containing frontmatter
|
|
182
|
+
* @returns Parsed frontmatter as a record, or empty object if none found
|
|
183
|
+
*/
|
|
184
|
+
export function extractFrontmatter(content: string): Record<string, unknown> {
|
|
185
|
+
// Find ALL frontmatter blocks. Use the LAST one (corruption recovery).
|
|
186
|
+
const allBlocks = [...content.matchAll(/(?:^|\n)\s*---\r?\n([\s\S]+?)\r?\n---/g)];
|
|
187
|
+
const match = allBlocks.length > 0 ? allBlocks[allBlocks.length - 1] : null;
|
|
188
|
+
if (!match) return {};
|
|
189
|
+
|
|
190
|
+
return parseFrontmatterYamlLines(match[1]);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ─── stripFrontmatter ───────────────────────────────────────────────────────
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Strip all frontmatter blocks from the start of content.
|
|
197
|
+
*
|
|
198
|
+
* Handles CRLF line endings and multiple stacked blocks (corruption recovery).
|
|
199
|
+
* Greedy: keeps stripping ---...--- blocks separated by optional whitespace.
|
|
200
|
+
*
|
|
201
|
+
* @param content - File content with potential frontmatter
|
|
202
|
+
* @returns Content with frontmatter removed
|
|
203
|
+
*/
|
|
204
|
+
export function stripFrontmatter(content: string): string {
|
|
205
|
+
let result = content;
|
|
206
|
+
// eslint-disable-next-line no-constant-condition
|
|
207
|
+
while (true) {
|
|
208
|
+
const stripped = result.replace(/^\s*---\r?\n[\s\S]*?\r?\n---\s*/, '');
|
|
209
|
+
if (stripped === result) break;
|
|
210
|
+
result = stripped;
|
|
211
|
+
}
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ─── parseMustHavesBlock ────────────────────────────────────────────────────
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Result of parsing a must_haves block from frontmatter.
|
|
219
|
+
*/
|
|
220
|
+
export interface MustHavesBlockResult {
|
|
221
|
+
items: unknown[];
|
|
222
|
+
warnings: string[];
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Parse a named block from must_haves in raw frontmatter YAML.
|
|
227
|
+
*
|
|
228
|
+
* Port of `parseMustHavesBlock` from `get-shit-done/bin/lib/frontmatter.cjs` lines 195-301.
|
|
229
|
+
* Handles 3-level nesting: `must_haves > blockName > [{key: value, ...}]`.
|
|
230
|
+
* Supports simple string items, structured objects with key-value pairs,
|
|
231
|
+
* and nested arrays within items.
|
|
232
|
+
*
|
|
233
|
+
* @param content - File content with frontmatter
|
|
234
|
+
* @param blockName - Block name under must_haves (e.g. 'artifacts', 'key_links', 'truths')
|
|
235
|
+
* @returns Structured result with items array and warnings
|
|
236
|
+
*/
|
|
237
|
+
export function parseMustHavesBlock(content: string, blockName: string): MustHavesBlockResult {
|
|
238
|
+
const warnings: string[] = [];
|
|
239
|
+
|
|
240
|
+
// Extract raw YAML from first ---\n...\n--- block
|
|
241
|
+
const fmMatch = content.match(/^---\r?\n([\s\S]+?)\r?\n---/);
|
|
242
|
+
if (!fmMatch) return { items: [], warnings };
|
|
243
|
+
|
|
244
|
+
const yaml = fmMatch[1];
|
|
245
|
+
|
|
246
|
+
// Find must_haves: at its indentation level
|
|
247
|
+
const mustHavesMatch = yaml.match(/^(\s*)must_haves:\s*$/m);
|
|
248
|
+
if (!mustHavesMatch) return { items: [], warnings };
|
|
249
|
+
const mustHavesIndent = mustHavesMatch[1].length;
|
|
250
|
+
|
|
251
|
+
// Find the block (e.g., "artifacts:", "key_links:") under must_haves
|
|
252
|
+
const blockPattern = new RegExp(`^(\\s+)${escapeRegex(blockName)}:\\s*$`, 'm');
|
|
253
|
+
const blockMatch = yaml.match(blockPattern);
|
|
254
|
+
if (!blockMatch) return { items: [], warnings };
|
|
255
|
+
|
|
256
|
+
const blockIndent = blockMatch[1].length;
|
|
257
|
+
// The block must be nested under must_haves (more indented)
|
|
258
|
+
if (blockIndent <= mustHavesIndent) return { items: [], warnings };
|
|
259
|
+
|
|
260
|
+
// Find where the block starts in the yaml string
|
|
261
|
+
const blockStart = yaml.indexOf(blockMatch[0]);
|
|
262
|
+
if (blockStart === -1) return { items: [], warnings };
|
|
263
|
+
|
|
264
|
+
const afterBlock = yaml.slice(blockStart);
|
|
265
|
+
const blockLines = afterBlock.split(/\r?\n/).slice(1); // skip the header line
|
|
266
|
+
|
|
267
|
+
// List items are indented one level deeper than blockIndent
|
|
268
|
+
// Continuation KVs are indented one level deeper than list items
|
|
269
|
+
const items: unknown[] = [];
|
|
270
|
+
let current: Record<string, unknown> | string | null = null;
|
|
271
|
+
let listItemIndent = -1; // detected from first "- " line
|
|
272
|
+
|
|
273
|
+
for (const line of blockLines) {
|
|
274
|
+
// Skip empty lines
|
|
275
|
+
if (line.trim() === '') continue;
|
|
276
|
+
const indentMatch = line.match(/^(\s*)/);
|
|
277
|
+
const indent = indentMatch ? indentMatch[1].length : 0;
|
|
278
|
+
// Stop at same or lower indent level than the block header
|
|
279
|
+
if (indent <= blockIndent && line.trim() !== '') break;
|
|
280
|
+
|
|
281
|
+
const trimmed = line.trim();
|
|
282
|
+
|
|
283
|
+
if (trimmed.startsWith('- ')) {
|
|
284
|
+
// Detect list item indent from the first occurrence
|
|
285
|
+
if (listItemIndent === -1) listItemIndent = indent;
|
|
286
|
+
|
|
287
|
+
// Only treat as a top-level list item if at the expected indent
|
|
288
|
+
if (indent === listItemIndent) {
|
|
289
|
+
if (current !== null) items.push(current);
|
|
290
|
+
const afterDash = trimmed.slice(2);
|
|
291
|
+
// Check if it's a simple string item (no colon means not a key-value)
|
|
292
|
+
if (!afterDash.includes(':')) {
|
|
293
|
+
current = afterDash.replace(/^["']|["']$/g, '');
|
|
294
|
+
} else {
|
|
295
|
+
// Key-value on same line as dash: "- path: value"
|
|
296
|
+
const kvMatch = afterDash.match(/^(\w+):\s*"?([^"]*)"?\s*$/);
|
|
297
|
+
if (kvMatch) {
|
|
298
|
+
current = {} as Record<string, unknown>;
|
|
299
|
+
current[kvMatch[1]] = kvMatch[2];
|
|
300
|
+
} else {
|
|
301
|
+
current = {} as Record<string, unknown>;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (current !== null && typeof current === 'object' && indent > listItemIndent) {
|
|
309
|
+
// Continuation key-value or nested array item
|
|
310
|
+
if (trimmed.startsWith('- ')) {
|
|
311
|
+
// Array item under a key
|
|
312
|
+
const arrVal = trimmed.slice(2).replace(/^["']|["']$/g, '');
|
|
313
|
+
const keys = Object.keys(current);
|
|
314
|
+
const lastKey = keys[keys.length - 1];
|
|
315
|
+
if (lastKey && !Array.isArray(current[lastKey])) {
|
|
316
|
+
current[lastKey] = current[lastKey] ? [current[lastKey]] : [];
|
|
317
|
+
}
|
|
318
|
+
if (lastKey) (current[lastKey] as unknown[]).push(arrVal);
|
|
319
|
+
} else {
|
|
320
|
+
const kvMatch = trimmed.match(/^(\w+):\s*"?([^"]*)"?\s*$/);
|
|
321
|
+
if (kvMatch) {
|
|
322
|
+
const val = kvMatch[2];
|
|
323
|
+
// Try to parse as number
|
|
324
|
+
current[kvMatch[1]] = /^\d+$/.test(val) ? parseInt(val, 10) : val;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (current !== null) items.push(current);
|
|
330
|
+
|
|
331
|
+
// Diagnostic warning when block has content lines but parsed 0 items
|
|
332
|
+
if (items.length === 0 && blockLines.length > 0) {
|
|
333
|
+
const nonEmptyLines = blockLines.filter(l => l.trim() !== '').length;
|
|
334
|
+
if (nonEmptyLines > 0) {
|
|
335
|
+
warnings.push(
|
|
336
|
+
`must_haves.${blockName} block has ${nonEmptyLines} content lines but parsed 0 items. ` +
|
|
337
|
+
`Possible YAML formatting issue.`
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return { items, warnings };
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// ─── frontmatterGet ─────────────────────────────────────────────────────────
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Query handler for frontmatter.get command.
|
|
349
|
+
*
|
|
350
|
+
* Reads a file, extracts frontmatter, and optionally returns a single field.
|
|
351
|
+
* Rejects null bytes in path (security: path traversal guard).
|
|
352
|
+
*
|
|
353
|
+
* @param args - args[0]: file path, args[1]: optional field name
|
|
354
|
+
* @param projectDir - Project root directory
|
|
355
|
+
* @returns QueryResult with parsed frontmatter or single field value
|
|
356
|
+
*/
|
|
357
|
+
export const frontmatterGet: QueryHandler = async (args, projectDir) => {
|
|
358
|
+
const filePath = args[0];
|
|
359
|
+
if (!filePath) {
|
|
360
|
+
throw new GSDError('file path required', ErrorClassification.Validation);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Path traversal guard: reject null bytes
|
|
364
|
+
if (filePath.includes('\0')) {
|
|
365
|
+
throw new GSDError('file path contains null bytes', ErrorClassification.Validation);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
let fullPath: string;
|
|
369
|
+
try {
|
|
370
|
+
fullPath = await resolvePathUnderProject(projectDir, filePath);
|
|
371
|
+
} catch (err) {
|
|
372
|
+
if (err instanceof GSDError) {
|
|
373
|
+
return { data: { error: err.message, path: filePath } };
|
|
374
|
+
}
|
|
375
|
+
throw err;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
let content: string;
|
|
379
|
+
try {
|
|
380
|
+
content = await readFile(fullPath, 'utf-8');
|
|
381
|
+
} catch {
|
|
382
|
+
return { data: { error: 'File not found', path: filePath } };
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const fm = extractFrontmatter(content);
|
|
386
|
+
const field = args[1];
|
|
387
|
+
|
|
388
|
+
if (field) {
|
|
389
|
+
const value = fm[field];
|
|
390
|
+
if (value === undefined) {
|
|
391
|
+
return { data: { error: 'Field not found', field } };
|
|
392
|
+
}
|
|
393
|
+
return { data: { [field]: value } };
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return { data: fm };
|
|
397
|
+
};
|