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,17 @@
1
+ """Git ref names for per-task checkpoints.
2
+
3
+ Owned by the domain layer so the writer (execution.checkpoints.CheckpointService)
4
+ and the reader (verification.verifier.Verifier) share one definition of the ref
5
+ layout without importing each other — checkpoints.py imports Verifier, so the
6
+ verifier must not import checkpoints back. This module must stay dependency-free
7
+ (no imports from execution/ or verification/).
8
+ """
9
+
10
+ TASK_REF_PREFIX = "refs/devcouncil/tasks"
11
+
12
+ REF_BEFORE_TEMPLATE = TASK_REF_PREFIX + "/{task_id}/before"
13
+
14
+
15
+ def task_before_ref(task_id: str) -> str:
16
+ """Ref pointing at the snapshot taken before ``task_id`` started."""
17
+ return REF_BEFORE_TEMPLATE.format(task_id=task_id)
@@ -7,6 +7,7 @@ class CommandResult(BaseModel):
7
7
  stdout_path: str
8
8
  stderr_path: str
9
9
  summary: str
10
+ timed_out: bool = False
10
11
 
11
12
  class DiffEvidence(BaseModel):
12
13
  task_id: str
@@ -22,6 +22,16 @@ class Gap(BaseModel):
22
22
  "quality_gate_failed",
23
23
  "skipped_verification_command",
24
24
  "coarse_acceptance_proof",
25
+ "stub_detected",
26
+ "stub_declared",
27
+ "suspicious_effort",
28
+ "unwired_file",
29
+ "dead_symbol",
30
+ "stranded_code",
31
+ "stale_map",
32
+ "corpus_stale",
33
+ "doc_code_ref",
34
+ "acceptance_corpus",
25
35
  ]
26
36
  requirement_id: Optional[str] = None
27
37
  task_id: Optional[str] = None
@@ -18,5 +18,9 @@ class Requirement(BaseModel):
18
18
  title: str
19
19
  description: str
20
20
  priority: Literal["low", "medium", "high", "critical"]
21
- source: Literal["user", "planner", "critic", "arbiter"]
21
+ # Defaulted: requirements come back through LLM round-trips (arbiter/critic
22
+ # rewrites) where weaker models sometimes drop provenance metadata; a missing
23
+ # source shouldn't invalidate an otherwise-sound requirement. "planner" is the
24
+ # most common origin for machine-generated requirements.
25
+ source: Literal["user", "planner", "critic", "arbiter"] = "planner"
22
26
  acceptance_criteria: List[AcceptanceCriterion] = Field(default_factory=list)
@@ -1,5 +1,5 @@
1
1
  from pydantic import BaseModel, Field
2
- from typing import Literal, List
2
+ from typing import Literal, List, Optional
3
3
 
4
4
  class PlannedFile(BaseModel):
5
5
  path: str
@@ -27,6 +27,14 @@ class Task(BaseModel):
27
27
  "repo is already configured for them."
28
28
  ),
29
29
  )
30
+ agent_appended_expected_tests: List[str] = Field(
31
+ default_factory=list,
32
+ description=(
33
+ "Subset of expected_tests appended at runtime by a leased agent via "
34
+ "devcouncil_update_task_scope. These may run during verification but "
35
+ "cannot coarse-prove acceptance criteria."
36
+ ),
37
+ )
30
38
  allowed_commands: List[str] = Field(
31
39
  default_factory=list,
32
40
  description=(
@@ -36,7 +44,39 @@ class Task(BaseModel):
36
44
  "only when the task needs no commands beyond those in expected_tests."
37
45
  ),
38
46
  )
47
+ agent_appended_allowed_commands: List[str] = Field(
48
+ default_factory=list,
49
+ description=(
50
+ "Subset of allowed_commands appended at runtime by a leased agent via "
51
+ "devcouncil_update_task_scope. These may run during execution/verification "
52
+ "but cannot coarse-prove acceptance criteria (self-certification guard, "
53
+ "mirroring agent_appended_expected_tests)."
54
+ ),
55
+ )
56
+ agent_appended_planned_files: List[str] = Field(
57
+ default_factory=list,
58
+ description=(
59
+ "Paths appended to planned_files at runtime by a leased agent via "
60
+ "devcouncil_update_task_scope / `dev scope update --planned-file`. "
61
+ "Always added as modify-op only; secret/restricted paths are rejected. "
62
+ "Lets an agent wire an unwired file into its intended caller without "
63
+ "hitting orphan_diff / write-policy deadlock."
64
+ ),
65
+ )
39
66
  forbidden_changes: List[str] = Field(default_factory=list)
