@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
package/lib/backend.ts
ADDED
|
@@ -0,0 +1,1252 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GRD Backend Detection, Model Resolution & Capabilities
|
|
5
|
+
*
|
|
6
|
+
* Detects which AI coding CLI is running (Claude Code, Codex CLI, Gemini CLI,
|
|
7
|
+
* OpenCode) via a detection waterfall: config override > env vars > filesystem
|
|
8
|
+
* clues > default. Resolves abstract model tiers (opus/sonnet/haiku) to
|
|
9
|
+
* backend-specific model names. Provides capability flags per backend.
|
|
10
|
+
*
|
|
11
|
+
* Supported backends (March 2026):
|
|
12
|
+
* - Claude Code v2.1.71 — Anthropic's native CLI (opus/sonnet/haiku tiers)
|
|
13
|
+
* - Codex CLI v0.112.0 — OpenAI's CLI (GPT-5.4, GPT-5.3-Codex-Spark, GPT-5.4-mini)
|
|
14
|
+
* - Gemini CLI v0.32.1 — Google's CLI (Gemini 3.1 Pro, 3.1 Flash, 3.1 Flash-Lite)
|
|
15
|
+
* - OpenCode v1.2.21 — Provider-agnostic CLI by anomalyco (actively maintained, 70K+ stars)
|
|
16
|
+
* - Superpowers — Plugin/skill layer that orchestrates any AI CLI backend with account rotation
|
|
17
|
+
* - GRD — Native mode using GRD's own commands/skills with the configured AI backend
|
|
18
|
+
*
|
|
19
|
+
* This module reads config.json directly with fs.readFileSync to avoid
|
|
20
|
+
* circular dependency with lib/utils.js (which will later import from here).
|
|
21
|
+
*
|
|
22
|
+
* Research basis:
|
|
23
|
+
* - Detection waterfall: .planning/research/multi-backend-detection.md (Section 2)
|
|
24
|
+
* - Model mappings: .planning/research/ARCHITECTURE.md
|
|
25
|
+
* - Capability flags: .planning/research/ARCHITECTURE.md
|
|
26
|
+
* - Pitfall avoidance: .planning/research/PITFALLS.md (P5: no AGENT env var)
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import type {
|
|
30
|
+
BackendId,
|
|
31
|
+
BackendCapabilities,
|
|
32
|
+
ModelTierMap,
|
|
33
|
+
ModelTier,
|
|
34
|
+
ModelProfileName,
|
|
35
|
+
EffortLevel,
|
|
36
|
+
AgentEffortProfiles,
|
|
37
|
+
WebMcpResult,
|
|
38
|
+
PlaywrightResult,
|
|
39
|
+
BackendAvailability,
|
|
40
|
+
TokenProfileName,
|
|
41
|
+
BudgetPressureLevel,
|
|
42
|
+
ComplexityLevel,
|
|
43
|
+
GrdConfig,
|
|
44
|
+
SchedulerConfig,
|
|
45
|
+
SuperpowersConfig,
|
|
46
|
+
BackendUsageState,
|
|
47
|
+
BudgetPressureThresholds,
|
|
48
|
+
AdapterBackendId,
|
|
49
|
+
} from './types';
|
|
50
|
+
|
|
51
|
+
const fs = require('fs');
|
|
52
|
+
const os = require('os');
|
|
53
|
+
const path = require('path');
|
|
54
|
+
const { execFileSync } = require('child_process');
|
|
55
|
+
|
|
56
|
+
// --- Constants ---------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* List of valid backend identifiers.
|
|
60
|
+
*/
|
|
61
|
+
const VALID_BACKENDS: readonly BackendId[] = [
|
|
62
|
+
'claude',
|
|
63
|
+
'codex',
|
|
64
|
+
'gemini',
|
|
65
|
+
'opencode',
|
|
66
|
+
'overstory',
|
|
67
|
+
'superpowers',
|
|
68
|
+
'grd',
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Default model name mappings per backend and tier.
|
|
73
|
+
* Each backend maps the abstract tiers (opus, sonnet, haiku) to concrete
|
|
74
|
+
* model identifiers recognized by that backend's CLI.
|
|
75
|
+
*/
|
|
76
|
+
const DEFAULT_BACKEND_MODELS: Record<BackendId, ModelTierMap> = {
|
|
77
|
+
claude: { opus: 'opus', sonnet: 'sonnet', haiku: 'haiku' },
|
|
78
|
+
codex: {
|
|
79
|
+
opus: 'gpt-5.4',
|
|
80
|
+
sonnet: 'gpt-5.3-codex-spark',
|
|
81
|
+
haiku: 'gpt-5.4-mini',
|
|
82
|
+
},
|
|
83
|
+
gemini: {
|
|
84
|
+
opus: 'gemini-3.1-pro',
|
|
85
|
+
sonnet: 'gemini-3.1-flash',
|
|
86
|
+
haiku: 'gemini-3.1-flash-lite',
|
|
87
|
+
},
|
|
88
|
+
opencode: {
|
|
89
|
+
opus: 'anthropic/claude-opus-4-6',
|
|
90
|
+
sonnet: 'anthropic/claude-sonnet-4-6',
|
|
91
|
+
haiku: 'anthropic/claude-haiku-4-5',
|
|
92
|
+
},
|
|
93
|
+
overstory: { opus: 'opus', sonnet: 'sonnet', haiku: 'haiku' },
|
|
94
|
+
superpowers: { opus: 'opus', sonnet: 'sonnet', haiku: 'haiku' },
|
|
95
|
+
grd: { opus: 'opus', sonnet: 'sonnet', haiku: 'haiku' },
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Capability flags per backend. Describes what orchestration features each
|
|
100
|
+
* backend supports. Used to degrade gracefully for backends with limited features.
|
|
101
|
+
*/
|
|
102
|
+
const BACKEND_CAPABILITIES: Record<BackendId, BackendCapabilities> = {
|
|
103
|
+
claude: {
|
|
104
|
+
subagents: true,
|
|
105
|
+
parallel: true,
|
|
106
|
+
teams: true,
|
|
107
|
+
hooks: true,
|
|
108
|
+
mcp: true,
|
|
109
|
+
native_worktree_isolation: true,
|
|
110
|
+
effort: true,
|
|
111
|
+
http_hooks: true,
|
|
112
|
+
cron: true,
|
|
113
|
+
smart_approvals: false,
|
|
114
|
+
plan_mode: false,
|
|
115
|
+
sandbox_gvisor: false,
|
|
116
|
+
sandbox_lxc: false,
|
|
117
|
+
mcp_elicitation: true,
|
|
118
|
+
model_overrides: true,
|
|
119
|
+
max_output_tokens: { default: 64000, upper_bound: 128000 },
|
|
120
|
+
},
|
|
121
|
+
codex: {
|
|
122
|
+
subagents: true,
|
|
123
|
+
parallel: true,
|
|
124
|
+
teams: true,
|
|
125
|
+
hooks: true,
|
|
126
|
+
mcp: true,
|
|
127
|
+
native_worktree_isolation: false,
|
|
128
|
+
effort: false,
|
|
129
|
+
http_hooks: false,
|
|
130
|
+
cron: false,
|
|
131
|
+
smart_approvals: true,
|
|
132
|
+
plan_mode: false,
|
|
133
|
+
sandbox_gvisor: false,
|
|
134
|
+
sandbox_lxc: false,
|
|
135
|
+
mcp_elicitation: false,
|
|
136
|
+
model_overrides: true,
|
|
137
|
+
max_output_tokens: null,
|
|
138
|
+
},
|
|
139
|
+
gemini: {
|
|
140
|
+
subagents: true,
|
|
141
|
+
parallel: true,
|
|
142
|
+
teams: false,
|
|
143
|
+
hooks: true,
|
|
144
|
+
mcp: true,
|
|
145
|
+
native_worktree_isolation: false,
|
|
146
|
+
effort: false,
|
|
147
|
+
http_hooks: false,
|
|
148
|
+
cron: false,
|
|
149
|
+
smart_approvals: false,
|
|
150
|
+
plan_mode: true,
|
|
151
|
+
sandbox_gvisor: true,
|
|
152
|
+
sandbox_lxc: false,
|
|
153
|
+
mcp_elicitation: false,
|
|
154
|
+
model_overrides: true,
|
|
155
|
+
max_output_tokens: null,
|
|
156
|
+
},
|
|
157
|
+
opencode: {
|
|
158
|
+
subagents: true,
|
|
159
|
+
parallel: true,
|
|
160
|
+
teams: false,
|
|
161
|
+
hooks: true,
|
|
162
|
+
mcp: true,
|
|
163
|
+
native_worktree_isolation: false,
|
|
164
|
+
effort: false,
|
|
165
|
+
http_hooks: false,
|
|
166
|
+
cron: false,
|
|
167
|
+
smart_approvals: false,
|
|
168
|
+
plan_mode: false,
|
|
169
|
+
sandbox_gvisor: false,
|
|
170
|
+
sandbox_lxc: false,
|
|
171
|
+
mcp_elicitation: false,
|
|
172
|
+
model_overrides: true,
|
|
173
|
+
max_output_tokens: null,
|
|
174
|
+
},
|
|
175
|
+
overstory: {
|
|
176
|
+
subagents: true,
|
|
177
|
+
parallel: true,
|
|
178
|
+
teams: true,
|
|
179
|
+
hooks: false,
|
|
180
|
+
mcp: true,
|
|
181
|
+
native_worktree_isolation: true,
|
|
182
|
+
effort: false,
|
|
183
|
+
http_hooks: false,
|
|
184
|
+
cron: false,
|
|
185
|
+
smart_approvals: false,
|
|
186
|
+
plan_mode: false,
|
|
187
|
+
sandbox_gvisor: false,
|
|
188
|
+
sandbox_lxc: false,
|
|
189
|
+
mcp_elicitation: false,
|
|
190
|
+
model_overrides: true,
|
|
191
|
+
max_output_tokens: null,
|
|
192
|
+
},
|
|
193
|
+
superpowers: {
|
|
194
|
+
subagents: true,
|
|
195
|
+
parallel: true,
|
|
196
|
+
teams: true,
|
|
197
|
+
hooks: true,
|
|
198
|
+
mcp: true,
|
|
199
|
+
native_worktree_isolation: true,
|
|
200
|
+
effort: true,
|
|
201
|
+
http_hooks: false,
|
|
202
|
+
cron: false,
|
|
203
|
+
smart_approvals: false,
|
|
204
|
+
plan_mode: false,
|
|
205
|
+
sandbox_gvisor: false,
|
|
206
|
+
sandbox_lxc: false,
|
|
207
|
+
mcp_elicitation: false,
|
|
208
|
+
model_overrides: true,
|
|
209
|
+
max_output_tokens: null,
|
|
210
|
+
},
|
|
211
|
+
grd: {
|
|
212
|
+
subagents: true,
|
|
213
|
+
parallel: true,
|
|
214
|
+
teams: true,
|
|
215
|
+
hooks: true,
|
|
216
|
+
mcp: true,
|
|
217
|
+
native_worktree_isolation: true,
|
|
218
|
+
effort: false,
|
|
219
|
+
http_hooks: false,
|
|
220
|
+
cron: false,
|
|
221
|
+
smart_approvals: false,
|
|
222
|
+
plan_mode: false,
|
|
223
|
+
sandbox_gvisor: false,
|
|
224
|
+
sandbox_lxc: false,
|
|
225
|
+
mcp_elicitation: false,
|
|
226
|
+
model_overrides: false,
|
|
227
|
+
max_output_tokens: null,
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
// --- Effort Level Profiles ---------------------------------------------------
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Default effort levels per agent and profile. Mirrors MODEL_PROFILES in utils.ts
|
|
235
|
+
* but for the effort dimension. Effort controls reasoning depth in backends that
|
|
236
|
+
* support it (currently Claude Code v2.1.68+).
|
|
237
|
+
*
|
|
238
|
+
* Design intent (from REQ-92):
|
|
239
|
+
* quality => planners/executors get high (deep reasoning), verifiers get medium
|
|
240
|
+
* balanced => planners get high, executors get medium, verifiers/lightweight agents get low
|
|
241
|
+
* budget => everything low (fast, minimal reasoning)
|
|
242
|
+
*/
|
|
243
|
+
const EFFORT_PROFILES: AgentEffortProfiles = {
|
|
244
|
+
'grd-planner': { quality: 'high', balanced: 'high', budget: 'low' },
|
|
245
|
+
'grd-roadmapper': { quality: 'high', balanced: 'medium', budget: 'low' },
|
|
246
|
+
'grd-executor': { quality: 'high', balanced: 'medium', budget: 'low' },
|
|
247
|
+
'grd-phase-researcher': { quality: 'high', balanced: 'medium', budget: 'low' },
|
|
248
|
+
'grd-project-researcher': { quality: 'high', balanced: 'medium', budget: 'low' },
|
|
249
|
+
'grd-research-synthesizer': { quality: 'medium', balanced: 'medium', budget: 'low' },
|
|
250
|
+
'grd-debugger': { quality: 'high', balanced: 'medium', budget: 'low' },
|
|
251
|
+
'grd-codebase-mapper': { quality: 'medium', balanced: 'low', budget: 'low' },
|
|
252
|
+
'grd-verifier': { quality: 'medium', balanced: 'low', budget: 'low' },
|
|
253
|
+
'grd-critique-agent': { quality: 'medium', balanced: 'low', budget: 'low' },
|
|
254
|
+
'grd-plan-checker': { quality: 'medium', balanced: 'medium', budget: 'low' },
|
|
255
|
+
'grd-integration-checker': { quality: 'medium', balanced: 'medium', budget: 'low' },
|
|
256
|
+
'grd-surveyor': { quality: 'medium', balanced: 'medium', budget: 'low' },
|
|
257
|
+
'grd-deep-diver': { quality: 'high', balanced: 'medium', budget: 'low' },
|
|
258
|
+
'grd-feasibility-analyst': { quality: 'high', balanced: 'medium', budget: 'low' },
|
|
259
|
+
'grd-eval-planner': { quality: 'high', balanced: 'medium', budget: 'low' },
|
|
260
|
+
'grd-eval-reporter': { quality: 'medium', balanced: 'medium', budget: 'low' },
|
|
261
|
+
'grd-product-owner': { quality: 'high', balanced: 'high', budget: 'low' },
|
|
262
|
+
'grd-baseline-assessor': { quality: 'medium', balanced: 'medium', budget: 'low' },
|
|
263
|
+
'grd-code-reviewer': { quality: 'high', balanced: 'medium', budget: 'low' },
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Resolve the effort level for a given agent type and model profile.
|
|
268
|
+
*
|
|
269
|
+
* Returns the effort level from EFFORT_PROFILES for the given agent and profile.
|
|
270
|
+
* Unknown agent types return 'medium' as a safe default. Unknown profiles
|
|
271
|
+
* fall back to 'balanced', then to 'medium'.
|
|
272
|
+
*
|
|
273
|
+
* @param agentType - Agent type key (e.g., 'grd-executor', 'grd-planner')
|
|
274
|
+
* @param profile - Model profile name ('quality', 'balanced', or 'budget')
|
|
275
|
+
* @returns The effort level string: 'low', 'medium', or 'high'
|
|
276
|
+
*/
|
|
277
|
+
function resolveEffortLevel(agentType: string, profile: ModelProfileName): EffortLevel {
|
|
278
|
+
const agentEffort = EFFORT_PROFILES[agentType];
|
|
279
|
+
if (!agentEffort) return 'medium';
|
|
280
|
+
return agentEffort[profile] || agentEffort['balanced'] || 'medium';
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// --- Internal Helpers --------------------------------------------------------
|
|
284
|
+
|
|
285
|
+
/** Detected model tier map: opus, sonnet, haiku each nullable. */
|
|
286
|
+
interface DetectedModels {
|
|
287
|
+
opus: string | null;
|
|
288
|
+
sonnet: string | null;
|
|
289
|
+
haiku: string | null;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/** Model cache entry with TTL tracking. */
|
|
293
|
+
interface ModelCacheEntry {
|
|
294
|
+
models: DetectedModels | null;
|
|
295
|
+
ts: number;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Read and parse .planning/config.json from cwd. Returns parsed object or null.
|
|
300
|
+
* Uses fs.readFileSync directly to avoid circular dependency with lib/utils.js.
|
|
301
|
+
*/
|
|
302
|
+
function readConfig(cwd: string): Record<string, unknown> | null {
|
|
303
|
+
try {
|
|
304
|
+
const configPath: string = path.join(cwd, '.planning', 'config.json');
|
|
305
|
+
const raw: string = fs.readFileSync(configPath, 'utf-8');
|
|
306
|
+
return JSON.parse(raw) as Record<string, unknown>;
|
|
307
|
+
} catch {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Check if any environment variable starts with a given prefix.
|
|
314
|
+
*/
|
|
315
|
+
function hasEnvPrefix(prefix: string): boolean {
|
|
316
|
+
return Object.keys(process.env).some((k) => k.startsWith(prefix));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Check if a file exists at a given path.
|
|
321
|
+
*/
|
|
322
|
+
function fileExists(filePath: string): boolean {
|
|
323
|
+
try {
|
|
324
|
+
fs.statSync(filePath);
|
|
325
|
+
return true;
|
|
326
|
+
} catch {
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// --- Exported Functions ------------------------------------------------------
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Detect which AI coding CLI backend is currently running.
|
|
335
|
+
*
|
|
336
|
+
* Detection waterfall (highest to lowest priority):
|
|
337
|
+
* 1. Config override: .planning/config.json `backend` field
|
|
338
|
+
* 2. Environment variables: CLAUDE_CODE_*, CODEX_HOME, GEMINI_CLI_HOME, OPENCODE
|
|
339
|
+
* 3. Filesystem clues: .claude-plugin/plugin.json, .codex/config.toml, etc.
|
|
340
|
+
* 4. Default: 'claude' (backward compatible)
|
|
341
|
+
*
|
|
342
|
+
* Note: The AGENT env var is NOT used for OpenCode detection per PITFALLS.md P5
|
|
343
|
+
* (too generic, may collide with other tools).
|
|
344
|
+
*
|
|
345
|
+
* @param cwd - Absolute path to the project root directory used for config and filesystem detection
|
|
346
|
+
* @returns The detected backend identifier (e.g. 'claude', 'codex', 'gemini', 'opencode')
|
|
347
|
+
*/
|
|
348
|
+
function detectBackend(cwd: string): BackendId {
|
|
349
|
+
// Step 1: Config override (highest priority)
|
|
350
|
+
const config = readConfig(cwd);
|
|
351
|
+
if (config && config.backend && VALID_BACKENDS.includes(config.backend as BackendId)) {
|
|
352
|
+
return config.backend as BackendId;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Step 2: Environment variable detection
|
|
356
|
+
// Superpowers detection (highest env priority — orchestrates other backends)
|
|
357
|
+
if (process.env.SUPERPOWERS_HOME || process.env.SUPERPOWERS_SESSION) return 'superpowers';
|
|
358
|
+
// Overstory detection (before Claude — takes priority when both present)
|
|
359
|
+
if (process.env.OVERSTORY_HOME || process.env.OVERSTORY_SESSION) return 'overstory';
|
|
360
|
+
if (hasEnvPrefix('CLAUDE_CODE_')) return 'claude';
|
|
361
|
+
// CODEX_THREAD_ID: may be deprecated in newer Codex CLI versions (no docs mention
|
|
362
|
+
// as of March 2026), but kept for backward compatibility with older installations.
|
|
363
|
+
if (process.env.CODEX_HOME || process.env.CODEX_THREAD_ID) return 'codex';
|
|
364
|
+
if (process.env.GEMINI_CLI_HOME) return 'gemini';
|
|
365
|
+
// OpenCode: actively maintained under anomalyco/opencode (original opencode-ai
|
|
366
|
+
// repo archived Sept 2025). OPENCODE_PID is NOT used — it's a process management
|
|
367
|
+
// var, not a presence indicator.
|
|
368
|
+
if (process.env.OPENCODE) return 'opencode';
|
|
369
|
+
|
|
370
|
+
// Step 3: Filesystem clues
|
|
371
|
+
if (fileExists(path.join(cwd, '.superpowers', 'config.json'))) return 'superpowers';
|
|
372
|
+
if (fileExists(path.join(cwd, '.overstory', 'config.yaml'))) return 'overstory';
|
|
373
|
+
if (fileExists(path.join(cwd, '.claude-plugin', 'plugin.json'))) return 'claude';
|
|
374
|
+
if (fileExists(path.join(cwd, '.codex', 'config.toml'))) return 'codex';
|
|
375
|
+
if (fileExists(path.join(cwd, '.gemini', 'settings.json'))) return 'gemini';
|
|
376
|
+
if (fileExists(path.join(cwd, 'opencode.json'))) return 'opencode';
|
|
377
|
+
|
|
378
|
+
// Step 4: Default (backward compatible)
|
|
379
|
+
return 'claude';
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// --- Dynamic Model Detection -------------------------------------------------
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Parse `opencode models` stdout into tier-classified model map.
|
|
386
|
+
* Classifies each model ID by keyword patterns:
|
|
387
|
+
* opus tier: /opus/i, /pro/i (non-flash)
|
|
388
|
+
* sonnet tier: /sonnet/i
|
|
389
|
+
* haiku tier: /haiku/i, /flash/i, /mini/i, /spark/i
|
|
390
|
+
*
|
|
391
|
+
* @param stdout - Raw stdout string from the `opencode models` CLI command
|
|
392
|
+
* @returns A DetectedModels map with opus/sonnet/haiku slots filled where matched, or null if no models were matched
|
|
393
|
+
*/
|
|
394
|
+
function parseOpenCodeModels(stdout: string): DetectedModels | null {
|
|
395
|
+
if (!stdout || typeof stdout !== 'string') return null;
|
|
396
|
+
|
|
397
|
+
const lines: string[] = stdout
|
|
398
|
+
.split('\n')
|
|
399
|
+
.map((l) => l.trim())
|
|
400
|
+
.filter((l) => l && !l.startsWith('Available') && !l.startsWith('---') && !l.startsWith('#'));
|
|
401
|
+
|
|
402
|
+
const result: DetectedModels = { opus: null, sonnet: null, haiku: null };
|
|
403
|
+
let matched = false;
|
|
404
|
+
|
|
405
|
+
for (const line of lines) {
|
|
406
|
+
const model: string | undefined = line.split(/\s+/)[0];
|
|
407
|
+
if (!model || !model.includes('/')) continue;
|
|
408
|
+
|
|
409
|
+
if (/opus/i.test(model)) {
|
|
410
|
+
if (!result.opus) {
|
|
411
|
+
result.opus = model;
|
|
412
|
+
matched = true;
|
|
413
|
+
}
|
|
414
|
+
} else if (/sonnet/i.test(model)) {
|
|
415
|
+
if (!result.sonnet) {
|
|
416
|
+
result.sonnet = model;
|
|
417
|
+
matched = true;
|
|
418
|
+
}
|
|
419
|
+
} else if (/haiku/i.test(model)) {
|
|
420
|
+
if (!result.haiku) {
|
|
421
|
+
result.haiku = model;
|
|
422
|
+
matched = true;
|
|
423
|
+
}
|
|
424
|
+
} else if (/pro/i.test(model) && !/flash/i.test(model)) {
|
|
425
|
+
if (!result.opus) {
|
|
426
|
+
result.opus = model;
|
|
427
|
+
matched = true;
|
|
428
|
+
}
|
|
429
|
+
} else if (/flash/i.test(model) || /mini/i.test(model) || /spark/i.test(model)) {
|
|
430
|
+
if (!result.haiku) {
|
|
431
|
+
result.haiku = model;
|
|
432
|
+
matched = true;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return matched ? result : null;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Run backend-specific CLI command to detect available models.
|
|
442
|
+
* Currently only OpenCode supports programmatic model listing.
|
|
443
|
+
*/
|
|
444
|
+
function detectModels(backend: string, cwd?: string): DetectedModels | null {
|
|
445
|
+
if (backend !== 'opencode') return null;
|
|
446
|
+
|
|
447
|
+
const effectiveCwd: string = cwd || process.cwd();
|
|
448
|
+
const cfg = readConfig(effectiveCwd);
|
|
449
|
+
const timeouts = cfg?.timeouts as Record<string, unknown> | undefined;
|
|
450
|
+
const timeout: number =
|
|
451
|
+
typeof timeouts?.backend_detect_ms === 'number' ? timeouts.backend_detect_ms : 10000;
|
|
452
|
+
try {
|
|
453
|
+
const stdout: string = execFileSync('opencode', ['models'], {
|
|
454
|
+
cwd: effectiveCwd,
|
|
455
|
+
timeout,
|
|
456
|
+
encoding: 'utf-8',
|
|
457
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
458
|
+
});
|
|
459
|
+
return parseOpenCodeModels(stdout);
|
|
460
|
+
} catch {
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const _modelCache: Map<string, ModelCacheEntry> = new Map();
|
|
466
|
+
const MODEL_CACHE_TTL_MS: number = 5 * 60 * 1000;
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Get cached detected models for a backend, refreshing if TTL expired.
|
|
470
|
+
*/
|
|
471
|
+
function getCachedModels(backend: string, cwd?: string): DetectedModels | null {
|
|
472
|
+
const entry: ModelCacheEntry | undefined = _modelCache.get(backend);
|
|
473
|
+
const now: number = Date.now();
|
|
474
|
+
if (entry && now - entry.ts < MODEL_CACHE_TTL_MS) {
|
|
475
|
+
return entry.models;
|
|
476
|
+
}
|
|
477
|
+
const models: DetectedModels | null = detectModels(backend, cwd);
|
|
478
|
+
_modelCache.set(backend, { models, ts: now });
|
|
479
|
+
return models;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Clear the model detection cache. Exported for testing.
|
|
484
|
+
*/
|
|
485
|
+
function clearModelCache(): void {
|
|
486
|
+
_modelCache.clear();
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Resolve an abstract model tier to a backend-specific model name.
|
|
491
|
+
*
|
|
492
|
+
* Checks config.backend_models for user overrides first, then falls back
|
|
493
|
+
* to DEFAULT_BACKEND_MODELS. Unknown backends fall back to claude mappings.
|
|
494
|
+
* Unknown tiers return undefined.
|
|
495
|
+
*
|
|
496
|
+
* @param backend - The backend identifier (e.g. 'claude', 'codex', 'gemini', 'opencode')
|
|
497
|
+
* @param tier - The abstract model tier to resolve ('opus', 'sonnet', or 'haiku')
|
|
498
|
+
* @param config - Optional parsed config.json object used for user-defined backend_models overrides
|
|
499
|
+
* @param cwd - Optional project root path used for dynamic model detection (opencode only)
|
|
500
|
+
* @returns The backend-specific model name string, or undefined if the tier is not mapped
|
|
501
|
+
*/
|
|
502
|
+
function resolveBackendModel(
|
|
503
|
+
backend: string,
|
|
504
|
+
tier: ModelTier,
|
|
505
|
+
config?: Record<string, unknown>,
|
|
506
|
+
cwd?: string
|
|
507
|
+
): string | undefined {
|
|
508
|
+
// Check user override from config (highest priority)
|
|
509
|
+
if (config && config.backend_models) {
|
|
510
|
+
const backendModelsConfig = config.backend_models as Record<string, Record<string, string>>;
|
|
511
|
+
const backendOverrides = backendModelsConfig[backend];
|
|
512
|
+
if (backendOverrides && backendOverrides[tier] !== undefined) {
|
|
513
|
+
return backendOverrides[tier];
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Check dynamically detected models (middle priority)
|
|
518
|
+
if (cwd) {
|
|
519
|
+
const detected: DetectedModels | null = getCachedModels(backend, cwd);
|
|
520
|
+
if (detected && detected[tier]) {
|
|
521
|
+
return detected[tier];
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Use built-in defaults, falling back to claude for unknown backends
|
|
526
|
+
const backendModels: ModelTierMap =
|
|
527
|
+
DEFAULT_BACKEND_MODELS[backend as BackendId] || DEFAULT_BACKEND_MODELS.claude;
|
|
528
|
+
return backendModels[tier];
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Get capability flags for a backend.
|
|
533
|
+
*
|
|
534
|
+
* Returns an object describing what orchestration features the backend supports.
|
|
535
|
+
* Unknown backends get minimal capabilities (all false) to prevent
|
|
536
|
+
* accidentally enabling features like native_worktree_isolation or effort.
|
|
537
|
+
*
|
|
538
|
+
* @param backend - The backend identifier (e.g. 'claude', 'codex', 'gemini', 'opencode')
|
|
539
|
+
* @returns A BackendCapabilities object describing which orchestration features are supported
|
|
540
|
+
*/
|
|
541
|
+
function getBackendCapabilities(backend: string): BackendCapabilities {
|
|
542
|
+
if (BACKEND_CAPABILITIES[backend as BackendId]) {
|
|
543
|
+
return BACKEND_CAPABILITIES[backend as BackendId];
|
|
544
|
+
}
|
|
545
|
+
// Unknown backend: warn and return minimal capabilities
|
|
546
|
+
process.stderr.write(`[grd] WARNING: unknown backend "${backend}", using minimal capabilities\n`);
|
|
547
|
+
return {
|
|
548
|
+
subagents: true,
|
|
549
|
+
parallel: false,
|
|
550
|
+
teams: false,
|
|
551
|
+
hooks: false,
|
|
552
|
+
mcp: false,
|
|
553
|
+
native_worktree_isolation: false,
|
|
554
|
+
effort: false,
|
|
555
|
+
http_hooks: false,
|
|
556
|
+
cron: false,
|
|
557
|
+
smart_approvals: false,
|
|
558
|
+
plan_mode: false,
|
|
559
|
+
sandbox_gvisor: false,
|
|
560
|
+
sandbox_lxc: false,
|
|
561
|
+
mcp_elicitation: false,
|
|
562
|
+
model_overrides: false,
|
|
563
|
+
max_output_tokens: null,
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// --- WebMCP Detection --------------------------------------------------------
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Detect whether Chrome DevTools MCP is available.
|
|
571
|
+
*
|
|
572
|
+
* Detection waterfall (highest to lowest priority):
|
|
573
|
+
* 1. Config override: .planning/config.json `webmcp.enabled` field
|
|
574
|
+
* 2. Environment variables: CHROME_DEVTOOLS_MCP, WEBMCP_AVAILABLE
|
|
575
|
+
* 3. Claude Code MCP settings: ~/.claude.json `mcpServers` key
|
|
576
|
+
* 4. Default: not available
|
|
577
|
+
*
|
|
578
|
+
* @param cwd - Absolute path to the project root directory used for config-based detection
|
|
579
|
+
* @returns A WebMcpResult indicating availability, the detection source, and an optional reason when unavailable
|
|
580
|
+
*/
|
|
581
|
+
function detectWebMcp(cwd: string): WebMcpResult {
|
|
582
|
+
// Step 1: Config override (highest priority)
|
|
583
|
+
const config = readConfig(cwd);
|
|
584
|
+
if (config && config.webmcp && typeof config.webmcp === 'object') {
|
|
585
|
+
const webmcp = config.webmcp as Record<string, unknown>;
|
|
586
|
+
if (typeof webmcp.enabled === 'boolean') {
|
|
587
|
+
if (webmcp.enabled) {
|
|
588
|
+
return { available: true, source: 'config' };
|
|
589
|
+
}
|
|
590
|
+
return {
|
|
591
|
+
available: false,
|
|
592
|
+
source: 'config',
|
|
593
|
+
reason: 'Disabled via config',
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Step 2: Environment variable check
|
|
599
|
+
const chromeDevToolsMcp: string | undefined = process.env.CHROME_DEVTOOLS_MCP;
|
|
600
|
+
const webmcpAvailable: string | undefined = process.env.WEBMCP_AVAILABLE;
|
|
601
|
+
|
|
602
|
+
if (chromeDevToolsMcp !== undefined) {
|
|
603
|
+
if (chromeDevToolsMcp === 'true' || chromeDevToolsMcp === '1') {
|
|
604
|
+
return { available: true, source: 'env' };
|
|
605
|
+
}
|
|
606
|
+
if (chromeDevToolsMcp === 'false' || chromeDevToolsMcp === '0') {
|
|
607
|
+
return {
|
|
608
|
+
available: false,
|
|
609
|
+
source: 'env',
|
|
610
|
+
reason: 'Disabled via environment variable',
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (webmcpAvailable !== undefined) {
|
|
616
|
+
if (webmcpAvailable === 'true' || webmcpAvailable === '1') {
|
|
617
|
+
return { available: true, source: 'env' };
|
|
618
|
+
}
|
|
619
|
+
if (webmcpAvailable === 'false' || webmcpAvailable === '0') {
|
|
620
|
+
return {
|
|
621
|
+
available: false,
|
|
622
|
+
source: 'env',
|
|
623
|
+
reason: 'Disabled via environment variable',
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Step 3: Claude Code MCP settings check (~/.claude.json)
|
|
629
|
+
try {
|
|
630
|
+
const homeDir: string = os.homedir();
|
|
631
|
+
const claudeConfigPath: string = path.join(homeDir, '.claude.json');
|
|
632
|
+
const raw: string = fs.readFileSync(claudeConfigPath, 'utf-8');
|
|
633
|
+
const claudeConfig = JSON.parse(raw) as Record<string, unknown>;
|
|
634
|
+
if (claudeConfig && claudeConfig.mcpServers) {
|
|
635
|
+
const serverNames: string[] = Object.keys(claudeConfig.mcpServers as Record<string, unknown>);
|
|
636
|
+
const hasBrowserMcp: boolean = serverNames.some((name) =>
|
|
637
|
+
/chrome|devtools|playwright|browser/i.test(name)
|
|
638
|
+
);
|
|
639
|
+
if (hasBrowserMcp) {
|
|
640
|
+
return { available: true, source: 'mcp-config' };
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
} catch {
|
|
644
|
+
// ~/.claude.json not found or malformed -- continue to default
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// Step 4: Default
|
|
648
|
+
return {
|
|
649
|
+
available: false,
|
|
650
|
+
source: 'default',
|
|
651
|
+
reason: 'Chrome DevTools MCP not detected in config, environment, or MCP server settings',
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// --- Playwright Detection ----------------------------------------------------
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Detect whether Playwright MCP is available.
|
|
659
|
+
*
|
|
660
|
+
* Detection waterfall (highest to lowest priority):
|
|
661
|
+
* 1. Config override: .planning/config.json `playwright.enabled` field
|
|
662
|
+
* 2. Environment variable: PLAYWRIGHT_AVAILABLE
|
|
663
|
+
* 3. Claude Code MCP settings: ~/.claude.json `mcpServers` key (playwright name match)
|
|
664
|
+
* 4. Default: not available
|
|
665
|
+
*
|
|
666
|
+
* Mirrors the detectWebMcp() pattern exactly — same try/catch around ~/.claude.json,
|
|
667
|
+
* same config reading via readConfig(cwd), same env var parsing.
|
|
668
|
+
*
|
|
669
|
+
* @param cwd - Absolute path to the project root directory used for config-based detection
|
|
670
|
+
* @returns A PlaywrightResult indicating availability, the detection source, and an optional reason when unavailable
|
|
671
|
+
*/
|
|
672
|
+
function detectPlaywright(cwd: string): PlaywrightResult {
|
|
673
|
+
// Step 1: Config override (highest priority)
|
|
674
|
+
const config = readConfig(cwd);
|
|
675
|
+
if (config && config.playwright && typeof config.playwright === 'object') {
|
|
676
|
+
const playwright = config.playwright as Record<string, unknown>;
|
|
677
|
+
if (typeof playwright.enabled === 'boolean') {
|
|
678
|
+
if (playwright.enabled) {
|
|
679
|
+
return { available: true, source: 'config' };
|
|
680
|
+
}
|
|
681
|
+
return {
|
|
682
|
+
available: false,
|
|
683
|
+
source: 'config',
|
|
684
|
+
reason: 'Disabled via config',
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// Step 2: Environment variable check
|
|
690
|
+
const playwrightAvailable: string | undefined = process.env.PLAYWRIGHT_AVAILABLE;
|
|
691
|
+
|
|
692
|
+
if (playwrightAvailable !== undefined) {
|
|
693
|
+
if (playwrightAvailable === 'true' || playwrightAvailable === '1') {
|
|
694
|
+
return { available: true, source: 'env' };
|
|
695
|
+
}
|
|
696
|
+
if (playwrightAvailable === 'false' || playwrightAvailable === '0') {
|
|
697
|
+
return {
|
|
698
|
+
available: false,
|
|
699
|
+
source: 'env',
|
|
700
|
+
reason: 'Disabled via environment variable',
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// Step 3: Claude Code MCP settings check (~/.claude.json)
|
|
706
|
+
try {
|
|
707
|
+
const homeDir: string = os.homedir();
|
|
708
|
+
const claudeConfigPath: string = path.join(homeDir, '.claude.json');
|
|
709
|
+
const raw: string = fs.readFileSync(claudeConfigPath, 'utf-8');
|
|
710
|
+
const claudeConfig = JSON.parse(raw) as Record<string, unknown>;
|
|
711
|
+
if (claudeConfig && claudeConfig.mcpServers) {
|
|
712
|
+
const serverNames: string[] = Object.keys(claudeConfig.mcpServers as Record<string, unknown>);
|
|
713
|
+
const hasPlaywrightMcp: boolean = serverNames.some((name) => /playwright/i.test(name));
|
|
714
|
+
if (hasPlaywrightMcp) {
|
|
715
|
+
return { available: true, source: 'mcp-config' };
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
} catch {
|
|
719
|
+
// ~/.claude.json not found or malformed -- continue to default
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// Step 4: Default
|
|
723
|
+
return {
|
|
724
|
+
available: false,
|
|
725
|
+
source: 'default',
|
|
726
|
+
reason: 'Playwright MCP not detected in config, environment, or MCP server settings',
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// --- Backend Availability Detection ------------------------------------------
|
|
731
|
+
|
|
732
|
+
/** Cache entry for detectAvailableBackends result. */
|
|
733
|
+
interface AvailabilityCacheEntry {
|
|
734
|
+
result: Record<BackendId, BackendAvailability>;
|
|
735
|
+
ts: number;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
let _availabilityCache: AvailabilityCacheEntry | null = null;
|
|
739
|
+
const AVAILABILITY_CACHE_TTL_MS: number = 5 * 60 * 1000;
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* Dispatchable backends — the four CLIs that discussion.ts can spawn directly.
|
|
743
|
+
* Meta-backends (overstory, superpowers, grd) are probed as unavailable.
|
|
744
|
+
*/
|
|
745
|
+
const DISPATCHABLE_BACKENDS: readonly string[] = ['claude', 'codex', 'gemini', 'opencode'];
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Environment variable that controls the config directory for each backend CLI.
|
|
749
|
+
* When set, the CLI uses the specified directory for auth/credentials instead of default.
|
|
750
|
+
*/
|
|
751
|
+
const BACKEND_CONFIG_ENV: Record<string, string> = {
|
|
752
|
+
claude: 'CLAUDE_CONFIG_DIR',
|
|
753
|
+
codex: 'CODEX_HOME',
|
|
754
|
+
gemini: 'GEMINI_CLI_HOME',
|
|
755
|
+
opencode: 'OPENCODE_CONFIG_DIR',
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* Files that prove a config directory has valid auth/credentials for a backend.
|
|
760
|
+
* Must be actual credential files, not files created by a bare first-run.
|
|
761
|
+
*
|
|
762
|
+
* Paths are relative to the config dir. Use nested paths for backends that
|
|
763
|
+
* store auth in a subdirectory (e.g. gemini stores creds in <dir>/.gemini/).
|
|
764
|
+
*/
|
|
765
|
+
const BACKEND_AUTH_MARKERS: Record<string, string[]> = {
|
|
766
|
+
claude: ['credentials.json', '.credentials.json', 'settings.json'],
|
|
767
|
+
codex: ['auth.json'],
|
|
768
|
+
gemini: ['.gemini/oauth_creds.json', '.gemini/google_accounts.json'],
|
|
769
|
+
opencode: ['auth.json', 'config.json'],
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
/** Cached config dir discovery result. */
|
|
773
|
+
let _configDirCache: Record<string, string | null> | null = null;
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Discover the actual config directory for each backend by scanning the home
|
|
777
|
+
* directory for directories matching ~/.<backend>* that contain auth marker files.
|
|
778
|
+
*
|
|
779
|
+
* Priority:
|
|
780
|
+
* 1. Current env var (e.g. CLAUDE_CONFIG_DIR already set)
|
|
781
|
+
* 2. First ~/.<backend>-* directory containing an auth marker file
|
|
782
|
+
* 3. Default ~/.<backend> if it contains an auth marker file
|
|
783
|
+
* 4. null (no config dir found — use backend's default)
|
|
784
|
+
*/
|
|
785
|
+
function discoverBackendConfigDirs(): Record<string, string | null> {
|
|
786
|
+
if (_configDirCache) return _configDirCache;
|
|
787
|
+
|
|
788
|
+
const homeDir: string = os.homedir();
|
|
789
|
+
const result: Record<string, string | null> = {};
|
|
790
|
+
|
|
791
|
+
for (const backend of DISPATCHABLE_BACKENDS) {
|
|
792
|
+
const envVar = BACKEND_CONFIG_ENV[backend];
|
|
793
|
+
const markers = BACKEND_AUTH_MARKERS[backend];
|
|
794
|
+
|
|
795
|
+
// 1. Check if env var is already set
|
|
796
|
+
if (envVar && process.env[envVar]) {
|
|
797
|
+
result[backend] = process.env[envVar] as string;
|
|
798
|
+
continue;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// 2. Scan home directory for matching config dirs
|
|
802
|
+
let found: string | null = null;
|
|
803
|
+
try {
|
|
804
|
+
const entries: string[] = fs.readdirSync(homeDir);
|
|
805
|
+
// Collect candidates: ~/.<backend>-* first (custom profiles), then ~/.<backend> (default)
|
|
806
|
+
const profileDirs: string[] = entries
|
|
807
|
+
.filter((e: string) => e.startsWith(`.${backend}-`))
|
|
808
|
+
.sort();
|
|
809
|
+
const defaultDir: string[] = entries.filter((e: string) => e === `.${backend}`);
|
|
810
|
+
const candidates: string[] = [...profileDirs, ...defaultDir]
|
|
811
|
+
.map((e: string) => path.join(homeDir, e))
|
|
812
|
+
.filter((p: string) => {
|
|
813
|
+
try {
|
|
814
|
+
return fs.statSync(p).isDirectory();
|
|
815
|
+
} catch {
|
|
816
|
+
return false;
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
// Check each candidate for auth marker files
|
|
821
|
+
for (const candidate of candidates) {
|
|
822
|
+
const hasAuth = markers.some((marker: string) => {
|
|
823
|
+
try {
|
|
824
|
+
return fs.statSync(path.join(candidate, marker)).isFile();
|
|
825
|
+
} catch {
|
|
826
|
+
return false;
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
if (hasAuth) {
|
|
830
|
+
found = candidate;
|
|
831
|
+
break;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
} catch {
|
|
835
|
+
// Home dir not readable — skip
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
result[backend] = found;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
_configDirCache = result;
|
|
842
|
+
return result;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* Clear the config dir discovery cache. Exported for testing.
|
|
847
|
+
*/
|
|
848
|
+
function clearConfigDirCache(): void {
|
|
849
|
+
_configDirCache = null;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/**
|
|
853
|
+
* Build the environment variables needed to run a backend CLI with the correct
|
|
854
|
+
* config directory. Returns a copy of process.env with the override applied.
|
|
855
|
+
*/
|
|
856
|
+
function buildBackendEnv(backend: string): Record<string, string | undefined> {
|
|
857
|
+
const env: Record<string, string | undefined> = { ...process.env };
|
|
858
|
+
|
|
859
|
+
// Strip Claude session env vars so subprocess doesn't detect nested invocation
|
|
860
|
+
for (const key of Object.keys(env)) {
|
|
861
|
+
if (key === 'CLAUDECODE' || key.startsWith('CLAUDE_CODE_') || key.startsWith('CLAUDECODE_')) {
|
|
862
|
+
delete env[key];
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
const configDirs = discoverBackendConfigDirs();
|
|
867
|
+
const configDir = configDirs[backend];
|
|
868
|
+
if (!configDir) return env;
|
|
869
|
+
|
|
870
|
+
const envVar = BACKEND_CONFIG_ENV[backend];
|
|
871
|
+
if (!envVar) return env;
|
|
872
|
+
|
|
873
|
+
return { ...env, [envVar]: configDir };
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* Probe which AI CLI backends are available on PATH.
|
|
878
|
+
*
|
|
879
|
+
* For each of the four dispatchable backends (claude, codex, gemini, opencode),
|
|
880
|
+
* runs `<binary> --version` with a 5-second timeout. Success means available.
|
|
881
|
+
* Meta-backends (overstory, superpowers, grd) are always marked unavailable here.
|
|
882
|
+
*
|
|
883
|
+
* Result is cached for 5 minutes (AVAILABILITY_CACHE_TTL_MS). Call
|
|
884
|
+
* clearAvailabilityCache() to force re-detection in tests.
|
|
885
|
+
*
|
|
886
|
+
* @param cwd - Optional working directory for subprocess (defaults to process.cwd())
|
|
887
|
+
* @returns A map of BackendId to BackendAvailability for all known backends
|
|
888
|
+
*/
|
|
889
|
+
function detectAvailableBackends(cwd?: string): Record<BackendId, BackendAvailability> {
|
|
890
|
+
const now: number = Date.now();
|
|
891
|
+
if (_availabilityCache && now - _availabilityCache.ts < AVAILABILITY_CACHE_TTL_MS) {
|
|
892
|
+
return _availabilityCache.result;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
const effectiveCwd: string = cwd || process.cwd();
|
|
896
|
+
const unavailable: BackendAvailability = { available: false, version: null };
|
|
897
|
+
|
|
898
|
+
const probeCfg = readConfig(effectiveCwd);
|
|
899
|
+
const probeTimeouts = probeCfg?.timeouts as Record<string, unknown> | undefined;
|
|
900
|
+
const probeTimeout: number =
|
|
901
|
+
typeof probeTimeouts?.backend_probe_ms === 'number' ? probeTimeouts.backend_probe_ms : 5000;
|
|
902
|
+
|
|
903
|
+
const result: Record<BackendId, BackendAvailability> = {
|
|
904
|
+
claude: unavailable,
|
|
905
|
+
codex: unavailable,
|
|
906
|
+
gemini: unavailable,
|
|
907
|
+
opencode: unavailable,
|
|
908
|
+
overstory: unavailable,
|
|
909
|
+
superpowers: unavailable,
|
|
910
|
+
grd: unavailable,
|
|
911
|
+
};
|
|
912
|
+
|
|
913
|
+
for (const backend of DISPATCHABLE_BACKENDS) {
|
|
914
|
+
try {
|
|
915
|
+
const stdout: string = execFileSync(backend, ['--version'], {
|
|
916
|
+
cwd: effectiveCwd,
|
|
917
|
+
timeout: probeTimeout,
|
|
918
|
+
encoding: 'utf-8',
|
|
919
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
920
|
+
env: buildBackendEnv(backend),
|
|
921
|
+
});
|
|
922
|
+
result[backend as BackendId] = {
|
|
923
|
+
available: true,
|
|
924
|
+
version: stdout.trim().split('\n')[0] || null,
|
|
925
|
+
};
|
|
926
|
+
} catch {
|
|
927
|
+
result[backend as BackendId] = { available: false, version: null };
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
_availabilityCache = { result, ts: now };
|
|
932
|
+
return result;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
/**
|
|
936
|
+
* Clear the availability detection cache. Exported for testing.
|
|
937
|
+
*/
|
|
938
|
+
function clearAvailabilityCache(): void {
|
|
939
|
+
_availabilityCache = null;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// ─── Spec 4: adaptive model-tier routing ──────────────────────────────────
|
|
943
|
+
|
|
944
|
+
type _ModelTier = 'opus' | 'sonnet' | 'haiku';
|
|
945
|
+
const _TIER_ORDER: _ModelTier[] = ['opus', 'sonnet', 'haiku'];
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* Looks up how many tiers to downgrade given the profile, pressure,
|
|
949
|
+
* and complexity. Returns 0, 1, or 2. Pure function — table-driven.
|
|
950
|
+
*/
|
|
951
|
+
function _lookupDowngradeCount(
|
|
952
|
+
profile: TokenProfileName,
|
|
953
|
+
pressure: BudgetPressureLevel,
|
|
954
|
+
complexity: ComplexityLevel
|
|
955
|
+
): number {
|
|
956
|
+
// quality: only downgrade on critical pressure
|
|
957
|
+
if (profile === 'quality') {
|
|
958
|
+
if (pressure === 'critical') return 1;
|
|
959
|
+
return 0;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
// balanced: moderate adaptive downgrade
|
|
963
|
+
if (profile === 'balanced') {
|
|
964
|
+
if (pressure === 'none') {
|
|
965
|
+
if (complexity === 'low') return 1;
|
|
966
|
+
return 0;
|
|
967
|
+
}
|
|
968
|
+
if (pressure === 'warning') {
|
|
969
|
+
if (complexity === 'high') return 0;
|
|
970
|
+
return 1;
|
|
971
|
+
}
|
|
972
|
+
if (pressure === 'high') {
|
|
973
|
+
if (complexity === 'high') return 0;
|
|
974
|
+
if (complexity === 'medium') return 1;
|
|
975
|
+
return 2; // low
|
|
976
|
+
}
|
|
977
|
+
if (pressure === 'critical') {
|
|
978
|
+
if (complexity === 'high') return 1;
|
|
979
|
+
return 2;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// frugal: aggressive downgrade
|
|
984
|
+
if (profile === 'frugal') {
|
|
985
|
+
if (pressure === 'none') {
|
|
986
|
+
if (complexity === 'high') return 0;
|
|
987
|
+
return 1; // medium or low
|
|
988
|
+
}
|
|
989
|
+
if (pressure === 'warning') {
|
|
990
|
+
if (complexity === 'low') return 2;
|
|
991
|
+
return 1;
|
|
992
|
+
}
|
|
993
|
+
// high or critical
|
|
994
|
+
return 2;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
return 0;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
/**
|
|
1001
|
+
* Applies a downgrade count to a base tier, floored at the lowest tier.
|
|
1002
|
+
* Returns the base tier unchanged if it's not in _TIER_ORDER (passthrough).
|
|
1003
|
+
*/
|
|
1004
|
+
function _applyDowngrade(baseTier: _ModelTier, count: number): _ModelTier {
|
|
1005
|
+
const baseIndex = _TIER_ORDER.indexOf(baseTier);
|
|
1006
|
+
if (baseIndex === -1) return baseTier;
|
|
1007
|
+
const targetIndex = Math.min(baseIndex + count, _TIER_ORDER.length - 1);
|
|
1008
|
+
return _TIER_ORDER[targetIndex];
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
/**
|
|
1012
|
+
* Applies an upgrade count to a base tier, capped at the strongest tier.
|
|
1013
|
+
* Symmetric to _applyDowngrade. Used for verify-fail retry escalation
|
|
1014
|
+
* (Tier-2 #5 of the Ouroboros integration) — when a dispatch is a retry
|
|
1015
|
+
* after a verification failure, the agent runs at a stronger tier than
|
|
1016
|
+
* the base, capped at opus. Returns the base tier unchanged if it's not
|
|
1017
|
+
* in _TIER_ORDER (passthrough).
|
|
1018
|
+
*
|
|
1019
|
+
* Note: _TIER_ORDER is ordered strongest-to-weakest (opus, sonnet, haiku),
|
|
1020
|
+
* so "upgrade" decreases the index.
|
|
1021
|
+
*/
|
|
1022
|
+
function _applyUpgrade(baseTier: _ModelTier, count: number): _ModelTier {
|
|
1023
|
+
const baseIndex = _TIER_ORDER.indexOf(baseTier);
|
|
1024
|
+
if (baseIndex === -1) return baseTier;
|
|
1025
|
+
const targetIndex = Math.max(baseIndex - count, 0);
|
|
1026
|
+
return _TIER_ORDER[targetIndex];
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
/**
|
|
1030
|
+
* Computes the effective model tier for an agent dispatch given the
|
|
1031
|
+
* base tier (from MODEL_PROFILES), the user's token_profile preference,
|
|
1032
|
+
* the current budget pressure level, and the task's complexity level.
|
|
1033
|
+
*
|
|
1034
|
+
* Pure function. Returns a possibly-downgraded ModelTier. The decision
|
|
1035
|
+
* matrix is documented in the spec and implemented in _lookupDowngradeCount.
|
|
1036
|
+
*/
|
|
1037
|
+
function computeEffectiveModelTier(opts: {
|
|
1038
|
+
baseTier: _ModelTier;
|
|
1039
|
+
tokenProfile: TokenProfileName;
|
|
1040
|
+
pressure: BudgetPressureLevel;
|
|
1041
|
+
complexity: ComplexityLevel;
|
|
1042
|
+
}): _ModelTier {
|
|
1043
|
+
const count = _lookupDowngradeCount(opts.tokenProfile, opts.pressure, opts.complexity);
|
|
1044
|
+
return _applyDowngrade(opts.baseTier, count);
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
// --- Adaptive tier dispatch helper -------------------------------------------
|
|
1048
|
+
|
|
1049
|
+
/**
|
|
1050
|
+
* Structural interface for the scheduler's state accessor.
|
|
1051
|
+
* Using a structural interface avoids circular imports between
|
|
1052
|
+
* scheduler.ts (which imports from types.ts) and backend.ts.
|
|
1053
|
+
*/
|
|
1054
|
+
interface _SchedulerLike {
|
|
1055
|
+
getStates(): Map<string, BackendUsageState>;
|
|
1056
|
+
readonly sessionKey: string;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
const { estimateComplexity } = require('./complexity') as {
|
|
1060
|
+
estimateComplexity: (opts: {
|
|
1061
|
+
agentType: string;
|
|
1062
|
+
promptLength?: number;
|
|
1063
|
+
recentSamples?: { duration: number; tokenEstimate: number }[];
|
|
1064
|
+
baselineOverride?: ComplexityLevel;
|
|
1065
|
+
heuristics?: {
|
|
1066
|
+
prompt_length_high_threshold?: number;
|
|
1067
|
+
sample_demote_high_to_medium?: number;
|
|
1068
|
+
sample_demote_medium_to_low?: number;
|
|
1069
|
+
min_samples_for_demotion?: number;
|
|
1070
|
+
};
|
|
1071
|
+
}) => ComplexityLevel;
|
|
1072
|
+
};
|
|
1073
|
+
|
|
1074
|
+
const { computeBudgetPressureLevel, logPressureTransition } = require('./scheduler') as {
|
|
1075
|
+
computeBudgetPressureLevel: (
|
|
1076
|
+
states: Map<string, BackendUsageState>,
|
|
1077
|
+
priority: BackendId[],
|
|
1078
|
+
accounts: SuperpowersConfig['accounts'],
|
|
1079
|
+
thresholds?: BudgetPressureThresholds
|
|
1080
|
+
) => BudgetPressureLevel;
|
|
1081
|
+
logPressureTransition: (
|
|
1082
|
+
sessionKey: string,
|
|
1083
|
+
current: BudgetPressureLevel,
|
|
1084
|
+
agentType: string,
|
|
1085
|
+
baseTier: string,
|
|
1086
|
+
effectiveTier: string
|
|
1087
|
+
) => void;
|
|
1088
|
+
};
|
|
1089
|
+
|
|
1090
|
+
/**
|
|
1091
|
+
* Computes the effective model tier for an agent dispatch by running
|
|
1092
|
+
* the Spec 4 chain: estimateComplexity → computeBudgetPressureLevel →
|
|
1093
|
+
* computeEffectiveModelTier. Returns the tier to pass to
|
|
1094
|
+
* resolveModelForAgent as effectiveTierOverride.
|
|
1095
|
+
*
|
|
1096
|
+
* When scheduler/schedulerConfig/superpowersConfig are null/undefined,
|
|
1097
|
+
* returns the base tier unchanged (preserving pre-Spec-4 behavior).
|
|
1098
|
+
*
|
|
1099
|
+
* @param opts.agentType - Agent type key (e.g. 'grd-executor')
|
|
1100
|
+
* @param opts.prompt - The prompt string (used for promptLength)
|
|
1101
|
+
* @param opts.config - GrdConfig with model_profile and token_profile fields
|
|
1102
|
+
* @param opts.scheduler - Scheduler instance or null when not configured
|
|
1103
|
+
* @param opts.schedulerConfig - Scheduler configuration from config.scheduler
|
|
1104
|
+
* @param opts.superpowersConfig - Superpowers config from config.superpowers
|
|
1105
|
+
* @param opts.modelProfiles - MODEL_PROFILES table (passed in to avoid circular dep)
|
|
1106
|
+
* @returns Effective model tier for this dispatch
|
|
1107
|
+
*/
|
|
1108
|
+
function getEffectiveTierForDispatch(opts: {
|
|
1109
|
+
agentType: string;
|
|
1110
|
+
prompt: string;
|
|
1111
|
+
config: GrdConfig;
|
|
1112
|
+
scheduler: _SchedulerLike | null;
|
|
1113
|
+
schedulerConfig?: SchedulerConfig;
|
|
1114
|
+
superpowersConfig?: SuperpowersConfig;
|
|
1115
|
+
modelProfiles: Record<string, Record<string, string>>;
|
|
1116
|
+
/**
|
|
1117
|
+
* Verify-fail retry escalation (Tier-2 #5). Per-dispatch metadata: when
|
|
1118
|
+
* this dispatch is a retry after a previous verification failed, set to
|
|
1119
|
+
* the retry count (1 for the first retry, 2 for the second, etc.). The
|
|
1120
|
+
* effective tier is escalated by this many notches, capped at the
|
|
1121
|
+
* strongest tier (opus). 0 or omitted means first attempt — no escalation.
|
|
1122
|
+
*
|
|
1123
|
+
* This is per-work-item metadata only; it does NOT mutate global model
|
|
1124
|
+
* preferences. The caller (e.g. the refinement loop in autopilot-pipeline)
|
|
1125
|
+
* supplies its own retry counter.
|
|
1126
|
+
*/
|
|
1127
|
+
retry_attempt?: number;
|
|
1128
|
+
}): _ModelTier {
|
|
1129
|
+
const profile = opts.config.model_profile || 'balanced';
|
|
1130
|
+
const agentEntry = opts.modelProfiles[opts.agentType];
|
|
1131
|
+
const baseTier = ((agentEntry && agentEntry[profile]) || 'sonnet') as _ModelTier;
|
|
1132
|
+
const retryAttempt = opts.retry_attempt && opts.retry_attempt > 0 ? opts.retry_attempt : 0;
|
|
1133
|
+
|
|
1134
|
+
if (!opts.scheduler || !opts.schedulerConfig || !opts.superpowersConfig) {
|
|
1135
|
+
// No adaptive chain available — apply retry escalation directly to
|
|
1136
|
+
// the base tier so retry_attempt still has an effect.
|
|
1137
|
+
return retryAttempt > 0 ? _applyUpgrade(baseTier, retryAttempt) : baseTier;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
const states = opts.scheduler.getStates();
|
|
1141
|
+
|
|
1142
|
+
// Collect recent samples from all priority accounts (most recent first).
|
|
1143
|
+
// Spec 4 M2: collect agentType so we can prefer same-agent samples for
|
|
1144
|
+
// complexity estimation. Old samples without agentType participate in the
|
|
1145
|
+
// global pool only.
|
|
1146
|
+
let recentSamples: { duration: number; tokenEstimate: number }[] | undefined;
|
|
1147
|
+
const allSamples: {
|
|
1148
|
+
duration: number;
|
|
1149
|
+
tokenEstimate: number;
|
|
1150
|
+
timestamp: number;
|
|
1151
|
+
agentType?: string;
|
|
1152
|
+
}[] = [];
|
|
1153
|
+
for (const backend of opts.schedulerConfig.backend_priority) {
|
|
1154
|
+
const backendAccounts = opts.superpowersConfig.accounts[backend as AdapterBackendId] || [];
|
|
1155
|
+
for (const account of backendAccounts) {
|
|
1156
|
+
const stateKey = `${backend}/${account.config_dir}`;
|
|
1157
|
+
const state = states.get(stateKey);
|
|
1158
|
+
if (!state) continue;
|
|
1159
|
+
for (const sample of state.samples) {
|
|
1160
|
+
allSamples.push({
|
|
1161
|
+
duration: sample.duration,
|
|
1162
|
+
tokenEstimate: sample.tokenEstimate,
|
|
1163
|
+
timestamp: sample.timestamp,
|
|
1164
|
+
agentType: sample.agentType,
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
if (allSamples.length >= 3) {
|
|
1170
|
+
// Spec 4 M2: prefer per-agent samples if we have enough, else fall back
|
|
1171
|
+
// to the global tail. Old samples without agentType participate in the
|
|
1172
|
+
// global pool only.
|
|
1173
|
+
const ownAgentSamples = allSamples.filter((s) => s.agentType === opts.agentType);
|
|
1174
|
+
const samplesToUse = ownAgentSamples.length >= 3 ? ownAgentSamples : allSamples;
|
|
1175
|
+
// Sort by timestamp descending, take up to 10 most recent
|
|
1176
|
+
samplesToUse.sort((a, b) => b.timestamp - a.timestamp);
|
|
1177
|
+
recentSamples = samplesToUse.slice(0, 10).map((s) => ({
|
|
1178
|
+
duration: s.duration,
|
|
1179
|
+
tokenEstimate: s.tokenEstimate,
|
|
1180
|
+
}));
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
const complexity = estimateComplexity({
|
|
1184
|
+
agentType: opts.agentType,
|
|
1185
|
+
promptLength: opts.prompt.length,
|
|
1186
|
+
recentSamples,
|
|
1187
|
+
baselineOverride: opts.config.agent_complexity_overrides?.[opts.agentType],
|
|
1188
|
+
heuristics: opts.config.complexity_heuristics,
|
|
1189
|
+
});
|
|
1190
|
+
const pressure = computeBudgetPressureLevel(
|
|
1191
|
+
states,
|
|
1192
|
+
opts.schedulerConfig.backend_priority as BackendId[],
|
|
1193
|
+
opts.superpowersConfig.accounts,
|
|
1194
|
+
opts.schedulerConfig.budget_pressure_thresholds
|
|
1195
|
+
);
|
|
1196
|
+
const tokenProfile: TokenProfileName = opts.config.token_profile || 'balanced';
|
|
1197
|
+
|
|
1198
|
+
const adaptiveTier = computeEffectiveModelTier({
|
|
1199
|
+
baseTier,
|
|
1200
|
+
tokenProfile,
|
|
1201
|
+
pressure,
|
|
1202
|
+
complexity,
|
|
1203
|
+
});
|
|
1204
|
+
|
|
1205
|
+
// Verify-fail retry escalation (Tier-2 #5). Applied AFTER adaptive
|
|
1206
|
+
// downgrade so that a retry escalates from whatever tier the adaptive
|
|
1207
|
+
// chain landed on, not from the original baseTier. Capped at opus.
|
|
1208
|
+
const effectiveTier =
|
|
1209
|
+
retryAttempt > 0 ? _applyUpgrade(adaptiveTier, retryAttempt) : adaptiveTier;
|
|
1210
|
+
|
|
1211
|
+
// Spec 4 Goal #7: log on pressure transitions only (O3: use per-scheduler
|
|
1212
|
+
// sessionKey instead of process.pid to avoid shared state across multiple
|
|
1213
|
+
// createScheduler calls in the same process).
|
|
1214
|
+
logPressureTransition(
|
|
1215
|
+
opts.scheduler.sessionKey,
|
|
1216
|
+
pressure,
|
|
1217
|
+
opts.agentType,
|
|
1218
|
+
baseTier,
|
|
1219
|
+
effectiveTier
|
|
1220
|
+
);
|
|
1221
|
+
|
|
1222
|
+
return effectiveTier;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
// --- Exports -----------------------------------------------------------------
|
|
1226
|
+
|
|
1227
|
+
module.exports = {
|
|
1228
|
+
VALID_BACKENDS,
|
|
1229
|
+
DEFAULT_BACKEND_MODELS,
|
|
1230
|
+
BACKEND_CAPABILITIES,
|
|
1231
|
+
EFFORT_PROFILES,
|
|
1232
|
+
_applyUpgrade,
|
|
1233
|
+
detectBackend,
|
|
1234
|
+
resolveBackendModel,
|
|
1235
|
+
resolveEffortLevel,
|
|
1236
|
+
getBackendCapabilities,
|
|
1237
|
+
parseOpenCodeModels,
|
|
1238
|
+
detectModels,
|
|
1239
|
+
getCachedModels,
|
|
1240
|
+
clearModelCache,
|
|
1241
|
+
detectWebMcp,
|
|
1242
|
+
detectPlaywright,
|
|
1243
|
+
detectAvailableBackends,
|
|
1244
|
+
clearAvailabilityCache,
|
|
1245
|
+
discoverBackendConfigDirs,
|
|
1246
|
+
clearConfigDirCache,
|
|
1247
|
+
buildBackendEnv,
|
|
1248
|
+
BACKEND_CONFIG_ENV,
|
|
1249
|
+
readConfig,
|
|
1250
|
+
computeEffectiveModelTier,
|
|
1251
|
+
getEffectiveTierForDispatch,
|
|
1252
|
+
};
|