devcouncil 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (365) hide show
  1. package/README.md +46 -30
  2. package/package.json +6 -2
  3. package/packages/codeintel-grammars/hatch_build.py +43 -0
  4. package/packages/codeintel-grammars/pyproject.toml +16 -0
  5. package/packages/codeintel-grammars/src/devcouncil_codeintel_grammars/__init__.py +93 -0
  6. package/pyproject.toml +99 -4
  7. package/src/devcouncil/app/config.py +512 -20
  8. package/src/devcouncil/app/events.py +4 -23
  9. package/src/devcouncil/app/orchestrator.py +5 -0
  10. package/src/devcouncil/app/run_context.py +3 -3
  11. package/src/devcouncil/assets/__init__.py +4 -1
  12. package/src/devcouncil/assets/vendor/force-graph.min.js +5 -0
  13. package/src/devcouncil/campaign/__init__.py +71 -0
  14. package/src/devcouncil/campaign/bloom.py +137 -0
  15. package/src/devcouncil/campaign/dashboard.py +123 -0
  16. package/src/devcouncil/campaign/mailbox.py +305 -0
  17. package/src/devcouncil/campaign/notify.py +91 -0
  18. package/src/devcouncil/campaign/orchestrator.py +592 -0
  19. package/src/devcouncil/campaign/prompts/coordinator.md +29 -0
  20. package/src/devcouncil/campaign/prompts/director.md +21 -0
  21. package/src/devcouncil/campaign/prompts/protocol.md +46 -0
  22. package/src/devcouncil/campaign/prompts/reviewer.md +24 -0
  23. package/src/devcouncil/campaign/prompts/worker.md +24 -0
  24. package/src/devcouncil/campaign/roles.py +202 -0
  25. package/src/devcouncil/campaign/watcher.py +153 -0
  26. package/src/devcouncil/cli/commands/agents.py +24 -17
  27. package/src/devcouncil/cli/commands/artifacts.py +36 -27
  28. package/src/devcouncil/cli/commands/ast.py +12 -3
  29. package/src/devcouncil/cli/commands/baseline.py +21 -12
  30. package/src/devcouncil/cli/commands/boot.py +218 -0
  31. package/src/devcouncil/cli/commands/campaign.py +302 -0
  32. package/src/devcouncil/cli/commands/check.py +225 -12
  33. package/src/devcouncil/cli/commands/config.py +221 -74
  34. package/src/devcouncil/cli/commands/cost.py +137 -28
  35. package/src/devcouncil/cli/commands/dashboard.py +12 -4
  36. package/src/devcouncil/cli/commands/debug_cmd.py +249 -0
  37. package/src/devcouncil/cli/commands/design.py +27 -17
  38. package/src/devcouncil/cli/commands/doctor.py +790 -8
  39. package/src/devcouncil/cli/commands/evidence.py +41 -20
  40. package/src/devcouncil/cli/commands/export.py +73 -0
  41. package/src/devcouncil/cli/commands/gaps.py +175 -0
  42. package/src/devcouncil/cli/commands/gated_write.py +76 -0
  43. package/src/devcouncil/cli/commands/go.py +220 -68
  44. package/src/devcouncil/cli/commands/graph_cmd.py +1192 -0
  45. package/src/devcouncil/cli/commands/handoff.py +45 -34
  46. package/src/devcouncil/cli/commands/hook.py +630 -85
  47. package/src/devcouncil/cli/commands/init.py +89 -30
  48. package/src/devcouncil/cli/commands/integrate.py +296 -1385
  49. package/src/devcouncil/cli/commands/lease.py +120 -0
  50. package/src/devcouncil/cli/commands/logs.py +12 -5
  51. package/src/devcouncil/cli/commands/lsp.py +40 -5
  52. package/src/devcouncil/cli/commands/map.py +317 -74
  53. package/src/devcouncil/cli/commands/mcp_server.py +12 -2
  54. package/src/devcouncil/cli/commands/okf.py +44 -6
  55. package/src/devcouncil/cli/commands/plan.py +184 -69
  56. package/src/devcouncil/cli/commands/prompt.py +26 -17
  57. package/src/devcouncil/cli/commands/provenance.py +79 -0
  58. package/src/devcouncil/cli/commands/repair.py +60 -49
  59. package/src/devcouncil/cli/commands/report.py +148 -40
  60. package/src/devcouncil/cli/commands/requirements.py +104 -0
  61. package/src/devcouncil/cli/commands/reset_demo_state.py +13 -4
  62. package/src/devcouncil/cli/commands/rollback.py +46 -35
  63. package/src/devcouncil/cli/commands/run.py +173 -8
  64. package/src/devcouncil/cli/commands/runs.py +298 -68
  65. package/src/devcouncil/cli/commands/scaffold.py +33 -12
  66. package/src/devcouncil/cli/commands/semantic.py +29 -14
  67. package/src/devcouncil/cli/commands/setup.py +103 -93
  68. package/src/devcouncil/cli/commands/shell.py +51 -42
  69. package/src/devcouncil/cli/commands/show.py +56 -42
  70. package/src/devcouncil/cli/commands/skills.py +29 -20
  71. package/src/devcouncil/cli/commands/status.py +80 -67
  72. package/src/devcouncil/cli/commands/task_gate.py +295 -0
  73. package/src/devcouncil/cli/commands/tasks.py +248 -19
  74. package/src/devcouncil/cli/commands/trace.py +14 -8
  75. package/src/devcouncil/cli/commands/verify.py +33 -8
  76. package/src/devcouncil/cli/commands/version.py +14 -6
  77. package/src/devcouncil/cli/commands/watch.py +49 -30
  78. package/src/devcouncil/cli/commands/watch_fs.py +30 -19
  79. package/src/devcouncil/cli/commands/wiki.py +278 -0
  80. package/src/devcouncil/cli/main.py +58 -1
  81. package/src/devcouncil/codeintel/__init__.py +16 -0
  82. package/src/devcouncil/codeintel/build_control.py +429 -0
  83. package/src/devcouncil/codeintel/build_worker.py +78 -0
  84. package/src/devcouncil/codeintel/debug/__init__.py +17 -0
  85. package/src/devcouncil/codeintel/debug/broker.py +114 -0
  86. package/src/devcouncil/codeintel/debug/broker_client.py +61 -0
  87. package/src/devcouncil/codeintel/debug/consent.py +36 -0
  88. package/src/devcouncil/codeintel/debug/discovery.py +132 -0
  89. package/src/devcouncil/codeintel/debug/fingerprint.py +85 -0
  90. package/src/devcouncil/codeintel/debug/protocol.py +259 -0
  91. package/src/devcouncil/codeintel/debug/python_trace_runner.py +81 -0
  92. package/src/devcouncil/codeintel/debug/session.py +238 -0
  93. package/src/devcouncil/codeintel/debug/tracing.py +201 -0
  94. package/src/devcouncil/codeintel/languages/__init__.py +17 -0
  95. package/src/devcouncil/codeintel/languages/generic_extractor.py +236 -0
  96. package/src/devcouncil/codeintel/languages/registry.py +149 -0
  97. package/src/devcouncil/codeintel/languages/workers.py +245 -0
  98. package/src/devcouncil/codeintel/query/__init__.py +5 -0
  99. package/src/devcouncil/codeintel/query/engine.py +289 -0
  100. package/src/devcouncil/codeintel/resolution/__init__.py +6 -0
  101. package/src/devcouncil/codeintel/resolution/abstract_state.py +301 -0
  102. package/src/devcouncil/codeintel/resolution/frameworks/__init__.py +33 -0
  103. package/src/devcouncil/codeintel/resolution/frameworks/base.py +46 -0
  104. package/src/devcouncil/codeintel/resolution/frameworks/di.py +56 -0
  105. package/src/devcouncil/codeintel/resolution/frameworks/events.py +45 -0
  106. package/src/devcouncil/codeintel/resolution/frameworks/routes.py +88 -0
  107. package/src/devcouncil/codeintel/resolution/semantic.py +887 -0
  108. package/src/devcouncil/codeintel/service.py +104 -0
  109. package/src/devcouncil/codeintel/store/__init__.py +15 -0
  110. package/src/devcouncil/codeintel/store/sqlite.py +1565 -0
  111. package/src/devcouncil/codeintel/sync/__init__.py +19 -0
  112. package/src/devcouncil/codeintel/sync/coordinator.py +430 -0
  113. package/src/devcouncil/codeintel/sync/incremental.py +484 -0
  114. package/src/devcouncil/codeintel/sync/lease.py +96 -0
  115. package/src/devcouncil/codeintel/sync/scope.py +98 -0
  116. package/src/devcouncil/council/__init__.py +4 -0
  117. package/src/devcouncil/council/prompts/__init__.py +4 -0
  118. package/src/devcouncil/domain/checkpoint_refs.py +17 -0
  119. package/src/devcouncil/domain/evidence.py +1 -0
  120. package/src/devcouncil/domain/gap.py +10 -0
  121. package/src/devcouncil/domain/requirement.py +5 -1
  122. package/src/devcouncil/domain/task.py +43 -2
  123. package/src/devcouncil/execution/checkpoints.py +25 -31
  124. package/src/devcouncil/execution/context_builder.py +15 -44
  125. package/src/devcouncil/execution/fs_watcher.py +64 -0
  126. package/src/devcouncil/execution/gated_write.py +203 -0
  127. package/src/devcouncil/execution/handoff.py +2 -1
  128. package/src/devcouncil/execution/hook_policy.py +19 -5
  129. package/src/devcouncil/execution/lease_ops.py +177 -0
  130. package/src/devcouncil/execution/lease_validation.py +71 -0
  131. package/src/devcouncil/execution/patch.py +3 -0
  132. package/src/devcouncil/execution/permissions.py +1 -0
  133. package/src/devcouncil/execution/policy_engine.py +205 -10
  134. package/src/devcouncil/execution/prompt_builder.py +278 -33
  135. package/src/devcouncil/execution/run_trace.py +356 -0
  136. package/src/devcouncil/execution/shell_session.py +46 -5
  137. package/src/devcouncil/execution/stop_gate.py +746 -0
  138. package/src/devcouncil/execution/stop_gate_history.py +113 -0
  139. package/src/devcouncil/execution/stop_gate_state.py +54 -0
  140. package/src/devcouncil/execution/stop_gate_verify_cache.py +69 -0
  141. package/src/devcouncil/execution/task_gate_ops.py +590 -0
  142. package/src/devcouncil/execution/task_runner.py +19 -0
  143. package/src/devcouncil/executors/advisor_tool.py +315 -0
  144. package/src/devcouncil/executors/agent_registry.py +125 -17
  145. package/src/devcouncil/executors/claude_sdk.py +376 -0
  146. package/src/devcouncil/executors/coding_cli.py +724 -25
  147. package/src/devcouncil/executors/mini_swe.py +50 -8
  148. package/src/devcouncil/executors/native/agent.py +224 -19
  149. package/src/devcouncil/executors/openhands.py +50 -8
  150. package/src/devcouncil/executors/transient_retry.py +99 -0
  151. package/src/devcouncil/gating/checks/clean_git.py +5 -2
  152. package/src/devcouncil/gating/checks/planned_files_check.py +38 -11
  153. package/src/devcouncil/gating/checks/secret_scan_check.py +2 -2
  154. package/src/devcouncil/gating/policy.py +46 -2
  155. package/src/devcouncil/indexing/ast_matcher.py +41 -4
  156. package/src/devcouncil/indexing/graph/__init__.py +78 -0
  157. package/src/devcouncil/indexing/graph/api_routes.py +522 -0
  158. package/src/devcouncil/indexing/graph/build.py +862 -0
  159. package/src/devcouncil/indexing/graph/cache.py +329 -0
  160. package/src/devcouncil/indexing/graph/communities.py +28 -0
  161. package/src/devcouncil/indexing/graph/cypher.py +107 -0
  162. package/src/devcouncil/indexing/graph/embeddings.py +194 -0
  163. package/src/devcouncil/indexing/graph/export.py +381 -0
  164. package/src/devcouncil/indexing/graph/export_links.py +81 -0
  165. package/src/devcouncil/indexing/graph/extract_python.py +307 -0
  166. package/src/devcouncil/indexing/graph/extract_ts.py +1205 -0
  167. package/src/devcouncil/indexing/graph/intel.py +668 -0
  168. package/src/devcouncil/indexing/graph/liveness.py +992 -0
  169. package/src/devcouncil/indexing/graph/okf_export.py +65 -0
  170. package/src/devcouncil/indexing/graph/pdg/__init__.py +67 -0
  171. package/src/devcouncil/indexing/graph/pdg/build.py +11 -0
  172. package/src/devcouncil/indexing/graph/pdg/cdg.py +41 -0
  173. package/src/devcouncil/indexing/graph/pdg/cfg.py +199 -0
  174. package/src/devcouncil/indexing/graph/pdg/query.py +21 -0
  175. package/src/devcouncil/indexing/graph/pdg/reaching_def.py +126 -0
  176. package/src/devcouncil/indexing/graph/pdg/schema.py +253 -0
  177. package/src/devcouncil/indexing/graph/pdg/taint.py +154 -0
  178. package/src/devcouncil/indexing/graph/query.py +302 -0
  179. package/src/devcouncil/indexing/graph/resolve.py +1020 -0
  180. package/src/devcouncil/indexing/graph/schema.py +103 -0
  181. package/src/devcouncil/indexing/graph_index.py +20 -29
  182. package/src/devcouncil/indexing/lsp.py +57 -25
  183. package/src/devcouncil/indexing/lsp_client.py +577 -0
  184. package/src/devcouncil/indexing/map_artifacts.py +355 -0
  185. package/src/devcouncil/indexing/map_refresh.py +141 -0
  186. package/src/devcouncil/indexing/repo_mapper.py +1509 -138
  187. package/src/devcouncil/indexing/semantic_index.py +12 -6
  188. package/src/devcouncil/indexing/subsystem_map.py +163 -0
  189. package/src/devcouncil/indexing/ts_imports.py +343 -0
  190. package/src/devcouncil/indexing/viz.py +960 -0
  191. package/src/devcouncil/indexing/walk.py +52 -0
  192. package/src/devcouncil/indexing/wiring.py +1776 -0
  193. package/src/devcouncil/integrations/actions.py +27 -4
  194. package/src/devcouncil/integrations/check.py +211 -16
  195. package/src/devcouncil/integrations/claude_assets.py +209 -12
  196. package/src/devcouncil/integrations/clients/__init__.py +1 -0
  197. package/src/devcouncil/integrations/clients/aider.py +52 -0
  198. package/src/devcouncil/integrations/clients/antigravity.py +87 -0
  199. package/src/devcouncil/integrations/clients/claude.py +339 -0
  200. package/src/devcouncil/integrations/clients/codex.py +39 -0
  201. package/src/devcouncil/integrations/clients/common.py +332 -0
  202. package/src/devcouncil/integrations/clients/cursor.py +164 -0
  203. package/src/devcouncil/integrations/clients/gemini.py +49 -0
  204. package/src/devcouncil/integrations/clients/grok.py +105 -0
  205. package/src/devcouncil/integrations/clients/hooks.py +500 -0
  206. package/src/devcouncil/integrations/clients/opencode.py +96 -0
  207. package/src/devcouncil/integrations/clients/warp.py +75 -0
  208. package/src/devcouncil/integrations/code_review_graph.py +2 -2
  209. package/src/devcouncil/integrations/github.py +73 -7
  210. package/src/devcouncil/integrations/integration_cli.py +197 -0
  211. package/src/devcouncil/integrations/mcp/handlers/__init__.py +1 -0
  212. package/src/devcouncil/integrations/mcp/handlers/ast_lsp.py +77 -0
  213. package/src/devcouncil/integrations/mcp/handlers/checkout.py +50 -0
  214. package/src/devcouncil/integrations/mcp/handlers/cli_gate.py +43 -0
  215. package/src/devcouncil/integrations/mcp/handlers/codeintel.py +182 -0
  216. package/src/devcouncil/integrations/mcp/handlers/debug.py +236 -0
  217. package/src/devcouncil/integrations/mcp/handlers/evidence.py +70 -0
  218. package/src/devcouncil/integrations/mcp/handlers/git.py +99 -0
  219. package/src/devcouncil/integrations/mcp/handlers/graph.py +36 -0
  220. package/src/devcouncil/integrations/mcp/handlers/handoff.py +53 -0
  221. package/src/devcouncil/integrations/mcp/handlers/knowledge.py +30 -0
  222. package/src/devcouncil/integrations/mcp/handlers/lease.py +70 -0
  223. package/src/devcouncil/integrations/mcp/handlers/live.py +108 -0
  224. package/src/devcouncil/integrations/mcp/handlers/map.py +676 -0
  225. package/src/devcouncil/integrations/mcp/handlers/next_task.py +35 -0
  226. package/src/devcouncil/integrations/mcp/handlers/policy.py +80 -0
  227. package/src/devcouncil/integrations/mcp/handlers/prompts.py +168 -0
  228. package/src/devcouncil/integrations/mcp/handlers/provenance.py +101 -0
  229. package/src/devcouncil/integrations/mcp/handlers/read.py +74 -0
  230. package/src/devcouncil/integrations/mcp/handlers/router_cache.py +53 -0
  231. package/src/devcouncil/integrations/mcp/handlers/run.py +53 -0
  232. package/src/devcouncil/integrations/mcp/handlers/runs.py +84 -0
  233. package/src/devcouncil/integrations/mcp/handlers/scope.py +56 -0
  234. package/src/devcouncil/integrations/mcp/handlers/status.py +114 -0
  235. package/src/devcouncil/integrations/mcp/handlers/task.py +100 -0
  236. package/src/devcouncil/integrations/mcp/handlers/tool_specs.py +904 -0
  237. package/src/devcouncil/integrations/mcp/handlers/trace.py +65 -0
  238. package/src/devcouncil/integrations/mcp/handlers/verify.py +45 -0
  239. package/src/devcouncil/integrations/mcp/handlers/wiki.py +60 -0
  240. package/src/devcouncil/integrations/mcp/handlers/write.py +69 -0
  241. package/src/devcouncil/integrations/mcp/server.py +265 -2391
  242. package/src/devcouncil/integrations/mcp/util.py +303 -0
  243. package/src/devcouncil/integrations/setup.py +152 -0
  244. package/src/devcouncil/knowledge/fetch.py +4 -0
  245. package/src/devcouncil/knowledge/knowledge_select.py +38 -0
  246. package/src/devcouncil/knowledge/okf.py +2 -1
  247. package/src/devcouncil/knowledge/resource_discovery.py +40 -0
  248. package/src/devcouncil/knowledge/wiki.py +643 -0
  249. package/src/devcouncil/knowledge/wiki_read.py +87 -0
  250. package/src/devcouncil/live/cards.py +7 -7
  251. package/src/devcouncil/live/models.py +4 -1
  252. package/src/devcouncil/live/reviewer.py +90 -11
  253. package/src/devcouncil/live/signals.py +4 -2
  254. package/src/devcouncil/live/tasks.py +12 -3
  255. package/src/devcouncil/live/transcripts.py +69 -2
  256. package/src/devcouncil/llm/cache.py +5 -6
  257. package/src/devcouncil/llm/model_defaults.yaml +10 -10
  258. package/src/devcouncil/llm/provider.py +647 -73
  259. package/src/devcouncil/llm/router.py +271 -46
  260. package/src/devcouncil/llm/semantic_bridge.py +614 -0
  261. package/src/devcouncil/optimization/gepa_agent.py +6 -4
  262. package/src/devcouncil/optimization/skillopt.py +9 -5
  263. package/src/devcouncil/planning/arbiter_service.py +12 -3
  264. package/src/devcouncil/planning/correction_manifest.py +107 -10
  265. package/src/devcouncil/planning/plan_difficulty.py +69 -0
  266. package/src/devcouncil/planning/plan_service.py +5 -2
  267. package/src/devcouncil/planning/planned_files_reconcile.py +191 -0
  268. package/src/devcouncil/planning/prompt_enhancer_service.py +6 -5
  269. package/src/devcouncil/planning/question_conversion.py +56 -0
  270. package/src/devcouncil/planning/spec_service.py +9 -3
  271. package/src/devcouncil/repo/ci_scaffold.py +197 -1
  272. package/src/devcouncil/repo/gitignore.py +1 -2
  273. package/src/devcouncil/reporting/evidence_export.py +124 -0
  274. package/src/devcouncil/reporting/evidence_html.py +210 -0
  275. package/src/devcouncil/reporting/json_report.py +16 -12
  276. package/src/devcouncil/reporting/markdown_report.py +38 -9
  277. package/src/devcouncil/reporting/mcp_resources.py +142 -0
  278. package/src/devcouncil/reporting/report_builder.py +40 -4
  279. package/src/devcouncil/reporting/task_provenance.py +42 -0
  280. package/src/devcouncil/reporting/verdict.py +75 -0
  281. package/src/devcouncil/skills/library/README.md +1 -0
  282. package/src/devcouncil/skills/library/devcouncil-hero-loop.md +109 -0
  283. package/src/devcouncil/skills/library/devcouncil-verification.md +109 -0
  284. package/src/devcouncil/skills/library/devcouncil.md +93 -0
  285. package/src/devcouncil/skills/registry.py +43 -12
  286. package/src/devcouncil/storage/db.py +57 -11
  287. package/src/devcouncil/storage/models.py +6 -0
  288. package/src/devcouncil/storage/native.py +5 -3
  289. package/src/devcouncil/storage/repositories.py +50 -18
  290. package/src/devcouncil/telemetry/context.py +28 -0
  291. package/src/devcouncil/telemetry/cost.py +4 -5
  292. package/src/devcouncil/telemetry/logging_setup.py +78 -11
  293. package/src/devcouncil/telemetry/model_pricing.yaml +7 -0
  294. package/src/devcouncil/telemetry/stages.py +27 -2
  295. package/src/devcouncil/telemetry/tracker.py +50 -13
  296. package/src/devcouncil/ui/dashboard.py +120 -8
  297. package/src/devcouncil/utils/fsio.py +58 -0
  298. package/src/devcouncil/utils/git_snapshot.py +112 -0
  299. package/src/devcouncil/utils/json_persist.py +53 -0
  300. package/src/devcouncil/utils/proc.py +89 -0
  301. package/src/devcouncil/verification/acceptance_compiler.py +36 -13
  302. package/src/devcouncil/verification/ad_hoc_check.py +95 -3
  303. package/src/devcouncil/verification/checks/__init__.py +41 -0
  304. package/src/devcouncil/verification/checks/acceptance.py +39 -0
  305. package/src/devcouncil/verification/checks/acceptance_corpus.py +194 -0
  306. package/src/devcouncil/verification/checks/acceptance_evidence.py +239 -0
  307. package/src/devcouncil/verification/checks/command_evidence.py +148 -0
  308. package/src/devcouncil/verification/checks/compiled_acceptance.py +179 -0
  309. package/src/devcouncil/verification/checks/corpus_stale.py +124 -0
  310. package/src/devcouncil/verification/checks/corpus_verification.py +9 -0
  311. package/src/devcouncil/verification/checks/dead_symbols.py +360 -0
  312. package/src/devcouncil/verification/checks/diff_coverage_gate.py +101 -0
  313. package/src/devcouncil/verification/checks/doc_code_ref.py +79 -0
  314. package/src/devcouncil/verification/checks/liveness_ratchet.py +336 -0
  315. package/src/devcouncil/verification/checks/orphan_diff.py +104 -0
  316. package/src/devcouncil/verification/checks/planned_files.py +98 -0
  317. package/src/devcouncil/verification/checks/semantic_diff.py +241 -0
  318. package/src/devcouncil/verification/checks/stale_map.py +80 -0
  319. package/src/devcouncil/verification/checks/stub_scan.py +71 -0
  320. package/src/devcouncil/verification/checks/subsystem_boundary.py +103 -0
  321. package/src/devcouncil/verification/checks/wiring.py +216 -0
  322. package/src/devcouncil/verification/claims/__init__.py +23 -0
  323. package/src/devcouncil/verification/claims/checks.py +395 -0
  324. package/src/devcouncil/verification/claims/mapper.py +168 -0
  325. package/src/devcouncil/verification/claims/models.py +39 -0
  326. package/src/devcouncil/verification/claims/transcript.py +92 -0
  327. package/src/devcouncil/verification/claims/verdict.py +88 -0
  328. package/src/devcouncil/verification/command_evidence.py +170 -0
  329. package/src/devcouncil/verification/command_malformation.py +147 -0
  330. package/src/devcouncil/verification/command_runner.py +164 -0
  331. package/src/devcouncil/verification/coverage_measurement.py +292 -0
  332. package/src/devcouncil/verification/diff_coverage.py +151 -0
  333. package/src/devcouncil/verification/difficulty.py +296 -0
  334. package/src/devcouncil/verification/effort_heuristics.py +178 -0
  335. package/src/devcouncil/verification/gap_ids.py +63 -0
  336. package/src/devcouncil/verification/gate_cache.py +194 -0
  337. package/src/devcouncil/verification/gate_selector.py +344 -0
  338. package/src/devcouncil/verification/git_diff_fallback.py +272 -0
  339. package/src/devcouncil/verification/implementation_reviewer.py +13 -0
  340. package/src/devcouncil/verification/incremental_check.py +241 -0
  341. package/src/devcouncil/verification/next_actions.py +60 -1
  342. package/src/devcouncil/verification/rigor_analytics.py +130 -0
  343. package/src/devcouncil/verification/sandbox.py +38 -11
  344. package/src/devcouncil/verification/stub_detector.py +369 -0
  345. package/src/devcouncil/verification/test_resolver.py +67 -1
  346. package/src/devcouncil/verification/verifier.py +137 -1666
  347. package/src/devcouncil/verification/verify_orchestration.py +610 -0
  348. package/src/devcouncil/verification/verify_setup.py +176 -0
  349. package/src/devcouncil/verification/wiki_refresh.py +208 -0
  350. package/src/semantic_layer/__init__.py +58 -0
  351. package/src/semantic_layer/benchmark.py +75 -0
  352. package/src/semantic_layer/cache.py +290 -0
  353. package/src/semantic_layer/compressor.py +137 -0
  354. package/src/semantic_layer/config.py +75 -0
  355. package/src/semantic_layer/embeddings.py +69 -0
  356. package/src/semantic_layer/llm_backends.py +99 -0
  357. package/src/semantic_layer/pipeline.py +111 -0
  358. package/src/semantic_layer/router.py +128 -0
  359. package/src/semantic_layer/tuner.py +72 -0
  360. package/uv.lock +973 -9
  361. package/src/devcouncil/artifacts/migrations.py +0 -20
  362. package/src/devcouncil/artifacts/schemas.py +0 -23
  363. package/src/devcouncil/artifacts/serializer.py +0 -21
  364. package/src/devcouncil/integrations/gitnexus.py +0 -70
  365. package/src/devcouncil/integrations/graphify.py +0 -34
