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,4 +1,5 @@
1
- import json
1
+ from devcouncil.utils.json_persist import dump_json
2
+ import logging
2
3
  import typer
3
4
  from pathlib import Path
4
5
  from rich.console import Console
@@ -8,9 +9,23 @@ from devcouncil.storage.db import get_db
8
9
  from devcouncil.storage.repositories import TaskRepository
9
10
  from devcouncil.verification.test_resolver import TestResolver
10
11
  from devcouncil.verification.verifier import Verifier
12
+ from devcouncil.telemetry.stages import log_stage, log_step
11
13
 
12
14
  app = typer.Typer(help="Evidence suggestion utilities.")
13
15
  console = Console()
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ def _load_repo_map(root: Path) -> dict | None:
20
+ """Load `.devcouncil/repo_map.json` so test suggestions can use the map's
21
+ dependents/role_files index; returns None (name-based fallback) when absent."""
22
+ import json
23
+ from typing import Any, cast
24
+
25
+ try:
26
+ return cast(dict[Any, Any], json.loads((root / ".devcouncil" / "repo_map.json").read_text(encoding="utf-8")))
27
+ except (OSError, ValueError):
28
+ return None
14
29
 
15
30
 
16
31
  @app.command("suggest")
@@ -21,28 +36,34 @@ def suggest(
21
36
  project_root: Path = typer.Option(Path("."), "--project-root"),
22
37
  ):
23
38
  root = project_root.expanduser().resolve()
39
+ from devcouncil.telemetry.logging_setup import set_log_dir
40
+ set_log_dir(root)
41
+ logger.info("dev evidence suggest: task=%s apply=%s", task_id, apply)
24
42
  initialize_project(root, quiet=True)
25
43
  db = get_db(root)
26
44
  if not db:
27
45
  console.print("[red]DevCouncil not initialized.[/red]")
28
46
  raise typer.Exit(code=1)
29
47
 
