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.
Files changed (365) hide show
  1. package/README.md +46 -30
  2. package/package.json +6 -2
  3. package/packages/codeintel-grammars/hatch_build.py +43 -0
  4. package/packages/codeintel-grammars/pyproject.toml +16 -0
  5. package/packages/codeintel-grammars/src/devcouncil_codeintel_grammars/__init__.py +93 -0
  6. package/pyproject.toml +99 -4
  7. package/src/devcouncil/app/config.py +512 -20
  8. package/src/devcouncil/app/events.py +4 -23
  9. package/src/devcouncil/app/orchestrator.py +5 -0
  10. package/src/devcouncil/app/run_context.py +3 -3
  11. package/src/devcouncil/assets/__init__.py +4 -1
  12. package/src/devcouncil/assets/vendor/force-graph.min.js +5 -0
  13. package/src/devcouncil/campaign/__init__.py +71 -0
  14. package/src/devcouncil/campaign/bloom.py +137 -0
  15. package/src/devcouncil/campaign/dashboard.py +123 -0
  16. package/src/devcouncil/campaign/mailbox.py +305 -0
  17. package/src/devcouncil/campaign/notify.py +91 -0
  18. package/src/devcouncil/campaign/orchestrator.py +592 -0
  19. package/src/devcouncil/campaign/prompts/coordinator.md +29 -0
  20. package/src/devcouncil/campaign/prompts/director.md +21 -0
  21. package/src/devcouncil/campaign/prompts/protocol.md +46 -0
  22. package/src/devcouncil/campaign/prompts/reviewer.md +24 -0
  23. package/src/devcouncil/campaign/prompts/worker.md +24 -0
  24. package/src/devcouncil/campaign/roles.py +202 -0
  25. package/src/devcouncil/campaign/watcher.py +153 -0
  26. package/src/devcouncil/cli/commands/agents.py +24 -17
  27. package/src/devcouncil/cli/commands/artifacts.py +36 -27
  28. package/src/devcouncil/cli/commands/ast.py +12 -3
  29. package/src/devcouncil/cli/commands/baseline.py +21 -12
  30. package/src/devcouncil/cli/commands/boot.py +218 -0
  31. package/src/devcouncil/cli/commands/campaign.py +302 -0
  32. package/src/devcouncil/cli/commands/check.py +225 -12
  33. package/src/devcouncil/cli/commands/config.py +221 -74
  34. package/src/devcouncil/cli/commands/cost.py +137 -28
  35. package/src/devcouncil/cli/commands/dashboard.py +12 -4
  36. package/src/devcouncil/cli/commands/debug_cmd.py +249 -0
  37. package/src/devcouncil/cli/commands/design.py +27 -17
  38. package/src/devcouncil/cli/commands/doctor.py +790 -8
  39. package/src/devcouncil/cli/commands/evidence.py +41 -20
  40. package/src/devcouncil/cli/commands/export.py +73 -0
  41. package/src/devcouncil/cli/commands/gaps.py +175 -0
  42. package/src/devcouncil/cli/commands/gated_write.py +76 -0
  43. package/src/devcouncil/cli/commands/go.py +220 -68
  44. package/src/devcouncil/cli/commands/graph_cmd.py +1192 -0
  45. package/src/devcouncil/cli/commands/handoff.py +45 -34
  46. package/src/devcouncil/cli/commands/hook.py +630 -85
  47. package/src/devcouncil/cli/commands/init.py +89 -30
  48. package/src/devcouncil/cli/commands/integrate.py +296 -1385
  49. package/src/devcouncil/cli/commands/lease.py +120 -0
  50. package/src/devcouncil/cli/commands/logs.py +12 -5
  51. package/src/devcouncil/cli/commands/lsp.py +40 -5
  52. package/src/devcouncil/cli/commands/map.py +317 -74
  53. package/src/devcouncil/cli/commands/mcp_server.py +12 -2
  54. package/src/devcouncil/cli/commands/okf.py +44 -6
  55. package/src/devcouncil/cli/commands/plan.py +184 -69
  56. package/src/devcouncil/cli/commands/prompt.py +26 -17
  57. package/src/devcouncil/cli/commands/provenance.py +79 -0
  58. package/src/devcouncil/cli/commands/repair.py +60 -49
  59. package/src/devcouncil/cli/commands/report.py +148 -40
  60. package/src/devcouncil/cli/commands/requirements.py +104 -0
  61. package/src/devcouncil/cli/commands/reset_demo_state.py +13 -4
  62. package/src/devcouncil/cli/commands/rollback.py +46 -35
  63. package/src/devcouncil/cli/commands/run.py +173 -8
  64. package/src/devcouncil/cli/commands/runs.py +298 -68
  65. package/src/devcouncil/cli/commands/scaffold.py +33 -12
  66. package/src/devcouncil/cli/commands/semantic.py +29 -14
  67. package/src/devcouncil/cli/commands/setup.py +103 -93
  68. package/src/devcouncil/cli/commands/shell.py +51 -42
  69. package/src/devcouncil/cli/commands/show.py +56 -42
  70. package/src/devcouncil/cli/commands/skills.py +29 -20
  71. package/src/devcouncil/cli/commands/status.py +80 -67
  72. package/src/devcouncil/cli/commands/task_gate.py +295 -0
  73. package/src/devcouncil/cli/commands/tasks.py +248 -19
  74. package/src/devcouncil/cli/commands/trace.py +14 -8
  75. package/src/devcouncil/cli/commands/verify.py +33 -8
  76. package/src/devcouncil/cli/commands/version.py +14 -6
  77. package/src/devcouncil/cli/commands/watch.py +49 -30
  78. package/src/devcouncil/cli/commands/watch_fs.py +30 -19
  79. package/src/devcouncil/cli/commands/wiki.py +278 -0
  80. package/src/devcouncil/cli/main.py +58 -1
  81. package/src/devcouncil/codeintel/__init__.py +16 -0
  82. package/src/devcouncil/codeintel/build_control.py +429 -0
  83. package/src/devcouncil/codeintel/build_worker.py +78 -0
  84. package/src/devcouncil/codeintel/debug/__init__.py +17 -0
  85. package/src/devcouncil/codeintel/debug/broker.py +114 -0
  86. package/src/devcouncil/codeintel/debug/broker_client.py +61 -0
  87. package/src/devcouncil/codeintel/debug/consent.py +36 -0
  88. package/src/devcouncil/codeintel/debug/discovery.py +132 -0
  89. package/src/devcouncil/codeintel/debug/fingerprint.py +85 -0
  90. package/src/devcouncil/codeintel/debug/protocol.py +259 -0
  91. package/src/devcouncil/codeintel/debug/python_trace_runner.py +81 -0
  92. package/src/devcouncil/codeintel/debug/session.py +238 -0
  93. package/src/devcouncil/codeintel/debug/tracing.py +201 -0
  94. package/src/devcouncil/codeintel/languages/__init__.py +17 -0
  95. package/src/devcouncil/codeintel/languages/generic_extractor.py +236 -0
  96. package/src/devcouncil/codeintel/languages/registry.py +149 -0
  97. package/src/devcouncil/codeintel/languages/workers.py +245 -0
  98. package/src/devcouncil/codeintel/query/__init__.py +5 -0
  99. package/src/devcouncil/codeintel/query/engine.py +289 -0
  100. package/src/devcouncil/codeintel/resolution/__init__.py +6 -0
  101. package/src/devcouncil/codeintel/resolution/abstract_state.py +301 -0
  102. package/src/devcouncil/codeintel/resolution/frameworks/__init__.py +33 -0
  103. package/src/devcouncil/codeintel/resolution/frameworks/base.py +46 -0
  104. package/src/devcouncil/codeintel/resolution/frameworks/di.py +56 -0
  105. package/src/devcouncil/codeintel/resolution/frameworks/events.py +45 -0
  106. package/src/devcouncil/codeintel/resolution/frameworks/routes.py +88 -0
  107. package/src/devcouncil/codeintel/resolution/semantic.py +887 -0
  108. package/src/devcouncil/codeintel/service.py +104 -0
  109. package/src/devcouncil/codeintel/store/__init__.py +15 -0
  110. package/src/devcouncil/codeintel/store/sqlite.py +1565 -0
  111. package/src/devcouncil/codeintel/sync/__init__.py +19 -0
  112. package/src/devcouncil/codeintel/sync/coordinator.py +430 -0
  113. package/src/devcouncil/codeintel/sync/incremental.py +484 -0
  114. package/src/devcouncil/codeintel/sync/lease.py +96 -0
  115. package/src/devcouncil/codeintel/sync/scope.py +98 -0
  116. package/src/devcouncil/council/__init__.py +4 -0
  117. package/src/devcouncil/council/prompts/__init__.py +4 -0
  118. package/src/devcouncil/domain/checkpoint_refs.py +17 -0
  119. package/src/devcouncil/domain/evidence.py +1 -0
  120. package/src/devcouncil/domain/gap.py +10 -0
  121. package/src/devcouncil/domain/requirement.py +5 -1
  122. package/src/devcouncil/domain/task.py +43 -2
  123. package/src/devcouncil/execution/checkpoints.py +25 -31
  124. package/src/devcouncil/execution/context_builder.py +15 -44
  125. package/src/devcouncil/execution/fs_watcher.py +64 -0
  126. package/src/devcouncil/execution/gated_write.py +203 -0
  127. package/src/devcouncil/execution/handoff.py +2 -1
  128. package/src/devcouncil/execution/hook_policy.py +19 -5
  129. package/src/devcouncil/execution/lease_ops.py +177 -0
  130. package/src/devcouncil/execution/lease_validation.py +71 -0
  131. package/src/devcouncil/execution/patch.py +3 -0
  132. package/src/devcouncil/execution/permissions.py +1 -0
  133. package/src/devcouncil/execution/policy_engine.py +205 -10
  134. package/src/devcouncil/execution/prompt_builder.py +278 -33
  135. package/src/devcouncil/execution/run_trace.py +356 -0
  136. package/src/devcouncil/execution/shell_session.py +46 -5
  137. package/src/devcouncil/execution/stop_gate.py +746 -0
  138. package/src/devcouncil/execution/stop_gate_history.py +113 -0
  139. package/src/devcouncil/execution/stop_gate_state.py +54 -0
  140. package/src/devcouncil/execution/stop_gate_verify_cache.py +69 -0
  141. package/src/devcouncil/execution/task_gate_ops.py +590 -0
  142. package/src/devcouncil/execution/task_runner.py +19 -0
  143. package/src/devcouncil/executors/advisor_tool.py +315 -0
  144. package/src/devcouncil/executors/agent_registry.py +125 -17
  145. package/src/devcouncil/executors/claude_sdk.py +376 -0
  146. package/src/devcouncil/executors/coding_cli.py +724 -25
  147. package/src/devcouncil/executors/mini_swe.py +50 -8
  148. package/src/devcouncil/executors/native/agent.py +224 -19
  149. package/src/devcouncil/executors/openhands.py +50 -8
  150. package/src/devcouncil/executors/transient_retry.py +99 -0
  151. package/src/devcouncil/gating/checks/clean_git.py +5 -2
  152. package/src/devcouncil/gating/checks/planned_files_check.py +38 -11
  153. package/src/devcouncil/gating/checks/secret_scan_check.py +2 -2
  154. package/src/devcouncil/gating/policy.py +46 -2
  155. package/src/devcouncil/indexing/ast_matcher.py +41 -4
  156. package/src/devcouncil/indexing/graph/__init__.py +78 -0
  157. package/src/devcouncil/indexing/graph/api_routes.py +522 -0
  158. package/src/devcouncil/indexing/graph/build.py +862 -0
  159. package/src/devcouncil/indexing/graph/cache.py +329 -0
  160. package/src/devcouncil/indexing/graph/communities.py +28 -0
  161. package/src/devcouncil/indexing/graph/cypher.py +107 -0
  162. package/src/devcouncil/indexing/graph/embeddings.py +194 -0
  163. package/src/devcouncil/indexing/graph/export.py +381 -0
  164. package/src/devcouncil/indexing/graph/export_links.py +81 -0
  165. package/src/devcouncil/indexing/graph/extract_python.py +307 -0
  166. package/src/devcouncil/indexing/graph/extract_ts.py +1205 -0
  167. package/src/devcouncil/indexing/graph/intel.py +668 -0
  168. package/src/devcouncil/indexing/graph/liveness.py +992 -0
  169. package/src/devcouncil/indexing/graph/okf_export.py +65 -0
  170. package/src/devcouncil/indexing/graph/pdg/__init__.py +67 -0
  171. package/src/devcouncil/indexing/graph/pdg/build.py +11 -0
  172. package/src/devcouncil/indexing/graph/pdg/cdg.py +41 -0
  173. package/src/devcouncil/indexing/graph/pdg/cfg.py +199 -0
  174. package/src/devcouncil/indexing/graph/pdg/query.py +21 -0
  175. package/src/devcouncil/indexing/graph/pdg/reaching_def.py +126 -0
  176. package/src/devcouncil/indexing/graph/pdg/schema.py +253 -0
  177. package/src/devcouncil/indexing/graph/pdg/taint.py +154 -0
  178. package/src/devcouncil/indexing/graph/query.py +302 -0
  179. package/src/devcouncil/indexing/graph/resolve.py +1020 -0
  180. package/src/devcouncil/indexing/graph/schema.py +103 -0
  181. package/src/devcouncil/indexing/graph_index.py +20 -29
  182. package/src/devcouncil/indexing/lsp.py +57 -25
  183. package/src/devcouncil/indexing/lsp_client.py +577 -0
  184. package/src/devcouncil/indexing/map_artifacts.py +355 -0
  185. package/src/devcouncil/indexing/map_refresh.py +141 -0
  186. package/src/devcouncil/indexing/repo_mapper.py +1509 -138
  187. package/src/devcouncil/indexing/semantic_index.py +12 -6
  188. package/src/devcouncil/indexing/subsystem_map.py +163 -0
  189. package/src/devcouncil/indexing/ts_imports.py +343 -0
  190. package/src/devcouncil/indexing/viz.py +960 -0
  191. package/src/devcouncil/indexing/walk.py +52 -0
  192. package/src/devcouncil/indexing/wiring.py +1776 -0
  193. package/src/devcouncil/integrations/actions.py +27 -4
  194. package/src/devcouncil/integrations/check.py +211 -16
  195. package/src/devcouncil/integrations/claude_assets.py +209 -12
  196. package/src/devcouncil/integrations/clients/__init__.py +1 -0
  197. package/src/devcouncil/integrations/clients/aider.py +52 -0
  198. package/src/devcouncil/integrations/clients/antigravity.py +87 -0
  199. package/src/devcouncil/integrations/clients/claude.py +339 -0
  200. package/src/devcouncil/integrations/clients/codex.py +39 -0
  201. package/src/devcouncil/integrations/clients/common.py +332 -0
  202. package/src/devcouncil/integrations/clients/cursor.py +164 -0
  203. package/src/devcouncil/integrations/clients/gemini.py +49 -0
  204. package/src/devcouncil/integrations/clients/grok.py +105 -0
  205. package/src/devcouncil/integrations/clients/hooks.py +500 -0
  206. package/src/devcouncil/integrations/clients/opencode.py +96 -0
  207. package/src/devcouncil/integrations/clients/warp.py +75 -0
  208. package/src/devcouncil/integrations/code_review_graph.py +2 -2
  209. package/src/devcouncil/integrations/github.py +73 -7
  210. package/src/devcouncil/integrations/integration_cli.py +197 -0
  211. package/src/devcouncil/integrations/mcp/handlers/__init__.py +1 -0
  212. package/src/devcouncil/integrations/mcp/handlers/ast_lsp.py +77 -0
  213. package/src/devcouncil/integrations/mcp/handlers/checkout.py +50 -0
  214. package/src/devcouncil/integrations/mcp/handlers/cli_gate.py +43 -0
  215. package/src/devcouncil/integrations/mcp/handlers/codeintel.py +182 -0
  216. package/src/devcouncil/integrations/mcp/handlers/debug.py +236 -0
  217. package/src/devcouncil/integrations/mcp/handlers/evidence.py +70 -0
  218. package/src/devcouncil/integrations/mcp/handlers/git.py +99 -0
  219. package/src/devcouncil/integrations/mcp/handlers/graph.py +36 -0
  220. package/src/devcouncil/integrations/mcp/handlers/handoff.py +53 -0
  221. package/src/devcouncil/integrations/mcp/handlers/knowledge.py +30 -0
  222. package/src/devcouncil/integrations/mcp/handlers/lease.py +70 -0
  223. package/src/devcouncil/integrations/mcp/handlers/live.py +108 -0
  224. package/src/devcouncil/integrations/mcp/handlers/map.py +676 -0
  225. package/src/devcouncil/integrations/mcp/handlers/next_task.py +35 -0
  226. package/src/devcouncil/integrations/mcp/handlers/policy.py +80 -0
  227. package/src/devcouncil/integrations/mcp/handlers/prompts.py +168 -0
  228. package/src/devcouncil/integrations/mcp/handlers/provenance.py +101 -0
  229. package/src/devcouncil/integrations/mcp/handlers/read.py +74 -0
  230. package/src/devcouncil/integrations/mcp/handlers/router_cache.py +53 -0
  231. package/src/devcouncil/integrations/mcp/handlers/run.py +53 -0
  232. package/src/devcouncil/integrations/mcp/handlers/runs.py +84 -0
  233. package/src/devcouncil/integrations/mcp/handlers/scope.py +56 -0
  234. package/src/devcouncil/integrations/mcp/handlers/status.py +114 -0
  235. package/src/devcouncil/integrations/mcp/handlers/task.py +100 -0
  236. package/src/devcouncil/integrations/mcp/handlers/tool_specs.py +904 -0
  237. package/src/devcouncil/integrations/mcp/handlers/trace.py +65 -0
  238. package/src/devcouncil/integrations/mcp/handlers/verify.py +45 -0
  239. package/src/devcouncil/integrations/mcp/handlers/wiki.py +60 -0
  240. package/src/devcouncil/integrations/mcp/handlers/write.py +69 -0
  241. package/src/devcouncil/integrations/mcp/server.py +265 -2391
  242. package/src/devcouncil/integrations/mcp/util.py +303 -0
  243. package/src/devcouncil/integrations/setup.py +152 -0
  244. package/src/devcouncil/knowledge/fetch.py +4 -0
  245. package/src/devcouncil/knowledge/knowledge_select.py +38 -0
  246. package/src/devcouncil/knowledge/okf.py +2 -1
  247. package/src/devcouncil/knowledge/resource_discovery.py +40 -0
  248. package/src/devcouncil/knowledge/wiki.py +643 -0
  249. package/src/devcouncil/knowledge/wiki_read.py +87 -0
  250. package/src/devcouncil/live/cards.py +7 -7
  251. package/src/devcouncil/live/models.py +4 -1
  252. package/src/devcouncil/live/reviewer.py +90 -11
  253. package/src/devcouncil/live/signals.py +4 -2
  254. package/src/devcouncil/live/tasks.py +12 -3
  255. package/src/devcouncil/live/transcripts.py +69 -2
  256. package/src/devcouncil/llm/cache.py +5 -6
  257. package/src/devcouncil/llm/model_defaults.yaml +10 -10
  258. package/src/devcouncil/llm/provider.py +647 -73
  259. package/src/devcouncil/llm/router.py +271 -46
  260. package/src/devcouncil/llm/semantic_bridge.py +614 -0
  261. package/src/devcouncil/optimization/gepa_agent.py +6 -4
  262. package/src/devcouncil/optimization/skillopt.py +9 -5
  263. package/src/devcouncil/planning/arbiter_service.py +12 -3
  264. package/src/devcouncil/planning/correction_manifest.py +107 -10
  265. package/src/devcouncil/planning/plan_difficulty.py +69 -0
  266. package/src/devcouncil/planning/plan_service.py +5 -2
  267. package/src/devcouncil/planning/planned_files_reconcile.py +191 -0
  268. package/src/devcouncil/planning/prompt_enhancer_service.py +6 -5
  269. package/src/devcouncil/planning/question_conversion.py +56 -0
  270. package/src/devcouncil/planning/spec_service.py +9 -3
  271. package/src/devcouncil/repo/ci_scaffold.py +197 -1
  272. package/src/devcouncil/repo/gitignore.py +1 -2
  273. package/src/devcouncil/reporting/evidence_export.py +124 -0
  274. package/src/devcouncil/reporting/evidence_html.py +210 -0
  275. package/src/devcouncil/reporting/json_report.py +16 -12
  276. package/src/devcouncil/reporting/markdown_report.py +38 -9
  277. package/src/devcouncil/reporting/mcp_resources.py +142 -0
  278. package/src/devcouncil/reporting/report_builder.py +40 -4
  279. package/src/devcouncil/reporting/task_provenance.py +42 -0
  280. package/src/devcouncil/reporting/verdict.py +75 -0
  281. package/src/devcouncil/skills/library/README.md +1 -0
  282. package/src/devcouncil/skills/library/devcouncil-hero-loop.md +109 -0
  283. package/src/devcouncil/skills/library/devcouncil-verification.md +109 -0
  284. package/src/devcouncil/skills/library/devcouncil.md +93 -0
  285. package/src/devcouncil/skills/registry.py +43 -12
  286. package/src/devcouncil/storage/db.py +57 -11
  287. package/src/devcouncil/storage/models.py +6 -0
  288. package/src/devcouncil/storage/native.py +5 -3
  289. package/src/devcouncil/storage/repositories.py +50 -18
  290. package/src/devcouncil/telemetry/context.py +28 -0
  291. package/src/devcouncil/telemetry/cost.py +4 -5
  292. package/src/devcouncil/telemetry/logging_setup.py +78 -11
  293. package/src/devcouncil/telemetry/model_pricing.yaml +7 -0
  294. package/src/devcouncil/telemetry/stages.py +27 -2
  295. package/src/devcouncil/telemetry/tracker.py +50 -13
  296. package/src/devcouncil/ui/dashboard.py +120 -8
  297. package/src/devcouncil/utils/fsio.py +58 -0
  298. package/src/devcouncil/utils/git_snapshot.py +112 -0
  299. package/src/devcouncil/utils/json_persist.py +53 -0
  300. package/src/devcouncil/utils/proc.py +89 -0
  301. package/src/devcouncil/verification/acceptance_compiler.py +36 -13
  302. package/src/devcouncil/verification/ad_hoc_check.py +95 -3
  303. package/src/devcouncil/verification/checks/__init__.py +41 -0
  304. package/src/devcouncil/verification/checks/acceptance.py +39 -0
  305. package/src/devcouncil/verification/checks/acceptance_corpus.py +194 -0
  306. package/src/devcouncil/verification/checks/acceptance_evidence.py +239 -0
  307. package/src/devcouncil/verification/checks/command_evidence.py +148 -0
  308. package/src/devcouncil/verification/checks/compiled_acceptance.py +179 -0
  309. package/src/devcouncil/verification/checks/corpus_stale.py +124 -0
  310. package/src/devcouncil/verification/checks/corpus_verification.py +9 -0
  311. package/src/devcouncil/verification/checks/dead_symbols.py +360 -0
  312. package/src/devcouncil/verification/checks/diff_coverage_gate.py +101 -0
  313. package/src/devcouncil/verification/checks/doc_code_ref.py +79 -0
  314. package/src/devcouncil/verification/checks/liveness_ratchet.py +336 -0
  315. package/src/devcouncil/verification/checks/orphan_diff.py +104 -0
  316. package/src/devcouncil/verification/checks/planned_files.py +98 -0
  317. package/src/devcouncil/verification/checks/semantic_diff.py +241 -0
  318. package/src/devcouncil/verification/checks/stale_map.py +80 -0
  319. package/src/devcouncil/verification/checks/stub_scan.py +71 -0
  320. package/src/devcouncil/verification/checks/subsystem_boundary.py +103 -0
  321. package/src/devcouncil/verification/checks/wiring.py +216 -0
  322. package/src/devcouncil/verification/claims/__init__.py +23 -0
  323. package/src/devcouncil/verification/claims/checks.py +395 -0
  324. package/src/devcouncil/verification/claims/mapper.py +168 -0
  325. package/src/devcouncil/verification/claims/models.py +39 -0
  326. package/src/devcouncil/verification/claims/transcript.py +92 -0
  327. package/src/devcouncil/verification/claims/verdict.py +88 -0
  328. package/src/devcouncil/verification/command_evidence.py +170 -0
  329. package/src/devcouncil/verification/command_malformation.py +147 -0
  330. package/src/devcouncil/verification/command_runner.py +164 -0
  331. package/src/devcouncil/verification/coverage_measurement.py +292 -0
  332. package/src/devcouncil/verification/diff_coverage.py +151 -0
  333. package/src/devcouncil/verification/difficulty.py +296 -0
  334. package/src/devcouncil/verification/effort_heuristics.py +178 -0
  335. package/src/devcouncil/verification/gap_ids.py +63 -0
  336. package/src/devcouncil/verification/gate_cache.py +194 -0
  337. package/src/devcouncil/verification/gate_selector.py +344 -0
  338. package/src/devcouncil/verification/git_diff_fallback.py +272 -0
  339. package/src/devcouncil/verification/implementation_reviewer.py +13 -0
  340. package/src/devcouncil/verification/incremental_check.py +241 -0
  341. package/src/devcouncil/verification/next_actions.py +60 -1
  342. package/src/devcouncil/verification/rigor_analytics.py +130 -0
  343. package/src/devcouncil/verification/sandbox.py +38 -11
  344. package/src/devcouncil/verification/stub_detector.py +369 -0
  345. package/src/devcouncil/verification/test_resolver.py +67 -1
  346. package/src/devcouncil/verification/verifier.py +137 -1666
  347. package/src/devcouncil/verification/verify_orchestration.py +610 -0
  348. package/src/devcouncil/verification/verify_setup.py +176 -0
  349. package/src/devcouncil/verification/wiki_refresh.py +208 -0
  350. package/src/semantic_layer/__init__.py +58 -0
  351. package/src/semantic_layer/benchmark.py +75 -0
  352. package/src/semantic_layer/cache.py +290 -0
  353. package/src/semantic_layer/compressor.py +137 -0
  354. package/src/semantic_layer/config.py +75 -0
  355. package/src/semantic_layer/embeddings.py +69 -0
  356. package/src/semantic_layer/llm_backends.py +99 -0
  357. package/src/semantic_layer/pipeline.py +111 -0
  358. package/src/semantic_layer/router.py +128 -0
  359. package/src/semantic_layer/tuner.py +72 -0
  360. package/uv.lock +973 -9
  361. package/src/devcouncil/artifacts/migrations.py +0 -20
  362. package/src/devcouncil/artifacts/schemas.py +0 -23
  363. package/src/devcouncil/artifacts/serializer.py +0 -21
  364. package/src/devcouncil/integrations/gitnexus.py +0 -70
  365. package/src/devcouncil/integrations/graphify.py +0 -34
