devcouncil 0.3.1 → 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,218 @@
|
|
|
1
|
+
"""One-command onboarding: setup, integrate, and run the full DevCouncil loop."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
|
|
12
|
+
from devcouncil.app.config import load_config
|
|
13
|
+
from devcouncil.cli.commands.doctor import render_doctor_check
|
|
14
|
+
from devcouncil.cli.commands.go import go as go_command
|
|
15
|
+
from devcouncil.cli.commands.init import initialize_project, parse_role_model_overrides
|
|
16
|
+
from devcouncil.cli.commands.setup import (
|
|
17
|
+
_configure_api_key,
|
|
18
|
+
_configure_coding_cli_integrations,
|
|
19
|
+
_configure_vertexai_settings,
|
|
20
|
+
_set_model_provider,
|
|
21
|
+
_set_model_roles,
|
|
22
|
+
)
|
|
23
|
+
from devcouncil.llm.provider import validate_model_provider
|
|
24
|
+
from devcouncil.repo.ci_scaffold import EVIDENCE_WORKFLOW_RELPATH, WORKFLOW_RELPATH, scaffold_ci, scaffold_evidence_ci
|
|
25
|
+
from devcouncil.telemetry.logging_setup import set_log_dir
|
|
26
|
+
from devcouncil.telemetry.stages import log_stage, log_step
|
|
27
|
+
|
|
28
|
+
console = Console()
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _run_setup_path(
|
|
33
|
+
root: Path,
|
|
34
|
+
*,
|
|
35
|
+
name: str | None,
|
|
36
|
+
provider: str | None,
|
|
37
|
+
model: str | None,
|
|
38
|
+
role_models: dict[str, str],
|
|
39
|
+
api_key: str | None,
|
|
40
|
+
skip_api_key: bool,
|
|
41
|
+
skip_integrations: bool,
|
|
42
|
+
skip_map: bool,
|
|
43
|
+
skip_skills: bool,
|
|
44
|
+
scaffold_ci_flag: bool,
|
|
45
|
+
scaffold_ci_evidence: bool,
|
|
46
|
+
gemini_scope: str,
|
|
47
|
+
) -> None:
|
|
48
|
+
created = initialize_project(
|
|
49
|
+
root,
|
|
50
|
+
project_name=name,
|
|
51
|
+
model_provider=validate_model_provider(provider) if provider else "openrouter",
|
|
52
|
+
model=model,
|
|
53
|
+
role_models=role_models,
|
|
54
|
+
with_map=not skip_map,
|
|
55
|
+
with_skills=not skip_skills,
|
|
56
|
+
)
|
|
57
|
+
if not created:
|
|
58
|
+
console.print(f"[yellow]DevCouncil is already initialized at {root / '.devcouncil'}.[/yellow]")
|
|
59
|
+
|
|
60
|
+
if provider:
|
|
61
|
+
_set_model_provider(root, provider)
|
|
62
|
+
|
|
63
|
+
_set_model_roles(root, model=model, role_models=role_models)
|
|
64
|
+
|
|
65
|
+
configured_provider = load_config(root).models.provider
|
|
66
|
+
_configure_vertexai_settings(root, configured_provider, None, None)
|
|
67
|
+
_configure_api_key(root, api_key, skip_api_key)
|
|
68
|
+
|
|
69
|
+
console.print()
|
|
70
|
+
render_doctor_check(root)
|
|
71
|
+
|
|
72
|
+
if scaffold_ci_flag:
|
|
73
|
+
written = scaffold_ci(root)
|
|
74
|
+
if written is None:
|
|
75
|
+
console.print(f"[yellow]{WORKFLOW_RELPATH.as_posix()} already exists; left unchanged.[/yellow]")
|
|
76
|
+
else:
|
|
77
|
+
console.print(f"[green]Wrote starter CI workflow {written.relative_to(root).as_posix()}.[/green]")
|
|
78
|
+
|
|
79
|
+
if scaffold_ci_evidence:
|
|
80
|
+
evidence_written = scaffold_evidence_ci(root)
|
|
81
|
+
if evidence_written is None:
|
|
82
|
+
console.print(
|
|
83
|
+
f"[yellow]{EVIDENCE_WORKFLOW_RELPATH.as_posix()} already exists; left unchanged.[/yellow]"
|
|
84
|
+
)
|
|
85
|
+
else:
|
|
86
|
+
console.print(
|
|
87
|
+
f"[green]Wrote evidence CI workflow {evidence_written.relative_to(root).as_posix()}.[/green]"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if not skip_integrations:
|
|
91
|
+
_configure_coding_cli_integrations(root, apply=True, gemini_scope=gemini_scope)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def boot(
|
|
95
|
+
ctx: typer.Context,
|
|
96
|
+
goal: str = typer.Argument(..., help="Implementation goal passed through to dev go."),
|
|
97
|
+
executor: str | None = typer.Option(
|
|
98
|
+
None,
|
|
99
|
+
"--executor",
|
|
100
|
+
"-e",
|
|
101
|
+
help="Automated executor to use. Defaults to execution.default_executor in .devcouncil/config.yaml.",
|
|
102
|
+
),
|
|
103
|
+
dry_run: bool = typer.Option(False, "--dry-run", help="Use mock planning responses for local smoke testing."),
|
|
104
|
+
quick: bool = typer.Option(
|
|
105
|
+
False,
|
|
106
|
+
"--quick",
|
|
107
|
+
help="Skip the planning council for a single spec + plan.",
|
|
108
|
+
),
|
|
109
|
+
force: bool = typer.Option(
|
|
110
|
+
False,
|
|
111
|
+
"--force",
|
|
112
|
+
"--yes",
|
|
113
|
+
"-y",
|
|
114
|
+
help="Proceed past unresolved planning gaps without manual approval.",
|
|
115
|
+
),
|
|
116
|
+
continue_on_blocked: bool = typer.Option(
|
|
117
|
+
False,
|
|
118
|
+
"--continue-on-blocked",
|
|
119
|
+
help="Continue later tasks even if an earlier task is blocked by verification.",
|
|
120
|
+
),
|
|
121
|
+
json_report: bool = typer.Option(False, "--json-report", "--json", help="Print the final report as JSON."),
|
|
122
|
+
report_file: Path | None = typer.Option(
|
|
123
|
+
None,
|
|
124
|
+
"--report-file",
|
|
125
|
+
help="Write the final report to a file. Relative paths resolve from --project-root.",
|
|
126
|
+
),
|
|
127
|
+
agent: bool = typer.Option(
|
|
128
|
+
False,
|
|
129
|
+
"--agent",
|
|
130
|
+
help="Use coding-agent defaults: JSON report plus .devcouncil/reports/latest.json.",
|
|
131
|
+
),
|
|
132
|
+
profile: str | None = typer.Option(None, "--profile", help="CLI-agent execution profile to pass to dev run."),
|
|
133
|
+
stream: bool = typer.Option(
|
|
134
|
+
False,
|
|
135
|
+
"--stream",
|
|
136
|
+
help="Stream coding CLI stdout/stderr live during execution.",
|
|
137
|
+
),
|
|
138
|
+
project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
|
|
139
|
+
name: str | None = typer.Option(None, "--name", "-n", help="Project name for .devcouncil/config.yaml."),
|
|
140
|
+
provider: str | None = typer.Option(None, "--provider", help="Set models.provider before configuring the API key."),
|
|
141
|
+
model: str | None = typer.Option(None, "--model", "-m", help="Model id to use for every default role."),
|
|
142
|
+
role_model: list[str] | None = typer.Option(
|
|
143
|
+
None,
|
|
144
|
+
"--role-model",
|
|
145
|
+
help="Per-role model override in ROLE=MODEL form. Can be repeated.",
|
|
146
|
+
),
|
|
147
|
+
api_key: str | None = typer.Option(None, "--api-key", help="Store the configured provider API key in local secrets."),
|
|
148
|
+
skip_api_key: bool = typer.Option(False, "--skip-api-key", help="Skip the first-run model API key prompt."),
|
|
149
|
+
skip_integrations: bool = typer.Option(
|
|
150
|
+
False,
|
|
151
|
+
"--skip-integrations",
|
|
152
|
+
help="Skip applying coding CLI integrations (default applies dev integrate --apply).",
|
|
153
|
+
),
|
|
154
|
+
skip_map: bool = typer.Option(False, "--skip-map", help="Skip generating repo_map.json and agent guides on init."),
|
|
155
|
+
skip_skills: bool = typer.Option(False, "--skip-skills", help="Skip scaffolding engineering skills on init."),
|
|
156
|
+
scaffold_ci_flag: bool = typer.Option(False, "--scaffold-ci", help="Write a starter GitHub Actions workflow."),
|
|
157
|
+
scaffold_ci_evidence: bool = typer.Option(
|
|
158
|
+
False,
|
|
159
|
+
"--scaffold-ci-evidence",
|
|
160
|
+
help="Write .github/workflows/devcouncil-evidence.yml.",
|
|
161
|
+
),
|
|
162
|
+
gemini_scope: str = typer.Option("project", "--gemini-scope", help="Gemini MCP config scope: project or user."),
|
|
163
|
+
):
|
|
164
|
+
"""
|
|
165
|
+
Initialize the repo, apply integrations, and run the full DevCouncil loop.
|
|
166
|
+
"""
|
|
167
|
+
if gemini_scope not in {"project", "user"}:
|
|
168
|
+
console.print("[red]--gemini-scope must be 'project' or 'user'.[/red]")
|
|
169
|
+
raise typer.Exit(code=2)
|
|
170
|
+
|
|
171
|
+
root = project_root.expanduser().resolve()
|
|
172
|
+
set_log_dir(root)
|
|
173
|
+
logger.info("dev boot: goal=%r skip_integrations=%s", goal, skip_integrations)
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
role_models = parse_role_model_overrides(role_model)
|
|
177
|
+
if provider:
|
|
178
|
+
validate_model_provider(provider)
|
|
179
|
+
except ValueError as exc:
|
|
180
|
+
console.print(f"[red]{exc}[/red]")
|
|
181
|
+
raise typer.Exit(code=2) from exc
|
|
182
|
+
|
|
183
|
+
effective_skip_api_key = skip_api_key or not sys.stdin.isatty()
|
|
184
|
+
|
|
185
|
+
with log_stage("boot", project_root=root, goal=goal):
|
|
186
|
+
log_step("boot/1: setup", project_root=root, trace=True)
|
|
187
|
+
_run_setup_path(
|
|
188
|
+
root,
|
|
189
|
+
name=name,
|
|
190
|
+
provider=provider,
|
|
191
|
+
model=model,
|
|
192
|
+
role_models=role_models,
|
|
193
|
+
api_key=api_key,
|
|
194
|
+
skip_api_key=effective_skip_api_key,
|
|
195
|
+
skip_integrations=skip_integrations,
|
|
196
|
+
skip_map=skip_map,
|
|
197
|
+
skip_skills=skip_skills,
|
|
198
|
+
scaffold_ci_flag=scaffold_ci_flag,
|
|
199
|
+
scaffold_ci_evidence=scaffold_ci_evidence,
|
|
200
|
+
gemini_scope=gemini_scope,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
log_step("boot/2: go", project_root=root, trace=True)
|
|
204
|
+
go_command(
|
|
205
|
+
ctx,
|
|
206
|
+
goal=goal,
|
|
207
|
+
executor=executor,
|
|
208
|
+
dry_run=dry_run,
|
|
209
|
+
quick=quick,
|
|
210
|
+
force=force,
|
|
211
|
+
continue_on_blocked=continue_on_blocked,
|
|
212
|
+
json_report=json_report,
|
|
213
|
+
report_file=report_file,
|
|
214
|
+
agent=agent,
|
|
215
|
+
profile=profile,
|
|
216
|
+
stream=stream,
|
|
217
|
+
project_root=root,
|
|
218
|
+
)
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
"""``dev campaign`` — run the planned task graph as a parallel multi-agent campaign.
|
|
2
|
+
|
|
3
|
+
The Director relays your goal to the Coordinator, who dispatches the plan's tasks to a
|
|
4
|
+
pool of Worker in parallel (dependency-aware) and routes each finished task to
|
|
5
|
+
the Reviewer for verification. It reuses DevCouncil's real executors and Verifier;
|
|
6
|
+
without a ``--executor`` it performs a dry run so you can preview the campaign plan.
|
|
7
|
+
|
|
8
|
+
dev campaign run "Ship the settings page" --executor claude --workers 4
|
|
9
|
+
dev campaign status # print the campaign dashboard
|
|
10
|
+
dev campaign inbox coordinator # read an agent's mailbox
|
|
11
|
+
dev campaign roster # show the role hierarchy
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from devcouncil.utils.json_persist import dump_json
|
|
17
|
+
import logging
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import List, Optional
|
|
20
|
+
|
|
21
|
+
import typer
|
|
22
|
+
from rich.console import Console
|
|
23
|
+
from rich.panel import Panel
|
|
24
|
+
from rich.table import Table
|
|
25
|
+
|
|
26
|
+
from devcouncil.campaign import (
|
|
27
|
+
Mailbox,
|
|
28
|
+
ROLES,
|
|
29
|
+
Rank,
|
|
30
|
+
Campaign,
|
|
31
|
+
build_coding_executor_factory,
|
|
32
|
+
build_verifier_fn,
|
|
33
|
+
)
|
|
34
|
+
from devcouncil.campaign.notify import Notifier, NullNotifier
|
|
35
|
+
from devcouncil.telemetry.stages import log_stage, log_step
|
|
36
|
+
|
|
37
|
+
app = typer.Typer(help="Run a multi-agent campaign over the planned task graph.")
|
|
38
|
+
console = Console()
|
|
39
|
+
logger = logging.getLogger(__name__)
|
|
40
|
+
|
|
41
|
+
# Task statuses that still need work (everything else is a satisfied prerequisite).
|
|
42
|
+
_ACTIONABLE = {"planned", "ready", "blocked", "running"}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _load_plan(root: Path):
|
|
46
|
+
"""Return ``(all_tasks, requirements)`` from the DevCouncil store."""
|
|
47
|
+
from devcouncil.storage.db import get_db
|
|
48
|
+
from devcouncil.storage.repositories import RequirementRepository, TaskRepository
|
|
49
|
+
|
|
50
|
+
db = get_db(root)
|
|
51
|
+
if not db:
|
|
52
|
+
return [], []
|
|
53
|
+
with db.get_session() as session:
|
|
54
|
+
tasks = TaskRepository(session).get_all()
|
|
55
|
+
reqs = RequirementRepository(session).get_all()
|
|
56
|
+
return tasks, reqs
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _persist_statuses(root: Path, tasks) -> None:
|
|
60
|
+
from devcouncil.storage.db import get_db
|
|
61
|
+
from devcouncil.storage.repositories import TaskRepository
|
|
62
|
+
|
|
63
|
+
db = get_db(root)
|
|
64
|
+
if not db:
|
|
65
|
+
return
|
|
66
|
+
with db.get_session() as session:
|
|
67
|
+
repo = TaskRepository(session)
|
|
68
|
+
for task in tasks:
|
|
69
|
+
repo.save(task)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@app.command("run")
|
|
73
|
+
def run_campaign(
|
|
74
|
+
goal: str = typer.Argument(..., help="The order to give the Director (a natural-language goal)."),
|
|
75
|
+
project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
|
|
76
|
+
workers: int = typer.Option(4, "--workers", "-n", min=1, help="Number of Worker (worker slots)."),
|
|
77
|
+
max_parallel: int = typer.Option(
|
|
78
|
+
4,
|
|
79
|
+
"--max-parallel",
|
|
80
|
+
"-p",
|
|
81
|
+
min=1,
|
|
82
|
+
help=(
|
|
83
|
+
"Max tasks dispatched at once. Tasks with overlapping writable "
|
|
84
|
+
"planned_files are serialized automatically (shared git working tree)."
|
|
85
|
+
),
|
|
86
|
+
),
|
|
87
|
+
executor: Optional[str] = typer.Option(
|
|
88
|
+
None,
|
|
89
|
+
"--executor",
|
|
90
|
+
"-e",
|
|
91
|
+
help="Coding CLI the Worker use (claude, codex, …). Omit for a dry run (no changes).",
|
|
92
|
+
),
|
|
93
|
+
profile: Optional[str] = typer.Option(None, "--profile", help="Executor profile (default/yolo/prod)."),
|
|
94
|
+
verify: bool = typer.Option(True, "--verify/--no-verify", help="Reviewer QC via the DevCouncil Verifier."),
|
|
95
|
+
serial_verify: bool = typer.Option(
|
|
96
|
+
True,
|
|
97
|
+
"--serial-verify/--parallel-verify",
|
|
98
|
+
help="Run Reviewer QC one task at a time (safe against git races) vs. concurrently.",
|
|
99
|
+
),
|
|
100
|
+
stream: bool = typer.Option(False, "--stream", help="Stream executor output."),
|
|
101
|
+
ntfy_topic: Optional[str] = typer.Option(None, "--ntfy-topic", help="Push campaign updates to this ntfy topic."),
|
|
102
|
+
json_format: bool = typer.Option(False, "--json", help="Emit a machine-readable result."),
|
|
103
|
+
fail_on_blocking: bool = typer.Option(
|
|
104
|
+
False, "--fail-on-blocking", help="Exit non-zero if any task ends blocked (for CI/agents)."
|
|
105
|
+
),
|
|
106
|
+
cost_budget: Optional[float] = typer.Option(
|
|
107
|
+
None, "--cost-budget", min=0.0, help="Halt campaign when model-call spend exceeds this USD cap."
|
|
108
|
+
),
|
|
109
|
+
leases: Optional[bool] = typer.Option(
|
|
110
|
+
None, "--leases/--no-leases", help="Checkout per-task leases during execution (default: on with --executor)."
|
|
111
|
+
),
|
|
112
|
+
):
|
|
113
|
+
"""Start the campaign and execute the plan for GOAL."""
|
|
114
|
+
from devcouncil.cli.commands.init import initialize_project
|
|
115
|
+
|
|
116
|
+
root = project_root.expanduser().resolve()
|
|
117
|
+
from devcouncil.telemetry.logging_setup import set_log_dir
|
|
118
|
+
|
|
119
|
+
set_log_dir(root)
|
|
120
|
+
logger.info("dev campaign run: executor=%s worker=%s", executor, workers)
|
|
121
|
+
|
|
122
|
+
with log_stage("campaign", project_root=root, subcommand="run"):
|
|
123
|
+
initialize_project(root, quiet=True)
|
|
124
|
+
log_step("campaign/1: loading plan", project_root=root, trace=True)
|
|
125
|
+
tasks, reqs = _load_plan(root)
|
|
126
|
+
if not tasks:
|
|
127
|
+
console.print(
|
|
128
|
+
"[yellow]No plan found. Run [bold]dev plan[/bold] first to create tasks for the Director.[/yellow]"
|
|
129
|
+
)
|
|
130
|
+
raise typer.Exit(code=0)
|
|
131
|
+
|
|
132
|
+
dry_run = executor is None
|
|
133
|
+
if len(tasks) >= 5 and not dry_run:
|
|
134
|
+
console.print(
|
|
135
|
+
f"[dim]Large plan ({len(tasks)} tasks) — campaign parallelizes dependency waves "
|
|
136
|
+
f"with Reviewer QC. See .devcouncil/campaign/dashboard.md for progress.[/dim]"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
if dry_run:
|
|
140
|
+
console.print(
|
|
141
|
+
"[cyan]Dry run — no --executor given. The Coordinator will assign and route, "
|
|
142
|
+
"but Worker will not touch the repo.[/cyan]"
|
|
143
|
+
)
|
|
144
|
+
executor_factory = None # campaign default = echo executor
|
|
145
|
+
verify_fn = None # campaign default = pass-through QC
|
|
146
|
+
else:
|
|
147
|
+
assert executor is not None # dry_run is False iff --executor was given
|
|
148
|
+
executor_factory = build_coding_executor_factory(root, executor, profile=profile, stream=stream)
|
|
149
|
+
if verify:
|
|
150
|
+
from devcouncil.cli.commands.run import _build_verification_router
|
|
151
|
+
|
|
152
|
+
verify_fn = build_verifier_fn(root, _build_verification_router(root))
|
|
153
|
+
else:
|
|
154
|
+
verify_fn = None
|
|
155
|
+
|
|
156
|
+
notifier: Notifier = Notifier(topic=ntfy_topic) if ntfy_topic else NullNotifier()
|
|
157
|
+
|
|
158
|
+
use_leases = leases if leases is not None else (not dry_run)
|
|
159
|
+
budget = cost_budget
|
|
160
|
+
if budget is None:
|
|
161
|
+
try:
|
|
162
|
+
from devcouncil.app.config import load_config
|
|
163
|
+
|
|
164
|
+
budget = load_config(root).telemetry.cost_budget_usd
|
|
165
|
+
except Exception:
|
|
166
|
+
budget = None
|
|
167
|
+
|
|
168
|
+
campaign = Campaign(
|
|
169
|
+
root,
|
|
170
|
+
goal=goal,
|
|
171
|
+
tasks=tasks,
|
|
172
|
+
requirements=reqs,
|
|
173
|
+
num_workers=workers,
|
|
174
|
+
max_parallel=max_parallel,
|
|
175
|
+
executor_factory=executor_factory,
|
|
176
|
+
verify_fn=verify_fn,
|
|
177
|
+
notifier=notifier,
|
|
178
|
+
verify_serialized=serial_verify,
|
|
179
|
+
on_event=None if json_format else (lambda m: console.print(f" {m}")),
|
|
180
|
+
use_leases=use_leases,
|
|
181
|
+
cost_budget_usd=budget,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
log_step("campaign/2: dispatching", project_root=root, trace=True)
|
|
185
|
+
result = campaign.run()
|
|
186
|
+
|
|
187
|
+
if not dry_run:
|
|
188
|
+
touched = {o.task_id for o in result.outcomes}
|
|
189
|
+
_persist_statuses(root, [t for t in tasks if t.id in touched])
|
|
190
|
+
|
|
191
|
+
if json_format:
|
|
192
|
+
payload = {
|
|
193
|
+
"goal": result.goal,
|
|
194
|
+
"success": result.success,
|
|
195
|
+
"halted": result.halted,
|
|
196
|
+
"halt_reason": result.halt_reason,
|
|
197
|
+
"verified": result.verified,
|
|
198
|
+
"blocked": result.blocked,
|
|
199
|
+
"skipped": result.skipped,
|
|
200
|
+
"dashboard": str(result.dashboard_path) if result.dashboard_path else None,
|
|
201
|
+
"outcomes": [
|
|
202
|
+
{
|
|
203
|
+
"task_id": o.task_id,
|
|
204
|
+
"title": o.title,
|
|
205
|
+
"owner": o.owner,
|
|
206
|
+
"bloom": o.bloom,
|
|
207
|
+
"status": o.status,
|
|
208
|
+
"blocking_gaps": o.blocking_gaps,
|
|
209
|
+
}
|
|
210
|
+
for o in result.outcomes
|
|
211
|
+
],
|
|
212
|
+
}
|
|
213
|
+
typer.echo(dump_json(payload, indent=2))
|
|
214
|
+
else:
|
|
215
|
+
_render_result(result)
|
|
216
|
+
|
|
217
|
+
log_step("campaign/complete", project_root=root, trace=True)
|
|
218
|
+
if result.halted or (fail_on_blocking and result.blocked):
|
|
219
|
+
raise typer.Exit(code=1)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _render_result(result) -> None:
|
|
223
|
+
table = Table(title="Director Campaign")
|
|
224
|
+
table.add_column("Task", style="cyan")
|
|
225
|
+
table.add_column("Owner")
|
|
226
|
+
table.add_column("Bloom")
|
|
227
|
+
table.add_column("Verdict")
|
|
228
|
+
verdict_style = {
|
|
229
|
+
"verified": "[green]verified[/green]",
|
|
230
|
+
"blocked": "[red]blocked[/red]",
|
|
231
|
+
"failed": "[red]failed[/red]",
|
|
232
|
+
"skipped": "[yellow]skipped[/yellow]",
|
|
233
|
+
}
|
|
234
|
+
for o in result.outcomes:
|
|
235
|
+
table.add_row(o.title[:48], o.owner, o.bloom, verdict_style.get(o.status, o.status))
|
|
236
|
+
console.print(table)
|
|
237
|
+
console.print(
|
|
238
|
+
Panel(
|
|
239
|
+
result.summary_line(),
|
|
240
|
+
title="Result",
|
|
241
|
+
expand=False,
|
|
242
|
+
border_style="green" if result.success else "yellow",
|
|
243
|
+
)
|
|
244
|
+
)
|
|
245
|
+
if result.dashboard_path:
|
|
246
|
+
console.print(f"[dim]Dashboard: {result.dashboard_path}[/dim]")
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
@app.command("status")
|
|
250
|
+
def status(
|
|
251
|
+
project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
|
|
252
|
+
):
|
|
253
|
+
"""Print the campaign dashboard (written by the Coordinator)."""
|
|
254
|
+
root = project_root.expanduser().resolve()
|
|
255
|
+
path = root / ".devcouncil" / "campaign" / "dashboard.md"
|
|
256
|
+
if not path.exists():
|
|
257
|
+
console.print("[yellow]No campaign has been run yet — no dashboard to show.[/yellow]")
|
|
258
|
+
raise typer.Exit(code=0)
|
|
259
|
+
console.print(path.read_text(encoding="utf-8"))
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
@app.command("inbox")
|
|
263
|
+
def inbox(
|
|
264
|
+
agent: str = typer.Argument(..., help="Agent id: director, coordinator, worker1…, reviewer."),
|
|
265
|
+
project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
|
|
266
|
+
unread_only: bool = typer.Option(False, "--unread", help="Show only unread messages."),
|
|
267
|
+
):
|
|
268
|
+
"""Read an agent's mailbox — the on-disk message bus."""
|
|
269
|
+
root = project_root.expanduser().resolve()
|
|
270
|
+
mailbox = Mailbox(root)
|
|
271
|
+
messages = mailbox.unread(agent) if unread_only else mailbox.all(agent)
|
|
272
|
+
if not messages:
|
|
273
|
+
console.print(f"[dim]{agent}'s mailbox is empty.[/dim]")
|
|
274
|
+
raise typer.Exit(code=0)
|
|
275
|
+
table = Table(title=f"{agent} — {len(messages)} message(s)")
|
|
276
|
+
table.add_column("From", style="cyan")
|
|
277
|
+
table.add_column("Type")
|
|
278
|
+
table.add_column("Content")
|
|
279
|
+
table.add_column("Read", justify="center")
|
|
280
|
+
for m in messages:
|
|
281
|
+
table.add_row(m.from_agent, m.type, m.content[:60], "•" if m.read else "[yellow]![/yellow]")
|
|
282
|
+
console.print(table)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
@app.command("roster")
|
|
286
|
+
def roster():
|
|
287
|
+
"""Show the role hierarchy and each rank's remit."""
|
|
288
|
+
order: List[Rank] = [Rank.DIRECTOR, Rank.COORDINATOR, Rank.WORKER, Rank.REVIEWER]
|
|
289
|
+
table = Table(title="Director Chain of Command")
|
|
290
|
+
table.add_column("Rank", style="magenta")
|
|
291
|
+
table.add_column("Title")
|
|
292
|
+
table.add_column("Reports to")
|
|
293
|
+
table.add_column("Remit")
|
|
294
|
+
for rank in order:
|
|
295
|
+
role = ROLES[rank]
|
|
296
|
+
table.add_row(
|
|
297
|
+
rank.value,
|
|
298
|
+
role.title,
|
|
299
|
+
role.reports_to.value if role.reports_to else "the operator",
|
|
300
|
+
role.summary,
|
|
301
|
+
)
|
|
302
|
+
console.print(table)
|