devcouncil 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -30
- package/package.json +6 -2
- package/packages/codeintel-grammars/hatch_build.py +43 -0
- package/packages/codeintel-grammars/pyproject.toml +16 -0
- package/packages/codeintel-grammars/src/devcouncil_codeintel_grammars/__init__.py +93 -0
- package/pyproject.toml +99 -4
- package/src/devcouncil/app/config.py +512 -20
- package/src/devcouncil/app/events.py +4 -23
- package/src/devcouncil/app/orchestrator.py +5 -0
- package/src/devcouncil/app/run_context.py +3 -3
- package/src/devcouncil/assets/__init__.py +4 -1
- package/src/devcouncil/assets/vendor/force-graph.min.js +5 -0
- package/src/devcouncil/campaign/__init__.py +71 -0
- package/src/devcouncil/campaign/bloom.py +137 -0
- package/src/devcouncil/campaign/dashboard.py +123 -0
- package/src/devcouncil/campaign/mailbox.py +305 -0
- package/src/devcouncil/campaign/notify.py +91 -0
- package/src/devcouncil/campaign/orchestrator.py +592 -0
- package/src/devcouncil/campaign/prompts/coordinator.md +29 -0
- package/src/devcouncil/campaign/prompts/director.md +21 -0
- package/src/devcouncil/campaign/prompts/protocol.md +46 -0
- package/src/devcouncil/campaign/prompts/reviewer.md +24 -0
- package/src/devcouncil/campaign/prompts/worker.md +24 -0
- package/src/devcouncil/campaign/roles.py +202 -0
- package/src/devcouncil/campaign/watcher.py +153 -0
- package/src/devcouncil/cli/commands/agents.py +24 -17
- package/src/devcouncil/cli/commands/artifacts.py +36 -27
- package/src/devcouncil/cli/commands/ast.py +12 -3
- package/src/devcouncil/cli/commands/baseline.py +21 -12
- package/src/devcouncil/cli/commands/boot.py +218 -0
- package/src/devcouncil/cli/commands/campaign.py +302 -0
- package/src/devcouncil/cli/commands/check.py +225 -12
- package/src/devcouncil/cli/commands/config.py +221 -74
- package/src/devcouncil/cli/commands/cost.py +137 -28
- package/src/devcouncil/cli/commands/dashboard.py +12 -4
- package/src/devcouncil/cli/commands/debug_cmd.py +249 -0
- package/src/devcouncil/cli/commands/design.py +27 -17
- package/src/devcouncil/cli/commands/doctor.py +790 -8
- package/src/devcouncil/cli/commands/evidence.py +41 -20
- package/src/devcouncil/cli/commands/export.py +73 -0
- package/src/devcouncil/cli/commands/gaps.py +175 -0
- package/src/devcouncil/cli/commands/gated_write.py +76 -0
- package/src/devcouncil/cli/commands/go.py +220 -68
- package/src/devcouncil/cli/commands/graph_cmd.py +1192 -0
- package/src/devcouncil/cli/commands/handoff.py +45 -34
- package/src/devcouncil/cli/commands/hook.py +630 -85
- package/src/devcouncil/cli/commands/init.py +89 -30
- package/src/devcouncil/cli/commands/integrate.py +296 -1385
- package/src/devcouncil/cli/commands/lease.py +120 -0
- package/src/devcouncil/cli/commands/logs.py +12 -5
- package/src/devcouncil/cli/commands/lsp.py +40 -5
- package/src/devcouncil/cli/commands/map.py +317 -74
- package/src/devcouncil/cli/commands/mcp_server.py +12 -2
- package/src/devcouncil/cli/commands/okf.py +44 -6
- package/src/devcouncil/cli/commands/plan.py +184 -69
- package/src/devcouncil/cli/commands/prompt.py +26 -17
- package/src/devcouncil/cli/commands/provenance.py +79 -0
- package/src/devcouncil/cli/commands/repair.py +60 -49
- package/src/devcouncil/cli/commands/report.py +148 -40
- package/src/devcouncil/cli/commands/requirements.py +104 -0
- package/src/devcouncil/cli/commands/reset_demo_state.py +13 -4
- package/src/devcouncil/cli/commands/rollback.py +46 -35
- package/src/devcouncil/cli/commands/run.py +173 -8
- package/src/devcouncil/cli/commands/runs.py +298 -68
- package/src/devcouncil/cli/commands/scaffold.py +33 -12
- package/src/devcouncil/cli/commands/semantic.py +29 -14
- package/src/devcouncil/cli/commands/setup.py +103 -93
- package/src/devcouncil/cli/commands/shell.py +51 -42
- package/src/devcouncil/cli/commands/show.py +56 -42
- package/src/devcouncil/cli/commands/skills.py +29 -20
- package/src/devcouncil/cli/commands/status.py +80 -67
- package/src/devcouncil/cli/commands/task_gate.py +295 -0
- package/src/devcouncil/cli/commands/tasks.py +248 -19
- package/src/devcouncil/cli/commands/trace.py +14 -8
- package/src/devcouncil/cli/commands/verify.py +33 -8
- package/src/devcouncil/cli/commands/version.py +14 -6
- package/src/devcouncil/cli/commands/watch.py +49 -30
- package/src/devcouncil/cli/commands/watch_fs.py +30 -19
- package/src/devcouncil/cli/commands/wiki.py +278 -0
- package/src/devcouncil/cli/main.py +58 -1
- package/src/devcouncil/codeintel/__init__.py +16 -0
- package/src/devcouncil/codeintel/build_control.py +429 -0
- package/src/devcouncil/codeintel/build_worker.py +78 -0
- package/src/devcouncil/codeintel/debug/__init__.py +17 -0
- package/src/devcouncil/codeintel/debug/broker.py +114 -0
- package/src/devcouncil/codeintel/debug/broker_client.py +61 -0
- package/src/devcouncil/codeintel/debug/consent.py +36 -0
- package/src/devcouncil/codeintel/debug/discovery.py +132 -0
- package/src/devcouncil/codeintel/debug/fingerprint.py +85 -0
- package/src/devcouncil/codeintel/debug/protocol.py +259 -0
- package/src/devcouncil/codeintel/debug/python_trace_runner.py +81 -0
- package/src/devcouncil/codeintel/debug/session.py +238 -0
- package/src/devcouncil/codeintel/debug/tracing.py +201 -0
- package/src/devcouncil/codeintel/languages/__init__.py +17 -0
- package/src/devcouncil/codeintel/languages/generic_extractor.py +236 -0
- package/src/devcouncil/codeintel/languages/registry.py +149 -0
- package/src/devcouncil/codeintel/languages/workers.py +245 -0
- package/src/devcouncil/codeintel/query/__init__.py +5 -0
- package/src/devcouncil/codeintel/query/engine.py +289 -0
- package/src/devcouncil/codeintel/resolution/__init__.py +6 -0
- package/src/devcouncil/codeintel/resolution/abstract_state.py +301 -0
- package/src/devcouncil/codeintel/resolution/frameworks/__init__.py +33 -0
- package/src/devcouncil/codeintel/resolution/frameworks/base.py +46 -0
- package/src/devcouncil/codeintel/resolution/frameworks/di.py +56 -0
- package/src/devcouncil/codeintel/resolution/frameworks/events.py +45 -0
- package/src/devcouncil/codeintel/resolution/frameworks/routes.py +88 -0
- package/src/devcouncil/codeintel/resolution/semantic.py +887 -0
- package/src/devcouncil/codeintel/service.py +104 -0
- package/src/devcouncil/codeintel/store/__init__.py +15 -0
- package/src/devcouncil/codeintel/store/sqlite.py +1565 -0
- package/src/devcouncil/codeintel/sync/__init__.py +19 -0
- package/src/devcouncil/codeintel/sync/coordinator.py +430 -0
- package/src/devcouncil/codeintel/sync/incremental.py +484 -0
- package/src/devcouncil/codeintel/sync/lease.py +96 -0
- package/src/devcouncil/codeintel/sync/scope.py +98 -0
- package/src/devcouncil/council/__init__.py +4 -0
- package/src/devcouncil/council/prompts/__init__.py +4 -0
- package/src/devcouncil/domain/checkpoint_refs.py +17 -0
- package/src/devcouncil/domain/evidence.py +1 -0
- package/src/devcouncil/domain/gap.py +10 -0
- package/src/devcouncil/domain/requirement.py +5 -1
- package/src/devcouncil/domain/task.py +43 -2
- package/src/devcouncil/execution/checkpoints.py +25 -31
- package/src/devcouncil/execution/context_builder.py +15 -44
- package/src/devcouncil/execution/fs_watcher.py +64 -0
- package/src/devcouncil/execution/gated_write.py +203 -0
- package/src/devcouncil/execution/handoff.py +2 -1
- package/src/devcouncil/execution/hook_policy.py +19 -5
- package/src/devcouncil/execution/lease_ops.py +177 -0
- package/src/devcouncil/execution/lease_validation.py +71 -0
- package/src/devcouncil/execution/patch.py +3 -0
- package/src/devcouncil/execution/permissions.py +1 -0
- package/src/devcouncil/execution/policy_engine.py +205 -10
- package/src/devcouncil/execution/prompt_builder.py +278 -33
- package/src/devcouncil/execution/run_trace.py +356 -0
- package/src/devcouncil/execution/shell_session.py +46 -5
- package/src/devcouncil/execution/stop_gate.py +746 -0
- package/src/devcouncil/execution/stop_gate_history.py +113 -0
- package/src/devcouncil/execution/stop_gate_state.py +54 -0
- package/src/devcouncil/execution/stop_gate_verify_cache.py +69 -0
- package/src/devcouncil/execution/task_gate_ops.py +590 -0
- package/src/devcouncil/execution/task_runner.py +19 -0
- package/src/devcouncil/executors/advisor_tool.py +315 -0
- package/src/devcouncil/executors/agent_registry.py +125 -17
- package/src/devcouncil/executors/claude_sdk.py +376 -0
- package/src/devcouncil/executors/coding_cli.py +724 -25
- package/src/devcouncil/executors/mini_swe.py +50 -8
- package/src/devcouncil/executors/native/agent.py +224 -19
- package/src/devcouncil/executors/openhands.py +50 -8
- package/src/devcouncil/executors/transient_retry.py +99 -0
- package/src/devcouncil/gating/checks/clean_git.py +5 -2
- package/src/devcouncil/gating/checks/planned_files_check.py +38 -11
- package/src/devcouncil/gating/checks/secret_scan_check.py +2 -2
- package/src/devcouncil/gating/policy.py +46 -2
- package/src/devcouncil/indexing/ast_matcher.py +41 -4
- package/src/devcouncil/indexing/graph/__init__.py +78 -0
- package/src/devcouncil/indexing/graph/api_routes.py +522 -0
- package/src/devcouncil/indexing/graph/build.py +862 -0
- package/src/devcouncil/indexing/graph/cache.py +329 -0
- package/src/devcouncil/indexing/graph/communities.py +28 -0
- package/src/devcouncil/indexing/graph/cypher.py +107 -0
- package/src/devcouncil/indexing/graph/embeddings.py +194 -0
- package/src/devcouncil/indexing/graph/export.py +381 -0
- package/src/devcouncil/indexing/graph/export_links.py +81 -0
- package/src/devcouncil/indexing/graph/extract_python.py +307 -0
- package/src/devcouncil/indexing/graph/extract_ts.py +1205 -0
- package/src/devcouncil/indexing/graph/intel.py +668 -0
- package/src/devcouncil/indexing/graph/liveness.py +992 -0
- package/src/devcouncil/indexing/graph/okf_export.py +65 -0
- package/src/devcouncil/indexing/graph/pdg/__init__.py +67 -0
- package/src/devcouncil/indexing/graph/pdg/build.py +11 -0
- package/src/devcouncil/indexing/graph/pdg/cdg.py +41 -0
- package/src/devcouncil/indexing/graph/pdg/cfg.py +199 -0
- package/src/devcouncil/indexing/graph/pdg/query.py +21 -0
- package/src/devcouncil/indexing/graph/pdg/reaching_def.py +126 -0
- package/src/devcouncil/indexing/graph/pdg/schema.py +253 -0
- package/src/devcouncil/indexing/graph/pdg/taint.py +154 -0
- package/src/devcouncil/indexing/graph/query.py +302 -0
- package/src/devcouncil/indexing/graph/resolve.py +1020 -0
- package/src/devcouncil/indexing/graph/schema.py +103 -0
- package/src/devcouncil/indexing/graph_index.py +20 -29
- package/src/devcouncil/indexing/lsp.py +57 -25
- package/src/devcouncil/indexing/lsp_client.py +577 -0
- package/src/devcouncil/indexing/map_artifacts.py +355 -0
- package/src/devcouncil/indexing/map_refresh.py +141 -0
- package/src/devcouncil/indexing/repo_mapper.py +1509 -138
- package/src/devcouncil/indexing/semantic_index.py +12 -6
- package/src/devcouncil/indexing/subsystem_map.py +163 -0
- package/src/devcouncil/indexing/ts_imports.py +343 -0
- package/src/devcouncil/indexing/viz.py +960 -0
- package/src/devcouncil/indexing/walk.py +52 -0
- package/src/devcouncil/indexing/wiring.py +1776 -0
- package/src/devcouncil/integrations/actions.py +27 -4
- package/src/devcouncil/integrations/check.py +211 -16
- package/src/devcouncil/integrations/claude_assets.py +209 -12
- package/src/devcouncil/integrations/clients/__init__.py +1 -0
- package/src/devcouncil/integrations/clients/aider.py +52 -0
- package/src/devcouncil/integrations/clients/antigravity.py +87 -0
- package/src/devcouncil/integrations/clients/claude.py +339 -0
- package/src/devcouncil/integrations/clients/codex.py +39 -0
- package/src/devcouncil/integrations/clients/common.py +332 -0
- package/src/devcouncil/integrations/clients/cursor.py +164 -0
- package/src/devcouncil/integrations/clients/gemini.py +49 -0
- package/src/devcouncil/integrations/clients/grok.py +105 -0
- package/src/devcouncil/integrations/clients/hooks.py +500 -0
- package/src/devcouncil/integrations/clients/opencode.py +96 -0
- package/src/devcouncil/integrations/clients/warp.py +75 -0
- package/src/devcouncil/integrations/code_review_graph.py +2 -2
- package/src/devcouncil/integrations/github.py +73 -7
- package/src/devcouncil/integrations/integration_cli.py +197 -0
- package/src/devcouncil/integrations/mcp/handlers/__init__.py +1 -0
- package/src/devcouncil/integrations/mcp/handlers/ast_lsp.py +77 -0
- package/src/devcouncil/integrations/mcp/handlers/checkout.py +50 -0
- package/src/devcouncil/integrations/mcp/handlers/cli_gate.py +43 -0
- package/src/devcouncil/integrations/mcp/handlers/codeintel.py +182 -0
- package/src/devcouncil/integrations/mcp/handlers/debug.py +236 -0
- package/src/devcouncil/integrations/mcp/handlers/evidence.py +70 -0
- package/src/devcouncil/integrations/mcp/handlers/git.py +99 -0
- package/src/devcouncil/integrations/mcp/handlers/graph.py +36 -0
- package/src/devcouncil/integrations/mcp/handlers/handoff.py +53 -0
- package/src/devcouncil/integrations/mcp/handlers/knowledge.py +30 -0
- package/src/devcouncil/integrations/mcp/handlers/lease.py +70 -0
- package/src/devcouncil/integrations/mcp/handlers/live.py +108 -0
- package/src/devcouncil/integrations/mcp/handlers/map.py +676 -0
- package/src/devcouncil/integrations/mcp/handlers/next_task.py +35 -0
- package/src/devcouncil/integrations/mcp/handlers/policy.py +80 -0
- package/src/devcouncil/integrations/mcp/handlers/prompts.py +168 -0
- package/src/devcouncil/integrations/mcp/handlers/provenance.py +101 -0
- package/src/devcouncil/integrations/mcp/handlers/read.py +74 -0
- package/src/devcouncil/integrations/mcp/handlers/router_cache.py +53 -0
- package/src/devcouncil/integrations/mcp/handlers/run.py +53 -0
- package/src/devcouncil/integrations/mcp/handlers/runs.py +84 -0
- package/src/devcouncil/integrations/mcp/handlers/scope.py +56 -0
- package/src/devcouncil/integrations/mcp/handlers/status.py +114 -0
- package/src/devcouncil/integrations/mcp/handlers/task.py +100 -0
- package/src/devcouncil/integrations/mcp/handlers/tool_specs.py +904 -0
- package/src/devcouncil/integrations/mcp/handlers/trace.py +65 -0
- package/src/devcouncil/integrations/mcp/handlers/verify.py +45 -0
- package/src/devcouncil/integrations/mcp/handlers/wiki.py +60 -0
- package/src/devcouncil/integrations/mcp/handlers/write.py +69 -0
- package/src/devcouncil/integrations/mcp/server.py +265 -2391
- package/src/devcouncil/integrations/mcp/util.py +303 -0
- package/src/devcouncil/integrations/setup.py +152 -0
- package/src/devcouncil/knowledge/fetch.py +4 -0
- package/src/devcouncil/knowledge/knowledge_select.py +38 -0
- package/src/devcouncil/knowledge/okf.py +2 -1
- package/src/devcouncil/knowledge/resource_discovery.py +40 -0
- package/src/devcouncil/knowledge/wiki.py +643 -0
- package/src/devcouncil/knowledge/wiki_read.py +87 -0
- package/src/devcouncil/live/cards.py +7 -7
- package/src/devcouncil/live/models.py +4 -1
- package/src/devcouncil/live/reviewer.py +90 -11
- package/src/devcouncil/live/signals.py +4 -2
- package/src/devcouncil/live/tasks.py +12 -3
- package/src/devcouncil/live/transcripts.py +69 -2
- package/src/devcouncil/llm/cache.py +5 -6
- package/src/devcouncil/llm/model_defaults.yaml +10 -10
- package/src/devcouncil/llm/provider.py +647 -73
- package/src/devcouncil/llm/router.py +271 -46
- package/src/devcouncil/llm/semantic_bridge.py +614 -0
- package/src/devcouncil/optimization/gepa_agent.py +6 -4
- package/src/devcouncil/optimization/skillopt.py +9 -5
- package/src/devcouncil/planning/arbiter_service.py +12 -3
- package/src/devcouncil/planning/correction_manifest.py +107 -10
- package/src/devcouncil/planning/plan_difficulty.py +69 -0
- package/src/devcouncil/planning/plan_service.py +5 -2
- package/src/devcouncil/planning/planned_files_reconcile.py +191 -0
- package/src/devcouncil/planning/prompt_enhancer_service.py +6 -5
- package/src/devcouncil/planning/question_conversion.py +56 -0
- package/src/devcouncil/planning/spec_service.py +9 -3
- package/src/devcouncil/repo/ci_scaffold.py +197 -1
- package/src/devcouncil/repo/gitignore.py +1 -2
- package/src/devcouncil/reporting/evidence_export.py +124 -0
- package/src/devcouncil/reporting/evidence_html.py +210 -0
- package/src/devcouncil/reporting/json_report.py +16 -12
- package/src/devcouncil/reporting/markdown_report.py +38 -9
- package/src/devcouncil/reporting/mcp_resources.py +142 -0
- package/src/devcouncil/reporting/report_builder.py +40 -4
- package/src/devcouncil/reporting/task_provenance.py +42 -0
- package/src/devcouncil/reporting/verdict.py +75 -0
- package/src/devcouncil/skills/library/README.md +1 -0
- package/src/devcouncil/skills/library/devcouncil-hero-loop.md +109 -0
- package/src/devcouncil/skills/library/devcouncil-verification.md +109 -0
- package/src/devcouncil/skills/library/devcouncil.md +93 -0
- package/src/devcouncil/skills/registry.py +43 -12
- package/src/devcouncil/storage/db.py +57 -11
- package/src/devcouncil/storage/models.py +6 -0
- package/src/devcouncil/storage/native.py +5 -3
- package/src/devcouncil/storage/repositories.py +50 -18
- package/src/devcouncil/telemetry/context.py +28 -0
- package/src/devcouncil/telemetry/cost.py +4 -5
- package/src/devcouncil/telemetry/logging_setup.py +78 -11
- package/src/devcouncil/telemetry/model_pricing.yaml +7 -0
- package/src/devcouncil/telemetry/stages.py +27 -2
- package/src/devcouncil/telemetry/tracker.py +50 -13
- package/src/devcouncil/ui/dashboard.py +120 -8
- package/src/devcouncil/utils/fsio.py +58 -0
- package/src/devcouncil/utils/git_snapshot.py +112 -0
- package/src/devcouncil/utils/json_persist.py +53 -0
- package/src/devcouncil/utils/proc.py +89 -0
- package/src/devcouncil/verification/acceptance_compiler.py +36 -13
- package/src/devcouncil/verification/ad_hoc_check.py +95 -3
- package/src/devcouncil/verification/checks/__init__.py +41 -0
- package/src/devcouncil/verification/checks/acceptance.py +39 -0
- package/src/devcouncil/verification/checks/acceptance_corpus.py +194 -0
- package/src/devcouncil/verification/checks/acceptance_evidence.py +239 -0
- package/src/devcouncil/verification/checks/command_evidence.py +148 -0
- package/src/devcouncil/verification/checks/compiled_acceptance.py +179 -0
- package/src/devcouncil/verification/checks/corpus_stale.py +124 -0
- package/src/devcouncil/verification/checks/corpus_verification.py +9 -0
- package/src/devcouncil/verification/checks/dead_symbols.py +360 -0
- package/src/devcouncil/verification/checks/diff_coverage_gate.py +101 -0
- package/src/devcouncil/verification/checks/doc_code_ref.py +79 -0
- package/src/devcouncil/verification/checks/liveness_ratchet.py +336 -0
- package/src/devcouncil/verification/checks/orphan_diff.py +104 -0
- package/src/devcouncil/verification/checks/planned_files.py +98 -0
- package/src/devcouncil/verification/checks/semantic_diff.py +241 -0
- package/src/devcouncil/verification/checks/stale_map.py +80 -0
- package/src/devcouncil/verification/checks/stub_scan.py +71 -0
- package/src/devcouncil/verification/checks/subsystem_boundary.py +103 -0
- package/src/devcouncil/verification/checks/wiring.py +216 -0
- package/src/devcouncil/verification/claims/__init__.py +23 -0
- package/src/devcouncil/verification/claims/checks.py +395 -0
- package/src/devcouncil/verification/claims/mapper.py +168 -0
- package/src/devcouncil/verification/claims/models.py +39 -0
- package/src/devcouncil/verification/claims/transcript.py +92 -0
- package/src/devcouncil/verification/claims/verdict.py +88 -0
- package/src/devcouncil/verification/command_evidence.py +170 -0
- package/src/devcouncil/verification/command_malformation.py +147 -0
- package/src/devcouncil/verification/command_runner.py +164 -0
- package/src/devcouncil/verification/coverage_measurement.py +292 -0
- package/src/devcouncil/verification/diff_coverage.py +151 -0
- package/src/devcouncil/verification/difficulty.py +296 -0
- package/src/devcouncil/verification/effort_heuristics.py +178 -0
- package/src/devcouncil/verification/gap_ids.py +63 -0
- package/src/devcouncil/verification/gate_cache.py +194 -0
- package/src/devcouncil/verification/gate_selector.py +344 -0
- package/src/devcouncil/verification/git_diff_fallback.py +272 -0
- package/src/devcouncil/verification/implementation_reviewer.py +13 -0
- package/src/devcouncil/verification/incremental_check.py +241 -0
- package/src/devcouncil/verification/next_actions.py +60 -1
- package/src/devcouncil/verification/rigor_analytics.py +130 -0
- package/src/devcouncil/verification/sandbox.py +38 -11
- package/src/devcouncil/verification/stub_detector.py +369 -0
- package/src/devcouncil/verification/test_resolver.py +67 -1
- package/src/devcouncil/verification/verifier.py +137 -1666
- package/src/devcouncil/verification/verify_orchestration.py +610 -0
- package/src/devcouncil/verification/verify_setup.py +176 -0
- package/src/devcouncil/verification/wiki_refresh.py +208 -0
- package/src/semantic_layer/__init__.py +58 -0
- package/src/semantic_layer/benchmark.py +75 -0
- package/src/semantic_layer/cache.py +290 -0
- package/src/semantic_layer/compressor.py +137 -0
- package/src/semantic_layer/config.py +75 -0
- package/src/semantic_layer/embeddings.py +69 -0
- package/src/semantic_layer/llm_backends.py +99 -0
- package/src/semantic_layer/pipeline.py +111 -0
- package/src/semantic_layer/router.py +128 -0
- package/src/semantic_layer/tuner.py +72 -0
- package/uv.lock +973 -9
- package/src/devcouncil/artifacts/migrations.py +0 -20
- package/src/devcouncil/artifacts/schemas.py +0 -23
- package/src/devcouncil/artifacts/serializer.py +0 -21
- package/src/devcouncil/integrations/gitnexus.py +0 -70
- package/src/devcouncil/integrations/graphify.py +0 -34
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""`dev provenance` and `dev resource` — audit trail and MCP corpus reads."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from devcouncil.reporting.mcp_resources import list_mcp_resource_uris, read_mcp_resource
|
|
11
|
+
from devcouncil.reporting.task_provenance import task_provenance_payload
|
|
12
|
+
from devcouncil.telemetry.logging_setup import set_log_dir
|
|
13
|
+
from devcouncil.telemetry.stages import log_stage, log_step
|
|
14
|
+
from devcouncil.utils.json_persist import dump_json
|
|
15
|
+
|
|
16
|
+
resource_app = typer.Typer(help="Read MCP corpus resources through the CLI service layer.")
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def provenance(
|
|
21
|
+
task_id: str = typer.Argument(..., help="Task ID to inspect."),
|
|
22
|
+
json_format: bool = typer.Option(False, "--json", help="Output machine-readable JSON."),
|
|
23
|
+
project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
|
|
24
|
+
) -> None:
|
|
25
|
+
"""Show file changes, verification runs, and diff-coverage evidence for a task."""
|
|
26
|
+
root = project_root.expanduser().resolve()
|
|
27
|
+
set_log_dir(root)
|
|
28
|
+
logger.info("dev provenance: task=%s json=%s", task_id, json_format)
|
|
29
|
+
|
|
30
|
+
with log_stage("provenance", project_root=root, task_id=task_id):
|
|
31
|
+
payload = task_provenance_payload(root, task_id)
|
|
32
|
+
if json_format:
|
|
33
|
+
typer.echo(dump_json(payload, indent=2))
|
|
34
|
+
log_step("provenance/complete", project_root=root, task_id=task_id, trace=True)
|
|
35
|
+
if not payload.get("ok", True):
|
|
36
|
+
raise typer.Exit(code=1)
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
if not payload.get("ok", True):
|
|
40
|
+
typer.echo(payload.get("error", "provenance unavailable"))
|
|
41
|
+
raise typer.Exit(code=1)
|
|
42
|
+
|
|
43
|
+
typer.echo(
|
|
44
|
+
f"Task {task_id}: {len(payload.get('file_changes', []))} file change(s), "
|
|
45
|
+
f"{len(payload.get('verification_runs', []))} verification run(s), "
|
|
46
|
+
f"{len(payload.get('diff_coverage', []))} diff-coverage record(s)."
|
|
47
|
+
)
|
|
48
|
+
log_step("provenance/complete", project_root=root, task_id=task_id, trace=True)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@resource_app.command("list")
|
|
52
|
+
def resource_list(
|
|
53
|
+
json_format: bool = typer.Option(False, "--json", help="Output machine-readable JSON."),
|
|
54
|
+
project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
|
|
55
|
+
) -> None:
|
|
56
|
+
"""List browsable MCP corpus resource URIs."""
|
|
57
|
+
root = project_root.expanduser().resolve()
|
|
58
|
+
set_log_dir(root)
|
|
59
|
+
resources = list_mcp_resource_uris(root)
|
|
60
|
+
if json_format:
|
|
61
|
+
typer.echo(dump_json({"resources": resources}, indent=2))
|
|
62
|
+
return
|
|
63
|
+
for item in resources:
|
|
64
|
+
typer.echo(f"{item['uri']} ({item['mimeType']}) {item['name']}")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@resource_app.command("read")
|
|
68
|
+
def resource_read(
|
|
69
|
+
uri: str = typer.Argument(..., help="Resource URI, e.g. devcouncil://tasks"),
|
|
70
|
+
project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
|
|
71
|
+
) -> None:
|
|
72
|
+
"""Read one MCP corpus resource (stdout body)."""
|
|
73
|
+
root = project_root.expanduser().resolve()
|
|
74
|
+
set_log_dir(root)
|
|
75
|
+
try:
|
|
76
|
+
typer.echo(read_mcp_resource(root, uri), nl=False)
|
|
77
|
+
except ValueError as exc:
|
|
78
|
+
typer.echo(str(exc))
|
|
79
|
+
raise typer.Exit(code=1) from exc
|
|
@@ -8,6 +8,7 @@ from devcouncil.llm.provider import create_provider, validate_model_provider
|
|
|
8
8
|
from devcouncil.llm.router import ModelRouter
|
|
9
9
|
from devcouncil.app.config import load_config, get_api_key
|
|
10
10
|
from devcouncil.cli.commands.init import initialize_project
|
|
11
|
+
from devcouncil.telemetry.stages import log_stage, log_step
|
|
11
12
|
import asyncio
|
|
12
13
|
import logging
|
|
13
14
|
from pathlib import Path
|
|
@@ -26,63 +27,73 @@ async def run_repair_flow(project_root: Path = Path(".")):
|
|
|
26
27
|
console.print("[red]DevCouncil state is unavailable in this directory.[/red]")
|
|
27
28
|
return
|
|
28
29
|
|
|
29
|
-
with
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
with log_stage("repair", project_root=root):
|
|
31
|
+
log_step("repair/1: loading blocking gaps", project_root=root)
|
|
32
|
+
with db.get_session() as session:
|
|
33
|
+
gap_repo = GapRepository(session)
|
|
34
|
+
task_repo = TaskRepository(session)
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
all_gaps = gap_repo.get_all()
|
|
37
|
+
blocking_gaps = [g for g in all_gaps if g.blocking]
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
if not blocking_gaps:
|
|
40
|
+
logger.info("dev repair: no blocking gaps; nothing to repair")
|
|
41
|
+
console.print("[green]No blocking gaps found. Nothing to repair![/green]")
|
|
42
|
+
return
|
|
40
43
|
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
logger.info("dev repair: %d blocking gap(s) across %d task(s)", len(blocking_gaps), len({g.task_id for g in blocking_gaps if g.task_id}))
|
|
45
|
+
console.print(f"Found [bold]{len(blocking_gaps)}[/bold] blocking gaps. Orchestrating repair plan...")
|
|
43
46
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
# Build minimal context for repair.
|
|
64
|
-
project_context = context_builder.get_structure_summary()
|
|
47
|
+
# Load router when credentials are available. Correction manifests have a
|
|
48
|
+
# deterministic fallback path, so missing model credentials must not block
|
|
49
|
+
# repair artifact generation.
|
|
50
|
+
repair_service = None
|
|
51
|
+
config = None
|
|
52
|
+
try:
|
|
53
|
+
config = load_config(root)
|
|
54
|
+
validate_model_provider(config.models.provider)
|
|
55
|
+
api_key = get_api_key(config.models.provider, root)
|
|
56
|
+
except (FileNotFoundError, ValueError) as e:
|
|
57
|
+
logger.warning("dev repair: no LLM provider (%s); using deterministic manifest fallback", e)
|
|
58
|
+
console.print(f"[yellow]{e}[/yellow]")
|
|
59
|
+
else:
|
|
60
|
+
provider = create_provider(config.models.provider, api_key, project_root=root, provider_prefs=config.provider)
|
|
61
|
+
role_config = {name: role.model_dump() for name, role in config.models.roles.items()}
|
|
62
|
+
router = ModelRouter(provider, role_config, project_root=root)
|
|
63
|
+
repair_service = RepairService(router)
|
|
64
|
+
context_builder = ContextBuilder(root)
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
# Build minimal context for repair.
|
|
67
|
+
project_context = context_builder.get_structure_summary()
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
for scoped_task_id in task_ids:
|
|
70
|
-
if scoped_task_id:
|
|
71
|
-
path = write_correction_manifest(root, scoped_task_id, repair_service=repair_service, config=config)
|
|
72
|
-
if path:
|
|
73
|
-
console.print(f" - Wrote correction manifest [dim]{path}[/dim]")
|
|
69
|
+
from devcouncil.planning.correction_manifest import write_correction_manifest
|
|
74
70
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
console.print(f" - Created intelligent repair task [bold]{task.id}[/bold]: {task.title}")
|
|
71
|
+
task_ids = {gap.task_id for gap in blocking_gaps if gap.task_id}
|
|
72
|
+
log_step("repair/2: writing correction manifests", project_root=root, task_count=len(task_ids))
|
|
73
|
+
for scoped_task_id in task_ids:
|
|
74
|
+
if scoped_task_id:
|
|
75
|
+
path = write_correction_manifest(root, scoped_task_id, repair_service=repair_service, config=config)
|
|
76
|
+
if path:
|
|
77
|
+
console.print(f" - Wrote correction manifest [dim]{path}[/dim]")
|
|
83
78
|
|
|
84
|
-
|
|
85
|
-
|
|
79
|
+
repair_count = 0
|
|
80
|
+
if repair_service is not None:
|
|
81
|
+
log_step("repair/3: generating LLM repair plan", project_root=root)
|
|
82
|
+
repair_output = await repair_service.generate_repair_plan(blocking_gaps, str(project_context))
|
|
83
|
+
for task in repair_output.suggested_tasks:
|
|
84
|
+
task.id = f"REPAIR-{task.id}"
|
|
85
|
+
task_repo.save(task)
|
|
86
|
+
repair_count += 1
|
|
87
|
+
console.print(f" - Created intelligent repair task [bold]{task.id}[/bold]: {task.title}")
|
|
88
|
+
|
|
89
|
+
log_step(
|
|
90
|
+
"repair/complete",
|
|
91
|
+
project_root=root,
|
|
92
|
+
repair_tasks=repair_count,
|
|
93
|
+
trace=True,
|
|
94
|
+
)
|
|
95
|
+
logger.info("dev repair complete: generated %d repair task(s)", repair_count)
|
|
96
|
+
console.print(f"\n[green]Successfully generated {repair_count} repair tasks.[/green]")
|
|
86
97
|
|
|
87
98
|
@app.callback(invoke_without_command=True)
|
|
88
99
|
def repair(
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
from devcouncil.utils.json_persist import dump_json
|
|
2
|
+
from dataclasses import asdict
|
|
3
|
+
|
|
1
4
|
import typer
|
|
2
5
|
from rich.console import Console
|
|
3
6
|
from rich.markdown import Markdown
|
|
@@ -8,15 +11,18 @@ from devcouncil.integrations.github import GitHubIntegration
|
|
|
8
11
|
from devcouncil.integrations.pr_comments import GitHubPRCommenter, GitLabMRCommenter, build_pr_comment_body
|
|
9
12
|
from devcouncil.artifacts.graph import ArtifactGraph
|
|
10
13
|
from devcouncil.telemetry.traces import TraceLogger
|
|
14
|
+
from devcouncil.telemetry.stages import log_stage, log_step
|
|
11
15
|
from devcouncil.cli.commands.init import initialize_project
|
|
12
16
|
from devcouncil.live.summary import live_review_summary
|
|
13
17
|
import asyncio
|
|
18
|
+
import logging
|
|
14
19
|
import os
|
|
15
|
-
import subprocess
|
|
16
20
|
from pathlib import Path
|
|
21
|
+
from typing import Optional
|
|
17
22
|
|
|
18
23
|
app = typer.Typer()
|
|
19
24
|
console = Console()
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
20
26
|
|
|
21
27
|
async def run_github_report(graph: ArtifactGraph, project_root: Path):
|
|
22
28
|
token = os.environ.get("GITHUB_TOKEN")
|
|
@@ -27,8 +33,11 @@ async def run_github_report(graph: ArtifactGraph, project_root: Path):
|
|
|
27
33
|
return
|
|
28
34
|
|
|
29
35
|
try:
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
sha = os.environ.get("GITHUB_SHA", "").strip()
|
|
37
|
+
if not sha:
|
|
38
|
+
from devcouncil.utils.proc import git_output
|
|
39
|
+
|
|
40
|
+
sha = git_output(["rev-parse", "HEAD"], cwd=project_root).strip()
|
|
32
41
|
integration = GitHubIntegration(token, repo, sha)
|
|
33
42
|
await integration.report_verification(graph)
|
|
34
43
|
console.print(f"[green]Successfully reported to GitHub PR Checks for {repo} at {sha[:7]}[/green]")
|
|
@@ -78,6 +87,17 @@ def report(
|
|
|
78
87
|
github: bool = typer.Option(False, "--github", help="Post report to GitHub PR Checks"),
|
|
79
88
|
github_pr_comment: bool = typer.Option(False, "--github-pr-comment", help="Post report as a GitHub PR comment"),
|
|
80
89
|
gitlab_pr_comment: bool = typer.Option(False, "--gitlab-pr-comment", help="Post report as a GitLab merge request comment"),
|
|
90
|
+
evidence_json: Optional[Path] = typer.Option(
|
|
91
|
+
None,
|
|
92
|
+
"--evidence-json",
|
|
93
|
+
help="Write the requirement→task→diff→evidence graph as reviewer-readable JSON to this path "
|
|
94
|
+
"(e.g. for a CI/PR artifact).",
|
|
95
|
+
),
|
|
96
|
+
evidence_html: Optional[Path] = typer.Option(
|
|
97
|
+
None,
|
|
98
|
+
"--evidence-html",
|
|
99
|
+
help="Write a self-contained HTML evidence report (AC table + task/diff links) to this path.",
|
|
100
|
+
),
|
|
81
101
|
fail_on_blocking: bool = typer.Option(
|
|
82
102
|
False,
|
|
83
103
|
"--fail-on-blocking",
|
|
@@ -91,47 +111,135 @@ def report(
|
|
|
91
111
|
if ctx.invoked_subcommand is not None:
|
|
92
112
|
return
|
|
93
113
|
|
|
114
|
+
# Direct Python calls (e.g. from `dev go`) bypass Typer, leaving OptionInfo
|
|
115
|
+
# defaults in place of real values. Normalize them so `is not None` and
|
|
116
|
+
# truthiness checks behave as they do under CLI invocation.
|
|
117
|
+
if not isinstance(evidence_json, (Path, type(None))):
|
|
118
|
+
evidence_json = None
|
|
119
|
+
if not isinstance(evidence_html, (Path, type(None))):
|
|
120
|
+
evidence_html = None
|
|
121
|
+
if not isinstance(fail_on_blocking, bool):
|
|
122
|
+
fail_on_blocking = False
|
|
123
|
+
|
|
94
124
|
root = project_root.expanduser().resolve()
|
|
125
|
+
from devcouncil.telemetry.logging_setup import set_log_dir
|
|
126
|
+
set_log_dir(root)
|
|
127
|
+
logger.info("dev report: json=%s planning_only=%s", json_format, planning_only)
|
|
95
128
|
initialize_project(root, quiet=True)
|
|
96
129
|
db = get_db(root)
|
|
97
130
|
if not db:
|
|
98
131
|
console.print("[red]DevCouncil state is unavailable in this directory.[/red]")
|
|
99
132
|
raise typer.Exit(code=1)
|
|
100
133
|
|
|
101
|
-
with
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
"
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
134
|
+
with log_stage("report", project_root=root, planning_only=planning_only):
|
|
135
|
+
log_step("report/1: loading artifact graph", project_root=root)
|
|
136
|
+
with db.get_session() as session:
|
|
137
|
+
graph_repo = ArtifactGraphRepository(session)
|
|
138
|
+
graph = graph_repo.load_graph()
|
|
139
|
+
live_review = live_review_summary(root)
|
|
140
|
+
TraceLogger(root).log_event(
|
|
141
|
+
"report_generated",
|
|
142
|
+
{
|
|
143
|
+
"json": json_format,
|
|
144
|
+
"github": github,
|
|
145
|
+
"github_pr_comment": github_pr_comment,
|
|
146
|
+
"gitlab_pr_comment": gitlab_pr_comment,
|
|
147
|
+
"evidence_json": evidence_json is not None,
|
|
148
|
+
"evidence_html": evidence_html is not None,
|
|
149
|
+
"planning_only": planning_only,
|
|
150
|
+
},
|
|
151
|
+
summary="Generated DevCouncil report",
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
if github:
|
|
155
|
+
log_step("report/2: posting to GitHub checks", project_root=root)
|
|
156
|
+
asyncio.run(run_github_report(graph, root))
|
|
157
|
+
if fail_on_blocking and graph.blocking_gaps():
|
|
158
|
+
raise typer.Exit(code=1)
|
|
159
|
+
return
|
|
160
|
+
|
|
161
|
+
if github_pr_comment:
|
|
162
|
+
log_step("report/2: posting GitHub PR comment", project_root=root)
|
|
163
|
+
asyncio.run(run_github_pr_comment(graph, live_review=live_review))
|
|
164
|
+
if fail_on_blocking and graph.blocking_gaps():
|
|
165
|
+
raise typer.Exit(code=1)
|
|
166
|
+
return
|
|
167
|
+
|
|
168
|
+
if gitlab_pr_comment:
|
|
169
|
+
log_step("report/2: posting GitLab MR comment", project_root=root)
|
|
170
|
+
asyncio.run(run_gitlab_mr_comment(graph, live_review=live_review))
|
|
171
|
+
if fail_on_blocking and graph.blocking_gaps():
|
|
172
|
+
raise typer.Exit(code=1)
|
|
173
|
+
return
|
|
174
|
+
|
|
175
|
+
if evidence_json is not None:
|
|
176
|
+
log_step("report/2: writing evidence-export JSON", project_root=root)
|
|
177
|
+
output_path = evidence_json.expanduser()
|
|
178
|
+
try:
|
|
179
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
180
|
+
output_path.write_text(
|
|
181
|
+
ReportBuilder.build_evidence_export(graph, live_review=live_review) + "\n",
|
|
182
|
+
encoding="utf-8",
|
|
183
|
+
)
|
|
184
|
+
except OSError as exc:
|
|
185
|
+
console.print(f"[red]Failed to write evidence export to {output_path}: {exc}[/red]")
|
|
186
|
+
raise typer.Exit(code=1) from exc
|
|
187
|
+
console.print(f"[green]Wrote evidence export to {output_path}[/green]")
|
|
188
|
+
if fail_on_blocking and graph.blocking_gaps():
|
|
189
|
+
raise typer.Exit(code=1)
|
|
190
|
+
return
|
|
191
|
+
|
|
192
|
+
if evidence_html is not None:
|
|
193
|
+
log_step("report/2: writing evidence-export HTML", project_root=root)
|
|
194
|
+
output_path = evidence_html.expanduser()
|
|
195
|
+
try:
|
|
196
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
197
|
+
output_path.write_text(
|
|
198
|
+
ReportBuilder.build_evidence_html(graph, live_review=live_review) + "\n",
|
|
199
|
+
encoding="utf-8",
|
|
200
|
+
)
|
|
201
|
+
except OSError as exc:
|
|
202
|
+
console.print(f"[red]Failed to write evidence HTML to {output_path}: {exc}[/red]")
|
|
203
|
+
raise typer.Exit(code=1) from exc
|
|
204
|
+
console.print(f"[green]Wrote evidence HTML to {output_path}[/green]")
|
|
205
|
+
if fail_on_blocking and graph.blocking_gaps():
|
|
206
|
+
raise typer.Exit(code=1)
|
|
207
|
+
return
|
|
208
|
+
|
|
209
|
+
log_step(
|
|
210
|
+
"report/2: rendering output",
|
|
211
|
+
project_root=root,
|
|
212
|
+
json_format=json_format,
|
|
213
|
+
trace=True,
|
|
214
|
+
)
|
|
215
|
+
if json_format:
|
|
216
|
+
output = ReportBuilder.build_json(graph, live_review=live_review)
|
|
217
|
+
typer.echo(output)
|
|
218
|
+
else:
|
|
219
|
+
output = ReportBuilder.build_markdown(graph, live_review=live_review)
|
|
220
|
+
console.print(Markdown(output))
|
|
221
|
+
|
|
222
|
+
if fail_on_blocking and graph.blocking_gaps():
|
|
223
|
+
raise typer.Exit(code=1)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@app.command("rigor")
|
|
227
|
+
def rigor_report(
|
|
228
|
+
json_format: bool = typer.Option(False, "--json", help="Output machine-readable JSON."),
|
|
229
|
+
project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
|
|
230
|
+
):
|
|
231
|
+
"""Aggregate rigor-gap patterns across persisted verification runs.
|
|
232
|
+
|
|
233
|
+
Surfaces recurring stub/effort failures and repair-loop stats so teams can
|
|
234
|
+
tune ``verification.rigor`` thresholds from evidence rather than guesses.
|
|
235
|
+
"""
|
|
236
|
+
from devcouncil.verification.rigor_analytics import build_rigor_report
|
|
237
|
+
|
|
238
|
+
root = project_root.expanduser().resolve()
|
|
239
|
+
initialize_project(root, quiet=True)
|
|
240
|
+
report = build_rigor_report(root)
|
|
241
|
+
|
|
242
|
+
if json_format:
|
|
243
|
+
typer.echo(dump_json(asdict(report), indent=2))
|
|
244
|
+
else:
|
|
245
|
+
console.print(Markdown(report.to_markdown()))
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from devcouncil.utils.json_persist import dump_json
|
|
2
|
+
import logging
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.table import Table
|
|
8
|
+
|
|
9
|
+
from devcouncil.cli.commands.init import initialize_project
|
|
10
|
+
from devcouncil.domain.task import Task
|
|
11
|
+
from devcouncil.storage.db import get_db
|
|
12
|
+
from devcouncil.storage.repositories import RequirementRepository, TaskRepository
|
|
13
|
+
from devcouncil.telemetry.stages import log_stage, log_step
|
|
14
|
+
|
|
15
|
+
console = Console()
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _derive_requirement_status(req_id: str, tasks: list[Task]) -> str:
|
|
20
|
+
linked = [task for task in tasks if req_id in task.requirement_ids]
|
|
21
|
+
if not linked:
|
|
22
|
+
return "unmapped"
|
|
23
|
+
statuses = {task.status for task in linked}
|
|
24
|
+
if "blocked" in statuses:
|
|
25
|
+
return "blocked"
|
|
26
|
+
if statuses <= {"verified", "done"}:
|
|
27
|
+
return "verified"
|
|
28
|
+
if "running" in statuses:
|
|
29
|
+
return "in_progress"
|
|
30
|
+
return "planned"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _requirements_payload(project_root: Path) -> dict:
|
|
34
|
+
initialize_project(project_root, quiet=True)
|
|
35
|
+
db = get_db(project_root)
|
|
36
|
+
if not db:
|
|
37
|
+
return {"initialized": False, "requirements": []}
|
|
38
|
+
|
|
39
|
+
with db.get_session() as session:
|
|
40
|
+
requirements = RequirementRepository(session).get_all()
|
|
41
|
+
tasks = TaskRepository(session).get_all()
|
|
42
|
+
rows = []
|
|
43
|
+
for req in requirements:
|
|
44
|
+
linked_count = sum(1 for task in tasks if req.id in task.requirement_ids)
|
|
45
|
+
rows.append(
|
|
46
|
+
{
|
|
47
|
+
"id": req.id,
|
|
48
|
+
"title": req.title,
|
|
49
|
+
"priority": req.priority,
|
|
50
|
+
"status": _derive_requirement_status(req.id, tasks),
|
|
51
|
+
"linked_task_count": linked_count,
|
|
52
|
+
"requirement": req.model_dump(),
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
rows.sort(key=lambda row: (row["priority"], row["id"]))
|
|
56
|
+
return {
|
|
57
|
+
"initialized": True,
|
|
58
|
+
"requirements": rows,
|
|
59
|
+
"total_count": len(rows),
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def requirements(
|
|
64
|
+
json_format: bool = typer.Option(False, "--json", help="Output machine-readable JSON."),
|
|
65
|
+
project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
|
|
66
|
+
):
|
|
67
|
+
"""List requirements with priority, status, and linked task counts."""
|
|
68
|
+
root = project_root.expanduser().resolve()
|
|
69
|
+
from devcouncil.telemetry.logging_setup import set_log_dir
|
|
70
|
+
|
|
71
|
+
set_log_dir(root)
|
|
72
|
+
logger.info("dev requirements: json=%s", json_format)
|
|
73
|
+
|
|
74
|
+
with log_stage("requirements", project_root=root):
|
|
75
|
+
log_step("requirements/1: loading requirements", project_root=root, trace=True)
|
|
76
|
+
payload = _requirements_payload(root)
|
|
77
|
+
|
|
78
|
+
if json_format:
|
|
79
|
+
typer.echo(dump_json(payload, indent=2))
|
|
80
|
+
log_step("requirements/complete", project_root=root, trace=True)
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
if not payload["initialized"]:
|
|
84
|
+
console.print("[yellow]DevCouncil state is not available in this directory.[/yellow]")
|
|
85
|
+
log_step("requirements/complete", project_root=root, trace=True)
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
table = Table(title="DevCouncil Requirements")
|
|
89
|
+
table.add_column("ID", style="cyan", no_wrap=True)
|
|
90
|
+
table.add_column("Priority")
|
|
91
|
+
table.add_column("Status", style="magenta")
|
|
92
|
+
table.add_column("Linked Tasks", justify="right")
|
|
93
|
+
|
|
94
|
+
for row in payload["requirements"]:
|
|
95
|
+
table.add_row(
|
|
96
|
+
row["id"],
|
|
97
|
+
row["priority"],
|
|
98
|
+
row["status"],
|
|
99
|
+
str(row["linked_task_count"]),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
console.print(table)
|
|
103
|
+
console.print(f"\n[bold]{payload['total_count']}[/bold] requirement(s)")
|
|
104
|
+
log_step("requirements/complete", project_root=root, trace=True)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import typer
|
|
2
3
|
from rich.console import Console
|
|
3
4
|
from sqlmodel import delete
|
|
@@ -6,8 +7,10 @@ from pathlib import Path
|
|
|
6
7
|
from devcouncil.storage.db import get_db
|
|
7
8
|
from devcouncil.storage.models import EvidenceModel, GapModel, RequirementModel, TaskModel
|
|
8
9
|
from devcouncil.cli.commands.init import initialize_project
|
|
10
|
+
from devcouncil.telemetry.stages import log_stage, log_step
|
|
9
11
|
|
|
10
12
|
console = Console()
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
11
14
|
|
|
12
15
|
|
|
13
16
|
def reset_demo_state(
|
|
@@ -20,14 +23,20 @@ def reset_demo_state(
|
|
|
20
23
|
raise typer.Exit(code=1)
|
|
21
24
|
|
|
22
25
|
root = project_root.expanduser().resolve()
|
|
26
|
+
from devcouncil.telemetry.logging_setup import set_log_dir
|
|
27
|
+
set_log_dir(root)
|
|
28
|
+
logger.info("dev reset-demo-state")
|
|
23
29
|
initialize_project(root, quiet=True)
|
|
24
30
|
db = get_db(root)
|
|
25
31
|
if not db:
|
|
26
32
|
console.print("[red]DevCouncil state is unavailable in this directory.[/red]")
|
|
27
33
|
raise typer.Exit(code=1)
|
|
28
34
|
|
|
29
|
-
with
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
with log_stage("reset_demo_state", project_root=root):
|
|
36
|
+
log_step("reset_demo_state/1: clearing planning artifacts", project_root=root, trace=True)
|
|
37
|
+
with db.get_session() as session:
|
|
38
|
+
for model in (EvidenceModel, GapModel, TaskModel, RequirementModel):
|
|
39
|
+
session.exec(delete(model))
|
|
32
40
|
|
|
33
|
-
|
|
41
|
+
console.print("[green]Cleared requirements, tasks, gaps, and evidence from local state.[/green]")
|
|
42
|
+
log_step("reset_demo_state/complete", project_root=root, trace=True)
|