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,19 @@
1
+ """Native filesystem synchronization for code-intelligence generations."""
2
+
3
+ from devcouncil.codeintel.sync.coordinator import (
4
+ SyncCoordinator,
5
+ SyncState,
6
+ get_sync_coordinator,
7
+ stop_all_coordinators,
8
+ )
9
+ from devcouncil.codeintel.sync.incremental import sync_affected_paths
10
+ from devcouncil.codeintel.sync.scope import IndexScope
11
+
12
+ __all__ = [
13
+ "IndexScope",
14
+ "SyncCoordinator",
15
+ "SyncState",
16
+ "get_sync_coordinator",
17
+ "sync_affected_paths",
18
+ "stop_all_coordinators",
19
+ ]
@@ -0,0 +1,430 @@
1
+ """Debounced native watcher with reconciliation and a single writer."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import subprocess
7
+ import sys
8
+ import threading
9
+ import time
10
+ from dataclasses import asdict, dataclass, field
11
+ from pathlib import Path
12
+ from typing import Any, Callable
13
+
14
+ from devcouncil.codeintel.service import CodeIntelService, get_codeintel_service
15
+ from devcouncil.codeintel.sync.lease import WriterLease
16
+ from devcouncil.codeintel.sync.scope import IndexScope
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ def _fsevents_preflight(root: Path) -> bool:
22
+ """Probe FSEvents out of process because startup failure may abort Python.
23
+
24
+ Retries once: under heavy reconcile/build load the first probe can time out
25
+ even when FSEvents is healthy, which previously forced a permanent Kqueue
26
+ fallback for the process lifetime.
27
+ """
28
+ script = (
29
+ "import sys,time; "
30
+ "from watchdog.events import FileSystemEventHandler; "
31
+ "from watchdog.observers import Observer; "
32
+ "o=Observer(); o.schedule(FileSystemEventHandler(),sys.argv[1],recursive=True); "
33
+ "o.start(); time.sleep(0.35); "
34
+ "emitters=list(o.emitters); "
35
+ "ok=bool(emitters) and all(e.is_alive() for e in emitters); "
36
+ "o.stop(); o.join(2); raise SystemExit(0 if ok else 3)"
37
+ )
38
+ last_error = ""
39
+ for attempt in range(2):
40
+ try:
41
+ completed = subprocess.run(
42
+ [sys.executable, "-c", script, str(root)],
43
+ capture_output=True,
44
+ timeout=5.0,
45
+ check=False,
46
+ )
47
+ except subprocess.TimeoutExpired:
48
+ last_error = "timeout"
49
+ time.sleep(0.2)
50
+ continue
51
+ except OSError as exc:
52
+ last_error = str(exc)
53
+ break
54
+ if completed.returncode == 0:
55
+ return True
56
+ stderr = getattr(completed, "stderr", None) or b""
57
+ if isinstance(stderr, str):
58
+ stderr_text = stderr.strip()
59
+ else:
60
+ stderr_text = stderr.decode("utf-8", errors="replace").strip()
61
+ last_error = stderr_text or f"exit {completed.returncode}"
62
+ time.sleep(0.2)
63
+ if last_error:
64
+ logger.info("FSEvents preflight failed: %s", last_error)
65
+ return False
66
+
67
+
68
+ @dataclass
69
+ class SyncState:
70
+ state: str = "disabled"
71
+ backend: str = ""
72
+ backend_kind: str = ""
73
+ generation: int | None = None
74
+ pending: list[str] = field(default_factory=list)
75
+ last_sync_at: float | None = None
76
+ last_reconcile_at: float | None = None
77
+ degraded_reason: str = ""
78
+ last_error: str = ""
79
+ build_id: str = ""
80
+ build_state: str = "idle"
81
+ build_pid: int | None = None
82
+ build_phase: str = ""
83
+ build_completed: int = 0
84
+ build_total: int = 0
85
+ build_last_progress_at: float | None = None
86
+ generation_before: int | None = None
87
+ generation_after: int | None = None
88
+ compatibility_export: str = "unknown"
89
+
90
+ def as_dict(self) -> dict[str, object]:
91
+ return asdict(self)
92
+
93
+
94
+ class SyncCoordinator:
95
+ def __init__(
96
+ self,
97
+ service: CodeIntelService,
98
+ *,
99
+ debounce_seconds: float = 0.75,
100
+ reconcile_seconds: float = 60.0,
101
+ sync_callback: Callable[[list[str]], object] | None = None,
102
+ allow_polling_fallback: bool = True,
103
+ ):
104
+ self.service = service
105
+ self.root = service.project_root
106
+ self.scope = IndexScope(self.root)
107
+ self.debounce_seconds = max(0.1, min(60.0, debounce_seconds))
108
+ self.reconcile_seconds = max(1.0, reconcile_seconds)
109
+ self.sync_callback = sync_callback or self._default_sync
110
+ self.allow_polling_fallback = allow_polling_fallback
111
+ self._state = SyncState(generation=service.store.current_generation())
112
+ self._pending: set[str] = set()
113
+ self._pending_since: float | None = None
114
+ self._condition = threading.Condition()
115
+ self._stop = threading.Event()
116
+ self._observer: Any = None
117
+ self._worker: threading.Thread | None = None
118
+ self._sync_lock = threading.Lock()
119
+ self._lease = WriterLease(self.root / ".devcouncil" / "codeintel" / "writer.lock")
120
+
121
+ def start(self) -> SyncState:
122
+ if self._worker is not None and self._worker.is_alive():
123
+ return self.status()
124
+ self._stop.clear()
125
+ self._start_observer()
126
+ self._worker = threading.Thread(target=self._run, name="devcouncil-codeintel-sync", daemon=True)
127
+ self._worker.start()
128
+ return self.status()
129
+
130
+ def stop(self, *, timeout: float = 5.0) -> None:
131
+ self._stop.set()
132
+ with self._condition:
133
+ self._condition.notify_all()
134
+ observer = self._observer
135
+ if observer is not None:
136
+ observer.stop()
137
+ observer.join(timeout=timeout)
138
+ self._observer = None
139
+ if self._worker is not None:
140
+ self._worker.join(timeout=timeout)
141
+ if self._worker.is_alive():
142
+ self._state.state = "stopping"
143
+ self._state.degraded_reason = "sync worker did not stop before timeout"
144
+ return
145
+ self._worker = None
146
+ if self._sync_lock.locked():
147
+ self._state.state = "stopping"
148
+ self._state.degraded_reason = "an in-flight sync is still stopping"
149
+ return
150
+ self._state.state = "disabled"
151
+
152
+ def status(self) -> SyncState:
153
+ from devcouncil.codeintel.build_control import read_build_status
154
+
155
+ build = read_build_status(self.root)
156
+ with self._condition:
157
+ if (
158
+ self._worker is not None
159
+ and not self._worker.is_alive()
160
+ and self._stop.is_set()
161
+ ):
162
+ self._worker = None
163
+ if self._state.state == "stopping":
164
+ self._state.state = "disabled"
165
+ elif (
166
+ self._worker is None
167
+ and self._stop.is_set()
168
+ and not self._sync_lock.locked()
169
+ and self._state.state == "stopping"
170
+ ):
171
+ self._state.state = "disabled"
172
+ self._state.pending = sorted(self._pending)
173
+ self._state.generation = self.service.store.current_generation()
174
+ self._state.build_id = build.build_id
175
+ self._state.build_state = build.state
176
+ self._state.build_pid = build.pid
177
+ self._state.build_phase = build.phase
178
+ self._state.build_completed = build.completed
179
+ self._state.build_total = build.total
180
+ self._state.build_last_progress_at = build.last_progress_at
181
+ self._state.generation_before = build.generation_before
182
+ self._state.generation_after = build.generation_after
183
+ self._state.compatibility_export = build.compatibility_export
184
+ return SyncState(**asdict(self._state))
185
+
186
+ def mark_pending(self, path: str | Path) -> None:
187
+ try:
188
+ rel = self.scope.relative(path)
189
+ except ValueError:
190
+ return
191
+ # Deletions cannot pass an existence-based check, so extension + prefix
192
+ # filtering is enough for paths that were present in the committed index.
193
+ if not self.scope.includes(rel) and not self.service.store.has_indexed_path(rel):
194
+ return
195
+ with self._condition:
196
+ self._pending.add(rel)
197
+ self._pending_since = self._pending_since or time.monotonic()
198
+ self._state.state = "pending"
199
+ self._condition.notify_all()
200
+
201
+ def reconcile(self) -> list[str]:
202
+ indexed = self.service.store.file_metadata()
203
+ current: dict[str, tuple[int, int]] = {}
204
+ for rel in self.scope.files():
205
+ try:
206
+ stat = (self.root / rel).stat()
207
+ except OSError:
208
+ continue
209
+ current[rel] = (stat.st_size, stat.st_mtime_ns)
210
+ changed = {
211
+ rel
212
+ for rel, values in current.items()
213
+ if rel not in indexed or values != indexed[rel][:2]
214
+ }
215
+ changed.update(set(indexed) - set(current))
216
+ if changed:
217
+ # Every item came from IndexScope.files() or the persisted index, so
218
+ # repeating per-path git ignore probes through mark_pending() is both
219
+ # redundant and expensive.
220
+ with self._condition:
221
+ self._pending.update(changed)
222
+ self._pending_since = self._pending_since or time.monotonic()
223
+ self._state.state = "pending"
224
+ self._condition.notify_all()
225
+ self._state.last_reconcile_at = time.time()
226
+ return sorted(changed)
227
+
228
+ def sync_now(self, paths: list[str] | None = None) -> bool:
229
+ from devcouncil.codeintel.build_control import (
230
+ GraphBuildBusy,
231
+ _lease_timeouts,
232
+ graph_build_session,
233
+ )
234
+
235
+ with self._sync_lock:
236
+ if paths:
237
+ for path in paths:
238
+ self.mark_pending(path)
239
+ with self._condition:
240
+ batch = sorted(self._pending)
241
+ if not batch:
242
+ return True
243
+ try:
244
+ # Short bounded wait: another watcher/MCP writer often holds the
245
+ # lease briefly. Pending stays queued on timeout so the debounce
246
+ # loop retries instead of clearing work as healthy.
247
+ sync_timeout = _lease_timeouts(self.root)[1]
248
+ with graph_build_session(
249
+ self.root, lease=self._lease, timeout=sync_timeout
250
+ ):
251
+ self._state.state = "syncing"
252
+ self.sync_callback(batch)
253
+ with self._condition:
254
+ self._pending.difference_update(batch)
255
+ self._pending_since = None if not self._pending else time.monotonic()
256
+ if self._stop.is_set():
257
+ self._state.state = "stopping"
258
+ else:
259
+ # Drop transient lease-busy reasons so a later successful sync
260
+ # does not stay permanently ``degraded`` / ``read_only``.
261
+ if "writer lease" in (self._state.degraded_reason or "").lower():
262
+ self._state.degraded_reason = ""
263
+ self._state.state = (
264
+ "healthy" if not self._state.degraded_reason else "degraded"
265
+ )
266
+ self._state.last_sync_at = time.time()
267
+ self._state.last_error = ""
268
+ self._state.generation = self.service.store.current_generation()
269
+ return True
270
+ except GraphBuildBusy as exc:
271
+ # Keep pending queued; debounce/reconcile will retry after backoff.
272
+ self._state.state = "pending" if batch or self._pending else "read_only"
273
+ self._state.degraded_reason = str(exc)
274
+ return False
275
+ except Exception as exc:
276
+ logger.exception("code-intelligence sync failed")
277
+ self._state.state = "degraded"
278
+ self._state.last_error = f"{type(exc).__name__}: {exc}"
279
+ return False
280
+
281
+ def wait_until_fresh(self, *, timeout: float = 2.0) -> bool:
282
+ deadline = time.monotonic() + max(0.0, timeout)
283
+ while time.monotonic() < deadline:
284
+ if not self.status().pending:
285
+ return True
286
+ self.sync_now()
287
+ if not self.status().pending:
288
+ return True
289
+ time.sleep(0.05)
290
+ return not self.status().pending
291
+
292
+ def _run(self) -> None:
293
+ # Initial reconciliation is intentionally asynchronous. MCP clients must
294
+ # be able to complete initialize/list_tools immediately while the index is
295
+ # brought current in the background.
296
+ try:
297
+ self.reconcile()
298
+ except Exception as exc: # noqa: BLE001
299
+ logger.exception("initial code-intelligence reconciliation failed")
300
+ self._state.state = "degraded"
301
+ self._state.last_error = f"{type(exc).__name__}: {exc}"
302
+ next_reconcile = time.monotonic() + self.reconcile_seconds
303
+ while not self._stop.is_set():
304
+ with self._condition:
305
+ self._condition.wait(timeout=min(self.debounce_seconds, 1.0))
306
+ pending_since = self._pending_since
307
+ now = time.monotonic()
308
+ if pending_since is not None and now - pending_since >= self.debounce_seconds:
309
+ self.sync_now()
310
+ if now >= next_reconcile:
311
+ self.reconcile()
312
+ next_reconcile = now + self.reconcile_seconds
313
+
314
+ def _start_observer(self) -> None:
315
+ try:
316
+ from watchdog.events import FileSystemEventHandler
317
+ from watchdog.observers import Observer
318
+ from watchdog.observers.polling import PollingObserver
319
+
320
+ coordinator = self
321
+
322
+ class Handler(FileSystemEventHandler):
323
+ def on_any_event(self, event): # noqa: ANN001
324
+ if getattr(event, "is_directory", False):
325
+ return
326
+ source = getattr(event, "src_path", "")
327
+ destination = getattr(event, "dest_path", "")
328
+ if source:
329
+ coordinator.mark_pending(source)
330
+ if destination:
331
+ coordinator.mark_pending(destination)
332
+
333
+ candidates: list[tuple[str, Callable[[], Any]]] = [("native", Observer)]
334
+ try:
335
+ from watchdog.observers.kqueue import KqueueObserver
336
+
337
+ if KqueueObserver is not Observer:
338
+ candidates.append(("native-fallback", KqueueObserver))
339
+ except (ImportError, AttributeError):
340
+ pass
341
+
342
+ if self.allow_polling_fallback:
343
+ candidates.append((
344
+ "polling",
345
+ lambda: PollingObserver(timeout=max(1.0, self.debounce_seconds)),
346
+ ))
347
+
348
+ observer = None
349
+ failures: list[str] = []
350
+ backend = ""
351
+ selected_kind = ""
352
+ for kind, factory in candidates:
353
+ candidate = factory()
354
+ try:
355
+ if candidate.__class__.__name__ == "FSEventsObserver" and not _fsevents_preflight(self.root):
356
+ raise RuntimeError("FSEvents preflight failed in isolated process")
357
+ candidate.schedule(Handler(), str(self.root), recursive=True)
358
+ candidate.start()
359
+ emitters = list(getattr(candidate, "emitters", []))
360
+ if emitters:
361
+ # FSEvents can report startup failure only from its
362
+ # emitter thread, after Observer.start() has returned.
363
+ time.sleep(0.1)
364
+ if not all(emitter.is_alive() for emitter in emitters):
365
+ raise RuntimeError("observer emitter terminated during startup")
366
+ observer = candidate
367
+ backend = candidate.__class__.__name__
368
+ selected_kind = kind
369
+ break
370
+ except Exception as exc:
371
+ failures.append(f"{candidate.__class__.__name__}: {type(exc).__name__}: {exc}")
372
+ try:
373
+ candidate.stop()
374
+ candidate.join(timeout=1.0)
375
+ except Exception:
376
+ logger.debug("failed to stop rejected observer", exc_info=True)
377
+ if observer is None:
378
+ raise RuntimeError("; ".join(failures) or "no observer backend available")
379
+ if failures or selected_kind == "polling":
380
+ self._state.degraded_reason = "; ".join(failures) or "polling fallback selected"
381
+ logger.warning(
382
+ "filesystem observer degraded to %s: %s",
383
+ backend,
384
+ self._state.degraded_reason,
385
+ )
386
+ self._observer = observer
387
+ self._state.backend = backend
388
+ self._state.backend_kind = "polling" if selected_kind == "polling" else "native"
389
+ self._state.state = "degraded" if self._state.degraded_reason else "healthy"
390
+ except Exception as exc:
391
+ self._observer = None
392
+ self._state.state = "degraded"
393
+ self._state.degraded_reason = f"watcher unavailable: {type(exc).__name__}: {exc}"
394
+
395
+ def _default_sync(self, paths: list[str]) -> None:
396
+ # Keep graph watch and ``dev map --watch`` on the same path: incremental
397
+ # graph sync plus a full repo-map rebuild (subsystems/dependents/guides).
398
+ from devcouncil.indexing.graph.build import refresh_map_for_paths
399
+
400
+ refresh_map_for_paths(self.service.project_root, paths, liveness=True)
401
+
402
+
403
+ _COORDINATORS: dict[Path, SyncCoordinator] = {}
404
+ _COORDINATORS_LOCK = threading.Lock()
405
+
406
+
407
+ def get_sync_coordinator(root: Path, **kwargs: object) -> SyncCoordinator:
408
+ service = get_codeintel_service(root)
409
+ with _COORDINATORS_LOCK:
410
+ coordinator = _COORDINATORS.get(service.project_root)
411
+ if coordinator is None:
412
+ coordinator = SyncCoordinator(service, **kwargs) # type: ignore[arg-type]
413
+ _COORDINATORS[service.project_root] = coordinator
414
+ else:
415
+ for name, requested in kwargs.items():
416
+ current = getattr(coordinator, name)
417
+ if current != requested:
418
+ raise ValueError(
419
+ f"sync coordinator for {service.project_root} already uses "
420
+ f"a different {name}"
421
+ )
422
+ return coordinator
423
+
424
+
425
+ def stop_all_coordinators() -> None:
426
+ with _COORDINATORS_LOCK:
427
+ coordinators = list(_COORDINATORS.values())
428
+ _COORDINATORS.clear()
429
+ for coordinator in coordinators:
430
+ coordinator.stop()