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,87 @@
1
+ """Read-only wiki page lookup shared by CLI and MCP."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+
9
+ def wiki_dir_for(root: Path) -> Path:
10
+ from devcouncil.knowledge.wiki import wiki_dir_for as _wiki_dir_for
11
+
12
+ return _wiki_dir_for(root)
13
+
14
+
15
+ def _summary(doc: Any) -> dict[str, object]:
16
+ return {
17
+ "page": doc.rel_path,
18
+ "type": doc.type,
19
+ "title": doc.title,
20
+ "description": doc.description,
21
+ "tags": doc.tags,
22
+ }
23
+
24
+
25
+ def read_wiki_page(
26
+ root: Path,
27
+ *,
28
+ page: str | None = None,
29
+ query: str | None = None,
30
+ ) -> dict[str, object]:
31
+ """Return MCP/CLI-compatible payload for wiki listing, page fetch, or search."""
32
+ from devcouncil.knowledge.okf import read_bundle
33
+
34
+ wiki_dir = wiki_dir_for(root)
35
+ if not (wiki_dir / "index.md").is_file():
36
+ return {
37
+ "ok": False,
38
+ "error": "No codebase wiki has been generated. Run `dev wiki update` to create it.",
39
+ "code": "not_found",
40
+ }
41
+
42
+ bundle = read_bundle(wiki_dir)
43
+ pages = {
44
+ doc.rel_path: doc
45
+ for doc in bundle.documents
46
+ if doc.rel_path not in ("log.md",)
47
+ }
48
+
49
+ if page:
50
+ doc = pages.get(page.strip())
51
+ if doc is None:
52
+ return {
53
+ "ok": False,
54
+ "error": f"Wiki page {page!r} not found.",
55
+ "code": "not_found",
56
+ "available": sorted(pages),
57
+ }
58
+ return {"ok": True, **_summary(doc), "body": doc.body, "truncated": False}
59
+
60
+ if query:
61
+ terms = [term for term in query.lower().split() if term]
62
+ scored: list[tuple[int, Any]] = []
63
+ for doc in pages.values():
64
+ haystack = " ".join([doc.title, doc.description, " ".join(doc.tags), doc.rel_path]).lower()
65
+ hits = sum(1 for term in terms if term in haystack)
66
+ if hits:
67
+ scored.append((hits, doc))
68
+ scored.sort(key=lambda item: (-item[0], item[1].rel_path))
69
+ matches = [doc for _, doc in scored[:5]]
70
+ if not matches:
71
+ return {
72
+ "ok": True,
73
+ "query": query,
74
+ "matches": [],
75
+ "available": [_summary(doc) for doc in pages.values()],
76
+ }
77
+ top = matches[0]
78
+ return {
79
+ "ok": True,
80
+ "query": query,
81
+ "matches": [_summary(doc) for doc in matches],
82
+ **_summary(top),
83
+ "body": top.body,
84
+ "truncated": False,
85
+ }
86
+
87
+ return {"ok": True, "pages": [_summary(doc) for doc in pages.values()]}
@@ -1,12 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import hashlib
4
- import json
5
4
  import re
6
5
  from dataclasses import dataclass
7
6
  from pathlib import Path
8
7
 
9
8
  from devcouncil.live.models import AgentTurn, CardStatus, CritiqueCard, Verdict
9
+ from devcouncil.utils.json_persist import read_model_json, write_model_json
10
10
 
11
11
  RISK_TERMS = (
12
12
  "skip tests",
@@ -239,7 +239,7 @@ def save_card(project_root: Path, card: CritiqueCard) -> Path:
239
239
  cards_dir = project_root / ".devcouncil" / "live" / "cards"
240
240
  cards_dir.mkdir(parents=True, exist_ok=True)
241
241
  path = cards_dir / f"{card.id}.json"
242
- path.write_text(card.model_dump_json(indent=2) + "\n", encoding="utf-8")
242
+ write_model_json(path, card)
243
243
  return path
244
244
 
245
245
 
@@ -254,7 +254,7 @@ def load_cards(project_root: Path) -> list[CritiqueCard]:
254
254
  cards: list[CritiqueCard] = []
255
255
  for path in sorted(cards_dir.glob("*.json"), key=lambda item: item.stat().st_mtime, reverse=True):
256
256
  try:
257
- cards.append(CritiqueCard.model_validate(json.loads(path.read_text(encoding="utf-8"))))
257
+ cards.append(read_model_json(path, CritiqueCard))
258
258
  except Exception:
259
259
  continue
260
260
  return cards
@@ -305,7 +305,7 @@ def load_card_by_id(project_root: Path, card_id: str) -> CritiqueCard | None:
305
305
  if not path.exists():
306
306
  return None
307
307
  try:
308
- return CritiqueCard.model_validate(json.loads(path.read_text(encoding="utf-8")))
308
+ return read_model_json(path, CritiqueCard)
309
309
  except Exception:
310
310
  return None
311
311
 
@@ -319,9 +319,9 @@ def update_card_status(project_root: Path, card_id: str, status: CardStatus) ->
319
319
  path = cards_dir / f"{card_id}.json"
320
320
  if not path.exists():
321
321
  return None
322
- card = CritiqueCard.model_validate(json.loads(path.read_text(encoding="utf-8")))
322
+ card = read_model_json(path, CritiqueCard)
323
323
  updated = card.model_copy(update={"status": status})
324
- path.write_text(updated.model_dump_json(indent=2) + "\n", encoding="utf-8")
324
+ write_model_json(path, updated)
325
325
  return updated
326
326
 
327
327
 
@@ -334,7 +334,7 @@ def unresolved_blocking_cards(
334
334
  source = cards if cards is not None else load_cards(project_root)
335
335
  return [
336
336
  card for card in source
337
- if card.status == "open" and card.verdict == "Critical Issues"
337
+ if card.status == "open" and card.blocks_completion
338
338
  and (task_id is None or card.task_id in {None, task_id})
339
339
  ]
340
340
 
@@ -53,10 +53,13 @@ class CritiqueCard(BaseModel):
53
53
  status: CardStatus = "open"
54
54
  created_at: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
55
55
  source_path: str | None = None
56
+ # When False the card is advisory (``dev run``/``dev e2e``); only ``dev watch`` cards
57
+ # with blocks_gate=True may block verification.
58
+ blocks_gate: bool = True
56
59
 
57
60
  @property
58
61
  def blocks_completion(self) -> bool:
59
- return self.verdict == "Critical Issues"
62
+ return self.verdict == "Critical Issues" and self.blocks_gate
60
63
 
61
64
 
62
65
  def session_id_from_path(path: Path) -> str:
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import asyncio
3
4
  import logging
4
5
  from pathlib import Path
5
6
 
@@ -23,12 +24,21 @@ class LiveReviewService:
23
24
  project_root: Path,
24
25
  client: str = "generic",
25
26
  use_llm: bool = False,
27
+ task_id: str | None = None,
26
28
  ) -> CritiqueCard:
27
- fallback = review_turn(turn, project_root, client=client)
29
+ fallback = review_turn(turn, project_root, client=client, task_id=task_id)
28
30
  if not use_llm or self.router is None:
29
31
  logger.debug("Live review (deterministic) for %s turn=%s: %s", client, turn.turn_id, fallback.verdict)
30
32
  return fallback
31
33
 
34
+ # Ground the model in the task's RECORDED verification state. Without this
35
+ # the reviewer sees only the agent's prose and reliably hallucinates
36
+ # "provides no evidence of implementation" against code whose acceptance
37
+ # criteria DevCouncil has already proven with executed checks (observed:
38
+ # a benchmark task blocked at 4/4 hidden-test correctness because the
39
+ # ungrounded reviewer critiqued the agent's summary message).
40
+ grounding_block = self._grounding_block(project_root, task_id)
41
+
32
42
  prompt = f"""
