devcouncil 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (366) hide show
  1. package/README.md +92 -33
  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 +56 -40
  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 +964 -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 +281 -0
  219. package/src/devcouncil/integrations/mcp/handlers/graph.py +34 -0
  220. package/src/devcouncil/integrations/mcp/handlers/handoff.py +53 -0
  221. package/src/devcouncil/integrations/mcp/handlers/knowledge.py +28 -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 +87 -0
  229. package/src/devcouncil/integrations/mcp/handlers/read.py +103 -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 +69 -0
  233. package/src/devcouncil/integrations/mcp/handlers/scope.py +56 -0
  234. package/src/devcouncil/integrations/mcp/handlers/status.py +199 -0
  235. package/src/devcouncil/integrations/mcp/handlers/task.py +88 -0
  236. package/src/devcouncil/integrations/mcp/handlers/tool_specs.py +922 -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 +53 -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 +325 -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/summary.py +21 -3
  255. package/src/devcouncil/live/tasks.py +12 -3
  256. package/src/devcouncil/live/transcripts.py +69 -2
  257. package/src/devcouncil/llm/cache.py +5 -6
  258. package/src/devcouncil/llm/model_defaults.yaml +10 -10
  259. package/src/devcouncil/llm/provider.py +647 -73
  260. package/src/devcouncil/llm/router.py +271 -46
  261. package/src/devcouncil/llm/semantic_bridge.py +614 -0
  262. package/src/devcouncil/optimization/gepa_agent.py +6 -4
  263. package/src/devcouncil/optimization/skillopt.py +9 -5
  264. package/src/devcouncil/planning/arbiter_service.py +12 -3
  265. package/src/devcouncil/planning/correction_manifest.py +107 -10
  266. package/src/devcouncil/planning/plan_difficulty.py +69 -0
  267. package/src/devcouncil/planning/plan_service.py +5 -2
  268. package/src/devcouncil/planning/planned_files_reconcile.py +191 -0
  269. package/src/devcouncil/planning/prompt_enhancer_service.py +6 -5
  270. package/src/devcouncil/planning/question_conversion.py +56 -0
  271. package/src/devcouncil/planning/spec_service.py +9 -3
  272. package/src/devcouncil/repo/ci_scaffold.py +197 -1
  273. package/src/devcouncil/repo/gitignore.py +1 -2
  274. package/src/devcouncil/reporting/evidence_export.py +124 -0
  275. package/src/devcouncil/reporting/evidence_html.py +210 -0
  276. package/src/devcouncil/reporting/json_report.py +16 -12
  277. package/src/devcouncil/reporting/markdown_report.py +38 -9
  278. package/src/devcouncil/reporting/mcp_resources.py +142 -0
  279. package/src/devcouncil/reporting/report_builder.py +40 -4
  280. package/src/devcouncil/reporting/task_provenance.py +42 -0
  281. package/src/devcouncil/reporting/verdict.py +75 -0
  282. package/src/devcouncil/skills/library/README.md +1 -0
  283. package/src/devcouncil/skills/library/devcouncil-hero-loop.md +109 -0
  284. package/src/devcouncil/skills/library/devcouncil-verification.md +109 -0
  285. package/src/devcouncil/skills/library/devcouncil.md +93 -0
  286. package/src/devcouncil/skills/registry.py +43 -12
  287. package/src/devcouncil/storage/db.py +57 -11
  288. package/src/devcouncil/storage/models.py +6 -0
  289. package/src/devcouncil/storage/native.py +5 -3
  290. package/src/devcouncil/storage/repositories.py +50 -18
  291. package/src/devcouncil/telemetry/context.py +28 -0
  292. package/src/devcouncil/telemetry/cost.py +4 -5
  293. package/src/devcouncil/telemetry/logging_setup.py +78 -11
  294. package/src/devcouncil/telemetry/model_pricing.yaml +7 -0
  295. package/src/devcouncil/telemetry/stages.py +27 -2
  296. package/src/devcouncil/telemetry/tracker.py +50 -13
  297. package/src/devcouncil/ui/dashboard.py +120 -8
  298. package/src/devcouncil/utils/fsio.py +58 -0
  299. package/src/devcouncil/utils/git_snapshot.py +112 -0
  300. package/src/devcouncil/utils/json_persist.py +53 -0
  301. package/src/devcouncil/utils/proc.py +89 -0
  302. package/src/devcouncil/verification/acceptance_compiler.py +36 -13
  303. package/src/devcouncil/verification/ad_hoc_check.py +95 -3
  304. package/src/devcouncil/verification/checks/__init__.py +41 -0
  305. package/src/devcouncil/verification/checks/acceptance.py +39 -0
  306. package/src/devcouncil/verification/checks/acceptance_corpus.py +194 -0
  307. package/src/devcouncil/verification/checks/acceptance_evidence.py +239 -0
  308. package/src/devcouncil/verification/checks/command_evidence.py +148 -0
  309. package/src/devcouncil/verification/checks/compiled_acceptance.py +179 -0
  310. package/src/devcouncil/verification/checks/corpus_stale.py +124 -0
  311. package/src/devcouncil/verification/checks/corpus_verification.py +9 -0
  312. package/src/devcouncil/verification/checks/dead_symbols.py +360 -0
  313. package/src/devcouncil/verification/checks/diff_coverage_gate.py +101 -0
  314. package/src/devcouncil/verification/checks/doc_code_ref.py +79 -0
  315. package/src/devcouncil/verification/checks/liveness_ratchet.py +336 -0
  316. package/src/devcouncil/verification/checks/orphan_diff.py +104 -0
  317. package/src/devcouncil/verification/checks/planned_files.py +98 -0
  318. package/src/devcouncil/verification/checks/semantic_diff.py +241 -0
  319. package/src/devcouncil/verification/checks/stale_map.py +80 -0
  320. package/src/devcouncil/verification/checks/stub_scan.py +71 -0
  321. package/src/devcouncil/verification/checks/subsystem_boundary.py +103 -0
  322. package/src/devcouncil/verification/checks/wiring.py +216 -0
  323. package/src/devcouncil/verification/claims/__init__.py +23 -0
  324. package/src/devcouncil/verification/claims/checks.py +395 -0
  325. package/src/devcouncil/verification/claims/mapper.py +168 -0
  326. package/src/devcouncil/verification/claims/models.py +39 -0
  327. package/src/devcouncil/verification/claims/transcript.py +92 -0
  328. package/src/devcouncil/verification/claims/verdict.py +88 -0
  329. package/src/devcouncil/verification/command_evidence.py +170 -0
  330. package/src/devcouncil/verification/command_malformation.py +147 -0
  331. package/src/devcouncil/verification/command_runner.py +164 -0
  332. package/src/devcouncil/verification/coverage_measurement.py +292 -0
  333. package/src/devcouncil/verification/diff_coverage.py +151 -0
  334. package/src/devcouncil/verification/difficulty.py +296 -0
  335. package/src/devcouncil/verification/effort_heuristics.py +178 -0
  336. package/src/devcouncil/verification/gap_ids.py +63 -0
  337. package/src/devcouncil/verification/gate_cache.py +194 -0
  338. package/src/devcouncil/verification/gate_selector.py +344 -0
  339. package/src/devcouncil/verification/git_diff_fallback.py +272 -0
  340. package/src/devcouncil/verification/implementation_reviewer.py +13 -0
  341. package/src/devcouncil/verification/incremental_check.py +241 -0
  342. package/src/devcouncil/verification/next_actions.py +60 -1
  343. package/src/devcouncil/verification/rigor_analytics.py +130 -0
  344. package/src/devcouncil/verification/sandbox.py +38 -11
  345. package/src/devcouncil/verification/stub_detector.py +369 -0
  346. package/src/devcouncil/verification/test_resolver.py +67 -1
  347. package/src/devcouncil/verification/verifier.py +137 -1666
  348. package/src/devcouncil/verification/verify_orchestration.py +610 -0
  349. package/src/devcouncil/verification/verify_setup.py +176 -0
  350. package/src/devcouncil/verification/wiki_refresh.py +208 -0
  351. package/src/semantic_layer/__init__.py +58 -0
  352. package/src/semantic_layer/benchmark.py +75 -0
  353. package/src/semantic_layer/cache.py +290 -0
  354. package/src/semantic_layer/compressor.py +137 -0
  355. package/src/semantic_layer/config.py +75 -0
  356. package/src/semantic_layer/embeddings.py +69 -0
  357. package/src/semantic_layer/llm_backends.py +99 -0
  358. package/src/semantic_layer/pipeline.py +111 -0
  359. package/src/semantic_layer/router.py +128 -0
  360. package/src/semantic_layer/tuner.py +72 -0
  361. package/uv.lock +973 -9
  362. package/src/devcouncil/artifacts/migrations.py +0 -20
  363. package/src/devcouncil/artifacts/schemas.py +0 -23
  364. package/src/devcouncil/artifacts/serializer.py +0 -21
  365. package/src/devcouncil/integrations/gitnexus.py +0 -70
  366. package/src/devcouncil/integrations/graphify.py +0 -34
