devcouncil 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -30
- package/package.json +6 -2
- package/packages/codeintel-grammars/hatch_build.py +43 -0
- package/packages/codeintel-grammars/pyproject.toml +16 -0
- package/packages/codeintel-grammars/src/devcouncil_codeintel_grammars/__init__.py +93 -0
- package/pyproject.toml +99 -4
- package/src/devcouncil/app/config.py +512 -20
- package/src/devcouncil/app/events.py +4 -23
- package/src/devcouncil/app/orchestrator.py +5 -0
- package/src/devcouncil/app/run_context.py +3 -3
- package/src/devcouncil/assets/__init__.py +4 -1
- package/src/devcouncil/assets/vendor/force-graph.min.js +5 -0
- package/src/devcouncil/campaign/__init__.py +71 -0
- package/src/devcouncil/campaign/bloom.py +137 -0
- package/src/devcouncil/campaign/dashboard.py +123 -0
- package/src/devcouncil/campaign/mailbox.py +305 -0
- package/src/devcouncil/campaign/notify.py +91 -0
- package/src/devcouncil/campaign/orchestrator.py +592 -0
- package/src/devcouncil/campaign/prompts/coordinator.md +29 -0
- package/src/devcouncil/campaign/prompts/director.md +21 -0
- package/src/devcouncil/campaign/prompts/protocol.md +46 -0
- package/src/devcouncil/campaign/prompts/reviewer.md +24 -0
- package/src/devcouncil/campaign/prompts/worker.md +24 -0
- package/src/devcouncil/campaign/roles.py +202 -0
- package/src/devcouncil/campaign/watcher.py +153 -0
- package/src/devcouncil/cli/commands/agents.py +24 -17
- package/src/devcouncil/cli/commands/artifacts.py +36 -27
- package/src/devcouncil/cli/commands/ast.py +12 -3
- package/src/devcouncil/cli/commands/baseline.py +21 -12
- package/src/devcouncil/cli/commands/boot.py +218 -0
- package/src/devcouncil/cli/commands/campaign.py +302 -0
- package/src/devcouncil/cli/commands/check.py +225 -12
- package/src/devcouncil/cli/commands/config.py +221 -74
- package/src/devcouncil/cli/commands/cost.py +137 -28
- package/src/devcouncil/cli/commands/dashboard.py +12 -4
- package/src/devcouncil/cli/commands/debug_cmd.py +249 -0
- package/src/devcouncil/cli/commands/design.py +27 -17
- package/src/devcouncil/cli/commands/doctor.py +790 -8
- package/src/devcouncil/cli/commands/evidence.py +41 -20
- package/src/devcouncil/cli/commands/export.py +73 -0
- package/src/devcouncil/cli/commands/gaps.py +175 -0
- package/src/devcouncil/cli/commands/gated_write.py +76 -0
- package/src/devcouncil/cli/commands/go.py +220 -68
- package/src/devcouncil/cli/commands/graph_cmd.py +1192 -0
- package/src/devcouncil/cli/commands/handoff.py +45 -34
- package/src/devcouncil/cli/commands/hook.py +630 -85
- package/src/devcouncil/cli/commands/init.py +89 -30
- package/src/devcouncil/cli/commands/integrate.py +296 -1385
- package/src/devcouncil/cli/commands/lease.py +120 -0
- package/src/devcouncil/cli/commands/logs.py +12 -5
- package/src/devcouncil/cli/commands/lsp.py +40 -5
- package/src/devcouncil/cli/commands/map.py +317 -74
- package/src/devcouncil/cli/commands/mcp_server.py +12 -2
- package/src/devcouncil/cli/commands/okf.py +44 -6
- package/src/devcouncil/cli/commands/plan.py +184 -69
- package/src/devcouncil/cli/commands/prompt.py +26 -17
- package/src/devcouncil/cli/commands/provenance.py +79 -0
- package/src/devcouncil/cli/commands/repair.py +60 -49
- package/src/devcouncil/cli/commands/report.py +148 -40
- package/src/devcouncil/cli/commands/requirements.py +104 -0
- package/src/devcouncil/cli/commands/reset_demo_state.py +13 -4
- package/src/devcouncil/cli/commands/rollback.py +46 -35
- package/src/devcouncil/cli/commands/run.py +173 -8
- package/src/devcouncil/cli/commands/runs.py +298 -68
- package/src/devcouncil/cli/commands/scaffold.py +33 -12
- package/src/devcouncil/cli/commands/semantic.py +29 -14
- package/src/devcouncil/cli/commands/setup.py +103 -93
- package/src/devcouncil/cli/commands/shell.py +51 -42
- package/src/devcouncil/cli/commands/show.py +56 -42
- package/src/devcouncil/cli/commands/skills.py +29 -20
- package/src/devcouncil/cli/commands/status.py +80 -67
- package/src/devcouncil/cli/commands/task_gate.py +295 -0
- package/src/devcouncil/cli/commands/tasks.py +248 -19
- package/src/devcouncil/cli/commands/trace.py +14 -8
- package/src/devcouncil/cli/commands/verify.py +33 -8
- package/src/devcouncil/cli/commands/version.py +14 -6
- package/src/devcouncil/cli/commands/watch.py +49 -30
- package/src/devcouncil/cli/commands/watch_fs.py +30 -19
- package/src/devcouncil/cli/commands/wiki.py +278 -0
- package/src/devcouncil/cli/main.py +58 -1
- package/src/devcouncil/codeintel/__init__.py +16 -0
- package/src/devcouncil/codeintel/build_control.py +429 -0
- package/src/devcouncil/codeintel/build_worker.py +78 -0
- package/src/devcouncil/codeintel/debug/__init__.py +17 -0
- package/src/devcouncil/codeintel/debug/broker.py +114 -0
- package/src/devcouncil/codeintel/debug/broker_client.py +61 -0
- package/src/devcouncil/codeintel/debug/consent.py +36 -0
- package/src/devcouncil/codeintel/debug/discovery.py +132 -0
- package/src/devcouncil/codeintel/debug/fingerprint.py +85 -0
- package/src/devcouncil/codeintel/debug/protocol.py +259 -0
- package/src/devcouncil/codeintel/debug/python_trace_runner.py +81 -0
- package/src/devcouncil/codeintel/debug/session.py +238 -0
- package/src/devcouncil/codeintel/debug/tracing.py +201 -0
- package/src/devcouncil/codeintel/languages/__init__.py +17 -0
- package/src/devcouncil/codeintel/languages/generic_extractor.py +236 -0
- package/src/devcouncil/codeintel/languages/registry.py +149 -0
- package/src/devcouncil/codeintel/languages/workers.py +245 -0
- package/src/devcouncil/codeintel/query/__init__.py +5 -0
- package/src/devcouncil/codeintel/query/engine.py +289 -0
- package/src/devcouncil/codeintel/resolution/__init__.py +6 -0
- package/src/devcouncil/codeintel/resolution/abstract_state.py +301 -0
- package/src/devcouncil/codeintel/resolution/frameworks/__init__.py +33 -0
- package/src/devcouncil/codeintel/resolution/frameworks/base.py +46 -0
- package/src/devcouncil/codeintel/resolution/frameworks/di.py +56 -0
- package/src/devcouncil/codeintel/resolution/frameworks/events.py +45 -0
- package/src/devcouncil/codeintel/resolution/frameworks/routes.py +88 -0
- package/src/devcouncil/codeintel/resolution/semantic.py +887 -0
- package/src/devcouncil/codeintel/service.py +104 -0
- package/src/devcouncil/codeintel/store/__init__.py +15 -0
- package/src/devcouncil/codeintel/store/sqlite.py +1565 -0
- package/src/devcouncil/codeintel/sync/__init__.py +19 -0
- package/src/devcouncil/codeintel/sync/coordinator.py +430 -0
- package/src/devcouncil/codeintel/sync/incremental.py +484 -0
- package/src/devcouncil/codeintel/sync/lease.py +96 -0
- package/src/devcouncil/codeintel/sync/scope.py +98 -0
- package/src/devcouncil/council/__init__.py +4 -0
- package/src/devcouncil/council/prompts/__init__.py +4 -0
- package/src/devcouncil/domain/checkpoint_refs.py +17 -0
- package/src/devcouncil/domain/evidence.py +1 -0
- package/src/devcouncil/domain/gap.py +10 -0
- package/src/devcouncil/domain/requirement.py +5 -1
- package/src/devcouncil/domain/task.py +43 -2
- package/src/devcouncil/execution/checkpoints.py +25 -31
- package/src/devcouncil/execution/context_builder.py +15 -44
- package/src/devcouncil/execution/fs_watcher.py +64 -0
- package/src/devcouncil/execution/gated_write.py +203 -0
- package/src/devcouncil/execution/handoff.py +2 -1
- package/src/devcouncil/execution/hook_policy.py +19 -5
- package/src/devcouncil/execution/lease_ops.py +177 -0
- package/src/devcouncil/execution/lease_validation.py +71 -0
- package/src/devcouncil/execution/patch.py +3 -0
- package/src/devcouncil/execution/permissions.py +1 -0
- package/src/devcouncil/execution/policy_engine.py +205 -10
- package/src/devcouncil/execution/prompt_builder.py +278 -33
- package/src/devcouncil/execution/run_trace.py +356 -0
- package/src/devcouncil/execution/shell_session.py +46 -5
- package/src/devcouncil/execution/stop_gate.py +746 -0
- package/src/devcouncil/execution/stop_gate_history.py +113 -0
- package/src/devcouncil/execution/stop_gate_state.py +54 -0
- package/src/devcouncil/execution/stop_gate_verify_cache.py +69 -0
- package/src/devcouncil/execution/task_gate_ops.py +590 -0
- package/src/devcouncil/execution/task_runner.py +19 -0
- package/src/devcouncil/executors/advisor_tool.py +315 -0
- package/src/devcouncil/executors/agent_registry.py +125 -17
- package/src/devcouncil/executors/claude_sdk.py +376 -0
- package/src/devcouncil/executors/coding_cli.py +724 -25
- package/src/devcouncil/executors/mini_swe.py +50 -8
- package/src/devcouncil/executors/native/agent.py +224 -19
- package/src/devcouncil/executors/openhands.py +50 -8
- package/src/devcouncil/executors/transient_retry.py +99 -0
- package/src/devcouncil/gating/checks/clean_git.py +5 -2
- package/src/devcouncil/gating/checks/planned_files_check.py +38 -11
- package/src/devcouncil/gating/checks/secret_scan_check.py +2 -2
- package/src/devcouncil/gating/policy.py +46 -2
- package/src/devcouncil/indexing/ast_matcher.py +41 -4
- package/src/devcouncil/indexing/graph/__init__.py +78 -0
- package/src/devcouncil/indexing/graph/api_routes.py +522 -0
- package/src/devcouncil/indexing/graph/build.py +862 -0
- package/src/devcouncil/indexing/graph/cache.py +329 -0
- package/src/devcouncil/indexing/graph/communities.py +28 -0
- package/src/devcouncil/indexing/graph/cypher.py +107 -0
- package/src/devcouncil/indexing/graph/embeddings.py +194 -0
- package/src/devcouncil/indexing/graph/export.py +381 -0
- package/src/devcouncil/indexing/graph/export_links.py +81 -0
- package/src/devcouncil/indexing/graph/extract_python.py +307 -0
- package/src/devcouncil/indexing/graph/extract_ts.py +1205 -0
- package/src/devcouncil/indexing/graph/intel.py +668 -0
- package/src/devcouncil/indexing/graph/liveness.py +992 -0
- package/src/devcouncil/indexing/graph/okf_export.py +65 -0
- package/src/devcouncil/indexing/graph/pdg/__init__.py +67 -0
- package/src/devcouncil/indexing/graph/pdg/build.py +11 -0
- package/src/devcouncil/indexing/graph/pdg/cdg.py +41 -0
- package/src/devcouncil/indexing/graph/pdg/cfg.py +199 -0
- package/src/devcouncil/indexing/graph/pdg/query.py +21 -0
- package/src/devcouncil/indexing/graph/pdg/reaching_def.py +126 -0
- package/src/devcouncil/indexing/graph/pdg/schema.py +253 -0
- package/src/devcouncil/indexing/graph/pdg/taint.py +154 -0
- package/src/devcouncil/indexing/graph/query.py +302 -0
- package/src/devcouncil/indexing/graph/resolve.py +1020 -0
- package/src/devcouncil/indexing/graph/schema.py +103 -0
- package/src/devcouncil/indexing/graph_index.py +20 -29
- package/src/devcouncil/indexing/lsp.py +57 -25
- package/src/devcouncil/indexing/lsp_client.py +577 -0
- package/src/devcouncil/indexing/map_artifacts.py +355 -0
- package/src/devcouncil/indexing/map_refresh.py +141 -0
- package/src/devcouncil/indexing/repo_mapper.py +1509 -138
- package/src/devcouncil/indexing/semantic_index.py +12 -6
- package/src/devcouncil/indexing/subsystem_map.py +163 -0
- package/src/devcouncil/indexing/ts_imports.py +343 -0
- package/src/devcouncil/indexing/viz.py +960 -0
- package/src/devcouncil/indexing/walk.py +52 -0
- package/src/devcouncil/indexing/wiring.py +1776 -0
- package/src/devcouncil/integrations/actions.py +27 -4
- package/src/devcouncil/integrations/check.py +211 -16
- package/src/devcouncil/integrations/claude_assets.py +209 -12
- package/src/devcouncil/integrations/clients/__init__.py +1 -0
- package/src/devcouncil/integrations/clients/aider.py +52 -0
- package/src/devcouncil/integrations/clients/antigravity.py +87 -0
- package/src/devcouncil/integrations/clients/claude.py +339 -0
- package/src/devcouncil/integrations/clients/codex.py +39 -0
- package/src/devcouncil/integrations/clients/common.py +332 -0
- package/src/devcouncil/integrations/clients/cursor.py +164 -0
- package/src/devcouncil/integrations/clients/gemini.py +49 -0
- package/src/devcouncil/integrations/clients/grok.py +105 -0
- package/src/devcouncil/integrations/clients/hooks.py +500 -0
- package/src/devcouncil/integrations/clients/opencode.py +96 -0
- package/src/devcouncil/integrations/clients/warp.py +75 -0
- package/src/devcouncil/integrations/code_review_graph.py +2 -2
- package/src/devcouncil/integrations/github.py +73 -7
- package/src/devcouncil/integrations/integration_cli.py +197 -0
- package/src/devcouncil/integrations/mcp/handlers/__init__.py +1 -0
- package/src/devcouncil/integrations/mcp/handlers/ast_lsp.py +77 -0
- package/src/devcouncil/integrations/mcp/handlers/checkout.py +50 -0
- package/src/devcouncil/integrations/mcp/handlers/cli_gate.py +43 -0
- package/src/devcouncil/integrations/mcp/handlers/codeintel.py +182 -0
- package/src/devcouncil/integrations/mcp/handlers/debug.py +236 -0
- package/src/devcouncil/integrations/mcp/handlers/evidence.py +70 -0
- package/src/devcouncil/integrations/mcp/handlers/git.py +99 -0
- package/src/devcouncil/integrations/mcp/handlers/graph.py +36 -0
- package/src/devcouncil/integrations/mcp/handlers/handoff.py +53 -0
- package/src/devcouncil/integrations/mcp/handlers/knowledge.py +30 -0
- package/src/devcouncil/integrations/mcp/handlers/lease.py +70 -0
- package/src/devcouncil/integrations/mcp/handlers/live.py +108 -0
- package/src/devcouncil/integrations/mcp/handlers/map.py +676 -0
- package/src/devcouncil/integrations/mcp/handlers/next_task.py +35 -0
- package/src/devcouncil/integrations/mcp/handlers/policy.py +80 -0
- package/src/devcouncil/integrations/mcp/handlers/prompts.py +168 -0
- package/src/devcouncil/integrations/mcp/handlers/provenance.py +101 -0
- package/src/devcouncil/integrations/mcp/handlers/read.py +74 -0
- package/src/devcouncil/integrations/mcp/handlers/router_cache.py +53 -0
- package/src/devcouncil/integrations/mcp/handlers/run.py +53 -0
- package/src/devcouncil/integrations/mcp/handlers/runs.py +84 -0
- package/src/devcouncil/integrations/mcp/handlers/scope.py +56 -0
- package/src/devcouncil/integrations/mcp/handlers/status.py +114 -0
- package/src/devcouncil/integrations/mcp/handlers/task.py +100 -0
- package/src/devcouncil/integrations/mcp/handlers/tool_specs.py +904 -0
- package/src/devcouncil/integrations/mcp/handlers/trace.py +65 -0
- package/src/devcouncil/integrations/mcp/handlers/verify.py +45 -0
- package/src/devcouncil/integrations/mcp/handlers/wiki.py +60 -0
- package/src/devcouncil/integrations/mcp/handlers/write.py +69 -0
- package/src/devcouncil/integrations/mcp/server.py +265 -2391
- package/src/devcouncil/integrations/mcp/util.py +303 -0
- package/src/devcouncil/integrations/setup.py +152 -0
- package/src/devcouncil/knowledge/fetch.py +4 -0
- package/src/devcouncil/knowledge/knowledge_select.py +38 -0
- package/src/devcouncil/knowledge/okf.py +2 -1
- package/src/devcouncil/knowledge/resource_discovery.py +40 -0
- package/src/devcouncil/knowledge/wiki.py +643 -0
- package/src/devcouncil/knowledge/wiki_read.py +87 -0
- package/src/devcouncil/live/cards.py +7 -7
- package/src/devcouncil/live/models.py +4 -1
- package/src/devcouncil/live/reviewer.py +90 -11
- package/src/devcouncil/live/signals.py +4 -2
- package/src/devcouncil/live/tasks.py +12 -3
- package/src/devcouncil/live/transcripts.py +69 -2
- package/src/devcouncil/llm/cache.py +5 -6
- package/src/devcouncil/llm/model_defaults.yaml +10 -10
- package/src/devcouncil/llm/provider.py +647 -73
- package/src/devcouncil/llm/router.py +271 -46
- package/src/devcouncil/llm/semantic_bridge.py +614 -0
- package/src/devcouncil/optimization/gepa_agent.py +6 -4
- package/src/devcouncil/optimization/skillopt.py +9 -5
- package/src/devcouncil/planning/arbiter_service.py +12 -3
- package/src/devcouncil/planning/correction_manifest.py +107 -10
- package/src/devcouncil/planning/plan_difficulty.py +69 -0
- package/src/devcouncil/planning/plan_service.py +5 -2
- package/src/devcouncil/planning/planned_files_reconcile.py +191 -0
- package/src/devcouncil/planning/prompt_enhancer_service.py +6 -5
- package/src/devcouncil/planning/question_conversion.py +56 -0
- package/src/devcouncil/planning/spec_service.py +9 -3
- package/src/devcouncil/repo/ci_scaffold.py +197 -1
- package/src/devcouncil/repo/gitignore.py +1 -2
- package/src/devcouncil/reporting/evidence_export.py +124 -0
- package/src/devcouncil/reporting/evidence_html.py +210 -0
- package/src/devcouncil/reporting/json_report.py +16 -12
- package/src/devcouncil/reporting/markdown_report.py +38 -9
- package/src/devcouncil/reporting/mcp_resources.py +142 -0
- package/src/devcouncil/reporting/report_builder.py +40 -4
- package/src/devcouncil/reporting/task_provenance.py +42 -0
- package/src/devcouncil/reporting/verdict.py +75 -0
- package/src/devcouncil/skills/library/README.md +1 -0
- package/src/devcouncil/skills/library/devcouncil-hero-loop.md +109 -0
- package/src/devcouncil/skills/library/devcouncil-verification.md +109 -0
- package/src/devcouncil/skills/library/devcouncil.md +93 -0
- package/src/devcouncil/skills/registry.py +43 -12
- package/src/devcouncil/storage/db.py +57 -11
- package/src/devcouncil/storage/models.py +6 -0
- package/src/devcouncil/storage/native.py +5 -3
- package/src/devcouncil/storage/repositories.py +50 -18
- package/src/devcouncil/telemetry/context.py +28 -0
- package/src/devcouncil/telemetry/cost.py +4 -5
- package/src/devcouncil/telemetry/logging_setup.py +78 -11
- package/src/devcouncil/telemetry/model_pricing.yaml +7 -0
- package/src/devcouncil/telemetry/stages.py +27 -2
- package/src/devcouncil/telemetry/tracker.py +50 -13
- package/src/devcouncil/ui/dashboard.py +120 -8
- package/src/devcouncil/utils/fsio.py +58 -0
- package/src/devcouncil/utils/git_snapshot.py +112 -0
- package/src/devcouncil/utils/json_persist.py +53 -0
- package/src/devcouncil/utils/proc.py +89 -0
- package/src/devcouncil/verification/acceptance_compiler.py +36 -13
- package/src/devcouncil/verification/ad_hoc_check.py +95 -3
- package/src/devcouncil/verification/checks/__init__.py +41 -0
- package/src/devcouncil/verification/checks/acceptance.py +39 -0
- package/src/devcouncil/verification/checks/acceptance_corpus.py +194 -0
- package/src/devcouncil/verification/checks/acceptance_evidence.py +239 -0
- package/src/devcouncil/verification/checks/command_evidence.py +148 -0
- package/src/devcouncil/verification/checks/compiled_acceptance.py +179 -0
- package/src/devcouncil/verification/checks/corpus_stale.py +124 -0
- package/src/devcouncil/verification/checks/corpus_verification.py +9 -0
- package/src/devcouncil/verification/checks/dead_symbols.py +360 -0
- package/src/devcouncil/verification/checks/diff_coverage_gate.py +101 -0
- package/src/devcouncil/verification/checks/doc_code_ref.py +79 -0
- package/src/devcouncil/verification/checks/liveness_ratchet.py +336 -0
- package/src/devcouncil/verification/checks/orphan_diff.py +104 -0
- package/src/devcouncil/verification/checks/planned_files.py +98 -0
- package/src/devcouncil/verification/checks/semantic_diff.py +241 -0
- package/src/devcouncil/verification/checks/stale_map.py +80 -0
- package/src/devcouncil/verification/checks/stub_scan.py +71 -0
- package/src/devcouncil/verification/checks/subsystem_boundary.py +103 -0
- package/src/devcouncil/verification/checks/wiring.py +216 -0
- package/src/devcouncil/verification/claims/__init__.py +23 -0
- package/src/devcouncil/verification/claims/checks.py +395 -0
- package/src/devcouncil/verification/claims/mapper.py +168 -0
- package/src/devcouncil/verification/claims/models.py +39 -0
- package/src/devcouncil/verification/claims/transcript.py +92 -0
- package/src/devcouncil/verification/claims/verdict.py +88 -0
- package/src/devcouncil/verification/command_evidence.py +170 -0
- package/src/devcouncil/verification/command_malformation.py +147 -0
- package/src/devcouncil/verification/command_runner.py +164 -0
- package/src/devcouncil/verification/coverage_measurement.py +292 -0
- package/src/devcouncil/verification/diff_coverage.py +151 -0
- package/src/devcouncil/verification/difficulty.py +296 -0
- package/src/devcouncil/verification/effort_heuristics.py +178 -0
- package/src/devcouncil/verification/gap_ids.py +63 -0
- package/src/devcouncil/verification/gate_cache.py +194 -0
- package/src/devcouncil/verification/gate_selector.py +344 -0
- package/src/devcouncil/verification/git_diff_fallback.py +272 -0
- package/src/devcouncil/verification/implementation_reviewer.py +13 -0
- package/src/devcouncil/verification/incremental_check.py +241 -0
- package/src/devcouncil/verification/next_actions.py +60 -1
- package/src/devcouncil/verification/rigor_analytics.py +130 -0
- package/src/devcouncil/verification/sandbox.py +38 -11
- package/src/devcouncil/verification/stub_detector.py +369 -0
- package/src/devcouncil/verification/test_resolver.py +67 -1
- package/src/devcouncil/verification/verifier.py +137 -1666
- package/src/devcouncil/verification/verify_orchestration.py +610 -0
- package/src/devcouncil/verification/verify_setup.py +176 -0
- package/src/devcouncil/verification/wiki_refresh.py +208 -0
- package/src/semantic_layer/__init__.py +58 -0
- package/src/semantic_layer/benchmark.py +75 -0
- package/src/semantic_layer/cache.py +290 -0
- package/src/semantic_layer/compressor.py +137 -0
- package/src/semantic_layer/config.py +75 -0
- package/src/semantic_layer/embeddings.py +69 -0
- package/src/semantic_layer/llm_backends.py +99 -0
- package/src/semantic_layer/pipeline.py +111 -0
- package/src/semantic_layer/router.py +128 -0
- package/src/semantic_layer/tuner.py +72 -0
- package/uv.lock +973 -9
- package/src/devcouncil/artifacts/migrations.py +0 -20
- package/src/devcouncil/artifacts/schemas.py +0 -23
- package/src/devcouncil/artifacts/serializer.py +0 -21
- package/src/devcouncil/integrations/gitnexus.py +0 -70
- package/src/devcouncil/integrations/graphify.py +0 -34
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"""Registry-backed MCP debugger and runtime-trace tools."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Awaitable, Callable
|
|
7
|
+
|
|
8
|
+
from mcp.types import TextContent, Tool
|
|
9
|
+
|
|
10
|
+
from devcouncil.codeintel.debug.consent import require_debug_consent, set_debug_consent
|
|
11
|
+
from devcouncil.codeintel.debug.discovery import adapter_by_id, discover_adapters
|
|
12
|
+
from devcouncil.codeintel.debug.session import get_debug_manager
|
|
13
|
+
from devcouncil.codeintel.debug.tracing import NodeCpuProfileProvider, PythonTraceProvider, import_runtime_trace
|
|
14
|
+
from devcouncil.codeintel.service import canonical_project_root
|
|
15
|
+
from devcouncil.integrations.mcp.util import error_text, json_text
|
|
16
|
+
|
|
17
|
+
Handler = Callable[[Path, dict], Awaitable[list[TextContent]]]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _schema(properties: dict, required: list[str] | None = None) -> dict:
|
|
21
|
+
schema: dict = {"type": "object", "properties": {
|
|
22
|
+
"projectPath": {"type": "string", "description": "Explicit repository path."},
|
|
23
|
+
**properties,
|
|
24
|
+
}}
|
|
25
|
+
if required:
|
|
26
|
+
schema["required"] = required
|
|
27
|
+
return schema
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def tools() -> list[Tool]:
|
|
31
|
+
return [
|
|
32
|
+
Tool(
|
|
33
|
+
name="devcouncil_debug_discover",
|
|
34
|
+
description=(
|
|
35
|
+
"Discover installed DAP adapters after explicit one-time consent; "
|
|
36
|
+
"returns paths, versions, hashes, and launch/attach support."
|
|
37
|
+
),
|
|
38
|
+
inputSchema=_schema({"consent": {"type": "boolean", "default": False}}),
|
|
39
|
+
),
|
|
40
|
+
Tool(
|
|
41
|
+
name="devcouncil_debug_start",
|
|
42
|
+
description="Launch or attach a capability-negotiated DAP session.",
|
|
43
|
+
inputSchema=_schema({
|
|
44
|
+
"adapterId": {"type": "string"},
|
|
45
|
+
"adapterCommand": {"type": "array", "items": {"type": "string"}},
|
|
46
|
+
"request": {"type": "string", "enum": ["launch", "attach"], "default": "launch"},
|
|
47
|
+
"configuration": {"type": "object"},
|
|
48
|
+
"initialBreakpoints": {
|
|
49
|
+
"type": "object",
|
|
50
|
+
"additionalProperties": {"type": "array", "items": {"type": "integer"}},
|
|
51
|
+
},
|
|
52
|
+
"timeout": {"type": "number", "minimum": 1, "maximum": 120, "default": 30},
|
|
53
|
+
}),
|
|
54
|
+
),
|
|
55
|
+
Tool(
|
|
56
|
+
name="devcouncil_debug_breakpoints",
|
|
57
|
+
description="Replace all breakpoints for one source in a DAP session.",
|
|
58
|
+
inputSchema=_schema({
|
|
59
|
+
"sessionId": {"type": "string"},
|
|
60
|
+
"source": {"type": "string"},
|
|
61
|
+
"lines": {"type": "array", "items": {"type": "integer"}},
|
|
62
|
+
}, ["sessionId", "source", "lines"]),
|
|
63
|
+
),
|
|
64
|
+
Tool(
|
|
65
|
+
name="devcouncil_debug_control",
|
|
66
|
+
description="Continue, pause, or step a DAP session.",
|
|
67
|
+
inputSchema=_schema({
|
|
68
|
+
"sessionId": {"type": "string"},
|
|
69
|
+
"action": {"type": "string", "enum": ["continue", "pause", "next", "stepIn", "stepOut"]},
|
|
70
|
+
"threadId": {"type": "integer"},
|
|
71
|
+
}, ["sessionId", "action"]),
|
|
72
|
+
),
|
|
73
|
+
Tool(
|
|
74
|
+
name="devcouncil_debug_inspect",
|
|
75
|
+
description="Inspect threads, stack frames, scopes, variables, source, or disassembly.",
|
|
76
|
+
inputSchema=_schema({
|
|
77
|
+
"sessionId": {"type": "string"},
|
|
78
|
+
"operation": {"type": "string", "enum": ["threads", "stackTrace", "scopes", "variables", "source", "disassemble"]},
|
|
79
|
+
"arguments": {"type": "object"},
|
|
80
|
+
}, ["sessionId", "operation"]),
|
|
81
|
+
),
|
|
82
|
+
Tool(
|
|
83
|
+
name="devcouncil_debug_evaluate",
|
|
84
|
+
description="Side-effectful DAP evaluate; allowSideEffects must be explicitly true.",
|
|
85
|
+
inputSchema=_schema({
|
|
86
|
+
"sessionId": {"type": "string"},
|
|
87
|
+
"expression": {"type": "string"},
|
|
88
|
+
"frameId": {"type": "integer"},
|
|
89
|
+
"allowSideEffects": {"type": "boolean", "const": True},
|
|
90
|
+
}, ["sessionId", "expression", "allowSideEffects"]),
|
|
91
|
+
),
|
|
92
|
+
Tool(
|
|
93
|
+
name="devcouncil_debug_trace",
|
|
94
|
+
description="Capture a DAP stack, run exact Python tracing, or import JSONL/Node CPU profile evidence.",
|
|
95
|
+
inputSchema=_schema({
|
|
96
|
+
"provider": {"type": "string", "enum": ["dap-stack", "python", "node", "import"]},
|
|
97
|
+
"sessionId": {"type": "string"},
|
|
98
|
+
"threadId": {"type": "integer"},
|
|
99
|
+
"script": {"type": "string"},
|
|
100
|
+
"args": {"type": "array", "items": {"type": "string"}},
|
|
101
|
+
"path": {"type": "string"},
|
|
102
|
+
}, ["provider"]),
|
|
103
|
+
),
|
|
104
|
+
Tool(
|
|
105
|
+
name="devcouncil_debug_stop",
|
|
106
|
+
description="Disconnect a DAP session and optionally leave the debuggee running.",
|
|
107
|
+
inputSchema=_schema({
|
|
108
|
+
"sessionId": {"type": "string"},
|
|
109
|
+
"terminateDebuggee": {"type": "boolean", "default": True},
|
|
110
|
+
}, ["sessionId"]),
|
|
111
|
+
),
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
async def _discover(root: Path, arguments: dict) -> list[TextContent]:
|
|
116
|
+
if arguments.get("consent") is True:
|
|
117
|
+
set_debug_consent(root, True)
|
|
118
|
+
require_debug_consent(root)
|
|
119
|
+
return json_text({"consent": True, "adapters": [adapter.as_dict() for adapter in discover_adapters()]})
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _command(arguments: dict) -> list[str]:
|
|
123
|
+
supplied = arguments.get("adapterCommand")
|
|
124
|
+
if isinstance(supplied, list) and supplied:
|
|
125
|
+
return [str(value) for value in supplied]
|
|
126
|
+
adapter_id = str(arguments.get("adapterId") or "debugpy")
|
|
127
|
+
adapter = adapter_by_id(adapter_id)
|
|
128
|
+
if adapter is None:
|
|
129
|
+
raise ValueError(f"adapter {adapter_id!r} was not discovered")
|
|
130
|
+
return list(adapter.command)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
async def _start(root: Path, arguments: dict) -> list[TextContent]:
|
|
134
|
+
require_debug_consent(root)
|
|
135
|
+
session = get_debug_manager().start(
|
|
136
|
+
root,
|
|
137
|
+
_command(arguments),
|
|
138
|
+
request=str(arguments.get("request", "launch")),
|
|
139
|
+
arguments=dict(arguments.get("configuration") or {}),
|
|
140
|
+
initial_breakpoints={
|
|
141
|
+
str(source): [int(line) for line in lines]
|
|
142
|
+
for source, lines in dict(arguments.get("initialBreakpoints") or {}).items()
|
|
143
|
+
},
|
|
144
|
+
timeout=float(arguments.get("timeout", 30.0)),
|
|
145
|
+
)
|
|
146
|
+
return json_text(session.as_dict())
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
async def _breakpoints(root: Path, arguments: dict) -> list[TextContent]:
|
|
150
|
+
require_debug_consent(root)
|
|
151
|
+
return json_text(get_debug_manager().set_breakpoints(
|
|
152
|
+
str(arguments["sessionId"]), str(arguments["source"]), [int(value) for value in arguments["lines"]]
|
|
153
|
+
))
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
async def _control(root: Path, arguments: dict) -> list[TextContent]:
|
|
157
|
+
require_debug_consent(root)
|
|
158
|
+
return json_text(get_debug_manager().control(
|
|
159
|
+
str(arguments["sessionId"]),
|
|
160
|
+
str(arguments["action"]),
|
|
161
|
+
thread_id=int(arguments["threadId"]) if arguments.get("threadId") is not None else None,
|
|
162
|
+
))
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
async def _inspect(root: Path, arguments: dict) -> list[TextContent]:
|
|
166
|
+
require_debug_consent(root)
|
|
167
|
+
return json_text(get_debug_manager().inspect(
|
|
168
|
+
str(arguments["sessionId"]), str(arguments["operation"]), dict(arguments.get("arguments") or {})
|
|
169
|
+
))
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
async def _evaluate(root: Path, arguments: dict) -> list[TextContent]:
|
|
173
|
+
require_debug_consent(root)
|
|
174
|
+
if arguments.get("allowSideEffects") is not True:
|
|
175
|
+
raise PermissionError("debug evaluate requires allowSideEffects=true")
|
|
176
|
+
return json_text(get_debug_manager().evaluate(
|
|
177
|
+
str(arguments["sessionId"]),
|
|
178
|
+
str(arguments["expression"]),
|
|
179
|
+
frame_id=int(arguments["frameId"]) if arguments.get("frameId") is not None else None,
|
|
180
|
+
allow_side_effects=True,
|
|
181
|
+
))
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
async def _trace(root: Path, arguments: dict) -> list[TextContent]:
|
|
185
|
+
require_debug_consent(root)
|
|
186
|
+
provider = str(arguments["provider"])
|
|
187
|
+
if provider == "dap-stack":
|
|
188
|
+
return json_text(get_debug_manager().capture_stack(
|
|
189
|
+
str(arguments["sessionId"]), thread_id=int(arguments["threadId"])
|
|
190
|
+
))
|
|
191
|
+
if provider == "python":
|
|
192
|
+
return json_text(PythonTraceProvider(root).run(
|
|
193
|
+
Path(str(arguments["script"])), [str(value) for value in arguments.get("args") or []]
|
|
194
|
+
))
|
|
195
|
+
if provider == "node":
|
|
196
|
+
return json_text(NodeCpuProfileProvider(root).run(
|
|
197
|
+
Path(str(arguments["script"])), [str(value) for value in arguments.get("args") or []]
|
|
198
|
+
))
|
|
199
|
+
if provider == "import":
|
|
200
|
+
return json_text(import_runtime_trace(root, Path(str(arguments["path"]))))
|
|
201
|
+
raise ValueError(f"unsupported trace provider: {provider}")
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
async def _stop(root: Path, arguments: dict) -> list[TextContent]:
|
|
205
|
+
require_debug_consent(root)
|
|
206
|
+
session_id = str(arguments["sessionId"])
|
|
207
|
+
get_debug_manager().stop(session_id, terminate_debuggee=bool(arguments.get("terminateDebuggee", True)))
|
|
208
|
+
return json_text({"stopped": session_id})
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
REGISTRY: dict[str, Handler] = {
|
|
212
|
+
"devcouncil_debug_discover": _discover,
|
|
213
|
+
"devcouncil_debug_start": _start,
|
|
214
|
+
"devcouncil_debug_breakpoints": _breakpoints,
|
|
215
|
+
"devcouncil_debug_control": _control,
|
|
216
|
+
"devcouncil_debug_inspect": _inspect,
|
|
217
|
+
"devcouncil_debug_evaluate": _evaluate,
|
|
218
|
+
"devcouncil_debug_trace": _trace,
|
|
219
|
+
"devcouncil_debug_stop": _stop,
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
async def dispatch(name: str, default_root: Path, arguments: dict) -> list[TextContent] | None:
|
|
224
|
+
handler = REGISTRY.get(name)
|
|
225
|
+
if handler is None:
|
|
226
|
+
return None
|
|
227
|
+
explicit = arguments.get("projectPath")
|
|
228
|
+
root = canonical_project_root(Path(explicit) if isinstance(explicit, str) and explicit else default_root)
|
|
229
|
+
try:
|
|
230
|
+
return await handler(root, arguments)
|
|
231
|
+
except PermissionError as exc:
|
|
232
|
+
return error_text(str(exc), code="debug_consent_required", tool=name)
|
|
233
|
+
except FileNotFoundError as exc:
|
|
234
|
+
return error_text(str(exc), code="not_found", tool=name)
|
|
235
|
+
except (KeyError, TypeError, ValueError, RuntimeError, TimeoutError) as exc:
|
|
236
|
+
return error_text(str(exc), code="debug_error", tool=name)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Evidence read/write MCP tool handlers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from mcp.types import TextContent
|
|
8
|
+
|
|
9
|
+
from devcouncil.integrations.mcp.util import (
|
|
10
|
+
error_text,
|
|
11
|
+
int_argument,
|
|
12
|
+
json_text,
|
|
13
|
+
optional_string_argument,
|
|
14
|
+
parse_cli_json,
|
|
15
|
+
required_string_argument,
|
|
16
|
+
run_cli_command,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async def handle_append_evidence(root: Path, db: object, arguments: dict) -> list[TextContent]:
|
|
21
|
+
del db # routed through CLI service layer
|
|
22
|
+
task_id, arg_error = required_string_argument(arguments, "task_id")
|
|
23
|
+
if arg_error:
|
|
24
|
+
return arg_error
|
|
25
|
+
lease_token, arg_error = required_string_argument(arguments, "lease_token")
|
|
26
|
+
if arg_error:
|
|
27
|
+
return arg_error
|
|
28
|
+
command, arg_error = required_string_argument(arguments, "command")
|
|
29
|
+
if arg_error:
|
|
30
|
+
return arg_error
|
|
31
|
+
summary_text, arg_error = required_string_argument(arguments, "summary")
|
|
32
|
+
if arg_error:
|
|
33
|
+
return arg_error
|
|
34
|
+
assert task_id is not None and lease_token is not None
|
|
35
|
+
exit_code = arguments.get("exit_code", 0)
|
|
36
|
+
if not isinstance(exit_code, int) or isinstance(exit_code, bool):
|
|
37
|
+
return error_text("exit_code must be an integer", code="invalid_arguments")
|
|
38
|
+
cli_args = [
|
|
39
|
+
"evidence-append", task_id,
|
|
40
|
+
"--lease-token", lease_token,
|
|
41
|
+
"--command", command or "",
|
|
42
|
+
"--summary", summary_text or "",
|
|
43
|
+
"--exit-code", str(exit_code),
|
|
44
|
+
"--json",
|
|
45
|
+
]
|
|
46
|
+
payload, cli_error = parse_cli_json(run_cli_command(cli_args, root))
|
|
47
|
+
if cli_error:
|
|
48
|
+
return cli_error
|
|
49
|
+
assert payload is not None
|
|
50
|
+
return json_text(payload)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def handle_get_evidence(root: Path, db: object, arguments: dict) -> list[TextContent]:
|
|
54
|
+
del db # routed through CLI service layer
|
|
55
|
+
task_id, arg_error = required_string_argument(arguments, "task_id")
|
|
56
|
+
if arg_error:
|
|
57
|
+
return arg_error
|
|
58
|
+
assert task_id is not None
|
|
59
|
+
command_filter = optional_string_argument(arguments, "command")
|
|
60
|
+
if command_filter == "":
|
|
61
|
+
return error_text("command must be a string", code="invalid_arguments", argument="command")
|
|
62
|
+
limit = int_argument(arguments, "limit", 20, minimum=1, maximum=100)
|
|
63
|
+
cli_args = ["evidence-list", task_id, "--limit", str(limit), "--json"]
|
|
64
|
+
if command_filter:
|
|
65
|
+
cli_args.extend(["--command", command_filter])
|
|
66
|
+
payload, cli_error = parse_cli_json(run_cli_command(cli_args, root))
|
|
67
|
+
if cli_error:
|
|
68
|
+
return cli_error
|
|
69
|
+
assert payload is not None
|
|
70
|
+
return json_text(payload)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Git diff MCP tool handlers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import subprocess
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from mcp.types import TextContent
|
|
10
|
+
|
|
11
|
+
from devcouncil.integrations.mcp.util import (
|
|
12
|
+
CLI_TIMEOUT_SECONDS,
|
|
13
|
+
error_text,
|
|
14
|
+
is_git_repo,
|
|
15
|
+
json_text,
|
|
16
|
+
optional_string_argument,
|
|
17
|
+
optional_string_list_argument,
|
|
18
|
+
truncate_text,
|
|
19
|
+
)
|
|
20
|
+
from devcouncil.storage.db import Database
|
|
21
|
+
from devcouncil.storage.repositories import TaskRepository
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
async def git_diff(root: Path, paths: list[str], staged: bool) -> dict[str, object]:
|
|
25
|
+
"""Compute a (optionally path-scoped, optionally staged) git diff."""
|
|
26
|
+
diff_args = ["git", "diff"]
|
|
27
|
+
numstat_args = ["git", "diff", "--numstat"]
|
|
28
|
+
namestatus_args = ["git", "diff", "--name-status"]
|
|
29
|
+
if staged:
|
|
30
|
+
for args in (diff_args, numstat_args, namestatus_args):
|
|
31
|
+
args.append("--cached")
|
|
32
|
+
if paths:
|
|
33
|
+
for args in (diff_args, numstat_args, namestatus_args):
|
|
34
|
+
args.append("--")
|
|
35
|
+
args.extend(paths)
|
|
36
|
+
|
|
37
|
+
def _run(args: list[str]) -> subprocess.CompletedProcess[str]:
|
|
38
|
+
return subprocess.run(
|
|
39
|
+
args, cwd=root, capture_output=True, text=True,
|
|
40
|
+
encoding="utf-8", errors="replace", timeout=CLI_TIMEOUT_SECONDS,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
loop = asyncio.get_event_loop()
|
|
45
|
+
diff_proc, numstat_proc, namestatus_proc = await asyncio.gather(
|
|
46
|
+
loop.run_in_executor(None, _run, diff_args),
|
|
47
|
+
loop.run_in_executor(None, _run, numstat_args),
|
|
48
|
+
loop.run_in_executor(None, _run, namestatus_args),
|
|
49
|
+
)
|
|
50
|
+
except (OSError, subprocess.TimeoutExpired) as exc:
|
|
51
|
+
return {"ok": False, "files": [], "unified_diff": "", "truncated": False, "error": str(exc)}
|
|
52
|
+
|
|
53
|
+
status_by_path: dict[str, str] = {}
|
|
54
|
+
for line in namestatus_proc.stdout.splitlines():
|
|
55
|
+
parts = line.split("\t")
|
|
56
|
+
if len(parts) >= 2:
|
|
57
|
+
status_by_path[parts[-1].replace("\\", "/")] = parts[0]
|
|
58
|
+
|
|
59
|
+
files: list[dict[str, object]] = []
|
|
60
|
+
for line in numstat_proc.stdout.splitlines():
|
|
61
|
+
parts = line.split("\t")
|
|
62
|
+
if len(parts) < 3:
|
|
63
|
+
continue
|
|
64
|
+
added_str, deleted_str, file_path = parts[0], parts[1], parts[-1]
|
|
65
|
+
file_path = file_path.replace("\\", "/")
|
|
66
|
+
files.append({
|
|
67
|
+
"path": file_path,
|
|
68
|
+
"status": status_by_path.get(file_path, "M"),
|
|
69
|
+
"additions": int(added_str) if added_str.isdigit() else 0,
|
|
70
|
+
"deletions": int(deleted_str) if deleted_str.isdigit() else 0,
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
unified_diff, truncated = truncate_text(diff_proc.stdout)
|
|
74
|
+
return {"ok": True, "files": files, "unified_diff": unified_diff, "truncated": truncated, "staged": staged}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
async def handle_get_diff(root: Path, db: Database | None, arguments: dict) -> list[TextContent]:
|
|
78
|
+
if not is_git_repo(root):
|
|
79
|
+
return error_text("get_diff requires a git repository.", code="not_a_git_repo")
|
|
80
|
+
task_id = optional_string_argument(arguments, "task_id")
|
|
81
|
+
if task_id == "":
|
|
82
|
+
return error_text("task_id must be a string", code="invalid_arguments", argument="task_id")
|
|
83
|
+
explicit_paths, arg_error = optional_string_list_argument(arguments, "paths")
|
|
84
|
+
if arg_error:
|
|
85
|
+
return arg_error
|
|
86
|
+
staged_value = arguments.get("staged", False)
|
|
87
|
+
if not isinstance(staged_value, bool):
|
|
88
|
+
return error_text("staged must be a boolean", code="invalid_arguments", argument="staged")
|
|
89
|
+
scope_paths: list[str] = list(explicit_paths)
|
|
90
|
+
if task_id and db:
|
|
91
|
+
with db.get_session() as session:
|
|
92
|
+
task = TaskRepository(session).get_by_id(task_id)
|
|
93
|
+
if task is None:
|
|
94
|
+
return error_text(f"Task {task_id} not found.", code="not_found", task_id=task_id)
|
|
95
|
+
for planned in task.planned_files:
|
|
96
|
+
p = planned.path.replace("\\", "/")
|
|
97
|
+
if p not in scope_paths:
|
|
98
|
+
scope_paths.append(p)
|
|
99
|
+
return json_text(await git_diff(root, scope_paths, staged_value))
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Code-review graph context MCP tool handlers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from mcp.types import TextContent
|
|
9
|
+
|
|
10
|
+
from devcouncil.integrations.mcp.util import (
|
|
11
|
+
json_text,
|
|
12
|
+
run_cli_command,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def handle_graph_context(root: Path, arguments: dict) -> list[TextContent]:
|
|
17
|
+
files = arguments.get("files", [])
|
|
18
|
+
if not isinstance(files, list):
|
|
19
|
+
files = []
|
|
20
|
+
file_args: list[str] = []
|
|
21
|
+
for item in files:
|
|
22
|
+
if isinstance(item, str) and item:
|
|
23
|
+
file_args.extend(["--file", item])
|
|
24
|
+
result = run_cli_command(["graph-context", "--json", *file_args], root)
|
|
25
|
+
if result.get("ok"):
|
|
26
|
+
try:
|
|
27
|
+
payload = json.loads(str(result.get("stdout") or "{}"))
|
|
28
|
+
return json_text(payload)
|
|
29
|
+
except json.JSONDecodeError:
|
|
30
|
+
pass
|
|
31
|
+
from devcouncil.integrations.code_review_graph import CodeReviewGraphAdapter
|
|
32
|
+
|
|
33
|
+
context = CodeReviewGraphAdapter(root).get_context(
|
|
34
|
+
[file for file in files if isinstance(file, str)]
|
|
35
|
+
)
|
|
36
|
+
return [TextContent(type="text", text=context.model_dump_json(indent=2))]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Agent handoff MCP tool handler."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from mcp.types import TextContent
|
|
8
|
+
|
|
9
|
+
from devcouncil.integrations.mcp.util import (
|
|
10
|
+
json_text,
|
|
11
|
+
parse_cli_json,
|
|
12
|
+
required_string_argument,
|
|
13
|
+
run_cli_command,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
async def handle_handoff_agent(root: Path, db: object, arguments: dict) -> list[TextContent]:
|
|
18
|
+
del db # routed through CLI service layer
|
|
19
|
+
task_id, arg_error = required_string_argument(arguments, "task_id")
|
|
20
|
+
if arg_error:
|
|
21
|
+
return arg_error
|
|
22
|
+
lease_token, arg_error = required_string_argument(arguments, "lease_token")
|
|
23
|
+
if arg_error:
|
|
24
|
+
return arg_error
|
|
25
|
+
from_agent, arg_error = required_string_argument(arguments, "from_agent")
|
|
26
|
+
if arg_error:
|
|
27
|
+
return arg_error
|
|
28
|
+
to_agent, arg_error = required_string_argument(arguments, "to_agent")
|
|
29
|
+
if arg_error:
|
|
30
|
+
return arg_error
|
|
31
|
+
assert task_id is not None and lease_token is not None
|
|
32
|
+
cli_args = [
|
|
33
|
+
"handoff-leased", task_id,
|
|
34
|
+
"--lease-token", lease_token,
|
|
35
|
+
"--from", from_agent or "",
|
|
36
|
+
"--to", to_agent or "",
|
|
37
|
+
"--json",
|
|
38
|
+
]
|
|
39
|
+
instruction = str(arguments.get("instruction") or "")
|
|
40
|
+
if instruction:
|
|
41
|
+
cli_args.extend(["--instruction", instruction])
|
|
42
|
+
payload, cli_error = parse_cli_json(run_cli_command(cli_args, root))
|
|
43
|
+
if cli_error:
|
|
44
|
+
return cli_error
|
|
45
|
+
assert payload is not None
|
|
46
|
+
if not payload.get("ok"):
|
|
47
|
+
from devcouncil.integrations.mcp.util import error_text
|
|
48
|
+
return error_text(
|
|
49
|
+
str(payload.get("error") or "handoff failed"),
|
|
50
|
+
code=str(payload.get("code") or "handoff_failed"),
|
|
51
|
+
task_id=task_id,
|
|
52
|
+
)
|
|
53
|
+
return json_text(payload)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Knowledge selection MCP tool handlers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from mcp.types import TextContent
|
|
9
|
+
|
|
10
|
+
from devcouncil.integrations.mcp.util import (
|
|
11
|
+
json_text,
|
|
12
|
+
required_string_argument,
|
|
13
|
+
run_cli_command,
|
|
14
|
+
)
|
|
15
|
+
from devcouncil.knowledge.knowledge_select import select_knowledge_payload
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
async def handle_select_knowledge(root: Path, arguments: dict) -> list[TextContent]:
|
|
19
|
+
goal, arg_error = required_string_argument(arguments, "goal")
|
|
20
|
+
if arg_error:
|
|
21
|
+
return arg_error
|
|
22
|
+
assert goal is not None
|
|
23
|
+
result = run_cli_command(["okf", "select", "--json", "--goal", goal], root)
|
|
24
|
+
if result.get("ok"):
|
|
25
|
+
try:
|
|
26
|
+
payload = json.loads(str(result.get("stdout") or "{}"))
|
|
27
|
+
return json_text(payload)
|
|
28
|
+
except json.JSONDecodeError:
|
|
29
|
+
pass
|
|
30
|
+
return json_text(select_knowledge_payload(root, goal))
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Task lease MCP tool handlers (release, renew, list)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from mcp.types import TextContent
|
|
8
|
+
|
|
9
|
+
from devcouncil.integrations.mcp.util import (
|
|
10
|
+
error_text,
|
|
11
|
+
json_text,
|
|
12
|
+
parse_cli_json,
|
|
13
|
+
required_string_argument,
|
|
14
|
+
run_cli_command,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
async def handle_release_task(root: Path, db: object, arguments: dict) -> list[TextContent]:
|
|
19
|
+
del db # routed through CLI service layer
|
|
20
|
+
task_id, arg_error = required_string_argument(arguments, "task_id")
|
|
21
|
+
if arg_error:
|
|
22
|
+
return arg_error
|
|
23
|
+
lease_token, arg_error = required_string_argument(arguments, "lease_token")
|
|
24
|
+
if arg_error:
|
|
25
|
+
return arg_error
|
|
26
|
+
assert task_id is not None and lease_token is not None
|
|
27
|
+
payload, cli_error = parse_cli_json(
|
|
28
|
+
run_cli_command(["release", task_id, "--lease-token", lease_token, "--json"], root),
|
|
29
|
+
)
|
|
30
|
+
if cli_error:
|
|
31
|
+
return cli_error
|
|
32
|
+
assert payload is not None
|
|
33
|
+
return json_text(payload)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
async def handle_renew_lease(root: Path, db: object, arguments: dict) -> list[TextContent]:
|
|
37
|
+
del db # routed through CLI service layer
|
|
38
|
+
task_id, arg_error = required_string_argument(arguments, "task_id")
|
|
39
|
+
if arg_error:
|
|
40
|
+
return arg_error
|
|
41
|
+
lease_token, arg_error = required_string_argument(arguments, "lease_token")
|
|
42
|
+
if arg_error:
|
|
43
|
+
return arg_error
|
|
44
|
+
assert task_id is not None and lease_token is not None
|
|
45
|
+
ttl_value = arguments.get("ttl_seconds")
|
|
46
|
+
if ttl_value is not None and (not isinstance(ttl_value, int) or isinstance(ttl_value, bool)):
|
|
47
|
+
return error_text("ttl_seconds must be an integer", code="invalid_arguments", argument="ttl_seconds")
|
|
48
|
+
cli_args = ["lease", "renew", task_id, "--lease-token", lease_token, "--json"]
|
|
49
|
+
if isinstance(ttl_value, int) and not isinstance(ttl_value, bool):
|
|
50
|
+
cli_args.extend(["--ttl-seconds", str(ttl_value)])
|
|
51
|
+
payload, cli_error = parse_cli_json(run_cli_command(cli_args, root))
|
|
52
|
+
if cli_error:
|
|
53
|
+
return cli_error
|
|
54
|
+
assert payload is not None
|
|
55
|
+
return json_text(payload)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
async def handle_list_leases(root: Path, db: object, arguments: dict) -> list[TextContent]:
|
|
59
|
+
del db # routed through CLI service layer
|
|
60
|
+
active_only = arguments.get("active_only", True)
|
|
61
|
+
if not isinstance(active_only, bool):
|
|
62
|
+
return error_text("active_only must be a boolean", code="invalid_arguments", argument="active_only")
|
|
63
|
+
cli_args = ["lease", "list", "--json"]
|
|
64
|
+
if not active_only:
|
|
65
|
+
cli_args.append("--all")
|
|
66
|
+
payload, cli_error = parse_cli_json(run_cli_command(cli_args, root))
|
|
67
|
+
if cli_error:
|
|
68
|
+
return cli_error
|
|
69
|
+
assert payload is not None
|
|
70
|
+
return json_text(payload)
|