33
43
  You are DevCouncil's live coding-agent reviewer.
34
44
  Review the latest assistant response before the developer follows it.
@@ -43,6 +53,14 @@ Return a critique card with:
43
53
  Do not praise. Do not review formatting. Focus on correctness, missing requirements, architectural drift,
44
54
  unsafe commands, weak evidence, and premature completion claims.
45
55
 
56
+ Rules for the verdict:
57
+ - The response text is a SUMMARY, not the work itself. Never conclude "no code change /
58
+ no evidence" from the response alone — the recorded verification state below (when
59
+ present) is the authoritative account of the work.
60
+ - "Critical Issues" requires a concrete, checkable problem: a claim contradicted by the
61
+ recorded state, an unsafe command, or a missing requirement — never vagueness, brevity,
62
+ style, or your estimate of the agent's effort.
63
+ {grounding_block}
46
64
  Client: {client}
47
65
  Session: {turn.session_id}
48
66
  Turn: {turn.turn_id}
@@ -51,30 +69,43 @@ Assistant response:
51
69
  {turn.content}
52
70
  """
53
71
  samples = self._samples(project_root)
54
- cards: list[CritiqueCard] = []
55
- for attempt in range(samples):
72
+
73
+ async def _one_sample(attempt: int) -> CritiqueCard | None:
74
+ if self.router is None:
75
+ return None
56
76
  # Vary temperature so independent samples actually differ (and so the router
57
77
  # cache returns distinct generations). Attempt 0 stays deterministic.
58
78
  temperature = 0.0 if attempt == 0 else min(0.8, 0.3 + 0.2 * attempt)
59
79
  try:
60
- cards.append(await self.router.complete_structured(
80
+ return await self.router.complete_structured(
61
81
  role=self.role,
62
82
  messages=[{"role": "user", "content": prompt}],
63
83
  schema=CritiqueCard,
64
84
  temperature=temperature,
65
- ))
85
+ )
66
86
  except ValueError:
67
87
  try:
68
- cards.append(await self.router.complete_structured(
88
+ return await self.router.complete_structured(
69
89
  role="implementation_reviewer",
70
90
  messages=[{"role": "user", "content": prompt}],
71
91
  schema=CritiqueCard,
72
92
  temperature=temperature,
73
- ))
93
+ )
74
94
  except Exception:
75
- continue
95
+ return None
76
96
  except Exception:
77
- continue
97
+ return None
98
+
99
+ # The samples are independent by construction (no sample reads another's
100
+ # output), so gather them concurrently instead of awaiting one at a time.
101
+ # Order is preserved (attempt 0's deterministic card stays first) and each
102
+ # sample keeps its own error handling above; return_exceptions only shields
103
+ # siblings from anything that still escapes, which the filter then drops.
104
+ results = await asyncio.gather(
105
+ *(_one_sample(attempt) for attempt in range(samples)),
106
+ return_exceptions=True,
107
+ )
108
+ cards: list[CritiqueCard] = [card for card in results if isinstance(card, CritiqueCard)]
78
109
 
79
110
  if not cards:
80
111
  logger.warning("Live review produced no cards for %s turn=%s; using deterministic fallback", client, turn.turn_id)
@@ -93,10 +124,58 @@ Assistant response:
93
124
  "source_path": fallback.source_path,
94
125
  })
95
126
 
127
+ @staticmethod
128
+ def _grounding_block(project_root: Path, task_id: str | None) -> str:
129
+ """Render the task's recorded verification state for the review prompt.
130
+
131
+ Best-effort: no task id / no DB / unknown task yields an empty block and
132
+ the model reviews ungrounded (the deterministic fallback stays grounded
133
+ via ``review_turn``)."""
134
+ try:
135
+ from devcouncil.live.cards import _load_task_grounding
136
+
137
+ grounding = _load_task_grounding(project_root, task_id)
138
+ except Exception:
139
+ grounding = None
140
+ if grounding is None:
141
+ return ""
142
+ if grounding.is_satisfied:
143
+ corroboration = (
144
+ "This state CORROBORATES a completion claim: do not demand further "
145
+ "evidence and do not raise Critical Issues about proof — the "
146
+ "executable evidence already exists in DevCouncil's records."
147
+ )
148
+ else:
149
+ corroboration = (
150
+ "This state does NOT yet corroborate a completion claim: if the "
151
+ "response claims success, that mismatch is the concern to raise "
152
+ "(cite the numbers above)."
153
+ )
154
+ return f"""
155
+ Recorded verification state for task {grounding.task_id} (authoritative — measured from
156
+ executed checks and the artifact graph, not taken from the agent's words):
157
+ - task status: {grounding.status}
158
+ - blocking gaps: {grounding.blocking_gaps}
159
+ - failing verification commands: {grounding.failing_commands}
160
+ - acceptance criteria with passing evidence: {grounding.acs_passing}/{grounding.acs_total}
161
+ {corroboration}
162
+ """
163
+
96
164
  def _samples(self, project_root: Path) -> int:
97
165
  try:
98
- from devcouncil.app.config import load_config
99
- return max(1, load_config(project_root).verification.reviewer_checks.samples)
166
+ from devcouncil.app.config import load_config, role_runs_on_local_provider
167
+
168
+ cfg = load_config(project_root)
169
+ # Auto-tune by reviewer locality: a cost-free local (Ollama) reviewer votes
170
+ # over 3 independent samples (outvoting a lone mis-calibrated verdict); a
171
+ # paid cloud reviewer stays single-shot. Explicit config always wins —
172
+ # but an explicitly single-shot LOCAL reviewer is flagged, never silent.
173
+ local_reviewer = role_runs_on_local_provider(cfg, "live_reviewer")
174
+ from devcouncil.telemetry.logging_setup import warn_once
175
+
176
+ for warning in cfg.verification.reviewer_checks.unsafe_override_warnings(local_reviewer):
177
+ warn_once(logger, warning) # _samples() runs per review; warn once
178
+ return cfg.verification.reviewer_checks.resolved(local_reviewer)
100
179
  except Exception:
101
180
  return 1
102
181
 
@@ -8,6 +8,8 @@ from typing import Any
8
8
 
9
9
  from pydantic import BaseModel, Field
10
10
 
11
+ from devcouncil.utils.json_persist import read_json, write_model_json
12
+
11
13
 
12
14
  class ReviewSignal(BaseModel):
13
15
  client: str = "generic"
@@ -47,7 +49,7 @@ def write_signal(project_root: Path, client: str, payload: dict[str, Any]) -> Pa
47
49
  digest = hashlib.sha256(key.encode("utf-8", errors="replace")).hexdigest()[:12]
48
50
  path = directory / f"{client.lower()}-{digest}.json"
49
51
  signal.path = str(path)
50
- path.write_text(signal.model_dump_json(indent=2) + "\n", encoding="utf-8")
52
+ write_model_json(path, signal)
51
53
  return path
52
54
 
53
55
 
@@ -58,7 +60,7 @@ def load_signals(project_root: Path) -> list[ReviewSignal]:
58
60
  signals: list[ReviewSignal] = []
59
61
  for path in sorted(directory.glob("*.json"), key=lambda item: item.stat().st_mtime, reverse=True):
60
62
  try:
61
- raw = json.loads(path.read_text(encoding="utf-8"))
63
+ raw = read_json(path)
62
64
  signal = ReviewSignal.model_validate(raw)
63
65
  signal.path = str(path)
64
66
  signals.append(signal)
@@ -3,16 +3,25 @@ from __future__ import annotations
3
3
  from pathlib import Path
4
4
 
5
5
  from devcouncil.storage.db import get_db
6
+ from devcouncil.storage.native import TaskLeaseRepository
6
7
  from devcouncil.storage.repositories import TaskRepository
7
8
 
8
9
 
9
10
  def active_task_id(project_root: Path) -> str | None:
10
- """Return the single running DevCouncil task ID, if one is unambiguous."""
11
+ """Return the single active DevCouncil task ID, if one is unambiguous.
12
+
13
+ Prefers a task in ``running`` status. When none are running, falls back to the
14
+ task behind exactly one non-expired active lease (``dev checkout`` path).
15
+ """
11
16
  db = get_db(project_root)
12
17
  if not db:
13
18
  return None
14
19
  with db.get_session() as session:
15
20
  running = [task for task in TaskRepository(session).get_all() if task.status == "running"]
16
- if len(running) != 1:
21
+ if len(running) == 1:
22
+ return running[0].id
23
+ leases = TaskLeaseRepository(session).list_leases(active_only=True)
24
+ active = [lease for lease, expired in leases if not expired]
25
+ if len(active) == 1:
26
+ return active[0].task_id
17
27
  return None
18
- return running[0].id
@@ -1,10 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import json
4
+ import shutil
4
5
  from pathlib import Path
5
6
  from typing import Any, Iterable, Literal
6
7
 
7
8
  from devcouncil.live.models import AgentSession, AgentTurn, session_id_from_path
9
+ from devcouncil.utils.json_persist import read_json
8
10
 
9
11
  RoleName = Literal["user", "assistant", "system", "tool", "unknown"]
10
12
  KNOWN_ROLES: set[RoleName] = {"user", "assistant", "system", "tool"}
@@ -86,11 +88,76 @@ def latest_assistant_turn(path: Path, client: str = "generic") -> AgentTurn | No
86
88
  return result
87
89
 
88
90
 
91
+ def claude_project_slug(project_root: Path | str) -> str:
92
+ """Encode a path the way Claude Code names dirs under ``~/.claude/projects/``."""
93
+ resolved = Path(project_root).expanduser().resolve()
94
+ return resolved.as_posix().replace("/", "-")
95
+
96
+
97
+ def _read_claude_session_id(project_root: Path, task_id: str | None) -> str | None:
98
+ if not task_id:
99
+ return None
100
+ path = project_root / ".devcouncil" / "sessions" / f"{task_id}-claude.json"
101
+ if not path.is_file():
102
+ return None
103
+ try:
104
+ data = read_json(path) or {}
105
+ except (json.JSONDecodeError, OSError):
106
+ return None
107
+ session_id = str(data.get("session_id") or "").strip()
108
+ return session_id or None
109
+
110
+
111
+ def claude_transcript_for_session(project_root: Path, session_id: str) -> Path | None:
112
+ """Return the native Claude JSONL for ``session_id`` in this project, if present."""
113
+ if not session_id:
114
+ return None
115
+ slug_dir = CLAUDE_TRANSCRIPT_ROOT / claude_project_slug(project_root)
116
+ direct = slug_dir / f"{session_id}.jsonl"
117
+ if direct.is_file():
118
+ return direct
119
+ for candidate in _claude_transcript_candidates(project_root):
120
+ if candidate.stem == session_id:
121
+ return candidate
122
+ return None
123
+
124
+
125
+ def claude_transcript_for_task(project_root: Path, task_id: str) -> Path | None:
126
+ """Resolve the Claude transcript DevCouncil pinned for ``task_id``."""
127
+ session_id = _read_claude_session_id(project_root, task_id)
128
+ if session_id:
129
+ path = claude_transcript_for_session(project_root, session_id)
130
+ if path is not None:
131
+ return path
132
+ return None
133
+
134
+
135
+ def mirror_claude_transcript(project_root: Path, session_id: str) -> Path | None:
136
+ """Copy the native Claude JSONL into ``.devcouncil/live/claude/`` for discovery.
137
+
138
+ Headless ``dev run``/``dev e2e`` pins a session id up front; mirroring the native
139
+ transcript into the project makes live review resilient even when Claude's global
140
+ project slug is slow to appear or ``discover_sessions`` would otherwise race."""
141
+ source = claude_transcript_for_session(project_root, session_id)
142
+ if source is None or not source.is_file():
143
+ return None
144
+ dest_dir = project_root / ".devcouncil" / "live" / "claude"
145
+ dest_dir.mkdir(parents=True, exist_ok=True)
146
+ dest = dest_dir / f"{session_id}.jsonl"
147
+ try:
148
+ shutil.copy2(source, dest)
149
+ except OSError:
150
+ return None
151
+ return dest
152
+
153
+
89
154
  def _claude_transcript_candidates(project_root: Path) -> list[Path]:
90
155
  local_runtime = project_root / ".devcouncil" / "live" / "claude"
91
156
  candidates = list(local_runtime.glob("*.jsonl"))
92
157
  if CLAUDE_TRANSCRIPT_ROOT.exists():
93
- candidates.extend(CLAUDE_TRANSCRIPT_ROOT.rglob("*.jsonl"))
158
+ slug_dir = CLAUDE_TRANSCRIPT_ROOT / claude_project_slug(project_root)
159
+ if slug_dir.is_dir():
160
+ candidates.extend(slug_dir.glob("*.jsonl"))
94
161
  # Dedup, then sort by path for a deterministic, stat-free order. discover_sessions
95
162
  # re-sorts the resulting sessions by mtime; because that sort is stable, giving it a
96
163
  # deterministic input keeps the tie-break (equal-mtime sessions) stable across runs —
@@ -144,7 +211,7 @@ def _role(raw: dict[str, Any]) -> RoleName:
144
211
  if isinstance(nested, str):
145
212
  return nested if nested in KNOWN_ROLES else "unknown"
146
213
  record_type = raw.get("type")
147
- if record_type in KNOWN_ROLES:
214
+ if isinstance(record_type, str) and record_type in KNOWN_ROLES:
148
215
  return record_type
149
216
  return "unknown"
150
217
 
@@ -4,6 +4,7 @@ import logging
4
4
  from pathlib import Path
5
5
  from typing import Optional
6
6
  from devcouncil.llm.provider import LLMResponse
7
+ from devcouncil.utils.json_persist import read_json, write_json
7
8
 
8
9
  logger = logging.getLogger(__name__)
9
10
 
@@ -33,10 +34,9 @@ class LLMCache:
33
34
  cache_file = self.cache_dir / f"{key}.json"
34
35
  if cache_file.exists():
35
36
  try:
36
- with open(cache_file, "r") as f:
37
- data = json.load(f)
38
- logger.debug("LLM cache HIT model=%s key=%s", model, key[:12])
39
- return LLMResponse(**data)
37
+ data = read_json(cache_file)
38
+ logger.debug("LLM cache HIT model=%s key=%s", model, key[:12])
39
+ return LLMResponse(**data)
40
40
  except Exception as e:
41
41
  logger.warning("LLM cache read failed for key=%s: %s", key[:12], e)
42
42
  logger.debug("LLM cache MISS model=%s key=%s", model, key[:12])
@@ -45,6 +45,5 @@ class LLMCache:
45
45
  def set(self, model: str, messages: list, temp: float, json_mode: bool, response: LLMResponse, provider_fingerprint: str = "", cache_key: Optional[str] = None):
46
46
  key = cache_key if cache_key is not None else self._get_key(model, messages, temp, json_mode, provider_fingerprint)
47
47
  cache_file = self.cache_dir / f"{key}.json"
48
- with open(cache_file, "w") as f:
49
- json.dump(response.model_dump(), f)
48
+ write_json(cache_file, response.model_dump())
50
49
  logger.debug("LLM cache STORE model=%s key=%s", model, key[:12])
@@ -1,14 +1,14 @@
1
1
  openrouter:
2
- spec_writer: anthropic/claude-sonnet-4.6
3
- prompt_enhancer: anthropic/claude-sonnet-4.6
4
- planner_a: anthropic/claude-sonnet-4.6
5
- planner_b: google/gemini-2.5-pro
6
- critic_a: openai/gpt-5.5
7
- critic_b: anthropic/claude-opus-4.8
8
- arbiter: openai/gpt-5.5
9
- native_agent: anthropic/claude-sonnet-4.6
10
- implementation_reviewer: openai/gpt-5.5
11
- live_reviewer: openai/gpt-5.5
2
+ spec_writer: qwen/qwen3-max
3
+ prompt_enhancer: qwen/qwen3-max
4
+ planner_a: qwen/qwen3-max
5
+ planner_b: qwen/qwen3-max
6
+ critic_a: qwen/qwen3-max
7
+ critic_b: qwen/qwen3-max
8
+ arbiter: qwen/qwen3-max
9
+ native_agent: qwen/qwen3-max
10
+ implementation_reviewer: qwen/qwen3-max
11
+ live_reviewer: qwen/qwen3-max
12
12
  vertexai:
13
13
  spec_writer: google/gemini-2.5-flash
14
14
  prompt_enhancer: google/gemini-2.5-flash