@@ -0,0 +1,176 @@
1
+ """Verify-task setup and teardown helpers extracted from Verifier."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import logging
7
+ from dataclasses import dataclass, field
8
+ from typing import Any, List, Optional, TYPE_CHECKING
9
+
10
+ from devcouncil.app.config import load_config
11
+ from devcouncil.domain.requirement import Requirement
12
+ from devcouncil.domain.task import Task
13
+ from devcouncil.telemetry.stages import log_step
14
+ from devcouncil.utils.git_snapshot import GitWorktreeSnapshot
15
+ from devcouncil.verification.difficulty import resolve_rigor_policy
16
+
17
+ if TYPE_CHECKING:
18
+ from devcouncil.verification.verifier import Verifier
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+ _AD_HOC_TASK_ID = "CHECK"
23
+
24
+
25
+ @dataclass
26
+ class VerifyRunContext:
27
+ """Configuration and diff state primed at the start of verify_task."""
28
+
29
+ config: Any | None
30
+ rigor: Any
31
+ ac_samples: int = 1
32
+ ac_repair_attempts: int = 1
33
+ ac_per_criterion: bool = False
34
+ command_timeout: int = 300
35
+ changed_files: List[str] = field(default_factory=list)
36
+ diff_content: str = ""
37
+ diff_empty: bool = True
38
+
39
+
40
+ def prime_verify_memos(verifier: Verifier) -> None:
41
+ """Batch git plumbing and prime per-call caches."""
42
+ snapshot = GitWorktreeSnapshot.capture(verifier.project_root)
43
+ verifier._git_fallback.git_snapshot = snapshot
44
+ verifier._git_fallback.untracked_cache = snapshot.untracked_files
45
+
46
+
47
+ def resolve_verify_context(
48
+ verifier: Verifier,
49
+ task: Task,
50
+ requirements: List[Requirement],
51
+ ) -> VerifyRunContext:
52
+ """Load config, rigor policy, and diff snapshot for a verify run."""
53
+ ac_samples, ac_repair_attempts = 1, 1
54
+ ac_per_criterion = False
55
+ cfg = None
56
+ command_timeout = 300
57
+ try:
58
+ from devcouncil.app.config import role_runs_on_local_provider
59
+
60
+ cfg = load_config(verifier.project_root)
61
+ command_timeout = cfg.execution.command_timeout
62
+ local_monitor = role_runs_on_local_provider(cfg, "implementation_reviewer")
63
+ ac_samples, ac_repair_attempts, ac_per_criterion = (
64
+ cfg.verification.acceptance_checks.resolved(local_monitor)
65
+ )
66
+ # Explicit overrides are honored but never silently: single-shot acceptance
67
+ # checks on a local monitor rubber-stamped real defects in calibration probes.
68
+ # Once per process — verify runs per task, and 20 identical lines are spam.
69
+ from devcouncil.telemetry.logging_setup import warn_once
70
+
71
+ for warning in cfg.verification.acceptance_checks.unsafe_override_warnings(local_monitor):
72
+ warn_once(logger, warning)
73
+ except Exception as exc:
74
+ # Without config we cannot detect a local monitor, so this falls back to
75
+ # single-shot cloud defaults — the exact mode that is unsafe if the monitor
76
+ # IS local. Say so instead of degrading silently.
77
+ from devcouncil.telemetry.logging_setup import warn_once
78
+
79
+ warn_once(
80
+ logger,
81
+ f"Verification config unavailable ({exc}); acceptance checks fall back to "
82
+ "single-shot defaults (samples=1). If the monitor runs on a local model, "
83
+ "fix the config load — single-shot local checks miss real defects.",
84
+ )
85
+ cfg = None
86
+ rigor = resolve_rigor_policy(task, requirements, config=cfg)
87
+ if rigor.min_acceptance_samples > 1:
88
+ ac_samples = max(ac_samples, rigor.min_acceptance_samples)
89
+ log_step(
90
+ "verify/1: resolved rigor policy",
91
+ project_root=verifier.project_root,
92
+ task_id=task.id,
93
+ difficulty=rigor.difficulty,
94
+ rigor_applied=list(rigor.applied),
95
+ )
96
+ verifier._command_timeout_cache = command_timeout
97
+ is_ad_hoc_check = task.id == _AD_HOC_TASK_ID
98
+ # CHECK represents the live working tree, including edits recorded in the global
99
+ # baseline. Baseline/checkpoint subtraction only applies to persisted workflow tasks.
100
+ changed_files = (
101
+ verifier.get_changed_files()
102
+ if is_ad_hoc_check
103
+ else verifier.get_task_changed_files(task.id)
104
+ )
105
+ diff_content = verifier.get_diff()
106
+ if not is_ad_hoc_check:
107
+ committed_diff = verifier._committed_task_diff(task.id)
108
+ if committed_diff.strip():
109
+ diff_content = committed_diff
110
+ diff_empty = not bool(diff_content.strip())
111
+ log_step(
112
+ "verify/2: collected diff and changed files",
113
+ project_root=verifier.project_root,
114
+ task_id=task.id,
115
+ changed_files=len(changed_files),
116
+ diff_empty=diff_empty,
117
+ )
118
+ return VerifyRunContext(
119
+ config=cfg,
120
+ rigor=rigor,
121
+ ac_samples=ac_samples,
122
+ ac_repair_attempts=ac_repair_attempts,
123
+ ac_per_criterion=ac_per_criterion,
124
+ command_timeout=command_timeout,
125
+ changed_files=changed_files,
126
+ diff_content=diff_content,
127
+ diff_empty=diff_empty,
128
+ )
129
+
130
+
131
+ def start_verify_futures(
132
+ verifier: Verifier,
133
+ *,
134
+ task: Task,
135
+ requirements: List[Requirement],
136
+ diff_content: str,
137
+ ac_samples: int,
138
+ ac_per_criterion: bool,
139
+ ) -> tuple[Optional[asyncio.Task[Any]], Optional[asyncio.Task[Any]]]:
140
+ """Launch acceptance-compiler and implementation-reviewer LLM passes."""
141
+ compile_future: Optional[asyncio.Task[Any]] = None
142
+ if verifier.acceptance_compiler and diff_content and task.acceptance_criterion_ids:
143
+ if hasattr(verifier.acceptance_compiler, "compile_candidates"):
144
+ compile_coro = verifier.acceptance_compiler.compile_candidates(
145
+ task, requirements, diff_content, samples=ac_samples,
146
+ per_criterion=ac_per_criterion,
147
+ )
148
+ else:
149
+ compile_coro = verifier.acceptance_compiler.compile(task, requirements, diff_content)
150
+ compile_future = asyncio.create_task(compile_coro)
151
+ review_future: Optional[asyncio.Task[Any]] = None
152
+ if verifier.reviewer and diff_content:
153
+ review_future = asyncio.create_task(
154
+ verifier.reviewer.review_changes(task, requirements, diff_content)
155
+ )
156
+ return compile_future, review_future
157
+
158
+
159
+ async def cleanup_verify_futures(
160
+ verifier: Verifier,
161
+ compile_future: Optional[asyncio.Task[Any]],
162
+ review_future: Optional[asyncio.Task[Any]],
163
+ ) -> None:
164
+ """Drain background LLM tasks and clear per-call memos."""
165
+ for fut in (compile_future, review_future):
166
+ if fut is not None:
167
+ if not fut.done():
168
+ fut.cancel()
169
+ try:
170
+ await fut
171
+ except (asyncio.CancelledError, Exception):
172
+ pass
173
+ verifier._git_fallback.git_snapshot = None
174
+ verifier._git_fallback.untracked_cache = None
175
+ verifier._command_timeout_cache = None
176
+ verifier._project_deps_cache = None
@@ -0,0 +1,208 @@
1
+ """Post-verify wiki-freshness trigger for large refactors.
2
+
3
+ The agent-facing codebase wiki (``dev wiki``) drifts as the code changes. A big
4
+ refactor — one that spans several subsystems or touches many files — is exactly the kind
5
+ of change that leaves the wiki stale. This module decides, after a verify run, whether a
6
+ change was "large" and either FLAGS the pages that a refresh would rewrite (cheap, the
7
+ default) or actually triggers ``dev wiki update --no-llm`` as a post-step
8
+ (``verification.wiki_refresh.auto_update``).
9
+
10
+ Deliberately best-effort and non-blocking: it is an orientation aid, never a gate. Any
11
+ failure (no map, no wiki, config error) degrades to "not considered" and is logged, not
12
+ raised. Flagging is free of model calls; auto-update runs the deterministic skeleton.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import logging
18
+ from dataclasses import dataclass, field
19
+ from pathlib import Path
20
+ from typing import TYPE_CHECKING, List, Mapping, Optional, Sequence
21
+
22
+ from devcouncil.indexing.subsystem_map import areas_touched
23
+
24
+ if TYPE_CHECKING:
25
+ from devcouncil.domain.gap import Gap
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+
30
+ @dataclass
31
+ class WikiRefreshOutcome:
32
+ considered: bool = False # was the change large enough to act on?
33
+ triggered: bool = False # did we actually run an update?
34
+ subsystems_touched: int = 0
35
+ files_touched: int = 0
36
+ stale_pages: List[str] = field(default_factory=list)
37
+ reason: str = ""
38
+
39
+ def to_dict(self) -> dict:
40
+ return {
41
+ "considered": self.considered,
42
+ "triggered": self.triggered,
43
+ "subsystems_touched": self.subsystems_touched,
44
+ "files_touched": self.files_touched,
45
+ "stale_pages": list(self.stale_pages),
46
+ "reason": self.reason,
47
+ }
48
+
49
+
50
+ def _resolve_config(project_root: Path, config):
51
+ if config is not None:
52
+ return config
53
+ try:
54
+ from devcouncil.app.config import load_config
55
+
56
+ return load_config(project_root).verification.wiki_refresh
57
+ except Exception as exc:
58
+ logger.debug("wiki refresh: config unavailable: %s", exc)
59
+ return None
60
+
61
+
62
+ def is_large_change(
63
+ *, files_touched: int, subsystems_touched: int, min_files: int, min_subsystems: int
64
+ ) -> bool:
65
+ """A change is "large" when it spans many subsystems OR touches many files."""
66
+ return subsystems_touched >= min_subsystems or files_touched >= min_files
67
+
68
+
69
+ def _stale_pages(project_root: Path) -> List[str]:
70
+ """Pages a refresh would rewrite, or ``[]`` when the wiki/map is unavailable."""
71
+ try:
72
+ from devcouncil.indexing.repo_mapper import RepoMap
73
+ from devcouncil.knowledge.wiki import wiki_dir_for, wiki_stale_pages
74
+
75
+ map_path = project_root / ".devcouncil" / "repo_map.json"
76
+ wiki_dir = wiki_dir_for(project_root)
77
+ if not map_path.is_file() or not (wiki_dir / "index.md").is_file():
78
+ return []
79
+ repo_map = RepoMap.model_validate_json(map_path.read_text(encoding="utf-8"))
80
+ return sorted(wiki_stale_pages(project_root, repo_map, wiki_dir).keys())
81
+ except Exception as exc:
82
+ logger.debug("wiki refresh: stale-page detection failed: %s", exc)
83
+ return []
84
+
85
+
86
+ def _run_update(project_root: Path) -> bool:
87
+ """Run the deterministic wiki refresh (no model calls). True on success."""
88
+ try:
89
+ from devcouncil.knowledge.wiki import refresh_wiki
90
+
91
+ refresh_wiki(project_root, llm=False, force=False, remap=False)
92
+ return True
93
+ except Exception as exc:
94
+ logger.warning("wiki refresh: auto-update failed: %s", exc)
95
+ return False
96
+
97
+
98
+ def wiki_refresh_advisory_gap(
99
+ outcome: WikiRefreshOutcome,
100
+ *,
101
+ task_id: str,
102
+ gap_id: str,
103
+ ) -> "Gap | None":
104
+ """Return a non-blocking advisory gap when stale wiki pages were flagged."""
105
+ if not (outcome.considered and not outcome.triggered and outcome.stale_pages):
106
+ return None
107
+ from devcouncil.domain.gap import Gap
108
+
109
+ preview = ", ".join(outcome.stale_pages[:5])
110
+ if len(outcome.stale_pages) > 5:
111
+ preview = f"{preview} (+{len(outcome.stale_pages) - 5} more)"
112
+ return Gap(
113
+ id=gap_id,
114
+ severity="low",
115
+ gap_type="architecture_drift",
116
+ task_id=task_id,
117
+ description=(
118
+ f"Codebase wiki may be stale after this large change: "
119
+ f"{len(outcome.stale_pages)} page(s) need refresh ({preview})."
120
+ ),
121
+ evidence=list(outcome.stale_pages[:10]),
122
+ recommended_fix="Run `dev wiki update` to refresh agent-facing documentation.",
123
+ blocking=False,
124
+ )
125
+
126
+
127
+ def evaluate_wiki_refresh(
128
+ project_root: Path,
129
+ changed_files: Sequence[str],
130
+ *,
131
+ repo_map: Optional[Mapping] = None,
132
+ config=None,
133
+ ) -> WikiRefreshOutcome:
134
+ """Decide whether the change warrants a wiki refresh and act per config.
135
+
136
+ Returns a :class:`WikiRefreshOutcome`. When the change is large: with
137
+ ``auto_update`` it runs ``dev wiki update --no-llm`` and reports ``triggered``;
138
+ otherwise it reports the ``stale_pages`` a manual ``dev wiki update`` would rewrite.
139
+ """
140
+ root = Path(project_root)
141
+ changed = [p for p in changed_files if p and p.strip()]
142
+ cfg = _resolve_config(root, config)
143
+ if cfg is not None and not getattr(cfg, "enabled", True):
144
+ return WikiRefreshOutcome(reason="wiki refresh disabled", files_touched=len(changed))
145
+
146
+ min_files = int(getattr(cfg, "min_files", 8)) if cfg is not None else 8
147
+ min_subsystems = int(getattr(cfg, "min_subsystems", 3)) if cfg is not None else 3
148
+ auto_update = bool(getattr(cfg, "auto_update", False)) if cfg is not None else False
149
+
150
+ if repo_map is None:
151
+ try:
152
+ from devcouncil.utils.json_persist import read_json
153
+
154
+ map_path = root / ".devcouncil" / "repo_map.json"
155
+ repo_map = read_json(map_path) if map_path.is_file() else None
156
+ except Exception:
157
+ repo_map = None
158
+
159
+ areas = areas_touched(changed, repo_map) if repo_map else []
160
+ files_touched = len(changed)
161
+ subsystems_touched = len(areas)
162
+
163
+ if not is_large_change(
164
+ files_touched=files_touched,
165
+ subsystems_touched=subsystems_touched,
166
+ min_files=min_files,
167
+ min_subsystems=min_subsystems,
168
+ ):
169
+ return WikiRefreshOutcome(
170
+ considered=False,
171
+ files_touched=files_touched,
172
+ subsystems_touched=subsystems_touched,
173
+ reason=(
174
+ f"change below threshold ({files_touched} file(s), "
175
+ f"{subsystems_touched} subsystem(s); need >={min_files} files "
176
+ f"or >={min_subsystems} subsystems)"
177
+ ),
178
+ )
179
+
180
+ outcome = WikiRefreshOutcome(
181
+ considered=True,
182
+ files_touched=files_touched,
183
+ subsystems_touched=subsystems_touched,
184
+ )
185
+ if auto_update:
186
+ outcome.triggered = _run_update(root)
187
+ outcome.reason = (
188
+ "large change — ran `dev wiki update --no-llm`"
189
+ if outcome.triggered else "large change — auto-update attempted but failed"
190
+ )
191
+ logger.info(
192
+ "wiki refresh: large change (%d files / %d subsystems) — auto-update %s",
193
+ files_touched, subsystems_touched, "ok" if outcome.triggered else "failed",
194
+ )
195
+ else:
196
+ outcome.stale_pages = _stale_pages(root)
197
+ outcome.reason = (
198
+ f"large change — {len(outcome.stale_pages)} stale wiki page(s); "
199
+ "run `dev wiki update`"
200
+ if outcome.stale_pages
201
+ else "large change — wiki may be stale; run `dev wiki update`"
202
+ )
203
+ logger.info(
204
+ "wiki refresh: large change (%d files / %d subsystems) — %d stale page(s) "
205
+ "flagged (run `dev wiki update`)",
206
+ files_touched, subsystems_touched, len(outcome.stale_pages),
207
+ )
208
+ return outcome
@@ -0,0 +1,58 @@
1
+ """Semantic layer for local LLM pipelines — cache, routing, and RAG compression."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .benchmark import benchmark_cache_path, run_benchmark
6
+ from .cache import CacheEntry, CacheLookupResult, SemanticCache
7
+ from .compressor import CompressedContext, SemanticCompressor
8
+ from .config import (
9
+ CacheConfig,
10
+ CompressorConfig,
11
+ EmbeddingConfig,
12
+ LLMConfig,
13
+ ModelTier,
14
+ RouterConfig,
15
+ SemanticLayerConfig,
16
+ )
17
+ from .embeddings import EmbeddingService
18
+ from .llm_backends import (
19
+ HuggingFaceBackend,
20
+ LLMBackend,
21
+ LlamaCppBackend,
22
+ OllamaBackend,
23
+ create_backend,
24
+ )
25
+ from .pipeline import PipelineResponse, SemanticPipeline
26
+ from .router import RouteDecision, SemanticRouter
27
+ from .tuner import LookupEvent, ThresholdAutoTuner
28
+
29
+ __all__ = [
30
+ "CacheConfig",
31
+ "CacheEntry",
32
+ "CacheLookupResult",
33
+ "CompressedContext",
34
+ "CompressorConfig",
35
+ "EmbeddingConfig",
36
+ "EmbeddingService",
37
+ "HuggingFaceBackend",
38
+ "LLMBackend",
39
+ "LLMConfig",
40
+ "LlamaCppBackend",
41
+ "LookupEvent",
42
+ "ModelTier",
43
+ "OllamaBackend",
44
+ "PipelineResponse",
45
+ "RouteDecision",
46
+ "RouterConfig",
47
+ "SemanticCache",
48
+ "SemanticCompressor",
49
+ "SemanticLayerConfig",
50
+ "SemanticPipeline",
51
+ "SemanticRouter",
52
+ "ThresholdAutoTuner",
53
+ "benchmark_cache_path",
54
+ "create_backend",
55
+ "run_benchmark",
56
+ ]
57
+
58
+ __version__ = "1.0.0"
@@ -0,0 +1,75 @@
1
+ """Benchmark harness for semantic layer latency and cache hit rate."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import statistics
6
+ import time
7
+ from typing import Sequence
8
+
9
+ from .config import SemanticLayerConfig
10
+ from .pipeline import SemanticPipeline
11
+
12
+
13
+ def benchmark_cache_path(
14
+ pipeline: SemanticPipeline,
15
+ queries: Sequence[str],
16
+ warmup: int = 5,
17
+ ) -> dict[str, float]:
18
+ """Measure semantic-layer-only latency (no LLM) for cache hits."""
19
+ from .embeddings import EmbeddingService
20
+
21
+ embedder = EmbeddingService.get_instance()
22
+ latencies: list[float] = []
23
+
24
+ for q in list(queries)[:warmup]:
25
+ embedder.embed_one(q)
26
+
27
+ for q in queries:
28
+ t0 = time.perf_counter()
29
+ vec = embedder.embed_one(q)
30
+ pipeline.cache.lookup(q, vec)
31
+ latencies.append((time.perf_counter() - t0) * 1000)
32
+
33
+ if not latencies:
34
+ return {
35
+ "p50_ms": 0.0,
36
+ "p95_ms": 0.0,
37
+ "p99_ms": 0.0,
38
+ "mean_ms": 0.0,
39
+ "hit_rate": pipeline.cache.hit_rate,
40
+ }
41
+
42
+ latencies.sort()
43
+ last = len(latencies) - 1
44
+ return {
45
+ "p50_ms": latencies[len(latencies) // 2],
46
+ "p95_ms": latencies[int(last * 0.95)],
47
+ "p99_ms": latencies[int(last * 0.99)],
48
+ "mean_ms": statistics.mean(latencies),
49
+ "hit_rate": pipeline.cache.hit_rate,
50
+ }
51
+
52
+
53
+ def run_benchmark(queries: list[str], documents: list[str] | None = None) -> None:
54
+ config = SemanticLayerConfig()
55
+ pipeline = SemanticPipeline(config)
56
+
57
+ # Prime cache
58
+ for q in queries[:10]:
59
+ pipeline.run(q, documents)
60
+
61
+ stats = benchmark_cache_path(pipeline, queries)
62
+ print("Semantic layer cache-path benchmark:")
63
+ for k, v in stats.items():
64
+ print(f" {k}: {v:.2f}" if isinstance(v, float) else f" {k}: {v}")
65
+
66
+
67
+ if __name__ == "__main__":
68
+ sample_queries = [
69
+ "What is Python?",
70
+ "Explain Python programming language",
71
+ "Define Python language",
72
+ "How do I write a for loop in Python?",
73
+ "Implement a distributed cache with consistent hashing",
74
+ ] * 20
75
+ run_benchmark(sample_queries)