@jokerized/getresearchdone 0.4.1
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/.claude-plugin/plugin.json +103 -0
- package/README.md +211 -0
- package/agents/grd-baseline-assessor.md +684 -0
- package/agents/grd-code-reviewer.md +300 -0
- package/agents/grd-codebase-mapper.md +355 -0
- package/agents/grd-critique-agent.md +119 -0
- package/agents/grd-debugger.md +519 -0
- package/agents/grd-deep-diver.md +737 -0
- package/agents/grd-eval-planner.md +913 -0
- package/agents/grd-eval-reporter.md +717 -0
- package/agents/grd-executor.md +683 -0
- package/agents/grd-feasibility-analyst.md +624 -0
- package/agents/grd-integration-checker.md +367 -0
- package/agents/grd-knowledge-miner.md +81 -0
- package/agents/grd-migrator.md +88 -0
- package/agents/grd-phase-researcher.md +697 -0
- package/agents/grd-plan-checker.md +443 -0
- package/agents/grd-planner.md +1532 -0
- package/agents/grd-product-owner.md +562 -0
- package/agents/grd-project-researcher.md +513 -0
- package/agents/grd-research-synthesizer.md +273 -0
- package/agents/grd-roadmapper.md +798 -0
- package/agents/grd-surveyor.md +566 -0
- package/agents/grd-verifier.md +893 -0
- package/bin/gd.js +4 -0
- package/bin/gd.ts +227 -0
- package/bin/grd-manifest.js +4 -0
- package/bin/grd-manifest.ts +286 -0
- package/bin/grd-mcp-server.js +4 -0
- package/bin/grd-mcp-server.ts +124 -0
- package/bin/grd-tools.js +4 -0
- package/bin/grd-tools.ts +2471 -0
- package/bin/postinstall.js +4 -0
- package/bin/postinstall.ts +80 -0
- package/commands/add-phase.md +123 -0
- package/commands/add-todo.md +87 -0
- package/commands/assess-baseline.md +289 -0
- package/commands/autopilot.md +100 -0
- package/commands/autoplan.md +55 -0
- package/commands/check-todos.md +87 -0
- package/commands/compare-methods.md +262 -0
- package/commands/complete-milestone.md +225 -0
- package/commands/debug.md +372 -0
- package/commands/deep-dive.md +288 -0
- package/commands/discover.md +281 -0
- package/commands/discuss-phase.md +188 -0
- package/commands/discuss.md +55 -0
- package/commands/eval-report.md +310 -0
- package/commands/evolve.md +79 -0
- package/commands/execute-phase.md +1017 -0
- package/commands/feasibility.md +292 -0
- package/commands/help.md +407 -0
- package/commands/init.md +1508 -0
- package/commands/insert-phase.md +113 -0
- package/commands/iterate.md +327 -0
- package/commands/list-phase-assumptions.md +217 -0
- package/commands/long-term-roadmap.md +202 -0
- package/commands/map-codebase.md +111 -0
- package/commands/migrate.md +159 -0
- package/commands/new-milestone.md +169 -0
- package/commands/pause-work.md +83 -0
- package/commands/plan-milestone-gaps.md +373 -0
- package/commands/plan-phase.md +655 -0
- package/commands/principles.md +328 -0
- package/commands/product-plan.md +319 -0
- package/commands/progress.md +481 -0
- package/commands/quick.md +167 -0
- package/commands/reapply-patches.md +154 -0
- package/commands/remove-phase.md +97 -0
- package/commands/requirement.md +96 -0
- package/commands/resume-project.md +113 -0
- package/commands/settings.md +1144 -0
- package/commands/survey.md +242 -0
- package/commands/sync.md +246 -0
- package/commands/tracker-setup.md +322 -0
- package/commands/update.md +202 -0
- package/commands/verify-phase.md +335 -0
- package/commands/verify-work.md +701 -0
- package/commands/wireup.md +29 -0
- package/dist/bin/gd.d.ts +3 -0
- package/dist/bin/gd.d.ts.map +1 -0
- package/dist/bin/gd.js +178 -0
- package/dist/bin/gd.js.map +1 -0
- package/dist/bin/grd-manifest.d.ts +3 -0
- package/dist/bin/grd-manifest.d.ts.map +1 -0
- package/dist/bin/grd-manifest.js +202 -0
- package/dist/bin/grd-manifest.js.map +1 -0
- package/dist/bin/grd-mcp-server.d.ts +3 -0
- package/dist/bin/grd-mcp-server.d.ts.map +1 -0
- package/dist/bin/grd-mcp-server.js +71 -0
- package/dist/bin/grd-mcp-server.js.map +1 -0
- package/dist/bin/grd-tools.d.ts +3 -0
- package/dist/bin/grd-tools.d.ts.map +1 -0
- package/dist/bin/grd-tools.js +1680 -0
- package/dist/bin/grd-tools.js.map +1 -0
- package/dist/bin/postinstall.d.ts +3 -0
- package/dist/bin/postinstall.d.ts.map +1 -0
- package/dist/bin/postinstall.js +61 -0
- package/dist/bin/postinstall.js.map +1 -0
- package/dist/lib/autopilot-milestone.d.ts +2 -0
- package/dist/lib/autopilot-milestone.d.ts.map +1 -0
- package/dist/lib/autopilot-milestone.js +94 -0
- package/dist/lib/autopilot-milestone.js.map +1 -0
- package/dist/lib/autopilot-pipeline.d.ts +2 -0
- package/dist/lib/autopilot-pipeline.d.ts.map +1 -0
- package/dist/lib/autopilot-pipeline.js +830 -0
- package/dist/lib/autopilot-pipeline.js.map +1 -0
- package/dist/lib/autopilot-waves.d.ts +2 -0
- package/dist/lib/autopilot-waves.d.ts.map +1 -0
- package/dist/lib/autopilot-waves.js +266 -0
- package/dist/lib/autopilot-waves.js.map +1 -0
- package/dist/lib/autopilot.d.ts +2 -0
- package/dist/lib/autopilot.d.ts.map +1 -0
- package/dist/lib/autopilot.js +1314 -0
- package/dist/lib/autopilot.js.map +1 -0
- package/dist/lib/autoplan.d.ts +2 -0
- package/dist/lib/autoplan.d.ts.map +1 -0
- package/dist/lib/autoplan.js +198 -0
- package/dist/lib/autoplan.js.map +1 -0
- package/dist/lib/autoresearch.d.ts +2 -0
- package/dist/lib/autoresearch.d.ts.map +1 -0
- package/dist/lib/autoresearch.js +626 -0
- package/dist/lib/autoresearch.js.map +1 -0
- package/dist/lib/backend.d.ts +2 -0
- package/dist/lib/backend.d.ts.map +1 -0
- package/dist/lib/backend.js +1036 -0
- package/dist/lib/backend.js.map +1 -0
- package/dist/lib/benchmark.d.ts +99 -0
- package/dist/lib/benchmark.d.ts.map +1 -0
- package/dist/lib/benchmark.js +278 -0
- package/dist/lib/benchmark.js.map +1 -0
- package/dist/lib/citations.d.ts +2 -0
- package/dist/lib/citations.d.ts.map +1 -0
- package/dist/lib/citations.js +642 -0
- package/dist/lib/citations.js.map +1 -0
- package/dist/lib/cleanup.d.ts +2 -0
- package/dist/lib/cleanup.d.ts.map +1 -0
- package/dist/lib/cleanup.js +1222 -0
- package/dist/lib/cleanup.js.map +1 -0
- package/dist/lib/cli/adapters.d.ts +10 -0
- package/dist/lib/cli/adapters.d.ts.map +1 -0
- package/dist/lib/cli/adapters.js +27 -0
- package/dist/lib/cli/adapters.js.map +1 -0
- package/dist/lib/cli/agent.d.ts +17 -0
- package/dist/lib/cli/agent.d.ts.map +1 -0
- package/dist/lib/cli/agent.js +53 -0
- package/dist/lib/cli/agent.js.map +1 -0
- package/dist/lib/cli/index.d.ts +21 -0
- package/dist/lib/cli/index.d.ts.map +1 -0
- package/dist/lib/cli/index.js +264 -0
- package/dist/lib/cli/index.js.map +1 -0
- package/dist/lib/cli/output.d.ts +20 -0
- package/dist/lib/cli/output.d.ts.map +1 -0
- package/dist/lib/cli/output.js +22 -0
- package/dist/lib/cli/output.js.map +1 -0
- package/dist/lib/cli/scan-dispatch.d.ts +9 -0
- package/dist/lib/cli/scan-dispatch.d.ts.map +1 -0
- package/dist/lib/cli/scan-dispatch.js +107 -0
- package/dist/lib/cli/scan-dispatch.js.map +1 -0
- package/dist/lib/cli/tools.d.ts +16 -0
- package/dist/lib/cli/tools.d.ts.map +1 -0
- package/dist/lib/cli/tools.js +168 -0
- package/dist/lib/cli/tools.js.map +1 -0
- package/dist/lib/commands/_dashboard-parsers.d.ts +2 -0
- package/dist/lib/commands/_dashboard-parsers.d.ts.map +1 -0
- package/dist/lib/commands/_dashboard-parsers.js +192 -0
- package/dist/lib/commands/_dashboard-parsers.js.map +1 -0
- package/dist/lib/commands/analysis.d.ts +2 -0
- package/dist/lib/commands/analysis.d.ts.map +1 -0
- package/dist/lib/commands/analysis.js +1418 -0
- package/dist/lib/commands/analysis.js.map +1 -0
- package/dist/lib/commands/assumptions.d.ts +2 -0
- package/dist/lib/commands/assumptions.d.ts.map +1 -0
- package/dist/lib/commands/assumptions.js +166 -0
- package/dist/lib/commands/assumptions.js.map +1 -0
- package/dist/lib/commands/blame.d.ts +2 -0
- package/dist/lib/commands/blame.d.ts.map +1 -0
- package/dist/lib/commands/blame.js +133 -0
- package/dist/lib/commands/blame.js.map +1 -0
- package/dist/lib/commands/budget.d.ts +2 -0
- package/dist/lib/commands/budget.d.ts.map +1 -0
- package/dist/lib/commands/budget.js +100 -0
- package/dist/lib/commands/budget.js.map +1 -0
- package/dist/lib/commands/check-plans.d.ts +2 -0
- package/dist/lib/commands/check-plans.d.ts.map +1 -0
- package/dist/lib/commands/check-plans.js +190 -0
- package/dist/lib/commands/check-plans.js.map +1 -0
- package/dist/lib/commands/config.d.ts +2 -0
- package/dist/lib/commands/config.d.ts.map +1 -0
- package/dist/lib/commands/config.js +188 -0
- package/dist/lib/commands/config.js.map +1 -0
- package/dist/lib/commands/dashboard.d.ts +2 -0
- package/dist/lib/commands/dashboard.d.ts.map +1 -0
- package/dist/lib/commands/dashboard.js +466 -0
- package/dist/lib/commands/dashboard.js.map +1 -0
- package/dist/lib/commands/estimate.d.ts +2 -0
- package/dist/lib/commands/estimate.d.ts.map +1 -0
- package/dist/lib/commands/estimate.js +148 -0
- package/dist/lib/commands/estimate.js.map +1 -0
- package/dist/lib/commands/eval-diff.d.ts +2 -0
- package/dist/lib/commands/eval-diff.d.ts.map +1 -0
- package/dist/lib/commands/eval-diff.js +213 -0
- package/dist/lib/commands/eval-diff.js.map +1 -0
- package/dist/lib/commands/freshness.d.ts +2 -0
- package/dist/lib/commands/freshness.d.ts.map +1 -0
- package/dist/lib/commands/freshness.js +163 -0
- package/dist/lib/commands/freshness.js.map +1 -0
- package/dist/lib/commands/health.d.ts +2 -0
- package/dist/lib/commands/health.d.ts.map +1 -0
- package/dist/lib/commands/health.js +435 -0
- package/dist/lib/commands/health.js.map +1 -0
- package/dist/lib/commands/index.d.ts +2 -0
- package/dist/lib/commands/index.d.ts.map +1 -0
- package/dist/lib/commands/index.js +128 -0
- package/dist/lib/commands/index.js.map +1 -0
- package/dist/lib/commands/install.d.ts +56 -0
- package/dist/lib/commands/install.d.ts.map +1 -0
- package/dist/lib/commands/install.js +214 -0
- package/dist/lib/commands/install.js.map +1 -0
- package/dist/lib/commands/knowhow-aggregator.d.ts +2 -0
- package/dist/lib/commands/knowhow-aggregator.d.ts.map +1 -0
- package/dist/lib/commands/knowhow-aggregator.js +279 -0
- package/dist/lib/commands/knowhow-aggregator.js.map +1 -0
- package/dist/lib/commands/knowledge-search.d.ts +2 -0
- package/dist/lib/commands/knowledge-search.d.ts.map +1 -0
- package/dist/lib/commands/knowledge-search.js +113 -0
- package/dist/lib/commands/knowledge-search.js.map +1 -0
- package/dist/lib/commands/long-term-roadmap.d.ts +2 -0
- package/dist/lib/commands/long-term-roadmap.d.ts.map +1 -0
- package/dist/lib/commands/long-term-roadmap.js +272 -0
- package/dist/lib/commands/long-term-roadmap.js.map +1 -0
- package/dist/lib/commands/patterns.d.ts +91 -0
- package/dist/lib/commands/patterns.d.ts.map +1 -0
- package/dist/lib/commands/patterns.js +391 -0
- package/dist/lib/commands/patterns.js.map +1 -0
- package/dist/lib/commands/phase-info.d.ts +2 -0
- package/dist/lib/commands/phase-info.d.ts.map +1 -0
- package/dist/lib/commands/phase-info.js +509 -0
- package/dist/lib/commands/phase-info.js.map +1 -0
- package/dist/lib/commands/plan-lint.d.ts +56 -0
- package/dist/lib/commands/plan-lint.d.ts.map +1 -0
- package/dist/lib/commands/plan-lint.js +481 -0
- package/dist/lib/commands/plan-lint.js.map +1 -0
- package/dist/lib/commands/plan-phase.d.ts +53 -0
- package/dist/lib/commands/plan-phase.d.ts.map +1 -0
- package/dist/lib/commands/plan-phase.js +288 -0
- package/dist/lib/commands/plan-phase.js.map +1 -0
- package/dist/lib/commands/progress.d.ts +2 -0
- package/dist/lib/commands/progress.d.ts.map +1 -0
- package/dist/lib/commands/progress.js +266 -0
- package/dist/lib/commands/progress.js.map +1 -0
- package/dist/lib/commands/quality.d.ts +2 -0
- package/dist/lib/commands/quality.d.ts.map +1 -0
- package/dist/lib/commands/quality.js +80 -0
- package/dist/lib/commands/quality.js.map +1 -0
- package/dist/lib/commands/rollback.d.ts +2 -0
- package/dist/lib/commands/rollback.d.ts.map +1 -0
- package/dist/lib/commands/rollback.js +145 -0
- package/dist/lib/commands/rollback.js.map +1 -0
- package/dist/lib/commands/scan.d.ts +25 -0
- package/dist/lib/commands/scan.d.ts.map +1 -0
- package/dist/lib/commands/scan.js +28 -0
- package/dist/lib/commands/scan.js.map +1 -0
- package/dist/lib/commands/search.d.ts +2 -0
- package/dist/lib/commands/search.d.ts.map +1 -0
- package/dist/lib/commands/search.js +212 -0
- package/dist/lib/commands/search.js.map +1 -0
- package/dist/lib/commands/select-candidate.d.ts +128 -0
- package/dist/lib/commands/select-candidate.d.ts.map +1 -0
- package/dist/lib/commands/select-candidate.js +518 -0
- package/dist/lib/commands/select-candidate.js.map +1 -0
- package/dist/lib/commands/singularity.d.ts +2 -0
- package/dist/lib/commands/singularity.d.ts.map +1 -0
- package/dist/lib/commands/singularity.js +185 -0
- package/dist/lib/commands/singularity.js.map +1 -0
- package/dist/lib/commands/slug-timestamp.d.ts +2 -0
- package/dist/lib/commands/slug-timestamp.d.ts.map +1 -0
- package/dist/lib/commands/slug-timestamp.js +54 -0
- package/dist/lib/commands/slug-timestamp.js.map +1 -0
- package/dist/lib/commands/tail.d.ts +2 -0
- package/dist/lib/commands/tail.d.ts.map +1 -0
- package/dist/lib/commands/tail.js +100 -0
- package/dist/lib/commands/tail.js.map +1 -0
- package/dist/lib/commands/todo.d.ts +2 -0
- package/dist/lib/commands/todo.d.ts.map +1 -0
- package/dist/lib/commands/todo.js +200 -0
- package/dist/lib/commands/todo.js.map +1 -0
- package/dist/lib/commands/watch.d.ts +2 -0
- package/dist/lib/commands/watch.d.ts.map +1 -0
- package/dist/lib/commands/watch.js +72 -0
- package/dist/lib/commands/watch.js.map +1 -0
- package/dist/lib/complexity.d.ts +55 -0
- package/dist/lib/complexity.d.ts.map +1 -0
- package/dist/lib/complexity.js +80 -0
- package/dist/lib/complexity.js.map +1 -0
- package/dist/lib/context/agents.d.ts +2 -0
- package/dist/lib/context/agents.d.ts.map +1 -0
- package/dist/lib/context/agents.js +344 -0
- package/dist/lib/context/agents.js.map +1 -0
- package/dist/lib/context/base.d.ts +2 -0
- package/dist/lib/context/base.d.ts.map +1 -0
- package/dist/lib/context/base.js +81 -0
- package/dist/lib/context/base.js.map +1 -0
- package/dist/lib/context/execute.d.ts +2 -0
- package/dist/lib/context/execute.d.ts.map +1 -0
- package/dist/lib/context/execute.js +753 -0
- package/dist/lib/context/execute.js.map +1 -0
- package/dist/lib/context/index.d.ts +2 -0
- package/dist/lib/context/index.d.ts.map +1 -0
- package/dist/lib/context/index.js +88 -0
- package/dist/lib/context/index.js.map +1 -0
- package/dist/lib/context/progress.d.ts +2 -0
- package/dist/lib/context/progress.d.ts.map +1 -0
- package/dist/lib/context/progress.js +178 -0
- package/dist/lib/context/progress.js.map +1 -0
- package/dist/lib/context/project.d.ts +2 -0
- package/dist/lib/context/project.d.ts.map +1 -0
- package/dist/lib/context/project.js +413 -0
- package/dist/lib/context/project.js.map +1 -0
- package/dist/lib/context/research.d.ts +2 -0
- package/dist/lib/context/research.d.ts.map +1 -0
- package/dist/lib/context/research.js +466 -0
- package/dist/lib/context/research.js.map +1 -0
- package/dist/lib/dead-ends.d.ts +28 -0
- package/dist/lib/dead-ends.d.ts.map +1 -0
- package/dist/lib/dead-ends.js +451 -0
- package/dist/lib/dead-ends.js.map +1 -0
- package/dist/lib/deps.d.ts +2 -0
- package/dist/lib/deps.d.ts.map +1 -0
- package/dist/lib/deps.js +630 -0
- package/dist/lib/deps.js.map +1 -0
- package/dist/lib/discussion.d.ts +2 -0
- package/dist/lib/discussion.d.ts.map +1 -0
- package/dist/lib/discussion.js +1041 -0
- package/dist/lib/discussion.js.map +1 -0
- package/dist/lib/drift.d.ts +36 -0
- package/dist/lib/drift.d.ts.map +1 -0
- package/dist/lib/drift.js +481 -0
- package/dist/lib/drift.js.map +1 -0
- package/dist/lib/evolve/_dimensions-features.d.ts +2 -0
- package/dist/lib/evolve/_dimensions-features.d.ts.map +1 -0
- package/dist/lib/evolve/_dimensions-features.js +369 -0
- package/dist/lib/evolve/_dimensions-features.js.map +1 -0
- package/dist/lib/evolve/_dimensions.d.ts +2 -0
- package/dist/lib/evolve/_dimensions.d.ts.map +1 -0
- package/dist/lib/evolve/_dimensions.js +358 -0
- package/dist/lib/evolve/_dimensions.js.map +1 -0
- package/dist/lib/evolve/_product-ideation.d.ts +2 -0
- package/dist/lib/evolve/_product-ideation.d.ts.map +1 -0
- package/dist/lib/evolve/_product-ideation.js +281 -0
- package/dist/lib/evolve/_product-ideation.js.map +1 -0
- package/dist/lib/evolve/_prompts.d.ts +2 -0
- package/dist/lib/evolve/_prompts.d.ts.map +1 -0
- package/dist/lib/evolve/_prompts.js +153 -0
- package/dist/lib/evolve/_prompts.js.map +1 -0
- package/dist/lib/evolve/cli.d.ts +2 -0
- package/dist/lib/evolve/cli.d.ts.map +1 -0
- package/dist/lib/evolve/cli.js +224 -0
- package/dist/lib/evolve/cli.js.map +1 -0
- package/dist/lib/evolve/discovery.d.ts +2 -0
- package/dist/lib/evolve/discovery.d.ts.map +1 -0
- package/dist/lib/evolve/discovery.js +391 -0
- package/dist/lib/evolve/discovery.js.map +1 -0
- package/dist/lib/evolve/index.d.ts +2 -0
- package/dist/lib/evolve/index.d.ts.map +1 -0
- package/dist/lib/evolve/index.js +88 -0
- package/dist/lib/evolve/index.js.map +1 -0
- package/dist/lib/evolve/orchestrator.d.ts +2 -0
- package/dist/lib/evolve/orchestrator.d.ts.map +1 -0
- package/dist/lib/evolve/orchestrator.js +851 -0
- package/dist/lib/evolve/orchestrator.js.map +1 -0
- package/dist/lib/evolve/scoring.d.ts +2 -0
- package/dist/lib/evolve/scoring.d.ts.map +1 -0
- package/dist/lib/evolve/scoring.js +118 -0
- package/dist/lib/evolve/scoring.js.map +1 -0
- package/dist/lib/evolve/state.d.ts +2 -0
- package/dist/lib/evolve/state.d.ts.map +1 -0
- package/dist/lib/evolve/state.js +264 -0
- package/dist/lib/evolve/state.js.map +1 -0
- package/dist/lib/evolve/types.d.ts +249 -0
- package/dist/lib/evolve/types.d.ts.map +1 -0
- package/dist/lib/evolve/types.js +3 -0
- package/dist/lib/evolve/types.js.map +1 -0
- package/dist/lib/frontmatter.d.ts +2 -0
- package/dist/lib/frontmatter.d.ts.map +1 -0
- package/dist/lib/frontmatter.js +513 -0
- package/dist/lib/frontmatter.js.map +1 -0
- package/dist/lib/gates.d.ts +2 -0
- package/dist/lib/gates.d.ts.map +1 -0
- package/dist/lib/gates.js +578 -0
- package/dist/lib/gates.js.map +1 -0
- package/dist/lib/genome.d.ts +10 -0
- package/dist/lib/genome.d.ts.map +1 -0
- package/dist/lib/genome.js +368 -0
- package/dist/lib/genome.js.map +1 -0
- package/dist/lib/got.d.ts +2 -0
- package/dist/lib/got.d.ts.map +1 -0
- package/dist/lib/got.js +280 -0
- package/dist/lib/got.js.map +1 -0
- package/dist/lib/invariants.d.ts +2 -0
- package/dist/lib/invariants.d.ts.map +1 -0
- package/dist/lib/invariants.js +298 -0
- package/dist/lib/invariants.js.map +1 -0
- package/dist/lib/knowledge.d.ts +2 -0
- package/dist/lib/knowledge.d.ts.map +1 -0
- package/dist/lib/knowledge.js +658 -0
- package/dist/lib/knowledge.js.map +1 -0
- package/dist/lib/long-term-roadmap.d.ts +2 -0
- package/dist/lib/long-term-roadmap.d.ts.map +1 -0
- package/dist/lib/long-term-roadmap.js +602 -0
- package/dist/lib/long-term-roadmap.js.map +1 -0
- package/dist/lib/markdown-split.d.ts +2 -0
- package/dist/lib/markdown-split.d.ts.map +1 -0
- package/dist/lib/markdown-split.js +199 -0
- package/dist/lib/markdown-split.js.map +1 -0
- package/dist/lib/mcp-server.d.ts +2 -0
- package/dist/lib/mcp-server.d.ts.map +1 -0
- package/dist/lib/mcp-server.js +2424 -0
- package/dist/lib/mcp-server.js.map +1 -0
- package/dist/lib/metrics.d.ts +16 -0
- package/dist/lib/metrics.d.ts.map +1 -0
- package/dist/lib/metrics.js +48 -0
- package/dist/lib/metrics.js.map +1 -0
- package/dist/lib/overstory.d.ts +2 -0
- package/dist/lib/overstory.d.ts.map +1 -0
- package/dist/lib/overstory.js +211 -0
- package/dist/lib/overstory.js.map +1 -0
- package/dist/lib/parallel.d.ts +2 -0
- package/dist/lib/parallel.d.ts.map +1 -0
- package/dist/lib/parallel.js +349 -0
- package/dist/lib/parallel.js.map +1 -0
- package/dist/lib/paths.d.ts +2 -0
- package/dist/lib/paths.d.ts.map +1 -0
- package/dist/lib/paths.js +254 -0
- package/dist/lib/paths.js.map +1 -0
- package/dist/lib/phase-complete-llm.d.ts +22 -0
- package/dist/lib/phase-complete-llm.d.ts.map +1 -0
- package/dist/lib/phase-complete-llm.js +331 -0
- package/dist/lib/phase-complete-llm.js.map +1 -0
- package/dist/lib/phase-complete.d.ts +46 -0
- package/dist/lib/phase-complete.d.ts.map +1 -0
- package/dist/lib/phase-complete.js +278 -0
- package/dist/lib/phase-complete.js.map +1 -0
- package/dist/lib/phase-io.d.ts +2 -0
- package/dist/lib/phase-io.d.ts.map +1 -0
- package/dist/lib/phase-io.js +126 -0
- package/dist/lib/phase-io.js.map +1 -0
- package/dist/lib/phase.d.ts +2 -0
- package/dist/lib/phase.d.ts.map +1 -0
- package/dist/lib/phase.js +1344 -0
- package/dist/lib/phase.js.map +1 -0
- package/dist/lib/plan-tournament.d.ts +63 -0
- package/dist/lib/plan-tournament.d.ts.map +1 -0
- package/dist/lib/plan-tournament.js +353 -0
- package/dist/lib/plan-tournament.js.map +1 -0
- package/dist/lib/refinement.d.ts +74 -0
- package/dist/lib/refinement.d.ts.map +1 -0
- package/dist/lib/refinement.js +283 -0
- package/dist/lib/refinement.js.map +1 -0
- package/dist/lib/requirements.d.ts +2 -0
- package/dist/lib/requirements.d.ts.map +1 -0
- package/dist/lib/requirements.js +355 -0
- package/dist/lib/requirements.js.map +1 -0
- package/dist/lib/research-bundle.d.ts +2 -0
- package/dist/lib/research-bundle.d.ts.map +1 -0
- package/dist/lib/research-bundle.js +246 -0
- package/dist/lib/research-bundle.js.map +1 -0
- package/dist/lib/roadmap.d.ts +2 -0
- package/dist/lib/roadmap.d.ts.map +1 -0
- package/dist/lib/roadmap.js +541 -0
- package/dist/lib/roadmap.js.map +1 -0
- package/dist/lib/sample.d.ts +16 -0
- package/dist/lib/sample.d.ts.map +1 -0
- package/dist/lib/sample.js +20 -0
- package/dist/lib/sample.js.map +1 -0
- package/dist/lib/scaffold.d.ts +2 -0
- package/dist/lib/scaffold.d.ts.map +1 -0
- package/dist/lib/scaffold.js +355 -0
- package/dist/lib/scaffold.js.map +1 -0
- package/dist/lib/scan/_utils.d.ts +11 -0
- package/dist/lib/scan/_utils.d.ts.map +1 -0
- package/dist/lib/scan/_utils.js +36 -0
- package/dist/lib/scan/_utils.js.map +1 -0
- package/dist/lib/scan/base64.d.ts +15 -0
- package/dist/lib/scan/base64.d.ts.map +1 -0
- package/dist/lib/scan/base64.js +66 -0
- package/dist/lib/scan/base64.js.map +1 -0
- package/dist/lib/scan/ignorefile.d.ts +30 -0
- package/dist/lib/scan/ignorefile.d.ts.map +1 -0
- package/dist/lib/scan/ignorefile.js +101 -0
- package/dist/lib/scan/ignorefile.js.map +1 -0
- package/dist/lib/scan/injection.d.ts +14 -0
- package/dist/lib/scan/injection.d.ts.map +1 -0
- package/dist/lib/scan/injection.js +39 -0
- package/dist/lib/scan/injection.js.map +1 -0
- package/dist/lib/scan/patterns.d.ts +17 -0
- package/dist/lib/scan/patterns.d.ts.map +1 -0
- package/dist/lib/scan/patterns.js +123 -0
- package/dist/lib/scan/patterns.js.map +1 -0
- package/dist/lib/scan/strip-markdown.d.ts +7 -0
- package/dist/lib/scan/strip-markdown.d.ts.map +1 -0
- package/dist/lib/scan/strip-markdown.js +38 -0
- package/dist/lib/scan/strip-markdown.js.map +1 -0
- package/dist/lib/scan/types.d.ts +23 -0
- package/dist/lib/scan/types.d.ts.map +1 -0
- package/dist/lib/scan/types.js +3 -0
- package/dist/lib/scan/types.js.map +1 -0
- package/dist/lib/scheduler-wait.d.ts +2 -0
- package/dist/lib/scheduler-wait.d.ts.map +1 -0
- package/dist/lib/scheduler-wait.js +59 -0
- package/dist/lib/scheduler-wait.js.map +1 -0
- package/dist/lib/scheduler.d.ts +254 -0
- package/dist/lib/scheduler.d.ts.map +1 -0
- package/dist/lib/scheduler.js +1147 -0
- package/dist/lib/scheduler.js.map +1 -0
- package/dist/lib/state.d.ts +2 -0
- package/dist/lib/state.d.ts.map +1 -0
- package/dist/lib/state.js +744 -0
- package/dist/lib/state.js.map +1 -0
- package/dist/lib/think.d.ts +18 -0
- package/dist/lib/think.d.ts.map +1 -0
- package/dist/lib/think.js +317 -0
- package/dist/lib/think.js.map +1 -0
- package/dist/lib/tracker.d.ts +2 -0
- package/dist/lib/tracker.d.ts.map +1 -0
- package/dist/lib/tracker.js +1121 -0
- package/dist/lib/tracker.js.map +1 -0
- package/dist/lib/types.d.ts +1514 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +4 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +1363 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/lib/verify.d.ts +2 -0
- package/dist/lib/verify.d.ts.map +1 -0
- package/dist/lib/verify.js +1153 -0
- package/dist/lib/verify.js.map +1 -0
- package/dist/lib/wireup/autofix.d.ts +2 -0
- package/dist/lib/wireup/autofix.d.ts.map +1 -0
- package/dist/lib/wireup/autofix.js +188 -0
- package/dist/lib/wireup/autofix.js.map +1 -0
- package/dist/lib/wireup/cli.d.ts +2 -0
- package/dist/lib/wireup/cli.d.ts.map +1 -0
- package/dist/lib/wireup/cli.js +194 -0
- package/dist/lib/wireup/cli.js.map +1 -0
- package/dist/lib/wireup/detection.d.ts +47 -0
- package/dist/lib/wireup/detection.d.ts.map +1 -0
- package/dist/lib/wireup/detection.js +410 -0
- package/dist/lib/wireup/detection.js.map +1 -0
- package/dist/lib/wireup/discovery.d.ts +2 -0
- package/dist/lib/wireup/discovery.d.ts.map +1 -0
- package/dist/lib/wireup/discovery.js +934 -0
- package/dist/lib/wireup/discovery.js.map +1 -0
- package/dist/lib/wireup/execution.d.ts +2 -0
- package/dist/lib/wireup/execution.d.ts.map +1 -0
- package/dist/lib/wireup/execution.js +573 -0
- package/dist/lib/wireup/execution.js.map +1 -0
- package/dist/lib/wireup/index.d.ts +2 -0
- package/dist/lib/wireup/index.d.ts.map +1 -0
- package/dist/lib/wireup/index.js +85 -0
- package/dist/lib/wireup/index.js.map +1 -0
- package/dist/lib/wireup/orchestrator.d.ts +2 -0
- package/dist/lib/wireup/orchestrator.d.ts.map +1 -0
- package/dist/lib/wireup/orchestrator.js +366 -0
- package/dist/lib/wireup/orchestrator.js.map +1 -0
- package/dist/lib/wireup/report.d.ts +47 -0
- package/dist/lib/wireup/report.d.ts.map +1 -0
- package/dist/lib/wireup/report.js +201 -0
- package/dist/lib/wireup/report.js.map +1 -0
- package/dist/lib/wireup/scenarios.d.ts +2 -0
- package/dist/lib/wireup/scenarios.d.ts.map +1 -0
- package/dist/lib/wireup/scenarios.js +516 -0
- package/dist/lib/wireup/scenarios.js.map +1 -0
- package/dist/lib/wireup/state.d.ts +2 -0
- package/dist/lib/wireup/state.d.ts.map +1 -0
- package/dist/lib/wireup/state.js +102 -0
- package/dist/lib/wireup/state.js.map +1 -0
- package/dist/lib/wireup/types.d.ts +376 -0
- package/dist/lib/wireup/types.d.ts.map +1 -0
- package/dist/lib/wireup/types.js +3 -0
- package/dist/lib/wireup/types.js.map +1 -0
- package/dist/lib/worktree.d.ts +2 -0
- package/dist/lib/worktree.d.ts.map +1 -0
- package/dist/lib/worktree.js +999 -0
- package/dist/lib/worktree.js.map +1 -0
- package/lib/autopilot-milestone.ts +136 -0
- package/lib/autopilot-pipeline.ts +1179 -0
- package/lib/autopilot-waves.ts +361 -0
- package/lib/autopilot.ts +1874 -0
- package/lib/autoplan.ts +280 -0
- package/lib/autoresearch.js +4 -0
- package/lib/autoresearch.ts +886 -0
- package/lib/backend.ts +1252 -0
- package/lib/benchmark.ts +341 -0
- package/lib/citations.ts +760 -0
- package/lib/cleanup.ts +1588 -0
- package/lib/cli/adapters.ts +41 -0
- package/lib/cli/agent.ts +83 -0
- package/lib/cli/index.ts +273 -0
- package/lib/cli/output.ts +33 -0
- package/lib/cli/scan-dispatch.ts +130 -0
- package/lib/cli/tools.ts +198 -0
- package/lib/commands/_dashboard-parsers.ts +275 -0
- package/lib/commands/analysis.ts +1851 -0
- package/lib/commands/assumptions.ts +232 -0
- package/lib/commands/blame.ts +174 -0
- package/lib/commands/budget.ts +148 -0
- package/lib/commands/check-plans.ts +233 -0
- package/lib/commands/config.ts +287 -0
- package/lib/commands/dashboard.ts +680 -0
- package/lib/commands/estimate.ts +204 -0
- package/lib/commands/eval-diff.ts +252 -0
- package/lib/commands/freshness.ts +213 -0
- package/lib/commands/health.ts +607 -0
- package/lib/commands/index.ts +266 -0
- package/lib/commands/install.ts +307 -0
- package/lib/commands/knowhow-aggregator.ts +345 -0
- package/lib/commands/knowledge-search.ts +153 -0
- package/lib/commands/long-term-roadmap.ts +390 -0
- package/lib/commands/patterns.ts +465 -0
- package/lib/commands/phase-info.ts +698 -0
- package/lib/commands/plan-lint.ts +546 -0
- package/lib/commands/plan-phase.ts +375 -0
- package/lib/commands/progress.ts +319 -0
- package/lib/commands/quality.ts +138 -0
- package/lib/commands/rollback.ts +195 -0
- package/lib/commands/scan.ts +72 -0
- package/lib/commands/search.ts +300 -0
- package/lib/commands/select-candidate.ts +687 -0
- package/lib/commands/singularity.ts +222 -0
- package/lib/commands/slug-timestamp.ts +74 -0
- package/lib/commands/tail.ts +129 -0
- package/lib/commands/todo.ts +273 -0
- package/lib/commands/watch.ts +80 -0
- package/lib/complexity.ts +117 -0
- package/lib/context/agents.ts +505 -0
- package/lib/context/base.ts +123 -0
- package/lib/context/execute.ts +977 -0
- package/lib/context/index.ts +110 -0
- package/lib/context/progress.ts +278 -0
- package/lib/context/project.ts +531 -0
- package/lib/context/research.ts +646 -0
- package/lib/dead-ends.ts +506 -0
- package/lib/deps.ts +773 -0
- package/lib/discussion.ts +1275 -0
- package/lib/drift.ts +519 -0
- package/lib/evolve/_dimensions-features.ts +525 -0
- package/lib/evolve/_dimensions.ts +511 -0
- package/lib/evolve/_product-ideation.ts +405 -0
- package/lib/evolve/_prompts.ts +178 -0
- package/lib/evolve/cli.ts +330 -0
- package/lib/evolve/discovery.ts +571 -0
- package/lib/evolve/index.ts +105 -0
- package/lib/evolve/orchestrator.ts +1139 -0
- package/lib/evolve/scoring.ts +167 -0
- package/lib/evolve/state.ts +330 -0
- package/lib/evolve/types.ts +290 -0
- package/lib/frontmatter.ts +615 -0
- package/lib/gates.ts +695 -0
- package/lib/genome.ts +402 -0
- package/lib/got.js +4 -0
- package/lib/got.ts +361 -0
- package/lib/invariants.ts +378 -0
- package/lib/knowledge.ts +768 -0
- package/lib/long-term-roadmap.ts +806 -0
- package/lib/markdown-split.ts +273 -0
- package/lib/mcp-server.ts +3292 -0
- package/lib/metrics.ts +49 -0
- package/lib/overstory.ts +270 -0
- package/lib/parallel.ts +570 -0
- package/lib/paths.ts +293 -0
- package/lib/phase-complete-llm.ts +376 -0
- package/lib/phase-complete.ts +366 -0
- package/lib/phase-io.ts +101 -0
- package/lib/phase.ts +1981 -0
- package/lib/plan-tournament.ts +426 -0
- package/lib/refinement.ts +349 -0
- package/lib/requirements.ts +469 -0
- package/lib/research-bundle.ts +300 -0
- package/lib/roadmap.ts +775 -0
- package/lib/scaffold.ts +480 -0
- package/lib/scan/_utils.ts +37 -0
- package/lib/scan/base64.ts +90 -0
- package/lib/scan/ignorefile.ts +109 -0
- package/lib/scan/injection.ts +67 -0
- package/lib/scan/patterns.ts +139 -0
- package/lib/scan/strip-markdown.ts +39 -0
- package/lib/scan/types.ts +28 -0
- package/lib/scheduler-wait.ts +58 -0
- package/lib/scheduler.ts +1370 -0
- package/lib/state.ts +1000 -0
- package/lib/think.ts +365 -0
- package/lib/tracker.ts +1591 -0
- package/lib/types.ts +1663 -0
- package/lib/utils.ts +1479 -0
- package/lib/verify.ts +1434 -0
- package/lib/wireup/autofix.ts +241 -0
- package/lib/wireup/cli.ts +278 -0
- package/lib/wireup/detection.ts +542 -0
- package/lib/wireup/discovery.ts +1063 -0
- package/lib/wireup/execution.ts +686 -0
- package/lib/wireup/index.ts +117 -0
- package/lib/wireup/orchestrator.ts +519 -0
- package/lib/wireup/report.ts +286 -0
- package/lib/wireup/scenarios.ts +616 -0
- package/lib/wireup/state.ts +139 -0
- package/lib/wireup/types.ts +436 -0
- package/lib/worktree.ts +1309 -0
- package/package.json +67 -0
|
@@ -0,0 +1,1344 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { normalizePhaseName, generateSlugInternal, stripShippedSections, execGit, loadConfig, getMilestoneInfo: getMilestoneInfoUtil, output, error, } = require('./utils');
|
|
6
|
+
const { extractFrontmatter, } = require('./frontmatter');
|
|
7
|
+
const { runPreflightGates, checkOrphanedPhases, } = require('./gates');
|
|
8
|
+
const { phasesDir: getPhasesDirPath, phaseDir: getPhaseDirPath, milestonesDir: getMilestonesDirPath, archivedPhasesDir: getArchivedPhasesDir, } = require('./paths');
|
|
9
|
+
const { _phaseCompleteCore } = require('./phase-complete');
|
|
10
|
+
const { readRoadmapFile, writeRoadmapFile, readStateFile, writeStateFile } = require('./phase-io');
|
|
11
|
+
// ─── Phases List ──────────────────────────────────────────────────────────────
|
|
12
|
+
/**
|
|
13
|
+
* CLI command: List phase directories with optional filtering by type or phase number.
|
|
14
|
+
* @param cwd - Project working directory
|
|
15
|
+
* @param options - List options
|
|
16
|
+
* @param raw - Output raw text (newline-separated) instead of JSON
|
|
17
|
+
* @returns void — writes JSON or raw text to stdout and exits on error
|
|
18
|
+
*/
|
|
19
|
+
function cmdPhasesList(cwd, options, raw) {
|
|
20
|
+
const phasesDir = getPhasesDirPath(cwd);
|
|
21
|
+
const { type, phase } = options;
|
|
22
|
+
// If no phases directory, return empty
|
|
23
|
+
if (!fs.existsSync(phasesDir)) {
|
|
24
|
+
if (type) {
|
|
25
|
+
output({ files: [], count: 0 }, raw, '');
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
output({ directories: [], count: 0 }, raw, '');
|
|
29
|
+
}
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
// Get all phase directories
|
|
34
|
+
const entries = fs.readdirSync(phasesDir, {
|
|
35
|
+
withFileTypes: true,
|
|
36
|
+
});
|
|
37
|
+
let dirs = entries
|
|
38
|
+
.filter((e) => e.isDirectory())
|
|
39
|
+
.map((e) => e.name);
|
|
40
|
+
// Sort numerically (handles decimals: 01, 02, 02.1, 02.2, 03)
|
|
41
|
+
dirs.sort((a, b) => {
|
|
42
|
+
const aNum = parseFloat(a.match(/^(\d+(?:\.\d+)?)/)?.[1] || '0');
|
|
43
|
+
const bNum = parseFloat(b.match(/^(\d+(?:\.\d+)?)/)?.[1] || '0');
|
|
44
|
+
return aNum - bNum;
|
|
45
|
+
});
|
|
46
|
+
// If filtering by phase number
|
|
47
|
+
if (phase) {
|
|
48
|
+
const normalized = normalizePhaseName(phase);
|
|
49
|
+
const match = dirs.find((d) => d.startsWith(normalized + '-') || d === normalized);
|
|
50
|
+
if (!match) {
|
|
51
|
+
output({ files: [], count: 0, phase_dir: null, error: 'Phase not found' }, raw, '');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
dirs = [match];
|
|
55
|
+
}
|
|
56
|
+
// If listing files of a specific type
|
|
57
|
+
if (type) {
|
|
58
|
+
const files = [];
|
|
59
|
+
for (const dir of dirs) {
|
|
60
|
+
const dirPath = path.join(phasesDir, dir);
|
|
61
|
+
const dirFiles = fs.readdirSync(dirPath);
|
|
62
|
+
let filtered;
|
|
63
|
+
if (type === 'plans') {
|
|
64
|
+
filtered = dirFiles.filter((f) => f.endsWith('-PLAN.md') || f === 'PLAN.md');
|
|
65
|
+
}
|
|
66
|
+
else if (type === 'summaries') {
|
|
67
|
+
filtered = dirFiles.filter((f) => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
filtered = dirFiles;
|
|
71
|
+
}
|
|
72
|
+
files.push(...filtered.sort());
|
|
73
|
+
}
|
|
74
|
+
const result = {
|
|
75
|
+
files,
|
|
76
|
+
count: files.length,
|
|
77
|
+
phase_dir: phase ? dirs[0].replace(/^\d+(?:\.\d+)?-?/, '') : null,
|
|
78
|
+
};
|
|
79
|
+
output(result, raw, files.join('\n'));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
// Default: list directories
|
|
83
|
+
output({ directories: dirs, count: dirs.length }, raw, dirs.join('\n'));
|
|
84
|
+
}
|
|
85
|
+
catch (e) {
|
|
86
|
+
error('Failed to list phases: ' + e.message);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// ─── Phase Add ────────────────────────────────────────────────────────────────
|
|
90
|
+
/**
|
|
91
|
+
* CLI command: Add a new phase to the end of the roadmap and create its directory.
|
|
92
|
+
* @param cwd - Project working directory
|
|
93
|
+
* @param description - Human-readable phase description for the roadmap heading
|
|
94
|
+
* @param raw - Output raw padded number instead of JSON
|
|
95
|
+
* @param context - Optional context text for CONTEXT.md
|
|
96
|
+
* @returns void — writes JSON or raw text to stdout and exits on error
|
|
97
|
+
*/
|
|
98
|
+
function cmdPhaseAdd(cwd, description, raw, context) {
|
|
99
|
+
if (!description) {
|
|
100
|
+
error('description required for phase add');
|
|
101
|
+
}
|
|
102
|
+
if (description.length > 60) {
|
|
103
|
+
error(`description too long (${description.length} chars): must not exceed 60 characters. Shorten your description to fewer than the maximum characters, e.g.: phase add 'Short name'`);
|
|
104
|
+
}
|
|
105
|
+
// Pre-flight gate checks
|
|
106
|
+
const gates = runPreflightGates(cwd, 'phase-add');
|
|
107
|
+
if (!gates.passed) {
|
|
108
|
+
output({
|
|
109
|
+
gate_failed: true,
|
|
110
|
+
gate_errors: gates.errors,
|
|
111
|
+
gate_warnings: gates.warnings,
|
|
112
|
+
}, raw);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const roadmapPath = path.join(cwd, '.planning', 'ROADMAP.md');
|
|
116
|
+
let content;
|
|
117
|
+
try {
|
|
118
|
+
content = readRoadmapFile(roadmapPath);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
error('ROADMAP.md not found');
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const slug = generateSlugInternal(description);
|
|
125
|
+
// Find highest integer phase number across full content (including shipped sections)
|
|
126
|
+
const phasePattern = /#{2,3}\s*Phase\s+(\d+)(?:\.\d+)?:/gi;
|
|
127
|
+
let maxPhase = 0;
|
|
128
|
+
const existingPhaseNums = [];
|
|
129
|
+
let m;
|
|
130
|
+
while ((m = phasePattern.exec(content)) !== null) {
|
|
131
|
+
const num = parseInt(m[1], 10);
|
|
132
|
+
if (!existingPhaseNums.includes(num))
|
|
133
|
+
existingPhaseNums.push(num);
|
|
134
|
+
if (num > maxPhase)
|
|
135
|
+
maxPhase = num;
|
|
136
|
+
}
|
|
137
|
+
// Detect numbering gaps in existing phases
|
|
138
|
+
const addWarnings = [];
|
|
139
|
+
existingPhaseNums.sort((a, b) => a - b);
|
|
140
|
+
for (let i = 1; i < existingPhaseNums.length; i++) {
|
|
141
|
+
if (existingPhaseNums[i] !== existingPhaseNums[i - 1] + 1) {
|
|
142
|
+
addWarnings.push(`Gap in phase sequence: ${existingPhaseNums[i - 1]} to ${existingPhaseNums[i]} (missing ${existingPhaseNums[i - 1] + 1})`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const newPhaseNum = maxPhase + 1;
|
|
146
|
+
const paddedNum = String(newPhaseNum).padStart(2, '0');
|
|
147
|
+
const dirName = `${paddedNum}-${slug}`;
|
|
148
|
+
const dirPath = getPhaseDirPath(cwd, null, dirName);
|
|
149
|
+
// Create directory
|
|
150
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
151
|
+
// Write CONTEXT.md if context provided
|
|
152
|
+
if (context) {
|
|
153
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
154
|
+
const contextContent = `---\nphase: "${paddedNum}"\nname: "${description}"\ncreated: ${today}\n---\n\n# Phase ${newPhaseNum}: ${description} -- Context\n\n${context}\n`;
|
|
155
|
+
fs.writeFileSync(path.join(dirPath, `${paddedNum}-CONTEXT.md`), contextContent, 'utf-8');
|
|
156
|
+
}
|
|
157
|
+
// Detect heading level used in existing ROADMAP (## or ###)
|
|
158
|
+
const headingLevel = /^## Phase \d+:/m.test(content) ? '##' : '###';
|
|
159
|
+
// Build phase entry (includes Duration for schedule computation)
|
|
160
|
+
const phaseEntry = `\n${headingLevel} Phase ${newPhaseNum}: ${description}\n\n**Goal:** ${description}\n**Depends on:** Phase ${maxPhase}\n**Duration:** 7d\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run /grd:plan-phase ${newPhaseNum} to break down)\n`;
|
|
161
|
+
// Find insertion point: before last "---" or at end
|
|
162
|
+
let updatedContent;
|
|
163
|
+
const lastSeparator = content.lastIndexOf('\n---');
|
|
164
|
+
if (lastSeparator > 0) {
|
|
165
|
+
updatedContent = content.slice(0, lastSeparator) + phaseEntry + content.slice(lastSeparator);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
updatedContent = content + phaseEntry;
|
|
169
|
+
}
|
|
170
|
+
writeRoadmapFile(roadmapPath, updatedContent);
|
|
171
|
+
const result = {
|
|
172
|
+
phase_number: newPhaseNum,
|
|
173
|
+
padded: paddedNum,
|
|
174
|
+
name: description,
|
|
175
|
+
slug,
|
|
176
|
+
directory: path.relative(cwd, dirPath),
|
|
177
|
+
schedule_affected: true,
|
|
178
|
+
...(addWarnings.length > 0 ? { warnings: addWarnings } : {}),
|
|
179
|
+
};
|
|
180
|
+
output(result, raw, paddedNum);
|
|
181
|
+
}
|
|
182
|
+
// ─── Phase Insert (Decimal) ──────────────────────────────────────────────────
|
|
183
|
+
/**
|
|
184
|
+
* CLI command: Insert a decimal phase after a specified phase in the roadmap.
|
|
185
|
+
* @param cwd - Project working directory
|
|
186
|
+
* @param afterPhase - Phase number to insert after (e.g., '06')
|
|
187
|
+
* @param description - Human-readable phase description
|
|
188
|
+
* @param raw - Output raw decimal phase number instead of JSON
|
|
189
|
+
* @returns void — writes JSON or raw text to stdout and exits on error
|
|
190
|
+
*/
|
|
191
|
+
function cmdPhaseInsert(cwd, afterPhase, description, raw) {
|
|
192
|
+
if (!afterPhase || !description) {
|
|
193
|
+
error('after-phase and description required for phase insert. Usage: phase insert <after-phase-number> <description>. Provide both arguments, e.g.: phase insert 2 "New phase description"');
|
|
194
|
+
}
|
|
195
|
+
// Pre-flight gate checks
|
|
196
|
+
const gates = runPreflightGates(cwd, 'phase-insert');
|
|
197
|
+
if (!gates.passed) {
|
|
198
|
+
output({
|
|
199
|
+
gate_failed: true,
|
|
200
|
+
gate_errors: gates.errors,
|
|
201
|
+
gate_warnings: gates.warnings,
|
|
202
|
+
}, raw);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const roadmapPath = path.join(cwd, '.planning', 'ROADMAP.md');
|
|
206
|
+
let content;
|
|
207
|
+
try {
|
|
208
|
+
content = readRoadmapFile(roadmapPath);
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
error('ROADMAP.md not found');
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const activeContent = stripShippedSections(content);
|
|
215
|
+
const slug = generateSlugInternal(description);
|
|
216
|
+
// Verify target phase exists (in active section only)
|
|
217
|
+
const afterPhaseEscaped = afterPhase.replace(/\./g, '\\.');
|
|
218
|
+
const targetPattern = new RegExp(`#{2,}\\s*Phase\\s+${afterPhaseEscaped}:`, 'i');
|
|
219
|
+
if (!targetPattern.test(activeContent)) {
|
|
220
|
+
error(`Phase ${afterPhase} not found in ROADMAP.md. Run "roadmap get-phase ${afterPhase}" to verify the phase exists, or check .planning/ROADMAP.md`);
|
|
221
|
+
}
|
|
222
|
+
// Calculate next decimal using existing logic
|
|
223
|
+
const phasesDir = getPhasesDirPath(cwd);
|
|
224
|
+
const normalizedBase = normalizePhaseName(afterPhase);
|
|
225
|
+
const existingDecimals = [];
|
|
226
|
+
try {
|
|
227
|
+
const entries = fs.readdirSync(phasesDir, {
|
|
228
|
+
withFileTypes: true,
|
|
229
|
+
});
|
|
230
|
+
const dirs = entries
|
|
231
|
+
.filter((e) => e.isDirectory())
|
|
232
|
+
.map((e) => e.name);
|
|
233
|
+
const decimalPattern = new RegExp(`^${normalizedBase}\\.(\\d+)`);
|
|
234
|
+
for (const dir of dirs) {
|
|
235
|
+
const dm = dir.match(decimalPattern);
|
|
236
|
+
if (dm)
|
|
237
|
+
existingDecimals.push(parseInt(dm[1], 10));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
// Phases directory may not exist yet; start decimal numbering from 1
|
|
242
|
+
}
|
|
243
|
+
const nextDecimal = existingDecimals.length === 0 ? 1 : Math.max(...existingDecimals) + 1;
|
|
244
|
+
const decimalPhase = `${normalizedBase}.${nextDecimal}`;
|
|
245
|
+
const dirName = `${decimalPhase}-${slug}`;
|
|
246
|
+
const dirPath = path.join(phasesDir, dirName);
|
|
247
|
+
// Create directory
|
|
248
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
249
|
+
// Detect heading level used in existing ROADMAP (## or ###)
|
|
250
|
+
const headingLevel = /^## Phase \d+:/m.test(content) ? '##' : '###';
|
|
251
|
+
// Build phase entry (includes Duration for schedule computation)
|
|
252
|
+
const phaseEntry = `\n${headingLevel} Phase ${decimalPhase}: ${description} (INSERTED)\n\n**Goal:** [Urgent work - to be planned]\n**Depends on:** Phase ${afterPhase}\n**Duration:** 3d\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run /grd:plan-phase ${decimalPhase} to break down)\n`;
|
|
253
|
+
// Insert after the target phase section
|
|
254
|
+
const headerPattern = new RegExp(`(#{2,}\\s*Phase\\s+${afterPhaseEscaped}:[^\\n]*\\n)`, 'i');
|
|
255
|
+
const headerMatch = content.match(headerPattern);
|
|
256
|
+
if (!headerMatch) {
|
|
257
|
+
error(`Could not find Phase ${afterPhase} header in ROADMAP.md. Ensure the phase heading matches the format "## Phase ${afterPhase}: <description>"`);
|
|
258
|
+
}
|
|
259
|
+
const headerIdx = content.indexOf(headerMatch[0]);
|
|
260
|
+
const afterHeader = content.slice(headerIdx + headerMatch[0].length);
|
|
261
|
+
const nextPhaseMatch = afterHeader.match(/\n#{2,}\s+Phase\s+\d/i);
|
|
262
|
+
let insertIdx;
|
|
263
|
+
if (nextPhaseMatch && nextPhaseMatch.index !== undefined) {
|
|
264
|
+
insertIdx = headerIdx + headerMatch[0].length + nextPhaseMatch.index;
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
insertIdx = content.length;
|
|
268
|
+
}
|
|
269
|
+
const updatedContent = content.slice(0, insertIdx) + phaseEntry + content.slice(insertIdx);
|
|
270
|
+
writeRoadmapFile(roadmapPath, updatedContent);
|
|
271
|
+
const result = {
|
|
272
|
+
phase_number: decimalPhase,
|
|
273
|
+
after_phase: afterPhase,
|
|
274
|
+
name: description,
|
|
275
|
+
slug,
|
|
276
|
+
directory: path.relative(cwd, dirPath),
|
|
277
|
+
schedule_affected: true,
|
|
278
|
+
};
|
|
279
|
+
output(result, raw, decimalPhase);
|
|
280
|
+
}
|
|
281
|
+
// ─── Phase Remove ─────────────────────────────────────────────────────────────
|
|
282
|
+
/**
|
|
283
|
+
* Validate the targetPhase argument for phase remove.
|
|
284
|
+
* Calls error() and returns false if invalid; returns true if valid.
|
|
285
|
+
* @param targetPhase - Phase number to validate
|
|
286
|
+
*/
|
|
287
|
+
function _validateRemoveArgs(targetPhase) {
|
|
288
|
+
if (!targetPhase) {
|
|
289
|
+
error("phase number required for phase remove. Usage: phase remove <N>. Provide the phase number to remove, e.g.: phase remove 3. Run 'phase list' to see available phases.");
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Renumber integer phase directories after removing an integer phase.
|
|
296
|
+
* All directories with an integer part greater than removedInt are shifted down by 1.
|
|
297
|
+
* Mutates renamedDirs and renamedFiles arrays in place.
|
|
298
|
+
* @param phasesDir - Absolute path to the phases directory
|
|
299
|
+
* @param removedInt - The integer phase number that was removed
|
|
300
|
+
* @param renamedDirs - Accumulator for renamed directories
|
|
301
|
+
* @param renamedFiles - Accumulator for renamed files
|
|
302
|
+
*/
|
|
303
|
+
function _renumberIntegerPhases(phasesDir, removedInt, renamedDirs, renamedFiles) {
|
|
304
|
+
try {
|
|
305
|
+
const entries = fs.readdirSync(phasesDir, {
|
|
306
|
+
withFileTypes: true,
|
|
307
|
+
});
|
|
308
|
+
const dirs = entries
|
|
309
|
+
.filter((e) => e.isDirectory())
|
|
310
|
+
.map((e) => e.name)
|
|
311
|
+
.sort();
|
|
312
|
+
// Collect directories that need renumbering (integer phases > removed, and their decimals)
|
|
313
|
+
const toRename = [];
|
|
314
|
+
for (const dir of dirs) {
|
|
315
|
+
const dm = dir.match(/^(\d+)(?:\.(\d+))?-(.+)$/);
|
|
316
|
+
if (!dm)
|
|
317
|
+
continue;
|
|
318
|
+
const dirInt = parseInt(dm[1], 10);
|
|
319
|
+
if (dirInt > removedInt) {
|
|
320
|
+
toRename.push({
|
|
321
|
+
dir,
|
|
322
|
+
oldInt: dirInt,
|
|
323
|
+
decimal: dm[2] ? parseInt(dm[2], 10) : null,
|
|
324
|
+
slug: dm[3],
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
// Sort descending to avoid conflicts
|
|
329
|
+
toRename.sort((a, b) => {
|
|
330
|
+
if (a.oldInt !== b.oldInt)
|
|
331
|
+
return b.oldInt - a.oldInt;
|
|
332
|
+
return (b.decimal || 0) - (a.decimal || 0);
|
|
333
|
+
});
|
|
334
|
+
for (const item of toRename) {
|
|
335
|
+
const newInt = item.oldInt - 1;
|
|
336
|
+
const newPadded = String(newInt).padStart(2, '0');
|
|
337
|
+
const oldPadded = String(item.oldInt).padStart(2, '0');
|
|
338
|
+
const decimalSuffix = item.decimal !== null ? `.${item.decimal}` : '';
|
|
339
|
+
const oldPrefix = `${oldPadded}${decimalSuffix}`;
|
|
340
|
+
const newPrefix = `${newPadded}${decimalSuffix}`;
|
|
341
|
+
const newDirName = `${newPrefix}-${item.slug}`;
|
|
342
|
+
// Rename directory
|
|
343
|
+
fs.renameSync(path.join(phasesDir, item.dir), path.join(phasesDir, newDirName));
|
|
344
|
+
renamedDirs.push({ from: item.dir, to: newDirName });
|
|
345
|
+
// Rename files inside
|
|
346
|
+
let dirFiles;
|
|
347
|
+
try {
|
|
348
|
+
dirFiles = fs.readdirSync(path.join(phasesDir, newDirName));
|
|
349
|
+
}
|
|
350
|
+
catch (readDirErr) {
|
|
351
|
+
const typedErr = readDirErr;
|
|
352
|
+
if (typedErr.code && typedErr.code !== 'ENOENT') {
|
|
353
|
+
process.stderr.write(`[phase] renumber read error (${typedErr.code}): ${typedErr.message}\n`);
|
|
354
|
+
}
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
for (const f of dirFiles) {
|
|
358
|
+
if (f.startsWith(oldPrefix)) {
|
|
359
|
+
const newFileName = newPrefix + f.slice(oldPrefix.length);
|
|
360
|
+
fs.renameSync(path.join(phasesDir, newDirName, f), path.join(phasesDir, newDirName, newFileName));
|
|
361
|
+
renamedFiles.push({ from: f, to: newFileName });
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
catch (renumberErr) {
|
|
367
|
+
const typedErr = renumberErr;
|
|
368
|
+
if (typedErr.code && typedErr.code !== 'ENOENT') {
|
|
369
|
+
process.stderr.write(`[phase] renumber error (${typedErr.code}): ${typedErr.message}\n`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Renumber decimal phase directories after removing a decimal phase.
|
|
375
|
+
* Sibling decimals with a higher decimal part than removedDecimal are shifted down by 1.
|
|
376
|
+
* Mutates renamedDirs and renamedFiles arrays in place.
|
|
377
|
+
* @param phasesDir - Absolute path to the phases directory
|
|
378
|
+
* @param baseInt - The integer part of the removed decimal phase (e.g. "06")
|
|
379
|
+
* @param removedDecimal - The decimal part that was removed (e.g. 2 for "06.2")
|
|
380
|
+
* @param renamedDirs - Accumulator for renamed directories
|
|
381
|
+
* @param renamedFiles - Accumulator for renamed files
|
|
382
|
+
*/
|
|
383
|
+
function _renumberDecimalPhases(phasesDir, baseInt, removedDecimal, renamedDirs, renamedFiles) {
|
|
384
|
+
try {
|
|
385
|
+
const entries = fs.readdirSync(phasesDir, {
|
|
386
|
+
withFileTypes: true,
|
|
387
|
+
});
|
|
388
|
+
const dirs = entries
|
|
389
|
+
.filter((e) => e.isDirectory())
|
|
390
|
+
.map((e) => e.name)
|
|
391
|
+
.sort();
|
|
392
|
+
// Find sibling decimals with higher numbers
|
|
393
|
+
const decPattern = new RegExp(`^${baseInt}\\.(\\d+)-(.+)$`);
|
|
394
|
+
const toRename = [];
|
|
395
|
+
for (const dir of dirs) {
|
|
396
|
+
const dm = dir.match(decPattern);
|
|
397
|
+
if (dm && parseInt(dm[1], 10) > removedDecimal) {
|
|
398
|
+
toRename.push({
|
|
399
|
+
dir,
|
|
400
|
+
oldDecimal: parseInt(dm[1], 10),
|
|
401
|
+
slug: dm[2],
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
// Sort descending to avoid conflicts
|
|
406
|
+
toRename.sort((a, b) => b.oldDecimal - a.oldDecimal);
|
|
407
|
+
for (const item of toRename) {
|
|
408
|
+
const newDecimal = item.oldDecimal - 1;
|
|
409
|
+
const oldPhaseId = `${baseInt}.${item.oldDecimal}`;
|
|
410
|
+
const newPhaseId = `${baseInt}.${newDecimal}`;
|
|
411
|
+
const newDirName = `${baseInt}.${newDecimal}-${item.slug}`;
|
|
412
|
+
// Rename directory
|
|
413
|
+
fs.renameSync(path.join(phasesDir, item.dir), path.join(phasesDir, newDirName));
|
|
414
|
+
renamedDirs.push({ from: item.dir, to: newDirName });
|
|
415
|
+
// Rename files inside
|
|
416
|
+
const dirFiles = fs.readdirSync(path.join(phasesDir, newDirName));
|
|
417
|
+
for (const f of dirFiles) {
|
|
418
|
+
// Files may have phase prefix like "06.2-01-PLAN.md"
|
|
419
|
+
if (f.includes(oldPhaseId)) {
|
|
420
|
+
const newFileName = f.replace(oldPhaseId, newPhaseId);
|
|
421
|
+
fs.renameSync(path.join(phasesDir, newDirName, f), path.join(phasesDir, newDirName, newFileName));
|
|
422
|
+
renamedFiles.push({ from: f, to: newFileName });
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
catch {
|
|
428
|
+
// Phases directory may not exist; no decimal phases to rename
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Rename phase directories and files after a phase removal.
|
|
433
|
+
* Dispatches to _renumberIntegerPhases or _renumberDecimalPhases based on isDecimal.
|
|
434
|
+
* @param phasesDir - Absolute path to the phases directory
|
|
435
|
+
* @param normalized - Normalized phase number string (e.g. "06" or "06.2")
|
|
436
|
+
* @param isDecimal - Whether the removed phase was a decimal phase
|
|
437
|
+
*/
|
|
438
|
+
function _reorderDirectories(phasesDir, normalized, isDecimal) {
|
|
439
|
+
const renamedDirs = [];
|
|
440
|
+
const renamedFiles = [];
|
|
441
|
+
if (isDecimal) {
|
|
442
|
+
const baseParts = normalized.split('.');
|
|
443
|
+
const baseInt = baseParts[0];
|
|
444
|
+
const removedDecimal = parseInt(baseParts[1], 10);
|
|
445
|
+
_renumberDecimalPhases(phasesDir, baseInt, removedDecimal, renamedDirs, renamedFiles);
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
const removedInt = parseInt(normalized, 10);
|
|
449
|
+
_renumberIntegerPhases(phasesDir, removedInt, renamedDirs, renamedFiles);
|
|
450
|
+
}
|
|
451
|
+
return { renamedDirs, renamedFiles };
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Update ROADMAP.md text after a phase removal: remove the target section and renumber
|
|
455
|
+
* all subsequent phase references (headings, checkboxes, plan refs, table rows, depends-on).
|
|
456
|
+
* @param roadmapContent - Current ROADMAP.md content
|
|
457
|
+
* @param targetPhase - The phase number that was removed (e.g. "06" or "06.2")
|
|
458
|
+
* @param normalized - Normalized phase number string
|
|
459
|
+
* @param isDecimal - Whether the removed phase was a decimal phase
|
|
460
|
+
*/
|
|
461
|
+
function _reorderRoadmapEntries(roadmapContent, targetPhase, normalized, isDecimal) {
|
|
462
|
+
const targetEscaped = targetPhase.replace(/\./g, '\\.');
|
|
463
|
+
// Remove the target phase section
|
|
464
|
+
const sectionPattern = new RegExp(`\\n?#{2,}\\s*Phase\\s+${targetEscaped}\\s*:[\\s\\S]*?(?=\\n#{2,}\\s+Phase\\s+\\d|$)`, 'i');
|
|
465
|
+
roadmapContent = roadmapContent.replace(sectionPattern, '');
|
|
466
|
+
// Remove from phase list (checkbox)
|
|
467
|
+
const checkboxPattern = new RegExp(`\\n?-\\s*\\[[ x]\\]\\s*.*Phase\\s+${targetEscaped}[:\\s][^\\n]*`, 'gi');
|
|
468
|
+
roadmapContent = roadmapContent.replace(checkboxPattern, '');
|
|
469
|
+
// Remove from progress table
|
|
470
|
+
const tableRowPattern = new RegExp(`\\n?\\|\\s*${targetEscaped}\\.?\\s[^|]*\\|[^\\n]*`, 'gi');
|
|
471
|
+
roadmapContent = roadmapContent.replace(tableRowPattern, '');
|
|
472
|
+
// Renumber references in ROADMAP for subsequent integer phases
|
|
473
|
+
if (!isDecimal) {
|
|
474
|
+
const removedInt = parseInt(normalized, 10);
|
|
475
|
+
// Collect all integer phases > removedInt
|
|
476
|
+
const maxPhase = 99; // reasonable upper bound
|
|
477
|
+
for (let oldNum = maxPhase; oldNum > removedInt; oldNum--) {
|
|
478
|
+
const newNum = oldNum - 1;
|
|
479
|
+
const oldStr = String(oldNum);
|
|
480
|
+
const newStr = String(newNum);
|
|
481
|
+
const oldPad = oldStr.padStart(2, '0');
|
|
482
|
+
const newPad = newStr.padStart(2, '0');
|
|
483
|
+
// Phase headings: ### Phase 18: -> ### Phase 17: (or ## Phase)
|
|
484
|
+
roadmapContent = roadmapContent.replace(new RegExp(`(#{2,}\\s*Phase\\s+)${oldStr}(\\s*:)`, 'gi'), `$1${newStr}$2`);
|
|
485
|
+
// Checkbox items: - [ ] **Phase 18:** -> - [ ] **Phase 17:**
|
|
486
|
+
roadmapContent = roadmapContent.replace(new RegExp(`(Phase\\s+)${oldStr}([:\\s])`, 'g'), `$1${newStr}$2`);
|
|
487
|
+
// Plan references: 18-01 -> 17-01
|
|
488
|
+
roadmapContent = roadmapContent.replace(new RegExp(`${oldPad}-(\\d{2})`, 'g'), `${newPad}-$1`);
|
|
489
|
+
// Table rows: | 18. -> | 17.
|
|
490
|
+
roadmapContent = roadmapContent.replace(new RegExp(`(\\|\\s*)${oldStr}\\.\\s`, 'g'), `$1${newStr}. `);
|
|
491
|
+
// Depends on references
|
|
492
|
+
roadmapContent = roadmapContent.replace(new RegExp(`(Depends on:\\*\\*\\s*Phase\\s+)${oldStr}\\b`, 'gi'), `$1${newStr}`);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return roadmapContent;
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Patch frontmatter phase references inside plan/summary files when phases are renumbered.
|
|
499
|
+
* Currently a no-op placeholder -- frontmatter phase refs are not yet tracked in this workflow.
|
|
500
|
+
* @param _phasesDir - Absolute path to the phases directory
|
|
501
|
+
* @param _oldNum - Old phase number string
|
|
502
|
+
* @param _newNum - New phase number string
|
|
503
|
+
*/
|
|
504
|
+
function _patchFrontmatterRefs(_phasesDir, _oldNum, _newNum) {
|
|
505
|
+
// No frontmatter phase references are currently maintained in plan/summary files.
|
|
506
|
+
// This is a reserved hook for future frontmatter ref tracking.
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* CLI command: Remove a phase from the roadmap, delete its directory, and renumber subsequent phases.
|
|
510
|
+
* @param cwd - Project working directory
|
|
511
|
+
* @param targetPhase - Phase number to remove (integer or decimal)
|
|
512
|
+
* @param options - Remove options
|
|
513
|
+
* @param raw - Output raw text instead of JSON
|
|
514
|
+
* @returns void — writes JSON or raw text to stdout and exits on error
|
|
515
|
+
*/
|
|
516
|
+
function cmdPhaseRemove(cwd, targetPhase, options, raw) {
|
|
517
|
+
if (!_validateRemoveArgs(targetPhase))
|
|
518
|
+
return;
|
|
519
|
+
const roadmapPath = path.join(cwd, '.planning', 'ROADMAP.md');
|
|
520
|
+
const phasesDir = getPhasesDirPath(cwd);
|
|
521
|
+
const force = options.force || false;
|
|
522
|
+
const dryRun = options.dryRun || false;
|
|
523
|
+
// Read ROADMAP.md FIRST (before any mutations) to detect unreadable state early
|
|
524
|
+
let roadmapContent;
|
|
525
|
+
try {
|
|
526
|
+
roadmapContent = readRoadmapFile(roadmapPath);
|
|
527
|
+
}
|
|
528
|
+
catch (readErr) {
|
|
529
|
+
error(`Cannot read ROADMAP.md: ${readErr.message}. Ensure .planning/ROADMAP.md exists and is readable, then retry.`);
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
// Normalize the target
|
|
533
|
+
const normalized = normalizePhaseName(targetPhase);
|
|
534
|
+
const isDecimal = targetPhase.includes('.');
|
|
535
|
+
// Find and validate target directory
|
|
536
|
+
let targetDir = null;
|
|
537
|
+
try {
|
|
538
|
+
const entries = fs.readdirSync(phasesDir, {
|
|
539
|
+
withFileTypes: true,
|
|
540
|
+
});
|
|
541
|
+
const dirs = entries
|
|
542
|
+
.filter((e) => e.isDirectory())
|
|
543
|
+
.map((e) => e.name)
|
|
544
|
+
.sort();
|
|
545
|
+
targetDir =
|
|
546
|
+
dirs.find((d) => d.startsWith(normalized + '-') || d === normalized) || null;
|
|
547
|
+
}
|
|
548
|
+
catch {
|
|
549
|
+
// Phases directory may not exist; targetDir stays null
|
|
550
|
+
}
|
|
551
|
+
// Check for executed work (SUMMARY.md files) -- skip when dry-run
|
|
552
|
+
if (targetDir && !force && !dryRun) {
|
|
553
|
+
const targetPath = path.join(phasesDir, targetDir);
|
|
554
|
+
const files = fs.readdirSync(targetPath);
|
|
555
|
+
const summaries = files.filter((f) => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
|
|
556
|
+
if (summaries.length > 0) {
|
|
557
|
+
error(`Phase ${targetPhase} has ${summaries.length} executed plan(s). Use --force to remove anyway.`);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
// Dry-run: collect what would happen and return early
|
|
561
|
+
if (dryRun) {
|
|
562
|
+
// Predict which phases would be renumbered
|
|
563
|
+
const wouldRenumber = [];
|
|
564
|
+
try {
|
|
565
|
+
const entries = fs.readdirSync(phasesDir, {
|
|
566
|
+
withFileTypes: true,
|
|
567
|
+
});
|
|
568
|
+
const dirs = entries
|
|
569
|
+
.filter((e) => e.isDirectory())
|
|
570
|
+
.map((e) => e.name)
|
|
571
|
+
.sort();
|
|
572
|
+
const removedInt = parseInt(normalized, 10);
|
|
573
|
+
for (const dir of dirs) {
|
|
574
|
+
const dm = dir.match(/^(\d+)(?:\.(\d+))?-(.+)$/);
|
|
575
|
+
if (!dm)
|
|
576
|
+
continue;
|
|
577
|
+
const dirInt = parseInt(dm[1], 10);
|
|
578
|
+
if (dirInt > removedInt) {
|
|
579
|
+
wouldRenumber.push(dir);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
catch {
|
|
584
|
+
// Phases directory may not exist; dry-run preview will show empty renumber list
|
|
585
|
+
}
|
|
586
|
+
const result = {
|
|
587
|
+
dry_run: true,
|
|
588
|
+
would_remove: targetDir || normalized,
|
|
589
|
+
would_renumber: wouldRenumber,
|
|
590
|
+
removed: targetPhase,
|
|
591
|
+
directory_deleted: null,
|
|
592
|
+
renamed_directories: [],
|
|
593
|
+
renamed_files: [],
|
|
594
|
+
roadmap_updated: false,
|
|
595
|
+
state_updated: false,
|
|
596
|
+
};
|
|
597
|
+
output(result, raw, `dry-run: would remove phase ${targetPhase}`);
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
// Delete target directory
|
|
601
|
+
if (targetDir) {
|
|
602
|
+
fs.rmSync(path.join(phasesDir, targetDir), {
|
|
603
|
+
recursive: true,
|
|
604
|
+
force: true,
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
// Clean up matching .worktrees/ directories
|
|
608
|
+
const cleanedWorktrees = [];
|
|
609
|
+
const worktreesDir = path.join(cwd, '.worktrees');
|
|
610
|
+
if (fs.existsSync(worktreesDir)) {
|
|
611
|
+
try {
|
|
612
|
+
const wtEntries = fs.readdirSync(worktreesDir, {
|
|
613
|
+
withFileTypes: true,
|
|
614
|
+
});
|
|
615
|
+
const wtPattern = new RegExp(`-${normalized}(?:-|$)`);
|
|
616
|
+
for (const entry of wtEntries) {
|
|
617
|
+
if (entry.isDirectory() && wtPattern.test(entry.name)) {
|
|
618
|
+
const wtPath = path.join(worktreesDir, entry.name);
|
|
619
|
+
fs.rmSync(wtPath, { recursive: true, force: true });
|
|
620
|
+
cleanedWorktrees.push(entry.name);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
catch {
|
|
625
|
+
// Non-fatal
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
// Renumber subsequent phases (directories + files)
|
|
629
|
+
const { renamedDirs, renamedFiles } = _reorderDirectories(phasesDir, normalized, isDecimal);
|
|
630
|
+
// Patch frontmatter refs for all renumbered phases
|
|
631
|
+
_patchFrontmatterRefs(phasesDir, normalized, isDecimal ? normalized : normalized);
|
|
632
|
+
// Update ROADMAP.md (already read above)
|
|
633
|
+
roadmapContent = _reorderRoadmapEntries(roadmapContent, targetPhase, normalized, isDecimal);
|
|
634
|
+
writeRoadmapFile(roadmapPath, roadmapContent);
|
|
635
|
+
// Update STATE.md phase count
|
|
636
|
+
const statePath = path.join(cwd, '.planning', 'STATE.md');
|
|
637
|
+
if (fs.existsSync(statePath)) {
|
|
638
|
+
let stateContent = readStateFile(statePath);
|
|
639
|
+
// Update "Total Phases" field
|
|
640
|
+
const totalPattern = /(\*\*Total Phases:\*\*\s*)(\d+)/;
|
|
641
|
+
const totalMatch = stateContent.match(totalPattern);
|
|
642
|
+
if (totalMatch) {
|
|
643
|
+
const oldTotal = parseInt(totalMatch[2], 10);
|
|
644
|
+
stateContent = stateContent.replace(totalPattern, `$1${oldTotal - 1}`);
|
|
645
|
+
}
|
|
646
|
+
// Update "Phase: X of Y" pattern
|
|
647
|
+
const ofPattern = /(\bof\s+)(\d+)(\s*(?:\(|phases?))/i;
|
|
648
|
+
const ofMatch = stateContent.match(ofPattern);
|
|
649
|
+
if (ofMatch) {
|
|
650
|
+
const oldTotal = parseInt(ofMatch[2], 10);
|
|
651
|
+
stateContent = stateContent.replace(ofPattern, `$1${oldTotal - 1}$3`);
|
|
652
|
+
}
|
|
653
|
+
writeStateFile(statePath, stateContent);
|
|
654
|
+
}
|
|
655
|
+
const result = {
|
|
656
|
+
removed: targetPhase,
|
|
657
|
+
directory_deleted: targetDir || null,
|
|
658
|
+
renamed_directories: renamedDirs,
|
|
659
|
+
renamed_files: renamedFiles,
|
|
660
|
+
roadmap_updated: true,
|
|
661
|
+
state_updated: fs.existsSync(statePath),
|
|
662
|
+
...(cleanedWorktrees.length > 0 ? { cleaned_worktrees: cleanedWorktrees } : {}),
|
|
663
|
+
};
|
|
664
|
+
output(result, raw, `Removed phase ${result.removed}`);
|
|
665
|
+
}
|
|
666
|
+
// ─── Phase Complete (Transition) ──────────────────────────────────────────────
|
|
667
|
+
// _phaseCompleteCore moved to lib/phase-complete.ts in Spec 3.
|
|
668
|
+
// cmdPhaseComplete and cmdPhaseBatchComplete below import it from there.
|
|
669
|
+
/**
|
|
670
|
+
* CLI command: Mark a phase as complete, update STATE.md, ROADMAP.md, and run quality analysis.
|
|
671
|
+
* @param cwd - Project working directory
|
|
672
|
+
* @param phaseNum - Phase number to complete (e.g., '02' or '2')
|
|
673
|
+
* @param raw - Output raw text instead of JSON
|
|
674
|
+
* @param options - Options (e.g., dryRun, force)
|
|
675
|
+
* @returns void — writes JSON or raw text to stdout and exits on error
|
|
676
|
+
*/
|
|
677
|
+
async function cmdPhaseComplete(cwd, phaseNum, raw, options) {
|
|
678
|
+
if (!phaseNum) {
|
|
679
|
+
error("phase number required for phase complete. Usage: phase complete <N>. Provide the phase number to mark complete, e.g.: phase complete 3. Run 'phase list' to see available phases.");
|
|
680
|
+
}
|
|
681
|
+
let result;
|
|
682
|
+
try {
|
|
683
|
+
result = _phaseCompleteCore(cwd, phaseNum, options);
|
|
684
|
+
}
|
|
685
|
+
catch (e) {
|
|
686
|
+
const config = loadConfig(cwd);
|
|
687
|
+
if (config.phase_complete_llm_fallback === true) {
|
|
688
|
+
process.stderr.write(`[phase-complete-llm] mechanical path failed, attempting fallback\n`);
|
|
689
|
+
const { createScheduler } = require('./scheduler');
|
|
690
|
+
const { attemptLlmFallbackCompletion } = require('./phase-complete-llm');
|
|
691
|
+
const scheduler = createScheduler(config.scheduler, config.superpowers);
|
|
692
|
+
const fallbackResult = await attemptLlmFallbackCompletion(cwd, phaseNum, scheduler, e);
|
|
693
|
+
if (fallbackResult) {
|
|
694
|
+
result = fallbackResult;
|
|
695
|
+
}
|
|
696
|
+
else {
|
|
697
|
+
error(e.message);
|
|
698
|
+
return; // unreachable after error() but helps TS narrowing
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
else {
|
|
702
|
+
error(e.message);
|
|
703
|
+
return; // unreachable after error() but helps TS narrowing
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
// If the mechanical path returned gate_failed (not thrown), also try the LLM fallback.
|
|
707
|
+
if (result.gate_failed) {
|
|
708
|
+
const config = loadConfig(cwd);
|
|
709
|
+
if (config.phase_complete_llm_fallback === true) {
|
|
710
|
+
process.stderr.write(`[phase-complete-llm] gates failed, attempting fallback\n`);
|
|
711
|
+
const { createScheduler } = require('./scheduler');
|
|
712
|
+
const { attemptLlmFallbackCompletion } = require('./phase-complete-llm');
|
|
713
|
+
const scheduler = createScheduler(config.scheduler, config.superpowers);
|
|
714
|
+
const fallbackResult = await attemptLlmFallbackCompletion(cwd, phaseNum, scheduler, { gate_errors: result.gate_errors });
|
|
715
|
+
if (fallbackResult) {
|
|
716
|
+
result = fallbackResult;
|
|
717
|
+
}
|
|
718
|
+
// else: leave result as gate_failed — existing output logic renders it
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
let rawOutput = '';
|
|
722
|
+
if (raw) {
|
|
723
|
+
if (result.dry_run) {
|
|
724
|
+
rawOutput = `dry-run: would complete phase ${phaseNum}`;
|
|
725
|
+
}
|
|
726
|
+
else if (result.gate_failed) {
|
|
727
|
+
rawOutput = '';
|
|
728
|
+
}
|
|
729
|
+
else {
|
|
730
|
+
rawOutput = `Phase ${phaseNum} complete. ${result.plans_executed} plans executed.`;
|
|
731
|
+
if (result.next_phase) {
|
|
732
|
+
rawOutput += ` Next: Phase ${result.next_phase}`;
|
|
733
|
+
}
|
|
734
|
+
if (result.quality_report &&
|
|
735
|
+
result.quality_report.summary &&
|
|
736
|
+
result.quality_report.summary.total_issues > 0) {
|
|
737
|
+
rawOutput += ` | Quality: ${result.quality_report.summary.total_issues} issue(s) found`;
|
|
738
|
+
}
|
|
739
|
+
if (result.cleanup_plan_generated) {
|
|
740
|
+
rawOutput += ` | Cleanup plan generated: ${result.cleanup_plan_generated.path}`;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
output(result, raw, rawOutput);
|
|
745
|
+
}
|
|
746
|
+
// ─── Milestone Complete ───────────────────────────────────────────────────────
|
|
747
|
+
/**
|
|
748
|
+
* Archive phase directories and supporting files (.planning/ROADMAP.md,
|
|
749
|
+
* REQUIREMENTS.md, audit) for a completed milestone.
|
|
750
|
+
* @param cwd - Project working directory
|
|
751
|
+
* @param _version - Milestone version string (unused but kept for API consistency)
|
|
752
|
+
* @param _sourceDir - Directory containing the phase subdirectories (unused but kept for API consistency)
|
|
753
|
+
* @param archiveDir - Destination directory for archived files
|
|
754
|
+
* @param ctx - Additional context for archival
|
|
755
|
+
*/
|
|
756
|
+
function _archiveMilestone(cwd, version, _sourceDir, archiveDir, ctx) {
|
|
757
|
+
const { roadmapPath, reqPath, milestoneName, today, phasesDir, phaseCount, totalPlans, totalTasks, accomplishments, phasesAlreadyInPlace, } = ctx;
|
|
758
|
+
// Archive phase directories to .planning/milestones/{version}-phases/
|
|
759
|
+
const phasesArchiveDir = getArchivedPhasesDir(cwd, version);
|
|
760
|
+
let archivedPhaseCount = 0;
|
|
761
|
+
if (phasesAlreadyInPlace) {
|
|
762
|
+
// Phases already live under milestones/{version}/phases/ -- skip redundant copy
|
|
763
|
+
archivedPhaseCount = phaseCount;
|
|
764
|
+
}
|
|
765
|
+
else {
|
|
766
|
+
// Old-style layout -- copy phases to archive, then delete originals
|
|
767
|
+
try {
|
|
768
|
+
const phaseEntries = fs.readdirSync(phasesDir, {
|
|
769
|
+
withFileTypes: true,
|
|
770
|
+
});
|
|
771
|
+
const phaseDirs = phaseEntries
|
|
772
|
+
.filter((e) => e.isDirectory())
|
|
773
|
+
.map((e) => e.name);
|
|
774
|
+
if (phaseDirs.length > 0) {
|
|
775
|
+
fs.mkdirSync(phasesArchiveDir, { recursive: true });
|
|
776
|
+
for (let _pi = 0; _pi < phaseDirs.length; _pi++) {
|
|
777
|
+
const dir = phaseDirs[_pi];
|
|
778
|
+
process.stderr.write(` Archiving phase ${_pi + 1}/${phaseDirs.length}: ${dir}\n`);
|
|
779
|
+
fs.cpSync(path.join(phasesDir, dir), path.join(phasesArchiveDir, dir), {
|
|
780
|
+
recursive: true,
|
|
781
|
+
});
|
|
782
|
+
fs.rmSync(path.join(phasesDir, dir), {
|
|
783
|
+
recursive: true,
|
|
784
|
+
force: true,
|
|
785
|
+
});
|
|
786
|
+
archivedPhaseCount++;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
catch {
|
|
791
|
+
// Phase archival is non-blocking
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
// Archive ROADMAP.md
|
|
795
|
+
if (fs.existsSync(roadmapPath)) {
|
|
796
|
+
const roadmapContent = readRoadmapFile(roadmapPath);
|
|
797
|
+
fs.writeFileSync(path.join(archiveDir, `${version}-ROADMAP.md`), roadmapContent, 'utf-8');
|
|
798
|
+
}
|
|
799
|
+
// Archive REQUIREMENTS.md
|
|
800
|
+
if (fs.existsSync(reqPath)) {
|
|
801
|
+
const reqContent = fs.readFileSync(reqPath, 'utf-8');
|
|
802
|
+
const archiveHeader = `# Requirements Archive: ${version} ${milestoneName}\n\n**Archived:** ${today}\n**Status:** SHIPPED\n\nFor current requirements, see \`.planning/REQUIREMENTS.md\`.\n\n---\n\n`;
|
|
803
|
+
fs.writeFileSync(path.join(archiveDir, `${version}-REQUIREMENTS.md`), archiveHeader + reqContent, 'utf-8');
|
|
804
|
+
}
|
|
805
|
+
// Archive audit file if exists
|
|
806
|
+
const auditFile = path.join(cwd, '.planning', `${version}-MILESTONE-AUDIT.md`);
|
|
807
|
+
if (fs.existsSync(auditFile)) {
|
|
808
|
+
fs.renameSync(auditFile, path.join(archiveDir, `${version}-MILESTONE-AUDIT.md`));
|
|
809
|
+
}
|
|
810
|
+
// Write archived.json metadata marker (REQ-60)
|
|
811
|
+
const milestoneVersionDir = path.join(cwd, '.planning', 'milestones', version);
|
|
812
|
+
fs.mkdirSync(milestoneVersionDir, { recursive: true });
|
|
813
|
+
const markerPath = path.join(milestoneVersionDir, 'archived.json');
|
|
814
|
+
const marker = {
|
|
815
|
+
version,
|
|
816
|
+
name: milestoneName,
|
|
817
|
+
archived_date: today,
|
|
818
|
+
phases: phaseCount,
|
|
819
|
+
plans: totalPlans,
|
|
820
|
+
tasks: totalTasks,
|
|
821
|
+
accomplishments,
|
|
822
|
+
};
|
|
823
|
+
fs.writeFileSync(markerPath, JSON.stringify(marker, null, 2) + '\n', 'utf-8');
|
|
824
|
+
return { archivedPhaseCount };
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Patch STATE.md after a milestone is marked complete.
|
|
828
|
+
* Updates Status, Last Activity, and Last Activity Description fields.
|
|
829
|
+
* @param cwd - Project working directory
|
|
830
|
+
* @param version - Milestone version string (e.g. 'v1.0')
|
|
831
|
+
* @param today - ISO date string for today (YYYY-MM-DD)
|
|
832
|
+
*/
|
|
833
|
+
function _updateStateAfterComplete(cwd, version, today) {
|
|
834
|
+
const statePath = path.join(cwd, '.planning', 'STATE.md');
|
|
835
|
+
if (!fs.existsSync(statePath))
|
|
836
|
+
return false;
|
|
837
|
+
let stateContent = readStateFile(statePath);
|
|
838
|
+
stateContent = stateContent.replace(/(\*\*Status:\*\*\s*).*/, `$1${version} milestone complete`);
|
|
839
|
+
stateContent = stateContent.replace(/(\*\*Last Activity:\*\*\s*).*/, `$1${today}`);
|
|
840
|
+
stateContent = stateContent.replace(/(\*\*Last Activity Description:\*\*\s*).*/, `$1${version} milestone completed and archived`);
|
|
841
|
+
writeStateFile(statePath, stateContent);
|
|
842
|
+
return true;
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Rewrite MILESTONES.md after a milestone is complete, appending the new entry.
|
|
846
|
+
* @param milestonesPath - Absolute path to MILESTONES.md
|
|
847
|
+
* @param version - Milestone version string (e.g. 'v1.0')
|
|
848
|
+
* @param milestoneName - Display name for the milestone
|
|
849
|
+
* @param today - ISO date string for today (YYYY-MM-DD)
|
|
850
|
+
* @param phaseCount - Number of phases completed
|
|
851
|
+
* @param totalPlans - Total plans executed
|
|
852
|
+
* @param totalTasks - Total tasks executed
|
|
853
|
+
* @param accomplishments - List of one-liner accomplishment strings
|
|
854
|
+
*/
|
|
855
|
+
function _rewriteRoadmapAfterComplete(milestonesPath, version, milestoneName, today, phaseCount, totalPlans, totalTasks, accomplishments) {
|
|
856
|
+
const accomplishmentsList = accomplishments.map((a) => `- ${a}`).join('\n');
|
|
857
|
+
const milestoneEntry = `## ${version} ${milestoneName} (Shipped: ${today})\n\n**Phases completed:** ${phaseCount} phases, ${totalPlans} plans, ${totalTasks} tasks\n\n**Key accomplishments:**\n${accomplishmentsList || '- (none recorded)'}\n\n---\n\n`;
|
|
858
|
+
if (fs.existsSync(milestonesPath)) {
|
|
859
|
+
const existing = fs.readFileSync(milestonesPath, 'utf-8');
|
|
860
|
+
fs.writeFileSync(milestonesPath, existing + '\n' + milestoneEntry, 'utf-8');
|
|
861
|
+
}
|
|
862
|
+
else {
|
|
863
|
+
fs.writeFileSync(milestonesPath, `# Milestones\n\n${milestoneEntry}`, 'utf-8');
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* CLI command: Archive a completed milestone, gather stats, and update MILESTONES.md and STATE.md.
|
|
868
|
+
* @param cwd - Project working directory
|
|
869
|
+
* @param version - Milestone version to complete (e.g., 'v1.0')
|
|
870
|
+
* @param options - Milestone options
|
|
871
|
+
* @param raw - Output raw text instead of JSON
|
|
872
|
+
* @returns void — writes JSON or raw text to stdout and exits on error
|
|
873
|
+
*/
|
|
874
|
+
function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
875
|
+
if (!version) {
|
|
876
|
+
error('version required for milestone complete (e.g., v1.0). Usage: milestone complete <version>. Provide the milestone version, e.g.: milestone complete v1.2.0. Check .planning/ROADMAP.md for the current milestone version.');
|
|
877
|
+
}
|
|
878
|
+
const dryRun = (options && options.dryRun) || false;
|
|
879
|
+
// Dry-run: return preview without modifying anything
|
|
880
|
+
if (dryRun) {
|
|
881
|
+
const result = {
|
|
882
|
+
dry_run: true,
|
|
883
|
+
would_archive_version: version,
|
|
884
|
+
};
|
|
885
|
+
output(result, raw, `dry-run: would archive milestone ${version}`);
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
// Pre-flight gate checks
|
|
889
|
+
const gates = runPreflightGates(cwd, 'milestone-complete');
|
|
890
|
+
if (!gates.passed) {
|
|
891
|
+
output({
|
|
892
|
+
gate_failed: true,
|
|
893
|
+
gate_errors: gates.errors,
|
|
894
|
+
gate_warnings: gates.warnings,
|
|
895
|
+
}, raw);
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
const roadmapPath = path.join(cwd, '.planning', 'ROADMAP.md');
|
|
899
|
+
const reqPath = path.join(cwd, '.planning', 'REQUIREMENTS.md');
|
|
900
|
+
const statePath = path.join(cwd, '.planning', 'STATE.md');
|
|
901
|
+
const milestonesPath = path.join(cwd, '.planning', 'MILESTONES.md');
|
|
902
|
+
const archiveDir = getMilestonesDirPath(cwd);
|
|
903
|
+
const phasesDir = getPhasesDirPath(cwd);
|
|
904
|
+
const today = new Date().toISOString().split('T')[0];
|
|
905
|
+
const milestoneName = options.name || version;
|
|
906
|
+
// Ensure archive directory exists
|
|
907
|
+
fs.mkdirSync(archiveDir, { recursive: true });
|
|
908
|
+
// Check if phases are already under the milestone directory (new-style layout)
|
|
909
|
+
const milestonePhaseDir = path.join(cwd, '.planning', 'milestones', version, 'phases');
|
|
910
|
+
let phasesAlreadyInPlace = false;
|
|
911
|
+
try {
|
|
912
|
+
phasesAlreadyInPlace =
|
|
913
|
+
fs.existsSync(milestonePhaseDir) &&
|
|
914
|
+
fs.readdirSync(milestonePhaseDir, {
|
|
915
|
+
withFileTypes: true,
|
|
916
|
+
}).some((e) => e.isDirectory());
|
|
917
|
+
}
|
|
918
|
+
catch {
|
|
919
|
+
// Milestone phases directory may not exist; assume old-style layout
|
|
920
|
+
}
|
|
921
|
+
// Determine the source directory for stat gathering
|
|
922
|
+
const statsSourceDir = phasesAlreadyInPlace ? milestonePhaseDir : phasesDir;
|
|
923
|
+
// Gather stats from phases
|
|
924
|
+
let phaseCount = 0;
|
|
925
|
+
let totalPlans = 0;
|
|
926
|
+
let totalTasks = 0;
|
|
927
|
+
const accomplishments = [];
|
|
928
|
+
try {
|
|
929
|
+
const entries = fs.readdirSync(statsSourceDir, {
|
|
930
|
+
withFileTypes: true,
|
|
931
|
+
});
|
|
932
|
+
const dirs = entries
|
|
933
|
+
.filter((e) => e.isDirectory())
|
|
934
|
+
.map((e) => e.name)
|
|
935
|
+
.sort();
|
|
936
|
+
for (const dir of dirs) {
|
|
937
|
+
phaseCount++;
|
|
938
|
+
const phaseFiles = fs.readdirSync(path.join(statsSourceDir, dir));
|
|
939
|
+
const plans = phaseFiles.filter((f) => f.endsWith('-PLAN.md') || f === 'PLAN.md');
|
|
940
|
+
const summaries = phaseFiles.filter((f) => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
|
|
941
|
+
totalPlans += plans.length;
|
|
942
|
+
// Extract one-liners from summaries
|
|
943
|
+
for (let _si = 0; _si < summaries.length; _si++) {
|
|
944
|
+
const s = summaries[_si];
|
|
945
|
+
process.stderr.write(` Reading summary ${_si + 1}/${summaries.length}: ${s}\n`);
|
|
946
|
+
try {
|
|
947
|
+
const content = fs.readFileSync(path.join(statsSourceDir, dir, s), 'utf-8');
|
|
948
|
+
const fm = extractFrontmatter(content);
|
|
949
|
+
if (fm['one-liner']) {
|
|
950
|
+
accomplishments.push(fm['one-liner']);
|
|
951
|
+
}
|
|
952
|
+
// Count tasks
|
|
953
|
+
const taskMatches = content.match(/##\s*Task\s*\d+/gi);
|
|
954
|
+
totalTasks += taskMatches ? taskMatches.length : 0;
|
|
955
|
+
}
|
|
956
|
+
catch {
|
|
957
|
+
// Summary file unreadable; skip one-liner and task count for this file
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
catch {
|
|
963
|
+
// Stats source directory may not exist; use zero stats
|
|
964
|
+
}
|
|
965
|
+
// Archive phases, documents, and write metadata marker
|
|
966
|
+
const archiveCtx = {
|
|
967
|
+
roadmapPath,
|
|
968
|
+
reqPath,
|
|
969
|
+
milestoneName,
|
|
970
|
+
today,
|
|
971
|
+
phasesDir,
|
|
972
|
+
phaseCount,
|
|
973
|
+
totalPlans,
|
|
974
|
+
totalTasks,
|
|
975
|
+
accomplishments,
|
|
976
|
+
phasesAlreadyInPlace,
|
|
977
|
+
};
|
|
978
|
+
const { archivedPhaseCount } = _archiveMilestone(cwd, version, statsSourceDir, archiveDir, archiveCtx);
|
|
979
|
+
// Append entry to MILESTONES.md
|
|
980
|
+
_rewriteRoadmapAfterComplete(milestonesPath, version, milestoneName, today, phaseCount, totalPlans, totalTasks, accomplishments);
|
|
981
|
+
// Update STATE.md
|
|
982
|
+
_updateStateAfterComplete(cwd, version, today);
|
|
983
|
+
// Merge milestone branch into base branch (if branching strategy is active)
|
|
984
|
+
let gitMerge = null;
|
|
985
|
+
try {
|
|
986
|
+
const config = loadConfig(cwd);
|
|
987
|
+
if (config.branching_strategy && config.branching_strategy !== 'none') {
|
|
988
|
+
const template = config.milestone_branch_template || 'grd/{milestone}-{slug}';
|
|
989
|
+
let msName = milestoneName;
|
|
990
|
+
try {
|
|
991
|
+
const msInfo = getMilestoneInfoUtil(cwd);
|
|
992
|
+
msName = msInfo.name || milestoneName;
|
|
993
|
+
}
|
|
994
|
+
catch {
|
|
995
|
+
// Use milestoneName from options
|
|
996
|
+
}
|
|
997
|
+
const msSlug = generateSlugInternal(msName) || 'milestone';
|
|
998
|
+
const msBranch = template.replace('{milestone}', version).replace('{slug}', msSlug);
|
|
999
|
+
const baseBranch = config.base_branch || 'main';
|
|
1000
|
+
// Check if milestone branch exists
|
|
1001
|
+
const msCheck = execGit(cwd, ['rev-parse', '--verify', msBranch]);
|
|
1002
|
+
if (msCheck.exitCode !== 0) {
|
|
1003
|
+
gitMerge = {
|
|
1004
|
+
skipped: true,
|
|
1005
|
+
reason: `Milestone branch '${msBranch}' not found`,
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
else {
|
|
1009
|
+
// Record current branch
|
|
1010
|
+
const headResult = execGit(cwd, ['rev-parse', '--abbrev-ref', 'HEAD']);
|
|
1011
|
+
const originalBranch = headResult.exitCode === 0 ? headResult.stdout.trim() : baseBranch;
|
|
1012
|
+
// Checkout base branch
|
|
1013
|
+
const coResult = execGit(cwd, ['checkout', baseBranch]);
|
|
1014
|
+
if (coResult.exitCode !== 0) {
|
|
1015
|
+
gitMerge = {
|
|
1016
|
+
skipped: true,
|
|
1017
|
+
reason: `Failed to checkout '${baseBranch}'`,
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
else {
|
|
1021
|
+
// Merge milestone branch
|
|
1022
|
+
const mergeResult = execGit(cwd, [
|
|
1023
|
+
'merge',
|
|
1024
|
+
'--no-ff',
|
|
1025
|
+
msBranch,
|
|
1026
|
+
'-m',
|
|
1027
|
+
`Merge milestone ${version}: ${milestoneName}`,
|
|
1028
|
+
]);
|
|
1029
|
+
if (mergeResult.exitCode !== 0) {
|
|
1030
|
+
// Conflict -- abort and restore
|
|
1031
|
+
execGit(cwd, ['merge', '--abort']);
|
|
1032
|
+
execGit(cwd, ['checkout', originalBranch]);
|
|
1033
|
+
gitMerge = {
|
|
1034
|
+
error: 'Merge conflict',
|
|
1035
|
+
milestone_branch: msBranch,
|
|
1036
|
+
base_branch: baseBranch,
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
else {
|
|
1040
|
+
// Delete milestone branch after successful merge
|
|
1041
|
+
execGit(cwd, ['branch', '-d', msBranch]);
|
|
1042
|
+
// Restore original branch (stay on base after milestone merge)
|
|
1043
|
+
if (originalBranch !== baseBranch) {
|
|
1044
|
+
execGit(cwd, ['checkout', originalBranch]);
|
|
1045
|
+
}
|
|
1046
|
+
gitMerge = {
|
|
1047
|
+
merged: true,
|
|
1048
|
+
milestone_branch: msBranch,
|
|
1049
|
+
base_branch: baseBranch,
|
|
1050
|
+
branch_deleted: true,
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
catch {
|
|
1058
|
+
// Git merge is non-blocking
|
|
1059
|
+
}
|
|
1060
|
+
const result = {
|
|
1061
|
+
version,
|
|
1062
|
+
name: milestoneName,
|
|
1063
|
+
date: today,
|
|
1064
|
+
phases: phaseCount,
|
|
1065
|
+
plans: totalPlans,
|
|
1066
|
+
tasks: totalTasks,
|
|
1067
|
+
accomplishments,
|
|
1068
|
+
phases_already_in_place: phasesAlreadyInPlace,
|
|
1069
|
+
archived: {
|
|
1070
|
+
roadmap: fs.existsSync(path.join(archiveDir, `${version}-ROADMAP.md`)),
|
|
1071
|
+
requirements: fs.existsSync(path.join(archiveDir, `${version}-REQUIREMENTS.md`)),
|
|
1072
|
+
audit: fs.existsSync(path.join(archiveDir, `${version}-MILESTONE-AUDIT.md`)),
|
|
1073
|
+
phases: archivedPhaseCount > 0,
|
|
1074
|
+
phase_count: archivedPhaseCount,
|
|
1075
|
+
marker: true,
|
|
1076
|
+
},
|
|
1077
|
+
milestones_updated: true,
|
|
1078
|
+
state_updated: fs.existsSync(statePath),
|
|
1079
|
+
...(gitMerge ? { git_merge: gitMerge } : {}),
|
|
1080
|
+
};
|
|
1081
|
+
output(result, raw, `Milestone ${result.version} complete: ${result.phases} phases, ${result.plans} plans`);
|
|
1082
|
+
}
|
|
1083
|
+
// ─── Validate Consistency ─────────────────────────────────────────────────────
|
|
1084
|
+
/**
|
|
1085
|
+
* CLI command: Validate phase numbering consistency between ROADMAP.md and disk directories.
|
|
1086
|
+
* @param cwd - Project working directory
|
|
1087
|
+
* @param raw - Output raw 'passed'/'failed' instead of JSON
|
|
1088
|
+
* @param options - Validation options (e.g., fix)
|
|
1089
|
+
* @returns void — writes JSON or raw text to stdout and exits on error
|
|
1090
|
+
*/
|
|
1091
|
+
function cmdValidateConsistency(cwd, raw, options) {
|
|
1092
|
+
const fix = (options && options.fix) || false;
|
|
1093
|
+
const roadmapPath = path.join(cwd, '.planning', 'ROADMAP.md');
|
|
1094
|
+
const phasesDir = getPhasesDirPath(cwd);
|
|
1095
|
+
const errors_list = [];
|
|
1096
|
+
const warnings = [];
|
|
1097
|
+
const fixed = [];
|
|
1098
|
+
// Check for ROADMAP
|
|
1099
|
+
let roadmapContent;
|
|
1100
|
+
try {
|
|
1101
|
+
roadmapContent = readRoadmapFile(roadmapPath);
|
|
1102
|
+
}
|
|
1103
|
+
catch {
|
|
1104
|
+
errors_list.push('ROADMAP.md not found');
|
|
1105
|
+
output({ passed: false, errors: errors_list, warnings }, raw, 'failed');
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
const activeContent = stripShippedSections(roadmapContent);
|
|
1109
|
+
// Extract phases from ROADMAP (active section only)
|
|
1110
|
+
const roadmapPhases = new Set();
|
|
1111
|
+
const phasePattern = /#{2,3}\s*Phase\s+(\d+(?:\.\d+)?)\s*:/gi;
|
|
1112
|
+
let m;
|
|
1113
|
+
while ((m = phasePattern.exec(activeContent)) !== null) {
|
|
1114
|
+
roadmapPhases.add(m[1]);
|
|
1115
|
+
}
|
|
1116
|
+
// Get phases on disk
|
|
1117
|
+
const diskPhases = new Set();
|
|
1118
|
+
try {
|
|
1119
|
+
const entries = fs.readdirSync(phasesDir, {
|
|
1120
|
+
withFileTypes: true,
|
|
1121
|
+
});
|
|
1122
|
+
const dirs = entries
|
|
1123
|
+
.filter((e) => e.isDirectory())
|
|
1124
|
+
.map((e) => e.name);
|
|
1125
|
+
for (const dir of dirs) {
|
|
1126
|
+
const dm = dir.match(/^(\d+(?:\.\d+)?)/);
|
|
1127
|
+
if (dm)
|
|
1128
|
+
diskPhases.add(dm[1]);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
catch {
|
|
1132
|
+
// Phases directory may not exist; diskPhases stays empty
|
|
1133
|
+
}
|
|
1134
|
+
// Check: phases in ROADMAP but not on disk
|
|
1135
|
+
for (const p of roadmapPhases) {
|
|
1136
|
+
if (!diskPhases.has(p) && !diskPhases.has(normalizePhaseName(p))) {
|
|
1137
|
+
warnings.push(`Phase ${p} in ROADMAP.md but no directory on disk`);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
// Check: orphaned phases on disk but not in ROADMAP (errors, not warnings)
|
|
1141
|
+
const orphanViolations = checkOrphanedPhases(cwd);
|
|
1142
|
+
for (const v of orphanViolations) {
|
|
1143
|
+
errors_list.push(v.message);
|
|
1144
|
+
}
|
|
1145
|
+
// Check: sequential phase numbers (integers only)
|
|
1146
|
+
const integerPhases = [...diskPhases]
|
|
1147
|
+
.filter((p) => !p.includes('.'))
|
|
1148
|
+
.map((p) => parseInt(p, 10))
|
|
1149
|
+
.sort((a, b) => a - b);
|
|
1150
|
+
for (let i = 1; i < integerPhases.length; i++) {
|
|
1151
|
+
if (integerPhases[i] !== integerPhases[i - 1] + 1) {
|
|
1152
|
+
warnings.push(`Gap in phase numbering: ${integerPhases[i - 1]} \u2192 ${integerPhases[i]}`);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
// Check: plan numbering within phases
|
|
1156
|
+
try {
|
|
1157
|
+
const entries = fs.readdirSync(phasesDir, {
|
|
1158
|
+
withFileTypes: true,
|
|
1159
|
+
});
|
|
1160
|
+
const dirs = entries
|
|
1161
|
+
.filter((e) => e.isDirectory())
|
|
1162
|
+
.map((e) => e.name)
|
|
1163
|
+
.sort();
|
|
1164
|
+
for (const dir of dirs) {
|
|
1165
|
+
const phaseFiles = fs.readdirSync(path.join(phasesDir, dir));
|
|
1166
|
+
const plans = phaseFiles.filter((f) => f.endsWith('-PLAN.md')).sort();
|
|
1167
|
+
// Extract plan numbers
|
|
1168
|
+
const planNums = plans
|
|
1169
|
+
.map((p) => {
|
|
1170
|
+
const pm = p.match(/-(\d{2})-PLAN\.md$/);
|
|
1171
|
+
return pm ? parseInt(pm[1], 10) : null;
|
|
1172
|
+
})
|
|
1173
|
+
.filter((n) => n !== null);
|
|
1174
|
+
for (let i = 1; i < planNums.length; i++) {
|
|
1175
|
+
if (planNums[i] !== planNums[i - 1] + 1) {
|
|
1176
|
+
warnings.push(`Gap in plan numbering in ${dir}: plan ${planNums[i - 1]} \u2192 ${planNums[i]}`);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
// Check: plans without summaries (completed plans)
|
|
1180
|
+
const summaries = phaseFiles.filter((f) => f.endsWith('-SUMMARY.md'));
|
|
1181
|
+
const planIds = new Set(plans.map((p) => p.replace('-PLAN.md', '')));
|
|
1182
|
+
const summaryIds = new Set(summaries.map((s) => s.replace('-SUMMARY.md', '')));
|
|
1183
|
+
// Summary without matching plan is suspicious (orphaned)
|
|
1184
|
+
for (const sid of summaryIds) {
|
|
1185
|
+
if (!planIds.has(sid)) {
|
|
1186
|
+
const orphanFile = `${sid}-SUMMARY.md`;
|
|
1187
|
+
const orphanPath = path.join(phasesDir, dir, orphanFile);
|
|
1188
|
+
if (fix) {
|
|
1189
|
+
try {
|
|
1190
|
+
fs.unlinkSync(orphanPath);
|
|
1191
|
+
fixed.push(orphanPath);
|
|
1192
|
+
}
|
|
1193
|
+
catch {
|
|
1194
|
+
warnings.push(`Failed to remove orphaned summary: ${orphanFile} in ${dir}`);
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
else {
|
|
1198
|
+
warnings.push(`Orphaned summary ${orphanFile} in ${dir} has no matching PLAN.md`);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
catch {
|
|
1205
|
+
// Phases directory may not exist; skip plan-numbering and orphan checks
|
|
1206
|
+
}
|
|
1207
|
+
// Check: frontmatter in plans has required fields
|
|
1208
|
+
try {
|
|
1209
|
+
const entries = fs.readdirSync(phasesDir, {
|
|
1210
|
+
withFileTypes: true,
|
|
1211
|
+
});
|
|
1212
|
+
const dirs = entries
|
|
1213
|
+
.filter((e) => e.isDirectory())
|
|
1214
|
+
.map((e) => e.name);
|
|
1215
|
+
for (const dir of dirs) {
|
|
1216
|
+
const phaseFiles = fs.readdirSync(path.join(phasesDir, dir));
|
|
1217
|
+
const plans = phaseFiles.filter((f) => f.endsWith('-PLAN.md'));
|
|
1218
|
+
for (const plan of plans) {
|
|
1219
|
+
const content = fs.readFileSync(path.join(phasesDir, dir, plan), 'utf-8');
|
|
1220
|
+
const fm = extractFrontmatter(content);
|
|
1221
|
+
if (!fm.wave) {
|
|
1222
|
+
warnings.push(`${dir}/${plan}: missing 'wave' in frontmatter`);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
catch {
|
|
1228
|
+
// Phases directory may not exist; skip frontmatter validation
|
|
1229
|
+
}
|
|
1230
|
+
const passed = errors_list.length === 0;
|
|
1231
|
+
output({
|
|
1232
|
+
passed,
|
|
1233
|
+
errors: errors_list,
|
|
1234
|
+
warnings,
|
|
1235
|
+
warning_count: warnings.length,
|
|
1236
|
+
...(fix ? { fixed } : {}),
|
|
1237
|
+
}, raw, passed ? 'passed' : 'failed');
|
|
1238
|
+
}
|
|
1239
|
+
// ─── Version Bump ─────────────────────────────────────────────────────────────
|
|
1240
|
+
/**
|
|
1241
|
+
* CLI command: Bump version in plugin.json, VERSION, and package.json.
|
|
1242
|
+
* @param cwd - Project working directory
|
|
1243
|
+
* @param version - Version string (with or without 'v' prefix)
|
|
1244
|
+
* @param raw - Output raw text instead of JSON
|
|
1245
|
+
* @returns void — writes JSON or raw text to stdout and exits on error
|
|
1246
|
+
*/
|
|
1247
|
+
function cmdVersionBump(cwd, version, raw) {
|
|
1248
|
+
if (!version) {
|
|
1249
|
+
error('version required for version bump (e.g., v1.0.0). Usage: milestone version-bump <version>. Provide the new version, e.g.: milestone version-bump v1.3.0. Current version can be found in .planning/config.json. To check current version: cat .planning/config.json | grep version');
|
|
1250
|
+
}
|
|
1251
|
+
// Strip leading 'v' prefix
|
|
1252
|
+
const semver = version.replace(/^v/, '');
|
|
1253
|
+
const files = {
|
|
1254
|
+
VERSION: path.join(cwd, 'VERSION'),
|
|
1255
|
+
'package.json': path.join(cwd, 'package.json'),
|
|
1256
|
+
'.claude-plugin/plugin.json': path.join(cwd, '.claude-plugin', 'plugin.json'),
|
|
1257
|
+
};
|
|
1258
|
+
const updated = [];
|
|
1259
|
+
// Update VERSION file
|
|
1260
|
+
if (fs.existsSync(files.VERSION)) {
|
|
1261
|
+
fs.writeFileSync(files.VERSION, semver + '\n', 'utf-8');
|
|
1262
|
+
updated.push('VERSION');
|
|
1263
|
+
}
|
|
1264
|
+
// Update package.json
|
|
1265
|
+
if (fs.existsSync(files['package.json'])) {
|
|
1266
|
+
const pkg = JSON.parse(fs.readFileSync(files['package.json'], 'utf-8'));
|
|
1267
|
+
pkg.version = semver;
|
|
1268
|
+
fs.writeFileSync(files['package.json'], JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
|
|
1269
|
+
updated.push('package.json');
|
|
1270
|
+
}
|
|
1271
|
+
// Update .claude-plugin/plugin.json
|
|
1272
|
+
if (fs.existsSync(files['.claude-plugin/plugin.json'])) {
|
|
1273
|
+
const plugin = JSON.parse(fs.readFileSync(files['.claude-plugin/plugin.json'], 'utf-8'));
|
|
1274
|
+
plugin.version = semver;
|
|
1275
|
+
fs.writeFileSync(files['.claude-plugin/plugin.json'], JSON.stringify(plugin, null, 2) + '\n', 'utf-8');
|
|
1276
|
+
updated.push('.claude-plugin/plugin.json');
|
|
1277
|
+
}
|
|
1278
|
+
const result = {
|
|
1279
|
+
version: semver,
|
|
1280
|
+
files_updated: updated,
|
|
1281
|
+
count: updated.length,
|
|
1282
|
+
};
|
|
1283
|
+
output(result, raw, `Bumped ${updated.length} files to ${semver}`);
|
|
1284
|
+
}
|
|
1285
|
+
// ─── Batch Phase Complete ─────────────────────────────────────────────────────
|
|
1286
|
+
/**
|
|
1287
|
+
* CLI command: Complete multiple phases in a single call.
|
|
1288
|
+
* @param cwd - Project working directory
|
|
1289
|
+
* @param phases - Array of phase numbers to complete
|
|
1290
|
+
* @param options - Options passed to each cmdPhaseComplete call
|
|
1291
|
+
* @param raw - Output raw text instead of JSON
|
|
1292
|
+
* @returns void — writes JSON or raw text to stdout and exits on error
|
|
1293
|
+
*/
|
|
1294
|
+
function cmdPhaseBatchComplete(cwd, phases, options, raw) {
|
|
1295
|
+
if (!phases || phases.length === 0) {
|
|
1296
|
+
output({ error: 'phases list is required and must not be empty' }, raw, 'error');
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
const results = [];
|
|
1300
|
+
let completedCount = 0;
|
|
1301
|
+
for (const phase of phases) {
|
|
1302
|
+
try {
|
|
1303
|
+
const phaseResult = _phaseCompleteCore(cwd, phase, options);
|
|
1304
|
+
completedCount++;
|
|
1305
|
+
results.push({ phase, result: phaseResult });
|
|
1306
|
+
}
|
|
1307
|
+
catch (e) {
|
|
1308
|
+
results.push({ phase, error: e.message });
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
output({
|
|
1312
|
+
results,
|
|
1313
|
+
total_phases: phases.length,
|
|
1314
|
+
completed_count: completedCount,
|
|
1315
|
+
}, raw, `Completed ${completedCount}/${phases.length} phases`);
|
|
1316
|
+
}
|
|
1317
|
+
// ─── Atomic Write ─────────────────────────────────────────────────────────────
|
|
1318
|
+
/**
|
|
1319
|
+
* Write content to a file atomically using a .tmp intermediate file.
|
|
1320
|
+
* On success, the .tmp file is renamed to the target path.
|
|
1321
|
+
* On failure, the original file is left untouched.
|
|
1322
|
+
* @param filePath - Absolute path to the target file
|
|
1323
|
+
* @param content - Content to write
|
|
1324
|
+
* @returns void — throws on write or rename failure
|
|
1325
|
+
*/
|
|
1326
|
+
function atomicWriteFile(filePath, content) {
|
|
1327
|
+
const tmpPath = filePath + '.tmp';
|
|
1328
|
+
fs.writeFileSync(tmpPath, content, 'utf-8');
|
|
1329
|
+
fs.renameSync(tmpPath, filePath);
|
|
1330
|
+
}
|
|
1331
|
+
// ─── Exports ──────────────────────────────────────────────────────────────────
|
|
1332
|
+
module.exports = {
|
|
1333
|
+
cmdPhasesList,
|
|
1334
|
+
cmdPhaseAdd,
|
|
1335
|
+
cmdPhaseInsert,
|
|
1336
|
+
cmdPhaseRemove,
|
|
1337
|
+
cmdPhaseComplete,
|
|
1338
|
+
cmdMilestoneComplete,
|
|
1339
|
+
cmdValidateConsistency,
|
|
1340
|
+
cmdVersionBump,
|
|
1341
|
+
cmdPhaseBatchComplete,
|
|
1342
|
+
atomicWriteFile,
|
|
1343
|
+
};
|
|
1344
|
+
//# sourceMappingURL=phase.js.map
|