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,332 @@
1
+
2
+ """Shared integration utilities."""
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import logging
7
+ import os
8
+ import shlex
9
+ import shutil
10
+ import subprocess
11
+ import sys
12
+ from contextlib import contextmanager
13
+ from pathlib import Path
14
+
15
+ import yaml
16
+ from rich.console import Console
17
+
18
+ from devcouncil.executors.agent_registry import (
19
+ CODING_CLI_INTEGRATION_INFO,
20
+ normalize_agent_name,
21
+ )
22
+ from devcouncil.utils.subprocess_env import clean_subprocess_env
23
+
24
+ console = Console()
25
+ logger = logging.getLogger(__name__)
26
+
27
+ OPENCODE_HOOK_PLUGIN_NAME = "opencode_devcouncil_plugin.mjs"
28
+ SUPPORTED_HOOK_TOOLS = ("claude", "codex", "cursor", "grok")
29
+ # Clients whose native hook installers wire Stop/SubagentStop handlers.
30
+ STOP_HOOK_TOOLS = frozenset({"claude", "codex"})
31
+
32
+
33
+ def seed_stop_gate_assist_if_unset(config: dict) -> None:
34
+ """Default ``execution.stop_gate.mode`` to ``assist`` when hooks are installed.
35
+
36
+ Code default remains ``off`` (``StopGateConfig.mode``); integrate/first-run setup
37
+ for Stop-hook clients opts into assistive warnings without blocking completion.
38
+ """
39
+ execution = config.setdefault("execution", {})
40
+ stop_gate = execution.setdefault("stop_gate", {})
41
+ if not isinstance(stop_gate, dict):
42
+ stop_gate = {}
43
+ execution["stop_gate"] = stop_gate
44
+ if not stop_gate.get("mode"):
45
+ stop_gate["mode"] = "assist"
46
+ stop_gate.setdefault("check_claims", True)
47
+ stop_gate.setdefault("verify_active_task", True)
48
+
49
+ # Recorded when hooks are installed so `dev integrate hooks --check` can detect drift
50
+ # between a stale global CLI and the project venv.
51
+ HOOK_DEV_EXECUTABLE_REL = Path(".devcouncil") / "cache" / "hook_dev_executable"
52
+
53
+
54
+ def resolve_dev_executable(project_root: Path) -> str:
55
+ """Resolve the ``dev`` CLI to invoke from hooks (project venv first, then PATH).
56
+
57
+ Preferring the project ``.venv`` avoids a globally installed stale ``dev`` silently
58
+ fighting the working tree over cache versions and map schema.
59
+ """
60
+ root = project_root.expanduser().resolve()
61
+ candidates: list[Path] = []
62
+ if sys.platform == "win32":
63
+ candidates.extend(
64
+ [
65
+ root / ".venv" / "Scripts" / "dev.exe",
66
+ root / ".venv" / "Scripts" / "devcouncil.exe",
67
+ root / "venv" / "Scripts" / "dev.exe",
68
+ ]
69
+ )
70
+ else:
71
+ candidates.extend(
72
+ [
73
+ root / ".venv" / "bin" / "dev",
74
+ root / ".venv" / "bin" / "devcouncil",
75
+ root / "venv" / "bin" / "dev",
76
+ root / "venv" / "bin" / "devcouncil",
77
+ ]
78
+ )
79
+ for candidate in candidates:
80
+ if candidate.is_file() and os.access(candidate, os.X_OK):
81
+ return str(candidate.resolve())
82
+ for name in ("dev", "devcouncil"):
83
+ found = shutil.which(name)
84
+ if found:
85
+ return found
86
+ return "dev"
87
+
88
+
89
+ def record_hook_dev_executable(project_root: Path, executable: str | None = None) -> Path:
90
+ """Persist the resolved ``dev`` path used in installed hook commands."""
91
+ root = project_root.expanduser().resolve()
92
+ path = root / HOOK_DEV_EXECUTABLE_REL
93
+ path.parent.mkdir(parents=True, exist_ok=True)
94
+ resolved = executable or resolve_dev_executable(root)
95
+ path.write_text(resolved + "\n", encoding="utf-8")
96
+ return path
97
+
98
+
99
+ def recorded_hook_dev_executable(project_root: Path) -> str | None:
100
+ path = project_root.expanduser().resolve() / HOOK_DEV_EXECUTABLE_REL
101
+ if not path.is_file():
102
+ return None
103
+ text = path.read_text(encoding="utf-8").strip()
104
+ return text or None
105
+
106
+
107
+ def check_hook_dev_executable(project_root: Path) -> tuple[bool, str]:
108
+ """Compare recorded hook executable against the currently resolved one."""
109
+ root = project_root.expanduser().resolve()
110
+ current = resolve_dev_executable(root)
111
+ recorded = recorded_hook_dev_executable(root)
112
+ if recorded is None:
113
+ return True, f"No recorded hook executable (current: {current})"
114
+ if Path(recorded).resolve() == Path(current).resolve() or recorded == current:
115
+ return True, f"Hook executable matches: {current}"
116
+ return False, f"Hook executable mismatch: recorded={recorded} current={current}"
117
+
118
+
119
+ def _project_root(path: str | Path | None) -> Path:
120
+ root = Path(path or ".").expanduser().resolve()
121
+ from devcouncil.telemetry.logging_setup import set_log_dir
122
+
123
+ set_log_dir(root)
124
+ return root
125
+
126
+ def _warn_if_verify_only(client: str) -> None:
127
+ """Print a prominent containment warning when wiring a verify-only client.
128
+
129
+ Verify-only clients have no native pre-tool-use hook, so DevCouncil cannot block a
130
+ forbidden write or command before it happens — it is only caught post-hoc at verify
131
+ time. Surface this loudly so users don't assume hard containment."""
132
+ info = CODING_CLI_INTEGRATION_INFO.get(normalize_agent_name(client))
133
+ if info is not None and not info.hooks:
134
+ console.print(
135
+ f"[bold yellow]Warning ({info.label}): No pre-action containment — "
136
+ "forbidden writes/commands are caught only at verify time.[/bold yellow]"
137
+ )
138
+
139
+ def _server_args(project_root: Path) -> list[str]:
140
+ """Return the exact MCP server command clients should persist.
141
+
142
+ Project integrations must execute the checkout they were generated from. A
143
+ bare ``devcouncil`` command can resolve to an older global installation and
144
+ silently serve a different schema than the working tree.
145
+ """
146
+ return [resolve_dev_executable(project_root), "mcp-server"]
147
+
148
+ _PENDING_RAW_CONFIG: dict | None = None
149
+
150
+ @contextmanager
151
+ def _batched_raw_config(project_root: Path):
152
+ global _PENDING_RAW_CONFIG
153
+ # Re-entrant: if a batch is already active, participate in it instead of
154
+ # starting a nested load/save (which would otherwise reset the shared
155
+ # buffer to None on inner exit and drop the outer batch's mutations).
156
+ if _PENDING_RAW_CONFIG is not None:
157
+ yield
158
+ return
159
+ _PENDING_RAW_CONFIG = _load_raw_config(project_root)
160
+ try:
161
+ yield
162
+ finally:
163
+ # Persist whatever mutations accumulated, even if an inner installer raised
164
+ # partway through — matching the old per-installer save, which committed each
165
+ # installer's change immediately rather than dropping the whole batch on a
166
+ # mid-loop failure.
167
+ pending = _PENDING_RAW_CONFIG
168
+ _PENDING_RAW_CONFIG = None
169
+ if pending is not None:
170
+ _save_raw_config(project_root, pending)
171
+
172
+ def _mutate_raw_config(project_root: Path, mutate) -> None:
173
+ if _PENDING_RAW_CONFIG is not None:
174
+ mutate(_PENDING_RAW_CONFIG)
175
+ return
176
+ config = _load_raw_config(project_root)
177
+ mutate(config)
178
+ _save_raw_config(project_root, config)
179
+
180
+ def _load_json_strict(path: Path, label: str = "JSON") -> dict:
181
+ if not path.exists():
182
+ return {}
183
+ try:
184
+ return json.loads(path.read_text(encoding="utf-8")) or {}
185
+ except json.JSONDecodeError as exc:
186
+ raise ValueError(f"{path} is not valid JSON. Fix the {label} config before rerunning integration setup.") from exc
187
+
188
+ def _format_command(command: list[str]) -> str:
189
+ if sys.platform == "win32":
190
+ return " ".join(_quote_powershell_arg(arg) for arg in command)
191
+ return shlex.join(command)
192
+
193
+ def _quote_powershell_arg(arg: str) -> str:
194
+ if arg == "":
195
+ return "''"
196
+ special_chars = set(" \t\r\n'\"{}[](),;|&<>")
197
+ if not any(char in special_chars for char in arg):
198
+ return arg
199
+ return "'" + arg.replace("'", "''") + "'"
200
+
201
+ def _probe_mcp_tools(root: Path, *, timeout_seconds: float = 30.0) -> list[str]:
202
+ from mcp import ClientSession, StdioServerParameters
203
+ from mcp.client.stdio import stdio_client
204
+ import asyncio
205
+ import os
206
+
207
+ async def _list_tools() -> list[str]:
208
+ env = os.environ.copy()
209
+ env["DEVCOUNCIL_PROJECT_ROOT"] = str(root)
210
+ command = _server_args(root)
211
+ params = StdioServerParameters(
212
+ command=command[0],
213
+ args=command[1:],
214
+ cwd=str(root),
215
+ env=env,
216
+ )
217
+ async with stdio_client(params) as (read, write):
218
+ async with ClientSession(read, write) as session:
219
+ await session.initialize()
220
+ tools = await session.list_tools()
221
+ return [tool.name for tool in tools.tools]
222
+
223
+ async def _list_tools_with_deadline() -> list[str]:
224
+ # A wedged server process would otherwise block `dev integrate check`
225
+ # indefinitely; the caller treats TimeoutError as a failed probe.
226
+ return await asyncio.wait_for(_list_tools(), timeout=timeout_seconds)
227
+
228
+ return asyncio.run(_list_tools_with_deadline())
229
+
230
+ _CLI_RUN_TIMEOUT_SECONDS = 120
231
+
232
+ def _run(command: list[str]) -> int:
233
+ executable = shutil.which(command[0])
234
+ if not executable:
235
+ return 127
236
+ resolved = [executable, *command[1:]]
237
+ use_shell = sys.platform == "win32" and Path(executable).suffix.lower() in {".bat", ".cmd", ".ps1"}
238
+ invocation = subprocess.list2cmdline(resolved) if use_shell else resolved
239
+ try:
240
+ result = subprocess.run(
241
+ invocation,
242
+ text=True,
243
+ shell=use_shell,
244
+ timeout=_CLI_RUN_TIMEOUT_SECONDS,
245
+ env=clean_subprocess_env(),
246
+ )
247
+ except subprocess.TimeoutExpired:
248
+ return 124
249
+ except (FileNotFoundError, OSError):
250
+ return 127
251
+ return result.returncode
252
+
253
+ def _run_capture(command: list[str], timeout: int = 10) -> tuple[int, str]:
254
+ executable = shutil.which(command[0])
255
+ if not executable:
256
+ return 127, f"{command[0]} not found on PATH"
257
+
258
+ resolved = [executable, *command[1:]]
259
+ use_shell = sys.platform == "win32" and Path(executable).suffix.lower() in {".bat", ".cmd", ".ps1"}
260
+ invocation = subprocess.list2cmdline(resolved) if use_shell else resolved
261
+ try:
262
+ result = subprocess.run(
263
+ invocation,
264
+ capture_output=True,
265
+ text=True,
266
+ encoding="utf-8",
267
+ errors="replace",
268
+ shell=use_shell,
269
+ timeout=timeout,
270
+ env=clean_subprocess_env(),
271
+ )
272
+ except subprocess.TimeoutExpired:
273
+ return 124, "timed out"
274
+ except (FileNotFoundError, OSError) as exc:
275
+ return 127, f"{command[0]} could not be executed: {exc}"
276
+ return result.returncode, (result.stdout + result.stderr).strip()
277
+
278
+ def _config_path(project_root: Path) -> Path:
279
+ return project_root / ".devcouncil" / "config.yaml"
280
+
281
+ def _load_raw_config(project_root: Path) -> dict:
282
+ path = _config_path(project_root)
283
+ if not path.exists():
284
+ return {}
285
+ return yaml.safe_load(path.read_text(encoding="utf-8")) or {}
286
+
287
+ def _save_raw_config(project_root: Path, config: dict) -> None:
288
+ path = _config_path(project_root)
289
+ path.parent.mkdir(parents=True, exist_ok=True)
290
+ path.write_text(yaml.safe_dump(config, sort_keys=False), encoding="utf-8")
291
+
292
+ def _load_json(path: Path) -> dict:
293
+ if not path.exists():
294
+ return {}
295
+ try:
296
+ return json.loads(path.read_text(encoding="utf-8")) or {}
297
+ except json.JSONDecodeError:
298
+ return {}
299
+
300
+ def _save_json(path: Path, data: dict) -> None:
301
+ path.parent.mkdir(parents=True, exist_ok=True)
302
+ from devcouncil.utils.json_persist import write_json
303
+
304
+ write_json(path, data)
305
+
306
+ def _print_command(tool: str, command: list[str], apply: bool):
307
+ if apply:
308
+ console.print(f"[cyan]Configuring {tool} MCP integration...[/cyan]")
309
+ else:
310
+ console.print(f"[bold]{tool}[/bold]")
311
+ console.print(_format_command(command), soft_wrap=True)
312
+
313
+ def _configure(tool: str, command: list[str], apply: bool) -> bool:
314
+ executable = command[0]
315
+ if not shutil.which(executable):
316
+ console.print(f"[yellow]{tool} CLI not found on PATH. Install it first, then rerun this command.[/yellow]")
317
+ console.print(_format_command(command), soft_wrap=True)
318
+ return False
319
+
320
+ _print_command(tool, command, apply)
321
+ if not apply:
322
+ return True
323
+
324
+ code = _run(command)
325
+ if code == 0:
326
+ console.print(f"[green]{tool} integration configured.[/green]")
327
+ return True
328
+
329
+ console.print(f"[red]{tool} integration command failed with exit code {code}.[/red]")
330
+ console.print("You can rerun it manually:")
331
+ console.print(_format_command(command), soft_wrap=True)
332
+ return False
@@ -0,0 +1,164 @@
1
+ """Cursor integration adapter."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import os
6
+ import shutil
7
+ from pathlib import Path
8
+
9
+ from devcouncil.executors.agent_registry import resolve_cursor_agent_executable
10
+ from devcouncil.integrations.clients import common as _common
11
+
12
+ _project_root = _common._project_root
13
+ _warn_if_verify_only = _common._warn_if_verify_only
14
+ _server_args = _common._server_args
15
+ _format_command = _common._format_command
16
+ _quote_powershell_arg = _common._quote_powershell_arg
17
+ _run = _common._run
18
+ _run_capture = _common._run_capture
19
+ _config_path = _common._config_path
20
+ _load_raw_config = _common._load_raw_config
21
+ _save_raw_config = _common._save_raw_config
22
+ _load_json = _common._load_json
23
+ _save_json = _common._save_json
24
+ _load_json_strict = _common._load_json_strict
25
+ _mutate_raw_config = _common._mutate_raw_config
26
+ _batched_raw_config = _common._batched_raw_config
27
+ _probe_mcp_tools = _common._probe_mcp_tools
28
+ _print_command = _common._print_command
29
+ _configure = _common._configure
30
+
31
+ console = _common.console
32
+
33
+
34
+ def _cursor_executable() -> str | None:
35
+ return resolve_cursor_agent_executable()
36
+
37
+
38
+ def _cursor_config_path(project_root: Path) -> Path:
39
+ return project_root / ".cursor" / "mcp.json"
40
+
41
+
42
+ def _cursor_mcp_command(project_root: Path) -> str:
43
+ """Prefer the project venv ``devcouncil`` binary for Cursor MCP stdio.
44
+
45
+ Absolute paths survive Cursor sessions that lack the project venv on PATH;
46
+ bare ``devcouncil`` still works when PATH is set. Status checks accept both.
47
+ """
48
+ from devcouncil.integrations.clients.common import resolve_dev_executable
49
+
50
+ resolved = Path(resolve_dev_executable(project_root))
51
+ name = resolved.name.lower()
52
+ if name in {"dev", "dev.exe"}:
53
+ sibling = resolved.with_name("devcouncil.exe" if name.endswith(".exe") else "devcouncil")
54
+ if sibling.is_file():
55
+ return str(sibling)
56
+ if name in {"devcouncil", "devcouncil.exe"}:
57
+ return str(resolved)
58
+ # Fall back to PATH name when no local binary exists.
59
+ return "devcouncil"
60
+
61
+
62
+ def _cursor_mcp_config(project_root: Path) -> dict:
63
+ root = project_root.expanduser().resolve()
64
+ if os.name == "nt":
65
+ venv_bin = root / ".venv" / "Scripts"
66
+ else:
67
+ venv_bin = root / ".venv" / "bin"
68
+ path_prefix = str(venv_bin) if venv_bin.is_dir() else ""
69
+ existing_path = os.environ.get("PATH", "/usr/bin:/bin")
70
+ env_path = f"{path_prefix}{os.pathsep}{existing_path}" if path_prefix else existing_path
71
+ return {
72
+ "mcpServers": {
73
+ "devcouncil": {
74
+ "type": "stdio",
75
+ "command": _cursor_mcp_command(root),
76
+ "args": ["mcp-server"],
77
+ "env": {
78
+ "DEVCOUNCIL_PROJECT_ROOT": str(root),
79
+ "PATH": env_path,
80
+ },
81
+ }
82
+ }
83
+ }
84
+
85
+
86
+ def _write_cursor_config(project_root: Path) -> Path:
87
+ path = _cursor_config_path(project_root)
88
+ data = _load_json_strict(path, "Cursor")
89
+ mcp_servers = data.setdefault("mcpServers", {})
90
+ mcp_servers["devcouncil"] = _cursor_mcp_config(project_root)["mcpServers"]["devcouncil"]
91
+ _save_json(path, data)
92
+ return path
93
+
94
+
95
+ def _record_cursor_config(project_root: Path) -> None:
96
+ def mutate(config: dict) -> None:
97
+ cursor = config.setdefault("integrations", {}).setdefault("cursor", {})
98
+ cursor.update({
99
+ "enabled": True,
100
+ "config_path": str(_cursor_config_path(project_root).relative_to(project_root)),
101
+ })
102
+
103
+ _mutate_raw_config(project_root, mutate)
104
+
105
+
106
+ def probe_cursor_auth() -> tuple[bool, str]:
107
+ """Probe Cursor CLI auth via agent status/about."""
108
+ executable = _cursor_executable()
109
+ if not executable:
110
+ return False, "agent/cursor-agent not found on PATH"
111
+ if os.environ.get("CURSOR_API_KEY"):
112
+ return True, "CURSOR_API_KEY set (CI headless auth)"
113
+ for subcommand in ("status", "about"):
114
+ code, output = _run_capture([executable, subcommand])
115
+ if code == 0 and output.strip():
116
+ first = output.strip().splitlines()[0]
117
+ return True, first
118
+ return False, "Run agent login or set CURSOR_API_KEY for CI headless auth"
119
+
120
+
121
+ def probe_cursor_mcp_list() -> tuple[bool, str]:
122
+ executable = _cursor_executable()
123
+ if not executable:
124
+ return False, "agent/cursor-agent not found on PATH"
125
+ code, output = _run_capture([executable, "mcp", "list"])
126
+ if code != 0:
127
+ return False, output.strip() or "agent mcp list failed"
128
+ return True, output.strip().splitlines()[0] if output.strip() else "MCP servers listed"
129
+
130
+
131
+ def _configure_cursor(project_root: Path, apply: bool) -> bool:
132
+ path = _cursor_config_path(project_root)
133
+ config = _cursor_mcp_config(project_root)
134
+ executable = _cursor_executable() or "agent"
135
+ if not apply:
136
+ console.print("[bold]Cursor[/bold]")
137
+ console.print(f"Project MCP config file: [dim]{path}[/dim]")
138
+ console.print(json.dumps(config, separators=(",", ":")), soft_wrap=True)
139
+ console.print(f"Verify in Cursor CLI with: [dim]{executable} mcp list[/dim]")
140
+ auth_ok, auth_details = probe_cursor_auth()
141
+ if auth_ok:
142
+ console.print(f"Auth: [green]{auth_details}[/green]")
143
+ else:
144
+ console.print(f"Auth: [yellow]{auth_details}[/yellow]")
145
+ if not os.environ.get("CURSOR_API_KEY"):
146
+ console.print("CI headless: export [dim]CURSOR_API_KEY[/dim] (see Cursor headless docs)")
147
+ return True
148
+
149
+ if not shutil.which("cursor") and not _cursor_executable():
150
+ console.print("[yellow]Cursor CLI not found on PATH. Project MCP config will still be available to Cursor.[/yellow]")
151
+ try:
152
+ written = _write_cursor_config(project_root)
153
+ except ValueError as exc:
154
+ console.print(f"[red]{exc}[/red]")
155
+ return False
156
+ _record_cursor_config(project_root)
157
+ console.print(f"[green]Cursor MCP config written:[/green] {written}")
158
+ mcp_ok, mcp_details = probe_cursor_mcp_list()
159
+ if mcp_ok:
160
+ console.print(f"[dim]MCP probe: {mcp_details}[/dim]")
161
+ auth_ok, auth_details = probe_cursor_auth()
162
+ if not auth_ok:
163
+ console.print(f"[yellow]Auth check: {auth_details}[/yellow]")
164
+ return True
@@ -0,0 +1,49 @@
1
+ """Gemini integration adapter (deprecated).
2
+
3
+ Google replaced Gemini CLI with Antigravity CLI. Prefer ``dev integrate antigravity --apply``.
4
+ Explicit ``dev integrate gemini`` remains for compatibility only.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import warnings
9
+ from pathlib import Path
10
+
11
+ from devcouncil.executors.agent_registry import GEMINI_DEPRECATION_MESSAGE
12
+ from devcouncil.integrations.clients import common as _common
13
+
14
+ _project_root = _common._project_root
15
+ _warn_if_verify_only = _common._warn_if_verify_only
16
+ _server_args = _common._server_args
17
+ _format_command = _common._format_command
18
+ _quote_powershell_arg = _common._quote_powershell_arg
19
+ _run = _common._run
20
+ _run_capture = _common._run_capture
21
+ _config_path = _common._config_path
22
+ _load_raw_config = _common._load_raw_config
23
+ _save_raw_config = _common._save_raw_config
24
+ _load_json = _common._load_json
25
+ _save_json = _common._save_json
26
+ _load_json_strict = _common._load_json_strict
27
+ _mutate_raw_config = _common._mutate_raw_config
28
+ _batched_raw_config = _common._batched_raw_config
29
+ _probe_mcp_tools = _common._probe_mcp_tools
30
+ _print_command = _common._print_command
31
+ _configure = _common._configure
32
+
33
+ console = _common.console
34
+
35
+
36
+ def _gemini_command(project_root: Path, scope: str) -> list[str]:
37
+ warnings.warn(GEMINI_DEPRECATION_MESSAGE, DeprecationWarning, stacklevel=2)
38
+ console.print(f"[yellow]{GEMINI_DEPRECATION_MESSAGE}[/yellow]")
39
+ return [
40
+ "gemini",
41
+ "mcp",
42
+ "add",
43
+ "--scope",
44
+ scope,
45
+ "--env",
46
+ f"DEVCOUNCIL_PROJECT_ROOT={project_root}",
47
+ "devcouncil",
48
+ *_server_args(project_root),
49
+ ]
@@ -0,0 +1,105 @@
1
+ """Grok Build integration adapter."""
2
+ from __future__ import annotations
3
+
4
+ import shutil
5
+ from pathlib import Path
6
+
7
+ from devcouncil.integrations.clients import common as _common
8
+
9
+ _project_root = _common._project_root
10
+ _format_command = _common._format_command
11
+ _run = _common._run
12
+ _mutate_raw_config = _common._mutate_raw_config
13
+ _configure = _common._configure
14
+
15
+ console = _common.console
16
+
17
+
18
+ def _grok_config_path(project_root: Path) -> Path:
19
+ return project_root / ".grok" / "config.toml"
20
+
21
+
22
+ def _grok_mcp_toml_snippet(project_root: Path) -> str:
23
+ root = str(project_root)
24
+ return (
25
+ "[mcp_servers.devcouncil]\n"
26
+ 'command = "devcouncil"\n'
27
+ 'args = ["mcp-server"]\n'
28
+ f'env = {{ DEVCOUNCIL_PROJECT_ROOT = "{root}" }}\n'
29
+ )
30
+
31
+
32
+ def _grok_mcp_command(project_root: Path) -> list[str]:
33
+ return [
34
+ "grok",
35
+ "mcp",
36
+ "add",
37
+ "devcouncil",
38
+ "--scope",
39
+ "project",
40
+ "-e",
41
+ f"DEVCOUNCIL_PROJECT_ROOT={project_root}",
42
+ "--",
43
+ "devcouncil",
44
+ "mcp-server",
45
+ ]
46
+
47
+
48
+ def _merge_grok_config_toml(project_root: Path) -> Path:
49
+ path = _grok_config_path(project_root)
50
+ path.parent.mkdir(parents=True, exist_ok=True)
51
+ snippet = _grok_mcp_toml_snippet(project_root)
52
+ existing = path.read_text(encoding="utf-8") if path.exists() else ""
53
+ if "mcp_servers.devcouncil" in existing or "[mcp_servers.devcouncil]" in existing:
54
+ return path
55
+ separator = "\n" if existing and not existing.endswith("\n") else ""
56
+ path.write_text(f"{existing}{separator}\n{snippet}", encoding="utf-8")
57
+ return path
58
+
59
+
60
+ def _record_grok_config(project_root: Path) -> None:
61
+ def mutate(config: dict) -> None:
62
+ grok = config.setdefault("integrations", {}).setdefault("grok", {})
63
+ grok.update({
64
+ "enabled": True,
65
+ "config_path": str(_grok_config_path(project_root).relative_to(project_root)),
66
+ })
67
+
68
+ _mutate_raw_config(project_root, mutate)
69
+
70
+
71
+ def _configure_grok(project_root: Path, apply: bool) -> bool:
72
+ path = _grok_config_path(project_root)
73
+ cli_command = _grok_mcp_command(project_root)
74
+ if not apply:
75
+ console.print("[bold]Grok Build[/bold]")
76
+ console.print(f"Preferred MCP registration: [dim]{_format_command(cli_command)}[/dim]")
77
+ console.print(f"Fallback project MCP config: [dim]{path}[/dim]")
78
+ console.print(_grok_mcp_toml_snippet(project_root), soft_wrap=True)
79
+ console.print(
80
+ "Project hooks require trust — run [dim]/hooks-trust[/dim] in Grok after install."
81
+ )
82
+ console.print("Verify with: [dim]grok mcp list --json[/dim]")
83
+ return True
84
+
85
+ if shutil.which("grok"):
86
+ code = _run(cli_command)
87
+ if code == 0:
88
+ _record_grok_config(project_root)
89
+ console.print("[green]Grok MCP server registered via grok mcp add.[/green]")
90
+ return True
91
+ console.print(
92
+ "[yellow]grok mcp add failed; falling back to .grok/config.toml merge.[/yellow]"
93
+ )
94
+
95
+ try:
96
+ written = _merge_grok_config_toml(project_root)
97
+ except OSError as exc:
98
+ console.print(f"[red]Failed to write Grok MCP config: {exc}[/red]")
99
+ return False
100
+ _record_grok_config(project_root)
101
+ console.print(f"[green]Grok MCP config written:[/green] {written}")
102
+ console.print(
103
+ "[dim]Trust project hooks with /hooks-trust (or grok --trust) before pre-action gates run.[/dim]"
104
+ )
105
+ return True