67
+ difficulty: Optional[Literal["easy", "normal", "hard"]] = Field(
68
+ default=None,
69
+ description=(
70
+ "Manual difficulty override. When set (by a planner or a human) it wins over "
71
+ "the deterministic estimator in devcouncil.verification.difficulty; hard tasks "
72
+ "get stricter verification (stub/effort gates block, coverage enforced) per "
73
+ "the verification.rigor config."
74
+ ),
75
+ )
76
+ priority: Optional[Literal["high", "medium", "low"]] = Field(
77
+ default=None,
78
+ description="Optional human/planner priority hint (high/medium/low). Default None.",
79
+ )
40
80
  depends_on: List[str] = Field(
41
81
  default_factory=list,
42
82
  description=(
@@ -51,5 +91,6 @@ class Task(BaseModel):
51
91
  "running",
52
92
  "blocked",
53
93
  "verified",
54
- "done"
94
+ "done",
95
+ "cancelled",
55
96
  ] = "planned"
@@ -2,7 +2,6 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import json
6
5
  import logging
7
6
  import os
8
7
  import subprocess
@@ -12,6 +11,10 @@ from pathlib import Path
12
11
 
13
12
  from pydantic import BaseModel
14
13
 
14
+ from devcouncil.domain.checkpoint_refs import REF_BEFORE_TEMPLATE
15
+ from devcouncil.utils.fsio import atomic_write_text
16
+ from devcouncil.utils.proc import GIT_TIMEOUT
17
+ from devcouncil.utils.json_persist import write_json
15
18
  from devcouncil.verification.verifier import Verifier
16
19
 
17
20
  logger = logging.getLogger(__name__)
@@ -27,9 +30,10 @@ class CheckpointResult(BaseModel):
27
30
 
28
31
 
29
32
  class CheckpointService:
30
- REF_BEFORE = "refs/devcouncil/tasks/{task_id}/before"
33
+ # The before-ref layout lives in devcouncil.domain.checkpoint_refs because the
34
+ # Verifier reads it too; kept as a class attr for compat with .format() callers.
35
+ REF_BEFORE = REF_BEFORE_TEMPLATE
31
36
  REF_AFTER = "refs/devcouncil/tasks/{task_id}/after"
32
- REF_ATTEMPT = "refs/devcouncil/tasks/{task_id}/attempts/{attempt}"
33
37
 
34
38
  def __init__(self, project_root: Path):
35
39
  self.project_root = project_root.resolve()
@@ -42,10 +46,6 @@ class CheckpointService:
42
46
  def create_after(self, task_id: str) -> CheckpointResult:
43
47
  return self._create(task_id, stage="after")
44
48
 
45
- def create_attempt(self, task_id: str, attempt: int) -> CheckpointResult:
46
- ref_template = self.REF_ATTEMPT.format(task_id=task_id, attempt=attempt)
47
- return self._create(task_id, stage="attempt", ref_name=ref_template)
48
-
49
49
  def rollback(self, task_id: str) -> CheckpointResult:
50
50
  logger.info("Rollback requested for %s", task_id)
51
51
  before_ref = self.REF_BEFORE.format(task_id=task_id)
@@ -60,6 +60,7 @@ class CheckpointService:
60
60
  text=True,
61
61
  encoding="utf-8",
62
62
  errors="replace",
63
+ timeout=GIT_TIMEOUT,
63
64
  )
64
65
  if diff.strip():
65
66
  subprocess.run(
@@ -68,6 +69,7 @@ class CheckpointService:
68
69
  input=diff,
69
70
  text=True,
70
71
  check=True,
72
+ timeout=GIT_TIMEOUT,
71
73
  )
72
74
  return CheckpointResult(
73
75
  task_id=task_id,
@@ -75,7 +77,7 @@ class CheckpointService:
75
77
  git_ref_created=True,
76
78
  message="Rolled back using git refs.",
77
79
  )