@@ -11,9 +11,10 @@ import shutil
11
11
  import subprocess
12
12
  import time
13
13
  from pathlib import Path
14
- from typing import Dict, List, Optional, Tuple
14
+ from typing import Dict, List, Literal, Optional, Tuple
15
15
 
16
- from pydantic import BaseModel, Field, field_validator
16
+ from annotated_types import Ge, Le
17
+ from pydantic import BaseModel, Field, field_validator, model_validator
17
18
 
18
19
  logger = logging.getLogger(__name__)
19
20
 
@@ -95,16 +96,65 @@ class AcceptanceCheckConfig(BaseModel):
95
96
  stays unproven with a non-blocking advisory). Local sampling is cost-free, so
96
97
  raising this (e.g. 3) outvotes a single mis-generated check without ever
97
98
  auto-passing a real defect. ``1`` reproduces today's single-check behavior.
99
+
100
+ Every knob is OPTIONAL (``None`` = auto). Auto resolves by where the monitor
101
+ role actually runs — see :meth:`resolved`: a cost-free local (Ollama) monitor
102
+ gets the calibration-friendly settings this class documents (``samples=3``,
103
+ ``repair_attempts=2``, ``per_criterion=True``); a paid cloud monitor keeps the
104
+ single-shot behavior. Setting a value explicitly always wins over auto.
98
105
  """
99
106
 
100
- samples: int = 1
101
- repair_attempts: int = 1
107
+ samples: int | None = None
108
+ repair_attempts: int | None = None
102
109
  # Compile one criterion per model call instead of batching them all into a single
103
110
  # prompt. A weak/local model batching N criteria into one JSON routinely omits or
104
111
  # mis-attributes some (a false "incomplete"); a focused single-criterion prompt is far
105
- # more reliable. Costs N× the calls — cheap on a local monitor, so opt-in. Off keeps
106
- # the single batched call.
107
- per_criterion: bool = False
112
+ # more reliable. Costs N× the calls — cheap on a local monitor, so auto-on there.
113
+ per_criterion: bool | None = None
114
+
115
+ # (samples, repair_attempts, per_criterion)
116
+ _CLOUD_DEFAULTS = (1, 1, False)
117
+ _LOCAL_DEFAULTS = (3, 2, True)
118
+
119
+ def resolved(self, local_monitor: bool) -> tuple[int, int, bool]:
120
+ """Concrete (samples, repair_attempts, per_criterion) with auto defaults."""
121
+ d_samples, d_repairs, d_per_criterion = (
122
+ self._LOCAL_DEFAULTS if local_monitor else self._CLOUD_DEFAULTS
123
+ )
124
+ return (
125
+ max(1, self.samples if self.samples is not None else d_samples),
126
+ max(0, self.repair_attempts if self.repair_attempts is not None else d_repairs),
127
+ self.per_criterion if self.per_criterion is not None else d_per_criterion,
128
+ )
129
+
130
+ def unsafe_override_warnings(self, local_monitor: bool) -> list[str]:
131
+ """Explicit overrides that disable the ensembling a LOCAL monitor needs.
132
+
133
+ Auto-resolution already picks safe local defaults; these warnings fire only
134
+ when a user has EXPLICITLY configured the settings calibration probes showed
135
+ to be unsafe on a local monitor (2026-07-03, Ornith-35B: ``samples=1``
136
+ rubber-stamped 1/6 buggy criteria as passing; ``samples=3`` +
137
+ ``per_criterion`` caught 6/6 with zero false passes). The config is honored
138
+ — explicit always wins — but never silently."""
139
+ if not local_monitor:
140
+ return []
141
+ warnings: list[str] = []
142
+ samples, _, per_criterion = self.resolved(local_monitor)
143
+ if self.samples is not None and samples < 3:
144
+ warnings.append(
145
+ f"verification.acceptance_checks.samples={samples} with a LOCAL monitor: "
146
+ "single/low-sample acceptance checks on a local model have rubber-stamped "
147
+ "real defects in calibration probes. Recommend samples>=3 (local sampling "
148
+ "is cost-free) or removing the override to use the auto default."
149
+ )
150
+ if self.per_criterion is False:
151
+ warnings.append(
152
+ "verification.acceptance_checks.per_criterion=false with a LOCAL monitor: "
153
+ "batched compilation on a local model routinely drops or mis-attributes "
154
+ "criteria (false 'incomplete'). Recommend per_criterion=true or removing "
155
+ "the override to use the auto default."
156
+ )
157
+ return warnings
108
158
 
109
159
 
110
160
  class ReviewerCheckConfig(BaseModel):
@@ -116,9 +166,107 @@ class ReviewerCheckConfig(BaseModel):
116
166
  escalates to the blocking verdict when a strict majority agree; a split de-escalates to
117
167
  the non-blocking "Concerns". ``samples=1`` reproduces today's single-review behavior, so
118
168
  a strong cloud model is unaffected. Local sampling is cost-free — raise it (e.g. 3) there.
169
+
170
+ ``samples`` is OPTIONAL (``None`` = auto): a local (Ollama) reviewer auto-resolves
171
+ to 3 votes, a cloud reviewer to 1. An explicit value always wins over auto.
119
172
  """
