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
@@ -1,7 +1,6 @@
1
1
  import asyncio
2
2
  import hashlib
3
3
  import logging
4
- import subprocess
5
4
  from pathlib import Path
6
5
  from types import SimpleNamespace
7
6
 
@@ -27,6 +26,7 @@ from devcouncil.integrations.github_intent import resolve_goal_intent
27
26
  from devcouncil.llm.provider import ProviderRequestError
28
27
  from devcouncil.llm.router import StructuredOutputError
29
28
  from devcouncil.storage.db import get_db
29
+ from devcouncil.utils.proc import run_git
30
30
  from devcouncil.storage.repositories import ArtifactGraphRepository, GapRepository, StateRepository, TaskRepository
31
31
  from devcouncil.app.state_machine import ProjectPhase
32
32
  from devcouncil.gating.policy import topological_order
@@ -63,10 +63,7 @@ def _is_git_repo(root: Path) -> bool:
63
63
  non-git tree would spuriously flag unrelated files as orphan diffs.
64
64
  """
65
65
  try:
66
- result = subprocess.run(
67
- ["git", "rev-parse", "--is-inside-work-tree"],
68
- cwd=root, capture_output=True, text=True,
69
- )
66
+ result = run_git(["rev-parse", "--is-inside-work-tree"], cwd=root)
70
67
  except Exception:
71
68
  return False
72
69
  return result.returncode == 0 and result.stdout.strip() == "true"
@@ -98,6 +95,22 @@ def _max_repair_attempts(root: Path) -> int:
98
95
  return 0
99
96
 
100
97
 
98
+ def _task_max_repairs(root: Path, task, base_max: int) -> int:
99
+ """Per-task repair budget: the base budget widened for HARD tasks per
100
+ ``verification.rigor.extra_repair_attempts_on_hard``. A disabled loop
101
+ (base 0) stays disabled. Never raises."""
102
+ if base_max <= 0:
103
+ return base_max
104
+ try:
105
+ from devcouncil.verification.difficulty import resolve_rigor_policy
106
+
107
+ config = load_config(root)
108
+ policy = resolve_rigor_policy(task, None, config=config)
109
+ return base_max + max(0, policy.extra_repair_attempts)
110
+ except Exception:
111
+ return base_max
112
+
113
+
101
114
  def _blocking_gap_signature(root: Path, task_id: str) -> str:
102
115
  """Fingerprint of a task's current blocking gaps, for no-progress detection.
103
116
 
@@ -130,9 +143,11 @@ def _remediable_incomplete_signature(root: Path, task_id: str) -> str:
130
143
 
131
144
 
132
145
  def _build_repair_service(root: Path):
133
- """Best-effort LLM repair service used to sharpen the correction manifest's root
134
- cause. Returns None when no provider key is configured — the manifest still has a
135
- deterministic, task-scoped fallback (allowed files, commands, forbidden changes)."""
146
+ """Optional LLM advisory layer for sharpening correction-manifest root cause.
147
+
148
+ Returns None when no provider key is configured — the Stable repair contract
149
+ does not require this: ``write_correction_manifest`` still builds a deterministic,
150
+ task-scoped manifest from blocking gaps, next-actions, and task scope."""
136
151
  try:
137
152
  from devcouncil.app.config import get_api_key
138
153
  from devcouncil.llm.provider import create_provider, validate_model_provider
@@ -149,6 +164,61 @@ def _build_repair_service(root: Path):
149
164
  return None
150
165
 
151
166
 