30
- with db.get_session() as session:
31
- task = TaskRepository(session).get_by_id(task_id)
32
- if not task:
33
- console.print(f"[red]Task {task_id} not found.[/red]")
34
- raise typer.Exit(code=1)
35
- changed = Verifier(root).get_task_changed_files(task_id)
36
- suggestions = TestResolver(root).suggest_for_task(task, changed)
37
- if not include_low_confidence:
38
- suggestions = [s for s in suggestions if s.confidence != "low"]
39
- if apply:
40
- for item in suggestions:
41
- if item.confidence == "high" and item.command not in task.expected_tests:
42
- task.expected_tests.append(item.command)
43
- TaskRepository(session).save(task)
44
- typer.echo(json.dumps({
45
- "task_id": task_id,
46
- "suggestions": [s.model_dump() for s in suggestions],
47
- "expected_tests": task.expected_tests,
48
- }, indent=2))
48
+ with log_stage("evidence", project_root=root, task_id=task_id):
49
+ log_step("evidence/1: resolving test suggestions", project_root=root, task_id=task_id, trace=True)
50
+ with db.get_session() as session:
51
+ task = TaskRepository(session).get_by_id(task_id)
52
+ if not task:
53
+ console.print(f"[red]Task {task_id} not found.[/red]")
54
+ raise typer.Exit(code=1)
55
+ changed = Verifier(root).get_task_changed_files(task_id)
56
+ suggestions = TestResolver(root, _load_repo_map(root)).suggest_for_task(task, changed)
57
+ if not include_low_confidence:
58
+ suggestions = [s for s in suggestions if s.confidence != "low"]
59
+ if apply:
60
+ for item in suggestions:
61
+ if item.confidence == "high" and item.command not in task.expected_tests:
62
+ task.expected_tests.append(item.command)
63
+ TaskRepository(session).save(task)
64
+ typer.echo(dump_json({
65
+ "task_id": task_id,
66
+ "suggestions": [s.model_dump() for s in suggestions],
67
+ "expected_tests": task.expected_tests,
68
+ }, indent=2))
69
+ log_step("evidence/complete", project_root=root, task_id=task_id, trace=True)
@@ -0,0 +1,73 @@
1
+ """Export DevCouncil project state to a portable JSON snapshot (no secrets)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from devcouncil.utils.json_persist import dump_json
6
+ import logging
7
+ from pathlib import Path
8
+
9
+ from devcouncil.utils.json_persist import write_json
10
+
11
+ import typer
12
+ from rich.console import Console
13
+
14
+ from devcouncil.cli.commands.gaps import _gaps_payload
15
+ from devcouncil.cli.commands.init import initialize_project
16
+ from devcouncil.storage.db import get_db
17
+ from devcouncil.storage.repositories import RequirementRepository, TaskRepository
18
+ from devcouncil.telemetry.stages import log_stage, log_step
19
+
20
+ console = Console()
21
+ logger = logging.getLogger(__name__)
22
+
23
+ EXPORT_RELATIVE = Path(".devcouncil") / "export" / "state.json"
24
+
25
+
26
+ def _export_payload(project_root: Path) -> dict:
27
+ initialize_project(project_root, quiet=True)
28
+ db = get_db(project_root)
29
+ if not db:
30
+ return {"initialized": False, "requirements": [], "tasks": [], "gaps": {}}
31
+
32
+ with db.get_session() as session:
33
+ requirements = [req.model_dump() for req in RequirementRepository(session).get_all()]
34
+ tasks = [task.model_dump() for task in TaskRepository(session).get_all()]
35
+ gaps = _gaps_payload(project_root)
36
+ return {
37
+ "initialized": True,
38
+ "requirements": requirements,
39
+ "tasks": tasks,
40
+ "gaps": {
41
+ "blocking_count": gaps.get("blocking_count", 0),
42
+ "advisory_count": gaps.get("advisory_count", 0),
43
+ "total_count": gaps.get("total_count", 0),
44
+ "items": gaps.get("gaps", []),
45
+ },
46
+ }
47
+
48
+
49
+ def export_state(
50
+ project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
51
+ output: Path | None = typer.Option(None, "--output", "-o", help="Output path (default: .devcouncil/export/state.json)."),
52
+ json_format: bool = typer.Option(False, "--json", help="Print payload to stdout instead of writing a file."),
53
+ ):
54
+ """Write requirements, tasks, and gaps summary to .devcouncil/export/state.json."""
55
+ root = project_root.expanduser().resolve()
56
+ from devcouncil.telemetry.logging_setup import set_log_dir
57
+
58
+ set_log_dir(root)
59
+ logger.info("dev export: json=%s", json_format)
60
+
61
+ with log_stage("export", project_root=root):
62
+ log_step("export/1: building snapshot", project_root=root, trace=True)
63
+ payload = _export_payload(root)
64
+ if json_format:
65
+ typer.echo(dump_json(payload, indent=2))
66
+ log_step("export/complete", project_root=root, trace=True)
67
+ return
68
+
69
+ dest = (output or (root / EXPORT_RELATIVE)).expanduser().resolve()
70
+ dest.parent.mkdir(parents=True, exist_ok=True)
71
+ write_json(dest, payload)
72
+ console.print(f"[green]Exported DevCouncil state[/green] to {dest}")
73
+ log_step("export/complete", project_root=root, path=str(dest), trace=True)
@@ -0,0 +1,175 @@
1
+ import logging
2
+ from pathlib import Path
3
+
4
+ import typer
5
+ from rich.console import Console
6
+ from rich.table import Table
7
+ from devcouncil.cli.commands.init import initialize_project
8
+ from devcouncil.storage.db import get_db
9
+ from devcouncil.storage.repositories import ArtifactGraphRepository
10
+ from devcouncil.telemetry.stages import log_stage, log_step
11
+ from devcouncil.utils.json_persist import dump_json
12
+
13
+ console = Console()
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ def _gaps_payload(
18
+ project_root: Path,
19
+ *,
20
+ blocking_only: bool = False,
21
+ task_id: str | None = None,
22
+ ) -> dict:
23
+ initialize_project(project_root, quiet=True)
24
+ db = get_db(project_root)
25
+ if not db:
26
+ return {"initialized": False, "gaps": [], "blocking_count": 0, "advisory_count": 0}
27
+
28
+ with db.get_session() as session:
29
+ if task_id:
30
+ from devcouncil.storage.repositories import GapRepository
31
+
32
+ gaps = GapRepository(session).get_for_task(task_id)
33
+ if blocking_only:
34
+ gaps = [g for g in gaps if g.blocking]
35
+ return {
36
+ "initialized": True,
37
+ "ok": True,
38
+ "task_id": task_id,
39
+ "gaps": [gap.model_dump() for gap in gaps],
40
+ "blocking_count": sum(1 for g in gaps if g.blocking),
41
+ }
42
+
43
+ graph = ArtifactGraphRepository(session).load_graph()
44
+ if blocking_only:
45
+ gaps = graph.blocking_gaps()
46
+ else:
47
+ gaps = sorted(graph.gaps.values(), key=lambda g: (not g.blocking, g.id))
48
+ blocking = [g for g in gaps if g.blocking]
49
+ advisory = [g for g in gaps if not g.blocking]
50
+ return {
51
+ "initialized": True,
52
+ "gaps": [gap.model_dump() for gap in gaps],
53
+ "blocking_count": len(blocking),
54
+ "advisory_count": len(advisory),
55
+ "total_count": len(gaps),
56
+ }
57
+
58
+
59
+ def _next_actions_payload(project_root: Path, task_id: str) -> dict:
60
+ from devcouncil.integrations.mcp.util import allowed_next_tools
61
+ from devcouncil.storage.repositories import GapRepository, TaskRepository
62
+ from devcouncil.verification.next_actions import split_next_actions
63
+
64
+ initialize_project(project_root, quiet=True)
65
+ db = get_db(project_root)
66
+ if not db:
67
+ return {"ok": False, "error": "DevCouncil state is unavailable in this directory."}
68
+
69
+ with db.get_session() as session:
70
+ gaps = GapRepository(session).get_for_task(task_id)
71
+ task = TaskRepository(session).get_by_id(task_id)
72
+ blocking_actions, advisory_actions = split_next_actions(gaps)
73
+ has_blocking = any(g.blocking for g in gaps)
74
+ return {
75
+ "ok": True,
76
+ "task_id": task_id,
77
+ "next_actions": [a.model_dump() for a in blocking_actions],
78
+ "advisory_actions": [a.model_dump() for a in advisory_actions],
79
+ "allowed_next_tools": allowed_next_tools(task.status if task else "planned", has_blocking),
80
+ }
81
+
82
+
83
+ def gaps(
84
+ json_format: bool = typer.Option(False, "--json", help="Output machine-readable JSON."),
85
+ blocking_only: bool = typer.Option(
86
+ False,
87
+ "--blocking-only",
88
+ help="Show only blocking gaps.",
89
+ ),
90
+ task_id: str | None = typer.Option(
91
+ None,
92
+ "--task-id",
93
+ help="Scope gaps to a single task (MCP-compatible payload).",
94
+ ),
95
+ next_actions: bool = typer.Option(
96
+ False,
97
+ "--next-actions",
98
+ help="Emit repair next-actions for --task-id instead of raw gaps.",
99
+ ),
100
+ fail_on_blocking: bool = typer.Option(
101
+ False,
102
+ "--fail-on-blocking",
103
+ help="Exit non-zero when blocking gaps remain.",
104
+ ),
105
+ project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
106
+ ):
107
+ """List all verification gaps (blocking and advisory)."""
108
+ root = project_root.expanduser().resolve()
109
+ from devcouncil.telemetry.logging_setup import set_log_dir
110
+ set_log_dir(root)
111
+ logger.info(
112
+ "dev gaps: json=%s blocking_only=%s fail_on_blocking=%s",
113
+ json_format,
114
+ blocking_only,
115
+ fail_on_blocking,
116
+ )
117
+
118
+ with log_stage("gaps", project_root=root):
119
+ log_step("gaps/1: loading gaps", project_root=root, trace=True)
120
+ if next_actions:
121
+ if not task_id:
122
+ message = "--next-actions requires --task-id"
123
+ if json_format:
124
+ typer.echo(dump_json({"ok": False, "error": message}, indent=2))
125
+ else:
126
+ console.print(f"[red]{message}[/red]")
127
+ raise typer.Exit(code=1)
128
+ payload = _next_actions_payload(root, task_id)
129
+ else:
130
+ payload = _gaps_payload(root, blocking_only=blocking_only, task_id=task_id)
131
+
132
+ def _maybe_fail() -> None:
133
+ if fail_on_blocking and payload.get("blocking_count", 0) > 0:
134
+ raise typer.Exit(code=1)
135
+
136
+ if json_format:
137
+ typer.echo(dump_json(payload, indent=2))
138
+ _maybe_fail()
139
+ log_step("gaps/complete", project_root=root, trace=True)
140
+ return
141
+
142
+ if not payload["initialized"]:
143
+ console.print("[yellow]DevCouncil state is not available in this directory.[/yellow]")
144
+ log_step("gaps/complete", project_root=root, trace=True)
145
+ return
146
+
147
+ table = Table(title="DevCouncil Gaps")
148
+ table.add_column("ID", style="cyan", no_wrap=True)
149
+ table.add_column("Blocking", justify="center")
150
+ table.add_column("Severity")
151
+ table.add_column("Type")
152
+ table.add_column("Task")
153
+ table.add_column("Description")
154
+
155
+ for gap in payload["gaps"]:
156
+ blocking_style = "[red]yes[/red]" if gap.get("blocking") else "[dim]no[/dim]"
157
+ desc = (gap.get("description") or "")[:80]
158
+ table.add_row(
159
+ gap.get("id", ""),
160
+ blocking_style,
161
+ gap.get("severity", ""),
162
+ gap.get("gap_type", ""),
163
+ gap.get("task_id") or "",
164
+ desc,
165
+ )
166
+
167
+ console.print(table)
168
+ console.print(
169
+ f"\n[bold]{payload['total_count']}[/bold] gap(s): "
170
+ f"[red]{payload['blocking_count']} blocking[/red], "
171
+ f"[dim]{payload['advisory_count']} advisory[/dim]"
172
+ )
173
+
174
+ _maybe_fail()
175
+ log_step("gaps/complete", project_root=root, trace=True)
@@ -0,0 +1,76 @@
1
+ """`dev write` and `dev apply-patch` — policy-gated file writes through the CLI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ import typer
10
+
11
+ from devcouncil.execution.gated_write import apply_patch_payload, write_file_payload
12
+ from devcouncil.telemetry.logging_setup import set_log_dir
13
+ from devcouncil.telemetry.stages import log_stage, log_step
14
+ from devcouncil.utils.json_persist import dump_json
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ def write(
20
+ task_id: str = typer.Argument(..., help="Task ID."),
21
+ lease_token: str = typer.Option(..., "--lease-token", help="Lease token from checkout."),
22
+ path: str = typer.Option(..., "--path", help="Repository-relative file path."),
23
+ content: str | None = typer.Option(None, "--content", help="File content (or read from stdin when omitted)."),
24
+ json_format: bool = typer.Option(False, "--json", help="Output machine-readable JSON."),
25
+ project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
26
+ ) -> None:
27
+ """Write a file for a leased task through DevCouncil's policy gate."""
28
+ root = project_root.expanduser().resolve()
29
+ set_log_dir(root)
30
+ body = content if content is not None else sys.stdin.read()
31
+ logger.info("dev write: task=%s path=%s", task_id, path)
32
+
33
+ with log_stage("write", project_root=root, task_id=task_id):
34
+ payload = write_file_payload(root, task_id=task_id, lease_token=lease_token, rel_path=path, content=body)
35
+ if json_format:
36
+ typer.echo(dump_json(payload, indent=2))
37
+ log_step("write/complete", project_root=root, task_id=task_id, trace=True)
38
+ if not payload.get("ok"):
39
+ raise typer.Exit(code=1)
40
+ return
41
+ if not payload.get("ok"):
42
+ typer.echo(payload.get("error") or payload.get("rejected_files"))
43
+ raise typer.Exit(code=1)
44
+ typer.echo(f"Wrote {path}")
45
+ log_step("write/complete", project_root=root, task_id=task_id, trace=True)
46
+
47
+
48
+ def apply_patch(
49
+ task_id: str = typer.Argument(..., help="Task ID."),
50
+ lease_token: str = typer.Option(..., "--lease-token", help="Lease token from checkout."),
51
+ unified_diff: str | None = typer.Option(None, "--unified-diff", help="Unified diff (or read from stdin)."),
52
+ json_format: bool = typer.Option(False, "--json", help="Output machine-readable JSON."),
53
+ project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
54
+ ) -> None:
55
+ """Apply a unified diff for a leased task through DevCouncil's policy gate."""
56
+ root = project_root.expanduser().resolve()
57
+ set_log_dir(root)
58
+ diff_body = unified_diff if unified_diff is not None else sys.stdin.read()
59
+ if not diff_body.strip():
60
+ typer.echo("unified_diff must be a non-empty string")
61
+ raise typer.Exit(code=1)
62
+ logger.info("dev apply-patch: task=%s", task_id)
63
+
64
+ with log_stage("apply-patch", project_root=root, task_id=task_id):
65
+ payload = apply_patch_payload(root, task_id=task_id, lease_token=lease_token, unified_diff=diff_body)
66
+ if json_format:
67
+ typer.echo(dump_json(payload, indent=2))
68
+ log_step("apply-patch/complete", project_root=root, task_id=task_id, trace=True)
69
+ if not payload.get("ok"):
70
+ raise typer.Exit(code=1)
71
+ return
72
+ if not payload.get("ok"):
73
+ typer.echo(payload.get("error") or payload.get("rejected_files"))
74
+ raise typer.Exit(code=1)
75
+ typer.echo(f"Applied patch to {payload.get('applied_files')}")
76
+ log_step("apply-patch/complete", project_root=root, task_id=task_id, trace=True)