120
173
 
121
- samples: int = 1
174
+ samples: int | None = None
175
+
176
+ def resolved(self, local_reviewer: bool) -> int:
177
+ """Concrete sample count with the local/cloud auto default."""
178
+ if self.samples is not None:
179
+ return max(1, self.samples)
180
+ return 3 if local_reviewer else 1
181
+
182
+ def unsafe_override_warnings(self, local_reviewer: bool) -> list[str]:
183
+ """Explicit single-shot voting on a LOCAL reviewer — honored, never silent.
184
+
185
+ A lone local review can emit a mis-calibrated blocking verdict; voting over
186
+ >=3 samples is what de-escalates that (see class docstring)."""
187
+ if not local_reviewer or self.samples is None:
188
+ return []
189
+ resolved = self.resolved(local_reviewer)
190
+ if resolved >= 3:
191
+ return []
192
+ return [
193
+ f"verification.reviewer_checks.samples={resolved} with a LOCAL reviewer: a "
194
+ "single local review can falsely BLOCK on one mis-calibrated verdict. "
195
+ "Recommend samples>=3 (local sampling is cost-free) or removing the override."
196
+ ]
197
+
198
+
199
+ class RigorConfig(BaseModel):
200
+ """Anti-laziness enforcement policy, scaled by task difficulty.
201
+
202
+ The stub/effort gates always *run* (unless ``never``); the mode decides when
203
+ their findings BLOCK: ``hard`` (default) blocks only on tasks classified hard
204
+ by ``devcouncil.verification.difficulty`` (or a manual ``Task.difficulty``),
205
+ ``always`` blocks everywhere, ``never`` disables the gate entirely. The
206
+ remaining knobs escalate existing machinery on hard tasks: coverage
207
+ enforcement flips ``diff_not_exercised`` to blocking, ``reviewer_required``
208
+ lets a critical implementation-review finding block (normally advisory), and
209
+ ``extra_repair_attempts_on_hard`` widens the `dev go` self-repair budget.
210
+ """
211
+
212
+ enabled: bool = True
213
+ stub_detection: str = "hard" # never | hard | always
214
+ effort_heuristics: str = "hard" # never | hard | always
215
+ # When coarse fallback proves an AC (passing command, not per-criterion check):
216
+ # advisory on easy/normal; blocking on hard by default.
217
+ coarse_acceptance_proof: str = "hard" # never | hard | always
218
+ enforce_coverage_on_hard: bool = True
219
+ reviewer_required_on_hard: bool = False
220
+ extra_repair_attempts_on_hard: int = 1
221
+ # Undersized-diff threshold: added CODE lines must reach this many per planned
222
+ # writable file (only checked when the scope is substantial — >=3 writable
223
+ # files or any file creation).
224
+ min_added_lines_per_planned_file: int = 5
225
+ # On hard tasks, compile at least this many independent acceptance checks per
226
+ # criterion (input variation) so agents cannot hardcode a single probe value.
227
+ acceptance_samples_on_hard: int = 2
228
+ # New files/symbols never imported or referenced: advisory on easy/normal,
229
+ # blocking on hard by default (same posture as placeholder/effort gates).
230
+ unwired_files: str = "hard" # never | hard | always
231
+ dead_symbols: str = "hard" # never | hard | always
232
+ # Existing code newly stranded vs checkout baseline (lost last importer/caller).
233
+ liveness_ratchet: str = "hard" # never | hard | always
234
+ # On-disk repo_map.json lags git HEAD / tracked file set: advisory on easy/normal,
235
+ # blocking on hard by default.
236
+ stale_map: str = "hard" # never | hard | always
237
+ corpus_stale: str = "soft" # never | soft | hard | always
238
+ doc_code_ref: str = "soft" # never | soft | hard | always
239
+ acceptance_corpus: str = "soft" # never | soft | hard | always
240
+
241
+
242
+ class SubsystemBoundaryConfig(BaseModel):
243
+ """Advisory architecture-drift gate over the mapped subsystem graph.
244
+
245
+ Flags a change that edits files in two subsystems the repo map does NOT consider
246
+ neighbors, when the crossing was not declared in the task plan — a signal that an
247
+ edit is reaching across an architectural boundary it shouldn't. Advisory
248
+ (non-blocking) by default: set ``blocking`` to make an undeclared crossing block
249
+ verification. Needs a fresh ``repo_map.json`` with ``subsystems``/``neighbors`` to
250
+ have anything to check; degrades to a no-op otherwise.
251
+ """
252
+
253
+ enabled: bool = True
254
+ blocking: bool = False
255
+
256
+
257
+ class WikiRefreshConfig(BaseModel):
258
+ """Post-verify wiki-freshness trigger for large refactors.
259
+
260
+ When a verified change spans at least ``min_subsystems`` subsystem areas OR touches
261
+ at least ``min_files`` files, the codebase wiki is likely stale. By default this
262
+ only FLAGS the stale pages (cheap, no model calls). Set ``auto_update`` to actually
263
+ run ``dev wiki update --no-llm`` as a post-step. Best-effort and never blocks.
264
+ """
265
+
266
+ enabled: bool = True
267
+ min_subsystems: int = 3
268
+ min_files: int = 8
269
+ auto_update: bool = False
122
270
 
