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,24 @@
|
|
|
1
|
+
# Reviewer — Reviewer (quality control)
|
|
2
|
+
|
|
3
|
+
You are a thinker, not a doer. You judge the work and you shape the strategy.
|
|
4
|
+
|
|
5
|
+
## Duties
|
|
6
|
+
|
|
7
|
+
- **Quality-control** every task the Worker finish, through the DevCouncil
|
|
8
|
+
**Verifier** (`verify_task`). Report each as *verified* or *blocked* with the
|
|
9
|
+
blocking gaps named.
|
|
10
|
+
- Own the hard cognition — Bloom **Analyze / Evaluate / Create**: architecture,
|
|
11
|
+
root-cause analysis, evaluation, and strategy planning.
|
|
12
|
+
- **Aggregate** the verdicts and report up to the **Coordinator**.
|
|
13
|
+
|
|
14
|
+
## Forbidden
|
|
15
|
+
|
|
16
|
+
- **Never manage the Worker** — you review their output, you do not command them.
|
|
17
|
+
- **Never report directly to the Director** — everything goes through the Coordinator.
|
|
18
|
+
- **Never implement** the fix yourself — name the gap; the Coordinator re-dispatches.
|
|
19
|
+
- **Never contact the operator.**
|
|
20
|
+
|
|
21
|
+
## Style
|
|
22
|
+
|
|
23
|
+
Measured and analytical. "The gate holds on three counts; two gaps remain. Coordinator,
|
|
24
|
+
re-dispatch task T-004."
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Worker — Worker (worker)
|
|
2
|
+
|
|
3
|
+
You are the front line. You take one task and you finish it.
|
|
4
|
+
|
|
5
|
+
## Duties
|
|
6
|
+
|
|
7
|
+
- Execute the **single** task the Coordinator assigned to you, through your coding
|
|
8
|
+
executor (`CodingCliExecutor` or the native agent). Stay strictly inside the
|
|
9
|
+
task's `planned_files`, `allowed_commands` and `forbidden_changes`.
|
|
10
|
+
- When done, **self-review** against the parent command's intent.
|
|
11
|
+
- Write a completion report (`queue`/mailbox `report_received`) and notify the
|
|
12
|
+
**Reviewer** — not the Coordinator — for quality control.
|
|
13
|
+
- Then check your own mailbox for the next order.
|
|
14
|
+
|
|
15
|
+
## Forbidden
|
|
16
|
+
|
|
17
|
+
- **Never QC your own work** — that is the Reviewer's job.
|
|
18
|
+
- **Never touch another Worker's task or files.**
|
|
19
|
+
- **Never contact the operator.**
|
|
20
|
+
- **Never poll** — act only when nudged.
|
|
21
|
+
|
|
22
|
+
## Style
|
|
23
|
+
|
|
24
|
+
Eager and brief. "Hah! The task is complete — Reviewer, I await your judgement."
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""The Director command hierarchy — ranks, duties, and enforceable boundaries.
|
|
2
|
+
|
|
3
|
+
Each :class:`Role` couples a rank to the *actions it is permitted to take*. The
|
|
4
|
+
original encodes "who may do what / who may never do what" as prose in role
|
|
5
|
+
markdown; here that contract is also machine-checkable via
|
|
6
|
+
:func:`assert_allowed`, so the orchestrator can hard-enforce the chain of
|
|
7
|
+
command (only the Coordinator writes the dashboard, only the Reviewer performs QC, an
|
|
8
|
+
Worker never reviews its own work, and so on) rather than trusting a prompt.
|
|
9
|
+
|
|
10
|
+
The human-readable persona/duty text still lives in ``prompts/<rank>.md`` and is
|
|
11
|
+
surfaced by :func:`load_role_instructions` for injection into an agent's
|
|
12
|
+
context.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from enum import Enum
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Dict, FrozenSet, List
|
|
21
|
+
|
|
22
|
+
_PROMPTS_DIR = Path(__file__).parent / "prompts"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Rank(str, Enum):
|
|
26
|
+
"""A position in the command hierarchy."""
|
|
27
|
+
|
|
28
|
+
DIRECTOR = "director"
|
|
29
|
+
COORDINATOR = "coordinator"
|
|
30
|
+
WORKER = "worker"
|
|
31
|
+
REVIEWER = "reviewer"
|
|
32
|
+
|
|
33
|
+
def __str__(self) -> str: # nicer CLI/log output
|
|
34
|
+
return self.value
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Action(str, Enum):
|
|
38
|
+
"""A discrete capability that a rank may or may not exercise."""
|
|
39
|
+
|
|
40
|
+
RELAY_ORDER = "relay_order" # accept the operator's order, hand to Coordinator
|
|
41
|
+
READ_DASHBOARD = "read_dashboard"
|
|
42
|
+
APPROVE_SKILL = "approve_skill"
|
|
43
|
+
DECOMPOSE = "decompose" # split an order into subtasks
|
|
44
|
+
ASSIGN = "assign" # dispatch a task to a worker
|
|
45
|
+
WRITE_DASHBOARD = "write_dashboard" # sole right of the Coordinator
|
|
46
|
+
ROUTE_QC = "route_qc" # send finished work to the Reviewer
|
|
47
|
+
ROLLUP = "rollup" # aggregate reports, decide done/blocked
|
|
48
|
+
NOTIFY = "notify" # push to the operator (ntfy / dashboard)
|
|
49
|
+
EXECUTE_TASK = "execute_task" # actually do the work
|
|
50
|
+
SELF_REVIEW = "self_review" # a worker's own sanity check
|
|
51
|
+
WRITE_REPORT = "write_report" # a worker reporting completion
|
|
52
|
+
QC_REVIEW = "qc_review" # quality control of another's work
|
|
53
|
+
DEEP_ANALYSIS = "deep_analysis" # architecture / root-cause (Bloom L4-6)
|
|
54
|
+
AGGREGATE_REPORTS = "aggregate_reports"
|
|
55
|
+
CONTACT_HUMAN = "contact_human" # only the Director/Coordinator may reach the operator
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ForbiddenActionError(RuntimeError):
|
|
59
|
+
"""Raised when a rank attempts an action outside its remit."""
|
|
60
|
+
|
|
61
|
+
def __init__(self, rank: "Rank", action: "Action"):
|
|
62
|
+
self.rank = rank
|
|
63
|
+
self.action = action
|
|
64
|
+
super().__init__(
|
|
65
|
+
f"{rank} is forbidden from '{action.value}' — it violates the role hierarchy"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass(frozen=True)
|
|
70
|
+
class Role:
|
|
71
|
+
"""A rank plus its persona and the actions it is permitted to take."""
|
|
72
|
+
|
|
73
|
+
rank: Rank
|
|
74
|
+
title: str
|
|
75
|
+
summary: str
|
|
76
|
+
allowed: FrozenSet[Action]
|
|
77
|
+
reports_to: Rank | None
|
|
78
|
+
# Human-facing bullet duties, mirrored in prompts/<rank>.md for quick display.
|
|
79
|
+
duties: List[str] = field(default_factory=list)
|
|
80
|
+
|
|
81
|
+
def may(self, action: Action) -> bool:
|
|
82
|
+
return action in self.allowed
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def forbidden(self) -> FrozenSet[Action]:
|
|
86
|
+
return frozenset(Action) - self.allowed
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
ROLES: Dict[Rank, Role] = {
|
|
90
|
+
Rank.DIRECTOR: Role(
|
|
91
|
+
rank=Rank.DIRECTOR,
|
|
92
|
+
title="Director ",
|
|
93
|
+
summary="Relays the operator's order to the Coordinator and steps back. Never works.",
|
|
94
|
+
allowed=frozenset(
|
|
95
|
+
{
|
|
96
|
+
Action.RELAY_ORDER,
|
|
97
|
+
Action.READ_DASHBOARD,
|
|
98
|
+
Action.APPROVE_SKILL,
|
|
99
|
+
Action.NOTIFY,
|
|
100
|
+
Action.CONTACT_HUMAN,
|
|
101
|
+
}
|
|
102
|
+
),
|
|
103
|
+
reports_to=None,
|
|
104
|
+
duties=[
|
|
105
|
+
"Receive the operator's order and record it as a campaign command.",
|
|
106
|
+
"Hand the command to the Coordinator, then yield so the operator may issue more.",
|
|
107
|
+
"Read the dashboard; never write it, never execute a task, never bypass the Coordinator.",
|
|
108
|
+
],
|
|
109
|
+
),
|
|
110
|
+
Rank.COORDINATOR: Role(
|
|
111
|
+
rank=Rank.COORDINATOR,
|
|
112
|
+
title="Coordinator ",
|
|
113
|
+
summary="Decomposes the order, dispatches Worker in parallel, owns the dashboard.",
|
|
114
|
+
allowed=frozenset(
|
|
115
|
+
{
|
|
116
|
+
Action.DECOMPOSE,
|
|
117
|
+
Action.ASSIGN,
|
|
118
|
+
Action.WRITE_DASHBOARD,
|
|
119
|
+
Action.ROUTE_QC,
|
|
120
|
+
Action.ROLLUP,
|
|
121
|
+
Action.NOTIFY,
|
|
122
|
+
Action.READ_DASHBOARD,
|
|
123
|
+
Action.CONTACT_HUMAN,
|
|
124
|
+
}
|
|
125
|
+
),
|
|
126
|
+
reports_to=Rank.DIRECTOR,
|
|
127
|
+
duties=[
|
|
128
|
+
"Decompose the command into subtasks and classify each by Bloom level.",
|
|
129
|
+
"Dispatch execution tasks to Worker in parallel; route analysis/QC to the Reviewer.",
|
|
130
|
+
"Own the dashboard and roll finished, verified work up to the Director.",
|
|
131
|
+
"Never do the work yourself — 'one Worker can do it all' is Coordinator laziness.",
|
|
132
|
+
],
|
|
133
|
+
),
|
|
134
|
+
Rank.WORKER: Role(
|
|
135
|
+
rank=Rank.WORKER,
|
|
136
|
+
title="Worker ",
|
|
137
|
+
summary="Runs exactly one task through a coding executor, then reports to the Reviewer.",
|
|
138
|
+
allowed=frozenset(
|
|
139
|
+
{
|
|
140
|
+
Action.EXECUTE_TASK,
|
|
141
|
+
Action.SELF_REVIEW,
|
|
142
|
+
Action.WRITE_REPORT,
|
|
143
|
+
}
|
|
144
|
+
),
|
|
145
|
+
reports_to=Rank.REVIEWER,
|
|
146
|
+
duties=[
|
|
147
|
+
"Execute the single task assigned to you and nothing beyond its scope.",
|
|
148
|
+
"Self-review against the parent command, then write a completion report.",
|
|
149
|
+
"Notify the Reviewer for QC. Never QC your own work, never touch another's task, never poll.",
|
|
150
|
+
],
|
|
151
|
+
),
|
|
152
|
+
Rank.REVIEWER: Role(
|
|
153
|
+
rank=Rank.REVIEWER,
|
|
154
|
+
title="Reviewer ",
|
|
155
|
+
summary="A thinker, not a doer. Verifies finished work and handles Bloom L4-L6.",
|
|
156
|
+
allowed=frozenset(
|
|
157
|
+
{
|
|
158
|
+
Action.QC_REVIEW,
|
|
159
|
+
Action.DEEP_ANALYSIS,
|
|
160
|
+
Action.AGGREGATE_REPORTS,
|
|
161
|
+
Action.WRITE_REPORT,
|
|
162
|
+
}
|
|
163
|
+
),
|
|
164
|
+
reports_to=Rank.COORDINATOR,
|
|
165
|
+
duties=[
|
|
166
|
+
"Quality-control Worker output through the DevCouncil Verifier.",
|
|
167
|
+
"Own architecture, root-cause and strategy work (Bloom Analyze/Evaluate/Create).",
|
|
168
|
+
"Aggregate verdicts and report up to the Coordinator. Never manage Worker, never implement.",
|
|
169
|
+
],
|
|
170
|
+
),
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def get_role(rank: Rank | str) -> Role:
|
|
175
|
+
"""Look up a :class:`Role` by rank (enum or string)."""
|
|
176
|
+
if isinstance(rank, str):
|
|
177
|
+
rank = Rank(rank)
|
|
178
|
+
return ROLES[rank]
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def assert_allowed(rank: Rank | str, action: Action) -> None:
|
|
182
|
+
"""Raise :class:`ForbiddenActionError` if ``rank`` may not perform ``action``."""
|
|
183
|
+
role = get_role(rank)
|
|
184
|
+
if not role.may(action):
|
|
185
|
+
raise ForbiddenActionError(role.rank, action)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def load_role_instructions(rank: Rank | str) -> str:
|
|
189
|
+
"""Return the persona/duty markdown for ``rank`` (``prompts/<rank>.md``).
|
|
190
|
+
|
|
191
|
+
Falls back to a synthesised summary when the file is missing so the system is
|
|
192
|
+
resilient to a trimmed install.
|
|
193
|
+
"""
|
|
194
|
+
if isinstance(rank, str):
|
|
195
|
+
rank = Rank(rank)
|
|
196
|
+
path = _PROMPTS_DIR / f"{rank.value}.md"
|
|
197
|
+
if path.exists():
|
|
198
|
+
return path.read_text(encoding="utf-8")
|
|
199
|
+
role = ROLES[rank]
|
|
200
|
+
lines = [f"# {role.title}", "", role.summary, ""]
|
|
201
|
+
lines += [f"- {duty}" for duty in role.duties]
|
|
202
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""Mailbox watcher — the "you have mail" nudge layer.
|
|
2
|
+
|
|
3
|
+
In the original, one ``inotifywait``/``fswatch`` daemon per agent blocks on a
|
|
4
|
+
kernel file-change event and, when the inbox changes, fires a content-free
|
|
5
|
+
``inboxN`` keystroke into that agent's tmux pane. Message *content* never travels
|
|
6
|
+
over the wire — only the wake-up.
|
|
7
|
+
|
|
8
|
+
DevCouncil runs its agents in-process, so the "keystroke" becomes a Python
|
|
9
|
+
callback: ``nudge(agent, unread_count)``. We still key off real filesystem
|
|
10
|
+
events (via :mod:`watchdog`, already a DevCouncil dependency) so cross-process
|
|
11
|
+
writers — e.g. a leased MCP agent appending to a mailbox — also wake the
|
|
12
|
+
campaign. When watchdog is unavailable we fall back to a polling thread, and
|
|
13
|
+
:meth:`MailboxWatcher.poll_once` lets a single-threaded orchestrator drive the
|
|
14
|
+
same logic deterministically (and keeps unit tests free of timing races).
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import threading
|
|
20
|
+
from typing import Any, Callable, Dict, Iterable, List, Optional
|
|
21
|
+
|
|
22
|
+
from devcouncil.campaign.mailbox import Mailbox
|
|
23
|
+
|
|
24
|
+
NudgeCallback = Callable[[str, int], None]
|
|
25
|
+
|
|
26
|
+
try: # watchdog is a declared dependency; degrade gracefully if it is missing.
|
|
27
|
+
from watchdog.events import FileSystemEventHandler
|
|
28
|
+
from watchdog.observers import Observer
|
|
29
|
+
|
|
30
|
+
_HAS_WATCHDOG = True
|
|
31
|
+
except Exception: # pragma: no cover - exercised only without watchdog
|
|
32
|
+
FileSystemEventHandler = object # type: ignore[assignment,misc]
|
|
33
|
+
Observer = None # type: ignore[assignment]
|
|
34
|
+
_HAS_WATCHDOG = False
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class MailboxWatcher:
|
|
38
|
+
"""Watch a set of agent mailboxes and nudge on new unread mail.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
mailbox:
|
|
43
|
+
The shared :class:`~devcouncil.campaign.mailbox.Mailbox`.
|
|
44
|
+
agents:
|
|
45
|
+
Agent ids to watch.
|
|
46
|
+
nudge:
|
|
47
|
+
Called ``nudge(agent, unread_count)`` whenever an agent's unread count
|
|
48
|
+
rises. Never called with a count of zero.
|
|
49
|
+
poll_interval:
|
|
50
|
+
Seconds between scans on the polling fallback.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
mailbox: Mailbox,
|
|
56
|
+
agents: Iterable[str],
|
|
57
|
+
nudge: NudgeCallback,
|
|
58
|
+
poll_interval: float = 1.0,
|
|
59
|
+
):
|
|
60
|
+
self.mailbox = mailbox
|
|
61
|
+
self.agents: List[str] = list(agents)
|
|
62
|
+
self.nudge = nudge
|
|
63
|
+
self.poll_interval = poll_interval
|
|
64
|
+
# Last unread count we nudged for, per agent — so a nudge fires on the
|
|
65
|
+
# *rising edge* only (0->N or N->N+1), never repeatedly for the same mail.
|
|
66
|
+
self._last: Dict[str, int] = {a: 0 for a in self.agents}
|
|
67
|
+
self._lock = threading.Lock()
|
|
68
|
+
self._observer: Any = None
|
|
69
|
+
self._stop = threading.Event()
|
|
70
|
+
self._poll_thread: Optional[threading.Thread] = None
|
|
71
|
+
|
|
72
|
+
# -- core logic ------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
def poll_once(self) -> List[str]:
|
|
75
|
+
"""Scan all mailboxes once; nudge agents whose unread count rose.
|
|
76
|
+
|
|
77
|
+
Returns the list of agents nudged. Safe to call from any thread and from
|
|
78
|
+
tests; this is the single choke-point through which every nudge flows.
|
|
79
|
+
"""
|
|
80
|
+
nudged: List[str] = []
|
|
81
|
+
with self._lock:
|
|
82
|
+
for agent in self.agents:
|
|
83
|
+
count = self.mailbox.count_unread(agent)
|
|
84
|
+
previous = self._last.get(agent, 0)
|
|
85
|
+
if count > previous and count > 0:
|
|
86
|
+
self._last[agent] = count
|
|
87
|
+
nudged.append(agent)
|
|
88
|
+
elif count < previous:
|
|
89
|
+
# Agent drained its inbox; re-arm so the next arrival nudges.
|
|
90
|
+
self._last[agent] = count
|
|
91
|
+
for agent in nudged:
|
|
92
|
+
self.nudge(agent, self.mailbox.count_unread(agent))
|
|
93
|
+
return nudged
|
|
94
|
+
|
|
95
|
+
def reset(self, agent: Optional[str] = None) -> None:
|
|
96
|
+
"""Forget prior nudge state so the next scan re-nudges pending mail."""
|
|
97
|
+
with self._lock:
|
|
98
|
+
if agent is None:
|
|
99
|
+
self._last = {a: 0 for a in self.agents}
|
|
100
|
+
else:
|
|
101
|
+
self._last[agent] = 0
|
|
102
|
+
|
|
103
|
+
# -- background operation --------------------------------------------------
|
|
104
|
+
|
|
105
|
+
def start(self) -> None:
|
|
106
|
+
"""Begin watching in the background (watchdog, or a polling thread)."""
|
|
107
|
+
self.mailbox.inbox_dir.mkdir(parents=True, exist_ok=True)
|
|
108
|
+
if _HAS_WATCHDOG and Observer is not None:
|
|
109
|
+
handler = _InboxEventHandler(self)
|
|
110
|
+
self._observer = Observer()
|
|
111
|
+
self._observer.schedule(handler, str(self.mailbox.inbox_dir), recursive=False)
|
|
112
|
+
self._observer.start()
|
|
113
|
+
else: # pragma: no cover - fallback path
|
|
114
|
+
self._poll_thread = threading.Thread(target=self._poll_loop, daemon=True)
|
|
115
|
+
self._poll_thread.start()
|
|
116
|
+
# Deliver anything already waiting at startup.
|
|
117
|
+
self.poll_once()
|
|
118
|
+
|
|
119
|
+
def _poll_loop(self) -> None: # pragma: no cover - timing loop
|
|
120
|
+
while not self._stop.wait(self.poll_interval):
|
|
121
|
+
self.poll_once()
|
|
122
|
+
|
|
123
|
+
def stop(self) -> None:
|
|
124
|
+
self._stop.set()
|
|
125
|
+
if self._observer is not None:
|
|
126
|
+
try:
|
|
127
|
+
self._observer.stop()
|
|
128
|
+
self._observer.join(timeout=2.0)
|
|
129
|
+
except Exception:
|
|
130
|
+
pass
|
|
131
|
+
self._observer = None
|
|
132
|
+
if self._poll_thread is not None: # pragma: no cover
|
|
133
|
+
self._poll_thread.join(timeout=2.0)
|
|
134
|
+
self._poll_thread = None
|
|
135
|
+
|
|
136
|
+
def __enter__(self) -> "MailboxWatcher":
|
|
137
|
+
self.start()
|
|
138
|
+
return self
|
|
139
|
+
|
|
140
|
+
def __exit__(self, *exc: object) -> None:
|
|
141
|
+
self.stop()
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class _InboxEventHandler(FileSystemEventHandler):
|
|
145
|
+
"""Translates filesystem change events into a debounced ``poll_once``."""
|
|
146
|
+
|
|
147
|
+
def __init__(self, watcher: MailboxWatcher):
|
|
148
|
+
self._watcher = watcher
|
|
149
|
+
|
|
150
|
+
def on_any_event(self, event: object) -> None: # pragma: no cover - needs fs events
|
|
151
|
+
# A YAML write shows up as create/modify/moved on the atomic replace; we
|
|
152
|
+
# do not care which — a single re-scan reconciles all mailboxes.
|
|
153
|
+
self._watcher.poll_once()
|
|
@@ -22,9 +22,12 @@ from devcouncil.executors.agent_registry import (
|
|
|
22
22
|
resolve_cursor_agent_executable,
|
|
23
23
|
)
|
|
24
24
|
from devcouncil.optimization.gepa_agent import GepaUnavailableError, optimize_agent_profile
|
|
25
|
+
from devcouncil.telemetry.stages import log_stage, log_step
|
|
26
|
+
import logging
|
|
25
27
|
|
|
26
28
|
app = typer.Typer(help="Manage DevCouncil CLI agents.")
|
|
27
29
|
console = Console()
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
28
31
|
|
|
29
32
|
|
|
30
33
|
@app.callback(invoke_without_command=True)
|
|
@@ -37,24 +40,28 @@ def list_agents(
|
|
|
37
40
|
return
|
|
38
41
|
|
|
39
42
|
root = _project_root(project_root)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
logger.info("dev agents list")
|
|
44
|
+
with log_stage("agents", project_root=root, subcommand="list"):
|
|
45
|
+
log_step("agents/1: loading agent registry", project_root=root, trace=True)
|
|
46
|
+
table = Table(title="DevCouncil Agents")
|
|
47
|
+
table.add_column("Agent", style="cyan")
|
|
48
|
+
table.add_column("Type")
|
|
49
|
+
table.add_column("Command")
|
|
50
|
+
table.add_column("Profile")
|
|
51
|
+
table.add_column("MCP")
|
|
52
|
+
table.add_column("Diff Review")
|
|
47
53
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
for name, spec in sorted(load_cli_agent_specs(root).items()):
|
|
55
|
+
table.add_row(
|
|
56
|
+
name,
|
|
57
|
+
"built-in" if spec.built_in else spec.kind,
|
|
58
|
+
" ".join(spec.base_command()),
|
|
59
|
+
spec.default_profile,
|
|
60
|
+
"yes" if spec.supports_mcp else "no",
|
|
61
|
+
"yes" if spec.supports_diff_review else "no",
|
|
62
|
+
)
|
|
63
|
+
console.print(table)
|
|
64
|
+
log_step("agents/complete", project_root=root, trace=True)
|
|
58
65
|
|
|
59
66
|
|
|
60
67
|
@app.command("add")
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import typer
|
|
2
3
|
from rich.console import Console
|
|
3
4
|
from rich.table import Table
|
|
@@ -8,9 +9,11 @@ from devcouncil.artifacts.validators import ArtifactValidator
|
|
|
8
9
|
from devcouncil.storage.db import get_db
|
|
9
10
|
from devcouncil.storage.repositories import RequirementRepository, TaskRepository
|
|
10
11
|
from devcouncil.cli.commands.init import initialize_project
|
|
12
|
+
from devcouncil.telemetry.stages import log_stage, log_step
|
|
11
13
|
|
|
12
14
|
app = typer.Typer()
|
|
13
15
|
console = Console()
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
14
17
|
|
|
15
18
|
|
|
16
19
|
@app.command(name="validate")
|
|
@@ -19,36 +22,42 @@ def validate(
|
|
|
19
22
|
):
|
|
20
23
|
"""Validate requirements and tasks stored in the artifact graph."""
|
|
21
24
|
root = project_root.expanduser().resolve()
|
|
25
|
+
from devcouncil.telemetry.logging_setup import set_log_dir
|
|
26
|
+
set_log_dir(root)
|
|
27
|
+
logger.info("dev artifacts validate")
|
|
22
28
|
initialize_project(root, quiet=True)
|
|
23
29
|
db = get_db(root)
|
|
24
30
|
if not db:
|
|
25
31
|
console.print("[red]DevCouncil state is unavailable in this directory.[/red]")
|
|
26
32
|
raise typer.Exit(code=1)
|
|
27
33
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
table
|
|
53
|
-
|
|
54
|
-
|
|
34
|
+
with log_stage("artifacts", project_root=root):
|
|
35
|
+
log_step("artifacts/1: validating requirements and tasks", project_root=root, trace=True)
|
|
36
|
+
errors: list[str] = []
|
|
37
|
+
with db.get_session() as session:
|
|
38
|
+
req_repo = RequirementRepository(session)
|
|
39
|
+
task_repo = TaskRepository(session)
|
|
40
|
+
|
|
41
|
+
for req in req_repo.get_all():
|
|
42
|
+
try:
|
|
43
|
+
ArtifactValidator.validate_requirement(req)
|
|
44
|
+
except GatingError as exc:
|
|
45
|
+
errors.append(str(exc))
|
|
46
|
+
|
|
47
|
+
for task in task_repo.get_all():
|
|
48
|
+
try:
|
|
49
|
+
ArtifactValidator.validate_task(task)
|
|
50
|
+
except GatingError as exc:
|
|
51
|
+
errors.append(str(exc))
|
|
52
|
+
|
|
53
|
+
if not errors:
|
|
54
|
+
console.print("[green]Artifacts are valid.[/green]")
|
|
55
|
+
log_step("artifacts/complete", project_root=root, trace=True)
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
table = Table(title="Artifact Validation Errors")
|
|
59
|
+
table.add_column("Error", style="red")
|
|
60
|
+
for error in errors:
|
|
61
|
+
table.add_row(error)
|
|
62
|
+
console.print(table)
|
|
63
|
+
raise typer.Exit(code=1)
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import
|
|
1
|
+
from devcouncil.utils.json_persist import dump_json
|
|
2
|
+
import logging
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
|
|
4
5
|
import typer
|
|
5
6
|
|
|
6
7
|
from devcouncil.indexing.ast_matcher import AstMatcher
|
|
8
|
+
from devcouncil.telemetry.stages import log_stage, log_step
|
|
7
9
|
|
|
8
10
|
app = typer.Typer(help="Run structural AST searches.")
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
9
12
|
|
|
10
13
|
|
|
11
14
|
@app.command("match")
|
|
@@ -18,5 +21,11 @@ def match(
|
|
|
18
21
|
):
|
|
19
22
|
"""Find function/class/type symbols using tree-sitter when available and fallbacks otherwise."""
|
|
20
23
|
root = project_root.expanduser().resolve()
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
from devcouncil.telemetry.logging_setup import set_log_dir
|
|
25
|
+
set_log_dir(root)
|
|
26
|
+
logger.info("dev ast match: query=%r language=%s kind=%s", query, language, kind)
|
|
27
|
+
with log_stage("ast", project_root=root):
|
|
28
|
+
log_step("ast/1: running structural match", project_root=root, trace=True)
|
|
29
|
+
matches = AstMatcher(root).match(query=query, language=language, kind=kind, limit=max(1, limit))
|
|
30
|
+
typer.echo(dump_json({"matches": [item.model_dump() for item in matches]}, indent=2))
|
|
31
|
+
log_step("ast/complete", project_root=root, count=len(matches), trace=True)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import logging
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
|
|
4
4
|
import typer
|
|
@@ -6,9 +6,12 @@ from rich.console import Console
|
|
|
6
6
|
|
|
7
7
|
from devcouncil.cli.commands.init import initialize_project
|
|
8
8
|
from devcouncil.storage.db import get_db
|
|
9
|
+
from devcouncil.telemetry.stages import log_stage, log_step
|
|
10
|
+
from devcouncil.utils.json_persist import write_json
|
|
9
11
|
from devcouncil.verification.verifier import Verifier
|
|
10
12
|
|
|
11
13
|
console = Console()
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
12
15
|
|
|
13
16
|
|
|
14
17
|
def baseline(
|
|
@@ -17,19 +20,25 @@ def baseline(
|
|
|
17
20
|
):
|
|
18
21
|
"""Capture the current repo state as DevCouncil's verification baseline."""
|
|
19
22
|
root = project_root.expanduser().resolve()
|
|
23
|
+
from devcouncil.telemetry.logging_setup import set_log_dir
|
|
24
|
+
set_log_dir(root)
|
|
25
|
+
logger.info("dev baseline: force=%s", force)
|
|
20
26
|
initialize_project(root, quiet=True)
|
|
21
27
|
if not get_db(root):
|
|
22
28
|
raise typer.Exit(code=1)
|
|
23
29
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
with log_stage("baseline", project_root=root, force=force):
|
|
31
|
+
log_step("baseline/1: capturing verification snapshot", project_root=root, trace=True)
|
|
32
|
+
baseline_path = root / ".devcouncil" / "baseline.json"
|
|
33
|
+
if baseline_path.exists() and not force:
|
|
34
|
+
console.print("[yellow]Baseline already exists. Use --force to replace it.[/yellow]")
|
|
35
|
+
raise typer.Exit(code=1)
|
|
28
36
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
changed_files = Verifier(root).get_changed_files()
|
|
38
|
+
payload = {
|
|
39
|
+
"changed_files": changed_files,
|
|
40
|
+
"note": "Files present in this snapshot are excluded from future task-scoped verification diffs.",
|
|
41
|
+
}
|
|
42
|
+
write_json(baseline_path, payload)
|
|
43
|
+
console.print(f"[green]Captured baseline with {len(changed_files)} changed file(s).[/green]")
|
|
44
|
+
log_step("baseline/complete", project_root=root, file_count=len(changed_files), trace=True)
|