devcouncil 0.3.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +92 -33
- 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 +56 -40
- 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 +964 -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 +281 -0
- package/src/devcouncil/integrations/mcp/handlers/graph.py +34 -0
- package/src/devcouncil/integrations/mcp/handlers/handoff.py +53 -0
- package/src/devcouncil/integrations/mcp/handlers/knowledge.py +28 -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 +87 -0
- package/src/devcouncil/integrations/mcp/handlers/read.py +103 -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 +69 -0
- package/src/devcouncil/integrations/mcp/handlers/scope.py +56 -0
- package/src/devcouncil/integrations/mcp/handlers/status.py +199 -0
- package/src/devcouncil/integrations/mcp/handlers/task.py +88 -0
- package/src/devcouncil/integrations/mcp/handlers/tool_specs.py +922 -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 +53 -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 +325 -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/summary.py +21 -3
- 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,93 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: devcouncil
|
|
3
|
+
title: DevCouncil Integration for Claude Code
|
|
4
|
+
description: Operate inside a DevCouncil-managed repo — use MCP tools and slash commands for status, scope, policy-gated writes, and evidence-first verification instead of guessing project state.
|
|
5
|
+
triggers:
|
|
6
|
+
keywords: [devcouncil, dev council, devcouncil mcp, mcp devcouncil, dev integrate]
|
|
7
|
+
markers: [.devcouncil/config.yaml]
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# DevCouncil Integration for Claude Code
|
|
11
|
+
|
|
12
|
+
This repository is managed by **DevCouncil**: a planning, execution, and verification
|
|
13
|
+
layer for coding agents. Evidence — not model confidence — decides when work is done.
|
|
14
|
+
|
|
15
|
+
Use DevCouncil's MCP tools and `dev` CLI for project state, task scope, and verification.
|
|
16
|
+
Do not guess at task status, file scope, or whether tests actually prove the diff.
|
|
17
|
+
|
|
18
|
+
## Navigate before you edit
|
|
19
|
+
|
|
20
|
+
1. Open `.devcouncil/repo_map.json` for subsystem entry points, critical files, and
|
|
21
|
+
cross-subsystem handoff paths. Regenerate with `dev map` after large refactors.
|
|
22
|
+
2. Read `AGENTS.md` / `CLAUDE.md` for workspace conventions.
|
|
23
|
+
3. Check status: `devcouncil_status` (MCP) or `/devcouncil:status` (slash command) or
|
|
24
|
+
`dev status` (CLI).
|
|
25
|
+
|
|
26
|
+
## MCP tools vs CLI vs slash commands
|
|
27
|
+
|
|
28
|
+
| Need | Prefer |
|
|
29
|
+
|---|---|
|
|
30
|
+
| Task loop (checkout, write, verify, release) | MCP tools (`mcp__devcouncil__devcouncil_*`) |
|
|
31
|
+
| Quick human-readable status / reports | Slash commands (`/devcouncil:status`, `/devcouncil:report`) |
|
|
32
|
+
| Scripting, CI, headless runs | `dev` CLI (`dev verify`, `dev go`, `dev e2e`) |
|
|
33
|
+
| Read-only inspection (gaps, provenance, diff) | MCP read tools before re-running verify |
|
|
34
|
+
|
|
35
|
+
MCP tool names in Claude Code are prefixed: `mcp__devcouncil__devcouncil_<name>`.
|
|
36
|
+
|
|
37
|
+
## Slash commands (Claude Code)
|
|
38
|
+
|
|
39
|
+
Install with `dev integrate claude --apply`. Commands live under `/devcouncil:*`:
|
|
40
|
+
|
|
41
|
+
| Command | Purpose |
|
|
42
|
+
|---|---|
|
|
43
|
+
| `/devcouncil:status` | Phase, tasks, blocking gaps |
|
|
44
|
+
| `/devcouncil:next` | Pick up the next unblocked task via MCP |
|
|
45
|
+
| `/devcouncil:verify [TASK-ID]` | Run verification, report gaps |
|
|
46
|
+
| `/devcouncil:repair [TASK-ID]` | Repair blocking gaps |
|
|
47
|
+
| `/devcouncil:plan <goal>` | Plan a goal into requirements/tasks |
|
|
48
|
+
| `/devcouncil:review [TASK-ID]` | Live-review critique cards |
|
|
49
|
+
| `/devcouncil:report` | Full coverage report |
|
|
50
|
+
| `/devcouncil:map [goal]` | Refresh/read the repo map |
|
|
51
|
+
| `/devcouncil:wiki [topic]` | Consult the codebase wiki |
|
|
52
|
+
| `/devcouncil:supervise [RUN-ID]` | Review a recorded agent run |
|
|
53
|
+
|
|
54
|
+
## Key MCP tools (by role)
|
|
55
|
+
|
|
56
|
+
**Orientation:** `devcouncil_status`, `devcouncil_report`, `devcouncil_integration_status`,
|
|
57
|
+
`devcouncil_wiki_page`, `devcouncil_graph_context`
|
|
58
|
+
|
|
59
|
+
**Task loop:** `devcouncil_next_task`, `devcouncil_checkout_task`, `devcouncil_get_task`,
|
|
60
|
+
`devcouncil_get_prompt`, `devcouncil_release_task`, `devcouncil_renew_lease`
|
|
61
|
+
|
|
62
|
+
**Policy-gated writes:** `devcouncil_write_file`, `devcouncil_apply_patch`,
|
|
63
|
+
`devcouncil_policy_check_write` (preflight), `devcouncil_run_command`,
|
|
64
|
+
`devcouncil_record_command`
|
|
65
|
+
|
|
66
|
+
**Read-only inspection:** `devcouncil_read_file`, `devcouncil_get_diff`,
|
|
67
|
+
`devcouncil_get_gaps`, `devcouncil_get_next_actions`, `devcouncil_get_evidence`,
|
|
68
|
+
`devcouncil_get_task_provenance`
|
|
69
|
+
|
|
70
|
+
**Verification:** `devcouncil_verify_task` (requires an active lease)
|
|
71
|
+
|
|
72
|
+
**Live review:** `devcouncil_live_review`, `devcouncil_live_cards`,
|
|
73
|
+
`devcouncil_live_repair_prompt`
|
|
74
|
+
|
|
75
|
+
## Subagents
|
|
76
|
+
|
|
77
|
+
When delegated, use the bundled subagents:
|
|
78
|
+
|
|
79
|
+
- **devcouncil-implementer** — checkout → scoped edits → verify → release
|
|
80
|
+
- **devcouncil-verifier** — read-only verification and gap reporting
|
|
81
|
+
- **devcouncil-reviewer** — policy-aware diff review via live critique cards
|
|
82
|
+
|
|
83
|
+
## Rules of engagement
|
|
84
|
+
|
|
85
|
+
- **Scope:** edit only files declared in the task's planned scope. Out-of-scope or
|
|
86
|
+
protected paths are rejected by the write gate.
|
|
87
|
+
- **Evidence:** run tests and call `devcouncil_verify_task` before claiming done.
|
|
88
|
+
- **Leases:** one agent owns a task at a time; checkout before writes, release when verified.
|
|
89
|
+
- **Repairs:** when gaps exist, read `devcouncil_get_next_actions` and act on each
|
|
90
|
+
typed action — do not declare success while blocking gaps remain.
|
|
91
|
+
|
|
92
|
+
For the full autonomous loop, follow the **devcouncil-hero-loop** skill. For verifier
|
|
93
|
+
gates and the next-actions contract, follow **devcouncil-verification**.
|
|
@@ -11,6 +11,7 @@ from __future__ import annotations
|
|
|
11
11
|
|
|
12
12
|
import fnmatch
|
|
13
13
|
import functools
|
|
14
|
+
import logging
|
|
14
15
|
import os
|
|
15
16
|
import re
|
|
16
17
|
from pathlib import Path
|
|
@@ -19,6 +20,8 @@ from pydantic import BaseModel, Field
|
|
|
19
20
|
|
|
20
21
|
from devcouncil.knowledge.frontmatter import build_frontmatter_markdown, split_frontmatter
|
|
21
22
|
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
22
25
|
LIBRARY_DIR = Path(__file__).resolve().parent / "library"
|
|
23
26
|
|
|
24
27
|
|
|
@@ -50,6 +53,7 @@ _MAX_WALK_FILES = 20_000
|
|
|
50
53
|
class SkillTriggers(BaseModel):
|
|
51
54
|
keywords: list[str] = Field(default_factory=list)
|
|
52
55
|
globs: list[str] = Field(default_factory=list)
|
|
56
|
+
markers: list[str] = Field(default_factory=list)
|
|
53
57
|
|
|
54
58
|
|
|
55
59
|
class Skill(BaseModel):
|
|
@@ -61,7 +65,12 @@ class Skill(BaseModel):
|
|
|
61
65
|
body: str = ""
|
|
62
66
|
source_path: Path | None = None
|
|
63
67
|
|
|
64
|
-
def matches(
|
|
68
|
+
def matches(
|
|
69
|
+
self,
|
|
70
|
+
goal: str,
|
|
71
|
+
repo_files_present: "set[str] | None" = None,
|
|
72
|
+
project_root: Path | None = None,
|
|
73
|
+
) -> bool:
|
|
65
74
|
"""True if this skill applies to the given goal text / repo file basenames."""
|
|
66
75
|
if self.always:
|
|
67
76
|
return True
|
|
@@ -73,9 +82,18 @@ class Skill(BaseModel):
|
|
|
73
82
|
pat = pattern.lower()
|
|
74
83
|
if any(fnmatch.fnmatch(name, pat) for name in repo_files_present):
|
|
75
84
|
return True
|
|
85
|
+
if project_root is not None:
|
|
86
|
+
for marker in self.triggers.markers:
|
|
87
|
+
if (project_root / marker).exists():
|
|
88
|
+
return True
|
|
76
89
|
return False
|
|
77
90
|
|
|
78
|
-
def relevance_score(
|
|
91
|
+
def relevance_score(
|
|
92
|
+
self,
|
|
93
|
+
goal: str,
|
|
94
|
+
repo_files_present: "set[str] | None" = None,
|
|
95
|
+
project_root: Path | None = None,
|
|
96
|
+
) -> int:
|
|
79
97
|
"""How strongly this skill applies — used to rank which skills ride inline before
|
|
80
98
|
the size budget truncates. Goal-text keyword hits weigh more than file-glob
|
|
81
99
|
presence; always-on skills sort first regardless."""
|
|
@@ -88,6 +106,8 @@ class Skill(BaseModel):
|
|
|
88
106
|
1 for pattern in self.triggers.globs
|
|
89
107
|
if any(fnmatch.fnmatch(name, pattern.lower()) for name in repo_files_present)
|
|
90
108
|
)
|
|
109
|
+
if project_root is not None:
|
|
110
|
+
score += sum(1 for marker in self.triggers.markers if (project_root / marker).exists())
|
|
91
111
|
return score
|
|
92
112
|
|
|
93
113
|
def to_skill_md(self) -> str:
|
|
@@ -113,6 +133,7 @@ def _skill_from_meta(path: Path, meta: dict, body: str) -> Skill:
|
|
|
113
133
|
triggers=SkillTriggers(
|
|
114
134
|
keywords=list(triggers.get("keywords") or []),
|
|
115
135
|
globs=list(triggers.get("globs") or []),
|
|
136
|
+
markers=list(triggers.get("markers") or []),
|
|
116
137
|
),
|
|
117
138
|
body=body.strip(),
|
|
118
139
|
source_path=path,
|
|
@@ -240,7 +261,9 @@ def load_skills(
|
|
|
240
261
|
# selection metadata when it doesn't declare its own — otherwise
|
|
241
262
|
# scaffolding a skill silently strips its `always`/triggers and the
|
|
242
263
|
# skill stops being selected (selection would return nothing).
|
|
243
|
-
has_own_triggers = bool(
|
|
264
|
+
has_own_triggers = bool(
|
|
265
|
+
skill.triggers.keywords or skill.triggers.globs or skill.triggers.markers
|
|
266
|
+
)
|
|
244
267
|
skill = skill.model_copy(update={
|
|
245
268
|
"always": skill.always or base.always,
|
|
246
269
|
"triggers": skill.triggers if has_own_triggers else base.triggers,
|
|
@@ -256,8 +279,8 @@ def load_skills(
|
|
|
256
279
|
try:
|
|
257
280
|
from devcouncil.app.config import load_config
|
|
258
281
|
knowledge_dir = load_config(project_root).knowledge.directory
|
|
259
|
-
except Exception:
|
|
260
|
-
|
|
282
|
+
except Exception as e:
|
|
283
|
+
logger.debug("Failed to load knowledge directory from config, using default: %s", e)
|
|
261
284
|
for skill in load_okf_skills(project_root, directory=knowledge_dir):
|
|
262
285
|
# Only fill gaps: library + repo-local skills win on name conflict.
|
|
263
286
|
by_name.setdefault(skill.name, skill)
|
|
@@ -342,7 +365,7 @@ def select_skills(
|
|
|
342
365
|
# Rank by relevance so the most applicable domain skill survives the inline budget on a
|
|
343
366
|
# polyglot repo; always-on skills keep their leading position; ties break by name.
|
|
344
367
|
scored = [
|
|
345
|
-
(s, score) for s in skills if (score := s.relevance_score(goal, repo_files)) > 0
|
|
368
|
+
(s, score) for s in skills if (score := s.relevance_score(goal, repo_files, project_root)) > 0
|
|
346
369
|
]
|
|
347
370
|
scored.sort(key=lambda item: (not item[0].always, -item[1], item[0].name))
|
|
348
371
|
return [skill for skill, _ in scored]
|
|
@@ -389,15 +412,23 @@ def scaffold_skills(project_root: Path, skills: list[Skill]) -> list[Path]:
|
|
|
389
412
|
"""
|
|
390
413
|
written: list[Path] = []
|
|
391
414
|
skills_root = project_root / ".claude" / "skills"
|
|
392
|
-
|
|
415
|
+
dev_skills_root = project_root / ".devcouncil" / "skills"
|
|
393
416
|
for skill in skills:
|
|
394
|
-
# Don't re-materialize a skill that already lives
|
|
417
|
+
# Don't re-materialize a skill that already lives in a repo-local skill dir.
|
|
418
|
+
# Packaged library files inside a monorepo (e.g. src/.../skills/library/) still
|
|
419
|
+
# scaffold to .claude/skills/ — only skip when the source IS the destination.
|
|
395
420
|
if skill.source_path is not None:
|
|
396
|
-
|
|
397
|
-
|
|
421
|
+
src = skill.source_path.resolve()
|
|
422
|
+
skip = False
|
|
423
|
+
for base in (skills_root, dev_skills_root):
|
|
424
|
+
try:
|
|
425
|
+
src.relative_to(base.resolve())
|
|
426
|
+
skip = True
|
|
427
|
+
break
|
|
428
|
+
except ValueError:
|
|
429
|
+
continue
|
|
430
|
+
if skip:
|
|
398
431
|
continue
|
|
399
|
-
except ValueError:
|
|
400
|
-
pass
|
|
401
432
|
target = skills_root / skill.name / "SKILL.md"
|
|
402
433
|
content = skill.to_skill_md()
|
|
403
434
|
if target.exists() and target.read_text(encoding="utf-8") == content:
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
from collections import defaultdict
|
|
2
3
|
from contextlib import contextmanager
|
|
3
4
|
from datetime import datetime, timezone
|
|
@@ -10,6 +11,8 @@ from typing import Optional
|
|
|
10
11
|
|
|
11
12
|
from devcouncil.storage.models import SchemaVersionModel
|
|
12
13
|
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
13
16
|
|
|
14
17
|
# v3: per-task indexes on the task/audit tables (task_id, lease status).
|
|
15
18
|
# v4: machine-routable gap columns (file, line, suggested_command,
|
|
@@ -17,7 +20,15 @@ from devcouncil.storage.models import SchemaVersionModel
|
|
|
17
20
|
# v5: partial unique index enforcing one ACTIVE lease per task (single-writer).
|
|
18
21
|
# v6: gap column expected_verification_method (repair loop tells an executor-remediable
|
|
19
22
|
# "incomplete" from a manual/llm one across a reload).
|
|
20
|
-
|
|
23
|
+
# v7: task columns agent_appended_expected_tests_json / agent_appended_allowed_commands_json
|
|
24
|
+
# (agent-negotiated scope extensions). These are NOT NULL with a scalar default, which
|
|
25
|
+
# the column migration now adds as ``ADD COLUMN ... NOT NULL DEFAULT <literal>`` —
|
|
26
|
+
# previously non-nullable additions were skipped entirely, so every SELECT on an
|
|
27
|
+
# existing database crashed with "no such column".
|
|
28
|
+
# v8: optional task.priority column (high/medium/low planner hint).
|
|
29
|
+
# v9: task column agent_appended_planned_files_json (lease-gated modify-op scope
|
|
30
|
+
# expansion so unwired_file / orphan_diff repairs can append the intended caller).
|
|
31
|
+
SCHEMA_VERSION = 9
|
|
21
32
|
|
|
22
33
|
|
|
23
34
|
# Module-level caches keyed by *resolved* paths so distinct project roots stay
|
|
@@ -42,8 +53,9 @@ def reset_db_cache() -> None:
|
|
|
42
53
|
for db in _db_instances.values():
|
|
43
54
|
try:
|
|
44
55
|
db.engine.dispose()
|
|
45
|
-
except Exception:
|
|
46
|
-
|
|
56
|
+
except Exception as e:
|
|
57
|
+
# A failed dispose can leave the SQLite file locked for later rebuilds.
|
|
58
|
+
logger.debug("Failed to dispose cached engine for %s: %s", db.db_path, e)
|
|
47
59
|
_db_instances.clear()
|
|
48
60
|
_dedup_done.clear()
|
|
49
61
|
|
|
@@ -126,13 +138,38 @@ class Database:
|
|
|
126
138
|
)
|
|
127
139
|
_dedup_done.add(key)
|
|
128
140
|
|
|
141
|
+
@staticmethod
|
|
142
|
+
def _scalar_default_literal(column) -> Optional[str]:
|
|
143
|
+
"""SQL literal for a column's scalar Python-side default, or None.
|
|
144
|
+
|
|
145
|
+
Only simple scalars (str/bool/int/float) are rendered — anything callable,
|
|
146
|
+
context-sensitive, or exotic returns None so the caller can skip the column
|
|
147
|
+
rather than emit wrong DDL."""
|
|
148
|
+
default = getattr(column, "default", None)
|
|
149
|
+
if default is None or getattr(default, "is_callable", False):
|
|
150
|
+
return None
|
|
151
|
+
arg = getattr(default, "arg", None)
|
|
152
|
+
if isinstance(arg, bool):
|
|
153
|
+
return "1" if arg else "0"
|
|
154
|
+
if isinstance(arg, (int, float)):
|
|
155
|
+
return str(arg)
|
|
156
|
+
if isinstance(arg, str):
|
|
157
|
+
escaped = arg.replace("'", "''")
|
|
158
|
+
return f"'{escaped}'"
|
|
159
|
+
return None
|
|
160
|
+
|
|
129
161
|
def _create_missing_columns(self):
|
|
130
162
|
# create_all never alters an existing table, so columns added to a model
|
|
131
163
|
# later (e.g. the v4 gap routing columns) are missing on databases created by
|
|
132
164
|
# an older version — and a SELECT of the model would then fail. Add any
|
|
133
|
-
# missing column
|
|
134
|
-
#
|
|
135
|
-
#
|
|
165
|
+
# missing column in place via ADD COLUMN: nullable columns as-is, and
|
|
166
|
+
# NON-nullable columns with a scalar default as
|
|
167
|
+
# ``NOT NULL DEFAULT <literal>`` (the shape SQLite accepts without a table
|
|
168
|
+
# rewrite — existing rows take the default). Previously non-nullable
|
|
169
|
+
# additions were skipped entirely, so a model gaining a required column
|
|
170
|
+
# (v7's agent_appended_*_json) crashed every SELECT on an existing database
|
|
171
|
+
# with "no such column". Non-nullable columns WITHOUT a scalar default are
|
|
172
|
+
# still skipped: existing rows could not satisfy them.
|
|
136
173
|
inspector = inspect(self.engine)
|
|
137
174
|
existing_tables = set(inspector.get_table_names())
|
|
138
175
|
for table in SQLModel.metadata.sorted_tables:
|
|
@@ -140,13 +177,22 @@ class Database:
|
|
|
140
177
|
continue
|
|
141
178
|
existing_cols = {col["name"] for col in inspector.get_columns(table.name)}
|
|
142
179
|
for column in table.columns:
|
|
143
|
-
if column.name in existing_cols
|
|
180
|
+
if column.name in existing_cols:
|
|
144
181
|
continue
|
|
145
182
|
ddl_type = column.type.compile(self.engine.dialect)
|
|
146
|
-
|
|
183
|
+
if column.nullable:
|
|
184
|
+
ddl = f'ALTER TABLE "{table.name}" ADD COLUMN "{column.name}" {ddl_type}'
|
|
185
|
+
else:
|
|
186
|
+
literal = self._scalar_default_literal(column)
|
|
187
|
+
if literal is None:
|
|
188
|
+
continue
|
|
189
|
+
ddl = (
|
|
190
|
+
f'ALTER TABLE "{table.name}" ADD COLUMN "{column.name}" '
|
|
191
|
+
f"{ddl_type} NOT NULL DEFAULT {literal}"
|
|
192
|
+
)
|
|
147
193
|
try:
|
|
148
194
|
with self.engine.begin() as conn:
|
|
149
|
-
conn.execute(
|
|
195
|
+
conn.execute(text(ddl))
|
|
150
196
|
except OperationalError as exc:
|
|
151
197
|
if "duplicate column name" not in str(exc).lower():
|
|
152
198
|
raise
|
|
@@ -194,8 +240,8 @@ def get_db(project_root: Path = Path(".")) -> Optional[Database]:
|
|
|
194
240
|
return cached
|
|
195
241
|
try:
|
|
196
242
|
cached.engine.dispose()
|
|
197
|
-
except Exception:
|
|
198
|
-
|
|
243
|
+
except Exception as e:
|
|
244
|
+
logger.debug("Failed to dispose stale cached engine for %s: %s", db_path, e)
|
|
199
245
|
|
|
200
246
|
db = Database(db_path)
|
|
201
247
|
db.ensure_schema_version()
|
|
@@ -38,8 +38,14 @@ class TaskModel(SQLModel, table=True):
|
|
|
38
38
|
acceptance_criterion_ids_json: str = Field(default="[]")
|
|
39
39
|
planned_files_json: str = Field(default="[]")
|
|
40
40
|
expected_tests_json: str = Field(default="[]")
|
|
41
|
+
agent_appended_expected_tests_json: str = Field(default="[]")
|
|
41
42
|
allowed_commands_json: str = Field(default="[]")
|
|
43
|
+
agent_appended_allowed_commands_json: str = Field(default="[]")
|
|
44
|
+
agent_appended_planned_files_json: str = Field(default="[]")
|
|
42
45
|
forbidden_changes_json: str = Field(default="[]")
|
|
46
|
+
# Manual/planner difficulty override ("easy"/"normal"/"hard"); None = estimator.
|
|
47
|
+
difficulty: Optional[str] = Field(default=None)
|
|
48
|
+
priority: Optional[str] = Field(default=None)
|
|
43
49
|
status: str = "planned"
|
|
44
50
|
|
|
45
51
|
class EvidenceModel(SQLModel, table=True):
|
|
@@ -12,6 +12,8 @@ from pydantic import BaseModel
|
|
|
12
12
|
from sqlalchemy.exc import IntegrityError
|
|
13
13
|
from sqlmodel import Session, col, select
|
|
14
14
|
|
|
15
|
+
from devcouncil.utils.json_persist import dump_json
|
|
16
|
+
|
|
15
17
|
from devcouncil.storage.models import (
|
|
16
18
|
AgentHandoffModel,
|
|
17
19
|
CorrectionManifestModel,
|
|
@@ -386,7 +388,7 @@ class SemanticDiffRepository:
|
|
|
386
388
|
task_id=task_id,
|
|
387
389
|
before_snapshot_path=before_snapshot_path,
|
|
388
390
|
after_snapshot_path=after_snapshot_path,
|
|
389
|
-
classifications_json=
|
|
391
|
+
classifications_json=dump_json(classifications),
|
|
390
392
|
summary=summary,
|
|
391
393
|
created_at=_utc_now(),
|
|
392
394
|
)
|
|
@@ -518,8 +520,8 @@ class VerificationRunRepository:
|
|
|
518
520
|
id=rid,
|
|
519
521
|
task_id=task_id,
|
|
520
522
|
sandbox=sandbox,
|
|
521
|
-
environment_json=
|
|
522
|
-
commands_json=
|
|
523
|
+
environment_json=dump_json(environment),
|
|
524
|
+
commands_json=dump_json(commands),
|
|
523
525
|
status=status,
|
|
524
526
|
started_at=_utc_now(),
|
|
525
527
|
finished_at=finished_at,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from sqlmodel import Session, col, delete, select
|
|
2
2
|
from typing import List, Optional, Any
|
|
3
3
|
import json
|
|
4
|
+
from devcouncil.utils.json_persist import dump_json
|
|
4
5
|
from devcouncil.storage.models import (
|
|
5
6
|
RequirementModel,
|
|
6
7
|
AssumptionModel,
|
|
@@ -45,7 +46,7 @@ class RequirementRepository:
|
|
|
45
46
|
description=req.description,
|
|
46
47
|
priority=req.priority,
|
|
47
48
|
source=req.source,
|
|
48
|
-
acceptance_criteria_json=
|
|
49
|
+
acceptance_criteria_json=dump_json([ac.model_dump() for ac in req.acceptance_criteria])
|
|
49
50
|
)
|
|
50
51
|
self.session.merge(model)
|
|
51
52
|
self.session.commit()
|
|
@@ -80,7 +81,7 @@ class AssumptionRepository:
|
|
|
80
81
|
impact=assumption.impact,
|
|
81
82
|
reversible=assumption.reversible,
|
|
82
83
|
requires_user_confirmation=assumption.requires_user_confirmation,
|
|
83
|
-
linked_requirement_ids_json=
|
|
84
|
+
linked_requirement_ids_json=dump_json(assumption.linked_requirement_ids),
|
|
84
85
|
status=assumption.status,
|
|
85
86
|
)
|
|
86
87
|
self.session.merge(model)
|
|
@@ -105,8 +106,19 @@ class TaskRepository:
|
|
|
105
106
|
"acceptance_criterion_ids": json.loads(m.acceptance_criterion_ids_json),
|
|
106
107
|
"planned_files": pf_list,
|
|
107
108
|
"expected_tests": json.loads(m.expected_tests_json),
|
|
109
|
+
"agent_appended_expected_tests": json.loads(
|
|
110
|
+
getattr(m, "agent_appended_expected_tests_json", None) or "[]"
|
|
111
|
+
),
|
|
108
112
|
"allowed_commands": json.loads(m.allowed_commands_json),
|
|
113
|
+
"agent_appended_allowed_commands": json.loads(
|
|
114
|
+
getattr(m, "agent_appended_allowed_commands_json", None) or "[]"
|
|
115
|
+
),
|
|
116
|
+
"agent_appended_planned_files": json.loads(
|
|
117
|
+
getattr(m, "agent_appended_planned_files_json", None) or "[]"
|
|
118
|
+
),
|
|
109
119
|
"forbidden_changes": json.loads(m.forbidden_changes_json),
|
|
120
|
+
"difficulty": getattr(m, "difficulty", None),
|
|
121
|
+
"priority": getattr(m, "priority", None),
|
|
110
122
|
"status": m.status,
|
|
111
123
|
})
|
|
112
124
|
|
|
@@ -124,8 +136,19 @@ class TaskRepository:
|
|
|
124
136
|
"acceptance_criterion_ids": json.loads(m.acceptance_criterion_ids_json),
|
|
125
137
|
"planned_files": pf_list,
|
|
126
138
|
"expected_tests": json.loads(m.expected_tests_json),
|
|
139
|
+
"agent_appended_expected_tests": json.loads(
|
|
140
|
+
getattr(m, "agent_appended_expected_tests_json", None) or "[]"
|
|
141
|
+
),
|
|
127
142
|
"allowed_commands": json.loads(m.allowed_commands_json),
|
|
143
|
+
"agent_appended_allowed_commands": json.loads(
|
|
144
|
+
getattr(m, "agent_appended_allowed_commands_json", None) or "[]"
|
|
145
|
+
),
|
|
146
|
+
"agent_appended_planned_files": json.loads(
|
|
147
|
+
getattr(m, "agent_appended_planned_files_json", None) or "[]"
|
|
148
|
+
),
|
|
128
149
|
"forbidden_changes": json.loads(m.forbidden_changes_json),
|
|
150
|
+
"difficulty": getattr(m, "difficulty", None),
|
|
151
|
+
"priority": getattr(m, "priority", None),
|
|
129
152
|
"status": m.status,
|
|
130
153
|
}))
|
|
131
154
|
return results
|
|
@@ -135,12 +158,17 @@ class TaskRepository:
|
|
|
135
158
|
id=task.id,
|
|
136
159
|
title=task.title,
|
|
137
160
|
description=task.description,
|
|
138
|
-
requirement_ids_json=
|
|
139
|
-
acceptance_criterion_ids_json=
|
|
140
|
-
planned_files_json=
|
|
141
|
-
expected_tests_json=
|
|
142
|
-
|
|
143
|
-
|
|
161
|
+
requirement_ids_json=dump_json(task.requirement_ids),
|
|
162
|
+
acceptance_criterion_ids_json=dump_json(task.acceptance_criterion_ids),
|
|
163
|
+
planned_files_json=dump_json([pf.model_dump() for pf in task.planned_files]),
|
|
164
|
+
expected_tests_json=dump_json(task.expected_tests),
|
|
165
|
+
agent_appended_expected_tests_json=dump_json(task.agent_appended_expected_tests),
|
|
166
|
+
allowed_commands_json=dump_json(task.allowed_commands),
|
|
167
|
+
agent_appended_allowed_commands_json=dump_json(task.agent_appended_allowed_commands),
|
|
168
|
+
agent_appended_planned_files_json=dump_json(task.agent_appended_planned_files),
|
|
169
|
+
forbidden_changes_json=dump_json(task.forbidden_changes),
|
|
170
|
+
difficulty=task.difficulty,
|
|
171
|
+
priority=task.priority,
|
|
144
172
|
status=task.status
|
|
145
173
|
)
|
|
146
174
|
self.session.merge(model)
|
|
@@ -196,7 +224,7 @@ class GapRepository:
|
|
|
196
224
|
requirement_id=gap.requirement_id,
|
|
197
225
|
task_id=gap.task_id,
|
|
198
226
|
description=gap.description,
|
|
199
|
-
evidence_json=
|
|
227
|
+
evidence_json=dump_json(gap.evidence),
|
|
200
228
|
recommended_fix=gap.recommended_fix,
|
|
201
229
|
blocking=gap.blocking,
|
|
202
230
|
file=gap.file,
|
|
@@ -360,7 +388,7 @@ class PlanningStateRepository:
|
|
|
360
388
|
description=req.description,
|
|
361
389
|
priority=req.priority,
|
|
362
390
|
source=req.source,
|
|
363
|
-
acceptance_criteria_json=
|
|
391
|
+
acceptance_criteria_json=dump_json([ac.model_dump() for ac in req.acceptance_criteria]),
|
|
364
392
|
))
|
|
365
393
|
|
|
366
394
|
for assumption in assumptions:
|
|
@@ -371,7 +399,7 @@ class PlanningStateRepository:
|
|
|
371
399
|
impact=assumption.impact,
|
|
372
400
|
reversible=assumption.reversible,
|
|
373
401
|
requires_user_confirmation=assumption.requires_user_confirmation,
|
|
374
|
-
linked_requirement_ids_json=
|
|
402
|
+
linked_requirement_ids_json=dump_json(assumption.linked_requirement_ids),
|
|
375
403
|
status=assumption.status,
|
|
376
404
|
))
|
|
377
405
|
|
|
@@ -380,12 +408,16 @@ class PlanningStateRepository:
|
|
|
380
408
|
id=task.id,
|
|
381
409
|
title=task.title,
|
|
382
410
|
description=task.description,
|
|
383
|
-
requirement_ids_json=
|
|
384
|
-
acceptance_criterion_ids_json=
|
|
385
|
-
planned_files_json=
|
|
386
|
-
expected_tests_json=
|
|
387
|
-
|
|
388
|
-
|
|
411
|
+
requirement_ids_json=dump_json(task.requirement_ids),
|
|
412
|
+
acceptance_criterion_ids_json=dump_json(task.acceptance_criterion_ids),
|
|
413
|
+
planned_files_json=dump_json([pf.model_dump() for pf in task.planned_files]),
|
|
414
|
+
expected_tests_json=dump_json(task.expected_tests),
|
|
415
|
+
agent_appended_expected_tests_json=dump_json(task.agent_appended_expected_tests),
|
|
416
|
+
allowed_commands_json=dump_json(task.allowed_commands),
|
|
417
|
+
agent_appended_allowed_commands_json=dump_json(task.agent_appended_allowed_commands),
|
|
418
|
+
agent_appended_planned_files_json=dump_json(task.agent_appended_planned_files),
|
|
419
|
+
forbidden_changes_json=dump_json(task.forbidden_changes),
|
|
420
|
+
difficulty=task.difficulty,
|
|
389
421
|
status=task.status,
|
|
390
422
|
))
|
|
391
423
|
|
|
@@ -455,7 +487,7 @@ class StateRepository:
|
|
|
455
487
|
state = ProjectStateModel(
|
|
456
488
|
id="singleton",
|
|
457
489
|
current_phase=state,
|
|
458
|
-
history_json=
|
|
490
|
+
history_json=dump_json(history or []),
|
|
459
491
|
)
|
|
460
492
|
self.session.merge(state)
|
|
461
493
|
self.session.commit()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Process-wide run attribution for telemetry.
|
|
2
|
+
|
|
3
|
+
Every model_calls.jsonl record has a ``run_id`` field, but historically it was
|
|
4
|
+
None on 100% of real records: the LLM router accepts ``run_id`` per call, and
|
|
5
|
+
none of the planning/verification services threaded it through. Rather than
|
|
6
|
+
touching every call site (and every future one), the orchestrator declares the
|
|
7
|
+
current run here and the router falls back to it when a caller doesn't pass one
|
|
8
|
+
explicitly.
|
|
9
|
+
|
|
10
|
+
A ``ContextVar`` (not a bare global) so concurrent runs in one process — e.g.
|
|
11
|
+
an MCP server planning two projects — attribute calls to their own run.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from contextvars import ContextVar
|
|
17
|
+
from typing import Optional
|
|
18
|
+
|
|
19
|
+
_current_run_id: ContextVar[Optional[str]] = ContextVar("devcouncil_run_id", default=None)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def set_current_run_id(run_id: Optional[str]) -> None:
|
|
23
|
+
"""Declare the run all subsequent (context-local) model calls belong to."""
|
|
24
|
+
_current_run_id.set(run_id)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_current_run_id() -> Optional[str]:
|
|
28
|
+
return _current_run_id.get()
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import logging
|
|
3
|
+
import os
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
from typing import Any, Dict, List
|
|
5
6
|
|
|
@@ -45,6 +46,9 @@ class CostEstimator:
|
|
|
45
46
|
|
|
46
47
|
|
|
47
48
|
def _model_calls_file(project_root: Path) -> Path:
|
|
49
|
+
override = os.environ.get("DEVCOUNCIL_LOG_DIR")
|
|
50
|
+
if override:
|
|
51
|
+
return Path(override) / "model_calls.jsonl"
|
|
48
52
|
return project_root / ".devcouncil" / "logs" / "model_calls.jsonl"
|
|
49
53
|
|
|
50
54
|
|
|
@@ -133,8 +137,3 @@ def group_cost(project_root: Path) -> Dict[str, Any]:
|
|
|
133
137
|
"by_task": _group(records, "task_id"),
|
|
134
138
|
"by_run": _group(records, "run_id"),
|
|
135
139
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def cost_by_task(project_root: Path) -> Dict[str, Dict[str, Any]]:
|
|
139
|
-
"""Convenience accessor for the per-task cost breakdown (used by ``dev status``)."""
|
|
140
|
-
return group_cost(project_root)["by_task"]
|