123
271
 
124
272
  class VerificationConfig(BaseModel):
@@ -126,6 +274,16 @@ class VerificationConfig(BaseModel):
126
274
  diff_coverage: DiffCoverageConfig = Field(default_factory=DiffCoverageConfig)
127
275
  acceptance_checks: AcceptanceCheckConfig = Field(default_factory=AcceptanceCheckConfig)
128
276
  reviewer_checks: ReviewerCheckConfig = Field(default_factory=ReviewerCheckConfig)
277
+ rigor: RigorConfig = Field(default_factory=RigorConfig)
278
+ subsystem_boundary: SubsystemBoundaryConfig = Field(default_factory=SubsystemBoundaryConfig)
279
+ wiki_refresh: WikiRefreshConfig = Field(default_factory=WikiRefreshConfig)
280
+ # Flaky-evidence retry: when an acceptance-capable verification command (planner
281
+ # expected_tests / config commands) genuinely fails, re-run it ONCE. If the re-run
282
+ # passes, the command counts as passed and its stored summary is tagged
283
+ # "[flaky: passed on retry 2/2]" so next-actions/reports can tell flaky from
284
+ # broken. Never applies to compiled per-criterion checks (they have their own
285
+ # repair/vote loop) or to diff-coverage instrumentation runs.
286
+ retry_flaky: bool = True
129
287
 