167
+ def _executor_run_failed(root: Path, task_id: str) -> bool:
168
+ """True when the latest coding-agent run for ``task_id`` exited non-zero."""
169
+ from devcouncil.planning.correction_manifest import _latest_agent_run
170
+
171
+ run = _latest_agent_run(root, task_id)
172
+ if not run:
173
+ return False
174
+ returncode = run.get("returncode")
175
+ return returncode is not None and returncode != 0
176
+
177
+
178
+ def _executor_run_unavailable(root: Path, task_id: str) -> bool:
179
+ """True when the agent failed for infra reasons (session/rate limit), not code defects."""
180
+ from devcouncil.executors.advisor_tool import ADVISOR_INFRA_FAILURE_MARKERS
181
+ from devcouncil.planning.correction_manifest import _latest_agent_run
182
+
183
+ run = _latest_agent_run(root, task_id)
184
+ if not run:
185
+ return False
186
+ returncode = run.get("returncode")
187
+ if returncode in (None, 0):
188
+ return False
189
+ parts: list[str] = [str(run.get("status") or "")]
190
+ for key in ("stderr_preview", "stdout_preview"):
191
+ val = run.get(key)
192
+ if isinstance(val, list):
193
+ parts.extend(str(x) for x in val)
194
+ elif val:
195
+ parts.append(str(val))
196
+ hay = " ".join(parts).lower()
197
+ needles = (
198
+ "session limit",
199
+ "rate limit",
200
+ "too many requests",
201
+ "limit_rpm",
202
+ "429",
203
+ *ADVISOR_INFRA_FAILURE_MARKERS,
204
+ )
205
+ return any(needle in hay for needle in needles)
206
+
207
+
208
+ def _reverify_task(root: Path, task_id: str) -> str:
209
+ """Re-run verification for one task without invoking the coding agent."""
210
+ db = get_db(root)
211
+ if not db:
212
+ return _task_status(root, task_id)
213
+ try:
214
+ verify_command._run_verify_body(root, task_id, "local", True, db)
215
+ except typer.Exit:
216
+ pass
217
+ except Exception as exc: # pragma: no cover - verify should not abort the loop
218
+ logger.warning("Verify-only pass for %s failed: %s", task_id, exc)
219
+ return _task_status(root, task_id)
220
+
221
+
152
222
  def _execute_task_with_repair(
153
223
  root: Path,
154
224
  task,
@@ -163,12 +233,17 @@ def _execute_task_with_repair(
163
233
  """Run a task, then self-repair in a bounded loop until it verifies or the budget
164
234
  is exhausted. Returns ``(final_status, repair_attempts_used)``.
165
235
 
166
- Each repair attempt writes a correction manifest (which the coding-CLI executor
167
- folds into its prompt) and re-runs the executor. Between attempts the prior work
168
- is committed so the readiness gate's clean-tree requirement holds and the next
169
- attempt builds on it; verification still recognises the committed work via the
170
- task's checkpoint. The loop stops early when an attempt makes no progress (the
171
- same blocking gaps reappear) so it never spins on an unfixable gate.
236
+ **Stable contract:** each repair attempt writes a deterministic correction manifest
237
+ (blocking gaps, next-actions, task scope, prior-attempt context), re-runs the
238
+ executor, respects ``max_repairs``, and stops on no-progress (identical gap
239
+ fingerprint). Optional ``repair_service`` LLM inference may sharpen the manifest but
240
+ is not required for the loop to function.
241
+
242
+ Between attempts the prior work is committed so the readiness gate's clean-tree
243
+ requirement holds and the next attempt builds on it; verification still recognises
244
+ the committed work via the task's checkpoint. The loop stops early when an attempt
245
+ makes no progress (the same blocking gaps reappear) so it never spins on an
246
+ unfixable gate.
172
247
 
173
248
  When the task ultimately verifies, the intermediate ``[blocked]`` commits made
174
249
  between attempts are squashed into a single verified commit (see
@@ -197,6 +272,13 @@ def _execute_task_with_repair(
197
272
  status = _task_status(root, task.id)
198
273
  logger.info("Initial run of %s finished as %s (max_repairs=%d)", task.id, status, max_repairs)
199
274
 
275
+ if status in {"verified", "done"} and _remediable_incomplete_signature(root, task.id):
276
+ console.print(
277
+ f"[dim]{task.id}: re-verifying (incomplete acceptance criteria only)...[/dim]"
278
+ )
279
+ status = _reverify_task(root, task.id)
280
+ logger.info("After initial verify-only pass, %s is %s", task.id, status)
281
+
200
282
  attempt = 0
201
283
  last_signature: str | None = None
202
284
  while attempt < max_repairs:
@@ -214,13 +296,68 @@ def _execute_task_with_repair(
214
296
  # (e.g. the executor failed to start).
215
297
  break
216
298
  if signature == last_signature:
217
- logger.warning("%s: repair made no progress (identical gaps) after attempt %d; stopping loop", task.id, attempt)
299
+ if _executor_run_unavailable(root, task.id):
300
+ # Advisor/session/rate-limit failures are not code gaps — do not burn
301
+ # the remaining repair budget on identical executor invocations.
302
+ logger.warning(
303
+ "%s: executor unavailable (infra); stopping self-repair loop",
304
+ task.id,
305
+ )
306
+ console.print(
307
+ f"[yellow]{task.id}: executor unavailable (infra failure); "
308
+ "stopping the self-repair loop.[/yellow]"
309
+ )
310
+ break
311
+ if _executor_run_failed(root, task.id):
312
+ logger.warning(
313
+ "%s: identical gaps but the last executor run failed to complete; "
314
+ "continuing the repair budget (infra failure is not no-progress)",
315
+ task.id,
316
+ )
317
+ console.print(
318
+ f"[yellow]{task.id}: executor failed on the last repair attempt; "
319
+ "retrying while repair budget remains.[/yellow]"
320
+ )
321
+ else:
322
+ logger.warning("%s: repair made no progress (identical gaps) after attempt %d; stopping loop", task.id, attempt)
323
+ console.print(
324
+ f"[yellow]{task.id}: repair made no progress (identical blocking gaps); "
325
+ "stopping the self-repair loop.[/yellow]"
326
+ )
327
+ break
328
+ last_signature = signature
329
+
330
+ incomplete_only = not blocked and bool(signature)
331
+ unavailable = _executor_run_unavailable(root, task.id)
332
+ # Verify-only when incomplete-only OR any infra unavailability (blocked or not).
333
+ if incomplete_only or unavailable:
218
334
  console.print(
219
- f"[yellow]{task.id}: repair made no progress (identical blocking gaps); "
220
- "stopping the self-repair loop.[/yellow]"
335
+ f"[dim]{task.id}: verify-only pass before repair "
336
+ f"(attempt {attempt + 1}/{max_repairs})...[/dim]"
221
337
  )
222
- break
223
- last_signature = signature
338
+ prev_sig = signature
339
+ status = _reverify_task(root, task.id)
340
+ blocked = status not in {"verified", "done"}
341
+ new_sig = (
342
+ _blocking_gap_signature(root, task.id) if blocked
343
+ else _remediable_incomplete_signature(root, task.id)
344
+ )
345
+ if not new_sig:
346
+ break
347
+ if new_sig != prev_sig:
348
+ last_signature = None
349
+ continue
350
+ if unavailable:
351
+ logger.warning(
352
+ "%s: executor unavailable and verify-only did not clear gaps; "
353
+ "skipping further executor invocations",
354
+ task.id,
355
+ )
356
+ console.print(
357
+ f"[yellow]{task.id}: executor unavailable; gaps remain after "
358
+ "verify-only — not burning more repair budget.[/yellow]"
359
+ )
360
+ break
224
361
 
225
362
  # Record where history started before the first failed-attempt commit, so the
226
363
  # squash collapses exactly this task's intermediate commits and nothing earlier.
@@ -239,14 +376,36 @@ def _execute_task_with_repair(
239
376
  if manifest_path is None:
240
377
  break
241
378
  attempt += 1
242
- logger.info("Self-repair attempt %d/%d for %s (was %s); manifest=%s", attempt, max_repairs, task.id, status, manifest_path)
243
- console.print(
244
- f"\n[bold]Self-repair attempt {attempt}/{max_repairs}[/bold] for "
245
- f"[bold]{task.id}[/bold] (was {status})..."
246
- )
247
- _run_once()
248
- status = _task_status(root, task.id)
249
- logger.info("After repair attempt %d, %s is now %s", attempt, task.id, status)
379
+ with log_stage(
380
+ "repair",
381
+ project_root=root,
382
+ task_id=task.id,
383
+ attempt=attempt,
384
+ max_repairs=max_repairs,
385
+ ):
386
+ log_step(
387
+ f"repair attempt {attempt}/{max_repairs} for {task.id}",
388
+ project_root=root,
389
+ task_id=task.id,
390
+ manifest=str(manifest_path),
391
+ prior_status=status,
392
+ trace=True,
393
+ )
394
+ logger.info("Self-repair attempt %d/%d for %s (was %s); manifest=%s", attempt, max_repairs, task.id, status, manifest_path)
395
+ console.print(
396
+ f"\n[bold]Self-repair attempt {attempt}/{max_repairs}[/bold] for "
397
+ f"[bold]{task.id}[/bold] (was {status})..."
398
+ )
399
+ _run_once()
400
+ status = _task_status(root, task.id)
401
+ logger.info("After repair attempt %d, %s is now %s", attempt, task.id, status)
402
+ log_step(
403
+ f"repair attempt {attempt}/{max_repairs} finished for {task.id}",
404
+ project_root=root,
405
+ task_id=task.id,
406
+ status=status,
407
+ trace=True,
408
+ )
250
409
 
251
410
  if status not in {"verified", "done"} and attempt >= max_repairs and max_repairs > 0:
252
411
  logger.warning("%s: gave up after %d repair attempt(s); still %s", task.id, attempt, status)
@@ -254,6 +413,11 @@ def _execute_task_with_repair(
254
413
  f"[yellow]{task.id}: gave up after {attempt} repair attempt(s); still {status}.[/yellow]"
255
414
  )
256
415
 
416
+ if _remediable_incomplete_signature(root, task.id):
417
+ console.print(f"[dim]{task.id}: final verify-only pass for remaining incomplete gaps...[/dim]")
418
+ status = _reverify_task(root, task.id)
419
+ logger.info("After final verify-only pass, %s is %s", task.id, status)
420
+
257
421
  # On success, squash the [blocked] attempt commits into one verified commit so the
258
422
  # user's history isn't littered with failed attempts. Only do this when the task
259
423
  # actually verified — a still-blocked task keeps its attempt commits so the work
@@ -272,10 +436,7 @@ def _execute_task_with_repair(
272
436
  def _current_head(root: Path) -> str | None:
273
437
  """Resolve the current HEAD commit, or None when there is no commit / no git."""
274
438
  try:
275
- result = subprocess.run(
276
- ["git", "rev-parse", "HEAD"],
277
- cwd=root, capture_output=True, text=True,
278
- )
439
+ result = run_git(["rev-parse", "HEAD"], cwd=root)
279
440
  except Exception:
280
441
  return None
281
442
  if result.returncode != 0:
@@ -305,41 +466,31 @@ def _squash_repair_commits(root: Path, task_id: str, base: str, status: str) ->
305
466
  head = _current_head(root)
306
467
  if not head or head == base:
307
468
  return False
308
- base_ok = subprocess.run(
309
- ["git", "rev-parse", "--verify", f"{base}^{{commit}}"],
310
- cwd=root, capture_output=True, text=True,
311
- )
469
+ base_ok = run_git(["rev-parse", "--verify", f"{base}^{{commit}}"], cwd=root)
312
470
  if base_ok.returncode != 0:
313
471
  return False
314
472
  # Soft reset keeps the working tree + index exactly as-is; only the branch
315
473
  # pointer moves back to base, so the next commit captures the whole task.
316
- reset = subprocess.run(
317
- ["git", "reset", "--soft", base],
318
- cwd=root, capture_output=True, text=True,
319
- )
474
+ reset = run_git(["reset", "--soft", base], cwd=root)
320
475
  if reset.returncode != 0:
321
476
  return False
322
477
  # Stage the FINAL (verified) attempt's still-uncommitted changes too, so they land
323
478
  # in this single squash commit. Without this they'd be committed separately by the
324
479
  # caller afterward, producing two commits for what the message calls "one verified
325
480
  # commit" (and leaving the squash to capture only the [blocked] diffs).
326
- add = subprocess.run(
327
- ["git", "add", "-A"],
328
- cwd=root, capture_output=True, text=True,
329
- )
481
+ add = run_git(["add", "-A"], cwd=root)
330
482
  if add.returncode != 0:
331
483
  return False
332
484
  # Re-commit the squashed tree. There may be nothing staged if every attempt's
333
485
  # changes cancelled out (unlikely for a verified task) — tolerate that.
334
- commit = subprocess.run(
486
+ commit = run_git(
335
487
  [
336
- "git",
337
488
  "-c", "user.name=DevCouncil",
338
489
  "-c", "user.email=devcouncil@local",
339
490
  "commit", "--no-verify", "--allow-empty",
340
491
  "-m", f"devcouncil(e2e): {task_id} [{status}]",
341
492
  ],
342
- cwd=root, capture_output=True, text=True,
493
+ cwd=root,
343
494
  )
344
495
  return commit.returncode == 0
345
496
  except Exception:
@@ -356,22 +507,18 @@ def _commit_task_changes(root: Path, task_id: str, status: str) -> bool:
356
507
  and can be squashed or reset afterwards. Returns True if a commit was made.
357
508
  """
358
509
  try:
359
- status_out = subprocess.run(
360
- ["git", "status", "--porcelain"],
361
- cwd=root, capture_output=True, text=True,
362
- )
510
+ status_out = run_git(["status", "--porcelain"], cwd=root)
363
511
  if status_out.returncode != 0 or not status_out.stdout.strip():
364
512
  return False
365
- subprocess.run(["git", "add", "-A"], cwd=root, check=True, capture_output=True)
366
- commit = subprocess.run(
513
+ run_git(["add", "-A"], cwd=root, check=True)
514
+ commit = run_git(
367
515
  [
368
- "git",
369
516
  "-c", "user.name=DevCouncil",
370
517
  "-c", "user.email=devcouncil@local",
371
518
  "commit", "--no-verify",
372
519
  "-m", f"devcouncil(e2e): {task_id} [{status}]",
373
520
  ],
374
- cwd=root, capture_output=True, text=True,
521
+ cwd=root,
375
522
  )
376
523
  return commit.returncode == 0
377
524
  except Exception:
@@ -626,12 +773,14 @@ def go(
626
773
  executed_task_ids.append(task.id)
627
774
  # Run, then self-repair in a bounded loop (closes the autonomous loop: the
628
775
  # one-shot executor no longer needs a human to run `dev repair` and re-run).
776
+ # Hard tasks get a wider budget per verification.rigor.
777
+ task_max_repairs = _task_max_repairs(root, task, max_repairs)
629
778
  with log_stage(
630
779
  "execute_task",
631
780
  project_root=root,
632
781
  task_id=task.id,
633
782
  executor=normalized_executor,
634
- max_repairs=max_repairs,
783
+ max_repairs=task_max_repairs,
635
784
  ):
636
785
  latest_status, repairs_used = _execute_task_with_repair(
637
786
  root,
@@ -639,7 +788,7 @@ def go(
639
788
  executor=normalized_executor,
640
789
  profile=profile,
641
790
  stream=stream,
642
- max_repairs=max_repairs,
791
+ max_repairs=task_max_repairs,
643
792
  repair_service=repair_service,
644
793
  config=repair_config,
645
794
  )
@@ -685,17 +834,18 @@ def go(
685
834
  # refreshes statuses/gaps cheaply for an honest final report.
686
835
  if executed_task_ids and _is_git_repo(root):
687
836
  console.print("\n[bold]Reconciling verification against the final integrated state...[/bold]")
688
- log_step("reconcile: re-verifying against integrated state", project_root=root)
689
- try:
690
- verify_command.verify(task_id=None, sandbox="local", json_format=True, project_root=root)
691
- except typer.Exit:
692
- # Expected signal: verify() raises Exit(code=1) when any task is blocked,
693
- # but it has already persisted every task status before raising. The
694
- # reconciliation pass therefore completed fall through to the reload so
695
- # blocked statuses are refreshed honestly. (Only real errors should skip.)
696
- pass
697
- except Exception as exc: # pragma: no cover - reconciliation is best-effort
698
- console.print(f"[yellow]Reconciliation pass skipped: {exc}[/yellow]")
837
+ with log_stage("reconcile", project_root=root, tasks=len(executed_task_ids)):
838
+ log_step("reconcile: re-verifying against integrated state", project_root=root)
839
+ try:
840
+ verify_command.verify(task_id=None, sandbox="local", json_format=True, project_root=root)
841
+ except typer.Exit:
842
+ # Expected signal: verify() raises Exit(code=1) when any task is blocked,
843
+ # but it has already persisted every task status before raising. The
844
+ # reconciliation pass therefore completed fall through to the reload so
845
+ # blocked statuses are refreshed honestly. (Only real errors should skip.)
846
+ pass
847
+ except Exception as exc: # pragma: no cover - reconciliation is best-effort
848
+ console.print(f"[yellow]Reconciliation pass skipped: {exc}[/yellow]")
699
849
  reconciled = {item.id: item for item in _load_tasks(root)}
700
850
  # Rebuild from the FULL planned set, not just executed_task_ids: a task skipped
701
851
  # for an unmet dependency, or one reconciliation downgraded from done->blocked,
@@ -715,7 +865,7 @@ def go(
715
865
  logger.warning("dev go finished with %d unfinished task(s): %s", len(failed), ", ".join(failed))
716
866
  _record_project_blocked(root)
717
867
 
718
- log_step("generating final report", project_root=root)
868
+ log_step("generating final report", project_root=root, trace=True)
719
869
  console.print("\n[bold]Final DevCouncil report[/bold]")
720
870
  report_command.report(
721
871
  SimpleNamespace(invoked_subcommand=None), # type: ignore[arg-type]
@@ -724,6 +874,8 @@ def go(
724
874
  github=False,
725
875
  github_pr_comment=False,
726
876
  gitlab_pr_comment=False,
877
+ evidence_json=None,
878
+ fail_on_blocking=False,
727
879
  project_root=root,
728
880
  )
729
881
  if report_file is not None: