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,315 @@
1
+ """Claude Code advisor-tool helpers (CLI ``--advisor`` / SDK ``extra_args`` / settings).
2
+
3
+ Kept out of :mod:`devcouncil.execution.prompt_builder` so Codex/Cursor/native never
4
+ see advisor steering. Pairing preflight is soft: clear bad pairs skip attaching
5
+ ``--advisor`` so Claude does not hard-exit and burn the go-loop repair budget.
6
+
7
+ DevCouncil only soft-filters obvious mismatches (haiku advisor, weaker family,
8
+ fable+non-fable). Claude Code validates the full versioned pairing matrix at launch.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import logging
14
+ import os
15
+ import re
16
+ import shutil
17
+ import subprocess
18
+ from functools import lru_cache
19
+ from typing import Literal, Mapping
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+ # Claude Code floor for the advisor server tool (Anthropic API only).
24
+ MIN_ADVISOR_CLAUDE_VERSION = (2, 1, 98)
25
+ # Fable as main or advisor requires a newer Claude Code floor.
26
+ MIN_FABLE_ADVISOR_CLAUDE_VERSION = (2, 1, 170)
27
+ DISABLE_ADVISOR_ENV = "CLAUDE_CODE_DISABLE_ADVISOR_TOOL"
28
+
29
+ # Cloud providers where Claude Code does not offer the Anthropic advisor tool.
30
+ NON_ANTHROPIC_ADVISOR_ENVS = (
31
+ "CLAUDE_CODE_USE_BEDROCK",
32
+ "CLAUDE_CODE_USE_VERTEX",
33
+ "CLAUDE_CODE_USE_FOUNDRY",
34
+ )
35
+
36
+ # Short interactive nudge shared by MCP prompts, slash commands, and subagents.
37
+ ADVISOR_STEERING_NUDGE = (
38
+ "When the advisor tool is available, consult it before committing to an approach, "
39
+ "when stuck on a recurring error, and before declaring the task complete."
40
+ )
41
+
42
+ # Anthropic "Suggested system prompt for coding tasks" — timing block.
43
+ ADVISOR_CODING_TIMING = (
44
+ "You have access to an `advisor` tool backed by a stronger reviewer model. "
45
+ "It takes NO parameters — when you call advisor(), your entire conversation "
46
+ "history is automatically forwarded. They see the task, every tool call you've "
47
+ "made, every result you've seen.\n\n"
48
+ "Call advisor BEFORE substantive work — before writing, before committing to an "
49
+ "interpretation, before building on an assumption. If the task requires orientation "
50
+ "first (finding files, fetching a source, seeing what's there), do that, then call "
51
+ "advisor. Orientation is not substantive work. Writing, editing, and declaring an "
52
+ "answer are.\n\n"
53
+ "Also call advisor:\n"
54
+ "- When you believe the task is complete. BEFORE this call, make your deliverable "
55
+ "durable: write the file, save the result, commit the change. The advisor call takes "
56
+ "time; if the session ends during it, a durable result persists and an unwritten "
57
+ "one doesn't.\n"
58
+ "- When stuck — errors recurring, approach not converging, results that don't fit.\n"
59
+ "- When considering a change of approach.\n\n"
60
+ "On tasks longer than a few steps, call advisor at least once before committing to "
61
+ "an approach and once before declaring done. On short reactive tasks where the next "
62
+ "action is dictated by tool output you just read, you don't need to keep calling — "
63
+ "the advisor adds most of its value on the first call, before the approach crystallizes."
64
+ )
65
+
66
+ # Anthropic treat-advice block (place directly after timing).
67
+ ADVISOR_TREAT_ADVICE = (
68
+ "Give the advice serious weight. If you follow a step and it fails empirically, or "
69
+ "you have primary-source evidence that contradicts a specific claim (the file says X, "
70
+ "the paper states Y), adapt. A passing self-test is not evidence the advice is wrong — "
71
+ "it's evidence your test doesn't check what the advice is checking.\n\n"
72
+ "If you've already retrieved data pointing one way and the advisor points another: "
73
+ "don't silently switch. Surface the conflict in one more advisor call — "
74
+ '"I found X, you suggest Y, which constraint breaks the tie?" The advisor saw your '
75
+ "evidence but may have underweighted it; a reconcile call is cheaper than committing "
76
+ "to the wrong branch."
77
+ )
78
+
79
+ REPAIR_ADVISOR_AUTHORITY = (
80
+ "A correction_manifest is present for this repair. Treat it as authoritative over "
81
+ "prior session history and any prior advisor advice; change approach on identical gaps."
82
+ )
83
+
84
+ # Soft cost trim for the user prompt (CC-passable; not Messages-API-only).
85
+ ADVISOR_COST_TRIM_USER = (
86
+ "(Advisor: keep guidance under ~80 words — I need a focused starting point, "
87
+ "not a comprehensive plan.)"
88
+ )
89
+
90
+ # Capability tiers for soft pairing. Higher = stronger. Unknown models are not
91
+ # rejected here — Claude Code validates those at launch.
92
+ _FAMILY_TIER: dict[str, int] = {
93
+ "haiku": 1,
94
+ "sonnet": 2,
95
+ "opus": 3,
96
+ "fable": 4,
97
+ }
98
+
99
+ # Markers that mean the CLI died on advisor/pairing/infra — not a code gap to repair.
100
+ ADVISOR_INFRA_FAILURE_MARKERS = (
101
+ "does not support the advisor",
102
+ "advisor is not supported",
103
+ "advisor model",
104
+ "--advisor",
105
+ "advisor tool",
106
+ "availablemodels",
107
+ "available models",
108
+ )
109
+
110
+
111
+ def advisor_steering_text(*, repair: bool = False) -> str:
112
+ """Attach-gated Claude-only system steering (timing + treat-advice + optional repair)."""
113
+ parts = [ADVISOR_CODING_TIMING, ADVISOR_TREAT_ADVICE]
114
+ if repair:
115
+ parts.append(REPAIR_ADVISOR_AUTHORITY)
116
+ return "\n\n".join(parts)
117
+
118
+
119
+ def advisor_user_cost_trim() -> str:
120
+ """Optional soft ~80-word ask for the advisor, placed in the user prompt when attached."""
121
+ return ADVISOR_COST_TRIM_USER
122
+
123
+
124
+ def normalize_model_family(model: str | None) -> str | None:
125
+ """Map a model alias or id to a coarse family name, or None if unrecognized."""
126
+ if not model:
127
+ return None
128
+ text = model.strip().lower()
129
+ if not text:
130
+ return None
131
+ # Prefer explicit family tokens (claude-opus-4-7, opus, sonnet-4-6, …).
132
+ for family in ("fable", "opus", "sonnet", "haiku"):
133
+ if family in text:
134
+ return family
135
+ return None
136
+
137
+
138
+ def advisor_pairing_ok(main_model: str | None, advisor_model: str | None) -> tuple[bool, str | None]:
139
+ """Soft check that *advisor* is not clearly weaker / invalid for *main*.
140
+
141
+ Returns ``(ok, skip_reason)``. Unknown families are allowed through (Claude
142
+ validates at launch). Clear mismatches return ``ok=False`` with a reason so
143
+ callers can skip ``--advisor`` instead of hard-exiting.
144
+
145
+ This is intentionally coarse — not Claude Code's full versioned matrix
146
+ (e.g. Sonnet 5 vs 4.6, Opus 4.6 vs 4.7+). Claude Code enforces those at launch.
147
+ """
148
+ advisor = (advisor_model or "").strip()
149
+ if not advisor:
150
+ return False, "no advisor_model configured"
151
+
152
+ adv_family = normalize_model_family(advisor)
153
+ if adv_family == "haiku":
154
+ return False, "haiku cannot act as an advisor"
155
+
156
+ main_family = normalize_model_family(main_model)
157
+ if main_family is None or adv_family is None:
158
+ # Unknown / unset main: attach and let Claude validate.
159
+ return True, None
160
+
161
+ main_tier = _FAMILY_TIER.get(main_family)
162
+ adv_tier = _FAMILY_TIER.get(adv_family)
163
+ if main_tier is None or adv_tier is None:
164
+ return True, None
165
+
166
+ # Fable main only accepts Fable advisor (docs).
167
+ if main_family == "fable" and adv_family != "fable":
168
+ return False, f"fable main rejects {adv_family} advisor"
169
+
170
+ if adv_tier < main_tier:
171
+ return False, f"{adv_family} advisor is weaker than {main_family} main"
172
+
173
+ return True, None
174
+
175
+
176
+ def _env_flag_set(source: Mapping[str, str], key: str) -> bool:
177
+ value = str(source.get(key) or "").strip().lower()
178
+ return value in {"1", "true", "yes", "on"}
179
+
180
+
181
+ def advisor_provider_unsupported(env: dict[str, str] | None = None) -> str | None:
182
+ """Return the cloud-provider env key if advisor cannot run (Bedrock/Vertex/Foundry)."""
183
+ source = env if env is not None else os.environ
184
+ for key in NON_ANTHROPIC_ADVISOR_ENVS:
185
+ if _env_flag_set(source, key):
186
+ return key
187
+ return None
188
+
189
+
190
+ def parse_claude_version(text: str) -> tuple[int, ...] | None:
191
+ """Extract a dotted version tuple from ``claude --version`` output."""
192
+ match = re.search(r"(\d+)\.(\d+)\.(\d+)", text or "")
193
+ if not match:
194
+ return None
195
+ return tuple(int(part) for part in match.groups())
196
+
197
+
198
+ @lru_cache(maxsize=1)
199
+ def probe_claude_version() -> tuple[int, ...] | None:
200
+ """Best-effort ``claude --version`` parse; None when CLI missing or unreadable."""
201
+ executable = shutil.which("claude")
202
+ if not executable:
203
+ return None
204
+ try:
205
+ result = subprocess.run(
206
+ [executable, "--version"],
207
+ capture_output=True,
208
+ text=True,
209
+ timeout=5,
210
+ check=False,
211
+ )
212
+ except (OSError, subprocess.TimeoutExpired):
213
+ return None
214
+ return parse_claude_version(f"{result.stdout or ''}\n{result.stderr or ''}")
215
+
216
+
217
+ @lru_cache(maxsize=1)
218
+ def claude_supports_append_system_prompt() -> bool:
219
+ """True when ``claude -h`` lists ``--append-system-prompt``."""
220
+ executable = shutil.which("claude")
221
+ if not executable:
222
+ return False
223
+ try:
224
+ result = subprocess.run(
225
+ [executable, "-h"],
226
+ capture_output=True,
227
+ text=True,
228
+ timeout=5,
229
+ check=False,
230
+ )
231
+ except (OSError, subprocess.TimeoutExpired):
232
+ return False
233
+ hay = f"{result.stdout or ''}\n{result.stderr or ''}"
234
+ return "--append-system-prompt" in hay
235
+
236
+
237
+ def advisor_disable_env_set(env: dict[str, str] | None = None) -> bool:
238
+ source = env if env is not None else os.environ
239
+ return _env_flag_set(source, DISABLE_ADVISOR_ENV)
240
+
241
+
242
+ def warn_advisor_preflight(
243
+ *,
244
+ env: dict[str, str] | None = None,
245
+ main_model: str | None = None,
246
+ advisor_model: str | None = None,
247
+ ) -> list[str]:
248
+ """Soft warnings for version floor / disable-env / Fable floor. Never blocks attaching."""
249
+ warnings: list[str] = []
250
+ if advisor_disable_env_set(env):
251
+ warnings.append(
252
+ f"{DISABLE_ADVISOR_ENV} is set; Claude Code will ignore --advisor / advisorModel."
253
+ )
254
+ version = probe_claude_version()
255
+ if version is not None and version < MIN_ADVISOR_CLAUDE_VERSION:
256
+ floor = ".".join(str(part) for part in MIN_ADVISOR_CLAUDE_VERSION)
257
+ current = ".".join(str(part) for part in version)
258
+ warnings.append(
259
+ f"Claude Code {current} is below the advisor floor ({floor}); "
260
+ "upgrade with `claude update` or advisor calls may fail."
261
+ )
262
+ uses_fable = any(
263
+ normalize_model_family(m) == "fable" for m in (main_model, advisor_model)
264
+ )
265
+ if uses_fable and version is not None and version < MIN_FABLE_ADVISOR_CLAUDE_VERSION:
266
+ floor = ".".join(str(part) for part in MIN_FABLE_ADVISOR_CLAUDE_VERSION)
267
+ current = ".".join(str(part) for part in version)
268
+ warnings.append(
269
+ f"Claude Code {current} is below the Fable advisor floor ({floor}); "
270
+ "Fable main/advisor pairs need Claude Code ≥ 2.1.170."
271
+ )
272
+ return warnings
273
+
274
+
275
+ def strip_duplicate_advisor_args(extra_args: list[str]) -> list[str]:
276
+ """Remove ``--advisor`` / ``--advisor=…`` from profile extras when DevCouncil attached."""
277
+ out: list[str] = []
278
+ skip_next = False
279
+ for part in extra_args:
280
+ if skip_next:
281
+ skip_next = False
282
+ continue
283
+ if part == "--advisor":
284
+ skip_next = True
285
+ continue
286
+ if part.startswith("--advisor="):
287
+ continue
288
+ out.append(part)
289
+ return out
290
+
291
+
292
+ AdvisorAttachDecision = Literal["attach", "skip"]
293
+
294
+
295
+ def decide_advisor_attach(
296
+ *,
297
+ main_model: str | None,
298
+ advisor_model: str | None,
299
+ env: dict[str, str] | None = None,
300
+ ) -> tuple[AdvisorAttachDecision, str | None, str | None]:
301
+ """Return ``(decision, resolved_advisor, reason)`` for CLI/SDK wiring."""
302
+ resolved = (advisor_model or "").strip() or None
303
+ if not resolved:
304
+ return "skip", None, None
305
+ provider = advisor_provider_unsupported(env)
306
+ if provider:
307
+ return (
308
+ "skip",
309
+ resolved,
310
+ f"advisor requires Anthropic API; {provider} is set (soft-skip)",
311
+ )
312
+ ok, reason = advisor_pairing_ok(main_model, resolved)
313
+ if not ok:
314
+ return "skip", resolved, reason
315
+ return "attach", resolved, None
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import logging
3
4
  import shutil
4
5
  from dataclasses import dataclass, field
5
6
  from pathlib import Path
@@ -7,6 +8,8 @@ from typing import Any
7
8
 
8
9
  from devcouncil.app.config import CliAgentProfileConfig, load_config
9
10
 
11
+ logger = logging.getLogger(__name__)
12
+
10
13
 
11
14
  AGENT_ALIASES = {
12
15
  "agy": "antigravity",
@@ -24,6 +27,11 @@ AGENT_ALIASES = {
24
27
  "oz-cli": "warp",
25
28
  "cursor-agent": "cursor",
26
29
  "cursor-cli": "cursor",
30
+ "grok-build": "grok",
31
+ "grok-cli": "grok",
32
+ "gork": "grok",
33
+ "gork-build": "grok",
34
+ "xai-grok": "grok",
27
35
  "copilot-cli": "copilot",
28
36
  "github-copilot": "copilot",
29
37
  "gh-copilot": "copilot",
@@ -46,6 +54,7 @@ BUILTIN_AGENT_NAMES = {
46
54
  "copilot",
47
55
  "crush",
48
56
  "cursor",
57
+ "grok",
49
58
  "gemini",
50
59
  "goose",
51
60
  "opencode",
@@ -56,6 +65,12 @@ BUILTIN_AGENT_NAMES = {
56
65
  BUILTIN_CODING_EXECUTOR_NAMES = tuple(sorted(BUILTIN_AGENT_NAMES))
57
66
  VALID_INPUT_MODES = {"stdin", "argument", "prompt-file"}
58
67
 
68
+ DEPRECATED_CODING_CLIS = frozenset({"gemini"})
69
+ GEMINI_DEPRECATION_MESSAGE = (
70
+ "Gemini CLI is deprecated (Google replaced it with Antigravity CLI). "
71
+ "Use: dev integrate antigravity --apply"
72
+ )
73
+
59
74
 
60
75
  @dataclass(frozen=True)
61
76
  class CliAgentSpec:
@@ -107,10 +122,10 @@ def resolve_cursor_agent_executable() -> str | None:
107
122
 
108
123
 
109
124
  CODING_CLI_PROBE_ORDER: tuple[str, ...] = (
110
- "codex",
111
- "gemini",
112
125
  "claude",
126
+ "codex",
113
127
  "cursor",
128
+ "grok",
114
129
  "opencode",
115
130
  "antigravity",
116
131
  "warp",
@@ -132,14 +147,17 @@ class CodingCliIntegrationInfo:
132
147
  hooks: bool
133
148
  launcher_shim: bool
134
149
  notes: str
150
+ blocking_hooks: bool = False
151
+ deprecated: bool = False
135
152
 
136
153
  @property
137
154
  def enforcement(self) -> str:
138
- """Honest containment posture: ``pre-action`` when a native hook blocks
139
- unauthorized writes/commands before they happen, else ``verify-only`` — for
140
- which "forbidden changes" are caught only post-hoc by verification, not blocked
141
- up front. Surfaced so users aren't misled into assuming hard containment."""
142
- return "pre-action" if self.hooks else "verify-only"
155
+ """Honest containment posture for the client's current native API."""
156
+ if self.blocking_hooks:
157
+ return "pre-action"
158
+ if self.hooks:
159
+ return "advisory+verify"
160
+ return "verify-only"
143
161
 