130
288
 
131
289
  class GatesConfig(BaseModel):
@@ -137,20 +295,149 @@ class GatesConfig(BaseModel):
137
295
  block_failed_commands: bool = True
138
296
 
139
297
 
298
+ class PlanningConfig(BaseModel):
299
+ # In non-interactive flows (stdin not a TTY), unanswered blocking questions from
300
+ # spec_writer are converted to open assumptions so plan approval does not stall.
301
+ auto_convert_blocking_questions_in_noninteractive: bool = True
302
+
303
+
304
+ class CorpusConfig(BaseModel):
305
+ """Advisory mixed-corpus side index (docs, PDFs, images)."""
306
+
307
+ enabled: bool = True
308
+ paths: List[str] = Field(default_factory=lambda: ["docs", "README.md"])
309
+ llm_enrichment: bool = False
310
+ vision_captions: bool = False
311
+ write_html: bool = False
312
+ auto_refresh_on_verify: bool = True
313
+
314
+
315
+ class EmbeddingsConfig(BaseModel):
316
+ """Opt-in local symbol embeddings for semantic graph search."""
317
+
318
+ enabled: bool = False
319
+ model_name: str = "hash-v1"
320
+
321
+
322
+ class IndexingConfig(BaseModel):
323
+ """Repo-map / symbol-index enhancements.
324
+
325
+ ``lsp_refs`` opts into spawning detected language servers (pyright, tsserver,
326
+ gopls, rust-analyzer) for dead-symbol confirmation and precise MCP impact.
327
+ Off by default — process cost and server variance make this opt-in only.
328
+
329
+ ``auto_refresh`` enables best-effort incremental map refresh from the
330
+ post-tool-use hook after agent file edits (never blocks the agent on failure).
331
+ ``auto_refresh_max_files`` skips refresh when a single hook reports more
332
+ changed paths than this guard (large refactors should run ``dev map``).
333
+
334
+ ``write_graph_html`` writes the interactive graph visualizer alongside the
335
+ code graph during ``dev map`` (off by default — HTML can be large).
336
+ """
337
+
338
+ lsp_refs: bool = False
339
+ auto_refresh: bool = True
340
+ auto_refresh_max_files: int = 40
341
+ # Serialized liveness debt is bounded independently from the uncapped graph.
342
+ # The defaults support large repositories while the validation ceilings prevent
343
+ # accidental multi-hundred-megabyte repo_map.json artifacts.
344
+ repo_map_liveness_cap: int = Field(default=20_000, ge=1, le=100_000)
345
+ repo_map_unwired_cap: int = Field(default=5_000, ge=1, le=100_000)
346
+ repo_map_dependents_cap: int = Field(default=1_024, ge=1, le=4_096)
347
+ # When true, ``dev map`` also writes ``.devcouncil/graph/graph.html``.
348
+ write_graph_html: bool = False
349
+ # Compact (indent=None) compatibility export for ``code_graph.json`` (default on).
350
+ # SQLite remains the canonical store; JSON is a size-sensitive export boundary.
351
+ compact_graph_json: bool = True
352
+ build_isolation: Literal["hybrid"] = "hybrid"
353
+ build_stall_timeout_seconds: float = Field(default=90.0, gt=0.0, le=3600.0)
354
+ build_total_timeout_seconds: float = Field(default=900.0, gt=0.0, le=86400.0)
355
+ # Wall-clock bound for semantic edge enrichment during graph assembly; on
356
+ # expiry enrichment keeps partial results (meta.semantic_enrich_partial)
357
+ # instead of hanging on very large repos.
358
+ semantic_enrich_timeout_seconds: float = Field(default=120.0, gt=0.0, le=3600.0)
359
+ graph_json_max_bytes: int = Field(
360
+ default=128 * 1024 * 1024,
361
+ ge=1024 * 1024,
362
+ le=2 * 1024 * 1024 * 1024,
363
+ )
364
+ # When unreachable_files / liveness code-file count exceeds this ratio,
365
+ # treat unreachable BFS as unreliable (static-analysis blind spots) and
366
+ # omit the flood. ``0`` disables the ratio check. Empty entry roots still
367
+ # fail-soft independently.
368
+ unreachable_unreliable_ratio: float = Field(default=0.25, ge=0.0, le=1.0)
369
+ entry_roots: List[str] = Field(default_factory=list)
370
+ corpus: CorpusConfig = Field(default_factory=CorpusConfig)
371
+ embeddings: EmbeddingsConfig = Field(default_factory=EmbeddingsConfig)
372
+
373
+ @model_validator(mode="after")
374
+ def _validate_build_timeouts(self) -> "IndexingConfig":
375
+ if self.build_total_timeout_seconds < self.build_stall_timeout_seconds:
376
+ raise ValueError(
377
+ "build_total_timeout_seconds must be at least build_stall_timeout_seconds"
378
+ )
379
+ return self
380
+
381
+
382
+ class CodeIntelligenceDebugConfig(BaseModel):
383
+ """Debugger discovery/execution consent and output safety."""
384
+
385
+ auto_discover: bool = False
386
+ redact_values: bool = True
387
+ max_value_chars: int = 4096
388
+
389
+
390
+ class CodeIntelligenceConfig(BaseModel):
391
+ """Transactional code graph, native synchronization, and debugger settings."""
392
+
393
+ enabled: bool = True
394
+ auto_sync: bool = True
395
+ debounce_ms: int = 750
396
+ reconcile_seconds: int = 60
397
+ query_freshness_timeout_ms: int = 2000
398
+ allow_polling_fallback: bool = True
399
+ # Bounded wait when acquiring ``writer.lock`` for full/isolated builds and when
400
+ # re-acquiring after yielding to a supervised child. Concurrent watchers retry
401
+ # with exponential backoff instead of failing the first busy probe.
402
+ writer_lease_timeout_seconds: float = Field(default=30.0, gt=0.0, le=600.0)
403
+ # Shorter wait for ``sync_now`` / watchers so MCP freshness probes stay snappy;
404
+ # pending paths remain queued when the lease stays busy past this budget.
405
+ writer_lease_sync_timeout_seconds: float = Field(default=5.0, gt=0.0, le=120.0)
406
+ debug: CodeIntelligenceDebugConfig = Field(default_factory=CodeIntelligenceDebugConfig)
407
+
408
+
409
+ class StopGateConfig(BaseModel):
410
+ """Unified Stop / SubagentStop gate (claim checks + active-task verify).
411
+
412
+ ``mode``: ``off`` disables the gate; ``assist`` surfaces failures in
413
+ ``systemMessage`` but allows stop; ``block`` reinjects corrective JSON.
414
+ """
415
+
416
+ mode: str = "off"
417
+ check_claims: bool = True
418
+ verify_active_task: bool = True
419
+ max_blocks: int = 2
420
+ per_command_timeout: int = 90
421
+ total_timeout: int = 120
422
+ notify_on_pass: bool = False
423
+ verify_cache_minutes: int = 5
424
+
425
+
140
426
  class ExecutionConfig(BaseModel):
