@opengsd/get-shit-done-redux 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.ja-JP.md +870 -0
- package/README.ko-KR.md +861 -0
- package/README.md +300 -0
- package/README.pt-BR.md +492 -0
- package/README.zh-CN.md +842 -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 +668 -0
- package/agents/gsd-code-reviewer.md +387 -0
- package/agents/gsd-codebase-mapper.md +853 -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 +774 -0
- package/agents/gsd-framework-selector.md +160 -0
- package/agents/gsd-integration-checker.md +470 -0
- package/agents/gsd-intel-updater.md +342 -0
- package/agents/gsd-nyquist-auditor.md +203 -0
- package/agents/gsd-pattern-mapper.md +335 -0
- package/agents/gsd-phase-researcher.md +928 -0
- package/agents/gsd-plan-checker.md +978 -0
- package/agents/gsd-planner.md +1278 -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 +917 -0
- package/bin/gsd-sdk.js +37 -0
- package/bin/install.js +11468 -0
- package/bin/lib/ui-safety-gate.cjs +107 -0
- package/commands/gsd/add-tests.md +42 -0
- package/commands/gsd/ai-integration-phase.md +37 -0
- package/commands/gsd/audit-fix.md +34 -0
- package/commands/gsd/audit-milestone.md +37 -0
- package/commands/gsd/audit-uat.md +24 -0
- package/commands/gsd/autonomous.md +46 -0
- package/commands/gsd/capture.md +62 -0
- package/commands/gsd/cleanup.md +24 -0
- package/commands/gsd/code-review.md +59 -0
- package/commands/gsd/complete-milestone.md +143 -0
- package/commands/gsd/config.md +58 -0
- package/commands/gsd/debug.md +52 -0
- package/commands/gsd/discuss-phase.md +76 -0
- package/commands/gsd/docs-update.md +49 -0
- package/commands/gsd/eval-review.md +33 -0
- package/commands/gsd/execute-phase.md +64 -0
- package/commands/gsd/explore.md +27 -0
- package/commands/gsd/extract-learnings.md +23 -0
- package/commands/gsd/fast.md +31 -0
- package/commands/gsd/forensics.md +57 -0
- package/commands/gsd/graphify.md +199 -0
- package/commands/gsd/health.md +31 -0
- package/commands/gsd/help.md +28 -0
- package/commands/gsd/import.md +41 -0
- package/commands/gsd/inbox.md +39 -0
- package/commands/gsd/ingest-docs.md +42 -0
- package/commands/gsd/manager.md +45 -0
- package/commands/gsd/map-codebase.md +83 -0
- package/commands/gsd/milestone-summary.md +51 -0
- package/commands/gsd/mvp-phase.md +45 -0
- package/commands/gsd/new-milestone.md +45 -0
- package/commands/gsd/new-project.md +47 -0
- package/commands/gsd/ns-context.md +23 -0
- package/commands/gsd/ns-ideate.md +24 -0
- package/commands/gsd/ns-manage.md +29 -0
- package/commands/gsd/ns-project.md +22 -0
- package/commands/gsd/ns-review.md +26 -0
- package/commands/gsd/ns-workflow.md +28 -0
- package/commands/gsd/pause-work.md +43 -0
- package/commands/gsd/phase.md +56 -0
- package/commands/gsd/plan-phase.md +62 -0
- package/commands/gsd/plan-review-convergence.md +59 -0
- package/commands/gsd/pr-branch.md +26 -0
- package/commands/gsd/profile-user.md +46 -0
- package/commands/gsd/progress.md +46 -0
- package/commands/gsd/quick.md +174 -0
- package/commands/gsd/resume-work.md +30 -0
- package/commands/gsd/review-backlog.md +63 -0
- package/commands/gsd/review.md +41 -0
- package/commands/gsd/secure-phase.md +36 -0
- package/commands/gsd/settings.md +29 -0
- package/commands/gsd/ship.md +24 -0
- package/commands/gsd/sketch.md +60 -0
- package/commands/gsd/spec-phase.md +63 -0
- package/commands/gsd/spike.md +57 -0
- package/commands/gsd/stats.md +19 -0
- package/commands/gsd/surface.md +155 -0
- package/commands/gsd/thread.md +24 -0
- package/commands/gsd/ui-phase.md +35 -0
- package/commands/gsd/ui-review.md +33 -0
- package/commands/gsd/ultraplan-phase.md +34 -0
- package/commands/gsd/undo.md +35 -0
- package/commands/gsd/update.md +48 -0
- package/commands/gsd/validate-phase.md +36 -0
- package/commands/gsd/verify-work.md +39 -0
- package/commands/gsd/workspace.md +52 -0
- package/commands/gsd/workstreams.md +70 -0
- package/get-shit-done/bin/check-latest-version.cjs +104 -0
- package/get-shit-done/bin/gsd-tools.cjs +1630 -0
- package/get-shit-done/bin/lib/active-workstream-store.cjs +85 -0
- package/get-shit-done/bin/lib/adr-parser.cjs +394 -0
- package/get-shit-done/bin/lib/artifacts.cjs +53 -0
- package/get-shit-done/bin/lib/audit.cjs +755 -0
- package/get-shit-done/bin/lib/cjs-command-router-adapter.cjs +39 -0
- package/get-shit-done/bin/lib/cjs-sdk-bridge.cjs +136 -0
- package/get-shit-done/bin/lib/clusters.cjs +135 -0
- package/get-shit-done/bin/lib/code-review-flags.cjs +74 -0
- package/get-shit-done/bin/lib/command-aliases.generated.cjs +824 -0
- package/get-shit-done/bin/lib/command-routing-hub.cjs +239 -0
- package/get-shit-done/bin/lib/commands.cjs +1035 -0
- package/get-shit-done/bin/lib/config-schema.cjs +31 -0
- package/get-shit-done/bin/lib/config.cjs +704 -0
- package/get-shit-done/bin/lib/configuration.generated.cjs +253 -0
- package/get-shit-done/bin/lib/context-utilization.cjs +47 -0
- package/get-shit-done/bin/lib/core.cjs +1922 -0
- package/get-shit-done/bin/lib/decisions.cjs +19 -0
- package/get-shit-done/bin/lib/decisions.generated.cjs +121 -0
- package/get-shit-done/bin/lib/docs.cjs +270 -0
- package/get-shit-done/bin/lib/drift.cjs +388 -0
- package/get-shit-done/bin/lib/fallow-runner.cjs +109 -0
- package/get-shit-done/bin/lib/frontmatter.cjs +389 -0
- package/get-shit-done/bin/lib/gap-checker.cjs +205 -0
- package/get-shit-done/bin/lib/graphify.cjs +592 -0
- package/get-shit-done/bin/lib/gsd2-import.cjs +514 -0
- package/get-shit-done/bin/lib/init-command-router.cjs +174 -0
- package/get-shit-done/bin/lib/init.cjs +2096 -0
- package/get-shit-done/bin/lib/install-profiles.cjs +603 -0
- package/get-shit-done/bin/lib/installer-migration-authoring.cjs +117 -0
- package/get-shit-done/bin/lib/installer-migration-report.cjs +354 -0
- package/get-shit-done/bin/lib/installer-migrations/000-first-time-baseline.cjs +220 -0
- package/get-shit-done/bin/lib/installer-migrations/001-legacy-orphan-files.cjs +41 -0
- package/get-shit-done/bin/lib/installer-migrations/002-codex-legacy-hooks-json.cjs +80 -0
- package/get-shit-done/bin/lib/installer-migrations.cjs +776 -0
- package/get-shit-done/bin/lib/intel.cjs +643 -0
- package/get-shit-done/bin/lib/learnings.cjs +379 -0
- package/get-shit-done/bin/lib/milestone.cjs +314 -0
- package/get-shit-done/bin/lib/model-catalog.cjs +136 -0
- package/get-shit-done/bin/lib/model-profiles.cjs +25 -0
- package/get-shit-done/bin/lib/phase-command-router.cjs +226 -0
- package/get-shit-done/bin/lib/phase.cjs +1490 -0
- package/get-shit-done/bin/lib/phases-command-router.cjs +97 -0
- package/get-shit-done/bin/lib/plan-scan.cjs +26 -0
- package/get-shit-done/bin/lib/plan-scan.generated.cjs +97 -0
- package/get-shit-done/bin/lib/planning-workspace.cjs +415 -0
- package/get-shit-done/bin/lib/profile-output.cjs +1130 -0
- package/get-shit-done/bin/lib/profile-pipeline.cjs +539 -0
- package/get-shit-done/bin/lib/project-root.generated.cjs +117 -0
- package/get-shit-done/bin/lib/prompt-budget.cjs +399 -0
- package/get-shit-done/bin/lib/review-reviewer-selection.cjs +125 -0
- package/get-shit-done/bin/lib/roadmap-command-router.cjs +99 -0
- package/get-shit-done/bin/lib/roadmap.cjs +642 -0
- package/get-shit-done/bin/lib/runtime-artifact-layout.cjs +301 -0
- package/get-shit-done/bin/lib/runtime-homes.cjs +185 -0
- package/get-shit-done/bin/lib/runtime-slash.cjs +109 -0
- package/get-shit-done/bin/lib/schema-detect.cjs +21 -0
- package/get-shit-done/bin/lib/schema-detect.generated.cjs +170 -0
- package/get-shit-done/bin/lib/secrets.cjs +20 -0
- package/get-shit-done/bin/lib/secrets.generated.cjs +37 -0
- package/get-shit-done/bin/lib/security.cjs +504 -0
- package/get-shit-done/bin/lib/shell-command-projection.cjs +552 -0
- package/get-shit-done/bin/lib/state-command-router.cjs +346 -0
- package/get-shit-done/bin/lib/state-document.cjs +12 -0
- package/get-shit-done/bin/lib/state-document.generated.cjs +127 -0
- package/get-shit-done/bin/lib/state.cjs +1940 -0
- package/get-shit-done/bin/lib/surface.cjs +430 -0
- package/get-shit-done/bin/lib/template.cjs +228 -0
- package/get-shit-done/bin/lib/uat.cjs +289 -0
- package/get-shit-done/bin/lib/validate-command-router.cjs +129 -0
- package/get-shit-done/bin/lib/verify-command-router.cjs +122 -0
- package/get-shit-done/bin/lib/verify.cjs +1458 -0
- package/get-shit-done/bin/lib/workstream-inventory-builder.generated.cjs +79 -0
- package/get-shit-done/bin/lib/workstream-inventory.cjs +132 -0
- package/get-shit-done/bin/lib/workstream-name-policy.cjs +19 -0
- package/get-shit-done/bin/lib/workstream-name-policy.generated.cjs +61 -0
- package/get-shit-done/bin/lib/workstream.cjs +374 -0
- package/get-shit-done/bin/lib/worktree-safety.cjs +985 -0
- package/get-shit-done/bin/verify-reapply-patches.cjs +336 -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 +23 -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 +814 -0
- package/get-shit-done/references/common-bug-patterns.md +114 -0
- package/get-shit-done/references/context-budget.md +85 -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/execute-mvp-tdd.md +81 -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 +298 -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 +245 -0
- package/get-shit-done/references/mvp-concepts.md +49 -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-chunked.md +49 -0
- package/get-shit-done/references/planner-gap-closure.md +62 -0
- package/get-shit-done/references/planner-graphify-auto-update.md +67 -0
- package/get-shit-done/references/planner-human-verify-mode.md +57 -0
- package/get-shit-done/references/planner-mvp-mode.md +53 -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 +471 -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/scout-codebase.md +51 -0
- package/get-shit-done/references/skeleton-template.md +48 -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/spidr-splitting.md +69 -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/user-story-template.md +58 -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/verify-mvp-mode.md +85 -0
- package/get-shit-done/references/workstream-flag.md +111 -0
- package/get-shit-done/references/worktree-path-safety.md +89 -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 +77 -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 +62 -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-backlog.md +101 -0
- package/get-shit-done/workflows/add-phase.md +123 -0
- package/get-shit-done/workflows/add-tests.md +365 -0
- package/get-shit-done/workflows/add-todo.md +171 -0
- package/get-shit-done/workflows/ai-integration-phase.md +305 -0
- package/get-shit-done/workflows/analyze-dependencies.md +96 -0
- package/get-shit-done/workflows/audit-fix.md +188 -0
- package/get-shit-done/workflows/audit-milestone.md +368 -0
- package/get-shit-done/workflows/audit-uat.md +120 -0
- package/get-shit-done/workflows/autonomous.md +805 -0
- package/get-shit-done/workflows/check-todos.md +190 -0
- package/get-shit-done/workflows/cleanup.md +165 -0
- package/get-shit-done/workflows/code-review-fix.md +512 -0
- package/get-shit-done/workflows/code-review.md +666 -0
- package/get-shit-done/workflows/complete-milestone.md +865 -0
- package/get-shit-done/workflows/debug.md +242 -0
- package/get-shit-done/workflows/diagnose-issues.md +251 -0
- package/get-shit-done/workflows/discovery-phase.md +291 -0
- package/get-shit-done/workflows/discuss-phase/modes/advisor.md +175 -0
- package/get-shit-done/workflows/discuss-phase/modes/all.md +28 -0
- package/get-shit-done/workflows/discuss-phase/modes/analyze.md +44 -0
- package/get-shit-done/workflows/discuss-phase/modes/auto.md +56 -0
- package/get-shit-done/workflows/discuss-phase/modes/batch.md +52 -0
- package/get-shit-done/workflows/discuss-phase/modes/chain.md +97 -0
- package/get-shit-done/workflows/discuss-phase/modes/default.md +141 -0
- package/get-shit-done/workflows/discuss-phase/modes/power.md +44 -0
- package/get-shit-done/workflows/discuss-phase/modes/text.md +55 -0
- package/get-shit-done/workflows/discuss-phase/templates/checkpoint.json +18 -0
- package/get-shit-done/workflows/discuss-phase/templates/context.md +136 -0
- package/get-shit-done/workflows/discuss-phase/templates/discussion-log.md +50 -0
- package/get-shit-done/workflows/discuss-phase-assumptions.md +685 -0
- package/get-shit-done/workflows/discuss-phase-power.md +291 -0
- package/get-shit-done/workflows/discuss-phase.md +499 -0
- package/get-shit-done/workflows/do.md +122 -0
- package/get-shit-done/workflows/docs-update.md +1172 -0
- package/get-shit-done/workflows/edit-phase.md +305 -0
- package/get-shit-done/workflows/eval-review.md +166 -0
- package/get-shit-done/workflows/execute-phase/steps/codebase-drift-gate.md +81 -0
- package/get-shit-done/workflows/execute-phase/steps/per-plan-worktree-gate.md +94 -0
- package/get-shit-done/workflows/execute-phase/steps/post-merge-gate.md +116 -0
- package/get-shit-done/workflows/execute-phase.md +1717 -0
- package/get-shit-done/workflows/execute-plan.md +536 -0
- package/get-shit-done/workflows/explore.md +154 -0
- package/get-shit-done/workflows/extract-learnings.md +253 -0
- package/get-shit-done/workflows/fast.md +124 -0
- package/get-shit-done/workflows/forensics.md +289 -0
- package/get-shit-done/workflows/graduation.md +206 -0
- package/get-shit-done/workflows/health.md +234 -0
- package/get-shit-done/workflows/help/modes/brief.md +22 -0
- package/get-shit-done/workflows/help/modes/default.md +50 -0
- package/get-shit-done/workflows/help/modes/full.md +784 -0
- package/get-shit-done/workflows/help/modes/topic.md +74 -0
- package/get-shit-done/workflows/help.md +24 -0
- package/get-shit-done/workflows/import.md +264 -0
- package/get-shit-done/workflows/inbox.md +387 -0
- package/get-shit-done/workflows/ingest-docs.md +339 -0
- package/get-shit-done/workflows/insert-phase.md +162 -0
- package/get-shit-done/workflows/list-phase-assumptions.md +178 -0
- package/get-shit-done/workflows/list-workspaces.md +67 -0
- package/get-shit-done/workflows/manager.md +403 -0
- package/get-shit-done/workflows/map-codebase.md +454 -0
- package/get-shit-done/workflows/milestone-summary.md +234 -0
- package/get-shit-done/workflows/mvp-phase.md +232 -0
- package/get-shit-done/workflows/new-milestone.md +645 -0
- package/get-shit-done/workflows/new-project.md +1487 -0
- package/get-shit-done/workflows/new-workspace.md +250 -0
- package/get-shit-done/workflows/next.md +231 -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 +254 -0
- package/get-shit-done/workflows/plan-milestone-gaps.md +291 -0
- package/get-shit-done/workflows/plan-phase.md +1800 -0
- package/get-shit-done/workflows/plan-review-convergence.md +340 -0
- package/get-shit-done/workflows/plant-seed.md +240 -0
- package/get-shit-done/workflows/pr-branch.md +157 -0
- package/get-shit-done/workflows/profile-user.md +463 -0
- package/get-shit-done/workflows/progress.md +660 -0
- package/get-shit-done/workflows/quick.md +1049 -0
- package/get-shit-done/workflows/reapply-patches.md +426 -0
- package/get-shit-done/workflows/remove-phase.md +166 -0
- package/get-shit-done/workflows/remove-workspace.md +118 -0
- package/get-shit-done/workflows/resume-project.md +342 -0
- package/get-shit-done/workflows/review.md +633 -0
- package/get-shit-done/workflows/scan.md +115 -0
- package/get-shit-done/workflows/secure-phase.md +190 -0
- package/get-shit-done/workflows/session-report.md +146 -0
- package/get-shit-done/workflows/settings-advanced.md +590 -0
- package/get-shit-done/workflows/settings-integrations.md +292 -0
- package/get-shit-done/workflows/settings.md +545 -0
- package/get-shit-done/workflows/ship.md +366 -0
- package/get-shit-done/workflows/sketch-wrap-up.md +296 -0
- package/get-shit-done/workflows/sketch.md +371 -0
- package/get-shit-done/workflows/spec-phase.md +262 -0
- package/get-shit-done/workflows/spike-wrap-up.md +317 -0
- package/get-shit-done/workflows/spike.md +463 -0
- package/get-shit-done/workflows/stats.md +90 -0
- package/get-shit-done/workflows/sync-skills.md +182 -0
- package/get-shit-done/workflows/thread.md +232 -0
- package/get-shit-done/workflows/transition.md +704 -0
- package/get-shit-done/workflows/ui-phase.md +338 -0
- package/get-shit-done/workflows/ui-review.md +203 -0
- package/get-shit-done/workflows/ultraplan-phase.md +209 -0
- package/get-shit-done/workflows/undo.md +314 -0
- package/get-shit-done/workflows/update.md +664 -0
- package/get-shit-done/workflows/validate-phase.md +189 -0
- package/get-shit-done/workflows/verify-phase.md +554 -0
- package/get-shit-done/workflows/verify-work.md +791 -0
- package/hooks/dist/gsd-check-update-worker.js +117 -0
- package/hooks/dist/gsd-check-update.js +64 -0
- package/hooks/dist/gsd-context-monitor.js +192 -0
- package/hooks/dist/gsd-graphify-update.sh +158 -0
- package/hooks/dist/gsd-phase-boundary.sh +47 -0
- package/hooks/dist/gsd-prompt-guard.js +97 -0
- package/hooks/dist/gsd-read-guard.js +101 -0
- package/hooks/dist/gsd-read-injection-scanner.js +152 -0
- package/hooks/dist/gsd-session-state.sh +59 -0
- package/hooks/dist/gsd-statusline.js +537 -0
- package/hooks/dist/gsd-update-banner.js +134 -0
- package/hooks/dist/gsd-validate-commit.sh +57 -0
- package/hooks/dist/gsd-workflow-guard.js +94 -0
- package/hooks/dist/lib/git-cmd.js +150 -0
- package/hooks/dist/lib/gsd-graphify-rebuild.sh +65 -0
- package/hooks/gsd-check-update-worker.js +117 -0
- package/hooks/gsd-check-update.js +64 -0
- package/hooks/gsd-context-monitor.js +192 -0
- package/hooks/gsd-graphify-update.sh +158 -0
- package/hooks/gsd-phase-boundary.sh +47 -0
- package/hooks/gsd-prompt-guard.js +97 -0
- package/hooks/gsd-read-guard.js +101 -0
- package/hooks/gsd-read-injection-scanner.js +152 -0
- package/hooks/gsd-session-state.sh +59 -0
- package/hooks/gsd-statusline.js +537 -0
- package/hooks/gsd-update-banner.js +134 -0
- package/hooks/gsd-validate-commit.sh +57 -0
- package/hooks/gsd-workflow-guard.js +94 -0
- package/hooks/lib/git-cmd.js +150 -0
- package/hooks/lib/gsd-graphify-rebuild.sh +65 -0
- package/package.json +98 -0
- package/scripts/audit-workflow-script-paths.cjs +73 -0
- package/scripts/base64-scan.sh +262 -0
- package/scripts/build-hooks.js +227 -0
- package/scripts/changeset/cli.cjs +408 -0
- package/scripts/changeset/github-release-notes.cjs +198 -0
- package/scripts/changeset/lint.cjs +110 -0
- package/scripts/changeset/new.cjs +137 -0
- package/scripts/changeset/parse.cjs +114 -0
- package/scripts/changeset/render.cjs +34 -0
- package/scripts/changeset/serialize.cjs +130 -0
- package/scripts/command-contract-helpers.cjs +64 -0
- package/scripts/diff-touches-shipped-paths.cjs +147 -0
- package/scripts/fix-slash-commands.cjs +147 -0
- package/scripts/gen-inventory-manifest.cjs +109 -0
- package/scripts/lint-command-contract.cjs +108 -0
- package/scripts/lint-descriptions.cjs +83 -0
- package/scripts/lint-docs-required.cjs +222 -0
- package/scripts/lint-no-source-grep-extras.cjs +81 -0
- package/scripts/lint-no-source-grep.cjs +174 -0
- package/scripts/lint-pr-check-project-dir.cjs +98 -0
- package/scripts/lint-shared-module-handsync.cjs +331 -0
- package/scripts/lint-shell-command-projection-drift.cjs +57 -0
- package/scripts/lint-skill-deps.cjs +180 -0
- package/scripts/lint-test-file-count.allowlist.json +35 -0
- package/scripts/lint-test-file-count.cjs +190 -0
- package/scripts/pr-template-policy.cjs +268 -0
- package/scripts/prompt-injection-scan.sh +203 -0
- package/scripts/release-tarball-smoke.cjs +677 -0
- package/scripts/run-tests.cjs +178 -0
- package/scripts/secret-scan.sh +229 -0
- package/scripts/shared-module-handsync-allowlist.json +145 -0
- package/scripts/strip-prose-atrefs.cjs +106 -0
- package/scripts/sync-rulesets.sh +34 -0
- package/scripts/verify-tarball-sdk-dist.sh +69 -0
- package/sdk/dist/cli-transport.d.ts +19 -0
- package/sdk/dist/cli-transport.d.ts.map +1 -0
- package/sdk/dist/cli-transport.js +104 -0
- package/sdk/dist/cli-transport.js.map +1 -0
- package/sdk/dist/cli.d.ts +46 -0
- package/sdk/dist/cli.d.ts.map +1 -0
- package/sdk/dist/cli.js +511 -0
- package/sdk/dist/cli.js.map +1 -0
- package/sdk/dist/config.d.ts +108 -0
- package/sdk/dist/config.d.ts.map +1 -0
- package/sdk/dist/config.js +116 -0
- package/sdk/dist/config.js.map +1 -0
- package/sdk/dist/configuration/index.d.ts +85 -0
- package/sdk/dist/configuration/index.d.ts.map +1 -0
- package/sdk/dist/configuration/index.js +257 -0
- package/sdk/dist/configuration/index.js.map +1 -0
- package/sdk/dist/context-engine.d.ts +49 -0
- package/sdk/dist/context-engine.d.ts.map +1 -0
- package/sdk/dist/context-engine.js +142 -0
- package/sdk/dist/context-engine.js.map +1 -0
- package/sdk/dist/context-truncation.d.ts +33 -0
- package/sdk/dist/context-truncation.d.ts.map +1 -0
- package/sdk/dist/context-truncation.js +197 -0
- package/sdk/dist/context-truncation.js.map +1 -0
- package/sdk/dist/errors.d.ts +46 -0
- package/sdk/dist/errors.d.ts.map +1 -0
- package/sdk/dist/errors.js +64 -0
- package/sdk/dist/errors.js.map +1 -0
- package/sdk/dist/event-stream.d.ts +53 -0
- package/sdk/dist/event-stream.d.ts.map +1 -0
- package/sdk/dist/event-stream.js +321 -0
- package/sdk/dist/event-stream.js.map +1 -0
- package/sdk/dist/golden/capture.d.ts +15 -0
- package/sdk/dist/golden/capture.d.ts.map +1 -0
- package/sdk/dist/golden/capture.js +67 -0
- package/sdk/dist/golden/capture.js.map +1 -0
- package/sdk/dist/golden/golden-integration-covered.d.ts +6 -0
- package/sdk/dist/golden/golden-integration-covered.d.ts.map +1 -0
- package/sdk/dist/golden/golden-integration-covered.js +30 -0
- package/sdk/dist/golden/golden-integration-covered.js.map +1 -0
- package/sdk/dist/golden/golden-mutation-covered.d.ts +7 -0
- package/sdk/dist/golden/golden-mutation-covered.d.ts.map +1 -0
- package/sdk/dist/golden/golden-mutation-covered.js +17 -0
- package/sdk/dist/golden/golden-mutation-covered.js.map +1 -0
- package/sdk/dist/golden/golden-policy.d.ts +10 -0
- package/sdk/dist/golden/golden-policy.d.ts.map +1 -0
- package/sdk/dist/golden/golden-policy.js +98 -0
- package/sdk/dist/golden/golden-policy.js.map +1 -0
- package/sdk/dist/golden/init-golden-normalize.d.ts +8 -0
- package/sdk/dist/golden/init-golden-normalize.d.ts.map +1 -0
- package/sdk/dist/golden/init-golden-normalize.js +14 -0
- package/sdk/dist/golden/init-golden-normalize.js.map +1 -0
- package/sdk/dist/golden/read-only-golden-rows.d.ts +20 -0
- package/sdk/dist/golden/read-only-golden-rows.d.ts.map +1 -0
- package/sdk/dist/golden/read-only-golden-rows.js +67 -0
- package/sdk/dist/golden/read-only-golden-rows.js.map +1 -0
- package/sdk/dist/golden/registry-canonical-commands.d.ts +6 -0
- package/sdk/dist/golden/registry-canonical-commands.d.ts.map +1 -0
- package/sdk/dist/golden/registry-canonical-commands.js +30 -0
- package/sdk/dist/golden/registry-canonical-commands.js.map +1 -0
- package/sdk/dist/gsd-tools-error.d.ts +23 -0
- package/sdk/dist/gsd-tools-error.d.ts.map +1 -0
- package/sdk/dist/gsd-tools-error.js +29 -0
- package/sdk/dist/gsd-tools-error.js.map +1 -0
- package/sdk/dist/gsd-tools.d.ts +97 -0
- package/sdk/dist/gsd-tools.d.ts.map +1 -0
- package/sdk/dist/gsd-tools.js +168 -0
- package/sdk/dist/gsd-tools.js.map +1 -0
- package/sdk/dist/gsd-transport-policy.d.ts +10 -0
- package/sdk/dist/gsd-transport-policy.d.ts.map +1 -0
- package/sdk/dist/gsd-transport-policy.js +32 -0
- package/sdk/dist/gsd-transport-policy.js.map +1 -0
- package/sdk/dist/gsd-transport.d.ts +39 -0
- package/sdk/dist/gsd-transport.d.ts.map +1 -0
- package/sdk/dist/gsd-transport.js +78 -0
- package/sdk/dist/gsd-transport.js.map +1 -0
- package/sdk/dist/index.d.ts +127 -0
- package/sdk/dist/index.d.ts.map +1 -0
- package/sdk/dist/index.js +300 -0
- package/sdk/dist/index.js.map +1 -0
- package/sdk/dist/init-runner.d.ts +90 -0
- package/sdk/dist/init-runner.d.ts.map +1 -0
- package/sdk/dist/init-runner.js +613 -0
- package/sdk/dist/init-runner.js.map +1 -0
- package/sdk/dist/logger.d.ts +50 -0
- package/sdk/dist/logger.d.ts.map +1 -0
- package/sdk/dist/logger.js +70 -0
- package/sdk/dist/logger.js.map +1 -0
- package/sdk/dist/model-catalog.d.ts +31 -0
- package/sdk/dist/model-catalog.d.ts.map +1 -0
- package/sdk/dist/model-catalog.js +31 -0
- package/sdk/dist/model-catalog.js.map +1 -0
- package/sdk/dist/phase-prompt.d.ts +72 -0
- package/sdk/dist/phase-prompt.d.ts.map +1 -0
- package/sdk/dist/phase-prompt.js +213 -0
- package/sdk/dist/phase-prompt.js.map +1 -0
- package/sdk/dist/phase-runner.d.ts +145 -0
- package/sdk/dist/phase-runner.d.ts.map +1 -0
- package/sdk/dist/phase-runner.js +1206 -0
- package/sdk/dist/phase-runner.js.map +1 -0
- package/sdk/dist/plan-parser.d.ts +55 -0
- package/sdk/dist/plan-parser.d.ts.map +1 -0
- package/sdk/dist/plan-parser.js +389 -0
- package/sdk/dist/plan-parser.js.map +1 -0
- package/sdk/dist/planning-journal.d.ts +64 -0
- package/sdk/dist/planning-journal.d.ts.map +1 -0
- package/sdk/dist/planning-journal.js +88 -0
- package/sdk/dist/planning-journal.js.map +1 -0
- package/sdk/dist/planning-runtime.d.ts +67 -0
- package/sdk/dist/planning-runtime.d.ts.map +1 -0
- package/sdk/dist/planning-runtime.js +58 -0
- package/sdk/dist/planning-runtime.js.map +1 -0
- package/sdk/dist/project-root/index.d.ts +46 -0
- package/sdk/dist/project-root/index.d.ts.map +1 -0
- package/sdk/dist/project-root/index.js +138 -0
- package/sdk/dist/project-root/index.js.map +1 -0
- package/sdk/dist/prompt-builder.d.ts +44 -0
- package/sdk/dist/prompt-builder.d.ts.map +1 -0
- package/sdk/dist/prompt-builder.js +180 -0
- package/sdk/dist/prompt-builder.js.map +1 -0
- package/sdk/dist/prompt-sanitizer.d.ts +35 -0
- package/sdk/dist/prompt-sanitizer.d.ts.map +1 -0
- package/sdk/dist/prompt-sanitizer.js +101 -0
- package/sdk/dist/prompt-sanitizer.js.map +1 -0
- package/sdk/dist/query/active-workstream-store.d.ts +7 -0
- package/sdk/dist/query/active-workstream-store.d.ts.map +1 -0
- package/sdk/dist/query/active-workstream-store.js +56 -0
- package/sdk/dist/query/active-workstream-store.js.map +1 -0
- package/sdk/dist/query/agent-failure-classifier.d.ts +38 -0
- package/sdk/dist/query/agent-failure-classifier.d.ts.map +1 -0
- package/sdk/dist/query/agent-failure-classifier.js +83 -0
- package/sdk/dist/query/agent-failure-classifier.js.map +1 -0
- package/sdk/dist/query/audit-open.d.ts +46 -0
- package/sdk/dist/query/audit-open.d.ts.map +1 -0
- package/sdk/dist/query/audit-open.js +662 -0
- package/sdk/dist/query/audit-open.js.map +1 -0
- package/sdk/dist/query/check-auto-mode.d.ts +13 -0
- package/sdk/dist/query/check-auto-mode.d.ts.map +1 -0
- package/sdk/dist/query/check-auto-mode.js +40 -0
- package/sdk/dist/query/check-auto-mode.js.map +1 -0
- package/sdk/dist/query/check-completion.d.ts +10 -0
- package/sdk/dist/query/check-completion.d.ts.map +1 -0
- package/sdk/dist/query/check-completion.js +157 -0
- package/sdk/dist/query/check-completion.js.map +1 -0
- package/sdk/dist/query/check-decision-coverage.d.ts +33 -0
- package/sdk/dist/query/check-decision-coverage.d.ts.map +1 -0
- package/sdk/dist/query/check-decision-coverage.js +472 -0
- package/sdk/dist/query/check-decision-coverage.js.map +1 -0
- package/sdk/dist/query/check-gates.d.ts +10 -0
- package/sdk/dist/query/check-gates.d.ts.map +1 -0
- package/sdk/dist/query/check-gates.js +89 -0
- package/sdk/dist/query/check-gates.js.map +1 -0
- package/sdk/dist/query/check-ship-ready.d.ts +17 -0
- package/sdk/dist/query/check-ship-ready.d.ts.map +1 -0
- package/sdk/dist/query/check-ship-ready.js +121 -0
- package/sdk/dist/query/check-ship-ready.js.map +1 -0
- package/sdk/dist/query/check-verification-status.d.ts +10 -0
- package/sdk/dist/query/check-verification-status.d.ts.map +1 -0
- package/sdk/dist/query/check-verification-status.js +142 -0
- package/sdk/dist/query/check-verification-status.js.map +1 -0
- package/sdk/dist/query/command-aliases.generated.d.ts +31 -0
- package/sdk/dist/query/command-aliases.generated.d.ts.map +1 -0
- package/sdk/dist/query/command-aliases.generated.js +133 -0
- package/sdk/dist/query/command-aliases.generated.js.map +1 -0
- package/sdk/dist/query/command-catalog.d.ts +9 -0
- package/sdk/dist/query/command-catalog.d.ts.map +1 -0
- package/sdk/dist/query/command-catalog.js +17 -0
- package/sdk/dist/query/command-catalog.js.map +1 -0
- package/sdk/dist/query/command-definition.d.ts +19 -0
- package/sdk/dist/query/command-definition.d.ts.map +1 -0
- package/sdk/dist/query/command-definition.js +44 -0
- package/sdk/dist/query/command-definition.js.map +1 -0
- package/sdk/dist/query/command-family-handlers.d.ts +3 -0
- package/sdk/dist/query/command-family-handlers.d.ts.map +1 -0
- package/sdk/dist/query/command-family-handlers.js +101 -0
- package/sdk/dist/query/command-family-handlers.js.map +1 -0
- package/sdk/dist/query/command-manifest.d.ts +2 -0
- package/sdk/dist/query/command-manifest.d.ts.map +1 -0
- package/sdk/dist/query/command-manifest.init.d.ts +6 -0
- package/sdk/dist/query/command-manifest.init.d.ts.map +1 -0
- package/sdk/dist/query/command-manifest.init.js +23 -0
- package/sdk/dist/query/command-manifest.init.js.map +1 -0
- package/sdk/dist/query/command-manifest.js +17 -0
- package/sdk/dist/query/command-manifest.js.map +1 -0
- package/sdk/dist/query/command-manifest.non-family.d.ts +9 -0
- package/sdk/dist/query/command-manifest.non-family.d.ts.map +1 -0
- package/sdk/dist/query/command-manifest.non-family.js +60 -0
- package/sdk/dist/query/command-manifest.non-family.js.map +1 -0
- package/sdk/dist/query/command-manifest.phase.d.ts +6 -0
- package/sdk/dist/query/command-manifest.phase.d.ts.map +1 -0
- package/sdk/dist/query/command-manifest.phase.js +16 -0
- package/sdk/dist/query/command-manifest.phase.js.map +1 -0
- package/sdk/dist/query/command-manifest.phases.d.ts +7 -0
- package/sdk/dist/query/command-manifest.phases.d.ts.map +1 -0
- package/sdk/dist/query/command-manifest.phases.js +10 -0
- package/sdk/dist/query/command-manifest.phases.js.map +1 -0
- package/sdk/dist/query/command-manifest.roadmap.d.ts +6 -0
- package/sdk/dist/query/command-manifest.roadmap.d.ts.map +1 -0
- package/sdk/dist/query/command-manifest.roadmap.js +10 -0
- package/sdk/dist/query/command-manifest.roadmap.js.map +1 -0
- package/sdk/dist/query/command-manifest.state.d.ts +9 -0
- package/sdk/dist/query/command-manifest.state.d.ts.map +1 -0
- package/sdk/dist/query/command-manifest.state.js +30 -0
- package/sdk/dist/query/command-manifest.state.js.map +1 -0
- package/sdk/dist/query/command-manifest.types.d.ts +12 -0
- package/sdk/dist/query/command-manifest.types.d.ts.map +1 -0
- package/sdk/dist/query/command-manifest.types.js +2 -0
- package/sdk/dist/query/command-manifest.types.js.map +1 -0
- package/sdk/dist/query/command-manifest.validate.d.ts +6 -0
- package/sdk/dist/query/command-manifest.validate.d.ts.map +1 -0
- package/sdk/dist/query/command-manifest.validate.js +10 -0
- package/sdk/dist/query/command-manifest.validate.js.map +1 -0
- package/sdk/dist/query/command-manifest.verify.d.ts +6 -0
- package/sdk/dist/query/command-manifest.verify.d.ts.map +1 -0
- package/sdk/dist/query/command-manifest.verify.js +16 -0
- package/sdk/dist/query/command-manifest.verify.js.map +1 -0
- package/sdk/dist/query/command-static-catalog-domain.d.ts +3 -0
- package/sdk/dist/query/command-static-catalog-domain.d.ts.map +1 -0
- package/sdk/dist/query/command-static-catalog-domain.js +110 -0
- package/sdk/dist/query/command-static-catalog-domain.js.map +1 -0
- package/sdk/dist/query/command-static-catalog-foundation.d.ts +7 -0
- package/sdk/dist/query/command-static-catalog-foundation.d.ts.map +1 -0
- package/sdk/dist/query/command-static-catalog-foundation.js +106 -0
- package/sdk/dist/query/command-static-catalog-foundation.js.map +1 -0
- package/sdk/dist/query/command-topology.d.ts +32 -0
- package/sdk/dist/query/command-topology.d.ts.map +1 -0
- package/sdk/dist/query/command-topology.js +66 -0
- package/sdk/dist/query/command-topology.js.map +1 -0
- package/sdk/dist/query/commands-list.d.ts +14 -0
- package/sdk/dist/query/commands-list.d.ts.map +1 -0
- package/sdk/dist/query/commands-list.js +18 -0
- package/sdk/dist/query/commands-list.js.map +1 -0
- package/sdk/dist/query/commit.d.ts +179 -0
- package/sdk/dist/query/commit.d.ts.map +1 -0
- package/sdk/dist/query/commit.js +632 -0
- package/sdk/dist/query/commit.js.map +1 -0
- package/sdk/dist/query/config-gates.d.ts +12 -0
- package/sdk/dist/query/config-gates.d.ts.map +1 -0
- package/sdk/dist/query/config-gates.js +66 -0
- package/sdk/dist/query/config-gates.js.map +1 -0
- package/sdk/dist/query/config-mutation.d.ts +86 -0
- package/sdk/dist/query/config-mutation.d.ts.map +1 -0
- package/sdk/dist/query/config-mutation.js +602 -0
- package/sdk/dist/query/config-mutation.js.map +1 -0
- package/sdk/dist/query/config-query.d.ts +57 -0
- package/sdk/dist/query/config-query.d.ts.map +1 -0
- package/sdk/dist/query/config-query.js +277 -0
- package/sdk/dist/query/config-query.js.map +1 -0
- package/sdk/dist/query/config-schema.d.ts +19 -0
- package/sdk/dist/query/config-schema.d.ts.map +1 -0
- package/sdk/dist/query/config-schema.js +26 -0
- package/sdk/dist/query/config-schema.js.map +1 -0
- package/sdk/dist/query/decisions.d.ts +58 -0
- package/sdk/dist/query/decisions.d.ts.map +1 -0
- package/sdk/dist/query/decisions.js +165 -0
- package/sdk/dist/query/decisions.js.map +1 -0
- package/sdk/dist/query/detect-custom-files.d.ts +11 -0
- package/sdk/dist/query/detect-custom-files.d.ts.map +1 -0
- package/sdk/dist/query/detect-custom-files.js +89 -0
- package/sdk/dist/query/detect-custom-files.js.map +1 -0
- package/sdk/dist/query/detect-phase-type.d.ts +9 -0
- package/sdk/dist/query/detect-phase-type.d.ts.map +1 -0
- package/sdk/dist/query/detect-phase-type.js +124 -0
- package/sdk/dist/query/detect-phase-type.js.map +1 -0
- package/sdk/dist/query/docs-init.d.ts +26 -0
- package/sdk/dist/query/docs-init.d.ts.map +1 -0
- package/sdk/dist/query/docs-init.js +231 -0
- package/sdk/dist/query/docs-init.js.map +1 -0
- package/sdk/dist/query/fallow-audit.d.ts +44 -0
- package/sdk/dist/query/fallow-audit.d.ts.map +1 -0
- package/sdk/dist/query/fallow-audit.js +44 -0
- package/sdk/dist/query/fallow-audit.js.map +1 -0
- package/sdk/dist/query/frontmatter-mutation.d.ts +77 -0
- package/sdk/dist/query/frontmatter-mutation.d.ts.map +1 -0
- package/sdk/dist/query/frontmatter-mutation.js +299 -0
- package/sdk/dist/query/frontmatter-mutation.js.map +1 -0
- package/sdk/dist/query/frontmatter.d.ts +93 -0
- package/sdk/dist/query/frontmatter.d.ts.map +1 -0
- package/sdk/dist/query/frontmatter.js +364 -0
- package/sdk/dist/query/frontmatter.js.map +1 -0
- package/sdk/dist/query/helpers.d.ts +194 -0
- package/sdk/dist/query/helpers.d.ts.map +1 -0
- package/sdk/dist/query/helpers.js +540 -0
- package/sdk/dist/query/helpers.js.map +1 -0
- package/sdk/dist/query/index.d.ts +8 -0
- package/sdk/dist/query/index.d.ts.map +1 -0
- package/sdk/dist/query/index.js +6 -0
- package/sdk/dist/query/index.js.map +1 -0
- package/sdk/dist/query/init-complex.d.ts +47 -0
- package/sdk/dist/query/init-complex.d.ts.map +1 -0
- package/sdk/dist/query/init-complex.js +735 -0
- package/sdk/dist/query/init-complex.js.map +1 -0
- package/sdk/dist/query/init.d.ts +106 -0
- package/sdk/dist/query/init.d.ts.map +1 -0
- package/sdk/dist/query/init.js +1228 -0
- package/sdk/dist/query/init.js.map +1 -0
- package/sdk/dist/query/intel.d.ts +43 -0
- package/sdk/dist/query/intel.d.ts.map +1 -0
- package/sdk/dist/query/intel.js +416 -0
- package/sdk/dist/query/intel.js.map +1 -0
- package/sdk/dist/query/mutation-event-decorator.d.ts +5 -0
- package/sdk/dist/query/mutation-event-decorator.d.ts.map +1 -0
- package/sdk/dist/query/mutation-event-decorator.js +28 -0
- package/sdk/dist/query/mutation-event-decorator.js.map +1 -0
- package/sdk/dist/query/mutation-event-mapper.d.ts +4 -0
- package/sdk/dist/query/mutation-event-mapper.d.ts.map +1 -0
- package/sdk/dist/query/mutation-event-mapper.js +70 -0
- package/sdk/dist/query/mutation-event-mapper.js.map +1 -0
- package/sdk/dist/query/mvp.d.ts +113 -0
- package/sdk/dist/query/mvp.d.ts.map +1 -0
- package/sdk/dist/query/mvp.js +225 -0
- package/sdk/dist/query/mvp.js.map +1 -0
- package/sdk/dist/query/phase-filesystem-adapter.d.ts +4 -0
- package/sdk/dist/query/phase-filesystem-adapter.d.ts.map +1 -0
- package/sdk/dist/query/phase-filesystem-adapter.js +33 -0
- package/sdk/dist/query/phase-filesystem-adapter.js.map +1 -0
- package/sdk/dist/query/phase-lifecycle-policy.d.ts +34 -0
- package/sdk/dist/query/phase-lifecycle-policy.d.ts.map +1 -0
- package/sdk/dist/query/phase-lifecycle-policy.js +138 -0
- package/sdk/dist/query/phase-lifecycle-policy.js.map +1 -0
- package/sdk/dist/query/phase-lifecycle.d.ts +116 -0
- package/sdk/dist/query/phase-lifecycle.d.ts.map +1 -0
- package/sdk/dist/query/phase-lifecycle.js +1823 -0
- package/sdk/dist/query/phase-lifecycle.js.map +1 -0
- package/sdk/dist/query/phase-list-queries.d.ts +20 -0
- package/sdk/dist/query/phase-list-queries.d.ts.map +1 -0
- package/sdk/dist/query/phase-list-queries.js +129 -0
- package/sdk/dist/query/phase-list-queries.js.map +1 -0
- package/sdk/dist/query/phase-ready.d.ts +9 -0
- package/sdk/dist/query/phase-ready.d.ts.map +1 -0
- package/sdk/dist/query/phase-ready.js +132 -0
- package/sdk/dist/query/phase-ready.js.map +1 -0
- package/sdk/dist/query/phase-roadmap-mutation.d.ts +25 -0
- package/sdk/dist/query/phase-roadmap-mutation.d.ts.map +1 -0
- package/sdk/dist/query/phase-roadmap-mutation.js +76 -0
- package/sdk/dist/query/phase-roadmap-mutation.js.map +1 -0
- package/sdk/dist/query/phase-uat-passed.d.ts +46 -0
- package/sdk/dist/query/phase-uat-passed.d.ts.map +1 -0
- package/sdk/dist/query/phase-uat-passed.js +238 -0
- package/sdk/dist/query/phase-uat-passed.js.map +1 -0
- package/sdk/dist/query/phase.d.ts +104 -0
- package/sdk/dist/query/phase.d.ts.map +1 -0
- package/sdk/dist/query/phase.js +617 -0
- package/sdk/dist/query/phase.js.map +1 -0
- package/sdk/dist/query/pipeline.d.ts +53 -0
- package/sdk/dist/query/pipeline.d.ts.map +1 -0
- package/sdk/dist/query/pipeline.js +198 -0
- package/sdk/dist/query/pipeline.js.map +1 -0
- package/sdk/dist/query/plan-scan.d.ts +14 -0
- package/sdk/dist/query/plan-scan.d.ts.map +1 -0
- package/sdk/dist/query/plan-scan.js +70 -0
- package/sdk/dist/query/plan-scan.js.map +1 -0
- package/sdk/dist/query/plan-task-structure.d.ts +9 -0
- package/sdk/dist/query/plan-task-structure.d.ts.map +1 -0
- package/sdk/dist/query/plan-task-structure.js +59 -0
- package/sdk/dist/query/plan-task-structure.js.map +1 -0
- package/sdk/dist/query/profile-extract-messages.d.ts +40 -0
- package/sdk/dist/query/profile-extract-messages.d.ts.map +1 -0
- package/sdk/dist/query/profile-extract-messages.js +195 -0
- package/sdk/dist/query/profile-extract-messages.js.map +1 -0
- package/sdk/dist/query/profile-output.d.ts +11 -0
- package/sdk/dist/query/profile-output.d.ts.map +1 -0
- package/sdk/dist/query/profile-output.js +873 -0
- package/sdk/dist/query/profile-output.js.map +1 -0
- package/sdk/dist/query/profile-questionnaire-data.d.ts +21 -0
- package/sdk/dist/query/profile-questionnaire-data.d.ts.map +1 -0
- package/sdk/dist/query/profile-questionnaire-data.js +171 -0
- package/sdk/dist/query/profile-questionnaire-data.js.map +1 -0
- package/sdk/dist/query/profile-sample.d.ts +22 -0
- package/sdk/dist/query/profile-sample.d.ts.map +1 -0
- package/sdk/dist/query/profile-sample.js +136 -0
- package/sdk/dist/query/profile-sample.js.map +1 -0
- package/sdk/dist/query/profile-scan-sessions.d.ts +49 -0
- package/sdk/dist/query/profile-scan-sessions.d.ts.map +1 -0
- package/sdk/dist/query/profile-scan-sessions.js +137 -0
- package/sdk/dist/query/profile-scan-sessions.js.map +1 -0
- package/sdk/dist/query/profile.d.ts +61 -0
- package/sdk/dist/query/profile.d.ts.map +1 -0
- package/sdk/dist/query/profile.js +307 -0
- package/sdk/dist/query/profile.js.map +1 -0
- package/sdk/dist/query/progress.d.ts +77 -0
- package/sdk/dist/query/progress.d.ts.map +1 -0
- package/sdk/dist/query/progress.js +481 -0
- package/sdk/dist/query/progress.js.map +1 -0
- package/sdk/dist/query/prompt-budget.d.ts +14 -0
- package/sdk/dist/query/prompt-budget.d.ts.map +1 -0
- package/sdk/dist/query/prompt-budget.js +417 -0
- package/sdk/dist/query/prompt-budget.js.map +1 -0
- package/sdk/dist/query/query-cli-adapter.d.ts +8 -0
- package/sdk/dist/query/query-cli-adapter.d.ts.map +1 -0
- package/sdk/dist/query/query-cli-adapter.js +32 -0
- package/sdk/dist/query/query-cli-adapter.js.map +1 -0
- package/sdk/dist/query/query-cli-output.d.ts +9 -0
- package/sdk/dist/query/query-cli-output.d.ts.map +1 -0
- package/sdk/dist/query/query-cli-output.js +28 -0
- package/sdk/dist/query/query-cli-output.js.map +1 -0
- package/sdk/dist/query/query-command-diagnosis.d.ts +6 -0
- package/sdk/dist/query/query-command-diagnosis.d.ts.map +1 -0
- package/sdk/dist/query/query-command-diagnosis.js +6 -0
- package/sdk/dist/query/query-command-diagnosis.js.map +1 -0
- package/sdk/dist/query/query-command-resolution-strategy.d.ts +29 -0
- package/sdk/dist/query/query-command-resolution-strategy.d.ts.map +1 -0
- package/sdk/dist/query/query-command-resolution-strategy.js +103 -0
- package/sdk/dist/query/query-command-resolution-strategy.js.map +1 -0
- package/sdk/dist/query/query-command-semantics.d.ts +7 -0
- package/sdk/dist/query/query-command-semantics.d.ts.map +1 -0
- package/sdk/dist/query/query-command-semantics.js +7 -0
- package/sdk/dist/query/query-command-semantics.js.map +1 -0
- package/sdk/dist/query/query-dispatch-contract.d.ts +21 -0
- package/sdk/dist/query/query-dispatch-contract.d.ts.map +1 -0
- package/sdk/dist/query/query-dispatch-contract.js +2 -0
- package/sdk/dist/query/query-dispatch-contract.js.map +1 -0
- package/sdk/dist/query/query-dispatch-error-mapper.d.ts +6 -0
- package/sdk/dist/query/query-dispatch-error-mapper.d.ts.map +1 -0
- package/sdk/dist/query/query-dispatch-error-mapper.js +6 -0
- package/sdk/dist/query/query-dispatch-error-mapper.js.map +1 -0
- package/sdk/dist/query/query-dispatch-formatting.d.ts +6 -0
- package/sdk/dist/query/query-dispatch-formatting.d.ts.map +1 -0
- package/sdk/dist/query/query-dispatch-formatting.js +6 -0
- package/sdk/dist/query/query-dispatch-formatting.js.map +1 -0
- package/sdk/dist/query/query-dispatch-observability.d.ts +2 -0
- package/sdk/dist/query/query-dispatch-observability.d.ts.map +1 -0
- package/sdk/dist/query/query-dispatch-observability.js +7 -0
- package/sdk/dist/query/query-dispatch-observability.js.map +1 -0
- package/sdk/dist/query/query-dispatch.d.ts +48 -0
- package/sdk/dist/query/query-dispatch.d.ts.map +1 -0
- package/sdk/dist/query/query-dispatch.js +175 -0
- package/sdk/dist/query/query-dispatch.js.map +1 -0
- package/sdk/dist/query/query-error-details-schema.d.ts +19 -0
- package/sdk/dist/query/query-error-details-schema.d.ts.map +1 -0
- package/sdk/dist/query/query-error-details-schema.js +10 -0
- package/sdk/dist/query/query-error-details-schema.js.map +1 -0
- package/sdk/dist/query/query-error-taxonomy.d.ts +38 -0
- package/sdk/dist/query/query-error-taxonomy.d.ts.map +1 -0
- package/sdk/dist/query/query-error-taxonomy.js +74 -0
- package/sdk/dist/query/query-error-taxonomy.js.map +1 -0
- package/sdk/dist/query/query-fallback-bridge-adapter.d.ts +14 -0
- package/sdk/dist/query/query-fallback-bridge-adapter.d.ts.map +1 -0
- package/sdk/dist/query/query-fallback-bridge-adapter.js +33 -0
- package/sdk/dist/query/query-fallback-bridge-adapter.js.map +1 -0
- package/sdk/dist/query/query-fallback-executor.d.ts +11 -0
- package/sdk/dist/query/query-fallback-executor.d.ts.map +1 -0
- package/sdk/dist/query/query-fallback-executor.js +31 -0
- package/sdk/dist/query/query-fallback-executor.js.map +1 -0
- package/sdk/dist/query/query-fallback-output-classifier.d.ts +6 -0
- package/sdk/dist/query/query-fallback-output-classifier.d.ts.map +1 -0
- package/sdk/dist/query/query-fallback-output-classifier.js +27 -0
- package/sdk/dist/query/query-fallback-output-classifier.js.map +1 -0
- package/sdk/dist/query/query-fallback-policy.d.ts +6 -0
- package/sdk/dist/query/query-fallback-policy.d.ts.map +1 -0
- package/sdk/dist/query/query-fallback-policy.js +7 -0
- package/sdk/dist/query/query-fallback-policy.js.map +1 -0
- package/sdk/dist/query/query-native-dispatch-adapter.d.ts +7 -0
- package/sdk/dist/query/query-native-dispatch-adapter.d.ts.map +1 -0
- package/sdk/dist/query/query-native-dispatch-adapter.js +6 -0
- package/sdk/dist/query/query-native-dispatch-adapter.js.map +1 -0
- package/sdk/dist/query/query-policy-capability.d.ts +10 -0
- package/sdk/dist/query/query-policy-capability.d.ts.map +1 -0
- package/sdk/dist/query/query-policy-capability.js +17 -0
- package/sdk/dist/query/query-policy-capability.js.map +1 -0
- package/sdk/dist/query/query-runtime-context.d.ts +19 -0
- package/sdk/dist/query/query-runtime-context.d.ts.map +1 -0
- package/sdk/dist/query/query-runtime-context.js +31 -0
- package/sdk/dist/query/query-runtime-context.js.map +1 -0
- package/sdk/dist/query/query-unknown-command-hints.d.ts +2 -0
- package/sdk/dist/query/query-unknown-command-hints.d.ts.map +1 -0
- package/sdk/dist/query/query-unknown-command-hints.js +6 -0
- package/sdk/dist/query/query-unknown-command-hints.js.map +1 -0
- package/sdk/dist/query/registry-assembly-descriptor.d.ts +12 -0
- package/sdk/dist/query/registry-assembly-descriptor.d.ts.map +1 -0
- package/sdk/dist/query/registry-assembly-descriptor.js +61 -0
- package/sdk/dist/query/registry-assembly-descriptor.js.map +1 -0
- package/sdk/dist/query/registry-assembly-invariants.d.ts +30 -0
- package/sdk/dist/query/registry-assembly-invariants.d.ts.map +1 -0
- package/sdk/dist/query/registry-assembly-invariants.js +77 -0
- package/sdk/dist/query/registry-assembly-invariants.js.map +1 -0
- package/sdk/dist/query/registry-assembly.d.ts +10 -0
- package/sdk/dist/query/registry-assembly.d.ts.map +1 -0
- package/sdk/dist/query/registry-assembly.js +53 -0
- package/sdk/dist/query/registry-assembly.js.map +1 -0
- package/sdk/dist/query/registry.d.ts +90 -0
- package/sdk/dist/query/registry.d.ts.map +1 -0
- package/sdk/dist/query/registry.js +129 -0
- package/sdk/dist/query/registry.js.map +1 -0
- package/sdk/dist/query/requirements-extract-from-plans.d.ts +9 -0
- package/sdk/dist/query/requirements-extract-from-plans.d.ts.map +1 -0
- package/sdk/dist/query/requirements-extract-from-plans.js +76 -0
- package/sdk/dist/query/requirements-extract-from-plans.js.map +1 -0
- package/sdk/dist/query/roadmap-update-plan-progress.d.ts +11 -0
- package/sdk/dist/query/roadmap-update-plan-progress.d.ts.map +1 -0
- package/sdk/dist/query/roadmap-update-plan-progress.js +124 -0
- package/sdk/dist/query/roadmap-update-plan-progress.js.map +1 -0
- package/sdk/dist/query/roadmap.d.ts +160 -0
- package/sdk/dist/query/roadmap.d.ts.map +1 -0
- package/sdk/dist/query/roadmap.js +982 -0
- package/sdk/dist/query/roadmap.js.map +1 -0
- package/sdk/dist/query/route-next-action.d.ts +9 -0
- package/sdk/dist/query/route-next-action.d.ts.map +1 -0
- package/sdk/dist/query/route-next-action.js +318 -0
- package/sdk/dist/query/route-next-action.js.map +1 -0
- package/sdk/dist/query/schema-detect.d.ts +21 -0
- package/sdk/dist/query/schema-detect.d.ts.map +1 -0
- package/sdk/dist/query/schema-detect.js +146 -0
- package/sdk/dist/query/schema-detect.js.map +1 -0
- package/sdk/dist/query/secrets.d.ts +27 -0
- package/sdk/dist/query/secrets.d.ts.map +1 -0
- package/sdk/dist/query/secrets.js +42 -0
- package/sdk/dist/query/secrets.js.map +1 -0
- package/sdk/dist/query/skill-manifest.d.ts +50 -0
- package/sdk/dist/query/skill-manifest.d.ts.map +1 -0
- package/sdk/dist/query/skill-manifest.js +171 -0
- package/sdk/dist/query/skill-manifest.js.map +1 -0
- package/sdk/dist/query/skills.d.ts +27 -0
- package/sdk/dist/query/skills.d.ts.map +1 -0
- package/sdk/dist/query/skills.js +137 -0
- package/sdk/dist/query/skills.js.map +1 -0
- package/sdk/dist/query/state-document.d.ts +14 -0
- package/sdk/dist/query/state-document.d.ts.map +1 -0
- package/sdk/dist/query/state-document.js +110 -0
- package/sdk/dist/query/state-document.js.map +1 -0
- package/sdk/dist/query/state-mutation.d.ts +224 -0
- package/sdk/dist/query/state-mutation.d.ts.map +1 -0
- package/sdk/dist/query/state-mutation.js +1635 -0
- package/sdk/dist/query/state-mutation.js.map +1 -0
- package/sdk/dist/query/state-project-load.d.ts +23 -0
- package/sdk/dist/query/state-project-load.d.ts.map +1 -0
- package/sdk/dist/query/state-project-load.js +75 -0
- package/sdk/dist/query/state-project-load.js.map +1 -0
- package/sdk/dist/query/state.d.ts +78 -0
- package/sdk/dist/query/state.d.ts.map +1 -0
- package/sdk/dist/query/state.js +443 -0
- package/sdk/dist/query/state.js.map +1 -0
- package/sdk/dist/query/summary.d.ts +18 -0
- package/sdk/dist/query/summary.d.ts.map +1 -0
- package/sdk/dist/query/summary.js +249 -0
- package/sdk/dist/query/summary.js.map +1 -0
- package/sdk/dist/query/template.d.ts +46 -0
- package/sdk/dist/query/template.d.ts.map +1 -0
- package/sdk/dist/query/template.js +210 -0
- package/sdk/dist/query/template.js.map +1 -0
- package/sdk/dist/query/uat.d.ts +42 -0
- package/sdk/dist/query/uat.d.ts.map +1 -0
- package/sdk/dist/query/uat.js +339 -0
- package/sdk/dist/query/uat.js.map +1 -0
- package/sdk/dist/query/utils.d.ts +59 -0
- package/sdk/dist/query/utils.d.ts.map +1 -0
- package/sdk/dist/query/utils.js +74 -0
- package/sdk/dist/query/utils.js.map +1 -0
- package/sdk/dist/query/validate.d.ts +67 -0
- package/sdk/dist/query/validate.d.ts.map +1 -0
- package/sdk/dist/query/validate.js +1001 -0
- package/sdk/dist/query/validate.js.map +1 -0
- package/sdk/dist/query/verify.d.ts +98 -0
- package/sdk/dist/query/verify.d.ts.map +1 -0
- package/sdk/dist/query/verify.js +593 -0
- package/sdk/dist/query/verify.js.map +1 -0
- package/sdk/dist/query/websearch.d.ts +24 -0
- package/sdk/dist/query/websearch.d.ts.map +1 -0
- package/sdk/dist/query/websearch.js +68 -0
- package/sdk/dist/query/websearch.js.map +1 -0
- package/sdk/dist/query/workspace.d.ts +62 -0
- package/sdk/dist/query/workspace.d.ts.map +1 -0
- package/sdk/dist/query/workspace.js +104 -0
- package/sdk/dist/query/workspace.js.map +1 -0
- package/sdk/dist/query/workstream-inventory.d.ts +24 -0
- package/sdk/dist/query/workstream-inventory.d.ts.map +1 -0
- package/sdk/dist/query/workstream-inventory.js +120 -0
- package/sdk/dist/query/workstream-inventory.js.map +1 -0
- package/sdk/dist/query/workstream.d.ts +35 -0
- package/sdk/dist/query/workstream.d.ts.map +1 -0
- package/sdk/dist/query/workstream.js +298 -0
- package/sdk/dist/query/workstream.js.map +1 -0
- package/sdk/dist/query/worktree.d.ts +9 -0
- package/sdk/dist/query/worktree.d.ts.map +1 -0
- package/sdk/dist/query/worktree.js +79 -0
- package/sdk/dist/query/worktree.js.map +1 -0
- package/sdk/dist/query-command-executor.d.ts +22 -0
- package/sdk/dist/query-command-executor.d.ts.map +1 -0
- package/sdk/dist/query-command-executor.js +22 -0
- package/sdk/dist/query-command-executor.js.map +1 -0
- package/sdk/dist/query-execution-policy.d.ts +24 -0
- package/sdk/dist/query-execution-policy.d.ts.map +1 -0
- package/sdk/dist/query-execution-policy.js +27 -0
- package/sdk/dist/query-execution-policy.js.map +1 -0
- package/sdk/dist/query-failure-classification.d.ts +9 -0
- package/sdk/dist/query-failure-classification.d.ts.map +1 -0
- package/sdk/dist/query-failure-classification.js +32 -0
- package/sdk/dist/query-failure-classification.js.map +1 -0
- package/sdk/dist/query-gsd-tools-path.d.ts +2 -0
- package/sdk/dist/query-gsd-tools-path.d.ts.map +1 -0
- package/sdk/dist/query-gsd-tools-path.js +2 -0
- package/sdk/dist/query-gsd-tools-path.js.map +1 -0
- package/sdk/dist/query-gsd-tools-runtime.d.ts +20 -0
- package/sdk/dist/query-gsd-tools-runtime.d.ts.map +1 -0
- package/sdk/dist/query-gsd-tools-runtime.js +47 -0
- package/sdk/dist/query-gsd-tools-runtime.js.map +1 -0
- package/sdk/dist/query-hotpath-methods.d.ts +19 -0
- package/sdk/dist/query-hotpath-methods.d.ts.map +1 -0
- package/sdk/dist/query-hotpath-methods.js +34 -0
- package/sdk/dist/query-hotpath-methods.js.map +1 -0
- package/sdk/dist/query-native-direct-adapter.d.ts +20 -0
- package/sdk/dist/query-native-direct-adapter.d.ts.map +1 -0
- package/sdk/dist/query-native-direct-adapter.js +52 -0
- package/sdk/dist/query-native-direct-adapter.js.map +1 -0
- package/sdk/dist/query-native-hotpath-adapter.d.ts +15 -0
- package/sdk/dist/query-native-hotpath-adapter.d.ts.map +1 -0
- package/sdk/dist/query-native-hotpath-adapter.js +32 -0
- package/sdk/dist/query-native-hotpath-adapter.js.map +1 -0
- package/sdk/dist/query-raw-output-projection.d.ts +6 -0
- package/sdk/dist/query-raw-output-projection.d.ts.map +1 -0
- package/sdk/dist/query-raw-output-projection.js +86 -0
- package/sdk/dist/query-raw-output-projection.js.map +1 -0
- package/sdk/dist/query-runtime-bridge.d.ts +61 -0
- package/sdk/dist/query-runtime-bridge.d.ts.map +1 -0
- package/sdk/dist/query-runtime-bridge.js +144 -0
- package/sdk/dist/query-runtime-bridge.js.map +1 -0
- package/sdk/dist/query-subprocess-adapter.d.ts +18 -0
- package/sdk/dist/query-subprocess-adapter.d.ts.map +1 -0
- package/sdk/dist/query-subprocess-adapter.js +92 -0
- package/sdk/dist/query-subprocess-adapter.js.map +1 -0
- package/sdk/dist/query-tools-error-factory.d.ts +16 -0
- package/sdk/dist/query-tools-error-factory.d.ts.map +1 -0
- package/sdk/dist/query-tools-error-factory.js +33 -0
- package/sdk/dist/query-tools-error-factory.js.map +1 -0
- package/sdk/dist/research-gate.d.ts +24 -0
- package/sdk/dist/research-gate.d.ts.map +1 -0
- package/sdk/dist/research-gate.js +70 -0
- package/sdk/dist/research-gate.js.map +1 -0
- package/sdk/dist/runtime-bridge-sync/index.d.ts +96 -0
- package/sdk/dist/runtime-bridge-sync/index.d.ts.map +1 -0
- package/sdk/dist/runtime-bridge-sync/index.js +109 -0
- package/sdk/dist/runtime-bridge-sync/index.js.map +1 -0
- package/sdk/dist/runtime-bridge-sync/worker.d.ts +2 -0
- package/sdk/dist/runtime-bridge-sync/worker.d.ts.map +1 -0
- package/sdk/dist/runtime-bridge-sync/worker.js +180 -0
- package/sdk/dist/runtime-bridge-sync/worker.js.map +1 -0
- package/sdk/dist/runtime-gate.d.ts +14 -0
- package/sdk/dist/runtime-gate.d.ts.map +1 -0
- package/sdk/dist/runtime-gate.js +48 -0
- package/sdk/dist/runtime-gate.js.map +1 -0
- package/sdk/dist/sdk-package-compatibility.d.ts +38 -0
- package/sdk/dist/sdk-package-compatibility.d.ts.map +1 -0
- package/sdk/dist/sdk-package-compatibility.js +90 -0
- package/sdk/dist/sdk-package-compatibility.js.map +1 -0
- package/sdk/dist/session-runner.d.ts +40 -0
- package/sdk/dist/session-runner.d.ts.map +1 -0
- package/sdk/dist/session-runner.js +274 -0
- package/sdk/dist/session-runner.js.map +1 -0
- package/sdk/dist/tool-scoping.d.ts +31 -0
- package/sdk/dist/tool-scoping.d.ts.map +1 -0
- package/sdk/dist/tool-scoping.js +54 -0
- package/sdk/dist/tool-scoping.js.map +1 -0
- package/sdk/dist/types.d.ts +794 -0
- package/sdk/dist/types.d.ts.map +1 -0
- package/sdk/dist/types.js +77 -0
- package/sdk/dist/types.js.map +1 -0
- package/sdk/dist/workstream-inventory/builder.d.ts +88 -0
- package/sdk/dist/workstream-inventory/builder.d.ts.map +1 -0
- package/sdk/dist/workstream-inventory/builder.js +84 -0
- package/sdk/dist/workstream-inventory/builder.js.map +1 -0
- package/sdk/dist/workstream-name-policy.d.ts +37 -0
- package/sdk/dist/workstream-name-policy.d.ts.map +1 -0
- package/sdk/dist/workstream-name-policy.js +53 -0
- package/sdk/dist/workstream-name-policy.js.map +1 -0
- package/sdk/dist/workstream-utils.d.ts +23 -0
- package/sdk/dist/workstream-utils.d.ts.map +1 -0
- package/sdk/dist/workstream-utils.js +34 -0
- package/sdk/dist/workstream-utils.js.map +1 -0
- package/sdk/dist/ws-transport.d.ts +32 -0
- package/sdk/dist/ws-transport.d.ts.map +1 -0
- package/sdk/dist/ws-transport.js +84 -0
- package/sdk/dist/ws-transport.js.map +1 -0
- package/sdk/package-lock.json +2530 -0
- package/sdk/package.json +77 -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/shared/config-defaults.manifest.json +75 -0
- package/sdk/shared/config-schema.manifest.json +151 -0
- package/sdk/shared/model-catalog.json +122 -0
- package/sdk/src/assembled-prompts.test.ts +349 -0
- package/sdk/src/bug-3589-planning-paths-validation.test.ts +89 -0
- package/sdk/src/bug-3591-gsdtools-runtime-workstream.test.ts +179 -0
- package/sdk/src/cli-transport.test.ts +388 -0
- package/sdk/src/cli-transport.ts +130 -0
- package/sdk/src/cli.test.ts +426 -0
- package/sdk/src/cli.ts +589 -0
- package/sdk/src/config.test.ts +277 -0
- package/sdk/src/config.ts +202 -0
- package/sdk/src/configuration/index.test.ts +318 -0
- package/sdk/src/configuration/index.ts +325 -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 +181 -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/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 +17 -0
- package/sdk/src/golden/golden-policy.test.ts +8 -0
- package/sdk/src/golden/golden-policy.ts +120 -0
- package/sdk/src/golden/golden.integration.test.ts +1031 -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 +133 -0
- package/sdk/src/golden/registry-canonical-commands.ts +31 -0
- package/sdk/src/gsd-tools-error.test.ts +21 -0
- package/sdk/src/gsd-tools-error.ts +65 -0
- package/sdk/src/gsd-tools.test.ts +472 -0
- package/sdk/src/gsd-tools.ts +237 -0
- package/sdk/src/gsd-transport-policy.test.ts +34 -0
- package/sdk/src/gsd-transport-policy.ts +48 -0
- package/sdk/src/gsd-transport.test.ts +299 -0
- package/sdk/src/gsd-transport.ts +118 -0
- package/sdk/src/index.ts +366 -0
- package/sdk/src/init-e2e.integration.test.ts +138 -0
- package/sdk/src/init-runner.test.ts +740 -0
- package/sdk/src/init-runner.ts +734 -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/model-catalog.ts +70 -0
- package/sdk/src/phase-prompt.ts +259 -0
- package/sdk/src/phase-runner.integration.test.ts +377 -0
- package/sdk/src/phase-runner.test.ts +3660 -0
- package/sdk/src/phase-runner.ts +1442 -0
- package/sdk/src/plan-parser.test.ts +579 -0
- package/sdk/src/plan-parser.ts +431 -0
- package/sdk/src/planning-journal.test.ts +70 -0
- package/sdk/src/planning-journal.ts +153 -0
- package/sdk/src/planning-runtime.test.ts +29 -0
- package/sdk/src/planning-runtime.ts +100 -0
- package/sdk/src/project-root/index.test.ts +186 -0
- package/sdk/src/project-root/index.ts +144 -0
- package/sdk/src/prompt-builder.test.ts +318 -0
- package/sdk/src/prompt-builder.ts +218 -0
- package/sdk/src/prompt-sanitizer.test.ts +260 -0
- package/sdk/src/prompt-sanitizer.ts +116 -0
- package/sdk/src/query/QUERY-HANDLERS.md +349 -0
- package/sdk/src/query/active-workstream-store.ts +50 -0
- package/sdk/src/query/agent-failure-classifier.test.ts +157 -0
- package/sdk/src/query/agent-failure-classifier.ts +105 -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 +49 -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-decision-coverage.test.ts +519 -0
- package/sdk/src/query/check-decision-coverage.ts +554 -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 +303 -0
- package/sdk/src/query/check-ship-ready.ts +136 -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/command-aliases.generated.ts +154 -0
- package/sdk/src/query/command-catalog.ts +31 -0
- package/sdk/src/query/command-definition.test.ts +47 -0
- package/sdk/src/query/command-definition.ts +70 -0
- package/sdk/src/query/command-family-handlers.ts +123 -0
- package/sdk/src/query/command-manifest.init.ts +24 -0
- package/sdk/src/query/command-manifest.non-family.ts +86 -0
- package/sdk/src/query/command-manifest.phase.ts +17 -0
- package/sdk/src/query/command-manifest.phases.ts +11 -0
- package/sdk/src/query/command-manifest.roadmap.ts +11 -0
- package/sdk/src/query/command-manifest.state.ts +31 -0
- package/sdk/src/query/command-manifest.ts +17 -0
- package/sdk/src/query/command-manifest.types.ts +13 -0
- package/sdk/src/query/command-manifest.validate.ts +11 -0
- package/sdk/src/query/command-manifest.verify.ts +17 -0
- package/sdk/src/query/command-resolution.test.ts +70 -0
- package/sdk/src/query/command-seam-coverage.test.ts +118 -0
- package/sdk/src/query/command-static-catalog-domain.ts +111 -0
- package/sdk/src/query/command-static-catalog-foundation.ts +111 -0
- package/sdk/src/query/command-topology.test.ts +28 -0
- package/sdk/src/query/command-topology.ts +114 -0
- package/sdk/src/query/commands-list.test.ts +36 -0
- package/sdk/src/query/commands-list.ts +19 -0
- package/sdk/src/query/commit.test.ts +485 -0
- package/sdk/src/query/commit.ts +717 -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 +598 -0
- package/sdk/src/query/config-mutation.ts +705 -0
- package/sdk/src/query/config-query.test.ts +472 -0
- package/sdk/src/query/config-query.ts +314 -0
- package/sdk/src/query/config-schema.ts +35 -0
- package/sdk/src/query/decisions.test.ts +221 -0
- package/sdk/src/query/decisions.ts +196 -0
- package/sdk/src/query/decomposed-handlers.test.ts +431 -0
- package/sdk/src/query/detect-custom-files.test.ts +115 -0
- package/sdk/src/query/detect-custom-files.ts +96 -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 +258 -0
- package/sdk/src/query/fallow-audit.ts +88 -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 +328 -0
- package/sdk/src/query/frontmatter.test.ts +326 -0
- package/sdk/src/query/frontmatter.ts +395 -0
- package/sdk/src/query/helpers.test.ts +615 -0
- package/sdk/src/query/helpers.ts +566 -0
- package/sdk/src/query/index-thin-seam.test.ts +16 -0
- package/sdk/src/query/index.ts +9 -0
- package/sdk/src/query/init-complex.test.ts +788 -0
- package/sdk/src/query/init-complex.ts +815 -0
- package/sdk/src/query/init-workstream-milestone-op.test.ts +321 -0
- package/sdk/src/query/init.test.ts +791 -0
- package/sdk/src/query/init.ts +1335 -0
- package/sdk/src/query/intel.test.ts +90 -0
- package/sdk/src/query/intel.ts +404 -0
- package/sdk/src/query/mutation-event-decorator.test.ts +45 -0
- package/sdk/src/query/mutation-event-decorator.ts +37 -0
- package/sdk/src/query/mutation-event-mapper.test.ts +33 -0
- package/sdk/src/query/mutation-event-mapper.ts +102 -0
- package/sdk/src/query/mvp.test.ts +335 -0
- package/sdk/src/query/mvp.ts +292 -0
- package/sdk/src/query/normalize-query-command.test.ts +102 -0
- package/sdk/src/query/phase-filesystem-adapter.ts +35 -0
- package/sdk/src/query/phase-lifecycle-policy.ts +171 -0
- package/sdk/src/query/phase-lifecycle.test.ts +1971 -0
- package/sdk/src/query/phase-lifecycle.ts +2210 -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 +159 -0
- package/sdk/src/query/phase-roadmap-mutation.ts +82 -0
- package/sdk/src/query/phase-uat-passed.test.ts +593 -0
- package/sdk/src/query/phase-uat-passed.ts +297 -0
- package/sdk/src/query/phase.test.ts +693 -0
- package/sdk/src/query/phase.ts +741 -0
- package/sdk/src/query/pipeline.test.ts +169 -0
- package/sdk/src/query/pipeline.ts +243 -0
- package/sdk/src/query/plan-scan.test.ts +35 -0
- package/sdk/src/query/plan-scan.ts +82 -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/policy-convergence.test.ts +28 -0
- package/sdk/src/query/profile-extract-messages.ts +247 -0
- package/sdk/src/query/profile-output.ts +929 -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 +136 -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/prompt-budget.ts +556 -0
- package/sdk/src/query/query-cli-adapter.test.ts +79 -0
- package/sdk/src/query/query-cli-adapter.ts +39 -0
- package/sdk/src/query/query-cli-output.test.ts +33 -0
- package/sdk/src/query/query-cli-output.ts +35 -0
- package/sdk/src/query/query-command-diagnosis.test.ts +22 -0
- package/sdk/src/query/query-command-diagnosis.ts +5 -0
- package/sdk/src/query/query-command-resolution-strategy.test.ts +34 -0
- package/sdk/src/query/query-command-resolution-strategy.ts +121 -0
- package/sdk/src/query/query-command-semantics.test.ts +22 -0
- package/sdk/src/query/query-command-semantics.ts +22 -0
- package/sdk/src/query/query-dispatch-contract.ts +30 -0
- package/sdk/src/query/query-dispatch-error-mapper.ts +5 -0
- package/sdk/src/query/query-dispatch-formatting.ts +5 -0
- package/sdk/src/query/query-dispatch-observability.ts +6 -0
- package/sdk/src/query/query-dispatch.test.ts +699 -0
- package/sdk/src/query/query-dispatch.ts +243 -0
- package/sdk/src/query/query-error-details-schema.ts +29 -0
- package/sdk/src/query/query-error-taxonomy.test.ts +39 -0
- package/sdk/src/query/query-error-taxonomy.ts +117 -0
- package/sdk/src/query/query-fallback-bridge-adapter.test.ts +32 -0
- package/sdk/src/query/query-fallback-bridge-adapter.ts +54 -0
- package/sdk/src/query/query-fallback-executor.test.ts +82 -0
- package/sdk/src/query/query-fallback-executor.ts +44 -0
- package/sdk/src/query/query-fallback-output-classifier.test.ts +36 -0
- package/sdk/src/query/query-fallback-output-classifier.ts +31 -0
- package/sdk/src/query/query-fallback-policy.test.ts +13 -0
- package/sdk/src/query/query-fallback-policy.ts +11 -0
- package/sdk/src/query/query-native-dispatch-adapter.ts +16 -0
- package/sdk/src/query/query-policy-capability.test.ts +10 -0
- package/sdk/src/query/query-policy-capability.ts +26 -0
- package/sdk/src/query/query-policy-snapshot.test.ts +9 -0
- package/sdk/src/query/query-registry-capability.test.ts +14 -0
- package/sdk/src/query/query-runtime-context.ts +44 -0
- package/sdk/src/query/query-unknown-command-hints.test.ts +9 -0
- package/sdk/src/query/query-unknown-command-hints.ts +5 -0
- package/sdk/src/query/registry-assembly-descriptor.ts +87 -0
- package/sdk/src/query/registry-assembly-invariants.ts +127 -0
- package/sdk/src/query/registry-assembly.test.ts +138 -0
- package/sdk/src/query/registry-assembly.ts +78 -0
- package/sdk/src/query/registry.test.ts +208 -0
- package/sdk/src/query/registry.ts +142 -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.test.ts +233 -0
- package/sdk/src/query/roadmap-update-plan-progress.ts +159 -0
- package/sdk/src/query/roadmap.test.ts +1250 -0
- package/sdk/src/query/roadmap.ts +1131 -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/schema-detect.ts +189 -0
- package/sdk/src/query/secrets.test.ts +66 -0
- package/sdk/src/query/secrets.ts +43 -0
- package/sdk/src/query/skill-manifest.test.ts +62 -0
- package/sdk/src/query/skill-manifest.ts +216 -0
- package/sdk/src/query/skills.test.ts +234 -0
- package/sdk/src/query/skills.ts +143 -0
- package/sdk/src/query/state-document.test.ts +197 -0
- package/sdk/src/query/state-document.ts +129 -0
- package/sdk/src/query/state-mutation.test.ts +1210 -0
- package/sdk/src/query/state-mutation.ts +1814 -0
- package/sdk/src/query/state-project-load.ts +80 -0
- package/sdk/src/query/state.test.ts +616 -0
- package/sdk/src/query/state.ts +476 -0
- package/sdk/src/query/sub-repos-root.integration.test.ts +79 -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 +365 -0
- package/sdk/src/query/utils.test.ts +82 -0
- package/sdk/src/query/utils.ts +106 -0
- package/sdk/src/query/validate.test.ts +924 -0
- package/sdk/src/query/validate.ts +1054 -0
- package/sdk/src/query/verify.test.ts +414 -0
- package/sdk/src/query/verify.ts +656 -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 +120 -0
- package/sdk/src/query/workspace.ts +145 -0
- package/sdk/src/query/workstream-inventory.ts +143 -0
- package/sdk/src/query/workstream.test.ts +153 -0
- package/sdk/src/query/workstream.ts +324 -0
- package/sdk/src/query/worktree.ts +84 -0
- package/sdk/src/query-command-executor.ts +31 -0
- package/sdk/src/query-execution-policy.test.ts +52 -0
- package/sdk/src/query-execution-policy.ts +46 -0
- package/sdk/src/query-failure-classification.test.ts +23 -0
- package/sdk/src/query-failure-classification.ts +42 -0
- package/sdk/src/query-gsd-tools-path.ts +1 -0
- package/sdk/src/query-gsd-tools-runtime.ts +89 -0
- package/sdk/src/query-hotpath-methods.ts +48 -0
- package/sdk/src/query-native-direct-adapter.test.ts +35 -0
- package/sdk/src/query-native-direct-adapter.ts +70 -0
- package/sdk/src/query-native-hotpath-adapter.test.ts +43 -0
- package/sdk/src/query-native-hotpath-adapter.ts +45 -0
- package/sdk/src/query-raw-output-projection.test.ts +39 -0
- package/sdk/src/query-raw-output-projection.ts +93 -0
- package/sdk/src/query-runtime-bridge.test.ts +150 -0
- package/sdk/src/query-runtime-bridge.ts +215 -0
- package/sdk/src/query-runtime-seam-coverage.test.ts +20 -0
- package/sdk/src/query-subprocess-adapter.test.ts +84 -0
- package/sdk/src/query-subprocess-adapter.ts +146 -0
- package/sdk/src/query-tools-error-factory.test.ts +35 -0
- package/sdk/src/query-tools-error-factory.ts +76 -0
- package/sdk/src/research-gate.test.ts +190 -0
- package/sdk/src/research-gate.ts +94 -0
- package/sdk/src/runtime-bridge-options.test.ts +33 -0
- package/sdk/src/runtime-bridge-sync/index.test.ts +164 -0
- package/sdk/src/runtime-bridge-sync/index.ts +154 -0
- package/sdk/src/runtime-bridge-sync/projectdir-regression.test.ts +150 -0
- package/sdk/src/runtime-bridge-sync/worker.ts +224 -0
- package/sdk/src/runtime-gate.test.ts +84 -0
- package/sdk/src/runtime-gate.ts +52 -0
- package/sdk/src/sdk-package-compatibility.test.ts +97 -0
- package/sdk/src/sdk-package-compatibility.ts +141 -0
- package/sdk/src/session-runner.test.ts +164 -0
- package/sdk/src/session-runner.ts +327 -0
- package/sdk/src/tool-scoping.test.ts +160 -0
- package/sdk/src/tool-scoping.ts +61 -0
- package/sdk/src/types.ts +927 -0
- package/sdk/src/workflow-agent-skills-consistency.test.ts +98 -0
- package/sdk/src/workstream-inventory/builder.test.ts +241 -0
- package/sdk/src/workstream-inventory/builder.ts +170 -0
- package/sdk/src/workstream-name-policy.ts +57 -0
- package/sdk/src/workstream-utils.ts +36 -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,1823 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase lifecycle handlers — add, insert, scaffold operations.
|
|
3
|
+
*
|
|
4
|
+
* Ported from get-shit-done/bin/lib/phase.cjs and commands.cjs.
|
|
5
|
+
* Provides phaseAdd (append phase), phaseAddBatch (append multiple phases),
|
|
6
|
+
* phaseInsert (decimal phase insertion), and phaseScaffold (template file/directory creation).
|
|
7
|
+
*
|
|
8
|
+
* Shared helpers replaceInCurrentMilestone and readModifyWriteRoadmapMd
|
|
9
|
+
* are exported for use by downstream handlers (phaseComplete in Plan 03).
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { phaseAdd, phaseInsert, phaseScaffold } from './phase-lifecycle.js';
|
|
14
|
+
*
|
|
15
|
+
* await phaseAdd(['New Feature'], '/project');
|
|
16
|
+
* await phaseInsert(['10', 'Urgent Fix'], '/project');
|
|
17
|
+
* await phaseScaffold(['context', '9'], '/project');
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
import { readFile, writeFile, mkdir, readdir, rename, rm } from 'node:fs/promises';
|
|
21
|
+
import { existsSync } from 'node:fs';
|
|
22
|
+
import { join, relative } from 'node:path';
|
|
23
|
+
import { GSDError, ErrorClassification } from '../errors.js';
|
|
24
|
+
import { escapeRegex, normalizeMd, normalizePhaseName, comparePhaseNum, phaseTokenMatches, toPosixPath, planningPaths, } from './helpers.js';
|
|
25
|
+
import { extractFrontmatter } from './frontmatter.js';
|
|
26
|
+
import { extractCurrentMilestone, phaseMarkdownRegexSource } from './roadmap.js';
|
|
27
|
+
import { getMilestonePhaseFilter } from './state.js';
|
|
28
|
+
import { isCanonicalPlanFile, describeNonCanonicalPlans } from './phase.js';
|
|
29
|
+
import { acquireStateLock, readModifyWriteStateMdFull, releaseStateLock, stateReplaceField, } from './state-mutation.js';
|
|
30
|
+
import { stateExtractField, stateReplaceFieldWithFallback } from './state-document.js';
|
|
31
|
+
import { assertNoNullBytes, assertSafePhaseDirName, assertSafeProjectCode, buildPhaseRoadmapEntry, collectDecimalSuffixesFromDirNames, collectDecimalSuffixesFromRoadmap, computeNextDecimalPhase, computeNextSequentialPhaseId, computePhaseDirectory, extractOneLinerFromBody, generatePhaseSlug, parseMultiwordArg, } from './phase-lifecycle-policy.js';
|
|
32
|
+
import { archiveDirectories, ensureDirectoryWithGitkeep, listDirectories, } from './phase-filesystem-adapter.js';
|
|
33
|
+
import { readModifyWriteRoadmapMd, replaceInCurrentMilestone, } from './phase-roadmap-mutation.js';
|
|
34
|
+
export { readModifyWriteRoadmapMd, replaceInCurrentMilestone };
|
|
35
|
+
// ─── Milestone-scoped directory helpers (#3816) ───────────────────────────
|
|
36
|
+
/**
|
|
37
|
+
* Read the current milestone identifier from STATE.md.
|
|
38
|
+
* Returns null on any error (absent, unreadable, or no `milestone:` field).
|
|
39
|
+
*/
|
|
40
|
+
async function readCurrentMilestoneFromState(projectDir, workstream) {
|
|
41
|
+
try {
|
|
42
|
+
const statePath = planningPaths(projectDir, workstream).state;
|
|
43
|
+
const content = await readFile(statePath, 'utf-8');
|
|
44
|
+
const m = content.match(/^milestone:\s*(.+)$/m);
|
|
45
|
+
if (!m)
|
|
46
|
+
return null;
|
|
47
|
+
return m[1].trim().replace(/^["']|["']$/g, '');
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Resolve the phases directory for a `phase.add` / `phase.add-batch` write.
|
|
55
|
+
*
|
|
56
|
+
* When the project uses a milestone-scoped layout (`.planning/milestones/
|
|
57
|
+
* <milestone>-phases/` already exists on disk), return that path so newly
|
|
58
|
+
* created phase directories land in the correct location alongside existing
|
|
59
|
+
* phases (#3816 fix for Symptom 3a).
|
|
60
|
+
*
|
|
61
|
+
* Falls back to the flat `.planning/phases/` path for all other projects.
|
|
62
|
+
*/
|
|
63
|
+
async function resolveWritePhasesDir(projectDir, workstream) {
|
|
64
|
+
const flatPhasesDir = planningPaths(projectDir, workstream).phases;
|
|
65
|
+
try {
|
|
66
|
+
const milestone = await readCurrentMilestoneFromState(projectDir, workstream);
|
|
67
|
+
if (!milestone)
|
|
68
|
+
return flatPhasesDir;
|
|
69
|
+
const milestoneScopedDir = join(projectDir, '.planning', 'milestones', `${milestone}-phases`);
|
|
70
|
+
if (existsSync(milestoneScopedDir)) {
|
|
71
|
+
return milestoneScopedDir;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch { /* fall through to flat layout */ }
|
|
75
|
+
return flatPhasesDir;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Find the insertion point for a new phase entry in the ROADMAP.md content.
|
|
79
|
+
*
|
|
80
|
+
* #3816 fix for Symptom 3b: the previous implementation used
|
|
81
|
+
* `lastIndexOf('\n---')` which would place a new phase AFTER the Backlog
|
|
82
|
+
* section when Backlog precedes the trailing `---`. The new implementation
|
|
83
|
+
* inserts at the end of the active milestone section (just before the next
|
|
84
|
+
* `## Backlog` / `## Planned` / etc. heading, or before the trailing `---`).
|
|
85
|
+
*
|
|
86
|
+
* Returns the index at which the new phase entry should be inserted.
|
|
87
|
+
*/
|
|
88
|
+
function findPhaseInsertionPoint(rawContent) {
|
|
89
|
+
// Look for the next top-level (## or ###) section that marks the end of the
|
|
90
|
+
// active phases list: Backlog, Planned milestones, or similar.
|
|
91
|
+
// Matches lines starting with ## or ### that are NOT the trailing `---`.
|
|
92
|
+
// We insert BEFORE this heading.
|
|
93
|
+
const backlogBoundaryRe = /\n(#{2,3}\s+(?:Backlog|Planned|Shipped|Archived)\b[^\n]*)/i;
|
|
94
|
+
const boundaryMatch = rawContent.match(backlogBoundaryRe);
|
|
95
|
+
if (boundaryMatch && boundaryMatch.index !== undefined) {
|
|
96
|
+
return boundaryMatch.index;
|
|
97
|
+
}
|
|
98
|
+
// Fallback: insert before the last `\n---` separator (original behaviour for
|
|
99
|
+
// ROADMAPs without a Backlog section).
|
|
100
|
+
const lastSeparator = rawContent.lastIndexOf('\n---');
|
|
101
|
+
if (lastSeparator > 0)
|
|
102
|
+
return lastSeparator;
|
|
103
|
+
return rawContent.length;
|
|
104
|
+
}
|
|
105
|
+
// ─── phaseAdd handler ───────────────────────────────────────────────────
|
|
106
|
+
/**
|
|
107
|
+
* Query handler for phase.add.
|
|
108
|
+
*
|
|
109
|
+
* Port of cmdPhaseAdd from phase.cjs lines 312-392.
|
|
110
|
+
* Creates a new phase directory with .gitkeep, appends a phase section
|
|
111
|
+
* to ROADMAP.md before the last "---" separator.
|
|
112
|
+
*
|
|
113
|
+
* @param args - description (required), optional customId, optional --dry-run flag.
|
|
114
|
+
* Recognized flags: --dry-run (compute result without writing to disk).
|
|
115
|
+
* Any other --flag argument is rejected with a validation error.
|
|
116
|
+
* @param projectDir - Project root directory
|
|
117
|
+
* @returns QueryResult with { phase_number, padded, name, slug, directory, naming_mode }
|
|
118
|
+
* In --dry-run mode also includes { dry_run: true, roadmap_entry: string }
|
|
119
|
+
*/
|
|
120
|
+
export const phaseAdd = async (args, projectDir, workstream) => {
|
|
121
|
+
// ── Flag parsing ────────────────────────────────────────────────────────
|
|
122
|
+
// Mirrors the CJS phase add router (phase-command-router.cjs): recognise
|
|
123
|
+
// --dry-run and --id <value>; reject every other --flag; ignore --raw so it
|
|
124
|
+
// never leaks into the description; join the remaining positional tokens
|
|
125
|
+
// with a single space so multi-word descriptions like `phase add User
|
|
126
|
+
// Dashboard` produce description "User Dashboard". customId comes from the
|
|
127
|
+
// --id flag, never from positional[1].
|
|
128
|
+
let dryRun = false;
|
|
129
|
+
let customIdArg = null;
|
|
130
|
+
const positional = [];
|
|
131
|
+
for (let i = 0; i < args.length; i++) {
|
|
132
|
+
const arg = args[i];
|
|
133
|
+
if (arg === '--raw') {
|
|
134
|
+
// CJS router strips --raw before invoking the handler; preserve parity
|
|
135
|
+
// so a stray --raw never poisons the description.
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (arg === '--dry-run') {
|
|
139
|
+
dryRun = true;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (arg === '--id') {
|
|
143
|
+
const id = args[i + 1];
|
|
144
|
+
if (!id || id.startsWith('--')) {
|
|
145
|
+
throw new GSDError('--id requires a value', ErrorClassification.Validation);
|
|
146
|
+
}
|
|
147
|
+
customIdArg = id;
|
|
148
|
+
i++;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (arg.startsWith('--')) {
|
|
152
|
+
throw new GSDError(`phase add does not support ${arg}`, ErrorClassification.Validation);
|
|
153
|
+
}
|
|
154
|
+
positional.push(arg);
|
|
155
|
+
}
|
|
156
|
+
const description = positional.join(' ').trim();
|
|
157
|
+
if (!description) {
|
|
158
|
+
throw new GSDError('description required for phase add', ErrorClassification.Validation);
|
|
159
|
+
}
|
|
160
|
+
assertNoNullBytes(description, 'description');
|
|
161
|
+
const configPath = planningPaths(projectDir, workstream).config;
|
|
162
|
+
let config = {};
|
|
163
|
+
try {
|
|
164
|
+
config = JSON.parse(await readFile(configPath, 'utf-8'));
|
|
165
|
+
}
|
|
166
|
+
catch { /* use defaults */ }
|
|
167
|
+
const slug = generatePhaseSlug(description);
|
|
168
|
+
// customId always comes from the --id flag; positional tokens are reserved
|
|
169
|
+
// for the description (which is joined above).
|
|
170
|
+
const customId = customIdArg;
|
|
171
|
+
// Optional project code prefix (e.g., 'CK' -> 'CK-01-foundation')
|
|
172
|
+
const projectCode = config.project_code || '';
|
|
173
|
+
assertSafeProjectCode(projectCode);
|
|
174
|
+
const prefix = projectCode ? `${projectCode}-` : '';
|
|
175
|
+
// ── Helper: compute newPhaseId / dirName / computedPhaseEntry from raw ROADMAP content ──
|
|
176
|
+
// Extracted as a local async function so it can be called both inside the
|
|
177
|
+
// roadmap lock (non-dry-run) and outside (dry-run, where no write occurs and
|
|
178
|
+
// there is no race condition to guard against).
|
|
179
|
+
const computePhaseFields = async (rawRoadmapContent) => {
|
|
180
|
+
const milestoneContent = await extractCurrentMilestone(rawRoadmapContent, projectDir);
|
|
181
|
+
// #3816: use milestone-scoped directory if it exists, otherwise flat layout
|
|
182
|
+
const phasesDir = await resolveWritePhasesDir(projectDir, workstream);
|
|
183
|
+
const dirNames = await listDirectories(phasesDir);
|
|
184
|
+
const nextSequentialPhaseId = computeNextSequentialPhaseId(milestoneContent, dirNames);
|
|
185
|
+
const { phaseId: resolvedPhaseId, dirName: resolvedDirName } = computePhaseDirectory(config.phase_naming, slug, prefix, nextSequentialPhaseId, customId || undefined);
|
|
186
|
+
if (!resolvedDirName) {
|
|
187
|
+
throw new GSDError('Phase directory name was not computed', ErrorClassification.Execution);
|
|
188
|
+
}
|
|
189
|
+
if (resolvedPhaseId === '') {
|
|
190
|
+
throw new GSDError('Phase ID was not computed', ErrorClassification.Execution);
|
|
191
|
+
}
|
|
192
|
+
const resolvedEntry = buildPhaseRoadmapEntry(resolvedPhaseId, description, config.phase_naming);
|
|
193
|
+
return { resolvedPhaseId, resolvedDirName, resolvedEntry };
|
|
194
|
+
};
|
|
195
|
+
let newPhaseId = '';
|
|
196
|
+
let dirName = '';
|
|
197
|
+
let computedPhaseEntry = '';
|
|
198
|
+
if (dryRun) {
|
|
199
|
+
// Dry-run: no write, no race condition — compute outside the lock.
|
|
200
|
+
const roadmapPath = planningPaths(projectDir, workstream).roadmap;
|
|
201
|
+
let rawRoadmapContent = '';
|
|
202
|
+
try {
|
|
203
|
+
rawRoadmapContent = await readFile(roadmapPath, 'utf-8');
|
|
204
|
+
}
|
|
205
|
+
catch { /* ROADMAP.md may not exist yet */ }
|
|
206
|
+
const { resolvedPhaseId, resolvedDirName, resolvedEntry } = await computePhaseFields(rawRoadmapContent);
|
|
207
|
+
newPhaseId = resolvedPhaseId;
|
|
208
|
+
dirName = resolvedDirName;
|
|
209
|
+
computedPhaseEntry = resolvedEntry;
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
// Real write path: hold the roadmap lock across the entire read → compute → write
|
|
213
|
+
// cycle so that two concurrent phase.add calls cannot both observe the same
|
|
214
|
+
// maxPhase and produce duplicate phase IDs.
|
|
215
|
+
await readModifyWriteRoadmapMd(projectDir, async (roadmapRaw) => {
|
|
216
|
+
const { resolvedPhaseId, resolvedDirName, resolvedEntry } = await computePhaseFields(roadmapRaw);
|
|
217
|
+
newPhaseId = resolvedPhaseId;
|
|
218
|
+
dirName = resolvedDirName;
|
|
219
|
+
computedPhaseEntry = resolvedEntry;
|
|
220
|
+
// #3816: resolve to milestone-scoped dir if it exists
|
|
221
|
+
const phasesWriteDir = await resolveWritePhasesDir(projectDir, workstream);
|
|
222
|
+
const dirPath = join(phasesWriteDir, dirName);
|
|
223
|
+
// Create directory with .gitkeep so git tracks empty folders
|
|
224
|
+
await ensureDirectoryWithGitkeep(dirPath);
|
|
225
|
+
// #3816: insert before Backlog/Planned boundary, not after last `---`
|
|
226
|
+
const insertIdx = findPhaseInsertionPoint(roadmapRaw);
|
|
227
|
+
return roadmapRaw.slice(0, insertIdx) + computedPhaseEntry + roadmapRaw.slice(insertIdx);
|
|
228
|
+
}, workstream);
|
|
229
|
+
}
|
|
230
|
+
const result = {
|
|
231
|
+
phase_number: typeof newPhaseId === 'number' ? newPhaseId : String(newPhaseId),
|
|
232
|
+
padded: typeof newPhaseId === 'number' ? String(newPhaseId).padStart(2, '0') : String(newPhaseId),
|
|
233
|
+
name: description,
|
|
234
|
+
slug,
|
|
235
|
+
// #3816: result.directory must reflect the actual write location
|
|
236
|
+
directory: toPosixPath(relative(projectDir, join(await resolveWritePhasesDir(projectDir, workstream), dirName))),
|
|
237
|
+
naming_mode: config.phase_naming || 'sequential',
|
|
238
|
+
};
|
|
239
|
+
if (dryRun) {
|
|
240
|
+
result.dry_run = true;
|
|
241
|
+
result.roadmap_entry = computedPhaseEntry;
|
|
242
|
+
}
|
|
243
|
+
return { data: result };
|
|
244
|
+
};
|
|
245
|
+
// ─── phaseAddBatch handler ────────────────────────────────────────────────
|
|
246
|
+
/**
|
|
247
|
+
* Query handler for phase.add-batch.
|
|
248
|
+
*
|
|
249
|
+
* Port of cmdPhaseAddBatch from phase.cjs lines 411-478.
|
|
250
|
+
* Appends multiple phases in one locked ROADMAP pass (sequential or custom naming).
|
|
251
|
+
*
|
|
252
|
+
* @param args - Either `--descriptions` followed by a JSON array string, or one description per arg (`--raw` ignored)
|
|
253
|
+
*/
|
|
254
|
+
export const phaseAddBatch = async (args, projectDir, workstream) => {
|
|
255
|
+
let descriptions;
|
|
256
|
+
const descIdx = args.indexOf('--descriptions');
|
|
257
|
+
if (descIdx !== -1) {
|
|
258
|
+
// CJS router parity (phase-command-router.cjs): a dangling --descriptions
|
|
259
|
+
// or one whose value is another flag must surface the same JSON-array error
|
|
260
|
+
// string, not silently fall through to positional parsing or throw a
|
|
261
|
+
// different "valid JSON" variant.
|
|
262
|
+
const rawValue = args[descIdx + 1];
|
|
263
|
+
if (rawValue === undefined || rawValue.startsWith('--')) {
|
|
264
|
+
throw new GSDError('--descriptions must be a JSON array', ErrorClassification.Validation);
|
|
265
|
+
}
|
|
266
|
+
let parsed;
|
|
267
|
+
try {
|
|
268
|
+
parsed = JSON.parse(rawValue);
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
throw new GSDError('--descriptions must be a JSON array', ErrorClassification.Validation);
|
|
272
|
+
}
|
|
273
|
+
if (!Array.isArray(parsed)) {
|
|
274
|
+
throw new GSDError('--descriptions must be a JSON array', ErrorClassification.Validation);
|
|
275
|
+
}
|
|
276
|
+
descriptions = parsed.map((x) => String(x));
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
descriptions = args.filter((a) => a !== '--raw');
|
|
280
|
+
}
|
|
281
|
+
if (descriptions.length === 0) {
|
|
282
|
+
throw new GSDError('descriptions array required for phase add-batch', ErrorClassification.Validation);
|
|
283
|
+
}
|
|
284
|
+
for (const d of descriptions) {
|
|
285
|
+
assertNoNullBytes(d, 'description');
|
|
286
|
+
if (!d.trim()) {
|
|
287
|
+
throw new GSDError('description must be non-empty', ErrorClassification.Validation);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const roadmapPath = planningPaths(projectDir, workstream).roadmap;
|
|
291
|
+
if (!existsSync(roadmapPath)) {
|
|
292
|
+
throw new GSDError('ROADMAP.md not found', ErrorClassification.Validation);
|
|
293
|
+
}
|
|
294
|
+
let config = {};
|
|
295
|
+
try {
|
|
296
|
+
config = JSON.parse(await readFile(planningPaths(projectDir, workstream).config, 'utf-8'));
|
|
297
|
+
}
|
|
298
|
+
catch { /* use defaults */ }
|
|
299
|
+
const projectCode = config.project_code || '';
|
|
300
|
+
assertSafeProjectCode(projectCode);
|
|
301
|
+
const prefix = projectCode ? `${projectCode}-` : '';
|
|
302
|
+
const added = [];
|
|
303
|
+
await readModifyWriteRoadmapMd(projectDir, async (initialContent) => {
|
|
304
|
+
let rawContent = initialContent;
|
|
305
|
+
const content = await extractCurrentMilestone(rawContent, projectDir);
|
|
306
|
+
let maxPhase = 0;
|
|
307
|
+
// #3816: resolve to milestone-scoped dir if it exists for the whole batch
|
|
308
|
+
const phasesWriteDir = await resolveWritePhasesDir(projectDir, workstream);
|
|
309
|
+
if (config.phase_naming !== 'custom') {
|
|
310
|
+
const dirNames = await listDirectories(phasesWriteDir);
|
|
311
|
+
maxPhase = computeNextSequentialPhaseId(content, dirNames) - 1;
|
|
312
|
+
}
|
|
313
|
+
for (const description of descriptions) {
|
|
314
|
+
const slug = generatePhaseSlug(description);
|
|
315
|
+
let newPhaseId;
|
|
316
|
+
let dirName;
|
|
317
|
+
if (config.phase_naming === 'custom') {
|
|
318
|
+
// Match CJS cmdPhaseAddBatch: slug.toUpperCase().replace(/-/g, '-') (identity on hyphens)
|
|
319
|
+
newPhaseId = slug.toUpperCase();
|
|
320
|
+
dirName = `${prefix}${newPhaseId}-${slug}`;
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
maxPhase += 1;
|
|
324
|
+
newPhaseId = maxPhase;
|
|
325
|
+
dirName = `${prefix}${String(newPhaseId).padStart(2, '0')}-${slug}`;
|
|
326
|
+
}
|
|
327
|
+
assertSafePhaseDirName(dirName);
|
|
328
|
+
// #3816: use resolved milestone-scoped dir for directory creation
|
|
329
|
+
const dirPath = join(phasesWriteDir, dirName);
|
|
330
|
+
await ensureDirectoryWithGitkeep(dirPath);
|
|
331
|
+
const phaseEntry = buildPhaseRoadmapEntry(newPhaseId, description, config.phase_naming);
|
|
332
|
+
// #3816: insert before Backlog/Planned boundary, not after last `---`
|
|
333
|
+
const insertIdx = findPhaseInsertionPoint(rawContent);
|
|
334
|
+
rawContent = rawContent.slice(0, insertIdx) + phaseEntry + rawContent.slice(insertIdx);
|
|
335
|
+
added.push({
|
|
336
|
+
phase_number: typeof newPhaseId === 'number' ? newPhaseId : String(newPhaseId),
|
|
337
|
+
padded: typeof newPhaseId === 'number' ? String(newPhaseId).padStart(2, '0') : String(newPhaseId),
|
|
338
|
+
name: description,
|
|
339
|
+
slug,
|
|
340
|
+
// #3816: result.directory reflects the actual write location
|
|
341
|
+
directory: toPosixPath(relative(projectDir, join(phasesWriteDir, dirName))),
|
|
342
|
+
naming_mode: config.phase_naming || 'sequential',
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
return rawContent;
|
|
346
|
+
}, workstream);
|
|
347
|
+
return { data: { phases: added, count: added.length } };
|
|
348
|
+
};
|
|
349
|
+
// ─── phaseInsert handler ────────────────────────────────────────────────
|
|
350
|
+
/**
|
|
351
|
+
* Query handler for phase.insert.
|
|
352
|
+
*
|
|
353
|
+
* Port of cmdPhaseInsert from phase.cjs lines 394-492.
|
|
354
|
+
* Creates a decimal phase directory after a target phase, inserting
|
|
355
|
+
* the phase section in ROADMAP.md after the target.
|
|
356
|
+
*
|
|
357
|
+
* @param args - args[0]: afterPhase (required), args[1]: description (required)
|
|
358
|
+
* @param projectDir - Project root directory
|
|
359
|
+
* @returns QueryResult with { phase_number, after_phase, name, slug, directory }
|
|
360
|
+
*/
|
|
361
|
+
export const phaseInsert = async (args, projectDir, workstream) => {
|
|
362
|
+
// CJS router parity (phase-command-router.cjs): explicitly reject
|
|
363
|
+
// --dry-run (insert is destructive on disk + roadmap and has no preview
|
|
364
|
+
// path), strip --raw, and join all positional args after `afterPhase` into
|
|
365
|
+
// a single space-delimited description so `phase insert 1 Fix Critical Bug`
|
|
366
|
+
// produces description "Fix Critical Bug" instead of just "Fix".
|
|
367
|
+
const positional = [];
|
|
368
|
+
for (const arg of args) {
|
|
369
|
+
if (arg === '--dry-run') {
|
|
370
|
+
throw new GSDError('phase insert does not support --dry-run', ErrorClassification.Validation);
|
|
371
|
+
}
|
|
372
|
+
if (arg === '--raw')
|
|
373
|
+
continue;
|
|
374
|
+
if (arg.startsWith('--')) {
|
|
375
|
+
throw new GSDError(`phase insert does not support ${arg}`, ErrorClassification.Validation);
|
|
376
|
+
}
|
|
377
|
+
positional.push(arg);
|
|
378
|
+
}
|
|
379
|
+
const afterPhase = positional[0];
|
|
380
|
+
const description = positional.slice(1).join(' ').trim();
|
|
381
|
+
if (!afterPhase || !description) {
|
|
382
|
+
throw new GSDError('after-phase and description required for phase insert', ErrorClassification.Validation);
|
|
383
|
+
}
|
|
384
|
+
assertNoNullBytes(afterPhase, 'afterPhase');
|
|
385
|
+
assertNoNullBytes(description, 'description');
|
|
386
|
+
const slug = generatePhaseSlug(description);
|
|
387
|
+
let decimalPhase = '';
|
|
388
|
+
let dirName = '';
|
|
389
|
+
await readModifyWriteRoadmapMd(projectDir, async (rawContent) => {
|
|
390
|
+
const content = await extractCurrentMilestone(rawContent, projectDir);
|
|
391
|
+
// Normalize input then strip leading zeros for flexible matching
|
|
392
|
+
const normalizedAfter = normalizePhaseName(afterPhase);
|
|
393
|
+
const unpadded = normalizedAfter.replace(/^0+/, '');
|
|
394
|
+
const afterPhaseEscaped = unpadded.replace(/\./g, '\\.');
|
|
395
|
+
const targetPattern = new RegExp(`#{2,4}\\s*Phase\\s+0*${afterPhaseEscaped}:`, 'i');
|
|
396
|
+
const headingMatch = targetPattern.test(content);
|
|
397
|
+
// #3815: also recognise the checked-bullet phase format used by projects
|
|
398
|
+
// that list phases as `- [ ] **Phase N: name**` or `- [ ] Phase N: name`
|
|
399
|
+
// (both bold and plain variants). This mirrors the patterns already used
|
|
400
|
+
// by phaseRemove / phaseComplete so insert is consistent with its siblings.
|
|
401
|
+
//
|
|
402
|
+
// Bullet-style is only used when there are NO heading-style phases at all in
|
|
403
|
+
// the milestone content. If the ROADMAP mixes headings + bullets (hybrid
|
|
404
|
+
// format), a bullet-only match means the detail section is missing — that
|
|
405
|
+
// is the #3098 case and must continue to produce the "missing a detail
|
|
406
|
+
// section" error. Only a purely bullet-style ROADMAP (zero heading-style
|
|
407
|
+
// phase entries in the milestone) goes through the bullet insert path.
|
|
408
|
+
const bulletPattern = new RegExp(`-\\s*\\[[ x]\\]\\s*(?:\\*\\*)?Phase\\s+0*${afterPhaseEscaped}[:\\s]`, 'i');
|
|
409
|
+
const anyHeadingPattern = /#{2,4}\s*Phase\s+\d/i;
|
|
410
|
+
const roadmapHasHeadingPhases = anyHeadingPattern.test(content);
|
|
411
|
+
const isBulletStyle = !headingMatch && bulletPattern.test(content) && !roadmapHasHeadingPhases;
|
|
412
|
+
if (!headingMatch && !isBulletStyle) {
|
|
413
|
+
// Bug #3098 parity: when the ROADMAP uses heading-style phases and only
|
|
414
|
+
// the summary checklist exists for this phase (no `### Phase N:` detail
|
|
415
|
+
// section), point the user at the missing detail section rather than
|
|
416
|
+
// implying the phase is absent.
|
|
417
|
+
const checklistPattern = new RegExp(`-\\s*\\[[ x]\\]\\s*(?:\\*\\*)?Phase\\s+0*${afterPhaseEscaped}[:\\s]`, 'i');
|
|
418
|
+
if (checklistPattern.test(content)) {
|
|
419
|
+
throw new GSDError(`Phase ${afterPhase} exists in roadmap summary but is missing a detail section (### Phase ${afterPhase}: ...).`, ErrorClassification.Validation);
|
|
420
|
+
}
|
|
421
|
+
throw new GSDError(`Phase ${afterPhase} not found in ROADMAP.md`, ErrorClassification.Validation);
|
|
422
|
+
}
|
|
423
|
+
// Calculate next decimal by scanning both directories AND ROADMAP.md entries
|
|
424
|
+
const phasesDir = planningPaths(projectDir, workstream).phases;
|
|
425
|
+
const normalizedBase = normalizePhaseName(afterPhase);
|
|
426
|
+
const decimalSet = new Set();
|
|
427
|
+
try {
|
|
428
|
+
const dirs = await listDirectories(phasesDir);
|
|
429
|
+
for (const suffix of collectDecimalSuffixesFromDirNames(normalizedBase, dirs)) {
|
|
430
|
+
decimalSet.add(suffix);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
catch { /* intentionally empty */ }
|
|
434
|
+
// Also scan ROADMAP.md content for decimal entries
|
|
435
|
+
for (const suffix of collectDecimalSuffixesFromRoadmap(normalizedBase, rawContent)) {
|
|
436
|
+
decimalSet.add(suffix);
|
|
437
|
+
}
|
|
438
|
+
decimalPhase = computeNextDecimalPhase(normalizedBase, decimalSet).next;
|
|
439
|
+
// Optional project code prefix
|
|
440
|
+
let insertConfig = {};
|
|
441
|
+
try {
|
|
442
|
+
insertConfig = JSON.parse(await readFile(planningPaths(projectDir, workstream).config, 'utf-8'));
|
|
443
|
+
}
|
|
444
|
+
catch { /* use defaults */ }
|
|
445
|
+
const projectCode = insertConfig.project_code || '';
|
|
446
|
+
assertSafeProjectCode(projectCode);
|
|
447
|
+
const pfx = projectCode ? `${projectCode}-` : '';
|
|
448
|
+
dirName = `${pfx}${decimalPhase}-${slug}`;
|
|
449
|
+
assertSafePhaseDirName(dirName);
|
|
450
|
+
const dirPath = join(phasesDir, dirName);
|
|
451
|
+
// Create directory with .gitkeep
|
|
452
|
+
await ensureDirectoryWithGitkeep(dirPath);
|
|
453
|
+
if (isBulletStyle) {
|
|
454
|
+
// #3815: Insert in checked-bullet format, mirroring the style of the
|
|
455
|
+
// surrounding entries. Detect whether the matched bullet uses bold
|
|
456
|
+
// (`**Phase N: …**`) to preserve file-internal format consistency.
|
|
457
|
+
const boldBulletPattern = new RegExp(`-\\s*\\[[ x]\\]\\s*\\*\\*Phase\\s+0*${afterPhaseEscaped}:`, 'i');
|
|
458
|
+
const useBold = boldBulletPattern.test(content);
|
|
459
|
+
const phaseLabel = useBold
|
|
460
|
+
? `**Phase ${decimalPhase}: ${description}**`
|
|
461
|
+
: `Phase ${decimalPhase}: ${description}`;
|
|
462
|
+
const bulletEntry = `\n- [ ] ${phaseLabel}`;
|
|
463
|
+
// Locate the target bullet line in the raw content
|
|
464
|
+
const targetBulletPattern = new RegExp(`(-\\s*\\[[ x]\\]\\s*(?:\\*\\*)?Phase\\s+0*${afterPhaseEscaped}[:\\s][^\\n]*)`, 'i');
|
|
465
|
+
const bulletMatchResult = rawContent.match(targetBulletPattern);
|
|
466
|
+
if (!bulletMatchResult) {
|
|
467
|
+
throw new GSDError(`Could not find Phase ${afterPhase} bullet line`, ErrorClassification.Execution);
|
|
468
|
+
}
|
|
469
|
+
const bulletLineEnd = rawContent.indexOf(bulletMatchResult[0]) + bulletMatchResult[0].length;
|
|
470
|
+
// Find where the next phase bullet starts (or use end of content)
|
|
471
|
+
const afterBullet = rawContent.slice(bulletLineEnd);
|
|
472
|
+
const nextBulletMatch = afterBullet.match(/\n-\s*\[[ x]\]\s*(?:\*\*)?Phase\s+\d/i);
|
|
473
|
+
let insertIdx;
|
|
474
|
+
if (nextBulletMatch && nextBulletMatch.index !== undefined) {
|
|
475
|
+
insertIdx = bulletLineEnd + nextBulletMatch.index;
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
insertIdx = bulletLineEnd;
|
|
479
|
+
}
|
|
480
|
+
return rawContent.slice(0, insertIdx) + bulletEntry + rawContent.slice(insertIdx);
|
|
481
|
+
}
|
|
482
|
+
// Heading-style insert (original path)
|
|
483
|
+
// Build phase entry
|
|
484
|
+
const phaseEntry = `\n### Phase ${decimalPhase}: ${description} (INSERTED)\n\n**Goal:** [Urgent work - to be planned]\n**Requirements**: TBD\n**Depends on:** Phase ${afterPhase}\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run /gsd-plan-phase ${decimalPhase} to break down)\n`;
|
|
485
|
+
// Insert after the target phase section
|
|
486
|
+
const headerPattern = new RegExp(`(#{2,4}\\s*Phase\\s+0*${afterPhaseEscaped}:[^\\n]*\\n)`, 'i');
|
|
487
|
+
const headerMatch = rawContent.match(headerPattern);
|
|
488
|
+
if (!headerMatch) {
|
|
489
|
+
throw new GSDError(`Could not find Phase ${afterPhase} header`, ErrorClassification.Execution);
|
|
490
|
+
}
|
|
491
|
+
const headerIdx = rawContent.indexOf(headerMatch[0]);
|
|
492
|
+
const afterHeader = rawContent.slice(headerIdx + headerMatch[0].length);
|
|
493
|
+
const nextPhaseMatch = afterHeader.match(/\n#{2,4}\s+Phase\s+\d/i);
|
|
494
|
+
let insertIdx;
|
|
495
|
+
if (nextPhaseMatch && nextPhaseMatch.index !== undefined) {
|
|
496
|
+
insertIdx = headerIdx + headerMatch[0].length + nextPhaseMatch.index;
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
insertIdx = rawContent.length;
|
|
500
|
+
}
|
|
501
|
+
return rawContent.slice(0, insertIdx) + phaseEntry + rawContent.slice(insertIdx);
|
|
502
|
+
}, workstream);
|
|
503
|
+
if (!decimalPhase) {
|
|
504
|
+
throw new GSDError('Decimal phase was not computed', ErrorClassification.Execution);
|
|
505
|
+
}
|
|
506
|
+
if (!dirName) {
|
|
507
|
+
throw new GSDError('Phase directory name was not computed', ErrorClassification.Execution);
|
|
508
|
+
}
|
|
509
|
+
const result = {
|
|
510
|
+
phase_number: decimalPhase,
|
|
511
|
+
after_phase: afterPhase,
|
|
512
|
+
name: description,
|
|
513
|
+
slug,
|
|
514
|
+
directory: toPosixPath(relative(projectDir, join(planningPaths(projectDir, workstream).phases, dirName))),
|
|
515
|
+
};
|
|
516
|
+
return { data: result };
|
|
517
|
+
};
|
|
518
|
+
// ─── phaseScaffold handler ──────────────────────────────────────────────
|
|
519
|
+
/**
|
|
520
|
+
* Internal helper: find phase directory matching a phase identifier.
|
|
521
|
+
*
|
|
522
|
+
* Reuses the same logic as findPhase handler but returns just the directory info.
|
|
523
|
+
*/
|
|
524
|
+
async function findPhaseDir(projectDir, phase, workstream) {
|
|
525
|
+
const phasesDir = planningPaths(projectDir, workstream).phases;
|
|
526
|
+
const normalized = normalizePhaseName(phase);
|
|
527
|
+
const dirs = await listDirectories(phasesDir);
|
|
528
|
+
const match = dirs.find((d) => phaseTokenMatches(d, normalized));
|
|
529
|
+
if (!match)
|
|
530
|
+
return null;
|
|
531
|
+
// Extract phase name from directory
|
|
532
|
+
const dirMatch = match.match(/^(?:[A-Z]{1,6}-)?\d+[A-Z]?(?:\.\d+)*-(.+)/i);
|
|
533
|
+
const phaseName = dirMatch ? dirMatch[1] : null;
|
|
534
|
+
return {
|
|
535
|
+
dirPath: join(phasesDir, match),
|
|
536
|
+
dirName: match,
|
|
537
|
+
phaseName,
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Query handler for phase.scaffold.
|
|
542
|
+
*
|
|
543
|
+
* Port of cmdScaffold from commands.cjs lines 750-806.
|
|
544
|
+
* Creates template files (context, uat, verification) or phase directories.
|
|
545
|
+
*
|
|
546
|
+
* @param args - Positional `[type, phase, name?]` **or** gsd-tools style
|
|
547
|
+
* `[type, '--phase', N, '--name', title]` (name may be multiple words).
|
|
548
|
+
* @param projectDir - Project root directory
|
|
549
|
+
* @returns QueryResult with { created, path } or { created: false, reason: 'already_exists' }
|
|
550
|
+
*/
|
|
551
|
+
function normalizeScaffoldArgs(args) {
|
|
552
|
+
const type = args[0];
|
|
553
|
+
if (!type || !args.includes('--phase')) {
|
|
554
|
+
return args;
|
|
555
|
+
}
|
|
556
|
+
const phaseIdx = args.indexOf('--phase');
|
|
557
|
+
const phase = phaseIdx !== -1 && args[phaseIdx + 1] && !args[phaseIdx + 1].startsWith('--')
|
|
558
|
+
? args[phaseIdx + 1]
|
|
559
|
+
: '';
|
|
560
|
+
const nameIdx = args.indexOf('--name');
|
|
561
|
+
let name;
|
|
562
|
+
if (nameIdx !== -1) {
|
|
563
|
+
const tail = args.slice(nameIdx + 1);
|
|
564
|
+
const stop = tail.findIndex(a => a.startsWith('--'));
|
|
565
|
+
const parts = stop === -1 ? tail : tail.slice(0, stop);
|
|
566
|
+
name = parts.join(' ').trim() || undefined;
|
|
567
|
+
}
|
|
568
|
+
return [type, phase, ...(name !== undefined && name !== '' ? [name] : [])];
|
|
569
|
+
}
|
|
570
|
+
export const phaseScaffold = async (args, projectDir, workstream) => {
|
|
571
|
+
const normalized = normalizeScaffoldArgs(args);
|
|
572
|
+
const type = normalized[0];
|
|
573
|
+
const phase = normalized[1];
|
|
574
|
+
const name = normalized[2] || undefined;
|
|
575
|
+
if (!type) {
|
|
576
|
+
throw new GSDError('type required for scaffold', ErrorClassification.Validation);
|
|
577
|
+
}
|
|
578
|
+
const validTypes = new Set(['context', 'uat', 'verification', 'phase-dir']);
|
|
579
|
+
if (!validTypes.has(type)) {
|
|
580
|
+
throw new GSDError(`Unknown scaffold type: ${type}. Available: context, uat, verification, phase-dir`, ErrorClassification.Validation);
|
|
581
|
+
}
|
|
582
|
+
if (phase) {
|
|
583
|
+
assertNoNullBytes(phase, 'phase');
|
|
584
|
+
}
|
|
585
|
+
if (name) {
|
|
586
|
+
assertNoNullBytes(name, 'name');
|
|
587
|
+
}
|
|
588
|
+
const padded = phase ? normalizePhaseName(phase) : '00';
|
|
589
|
+
const today = new Date().toISOString().split('T')[0];
|
|
590
|
+
// Handle phase-dir type separately
|
|
591
|
+
if (type === 'phase-dir') {
|
|
592
|
+
if (!phase || !name) {
|
|
593
|
+
throw new GSDError('phase and name required for phase-dir scaffold', ErrorClassification.Validation);
|
|
594
|
+
}
|
|
595
|
+
const slug = generatePhaseSlug(name);
|
|
596
|
+
// #3287: apply project_code prefix to stay consistent with phase.add/phase.insert
|
|
597
|
+
let scaffoldConfig = {};
|
|
598
|
+
try {
|
|
599
|
+
scaffoldConfig = JSON.parse(await readFile(planningPaths(projectDir, workstream).config, 'utf-8'));
|
|
600
|
+
}
|
|
601
|
+
catch { /* use defaults */ }
|
|
602
|
+
const scaffoldProjectCode = scaffoldConfig.project_code || '';
|
|
603
|
+
assertSafeProjectCode(scaffoldProjectCode);
|
|
604
|
+
const scaffoldPrefix = scaffoldProjectCode ? `${scaffoldProjectCode}-` : '';
|
|
605
|
+
const dirNameNew = `${scaffoldPrefix}${padded}-${slug}`;
|
|
606
|
+
assertSafePhaseDirName(dirNameNew, 'scaffold phase directory');
|
|
607
|
+
const phasesParent = planningPaths(projectDir, workstream).phases;
|
|
608
|
+
await mkdir(phasesParent, { recursive: true });
|
|
609
|
+
const dirPath = join(phasesParent, dirNameNew);
|
|
610
|
+
await ensureDirectoryWithGitkeep(dirPath);
|
|
611
|
+
return {
|
|
612
|
+
data: {
|
|
613
|
+
created: true,
|
|
614
|
+
directory: toPosixPath(relative(projectDir, dirPath)),
|
|
615
|
+
path: dirPath,
|
|
616
|
+
},
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
// For context/uat/verification types, find the phase directory
|
|
620
|
+
const phaseInfo = phase ? await findPhaseDir(projectDir, phase, workstream) : null;
|
|
621
|
+
if (phase && !phaseInfo) {
|
|
622
|
+
throw new GSDError(`Phase ${phase} directory not found`, ErrorClassification.Blocked);
|
|
623
|
+
}
|
|
624
|
+
const phaseDir = phaseInfo.dirPath;
|
|
625
|
+
const phaseName = name || phaseInfo?.phaseName || 'Unnamed';
|
|
626
|
+
let filePath;
|
|
627
|
+
let content;
|
|
628
|
+
switch (type) {
|
|
629
|
+
case 'context': {
|
|
630
|
+
filePath = join(phaseDir, `${padded}-CONTEXT.md`);
|
|
631
|
+
content = `---\nphase: "${padded}"\nname: "${phaseName}"\ncreated: ${today}\n---\n\n# Phase ${phase}: ${phaseName} — Context\n\n## Decisions\n\n_Decisions will be captured during /gsd-discuss-phase ${phase}_\n\n## Discretion Areas\n\n_Areas where the executor can use judgment_\n\n## Deferred Ideas\n\n_Ideas to consider later_\n`;
|
|
632
|
+
break;
|
|
633
|
+
}
|
|
634
|
+
case 'uat': {
|
|
635
|
+
filePath = join(phaseDir, `${padded}-UAT.md`);
|
|
636
|
+
content = `---\nphase: "${padded}"\nname: "${phaseName}"\ncreated: ${today}\nstatus: pending\n---\n\n# Phase ${phase}: ${phaseName} — User Acceptance Testing\n\n## Test Results\n\n| # | Test | Status | Notes |\n|---|------|--------|-------|\n\n## Summary\n\n_Pending UAT_\n`;
|
|
637
|
+
break;
|
|
638
|
+
}
|
|
639
|
+
case 'verification': {
|
|
640
|
+
filePath = join(phaseDir, `${padded}-VERIFICATION.md`);
|
|
641
|
+
content = `---\nphase: "${padded}"\nname: "${phaseName}"\ncreated: ${today}\nstatus: pending\n---\n\n# Phase ${phase}: ${phaseName} — Verification\n\n## Goal-Backward Verification\n\n**Phase Goal:** [From ROADMAP.md]\n\n## Checks\n\n| # | Requirement | Status | Evidence |\n|---|------------|--------|----------|\n\n## Result\n\n_Pending verification_\n`;
|
|
642
|
+
break;
|
|
643
|
+
}
|
|
644
|
+
default:
|
|
645
|
+
throw new GSDError(`Unknown scaffold type: ${type}`, ErrorClassification.Validation);
|
|
646
|
+
}
|
|
647
|
+
// Check if file already exists
|
|
648
|
+
if (existsSync(filePath)) {
|
|
649
|
+
return {
|
|
650
|
+
data: {
|
|
651
|
+
created: false,
|
|
652
|
+
reason: 'already_exists',
|
|
653
|
+
path: filePath,
|
|
654
|
+
},
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
await writeFile(filePath, content, 'utf-8');
|
|
658
|
+
const relPath = toPosixPath(relative(projectDir, filePath));
|
|
659
|
+
return { data: { created: true, path: relPath } };
|
|
660
|
+
};
|
|
661
|
+
// ─── renameDecimalPhases ───────────────────────────────────────────────
|
|
662
|
+
/**
|
|
663
|
+
* Renumber sibling decimal phases after a decimal phase is removed.
|
|
664
|
+
*
|
|
665
|
+
* Port of renameDecimalPhases from phase.cjs lines 499-524.
|
|
666
|
+
* e.g. removing 06.2 -> 06.3 becomes 06.2, 06.4 becomes 06.3, etc.
|
|
667
|
+
* Renames directories AND files inside them that contain the old phase ID.
|
|
668
|
+
*
|
|
669
|
+
* CRITICAL: Sorted in DESCENDING order to avoid rename conflicts.
|
|
670
|
+
*
|
|
671
|
+
* @param phasesDir - Path to the phases directory
|
|
672
|
+
* @param baseInt - The integer part of the decimal phase (e.g. "06")
|
|
673
|
+
* @param removedDecimal - The decimal part that was removed (e.g. 2 for 06.2)
|
|
674
|
+
* @returns { renamedDirs, renamedFiles }
|
|
675
|
+
*/
|
|
676
|
+
async function renameDecimalPhases(phasesDir, baseInt, removedDecimal) {
|
|
677
|
+
const renamedDirs = [];
|
|
678
|
+
const renamedFiles = [];
|
|
679
|
+
const decPattern = new RegExp(`^${escapeRegex(baseInt)}\\.(\\d+)-(.+)$`);
|
|
680
|
+
const entries = await readdir(phasesDir, { withFileTypes: true });
|
|
681
|
+
const dirs = entries.filter(e => e.isDirectory()).map(e => e.name);
|
|
682
|
+
const toRename = dirs
|
|
683
|
+
.map(dir => {
|
|
684
|
+
const m = dir.match(decPattern);
|
|
685
|
+
return m ? { dir, oldDecimal: parseInt(m[1], 10), slug: m[2] } : null;
|
|
686
|
+
})
|
|
687
|
+
.filter((item) => item !== null && item.oldDecimal > removedDecimal)
|
|
688
|
+
.sort((a, b) => b.oldDecimal - a.oldDecimal); // DESCENDING to avoid conflicts
|
|
689
|
+
for (const item of toRename) {
|
|
690
|
+
const newDecimal = item.oldDecimal - 1;
|
|
691
|
+
const oldPhaseId = `${baseInt}.${item.oldDecimal}`;
|
|
692
|
+
const newPhaseId = `${baseInt}.${newDecimal}`;
|
|
693
|
+
const newDirName = `${baseInt}.${newDecimal}-${item.slug}`;
|
|
694
|
+
await rename(join(phasesDir, item.dir), join(phasesDir, newDirName));
|
|
695
|
+
renamedDirs.push({ from: item.dir, to: newDirName });
|
|
696
|
+
// Rename files inside that contain the old phase ID
|
|
697
|
+
const files = await readdir(join(phasesDir, newDirName));
|
|
698
|
+
for (const f of files) {
|
|
699
|
+
if (f.includes(oldPhaseId)) {
|
|
700
|
+
const newFileName = f.replace(oldPhaseId, newPhaseId);
|
|
701
|
+
await rename(join(phasesDir, newDirName, f), join(phasesDir, newDirName, newFileName));
|
|
702
|
+
renamedFiles.push({ from: f, to: newFileName });
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
return { renamedDirs, renamedFiles };
|
|
707
|
+
}
|
|
708
|
+
// ─── renameIntegerPhases ───────────────────────────────────────────────
|
|
709
|
+
/**
|
|
710
|
+
* Renumber all integer phases after a removed integer phase.
|
|
711
|
+
*
|
|
712
|
+
* Port of renameIntegerPhases from phase.cjs lines 531-564.
|
|
713
|
+
* e.g. removing phase 5 -> phase 6 becomes 5, phase 7 becomes 6, etc.
|
|
714
|
+
* Handles letter suffixes (12A) and decimals (6.1).
|
|
715
|
+
*
|
|
716
|
+
* CRITICAL: Sorted in DESCENDING order to avoid rename conflicts.
|
|
717
|
+
*
|
|
718
|
+
* @param phasesDir - Path to the phases directory
|
|
719
|
+
* @param removedInt - The integer phase number that was removed
|
|
720
|
+
* @returns { renamedDirs, renamedFiles }
|
|
721
|
+
*/
|
|
722
|
+
async function renameIntegerPhases(phasesDir, removedInt) {
|
|
723
|
+
const renamedDirs = [];
|
|
724
|
+
const renamedFiles = [];
|
|
725
|
+
const entries = await readdir(phasesDir, { withFileTypes: true });
|
|
726
|
+
const dirs = entries.filter(e => e.isDirectory()).map(e => e.name);
|
|
727
|
+
const toRename = dirs
|
|
728
|
+
.map(dir => {
|
|
729
|
+
const m = dir.match(/^(\d+)([A-Z])?(?:\.(\d+))?-(.+)$/i);
|
|
730
|
+
if (!m)
|
|
731
|
+
return null;
|
|
732
|
+
const dirInt = parseInt(m[1], 10);
|
|
733
|
+
// CJS parity: skip backlog phases (999.x). These are parked ideas with a
|
|
734
|
+
// numbering convention that lives outside the active sequence; renumbering
|
|
735
|
+
// them would clobber the convention and corrupt downstream lookups.
|
|
736
|
+
// (bug-2434)
|
|
737
|
+
if (dirInt <= removedInt || dirInt >= 999)
|
|
738
|
+
return null;
|
|
739
|
+
return {
|
|
740
|
+
dir,
|
|
741
|
+
oldInt: dirInt,
|
|
742
|
+
letter: m[2] ? m[2].toUpperCase() : '',
|
|
743
|
+
decimal: m[3] !== undefined ? parseInt(m[3], 10) : null,
|
|
744
|
+
slug: m[4],
|
|
745
|
+
};
|
|
746
|
+
})
|
|
747
|
+
.filter((item) => item !== null)
|
|
748
|
+
.sort((a, b) => a.oldInt !== b.oldInt
|
|
749
|
+
? b.oldInt - a.oldInt
|
|
750
|
+
: (b.decimal ?? 0) - (a.decimal ?? 0)); // DESCENDING
|
|
751
|
+
for (const item of toRename) {
|
|
752
|
+
const newInt = item.oldInt - 1;
|
|
753
|
+
const newPadded = String(newInt).padStart(2, '0');
|
|
754
|
+
const oldPadded = String(item.oldInt).padStart(2, '0');
|
|
755
|
+
const letterSuffix = item.letter || '';
|
|
756
|
+
const decimalSuffix = item.decimal !== null ? `.${item.decimal}` : '';
|
|
757
|
+
const oldPrefix = `${oldPadded}${letterSuffix}${decimalSuffix}`;
|
|
758
|
+
const newPrefix = `${newPadded}${letterSuffix}${decimalSuffix}`;
|
|
759
|
+
const newDirName = `${newPrefix}-${item.slug}`;
|
|
760
|
+
await rename(join(phasesDir, item.dir), join(phasesDir, newDirName));
|
|
761
|
+
renamedDirs.push({ from: item.dir, to: newDirName });
|
|
762
|
+
// Rename files that start with the old prefix
|
|
763
|
+
const files = await readdir(join(phasesDir, newDirName));
|
|
764
|
+
for (const f of files) {
|
|
765
|
+
if (f.startsWith(oldPrefix)) {
|
|
766
|
+
const newFileName = newPrefix + f.slice(oldPrefix.length);
|
|
767
|
+
await rename(join(phasesDir, newDirName, f), join(phasesDir, newDirName, newFileName));
|
|
768
|
+
renamedFiles.push({ from: f, to: newFileName });
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
return { renamedDirs, renamedFiles };
|
|
773
|
+
}
|
|
774
|
+
// ─── updateRoadmapAfterPhaseRemoval ────────────────────────────────────
|
|
775
|
+
/**
|
|
776
|
+
* Decrement integer phase number while skipping non-renumbered ranges. Mirrors
|
|
777
|
+
* `decrementRoadmapPhaseNumber` in phase.cjs lines 860-864.
|
|
778
|
+
*
|
|
779
|
+
* Skips when:
|
|
780
|
+
* • not an integer
|
|
781
|
+
* • num <= removedInt (already-renumbered phases stay put)
|
|
782
|
+
* • num >= 999 (backlog/parked-idea numbering range)
|
|
783
|
+
*
|
|
784
|
+
* Returns the original raw string when the guards trip so the regex pass
|
|
785
|
+
* leaves dates and unrelated numerics intact.
|
|
786
|
+
*/
|
|
787
|
+
function decrementRoadmapPhaseNumber(raw, removedInt) {
|
|
788
|
+
const num = parseInt(raw, 10);
|
|
789
|
+
if (!Number.isInteger(num) || num <= removedInt || num >= 999)
|
|
790
|
+
return raw;
|
|
791
|
+
return String(num - 1);
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* Decrement integer or decimal phase token (e.g. "5" or "5.2"). Mirrors
|
|
795
|
+
* `decrementRoadmapPhaseToken` in phase.cjs lines 866-872 — preserves the
|
|
796
|
+
* decimal suffix when present and applies the same guards.
|
|
797
|
+
*/
|
|
798
|
+
function decrementRoadmapPhaseToken(raw, removedInt) {
|
|
799
|
+
const match = String(raw).match(/^(\d+)(\.\d+)?$/);
|
|
800
|
+
if (!match)
|
|
801
|
+
return raw;
|
|
802
|
+
const num = parseInt(match[1], 10);
|
|
803
|
+
if (!Number.isInteger(num) || num <= removedInt || num >= 999)
|
|
804
|
+
return raw;
|
|
805
|
+
return `${num - 1}${match[2] || ''}`;
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Decrement zero-padded phase number while preserving the original pad width.
|
|
809
|
+
* Mirrors `decrementRoadmapPaddedPhaseNumber` in phase.cjs lines 874-878.
|
|
810
|
+
*/
|
|
811
|
+
function decrementRoadmapPaddedPhaseNumber(raw, removedInt) {
|
|
812
|
+
const num = parseInt(raw, 10);
|
|
813
|
+
if (!Number.isInteger(num) || num <= removedInt || num >= 999)
|
|
814
|
+
return raw;
|
|
815
|
+
return String(num - 1).padStart(raw.length, '0');
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Remove a phase section from ROADMAP.md and renumber subsequent integer phases.
|
|
819
|
+
*
|
|
820
|
+
* Port of updateRoadmapAfterPhaseRemoval from phase.cjs lines 880-922.
|
|
821
|
+
* Uses readModifyWriteRoadmapMd for atomic writes.
|
|
822
|
+
*
|
|
823
|
+
* The renumbering pass uses **5 targeted regex replacements** (not a loop)
|
|
824
|
+
* because the loop approach is dangerous:
|
|
825
|
+
* • It can match YYYY-MM-DD substrings and corrupt dates (bug-2435).
|
|
826
|
+
* • It can rename backlog phases (999.x) that should stay frozen (bug-2434).
|
|
827
|
+
* • It can renumber the same phase multiple times if the regex matches
|
|
828
|
+
* overlap (bug-3355 — phase 7 → 6 → 5 → ...).
|
|
829
|
+
*
|
|
830
|
+
* The CJS pattern uses negative lookbehind/ahead on the padded-prefix regex
|
|
831
|
+
* to skip dates and decrement helpers that guard against `num >= 999`. Keep
|
|
832
|
+
* this implementation byte-for-byte in lockstep with phase.cjs:880-922 —
|
|
833
|
+
* deviations are how the three bugs above slipped in.
|
|
834
|
+
*
|
|
835
|
+
* @param projectDir - Project root directory
|
|
836
|
+
* @param targetPhase - Phase identifier that was removed
|
|
837
|
+
* @param isDecimal - Whether the removed phase was a decimal phase
|
|
838
|
+
* @param removedInt - The integer part of the removed phase
|
|
839
|
+
*/
|
|
840
|
+
async function updateRoadmapAfterPhaseRemoval(projectDir, targetPhase, isDecimal, removedInt, workstream) {
|
|
841
|
+
await readModifyWriteRoadmapMd(projectDir, (content) => {
|
|
842
|
+
const escaped = escapeRegex(targetPhase);
|
|
843
|
+
// Remove the phase section (header + body until next phase header or end).
|
|
844
|
+
//
|
|
845
|
+
// #3601: the end-of-section lookahead is DEPTH-AWARE. The named capture
|
|
846
|
+
// (?<h>#{2,4}) records the hash count of the header being removed and the
|
|
847
|
+
// lookahead requires the same depth via \k<h>(?!#). Two contracts are
|
|
848
|
+
// preserved:
|
|
849
|
+
//
|
|
850
|
+
// (#3601 case) Remove `### Phase 2:` and stop at `### Phase 2.1:` —
|
|
851
|
+
// Phase 2.1 is a peer-level decimal phase (depth 3) and must survive.
|
|
852
|
+
//
|
|
853
|
+
// (#3355 case) Remove `### Phase 27:` and CONTINUE past
|
|
854
|
+
// `#### Phase 27.1:` (depth 4 — child of Phase 27) until the next
|
|
855
|
+
// depth-3 header. The child decimal is part of the integer phase
|
|
856
|
+
// being removed.
|
|
857
|
+
//
|
|
858
|
+
// The `(?!#)` negative lookahead after the backreference prevents the
|
|
859
|
+
// depth-3 match from being satisfied by a depth-4+ header that starts
|
|
860
|
+
// with the same three hashes. `[^\n:]+` accepts numeric, decimal, AND
|
|
861
|
+
// custom phase IDs (PROJ-42) as terminators.
|
|
862
|
+
content = content.replace(new RegExp(`\\n?(?<h>#{2,4})\\s*Phase\\s+${escaped}\\s*:[\\s\\S]*?(?=\\n\\k<h>(?!#)\\s+Phase\\s+[^\\n:]+\\s*:|$)`, 'i'), '');
|
|
863
|
+
// Remove checkbox lines referencing the phase
|
|
864
|
+
content = content.replace(new RegExp(`\\n?-\\s*\\[[ x]\\]\\s*.*Phase\\s+${escaped}[:\\s][^\\n]*`, 'gi'), '');
|
|
865
|
+
// Remove table rows referencing the phase
|
|
866
|
+
content = content.replace(new RegExp(`\\n?\\|\\s*${escaped}\\.?\\s[^|]*\\|[^\\n]*`, 'gi'), '');
|
|
867
|
+
if (!isDecimal) {
|
|
868
|
+
// Phase headers: ### Phase N: / ### Phase N.M:
|
|
869
|
+
content = content.replace(/(#{2,4}\s*Phase\s+)(\d+(?:\.\d+)?)(\s*:)/gi, (_match, prefix, num, suffix) => `${prefix}${decrementRoadmapPhaseToken(num, removedInt)}${suffix}`);
|
|
870
|
+
// Checkbox-list summary references: `- [ ] Phase N:`
|
|
871
|
+
content = content.replace(/(-\s*\[[ x]\]\s*.*?Phase\s+)(\d+)(\s*:|\s+)/gi, (_match, prefix, num, suffix) => `${prefix}${decrementRoadmapPhaseNumber(num, removedInt)}${suffix}`);
|
|
872
|
+
// Table-row phase numbers: `| N. ` — bare integer in a cell.
|
|
873
|
+
content = content.replace(/(\|\s*)(\d+)(\.\s)/g, (_match, prefix, num, suffix) => `${prefix}${decrementRoadmapPhaseNumber(num, removedInt)}${suffix}`);
|
|
874
|
+
// Padded plan references: NN-NN (optionally followed by an arbitrary
|
|
875
|
+
// kebab-case slug, then -PLAN.md / -SUMMARY.md).
|
|
876
|
+
//
|
|
877
|
+
// #2435: negative lookbehind `(?<![0-9-])` and negative lookahead
|
|
878
|
+
// `(?![0-9-])` exclude YYYY-MM-DD substrings.
|
|
879
|
+
//
|
|
880
|
+
// #3602: the original pattern only allowed a compact `-(PLAN|SUMMARY).md`
|
|
881
|
+
// immediately after the plan number; a slug between the number and the
|
|
882
|
+
// `-PLAN.md` / `-SUMMARY.md` suffix (e.g.
|
|
883
|
+
// `07-01-cherry-pick-foundation-PLAN.md`) made the lookahead fail and
|
|
884
|
+
// left the stale `07-01-` prefix in ROADMAP text while the on-disk file
|
|
885
|
+
// was already renumbered to `06-01-…`. The slug segment
|
|
886
|
+
// `(?:-[A-Za-z][A-Za-z0-9-]*)*` allows any number of kebab-case tokens
|
|
887
|
+
// before the canonical PLAN/SUMMARY suffix.
|
|
888
|
+
content = content.replace(/(?<![0-9-])(\d{2})-(\d{2})(?=(?:(?:-[A-Za-z][A-Za-z0-9-]*)*-(?:PLAN|SUMMARY)\.md)?(?![0-9-]))/g, (_match, phaseNum, planNum) => `${decrementRoadmapPaddedPhaseNumber(phaseNum, removedInt)}-${planNum}`);
|
|
889
|
+
// Depends-on references — two bold-colon variants in the wild.
|
|
890
|
+
content = content.replace(/(\*\*Depends on\*\*\s*:\s*Phase\s+)(\d+(?:\.\d+)?)\b/gi, (_match, prefix, num) => `${prefix}${decrementRoadmapPhaseToken(num, removedInt)}`);
|
|
891
|
+
content = content.replace(/(Depends on:\*\*\s*Phase\s+)(\d+(?:\.\d+)?)\b/gi, (_match, prefix, num) => `${prefix}${decrementRoadmapPhaseToken(num, removedInt)}`);
|
|
892
|
+
}
|
|
893
|
+
return content;
|
|
894
|
+
}, workstream);
|
|
895
|
+
}
|
|
896
|
+
// ─── phaseRemove handler ───────────────────────────────────────────────
|
|
897
|
+
/**
|
|
898
|
+
* Query handler for phase.remove.
|
|
899
|
+
*
|
|
900
|
+
* Port of cmdPhaseRemove from phase.cjs lines 597-661.
|
|
901
|
+
* Deletes phase directory, renumbers subsequent phases on disk,
|
|
902
|
+
* updates ROADMAP.md (removes section + renumbers), and decrements
|
|
903
|
+
* STATE.md total_phases count.
|
|
904
|
+
*
|
|
905
|
+
* @param args - args[0]: targetPhase (required), args[1]: '--force' (optional)
|
|
906
|
+
* @param projectDir - Project root directory
|
|
907
|
+
* @returns QueryResult with { removed, directory_deleted, renamed_directories, renamed_files, roadmap_updated, state_updated }
|
|
908
|
+
*/
|
|
909
|
+
export const phaseRemove = async (args, projectDir, workstream) => {
|
|
910
|
+
let force = false;
|
|
911
|
+
const positional = [];
|
|
912
|
+
for (const token of args) {
|
|
913
|
+
if (token === '--force') {
|
|
914
|
+
force = true;
|
|
915
|
+
continue;
|
|
916
|
+
}
|
|
917
|
+
if (token.startsWith('--')) {
|
|
918
|
+
throw new GSDError(`phase remove does not support ${token}`, ErrorClassification.Validation);
|
|
919
|
+
}
|
|
920
|
+
positional.push(token);
|
|
921
|
+
}
|
|
922
|
+
if (positional.length > 1) {
|
|
923
|
+
throw new GSDError('phase remove accepts exactly one phase number', ErrorClassification.Validation);
|
|
924
|
+
}
|
|
925
|
+
const targetPhase = positional[0];
|
|
926
|
+
if (!targetPhase) {
|
|
927
|
+
throw new GSDError('phase number required for phase remove', ErrorClassification.Validation);
|
|
928
|
+
}
|
|
929
|
+
assertNoNullBytes(targetPhase, 'targetPhase');
|
|
930
|
+
const paths = planningPaths(projectDir, workstream);
|
|
931
|
+
const phasesDir = paths.phases;
|
|
932
|
+
if (!existsSync(paths.roadmap)) {
|
|
933
|
+
throw new GSDError('ROADMAP.md not found', ErrorClassification.Validation);
|
|
934
|
+
}
|
|
935
|
+
const normalized = normalizePhaseName(targetPhase);
|
|
936
|
+
const isDecimal = targetPhase.includes('.');
|
|
937
|
+
// Find target directory
|
|
938
|
+
const entries = await readdir(phasesDir, { withFileTypes: true });
|
|
939
|
+
const dirs = entries.filter(e => e.isDirectory()).map(e => e.name);
|
|
940
|
+
const targetDir = dirs.find(d => phaseTokenMatches(d, normalized)) ?? null;
|
|
941
|
+
if (!targetDir) {
|
|
942
|
+
throw new GSDError(`Phase ${targetPhase} not found`, ErrorClassification.Validation);
|
|
943
|
+
}
|
|
944
|
+
// Guard against removing executed work
|
|
945
|
+
if (!force) {
|
|
946
|
+
const files = await readdir(join(phasesDir, targetDir));
|
|
947
|
+
const summaries = files.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
|
|
948
|
+
if (summaries.length > 0) {
|
|
949
|
+
throw new GSDError(`Phase ${targetPhase} has ${summaries.length} executed plan(s). Use --force to remove anyway.`, ErrorClassification.Validation);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
// Delete directory
|
|
953
|
+
await rm(join(phasesDir, targetDir), { recursive: true, force: true });
|
|
954
|
+
// Renumber subsequent phases on disk
|
|
955
|
+
let renamedDirs = [];
|
|
956
|
+
let renamedFiles = [];
|
|
957
|
+
try {
|
|
958
|
+
let renamed;
|
|
959
|
+
if (isDecimal) {
|
|
960
|
+
const parts = normalized.split('.');
|
|
961
|
+
if (parts.length < 2 || !parts[1]) {
|
|
962
|
+
throw new GSDError(`Invalid decimal phase identifier: ${targetPhase}`, ErrorClassification.Validation);
|
|
963
|
+
}
|
|
964
|
+
const decimalPart = parseInt(parts[1], 10);
|
|
965
|
+
if (isNaN(decimalPart)) {
|
|
966
|
+
throw new GSDError(`Invalid decimal part in phase: ${targetPhase}`, ErrorClassification.Validation);
|
|
967
|
+
}
|
|
968
|
+
renamed = await renameDecimalPhases(phasesDir, parts[0], decimalPart);
|
|
969
|
+
}
|
|
970
|
+
else {
|
|
971
|
+
renamed = await renameIntegerPhases(phasesDir, parseInt(normalized, 10));
|
|
972
|
+
}
|
|
973
|
+
renamedDirs = renamed.renamedDirs;
|
|
974
|
+
renamedFiles = renamed.renamedFiles;
|
|
975
|
+
}
|
|
976
|
+
catch { /* intentionally empty — renaming is best-effort */ }
|
|
977
|
+
// Update ROADMAP.md
|
|
978
|
+
await updateRoadmapAfterPhaseRemoval(projectDir, targetPhase, isDecimal, parseInt(normalized, 10), workstream);
|
|
979
|
+
// Update STATE.md: decrement total_phases
|
|
980
|
+
let stateUpdated = false;
|
|
981
|
+
const statePath = paths.state;
|
|
982
|
+
if (existsSync(statePath)) {
|
|
983
|
+
const lockPath = await acquireStateLock(statePath);
|
|
984
|
+
try {
|
|
985
|
+
let stateContent = await readFile(statePath, 'utf-8');
|
|
986
|
+
// Decrement total_phases in frontmatter
|
|
987
|
+
const totalPhasesMatch = stateContent.match(/total_phases:\s*(\d+)/);
|
|
988
|
+
if (totalPhasesMatch) {
|
|
989
|
+
const oldTotal = parseInt(totalPhasesMatch[1], 10);
|
|
990
|
+
stateContent = stateContent.replace(/total_phases:\s*\d+/, `total_phases: ${oldTotal - 1}`);
|
|
991
|
+
}
|
|
992
|
+
// Decrement "of N" pattern in body (e.g., "Plan: 2 of 3")
|
|
993
|
+
const ofMatch = stateContent.match(/(\bof\s+)(\d+)(\s*(?:\(|phases?))/i);
|
|
994
|
+
if (ofMatch) {
|
|
995
|
+
stateContent = stateContent.replace(/(\bof\s+)(\d+)(\s*(?:\(|phases?))/i, `$1${parseInt(ofMatch[2], 10) - 1}$3`);
|
|
996
|
+
}
|
|
997
|
+
// Also try stateReplaceField for "Total Phases" field
|
|
998
|
+
const totalRaw = stateExtractField(stateContent, 'Total Phases');
|
|
999
|
+
if (totalRaw) {
|
|
1000
|
+
const replaced = stateReplaceField(stateContent, 'Total Phases', String(parseInt(totalRaw, 10) - 1));
|
|
1001
|
+
if (replaced)
|
|
1002
|
+
stateContent = replaced;
|
|
1003
|
+
}
|
|
1004
|
+
await writeFile(statePath, stateContent, 'utf-8');
|
|
1005
|
+
stateUpdated = true;
|
|
1006
|
+
}
|
|
1007
|
+
finally {
|
|
1008
|
+
await releaseStateLock(lockPath);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
return {
|
|
1012
|
+
data: {
|
|
1013
|
+
removed: targetPhase,
|
|
1014
|
+
directory_deleted: targetDir,
|
|
1015
|
+
renamed_directories: renamedDirs,
|
|
1016
|
+
renamed_files: renamedFiles,
|
|
1017
|
+
roadmap_updated: true,
|
|
1018
|
+
state_updated: stateUpdated,
|
|
1019
|
+
},
|
|
1020
|
+
};
|
|
1021
|
+
};
|
|
1022
|
+
// ─── updatePerformanceMetricsSection ───────────────────────────────────────
|
|
1023
|
+
/**
|
|
1024
|
+
* Update the Performance Metrics section in STATE.md content.
|
|
1025
|
+
*
|
|
1026
|
+
* Port of updatePerformanceMetricsSection from state.cjs lines 1125-1156.
|
|
1027
|
+
* Updates "Total plans completed" counter and upserts a row in the By Phase table.
|
|
1028
|
+
*
|
|
1029
|
+
* @param content - STATE.md content
|
|
1030
|
+
* @param phaseNum - Phase number being completed
|
|
1031
|
+
* @param planCount - Total number of plans in the phase
|
|
1032
|
+
* @param summaryCount - Number of completed summaries
|
|
1033
|
+
* @returns Modified content
|
|
1034
|
+
*/
|
|
1035
|
+
function updatePerformanceMetricsSection(content, phaseNum, planCount, summaryCount) {
|
|
1036
|
+
// Update Velocity: Total plans completed
|
|
1037
|
+
const totalMatch = content.match(/Total plans completed:\s*(\d+|\[N\])/);
|
|
1038
|
+
const prevTotal = totalMatch && totalMatch[1] !== '[N]' ? parseInt(totalMatch[1], 10) : 0;
|
|
1039
|
+
const newTotal = prevTotal + summaryCount;
|
|
1040
|
+
content = content.replace(/Total plans completed:\s*(\d+|\[N\])/, `Total plans completed: ${newTotal}`);
|
|
1041
|
+
// Update By Phase table — upsert row for this phase
|
|
1042
|
+
const byPhaseTablePattern = /(\|\s*Phase\s*\|\s*Plans\s*\|\s*Total\s*\|\s*Avg\/Plan\s*\|[ \t]*\n\|(?:[- :\t]+\|)+[ \t]*\n)((?:[ \t]*\|[^\n]*\n)*)(?=\n|$)/i;
|
|
1043
|
+
const byPhaseMatch = content.match(byPhaseTablePattern);
|
|
1044
|
+
if (byPhaseMatch) {
|
|
1045
|
+
let tableBody = byPhaseMatch[2].trim();
|
|
1046
|
+
const phaseRowPattern = new RegExp(`^\\|\\s*${escapeRegex(String(phaseNum))}\\s*\\|.*$`, 'm');
|
|
1047
|
+
const newRow = `| ${phaseNum} | ${summaryCount} | - | - |`;
|
|
1048
|
+
if (phaseRowPattern.test(tableBody)) {
|
|
1049
|
+
// Update existing row
|
|
1050
|
+
tableBody = tableBody.replace(new RegExp(`^\\|\\s*${escapeRegex(String(phaseNum))}\\s*\\|.*$`, 'm'), newRow);
|
|
1051
|
+
}
|
|
1052
|
+
else {
|
|
1053
|
+
// Remove placeholder row and add new row
|
|
1054
|
+
tableBody = tableBody.replace(/^\|\s*-\s*\|\s*-\s*\|\s*-\s*\|\s*-\s*\|$/m, '').trim();
|
|
1055
|
+
tableBody = tableBody ? tableBody + '\n' + newRow : newRow;
|
|
1056
|
+
}
|
|
1057
|
+
content = content.replace(byPhaseTablePattern, `$1${tableBody}\n`);
|
|
1058
|
+
}
|
|
1059
|
+
return content;
|
|
1060
|
+
}
|
|
1061
|
+
// ─── phaseComplete handler ────────────────────────────────────────────────
|
|
1062
|
+
/**
|
|
1063
|
+
* Query handler for phase.complete.
|
|
1064
|
+
*
|
|
1065
|
+
* Port of cmdPhaseComplete from phase.cjs lines 663-932.
|
|
1066
|
+
* Marks a phase as done — updates ROADMAP.md (checkbox, progress table,
|
|
1067
|
+
* plan count, plan checkboxes), REQUIREMENTS.md (requirement checkboxes,
|
|
1068
|
+
* traceability table), and STATE.md (current phase, status, progress,
|
|
1069
|
+
* performance metrics) atomically with per-file locks.
|
|
1070
|
+
*
|
|
1071
|
+
* @param args - args[0]: phaseNum (required)
|
|
1072
|
+
* @param projectDir - Project root directory
|
|
1073
|
+
* @returns QueryResult with completion details and warnings
|
|
1074
|
+
*/
|
|
1075
|
+
export const phaseComplete = async (args, projectDir, workstream) => {
|
|
1076
|
+
const phaseNum = args[0];
|
|
1077
|
+
if (!phaseNum) {
|
|
1078
|
+
throw new GSDError('phase number required for phase complete', ErrorClassification.Validation);
|
|
1079
|
+
}
|
|
1080
|
+
assertNoNullBytes(phaseNum, 'phaseNum');
|
|
1081
|
+
const paths = planningPaths(projectDir, workstream);
|
|
1082
|
+
const today = new Date().toISOString().split('T')[0];
|
|
1083
|
+
// Step A: Validate phase exists and get info
|
|
1084
|
+
const phaseInfo = await findPhaseDir(projectDir, phaseNum, workstream);
|
|
1085
|
+
if (!phaseInfo) {
|
|
1086
|
+
throw new GSDError(`Phase ${phaseNum} not found`, ErrorClassification.Validation);
|
|
1087
|
+
}
|
|
1088
|
+
const phaseDir = phaseInfo.dirPath;
|
|
1089
|
+
let phaseFiles;
|
|
1090
|
+
try {
|
|
1091
|
+
phaseFiles = await readdir(phaseDir);
|
|
1092
|
+
}
|
|
1093
|
+
catch {
|
|
1094
|
+
phaseFiles = [];
|
|
1095
|
+
}
|
|
1096
|
+
const plans = phaseFiles.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md');
|
|
1097
|
+
const summaries = phaseFiles.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
|
|
1098
|
+
const planCount = plans.length;
|
|
1099
|
+
const summaryCount = summaries.length;
|
|
1100
|
+
let requirementsUpdated = false;
|
|
1101
|
+
// Step B: Check for verification warnings (non-blocking)
|
|
1102
|
+
const warnings = [];
|
|
1103
|
+
for (const file of phaseFiles.filter(f => f.includes('-UAT') && f.endsWith('.md'))) {
|
|
1104
|
+
try {
|
|
1105
|
+
const content = await readFile(join(phaseDir, file), 'utf-8');
|
|
1106
|
+
if (/result: pending/.test(content))
|
|
1107
|
+
warnings.push(`${file}: has pending tests`);
|
|
1108
|
+
if (/result: blocked/.test(content))
|
|
1109
|
+
warnings.push(`${file}: has blocked tests`);
|
|
1110
|
+
if (/status: partial/.test(content))
|
|
1111
|
+
warnings.push(`${file}: testing incomplete (partial)`);
|
|
1112
|
+
if (/status: diagnosed/.test(content))
|
|
1113
|
+
warnings.push(`${file}: has diagnosed gaps`);
|
|
1114
|
+
}
|
|
1115
|
+
catch { /* intentionally empty */ }
|
|
1116
|
+
}
|
|
1117
|
+
for (const file of phaseFiles.filter(f => f.includes('-VERIFICATION') && f.endsWith('.md'))) {
|
|
1118
|
+
try {
|
|
1119
|
+
const content = await readFile(join(phaseDir, file), 'utf-8');
|
|
1120
|
+
if (/status: human_needed/.test(content))
|
|
1121
|
+
warnings.push(`${file}: needs human verification`);
|
|
1122
|
+
if (/status: gaps_found/.test(content))
|
|
1123
|
+
warnings.push(`${file}: has unresolved gaps`);
|
|
1124
|
+
}
|
|
1125
|
+
catch { /* intentionally empty */ }
|
|
1126
|
+
}
|
|
1127
|
+
// Step C: Update ROADMAP.md atomically
|
|
1128
|
+
if (existsSync(paths.roadmap)) {
|
|
1129
|
+
await readModifyWriteRoadmapMd(projectDir, async (roadmapContent) => {
|
|
1130
|
+
// Padding-tolerant fragment so a padded input like "02.7" still matches
|
|
1131
|
+
// un-padded ROADMAP prose ("### Phase 2.7:"). CJS routes every phase-
|
|
1132
|
+
// number ROADMAP regex through phaseMarkdownRegexSource (#3537) —
|
|
1133
|
+
// mirror that contract here so phase.complete with the padded form
|
|
1134
|
+
// produces the same ROADMAP as the un-padded form.
|
|
1135
|
+
const phaseEscaped = phaseMarkdownRegexSource(phaseNum);
|
|
1136
|
+
// Checkbox: - [ ] Phase N: -> - [x] Phase N: (...completed DATE)
|
|
1137
|
+
// CJS parity (phase.cjs): direct replace, NOT scoped through
|
|
1138
|
+
// replaceInCurrentMilestone. Same reasoning as the plan-count
|
|
1139
|
+
// update below — milestone wrapped in <details> would otherwise be
|
|
1140
|
+
// skipped (bug-2005).
|
|
1141
|
+
const checkboxPattern = new RegExp(`(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${phaseEscaped}[:\\s][^\\n]*)`, 'i');
|
|
1142
|
+
roadmapContent = roadmapContent.replace(checkboxPattern, `$1x$2 (completed ${today})`);
|
|
1143
|
+
// Progress table: update Status to Complete, add date
|
|
1144
|
+
const tableRowPattern = new RegExp(`^(\\|\\s*${phaseEscaped}\\.?\\s[^|]*(?:\\|[^\\n]*))$`, 'im');
|
|
1145
|
+
roadmapContent = roadmapContent.replace(tableRowPattern, (fullRow) => {
|
|
1146
|
+
const cells = fullRow.split('|').slice(1, -1);
|
|
1147
|
+
if (cells.length === 5) {
|
|
1148
|
+
cells[2] = ` ${summaryCount}/${planCount} `;
|
|
1149
|
+
cells[3] = ' Complete ';
|
|
1150
|
+
cells[4] = ` ${today} `;
|
|
1151
|
+
}
|
|
1152
|
+
else if (cells.length === 4) {
|
|
1153
|
+
cells[1] = ` ${summaryCount}/${planCount} `;
|
|
1154
|
+
cells[2] = ' Complete ';
|
|
1155
|
+
cells[3] = ` ${today} `;
|
|
1156
|
+
}
|
|
1157
|
+
return '|' + cells.join('|') + '|';
|
|
1158
|
+
});
|
|
1159
|
+
// Update plan count in phase section.
|
|
1160
|
+
// CJS parity (phase.cjs:1076-1083): direct replace, NOT scoped through
|
|
1161
|
+
// replaceInCurrentMilestone. Scoping to "after last </details>" fails
|
|
1162
|
+
// when the current milestone itself is wrapped in <details open>...
|
|
1163
|
+
// </details> — there's no content after the close tag, so the regex
|
|
1164
|
+
// never matches and **Plans:** stays at 0/N (bug-2005).
|
|
1165
|
+
const planCountPattern = new RegExp(`(#{2,4}\\s*Phase\\s+${phaseEscaped}(?:(?!\\n#{2,4})[\\s\\S])*?\\*\\*Plans:\\*\\*[ \\t]*)[^\\n]+`, 'i');
|
|
1166
|
+
roadmapContent = roadmapContent.replace(planCountPattern, `$1${summaryCount}/${planCount} plans complete`);
|
|
1167
|
+
// Mark completed plan checkboxes
|
|
1168
|
+
for (const summaryFile of summaries) {
|
|
1169
|
+
const planId = summaryFile.replace('-SUMMARY.md', '').replace('SUMMARY.md', '');
|
|
1170
|
+
if (!planId)
|
|
1171
|
+
continue;
|
|
1172
|
+
const planEscaped = escapeRegex(planId);
|
|
1173
|
+
const planCheckboxPattern = new RegExp(`(-\\s*\\[) (\\]\\s*(?:\\*\\*)?${planEscaped}(?:\\*\\*)?)`, 'i');
|
|
1174
|
+
roadmapContent = roadmapContent.replace(planCheckboxPattern, '$1x$2');
|
|
1175
|
+
}
|
|
1176
|
+
// Step D: Update REQUIREMENTS.md
|
|
1177
|
+
const reqPath = paths.requirements;
|
|
1178
|
+
if (existsSync(reqPath)) {
|
|
1179
|
+
const currentMilestoneRoadmap = await extractCurrentMilestone(roadmapContent, projectDir);
|
|
1180
|
+
const phaseSectionMatch = currentMilestoneRoadmap.match(new RegExp(`(#{2,4}\\s*Phase\\s+${phaseEscaped}[:\\s][\\s\\S]*?)(?=#{2,4}\\s*Phase\\s+|$)`, 'i'));
|
|
1181
|
+
const sectionText = phaseSectionMatch ? phaseSectionMatch[1] : '';
|
|
1182
|
+
const reqMatch = sectionText.match(/\*\*Requirements\*?\*?:?\s*([^\n]+)/i);
|
|
1183
|
+
let reqContent = await readFile(reqPath, 'utf-8');
|
|
1184
|
+
let reqContentChanged = false;
|
|
1185
|
+
if (reqMatch) {
|
|
1186
|
+
const reqIds = reqMatch[1].replace(/[[\]]/g, '').split(/[,\s]+/).map(r => r.trim()).filter(Boolean);
|
|
1187
|
+
for (const reqId of reqIds) {
|
|
1188
|
+
const reqEscaped = escapeRegex(reqId);
|
|
1189
|
+
const before = reqContent;
|
|
1190
|
+
// Update checkbox: - [ ] **REQ-ID** -> - [x] **REQ-ID**
|
|
1191
|
+
reqContent = reqContent.replace(new RegExp(`(-\\s*\\[)[ ](\\]\\s*\\*\\*${reqEscaped}\\*\\*)`, 'gi'), '$1x$2');
|
|
1192
|
+
// Update traceability table: Pending/In Progress -> Complete
|
|
1193
|
+
reqContent = reqContent.replace(new RegExp(`(\\|\\s*${reqEscaped}\\s*\\|[^|]+\\|)\\s*(?:Pending|In Progress)\\s*(\\|)`, 'gi'), '$1 Complete $2');
|
|
1194
|
+
if (reqContent !== before)
|
|
1195
|
+
reqContentChanged = true;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
// Bug #2526 parity (phase.cjs:1140-1167): independent of whether the
|
|
1199
|
+
// roadmap declared a Requirements: line, scan the REQUIREMENTS.md
|
|
1200
|
+
// body for `**REQ-ID**` references and compare against the IDs that
|
|
1201
|
+
// actually appear in the Traceability table. Surface every body
|
|
1202
|
+
// ID that has no traceability row so the operator can keep the
|
|
1203
|
+
// table in sync.
|
|
1204
|
+
const bodyReqIds = [];
|
|
1205
|
+
const bodyReqPattern = /\*\*([A-Z][A-Z0-9]*-\d+)\*\*/g;
|
|
1206
|
+
let bodyMatch;
|
|
1207
|
+
while ((bodyMatch = bodyReqPattern.exec(reqContent)) !== null) {
|
|
1208
|
+
if (!bodyReqIds.includes(bodyMatch[1]))
|
|
1209
|
+
bodyReqIds.push(bodyMatch[1]);
|
|
1210
|
+
}
|
|
1211
|
+
const traceabilityHeadingMatch = reqContent.match(/^#{1,6}\s+Traceability\b/im);
|
|
1212
|
+
const traceabilitySection = traceabilityHeadingMatch
|
|
1213
|
+
? reqContent.slice(traceabilityHeadingMatch.index)
|
|
1214
|
+
: '';
|
|
1215
|
+
const tableReqIds = new Set();
|
|
1216
|
+
const tableRowPattern = /^\|\s*([A-Z][A-Z0-9]*-\d+)\s*\|/gm;
|
|
1217
|
+
let tableMatch;
|
|
1218
|
+
while ((tableMatch = tableRowPattern.exec(traceabilitySection)) !== null) {
|
|
1219
|
+
tableReqIds.add(tableMatch[1]);
|
|
1220
|
+
}
|
|
1221
|
+
const unregistered = bodyReqIds.filter((id) => !tableReqIds.has(id));
|
|
1222
|
+
if (unregistered.length > 0) {
|
|
1223
|
+
warnings.push(`REQUIREMENTS.md: ${unregistered.length} REQ-ID(s) found in body but missing from Traceability table: ${unregistered.join(', ')} — add them manually to keep traceability in sync`);
|
|
1224
|
+
}
|
|
1225
|
+
if (reqContentChanged) {
|
|
1226
|
+
await writeFile(reqPath, reqContent, 'utf-8');
|
|
1227
|
+
requirementsUpdated = true;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
return roadmapContent;
|
|
1231
|
+
}, workstream);
|
|
1232
|
+
}
|
|
1233
|
+
// Step E: Find next phase — filesystem first, then ROADMAP.md fallback
|
|
1234
|
+
let nextPhaseNum = null;
|
|
1235
|
+
let nextPhaseName = null;
|
|
1236
|
+
let isLastPhase = true;
|
|
1237
|
+
// Tracks whether the completed phase belongs to the primary milestone in STATE.md.
|
|
1238
|
+
// When false (parallel-milestone case, Bug #2676), the milestone filter is bypassed
|
|
1239
|
+
// for next-phase detection so phases from the same secondary milestone are visible.
|
|
1240
|
+
let completedPhaseInPrimaryMilestone = true;
|
|
1241
|
+
try {
|
|
1242
|
+
const isDirInMilestone = await getMilestonePhaseFilter(projectDir, workstream);
|
|
1243
|
+
const entries = await readdir(paths.phases, { withFileTypes: true });
|
|
1244
|
+
const allDirs = entries.filter(e => e.isDirectory()).map(e => e.name);
|
|
1245
|
+
// Guard: if the completed phase's directory is not in the current-milestone filter
|
|
1246
|
+
// set, the filter was built from a different (primary) milestone in STATE.md.
|
|
1247
|
+
// In that case skip the filter so we can find the true next phase on disk.
|
|
1248
|
+
// This handles parallel-milestone workflows where STATE.md's `milestone:` field
|
|
1249
|
+
// points at the primary milestone but the phase being completed belongs to a
|
|
1250
|
+
// secondary in-flight milestone. (Bug #2676)
|
|
1251
|
+
const completedDirInFilter = allDirs.some((d) => {
|
|
1252
|
+
const dm = d.match(/^(\d+[A-Z]?(?:\.\d+)*)-?/i);
|
|
1253
|
+
return dm && comparePhaseNum(dm[1], phaseNum) === 0 && isDirInMilestone(d);
|
|
1254
|
+
});
|
|
1255
|
+
completedPhaseInPrimaryMilestone = completedDirInFilter;
|
|
1256
|
+
const effectiveFilter = completedDirInFilter ? isDirInMilestone : (_d) => true;
|
|
1257
|
+
const dirs = allDirs
|
|
1258
|
+
.filter(effectiveFilter)
|
|
1259
|
+
.sort((a, b) => comparePhaseNum(a, b));
|
|
1260
|
+
for (const dir of dirs) {
|
|
1261
|
+
const dm = dir.match(/^(\d+[A-Z]?(?:\.\d+)*)-?(.*)/i);
|
|
1262
|
+
if (dm) {
|
|
1263
|
+
// Bug #2129 parity: skip backlog phases (999.x). They are parked
|
|
1264
|
+
// ideas with reserved numbering, not part of the active sequence.
|
|
1265
|
+
// Without this, completing phase 2 in a project that has a 999.1
|
|
1266
|
+
// backlog directory would jump next_phase to 999.1 instead of the
|
|
1267
|
+
// intended Phase 3 from ROADMAP.
|
|
1268
|
+
if (/^999(?:\.|$)/.test(dm[1]))
|
|
1269
|
+
continue;
|
|
1270
|
+
if (comparePhaseNum(dm[1], phaseNum) > 0) {
|
|
1271
|
+
nextPhaseNum = dm[1];
|
|
1272
|
+
nextPhaseName = dm[2] || null;
|
|
1273
|
+
isLastPhase = false;
|
|
1274
|
+
break;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
catch { /* intentionally empty */ }
|
|
1280
|
+
// Fallback: check ROADMAP.md for phases not yet scaffolded.
|
|
1281
|
+
// When the completed phase is from a parallel (non-primary) milestone, scan the
|
|
1282
|
+
// full ROADMAP rather than the primary-milestone slice so 41.3 is visible when
|
|
1283
|
+
// completing 41.2 for a secondary milestone. (Bug #2676)
|
|
1284
|
+
if (isLastPhase && existsSync(paths.roadmap)) {
|
|
1285
|
+
try {
|
|
1286
|
+
const roadmapContent = await readFile(paths.roadmap, 'utf-8');
|
|
1287
|
+
const roadmapForPhases = completedPhaseInPrimaryMilestone
|
|
1288
|
+
? await extractCurrentMilestone(roadmapContent, projectDir)
|
|
1289
|
+
: roadmapContent;
|
|
1290
|
+
const phasePattern = /#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:\s*([^\n]+)/gi;
|
|
1291
|
+
let pm;
|
|
1292
|
+
while ((pm = phasePattern.exec(roadmapForPhases)) !== null) {
|
|
1293
|
+
if (comparePhaseNum(pm[1], phaseNum) > 0) {
|
|
1294
|
+
nextPhaseNum = pm[1];
|
|
1295
|
+
nextPhaseName = pm[2].replace(/\(INSERTED\)/i, '').trim().toLowerCase().replace(/\s+/g, '-');
|
|
1296
|
+
isLastPhase = false;
|
|
1297
|
+
break;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
catch { /* intentionally empty */ }
|
|
1302
|
+
}
|
|
1303
|
+
// Step F: Update STATE.md atomically
|
|
1304
|
+
let stateUpdated = false;
|
|
1305
|
+
if (existsSync(paths.state)) {
|
|
1306
|
+
const lockPath = await acquireStateLock(paths.state);
|
|
1307
|
+
try {
|
|
1308
|
+
const rawState = await readFile(paths.state, 'utf-8');
|
|
1309
|
+
// Split into frontmatter and body to prevent field replacement from
|
|
1310
|
+
// matching YAML keys (e.g., `status:` in frontmatter vs `Status:` in body).
|
|
1311
|
+
// Pattern 11: Strip frontmatter before modifier (from Phase 11 decisions).
|
|
1312
|
+
const fmMatch = rawState.match(/^(---\r?\n[\s\S]*?\r?\n---)\s*/);
|
|
1313
|
+
let frontmatter = fmMatch ? fmMatch[1] : '';
|
|
1314
|
+
let body = fmMatch ? rawState.slice(fmMatch[0].length) : rawState;
|
|
1315
|
+
// Update Current Phase — preserve "X of Y (Name)" compound format
|
|
1316
|
+
const phaseValue = nextPhaseNum || phaseNum;
|
|
1317
|
+
const existingPhaseField = stateExtractField(body, 'Current Phase')
|
|
1318
|
+
|| stateExtractField(body, 'Phase');
|
|
1319
|
+
let newPhaseValue = String(phaseValue);
|
|
1320
|
+
if (existingPhaseField) {
|
|
1321
|
+
const totalMatch = existingPhaseField.match(/of\s+(\d+)/);
|
|
1322
|
+
const nameMatch = existingPhaseField.match(/\(([^)]+)\)/);
|
|
1323
|
+
if (totalMatch) {
|
|
1324
|
+
const total = totalMatch[1];
|
|
1325
|
+
const nameStr = nextPhaseName
|
|
1326
|
+
? ` (${nextPhaseName.replace(/-/g, ' ')})`
|
|
1327
|
+
: (nameMatch ? ` (${nameMatch[1]})` : '');
|
|
1328
|
+
newPhaseValue = `${phaseValue} of ${total}${nameStr}`;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
body = stateReplaceFieldWithFallback(body, 'Current Phase', 'Phase', newPhaseValue);
|
|
1332
|
+
// Update Status
|
|
1333
|
+
body = stateReplaceFieldWithFallback(body, 'Status', null, isLastPhase ? 'Milestone complete' : 'Ready to plan');
|
|
1334
|
+
// Update Current Plan
|
|
1335
|
+
body = stateReplaceFieldWithFallback(body, 'Current Plan', 'Plan', 'Not started');
|
|
1336
|
+
// Update Last Activity
|
|
1337
|
+
body = stateReplaceFieldWithFallback(body, 'Last Activity', 'Last activity', today);
|
|
1338
|
+
// Update Performance Metrics section (operates on body only)
|
|
1339
|
+
body = updatePerformanceMetricsSection(body, phaseNum, planCount, summaryCount);
|
|
1340
|
+
// ── Root cause 1 fix: derive completed_phases from ROADMAP, not blind increment ──
|
|
1341
|
+
// Read the freshly-updated ROADMAP (after Step C) to count Complete rows.
|
|
1342
|
+
// This makes phase.complete idempotent: running it twice on the same phase
|
|
1343
|
+
// produces the same completed_phases value.
|
|
1344
|
+
let derivedCompletedPhases = null;
|
|
1345
|
+
let derivedTotalPhases = null;
|
|
1346
|
+
let derivedTotalPlans = null;
|
|
1347
|
+
if (existsSync(paths.roadmap)) {
|
|
1348
|
+
try {
|
|
1349
|
+
const freshRoadmap = await readFile(paths.roadmap, 'utf-8');
|
|
1350
|
+
// Count Complete rows in the progress table (Status column = "Complete")
|
|
1351
|
+
const tableCompletePattern = /\|\s*\d+[A-Z]?\S*\s*\|[^|]*\|\s*Complete\s*\|/gi;
|
|
1352
|
+
const completeMatches = freshRoadmap.match(tableCompletePattern);
|
|
1353
|
+
derivedCompletedPhases = completeMatches ? completeMatches.length : null;
|
|
1354
|
+
// Count total phase rows in progress table (header + separator + data rows)
|
|
1355
|
+
// Identify the progress table by looking for Phase|Plans|Status|Completed header
|
|
1356
|
+
const progressTableMatch = freshRoadmap.match(/\|\s*Phase\s*\|\s*Plans\s*\|\s*Status\s*\|\s*Completed\s*\|(.*\n)*?(?:\n|$)/i);
|
|
1357
|
+
if (progressTableMatch) {
|
|
1358
|
+
const tableText = progressTableMatch[0];
|
|
1359
|
+
const dataRowPattern = /^\|\s*\d+[A-Z]?\S*\s*\|/gm;
|
|
1360
|
+
const dataRows = tableText.match(dataRowPattern);
|
|
1361
|
+
derivedTotalPhases = dataRows ? dataRows.length : null;
|
|
1362
|
+
}
|
|
1363
|
+
// Sum plan counts from M/N or 0/N columns in the progress table
|
|
1364
|
+
let totalPlansSum = 0;
|
|
1365
|
+
const planCellPattern = /\|\s*\d+[A-Z]?\S*\s*\|\s*(\d+)\/(\d+)\s*\|/gi;
|
|
1366
|
+
let pm;
|
|
1367
|
+
while ((pm = planCellPattern.exec(freshRoadmap)) !== null) {
|
|
1368
|
+
totalPlansSum += parseInt(pm[2], 10);
|
|
1369
|
+
}
|
|
1370
|
+
if (totalPlansSum > 0)
|
|
1371
|
+
derivedTotalPlans = totalPlansSum;
|
|
1372
|
+
}
|
|
1373
|
+
catch { /* intentionally empty — fall through to existing values */ }
|
|
1374
|
+
}
|
|
1375
|
+
// Count completed plans from all SUMMARY files across phase dirs
|
|
1376
|
+
let derivedCompletedPlans = null;
|
|
1377
|
+
try {
|
|
1378
|
+
const phaseEntries = await readdir(paths.phases, { withFileTypes: true });
|
|
1379
|
+
let summaryTotal = 0;
|
|
1380
|
+
for (const entry of phaseEntries) {
|
|
1381
|
+
if (!entry.isDirectory())
|
|
1382
|
+
continue;
|
|
1383
|
+
try {
|
|
1384
|
+
const files = await readdir(join(paths.phases, entry.name));
|
|
1385
|
+
summaryTotal += files.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md').length;
|
|
1386
|
+
}
|
|
1387
|
+
catch { /* intentionally empty */ }
|
|
1388
|
+
}
|
|
1389
|
+
derivedCompletedPlans = summaryTotal;
|
|
1390
|
+
}
|
|
1391
|
+
catch { /* intentionally empty */ }
|
|
1392
|
+
// ── Root cause 2 fix: update all stale frontmatter fields ──
|
|
1393
|
+
// completed_phases — derived from ROADMAP (idempotent)
|
|
1394
|
+
if (derivedCompletedPhases !== null && frontmatter.includes('completed_phases:')) {
|
|
1395
|
+
frontmatter = frontmatter.replace(/completed_phases:\s*\d+/, `completed_phases: ${derivedCompletedPhases}`);
|
|
1396
|
+
}
|
|
1397
|
+
// total_phases — keep in sync with ROADMAP
|
|
1398
|
+
if (derivedTotalPhases !== null && frontmatter.includes('total_phases:')) {
|
|
1399
|
+
frontmatter = frontmatter.replace(/total_phases:\s*\d+/, `total_phases: ${derivedTotalPhases}`);
|
|
1400
|
+
}
|
|
1401
|
+
// total_plans — derived from ROADMAP plan column sums
|
|
1402
|
+
if (derivedTotalPlans !== null && frontmatter.includes('total_plans:')) {
|
|
1403
|
+
frontmatter = frontmatter.replace(/total_plans:\s*\d+/, `total_plans: ${derivedTotalPlans}`);
|
|
1404
|
+
}
|
|
1405
|
+
// completed_plans — count of SUMMARY files on disk
|
|
1406
|
+
if (derivedCompletedPlans !== null && frontmatter.includes('completed_plans:')) {
|
|
1407
|
+
frontmatter = frontmatter.replace(/completed_plans:\s*\d+/, `completed_plans: ${derivedCompletedPlans}`);
|
|
1408
|
+
}
|
|
1409
|
+
// percent — recompute from fresh derived values
|
|
1410
|
+
const effectiveCompleted = derivedCompletedPhases ?? parseInt(frontmatter.match(/completed_phases:\s*(\d+)/)?.[1] ?? '0', 10);
|
|
1411
|
+
const effectiveTotal = derivedTotalPhases ?? parseInt(frontmatter.match(/total_phases:\s*(\d+)/)?.[1] ?? '0', 10);
|
|
1412
|
+
if (effectiveTotal > 0 && frontmatter.includes('percent:')) {
|
|
1413
|
+
const newPercent = Math.round((effectiveCompleted / effectiveTotal) * 100);
|
|
1414
|
+
frontmatter = frontmatter.replace(/(percent:\s*)\d+/, `$1${newPercent}`);
|
|
1415
|
+
}
|
|
1416
|
+
// last_updated — refresh to current timestamp
|
|
1417
|
+
const nowIso = new Date().toISOString();
|
|
1418
|
+
if (frontmatter.includes('last_updated:')) {
|
|
1419
|
+
frontmatter = frontmatter.replace(/last_updated:\s*\S+/, `last_updated: ${nowIso}`);
|
|
1420
|
+
}
|
|
1421
|
+
// stopped_at — set to phase completion message
|
|
1422
|
+
const stoppedAtValue = isLastPhase
|
|
1423
|
+
? `Milestone complete (Phase ${phaseNum} was final phase)`
|
|
1424
|
+
: `Phase ${phaseNum} complete (${summaryCount}/${planCount}) — ready to discuss Phase ${nextPhaseNum}`;
|
|
1425
|
+
if (frontmatter.includes('stopped_at:')) {
|
|
1426
|
+
frontmatter = frontmatter.replace(/stopped_at:\s*.+/, `stopped_at: ${stoppedAtValue}`);
|
|
1427
|
+
}
|
|
1428
|
+
else {
|
|
1429
|
+
// Insert stopped_at before closing ---
|
|
1430
|
+
frontmatter = frontmatter.replace(/(---\s*)$/, `stopped_at: ${stoppedAtValue}\n$1`);
|
|
1431
|
+
}
|
|
1432
|
+
// ── Root cause 2 fix: update body Current focus ──
|
|
1433
|
+
const focusValue = isLastPhase
|
|
1434
|
+
? 'Milestone complete'
|
|
1435
|
+
: (nextPhaseName
|
|
1436
|
+
? `Phase ${nextPhaseNum} — ${nextPhaseName.replace(/-/g, ' ')}`
|
|
1437
|
+
: `Phase ${nextPhaseNum}`);
|
|
1438
|
+
const focusPattern = /(\*\*Current focus:\*\*\s*).*/i;
|
|
1439
|
+
if (focusPattern.test(body)) {
|
|
1440
|
+
body = body.replace(focusPattern, (_m, prefix) => `${prefix}${focusValue}`);
|
|
1441
|
+
}
|
|
1442
|
+
// Update frontmatter status field
|
|
1443
|
+
frontmatter = frontmatter.replace(/status:\s*.+/, `status: ${isLastPhase ? 'milestone_complete' : 'ready_to_plan'}`);
|
|
1444
|
+
// Reassemble and write
|
|
1445
|
+
const stateContent = frontmatter + '\n\n' + body;
|
|
1446
|
+
await writeFile(paths.state, stateContent, 'utf-8');
|
|
1447
|
+
stateUpdated = true;
|
|
1448
|
+
}
|
|
1449
|
+
finally {
|
|
1450
|
+
await releaseStateLock(lockPath);
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
// Step F2: Auto-prune STATE.md decisions when `workflow.auto_prune_state`
|
|
1454
|
+
// is true. Mirrors CJS cmdPhaseComplete (bin/lib/phase.cjs:1378-1390) which
|
|
1455
|
+
// calls cmdStatePrune({keepRecent:'3', dryRun:false, silent:true}). Without
|
|
1456
|
+
// this, completing phase N with auto_prune_state=true leaves stale [Phase
|
|
1457
|
+
// 1..N-3] decisions in STATE.md forever. (#2087)
|
|
1458
|
+
let autoPruned = false;
|
|
1459
|
+
try {
|
|
1460
|
+
if (existsSync(paths.config)) {
|
|
1461
|
+
const rawConfig = JSON.parse(await readFile(paths.config, 'utf-8'));
|
|
1462
|
+
const wf = rawConfig.workflow;
|
|
1463
|
+
if (wf && wf.auto_prune_state === true && existsSync(paths.state)) {
|
|
1464
|
+
const { statePrune } = await import('./state-mutation.js');
|
|
1465
|
+
await statePrune(['--keep-recent', '3', '--silent'], projectDir, workstream);
|
|
1466
|
+
autoPruned = true;
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
catch { /* best-effort, matches CJS */ }
|
|
1471
|
+
// Step G: Return result
|
|
1472
|
+
return {
|
|
1473
|
+
data: {
|
|
1474
|
+
completed_phase: phaseNum,
|
|
1475
|
+
phase_name: phaseInfo.phaseName,
|
|
1476
|
+
plans_executed: `${summaryCount}/${planCount}`,
|
|
1477
|
+
next_phase: nextPhaseNum,
|
|
1478
|
+
next_phase_name: nextPhaseName,
|
|
1479
|
+
is_last_phase: isLastPhase,
|
|
1480
|
+
date: today,
|
|
1481
|
+
roadmap_updated: existsSync(paths.roadmap),
|
|
1482
|
+
state_updated: stateUpdated,
|
|
1483
|
+
requirements_updated: requirementsUpdated,
|
|
1484
|
+
auto_pruned: autoPruned,
|
|
1485
|
+
warnings,
|
|
1486
|
+
has_warnings: warnings.length > 0,
|
|
1487
|
+
},
|
|
1488
|
+
};
|
|
1489
|
+
};
|
|
1490
|
+
// ─── phasesClear handler ──────────────────────────────────────────────────
|
|
1491
|
+
/**
|
|
1492
|
+
* Query handler for phases.clear.
|
|
1493
|
+
*
|
|
1494
|
+
* Port of cmdPhasesClear from milestone.cjs lines 250-277.
|
|
1495
|
+
* Deletes all phase directories except 999.x backlog phases.
|
|
1496
|
+
* Requires --confirm flag to proceed.
|
|
1497
|
+
*
|
|
1498
|
+
* @param args - args[0]: '--confirm' to proceed (optional)
|
|
1499
|
+
* @param projectDir - Project root directory
|
|
1500
|
+
* @returns QueryResult with { cleared: count }
|
|
1501
|
+
*/
|
|
1502
|
+
export const phasesClear = async (args, projectDir, workstream) => {
|
|
1503
|
+
const phasesDir = planningPaths(projectDir, workstream).phases;
|
|
1504
|
+
const confirm = Array.isArray(args) && args.includes('--confirm');
|
|
1505
|
+
let cleared = 0;
|
|
1506
|
+
if (existsSync(phasesDir)) {
|
|
1507
|
+
const entries = await readdir(phasesDir, { withFileTypes: true });
|
|
1508
|
+
const dirs = entries.filter(e => e.isDirectory() && !/^999(?:\.|$)/.test(e.name));
|
|
1509
|
+
if (dirs.length > 0 && !confirm) {
|
|
1510
|
+
throw new GSDError(`phases clear would delete ${dirs.length} phase director${dirs.length === 1 ? 'y' : 'ies'}. ` +
|
|
1511
|
+
`Pass --confirm to proceed.`, ErrorClassification.Validation);
|
|
1512
|
+
}
|
|
1513
|
+
for (const entry of dirs) {
|
|
1514
|
+
await rm(join(phasesDir, entry.name), { recursive: true, force: true });
|
|
1515
|
+
cleared++;
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
return { data: { cleared } };
|
|
1519
|
+
};
|
|
1520
|
+
// ─── phasesArchive handler ────────────────────────────────────────────────
|
|
1521
|
+
/**
|
|
1522
|
+
* Query handler for phases.archive.
|
|
1523
|
+
*
|
|
1524
|
+
* Extracted from cmdMilestoneComplete, milestone.cjs lines 210-227.
|
|
1525
|
+
* Moves milestone phase directories to milestones/{version}-phases/.
|
|
1526
|
+
*
|
|
1527
|
+
* @param args - args[0]: version string (e.g., "v3.0")
|
|
1528
|
+
* @param projectDir - Project root directory
|
|
1529
|
+
* @returns QueryResult with { archived: count, version, archive_directory }
|
|
1530
|
+
*/
|
|
1531
|
+
export const phasesList = async (args, projectDir, workstream) => {
|
|
1532
|
+
const paths = planningPaths(projectDir, workstream);
|
|
1533
|
+
const phasesDir = paths.phases;
|
|
1534
|
+
const typeIdx = args.indexOf('--type');
|
|
1535
|
+
const phaseIdx = args.indexOf('--phase');
|
|
1536
|
+
const type = typeIdx !== -1 ? args[typeIdx + 1] : null;
|
|
1537
|
+
const phase = phaseIdx !== -1 ? args[phaseIdx + 1] : null;
|
|
1538
|
+
const includeArchived = args.includes('--include-archived');
|
|
1539
|
+
if (!existsSync(phasesDir)) {
|
|
1540
|
+
return { data: type ? { files: [], count: 0 } : { directories: [], count: 0 } };
|
|
1541
|
+
}
|
|
1542
|
+
const entries = await readdir(phasesDir, { withFileTypes: true });
|
|
1543
|
+
let dirs = entries.filter(e => e.isDirectory()).map(e => e.name);
|
|
1544
|
+
if (includeArchived) {
|
|
1545
|
+
const milestonesDir = join(paths.planning, 'milestones');
|
|
1546
|
+
if (existsSync(milestonesDir)) {
|
|
1547
|
+
const milestoneEntries = await readdir(milestonesDir, { withFileTypes: true });
|
|
1548
|
+
for (const mDir of milestoneEntries.filter(e => e.isDirectory() && e.name.endsWith('-phases'))) {
|
|
1549
|
+
const milestone = mDir.name.replace(/-phases$/, '');
|
|
1550
|
+
const archivedEntries = await readdir(join(milestonesDir, mDir.name), { withFileTypes: true });
|
|
1551
|
+
for (const a of archivedEntries.filter(e => e.isDirectory())) {
|
|
1552
|
+
dirs.push(`${a.name} [${milestone}]`);
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
dirs.sort((a, b) => comparePhaseNum(a, b));
|
|
1558
|
+
if (phase) {
|
|
1559
|
+
const normalized = normalizePhaseName(phase);
|
|
1560
|
+
const match = dirs.find(d => phaseTokenMatches(d, normalized));
|
|
1561
|
+
if (!match) {
|
|
1562
|
+
return { data: { files: [], count: 0, phase_dir: null, error: 'Phase not found' } };
|
|
1563
|
+
}
|
|
1564
|
+
dirs = [match];
|
|
1565
|
+
}
|
|
1566
|
+
if (type) {
|
|
1567
|
+
const files = [];
|
|
1568
|
+
const warnings = [];
|
|
1569
|
+
for (const dir of dirs) {
|
|
1570
|
+
const dirPath = join(phasesDir, dir);
|
|
1571
|
+
if (!existsSync(dirPath))
|
|
1572
|
+
continue;
|
|
1573
|
+
const dirFiles = await readdir(dirPath);
|
|
1574
|
+
let filtered;
|
|
1575
|
+
if (type === 'plans') {
|
|
1576
|
+
filtered = dirFiles.filter(isCanonicalPlanFile);
|
|
1577
|
+
// #2893 parity — surface plan-shaped files the canonical filter
|
|
1578
|
+
// rejected so callers (executor init, etc.) don't silently see zero
|
|
1579
|
+
// plans. Per-dir prefix mirrors phase.cjs:120.
|
|
1580
|
+
const w = describeNonCanonicalPlans(dirFiles, filtered);
|
|
1581
|
+
if (w)
|
|
1582
|
+
warnings.push(`${dir}: ${w}`);
|
|
1583
|
+
}
|
|
1584
|
+
else if (type === 'summaries') {
|
|
1585
|
+
filtered = dirFiles.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
|
|
1586
|
+
}
|
|
1587
|
+
else {
|
|
1588
|
+
filtered = dirFiles;
|
|
1589
|
+
}
|
|
1590
|
+
files.push(...filtered.sort());
|
|
1591
|
+
}
|
|
1592
|
+
const result = {
|
|
1593
|
+
files,
|
|
1594
|
+
count: files.length,
|
|
1595
|
+
phase_dir: phase ? dirs[0]?.replace(/^\d+(?:\.\d+)*-?/, '') : null,
|
|
1596
|
+
};
|
|
1597
|
+
if (warnings.length)
|
|
1598
|
+
result['warning'] = warnings.join(' | ');
|
|
1599
|
+
return { data: result };
|
|
1600
|
+
}
|
|
1601
|
+
return { data: { directories: dirs, count: dirs.length } };
|
|
1602
|
+
};
|
|
1603
|
+
export const phaseNextDecimal = async (args, projectDir, workstream) => {
|
|
1604
|
+
const basePhase = args[0];
|
|
1605
|
+
if (!basePhase) {
|
|
1606
|
+
throw new GSDError('base phase number required', ErrorClassification.Validation);
|
|
1607
|
+
}
|
|
1608
|
+
assertNoNullBytes(basePhase, 'basePhase');
|
|
1609
|
+
const paths = planningPaths(projectDir, workstream);
|
|
1610
|
+
const phasesDir = paths.phases;
|
|
1611
|
+
const normalized = normalizePhaseName(basePhase);
|
|
1612
|
+
const decimalSet = new Set();
|
|
1613
|
+
let baseExists = false;
|
|
1614
|
+
const dirNames = await listDirectories(phasesDir);
|
|
1615
|
+
baseExists = dirNames.some((d) => phaseTokenMatches(d, normalized));
|
|
1616
|
+
for (const suffix of collectDecimalSuffixesFromDirNames(normalized, dirNames)) {
|
|
1617
|
+
decimalSet.add(suffix);
|
|
1618
|
+
}
|
|
1619
|
+
const roadmapPath = paths.roadmap;
|
|
1620
|
+
if (existsSync(roadmapPath)) {
|
|
1621
|
+
try {
|
|
1622
|
+
const roadmapContent = await readFile(roadmapPath, 'utf-8');
|
|
1623
|
+
for (const suffix of collectDecimalSuffixesFromRoadmap(normalized, roadmapContent)) {
|
|
1624
|
+
decimalSet.add(suffix);
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
catch { /* ROADMAP.md read failure is non-fatal */ }
|
|
1628
|
+
}
|
|
1629
|
+
const { next: nextDecimal, existing: existingDecimals } = computeNextDecimalPhase(normalized, decimalSet);
|
|
1630
|
+
return {
|
|
1631
|
+
data: {
|
|
1632
|
+
found: baseExists,
|
|
1633
|
+
base_phase: normalized,
|
|
1634
|
+
next: nextDecimal,
|
|
1635
|
+
existing: existingDecimals,
|
|
1636
|
+
},
|
|
1637
|
+
};
|
|
1638
|
+
};
|
|
1639
|
+
export const phasesArchive = async (args, projectDir, workstream) => {
|
|
1640
|
+
const version = args[0];
|
|
1641
|
+
if (!version) {
|
|
1642
|
+
throw new GSDError('version required for phases archive', ErrorClassification.Validation);
|
|
1643
|
+
}
|
|
1644
|
+
assertNoNullBytes(version, 'version');
|
|
1645
|
+
const paths = planningPaths(projectDir, workstream);
|
|
1646
|
+
const phasesDir = paths.phases;
|
|
1647
|
+
const isDirInMilestone = await getMilestonePhaseFilter(projectDir, workstream);
|
|
1648
|
+
const archiveDir = join(paths.planning, 'milestones', `${version}-phases`);
|
|
1649
|
+
const archivedCount = await archiveDirectories(phasesDir, archiveDir, (dirName) => isDirInMilestone(dirName));
|
|
1650
|
+
return {
|
|
1651
|
+
data: {
|
|
1652
|
+
archived: archivedCount,
|
|
1653
|
+
version,
|
|
1654
|
+
archive_directory: toPosixPath(relative(projectDir, archiveDir)),
|
|
1655
|
+
},
|
|
1656
|
+
};
|
|
1657
|
+
};
|
|
1658
|
+
// ─── milestoneComplete ────────────────────────────────────────────────────
|
|
1659
|
+
/**
|
|
1660
|
+
* Query handler for `milestone.complete` — port of `cmdMilestoneComplete` from `milestone.cjs`.
|
|
1661
|
+
*/
|
|
1662
|
+
export const milestoneComplete = async (args, projectDir, workstream) => {
|
|
1663
|
+
const version = args[0];
|
|
1664
|
+
if (!version) {
|
|
1665
|
+
throw new GSDError('version required for milestone complete (e.g., v1.0)', ErrorClassification.Validation);
|
|
1666
|
+
}
|
|
1667
|
+
// #3259: defense-in-depth — reject --help / -h as a version value before
|
|
1668
|
+
// any disk write, regardless of whether the dispatcher guard intercepted first.
|
|
1669
|
+
if (version === '--help' || version === '-h') {
|
|
1670
|
+
throw new GSDError(`"${version}" is not a valid milestone version; see \`gsd-sdk query --help\` for command list`, ErrorClassification.Validation);
|
|
1671
|
+
}
|
|
1672
|
+
assertNoNullBytes(version, 'version');
|
|
1673
|
+
const nameOpt = parseMultiwordArg(args, 'name');
|
|
1674
|
+
const archivePhases = args.includes('--archive-phases');
|
|
1675
|
+
const paths = planningPaths(projectDir, workstream);
|
|
1676
|
+
const roadmapPath = paths.roadmap;
|
|
1677
|
+
const reqPath = paths.requirements;
|
|
1678
|
+
const statePath = paths.state;
|
|
1679
|
+
const milestonesPath = join(paths.planning, 'MILESTONES.md');
|
|
1680
|
+
const archiveDir = join(paths.planning, 'milestones');
|
|
1681
|
+
const phasesDir = paths.phases;
|
|
1682
|
+
const today = new Date().toISOString().split('T')[0];
|
|
1683
|
+
const milestoneName = nameOpt || version;
|
|
1684
|
+
await mkdir(archiveDir, { recursive: true });
|
|
1685
|
+
const isDirInMilestone = await getMilestonePhaseFilter(projectDir, workstream);
|
|
1686
|
+
let phaseCount = 0;
|
|
1687
|
+
let totalPlans = 0;
|
|
1688
|
+
let totalTasks = 0;
|
|
1689
|
+
const accomplishments = [];
|
|
1690
|
+
try {
|
|
1691
|
+
const dirs = (await listDirectories(phasesDir)).sort();
|
|
1692
|
+
for (const dir of dirs) {
|
|
1693
|
+
if (!isDirInMilestone(dir))
|
|
1694
|
+
continue;
|
|
1695
|
+
phaseCount++;
|
|
1696
|
+
const phaseFiles = await readdir(join(phasesDir, dir));
|
|
1697
|
+
const plans = phaseFiles.filter((f) => f.endsWith('-PLAN.md') || f === 'PLAN.md');
|
|
1698
|
+
const summaries = phaseFiles.filter((f) => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
|
|
1699
|
+
totalPlans += plans.length;
|
|
1700
|
+
for (const s of summaries) {
|
|
1701
|
+
try {
|
|
1702
|
+
const content = await readFile(join(phasesDir, dir, s), 'utf-8');
|
|
1703
|
+
const fm = extractFrontmatter(content);
|
|
1704
|
+
const oneLiner = fm['one-liner'] || extractOneLinerFromBody(content);
|
|
1705
|
+
if (oneLiner) {
|
|
1706
|
+
accomplishments.push(oneLiner);
|
|
1707
|
+
}
|
|
1708
|
+
const tasksFieldMatch = content.match(/\*\*Tasks:\*\*\s*(\d+)/);
|
|
1709
|
+
if (tasksFieldMatch) {
|
|
1710
|
+
totalTasks += parseInt(tasksFieldMatch[1], 10);
|
|
1711
|
+
}
|
|
1712
|
+
else {
|
|
1713
|
+
const xmlTaskMatches = content.match(/<task[\s>]/gi) || [];
|
|
1714
|
+
const mdTaskMatches = content.match(/##\s*Task\s*\d+/gi) || [];
|
|
1715
|
+
totalTasks += xmlTaskMatches.length || mdTaskMatches.length;
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
catch {
|
|
1719
|
+
/* intentionally empty */
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
catch {
|
|
1725
|
+
/* intentionally empty */
|
|
1726
|
+
}
|
|
1727
|
+
if (existsSync(roadmapPath)) {
|
|
1728
|
+
const roadmapContent = await readFile(roadmapPath, 'utf-8');
|
|
1729
|
+
await writeFile(join(archiveDir, `${version}-ROADMAP.md`), roadmapContent, 'utf-8');
|
|
1730
|
+
}
|
|
1731
|
+
if (existsSync(reqPath)) {
|
|
1732
|
+
const reqContent = await readFile(reqPath, 'utf-8');
|
|
1733
|
+
const archiveHeader = `# Requirements Archive: ${version} ${milestoneName}\n\n` +
|
|
1734
|
+
`**Archived:** ${today}\n**Status:** SHIPPED\n\n` +
|
|
1735
|
+
`For current requirements, see \`.planning/REQUIREMENTS.md\`.\n\n---\n\n`;
|
|
1736
|
+
await writeFile(join(archiveDir, `${version}-REQUIREMENTS.md`), archiveHeader + reqContent, 'utf-8');
|
|
1737
|
+
}
|
|
1738
|
+
const auditFile = join(projectDir, '.planning', `${version}-MILESTONE-AUDIT.md`);
|
|
1739
|
+
if (existsSync(auditFile)) {
|
|
1740
|
+
await rename(auditFile, join(archiveDir, `${version}-MILESTONE-AUDIT.md`));
|
|
1741
|
+
}
|
|
1742
|
+
const accomplishmentsList = accomplishments.map((a) => `- ${a}`).join('\n');
|
|
1743
|
+
const milestoneEntry = `## ${version} ${milestoneName} (Shipped: ${today})\n\n` +
|
|
1744
|
+
`**Phases completed:** ${phaseCount} phases, ${totalPlans} plans, ${totalTasks} tasks\n\n` +
|
|
1745
|
+
`**Key accomplishments:**\n${accomplishmentsList || '- (none recorded)'}\n\n---\n\n`;
|
|
1746
|
+
if (existsSync(milestonesPath)) {
|
|
1747
|
+
const existing = await readFile(milestonesPath, 'utf-8');
|
|
1748
|
+
if (!existing.trim()) {
|
|
1749
|
+
await writeFile(milestonesPath, normalizeMd(`# Milestones\n\n${milestoneEntry}`), 'utf-8');
|
|
1750
|
+
}
|
|
1751
|
+
else {
|
|
1752
|
+
const headerMatch = existing.match(/^(#{1,3}\s+[^\n]*\n\n?)/);
|
|
1753
|
+
if (headerMatch) {
|
|
1754
|
+
const header = headerMatch[1];
|
|
1755
|
+
const rest = existing.slice(header.length);
|
|
1756
|
+
await writeFile(milestonesPath, normalizeMd(header + milestoneEntry + rest), 'utf-8');
|
|
1757
|
+
}
|
|
1758
|
+
else {
|
|
1759
|
+
await writeFile(milestonesPath, normalizeMd(milestoneEntry + existing), 'utf-8');
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
else {
|
|
1764
|
+
await writeFile(milestonesPath, normalizeMd(`# Milestones\n\n${milestoneEntry}`), 'utf-8');
|
|
1765
|
+
}
|
|
1766
|
+
if (existsSync(statePath)) {
|
|
1767
|
+
await readModifyWriteStateMdFull(projectDir, (stateContent) => {
|
|
1768
|
+
let next = stateReplaceFieldWithFallback(stateContent, 'Status', null, `${version} milestone complete`);
|
|
1769
|
+
next = stateReplaceFieldWithFallback(next, 'Last Activity', 'Last activity', today);
|
|
1770
|
+
next = stateReplaceFieldWithFallback(next, 'Last Activity Description', null, `${version} milestone completed and archived`);
|
|
1771
|
+
const positionPattern = /(##\s*Current Position\s*\n)([\s\S]*?)(?=\n##|$)/i;
|
|
1772
|
+
const closedPositionBody = `\nPhase: Milestone ${version} complete\n` +
|
|
1773
|
+
`Plan: —\n` +
|
|
1774
|
+
`Status: Awaiting next milestone\n` +
|
|
1775
|
+
`Last activity: ${today} — Milestone ${version} completed and archived\n\n`;
|
|
1776
|
+
if (positionPattern.test(next)) {
|
|
1777
|
+
next = next.replace(positionPattern, (_m, header) => `${header}${closedPositionBody}`);
|
|
1778
|
+
}
|
|
1779
|
+
else {
|
|
1780
|
+
next = `${next.trimEnd()}\n\n## Current Position\n${closedPositionBody}`;
|
|
1781
|
+
}
|
|
1782
|
+
const operatorPattern = /(##\s*Operator Next Steps\s*\n)([\s\S]*?)(?=\n##|$)/i;
|
|
1783
|
+
if (operatorPattern.test(next)) {
|
|
1784
|
+
next = next.replace(operatorPattern, `$1\n- Start the next milestone with /gsd-new-milestone\n\n`);
|
|
1785
|
+
}
|
|
1786
|
+
else {
|
|
1787
|
+
next = `${next.trimEnd()}\n\n## Operator Next Steps\n\n- Start the next milestone with /gsd-new-milestone\n`;
|
|
1788
|
+
}
|
|
1789
|
+
return next;
|
|
1790
|
+
}, workstream);
|
|
1791
|
+
}
|
|
1792
|
+
let phasesArchived = false;
|
|
1793
|
+
if (archivePhases) {
|
|
1794
|
+
try {
|
|
1795
|
+
const phaseArchiveDir = join(archiveDir, `${version}-phases`);
|
|
1796
|
+
const archivedCount = await archiveDirectories(phasesDir, phaseArchiveDir, (dirName) => isDirInMilestone(dirName));
|
|
1797
|
+
phasesArchived = archivedCount > 0;
|
|
1798
|
+
}
|
|
1799
|
+
catch {
|
|
1800
|
+
/* intentionally empty */
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
return {
|
|
1804
|
+
data: {
|
|
1805
|
+
version,
|
|
1806
|
+
name: milestoneName,
|
|
1807
|
+
date: today,
|
|
1808
|
+
phases: phaseCount,
|
|
1809
|
+
plans: totalPlans,
|
|
1810
|
+
tasks: totalTasks,
|
|
1811
|
+
accomplishments,
|
|
1812
|
+
archived: {
|
|
1813
|
+
roadmap: existsSync(join(archiveDir, `${version}-ROADMAP.md`)),
|
|
1814
|
+
requirements: existsSync(join(archiveDir, `${version}-REQUIREMENTS.md`)),
|
|
1815
|
+
audit: existsSync(join(archiveDir, `${version}-MILESTONE-AUDIT.md`)),
|
|
1816
|
+
phases: phasesArchived,
|
|
1817
|
+
},
|
|
1818
|
+
milestones_updated: true,
|
|
1819
|
+
state_updated: existsSync(statePath),
|
|
1820
|
+
},
|
|
1821
|
+
};
|
|
1822
|
+
};
|
|
1823
|
+
//# sourceMappingURL=phase-lifecycle.js.map
|