devcouncil 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -30
- package/package.json +6 -2
- package/packages/codeintel-grammars/hatch_build.py +43 -0
- package/packages/codeintel-grammars/pyproject.toml +16 -0
- package/packages/codeintel-grammars/src/devcouncil_codeintel_grammars/__init__.py +93 -0
- package/pyproject.toml +99 -4
- package/src/devcouncil/app/config.py +512 -20
- package/src/devcouncil/app/events.py +4 -23
- package/src/devcouncil/app/orchestrator.py +5 -0
- package/src/devcouncil/app/run_context.py +3 -3
- package/src/devcouncil/assets/__init__.py +4 -1
- package/src/devcouncil/assets/vendor/force-graph.min.js +5 -0
- package/src/devcouncil/campaign/__init__.py +71 -0
- package/src/devcouncil/campaign/bloom.py +137 -0
- package/src/devcouncil/campaign/dashboard.py +123 -0
- package/src/devcouncil/campaign/mailbox.py +305 -0
- package/src/devcouncil/campaign/notify.py +91 -0
- package/src/devcouncil/campaign/orchestrator.py +592 -0
- package/src/devcouncil/campaign/prompts/coordinator.md +29 -0
- package/src/devcouncil/campaign/prompts/director.md +21 -0
- package/src/devcouncil/campaign/prompts/protocol.md +46 -0
- package/src/devcouncil/campaign/prompts/reviewer.md +24 -0
- package/src/devcouncil/campaign/prompts/worker.md +24 -0
- package/src/devcouncil/campaign/roles.py +202 -0
- package/src/devcouncil/campaign/watcher.py +153 -0
- package/src/devcouncil/cli/commands/agents.py +24 -17
- package/src/devcouncil/cli/commands/artifacts.py +36 -27
- package/src/devcouncil/cli/commands/ast.py +12 -3
- package/src/devcouncil/cli/commands/baseline.py +21 -12
- package/src/devcouncil/cli/commands/boot.py +218 -0
- package/src/devcouncil/cli/commands/campaign.py +302 -0
- package/src/devcouncil/cli/commands/check.py +225 -12
- package/src/devcouncil/cli/commands/config.py +221 -74
- package/src/devcouncil/cli/commands/cost.py +137 -28
- package/src/devcouncil/cli/commands/dashboard.py +12 -4
- package/src/devcouncil/cli/commands/debug_cmd.py +249 -0
- package/src/devcouncil/cli/commands/design.py +27 -17
- package/src/devcouncil/cli/commands/doctor.py +790 -8
- package/src/devcouncil/cli/commands/evidence.py +41 -20
- package/src/devcouncil/cli/commands/export.py +73 -0
- package/src/devcouncil/cli/commands/gaps.py +175 -0
- package/src/devcouncil/cli/commands/gated_write.py +76 -0
- package/src/devcouncil/cli/commands/go.py +220 -68
- package/src/devcouncil/cli/commands/graph_cmd.py +1192 -0
- package/src/devcouncil/cli/commands/handoff.py +45 -34
- package/src/devcouncil/cli/commands/hook.py +630 -85
- package/src/devcouncil/cli/commands/init.py +89 -30
- package/src/devcouncil/cli/commands/integrate.py +296 -1385
- package/src/devcouncil/cli/commands/lease.py +120 -0
- package/src/devcouncil/cli/commands/logs.py +12 -5
- package/src/devcouncil/cli/commands/lsp.py +40 -5
- package/src/devcouncil/cli/commands/map.py +317 -74
- package/src/devcouncil/cli/commands/mcp_server.py +12 -2
- package/src/devcouncil/cli/commands/okf.py +44 -6
- package/src/devcouncil/cli/commands/plan.py +184 -69
- package/src/devcouncil/cli/commands/prompt.py +26 -17
- package/src/devcouncil/cli/commands/provenance.py +79 -0
- package/src/devcouncil/cli/commands/repair.py +60 -49
- package/src/devcouncil/cli/commands/report.py +148 -40
- package/src/devcouncil/cli/commands/requirements.py +104 -0
- package/src/devcouncil/cli/commands/reset_demo_state.py +13 -4
- package/src/devcouncil/cli/commands/rollback.py +46 -35
- package/src/devcouncil/cli/commands/run.py +173 -8
- package/src/devcouncil/cli/commands/runs.py +298 -68
- package/src/devcouncil/cli/commands/scaffold.py +33 -12
- package/src/devcouncil/cli/commands/semantic.py +29 -14
- package/src/devcouncil/cli/commands/setup.py +103 -93
- package/src/devcouncil/cli/commands/shell.py +51 -42
- package/src/devcouncil/cli/commands/show.py +56 -42
- package/src/devcouncil/cli/commands/skills.py +29 -20
- package/src/devcouncil/cli/commands/status.py +80 -67
- package/src/devcouncil/cli/commands/task_gate.py +295 -0
- package/src/devcouncil/cli/commands/tasks.py +248 -19
- package/src/devcouncil/cli/commands/trace.py +14 -8
- package/src/devcouncil/cli/commands/verify.py +33 -8
- package/src/devcouncil/cli/commands/version.py +14 -6
- package/src/devcouncil/cli/commands/watch.py +49 -30
- package/src/devcouncil/cli/commands/watch_fs.py +30 -19
- package/src/devcouncil/cli/commands/wiki.py +278 -0
- package/src/devcouncil/cli/main.py +58 -1
- package/src/devcouncil/codeintel/__init__.py +16 -0
- package/src/devcouncil/codeintel/build_control.py +429 -0
- package/src/devcouncil/codeintel/build_worker.py +78 -0
- package/src/devcouncil/codeintel/debug/__init__.py +17 -0
- package/src/devcouncil/codeintel/debug/broker.py +114 -0
- package/src/devcouncil/codeintel/debug/broker_client.py +61 -0
- package/src/devcouncil/codeintel/debug/consent.py +36 -0
- package/src/devcouncil/codeintel/debug/discovery.py +132 -0
- package/src/devcouncil/codeintel/debug/fingerprint.py +85 -0
- package/src/devcouncil/codeintel/debug/protocol.py +259 -0
- package/src/devcouncil/codeintel/debug/python_trace_runner.py +81 -0
- package/src/devcouncil/codeintel/debug/session.py +238 -0
- package/src/devcouncil/codeintel/debug/tracing.py +201 -0
- package/src/devcouncil/codeintel/languages/__init__.py +17 -0
- package/src/devcouncil/codeintel/languages/generic_extractor.py +236 -0
- package/src/devcouncil/codeintel/languages/registry.py +149 -0
- package/src/devcouncil/codeintel/languages/workers.py +245 -0
- package/src/devcouncil/codeintel/query/__init__.py +5 -0
- package/src/devcouncil/codeintel/query/engine.py +289 -0
- package/src/devcouncil/codeintel/resolution/__init__.py +6 -0
- package/src/devcouncil/codeintel/resolution/abstract_state.py +301 -0
- package/src/devcouncil/codeintel/resolution/frameworks/__init__.py +33 -0
- package/src/devcouncil/codeintel/resolution/frameworks/base.py +46 -0
- package/src/devcouncil/codeintel/resolution/frameworks/di.py +56 -0
- package/src/devcouncil/codeintel/resolution/frameworks/events.py +45 -0
- package/src/devcouncil/codeintel/resolution/frameworks/routes.py +88 -0
- package/src/devcouncil/codeintel/resolution/semantic.py +887 -0
- package/src/devcouncil/codeintel/service.py +104 -0
- package/src/devcouncil/codeintel/store/__init__.py +15 -0
- package/src/devcouncil/codeintel/store/sqlite.py +1565 -0
- package/src/devcouncil/codeintel/sync/__init__.py +19 -0
- package/src/devcouncil/codeintel/sync/coordinator.py +430 -0
- package/src/devcouncil/codeintel/sync/incremental.py +484 -0
- package/src/devcouncil/codeintel/sync/lease.py +96 -0
- package/src/devcouncil/codeintel/sync/scope.py +98 -0
- package/src/devcouncil/council/__init__.py +4 -0
- package/src/devcouncil/council/prompts/__init__.py +4 -0
- package/src/devcouncil/domain/checkpoint_refs.py +17 -0
- package/src/devcouncil/domain/evidence.py +1 -0
- package/src/devcouncil/domain/gap.py +10 -0
- package/src/devcouncil/domain/requirement.py +5 -1
- package/src/devcouncil/domain/task.py +43 -2
- package/src/devcouncil/execution/checkpoints.py +25 -31
- package/src/devcouncil/execution/context_builder.py +15 -44
- package/src/devcouncil/execution/fs_watcher.py +64 -0
- package/src/devcouncil/execution/gated_write.py +203 -0
- package/src/devcouncil/execution/handoff.py +2 -1
- package/src/devcouncil/execution/hook_policy.py +19 -5
- package/src/devcouncil/execution/lease_ops.py +177 -0
- package/src/devcouncil/execution/lease_validation.py +71 -0
- package/src/devcouncil/execution/patch.py +3 -0
- package/src/devcouncil/execution/permissions.py +1 -0
- package/src/devcouncil/execution/policy_engine.py +205 -10
- package/src/devcouncil/execution/prompt_builder.py +278 -33
- package/src/devcouncil/execution/run_trace.py +356 -0
- package/src/devcouncil/execution/shell_session.py +46 -5
- package/src/devcouncil/execution/stop_gate.py +746 -0
- package/src/devcouncil/execution/stop_gate_history.py +113 -0
- package/src/devcouncil/execution/stop_gate_state.py +54 -0
- package/src/devcouncil/execution/stop_gate_verify_cache.py +69 -0
- package/src/devcouncil/execution/task_gate_ops.py +590 -0
- package/src/devcouncil/execution/task_runner.py +19 -0
- package/src/devcouncil/executors/advisor_tool.py +315 -0
- package/src/devcouncil/executors/agent_registry.py +125 -17
- package/src/devcouncil/executors/claude_sdk.py +376 -0
- package/src/devcouncil/executors/coding_cli.py +724 -25
- package/src/devcouncil/executors/mini_swe.py +50 -8
- package/src/devcouncil/executors/native/agent.py +224 -19
- package/src/devcouncil/executors/openhands.py +50 -8
- package/src/devcouncil/executors/transient_retry.py +99 -0
- package/src/devcouncil/gating/checks/clean_git.py +5 -2
- package/src/devcouncil/gating/checks/planned_files_check.py +38 -11
- package/src/devcouncil/gating/checks/secret_scan_check.py +2 -2
- package/src/devcouncil/gating/policy.py +46 -2
- package/src/devcouncil/indexing/ast_matcher.py +41 -4
- package/src/devcouncil/indexing/graph/__init__.py +78 -0
- package/src/devcouncil/indexing/graph/api_routes.py +522 -0
- package/src/devcouncil/indexing/graph/build.py +862 -0
- package/src/devcouncil/indexing/graph/cache.py +329 -0
- package/src/devcouncil/indexing/graph/communities.py +28 -0
- package/src/devcouncil/indexing/graph/cypher.py +107 -0
- package/src/devcouncil/indexing/graph/embeddings.py +194 -0
- package/src/devcouncil/indexing/graph/export.py +381 -0
- package/src/devcouncil/indexing/graph/export_links.py +81 -0
- package/src/devcouncil/indexing/graph/extract_python.py +307 -0
- package/src/devcouncil/indexing/graph/extract_ts.py +1205 -0
- package/src/devcouncil/indexing/graph/intel.py +668 -0
- package/src/devcouncil/indexing/graph/liveness.py +992 -0
- package/src/devcouncil/indexing/graph/okf_export.py +65 -0
- package/src/devcouncil/indexing/graph/pdg/__init__.py +67 -0
- package/src/devcouncil/indexing/graph/pdg/build.py +11 -0
- package/src/devcouncil/indexing/graph/pdg/cdg.py +41 -0
- package/src/devcouncil/indexing/graph/pdg/cfg.py +199 -0
- package/src/devcouncil/indexing/graph/pdg/query.py +21 -0
- package/src/devcouncil/indexing/graph/pdg/reaching_def.py +126 -0
- package/src/devcouncil/indexing/graph/pdg/schema.py +253 -0
- package/src/devcouncil/indexing/graph/pdg/taint.py +154 -0
- package/src/devcouncil/indexing/graph/query.py +302 -0
- package/src/devcouncil/indexing/graph/resolve.py +1020 -0
- package/src/devcouncil/indexing/graph/schema.py +103 -0
- package/src/devcouncil/indexing/graph_index.py +20 -29
- package/src/devcouncil/indexing/lsp.py +57 -25
- package/src/devcouncil/indexing/lsp_client.py +577 -0
- package/src/devcouncil/indexing/map_artifacts.py +355 -0
- package/src/devcouncil/indexing/map_refresh.py +141 -0
- package/src/devcouncil/indexing/repo_mapper.py +1509 -138
- package/src/devcouncil/indexing/semantic_index.py +12 -6
- package/src/devcouncil/indexing/subsystem_map.py +163 -0
- package/src/devcouncil/indexing/ts_imports.py +343 -0
- package/src/devcouncil/indexing/viz.py +960 -0
- package/src/devcouncil/indexing/walk.py +52 -0
- package/src/devcouncil/indexing/wiring.py +1776 -0
- package/src/devcouncil/integrations/actions.py +27 -4
- package/src/devcouncil/integrations/check.py +211 -16
- package/src/devcouncil/integrations/claude_assets.py +209 -12
- package/src/devcouncil/integrations/clients/__init__.py +1 -0
- package/src/devcouncil/integrations/clients/aider.py +52 -0
- package/src/devcouncil/integrations/clients/antigravity.py +87 -0
- package/src/devcouncil/integrations/clients/claude.py +339 -0
- package/src/devcouncil/integrations/clients/codex.py +39 -0
- package/src/devcouncil/integrations/clients/common.py +332 -0
- package/src/devcouncil/integrations/clients/cursor.py +164 -0
- package/src/devcouncil/integrations/clients/gemini.py +49 -0
- package/src/devcouncil/integrations/clients/grok.py +105 -0
- package/src/devcouncil/integrations/clients/hooks.py +500 -0
- package/src/devcouncil/integrations/clients/opencode.py +96 -0
- package/src/devcouncil/integrations/clients/warp.py +75 -0
- package/src/devcouncil/integrations/code_review_graph.py +2 -2
- package/src/devcouncil/integrations/github.py +73 -7
- package/src/devcouncil/integrations/integration_cli.py +197 -0
- package/src/devcouncil/integrations/mcp/handlers/__init__.py +1 -0
- package/src/devcouncil/integrations/mcp/handlers/ast_lsp.py +77 -0
- package/src/devcouncil/integrations/mcp/handlers/checkout.py +50 -0
- package/src/devcouncil/integrations/mcp/handlers/cli_gate.py +43 -0
- package/src/devcouncil/integrations/mcp/handlers/codeintel.py +182 -0
- package/src/devcouncil/integrations/mcp/handlers/debug.py +236 -0
- package/src/devcouncil/integrations/mcp/handlers/evidence.py +70 -0
- package/src/devcouncil/integrations/mcp/handlers/git.py +99 -0
- package/src/devcouncil/integrations/mcp/handlers/graph.py +36 -0
- package/src/devcouncil/integrations/mcp/handlers/handoff.py +53 -0
- package/src/devcouncil/integrations/mcp/handlers/knowledge.py +30 -0
- package/src/devcouncil/integrations/mcp/handlers/lease.py +70 -0
- package/src/devcouncil/integrations/mcp/handlers/live.py +108 -0
- package/src/devcouncil/integrations/mcp/handlers/map.py +676 -0
- package/src/devcouncil/integrations/mcp/handlers/next_task.py +35 -0
- package/src/devcouncil/integrations/mcp/handlers/policy.py +80 -0
- package/src/devcouncil/integrations/mcp/handlers/prompts.py +168 -0
- package/src/devcouncil/integrations/mcp/handlers/provenance.py +101 -0
- package/src/devcouncil/integrations/mcp/handlers/read.py +74 -0
- package/src/devcouncil/integrations/mcp/handlers/router_cache.py +53 -0
- package/src/devcouncil/integrations/mcp/handlers/run.py +53 -0
- package/src/devcouncil/integrations/mcp/handlers/runs.py +84 -0
- package/src/devcouncil/integrations/mcp/handlers/scope.py +56 -0
- package/src/devcouncil/integrations/mcp/handlers/status.py +114 -0
- package/src/devcouncil/integrations/mcp/handlers/task.py +100 -0
- package/src/devcouncil/integrations/mcp/handlers/tool_specs.py +904 -0
- package/src/devcouncil/integrations/mcp/handlers/trace.py +65 -0
- package/src/devcouncil/integrations/mcp/handlers/verify.py +45 -0
- package/src/devcouncil/integrations/mcp/handlers/wiki.py +60 -0
- package/src/devcouncil/integrations/mcp/handlers/write.py +69 -0
- package/src/devcouncil/integrations/mcp/server.py +265 -2391
- package/src/devcouncil/integrations/mcp/util.py +303 -0
- package/src/devcouncil/integrations/setup.py +152 -0
- package/src/devcouncil/knowledge/fetch.py +4 -0
- package/src/devcouncil/knowledge/knowledge_select.py +38 -0
- package/src/devcouncil/knowledge/okf.py +2 -1
- package/src/devcouncil/knowledge/resource_discovery.py +40 -0
- package/src/devcouncil/knowledge/wiki.py +643 -0
- package/src/devcouncil/knowledge/wiki_read.py +87 -0
- package/src/devcouncil/live/cards.py +7 -7
- package/src/devcouncil/live/models.py +4 -1
- package/src/devcouncil/live/reviewer.py +90 -11
- package/src/devcouncil/live/signals.py +4 -2
- package/src/devcouncil/live/tasks.py +12 -3
- package/src/devcouncil/live/transcripts.py +69 -2
- package/src/devcouncil/llm/cache.py +5 -6
- package/src/devcouncil/llm/model_defaults.yaml +10 -10
- package/src/devcouncil/llm/provider.py +647 -73
- package/src/devcouncil/llm/router.py +271 -46
- package/src/devcouncil/llm/semantic_bridge.py +614 -0
- package/src/devcouncil/optimization/gepa_agent.py +6 -4
- package/src/devcouncil/optimization/skillopt.py +9 -5
- package/src/devcouncil/planning/arbiter_service.py +12 -3
- package/src/devcouncil/planning/correction_manifest.py +107 -10
- package/src/devcouncil/planning/plan_difficulty.py +69 -0
- package/src/devcouncil/planning/plan_service.py +5 -2
- package/src/devcouncil/planning/planned_files_reconcile.py +191 -0
- package/src/devcouncil/planning/prompt_enhancer_service.py +6 -5
- package/src/devcouncil/planning/question_conversion.py +56 -0
- package/src/devcouncil/planning/spec_service.py +9 -3
- package/src/devcouncil/repo/ci_scaffold.py +197 -1
- package/src/devcouncil/repo/gitignore.py +1 -2
- package/src/devcouncil/reporting/evidence_export.py +124 -0
- package/src/devcouncil/reporting/evidence_html.py +210 -0
- package/src/devcouncil/reporting/json_report.py +16 -12
- package/src/devcouncil/reporting/markdown_report.py +38 -9
- package/src/devcouncil/reporting/mcp_resources.py +142 -0
- package/src/devcouncil/reporting/report_builder.py +40 -4
- package/src/devcouncil/reporting/task_provenance.py +42 -0
- package/src/devcouncil/reporting/verdict.py +75 -0
- package/src/devcouncil/skills/library/README.md +1 -0
- package/src/devcouncil/skills/library/devcouncil-hero-loop.md +109 -0
- package/src/devcouncil/skills/library/devcouncil-verification.md +109 -0
- package/src/devcouncil/skills/library/devcouncil.md +93 -0
- package/src/devcouncil/skills/registry.py +43 -12
- package/src/devcouncil/storage/db.py +57 -11
- package/src/devcouncil/storage/models.py +6 -0
- package/src/devcouncil/storage/native.py +5 -3
- package/src/devcouncil/storage/repositories.py +50 -18
- package/src/devcouncil/telemetry/context.py +28 -0
- package/src/devcouncil/telemetry/cost.py +4 -5
- package/src/devcouncil/telemetry/logging_setup.py +78 -11
- package/src/devcouncil/telemetry/model_pricing.yaml +7 -0
- package/src/devcouncil/telemetry/stages.py +27 -2
- package/src/devcouncil/telemetry/tracker.py +50 -13
- package/src/devcouncil/ui/dashboard.py +120 -8
- package/src/devcouncil/utils/fsio.py +58 -0
- package/src/devcouncil/utils/git_snapshot.py +112 -0
- package/src/devcouncil/utils/json_persist.py +53 -0
- package/src/devcouncil/utils/proc.py +89 -0
- package/src/devcouncil/verification/acceptance_compiler.py +36 -13
- package/src/devcouncil/verification/ad_hoc_check.py +95 -3
- package/src/devcouncil/verification/checks/__init__.py +41 -0
- package/src/devcouncil/verification/checks/acceptance.py +39 -0
- package/src/devcouncil/verification/checks/acceptance_corpus.py +194 -0
- package/src/devcouncil/verification/checks/acceptance_evidence.py +239 -0
- package/src/devcouncil/verification/checks/command_evidence.py +148 -0
- package/src/devcouncil/verification/checks/compiled_acceptance.py +179 -0
- package/src/devcouncil/verification/checks/corpus_stale.py +124 -0
- package/src/devcouncil/verification/checks/corpus_verification.py +9 -0
- package/src/devcouncil/verification/checks/dead_symbols.py +360 -0
- package/src/devcouncil/verification/checks/diff_coverage_gate.py +101 -0
- package/src/devcouncil/verification/checks/doc_code_ref.py +79 -0
- package/src/devcouncil/verification/checks/liveness_ratchet.py +336 -0
- package/src/devcouncil/verification/checks/orphan_diff.py +104 -0
- package/src/devcouncil/verification/checks/planned_files.py +98 -0
- package/src/devcouncil/verification/checks/semantic_diff.py +241 -0
- package/src/devcouncil/verification/checks/stale_map.py +80 -0
- package/src/devcouncil/verification/checks/stub_scan.py +71 -0
- package/src/devcouncil/verification/checks/subsystem_boundary.py +103 -0
- package/src/devcouncil/verification/checks/wiring.py +216 -0
- package/src/devcouncil/verification/claims/__init__.py +23 -0
- package/src/devcouncil/verification/claims/checks.py +395 -0
- package/src/devcouncil/verification/claims/mapper.py +168 -0
- package/src/devcouncil/verification/claims/models.py +39 -0
- package/src/devcouncil/verification/claims/transcript.py +92 -0
- package/src/devcouncil/verification/claims/verdict.py +88 -0
- package/src/devcouncil/verification/command_evidence.py +170 -0
- package/src/devcouncil/verification/command_malformation.py +147 -0
- package/src/devcouncil/verification/command_runner.py +164 -0
- package/src/devcouncil/verification/coverage_measurement.py +292 -0
- package/src/devcouncil/verification/diff_coverage.py +151 -0
- package/src/devcouncil/verification/difficulty.py +296 -0
- package/src/devcouncil/verification/effort_heuristics.py +178 -0
- package/src/devcouncil/verification/gap_ids.py +63 -0
- package/src/devcouncil/verification/gate_cache.py +194 -0
- package/src/devcouncil/verification/gate_selector.py +344 -0
- package/src/devcouncil/verification/git_diff_fallback.py +272 -0
- package/src/devcouncil/verification/implementation_reviewer.py +13 -0
- package/src/devcouncil/verification/incremental_check.py +241 -0
- package/src/devcouncil/verification/next_actions.py +60 -1
- package/src/devcouncil/verification/rigor_analytics.py +130 -0
- package/src/devcouncil/verification/sandbox.py +38 -11
- package/src/devcouncil/verification/stub_detector.py +369 -0
- package/src/devcouncil/verification/test_resolver.py +67 -1
- package/src/devcouncil/verification/verifier.py +137 -1666
- package/src/devcouncil/verification/verify_orchestration.py +610 -0
- package/src/devcouncil/verification/verify_setup.py +176 -0
- package/src/devcouncil/verification/wiki_refresh.py +208 -0
- package/src/semantic_layer/__init__.py +58 -0
- package/src/semantic_layer/benchmark.py +75 -0
- package/src/semantic_layer/cache.py +290 -0
- package/src/semantic_layer/compressor.py +137 -0
- package/src/semantic_layer/config.py +75 -0
- package/src/semantic_layer/embeddings.py +69 -0
- package/src/semantic_layer/llm_backends.py +99 -0
- package/src/semantic_layer/pipeline.py +111 -0
- package/src/semantic_layer/router.py +128 -0
- package/src/semantic_layer/tuner.py +72 -0
- package/uv.lock +973 -9
- package/src/devcouncil/artifacts/migrations.py +0 -20
- package/src/devcouncil/artifacts/schemas.py +0 -23
- package/src/devcouncil/artifacts/serializer.py +0 -21
- package/src/devcouncil/integrations/gitnexus.py +0 -70
- package/src/devcouncil/integrations/graphify.py +0 -34
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
"""Claude Code advisor-tool helpers (CLI ``--advisor`` / SDK ``extra_args`` / settings).
|
|
2
|
+
|
|
3
|
+
Kept out of :mod:`devcouncil.execution.prompt_builder` so Codex/Cursor/native never
|
|
4
|
+
see advisor steering. Pairing preflight is soft: clear bad pairs skip attaching
|
|
5
|
+
``--advisor`` so Claude does not hard-exit and burn the go-loop repair budget.
|
|
6
|
+
|
|
7
|
+
DevCouncil only soft-filters obvious mismatches (haiku advisor, weaker family,
|
|
8
|
+
fable+non-fable). Claude Code validates the full versioned pairing matrix at launch.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
import os
|
|
15
|
+
import re
|
|
16
|
+
import shutil
|
|
17
|
+
import subprocess
|
|
18
|
+
from functools import lru_cache
|
|
19
|
+
from typing import Literal, Mapping
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
# Claude Code floor for the advisor server tool (Anthropic API only).
|
|
24
|
+
MIN_ADVISOR_CLAUDE_VERSION = (2, 1, 98)
|
|
25
|
+
# Fable as main or advisor requires a newer Claude Code floor.
|
|
26
|
+
MIN_FABLE_ADVISOR_CLAUDE_VERSION = (2, 1, 170)
|
|
27
|
+
DISABLE_ADVISOR_ENV = "CLAUDE_CODE_DISABLE_ADVISOR_TOOL"
|
|
28
|
+
|
|
29
|
+
# Cloud providers where Claude Code does not offer the Anthropic advisor tool.
|
|
30
|
+
NON_ANTHROPIC_ADVISOR_ENVS = (
|
|
31
|
+
"CLAUDE_CODE_USE_BEDROCK",
|
|
32
|
+
"CLAUDE_CODE_USE_VERTEX",
|
|
33
|
+
"CLAUDE_CODE_USE_FOUNDRY",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Short interactive nudge shared by MCP prompts, slash commands, and subagents.
|
|
37
|
+
ADVISOR_STEERING_NUDGE = (
|
|
38
|
+
"When the advisor tool is available, consult it before committing to an approach, "
|
|
39
|
+
"when stuck on a recurring error, and before declaring the task complete."
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Anthropic "Suggested system prompt for coding tasks" — timing block.
|
|
43
|
+
ADVISOR_CODING_TIMING = (
|
|
44
|
+
"You have access to an `advisor` tool backed by a stronger reviewer model. "
|
|
45
|
+
"It takes NO parameters — when you call advisor(), your entire conversation "
|
|
46
|
+
"history is automatically forwarded. They see the task, every tool call you've "
|
|
47
|
+
"made, every result you've seen.\n\n"
|
|
48
|
+
"Call advisor BEFORE substantive work — before writing, before committing to an "
|
|
49
|
+
"interpretation, before building on an assumption. If the task requires orientation "
|
|
50
|
+
"first (finding files, fetching a source, seeing what's there), do that, then call "
|
|
51
|
+
"advisor. Orientation is not substantive work. Writing, editing, and declaring an "
|
|
52
|
+
"answer are.\n\n"
|
|
53
|
+
"Also call advisor:\n"
|
|
54
|
+
"- When you believe the task is complete. BEFORE this call, make your deliverable "
|
|
55
|
+
"durable: write the file, save the result, commit the change. The advisor call takes "
|
|
56
|
+
"time; if the session ends during it, a durable result persists and an unwritten "
|
|
57
|
+
"one doesn't.\n"
|
|
58
|
+
"- When stuck — errors recurring, approach not converging, results that don't fit.\n"
|
|
59
|
+
"- When considering a change of approach.\n\n"
|
|
60
|
+
"On tasks longer than a few steps, call advisor at least once before committing to "
|
|
61
|
+
"an approach and once before declaring done. On short reactive tasks where the next "
|
|
62
|
+
"action is dictated by tool output you just read, you don't need to keep calling — "
|
|
63
|
+
"the advisor adds most of its value on the first call, before the approach crystallizes."
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Anthropic treat-advice block (place directly after timing).
|
|
67
|
+
ADVISOR_TREAT_ADVICE = (
|
|
68
|
+
"Give the advice serious weight. If you follow a step and it fails empirically, or "
|
|
69
|
+
"you have primary-source evidence that contradicts a specific claim (the file says X, "
|
|
70
|
+
"the paper states Y), adapt. A passing self-test is not evidence the advice is wrong — "
|
|
71
|
+
"it's evidence your test doesn't check what the advice is checking.\n\n"
|
|
72
|
+
"If you've already retrieved data pointing one way and the advisor points another: "
|
|
73
|
+
"don't silently switch. Surface the conflict in one more advisor call — "
|
|
74
|
+
'"I found X, you suggest Y, which constraint breaks the tie?" The advisor saw your '
|
|
75
|
+
"evidence but may have underweighted it; a reconcile call is cheaper than committing "
|
|
76
|
+
"to the wrong branch."
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
REPAIR_ADVISOR_AUTHORITY = (
|
|
80
|
+
"A correction_manifest is present for this repair. Treat it as authoritative over "
|
|
81
|
+
"prior session history and any prior advisor advice; change approach on identical gaps."
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Soft cost trim for the user prompt (CC-passable; not Messages-API-only).
|
|
85
|
+
ADVISOR_COST_TRIM_USER = (
|
|
86
|
+
"(Advisor: keep guidance under ~80 words — I need a focused starting point, "
|
|
87
|
+
"not a comprehensive plan.)"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Capability tiers for soft pairing. Higher = stronger. Unknown models are not
|
|
91
|
+
# rejected here — Claude Code validates those at launch.
|
|
92
|
+
_FAMILY_TIER: dict[str, int] = {
|
|
93
|
+
"haiku": 1,
|
|
94
|
+
"sonnet": 2,
|
|
95
|
+
"opus": 3,
|
|
96
|
+
"fable": 4,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# Markers that mean the CLI died on advisor/pairing/infra — not a code gap to repair.
|
|
100
|
+
ADVISOR_INFRA_FAILURE_MARKERS = (
|
|
101
|
+
"does not support the advisor",
|
|
102
|
+
"advisor is not supported",
|
|
103
|
+
"advisor model",
|
|
104
|
+
"--advisor",
|
|
105
|
+
"advisor tool",
|
|
106
|
+
"availablemodels",
|
|
107
|
+
"available models",
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def advisor_steering_text(*, repair: bool = False) -> str:
|
|
112
|
+
"""Attach-gated Claude-only system steering (timing + treat-advice + optional repair)."""
|
|
113
|
+
parts = [ADVISOR_CODING_TIMING, ADVISOR_TREAT_ADVICE]
|
|
114
|
+
if repair:
|
|
115
|
+
parts.append(REPAIR_ADVISOR_AUTHORITY)
|
|
116
|
+
return "\n\n".join(parts)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def advisor_user_cost_trim() -> str:
|
|
120
|
+
"""Optional soft ~80-word ask for the advisor, placed in the user prompt when attached."""
|
|
121
|
+
return ADVISOR_COST_TRIM_USER
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def normalize_model_family(model: str | None) -> str | None:
|
|
125
|
+
"""Map a model alias or id to a coarse family name, or None if unrecognized."""
|
|
126
|
+
if not model:
|
|
127
|
+
return None
|
|
128
|
+
text = model.strip().lower()
|
|
129
|
+
if not text:
|
|
130
|
+
return None
|
|
131
|
+
# Prefer explicit family tokens (claude-opus-4-7, opus, sonnet-4-6, …).
|
|
132
|
+
for family in ("fable", "opus", "sonnet", "haiku"):
|
|
133
|
+
if family in text:
|
|
134
|
+
return family
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def advisor_pairing_ok(main_model: str | None, advisor_model: str | None) -> tuple[bool, str | None]:
|
|
139
|
+
"""Soft check that *advisor* is not clearly weaker / invalid for *main*.
|
|
140
|
+
|
|
141
|
+
Returns ``(ok, skip_reason)``. Unknown families are allowed through (Claude
|
|
142
|
+
validates at launch). Clear mismatches return ``ok=False`` with a reason so
|
|
143
|
+
callers can skip ``--advisor`` instead of hard-exiting.
|
|
144
|
+
|
|
145
|
+
This is intentionally coarse — not Claude Code's full versioned matrix
|
|
146
|
+
(e.g. Sonnet 5 vs 4.6, Opus 4.6 vs 4.7+). Claude Code enforces those at launch.
|
|
147
|
+
"""
|
|
148
|
+
advisor = (advisor_model or "").strip()
|
|
149
|
+
if not advisor:
|
|
150
|
+
return False, "no advisor_model configured"
|
|
151
|
+
|
|
152
|
+
adv_family = normalize_model_family(advisor)
|
|
153
|
+
if adv_family == "haiku":
|
|
154
|
+
return False, "haiku cannot act as an advisor"
|
|
155
|
+
|
|
156
|
+
main_family = normalize_model_family(main_model)
|
|
157
|
+
if main_family is None or adv_family is None:
|
|
158
|
+
# Unknown / unset main: attach and let Claude validate.
|
|
159
|
+
return True, None
|
|
160
|
+
|
|
161
|
+
main_tier = _FAMILY_TIER.get(main_family)
|
|
162
|
+
adv_tier = _FAMILY_TIER.get(adv_family)
|
|
163
|
+
if main_tier is None or adv_tier is None:
|
|
164
|
+
return True, None
|
|
165
|
+
|
|
166
|
+
# Fable main only accepts Fable advisor (docs).
|
|
167
|
+
if main_family == "fable" and adv_family != "fable":
|
|
168
|
+
return False, f"fable main rejects {adv_family} advisor"
|
|
169
|
+
|
|
170
|
+
if adv_tier < main_tier:
|
|
171
|
+
return False, f"{adv_family} advisor is weaker than {main_family} main"
|
|
172
|
+
|
|
173
|
+
return True, None
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _env_flag_set(source: Mapping[str, str], key: str) -> bool:
|
|
177
|
+
value = str(source.get(key) or "").strip().lower()
|
|
178
|
+
return value in {"1", "true", "yes", "on"}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def advisor_provider_unsupported(env: dict[str, str] | None = None) -> str | None:
|
|
182
|
+
"""Return the cloud-provider env key if advisor cannot run (Bedrock/Vertex/Foundry)."""
|
|
183
|
+
source = env if env is not None else os.environ
|
|
184
|
+
for key in NON_ANTHROPIC_ADVISOR_ENVS:
|
|
185
|
+
if _env_flag_set(source, key):
|
|
186
|
+
return key
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def parse_claude_version(text: str) -> tuple[int, ...] | None:
|
|
191
|
+
"""Extract a dotted version tuple from ``claude --version`` output."""
|
|
192
|
+
match = re.search(r"(\d+)\.(\d+)\.(\d+)", text or "")
|
|
193
|
+
if not match:
|
|
194
|
+
return None
|
|
195
|
+
return tuple(int(part) for part in match.groups())
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@lru_cache(maxsize=1)
|
|
199
|
+
def probe_claude_version() -> tuple[int, ...] | None:
|
|
200
|
+
"""Best-effort ``claude --version`` parse; None when CLI missing or unreadable."""
|
|
201
|
+
executable = shutil.which("claude")
|
|
202
|
+
if not executable:
|
|
203
|
+
return None
|
|
204
|
+
try:
|
|
205
|
+
result = subprocess.run(
|
|
206
|
+
[executable, "--version"],
|
|
207
|
+
capture_output=True,
|
|
208
|
+
text=True,
|
|
209
|
+
timeout=5,
|
|
210
|
+
check=False,
|
|
211
|
+
)
|
|
212
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
213
|
+
return None
|
|
214
|
+
return parse_claude_version(f"{result.stdout or ''}\n{result.stderr or ''}")
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@lru_cache(maxsize=1)
|
|
218
|
+
def claude_supports_append_system_prompt() -> bool:
|
|
219
|
+
"""True when ``claude -h`` lists ``--append-system-prompt``."""
|
|
220
|
+
executable = shutil.which("claude")
|
|
221
|
+
if not executable:
|
|
222
|
+
return False
|
|
223
|
+
try:
|
|
224
|
+
result = subprocess.run(
|
|
225
|
+
[executable, "-h"],
|
|
226
|
+
capture_output=True,
|
|
227
|
+
text=True,
|
|
228
|
+
timeout=5,
|
|
229
|
+
check=False,
|
|
230
|
+
)
|
|
231
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
232
|
+
return False
|
|
233
|
+
hay = f"{result.stdout or ''}\n{result.stderr or ''}"
|
|
234
|
+
return "--append-system-prompt" in hay
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def advisor_disable_env_set(env: dict[str, str] | None = None) -> bool:
|
|
238
|
+
source = env if env is not None else os.environ
|
|
239
|
+
return _env_flag_set(source, DISABLE_ADVISOR_ENV)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def warn_advisor_preflight(
|
|
243
|
+
*,
|
|
244
|
+
env: dict[str, str] | None = None,
|
|
245
|
+
main_model: str | None = None,
|
|
246
|
+
advisor_model: str | None = None,
|
|
247
|
+
) -> list[str]:
|
|
248
|
+
"""Soft warnings for version floor / disable-env / Fable floor. Never blocks attaching."""
|
|
249
|
+
warnings: list[str] = []
|
|
250
|
+
if advisor_disable_env_set(env):
|
|
251
|
+
warnings.append(
|
|
252
|
+
f"{DISABLE_ADVISOR_ENV} is set; Claude Code will ignore --advisor / advisorModel."
|
|
253
|
+
)
|
|
254
|
+
version = probe_claude_version()
|
|
255
|
+
if version is not None and version < MIN_ADVISOR_CLAUDE_VERSION:
|
|
256
|
+
floor = ".".join(str(part) for part in MIN_ADVISOR_CLAUDE_VERSION)
|
|
257
|
+
current = ".".join(str(part) for part in version)
|
|
258
|
+
warnings.append(
|
|
259
|
+
f"Claude Code {current} is below the advisor floor ({floor}); "
|
|
260
|
+
"upgrade with `claude update` or advisor calls may fail."
|
|
261
|
+
)
|
|
262
|
+
uses_fable = any(
|
|
263
|
+
normalize_model_family(m) == "fable" for m in (main_model, advisor_model)
|
|
264
|
+
)
|
|
265
|
+
if uses_fable and version is not None and version < MIN_FABLE_ADVISOR_CLAUDE_VERSION:
|
|
266
|
+
floor = ".".join(str(part) for part in MIN_FABLE_ADVISOR_CLAUDE_VERSION)
|
|
267
|
+
current = ".".join(str(part) for part in version)
|
|
268
|
+
warnings.append(
|
|
269
|
+
f"Claude Code {current} is below the Fable advisor floor ({floor}); "
|
|
270
|
+
"Fable main/advisor pairs need Claude Code ≥ 2.1.170."
|
|
271
|
+
)
|
|
272
|
+
return warnings
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def strip_duplicate_advisor_args(extra_args: list[str]) -> list[str]:
|
|
276
|
+
"""Remove ``--advisor`` / ``--advisor=…`` from profile extras when DevCouncil attached."""
|
|
277
|
+
out: list[str] = []
|
|
278
|
+
skip_next = False
|
|
279
|
+
for part in extra_args:
|
|
280
|
+
if skip_next:
|
|
281
|
+
skip_next = False
|
|
282
|
+
continue
|
|
283
|
+
if part == "--advisor":
|
|
284
|
+
skip_next = True
|
|
285
|
+
continue
|
|
286
|
+
if part.startswith("--advisor="):
|
|
287
|
+
continue
|
|
288
|
+
out.append(part)
|
|
289
|
+
return out
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
AdvisorAttachDecision = Literal["attach", "skip"]
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def decide_advisor_attach(
|
|
296
|
+
*,
|
|
297
|
+
main_model: str | None,
|
|
298
|
+
advisor_model: str | None,
|
|
299
|
+
env: dict[str, str] | None = None,
|
|
300
|
+
) -> tuple[AdvisorAttachDecision, str | None, str | None]:
|
|
301
|
+
"""Return ``(decision, resolved_advisor, reason)`` for CLI/SDK wiring."""
|
|
302
|
+
resolved = (advisor_model or "").strip() or None
|
|
303
|
+
if not resolved:
|
|
304
|
+
return "skip", None, None
|
|
305
|
+
provider = advisor_provider_unsupported(env)
|
|
306
|
+
if provider:
|
|
307
|
+
return (
|
|
308
|
+
"skip",
|
|
309
|
+
resolved,
|
|
310
|
+
f"advisor requires Anthropic API; {provider} is set (soft-skip)",
|
|
311
|
+
)
|
|
312
|
+
ok, reason = advisor_pairing_ok(main_model, resolved)
|
|
313
|
+
if not ok:
|
|
314
|
+
return "skip", resolved, reason
|
|
315
|
+
return "attach", resolved, None
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
import shutil
|
|
4
5
|
from dataclasses import dataclass, field
|
|
5
6
|
from pathlib import Path
|
|
@@ -7,6 +8,8 @@ from typing import Any
|
|
|
7
8
|
|
|
8
9
|
from devcouncil.app.config import CliAgentProfileConfig, load_config
|
|
9
10
|
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
10
13
|
|
|
11
14
|
AGENT_ALIASES = {
|
|
12
15
|
"agy": "antigravity",
|
|
@@ -24,6 +27,11 @@ AGENT_ALIASES = {
|
|
|
24
27
|
"oz-cli": "warp",
|
|
25
28
|
"cursor-agent": "cursor",
|
|
26
29
|
"cursor-cli": "cursor",
|
|
30
|
+
"grok-build": "grok",
|
|
31
|
+
"grok-cli": "grok",
|
|
32
|
+
"gork": "grok",
|
|
33
|
+
"gork-build": "grok",
|
|
34
|
+
"xai-grok": "grok",
|
|
27
35
|
"copilot-cli": "copilot",
|
|
28
36
|
"github-copilot": "copilot",
|
|
29
37
|
"gh-copilot": "copilot",
|
|
@@ -46,6 +54,7 @@ BUILTIN_AGENT_NAMES = {
|
|
|
46
54
|
"copilot",
|
|
47
55
|
"crush",
|
|
48
56
|
"cursor",
|
|
57
|
+
"grok",
|
|
49
58
|
"gemini",
|
|
50
59
|
"goose",
|
|
51
60
|
"opencode",
|
|
@@ -56,6 +65,12 @@ BUILTIN_AGENT_NAMES = {
|
|
|
56
65
|
BUILTIN_CODING_EXECUTOR_NAMES = tuple(sorted(BUILTIN_AGENT_NAMES))
|
|
57
66
|
VALID_INPUT_MODES = {"stdin", "argument", "prompt-file"}
|
|
58
67
|
|
|
68
|
+
DEPRECATED_CODING_CLIS = frozenset({"gemini"})
|
|
69
|
+
GEMINI_DEPRECATION_MESSAGE = (
|
|
70
|
+
"Gemini CLI is deprecated (Google replaced it with Antigravity CLI). "
|
|
71
|
+
"Use: dev integrate antigravity --apply"
|
|
72
|
+
)
|
|
73
|
+
|
|
59
74
|
|
|
60
75
|
@dataclass(frozen=True)
|
|
61
76
|
class CliAgentSpec:
|
|
@@ -107,10 +122,10 @@ def resolve_cursor_agent_executable() -> str | None:
|
|
|
107
122
|
|
|
108
123
|
|
|
109
124
|
CODING_CLI_PROBE_ORDER: tuple[str, ...] = (
|
|
110
|
-
"codex",
|
|
111
|
-
"gemini",
|
|
112
125
|
"claude",
|
|
126
|
+
"codex",
|
|
113
127
|
"cursor",
|
|
128
|
+
"grok",
|
|
114
129
|
"opencode",
|
|
115
130
|
"antigravity",
|
|
116
131
|
"warp",
|
|
@@ -132,14 +147,17 @@ class CodingCliIntegrationInfo:
|
|
|
132
147
|
hooks: bool
|
|
133
148
|
launcher_shim: bool
|
|
134
149
|
notes: str
|
|
150
|
+
blocking_hooks: bool = False
|
|
151
|
+
deprecated: bool = False
|
|
135
152
|
|
|
136
153
|
@property
|
|
137
154
|
def enforcement(self) -> str:
|
|
138
|
-
"""Honest containment posture
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
155
|
+
"""Honest containment posture for the client's current native API."""
|
|
156
|
+
if self.blocking_hooks:
|
|
157
|
+
return "pre-action"
|
|
158
|
+
if self.hooks:
|
|
159
|
+
return "advisory+verify"
|
|
160
|
+
return "verify-only"
|
|
143
161
|
|
|
144
162
|
|
|
145
163
|
# Tier 1 = headless executor + verify; tier 2 = MCP companion; hooks = native pre-tool policy.
|
|
@@ -148,10 +166,19 @@ CODING_CLI_INTEGRATION_INFO: dict[str, CodingCliIntegrationInfo] = {
|
|
|
148
166
|
"codex", "Codex CLI", 1, True, True, True, True, "dev integrate codex --apply"
|
|
149
167
|
),
|
|
150
168
|
"gemini": CodingCliIntegrationInfo(
|
|
151
|
-
"gemini",
|
|
169
|
+
"gemini",
|
|
170
|
+
"Gemini CLI (deprecated)",
|
|
171
|
+
1,
|
|
172
|
+
True,
|
|
173
|
+
True,
|
|
174
|
+
True,
|
|
175
|
+
True,
|
|
176
|
+
"Deprecated — migrate to: dev integrate antigravity --apply",
|
|
177
|
+
blocking_hooks=True,
|
|
178
|
+
deprecated=True,
|
|
152
179
|
),
|
|
153
180
|
"claude": CodingCliIntegrationInfo(
|
|
154
|
-
"claude", "Claude Code", 1, True, True, True, True, "dev integrate claude --apply"
|
|
181
|
+
"claude", "Claude Code", 1, True, True, True, True, "dev integrate claude --apply", blocking_hooks=True
|
|
155
182
|
),
|
|
156
183
|
"cursor": CodingCliIntegrationInfo(
|
|
157
184
|
"cursor",
|
|
@@ -161,10 +188,22 @@ CODING_CLI_INTEGRATION_INFO: dict[str, CodingCliIntegrationInfo] = {
|
|
|
161
188
|
True,
|
|
162
189
|
True,
|
|
163
190
|
True,
|
|
164
|
-
"dev integrate cursor --apply; cursor-agent for headless",
|
|
191
|
+
"dev integrate cursor --apply; agent/cursor-agent for headless",
|
|
192
|
+
blocking_hooks=True,
|
|
193
|
+
),
|
|
194
|
+
"grok": CodingCliIntegrationInfo(
|
|
195
|
+
"grok",
|
|
196
|
+
"Grok Build",
|
|
197
|
+
1,
|
|
198
|
+
True,
|
|
199
|
+
True,
|
|
200
|
+
True,
|
|
201
|
+
True,
|
|
202
|
+
"dev integrate grok --apply",
|
|
203
|
+
blocking_hooks=True,
|
|
165
204
|
),
|
|
166
205
|
"opencode": CodingCliIntegrationInfo(
|
|
167
|
-
"opencode", "OpenCode", 1, True, True, True, True, "dev integrate opencode --apply"
|
|
206
|
+
"opencode", "OpenCode", 1, True, True, True, True, "dev integrate opencode --apply", blocking_hooks=True
|
|
168
207
|
),
|
|
169
208
|
"antigravity": CodingCliIntegrationInfo(
|
|
170
209
|
"antigravity",
|
|
@@ -204,7 +243,8 @@ CODING_CLI_VERSION_COMMANDS: dict[str, tuple[tuple[str, ...], ...]] = {
|
|
|
204
243
|
"codex": (("codex", "--version"),),
|
|
205
244
|
"gemini": (("gemini", "--version"),),
|
|
206
245
|
"claude": (("claude", "--version"),),
|
|
207
|
-
"cursor": (("cursor-agent", "--version"), ("cursor", "--version")),
|
|
246
|
+
"cursor": (("cursor-agent", "--version"), ("agent", "--version"), ("cursor", "--version")),
|
|
247
|
+
"grok": (("grok", "--version"),),
|
|
208
248
|
"opencode": (("opencode", "--version"),),
|
|
209
249
|
"antigravity": (("agy", "--version"),),
|
|
210
250
|
"warp": (("oz", "--version"),),
|
|
@@ -243,10 +283,36 @@ def resolve_coding_cli_probe_order(project_root: Path) -> tuple[str, ...]:
|
|
|
243
283
|
if configured:
|
|
244
284
|
return tuple(normalize_agent_name(name) for name in configured)
|
|
245
285
|
except Exception:
|
|
286
|
+
logger.debug("Could not load coding_cli_probe_order from config", exc_info=True)
|
|
246
287
|
pass
|
|
247
288
|
return CODING_CLI_PROBE_ORDER
|
|
248
289
|
|
|
249
290
|
|
|
291
|
+
def _configured_probe_order(project_root: Path) -> tuple[str, ...] | None:
|
|
292
|
+
try:
|
|
293
|
+
configured = load_config(project_root).execution.coding_cli_probe_order
|
|
294
|
+
if configured:
|
|
295
|
+
return tuple(normalize_agent_name(name) for name in configured)
|
|
296
|
+
except Exception:
|
|
297
|
+
logger.debug("Could not load coding_cli_probe_order from config", exc_info=True)
|
|
298
|
+
return None
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _deprecated_allowed_in_probe(
|
|
302
|
+
project_root: Path,
|
|
303
|
+
client: str,
|
|
304
|
+
*,
|
|
305
|
+
probe_order: tuple[str, ...] | None,
|
|
306
|
+
) -> bool:
|
|
307
|
+
normalized = normalize_agent_name(client)
|
|
308
|
+
if normalized not in DEPRECATED_CODING_CLIS:
|
|
309
|
+
return False
|
|
310
|
+
if probe_order is not None:
|
|
311
|
+
return normalized in {normalize_agent_name(name) for name in probe_order}
|
|
312
|
+
configured = _configured_probe_order(project_root)
|
|
313
|
+
return configured is not None and normalized in configured
|
|
314
|
+
|
|
315
|
+
|
|
250
316
|
def detect_available_coding_cli(
|
|
251
317
|
project_root: Path,
|
|
252
318
|
probe_order: tuple[str, ...] | None = None,
|
|
@@ -256,8 +322,14 @@ def detect_available_coding_cli(
|
|
|
256
322
|
# table and re-parse config.yaml). Resolution below mirrors
|
|
257
323
|
# resolve_coding_cli_executable() exactly, just against the prebuilt table.
|
|
258
324
|
specs = load_cli_agent_specs(project_root)
|
|
259
|
-
|
|
325
|
+
order = probe_order or resolve_coding_cli_probe_order(project_root)
|
|
326
|
+
for client in order:
|
|
260
327
|
normalized = normalize_agent_name(client)
|
|
328
|
+
if (
|
|
329
|
+
normalized in DEPRECATED_CODING_CLIS
|
|
330
|
+
and not _deprecated_allowed_in_probe(project_root, normalized, probe_order=probe_order)
|
|
331
|
+
):
|
|
332
|
+
continue
|
|
261
333
|
if normalized == "cursor":
|
|
262
334
|
if resolve_cursor_agent_executable():
|
|
263
335
|
return client
|
|
@@ -291,6 +363,27 @@ def resolve_automated_executor(
|
|
|
291
363
|
return detected or configured
|
|
292
364
|
|
|
293
365
|
|
|
366
|
+
RUN_EXECUTOR_SPECIAL = (
|
|
367
|
+
"manual",
|
|
368
|
+
"mini",
|
|
369
|
+
"openhands",
|
|
370
|
+
"claude-sdk",
|
|
371
|
+
"native",
|
|
372
|
+
"native-preview",
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def list_run_executor_names(project_root: Path) -> list[str]:
|
|
377
|
+
"""Executor names accepted by ``dev run --executor``."""
|
|
378
|
+
custom = {
|
|
379
|
+
name
|
|
380
|
+
for name, spec in load_cli_agent_specs(project_root).items()
|
|
381
|
+
if not spec.built_in
|
|
382
|
+
}
|
|
383
|
+
names = set(BUILTIN_AGENT_NAMES) | set(RUN_EXECUTOR_SPECIAL) | custom
|
|
384
|
+
return sorted(names)
|
|
385
|
+
|
|
386
|
+
|
|
294
387
|
def is_reserved_agent_name(name: str) -> bool:
|
|
295
388
|
return normalize_agent_name(name) in BUILTIN_AGENT_NAMES
|
|
296
389
|
|
|
@@ -319,11 +412,11 @@ def builtin_agent_specs(project_root: Path) -> dict[str, CliAgentSpec]:
|
|
|
319
412
|
),
|
|
320
413
|
"claude": CliAgentSpec(
|
|
321
414
|
name="claude",
|
|
322
|
-
# `-p` runs headless
|
|
323
|
-
#
|
|
324
|
-
#
|
|
415
|
+
# `-p` runs headless. Permission mode is profile-driven: yolo/auto adds
|
|
416
|
+
# `--permission-mode acceptEdits`; default/prod stay gated (no blanket
|
|
417
|
+
# auto-apply baked into the base spec).
|
|
325
418
|
command="claude",
|
|
326
|
-
args=["-p"
|
|
419
|
+
args=["-p"],
|
|
327
420
|
display_name="Claude Code",
|
|
328
421
|
kind="coding-cli",
|
|
329
422
|
supports_mcp=True,
|
|
@@ -395,6 +488,21 @@ def builtin_agent_specs(project_root: Path) -> dict[str, CliAgentSpec]:
|
|
|
395
488
|
supports_handoff=True,
|
|
396
489
|
built_in=True,
|
|
397
490
|
),
|
|
491
|
+
"grok": CliAgentSpec(
|
|
492
|
+
name="grok",
|
|
493
|
+
command="grok",
|
|
494
|
+
args=[
|
|
495
|
+
"-p",
|
|
496
|
+
"Read and execute the DevCouncil task prompt at {prompt_file}.",
|
|
497
|
+
],
|
|
498
|
+
input_mode="prompt-file",
|
|
499
|
+
display_name="Grok Build",
|
|
500
|
+
kind="coding-cli",
|
|
501
|
+
supports_mcp=True,
|
|
502
|
+
supports_diff_review=True,
|
|
503
|
+
supports_handoff=True,
|
|
504
|
+
built_in=True,
|
|
505
|
+
),
|
|
398
506
|
"aider": CliAgentSpec(
|
|
399
507
|
name="aider",
|
|
400
508
|
command="aider",
|