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
@@ -0,0 +1,112 @@
1
+ """Batch git worktree queries into a minimal set of subprocess calls.
2
+
3
+ Verifier hot paths previously invoked ``git diff``, ``git diff --name-only``, and
4
+ ``git ls-files --others`` independently (and sometimes repeatedly within one
5
+ ``verify_task``). ``GitWorktreeSnapshot.capture`` runs at most three plumbing
6
+ commands — ``rev-parse``, ``diff HEAD`` (when HEAD exists), and
7
+ ``status --porcelain -z`` — and caches the parsed results for the rest of the
8
+ verification pass.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass, field
14
+ from pathlib import Path
15
+ from typing import List, Union
16
+
17
+ from devcouncil.utils.proc import GIT_TIMEOUT, git_output, run_git
18
+
19
+ PathLike = Union[str, Path]
20
+
21
+
22
+ @dataclass
23
+ class GitWorktreeSnapshot:
24
+ """Cached worktree state from a single batched git probe."""
25
+
26
+ has_head: bool
27
+ tracked_diff: str = ""
28
+ tracked_changed_files: List[str] = field(default_factory=list)
29
+ untracked_files: List[str] = field(default_factory=list)
30
+ cached_diff: str = ""
31
+ working_diff: str = ""
32
+ status_paths: List[str] = field(default_factory=list)
33
+
34
+ @classmethod
35
+ def capture(cls, root: PathLike, *, timeout: float = GIT_TIMEOUT) -> GitWorktreeSnapshot:
36
+ root = Path(root)
37
+ has_head = (
38
+ run_git(["rev-parse", "--verify", "HEAD"], root, timeout=timeout).returncode == 0
39
+ )
40
+ status_entries = _parse_status(root, timeout=timeout)
41
+ untracked = [e.path for e in status_entries if e.xy == "??"]
42
+ status_paths = [e.path for e in status_entries]
43
+
44
+ if has_head:
45
+ tracked_diff = git_output(["diff", "HEAD"], root, timeout=timeout, default="")
46
+ tracked_changed = sorted({e.path for e in status_entries if e.xy != "??"})
47
+ if not tracked_changed and tracked_diff.strip():
48
+ name_out = git_output(
49
+ ["diff", "HEAD", "--name-only"], root, timeout=timeout, default=""
50
+ )
51
+ tracked_changed = [p.replace("\\", "/") for p in name_out.splitlines() if p.strip()]
52
+ return cls(
53
+ has_head=True,
54
+ tracked_diff=tracked_diff,
55
+ tracked_changed_files=tracked_changed,
56
+ untracked_files=sorted(set(untracked)),
57
+ status_paths=status_paths,
58
+ )
59
+
60
+ cached_diff = git_output(["diff", "--cached"], root, timeout=timeout, default="")
61
+ working_diff = git_output(["diff"], root, timeout=timeout, default="")
62
+ if not untracked:
63
+ untracked = _ls_untracked(root, timeout=timeout)
64
+ if not status_paths:
65
+ status_paths = sorted(set(untracked))
66
+ return cls(
67
+ has_head=False,
68
+ cached_diff=cached_diff,
69
+ working_diff=working_diff,
70
+ untracked_files=sorted(set(untracked)),
71
+ status_paths=status_paths,
72
+ )
73
+
74
+
75
+ @dataclass(frozen=True)
76
+ class _StatusEntry:
77
+ xy: str
78
+ path: str
79
+
80
+
81
+ def _parse_status(root: Path, *, timeout: float) -> List[_StatusEntry]:
82
+ result = run_git(["status", "--porcelain", "-z", "-u"], root, timeout=timeout)
83
+ if result.returncode != 0 or not result.stdout:
84
+ return []
85
+ entries: List[_StatusEntry] = []
86
+ parts = result.stdout.split("\0")
87
+ i = 0
88
+ while i < len(parts):
89
+ entry = parts[i]
90
+ if not entry or len(entry) < 3:
91
+ i += 1
92
+ continue
93
+ xy = entry[:2]
94
+ rest = entry[3:].replace("\\", "/")
95
+ if "R" in xy:
96
+ if i + 1 < len(parts) and parts[i + 1]:
97
+ entries.append(_StatusEntry(xy=xy, path=parts[i + 1].replace("\\", "/")))
98
+ i += 2
99
+ continue
100
+ entries.append(_StatusEntry(xy=xy, path=rest))
101
+ i += 1
102
+ return entries
103
+
104
+
105
+ def _ls_untracked(root: Path, *, timeout: float) -> List[str]:
106
+ out = git_output(
107
+ ["ls-files", "--others", "--exclude-standard"],
108
+ root,
109
+ timeout=timeout,
110
+ default="",
111
+ )
112
+ return [p.replace("\\", "/") for p in out.splitlines() if p.strip()]
@@ -0,0 +1,53 @@
1
+ """Central JSON read/write helpers built on atomic fsio writes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+ from typing import Any, TypeVar, Union
8
+
9
+ from pydantic import BaseModel
10
+
11
+ from devcouncil.utils.fsio import atomic_write_json, atomic_write_text
12
+
13
+ PathLike = Union[str, Path]
14
+ ModelT = TypeVar("ModelT", bound=BaseModel)
15
+
16
+
17
+ def write_json(path: PathLike, payload: Any, *, indent: int = 2, sort_keys: bool = False) -> None:
18
+ """Atomically serialize ``payload`` to JSON at ``path``."""
19
+ atomic_write_json(path, payload, indent=indent, sort_keys=sort_keys)
20
+
21
+
22
+ def write_model_json(
23
+ path: PathLike,
24
+ model: BaseModel,
25
+ *,
26
+ indent: int | None = 2,
27
+ exclude_none: bool = False,
28
+ ) -> None:
29
+ """Atomically write a Pydantic model as JSON.
30
+
31
+ Pass ``indent=None`` for compact (minified) output.
32
+ """
33
+ atomic_write_text(
34
+ path,
35
+ model.model_dump_json(indent=indent, exclude_none=exclude_none) + "\n",
36
+ )
37
+
38
+
39
+ def read_json(path: PathLike) -> Any:
40
+ """Load JSON from ``path``; raises ``FileNotFoundError`` / ``json.JSONDecodeError``."""
41
+ return json.loads(Path(path).read_text(encoding="utf-8"))
42
+
43
+
44
+ def read_model_json(path: PathLike, model_type: type[ModelT]) -> ModelT:
45
+ """Load and validate JSON as a Pydantic model."""
46
+ return model_type.model_validate_json(Path(path).read_text(encoding="utf-8"))
47
+
48
+
49
+ def dump_json(payload: Any, *, indent: int | None = None, sort_keys: bool = False, separators: tuple[str, str] | None = None) -> str:
50
+ """Serialize ``payload`` to a JSON string (stdout / DB columns)."""
51
+ if separators is not None:
52
+ return json.dumps(payload, indent=indent, sort_keys=sort_keys, separators=separators)
53
+ return json.dumps(payload, indent=indent, sort_keys=sort_keys)
@@ -0,0 +1,89 @@
1
+ """Shared subprocess helpers with consistent timeouts and error handling.
2
+
3
+ Every git/tool invocation in DevCouncil should go through these helpers (or at
4
+ minimum pass an explicit ``timeout=``) so a hung child process can never hang a
5
+ verification run or the CLI. Defaults are deliberately generous: they exist to
6
+ convert "hangs forever" into "fails loudly", not to race fast commands.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+ import subprocess
13
+ from pathlib import Path
14
+ from typing import List, Optional, Sequence, Union
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ # Default ceiling for git plumbing commands (status/diff/ls-files/rev-parse).
19
+ GIT_TIMEOUT: float = 60.0
20
+ # Default ceiling for generic tool invocations when the caller has no better bound.
21
+ DEFAULT_TIMEOUT: float = 120.0
22
+
23
+ Cmd = Sequence[str]
24
+ PathLike = Union[str, Path]
25
+
26
+
27
+ def run_git(
28
+ args: Cmd,
29
+ cwd: PathLike,
30
+ *,
31
+ timeout: float = GIT_TIMEOUT,
32
+ check: bool = False,
33
+ ) -> subprocess.CompletedProcess[str]:
34
+ """Run a git command with a timeout, capturing decoded text output.
35
+
36
+ Never raises ``TimeoutExpired``: a timeout is logged and surfaced as a
37
+ ``CompletedProcess`` with returncode 124 so existing ``returncode``/stdout
38
+ checks keep working. Raises ``CalledProcessError`` only when ``check=True``
39
+ and the command failed (mirroring ``subprocess.run``).
40
+ """
41
+ cmd: List[str] = ["git", *args] if args and args[0] != "git" else list(args)
42
+ try:
43
+ return subprocess.run(
44
+ cmd,
45
+ cwd=str(cwd),
46
+ capture_output=True,
47
+ text=True,
48
+ encoding="utf-8",
49
+ errors="replace",
50
+ timeout=timeout,
51
+ check=check,
52
+ )
53
+ except subprocess.TimeoutExpired:
54
+ logger.warning("git command timed out after %.0fs: %s (cwd=%s)", timeout, " ".join(cmd), cwd)
55
+ return subprocess.CompletedProcess(cmd, returncode=124, stdout="", stderr=f"timed out after {timeout}s")
56
+
57
+
58
+ def git_output(
59
+ args: Cmd,
60
+ cwd: PathLike,
61
+ *,
62
+ timeout: float = GIT_TIMEOUT,
63
+ default: Optional[str] = None,
64
+ ) -> str:
65
+ """Return stdout of a git command, or ``default`` on any failure.
66
+
67
+ When ``default`` is None a failure raises ``CalledProcessError`` /
68
+ ``TimeoutExpired`` for the caller's existing except paths; otherwise the
69
+ failure is logged at debug level and ``default`` is returned.
70
+ """
71
+ cmd: List[str] = ["git", *args] if args and args[0] != "git" else list(args)
72
+ try:
73
+ result = subprocess.run(
74
+ cmd,
75
+ cwd=str(cwd),
76
+ capture_output=True,
77
+ text=True,
78
+ encoding="utf-8",
79
+ errors="replace",
80
+ timeout=timeout,
81
+ )
82
+ if result.returncode != 0:
83
+ raise subprocess.CalledProcessError(result.returncode, cmd, result.stdout, result.stderr)
84
+ return result.stdout
85
+ except (subprocess.CalledProcessError, subprocess.TimeoutExpired, OSError) as e:
86
+ if default is not None:
87
+ logger.debug("git command failed (%s): %s (cwd=%s)", e, " ".join(cmd), cwd)
88
+ return default
89
+ raise
@@ -10,6 +10,7 @@ text and the code under review, then maps each check 1:1 to its criterion.
10
10
 
11
11
  from __future__ import annotations
12
12
 
13
+ import asyncio
13
14
  import json
14
15
  import logging
15
16
  from typing import Dict, List
@@ -92,9 +93,17 @@ class AcceptanceTestCompiler:
92
93
  return {}
93
94
 
94
95
  if per_criterion and len(target) > 1:
96
+ # Independent per-criterion compiles: run them CONCURRENTLY. Each call is
97
+ # independent (own prompt, own criterion), so wall-clock drops from N
98
+ # sequential model calls to max-of-N — the difference between minutes and
99
+ # tens of minutes on a slow local monitor. A server that processes
100
+ # requests serially just queues them (no worse than before).
101
+ results = await asyncio.gather(
102
+ *(self._sample_checks([ac], code_context, samples) for ac in target)
103
+ )
95
104
  out: Dict[str, List[str]] = {}
96
- for ac in target:
97
- out.update(await self._sample_checks([ac], code_context, samples))
105
+ for result in results:
106
+ out.update(result)
98
107
  return out
99
108
  return await self._sample_checks(target, code_context, samples)
100
109
 
@@ -104,21 +113,29 @@ class AcceptanceTestCompiler:
104
113
  """Generate up to ``samples`` independent check commands for ``target`` criteria."""
105
114
  valid_ids = {ac.id for ac in target}
106
115
  out: Dict[str, List[str]] = {}
116
+
107
117
  # Independent attempts. Each varies temperature + an attempt marker so the router
108
118
  # cache keys differ (otherwise an identical prompt+temp returns the cached answer
109
119
  # and every "sample" is the same command). Attempt 0 stays deterministic (temp 0).
110
- for attempt in range(max(1, samples)):
120
+ async def _one_attempt(attempt: int) -> "CompiledChecks":
111
121
  temperature = 0.0 if attempt == 0 else min(0.8, 0.3 + 0.2 * attempt)
112
- try:
113
- result = await self.router.complete_structured(
114
- role=self.role,
115
- messages=[{"role": "user", "content": self._compile_prompt(target, code_context, attempt)}],
116
- schema=CompiledChecks,
117
- temperature=temperature,
118
- fallback=CompiledChecks(checks=[]),
119
- )
120
- except Exception as e:
121
- logger.warning("Acceptance-check compile attempt %d failed: %s", attempt, e)
122
+ return await self.router.complete_structured(
123
+ role=self.role,
124
+ messages=[{"role": "user", "content": self._compile_prompt(target, code_context, attempt)}],
125
+ schema=CompiledChecks,
126
+ temperature=temperature,
127
+ fallback=CompiledChecks(checks=[]),
128
+ )
129
+
130
+ # Attempts are independent by construction (distinct prompts/temperatures), so
131
+ # run them concurrently; results are merged in attempt order to keep candidate
132
+ # ordering deterministic. A failed attempt costs only itself.
133
+ results = await asyncio.gather(
134
+ *(_one_attempt(a) for a in range(max(1, samples))), return_exceptions=True
135
+ )
136
+ for attempt, result in enumerate(results):
137
+ if isinstance(result, BaseException):
138
+ logger.warning("Acceptance-check compile attempt %d failed: %s", attempt, result)
122
139
  continue
123
140
  for check in result.checks:
124
141
  cmd = (check.command or "").strip()
@@ -228,6 +245,12 @@ Rules — the commands are executed verbatim by the verifier:
228
245
  python -c "import m; \\ntry: m.f([])\\nexcept ValueError: pass\\nelse: raise SystemExit(1)"
229
246
  (real newlines are fine; never put try/if/for after a ';').
230
247
  - Use the actual module name implied by the code (e.g. file 'stats.py' -> import stats).
248
+ - Choose DISCRIMINATING inputs: pick the input a plausible-but-lazy implementation
249
+ would get WRONG, not the easiest happy-path example (which often passes on broken
250
+ code and proves nothing). Examples: to prove "comment lines are skipped", the
251
+ comment must CONTAIN the delimiter ('# c=3'); to prove "split on the first X",
252
+ the value must itself contain X; to prove sorting/ordering, the input must start
253
+ unsorted; to prove a boundary, test AT the boundary.
231
254
 
232
255
  HARD PROHIBITIONS — a command that does any of these is INVALID; omit the
233
256
  criterion instead of emitting such a command:
@@ -45,6 +45,7 @@ class AdHocCheckResult:
45
45
  diff_coverage: Optional[DiffCoverageEvidence] = None
46
46
  passed: bool = True
47
47
  reason: str = ""
48
+ verification_mode: str = "coarse"
48
49
 
49
50
  def to_dict(self) -> dict:
50
51
  return {
@@ -53,6 +54,7 @@ class AdHocCheckResult:
53
54
  "requirement": self.requirement,
54
55
  "changed_files": self.changed_files,
55
56
  "reason": self.reason,
57
+ "verification_mode": self.verification_mode,
56
58
  "gap_count": len(self.gaps),
57
59
  "blocking_gap_count": len([g for g in self.gaps if g.blocking]),
58
60
  "gaps": [g.model_dump() for g in self.gaps],
@@ -61,13 +63,79 @@ class AdHocCheckResult:
61
63
  }
62
64
 
63
65
 
66
+ def _load_verify_router(project_root: Path) -> Optional[ModelRouter]:
67
+ """Best-effort router for compiled acceptance checks during ad-hoc verify."""
68
+ try:
69
+ from devcouncil.app.config import get_api_key, load_config
70
+ from devcouncil.llm.provider import create_provider, validate_model_provider
71
+
72
+ config = load_config(project_root)
73
+ validate_model_provider(config.models.provider)
74
+ api_key = get_api_key(config.models.provider, project_root)
75
+ provider = create_provider(
76
+ config.models.provider,
77
+ api_key,
78
+ project_root=project_root,
79
+ provider_prefs=config.provider,
80
+ )
81
+ role_config = {name: role.model_dump() for name, role in config.models.roles.items()}
82
+ return ModelRouter(provider, role_config, project_root=project_root)
83
+ except Exception as exc:
84
+ logger.debug("Ad-hoc check: no model router (%s)", exc)
85
+ return None
86
+
87
+
88
+ def _persist_ad_hoc_result(
89
+ project_root: Path,
90
+ *,
91
+ requirement: Requirement,
92
+ task: Task,
93
+ gaps: List[Gap],
94
+ evidence: list,
95
+ ) -> None:
96
+ """Write CHECK task gaps/evidence so ``dev report`` can export CI artifacts."""
97
+ from devcouncil.domain.evidence import CommandResult, DiffEvidence, TestEvidence
98
+ from devcouncil.storage.db import get_db
99
+ from devcouncil.storage.repositories import (
100
+ EvidenceRepository,
101
+ GapRepository,
102
+ RequirementRepository,
103
+ TaskRepository,
104
+ )
105
+
106
+ db = get_db(project_root)
107
+ if not db:
108
+ return
109
+ with db.get_session() as session:
110
+ RequirementRepository(session).save(requirement)
111
+ gap_repo = GapRepository(session)
112
+ ev_repo = EvidenceRepository(session)
113
+ gap_repo.delete_for_task(task.id)
114
+ ev_repo.delete_for_task(task.id)
115
+ for gap in gaps:
116
+ gap_repo.save(gap)
117
+ for ev in evidence:
118
+ if isinstance(ev, CommandResult):
119
+ ev_repo.save_command_result(task.id, ev)
120
+ elif isinstance(ev, DiffCoverageEvidence):
121
+ ev_repo.save_diff_coverage_evidence(ev)
122
+ elif isinstance(ev, DiffEvidence):
123
+ ev_repo.save_diff_evidence(ev)
124
+ elif isinstance(ev, TestEvidence):
125
+ ev_repo.save_test_evidence(ev, task.id)
126
+ task.status = "blocked" if any(g.blocking for g in gaps) else "verified"
127
+ TaskRepository(session).save(task)
128
+
129
+
64
130
  def run_working_tree_check(
65
131
  project_root: Path,
66
132
  requirement: Optional[str] = None,
67
133
  *,
134
+ base: Optional[str] = None,
68
135
  test_commands: Optional[List[str]] = None,
69
136
  enforce_coverage: bool = False,
70
137
  min_ratio: float = 0.0,
138
+ persist: bool = False,
71
139
  router: Optional[ModelRouter] = None,
72
140
  verifier: Optional[Verifier] = None,
73
141
  ) -> AdHocCheckResult:
@@ -77,14 +145,25 @@ def run_working_tree_check(
77
145
  result is about evidence, not scope noise) and whose expected tests are
78
146
  ``test_commands``. Diff coverage is always measured; pass ``enforce_coverage`` (or a
79
147
  positive ``min_ratio``) to make an unexercised diff blocking.
148
+
149
+ When ``base`` is set, the diff is ``git diff <base>`` (PR/base scope) instead of
150
+ uncommitted changes vs HEAD. Pass ``persist=True`` to write gaps/evidence for
151
+ ``dev report`` artifact export (typical in CI).
80
152
  """
