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
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import ast
|
|
2
|
-
import json
|
|
3
2
|
import logging
|
|
4
3
|
import re
|
|
5
4
|
from pathlib import Path
|
|
@@ -8,6 +7,7 @@ from typing import List
|
|
|
8
7
|
from devcouncil.domain.requirement import Requirement
|
|
9
8
|
from devcouncil.domain.task import Task
|
|
10
9
|
from devcouncil.integrations.code_review_graph import CodeReviewGraphAdapter
|
|
10
|
+
from devcouncil.utils.json_persist import read_json
|
|
11
11
|
|
|
12
12
|
logger = logging.getLogger(__name__)
|
|
13
13
|
|
|
@@ -22,6 +22,24 @@ MAX_SYMBOLS_PER_FILE = 40
|
|
|
22
22
|
# lowest-priority ones are dropped (with a marker) if the whole prompt would exceed this.
|
|
23
23
|
MAX_PROMPT_CHARS = 60_000
|
|
24
24
|
|
|
25
|
+
# Appended to the instructions when the task classifies as HARD (see
|
|
26
|
+
# devcouncil.verification.difficulty). Tells the executor up front that the
|
|
27
|
+
# anti-laziness gates will block, so it does not discover strict mode via a
|
|
28
|
+
# failed verify + repair loop. Kept compact — it rides in the never-dropped core.
|
|
29
|
+
_HARD_TASK_RIGOR_SECTION = """
|
|
30
|
+
## Rigor (this task is classified HARD — verification is strict)
|
|
31
|
+
- No stubs, placeholders, or TODO/FIXME markers in added code: the verifier scans the
|
|
32
|
+
diff and BLOCKS on them. Intentional scaffolding requires the task description to
|
|
33
|
+
mention "scaffolding" AND the line to carry `devcouncil: allow-stub` (declared stubs
|
|
34
|
+
are surfaced for human review even when suppressed).
|
|
35
|
+
- Every acceptance criterion needs a passing behavioral check; changed lines must be
|
|
36
|
+
exercised by the tests you run (diff coverage is enforced).
|
|
37
|
+
- Do not claim completion without running the expected tests and seeing them pass.
|
|
38
|
+
- Never delete, skip, or weaken a test to get to green.
|
|
39
|
+
- If part of the task is genuinely infeasible, say so explicitly and state what is
|
|
40
|
+
missing instead of stubbing around it.
|
|
41
|
+
"""
|
|
42
|
+
|
|
25
43
|
# Rough chars-per-token for English/code; deliberately conservative so the derived
|
|
26
44
|
# budget under-fills the window rather than over-fills it.
|
|
27
45
|
_CHARS_PER_TOKEN = 4
|
|
@@ -36,13 +54,15 @@ _MIN_PROMPT_CHARS = 8_000
|
|
|
36
54
|
def _local_context_window_budget(project_root: Path, cfg=None) -> int | None:
|
|
37
55
|
"""Char budget derived from a constrained local context window, or ``None``.
|
|
38
56
|
|
|
39
|
-
When the run targets the local Ollama provider
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
57
|
+
When the run targets the local Ollama provider, the server silently truncates
|
|
58
|
+
anything past its context window — so a char-only budget that ignores it lets the
|
|
59
|
+
carefully-assembled prompt get cut off mid-stream. Returns a char budget that fits
|
|
60
|
+
the window the provider will actually request (``OLLAMA_NUM_CTX``, else the
|
|
61
|
+
provider's raised ``DEFAULT_NUM_CTX``), minus completion headroom, so
|
|
62
|
+
:meth:`build_task_prompt` can cap itself. Returns ``None`` for cloud providers and
|
|
63
|
+
for an explicit ``OLLAMA_NUM_CTX=0`` opt-out (server-default window, unknowable
|
|
64
|
+
here), leaving the default behavior untouched. Best-effort: any error degrades
|
|
65
|
+
to ``None``.
|
|
46
66
|
|
|
47
67
|
``cfg`` may be a pre-loaded config (loaded once per task by ``build_task_prompt``); when
|
|
48
68
|
``None`` it is loaded here so other callers keep working."""
|
|
@@ -61,8 +81,8 @@ def _local_context_window_budget(project_root: Path, cfg=None) -> int | None:
|
|
|
61
81
|
|
|
62
82
|
num_ctx = OllamaProvider._resolve_num_ctx()
|
|
63
83
|
if not num_ctx:
|
|
64
|
-
#
|
|
65
|
-
#
|
|
84
|
+
# Explicit OLLAMA_NUM_CTX=0 opt-out: the server-default window applies and
|
|
85
|
+
# DevCouncil cannot know it; don't guess a cap here.
|
|
66
86
|
return None
|
|
67
87
|
usable_tokens = num_ctx - _RESERVED_COMPLETION_TOKENS
|
|
68
88
|
if usable_tokens <= 0:
|
|
@@ -81,6 +101,7 @@ _LANG_BY_EXT = {
|
|
|
81
101
|
class PromptBuilder:
|
|
82
102
|
def __init__(self, project_root: Path = Path(".")):
|
|
83
103
|
self.project_root = project_root
|
|
104
|
+
self._file_text_cache: dict[str, str] = {}
|
|
84
105
|
|
|
85
106
|
@staticmethod
|
|
86
107
|
def _lang_for(path: str) -> str:
|
|
@@ -101,7 +122,7 @@ class PromptBuilder:
|
|
|
101
122
|
``path``) so that if the file's content differs between the two reads, the outline
|
|
102
123
|
is recomputed from the current text rather than served stale — and using the text
|
|
103
124
|
directly (rather than its hash) means there is no collision risk."""
|
|
104
|
-
cache = getattr(self, "_outline_cache", None)
|
|
125
|
+
cache: dict[tuple[str, str], List[str]] | None = getattr(self, "_outline_cache", None)
|
|
105
126
|
key = (path, text)
|
|
106
127
|
if cache is not None and key in cache:
|
|
107
128
|
return cache[key]
|
|
@@ -231,6 +252,7 @@ class PromptBuilder:
|
|
|
231
252
|
new files are shown as headers only."""
|
|
232
253
|
from devcouncil.utils.redaction import redact_string
|
|
233
254
|
|
|
255
|
+
self._file_text_cache.clear()
|
|
234
256
|
blocks: List[str] = []
|
|
235
257
|
budget = MAX_FILE_CONTEXT_CHARS
|
|
236
258
|
omitted = 0
|
|
@@ -248,6 +270,7 @@ class PromptBuilder:
|
|
|
248
270
|
except Exception:
|
|
249
271
|
blocks.append(f"### `{pf.path}` [{label}] — [error reading file]\n")
|
|
250
272
|
continue
|
|
273
|
+
self._file_text_cache[pf.path.replace("\\", "/")] = raw
|
|
251
274
|
content = redact_string(raw)
|
|
252
275
|
cap = min(MAX_PER_FILE_CHARS, budget)
|
|
253
276
|
truncated = len(content) > cap
|
|
@@ -287,7 +310,7 @@ class PromptBuilder:
|
|
|
287
310
|
if not map_path.exists():
|
|
288
311
|
return None
|
|
289
312
|
try:
|
|
290
|
-
data =
|
|
313
|
+
data = read_json(map_path)
|
|
291
314
|
return data if isinstance(data, dict) else None
|
|
292
315
|
except Exception:
|
|
293
316
|
return None
|
|
@@ -440,6 +463,185 @@ class PromptBuilder:
|
|
|
440
463
|
)
|
|
441
464
|
return design_block, knowledge_block
|
|
442
465
|
|
|
466
|
+
# Impact block bounds: this is a short, always-on summary — keep it tight so it can
|
|
467
|
+
# never crowd out the detailed file bodies it complements.
|
|
468
|
+
_IMPACT_MAX_FILES = 8
|
|
469
|
+
_IMPACT_MAX_DEPS = 5
|
|
470
|
+
_IMPACT_MAX_NEIGHBORS = 4
|
|
471
|
+
|
|
472
|
+
def _impact_section(self, task: Task, data: dict | None) -> str:
|
|
473
|
+
"""A short "changing X touches Y" block sourced purely from ``repo_map.json``.
|
|
474
|
+
|
|
475
|
+
Unlike the optional code-review-graph structural section (which only appears when
|
|
476
|
+
that CLI is installed and enabled), this is ALWAYS emitted whenever the repo map
|
|
477
|
+
carries dependents or subsystem neighbors — so an agent always sees the blast
|
|
478
|
+
radius of its edits (who imports each changed file, and which neighboring
|
|
479
|
+
subsystems the change reaches) even on the common keyless path. Complements the
|
|
480
|
+
detailed ``_dependents_section``/``_call_sites_section`` with a one-line-per-file
|
|
481
|
+
orientation. When a code graph exists, also appends symbol-level inbound blast
|
|
482
|
+
(depth 1) for planned modify paths. Bounded and best-effort; never raises."""
|
|
483
|
+
if not data:
|
|
484
|
+
return ""
|
|
485
|
+
from devcouncil.indexing.subsystem_map import (
|
|
486
|
+
area_for_path,
|
|
487
|
+
dependents_of,
|
|
488
|
+
neighbors_for_area,
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
lines: List[str] = []
|
|
492
|
+
for pf in task.planned_files[: self._IMPACT_MAX_FILES]:
|
|
493
|
+
path = pf.path.replace("\\", "/")
|
|
494
|
+
is_new = pf.allowed_change == "create"
|
|
495
|
+
importers = [] if is_new else dependents_of(path, data)
|
|
496
|
+
area = area_for_path(path, data)
|
|
497
|
+
neighbors = neighbors_for_area(area, data)
|
|
498
|
+
if not importers and not neighbors and not area and not is_new:
|
|
499
|
+
continue
|
|
500
|
+
parts: List[str] = []
|
|
501
|
+
if importers:
|
|
502
|
+
shown = importers[: self._IMPACT_MAX_DEPS]
|
|
503
|
+
more = f" (+{len(importers) - len(shown)})" if len(importers) > len(shown) else ""
|
|
504
|
+
parts.append(
|
|
505
|
+
f"imported by {len(importers)} file(s): "
|
|
506
|
+
+ ", ".join(f"`{p}`" for p in shown) + more
|
|
507
|
+
)
|
|
508
|
+
elif is_new:
|
|
509
|
+
parts.append("new file — plan its importer before finishing")
|
|
510
|
+
if area:
|
|
511
|
+
neighbor_txt = ""
|
|
512
|
+
if neighbors:
|
|
513
|
+
shown_n = neighbors[: self._IMPACT_MAX_NEIGHBORS]
|
|
514
|
+
neighbor_txt = "; neighbors: " + ", ".join(f"`{n}`" for n in shown_n)
|
|
515
|
+
parts.append(f"subsystem `{area}`{neighbor_txt}")
|
|
516
|
+
if parts:
|
|
517
|
+
lines.append(f"- `{path}` → " + "; ".join(parts))
|
|
518
|
+
|
|
519
|
+
graph_lines = self._graph_impact_lines(task)
|
|
520
|
+
if not lines and not graph_lines:
|
|
521
|
+
return ""
|
|
522
|
+
body = "\n".join(lines) if lines else ""
|
|
523
|
+
if graph_lines:
|
|
524
|
+
extra = "\n".join(graph_lines)
|
|
525
|
+
body = f"{body}\n{extra}" if body else extra
|
|
526
|
+
return (
|
|
527
|
+
"\n## Impact (changing X touches Y)\n"
|
|
528
|
+
"_From the repo map — keep these call sites and neighboring subsystems working, "
|
|
529
|
+
"or update them in scope._\n"
|
|
530
|
+
+ body
|
|
531
|
+
+ "\n"
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
def _graph_impact_lines(self, task: Task) -> List[str]:
|
|
535
|
+
"""Optional symbol-level inbound blast (depth 1) from the code graph."""
|
|
536
|
+
try:
|
|
537
|
+
from devcouncil.indexing.graph.build import load_code_graph
|
|
538
|
+
from devcouncil.indexing.graph.intel import diff_impact
|
|
539
|
+
|
|
540
|
+
graph = load_code_graph(self.project_root)
|
|
541
|
+
if graph is None:
|
|
542
|
+
return []
|
|
543
|
+
paths = [
|
|
544
|
+
pf.path.replace("\\", "/")
|
|
545
|
+
for pf in task.planned_files[: self._IMPACT_MAX_FILES]
|
|
546
|
+
if pf.allowed_change != "create"
|
|
547
|
+
]
|
|
548
|
+
if not paths:
|
|
549
|
+
return []
|
|
550
|
+
result = diff_impact(
|
|
551
|
+
self.project_root, graph, paths=paths, use_diff=False, max_depth=1
|
|
552
|
+
)
|
|
553
|
+
out: List[str] = []
|
|
554
|
+
for item in result.get("paths") or []:
|
|
555
|
+
layers = (item.get("blast") or {}).get("layers") or []
|
|
556
|
+
depth1 = next((L for L in layers if L.get("depth") == 1), None)
|
|
557
|
+
nodes = (depth1 or {}).get("nodes") or []
|
|
558
|
+
if not nodes:
|
|
559
|
+
continue
|
|
560
|
+
shown = nodes[: self._IMPACT_MAX_DEPS]
|
|
561
|
+
more = f" (+{len(nodes) - len(shown)})" if len(nodes) > len(shown) else ""
|
|
562
|
+
out.append(
|
|
563
|
+
f"- `{item['path']}` symbol callers (depth 1): "
|
|
564
|
+
+ ", ".join(f"`{n}`" for n in shown)
|
|
565
|
+
+ more
|
|
566
|
+
)
|
|
567
|
+
return out
|
|
568
|
+
except Exception:
|
|
569
|
+
logger.debug("graph impact lines skipped", exc_info=True)
|
|
570
|
+
return []
|
|
571
|
+
|
|
572
|
+
def _liveness_debt_section(self, task: Task, data: dict | None) -> str:
|
|
573
|
+
"""Surface map unwired/dead-symbol candidates that overlap the task's subsystems."""
|
|
574
|
+
if not data:
|
|
575
|
+
return ""
|
|
576
|
+
try:
|
|
577
|
+
from devcouncil.indexing.subsystem_map import (
|
|
578
|
+
area_for_path,
|
|
579
|
+
areas_touched,
|
|
580
|
+
dead_symbol_candidates_of,
|
|
581
|
+
unreachable_of,
|
|
582
|
+
unwired_candidates_of,
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
planned = [pf.path.replace("\\", "/") for pf in task.planned_files]
|
|
586
|
+
task_areas = set(areas_touched(planned, data))
|
|
587
|
+
if not task_areas:
|
|
588
|
+
return ""
|
|
589
|
+
|
|
590
|
+
unwired_hits: list[str] = []
|
|
591
|
+
for cand in unwired_candidates_of(data)[:40]:
|
|
592
|
+
area = area_for_path(cand, data)
|
|
593
|
+
if area and area in task_areas:
|
|
594
|
+
unwired_hits.append(cand)
|
|
595
|
+
if len(unwired_hits) >= 6:
|
|
596
|
+
break
|
|
597
|
+
|
|
598
|
+
unreachable_hits: list[str] = []
|
|
599
|
+
roots = data.get("entry_roots") or []
|
|
600
|
+
unreachable_unreliable = (
|
|
601
|
+
data.get("liveness_unreachable_unreliable") is True
|
|
602
|
+
or not isinstance(roots, list)
|
|
603
|
+
or not roots
|
|
604
|
+
)
|
|
605
|
+
if not unreachable_unreliable:
|
|
606
|
+
for cand in unreachable_of(data)[:40]:
|
|
607
|
+
area = area_for_path(cand, data)
|
|
608
|
+
if area and area in task_areas:
|
|
609
|
+
unreachable_hits.append(cand)
|
|
610
|
+
if len(unreachable_hits) >= 6:
|
|
611
|
+
break
|
|
612
|
+
|
|
613
|
+
dead_hits: list[str] = []
|
|
614
|
+
for cand in dead_symbol_candidates_of(data)[:40]:
|
|
615
|
+
path = cand.split(":", 1)[0]
|
|
616
|
+
area = area_for_path(path, data)
|
|
617
|
+
if area and area in task_areas:
|
|
618
|
+
dead_hits.append(cand)
|
|
619
|
+
if len(dead_hits) >= 6:
|
|
620
|
+
break
|
|
621
|
+
|
|
622
|
+
if not unwired_hits and not unreachable_hits and not dead_hits:
|
|
623
|
+
return ""
|
|
624
|
+
lines = [
|
|
625
|
+
"\n## Nearby liveness debt (from repo map)",
|
|
626
|
+
"_Existing unwired / unreachable files and unused symbols near your "
|
|
627
|
+
"subsystems — do not add to this debt; wire what you create._",
|
|
628
|
+
"_Verify same-task island rule: a new file imported only by other files "
|
|
629
|
+
"added in this task still needs a pre-existing non-test caller._",
|
|
630
|
+
"_Prefer `dev graph dead --confidence extracted` + greps; treat inferred "
|
|
631
|
+
"as unconfirmed. If entry_roots are empty / liveness_unreachable_unreliable, "
|
|
632
|
+
"ignore unreachable_files and mass inferred dead._",
|
|
633
|
+
]
|
|
634
|
+
for p in unwired_hits:
|
|
635
|
+
lines.append(f"- unwired: `{p}`")
|
|
636
|
+
for p in unreachable_hits:
|
|
637
|
+
lines.append(f"- unreachable: `{p}`")
|
|
638
|
+
for p in dead_hits:
|
|
639
|
+
lines.append(f"- dead symbol: `{p}`")
|
|
640
|
+
return "\n".join(lines) + "\n"
|
|
641
|
+
except Exception:
|
|
642
|
+
logger.debug("liveness debt section skipped", exc_info=True)
|
|
643
|
+
return ""
|
|
644
|
+
|
|
443
645
|
def _dependents_section(self, task: Task, data: dict | None) -> str:
|
|
444
646
|
"""List, per planned file the agent will change, the files that import it — the
|
|
445
647
|
blast radius. Sourced from repo_map.json's precomputed reverse-import index, so
|
|
@@ -447,6 +649,9 @@ class PromptBuilder:
|
|
|
447
649
|
dependents = (data or {}).get("dependents") or {}
|
|
448
650
|
if not isinstance(dependents, dict) or not dependents:
|
|
449
651
|
return ""
|
|
652
|
+
totals = (data or {}).get("dependents_total") or {}
|
|
653
|
+
if not isinstance(totals, dict):
|
|
654
|
+
totals = {}
|
|
450
655
|
lines: List[str] = []
|
|
451
656
|
for pf in task.planned_files:
|
|
452
657
|
# New files have no dependents yet; only existing code carries blast radius.
|
|
@@ -455,18 +660,22 @@ class PromptBuilder:
|
|
|
455
660
|
# Normalize the planned path to posix before the lookup — the map keys are
|
|
456
661
|
# always posix, so a backslash planned path on Windows would otherwise miss
|
|
457
662
|
# and silently drop the whole blast-radius entry (see _repo_map_section).
|
|
458
|
-
|
|
663
|
+
key = pf.path.replace("\\", "/")
|
|
664
|
+
importers = dependents.get(key) or []
|
|
459
665
|
if not importers:
|
|
460
666
|
continue
|
|
461
667
|
shown = importers[:8]
|
|
462
|
-
|
|
668
|
+
full_n = int(totals.get(key) or len(importers))
|
|
669
|
+
hidden = max(0, full_n - len(shown))
|
|
670
|
+
more = f" (+{hidden} more; {full_n} total)" if hidden else ""
|
|
463
671
|
lines.append(f"- `{pf.path}` is imported by: " + ", ".join(f"`{p}`" for p in shown) + more)
|
|
464
672
|
if not lines:
|
|
465
673
|
return ""
|
|
466
674
|
return (
|
|
467
675
|
"\n## Dependents (blast radius)\n"
|
|
468
676
|
"_These files import the files you're changing — keep their call sites working, "
|
|
469
|
-
"or update them in scope.
|
|
677
|
+
"or update them in scope. Counts may exceed the listed sample when the map "
|
|
678
|
+
"truncated dependents._\n"
|
|
470
679
|
+ "\n".join(lines)
|
|
471
680
|
+ "\n"
|
|
472
681
|
)
|
|
@@ -515,10 +724,13 @@ class PromptBuilder:
|
|
|
515
724
|
if not importers:
|
|
516
725
|
continue
|
|
517
726
|
src_path = self.project_root / pf.path
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
727
|
+
key = pf.path.replace("\\", "/")
|
|
728
|
+
src_text = self._file_text_cache.get(key)
|
|
729
|
+
if src_text is None:
|
|
730
|
+
try:
|
|
731
|
+
src_text = src_path.read_text(encoding="utf-8", errors="replace")
|
|
732
|
+
except Exception:
|
|
733
|
+
continue
|
|
522
734
|
symbols = self._exported_symbol_names(pf.path, src_text)
|
|
523
735
|
if not symbols:
|
|
524
736
|
continue
|
|
@@ -526,10 +738,13 @@ class PromptBuilder:
|
|
|
526
738
|
for importer in importers[: self._CALL_SITES_MAX_DEP_FILES]:
|
|
527
739
|
if emitted >= self._CALL_SITES_MAX_TOTAL:
|
|
528
740
|
break
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
741
|
+
importer_key = importer.replace("\\", "/")
|
|
742
|
+
dep_text = self._file_text_cache.get(importer_key)
|
|
743
|
+
if dep_text is None:
|
|
744
|
+
try:
|
|
745
|
+
dep_text = (self.project_root / importer).read_text(encoding="utf-8", errors="replace")
|
|
746
|
+
except Exception:
|
|
747
|
+
continue
|
|
533
748
|
hits = 0
|
|
534
749
|
for lineno, raw in enumerate(dep_text.splitlines(), start=1):
|
|
535
750
|
if hits >= self._CALL_SITES_MAX_LINES_PER_FILE or emitted >= self._CALL_SITES_MAX_TOTAL:
|
|
@@ -630,7 +845,7 @@ class PromptBuilder:
|
|
|
630
845
|
max_chars = MAX_PROMPT_CHARS
|
|
631
846
|
# Per-task outline cache so a planned file's symbol outline is computed once even
|
|
632
847
|
# though both the planned-files section and the call-sites section consume it.
|
|
633
|
-
self._outline_cache: dict[str, List[str]] = {}
|
|
848
|
+
self._outline_cache: dict[tuple[str, str], List[str]] = {}
|
|
634
849
|
# Load the project config once and share it with the helpers that need it, instead
|
|
635
850
|
# of each independently re-reading + re-parsing it. Best-effort: if it fails the
|
|
636
851
|
# helpers fall back to loading it themselves (and degrade the same way).
|
|
@@ -705,10 +920,28 @@ class PromptBuilder:
|
|
|
705
920
|
acceptance criteria require. Do NOT remove, rename, or alter the signature of an
|
|
706
921
|
existing public symbol the task did not ask you to touch — verification flags an
|
|
707
922
|
unrequested public-API change as scope drift and blocks it.
|
|
708
|
-
5.
|
|
709
|
-
|
|
923
|
+
5. Wire every created file and every new public symbol into its intended caller
|
|
924
|
+
(import, register, or call it) before claiming done. A file nothing imports, or a
|
|
925
|
+
public function nothing calls, fails verification. If the caller is outside the
|
|
926
|
+
current planned files, append it with `dev scope update <task_id> --lease-token
|
|
927
|
+
<token> --planned-file <caller>` (modify-op only), then edit.
|
|
928
|
+
6. Run the allowed commands to verify your work.
|
|
929
|
+
7. Provide evidence of passing tests.
|
|
710
930
|
"""
|
|
711
931
|
|
|
932
|
+
# Hard-task rigor: when the task classifies as hard, verification runs in
|
|
933
|
+
# strict mode (stub/effort gates block, diff coverage enforced). Saying so
|
|
934
|
+
# up front is cheaper than a repair loop after the fact.
|
|
935
|
+
try:
|
|
936
|
+
rigor_enabled = True if cfg is None else bool(cfg.verification.rigor.enabled)
|
|
937
|
+
if rigor_enabled:
|
|
938
|
+
from devcouncil.verification.difficulty import estimate_difficulty
|
|
939
|
+
|
|
940
|
+
if estimate_difficulty(task, requirements) == "hard":
|
|
941
|
+
instructions += _HARD_TASK_RIGOR_SECTION
|
|
942
|
+
except Exception:
|
|
943
|
+
logger.debug("hard-task rigor section skipped", exc_info=True)
|
|
944
|
+
|
|
712
945
|
# --- Optional context: fitted within the remaining budget, dropped lowest-
|
|
713
946
|
# priority first. Priority: file contents (1) > structural (2) ~ dependents (2)
|
|
714
947
|
# > skills (3); display order keeps the original reading sequence. ---
|
|
@@ -738,39 +971,51 @@ class PromptBuilder:
|
|
|
738
971
|
"text": f"\n{self._NO_MAP_NOTE}",
|
|
739
972
|
})
|
|
740
973
|
|
|
974
|
+
# Always-on impact block ("changing X touches Y") from the repo map — present
|
|
975
|
+
# even when the code-review-graph CLI is absent (the common case). High priority
|
|
976
|
+
# (1) and short, so it survives all but the tightest budgets; ordered right after
|
|
977
|
+
# structural context so the agent sees the blast radius before the file bodies.
|
|
978
|
+
impact_text = self._impact_section(task, repo_map_data)
|
|
979
|
+
if impact_text:
|
|
980
|
+
segments.append({"order": 2, "priority": 1, "name": "impact", "text": impact_text})
|
|
981
|
+
|
|
982
|
+
liveness_text = self._liveness_debt_section(task, repo_map_data)
|
|
983
|
+
if liveness_text:
|
|
984
|
+
segments.append({"order": 2, "priority": 2, "name": "liveness debt", "text": liveness_text})
|
|
985
|
+
|
|
741
986
|
files_text = self._planned_files_section(task)
|
|
742
987
|
if files_text:
|
|
743
|
-
segments.append({"order":
|
|
988
|
+
segments.append({"order": 3, "priority": 1, "name": "file contents", "text": files_text})
|
|
744
989
|
|
|
745
990
|
dependents_section = self._dependents_section(task, repo_map_data)
|
|
746
991
|
if dependents_section:
|
|
747
992
|
prefix = f"\n{self._STALE_MAP_NOTE}" if (repo_map_stale and not struct_has_stale_note) else ""
|
|
748
|
-
segments.append({"order":
|
|
993
|
+
segments.append({"order": 4, "priority": 2, "name": "dependents", "text": prefix + dependents_section})
|
|
749
994
|
|
|
750
995
|
skills_text = self._skills_section(task)
|
|
751
996
|
if skills_text:
|
|
752
|
-
segments.append({"order":
|
|
997
|
+
segments.append({"order": 5, "priority": 3, "name": "engineering skills", "text": skills_text})
|
|
753
998
|
|
|
754
999
|
# Design system (a hard constraint for UI work) and OKF project knowledge. The
|
|
755
1000
|
# design system rides just above skills; OKF knowledge alongside them. Both are
|
|
756
1001
|
# bounded by config char budgets in `_knowledge_sections`.
|
|
757
1002
|
design_text, knowledge_text = self._knowledge_sections(task, cfg=cfg)
|
|
758
1003
|
if design_text:
|
|
759
|
-
segments.append({"order":
|
|
1004
|
+
segments.append({"order": 5, "priority": 2, "name": "design system", "text": design_text})
|
|
760
1005
|
if knowledge_text:
|
|
761
|
-
segments.append({"order":
|
|
1006
|
+
segments.append({"order": 5, "priority": 3, "name": "project knowledge", "text": knowledge_text})
|
|
762
1007
|
|
|
763
1008
|
# Lowest priority (4): the budget drops call sites first. It only adds value once
|
|
764
1009
|
# the file bodies + dependents are present anyway.
|
|
765
1010
|
call_sites_text = self._call_sites_section(task, repo_map_data)
|
|
766
1011
|
if call_sites_text:
|
|
767
|
-
segments.append({"order":
|
|
1012
|
+
segments.append({"order": 6, "priority": 4, "name": "call sites", "text": call_sites_text})
|
|
768
1013
|
|
|
769
1014
|
# Lowest priority (5): dependency risks are opt-in, advisory context — the
|
|
770
1015
|
# budget drops them first so they never displace structural/file context.
|
|
771
1016
|
dependency_risks_text = self._dependency_risks_section(repo_map_data)
|
|
772
1017
|
if dependency_risks_text:
|
|
773
|
-
segments.append({"order":
|
|
1018
|
+
segments.append({"order": 7, "priority": 5, "name": "dependency risks", "text": dependency_risks_text})
|
|
774
1019
|
|
|
775
1020
|
# The core + instructions are never dropped; if they alone exceed the budget the
|
|
776
1021
|
# model's server will truncate them, so warn loudly instead of failing silently.
|