141
427
  default_executor: str = "manual"
142
428
  max_repair_attempts: int = 3
143
429
  checkpoint_before_each_task: bool = True
144
- command_timeout: int = 300
430
+ command_timeout: int = 900
145
431
  stream_cli_output: bool = False
146
432
  # Default lifetime of an MCP task lease. A crashed/disconnected agent's lease
147
433
  # auto-expires after this, so the task frees up without a human running force.
148
434
  lease_ttl_seconds: int = 1800
149
- # When true, the post-task coding-CLI hook runs deterministic verification of the
150
- # active task (and records gaps) instead of only printing a reminder. Off by default
151
- # so hooks stay fast/cheap unless a team opts in.
435
+ stop_gate: StopGateConfig = Field(default_factory=StopGateConfig)
436
+ # Deprecated alias: true iff stop_gate.mode != off and verify_active_task.
437
+ # Migrated in load_config; kept for hook compatibility.
152
438
  verify_on_post_task: bool = False
153
439
  cursor_resume_mode: str = "off"
440
+ grok_resume_mode: str = "off"
154
441
  coding_cli_probe_order: List[str] = Field(default_factory=list)
155
442
  # Opt-in scope gate for executors WITHOUT a pre-write hook (CLI subprocesses that write
156
443
  # directly to disk). When true, DevCouncil re-checks every file a coding-CLI subprocess
@@ -159,6 +446,21 @@ class ExecutionConfig(BaseModel):
159
446
  # of only being flagged post-verify by orphan_diff. Off by default (it reverts the
160
447
  # agent's writes; teams opt in once their plans declare planned_files reliably).
161
448
  enforce_file_scope_pre_verify: bool = False
449
+ # Retries when a coding-CLI subprocess fails with a TRANSIENT network/provider
450
+ # error ("Connection closed mid-response", 429/5xx, overloaded). Such a failure
451
+ # says nothing about the task; without a retry it ends the task `blocked` and
452
+ # burns a repair attempt on a non-code problem. 0 disables the retry.
453
+ transient_retry_attempts: int = 2
454
+ # At task checkout, regenerate ``.devcouncil/repo_map.json`` when it is stale
455
+ # (HEAD / file-set fingerprint mismatch) before the prompt and liveness baseline
456
+ # are built. Disable to keep checkout cheap on huge repos where remap is costly.
457
+ refresh_stale_map_on_checkout: bool = True
458
+ refresh_stale_map_on_verify: bool = True
459
+ # UserPromptSubmit skips the status line when a compact briefing was injected
460
+ # within this many seconds (0 disables dedupe).
461
+ skip_prompt_status_after_compact_seconds: int = 60
462
+ # PreCompact emits a user-only systemMessage when the disk snapshot is saved.
463
+ compact_snapshot_toast: bool = True
162
464
 
163
465
 
164
466
  class PrivacyConfig(BaseModel):
@@ -167,6 +469,19 @@ class PrivacyConfig(BaseModel):
167
469
  store_prompts_locally: bool = True
168
470
 
169
471
 