78
- except subprocess.CalledProcessError as exc:
80
+ except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as exc:
79
81
  return CheckpointResult(
80
82
  task_id=task_id,
81
83
  message=f"Git ref rollback failed: {exc}",
@@ -86,13 +88,14 @@ class CheckpointService:
86
88
  subprocess.check_call(
87
89
  ["git", "apply", "-R", str(after_patch)],
88
90
  cwd=self.project_root,
91
+ timeout=GIT_TIMEOUT,
89
92
  )
90
93
  return CheckpointResult(
91
94
  task_id=task_id,
92
95
  patch_path=str(after_patch),
93
96
  message="Rolled back using after patch.",
94
97
  )
95
- except subprocess.CalledProcessError as exc:
98
+ except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as exc:
96
99
  return CheckpointResult(
97
100
  task_id=task_id,
98
101
  patch_path=str(after_patch),
@@ -104,23 +107,6 @@ class CheckpointService:
104
107
  message="No checkpoint refs or after patch found.",
105
108
  )
106
109
 
107
- def import_legacy_patch(self, task_id: str) -> CheckpointResult:
108
- before_patch = self.checkpoint_dir / f"{task_id}-before.patch"
109
- if not before_patch.exists():
110
- return CheckpointResult(
111
- task_id=task_id,
112
- message="No legacy before patch to import.",
113
- )
114
- ref = self.REF_BEFORE.format(task_id=task_id)
115
- created = self._update_ref(ref)
116
- return CheckpointResult(
117
- task_id=task_id,
118
- ref=ref if created else None,
119
- patch_path=str(before_patch),
120
- git_ref_created=created,
121
- message="Imported legacy before patch ref when possible.",
122
- )
123
-
124
110
  def _create(
125
111
  self,
126
112
  task_id: str,
@@ -145,7 +131,7 @@ class CheckpointService:
145
131
  try:
146
132
  diff = verifier.get_diff()
147
133
  if diff:
148
- patch_path.write_text(diff, encoding="utf-8")
134
+ atomic_write_text(patch_path, diff)
149
135
  except Exception as exc:
150
136
  # Without a patch (and if the ref also failed) rollback is impossible —
151
137
  # never let this fail silently.
@@ -157,7 +143,7 @@ class CheckpointService:
157
143
  "changed_files": verifier.get_changed_files(),
158
144
  }
159
145
  snapshot_path = self.checkpoint_dir / f"{task_id}-before.json"
160
- snapshot_path.write_text(json.dumps(snapshot, indent=2), encoding="utf-8")
146
+ write_json(snapshot_path, snapshot)
161
147
  json_path = str(snapshot_path)
162
148
 
163
149
  # Routine bookkeeping (fires before+after every task and repair attempt) — DEBUG
@@ -188,15 +174,17 @@ class CheckpointService:
188
174
  text=True,
189
175
  encoding="utf-8",
190
176
  errors="replace",
177
+ timeout=GIT_TIMEOUT,
191
178
  ).strip()
192
179
  if not target:
193
180
  return False
194
181
  subprocess.check_call(
195
182
  ["git", "update-ref", ref, target],
196
183
  cwd=self.project_root,
184
+ timeout=GIT_TIMEOUT,
197
185
  )
198
186
  return True
199
- except (subprocess.CalledProcessError, FileNotFoundError):
187
+ except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError):
200
188
  return False
201
189
 
202
190
  def _snapshot_commit(self) -> str | None:
@@ -218,14 +206,18 @@ class CheckpointService:
218
206
  subprocess.run(
219
207
  ["git", "read-tree", "HEAD"],
220
208
  cwd=self.project_root, env=env, check=True, capture_output=True, text=True,
209
+ timeout=GIT_TIMEOUT,
221
210
  )
211
+ # Staging the full tree scales with repo size — give it extra room.
222
212
  subprocess.run(
223
213
  ["git", "add", "-A", "--", ".", ":(exclude).devcouncil"],
224
214
  cwd=self.project_root, env=env, check=True, capture_output=True, text=True,
215
+ timeout=GIT_TIMEOUT * 2,
225
216
  )
226
217
  tree = subprocess.check_output(
227
218
  ["git", "write-tree"],
228
219
  cwd=self.project_root, env=env, text=True, encoding="utf-8", errors="replace",
220
+ timeout=GIT_TIMEOUT,
229
221
  ).strip()
230
222
  if not tree:
231
223
  return None
@@ -233,9 +225,10 @@ class CheckpointService:
233
225
  ["git", "-c", "user.name=DevCouncil", "-c", "user.email=devcouncil@local",
234
226
  "commit-tree", tree, "-p", "HEAD", "-m", "devcouncil checkpoint"],
235
227
  cwd=self.project_root, text=True, encoding="utf-8", errors="replace",
228
+ timeout=GIT_TIMEOUT,
236
229
  ).strip()
237
230
  return commit or None
238
- except (subprocess.CalledProcessError, FileNotFoundError):
231
+ except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError):
239
232
  return None
240
233
  finally:
241
234
  try:
@@ -250,7 +243,8 @@ class CheckpointService:
250
243
  cwd=self.project_root,
251
244
  stderr=subprocess.DEVNULL,
252
245
  text=True,
246
+ timeout=GIT_TIMEOUT,
253
247
  )
254
248
  return True
255
- except (subprocess.CalledProcessError, FileNotFoundError):
249
+ except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError):
256
250
  return False
@@ -1,60 +1,31 @@
1
1
  from pathlib import Path
2
2
  from typing import List
3
+
3
4
  from devcouncil.domain.task import Task
4
- from devcouncil.domain.requirement import Requirement
5
- from devcouncil.utils.redaction import redact_string
6
- import json
5
+
7
6
 
8
7
  class ContextBuilder:
9
- """Gathers repo-level and task-level context for agent prompts."""
10
-
8
+ """Gathers repo-level context for agent prompts."""
9
+
11
10
  def __init__(self, project_root: Path):
12
11
  self.project_root = project_root
13
12
 
14
- def build_task_context(self, task: Task, requirements: List[Requirement]) -> str:
15
- """Collects all relevant information for implementing a task."""
16
-
17
- # 1. Map requirements relevant to this task
18
- req_map = {r.id: r for r in requirements}
19
- task_reqs = [req_map[rid] for rid in task.requirement_ids if rid in req_map]
20
-
21
- # 2. Gather content of planned files (if they exist)
22
- file_contents = {}
23
- for pf in task.planned_files:
24
- file_path = self.project_root / pf.path
25
- if file_path.exists() and file_path.is_file():
26
- try:
27
- raw = file_path.read_text(encoding="utf-8")
28
- file_contents[pf.path] = redact_string(raw)
29
- except Exception:
30
- file_contents[pf.path] = "[Error reading file]"
31
- else:
32
- file_contents[pf.path] = "[New file - does not exist yet]"
33
-
34
- # 3. Assemble the context string
35
- context = {
36
- "task": task.model_dump(),
37
- "relevant_requirements": [r.model_dump() for r in task_reqs],
38
- "file_contents": file_contents,
39
- "project_structure": self.get_structure_summary(task)
40
- }
41
-
42
- return json.dumps(context, indent=2)
43
-
44
13
  def get_structure_summary(self, task: Task | None = None) -> List[str]:
45
14
  """Simple list of files in the project for context."""
46
15
  try:
47
- import subprocess
48
- output = subprocess.check_output(
49
- ["git", "ls-files", "--cached", "--others", "--exclude-standard"],
16
+ from devcouncil.utils.proc import git_output
17
+
18
+ output = git_output(
19
+ ["ls-files", "--cached", "--others", "--exclude-standard"],
50
20
  cwd=self.project_root,
51
- stderr=subprocess.DEVNULL
52
- ).decode().splitlines()
53
-
21
+ ).splitlines()
22
+
54
23
  if task and task.planned_files:
55
24
  planned_paths = {pf.path for pf in task.planned_files}
56
- output = [p for p in output if p in planned_paths] + [p for p in output if p not in planned_paths]
57
-
58
- return output[:100] # Limit to avoid context overflow
25
+ output = [p for p in output if p in planned_paths] + [
26
+ p for p in output if p not in planned_paths
27
+ ]
28
+
29
+ return output[:100] # Limit to avoid context overflow
59
30
  except Exception:
60
31
  return []
@@ -14,6 +14,7 @@ from devcouncil.execution.policy_engine import TaskPolicyEngine
14
14
  from devcouncil.storage.db import get_db
15
15
  from devcouncil.storage.native import FileChangeRepository, TaskLeaseRepository
16
16
  from devcouncil.storage.repositories import GapRepository, TaskRepository
17
+ from devcouncil.verification.stub_detector import detect_stubs, task_allows_scaffolding
17
18
  from devcouncil.verification.verifier import Verifier
18
19
 