144
162
 
145
163
  # Tier 1 = headless executor + verify; tier 2 = MCP companion; hooks = native pre-tool policy.
@@ -148,10 +166,19 @@ CODING_CLI_INTEGRATION_INFO: dict[str, CodingCliIntegrationInfo] = {
148
166
  "codex", "Codex CLI", 1, True, True, True, True, "dev integrate codex --apply"
149
167
  ),
150
168
  "gemini": CodingCliIntegrationInfo(
151
- "gemini", "Gemini CLI", 1, True, True, True, True, "dev integrate gemini --apply"
169
+ "gemini",
170
+ "Gemini CLI (deprecated)",
171
+ 1,
172
+ True,
173
+ True,
174
+ True,
175
+ True,
176
+ "Deprecated — migrate to: dev integrate antigravity --apply",
177
+ blocking_hooks=True,
178
+ deprecated=True,
152
179
  ),
153
180
  "claude": CodingCliIntegrationInfo(
154
- "claude", "Claude Code", 1, True, True, True, True, "dev integrate claude --apply"
181
+ "claude", "Claude Code", 1, True, True, True, True, "dev integrate claude --apply", blocking_hooks=True
155
182
  ),
156
183
  "cursor": CodingCliIntegrationInfo(
157
184
  "cursor",
@@ -161,10 +188,22 @@ CODING_CLI_INTEGRATION_INFO: dict[str, CodingCliIntegrationInfo] = {
161
188
  True,
162
189
  True,
163
190
  True,
164
- "dev integrate cursor --apply; cursor-agent for headless",
191
+ "dev integrate cursor --apply; agent/cursor-agent for headless",
192
+ blocking_hooks=True,
193
+ ),
194
+ "grok": CodingCliIntegrationInfo(
195
+ "grok",
196
+ "Grok Build",
197
+ 1,
198
+ True,
199
+ True,
200
+ True,
201
+ True,
202
+ "dev integrate grok --apply",
203
+ blocking_hooks=True,
165
204
  ),
166
205
  "opencode": CodingCliIntegrationInfo(
167
- "opencode", "OpenCode", 1, True, True, True, True, "dev integrate opencode --apply"
206
+ "opencode", "OpenCode", 1, True, True, True, True, "dev integrate opencode --apply", blocking_hooks=True
168
207
  ),
169
208
  "antigravity": CodingCliIntegrationInfo(
170
209
  "antigravity",
@@ -204,7 +243,8 @@ CODING_CLI_VERSION_COMMANDS: dict[str, tuple[tuple[str, ...], ...]] = {
204
243
  "codex": (("codex", "--version"),),
205
244
  "gemini": (("gemini", "--version"),),
206
245
  "claude": (("claude", "--version"),),
207
- "cursor": (("cursor-agent", "--version"), ("cursor", "--version")),
246
+ "cursor": (("cursor-agent", "--version"), ("agent", "--version"), ("cursor", "--version")),
247
+ "grok": (("grok", "--version"),),
208
248
  "opencode": (("opencode", "--version"),),
209
249
  "antigravity": (("agy", "--version"),),
210
250
  "warp": (("oz", "--version"),),
@@ -243,10 +283,36 @@ def resolve_coding_cli_probe_order(project_root: Path) -> tuple[str, ...]:
243
283
  if configured:
244
284
  return tuple(normalize_agent_name(name) for name in configured)
245
285
  except Exception:
286
+ logger.debug("Could not load coding_cli_probe_order from config", exc_info=True)
246
287
  pass
247
288
  return CODING_CLI_PROBE_ORDER
248
289
 
249
290
 
291
+ def _configured_probe_order(project_root: Path) -> tuple[str, ...] | None:
292
+ try:
293
+ configured = load_config(project_root).execution.coding_cli_probe_order
294
+ if configured:
295
+ return tuple(normalize_agent_name(name) for name in configured)
296
+ except Exception:
297
+ logger.debug("Could not load coding_cli_probe_order from config", exc_info=True)
298
+ return None
299
+
300
+
301
+ def _deprecated_allowed_in_probe(
302
+ project_root: Path,
303
+ client: str,
304
+ *,
305
+ probe_order: tuple[str, ...] | None,
306
+ ) -> bool:
307
+ normalized = normalize_agent_name(client)
308
+ if normalized not in DEPRECATED_CODING_CLIS:
309
+ return False
310
+ if probe_order is not None:
311
+ return normalized in {normalize_agent_name(name) for name in probe_order}
312
+ configured = _configured_probe_order(project_root)
313
+ return configured is not None and normalized in configured
314
+
315
+
250
316
  def detect_available_coding_cli(
251
317
  project_root: Path,
252
318
  probe_order: tuple[str, ...] | None = None,
@@ -256,8 +322,14 @@ def detect_available_coding_cli(
256
322
  # table and re-parse config.yaml). Resolution below mirrors
257
323
  # resolve_coding_cli_executable() exactly, just against the prebuilt table.
258
324
  specs = load_cli_agent_specs(project_root)
259
- for client in probe_order or resolve_coding_cli_probe_order(project_root):
325
+ order = probe_order or resolve_coding_cli_probe_order(project_root)
326
+ for client in order:
260
327
  normalized = normalize_agent_name(client)
328
+ if (
329
+ normalized in DEPRECATED_CODING_CLIS
330
+ and not _deprecated_allowed_in_probe(project_root, normalized, probe_order=probe_order)
331
+ ):
332
+ continue
261
333
  if normalized == "cursor":
262
334
  if resolve_cursor_agent_executable():
263
335
  return client
@@ -291,6 +363,27 @@ def resolve_automated_executor(
291
363
  return detected or configured
292
364
 
293
365
 
366
+ RUN_EXECUTOR_SPECIAL = (
367
+ "manual",
368
+ "mini",
369
+ "openhands",
370
+ "claude-sdk",
371
+ "native",
372
+ "native-preview",
373
+ )
374
+
375
+
376
+ def list_run_executor_names(project_root: Path) -> list[str]:
377
+ """Executor names accepted by ``dev run --executor``."""
378
+ custom = {
379
+ name
380
+ for name, spec in load_cli_agent_specs(project_root).items()
381
+ if not spec.built_in
382
+ }
383
+ names = set(BUILTIN_AGENT_NAMES) | set(RUN_EXECUTOR_SPECIAL) | custom
384
+ return sorted(names)
385
+
386
+
294
387
  def is_reserved_agent_name(name: str) -> bool:
295
388
  return normalize_agent_name(name) in BUILTIN_AGENT_NAMES
296
389
 
@@ -319,11 +412,11 @@ def builtin_agent_specs(project_root: Path) -> dict[str, CliAgentSpec]:
319
412
  ),
320
413
  "claude": CliAgentSpec(
321
414
  name="claude",
322
- # `-p` runs headless; `--permission-mode acceptEdits` lets Claude Code
323
- # actually apply file edits without an interactive approval prompt
324
- # (otherwise it only describes the change and the diff stays empty).
415
+ # `-p` runs headless. Permission mode is profile-driven: yolo/auto adds
416
+ # `--permission-mode acceptEdits`; default/prod stay gated (no blanket
417
+ # auto-apply baked into the base spec).
325
418
  command="claude",
326
- args=["-p", "--permission-mode", "acceptEdits"],
419
+ args=["-p"],
327
420
  display_name="Claude Code",
328
421
  kind="coding-cli",
329
422
  supports_mcp=True,
@@ -395,6 +488,21 @@ def builtin_agent_specs(project_root: Path) -> dict[str, CliAgentSpec]:
395
488
  supports_handoff=True,
396
489
  built_in=True,
397
490
  ),
491
+ "grok": CliAgentSpec(
492
+ name="grok",
493
+ command="grok",
494
+ args=[
495
+ "-p",
496
+ "Read and execute the DevCouncil task prompt at {prompt_file}.",
497
+ ],
498
+ input_mode="prompt-file",
499
+ display_name="Grok Build",
500
+ kind="coding-cli",
501
+ supports_mcp=True,
502
+ supports_diff_review=True,
503
+ supports_handoff=True,
504
+ built_in=True,
505
+ ),
398
506
  "aider": CliAgentSpec(
399
507
  name="aider",
400
508
  command="aider",