472
+ class TelemetryConfig(BaseModel):
473
+ """Knobs for the local cost/usage telemetry (ledger lives under .devcouncil/logs/).
474
+
475
+ ``cost_budget_usd`` is an advisory spend budget in USD for cumulative model-call
476
+ cost. ``None`` (default) disables budgeting. WARN-ONLY by design: crossing the
477
+ budget emits a ``logger.warning`` when usage is recorded and is surfaced by
478
+ ``dev cost budget``, but it never blocks a run. Set/cleared via
479
+ ``dev cost budget --set X`` / ``dev cost budget --clear``.
480
+ """
481
+
482
+ cost_budget_usd: float | None = Field(default=None, ge=0.0)
483
+
484
+
170
485
  class AgentFlowIntegrationConfig(BaseModel):
171
486
  enabled: bool = False
172
487
  trace_path: str = ".devcouncil/logs/traces.jsonl"
@@ -211,12 +526,25 @@ class CursorIntegrationConfig(BaseModel):
211
526
  enabled: bool = False
212
527
  config_path: str = ".cursor/mcp.json"
213
528
  hooks_path: str = ".cursor/hooks.json"
529
+ headless_force: bool | None = None
530
+
531
+
532
+ class GrokIntegrationConfig(BaseModel):
533
+ enabled: bool = False
534
+ config_path: str = ".grok/config.toml"
214
535
 
215
536
 
216
537
  class AiderIntegrationConfig(BaseModel):
217
538
  enabled: bool = False
218
539
 
219
540
 
541
+ class ClaudeIntegrationConfig(BaseModel):
542
+ enabled: bool = False
543
+ scope: str = "local"
544
+ write_gate: bool = False
545
+ settings_path: str = ".claude/settings.local.json"
546
+
547
+
220
548
  class CliAgentProfileConfig(BaseModel):
221
549
  description: str = ""
222
550
  timeout_seconds: int | None = None
@@ -231,6 +559,20 @@ class CliAgentProfileConfig(BaseModel):
231
559
  extra_args: List[str] = Field(default_factory=list)
232
560
  permission_mode: str | None = None
233
561
  model: str | None = None
562
+ # Claude Code only: enable Anthropic's advisor server tool via ``--advisor`` /
563
+ # SDK ``extra_args={"advisor": ...}`` / interactive ``advisorModel``. Opt-in;
564
+ # recommended pairing is main ``sonnet`` + ``advisor_model: opus``. Ignored for
565
+ # non-Claude executors. Requires Claude Code >= 2.1.98 and the Anthropic API.
566
+ advisor_model: str | None = None
567
+ # Per-profile environment overrides merged into the agent subprocess (and passed to
568
+ # the in-process claude-sdk executor). This is how a profile redirects the Claude
569
+ # Code harness at an alternative Anthropic-compatible endpoint (local proxy,
570
+ # LiteLLM-fronted Ollama, OpenRouter, ...) without touching the base agent spec:
571
+ # env: {ANTHROPIC_BASE_URL: "http://127.0.0.1:4000", ANTHROPIC_AUTH_TOKEN: "..."}
572
+ # Values may hold secrets: manifests/logs record only the KEY NAMES, never values.
573
+ # DevCouncil's own variables (DEVCOUNCIL_*) are applied after these and cannot be
574
+ # masked by a profile.
575
+ env: Dict[str, str] = Field(default_factory=dict)
234
576
 
235
577
 
236
578
  class CustomCliAgentConfig(BaseModel):
@@ -263,7 +605,9 @@ class IntegrationsConfig(BaseModel):
263
605
  agent_flow: AgentFlowIntegrationConfig = Field(default_factory=AgentFlowIntegrationConfig)
264
606
  code_review_graph: CodeReviewGraphIntegrationConfig = Field(default_factory=CodeReviewGraphIntegrationConfig)
265
607
  live_review: LiveReviewIntegrationConfig = Field(default_factory=LiveReviewIntegrationConfig)
608
+ claude: ClaudeIntegrationConfig = Field(default_factory=ClaudeIntegrationConfig)
266
609
  cursor: CursorIntegrationConfig = Field(default_factory=CursorIntegrationConfig)
610
+ grok: GrokIntegrationConfig = Field(default_factory=GrokIntegrationConfig)
267
611
  aider: AiderIntegrationConfig = Field(default_factory=AiderIntegrationConfig)
268
612
  antigravity: AntigravityIntegrationConfig = Field(default_factory=AntigravityIntegrationConfig)
269
613
  warp: WarpIntegrationConfig = Field(default_factory=WarpIntegrationConfig)
@@ -271,6 +615,44 @@ class IntegrationsConfig(BaseModel):
271
615
  cli_agents: CliAgentsIntegrationConfig = Field(default_factory=CliAgentsIntegrationConfig)
272
616
 
273
617
 
618
+
619
+ def _attach_repo_map_unwired_cap_bounds() -> None:
620
+ """Expose AC-2.1 FieldInfo.ge/le and a class attribute for hasattr checks.
621
+
622
+ Pydantic v2 keeps constraints in ``FieldInfo.metadata`` and does not put model
623
+ fields on the class ``__dict__``, so ``hasattr(IndexingConfig, name)`` is False
624
+ unless we attach the field explicitly for static acceptance checks.
625
+ """
626
+ field = IndexingConfig.model_fields.get("repo_map_unwired_cap")
627
+ if field is None:
628
+ return
629
+ ge = getattr(field, "ge", None)
630
+ le = getattr(field, "le", None)
631
+ if ge is None or le is None:
632
+ ge = next((m.ge for m in field.metadata if isinstance(m, Ge)), 1)
633
+ le = next((m.le for m in field.metadata if isinstance(m, Le)), 100_000)
634
+ try:
635
+ object.__setattr__(field, "ge", ge)
636
+ object.__setattr__(field, "le", le)
637
+ except (AttributeError, TypeError):
638
+ class _FieldProxy:
639
+ def __init__(self, wrapped, ge_value, le_value):
640
+ object.__setattr__(self, "_wrapped", wrapped)
641
+ object.__setattr__(self, "ge", ge_value)
642
+ object.__setattr__(self, "le", le_value)
643
+
644
+ def __getattr__(self, name):
645
+ return getattr(self._wrapped, name)
646
+
647
+ field = _FieldProxy(field, ge, le) # type: ignore[assignment]
648
+ IndexingConfig.model_fields["repo_map_unwired_cap"] = field
649
+ # Pydantic v2: model fields are not class attributes; AC-2.1 uses hasattr().
650
+ if not hasattr(IndexingConfig, "repo_map_unwired_cap"):
651
+ setattr(IndexingConfig, "repo_map_unwired_cap", field)
652
+
653
+
654
+ _attach_repo_map_unwired_cap_bounds()
655
+
274
656
  class KnowledgeConfig(BaseModel):
