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
@@ -0,0 +1,355 @@
1
+ """Repo-map artifact writers (JSON + agent guides) — indexing leaf, no CLI import.
2
+
3
+ CLI ``dev map``, init, wiki remap, and map_refresh all call into this module so
4
+ indexing/verification do not import ``cli.commands.map``.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+ import sqlite3
11
+ from dataclasses import dataclass
12
+ from pathlib import Path
13
+
14
+ from rich.console import Console
15
+
16
+ from devcouncil.indexing.repo_mapper import RepoMap, RepoMapper
17
+ from devcouncil.indexing.graph.schema import CodeGraph
18
+ from devcouncil.integrations.code_review_graph import CodeReviewGraphAdapter
19
+ from devcouncil.utils.json_persist import write_model_json
20
+
21
+ logger = logging.getLogger(__name__)
22
+ status_console = Console(stderr=True)
23
+
24
+ AGENT_GUIDE_MARKER = "<!-- Managed by dev map: keep this file in sync with .devcouncil/repo_map.json. -->"
25
+
26
+
27
+ def _stamp_existing_map_degraded(output: Path, *, reason: str) -> None:
28
+ """Mark an on-disk repo map degraded without rewriting fingerprints as fresh."""
29
+ if not output.is_file():
30
+ return
31
+ try:
32
+ from devcouncil.utils.json_persist import read_json
33
+
34
+ repo_map = RepoMap.model_validate(read_json(output))
35
+ repo_map.graph_degraded = True
36
+ repo_map.graph_degraded_reason = reason[:500]
37
+ write_model_json(output, repo_map)
38
+ except Exception:
39
+ logger.debug("failed to stamp graph_degraded on partial map", exc_info=True)
40
+
41
+
42
+ @dataclass
43
+ class GraphRefreshResult:
44
+ repo_map: RepoMap
45
+ graph: CodeGraph | None
46
+ generation: int | None
47
+ mode: str
48
+ degraded: bool = False
49
+ reason: str = ""
50
+ # True when SQLite committed but code_graph.json export was skipped (size cap).
51
+ # Not fail-closed for sync/watch — doctor/export health owns this signal.
52
+ compatibility_export_degraded: bool = False
53
+
54
+
55
+ def _important_surfaces(repo_map: RepoMap) -> list[str]:
56
+ """Derive the 'important surfaces' list from the computed map."""
57
+ lines: list[str] = []
58
+ for index, subsystem in enumerate(repo_map.subsystems[:6], start=1):
59
+ lines.append(f"{index}. `{subsystem.area}/` — {subsystem.summary}")
60
+ if not lines:
61
+ for index, path in enumerate(repo_map.important_files[:6], start=1):
62
+ lines.append(f"{index}. `{path}`")
63
+ return lines or ["1. See `.devcouncil/repo_map.json` for the file index."]
64
+
65
+
66
+ def _wiki_index_rel(repo_root: Path) -> str | None:
67
+ """Relative path to the codebase wiki index, when a generated wiki exists."""
68
+ from devcouncil.knowledge.wiki import wiki_dir_for
69
+
70
+ index = wiki_dir_for(repo_root) / "index.md"
71
+ if not index.is_file():
72
+ return None
73
+ try:
74
+ return index.relative_to(repo_root).as_posix()
75
+ except ValueError:
76
+ return str(index)
77
+
78
+
79
+ def agent_guide_text(repo_map_path: Path, repo_root: Path, repo_map: RepoMap) -> str:
80
+ wiki_index = _wiki_index_rel(repo_root)
81
+ wiki_lines = (
82
+ [
83
+ "",
84
+ f"Codebase wiki: `{wiki_index}` — agent-facing subsystem docs (OKF bundle). "
85
+ "Read the relevant subsystem page before working in it; refresh with `dev wiki update`.",
86
+ ]
87
+ if wiki_index
88
+ else []
89
+ )
90
+ return "\n".join(
91
+ [
92
+ AGENT_GUIDE_MARKER,
93
+ "",
94
+ "# Agent Workspace Guide",
95
+ "",
96
+ "Use `.devcouncil/repo_map.json` as the primary file index for this workspace.",
97
+ f"Repo map: `{repo_map_path.relative_to(repo_root).as_posix() if repo_map_path.is_relative_to(repo_root) else repo_map_path}`",
98
+ "Code graph: `.devcouncil/graph/code_graph.json` (symbol-level; query with `dev graph`).",
99
+ *wiki_lines,
100
+ "",
101
+ "Workflow for agents:",
102
+ "1. Open `.devcouncil/repo_map.json` before guessing at file locations.",
103
+ "2. Use the `files` list to resolve module ownership and nearby siblings.",
104
+ "3. Use `subsystems` for subsystem-level navigation.",
105
+ "4. In `subsystems`, use `entry_points` + `critical_files` for entry points and starting context.",
106
+ "5. Use `role_files` in `subsystems` for subsystem role buckets (entry, runtime, policy, adapters, etc.).",
107
+ "6. Use `neighbors` and `handoff_paths` in `subsystems` to follow cross-subsystem flow.",
108
+ "7. Prefer `dev graph dead --confidence extracted` + file greps for dead code. "
109
+ "Treat `inferred` as unconfirmed. Prefer `unwired_candidates` / "
110
+ "`dead_symbol_candidates` over `unreachable_files` (static BFS is often "
111
+ "noisy for routers / dynamic imports / JSX). If `entry_roots` are empty / "
112
+ "`liveness_unreachable_unreliable`, ignore `unreachable_files` and mass inferred dead. "
113
+ "Check `unwired_candidates` / `dead_symbol_candidates` before creating new modules — "
114
+ "wire what you create into a real caller.",
115
+ "8. Use `dev graph query <name>` / `dev graph trace <a> <b>` / `dev graph dead` "
116
+ "for symbol callers, paths, and dead-code tiers; `dev graph html` for the visualizer. "
117
+ "SQLite (`.devcouncil/codeintel/index.sqlite`) is canonical — prefer "
118
+ "`dev graph` commands when `code_graph.json` is missing or a size-capped stub.",
119
+ "9. Run `dev map` (or `dev map --watch` / `dev graph watch`) after large refactors.",
120
+ "",
121
+ "Important surfaces:",
122
+ *_important_surfaces(repo_map),
123
+ "",
124
+ "If the map and source disagree, trust the source and regenerate the map.",
125
+ ]
126
+ )
127
+
128
+
129
+ def write_agent_guides(repo_root: Path, repo_map_path: Path, repo_map: RepoMap) -> None:
130
+ for filename in ("AGENTS.md", "CLAUDE.md"):
131
+ path = repo_root / filename
132
+ existing: str | None = None
133
+ if path.exists():
134
+ try:
135
+ existing = path.read_text(encoding="utf-8", errors="replace")
136
+ except OSError:
137
+ continue
138
+ if AGENT_GUIDE_MARKER not in existing:
139
+ continue
140
+ text = agent_guide_text(repo_map_path, repo_root, repo_map) + "\n"
141
+ # Skip rewrite when unchanged so content_fingerprint (size+mtime) stays stable
142
+ # across consecutive identical `dev map` runs.
143
+ if existing == text:
144
+ continue
145
+ path.write_text(text, encoding="utf-8")
146
+
147
+
148
+ def generate_map_artifacts(
149
+ root: Path,
150
+ output: Path,
151
+ goal: str = "",
152
+ *,
153
+ scan_dependencies: bool = False,
154
+ liveness: bool = True,
155
+ lsp_refs: bool = False,
156
+ quiet: bool = False,
157
+ graph=None, # noqa: ANN001
158
+ paths: list[str] | None = None,
159
+ ) -> RepoMap:
160
+ """Build the repo map and write repo_map.json + agent guides (no LLM, no re-init).
161
+
162
+ Assumes ``.devcouncil/`` already exists. Shared by the ``dev map`` command and
163
+ by project initialization so a freshly set-up repo is immediately navigable.
164
+ ``scan_dependencies`` is opt-in (off for init and default mapping) because it can
165
+ shell out to dependency auditors.
166
+ ``lsp_refs`` opts into live LSP confirmation of dead-symbol candidates.
167
+ ``quiet`` suppresses stderr status lines (for ``dev status --json`` auto-init).
168
+ """
169
+ return refresh_map_artifacts(
170
+ root,
171
+ output,
172
+ goal,
173
+ scan_dependencies=scan_dependencies,
174
+ liveness=liveness,
175
+ lsp_refs=lsp_refs,
176
+ quiet=quiet,
177
+ graph=graph,
178
+ paths=paths,
179
+ ).repo_map
180
+
181
+
182
+ def refresh_map_artifacts(
183
+ root: Path,
184
+ output: Path,
185
+ goal: str = "",
186
+ *,
187
+ scan_dependencies: bool = False,
188
+ liveness: bool = True,
189
+ lsp_refs: bool = False,
190
+ quiet: bool = False,
191
+ graph=None, # noqa: ANN001
192
+ paths: list[str] | None = None,
193
+ ) -> GraphRefreshResult:
194
+ """Refresh graph and map once, falling back to a lean map on graph failure."""
195
+ import time
196
+
197
+ from devcouncil.codeintel import get_codeintel_service
198
+ from devcouncil.codeintel.build_control import (
199
+ GraphBuildBusy,
200
+ graph_build_session,
201
+ run_isolated_full_build,
202
+ )
203
+
204
+ t0 = time.perf_counter()
205
+ root = root.expanduser().resolve()
206
+ mode = "incremental" if paths is not None else "full"
207
+ degraded = False
208
+ reason = ""
209
+ compatibility_export_degraded = False
210
+ with graph_build_session(root):
211
+ # A damaged index.sqlite fails scattered reads all over the build; move
212
+ # it aside now (we hold the writer lease) so this run rebuilds cleanly
213
+ # instead of stamping a lean/degraded map forever.
214
+ service = get_codeintel_service(root)
215
+ try:
216
+ store_corrupt = service.store.exists() and service.status()["state"] == "corrupt"
217
+ except Exception:
218
+ store_corrupt = False
219
+ if store_corrupt and service.store.quarantine():
220
+ logger.warning(
221
+ "codeintel store was corrupt; quarantined to %s — running a full rebuild",
222
+ service.store.path.name + ".corrupt",
223
+ )
224
+ paths = None
225
+ mode = "full"
226
+ if graph is None:
227
+ try:
228
+ if paths is not None and get_codeintel_service(root).load() is not None:
229
+ from devcouncil.codeintel.sync.incremental import sync_affected_paths
230
+
231
+ graph = sync_affected_paths(
232
+ get_codeintel_service(root),
233
+ paths,
234
+ liveness=liveness,
235
+ )
236
+ # Export-only skips are recorded on build status; SQLite is healthy.
237
+ try:
238
+ from devcouncil.codeintel.build_control import read_build_status
239
+
240
+ status = read_build_status(root)
241
+ if status.compatibility_export == "degraded":
242
+ compatibility_export_degraded = True
243
+ reason = status.degraded_reason or reason
244
+ except Exception:
245
+ logger.debug("build status read after incremental failed", exc_info=True)
246
+ else:
247
+ isolated = run_isolated_full_build(
248
+ root,
249
+ changed_paths=None if paths is None else set(paths),
250
+ liveness=liveness,
251
+ )
252
+ graph = isolated.graph
253
+ mode = "full"
254
+ # Compatibility JSON size limits must not stamp graph_degraded
255
+ # (that would force perpetual --if-stale rebuilds while SQLite is fine).
256
+ if isolated.status.compatibility_export == "degraded":
257
+ compatibility_export_degraded = True
258
+ reason = isolated.status.degraded_reason or reason
259
+ elif isolated.status.state == "degraded" and graph is None:
260
+ degraded = True
261
+ reason = isolated.status.degraded_reason or reason
262
+ except GraphBuildBusy:
263
+ # Lease contention after/during a concurrent writer must not stamp a
264
+ # lean/degraded map over a healthy SQLite generation (retry storm).
265
+ raise
266
+ except Exception as exc: # noqa: BLE001
267
+ logger.warning("graph refresh failed; writing lean repo map", exc_info=True)
268
+ degraded = True
269
+ reason = f"{type(exc).__name__}: {exc}"
270
+ mode = "lean"
271
+
272
+ # True lean/unavailable only — never export-only.
273
+ if graph is None:
274
+ degraded = True
275
+ if mode != "lean":
276
+ mode = "lean"
277
+
278
+ mapper = RepoMapper(root)
279
+ if graph is not None:
280
+ mapper._prebuilt_code_graph = graph # type: ignore[attr-defined]
281
+ else:
282
+ mapper._skip_code_graph_build = True # type: ignore[attr-defined]
283
+ try:
284
+ repo_map = mapper.map_repo(
285
+ goal,
286
+ scan_dependencies=scan_dependencies,
287
+ liveness=liveness,
288
+ lsp_refs=lsp_refs,
289
+ )
290
+ except Exception as exc:
291
+ # Graph may already be committed (incremental/full) while the map
292
+ # rebuild failed — fail closed so --if-stale / verify keep retrying.
293
+ logger.warning("repo map rebuild failed after graph commit", exc_info=True)
294
+ _stamp_existing_map_degraded(
295
+ output if output.is_absolute() else root / output,
296
+ reason=f"{type(exc).__name__}: {exc}",
297
+ )
298
+ raise
299
+ # Stamp degraded handshake before first write so agents never see a
300
+ # fingerprint-fresh lean map without graph_degraded=True.
301
+ repo_map.graph_degraded = bool(degraded)
302
+ repo_map.graph_degraded_reason = reason if degraded else ""
303
+ elapsed = time.perf_counter() - t0
304
+ if not quiet:
305
+ status_console.print(f"[dim]map completed in {elapsed:.2f}s[/dim]")
306
+ if degraded:
307
+ status_console.print(
308
+ f"[yellow]Graph degraded; wrote {mode} map: {reason}[/yellow]"
309
+ )
310
+ elif compatibility_export_degraded:
311
+ status_console.print(
312
+ f"[yellow]Compatibility export degraded (SQLite ok; "
313
+ f"stub/compact JSON may still exist): {reason}[/yellow]"
314
+ )
315
+ graph_context = CodeReviewGraphAdapter(root).get_context()
316
+ output = output if output.is_absolute() else root / output
317
+ output.parent.mkdir(parents=True, exist_ok=True)
318
+ write_model_json(output, repo_map)
319
+ write_agent_guides(root, output, repo_map)
320
+ # Agent guides can add/change tracked files after the fingerprint was taken;
321
+ # re-stamp so the on-disk map is not immediately stale for checkout/verify.
322
+ mapper = RepoMapper(root)
323
+ try:
324
+ files = mapper.get_git_files()
325
+ repo_map.generated_head = mapper._git_head()
326
+ repo_map.indexed_hash = mapper._files_fingerprint(files)
327
+ repo_map.content_fingerprint = mapper._content_fingerprint(files)
328
+ repo_map.graph_degraded = bool(degraded)
329
+ repo_map.graph_degraded_reason = reason if degraded else ""
330
+ write_model_json(output, repo_map)
331
+ except Exception:
332
+ logger.debug("Failed to re-stamp map fingerprints after agent guides", exc_info=True)
333
+ if graph_context.available:
334
+ graph_output = output.with_name("code_review_graph_context.json")
335
+ write_model_json(graph_output, graph_context)
336
+ if not quiet:
337
+ status_console.print(
338
+ f"[green]Wrote code-review-graph context to {graph_output}[/green]"
339
+ )
340
+ try:
341
+ generation = get_codeintel_service(root).store.current_generation()
342
+ except sqlite3.DatabaseError:
343
+ # A store that turned unreadable mid-run must not crash the refresh
344
+ # result — the map artifacts above were already written.
345
+ logger.warning("could not read store generation after refresh", exc_info=True)
346
+ generation = None
347
+ return GraphRefreshResult(
348
+ repo_map=repo_map,
349
+ graph=graph,
350
+ generation=generation,
351
+ mode=mode,
352
+ degraded=degraded,
353
+ reason=reason,
354
+ compatibility_export_degraded=compatibility_export_degraded,
355
+ )
@@ -0,0 +1,141 @@
1
+ """Best-effort refresh of ``.devcouncil/repo_map.json`` when fingerprints drift."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from pathlib import Path
7
+
8
+ from devcouncil.utils.json_persist import read_json, write_model_json
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ def refresh_stale_map_if_needed(
14
+ project_root: Path,
15
+ *,
16
+ on_checkout: bool = True,
17
+ on_verify: bool = True,
18
+ ) -> bool:
19
+ """Regenerate ``repo_map.json`` when its tracked-content fingerprint is stale.
20
+
21
+ Honors ``execution.refresh_stale_map_on_checkout`` and
22
+ ``execution.refresh_stale_map_on_verify`` (both default on). Pass
23
+ ``on_checkout=False`` or ``on_verify=False`` to skip a context. Never raises;
24
+ returns True when a remap ran successfully.
25
+ """
26
+ try:
27
+ from devcouncil.app.config import load_config
28
+
29
+ try:
30
+ cfg = load_config(project_root)
31
+ checkout_enabled = bool(
32
+ getattr(cfg.execution, "refresh_stale_map_on_checkout", True)
33
+ )
34
+ verify_enabled = bool(
35
+ getattr(cfg.execution, "refresh_stale_map_on_verify", True)
36
+ )
37
+ except Exception:
38
+ checkout_enabled = True
39
+ verify_enabled = True
40
+
41
+ if on_checkout and not on_verify:
42
+ enabled = checkout_enabled
43
+ elif on_verify and not on_checkout:
44
+ enabled = verify_enabled
45
+ else:
46
+ enabled = checkout_enabled or verify_enabled
47
+ if not enabled:
48
+ return False
49
+
50
+ map_path = project_root / ".devcouncil" / "repo_map.json"
51
+ if not map_path.is_file():
52
+ data: dict = {}
53
+ else:
54
+ loaded = read_json(map_path)
55
+ data = loaded if isinstance(loaded, dict) else {}
56
+
57
+ from devcouncil.indexing.repo_mapper import RepoMapper
58
+
59
+ mapper = RepoMapper(project_root)
60
+ if map_path.is_file() and not mapper.map_is_stale(data):
61
+ return False
62
+
63
+ from devcouncil.indexing.map_artifacts import generate_map_artifacts
64
+
65
+ generate_map_artifacts(project_root, map_path, quiet=True)
66
+ return True
67
+ except Exception as exc:
68
+ # Concurrent MCP/watcher builds commonly hold the writer lease during
69
+ # checkout/verify. Defer quietly — map stays stale until the next
70
+ # successful refresh rather than failing the lease acquisition path.
71
+ try:
72
+ from devcouncil.codeintel.build_control import GraphBuildBusy
73
+ except Exception:
74
+ GraphBuildBusy = () # type: ignore[misc, assignment]
75
+ if isinstance(exc, GraphBuildBusy):
76
+ logger.info("map refresh deferred (writer lease busy): %s", exc)
77
+ return False
78
+ logger.warning("map refresh failed", exc_info=True)
79
+ return False
80
+
81
+
82
+ def refresh_map_for_paths(root, paths, *, liveness: bool = True):
83
+ """Re-export of graph.build.refresh_map_for_paths."""
84
+ from devcouncil.indexing.graph.build import refresh_map_for_paths as _refresh
85
+
86
+ return _refresh(root, paths, liveness=liveness)
87
+
88
+
89
+ def refresh_repo_map_from_graph(
90
+ root,
91
+ graph,
92
+ affected: set[str],
93
+ files: list[str],
94
+ *,
95
+ mapper=None,
96
+ ) -> None:
97
+ """Update repo-map compatibility fields from an already committed graph.
98
+
99
+ Incremental code-intelligence sync must not trigger a second repository-wide
100
+ graph build merely to refresh ``repo_map.json``. This function keeps the
101
+ compatibility artifact aligned using the graph and affected-file set that the
102
+ caller has already computed.
103
+ """
104
+ path = root / ".devcouncil" / "repo_map.json"
105
+ if not path.is_file():
106
+ return
107
+ try:
108
+ from devcouncil.indexing.repo_mapper import RepoMap, RepoMapper
109
+
110
+ repo_mapper = mapper or RepoMapper(root)
111
+ repo_map = RepoMap.model_validate(read_json(path))
112
+ by_path = {
113
+ entry.path: entry
114
+ for entry in repo_map.files
115
+ if entry.path not in affected
116
+ }
117
+ file_set = set(files)
118
+ for rel in affected:
119
+ if rel in file_set and (root / rel).is_file():
120
+ by_path[rel] = repo_mapper.describe_file(rel)
121
+ repo_map.files = [by_path[rel] for rel in sorted(by_path)]
122
+ repo_map.entry_roots = list(graph.entry_roots)
123
+ cap = repo_mapper._LIVENESS_CAP
124
+ repo_map.unwired_candidates = list(graph.unwired_candidates)[:cap]
125
+ repo_map.unreachable_files = list(graph.unreachable_files)[:cap]
126
+ repo_map.liveness_unreachable_unreliable = bool(
127
+ graph.meta.get("liveness_unreachable_unreliable")
128
+ )
129
+ repo_map.dead_symbol_candidates = list(
130
+ graph.meta.get("legacy_dead_symbol_candidates") or []
131
+ )[:cap]
132
+ # Do NOT stamp fingerprints or clear graph_degraded here. A later
133
+ # ``mapper.map_repo`` failure in ``refresh_map_artifacts`` would otherwise
134
+ # leave a fingerprint-fresh, non-degraded map while subsystems are stale.
135
+ # Full map writes own the freshness handshake.
136
+ write_model_json(path, repo_map)
137
+ except Exception:
138
+ logger.warning("incremental repo-map compatibility refresh failed", exc_info=True)
139
+
140
+ # Back-compat alias for plan expected tests.
141
+ refresh_repository_map = refresh_stale_map_if_needed