81
- verifier = verifier or Verifier(project_root, router=router)
153
+ verifier = verifier or Verifier(project_root, router=router or _load_verify_router(project_root))
154
+ if base:
155
+ verifier._git_fallback.diff_base = base
82
156
 
83
157
  diff = verifier.get_diff()
84
158
  changed_files = verifier.get_changed_files()
85
159
  if not diff or not changed_files:
86
- logger.info("Ad-hoc check: no working-tree changes; passing trivially")
87
- return AdHocCheckResult(requirement="", passed=True, reason="no_changes")
160
+ scope = f"against {base}" if base else "in working tree"
161
+ logger.info("Ad-hoc check: no changes %s; passing trivially", scope)
162
+ return AdHocCheckResult(
163
+ requirement=requirement or "",
164
+ passed=True,
165
+ reason="no_changes",
166
+ )
88
167
  logger.info("Ad-hoc check: %d changed file(s), enforce_coverage=%s", len(changed_files), enforce_coverage or min_ratio > 0)
89
168
 
90
169
  criterion = requirement or _DEFAULT_CRITERION
@@ -114,6 +193,7 @@ def run_working_tree_check(
114
193
  for path in changed_files
115
194
  ],
116
195
  expected_tests=list(test_commands or []),
196
+ difficulty="easy",
117
197
  )
