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,500 @@
1
+ """Hooks integration adapter."""
2
+ from __future__ import annotations
3
+
4
+ from pathlib import Path
5
+
6
+ import typer
7
+
8
+ from devcouncil.executors.agent_registry import GEMINI_DEPRECATION_MESSAGE
9
+ from devcouncil.integrations.clients import common as _common
10
+ from devcouncil.integrations.clients import opencode as _opencode
11
+ _project_root = _common._project_root
12
+ _warn_if_verify_only = _common._warn_if_verify_only
13
+ _server_args = _common._server_args
14
+ _format_command = _common._format_command
15
+ _quote_powershell_arg = _common._quote_powershell_arg
16
+ _run = _common._run
17
+ _run_capture = _common._run_capture
18
+ _config_path = _common._config_path
19
+ _load_raw_config = _common._load_raw_config
20
+ _save_raw_config = _common._save_raw_config
21
+ _load_json = _common._load_json
22
+ _save_json = _common._save_json
23
+ _load_json_strict = _common._load_json_strict
24
+ _mutate_raw_config = _common._mutate_raw_config
25
+ _batched_raw_config = _common._batched_raw_config
26
+ _probe_mcp_tools = _common._probe_mcp_tools
27
+ _print_command = _common._print_command
28
+ _configure = _common._configure
29
+ resolve_dev_executable = _common.resolve_dev_executable
30
+ record_hook_dev_executable = _common.record_hook_dev_executable
31
+ check_hook_dev_executable = _common.check_hook_dev_executable
32
+
33
+ console = _common.console
34
+ OPENCODE_HOOK_PLUGIN_NAME = _common.OPENCODE_HOOK_PLUGIN_NAME
35
+ SUPPORTED_HOOK_TOOLS = _common.SUPPORTED_HOOK_TOOLS
36
+
37
+ SESSION_START_MATCHER = "startup|resume|clear|compact"
38
+
39
+ _opencode_plugin_path = _opencode._opencode_plugin_path
40
+ _opencode_plugin_source = _opencode._opencode_plugin_source
41
+ _opencode_config_path = _opencode._opencode_config_path
42
+ _record_opencode_config = _opencode._record_opencode_config
43
+
44
+ def _stop_hook_timeout_seconds(project_root: Path) -> int:
45
+ """Allow up to 150 seconds when the stop gate runs claims + verification."""
46
+ try:
47
+ from devcouncil.app.config import load_config
48
+
49
+ sg = load_config(project_root).execution.stop_gate
50
+ mode = (sg.mode or "off").strip().lower()
51
+ if mode != "off" and (sg.check_claims or sg.verify_active_task):
52
+ return 150
53
+ except Exception:
54
+ pass
55
+ return 10
56
+
57
+
58
+ def _hook_command(project_root: Path, client: str, event: str) -> str:
59
+ # Absolute path to project-venv (or PATH) `dev` so a stale global install cannot
60
+ # shadow the repo's CLI from PostToolUse / PreToolUse hooks.
61
+ executable = resolve_dev_executable(project_root)
62
+ return _format_command([
63
+ executable,
64
+ "hook",
65
+ event,
66
+ "--client",
67
+ client,
68
+ "--project-root",
69
+ str(project_root),
70
+ ])
71
+
72
+ def _upsert_hook(settings: dict, event: str, matcher: str, command: str, name: str, *, timeout: int = 10) -> None:
73
+ hooks = settings.setdefault("hooks", {})
74
+ groups = hooks.setdefault(event, [])
75
+ target_group = None
76
+ for group in groups:
77
+ if group.get("matcher") == matcher:
78
+ target_group = group
79
+ break
80
+
81
+ hook_payload = {
82
+ "type": "command",
83
+ "name": name,
84
+ "command": command,
85
+ "timeout": timeout,
86
+ }
87
+
88
+ if target_group is None:
89
+ groups.append({"matcher": matcher, "hooks": [hook_payload]})
90
+ return
91
+
92
+ group_hooks = target_group.setdefault("hooks", [])
93
+ replaced = False
94
+ kept: list[dict] = []
95
+ for hook in group_hooks:
96
+ if hook.get("name") == name:
97
+ if not replaced:
98
+ # DevCouncil owns named hooks, including their timeout. Re-applying
99
+ # integration therefore migrates old millisecond values to the
100
+ # seconds expected by Claude Code and Codex.
101
+ kept.append(hook_payload)
102
+ replaced = True
103
+ continue
104
+ kept.append(hook)
105
+ if not replaced:
106
+ kept.append(hook_payload)
107
+ target_group["hooks"] = kept
108
+
109
+
110
+ def _remove_named_hook(settings: dict, event: str, name: str) -> None:
111
+ """Remove a DevCouncil-owned hook and prune empty matcher groups/events."""
112
+ hooks = settings.get("hooks")
113
+ if not isinstance(hooks, dict):
114
+ return
115
+ groups = hooks.get(event)
116
+ if not isinstance(groups, list):
117
+ return
118
+ kept_groups: list[dict] = []
119
+ for group in groups:
120
+ if not isinstance(group, dict):
121
+ continue
122
+ handlers = group.get("hooks")
123
+ if isinstance(handlers, list):
124
+ group = {
125
+ **group,
126
+ "hooks": [hook for hook in handlers if not isinstance(hook, dict) or hook.get("name") != name],
127
+ }
128
+ if group.get("hooks"):
129
+ kept_groups.append(group)
130
+ if kept_groups:
131
+ hooks[event] = kept_groups
132
+ else:
133
+ hooks.pop(event, None)
134
+
135
+ def _ensure_codex_hooks_enabled(project_root: Path) -> Path:
136
+ config_path = project_root / ".codex" / "config.toml"
137
+ config_path.parent.mkdir(parents=True, exist_ok=True)
138
+ existing = config_path.read_text(encoding="utf-8") if config_path.exists() else ""
139
+ lines = existing.splitlines()
140
+ updated: list[str] = []
141
+ in_features = False
142
+ saw_features = False
143
+ inserted = False
144
+ for line in lines:
145
+ stripped = line.strip()
146
+ if stripped.startswith("[") and stripped.endswith("]"):
147
+ if in_features and not inserted:
148
+ updated.append("hooks = true")
149
+ inserted = True
150
+ in_features = stripped == "[features]"
151
+ saw_features = saw_features or in_features
152
+ updated.append(line)
153
+ continue
154
+ if in_features:
155
+ key = stripped.split("=", 1)[0].strip() if "=" in stripped else ""
156
+ if key in {"hooks", "codex_hooks"}:
157
+ if not inserted:
158
+ updated.append("hooks = true")
159
+ inserted = True
160
+ continue
161
+ updated.append(line)
162
+ if in_features and not inserted:
163
+ updated.append("hooks = true")
164
+ inserted = True
165
+ if not saw_features:
166
+ if updated and updated[-1].strip():
167
+ updated.append("")
168
+ updated.extend(["[features]", "hooks = true"])
169
+ rendered = "\n".join(updated) + "\n"
170
+ if rendered != existing:
171
+ config_path.write_text(rendered, encoding="utf-8")
172
+ return config_path
173
+
174
+ def _install_codex_hooks(project_root: Path) -> list[Path]:
175
+ path = project_root / ".codex" / "hooks.json"
176
+ settings = _load_json(path)
177
+ matcher = "Bash|shell_command|exec_command|local_shell|Write|Edit|MultiEdit|write_file|edit_file|apply_patch"
178
+ _upsert_hook(
179
+ settings,
180
+ "PreToolUse",
181
+ matcher,
182
+ _hook_command(project_root, "codex", "pre-tool-use"),
183
+ "devcouncil-pre-tool-use",
184
+ )
185
+ _upsert_hook(
186
+ settings,
187
+ "PostToolUse",
188
+ matcher,
189
+ _hook_command(project_root, "codex", "post-tool-use"),
190
+ "devcouncil-post-tool-use",
191
+ )
192
+ _upsert_hook(
193
+ settings,
194
+ "SessionStart",
195
+ "startup|resume|clear|compact",
196
+ _hook_command(project_root, "codex", "session-start"),
197
+ "devcouncil-session-start",
198
+ )
199
+ _upsert_hook(
200
+ settings,
201
+ "Stop",
202
+ "",
203
+ _hook_command(project_root, "codex", "agent-response"),
204
+ "devcouncil-agent-response-ready",
205
+ timeout=_stop_hook_timeout_seconds(project_root),
206
+ )
207
+ _upsert_hook(
208
+ settings,
209
+ "SubagentStop",
210
+ "",
211
+ _hook_command(project_root, "codex", "subagent-stop"),
212
+ "devcouncil-subagent-stop",
213
+ timeout=_stop_hook_timeout_seconds(project_root),
214
+ )
215
+ _save_json(path, settings)
216
+ return [path, _ensure_codex_hooks_enabled(project_root)]
217
+
218
+ def _install_gemini_hooks(project_root: Path) -> list[Path]:
219
+ path = project_root / ".gemini" / "settings.json"
220
+ settings = _load_json(path)
221
+ matcher = "run_shell_command|shell_command|write_file|edit_file|replace|apply_patch"
222
+ _upsert_hook(
223
+ settings,
224
+ "BeforeTool",
225
+ matcher,
226
+ _hook_command(project_root, "gemini", "pre-tool-use"),
227
+ "devcouncil-pre-tool-use",
228
+ )
229
+ _upsert_hook(
230
+ settings,
231
+ "AfterTool",
232
+ matcher,
233
+ _hook_command(project_root, "gemini", "post-tool-use"),
234
+ "devcouncil-post-tool-use",
235
+ )
236
+ _save_json(path, settings)
237
+ return [path]
238
+
239
+ def _upsert_cursor_hook(settings: dict, event: str, matcher: str, command: str) -> None:
240
+ hooks = settings.setdefault("hooks", {})
241
+ entries = hooks.setdefault(event, [])
242
+ for entry in entries:
243
+ if entry.get("command") == command:
244
+ return
245
+ payload: dict = {"command": command}
246
+ if matcher:
247
+ payload["matcher"] = matcher
248
+ entries.append(payload)
249
+
250
+ def _install_cursor_hooks(project_root: Path) -> list[Path]:
251
+ path = project_root / ".cursor" / "hooks.json"
252
+ settings = _load_json(path)
253
+ settings.setdefault("version", 1)
254
+ matcher = "Shell|Write|Edit|MultiEdit|Read|Task"
255
+ _upsert_cursor_hook(
256
+ settings,
257
+ "preToolUse",
258
+ matcher,
259
+ _hook_command(project_root, "cursor", "pre-tool-use"),
260
+ )
261
+ _upsert_cursor_hook(
262
+ settings,
263
+ "postToolUse",
264
+ matcher,
265
+ _hook_command(project_root, "cursor", "post-tool-use"),
266
+ )
267
+ _save_json(path, settings)
268
+
269
+ def mutate(config: dict) -> None:
270
+ cursor = config.setdefault("integrations", {}).setdefault("cursor", {})
271
+ cursor.update({
272
+ "hooks_path": str(path.relative_to(project_root)),
273
+ })
274
+
275
+ _mutate_raw_config(project_root, mutate)
276
+ return [path]
277
+
278
+ def _install_grok_hooks(project_root: Path) -> list[Path]:
279
+ hooks_dir = project_root / ".grok" / "hooks"
280
+ hooks_dir.mkdir(parents=True, exist_ok=True)
281
+ path = hooks_dir / "devcouncil.json"
282
+ settings = _load_json(path)
283
+ matcher = "Bash|Write|Edit|MultiEdit|run_terminal_cmd|write_file|edit_file|apply_patch"
284
+ _upsert_hook(
285
+ settings,
286
+ "PreToolUse",
287
+ matcher,
288
+ _hook_command(project_root, "grok", "pre-tool-use"),
289
+ "devcouncil-pre-tool-use",
290
+ )
291
+ _upsert_hook(
292
+ settings,
293
+ "PostToolUse",
294
+ matcher,
295
+ _hook_command(project_root, "grok", "post-tool-use"),
296
+ "devcouncil-post-tool-use",
297
+ )
298
+ _save_json(path, settings)
299
+
300
+ def mutate(config: dict) -> None:
301
+ grok = config.setdefault("integrations", {}).setdefault("grok", {})
302
+ grok.update({
303
+ "hooks_path": str(path.relative_to(project_root)),
304
+ })
305
+
306
+ _mutate_raw_config(project_root, mutate)
307
+ return [path]
308
+
309
+ def _install_opencode_hooks(project_root: Path) -> list[Path]:
310
+ source = _opencode_plugin_source()
311
+ if not source.exists():
312
+ raise FileNotFoundError(f"Missing bundled OpenCode hook plugin: {source}")
313
+ destination = _opencode_plugin_path(project_root)
314
+ destination.parent.mkdir(parents=True, exist_ok=True)
315
+ destination.write_text(source.read_text(encoding="utf-8"), encoding="utf-8")
316
+
317
+ path = _opencode_config_path(project_root)
318
+ data = _load_json_strict(path, "OpenCode") if path.exists() else {"$schema": "https://opencode.ai/config.json"}
319
+ data.setdefault("$schema", "https://opencode.ai/config.json")
320
+ plugins_raw = data.setdefault("plugin", [])
321
+ if not isinstance(plugins_raw, list):
322
+ plugins_raw = []
323
+ data["plugin"] = plugins_raw
324
+ plugins: list[str] = [str(item) for item in plugins_raw]
325
+ data["plugin"] = plugins
326
+ plugin_ref = f"./.devcouncil/integrations/{OPENCODE_HOOK_PLUGIN_NAME}"
327
+ if plugin_ref not in plugins:
328
+ plugins.append(plugin_ref)
329
+ _save_json(path, data)
330
+ _record_opencode_config(project_root)
331
+ return [destination, path]
332
+
333
+ def _install_claude_hooks(project_root: Path, *, write_gate: bool = False) -> list[Path]:
334
+ """Install DevCouncil's Claude Code hooks into .claude/settings.local.json.
335
+
336
+ By default this installs *assistive* lifecycle hooks plus a refresh-only
337
+ **PostToolUse** hook (map auto-refresh; never gates writes). These never block
338
+ a tool call.
339
+
340
+ The blocking pre-action **write-gate** (**PreToolUse**, which denies any
341
+ Bash/Write/Edit not authorized by an active task lease) is installed ONLY when
342
+ ``write_gate`` is True. It is meant for autonomous executor runs, not interactive
343
+ human sessions — in an interactive session there is no task lease, so the gate would
344
+ fail-closed and deny every command. (``dev run --executor claude`` does its own
345
+ post-hoc scope enforcement and does not depend on this hook, so leaving PreToolUse
346
+ off by default loses no containment.)"""
347
+ path = project_root / ".claude" / "settings.local.json"
348
+ settings = _load_json(path)
349
+ matcher = "Bash|Write|Edit|MultiEdit"
350
+ # Refresh-only PostToolUse is always installed so assist mode keeps the map warm.
351
+ _upsert_hook(
352
+ settings,
353
+ "PostToolUse",
354
+ matcher,
355
+ _hook_command(project_root, "claude", "post-tool-use"),
356
+ "devcouncil-post-tool-use",
357
+ )
358
+ if write_gate:
359
+ _upsert_hook(
360
+ settings,
361
+ "PreToolUse",
362
+ matcher,
363
+ _hook_command(project_root, "claude", "pre-tool-use"),
364
+ "devcouncil-pre-tool-use",
365
+ )
366
+ else:
367
+ # Reapplying the default assist integration must actually disable a
368
+ # previously opted-in blocking gate; otherwise interactive sessions stay
369
+ # fail-closed forever despite --no-write-gate.
370
+ _remove_named_hook(settings, "PreToolUse", "devcouncil-pre-tool-use")
371
+ _upsert_hook(
372
+ settings,
373
+ "Stop",
374
+ "",
375
+ _hook_command(project_root, "claude", "agent-response"),
376
+ "devcouncil-agent-response-ready",
377
+ timeout=_stop_hook_timeout_seconds(project_root),
378
+ )
379
+ # Lifecycle events: status-on-start/prompt, teardown, compaction, subagent finish,
380
+ # and notifications. These complete DevCouncil's coverage of the documented Claude
381
+ # Code hook surface beyond the pre/post/stop gate.
382
+ _upsert_hook(
383
+ settings,
384
+ "SessionStart",
385
+ SESSION_START_MATCHER,
386
+ _hook_command(project_root, "claude", "session-start"),
387
+ "devcouncil-session-start",
388
+ )
389
+ _upsert_hook(
390
+ settings,
391
+ "UserPromptSubmit",
392
+ "",
393
+ _hook_command(project_root, "claude", "user-prompt-submit"),
394
+ "devcouncil-user-prompt-submit",
395
+ )
396
+ _upsert_hook(
397
+ settings,
398
+ "SessionEnd",
399
+ "",
400
+ _hook_command(project_root, "claude", "session-end"),
401
+ "devcouncil-session-end",
402
+ )
403
+ _upsert_hook(
404
+ settings,
405
+ "PreCompact",
406
+ "",
407
+ _hook_command(project_root, "claude", "pre-compact"),
408
+ "devcouncil-pre-compact",
409
+ )
410
+ _upsert_hook(
411
+ settings,
412
+ "PostCompact",
413
+ "",
414
+ _hook_command(project_root, "claude", "post-compact"),
415
+ "devcouncil-post-compact",
416
+ )
417
+ _upsert_hook(
418
+ settings,
419
+ "SubagentStop",
420
+ "",
421
+ _hook_command(project_root, "claude", "subagent-stop"),
422
+ "devcouncil-subagent-stop",
423
+ timeout=_stop_hook_timeout_seconds(project_root),
424
+ )
425
+ _upsert_hook(
426
+ settings,
427
+ "Notification",
428
+ "",
429
+ _hook_command(project_root, "claude", "notification"),
430
+ "devcouncil-notification",
431
+ )
432
+ _save_json(path, settings)
433
+ return [path]
434
+
435
+ def _preview_hook_paths(project_root: Path, tool: str) -> list[tuple[str, Path]]:
436
+ paths = {
437
+ "codex": [project_root / ".codex" / "hooks.json", project_root / ".codex" / "config.toml"],
438
+ "gemini": [project_root / ".gemini" / "settings.json"],
439
+ "claude": [project_root / ".claude" / "settings.local.json"],
440
+ "cursor": [project_root / ".cursor" / "hooks.json"],
441
+ "grok": [project_root / ".grok" / "hooks" / "devcouncil.json"],
442
+ "opencode": [_opencode_plugin_path(project_root), _opencode_config_path(project_root)],
443
+ }
444
+ selected: tuple[str, ...]
445
+ if tool == "all":
446
+ selected = (*SUPPORTED_HOOK_TOOLS, "opencode")
447
+ elif tool == "opencode":
448
+ selected = ("opencode",)
449
+ else:
450
+ selected = (tool,)
451
+ return [(client, path) for client in selected for path in paths.get(client, [])]
452
+
453
+ def _configure_native_hooks(
454
+ project_root: Path, tool: str = "all", apply: bool = False, *, claude_write_gate: bool = False
455
+ ) -> None:
456
+ allowed = {"all", *SUPPORTED_HOOK_TOOLS, "gemini", "opencode"}
457
+ if tool not in allowed:
458
+ console.print("[red]--tool must be one of: all, codex, gemini, claude, cursor, grok, opencode.[/red]")
459
+ raise typer.Exit(code=2)
460
+
461
+ if tool == "gemini":
462
+ console.print(f"[yellow]{GEMINI_DEPRECATION_MESSAGE}[/yellow]")
463
+
464
+ if not apply:
465
+ console.print("[bold]Native hook config preview[/bold]")
466
+ for client, path in _preview_hook_paths(project_root, tool):
467
+ console.print(f"{client}: {path}", soft_wrap=True)
468
+ console.print("[yellow]Preview only. Rerun with --apply to write hook config files.[/yellow]")
469
+ return
470
+
471
+ selected: tuple[str, ...]
472
+ if tool == "all":
473
+ selected = (*SUPPORTED_HOOK_TOOLS, "opencode")
474
+ elif tool == "opencode":
475
+ selected = ("opencode",)
476
+ else:
477
+ selected = (tool,)
478
+ installers = {
479
+ "codex": _install_codex_hooks,
480
+ "gemini": _install_gemini_hooks,
481
+ # Claude's blocking write-gate is opt-in (assist-mode default); the other clients
482
+ # install their native pre/post hooks unconditionally as before.
483
+ "claude": lambda root: _install_claude_hooks(root, write_gate=claude_write_gate),
484
+ "cursor": _install_cursor_hooks,
485
+ "grok": _install_grok_hooks,
486
+ "opencode": _install_opencode_hooks,
487
+ }
488
+ # Batch the per-installer config.yaml record updates (cursor/opencode)
489
+ # into one load/save instead of re-parsing YAML per tool.
490
+ with _batched_raw_config(project_root):
491
+ for client in selected:
492
+ try:
493
+ written = installers[client](project_root)
494
+ except (ValueError, FileNotFoundError) as exc:
495
+ console.print(f"[red]{client} hook setup failed: {exc}[/red]")
496
+ raise typer.Exit(code=1) from exc
497
+ console.print(f"[green]{client} native hooks configured:[/green] {', '.join(str(path) for path in written)}")
498
+ if any(client in _common.STOP_HOOK_TOOLS for client in selected):
499
+ _mutate_raw_config(project_root, _common.seed_stop_gate_assist_if_unset)
500
+ record_hook_dev_executable(project_root)
@@ -0,0 +1,96 @@
1
+ """Opencode integration adapter."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import shutil
6
+ from pathlib import Path
7
+
8
+
9
+
10
+ from devcouncil.integrations.clients import common as _common
11
+ _project_root = _common._project_root
12
+ _warn_if_verify_only = _common._warn_if_verify_only
13
+ _server_args = _common._server_args
14
+ _format_command = _common._format_command
15
+ _quote_powershell_arg = _common._quote_powershell_arg
16
+ _run = _common._run
17
+ _run_capture = _common._run_capture
18
+ _config_path = _common._config_path
19
+ _load_raw_config = _common._load_raw_config
20
+ _save_raw_config = _common._save_raw_config
21
+ _load_json = _common._load_json
22
+ _save_json = _common._save_json
23
+ _load_json_strict = _common._load_json_strict
24
+ _mutate_raw_config = _common._mutate_raw_config
25
+ _batched_raw_config = _common._batched_raw_config
26
+ _probe_mcp_tools = _common._probe_mcp_tools
27
+ _print_command = _common._print_command
28
+ _configure = _common._configure
29
+
30
+ console = _common.console
31
+ OPENCODE_HOOK_PLUGIN_NAME = _common.OPENCODE_HOOK_PLUGIN_NAME
32
+
33
+ def _opencode_config_path(project_root: Path) -> Path:
34
+ return project_root / "opencode.json"
35
+
36
+ def _opencode_mcp_entry(project_root: Path) -> dict:
37
+ return {
38
+ "type": "local",
39
+ "command": ["devcouncil", "mcp-server"],
40
+ "environment": {"DEVCOUNCIL_PROJECT_ROOT": str(project_root)},
41
+ "enabled": True,
42
+ "timeout": 10000,
43
+ }
44
+
45
+ def _record_opencode_config(project_root: Path) -> None:
46
+ def mutate(config: dict) -> None:
47
+ opencode = config.setdefault("integrations", {}).setdefault("opencode", {})
48
+ opencode.update({
49
+ "enabled": True,
50
+ "config_path": str(_opencode_config_path(project_root).relative_to(project_root)),
51
+ })
52
+
53
+ _mutate_raw_config(project_root, mutate)
54
+
55
+ def _write_opencode_config(project_root: Path) -> Path:
56
+ path = _opencode_config_path(project_root)
57
+ data = _load_json_strict(path, "OpenCode")
58
+ data.setdefault("$schema", "https://opencode.ai/config.json")
59
+ mcp = data.setdefault("mcp", {})
60
+ mcp["devcouncil"] = _opencode_mcp_entry(project_root)
61
+ _save_json(path, data)
62
+ return path
63
+
64
+ def _configure_opencode(project_root: Path, apply: bool) -> bool:
65
+ path = _opencode_config_path(project_root)
66
+ config = {
67
+ "$schema": "https://opencode.ai/config.json",
68
+ "mcp": {"devcouncil": _opencode_mcp_entry(project_root)},
69
+ }
70
+ if not apply:
71
+ console.print("[bold]OpenCode[/bold]")
72
+ console.print(f"Project config file: [dim]{path}[/dim]")
73
+ console.print(json.dumps(config, separators=(",", ":")), soft_wrap=True)
74
+ console.print(
75
+ "Direct executor command: "
76
+ "[dim]opencode run --file .devcouncil/TASK-001-opencode-task.md "
77
+ '"Execute the DevCouncil task described in the attached prompt file."[/dim]'
78
+ )
79
+ return True
80
+
81
+ if not shutil.which("opencode"):
82
+ console.print("[yellow]OpenCode CLI not found on PATH. Install it before using `dev run --executor opencode`.[/yellow]")
83
+ try:
84
+ written = _write_opencode_config(project_root)
85
+ except ValueError as exc:
86
+ console.print(f"[red]{exc}[/red]")
87
+ return False
88
+ _record_opencode_config(project_root)
89
+ console.print(f"[green]OpenCode MCP config written:[/green] {written}")
90
+ return True
91
+
92
+ def _opencode_plugin_source() -> Path:
93
+ return Path(__file__).resolve().parents[2] / "integrations" / OPENCODE_HOOK_PLUGIN_NAME
94
+
95
+ def _opencode_plugin_path(project_root: Path) -> Path:
96
+ return project_root / ".devcouncil" / "integrations" / OPENCODE_HOOK_PLUGIN_NAME
@@ -0,0 +1,75 @@
1
+ """Warp integration adapter."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import shutil
6
+ from pathlib import Path
7
+
8
+
9
+
10
+ from devcouncil.integrations.clients import common as _common
11
+ _project_root = _common._project_root
12
+ _warn_if_verify_only = _common._warn_if_verify_only
13
+ _server_args = _common._server_args
14
+ _format_command = _common._format_command
15
+ _quote_powershell_arg = _common._quote_powershell_arg
16
+ _run = _common._run
17
+ _run_capture = _common._run_capture
18
+ _config_path = _common._config_path
19
+ _load_raw_config = _common._load_raw_config
20
+ _save_raw_config = _common._save_raw_config
21
+ _load_json = _common._load_json
22
+ _save_json = _common._save_json
23
+ _load_json_strict = _common._load_json_strict
24
+ _mutate_raw_config = _common._mutate_raw_config
25
+ _batched_raw_config = _common._batched_raw_config
26
+ _probe_mcp_tools = _common._probe_mcp_tools
27
+ _print_command = _common._print_command
28
+ _configure = _common._configure
29
+
30
+ console = _common.console
31
+ def _warp_mcp_config(project_root: Path) -> dict:
32
+ return {
33
+ "devcouncil": {
34
+ "command": "devcouncil",
35
+ "args": ["mcp-server"],
36
+ "env": {"DEVCOUNCIL_PROJECT_ROOT": str(project_root)},
37
+ }
38
+ }
39
+
40
+ def _warp_mcp_path(project_root: Path) -> Path:
41
+ return project_root / ".devcouncil" / "integrations" / "warp-mcp.json"
42
+
43
+ def _write_warp_mcp_config(project_root: Path) -> Path:
44
+ path = _warp_mcp_path(project_root)
45
+ _save_json(path, _warp_mcp_config(project_root))
46
+ return path
47
+
48
+ def _record_warp_config(project_root: Path) -> None:
49
+ def mutate(config: dict) -> None:
50
+ warp = config.setdefault("integrations", {}).setdefault("warp", {})
51
+ warp.update({
52
+ "enabled": True,
53
+ "command": warp.get("command", "oz"),
54
+ "run_mode": warp.get("run_mode", "local"),
55
+ "mcp_config_path": str(_warp_mcp_path(project_root).relative_to(project_root)),
56
+ })
57
+
58
+ _mutate_raw_config(project_root, mutate)
59
+
60
+ def _configure_warp(project_root: Path, apply: bool) -> bool:
61
+ path = _warp_mcp_path(project_root)
62
+ config = _warp_mcp_config(project_root)
63
+ if not apply:
64
+ console.print("[bold]Warp / Oz[/bold]")
65
+ console.print(f"MCP config file: [dim]{path}[/dim]")
66
+ console.print(json.dumps(config, separators=(",", ":")), soft_wrap=True)
67
+ console.print(f"Direct executor command: [dim]oz agent run --cwd {project_root} --mcp {path} --prompt <task prompt>[/dim]")
68
+ return True
69
+
70
+ written = _write_warp_mcp_config(project_root)
71
+ _record_warp_config(project_root)
72
+ console.print(f"[green]Warp MCP config written:[/green] {written}")
73
+ if not shutil.which("oz"):
74
+ console.print("[yellow]oz CLI not found on PATH. Install Warp/Oz before using `dev run --executor warp`.[/yellow]")
75
+ return True
@@ -37,7 +37,7 @@ class CodeReviewGraphAdapter:
37
37
 
38
38
  def is_enabled(self) -> bool:
39
39
  try:
40
- return self._get_config().integrations.code_review_graph.enabled
40
+ return bool(self._get_config().integrations.code_review_graph.enabled)
41
41
  except Exception:
42
42
  return False
43
43
 
@@ -95,7 +95,7 @@ class CodeReviewGraphAdapter:
95
95
 
96
96
  def _configured_command(self) -> str:
97
97
  try:
98
- return self._get_config().integrations.code_review_graph.command
98
+ return str(self._get_config().integrations.code_review_graph.command)
99
99
  except Exception:
100
100
  return "code-review-graph"
101
101