@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,1418 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/** GRD Commands/Analysis -- Project analysis: risk assessment, citation tracking,
|
|
4
|
+
* eval regression, time budget, config diff, readiness, health score,
|
|
5
|
+
* decision timeline, knowledge import, todo duplicates */
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { safeReadFile, safeReadJSON, output, error, } = require('../utils');
|
|
9
|
+
const { planningDir: getPlanningDir, phasesDir: getPhasesDirPath, researchDir: getResearchDir, todosDir: getTodosDir, currentMilestone, } = require('../paths');
|
|
10
|
+
// ─── Internal Helpers ─────────────────────────────────────────────────────────
|
|
11
|
+
/** Recursively collect all .md files under a directory. */
|
|
12
|
+
function _collectMarkdownFiles(dir) {
|
|
13
|
+
const results = [];
|
|
14
|
+
try {
|
|
15
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
16
|
+
for (const entry of entries) {
|
|
17
|
+
const fullPath = path.join(dir, entry.name);
|
|
18
|
+
if (entry.isDirectory()) {
|
|
19
|
+
results.push(..._collectMarkdownFiles(fullPath));
|
|
20
|
+
}
|
|
21
|
+
else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
22
|
+
results.push(fullPath);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
/* Directory doesn't exist */
|
|
28
|
+
}
|
|
29
|
+
return results;
|
|
30
|
+
}
|
|
31
|
+
/** Find all phase directories for the current milestone. */
|
|
32
|
+
function _listPhaseDirs(cwd) {
|
|
33
|
+
const phasesPath = getPhasesDirPath(cwd);
|
|
34
|
+
try {
|
|
35
|
+
return fs
|
|
36
|
+
.readdirSync(phasesPath, { withFileTypes: true })
|
|
37
|
+
.filter((e) => e.isDirectory())
|
|
38
|
+
.map((e) => path.join(phasesPath, e.name));
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Collect all PLAN.md files in a specific phase directory. Codex r7 P2:
|
|
46
|
+
* include bare `PLAN.md` alongside the prefixed `*-PLAN.md` form so
|
|
47
|
+
* single-plan phases are not silently skipped by callers like
|
|
48
|
+
* `gd estimate-phase`.
|
|
49
|
+
*/
|
|
50
|
+
function _collectPlanFiles(phasePath) {
|
|
51
|
+
try {
|
|
52
|
+
return fs
|
|
53
|
+
.readdirSync(phasePath)
|
|
54
|
+
.filter((f) => f === 'PLAN.md' || f.endsWith('-PLAN.md'))
|
|
55
|
+
.map((f) => path.join(phasePath, f));
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** Find a phase directory by phase number prefix (e.g., "01" or "1"). */
|
|
62
|
+
function _findPhaseDirByNumber(cwd, phaseNum) {
|
|
63
|
+
const phasesPath = getPhasesDirPath(cwd);
|
|
64
|
+
const normalized = phaseNum.padStart(2, '0');
|
|
65
|
+
try {
|
|
66
|
+
const entries = fs.readdirSync(phasesPath, {
|
|
67
|
+
withFileTypes: true,
|
|
68
|
+
});
|
|
69
|
+
const match = entries.find((e) => e.isDirectory() &&
|
|
70
|
+
(e.name.startsWith(normalized + '-') || e.name.startsWith(phaseNum + '-')));
|
|
71
|
+
return match ? path.join(phasesPath, match.name) : null;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** Compute Jaccard similarity between two sets of words. */
|
|
78
|
+
function _jaccardSimilarity(a, b) {
|
|
79
|
+
const wordsA = new Set(a
|
|
80
|
+
.toLowerCase()
|
|
81
|
+
.split(/\W+/)
|
|
82
|
+
.filter((w) => w.length > 2));
|
|
83
|
+
const wordsB = new Set(b
|
|
84
|
+
.toLowerCase()
|
|
85
|
+
.split(/\W+/)
|
|
86
|
+
.filter((w) => w.length > 2));
|
|
87
|
+
if (wordsA.size === 0 || wordsB.size === 0)
|
|
88
|
+
return 0;
|
|
89
|
+
let intersection = 0;
|
|
90
|
+
for (const w of wordsA) {
|
|
91
|
+
if (wordsB.has(w))
|
|
92
|
+
intersection++;
|
|
93
|
+
}
|
|
94
|
+
const union = wordsA.size + wordsB.size - intersection;
|
|
95
|
+
return union === 0 ? 0 : intersection / union;
|
|
96
|
+
}
|
|
97
|
+
/** Extract the first non-blank line as a title from file content. */
|
|
98
|
+
function _extractTodoTitle(content) {
|
|
99
|
+
const lines = content.split('\n');
|
|
100
|
+
for (const line of lines) {
|
|
101
|
+
const trimmed = line.replace(/^#+\s*/, '').trim();
|
|
102
|
+
if (trimmed && !trimmed.startsWith('---'))
|
|
103
|
+
return trimmed.slice(0, 80);
|
|
104
|
+
}
|
|
105
|
+
return '(untitled)';
|
|
106
|
+
}
|
|
107
|
+
/** Parse numeric metrics from EVAL.md or SUMMARY.md content using matchAll. */
|
|
108
|
+
function _parseMetricsFromContent(content) {
|
|
109
|
+
const metrics = {};
|
|
110
|
+
const metricPattern = /\b([A-Za-z][A-Za-z0-9_\-/ ]{0,30}):\s*([\d]+(?:\.[\d]+)?)\s*(?:%|dB|ms|s|min)?\b/g;
|
|
111
|
+
for (const m of content.matchAll(metricPattern)) {
|
|
112
|
+
const key = m[1].trim().toLowerCase().replace(/\s+/g, '_');
|
|
113
|
+
const val = parseFloat(m[2]);
|
|
114
|
+
if (!isNaN(val) && key.length > 1) {
|
|
115
|
+
metrics[key] = val;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return metrics;
|
|
119
|
+
}
|
|
120
|
+
/** Explain what a config key change means for agent behavior. */
|
|
121
|
+
function _explainConfigChange(key, oldVal, newVal) {
|
|
122
|
+
const explanations = {
|
|
123
|
+
autonomous_mode: 'Controls YOLO mode — disables all gates when enabled',
|
|
124
|
+
'ceremony.default_level': 'Controls which agents run (light/standard/full)',
|
|
125
|
+
research_gates: 'Human review points for research decisions',
|
|
126
|
+
'code_review.enabled': 'Toggles automatic code review after execution',
|
|
127
|
+
'execution.agent_teams': 'Enables parallel agent team execution',
|
|
128
|
+
'git.enabled': 'Enables isolated git worktree per phase',
|
|
129
|
+
'tracker.provider': 'Sets issue tracker integration (github/mcp-atlassian/none)',
|
|
130
|
+
};
|
|
131
|
+
const keyLower = key.toLowerCase();
|
|
132
|
+
for (const [pattern, explanation] of Object.entries(explanations)) {
|
|
133
|
+
if (keyLower.includes(pattern.toLowerCase())) {
|
|
134
|
+
return `${explanation} (changed from ${JSON.stringify(oldVal)} to ${JSON.stringify(newVal)})`;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return `Changed from ${JSON.stringify(oldVal)} to ${JSON.stringify(newVal)}`;
|
|
138
|
+
}
|
|
139
|
+
/** Flatten a nested object into dot-separated keys. */
|
|
140
|
+
function _flattenObject(obj, prefix = '') {
|
|
141
|
+
const result = {};
|
|
142
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
143
|
+
const key = prefix ? `${prefix}.${k}` : k;
|
|
144
|
+
if (v !== null && typeof v === 'object' && !Array.isArray(v)) {
|
|
145
|
+
Object.assign(result, _flattenObject(v, key));
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
result[key] = v;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
// ─── CLI: Phase Risk Assessment ───────────────────────────────────────────────
|
|
154
|
+
/**
|
|
155
|
+
* CLI command: Analyze PLAN.md files in a phase for risk signals.
|
|
156
|
+
* Assigns a risk score (0-10) and returns actionable remediation suggestions.
|
|
157
|
+
* @param cwd - Project root
|
|
158
|
+
* @param phase - Phase number string (e.g. "1" or "01")
|
|
159
|
+
* @param raw - Raw output flag
|
|
160
|
+
*/
|
|
161
|
+
function cmdPhaseRiskAssessment(cwd, phase, raw) {
|
|
162
|
+
if (!phase) {
|
|
163
|
+
error('Phase number required');
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const phaseDir = _findPhaseDirByNumber(cwd, phase);
|
|
167
|
+
if (!phaseDir) {
|
|
168
|
+
output({
|
|
169
|
+
error: `Phase ${phase} directory not found`,
|
|
170
|
+
phase,
|
|
171
|
+
signals: [],
|
|
172
|
+
risk_score: 0,
|
|
173
|
+
risk_level: 'low',
|
|
174
|
+
plan_count: 0,
|
|
175
|
+
plans_analyzed: [],
|
|
176
|
+
}, raw);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const planFiles = _collectPlanFiles(phaseDir);
|
|
180
|
+
const signals = [];
|
|
181
|
+
const plansAnalyzed = [];
|
|
182
|
+
for (const planFile of planFiles) {
|
|
183
|
+
const content = safeReadFile(planFile);
|
|
184
|
+
if (!content)
|
|
185
|
+
continue;
|
|
186
|
+
const filename = path.basename(planFile);
|
|
187
|
+
plansAnalyzed.push(filename);
|
|
188
|
+
const lower = content.toLowerCase();
|
|
189
|
+
// Risk: vague success criteria (no numbers in success section)
|
|
190
|
+
const successSection = content.match(/##\s+(?:success|acceptance|criteria|must[_\s]haves?)[^\n]*\n([\s\S]*?)(?=\n##|$)/i);
|
|
191
|
+
if (successSection) {
|
|
192
|
+
const hasNumbers = /\d+(?:\.\d+)?/.test(successSection[1]);
|
|
193
|
+
const hasSpecificTerms = /must|shall|exactly|at least|no more than|< |> |>=|<=/i.test(successSection[1]);
|
|
194
|
+
if (!hasNumbers && !hasSpecificTerms) {
|
|
195
|
+
signals.push({
|
|
196
|
+
category: 'success_criteria',
|
|
197
|
+
signal: `${filename}: Success criteria lack quantitative targets`,
|
|
198
|
+
severity: 'high',
|
|
199
|
+
remediation: 'Add specific numeric thresholds (e.g., "accuracy >= 85%", "latency < 200ms")',
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
signals.push({
|
|
205
|
+
category: 'success_criteria',
|
|
206
|
+
signal: `${filename}: No success criteria section found`,
|
|
207
|
+
severity: 'high',
|
|
208
|
+
remediation: 'Add a ## Success Criteria or ## Must Haves section with measurable outcomes',
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
// Risk: missing baseline reference
|
|
212
|
+
if (!lower.includes('baseline') &&
|
|
213
|
+
!lower.includes('comparison') &&
|
|
214
|
+
!lower.includes('benchmark')) {
|
|
215
|
+
signals.push({
|
|
216
|
+
category: 'baseline',
|
|
217
|
+
signal: `${filename}: No baseline or comparison reference`,
|
|
218
|
+
severity: 'medium',
|
|
219
|
+
remediation: 'Reference a baseline metric so evaluation can measure improvement (see BASELINE.md)',
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
// Risk: overly large scope
|
|
223
|
+
const taskCount = (content.match(/^[-*]\s+\[/gm) || []).length;
|
|
224
|
+
if (taskCount > 20) {
|
|
225
|
+
signals.push({
|
|
226
|
+
category: 'scope',
|
|
227
|
+
signal: `${filename}: ${taskCount} checklist items — may be too large for one plan`,
|
|
228
|
+
severity: 'medium',
|
|
229
|
+
remediation: 'Consider splitting into multiple plans or waves; aim for <15 tasks per plan',
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
// Risk: no fallback strategy
|
|
233
|
+
if (!lower.includes('fallback') &&
|
|
234
|
+
!lower.includes('alternative') &&
|
|
235
|
+
!lower.includes('if this fails') &&
|
|
236
|
+
!lower.includes('rollback')) {
|
|
237
|
+
signals.push({
|
|
238
|
+
category: 'fallback',
|
|
239
|
+
signal: `${filename}: No fallback or rollback strategy mentioned`,
|
|
240
|
+
severity: 'low',
|
|
241
|
+
remediation: 'Add a fallback plan for if the primary approach fails (e.g., simpler model, cached results)',
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
// Risk: unclear dependencies
|
|
245
|
+
if (!lower.includes('depend') &&
|
|
246
|
+
!lower.includes('requires') &&
|
|
247
|
+
!lower.includes('prerequisite') &&
|
|
248
|
+
!lower.includes('phase')) {
|
|
249
|
+
signals.push({
|
|
250
|
+
category: 'dependencies',
|
|
251
|
+
signal: `${filename}: No explicit dependencies stated`,
|
|
252
|
+
severity: 'low',
|
|
253
|
+
remediation: 'List phase/plan prerequisites explicitly so blockers are visible before execution starts',
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (planFiles.length === 0) {
|
|
258
|
+
signals.push({
|
|
259
|
+
category: 'plans',
|
|
260
|
+
signal: 'No PLAN.md files found in phase directory',
|
|
261
|
+
severity: 'high',
|
|
262
|
+
remediation: 'Run /grd:plan-phase to generate execution plans before risk assessment',
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
// Score: 0=none, 10=critical. High=3pts, medium=2pts, low=1pt
|
|
266
|
+
let rawScore = 0;
|
|
267
|
+
for (const s of signals) {
|
|
268
|
+
if (s.severity === 'high')
|
|
269
|
+
rawScore += 3;
|
|
270
|
+
else if (s.severity === 'medium')
|
|
271
|
+
rawScore += 2;
|
|
272
|
+
else
|
|
273
|
+
rawScore += 1;
|
|
274
|
+
}
|
|
275
|
+
const risk_score = Math.min(10, rawScore);
|
|
276
|
+
const risk_level = risk_score >= 8 ? 'critical' : risk_score >= 5 ? 'high' : risk_score >= 3 ? 'medium' : 'low';
|
|
277
|
+
const result = {
|
|
278
|
+
phase,
|
|
279
|
+
risk_score,
|
|
280
|
+
risk_level,
|
|
281
|
+
signals,
|
|
282
|
+
plan_count: planFiles.length,
|
|
283
|
+
plans_analyzed: plansAnalyzed,
|
|
284
|
+
};
|
|
285
|
+
output(result, raw, `Phase ${phase} risk: ${risk_level} (score=${risk_score}, signals=${signals.length})`);
|
|
286
|
+
}
|
|
287
|
+
// ─── CLI: Citation Backlink Tracker ──────────────────────────────────────────
|
|
288
|
+
/**
|
|
289
|
+
* CLI command: Build a reverse index of which planning files reference each paper
|
|
290
|
+
* from PAPERS.md. Shows which papers drove real implementation decisions.
|
|
291
|
+
* @param cwd - Project root
|
|
292
|
+
* @param raw - Raw output flag
|
|
293
|
+
*/
|
|
294
|
+
function cmdCitationBacklinks(cwd, raw) {
|
|
295
|
+
const researchPath = getResearchDir(cwd);
|
|
296
|
+
const papersPath = path.join(researchPath, 'PAPERS.md');
|
|
297
|
+
const papersContent = safeReadFile(papersPath);
|
|
298
|
+
const milestone = currentMilestone(cwd);
|
|
299
|
+
if (!papersContent) {
|
|
300
|
+
output({ error: 'PAPERS.md not found', path: papersPath, backlinks: [] }, raw);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
// Extract paper headings from PAPERS.md
|
|
304
|
+
const papers = [];
|
|
305
|
+
for (const m of papersContent.matchAll(/^##\s+(.+)$/gm)) {
|
|
306
|
+
const title = m[1].trim();
|
|
307
|
+
const id = title
|
|
308
|
+
.toLowerCase()
|
|
309
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
310
|
+
.replace(/^-|-$/g, '')
|
|
311
|
+
.slice(0, 50);
|
|
312
|
+
if (id)
|
|
313
|
+
papers.push({ id, title });
|
|
314
|
+
}
|
|
315
|
+
// Also match slug-style entries: **[slug]**
|
|
316
|
+
for (const m of papersContent.matchAll(/\*\*\[([a-z0-9-]+)\]\*\*/g)) {
|
|
317
|
+
const id = m[1].trim();
|
|
318
|
+
if (!papers.find((p) => p.id === id))
|
|
319
|
+
papers.push({ id, title: id });
|
|
320
|
+
}
|
|
321
|
+
if (papers.length === 0) {
|
|
322
|
+
output({ milestone, papers_found: 0, backlinks: [], note: 'No paper headings found in PAPERS.md' }, raw);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
// Scan all .planning/ markdown files for references
|
|
326
|
+
const planningPath = getPlanningDir(cwd);
|
|
327
|
+
const allMdFiles = _collectMarkdownFiles(planningPath);
|
|
328
|
+
const backlinks = [];
|
|
329
|
+
for (const paper of papers) {
|
|
330
|
+
const refs = [];
|
|
331
|
+
const searchTerms = [paper.id, ...paper.title.split(/\s+/).filter((w) => w.length > 4)];
|
|
332
|
+
for (const mdFile of allMdFiles) {
|
|
333
|
+
if (mdFile === papersPath)
|
|
334
|
+
continue;
|
|
335
|
+
const content = safeReadFile(mdFile);
|
|
336
|
+
if (!content)
|
|
337
|
+
continue;
|
|
338
|
+
const lines = content.split('\n');
|
|
339
|
+
for (let i = 0; i < lines.length; i++) {
|
|
340
|
+
const lineLower = lines[i].toLowerCase();
|
|
341
|
+
const matched = searchTerms.some((term) => lineLower.includes(term.toLowerCase()));
|
|
342
|
+
if (matched) {
|
|
343
|
+
refs.push({
|
|
344
|
+
file: path.relative(planningPath, mdFile),
|
|
345
|
+
line: i + 1,
|
|
346
|
+
context: lines[i].trim().slice(0, 120),
|
|
347
|
+
});
|
|
348
|
+
break; // one reference per file
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
backlinks.push({
|
|
353
|
+
paper_id: paper.id,
|
|
354
|
+
paper_title: paper.title,
|
|
355
|
+
references: refs,
|
|
356
|
+
reference_count: refs.length,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
backlinks.sort((a, b) => b.reference_count - a.reference_count);
|
|
360
|
+
const unreferenced = backlinks.filter((b) => b.reference_count === 0);
|
|
361
|
+
output({
|
|
362
|
+
milestone,
|
|
363
|
+
papers_indexed: papers.length,
|
|
364
|
+
total_references: backlinks.reduce((s, b) => s + b.reference_count, 0),
|
|
365
|
+
unreferenced_count: unreferenced.length,
|
|
366
|
+
backlinks,
|
|
367
|
+
}, raw);
|
|
368
|
+
}
|
|
369
|
+
// ─── CLI: Eval Regression Check ───────────────────────────────────────────────
|
|
370
|
+
/**
|
|
371
|
+
* CLI command: Compare eval metrics from a phase's EVAL.md against BASELINE.md,
|
|
372
|
+
* emitting warnings if metrics regressed beyond the threshold.
|
|
373
|
+
* @param cwd - Project root
|
|
374
|
+
* @param phase - Phase number string
|
|
375
|
+
* @param raw - Raw output flag
|
|
376
|
+
* @param thresholdPct - Regression threshold percentage (default 5%)
|
|
377
|
+
*/
|
|
378
|
+
function cmdEvalRegressionCheck(cwd, phase, raw, thresholdPct = 5) {
|
|
379
|
+
if (!phase) {
|
|
380
|
+
error('Phase number required');
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
const phaseDir = _findPhaseDirByNumber(cwd, phase);
|
|
384
|
+
if (!phaseDir) {
|
|
385
|
+
output({ error: `Phase ${phase} not found`, phase, has_regressions: false, regressions: [] }, raw);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
let evalFiles = [];
|
|
389
|
+
try {
|
|
390
|
+
evalFiles = fs.readdirSync(phaseDir).filter((f) => f.endsWith('-EVAL.md'));
|
|
391
|
+
}
|
|
392
|
+
catch {
|
|
393
|
+
/* ignore */
|
|
394
|
+
}
|
|
395
|
+
if (evalFiles.length === 0) {
|
|
396
|
+
output({
|
|
397
|
+
phase,
|
|
398
|
+
note: 'No EVAL.md found in phase directory',
|
|
399
|
+
has_regressions: false,
|
|
400
|
+
regressions: [],
|
|
401
|
+
}, raw);
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
const evalContent = safeReadFile(path.join(phaseDir, evalFiles[0]));
|
|
405
|
+
if (!evalContent) {
|
|
406
|
+
output({ phase, note: 'Could not read EVAL.md', has_regressions: false, regressions: [] }, raw);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
const baselinePath = path.join(getPlanningDir(cwd), 'BASELINE.md');
|
|
410
|
+
const baselineContent = safeReadFile(baselinePath);
|
|
411
|
+
const evalMetrics = _parseMetricsFromContent(evalContent);
|
|
412
|
+
const baselineMetrics = baselineContent ? _parseMetricsFromContent(baselineContent) : {};
|
|
413
|
+
const regressions = [];
|
|
414
|
+
const improvements = [];
|
|
415
|
+
const stable = [];
|
|
416
|
+
for (const [name, value] of Object.entries(evalMetrics)) {
|
|
417
|
+
const baseline = baselineMetrics[name] ?? null;
|
|
418
|
+
if (baseline === null) {
|
|
419
|
+
stable.push({ name, value, baseline: null, regression: false, delta: null });
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
const delta = value - baseline;
|
|
423
|
+
const deltaPct = baseline !== 0 ? Math.abs(delta / baseline) * 100 : 0;
|
|
424
|
+
const regressed = delta < 0 && deltaPct > thresholdPct;
|
|
425
|
+
const improved = delta > 0 && deltaPct > thresholdPct;
|
|
426
|
+
const entry = {
|
|
427
|
+
name,
|
|
428
|
+
value,
|
|
429
|
+
baseline,
|
|
430
|
+
regression: regressed,
|
|
431
|
+
delta: parseFloat(delta.toFixed(4)),
|
|
432
|
+
};
|
|
433
|
+
if (regressed)
|
|
434
|
+
regressions.push(entry);
|
|
435
|
+
else if (improved)
|
|
436
|
+
improvements.push(entry);
|
|
437
|
+
else
|
|
438
|
+
stable.push(entry);
|
|
439
|
+
}
|
|
440
|
+
const result = {
|
|
441
|
+
phase,
|
|
442
|
+
threshold_pct: thresholdPct,
|
|
443
|
+
regressions,
|
|
444
|
+
improvements,
|
|
445
|
+
stable,
|
|
446
|
+
has_regressions: regressions.length > 0,
|
|
447
|
+
};
|
|
448
|
+
output(result, raw, regressions.length > 0
|
|
449
|
+
? `WARNING: ${regressions.length} metric(s) regressed in phase ${phase}`
|
|
450
|
+
: `Phase ${phase}: No eval regressions detected`);
|
|
451
|
+
}
|
|
452
|
+
// ─── CLI: Phase Time Budget Tracking ─────────────────────────────────────────
|
|
453
|
+
/**
|
|
454
|
+
* CLI command: Compare estimated durations (from ROADMAP.md Duration fields)
|
|
455
|
+
* against actual execution time (from STATE.md metric records) for all phases.
|
|
456
|
+
* @param cwd - Project root
|
|
457
|
+
* @param raw - Raw output flag
|
|
458
|
+
*/
|
|
459
|
+
function cmdPhaseTimeBudget(cwd, raw) {
|
|
460
|
+
const roadmapPath = path.join(getPlanningDir(cwd), 'ROADMAP.md');
|
|
461
|
+
const statePath = path.join(getPlanningDir(cwd), 'STATE.md');
|
|
462
|
+
const roadmapContent = safeReadFile(roadmapPath);
|
|
463
|
+
const stateContent = safeReadFile(statePath);
|
|
464
|
+
if (!roadmapContent) {
|
|
465
|
+
output({ error: 'ROADMAP.md not found', phases: [] }, raw);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
// Scan roadmap lines for phase + duration pairs
|
|
469
|
+
const phases = [];
|
|
470
|
+
const roadmapLines = roadmapContent.split('\n');
|
|
471
|
+
let currentPhase = null;
|
|
472
|
+
let currentName = null;
|
|
473
|
+
for (const line of roadmapLines) {
|
|
474
|
+
const phaseMatch = line.match(/^[-*]\s+(?:Phase\s+)?(\d+(?:\.\d+)?)[:\s]+(.+)/i);
|
|
475
|
+
if (phaseMatch) {
|
|
476
|
+
currentPhase = phaseMatch[1].trim();
|
|
477
|
+
currentName = phaseMatch[2].replace(/\s*\(.*\)$/, '').trim();
|
|
478
|
+
}
|
|
479
|
+
const durMatch = line.match(/\*\*Duration:\*\*\s*(\d+(?:\.\d+)?)\s*d(?:ay)?s?\b/i);
|
|
480
|
+
if (durMatch && currentPhase) {
|
|
481
|
+
const estimatedDays = parseFloat(durMatch[1]);
|
|
482
|
+
const estimatedMin = estimatedDays * 8 * 60; // 8h/day
|
|
483
|
+
phases.push({
|
|
484
|
+
phase: currentPhase,
|
|
485
|
+
name: currentName || currentPhase,
|
|
486
|
+
estimated_days: estimatedDays,
|
|
487
|
+
actual_min: null,
|
|
488
|
+
estimated_min: estimatedMin,
|
|
489
|
+
variance_pct: null,
|
|
490
|
+
over_budget: false,
|
|
491
|
+
});
|
|
492
|
+
currentPhase = null;
|
|
493
|
+
currentName = null;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
// Parse actual durations from STATE.md
|
|
497
|
+
if (stateContent) {
|
|
498
|
+
const actualByPhase = {};
|
|
499
|
+
for (const m of stateContent.matchAll(/Phase\s+(\d+(?:\.\d+)?)[^:]*:\s*.*?(\d+)\s*min/gi)) {
|
|
500
|
+
const phaseNum = m[1];
|
|
501
|
+
const mins = parseInt(m[2], 10);
|
|
502
|
+
if (!isNaN(mins)) {
|
|
503
|
+
actualByPhase[phaseNum] = (actualByPhase[phaseNum] || 0) + mins;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
for (const entry of phases) {
|
|
507
|
+
const actual = actualByPhase[entry.phase] ?? null;
|
|
508
|
+
if (actual !== null) {
|
|
509
|
+
entry.actual_min = actual;
|
|
510
|
+
entry.variance_pct =
|
|
511
|
+
entry.estimated_min > 0
|
|
512
|
+
? parseFloat((((actual - entry.estimated_min) / entry.estimated_min) * 100).toFixed(1))
|
|
513
|
+
: null;
|
|
514
|
+
entry.over_budget = entry.variance_pct !== null && entry.variance_pct > 20;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
const over = phases.filter((p) => p.over_budget);
|
|
519
|
+
const tracked = phases.filter((p) => p.actual_min !== null);
|
|
520
|
+
output({
|
|
521
|
+
total_phases: phases.length,
|
|
522
|
+
tracked_phases: tracked.length,
|
|
523
|
+
over_budget_count: over.length,
|
|
524
|
+
avg_variance_pct: tracked.length > 0
|
|
525
|
+
? parseFloat((tracked.reduce((s, p) => s + (p.variance_pct ?? 0), 0) / tracked.length).toFixed(1))
|
|
526
|
+
: null,
|
|
527
|
+
phases,
|
|
528
|
+
}, raw);
|
|
529
|
+
}
|
|
530
|
+
// ─── CLI: Config Change Diff Viewer ──────────────────────────────────────────
|
|
531
|
+
/**
|
|
532
|
+
* CLI command: Diff current config.json against a stored snapshot.
|
|
533
|
+
* On first run (or with --reset), saves a snapshot. Subsequent runs show
|
|
534
|
+
* what changed and explain the impact on agent behavior.
|
|
535
|
+
* @param cwd - Project root
|
|
536
|
+
* @param raw - Raw output flag
|
|
537
|
+
* @param reset - If true, overwrite snapshot with current config
|
|
538
|
+
*/
|
|
539
|
+
function cmdConfigDiff(cwd, raw, reset = false, dryRun = false) {
|
|
540
|
+
const configPath = path.join(getPlanningDir(cwd), 'config.json');
|
|
541
|
+
const snapshotPath = path.join(getPlanningDir(cwd), '.config-snapshot.json');
|
|
542
|
+
const currentConfig = safeReadJSON(configPath, null);
|
|
543
|
+
if (!currentConfig) {
|
|
544
|
+
output({ error: 'config.json not found', path: configPath }, raw);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
if (dryRun) {
|
|
548
|
+
const snapshot = fs.existsSync(snapshotPath)
|
|
549
|
+
? safeReadJSON(snapshotPath, null)
|
|
550
|
+
: null;
|
|
551
|
+
if (!snapshot) {
|
|
552
|
+
output({ dry_run: true, action: 'would_create_snapshot', path: snapshotPath, note: 'No snapshot exists yet; --dry-run shows current config only' }, raw, 'DRY RUN: would save config snapshot');
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const flatCurrent = _flattenObject(currentConfig);
|
|
556
|
+
const flatSnapshot = _flattenObject(snapshot);
|
|
557
|
+
const changes = [];
|
|
558
|
+
const allKeys = new Set([...Object.keys(flatCurrent), ...Object.keys(flatSnapshot)]);
|
|
559
|
+
for (const key of allKeys) {
|
|
560
|
+
const cur = flatCurrent[key];
|
|
561
|
+
const snap = flatSnapshot[key];
|
|
562
|
+
if (JSON.stringify(cur) !== JSON.stringify(snap)) {
|
|
563
|
+
changes.push({ key, old_value: snap ?? undefined, new_value: cur ?? undefined, explanation: _explainConfigChange(key, snap, cur) });
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
output({ dry_run: true, changes_count: changes.length, has_changes: changes.length > 0, changes, note: 'No files written (--dry-run)' }, raw, `DRY RUN: ${changes.length} change(s) detected — no snapshot updated`);
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
if (reset || !fs.existsSync(snapshotPath)) {
|
|
570
|
+
fs.writeFileSync(snapshotPath, JSON.stringify(currentConfig, null, 2));
|
|
571
|
+
output({
|
|
572
|
+
action: 'snapshot_saved',
|
|
573
|
+
message: 'Config snapshot saved. Run again to see diffs.',
|
|
574
|
+
path: snapshotPath,
|
|
575
|
+
}, raw);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
const snapshot = safeReadJSON(snapshotPath, null);
|
|
579
|
+
if (!snapshot) {
|
|
580
|
+
fs.writeFileSync(snapshotPath, JSON.stringify(currentConfig, null, 2));
|
|
581
|
+
output({
|
|
582
|
+
action: 'snapshot_saved',
|
|
583
|
+
message: 'Config snapshot saved (previous was unreadable).',
|
|
584
|
+
path: snapshotPath,
|
|
585
|
+
}, raw);
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
const flatCurrent = _flattenObject(currentConfig);
|
|
589
|
+
const flatSnapshot = _flattenObject(snapshot);
|
|
590
|
+
const changes = [];
|
|
591
|
+
const allKeys = new Set([...Object.keys(flatCurrent), ...Object.keys(flatSnapshot)]);
|
|
592
|
+
for (const key of allKeys) {
|
|
593
|
+
const cur = flatCurrent[key];
|
|
594
|
+
const snap = flatSnapshot[key];
|
|
595
|
+
if (JSON.stringify(cur) !== JSON.stringify(snap)) {
|
|
596
|
+
changes.push({
|
|
597
|
+
key,
|
|
598
|
+
old_value: snap ?? undefined,
|
|
599
|
+
new_value: cur ?? undefined,
|
|
600
|
+
explanation: _explainConfigChange(key, snap, cur),
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
output({
|
|
605
|
+
changes_count: changes.length,
|
|
606
|
+
has_changes: changes.length > 0,
|
|
607
|
+
changes,
|
|
608
|
+
snapshot_path: snapshotPath,
|
|
609
|
+
}, raw, changes.length > 0
|
|
610
|
+
? `${changes.length} config change(s) detected since last snapshot`
|
|
611
|
+
: 'No config changes since last snapshot');
|
|
612
|
+
}
|
|
613
|
+
// ─── CLI: Phase Kickoff Readiness Checklist ───────────────────────────────────
|
|
614
|
+
/**
|
|
615
|
+
* CLI command: Run a pre-flight readiness checklist before executing a phase.
|
|
616
|
+
* Verifies baseline, dependencies, eval plan, and research questions are addressed.
|
|
617
|
+
* @param cwd - Project root
|
|
618
|
+
* @param phase - Phase number string
|
|
619
|
+
* @param raw - Raw output flag
|
|
620
|
+
*/
|
|
621
|
+
function cmdPhaseReadiness(cwd, phase, raw) {
|
|
622
|
+
if (!phase) {
|
|
623
|
+
error('Phase number required');
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
const phaseDir = _findPhaseDirByNumber(cwd, phase);
|
|
627
|
+
const checks = [];
|
|
628
|
+
const blockers = [];
|
|
629
|
+
// Check 1: Phase directory exists
|
|
630
|
+
checks.push({
|
|
631
|
+
name: 'Phase directory exists',
|
|
632
|
+
passed: phaseDir !== null,
|
|
633
|
+
detail: phaseDir ?? `No directory found for phase ${phase} in ${getPhasesDirPath(cwd)}`,
|
|
634
|
+
});
|
|
635
|
+
if (!phaseDir)
|
|
636
|
+
blockers.push('Phase directory not found — run /grd:plan-phase first');
|
|
637
|
+
// Check 2: PLAN.md files exist
|
|
638
|
+
const planFiles = phaseDir ? _collectPlanFiles(phaseDir) : [];
|
|
639
|
+
checks.push({
|
|
640
|
+
name: 'Execution plans exist',
|
|
641
|
+
passed: planFiles.length > 0,
|
|
642
|
+
detail: planFiles.length > 0 ? `${planFiles.length} plan file(s) found` : 'No PLAN.md files',
|
|
643
|
+
});
|
|
644
|
+
if (planFiles.length === 0)
|
|
645
|
+
blockers.push('No PLAN.md files — run /grd:plan-phase');
|
|
646
|
+
// Check 3: BASELINE.md exists
|
|
647
|
+
const baselinePath = path.join(getPlanningDir(cwd), 'BASELINE.md');
|
|
648
|
+
const baselineExists = fs.existsSync(baselinePath);
|
|
649
|
+
checks.push({
|
|
650
|
+
name: 'Baseline metrics captured',
|
|
651
|
+
passed: baselineExists,
|
|
652
|
+
detail: baselineExists
|
|
653
|
+
? 'BASELINE.md exists'
|
|
654
|
+
: 'BASELINE.md missing — run /grd:assess-baseline',
|
|
655
|
+
});
|
|
656
|
+
if (!baselineExists)
|
|
657
|
+
blockers.push('No baseline metrics — run /grd:assess-baseline before executing');
|
|
658
|
+
// Check 4: EVAL plan exists for this phase
|
|
659
|
+
let evalFile;
|
|
660
|
+
if (phaseDir) {
|
|
661
|
+
try {
|
|
662
|
+
evalFile = fs.readdirSync(phaseDir).find((f) => f.endsWith('-EVAL.md'));
|
|
663
|
+
}
|
|
664
|
+
catch {
|
|
665
|
+
/* ignore */
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
checks.push({
|
|
669
|
+
name: 'Eval plan written',
|
|
670
|
+
passed: !!evalFile,
|
|
671
|
+
detail: evalFile ? `${evalFile} found` : 'No EVAL.md in phase directory',
|
|
672
|
+
});
|
|
673
|
+
// Check 5: No active blockers in STATE.md
|
|
674
|
+
const statePath = path.join(getPlanningDir(cwd), 'STATE.md');
|
|
675
|
+
const stateContent = safeReadFile(statePath) || '';
|
|
676
|
+
const blockerSection = stateContent.match(/##\s+Blockers?\s*\n([\s\S]*?)(?=\n##|$)/i);
|
|
677
|
+
const activeBlockers = blockerSection
|
|
678
|
+
? blockerSection[1]
|
|
679
|
+
.split('\n')
|
|
680
|
+
.filter((l) => l.trim().startsWith('-') && !l.includes('[x]') && l.trim().length > 2)
|
|
681
|
+
: [];
|
|
682
|
+
checks.push({
|
|
683
|
+
name: 'No active blockers in STATE.md',
|
|
684
|
+
passed: activeBlockers.length === 0,
|
|
685
|
+
detail: activeBlockers.length > 0
|
|
686
|
+
? `${activeBlockers.length} blocker(s): ${activeBlockers[0]?.trim().slice(0, 60)}`
|
|
687
|
+
: 'No active blockers',
|
|
688
|
+
});
|
|
689
|
+
if (activeBlockers.length > 0)
|
|
690
|
+
blockers.push(`${activeBlockers.length} active blocker(s) in STATE.md`);
|
|
691
|
+
// Check 6: Prior phase is complete (if phase > 1)
|
|
692
|
+
const phaseNum = parseFloat(phase);
|
|
693
|
+
if (phaseNum > 1) {
|
|
694
|
+
const priorPhaseNum = Math.floor(phaseNum - 1);
|
|
695
|
+
const priorDir = _findPhaseDirByNumber(cwd, String(priorPhaseNum));
|
|
696
|
+
if (priorDir) {
|
|
697
|
+
const priorPlans = _collectPlanFiles(priorDir);
|
|
698
|
+
const priorSummaries = priorPlans.filter((p) => fs.existsSync(p.replace('-PLAN.md', '-SUMMARY.md')));
|
|
699
|
+
const priorComplete = priorPlans.length === 0 || priorSummaries.length >= priorPlans.length;
|
|
700
|
+
checks.push({
|
|
701
|
+
name: `Prior phase (${priorPhaseNum}) complete`,
|
|
702
|
+
passed: priorComplete,
|
|
703
|
+
detail: priorComplete
|
|
704
|
+
? `Phase ${priorPhaseNum} has ${priorSummaries.length}/${priorPlans.length} summaries`
|
|
705
|
+
: `Phase ${priorPhaseNum} missing ${priorPlans.length - priorSummaries.length} summary file(s)`,
|
|
706
|
+
});
|
|
707
|
+
if (!priorComplete)
|
|
708
|
+
blockers.push(`Phase ${priorPhaseNum} not fully complete`);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
const passed = checks.filter((c) => c.passed).length;
|
|
712
|
+
const total = checks.length;
|
|
713
|
+
const score = Math.round((passed / total) * 100);
|
|
714
|
+
const ready = blockers.length === 0;
|
|
715
|
+
const result = { phase, ready, score, checks, blockers };
|
|
716
|
+
output(result, raw, ready
|
|
717
|
+
? `Phase ${phase} is READY to execute (${score}% checks passed)`
|
|
718
|
+
: `Phase ${phase} NOT ready: ${blockers.length} blocker(s) (${score}% passed)`);
|
|
719
|
+
}
|
|
720
|
+
// ─── CLI: Milestone Health Score ─────────────────────────────────────────────
|
|
721
|
+
/**
|
|
722
|
+
* CLI command: Compute a composite health score (0-100) for the current milestone
|
|
723
|
+
* from phase completion rate, blockers, eval hit rate, and estimate accuracy.
|
|
724
|
+
* @param cwd - Project root
|
|
725
|
+
* @param raw - Raw output flag
|
|
726
|
+
*/
|
|
727
|
+
function cmdMilestoneHealth(cwd, raw) {
|
|
728
|
+
const milestone = currentMilestone(cwd);
|
|
729
|
+
const phasesPath = getPhasesDirPath(cwd);
|
|
730
|
+
const statePath = path.join(getPlanningDir(cwd), 'STATE.md');
|
|
731
|
+
const stateContent = safeReadFile(statePath) || '';
|
|
732
|
+
// Component 1: Phase completion rate (40 pts)
|
|
733
|
+
let totalPhases = 0;
|
|
734
|
+
let completedPhases = 0;
|
|
735
|
+
try {
|
|
736
|
+
const phaseDirs = fs
|
|
737
|
+
.readdirSync(phasesPath, { withFileTypes: true })
|
|
738
|
+
.filter((e) => e.isDirectory())
|
|
739
|
+
.map((e) => e.name);
|
|
740
|
+
totalPhases = phaseDirs.length;
|
|
741
|
+
for (const dir of phaseDirs) {
|
|
742
|
+
const plans = _collectPlanFiles(path.join(phasesPath, dir));
|
|
743
|
+
if (plans.length === 0)
|
|
744
|
+
continue;
|
|
745
|
+
const summaries = plans.filter((p) => fs.existsSync(p.replace('-PLAN.md', '-SUMMARY.md')));
|
|
746
|
+
if (summaries.length === plans.length)
|
|
747
|
+
completedPhases++;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
catch {
|
|
751
|
+
/* no phases dir */
|
|
752
|
+
}
|
|
753
|
+
const completionRate = totalPhases > 0 ? completedPhases / totalPhases : 0;
|
|
754
|
+
const completionScore = Math.round(completionRate * 40);
|
|
755
|
+
// Component 2: Blocker penalty (20 pts max, deduct per blocker)
|
|
756
|
+
const blockerMatch = stateContent.match(/##\s+Blockers?\s*\n([\s\S]*?)(?=\n##|$)/i);
|
|
757
|
+
const activeBlockerCount = blockerMatch
|
|
758
|
+
? blockerMatch[1]
|
|
759
|
+
.split('\n')
|
|
760
|
+
.filter((l) => l.trim().startsWith('-') && !l.includes('[x]') && l.trim().length > 2).length
|
|
761
|
+
: 0;
|
|
762
|
+
const blockerPenalty = Math.min(20, activeBlockerCount * 5);
|
|
763
|
+
const blockerScore = 20 - blockerPenalty;
|
|
764
|
+
// Component 3: Eval hit rate (30 pts)
|
|
765
|
+
let evalTargetsTotal = 0;
|
|
766
|
+
let evalTargetsMet = 0;
|
|
767
|
+
const evalFiles = _collectMarkdownFiles(phasesPath).filter((f) => f.endsWith('-EVAL.md'));
|
|
768
|
+
for (const ef of evalFiles) {
|
|
769
|
+
const content = safeReadFile(ef);
|
|
770
|
+
if (!content)
|
|
771
|
+
continue;
|
|
772
|
+
const passes = (content.match(/\bPASS\b/gi) || []).length;
|
|
773
|
+
const fails = (content.match(/\bFAIL\b/gi) || []).length;
|
|
774
|
+
evalTargetsTotal += passes + fails;
|
|
775
|
+
evalTargetsMet += passes;
|
|
776
|
+
}
|
|
777
|
+
const evalHitRate = evalTargetsTotal > 0 ? evalTargetsMet / evalTargetsTotal : 0.5;
|
|
778
|
+
const evalScore = Math.round(evalHitRate * 30);
|
|
779
|
+
// Component 4: Estimate accuracy (10 pts)
|
|
780
|
+
const velocityMatch = stateContent.match(/avg[_\s]duration[_\s]min[:\s]+(\d+)/i);
|
|
781
|
+
const estimateScore = velocityMatch ? 8 : 5;
|
|
782
|
+
const totalScore = completionScore + blockerScore + evalScore + estimateScore;
|
|
783
|
+
const grade = totalScore >= 90
|
|
784
|
+
? 'A'
|
|
785
|
+
: totalScore >= 75
|
|
786
|
+
? 'B'
|
|
787
|
+
: totalScore >= 60
|
|
788
|
+
? 'C'
|
|
789
|
+
: totalScore >= 45
|
|
790
|
+
? 'D'
|
|
791
|
+
: 'F';
|
|
792
|
+
const result = {
|
|
793
|
+
milestone,
|
|
794
|
+
score: totalScore,
|
|
795
|
+
grade,
|
|
796
|
+
components: {
|
|
797
|
+
completion_rate: completionScore,
|
|
798
|
+
blocker_penalty: blockerPenalty,
|
|
799
|
+
eval_hit_rate: evalScore,
|
|
800
|
+
estimate_accuracy: estimateScore,
|
|
801
|
+
},
|
|
802
|
+
summary: `${completedPhases}/${totalPhases} phases complete, ${activeBlockerCount} blocker(s), ${evalTargetsMet}/${evalTargetsTotal} eval targets met`,
|
|
803
|
+
};
|
|
804
|
+
output(result, raw, `Milestone ${milestone} health: ${grade} (${totalScore}/100)`);
|
|
805
|
+
}
|
|
806
|
+
// ─── CLI: Decision Log Timeline ───────────────────────────────────────────────
|
|
807
|
+
/**
|
|
808
|
+
* CLI command: Build a chronological timeline of all decisions recorded in
|
|
809
|
+
* STATE.md and CONTEXT.md files across phases.
|
|
810
|
+
* @param cwd - Project root
|
|
811
|
+
* @param raw - Raw output flag
|
|
812
|
+
*/
|
|
813
|
+
function cmdDecisionTimeline(cwd, raw) {
|
|
814
|
+
const decisions = [];
|
|
815
|
+
// Parse decisions from STATE.md
|
|
816
|
+
const statePath = path.join(getPlanningDir(cwd), 'STATE.md');
|
|
817
|
+
const stateContent = safeReadFile(statePath) || '';
|
|
818
|
+
const decisionSection = stateContent.match(/##\s+(?:Key\s+)?Decisions?\s*\n([\s\S]*?)(?=\n##|$)/i);
|
|
819
|
+
if (decisionSection) {
|
|
820
|
+
const lines = decisionSection[1].split('\n');
|
|
821
|
+
for (const line of lines) {
|
|
822
|
+
const trimmed = line.trim();
|
|
823
|
+
if (!trimmed)
|
|
824
|
+
continue;
|
|
825
|
+
const datePhaseMatch = trimmed.match(/^[-*]\s+\[?(\d{4}-\d{2}-\d{2})\]?\s+(?:Phase\s+(\d+(?:\.\d+)?)[:\s]+)?(.+)/);
|
|
826
|
+
const phaseMatch = trimmed.match(/^[-*]\s+(?:Phase\s+(\d+(?:\.\d+)?)[:\s]+)?(.+)/);
|
|
827
|
+
if (datePhaseMatch) {
|
|
828
|
+
decisions.push({
|
|
829
|
+
phase: datePhaseMatch[2] || null,
|
|
830
|
+
date: datePhaseMatch[1],
|
|
831
|
+
summary: datePhaseMatch[3].trim(),
|
|
832
|
+
rationale: '',
|
|
833
|
+
source: 'STATE.md',
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
else if (phaseMatch && trimmed.startsWith('-')) {
|
|
837
|
+
decisions.push({
|
|
838
|
+
phase: phaseMatch[1] || null,
|
|
839
|
+
date: null,
|
|
840
|
+
summary: phaseMatch[2].trim(),
|
|
841
|
+
rationale: '',
|
|
842
|
+
source: 'STATE.md',
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
// Parse decisions from CONTEXT.md files
|
|
848
|
+
const phaseDirs = _listPhaseDirs(cwd);
|
|
849
|
+
for (const phaseDir of phaseDirs) {
|
|
850
|
+
const phaseName = path.basename(phaseDir);
|
|
851
|
+
const phaseNumMatch = phaseName.match(/^(\d+(?:\.\d+)?)/);
|
|
852
|
+
const phaseNum = phaseNumMatch ? phaseNumMatch[1] : phaseName;
|
|
853
|
+
let ctxFiles = [];
|
|
854
|
+
try {
|
|
855
|
+
ctxFiles = fs.readdirSync(phaseDir).filter((f) => f.endsWith('-CONTEXT.md'));
|
|
856
|
+
}
|
|
857
|
+
catch {
|
|
858
|
+
/* ignore */
|
|
859
|
+
}
|
|
860
|
+
for (const ctxFile of ctxFiles) {
|
|
861
|
+
const content = safeReadFile(path.join(phaseDir, ctxFile));
|
|
862
|
+
if (!content)
|
|
863
|
+
continue;
|
|
864
|
+
const section = content.match(/##\s+(?:Decisions?|Choices?|Resolution)\s*\n([\s\S]*?)(?=\n##|$)/i);
|
|
865
|
+
if (section) {
|
|
866
|
+
for (const line of section[1].split('\n')) {
|
|
867
|
+
const trimmed = line.trim();
|
|
868
|
+
if (!trimmed || !trimmed.startsWith('-'))
|
|
869
|
+
continue;
|
|
870
|
+
decisions.push({
|
|
871
|
+
phase: phaseNum,
|
|
872
|
+
date: null,
|
|
873
|
+
summary: trimmed.replace(/^[-*]\s+/, '').slice(0, 120),
|
|
874
|
+
rationale: '',
|
|
875
|
+
source: `${phaseName}/${ctxFile}`,
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
decisions.sort((a, b) => {
|
|
882
|
+
if (a.date && b.date)
|
|
883
|
+
return a.date.localeCompare(b.date);
|
|
884
|
+
if (a.date)
|
|
885
|
+
return -1;
|
|
886
|
+
if (b.date)
|
|
887
|
+
return 1;
|
|
888
|
+
return parseFloat(a.phase || '999') - parseFloat(b.phase || '999');
|
|
889
|
+
});
|
|
890
|
+
output({
|
|
891
|
+
total_decisions: decisions.length,
|
|
892
|
+
decisions_with_dates: decisions.filter((d) => d.date !== null).length,
|
|
893
|
+
timeline: decisions,
|
|
894
|
+
}, raw);
|
|
895
|
+
}
|
|
896
|
+
// ─── CLI: Cross-Project Knowledge Import ─────────────────────────────────────
|
|
897
|
+
/**
|
|
898
|
+
* CLI command: Import research artifacts (LANDSCAPE.md, PAPERS.md, KNOWHOW.md)
|
|
899
|
+
* from another GRD project into the current project's research directory.
|
|
900
|
+
* @param cwd - Project root
|
|
901
|
+
* @param sourcePath - Path to the source project root
|
|
902
|
+
* @param types - Comma-separated types to import (landscape,papers,knowhow,all)
|
|
903
|
+
* @param raw - Raw output flag
|
|
904
|
+
* @param force - If true, overwrite existing files
|
|
905
|
+
*/
|
|
906
|
+
function cmdImportKnowledge(cwd, sourcePath, types, raw, force = false, dryRun = false) {
|
|
907
|
+
if (!sourcePath) {
|
|
908
|
+
error('Source project path required');
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
const absoluteSource = path.resolve(cwd, sourcePath);
|
|
912
|
+
if (!fs.existsSync(absoluteSource)) {
|
|
913
|
+
output({ error: `Source path does not exist: ${absoluteSource}` }, raw);
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
const requestedTypes = !types || types === 'all'
|
|
917
|
+
? ['landscape', 'papers', 'knowhow']
|
|
918
|
+
: types.split(',').map((t) => t.trim().toLowerCase());
|
|
919
|
+
const fileMap = {
|
|
920
|
+
landscape: 'LANDSCAPE.md',
|
|
921
|
+
papers: 'PAPERS.md',
|
|
922
|
+
knowhow: 'KNOWHOW.md',
|
|
923
|
+
};
|
|
924
|
+
// Locate source research directory
|
|
925
|
+
const sourceMilestone = currentMilestone(absoluteSource);
|
|
926
|
+
const candidateDirs = [
|
|
927
|
+
path.join(absoluteSource, '.planning', 'milestones', sourceMilestone, 'research'),
|
|
928
|
+
path.join(absoluteSource, '.planning', 'research'),
|
|
929
|
+
];
|
|
930
|
+
let sourceResearchDir = null;
|
|
931
|
+
for (const dir of candidateDirs) {
|
|
932
|
+
if (fs.existsSync(dir)) {
|
|
933
|
+
sourceResearchDir = dir;
|
|
934
|
+
break;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
if (!sourceResearchDir) {
|
|
938
|
+
output({ error: `No research directory found in source project`, tried: candidateDirs }, raw);
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
const destResearchDir = getResearchDir(cwd);
|
|
942
|
+
// Codex r16 P2: defer mkdir to the actual copy site so --dry-run is
|
|
943
|
+
// truly side-effect-free. If we are not dry-running, create on first
|
|
944
|
+
// use right before the cpSync call.
|
|
945
|
+
if (!dryRun)
|
|
946
|
+
fs.mkdirSync(destResearchDir, { recursive: true });
|
|
947
|
+
const results = [];
|
|
948
|
+
for (const type of requestedTypes) {
|
|
949
|
+
const filename = fileMap[type];
|
|
950
|
+
if (!filename) {
|
|
951
|
+
results.push({
|
|
952
|
+
source: sourcePath,
|
|
953
|
+
type,
|
|
954
|
+
destination: '',
|
|
955
|
+
imported: false,
|
|
956
|
+
conflict: false,
|
|
957
|
+
message: `Unknown type: ${type}`,
|
|
958
|
+
});
|
|
959
|
+
continue;
|
|
960
|
+
}
|
|
961
|
+
const srcFile = path.join(sourceResearchDir, filename);
|
|
962
|
+
const destFile = path.join(destResearchDir, filename);
|
|
963
|
+
if (!fs.existsSync(srcFile)) {
|
|
964
|
+
results.push({
|
|
965
|
+
source: srcFile,
|
|
966
|
+
type,
|
|
967
|
+
destination: destFile,
|
|
968
|
+
imported: false,
|
|
969
|
+
conflict: false,
|
|
970
|
+
message: `Source file not found: ${filename}`,
|
|
971
|
+
});
|
|
972
|
+
continue;
|
|
973
|
+
}
|
|
974
|
+
const destExists = fs.existsSync(destFile);
|
|
975
|
+
// Codex r24 P3: dry-run should preview `would overwrite` for
|
|
976
|
+
// existing files without requiring --force. Skip the hard
|
|
977
|
+
// conflict only when actually writing.
|
|
978
|
+
if (destExists && !force && !dryRun) {
|
|
979
|
+
results.push({
|
|
980
|
+
source: srcFile,
|
|
981
|
+
type,
|
|
982
|
+
destination: destFile,
|
|
983
|
+
imported: false,
|
|
984
|
+
conflict: true,
|
|
985
|
+
message: `${filename} already exists — use --force to overwrite`,
|
|
986
|
+
});
|
|
987
|
+
continue;
|
|
988
|
+
}
|
|
989
|
+
if (dryRun) {
|
|
990
|
+
results.push({
|
|
991
|
+
source: srcFile,
|
|
992
|
+
type,
|
|
993
|
+
destination: destFile,
|
|
994
|
+
imported: false,
|
|
995
|
+
conflict: destExists,
|
|
996
|
+
message: destExists ? `DRY RUN: would overwrite ${filename}` : `DRY RUN: would import ${filename}`,
|
|
997
|
+
});
|
|
998
|
+
continue;
|
|
999
|
+
}
|
|
1000
|
+
try {
|
|
1001
|
+
fs.cpSync(srcFile, destFile);
|
|
1002
|
+
results.push({
|
|
1003
|
+
source: srcFile,
|
|
1004
|
+
type,
|
|
1005
|
+
destination: destFile,
|
|
1006
|
+
imported: true,
|
|
1007
|
+
conflict: destExists,
|
|
1008
|
+
message: destExists ? `Overwrote existing ${filename}` : `Imported ${filename}`,
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
catch (err) {
|
|
1012
|
+
results.push({
|
|
1013
|
+
source: srcFile,
|
|
1014
|
+
type,
|
|
1015
|
+
destination: destFile,
|
|
1016
|
+
imported: false,
|
|
1017
|
+
conflict: false,
|
|
1018
|
+
message: `Copy failed: ${err.message}`,
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
const importedCount = results.filter((r) => r.imported).length;
|
|
1023
|
+
output({
|
|
1024
|
+
source_project: absoluteSource,
|
|
1025
|
+
destination_project: cwd,
|
|
1026
|
+
imported_count: importedCount,
|
|
1027
|
+
conflict_count: results.filter((r) => r.conflict && !r.imported).length,
|
|
1028
|
+
results,
|
|
1029
|
+
}, raw, `${importedCount}/${requestedTypes.length} file(s) imported from ${sourcePath}`);
|
|
1030
|
+
}
|
|
1031
|
+
// ─── CLI: Semantic Todo Duplicate Detector ────────────────────────────────────
|
|
1032
|
+
/**
|
|
1033
|
+
* CLI command: Find semantically similar todo items using Jaccard word-overlap.
|
|
1034
|
+
* Surfaces near-duplicate pairs above a configurable similarity threshold.
|
|
1035
|
+
* @param cwd - Project root
|
|
1036
|
+
* @param raw - Raw output flag
|
|
1037
|
+
* @param threshold - Similarity threshold 0-1 (default 0.4)
|
|
1038
|
+
*/
|
|
1039
|
+
function cmdTodoDuplicates(cwd, raw, threshold = 0.4) {
|
|
1040
|
+
const todosPath = getTodosDir(cwd);
|
|
1041
|
+
const pendingDir = path.join(todosPath, 'pending');
|
|
1042
|
+
let todoFiles;
|
|
1043
|
+
try {
|
|
1044
|
+
todoFiles = fs
|
|
1045
|
+
.readdirSync(pendingDir)
|
|
1046
|
+
.filter((f) => f.endsWith('.md') || f.endsWith('.txt'))
|
|
1047
|
+
.map((f) => path.join(pendingDir, f));
|
|
1048
|
+
}
|
|
1049
|
+
catch {
|
|
1050
|
+
output({ error: `No pending todos directory found at ${pendingDir}`, duplicates: [] }, raw);
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
if (todoFiles.length < 2) {
|
|
1054
|
+
output({ total_todos: todoFiles.length, duplicates: [], note: 'Not enough todos to compare' }, raw);
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
const todos = [];
|
|
1058
|
+
for (const f of todoFiles) {
|
|
1059
|
+
const content = safeReadFile(f);
|
|
1060
|
+
if (!content)
|
|
1061
|
+
continue;
|
|
1062
|
+
todos.push({ file: path.basename(f), title: _extractTodoTitle(content), content });
|
|
1063
|
+
}
|
|
1064
|
+
const pairs = [];
|
|
1065
|
+
for (let i = 0; i < todos.length; i++) {
|
|
1066
|
+
for (let j = i + 1; j < todos.length; j++) {
|
|
1067
|
+
const sim = _jaccardSimilarity(todos[i].title + ' ' + todos[i].content.slice(0, 300), todos[j].title + ' ' + todos[j].content.slice(0, 300));
|
|
1068
|
+
if (sim >= threshold) {
|
|
1069
|
+
pairs.push({
|
|
1070
|
+
a: todos[i].file,
|
|
1071
|
+
b: todos[j].file,
|
|
1072
|
+
similarity: parseFloat(sim.toFixed(3)),
|
|
1073
|
+
a_title: todos[i].title,
|
|
1074
|
+
b_title: todos[j].title,
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
pairs.sort((a, b) => b.similarity - a.similarity);
|
|
1080
|
+
output({
|
|
1081
|
+
total_todos: todos.length,
|
|
1082
|
+
threshold,
|
|
1083
|
+
duplicate_pairs: pairs.length,
|
|
1084
|
+
duplicates: pairs,
|
|
1085
|
+
unique_todos_at_risk: new Set(pairs.flatMap((p) => [p.a, p.b])).size,
|
|
1086
|
+
}, raw, `Found ${pairs.length} potential duplicate pair(s) among ${todos.length} todos (threshold=${threshold})`);
|
|
1087
|
+
}
|
|
1088
|
+
// ─── Knowhow List ───────────────────────────────────────────────────────────
|
|
1089
|
+
function cmdKnowhowList(cwd, raw, moduleHint, limit) {
|
|
1090
|
+
const { parseKnowhowEntries, selectTopEntries, } = require('../knowledge');
|
|
1091
|
+
const researchDir = getResearchDir(cwd);
|
|
1092
|
+
const knowhowPath = path.join(researchDir, 'KNOWHOW.md');
|
|
1093
|
+
const content = safeReadFile(knowhowPath);
|
|
1094
|
+
if (!content) {
|
|
1095
|
+
output({ entries: [], total: 0, message: 'No KNOWHOW.md found' }, raw, 'No KNOWHOW.md found');
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
const allEntries = parseKnowhowEntries(content);
|
|
1099
|
+
const hints = moduleHint ? moduleHint.split(',').map((h) => h.trim()) : undefined;
|
|
1100
|
+
const maxEntries = limit || 20;
|
|
1101
|
+
const selected = hints ? selectTopEntries(allEntries, maxEntries, hints) : allEntries.slice(0, maxEntries);
|
|
1102
|
+
output({ total: allEntries.length, showing: selected.length, module_filter: hints || null, entries: selected.map((e) => ({ pattern: e.pattern_name, source: e.source, phase: e.phase_number, created: e.created_at })) }, raw, `${selected.length}/${allEntries.length} knowhow entries${hints ? ` (filtered: ${hints.join(', ')})` : ''}`);
|
|
1103
|
+
}
|
|
1104
|
+
// ─── Citation Graph ─────────────────────────────────────────────────────────
|
|
1105
|
+
function cmdCitationGraph(cwd, raw, unresolvedOnly = false) {
|
|
1106
|
+
const { buildCitationGraph, findUnresolved, } = require('../citations');
|
|
1107
|
+
const researchDir = getResearchDir(cwd);
|
|
1108
|
+
const papersPath = path.join(researchDir, 'PAPERS.md');
|
|
1109
|
+
const content = safeReadFile(papersPath);
|
|
1110
|
+
if (!content) {
|
|
1111
|
+
output({ nodes: 0, edges: 0, message: 'No PAPERS.md found' }, raw, 'No PAPERS.md found');
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
const graph = buildCitationGraph(content);
|
|
1115
|
+
const unresolved = findUnresolved(graph);
|
|
1116
|
+
if (unresolvedOnly) {
|
|
1117
|
+
output({ unresolved_count: unresolved.length, unresolved }, raw, `${unresolved.length} unresolved citation(s)`);
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
output({ nodes: graph.nodes.length, edges: graph.edges.length, unresolved_count: unresolved.length, papers: graph.nodes.map((n) => ({ slug: n.slug, title: n.title, resolved: n.resolved, priority: n.priority })), unresolved }, raw, `Citation graph: ${graph.nodes.length} papers, ${graph.edges.length} edges, ${unresolved.length} unresolved`);
|
|
1121
|
+
}
|
|
1122
|
+
// ─── Artifact DAG ───────────────────────────────────────────────────────────
|
|
1123
|
+
function cmdArtifactDAG(cwd, phase, raw) {
|
|
1124
|
+
const { buildArtifactDAG, validateArtifactDAG } = require('../deps');
|
|
1125
|
+
const { extractFrontmatter } = require('../frontmatter');
|
|
1126
|
+
const phasesDir = getPhasesDirPath(cwd);
|
|
1127
|
+
let phaseDir = null;
|
|
1128
|
+
try {
|
|
1129
|
+
const dirs = fs.readdirSync(phasesDir);
|
|
1130
|
+
const match = dirs.find((d) => d.startsWith(phase + '-') || d === phase);
|
|
1131
|
+
if (match)
|
|
1132
|
+
phaseDir = path.join(phasesDir, match);
|
|
1133
|
+
}
|
|
1134
|
+
catch { /* phases dir may not exist */ }
|
|
1135
|
+
if (!phaseDir) {
|
|
1136
|
+
output({ error: `Phase ${phase} not found` }, raw, `Phase ${phase} not found`);
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
const files = fs.readdirSync(phaseDir).filter((f) => f.endsWith('-PLAN.md'));
|
|
1140
|
+
const plans = [];
|
|
1141
|
+
for (let i = 0; i < files.length; i++) {
|
|
1142
|
+
const planPath = path.join(phaseDir, files[i]);
|
|
1143
|
+
try {
|
|
1144
|
+
const content = fs.readFileSync(planPath, 'utf-8');
|
|
1145
|
+
const fm = extractFrontmatter(content);
|
|
1146
|
+
plans.push({ objective: fm.objective || '', files_modified: fm.files_modified || [], phase, plan: i + 1, type: fm.type || 'implementation', wave: fm.wave || 1, depends_on: fm.depends_on || [], autonomous: fm.autonomous ?? true, provides: fm.provides || [], requires: fm.requires || [], integration_points: fm.integration_points || [] });
|
|
1147
|
+
}
|
|
1148
|
+
catch { /* skip malformed plans */ }
|
|
1149
|
+
}
|
|
1150
|
+
if (plans.length === 0) {
|
|
1151
|
+
output({ error: 'No plans with artifact declarations found' }, raw, 'No plans found');
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
const dag = buildArtifactDAG(plans);
|
|
1155
|
+
const validation = validateArtifactDAG(dag, plans);
|
|
1156
|
+
if (dag.missing_requires.length > 0) {
|
|
1157
|
+
process.stderr.write(`Warning: ${dag.missing_requires.length} unresolvable requires: ${dag.missing_requires.join(', ')}\n`);
|
|
1158
|
+
}
|
|
1159
|
+
output({ phase, plans: plans.length, nodes: dag.nodes.length, edges: dag.edges.length, sorted_plans: dag.sorted_plans, missing_requires: dag.missing_requires, valid: validation.valid, cycles: validation.cycles, missing_deps: validation.missing_deps, warnings: validation.warnings }, raw, `Phase ${phase} DAG: ${dag.nodes.length} nodes, ${dag.edges.length} edges, valid=${validation.valid}${dag.missing_requires.length > 0 ? ` (${dag.missing_requires.length} unresolvable requires)` : ''}`);
|
|
1160
|
+
}
|
|
1161
|
+
// ─── Benchmark Report ───────────────────────────────────────────────────────
|
|
1162
|
+
function cmdBenchmarkReport(cwd, raw) {
|
|
1163
|
+
// Codex r44 P1 #5: formatBenchmarkReport requires (results, entries).
|
|
1164
|
+
// The prior call passed entries-only, so the function iterated
|
|
1165
|
+
// undefined.semantic and crashed at runtime. Evaluate each corpus
|
|
1166
|
+
// entry first (with empty observation strings — this surfaces the
|
|
1167
|
+
// category multiplier + zero-baseline so the report is structurally
|
|
1168
|
+
// valid even before an eval run), then pass both arrays.
|
|
1169
|
+
const { loadCorpus, formatBenchmarkReport, evaluateEntry, } = require('../benchmark');
|
|
1170
|
+
const researchDir = getResearchDir(cwd);
|
|
1171
|
+
const corpusDir = path.join(researchDir, 'benchmarks');
|
|
1172
|
+
if (!fs.existsSync(corpusDir)) {
|
|
1173
|
+
output({ entries: 0, message: 'No benchmark corpus found', corpus_dir: corpusDir }, raw, 'No benchmark corpus found');
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
const entries = loadCorpus(corpusDir);
|
|
1177
|
+
if (entries.length === 0) {
|
|
1178
|
+
output({ entries: 0, message: 'Benchmark corpus is empty' }, raw, 'Benchmark corpus is empty');
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1181
|
+
const results = entries.map((e) => evaluateEntry(e, '', '', '', '', 0, '1.0', 'gd-benchmark-report'));
|
|
1182
|
+
const report = formatBenchmarkReport(results, entries);
|
|
1183
|
+
output({ entries: entries.length, results: results.length, report }, raw, report);
|
|
1184
|
+
}
|
|
1185
|
+
/** Average tokens per agent task, seeded from empirical observations. */
|
|
1186
|
+
const TOKEN_AVERAGES_BY_TIER = {
|
|
1187
|
+
high: 45000,
|
|
1188
|
+
medium: 22000,
|
|
1189
|
+
low: 8000,
|
|
1190
|
+
};
|
|
1191
|
+
const COST_PER_MILLION_BY_TIER = {
|
|
1192
|
+
high: 15.0, // e.g. Opus
|
|
1193
|
+
medium: 3.0, // e.g. Sonnet
|
|
1194
|
+
low: 0.25, // e.g. Haiku
|
|
1195
|
+
};
|
|
1196
|
+
/**
|
|
1197
|
+
* Estimate token and dollar cost for a phase by counting tasks across plan files,
|
|
1198
|
+
* looking up model tier from config, and applying historical per-agent averages.
|
|
1199
|
+
*/
|
|
1200
|
+
function estimatePhaseTokens(cwd, phaseNum, config) {
|
|
1201
|
+
const phaseDir = _findPhaseDirByNumber(cwd, phaseNum);
|
|
1202
|
+
const modelProfile = config.model_profile || 'balanced';
|
|
1203
|
+
// Codex r5 P2: model_profile values are quality/balanced/budget;
|
|
1204
|
+
// 'frugal' is a token_profile value and never appears here, so the
|
|
1205
|
+
// prior mapping silently routed `budget` projects to medium-tier
|
|
1206
|
+
// estimates. Map model_profile correctly.
|
|
1207
|
+
const tier = modelProfile === 'quality' ? 'high' : modelProfile === 'budget' ? 'low' : 'medium';
|
|
1208
|
+
const tokensPerTask = TOKEN_AVERAGES_BY_TIER[tier] ?? TOKEN_AVERAGES_BY_TIER.medium;
|
|
1209
|
+
const costPerMillion = COST_PER_MILLION_BY_TIER[tier] ?? COST_PER_MILLION_BY_TIER.medium;
|
|
1210
|
+
const agents = [];
|
|
1211
|
+
if (phaseDir) {
|
|
1212
|
+
const planFiles = _collectPlanFiles(phaseDir);
|
|
1213
|
+
for (const planFile of planFiles) {
|
|
1214
|
+
const content = safeReadFile(planFile);
|
|
1215
|
+
if (!content)
|
|
1216
|
+
continue;
|
|
1217
|
+
// Codex r5 P2: count XML <task> blocks alongside markdown
|
|
1218
|
+
// checkboxes. GRD plans use the XML form; counting only checkboxes
|
|
1219
|
+
// collapsed every multi-task XML plan to the fallback `1`.
|
|
1220
|
+
const taskCount = (content.match(/^[-*]\s+\[/gm) || []).length +
|
|
1221
|
+
(content.match(/<task\b[^>]*>/gi) || []).length || 1;
|
|
1222
|
+
const agentName = path.basename(planFile, '-PLAN.md');
|
|
1223
|
+
const estTokens = taskCount * tokensPerTask;
|
|
1224
|
+
agents.push({
|
|
1225
|
+
agent: agentName,
|
|
1226
|
+
tasks: taskCount,
|
|
1227
|
+
est_tokens: estTokens,
|
|
1228
|
+
est_cost_usd: parseFloat(((estTokens / 1_000_000) * costPerMillion).toFixed(4)),
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
const totalTokens = agents.reduce((s, a) => s + a.est_tokens, 0);
|
|
1233
|
+
const totalCost = parseFloat(agents.reduce((s, a) => s + a.est_cost_usd, 0).toFixed(4));
|
|
1234
|
+
// Codex r7 P3: model_profile values are quality/balanced/budget;
|
|
1235
|
+
// `frugal` is a token_profile value. Recommend the correct flag
|
|
1236
|
+
// (model_profile=budget) so users don't introduce config drift.
|
|
1237
|
+
const cheaperAlternative = totalCost > 0.5 && tier !== 'low'
|
|
1238
|
+
? `Use model_profile=budget to reduce cost by ~${Math.round((1 - COST_PER_MILLION_BY_TIER.low / costPerMillion) * 100)}%`
|
|
1239
|
+
: undefined;
|
|
1240
|
+
return { phase: phaseNum, agents, total_tokens: totalTokens, total_cost_usd: totalCost, model_tier: tier, cheaper_alternative: cheaperAlternative };
|
|
1241
|
+
}
|
|
1242
|
+
/**
|
|
1243
|
+
* CLI command: Estimate token and dollar cost for a phase before execution.
|
|
1244
|
+
* @param cwd - Project root
|
|
1245
|
+
* @param phase - Phase number string
|
|
1246
|
+
* @param raw - Raw output flag
|
|
1247
|
+
*/
|
|
1248
|
+
function cmdEstimatePhase(cwd, phase, raw) {
|
|
1249
|
+
if (!phase) {
|
|
1250
|
+
error('Phase number required');
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
const config = require('../utils').loadConfig(cwd);
|
|
1254
|
+
const estimate = estimatePhaseTokens(cwd, phase, config);
|
|
1255
|
+
const rawMsg = estimate.agents.length === 0
|
|
1256
|
+
? `Phase ${phase}: no plan files found`
|
|
1257
|
+
: `Phase ${phase}: ~${estimate.total_tokens.toLocaleString()} tokens / $${estimate.total_cost_usd} (${estimate.model_tier} tier)${estimate.cheaper_alternative ? ' — ' + estimate.cheaper_alternative : ''}`;
|
|
1258
|
+
output(estimate, raw, rawMsg);
|
|
1259
|
+
}
|
|
1260
|
+
/**
|
|
1261
|
+
* Compute downstream phases that depend (directly or transitively) on the given phase.
|
|
1262
|
+
* Parses phase dependency blocks from ROADMAP.md using a BFS traversal.
|
|
1263
|
+
*/
|
|
1264
|
+
function computeDownstreamImpact(cwd, phaseNum) {
|
|
1265
|
+
const roadmapPath = path.join(getPlanningDir(cwd), 'ROADMAP.md');
|
|
1266
|
+
const roadmapContent = safeReadFile(roadmapPath) || '';
|
|
1267
|
+
const phasesPath = getPhasesDirPath(cwd);
|
|
1268
|
+
// Build dependency graph: phaseN depends on phaseM means M → N edge
|
|
1269
|
+
const deps = new Map(); // parent → children
|
|
1270
|
+
const phaseNames = new Map();
|
|
1271
|
+
// Codex r1 P2: roadmap uses `- [x] **Phase 87: name**` form (bold,
|
|
1272
|
+
// checklist marker, comma-separated description); the prior regex
|
|
1273
|
+
// expected a bare `Phase N: name` and matched nothing on real
|
|
1274
|
+
// roadmaps. Accept both the canonical form and the lighter form.
|
|
1275
|
+
for (const m of roadmapContent.matchAll(/^[-*]\s+(?:\[[ x]\]\s+)?(?:\*\*)?(?:Phase\s+)?(\d+(?:\.\d+)?)[:\s]+([^\n*]+)/gim)) {
|
|
1276
|
+
const num = m[1].trim();
|
|
1277
|
+
const name = m[2].replace(/\s*\(.*\)$/, '').trim();
|
|
1278
|
+
phaseNames.set(num, name);
|
|
1279
|
+
if (!deps.has(num))
|
|
1280
|
+
deps.set(num, []);
|
|
1281
|
+
}
|
|
1282
|
+
// Codex r5 P2: most roadmap blocks have `Depends on: Phase N` inside
|
|
1283
|
+
// the *child* phase's section (i.e. the current phase being defined),
|
|
1284
|
+
// so the regex captures only the parent and leaves child undefined.
|
|
1285
|
+
// Walk the roadmap line-by-line tracking the enclosing phase context:
|
|
1286
|
+
// a `#### Phase N: ...` (or comparable heading) sets the current
|
|
1287
|
+
// phase; subsequent `Depends on: Phase M` lines add M → N edges.
|
|
1288
|
+
let currentPhase = null;
|
|
1289
|
+
const phaseHeadingRe = /^#{2,}\s*Phase\s+(\d+(?:\.\d+)?)\s*:/i;
|
|
1290
|
+
const dependsOnRe = /[Dd]epends?\s+on(?:\*\*)?[:\s]+[Pp]hase\s+(\d+(?:\.\d+)?)(?:[^\n]*?[Pp]hase\s+(\d+(?:\.\d+)?))?/;
|
|
1291
|
+
for (const line of roadmapContent.split('\n')) {
|
|
1292
|
+
const heading = line.match(phaseHeadingRe);
|
|
1293
|
+
if (heading) {
|
|
1294
|
+
currentPhase = heading[1].trim();
|
|
1295
|
+
continue;
|
|
1296
|
+
}
|
|
1297
|
+
const m = line.match(dependsOnRe);
|
|
1298
|
+
if (!m)
|
|
1299
|
+
continue;
|
|
1300
|
+
const parent = m[1].trim();
|
|
1301
|
+
const explicitChild = m[2]?.trim();
|
|
1302
|
+
if (!deps.has(parent))
|
|
1303
|
+
deps.set(parent, []);
|
|
1304
|
+
// Two-phase pattern wins; single-parent falls back to enclosing
|
|
1305
|
+
// phase context.
|
|
1306
|
+
const child = explicitChild ?? currentPhase;
|
|
1307
|
+
if (child && child !== parent)
|
|
1308
|
+
deps.get(parent).push(child);
|
|
1309
|
+
}
|
|
1310
|
+
// Codex r13 P2: only infer the sequential N → N+1 edge when the
|
|
1311
|
+
// child has no explicit parent declared. The prior loop always
|
|
1312
|
+
// added the edge, so `gd impact 2` over-reported phase 3 as
|
|
1313
|
+
// affected even when phase 3 declared `Depends on: Phase 1` only.
|
|
1314
|
+
const allPhaseNums = Array.from(phaseNames.keys()).sort((a, b) => parseFloat(a) - parseFloat(b));
|
|
1315
|
+
const childrenWithExplicitParent = new Set();
|
|
1316
|
+
for (const [, kids] of deps) {
|
|
1317
|
+
for (const k of kids)
|
|
1318
|
+
childrenWithExplicitParent.add(k);
|
|
1319
|
+
}
|
|
1320
|
+
for (let i = 0; i < allPhaseNums.length - 1; i++) {
|
|
1321
|
+
const parent = allPhaseNums[i];
|
|
1322
|
+
const child = allPhaseNums[i + 1];
|
|
1323
|
+
if (childrenWithExplicitParent.has(child))
|
|
1324
|
+
continue;
|
|
1325
|
+
const parentChildren = deps.get(parent) || [];
|
|
1326
|
+
if (!parentChildren.includes(child))
|
|
1327
|
+
parentChildren.push(child);
|
|
1328
|
+
deps.set(parent, parentChildren);
|
|
1329
|
+
}
|
|
1330
|
+
// Determine status of each phase
|
|
1331
|
+
function _phaseStatus(num) {
|
|
1332
|
+
const normalized = num.padStart(2, '0');
|
|
1333
|
+
try {
|
|
1334
|
+
const entries = require('fs').readdirSync(phasesPath, { withFileTypes: true });
|
|
1335
|
+
const dir = entries.find((e) => e.isDirectory() && (e.name.startsWith(normalized + '-') || e.name.startsWith(num + '-')));
|
|
1336
|
+
if (!dir)
|
|
1337
|
+
return 'not_found';
|
|
1338
|
+
const phaseDir = path.join(phasesPath, dir.name);
|
|
1339
|
+
const files = require('fs').readdirSync(phaseDir);
|
|
1340
|
+
// Codex r17 P2: include bare PLAN.md / SUMMARY.md so single-plan
|
|
1341
|
+
// phases are not misclassified as `planned` (the rest of this
|
|
1342
|
+
// module's plan discovery already handles both forms).
|
|
1343
|
+
const plans = files.filter((f) => f === 'PLAN.md' || f.endsWith('-PLAN.md')).length;
|
|
1344
|
+
const summaries = files.filter((f) => f === 'SUMMARY.md' || f.endsWith('-SUMMARY.md')).length;
|
|
1345
|
+
if (plans === 0)
|
|
1346
|
+
return 'planned';
|
|
1347
|
+
if (summaries >= plans)
|
|
1348
|
+
return 'done';
|
|
1349
|
+
if (summaries > 0)
|
|
1350
|
+
return 'executing';
|
|
1351
|
+
return 'planned';
|
|
1352
|
+
}
|
|
1353
|
+
catch {
|
|
1354
|
+
return 'unknown';
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
// BFS from phaseNum to find all downstream
|
|
1358
|
+
const visited = new Set();
|
|
1359
|
+
const queue = [{ phase: phaseNum, depth: 0 }];
|
|
1360
|
+
const affected = [];
|
|
1361
|
+
while (queue.length > 0) {
|
|
1362
|
+
const { phase, depth } = queue.shift();
|
|
1363
|
+
if (visited.has(phase))
|
|
1364
|
+
continue;
|
|
1365
|
+
visited.add(phase);
|
|
1366
|
+
if (phase !== phaseNum) {
|
|
1367
|
+
const status = _phaseStatus(phase);
|
|
1368
|
+
const risk = status === 'executing' ? 'HIGH' : status === 'planned' ? 'MEDIUM' : 'LOW';
|
|
1369
|
+
affected.push({ phase, name: phaseNames.get(phase) || phase, status, risk, depth });
|
|
1370
|
+
}
|
|
1371
|
+
for (const child of deps.get(phase) || []) {
|
|
1372
|
+
if (!visited.has(child))
|
|
1373
|
+
queue.push({ phase: child, depth: depth + 1 });
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
affected.sort((a, b) => a.depth - b.depth || parseFloat(a.phase) - parseFloat(b.phase));
|
|
1377
|
+
const highRisk = affected.filter((p) => p.risk === 'HIGH').length;
|
|
1378
|
+
return { source_phase: phaseNum, affected_count: affected.length, high_risk_count: highRisk, affected_phases: affected };
|
|
1379
|
+
}
|
|
1380
|
+
/**
|
|
1381
|
+
* CLI command: Show which downstream phases are affected if the given phase changes.
|
|
1382
|
+
* @param cwd - Project root
|
|
1383
|
+
* @param phase - Phase number string
|
|
1384
|
+
* @param raw - Raw output flag
|
|
1385
|
+
*/
|
|
1386
|
+
function cmdImpact(cwd, phase, raw) {
|
|
1387
|
+
if (!phase) {
|
|
1388
|
+
error('Phase number required');
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
const result = computeDownstreamImpact(cwd, phase);
|
|
1392
|
+
const rawMsg = result.affected_count === 0
|
|
1393
|
+
? `Phase ${phase} has no downstream dependencies`
|
|
1394
|
+
: `Phase ${phase} impacts ${result.affected_count} phase(s) (${result.high_risk_count} HIGH risk)`;
|
|
1395
|
+
output(result, raw, rawMsg);
|
|
1396
|
+
}
|
|
1397
|
+
// ─── Exports ─────────────────────────────────────────────────────────────────
|
|
1398
|
+
module.exports = {
|
|
1399
|
+
cmdPhaseRiskAssessment,
|
|
1400
|
+
cmdCitationBacklinks,
|
|
1401
|
+
cmdEvalRegressionCheck,
|
|
1402
|
+
cmdPhaseTimeBudget,
|
|
1403
|
+
cmdConfigDiff,
|
|
1404
|
+
cmdPhaseReadiness,
|
|
1405
|
+
cmdMilestoneHealth,
|
|
1406
|
+
cmdDecisionTimeline,
|
|
1407
|
+
cmdImportKnowledge,
|
|
1408
|
+
cmdTodoDuplicates,
|
|
1409
|
+
cmdKnowhowList,
|
|
1410
|
+
cmdCitationGraph,
|
|
1411
|
+
cmdArtifactDAG,
|
|
1412
|
+
cmdBenchmarkReport,
|
|
1413
|
+
estimatePhaseTokens,
|
|
1414
|
+
cmdEstimatePhase,
|
|
1415
|
+
computeDownstreamImpact,
|
|
1416
|
+
cmdImpact,
|
|
1417
|
+
};
|
|
1418
|
+
//# sourceMappingURL=analysis.js.map
|