118
198
 
119
199
  # Always measure diff coverage in lite mode; block on it only when asked. A positive
@@ -124,6 +204,17 @@ def run_working_tree_check(
124
204
  gaps, evidence = asyncio.run(verifier.verify_task(task, [req]))
125
205
  coverage = next((ev for ev in evidence if isinstance(ev, DiffCoverageEvidence)), None)
126
206
  blocking = [g for g in gaps if g.blocking]
207
+ outcome_mode = verifier.last_outcome.mode if verifier.last_outcome else (
208
+ "compiled" if verifier.acceptance_compiler else "coarse"
209
+ )
210
+ if persist:
211
+ _persist_ad_hoc_result(
212
+ project_root,
213
+ requirement=req,
214
+ task=task,
215
+ gaps=gaps,
216
+ evidence=evidence,
217
+ )
127
218
  logger.info("Ad-hoc check result: passed=%s (%d gap(s), %d blocking)", not blocking, len(gaps), len(blocking))
128
219
  return AdHocCheckResult(
129
220
  requirement=criterion,
@@ -132,4 +223,5 @@ def run_working_tree_check(
132
223
  next_actions=build_next_actions(gaps),
133
224
  diff_coverage=coverage,
134
225
  passed=not blocking,
226
+ verification_mode=outcome_mode,
135
227
  )
@@ -0,0 +1,41 @@
1
+ """Verification check helpers."""
2
+
3
+ from devcouncil.verification.checks.acceptance import coarse_proven_acceptance_ids, unproven_acceptance_ids
4
+ from devcouncil.verification.checks.command_evidence import run_verification_commands
5
+ from devcouncil.verification.checks.corpus_verification import (
6
+ detect_acceptance_corpus_gaps,
7
+ detect_corpus_stale_gaps,
8
+ detect_doc_code_ref_gaps,
9
+ )
10
+ from devcouncil.verification.checks.dead_symbols import detect_dead_symbol_gaps
11
+ from devcouncil.verification.checks.diff_coverage_gate import DiffCoverageGateResult, run_diff_coverage_gate
12
+ from devcouncil.verification.checks.liveness_ratchet import detect_liveness_regressions
13
+ from devcouncil.verification.checks.orphan_diff import detect_orphan_diff_gaps
14
+ from devcouncil.verification.checks.planned_files import (
15
+ detect_dependency_risk_gaps,
16
+ detect_no_work_gap,
17
+ detect_planned_file_gaps,
18
+ )
19
+ from devcouncil.verification.checks.stale_map import detect_stale_map_gaps
20
+ from devcouncil.verification.checks.stub_scan import detect_stub_gaps
21
+ from devcouncil.verification.checks.wiring import detect_unwired_file_gaps
22
+
23
+ __all__ = [
24
+ "DiffCoverageGateResult",
25
+ "detect_acceptance_corpus_gaps",
26
+ "detect_corpus_stale_gaps",
27
+ "detect_doc_code_ref_gaps",
28
+ "coarse_proven_acceptance_ids",
29
+ "detect_dead_symbol_gaps",
30
+ "detect_dependency_risk_gaps",
31
+ "detect_liveness_regressions",
32
+ "detect_no_work_gap",
33
+ "detect_orphan_diff_gaps",
34
+ "detect_planned_file_gaps",
35
+ "detect_stale_map_gaps",
36
+ "detect_stub_gaps",
37
+ "detect_unwired_file_gaps",
38
+ "run_diff_coverage_gate",
39
+ "run_verification_commands",
40
+ "unproven_acceptance_ids",
41
+ ]
@@ -0,0 +1,39 @@
1
+ """Acceptance-criteria evidence helpers extracted from Verifier."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Callable, Dict, Iterable, List, Set
6
+
7
+
8
+ def coarse_proven_acceptance_ids(
9
+ *,
10
+ task_acceptance_ids: Iterable[str],
11
+ successful_commands: List[str],
12
+ command_can_prove: Callable[[str, str], bool],
13
+ ) -> Set[str]:
14
+ """Return AC ids coarse-proven by any passing acceptance-capable command."""
15
+ if not task_acceptance_ids or not successful_commands:
16
+ return set()
17
+ if not any(command_can_prove("expected", cmd) for cmd in successful_commands):
18
+ return set()
19
+ return set(task_acceptance_ids)
20
+
21
+
22
+ def unproven_acceptance_ids(
23
+ *,
24
+ task_acceptance_ids: Iterable[str],
25
+ compiled_pass: Dict[str, bool],
26
+ coarse_proven: Set[str],
27
+ inconclusive: Set[str],
28
+ ) -> List[str]:
29
+ """AC ids still lacking decisive proof after compiled + coarse passes."""
30
+ unproven: List[str] = []
31
+ for ac_id in task_acceptance_ids:
32
+ if ac_id in inconclusive:
33
+ continue
34
+ if compiled_pass.get(ac_id):
35
+ continue
36
+ if ac_id in coarse_proven:
37
+ continue
38
+ unproven.append(ac_id)
39
+ return unproven