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
|
@@ -53,12 +53,110 @@ def normalize_repo_path(project_root: Path, raw_path: str) -> tuple[str, bool]:
|
|
|
53
53
|
|
|
54
54
|
_NO_TASK_ALLOWED_COMMANDS = (
|
|
55
55
|
"dev status",
|
|
56
|
+
"dev status *",
|
|
57
|
+
"uv run dev status",
|
|
58
|
+
"uv run dev status *",
|
|
56
59
|
"dev tasks",
|
|
60
|
+
"dev tasks *",
|
|
61
|
+
"uv run dev tasks",
|
|
62
|
+
"uv run dev tasks *",
|
|
63
|
+
# Lease bootstrap: agents must acquire a task lease before any other shell.
|
|
64
|
+
"dev approve",
|
|
65
|
+
"dev approve *",
|
|
66
|
+
"uv run dev approve",
|
|
67
|
+
"uv run dev approve *",
|
|
68
|
+
"dev checkout *",
|
|
69
|
+
"uv run dev checkout *",
|
|
70
|
+
"dev next-task",
|
|
71
|
+
"dev next-task *",
|
|
72
|
+
"uv run dev next-task",
|
|
73
|
+
"uv run dev next-task *",
|
|
57
74
|
"git status",
|
|
58
75
|
"git diff",
|
|
59
76
|
"git diff *",
|
|
77
|
+
# Harmless status probes agents chain after allowlisted commands.
|
|
78
|
+
"echo",
|
|
79
|
+
"echo *",
|
|
80
|
+
"true",
|
|
81
|
+
":",
|
|
60
82
|
)
|
|
61
83
|
|
|
84
|
+
# Lease lifecycle and repo maintenance commands are always allowed for the lease
|
|
85
|
+
# holder (or when no task is bound), independent of task-specific allowed_commands.
|
|
86
|
+
_LEASE_LIFECYCLE_ALLOWED_COMMANDS = (
|
|
87
|
+
"dev release *",
|
|
88
|
+
"uv run dev release *",
|
|
89
|
+
"dev lease *",
|
|
90
|
+
"uv run dev lease *",
|
|
91
|
+
"dev scope *",
|
|
92
|
+
"uv run dev scope *",
|
|
93
|
+
"dev map",
|
|
94
|
+
"dev map *",
|
|
95
|
+
"uv run dev map",
|
|
96
|
+
"uv run dev map *",
|
|
97
|
+
"dev doctor",
|
|
98
|
+
"dev doctor *",
|
|
99
|
+
"uv run dev doctor",
|
|
100
|
+
"uv run dev doctor *",
|
|
101
|
+
# Orientation / graph inspection without a lease (MCP substitute path).
|
|
102
|
+
"dev graph",
|
|
103
|
+
"dev graph *",
|
|
104
|
+
"uv run dev graph",
|
|
105
|
+
"uv run dev graph *",
|
|
106
|
+
"dev run-cmd *",
|
|
107
|
+
"uv run dev run-cmd *",
|
|
108
|
+
"python -m pytest *",
|
|
109
|
+
"uv run python -m pytest *",
|
|
110
|
+
"uv run pytest *",
|
|
111
|
+
"pytest *",
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Path-prefixed project CLIs (``.venv/bin/dev``, absolute hook paths) and ``uv run
|
|
115
|
+
# --project …`` wrappers must match the same allowlist entries as bare ``dev …``.
|
|
116
|
+
_UV_RUN_DIR_FLAG_RE = re.compile(
|
|
117
|
+
r"^(?P<head>uv\s+run)(?:\s+(?:--project|--directory|-p)\s+\S+)+(?P<tail>\s+.+)$"
|
|
118
|
+
)
|
|
119
|
+
_DEV_BINARIES = frozenset({"dev", "dev.exe", "devcouncil", "devcouncil.exe"})
|
|
120
|
+
_CD_SEGMENT_RE = re.compile(r"^(?:cd|pushd|popd)(?:\s|$)")
|
|
121
|
+
# Trailing shell redirections break fnmatch against ``dev map *``; strip for matching only.
|
|
122
|
+
_REDIRECT_TAIL_RE = re.compile(
|
|
123
|
+
r"(?:\s+(?:\d*)(?:>>|>|<|&>|&>>)\s*\S+|\s+\d*>&\d+)+\s*$"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def normalize_allowlist_command(command: str) -> str:
|
|
128
|
+
"""Collapse path-prefixed ``dev``/``devcouncil`` and ``uv run`` dir flags.
|
|
129
|
+
|
|
130
|
+
Hooks install absolute ``.venv/bin/dev`` paths; agents often copy that form into
|
|
131
|
+
Shell. Without normalization those commands miss ``dev map`` / ``dev status``
|
|
132
|
+
allowlist entries and fail closed until checkout.
|
|
133
|
+
|
|
134
|
+
Only rewrites bare ``dev``/``devcouncil`` tokens or executables under a ``bin`` /
|
|
135
|
+
``Scripts`` directory — never a repo folder whose basename happens to be
|
|
136
|
+
``DevCouncil``.
|
|
137
|
+
"""
|
|
138
|
+
normalized = " ".join(command.split())
|
|
139
|
+
if not normalized:
|
|
140
|
+
return normalized
|
|
141
|
+
normalized = _REDIRECT_TAIL_RE.sub("", normalized).rstrip()
|
|
142
|
+
flagged = _UV_RUN_DIR_FLAG_RE.match(normalized)
|
|
143
|
+
if flagged is not None:
|
|
144
|
+
normalized = f"{flagged.group('head')}{flagged.group('tail')}"
|
|
145
|
+
tokens = normalized.split()
|
|
146
|
+
rewritten: list[str] = []
|
|
147
|
+
for token in tokens:
|
|
148
|
+
posix = token.replace("\\", "/")
|
|
149
|
+
path = Path(posix)
|
|
150
|
+
name = path.name.lower()
|
|
151
|
+
parent = path.parent.name.lower()
|
|
152
|
+
bare = "/" not in posix and name in _DEV_BINARIES
|
|
153
|
+
under_bin = name in _DEV_BINARIES and parent in {"bin", "scripts"}
|
|
154
|
+
if bare or under_bin:
|
|
155
|
+
rewritten.append("dev")
|
|
156
|
+
else:
|
|
157
|
+
rewritten.append(token)
|
|
158
|
+
return " ".join(rewritten)
|
|
159
|
+
|
|
62
160
|
# Shared with HookPolicy — keep these as the single source of truth for
|
|
63
161
|
# protected/secret path patterns.
|
|
64
162
|
PROTECTED_WRITE_PATTERNS = (
|
|
@@ -143,7 +241,8 @@ class TaskPolicyEngine:
|
|
|
143
241
|
self.global_allowed_commands = global_allowed_commands or []
|
|
144
242
|
|
|
145
243
|
def evaluate_command(self, command: str, task: Task | None) -> PolicyDecision:
|
|
146
|
-
|
|
244
|
+
raw = " ".join(command.split())
|
|
245
|
+
normalized = normalize_allowlist_command(raw)
|
|
147
246
|
if not normalized:
|
|
148
247
|
return PolicyDecision(
|
|
149
248
|
action="deny",
|
|
@@ -152,27 +251,64 @@ class TaskPolicyEngine:
|
|
|
152
251
|
task_id=task.id if task else None,
|
|
153
252
|
)
|
|
154
253
|
|
|
254
|
+
# ``cd`` / ``pushd`` / ``popd`` alone cannot write; agents chain them before
|
|
255
|
+
# allowlisted ``dev map`` (e.g. ``cd repo && .venv/bin/dev map``).
|
|
256
|
+
if _CD_SEGMENT_RE.match(normalized):
|
|
257
|
+
return PolicyDecision(
|
|
258
|
+
action="allow",
|
|
259
|
+
reason="Working-directory change is not gated.",
|
|
260
|
+
target=normalized,
|
|
261
|
+
task_id=task.id if task else None,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
if any(
|
|
265
|
+
fnmatch.fnmatch(normalized, allowed) for allowed in _LEASE_LIFECYCLE_ALLOWED_COMMANDS
|
|
266
|
+
):
|
|
267
|
+
return PolicyDecision(
|
|
268
|
+
action="allow",
|
|
269
|
+
reason="Lease lifecycle or repo maintenance command allowed.",
|
|
270
|
+
target=normalized,
|
|
271
|
+
task_id=task.id if task else None,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
if any(fnmatch.fnmatch(normalized, allowed) for allowed in _NO_TASK_ALLOWED_COMMANDS):
|
|
275
|
+
return PolicyDecision(
|
|
276
|
+
action="allow",
|
|
277
|
+
reason="Bootstrap or read-only command allowed.",
|
|
278
|
+
target=normalized,
|
|
279
|
+
task_id=task.id if task else None,
|
|
280
|
+
)
|
|
281
|
+
|
|
155
282
|
if task is None:
|
|
156
|
-
if any(fnmatch.fnmatch(normalized, allowed) for allowed in _NO_TASK_ALLOWED_COMMANDS):
|
|
157
|
-
return PolicyDecision(
|
|
158
|
-
action="allow",
|
|
159
|
-
reason="Read-only command allowed without active task.",
|
|
160
|
-
target=normalized,
|
|
161
|
-
)
|
|
162
283
|
return PolicyDecision(
|
|
163
284
|
action="deny",
|
|
164
|
-
reason=
|
|
285
|
+
reason=(
|
|
286
|
+
"Shell commands require an active task lease. "
|
|
287
|
+
"Bootstrap with `dev checkout <TASK>`, or use allowlisted "
|
|
288
|
+
"orientation commands (`dev status`, `dev map`, `dev doctor`, "
|
|
289
|
+
"`dev graph …`)."
|
|
290
|
+
),
|
|
165
291
|
target=normalized,
|
|
166
292
|
)
|
|
167
293
|
|
|
168
|
-
|
|
294
|
+
# Match allowlist entries against both raw and normalized forms so a task
|
|
295
|
+
# that lists `.venv/bin/dev *` still works, while path-prefixed `dev map`
|
|
296
|
+
# continues to hit lifecycle patterns after normalization.
|
|
297
|
+
task_patterns = list(task.allowed_commands)
|
|
298
|
+
if any(
|
|
299
|
+
fnmatch.fnmatch(normalized, allowed) or fnmatch.fnmatch(raw, allowed)
|
|
300
|
+
for allowed in task_patterns
|
|
301
|
+
):
|
|
169
302
|
return PolicyDecision(
|
|
170
303
|
action="allow",
|
|
171
304
|
reason="Command matches task allowed_commands.",
|
|
172
305
|
target=normalized,
|
|
173
306
|
task_id=task.id,
|
|
174
307
|
)
|
|
175
|
-
if any(
|
|
308
|
+
if any(
|
|
309
|
+
fnmatch.fnmatch(normalized, allowed) or fnmatch.fnmatch(raw, allowed)
|
|
310
|
+
for allowed in self.global_allowed_commands
|
|
311
|
+
):
|
|
176
312
|
return PolicyDecision(
|
|
177
313
|
action="allow",
|
|
178
314
|
reason="Command matches global allowed commands.",
|
|
@@ -238,6 +374,9 @@ class TaskPolicyEngine:
|
|
|
238
374
|
|
|
239
375
|
planned = self._planned_file_for(normalized, task)
|
|
240
376
|
if planned is None:
|
|
377
|
+
neighbor_decision = self._neighbor_policy_decision(normalized, task)
|
|
378
|
+
if neighbor_decision is not None:
|
|
379
|
+
return neighbor_decision
|
|
241
380
|
return PolicyDecision(
|
|
242
381
|
action="deny",
|
|
243
382
|
reason=f"Task {task.id} does not authorize changes to {normalized}.",
|
|
@@ -333,6 +472,62 @@ class TaskPolicyEngine:
|
|
|
333
472
|
return planned
|
|
334
473
|
return None
|
|
335
474
|
|
|
475
|
+
def _neighbor_policy_decision(self, path: str, task: Task) -> PolicyDecision | None:
|
|
476
|
+
"""Soft-block writes outside planned files unless same subsystem or a neighbor."""
|
|
477
|
+
try:
|
|
478
|
+
from devcouncil.indexing.subsystem_map import (
|
|
479
|
+
area_for_path,
|
|
480
|
+
are_neighbors,
|
|
481
|
+
)
|
|
482
|
+
from devcouncil.utils.json_persist import read_json
|
|
483
|
+
|
|
484
|
+
map_path = self.project_root / ".devcouncil" / "repo_map.json"
|
|
485
|
+
if not map_path.is_file():
|
|
486
|
+
return None
|
|
487
|
+
loaded = read_json(map_path)
|
|
488
|
+
if not isinstance(loaded, dict):
|
|
489
|
+
return None
|
|
490
|
+
data = loaded
|
|
491
|
+
target_area = area_for_path(path, data)
|
|
492
|
+
if not target_area:
|
|
493
|
+
return None
|
|
494
|
+
planned_areas = {
|
|
495
|
+
area_for_path(pf.path.replace("\\", "/"), data)
|
|
496
|
+
for pf in task.planned_files
|
|
497
|
+
}
|
|
498
|
+
planned_areas.discard(None)
|
|
499
|
+
if not planned_areas:
|
|
500
|
+
return None
|
|
501
|
+
if target_area in planned_areas:
|
|
502
|
+
return PolicyDecision(
|
|
503
|
+
action="allow",
|
|
504
|
+
reason="File is in the same subsystem as a planned file.",
|
|
505
|
+
target=path,
|
|
506
|
+
task_id=task.id,
|
|
507
|
+
)
|
|
508
|
+
if any(
|
|
509
|
+
are_neighbors(target_area, planned_area, data)
|
|
510
|
+
for planned_area in planned_areas
|
|
511
|
+
):
|
|
512
|
+
return PolicyDecision(
|
|
513
|
+
action="allow",
|
|
514
|
+
reason="File is in a neighboring subsystem of a planned file.",
|
|
515
|
+
target=path,
|
|
516
|
+
task_id=task.id,
|
|
517
|
+
)
|
|
518
|
+
return PolicyDecision(
|
|
519
|
+
action="deny",
|
|
520
|
+
reason=(
|
|
521
|
+
f"Task {task.id} does not authorize changes to {path} "
|
|
522
|
+
f"(subsystem `{target_area}` is outside planned files and not a "
|
|
523
|
+
f"declared neighbor). Expand scope with `dev scope update`."
|
|
524
|
+
),
|
|
525
|
+
target=path,
|
|
526
|
+
task_id=task.id,
|
|
527
|
+
)
|
|
528
|
+
except Exception:
|
|
529
|
+
return None
|
|
530
|
+
|
|
336
531
|
def _matches_forbidden(self, path: str, task: Task) -> bool:
|
|
337
532
|
for forbidden in task.forbidden_changes:
|
|
338
533
|
pattern = forbidden.replace("\\", "/")
|