275
657
  """Ingested knowledge (Open Knowledge Format bundles + a project design.md) that gets
276
658
  injected into planning/council/task prompts.
@@ -295,6 +677,65 @@ class ProviderConfig(BaseModel):
295
677
  data_collection: str = "deny"
296
678
 
297
679
 
680
+ class SemanticCacheConfig(BaseModel):
681
+ """Semantic similarity cache (FAISS + embeddings)."""
682
+
683
+ enabled: bool = True
684
+ backend: str = "faiss"
685
+ similarity_threshold: float = 0.92
686
+ ood_threshold: float = 0.75
687
+ margin_threshold: float = 0.03
688
+ ttl_seconds: int = 3600
689
+ max_entries: int = 10_000
690
+ namespace: str = "devcouncil"
691
+ exploration_rate: float = 0.02
692
+
693
+
694
+ class SemanticRouterConfig(BaseModel):
695
+ """Complexity-based model tier routing (opt-in; local Ollama only today)."""
696
+
697
+ enabled: bool = False
698
+ complexity_threshold: float = 0.45
699
+ small_model: str | None = None
700
+ large_model: str | None = None
701
+
702
+
703
+ class SemanticCompressorConfig(BaseModel):
704
+ """Long-context compression before LLM calls."""
705
+
706
+ enabled: bool = True
707
+ token_budget: int = 2048
708
+ top_k: int = 8
709
+ chunk_token_size: int = 256
710
+ chunk_overlap: int = 32
711
+ min_chunk_score: float = 0.35
712
+ mmr_lambda: float = 0.7
713
+ min_chars: int = 8000
714
+
715
+
716
+ class SemanticEmbeddingConfig(BaseModel):
717
+ model_name: str = "sentence-transformers/all-MiniLM-L6-v2"
718
+ dimension: int = 384
719
+ device: str = "cpu"
720
+ batch_size: int = 32
721
+ normalize: bool = True
722
+
723
+
724
+ class SemanticLayerConfig(BaseModel):
725
+ """Optional semantic cache / routing / compression for LLM calls.
726
+
727
+ Disabled by default. When ``enabled: true``, DevCouncil wraps the existing
728
+ ``ModelRouter`` path transparently — no caller changes required. Missing
729
+ optional deps (``uv sync --group semantic``) degrade to today's behavior.
730
+ """
731
+
732
+ enabled: bool = False
733
+ cache: SemanticCacheConfig = Field(default_factory=SemanticCacheConfig)
734
+ router: SemanticRouterConfig = Field(default_factory=SemanticRouterConfig)
735
+ compressor: SemanticCompressorConfig = Field(default_factory=SemanticCompressorConfig)
736
+ embedding: SemanticEmbeddingConfig = Field(default_factory=SemanticEmbeddingConfig)
737
+
738
+
298
739
  class DevCouncilConfig(BaseModel):
299
740
  """Top-level validated configuration."""
300
741
 
@@ -303,11 +744,16 @@ class DevCouncilConfig(BaseModel):
303
744
  provider: ProviderConfig = Field(default_factory=ProviderConfig)
304
745
  commands: CommandsConfig = Field(default_factory=CommandsConfig)
305
746
  gates: GatesConfig = Field(default_factory=GatesConfig)
747
+ planning: PlanningConfig = Field(default_factory=PlanningConfig)
306
748
  execution: ExecutionConfig = Field(default_factory=ExecutionConfig)
749
+ indexing: IndexingConfig = Field(default_factory=IndexingConfig)
750
+ code_intelligence: CodeIntelligenceConfig = Field(default_factory=CodeIntelligenceConfig)
307
751
  verification: VerificationConfig = Field(default_factory=VerificationConfig)
308
752
  privacy: PrivacyConfig = Field(default_factory=PrivacyConfig)
753
+ telemetry: TelemetryConfig = Field(default_factory=TelemetryConfig)
309
754
  integrations: IntegrationsConfig = Field(default_factory=IntegrationsConfig)
310
755
  knowledge: KnowledgeConfig = Field(default_factory=KnowledgeConfig)
756
+ semantic_layer: SemanticLayerConfig = Field(default_factory=SemanticLayerConfig)
311
757
 
312
758
 
313
759
  # Memoized parsed configs keyed by resolved config path. Each entry stores the
@@ -327,7 +773,7 @@ def load_config(project_root: Path = Path(".")) -> DevCouncilConfig:
327
773
  file's mtime/size changes, so repeated callers avoid redundant disk reads while
328
774
  still picking up a rewritten config.
329
775
  """
330
- import yaml # type: ignore[import-untyped]
776
+ import yaml
331
777
 
332
778
  config_path = project_root / ".devcouncil" / "config.yaml"
333
779
  try:
@@ -357,11 +803,52 @@ def load_config(project_root: Path = Path(".")) -> DevCouncilConfig:
357
803
  ) from exc
358
804
 
359
805
  config = DevCouncilConfig.model_validate(raw)
806
+ _migrate_verify_on_post_task(config, raw)
360
807
  _CONFIG_CACHE[cache_key] = (signature, config)
361
808
  logger.debug("Config loaded: provider=%s", config.models.provider)
362
809
  return config
363
810
 
364
811
 
812
+ def _migrate_verify_on_post_task(config: DevCouncilConfig, raw: dict) -> None:
813
+ """Sync deprecated ``verify_on_post_task`` with ``execution.stop_gate``."""
814
+ raw_execution = raw.get("execution")
815
+ execution_raw: dict = raw_execution if isinstance(raw_execution, dict) else {}
816
+ stop_raw = execution_raw.get("stop_gate") if isinstance(execution_raw.get("stop_gate"), dict) else {}
817
+ has_stop_gate = bool(stop_raw)
818
+ sg = config.execution.stop_gate
819
+
820
+ if has_stop_gate:
821
+ # Canonical knob is stop_gate; derive alias for legacy readers.
822
+ config.execution.verify_on_post_task = (
823
+ sg.mode.strip().lower() != "off" and sg.verify_active_task
824
+ )
825
+ return
826
+
827
+ if config.execution.verify_on_post_task:
828
+ sg.mode = "assist"
829
+ sg.verify_active_task = True
830
+
831
+
832
+ def role_runs_on_local_provider(config: DevCouncilConfig, role: str) -> bool:
833
+ """True when ``role`` resolves to a cost-free LOCAL provider (Ollama).
834
+
835
+ Honors a per-role ``provider`` override, falling back to ``models.provider``.
836
+ Used to auto-tune verification knobs (acceptance-check samples / per-criterion
837
+ compilation / reviewer voting): extra calls are free on a local monitor and are
838
+ exactly what makes a weak local model's verdicts trustworthy, while a paid cloud
839
+ monitor keeps the single-shot defaults. Never raises — an unrecognized provider
840
+ just resolves to False (cloud behavior).
841
+ """
842
+ try:
843
+ from devcouncil.llm.provider import validate_model_provider
844
+
845
+ role_cfg = config.models.roles.get(role)
846
+ provider = (role_cfg.provider if role_cfg and role_cfg.provider else None) or config.models.provider
847
+ return validate_model_provider(provider) == "ollama"
848
+ except Exception:
849
+ return False
850
+
851
+
365
852
  def provider_api_key_env_var(provider: str = "openrouter") -> str:
366
853
  normalized = provider.strip().lower().replace("-", "").replace("_", "")
367
854
  env_map = {
@@ -423,7 +910,7 @@ def get_gcloud_access_token() -> str | None:
423
910
  if not executable:
424
911
  return None
425
912
  try:
426
- token = subprocess.check_output(
913
+ gcloud_output = subprocess.check_output(
427
914
  [executable, "auth", "print-access-token"],
428
915
  stderr=subprocess.STDOUT,
429
916
  text=True,
@@ -433,7 +920,7 @@ def get_gcloud_access_token() -> str | None:
433
920
  ).strip()
434
921
  except Exception:
435
922
  return None
436
- return token or None
923
+ return gcloud_output or None
437
924
 
438
925
 
439
926
  # Cached gcloud access token + monotonic expiry. gcloud tokens last ~60 min, so the
@@ -461,10 +948,10 @@ def get_cached_gcloud_access_token() -> str | None:
461
948
  if cached is not None and time.monotonic() < cached[1]:
462
949
  return cached[0]
463
950
 
464
- token = get_gcloud_access_token()
465
- if token:
466
- _gcloud_token_cache = (token, time.monotonic() + _GCLOUD_TOKEN_TTL_SECONDS)
467
- return token
951
+ fresh = get_gcloud_access_token()
952
+ if fresh:
953
+ _gcloud_token_cache = (fresh, time.monotonic() + _GCLOUD_TOKEN_TTL_SECONDS)
954
+ return fresh
468
955
 
469
956
 
470
957
  def get_api_key(provider: str = "openrouter", project_root: Path = Path(".")) -> str:
@@ -481,7 +968,12 @@ def get_api_key(provider: str = "openrouter", project_root: Path = Path(".")) ->
481
968
  # OLLAMA_API_KEY still flows through above if present.
482
969
  return ""
483
970
  if not key:
484
- logger.warning("API key not found for provider %s (env var %s)", provider, env_var)
971
+ # DEBUG, not WARNING: several callers (e.g. `dev verify` building an optional
972
+ # LLM router) treat a missing key as an expected, gracefully-degraded state and
973
+ # swallow the ValueError. The raise below already carries the full actionable
974
+ # message for callers that surface it; a WARNING here polluted agent-facing
975
+ # `--json` output whenever the console handler's stream was captured.
976
+ logger.debug("Provider credentials not configured for %s", provider)
485
977
  extra = (
486
978
  " You can also authenticate with 'gcloud auth login' for vertexai."
487
979
  if _normalized_provider_name(provider) == "vertexai"