@@ -0,0 +1,610 @@
1
+ """Core verify_task gate orchestration extracted from Verifier."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import Any, List, Literal, TYPE_CHECKING, cast
7
+
8
+ from devcouncil.domain.evidence import CommandResult, DiffEvidence
9
+ from devcouncil.domain.gap import Gap
10
+ from devcouncil.domain.requirement import Requirement
11
+ from devcouncil.domain.task import Task
12
+ from devcouncil.live.cards import unresolved_blocking_cards
13
+ from devcouncil.telemetry.stages import log_step
14
+ from devcouncil.verification.checks.acceptance_evidence import map_acceptance_criteria_evidence
15
+ from devcouncil.verification.checks.compiled_acceptance import (
16
+ promote_demoted_failures_when_compiler_incomplete,
17
+ run_compiled_acceptance_checks,
18
+ )
19
+ from devcouncil.verification.checks.command_evidence import run_verification_commands
20
+ from devcouncil.verification.checks.diff_coverage_gate import run_diff_coverage_gate
21
+ from devcouncil.verification.checks.planned_files import (
22
+ detect_dependency_risk_gaps,
23
+ detect_no_work_gap,
24
+ detect_planned_file_gaps,
25
+ )
26
+ from devcouncil.verification.checks.orphan_diff import detect_orphan_diff_gaps
27
+ from devcouncil.verification.checks.stub_scan import detect_stub_gaps
28
+ from devcouncil.verification.effort_heuristics import detect_effort_anomalies
29
+ from devcouncil.verification.verify_setup import VerifyRunContext
30
+
31
+ if TYPE_CHECKING:
32
+ from devcouncil.verification.verifier import Verifier
33
+
34
+ logger = logging.getLogger(__name__)
35
+
36
+
37
+ def _verification_mode_label(evidence_to_save: list[Any], *, compiler_active: bool) -> str:
38
+ """Derive reported verification mode from TestEvidence proof modes, not compiler presence."""
39
+ from devcouncil.domain.evidence import TestEvidence
40
+
41
+ test_evidence = [item for item in evidence_to_save if isinstance(item, TestEvidence)]
42
+ if not test_evidence:
43
+ return "compiled" if compiler_active else "coarse"
44
+ modes = {item.mode for item in test_evidence if item.mode}
45
+ if modes & {"compiled", "vote"}:
46
+ return "compiled"
47
+ if "coarse" in modes:
48
+ return "compiled" if compiler_active else "coarse"
49
+ return "compiled" if compiler_active else "coarse"
50
+
51
+
52
+ async def run_verify_orchestration(
53
+ verifier: "Verifier",
54
+ task: Task,
55
+ requirements: List[Requirement],
56
+ ctx: VerifyRunContext,
57
+ compile_future: Any,
58
+ review_future: Any,
59
+ ) -> tuple[list[Gap], list[Any]]:
60
+ """Run all verification gates and return gaps plus evidence to persist."""
61
+ gaps: list[Gap] = []
62
+ evidence_to_save: list[Any] = []
63
+ _cfg = ctx.config
64
+ rigor = ctx.rigor
65
+ ac_repair_attempts = ctx.ac_repair_attempts
66
+ changed_files = ctx.changed_files
67
+ diff_content = ctx.diff_content
68
+ diff_empty = ctx.diff_empty
69
+ coverage_measured = False
70
+ coverage_skipped_reason: str | None = None
71
+
72
+ work_present = (not diff_empty) or verifier._task_produced_changes(task.id)
73
+
74
+ no_work_gap = detect_no_work_gap(
75
+ task=task,
76
+ work_present=work_present,
77
+ next_gap_id=verifier._next_gap_id,
78
+ )
79
+ if no_work_gap is not None:
80
+ gaps.append(no_work_gap)
81
+
82
+ if diff_content:
83
+ added_files, deleted_files = verifier._classify_change_paths(changed_files)
84
+ diff_ev = DiffEvidence(
85
+ task_id=task.id,
86
+ changed_files=changed_files,
87
+ added_files=added_files,
88
+ deleted_files=deleted_files,
89
+ diff_summary=f"Diff captured for {len(changed_files)} files.",
90
+ )
91
+ evidence_to_save.append(diff_ev)
92
+
93
+ planned_paths = {pf.path for pf in task.planned_files}
94
+ gaps.extend(detect_planned_file_gaps(
95
+ task=task,
96
+ changed_files=changed_files,
97
+ next_gap_id=verifier._next_gap_id,
98
+ ))
99
+
100
+ gaps.extend(detect_orphan_diff_gaps(
101
+ task=task,
102
+ changed_files=changed_files,
103
+ planned_paths=planned_paths,
104
+ diff_content=diff_content,
105
+ project_root=verifier.project_root,
106
+ get_untracked_files=verifier._get_untracked_files,
107
+ next_gap_id=verifier._next_gap_id,
108
+ classify_fn=verifier._classify_change_paths,
109
+ ))
110
+
111
+ log_step(
112
+ "verify/3: planned-file and orphan-diff checks",
113
+ project_root=verifier.project_root,
114
+ task_id=task.id,
115
+ gaps_so_far=len(gaps),
116
+ )
117
+ gaps.extend(verifier._check_semantic_diff(task, requirements))
118
+
119
+ gaps.extend(detect_dependency_risk_gaps(
120
+ task=task,
121
+ changed_files=changed_files,
122
+ planned_paths=planned_paths,
123
+ next_gap_id=verifier._next_gap_id,
124
+ ))
125
+
126
+ # Advisory architecture-drift gate: flag edits that cross non-neighbor subsystem
127
+ # boundaries the plan never declared. Non-blocking by default, reads only the repo
128
+ # map, and degrades to a no-op when the map is absent/stale.
129
+ boundary_cfg = getattr(getattr(_cfg, "verification", None), "subsystem_boundary", None)
130
+ if changed_files and (boundary_cfg is None or boundary_cfg.enabled):
131
+ from devcouncil.verification.checks.subsystem_boundary import (
132
+ detect_subsystem_boundary_gaps,
133
+ )
134
+
135
+ gaps.extend(detect_subsystem_boundary_gaps(
136
+ task=task,
137
+ changed_files=changed_files,
138
+ repo_map=verifier._load_repo_map(),
139
+ next_gap_id=verifier._next_gap_id,
140
+ blocking=bool(getattr(boundary_cfg, "blocking", False)),
141
+ ))
142
+
143
+ # Stale-map gate: advisory on easy/normal, blocking on hard. Runs before
144
+ # map-consuming gates so agents refresh before trusting neighbors/dependents.
145
+ if rigor.stale_map_enabled:
146
+ try:
147
+ from devcouncil.verification.checks.stale_map import detect_stale_map_gaps
148
+
149
+ gaps.extend(detect_stale_map_gaps(
150
+ task=task,
151
+ project_root=verifier.project_root,
152
+ next_gap_id=verifier._next_gap_id,
153
+ stale_map_enabled=rigor.stale_map_enabled,
154
+ stale_map_blocking=rigor.stale_map_blocking,
155
+ repo_map=verifier._load_repo_map(),
156
+ ))
157
+ except Exception:
158
+ logger.warning("stale-map gate failed; skipping", exc_info=True)
159
+ if rigor.stale_map_blocking:
160
+ gaps.append(Gap(
161
+ id=verifier._next_gap_id(task.id, "QGFAIL-stale_map"),
162
+ severity="medium",
163
+ gap_type="quality_gate_failed",
164
+ task_id=task.id,
165
+ description="Stale-map verification gate crashed; results are incomplete.",
166
+ evidence=["gate:stale_map"],
167
+ recommended_fix="Re-run verify after fixing the gate error; do not treat this pass as proven.",
168
+ blocking=False,
169
+ ))
170
+
171
+ if rigor.corpus_stale_enabled or rigor.doc_code_ref_enabled:
172
+ try:
173
+ from devcouncil.verification.checks.corpus_stale import (
174
+ detect_corpus_stale_gaps,
175
+ refresh_corpus_on_verify_if_needed,
176
+ )
177
+
178
+ refresh_corpus_on_verify_if_needed(verifier.project_root)
179
+ gaps.extend(detect_corpus_stale_gaps(
180
+ task=task,
181
+ project_root=verifier.project_root,
182
+ changed_files=changed_files,
183
+ next_gap_id=verifier._next_gap_id,
184
+ corpus_stale_enabled=rigor.corpus_stale_enabled,
185
+ corpus_stale_blocking=rigor.corpus_stale_blocking,
186
+ ))
187
+ except Exception:
188
+ logger.warning("corpus-stale gate failed; skipping", exc_info=True)
189
+ try:
190
+ from devcouncil.verification.checks.doc_code_ref import detect_doc_code_ref_gaps
191
+
192
+ gaps.extend(detect_doc_code_ref_gaps(
193
+ task=task,
194
+ project_root=verifier.project_root,
195
+ changed_files=changed_files,
196
+ diff_content=diff_content,
197
+ next_gap_id=verifier._next_gap_id,
198
+ doc_code_ref_enabled=rigor.doc_code_ref_enabled,
199
+ doc_code_ref_blocking=rigor.doc_code_ref_blocking,
200
+ ))
201
+ except Exception:
202
+ logger.warning("doc-code-ref gate failed; skipping", exc_info=True)
203
+ if rigor.acceptance_corpus_enabled:
204
+ try:
205
+ from devcouncil.verification.checks.acceptance_corpus import (
206
+ detect_acceptance_corpus_gaps,
207
+ )
208
+
209
+ gaps.extend(detect_acceptance_corpus_gaps(
210
+ task=task,
211
+ requirements=requirements,
212
+ project_root=verifier.project_root,
213
+ changed_files=changed_files,
214
+ diff_content=diff_content,
215
+ next_gap_id=verifier._next_gap_id,
216
+ acceptance_corpus_enabled=rigor.acceptance_corpus_enabled,
217
+ acceptance_corpus_blocking=rigor.acceptance_corpus_blocking,
218
+ ))
219
+ except Exception:
220
+ logger.warning("acceptance-corpus gate failed; skipping", exc_info=True)
221
+
222
+ log_step(
223
+ "verify/4: semantic diff and dependency checks",
224
+ project_root=verifier.project_root,
225
+ task_id=task.id,
226
+ gaps_so_far=len(gaps),
227
+ )
228
+ gaps.extend(detect_stub_gaps(
229
+ task=task,
230
+ diff_content=diff_content,
231
+ project_root=verifier.project_root,
232
+ stub_enabled=rigor.stub_enabled,
233
+ stub_blocking=rigor.stub_blocking,
234
+ next_gap_id=verifier._next_gap_id,
235
+ ))
236
+
237
+ # Unwired-file + dead-symbol gates (liveness): run right after placeholder scan so
238
+ # reachability findings sit next to incomplete-code findings in the repair loop.
239
+ # Failures on hard rigor emit an advisory quality_gate_failed so agents see
240
+ # the swallow instead of a silent pass.
241
+ unwired_files_flagged: set[str] = set()
242
+ liveness_baseline_status: str | None = None
243
+ try:
244
+ from devcouncil.verification.checks.wiring import detect_unwired_file_gaps
245
+
246
+ unwired_gaps = detect_unwired_file_gaps(
247
+ task=task,
248
+ project_root=verifier.project_root,
249
+ changed_files=changed_files,
250
+ diff_content=diff_content,
251
+ get_untracked_files=verifier._get_untracked_files,
252
+ next_gap_id=verifier._next_gap_id,
253
+ unwired_enabled=rigor.unwired_enabled,
254
+ unwired_blocking=rigor.unwired_blocking,
255
+ classify_fn=verifier._classify_change_paths,
256
+ )
257
+ for g in unwired_gaps:
258
+ if g.file and g.gap_type == "unwired_file":
259
+ unwired_files_flagged.add(g.file)
260
+ gaps.extend(unwired_gaps)
261
+ except Exception:
262
+ logger.warning("unwired-file gate failed; skipping", exc_info=True)
263
+ if rigor.unwired_blocking:
264
+ gaps.append(Gap(
265
+ id=verifier._next_gap_id(task.id, "QGFAIL-unwired_file"),
266
+ severity="medium",
267
+ gap_type="quality_gate_failed",
268
+ task_id=task.id,
269
+ description="Unwired-file verification gate crashed; results are incomplete.",
270
+ evidence=["gate:unwired_file"],
271
+ recommended_fix="Re-run verify after fixing the gate error; do not treat this pass as proven.",
272
+ blocking=False,
273
+ ))
274
+
275
+ try:
276
+ from devcouncil.verification.checks.dead_symbols import detect_dead_symbol_gaps
277
+
278
+ gaps.extend(detect_dead_symbol_gaps(
279
+ task=task,
280
+ project_root=verifier.project_root,
281
+ diff_content=diff_content,
282
+ next_gap_id=verifier._next_gap_id,
283
+ dead_symbol_enabled=rigor.dead_symbol_enabled,
284
+ dead_symbol_blocking=rigor.dead_symbol_blocking,
285
+ requirements=requirements,
286
+ unwired_files=unwired_files_flagged,
287
+ ))
288
+ except Exception:
289
+ logger.warning("dead-symbol gate failed; skipping", exc_info=True)
290
+ if rigor.dead_symbol_blocking:
291
+ gaps.append(Gap(
292
+ id=verifier._next_gap_id(task.id, "QGFAIL-dead_symbol"),
293
+ severity="medium",
294
+ gap_type="quality_gate_failed",
295
+ task_id=task.id,
296
+ description="Dead-symbol verification gate crashed; results are incomplete.",
297
+ evidence=["gate:dead_symbol"],
298
+ recommended_fix="Re-run verify after fixing the gate error; do not treat this pass as proven.",
299
+ blocking=False,
300
+ ))
301
+
302
+ # Liveness ratchet: existing code newly stranded vs checkout baseline.
303
+ if rigor.liveness_ratchet_enabled:
304
+ try:
305
+ from devcouncil.indexing.repo_mapper import RepoMapper
306
+ from devcouncil.verification.checks.liveness_ratchet import (
307
+ detect_liveness_regressions,
308
+ load_liveness_baseline,
309
+ )
310
+ from devcouncil.verification.checks.wiring import collect_added_files
311
+ from devcouncil.verification.stub_detector import added_lines_by_file
312
+
313
+ baseline = load_liveness_baseline(verifier.project_root, task.id)
314
+ if baseline is None:
315
+ liveness_baseline_status = "missing"
316
+ else:
317
+ liveness_baseline_status = "ok"
318
+ mapper = RepoMapper(verifier.project_root)
319
+ current = mapper.liveness_snapshot()
320
+ added = collect_added_files(
321
+ project_root=verifier.project_root,
322
+ changed_files=changed_files,
323
+ diff_content=diff_content,
324
+ get_untracked_files=verifier._get_untracked_files,
325
+ classify_fn=verifier._classify_change_paths,
326
+ )
327
+ by_file = added_lines_by_file(diff_content or "")
328
+ diff_lines = {
329
+ path: {ln for ln, _ in lines}
330
+ for path, lines in by_file.items()
331
+ }
332
+ gaps.extend(detect_liveness_regressions(
333
+ baseline,
334
+ current,
335
+ added,
336
+ task=task,
337
+ next_gap_id=verifier._next_gap_id,
338
+ blocking=rigor.liveness_ratchet_blocking,
339
+ diff_added_lines=diff_lines,
340
+ ))
341
+ except Exception:
342
+ logger.warning("liveness-ratchet gate failed; skipping", exc_info=True)
343
+ if rigor.liveness_ratchet_blocking:
344
+ gaps.append(Gap(
345
+ id=verifier._next_gap_id(task.id, "QGFAIL-liveness_ratchet"),
346
+ severity="medium",
347
+ gap_type="quality_gate_failed",
348
+ task_id=task.id,
349
+ description="Liveness-ratchet verification gate crashed; results are incomplete.",
350
+ evidence=["gate:liveness_ratchet"],
351
+ recommended_fix="Re-run verify after fixing the gate error; do not treat this pass as proven.",
352
+ blocking=False,
353
+ ))
354
+
355
+ if rigor.effort_enabled and diff_content:
356
+ for eff in detect_effort_anomalies(
357
+ task, diff_content, requirements,
358
+ min_added_lines_per_planned_file=rigor.min_added_lines_per_planned_file,
359
+ ):
360
+ gaps.append(Gap(
361
+ id=verifier._next_gap_id(task.id, f"EFFORT-{eff.file or eff.reason[:40]}"),
362
+ severity=cast(Literal["low", "medium", "high", "critical"], eff.severity),
363
+ gap_type="suspicious_effort",
364
+ task_id=task.id,
365
+ description=eff.detail,
366
+ evidence=[eff.reason],
367
+ recommended_fix=(
368
+ "Review whether the implementation actually fulfills the task scope; "
369
+ "complete the planned work (or restore removed tests), then re-verify."
370
+ ),
371
+ blocking=rigor.effort_blocking,
372
+ file=eff.file,
373
+ ))
374
+
375
+ compiler_active = bool(verifier.acceptance_compiler and diff_content and task.acceptance_criterion_ids)
376
+ log_step(
377
+ "verify/5: stub and effort heuristics",
378
+ project_root=verifier.project_root,
379
+ task_id=task.id,
380
+ compiler_active=compiler_active,
381
+ gaps_so_far=len(gaps),
382
+ )
383
+
384
+ cmd_run = run_verification_commands(
385
+ task=task,
386
+ commands_for_task=verifier._commands_for_task(task),
387
+ compiler_active=compiler_active,
388
+ command_applicable=verifier._command_applicable,
389
+ run_command=lambda cmd: verifier._run_command(cmd, task_id=task.id),
390
+ command_can_prove_acceptance=verifier._command_can_prove_acceptance,
391
+ command_is_malformed=verifier._command_is_malformed,
392
+ failure_location=verifier._failure_location,
393
+ is_quality_only_command=verifier._is_quality_only_command,
394
+ next_gap_id=verifier._next_gap_id,
395
+ retry_flaky=bool(_cfg.verification.retry_flaky) if _cfg is not None else True,
396
+ )
397
+ gaps.extend(cmd_run.gaps)
398
+ command_results: list[CommandResult] = list(cmd_run.command_results)
399
+ evidence_results: list[CommandResult] = list(cmd_run.evidence_results)
400
+ genuine_failure = cmd_run.genuine_failure
401
+ had_unrunnable = cmd_run.had_unrunnable
402
+ demoted_failures: list[Gap] = list(cmd_run.demoted_failures)
403
+ evidence_to_save.extend(command_results)
404
+
405
+ log_step(
406
+ "verify/6: verification commands",
407
+ project_root=verifier.project_root,
408
+ task_id=task.id,
409
+ commands_run=len(command_results),
410
+ gaps_so_far=len(gaps),
411
+ )
412
+ _repair_fn = getattr(verifier.acceptance_compiler, "repair", None)
413
+ compiled_run = await run_compiled_acceptance_checks(
414
+ task=task,
415
+ requirements=requirements,
416
+ compile_future=compile_future,
417
+ diff_content=diff_content,
418
+ ac_repair_attempts=ac_repair_attempts,
419
+ acceptance_compiler=verifier.acceptance_compiler,
420
+ command_applicable=verifier._command_applicable,
421
+ run_command=lambda cmd: verifier._run_command(cmd, task_id=task.id),
422
+ command_is_malformed=verifier._command_is_malformed,
423
+ failure_location=verifier._failure_location,
424
+ next_gap_id=verifier._next_gap_id,
425
+ repair_command=_repair_fn,
426
+ had_unrunnable=had_unrunnable,
427
+ genuine_failure=genuine_failure,
428
+ )
429
+ gaps.extend(compiled_run.gaps)
430
+ command_results.extend(compiled_run.command_results)
431
+ evidence_to_save.extend(compiled_run.evidence)
432
+ had_unrunnable = compiled_run.had_unrunnable
433
+ genuine_failure = compiled_run.genuine_failure
434
+ compiled_pass = compiled_run.compiled_pass
435
+ compiled_cmds_by_ac = compiled_run.compiled_cmds_by_ac
436
+ failing_results_by_ac = compiled_run.failing_results_by_ac
437
+ compiled_vote = compiled_run.compiled_vote
438
+ inconclusive_acs = compiled_run.inconclusive_acs
439
+ genuine_failure = promote_demoted_failures_when_compiler_incomplete(
440
+ task=task,
441
+ compiler_active=compiler_active,
442
+ compiled_cmds_by_ac=compiled_cmds_by_ac,
443
+ demoted_failures=demoted_failures,
444
+ genuine_failure=genuine_failure,
445
+ )
446
+
447
+ log_step(
448
+ "verify/7: compiled acceptance checks",
449
+ project_root=verifier.project_root,
450
+ task_id=task.id,
451
+ compiled_criteria=len(compiled_pass),
452
+ gaps_so_far=len(gaps),
453
+ )
454
+ ac_evidence = map_acceptance_criteria_evidence(
455
+ task=task,
456
+ requirements=requirements,
457
+ evidence_results=evidence_results,
458
+ compiled_pass=compiled_pass,
459
+ compiled_cmds_by_ac=compiled_cmds_by_ac,
460
+ failing_results_by_ac=failing_results_by_ac,
461
+ compiled_vote=compiled_vote,
462
+ inconclusive_acs=inconclusive_acs,
463
+ work_present=work_present,
464
+ genuine_failure=genuine_failure,
465
+ had_unrunnable=had_unrunnable,
466
+ rigor=rigor,
467
+ is_quality_only_command=verifier._is_quality_only_command,
468
+ next_gap_id=verifier._next_gap_id,
469
+ )
470
+ gaps.extend(ac_evidence.gaps)
471
+ evidence_to_save.extend(ac_evidence.evidence)
472
+
473
+ log_step(
474
+ "verify/8: acceptance-criteria evidence mapping",
475
+ project_root=verifier.project_root,
476
+ task_id=task.id,
477
+ gaps_so_far=len(gaps),
478
+ )
479
+ measure_cov, enforce_cov, min_ratio = verifier._diff_coverage_settings()
480
+ if rigor.enforce_coverage:
481
+ enforce_cov = True
482
+ any_passing = any(
483
+ result.exit_code == 0 and not verifier._is_quality_only_command(result.command)
484
+ for result in evidence_results
485
+ ) or any(compiled_pass.values())
486
+ dc_result = run_diff_coverage_gate(
487
+ task=task,
488
+ diff_content=diff_content,
489
+ any_passing=any_passing,
490
+ enforce_cov=enforce_cov,
491
+ measure_cov=measure_cov,
492
+ min_ratio=min_ratio,
493
+ measure_diff_coverage=verifier.measure_diff_coverage,
494
+ coverage_target_commands=verifier._coverage_target_commands,
495
+ next_gap_id=verifier._next_gap_id,
496
+ )
497
+ gaps.extend(dc_result.gaps)
498
+ evidence_to_save.extend(dc_result.evidence)
499
+ coverage_measured = dc_result.coverage_measured
500
+ coverage_skipped_reason = dc_result.coverage_skipped_reason
501
+
502
+ log_step(
503
+ "verify/9: diff coverage",
504
+ project_root=verifier.project_root,
505
+ task_id=task.id,
506
+ coverage_measured=coverage_measured,
507
+ gaps_so_far=len(gaps),
508
+ )
509
+ if diff_content:
510
+ gaps.extend(verifier.secret_scanner.scan_diff(diff_content, task.id))
511
+
512
+ log_step(
513
+ "verify/10: secret scan",
514
+ project_root=verifier.project_root,
515
+ task_id=task.id,
516
+ gaps_so_far=len(gaps),
517
+ )
518
+ if review_future is not None:
519
+ try:
520
+ review_result = await review_future
521
+ for finding in review_result.findings:
522
+ review_key = (
523
+ f"{getattr(finding, 'file', '') or ''}:"
524
+ f"{getattr(finding, 'line', '') or ''}:"
525
+ f"{finding.description[:48]}"
526
+ )
527
+ finding.id = verifier._next_gap_id(task.id, f"REVIEW-{review_key}")
528
+ finding.blocking = bool(
529
+ rigor.reviewer_required and finding.severity == "critical"
530
+ )
531
+ gaps.append(finding)
532
+ except Exception as exc:
533
+ logger.error("Implementation review failed: %s", exc)
534
+
535
+ log_step(
536
+ "verify/11: implementation review",
537
+ project_root=verifier.project_root,
538
+ task_id=task.id,
539
+ gaps_so_far=len(gaps),
540
+ )
541
+ for card in unresolved_blocking_cards(verifier.project_root, task_id=task.id):
542
+ gaps.append(Gap(
543
+ id=verifier._next_gap_id(task.id, f"LIVE-{card.id}"),
544
+ severity="critical",
545
+ gap_type="architecture_drift",
546
+ task_id=task.id,
547
+ description=f"Open critical live-review card remains: {card.summary}",
548
+ evidence=[card.id, card.message_for_agent],
549
+ recommended_fix=(
550
+ f"Address the critique card, then run `dev watch resolve {card.id}` "
551
+ "or mark it ignored with justification outside the verification gate."
552
+ ),
553
+ blocking=True,
554
+ ))
555
+
556
+ # Post-step: a change spanning many subsystems / files likely stales the codebase
557
+ # wiki. Flag the stale pages (or auto-refresh, per config). Best-effort and never
558
+ # blocking — an orientation aid, not a gate.
559
+ wiki_refresh_outcome = None
560
+ try:
561
+ from devcouncil.verification.wiki_refresh import (
562
+ evaluate_wiki_refresh,
563
+ wiki_refresh_advisory_gap,
564
+ )
565
+
566
+ wiki_cfg = getattr(_cfg.verification, "wiki_refresh", None) if _cfg is not None else None
567
+ if changed_files and (wiki_cfg is None or wiki_cfg.enabled):
568
+ wiki_refresh_outcome = evaluate_wiki_refresh(
569
+ verifier.project_root,
570
+ changed_files,
571
+ repo_map=verifier._load_repo_map(),
572
+ config=wiki_cfg,
573
+ )
574
+ advisory = wiki_refresh_advisory_gap(
575
+ wiki_refresh_outcome,
576
+ task_id=task.id,
577
+ gap_id=verifier._next_gap_id(task.id, "WIKI"),
578
+ )
579
+ if advisory is not None:
580
+ gaps.append(advisory)
581
+ except Exception as exc:
582
+ logger.debug("wiki refresh post-step skipped: %s", exc)
583
+
584
+ from devcouncil.verification.gap_ids import normalize_verify_gaps
585
+ from devcouncil.verification.verifier import VerificationOutcome
586
+
587
+ gaps = normalize_verify_gaps(gaps)
588
+ blocking_count = len([g for g in gaps if g.blocking])
589
+ log_step(
590
+ "verify/complete",
591
+ project_root=verifier.project_root,
592
+ task_id=task.id,
593
+ total_gaps=len(gaps),
594
+ blocking_gaps=blocking_count,
595
+ evidence_items=len(evidence_to_save),
596
+ trace=True,
597
+ )
598
+
599
+ verifier.last_outcome = VerificationOutcome(
600
+ mode=_verification_mode_label(evidence_to_save, compiler_active=compiler_active),
601
+ compiler_active=compiler_active,
602
+ diff_empty=diff_empty,
603
+ coverage_measured=coverage_measured,
604
+ coverage_skipped_reason=coverage_skipped_reason,
605
+ difficulty=rigor.difficulty,
606
+ rigor_applied=list(rigor.applied),
607
+ wiki_refresh=wiki_refresh_outcome.to_dict() if wiki_refresh_outcome is not None else None,
608
+ liveness_baseline=liveness_baseline_status,
609
+ )
610
+ return gaps, evidence_to_save