devcouncil 0.3.1 → 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
@@ -8,6 +8,7 @@ from pathlib import Path
8
8
  from typing import Any
9
9
 
10
10
  from devcouncil.integrations.check import build_integration_check_report
11
+ from devcouncil.executors.agent_registry import GEMINI_DEPRECATION_MESSAGE
11
12
 
12
13
  logger = logging.getLogger(__name__)
13
14
 
@@ -18,6 +19,7 @@ VALID_INTEGRATION_TARGETS = {
18
19
  "gemini",
19
20
  "claude",
20
21
  "cursor",
22
+ "grok",
21
23
  "opencode",
22
24
  "antigravity",
23
25
  "agy",
@@ -104,6 +106,11 @@ def apply_integration_target(
104
106
  add_result(name, code == 0, None, "MCP registration command exited " + str(code))
105
107
 
106
108
  def apply_project_file(name: str) -> None:
109
+ if name == "grok":
110
+ ok = integrate._configure_grok(root, apply=True)
111
+ path = integrate._grok_config_path(root)
112
+ add_result(name, ok, path if path.exists() else None, "Grok MCP integration configured." if ok else "Grok MCP integration failed.")
113
+ return
107
114
  writers = {
108
115
  "cursor": integrate._write_cursor_config,
109
116
  "opencode": integrate._write_opencode_config,
@@ -126,7 +133,8 @@ def apply_integration_target(
126
133
 
127
134
  def apply_hooks() -> None:
128
135
  integrate._configure_native_hooks(root, "all", apply=True, claude_write_gate=claude_write_gate)
129
- add_result("hooks", True, None, "Native hook files configured.")
136
+ integrate._install_git_map_hooks(root, apply=True)
137
+ add_result("hooks", True, None, "Native hook files configured (incl. git map --if-stale).")
130
138
 
131
139
  def apply_claude_assets() -> None:
132
140
  try:
@@ -134,16 +142,21 @@ def apply_integration_target(
134
142
  except (ValueError, FileNotFoundError, OSError) as exc:
135
143
  add_result("claude-assets", False, None, f"Claude asset setup failed: {exc}")
136
144
  return
145
+ integrate._record_claude_config(
146
+ root,
147
+ scope=claude_scope,
148
+ write_gate=claude_write_gate,
149
+ )
137
150
  add_result("claude-assets", True, None, f"Claude assets installed ({len(written)} file(s)).")
138
151
 
139
152
  if normalized == "all":
140
- for name in ("codex", "gemini", "claude"):
153
+ for name in ("claude", "codex"):
141
154
  apply_first_party(name)
142
155
  # Batch the per-tool config.yaml record updates (project files, aider,
143
156
  # and native hooks) into one load/save cycle. _batched_raw_config is
144
157
  # re-entrant, so apply_hooks()'s own batching participates in this one.
145
158
  with integrate._batched_raw_config(root):
146
- for name in ("cursor", "opencode", "antigravity", "warp"):
159
+ for name in ("cursor", "grok", "opencode", "antigravity", "warp"):
147
160
  apply_project_file(name)
148
161
  apply_aider()
149
162
  if include_hooks:
@@ -153,8 +166,18 @@ def apply_integration_target(
153
166
  # the claude CLI is on PATH, since these are plain files.
154
167
  apply_claude_assets()
155
168
  elif normalized in {"codex", "gemini", "claude"}:
169
+ if normalized == "gemini":
170
+ warnings.append(GEMINI_DEPRECATION_MESSAGE)
171
+ integrate.console.print(f"[yellow]{GEMINI_DEPRECATION_MESSAGE}[/yellow]")
156
172
  apply_first_party(normalized)
157
- elif normalized in {"cursor", "opencode", "antigravity", "warp"}:
173
+ if include_hooks and normalized == "codex":
174
+ integrate._configure_native_hooks(root, "codex", apply=True)
175
+ add_result("codex-hooks", True, root / ".codex" / "hooks.json", "Codex native hooks configured; review trust with /hooks.")
176
+ elif include_hooks and normalized == "claude":
177
+ integrate._install_claude_hooks(root, write_gate=claude_write_gate)
178
+ apply_claude_assets()
179
+ add_result("claude-hooks", True, root / ".claude" / "settings.local.json", "Claude native hooks configured.")
180
+ elif normalized in {"cursor", "grok", "opencode", "antigravity", "warp"}:
158
181
  apply_project_file(normalized)
159
182
  elif normalized == "aider":
160
183
  apply_aider()
@@ -6,6 +6,7 @@ import json
6
6
  import shutil
7
7
  import subprocess
8
8
  import sys
9
+ import tomllib
9
10
  from dataclasses import dataclass
10
11
  from pathlib import Path
11
12
  from typing import Any
@@ -13,12 +14,15 @@ from typing import Any
13
14
  from devcouncil.executors.agent_registry import (
14
15
  CODING_CLI_INTEGRATION_INFO,
15
16
  CODING_CLI_VERSION_COMMANDS,
17
+ DEPRECATED_CODING_CLIS,
18
+ GEMINI_DEPRECATION_MESSAGE,
16
19
  detect_available_coding_cli,
17
20
  resolve_automated_executor,
18
21
  resolve_coding_cli_executable,
19
22
  resolve_coding_cli_probe_order,
20
23
  )
21
24
  from devcouncil.utils.subprocess_env import clean_subprocess_env
25
+ from devcouncil.utils.json_persist import read_json
22
26
 
23
27
 
24
28
  @dataclass(frozen=True)
@@ -58,6 +62,7 @@ CODING_CLI_CHECK_LABELS: dict[str, str] = {
58
62
  "gemini": "Gemini CLI",
59
63
  "claude": "Claude Code",
60
64
  "cursor": "Cursor",
65
+ "grok": "Grok Build",
61
66
  "opencode": "OpenCode",
62
67
  "antigravity": "Google Antigravity CLI",
63
68
  "warp": "Warp / Oz",
@@ -127,19 +132,123 @@ def _load_json_file(path: Path) -> dict[str, Any]:
127
132
  if not path.exists():
128
133
  return {}
129
134
  try:
130
- loaded = json.loads(path.read_text(encoding="utf-8")) or {}
135
+ loaded = read_json(path) or {}
131
136
  except json.JSONDecodeError:
132
137
  return {}
133
138
  return loaded if isinstance(loaded, dict) else {}
134
139
 
135
140
 
141
+ def _claude_config_status(project_root: Path) -> tuple[str, bool, list[str]]:
142
+ settings_path = project_root / ".claude" / "settings.local.json"
143
+ assets_dir = project_root / ".claude" / "commands" / "devcouncil"
144
+ data = _load_json_file(settings_path)
145
+ status_line = data.get("statusLine") if data else {}
146
+ status_line_ok = (
147
+ isinstance(status_line, dict)
148
+ and "hook claude-statusline" in str(status_line.get("command", ""))
149
+ )
150
+ mcp_ok = "devcouncil" in (data.get("enabledMcpjsonServers") or [])
151
+ assets_ok = assets_dir.is_dir() and (assets_dir / "status.md").exists()
152
+ ok = (status_line_ok or mcp_ok) and assets_ok
153
+ paths = [str(settings_path)]
154
+ if assets_ok:
155
+ paths.append(str(assets_dir))
156
+ if ok:
157
+ return "ok", False, paths
158
+ if not settings_path.exists() and not assets_ok:
159
+ return "missing", True, paths
160
+ return "drifted", True, paths
161
+
162
+
163
+ def _codex_config_status(project_root: Path) -> tuple[str, bool, list[str]]:
164
+ """Check the client-persisted MCP registration, not just repo hook files."""
165
+ executable = resolve_coding_cli_executable(project_root, "codex")
166
+ logical_path = "codex mcp:devcouncil"
167
+ if not executable:
168
+ return "missing", True, [logical_path]
169
+ try:
170
+ result = subprocess.run(
171
+ [executable, "mcp", "get", "devcouncil", "--json"],
172
+ cwd=project_root,
173
+ capture_output=True,
174
+ text=True,
175
+ encoding="utf-8",
176
+ errors="replace",
177
+ timeout=10,
178
+ env=clean_subprocess_env(),
179
+ )
180
+ data = json.loads(result.stdout) if result.returncode == 0 else {}
181
+ except (OSError, subprocess.TimeoutExpired, json.JSONDecodeError):
182
+ data = {}
183
+
184
+ from devcouncil.integrations.clients.common import _server_args
185
+
186
+ expected = _server_args(project_root)
187
+ transport = data.get("transport") if isinstance(data, dict) else {}
188
+ transport = transport if isinstance(transport, dict) else {}
189
+ env = transport.get("env") or {}
190
+ ok = (
191
+ data.get("enabled") is True
192
+ and transport.get("type") == "stdio"
193
+ and transport.get("command") == expected[0]
194
+ and transport.get("args") == expected[1:]
195
+ and isinstance(env, dict)
196
+ and env.get("DEVCOUNCIL_PROJECT_ROOT") == str(project_root)
197
+ )
198
+ return ("ok" if ok else "drifted"), not ok, [logical_path, str(project_root / ".codex" / "hooks.json")]
199
+
200
+
201
+ def _codex_hook_schema_status(project_root: Path) -> tuple[bool, str]:
202
+ """Validate the repository Codex hook schema and canonical feature flag."""
203
+ hooks_path = project_root / ".codex" / "hooks.json"
204
+ config_path = project_root / ".codex" / "config.toml"
205
+ try:
206
+ data = read_json(hooks_path)
207
+ config = tomllib.loads(config_path.read_text(encoding="utf-8"))
208
+ except (OSError, json.JSONDecodeError, tomllib.TOMLDecodeError) as exc:
209
+ return False, f"Invalid Codex hook configuration: {exc}"
210
+ hooks = data.get("hooks") if isinstance(data, dict) else None
211
+ required = {"PreToolUse", "PostToolUse", "SessionStart", "Stop", "SubagentStop"}
212
+ if not isinstance(hooks, dict) or not required.issubset(hooks):
213
+ missing = sorted(required - set(hooks or {}))
214
+ return False, f"Missing Codex hook events: {', '.join(missing)}"
215
+ for event, groups in hooks.items():
216
+ if not isinstance(groups, list):
217
+ return False, f"Codex hook event {event} must contain a list"
218
+ for group in groups:
219
+ for hook in group.get("hooks", []) if isinstance(group, dict) else []:
220
+ if not isinstance(hook, dict) or not str(hook.get("name", "")).startswith("devcouncil-"):
221
+ continue
222
+ timeout = hook.get("timeout")
223
+ if not isinstance(timeout, int) or not 1 <= timeout <= 600:
224
+ return False, f"Codex hook {hook.get('name')} timeout must be 1..600 seconds"
225
+ if "--client codex" not in str(hook.get("command", "")):
226
+ return False, f"Codex hook {hook.get('name')} does not select the Codex schema"
227
+ features = config.get("features") if isinstance(config, dict) else {}
228
+ if not isinstance(features, dict) or features.get("hooks") is not True:
229
+ return False, "[features].hooks must be true"
230
+ if "codex_hooks" in features:
231
+ return False, "Deprecated [features].codex_hooks is still present"
232
+ return True, f"{hooks_path}; hook trust must be reviewed in Codex with /hooks"
233
+
234
+
136
235
  def _cursor_config_status(project_root: Path) -> tuple[str, bool, list[str]]:
137
236
  path = project_root / ".cursor" / "mcp.json"
138
237
  data = _load_json_file(path)
139
238
  server = ((data.get("mcpServers") or {}).get("devcouncil") or {}) if data else {}
239
+ command = server.get("command")
240
+ # Accept bare ``devcouncil`` or an absolute/relative path whose basename is
241
+ # ``devcouncil`` (project venv). Both are valid; absolute paths are preferred
242
+ # when Cursor's shell PATH does not include the project venv.
243
+ from pathlib import Path as _Path
244
+
245
+ name_ok = False
246
+ if isinstance(command, str) and command.strip():
247
+ name = _Path(command.replace("\\", "/")).name.lower()
248
+ name_ok = name in {"devcouncil", "devcouncil.exe"}
140
249
  ok = (
141
250
  server.get("type") == "stdio"
142
- and server.get("command") == "devcouncil"
251
+ and name_ok
143
252
  and server.get("args") == ["mcp-server"]
144
253
  and (server.get("env") or {}).get("DEVCOUNCIL_PROJECT_ROOT") == str(project_root)
145
254
  )
@@ -148,6 +257,22 @@ def _cursor_config_status(project_root: Path) -> tuple[str, bool, list[str]]:
148
257
  return ("missing" if not path.exists() else "drifted"), True, [str(path)]
149
258
 
150
259
 
260
+ def _grok_config_status(project_root: Path) -> tuple[str, bool, list[str]]:
261
+ path = project_root / ".grok" / "config.toml"
262
+ if not path.exists():
263
+ return "missing", True, [str(path)]
264
+ text = path.read_text(encoding="utf-8")
265
+ ok = (
266
+ "[mcp_servers.devcouncil]" in text
267
+ and "devcouncil" in text
268
+ and "mcp-server" in text
269
+ and str(project_root) in text
270
+ )
271
+ if ok:
272
+ return "ok", False, [str(path)]
273
+ return "drifted", True, [str(path)]
274
+
275
+
151
276
  def _opencode_config_status(project_root: Path) -> tuple[str, bool, list[str]]:
152
277
  path = project_root / "opencode.json"
153
278
  data = _load_json_file(path)
@@ -203,8 +328,14 @@ def integration_capability_rows(project_root: Path) -> list[dict[str, object]]:
203
328
  config_status = "not_applicable"
204
329
  fixable = bool(info.mcp or info.hooks or info.launcher_shim)
205
330
  paths: list[str] = []
206
- if client == "cursor":
331
+ if client == "codex":
332
+ config_status, fixable, paths = _codex_config_status(project_root)
333
+ elif client == "cursor":
207
334
  config_status, fixable, paths = _cursor_config_status(project_root)
335
+ elif client == "grok":
336
+ config_status, fixable, paths = _grok_config_status(project_root)
337
+ elif client == "claude":
338
+ config_status, fixable, paths = _claude_config_status(project_root)
208
339
  elif client == "opencode":
209
340
  config_status, fixable, paths = _opencode_config_status(project_root)
210
341
  elif client == "antigravity":
@@ -235,20 +366,28 @@ def integration_capability_rows(project_root: Path) -> list[dict[str, object]]:
235
366
  def _hook_config_references_devcouncil(path: Path) -> bool | None:
236
367
  """Return whether a client hook config still wires DevCouncil's gate.
237
368
 
238
- ``True`` -> the file exists and references ``devcouncil`` somewhere in its hooks.
369
+ ``True`` -> the file exists and still invokes the DevCouncil pre/post-tool gate.
239
370
  ``False`` -> the file exists but no longer references it (tampered/disarmed).
240
371
  ``None`` -> the file does not exist (client was never integrated here).
241
372
 
242
373
  Reads the raw text rather than parsing each client's bespoke schema so it works
243
374
  uniformly across JSON hook files and is resilient to format drift; the goal is a
244
- tamper tripwire, not full schema validation."""
375
+ tamper tripwire, not full schema validation.
376
+
377
+ Accepts either an explicit ``devcouncil`` marker (named hooks / ``devcouncil`` CLI)
378
+ or the project-venv ``dev hook …`` command Cursor installs (no named hook id).
379
+ """
245
380
  if not path.exists():
246
381
  return None
247
382
  try:
248
383
  text = path.read_text(encoding="utf-8")
249
384
  except OSError:
250
385
  return False
251
- return "devcouncil" in text.lower()
386
+ lower = text.lower()
387
+ if "devcouncil" in lower:
388
+ return True
389
+ # Cursor flat hooks: ``…/bin/dev hook pre-tool-use --client cursor …``
390
+ return "hook pre-tool-use" in lower or "hook post-tool-use" in lower
252
391
 
253
392
 
254
393
  def _hook_config_tamper_targets(project_root: Path) -> list[tuple[str, Path]]:
@@ -257,6 +396,7 @@ def _hook_config_tamper_targets(project_root: Path) -> list[tuple[str, Path]]:
257
396
  ("Codex", project_root / ".codex" / "hooks.json"),
258
397
  ("Gemini", project_root / ".gemini" / "settings.json"),
259
398
  ("Cursor", project_root / ".cursor" / "hooks.json"),
399
+ ("Grok", project_root / ".grok" / "hooks" / "devcouncil.json"),
260
400
  ]
261
401
 
262
402
 
@@ -273,9 +413,9 @@ def build_integration_check_report(project_root: Path, *, strict: bool = False)
273
413
  failures += 1
274
414
 
275
415
  def add_optional(ok: bool, name: str, details: str) -> None:
276
- if strict and not ok:
277
- add(False, name, details)
278
- return
416
+ # Coding CLIs (and similar optional probes) stay informational even under
417
+ # --strict. Absence is never a failure; --strict still elevates real
418
+ # project/integration defects via add() / add_soft().
279
419
  rows.append(IntegrationCheckRow(name=name, status="ok" if ok else "missing", details=details))
280
420
 
281
421
  def add_skip(name: str, details: str) -> None:
@@ -284,20 +424,29 @@ def build_integration_check_report(project_root: Path, *, strict: bool = False)
284
424
  root = project_root.expanduser().resolve()
285
425
  detected = detect_available_coding_cli(root)
286
426
  add((root / ".devcouncil").exists(), "Project state", str(root / ".devcouncil"))
287
- devcouncil_path = shutil.which("devcouncil")
427
+ from devcouncil.integrations.clients.common import resolve_dev_executable
428
+
429
+ devcouncil_path = resolve_dev_executable(root)
430
+ resolved_dev = Path(devcouncil_path).exists() or shutil.which(devcouncil_path) is not None
431
+ devcouncil_launch = [devcouncil_path] if resolved_dev else [sys.executable, "-m", "devcouncil"]
288
432
  add(
289
- devcouncil_path is not None or Path(sys.executable).exists(),
433
+ resolved_dev or Path(sys.executable).exists(),
290
434
  "devcouncil CLI",
291
- devcouncil_path or f"{sys.executable} -m devcouncil",
435
+ " ".join(devcouncil_launch),
292
436
  )
293
437
 
294
- devcouncil_launch = [devcouncil_path] if devcouncil_path else [sys.executable, "-m", "devcouncil"]
295
438
  code, output = integrate._run_capture([*devcouncil_launch, "--help"])
296
439
  add(code == 0, "devcouncil command", output.splitlines()[0] if output else "No output")
297
440
 
298
- for client in resolve_coding_cli_probe_order(root):
441
+ probe_order = resolve_coding_cli_probe_order(root)
442
+ for client in probe_order:
299
443
  cli_ok, cli_details = probe_coding_cli_version(client)
300
444
  add_optional(cli_ok, CODING_CLI_CHECK_LABELS.get(client, client), cli_details)
445
+ for client in sorted(DEPRECATED_CODING_CLIS):
446
+ if client in probe_order:
447
+ continue
448
+ label = CODING_CLI_CHECK_LABELS.get(client, client)
449
+ add_skip(label, GEMINI_DEPRECATION_MESSAGE)
301
450
 
302
451
  rec_ok, rec_details = recommended_executor_status(root, detected)
303
452
  add_optional(rec_ok, "Recommended coding CLI", rec_details)
@@ -320,7 +469,7 @@ def build_integration_check_report(project_root: Path, *, strict: bool = False)
320
469
  hooks_ok = False
321
470
  if cursor_hooks.exists():
322
471
  try:
323
- hooks_data = json.loads(cursor_hooks.read_text(encoding="utf-8")) or {}
472
+ hooks_data = read_json(cursor_hooks) or {}
324
473
  hooks_ok = "preToolUse" in hooks_data.get("hooks", {})
325
474
  except json.JSONDecodeError:
326
475
  hooks_ok = False
@@ -329,6 +478,29 @@ def build_integration_check_report(project_root: Path, *, strict: bool = False)
329
478
  "Cursor hooks",
330
479
  str(cursor_hooks) if hooks_ok else "Run dev integrate hooks --apply --tool cursor.",
331
480
  )
481
+ from devcouncil.integrations.clients.cursor import probe_cursor_auth
482
+
483
+ auth_ok, auth_details = probe_cursor_auth()
484
+ # Authentication for an optional, non-selected client is machine/user
485
+ # state, not a broken repository integration. Keep it informational even
486
+ # under --strict, matching missing optional CLI handling.
487
+ add_optional(auth_ok, "Cursor auth", auth_details)
488
+
489
+ grok_hooks = root / ".grok" / "hooks" / "devcouncil.json"
490
+ grok_enabled = bool(raw_config.get("integrations", {}).get("grok", {}).get("enabled"))
491
+ if grok_enabled or grok_hooks.exists():
492
+ hooks_ok = False
493
+ if grok_hooks.exists():
494
+ try:
495
+ hooks_data = read_json(grok_hooks) or {}
496
+ hooks_ok = "PreToolUse" in hooks_data.get("hooks", {})
497
+ except json.JSONDecodeError:
498
+ hooks_ok = False
499
+ add(
500
+ hooks_ok,
501
+ "Grok hooks",
502
+ str(grok_hooks) if hooks_ok else "Run dev integrate hooks --apply --tool grok (then /hooks-trust in Grok).",
503
+ )
332
504
 
333
505
  opencode_config = integrate._opencode_config_path(root)
334
506
  opencode_enabled = bool(raw_config.get("integrations", {}).get("opencode", {}).get("enabled"))
@@ -338,7 +510,7 @@ def build_integration_check_report(project_root: Path, *, strict: bool = False)
338
510
  plugin_registered = False
339
511
  if plugin_ok and opencode_config.exists():
340
512
  try:
341
- opencode_data = json.loads(opencode_config.read_text(encoding="utf-8")) or {}
513
+ opencode_data = read_json(opencode_config) or {}
342
514
  plugin_registered = (
343
515
  f"./.devcouncil/integrations/{integrate.OPENCODE_HOOK_PLUGIN_NAME}"
344
516
  in (opencode_data.get("plugin") or [])
@@ -387,10 +559,30 @@ def build_integration_check_report(project_root: Path, *, strict: bool = False)
387
559
  str(hook_path) if references else f"{hook_path} no longer references devcouncil (tampered/disarmed).",
388
560
  )
389
561
 
562
+ codex_hooks_path = root / ".codex" / "hooks.json"
563
+ if codex_hooks_path.exists():
564
+ codex_schema_ok, codex_schema_details = _codex_hook_schema_status(root)
565
+ add(codex_schema_ok, "Codex hook schema", codex_schema_details)
566
+ add_skip("Codex hook trust", "Open Codex in this project and run /hooks after any hook change.")
567
+
568
+ from devcouncil.integrations.clients.common import check_hook_dev_executable
569
+
570
+ hook_exe_ok, hook_exe_details = check_hook_dev_executable(root)
571
+ if common_recorded_hook_exe(root) is not None:
572
+ add(hook_exe_ok, "Hook `dev` executable", hook_exe_details)
573
+ else:
574
+ add_skip("Hook `dev` executable", hook_exe_details)
575
+
390
576
  recommended = resolve_automated_executor(root, None) if detected else None
391
577
  return IntegrationCheckReport(tuple(rows), recommended, failures)
392
578
 
393
579
 
580
+ def common_recorded_hook_exe(project_root: Path) -> str | None:
581
+ from devcouncil.integrations.clients.common import recorded_hook_dev_executable
582
+
583
+ return recorded_hook_dev_executable(project_root)
584
+
585
+
394
586
  def integration_status_summary(project_root: Path) -> dict[str, Any]:
395
587
  from devcouncil.app.config import load_config
396
588
 
@@ -402,11 +594,13 @@ def integration_status_summary(project_root: Path) -> dict[str, Any]:
402
594
  default_executor = execution.default_executor
403
595
  stream_cli_output = execution.stream_cli_output
404
596
  cursor_resume_mode = execution.cursor_resume_mode
597
+ grok_resume_mode = execution.grok_resume_mode
405
598
  custom_probe_order = list(execution.coding_cli_probe_order)
406
599
  except Exception:
407
600
  default_executor = "manual"
408
601
  stream_cli_output = False
409
602
  cursor_resume_mode = "off"
603
+ grok_resume_mode = "off"
410
604
  custom_probe_order = []
411
605
 
412
606
  resolved = resolve_automated_executor(project_root, None) if detected or default_executor != "manual" else "manual"
@@ -422,5 +616,6 @@ def integration_status_summary(project_root: Path) -> dict[str, Any]:
422
616
  "custom_probe_order": custom_probe_order,
423
617
  "stream_cli_output": stream_cli_output,
424
618
  "cursor_resume_mode": cursor_resume_mode,
619
+ "grok_resume_mode": grok_resume_mode,
425
620
  "capabilities": integration_capability_rows(project_root),
426
621
  }