19
20
  _IGNORED_PREFIXES = (
@@ -37,6 +38,7 @@ _EVENT_IGNORED_PREFIXES = _IGNORED_PREFIXES + (".devcouncil/", ".gitignore")
37
38
 
38
39
  _TASK_CACHE_TTL_SECONDS = 10.0
39
40
  _EVENT_DEBOUNCE_SECONDS = 0.5
41
+ _STUB_SCAN_INTERVAL_SECONDS = 3.0
40
42
 
41
43
  logger = logging.getLogger(__name__)
42
44
 
@@ -58,6 +60,8 @@ class FilesystemWatcher:
58
60
  self._seen: dict[str, float] = {}
59
61
  self._seen_last_cleanup: float = 0.0
60
62
  self._task_cache: tuple[float, Task | None] | None = None
63
+ self._last_stub_scan: float = 0.0
64
+ self._reported_stub_keys: set[tuple[str, int, str]] = set()
61
65
  # Reuse one Verifier across polls (avoids rebuilding its scanners each tick) but
62
66
  # still run get_changed_files() fresh every poll — caching the git result would
63
67
  # make the watcher miss changes for the cache TTL, defeating the poll interval.
@@ -75,6 +79,7 @@ class FilesystemWatcher:
75
79
  if self.should_ignore(path):
76
80
  continue
77
81
  events.append(self._record_path(path, task, operation="modify"))
82
+ self._scan_stubs_live(task)
78
83
  return events
79
84
 
80
85
  def watch(self) -> None:
@@ -129,6 +134,7 @@ class FilesystemWatcher:
129
134
  return None
130
135
  event = self._record_path(rel, self._task_cached(), operation=operation)
131
136
  self._notify(event)
137
+ self._scan_stubs_live(self._task_cached())
132
138
  return event
133
139
 
134
140
  def _notify(self, event: dict) -> None:
@@ -203,3 +209,61 @@ class FilesystemWatcher:
203
209
  )
204
210
  )
205
211
  return {"path": path, "operation": operation, "allowed": allowed, "reason": decision.reason}
212
+
213
+ def _scan_stubs_live(self, task: Task | None) -> None:
214
+ """Run stub detection on the current working-tree diff during execution.
215
+
216
+ Surfaces non-blocking ``stub_detected`` gaps mid-session — cheaper to fix
217
+ than waiting for ``dev verify``. Throttled so rapid saves do not spam gaps.
218
+ """
219
+ now = time.monotonic()
220
+ if now - self._last_stub_scan < _STUB_SCAN_INTERVAL_SECONDS:
221
+ return
222
+ self._last_stub_scan = now
223
+ if task is None:
224
+ return
225
+ if self._verifier is None:
226
+ self._verifier = Verifier(self.project_root)
227
+ try:
228
+ diff = self._verifier.get_diff()
229
+ except Exception:
230
+ logger.debug("Live stub scan skipped: could not load diff", exc_info=True)
231
+ return
232
+ if not diff.strip():
233
+ return
234
+ scaffolding_ok = task_allows_scaffolding(task)
235
+ findings = detect_stubs(self.project_root, diff, honor_allow_stub=scaffolding_ok)
236
+ if not findings:
237
+ return
238
+ db = get_db(self.project_root)
239
+ if not db:
240
+ return
241
+ with db.get_session() as session:
242
+ gap_repo = GapRepository(session)
243
+ for finding in findings:
244
+ key = (finding.file, finding.line, finding.reason)
245
+ if key in self._reported_stub_keys:
246
+ continue
247
+ self._reported_stub_keys.add(key)
248
+ gap_repo.save(
249
+ Gap(
250
+ id=f"GAP-{self.task_id}-LIVESTUB-{uuid.uuid4().hex[:10]}",
251
+ severity="medium",
252
+ gap_type="stub_detected",
253
+ task_id=self.task_id,
254
+ description=(
255
+ f"Live stub/placeholder at {finding.file}:{finding.line}: {finding.reason}."
256
+ ),
257
+ evidence=[f"{finding.file}:{finding.line}", finding.snippet],
258
+ recommended_fix=(
259
+ f"Replace the placeholder at {finding.file}:{finding.line} before continuing."
260
+ ),
261
+ blocking=False,
262
+ file=finding.file,
263
+ line=finding.line,
264
+ )
265
+ )
266
+ logger.info(
267
+ "Live stub detected for %s at %s:%s (%s)",
268
+ self.task_id, finding.file, finding.line, finding.reason,
269
+ )