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,81 @@
1
+ """Child-process entrypoint for exact Python call/return tracing."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import runpy
8
+ import sys
9
+ import time
10
+ from collections import Counter
11
+ from pathlib import Path
12
+ from types import FrameType
13
+ from typing import Any
14
+
15
+
16
+ def _frame_id(frame: FrameType, root: Path) -> str | None:
17
+ path = Path(frame.f_code.co_filename).resolve()
18
+ try:
19
+ rel = path.relative_to(root).as_posix()
20
+ except ValueError:
21
+ return None
22
+ return f"{rel}::{frame.f_code.co_qualname}"
23
+
24
+
25
+ def run_trace(root: Path, output: Path, script: Path, argv: list[str]) -> int:
26
+ root = root.resolve()
27
+ calls: Counter[tuple[str, str]] = Counter()
28
+ first_seen: dict[tuple[str, str], float] = {}
29
+ last_seen: dict[tuple[str, str], float] = {}
30
+
31
+ def profile(frame: FrameType, event: str, _arg: Any):
32
+ if event != "call" or frame.f_back is None:
33
+ return profile
34
+ caller = _frame_id(frame.f_back, root)
35
+ callee = _frame_id(frame, root)
36
+ if caller and callee:
37
+ key = (caller, callee)
38
+ now = time.time()
39
+ calls[key] += 1
40
+ first_seen.setdefault(key, now)
41
+ last_seen[key] = now
42
+ return profile
43
+
44
+ previous_argv = sys.argv
45
+ sys.argv = [str(script), *argv]
46
+ sys.setprofile(profile)
47
+ exit_code = 0
48
+ try:
49
+ runpy.run_path(str(script), run_name="__main__")
50
+ except SystemExit as exc:
51
+ exit_code = int(exc.code) if isinstance(exc.code, int) else 1
52
+ finally:
53
+ sys.setprofile(None)
54
+ sys.argv = previous_argv
55
+ output.parent.mkdir(parents=True, exist_ok=True)
56
+ with output.open("w", encoding="utf-8") as handle:
57
+ for (source, target), count in sorted(calls.items()):
58
+ handle.write(json.dumps({
59
+ "source": source,
60
+ "target": target,
61
+ "kind": "observed_calls",
62
+ "count": count,
63
+ "first_seen": first_seen[(source, target)],
64
+ "last_seen": last_seen[(source, target)],
65
+ "evidence": {"provider": "python-sys-setprofile"},
66
+ }, sort_keys=True) + "\n")
67
+ return exit_code
68
+
69
+
70
+ def main() -> int:
71
+ parser = argparse.ArgumentParser()
72
+ parser.add_argument("--root", type=Path, required=True)
73
+ parser.add_argument("--output", type=Path, required=True)
74
+ parser.add_argument("script", type=Path)
75
+ parser.add_argument("args", nargs=argparse.REMAINDER)
76
+ ns = parser.parse_args()
77
+ return run_trace(ns.root, ns.output, ns.script, list(ns.args))
78
+
79
+
80
+ if __name__ == "__main__":
81
+ raise SystemExit(main())
@@ -0,0 +1,238 @@
1
+ """Persistent in-process debug-session manager used by MCP and embedded CLI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ import threading
7
+ import uuid
8
+ from dataclasses import dataclass, field
9
+ from pathlib import Path
10
+ from typing import Any, Sequence, cast
11
+
12
+ from devcouncil.codeintel.debug.discovery import adapter_by_command
13
+ from devcouncil.codeintel.debug.fingerprint import build_fingerprint, executable_hash, source_fingerprint
14
+ from devcouncil.codeintel.debug.protocol import DAPClient
15
+ from devcouncil.codeintel.service import get_codeintel_service
16
+
17
+ _SECRET = re.compile(r"(?i)(token|secret|password|api[_-]?key|authorization)\s*[:=]\s*([^,;\s]+)")
18
+
19
+
20
+ def redact_value(value: str, *, limit: int = 4096) -> str:
21
+ redacted = _SECRET.sub(lambda match: f"{match.group(1)}=[REDACTED]", value)
22
+ return redacted if len(redacted) <= limit else redacted[:limit] + "…[truncated]"
23
+
24
+
25
+ @dataclass
26
+ class DebugSession:
27
+ id: str
28
+ root: Path
29
+ client: DAPClient
30
+ adapter_command: tuple[str, ...]
31
+ adapter_id: str
32
+ adapter_version: str
33
+ adapter_requests: tuple[str, ...]
34
+ request: str
35
+ capabilities: dict[str, Any]
36
+ source_fingerprint: str
37
+ build_fingerprint: str
38
+ executable_hash: str
39
+ breakpoints: dict[str, list[int]] = field(default_factory=dict)
40
+
41
+ def as_dict(self) -> dict[str, Any]:
42
+ return {
43
+ "id": self.id,
44
+ "project_root": str(self.root),
45
+ "adapter_command": list(self.adapter_command),
46
+ "adapter_id": self.adapter_id,
47
+ "adapter_version": self.adapter_version,
48
+ "adapter_requests": list(self.adapter_requests),
49
+ "request": self.request,
50
+ "capabilities": self.capabilities,
51
+ "source_fingerprint": self.source_fingerprint,
52
+ "build_fingerprint": self.build_fingerprint,
53
+ "executable_hash": self.executable_hash,
54
+ "breakpoints": self.breakpoints,
55
+ }
56
+
57
+
58
+ class DebugSessionManager:
59
+ def __init__(self):
60
+ self._sessions: dict[str, DebugSession] = {}
61
+ self._lock = threading.Lock()
62
+
63
+ def start(
64
+ self,
65
+ root: Path,
66
+ adapter_command: Sequence[str],
67
+ *,
68
+ request: str,
69
+ arguments: dict[str, Any],
70
+ initial_breakpoints: dict[str, Sequence[int]] | None = None,
71
+ timeout: float = 30.0,
72
+ ) -> DebugSession:
73
+ if request not in {"launch", "attach"}:
74
+ raise ValueError("request must be launch or attach")
75
+ root = root.expanduser().resolve()
76
+ adapter = adapter_by_command(list(adapter_command))
77
+ client = DAPClient.start_stdio(adapter_command, cwd=str(root))
78
+ try:
79
+ capabilities = client.initialize(adapter_id=adapter.id if adapter else "devcouncil")
80
+ pending = client.begin_request(request, arguments)
81
+ client.wait_event("initialized", timeout=timeout, fail_on=pending)
82
+ normalized_breakpoints: dict[str, list[int]] = {}
83
+ for source, lines in (initial_breakpoints or {}).items():
84
+ normalized = str(Path(source).expanduser().resolve())
85
+ normalized_breakpoints[normalized] = [int(line) for line in lines]
86
+ client.request("setBreakpoints", {
87
+ "source": {"path": normalized},
88
+ "breakpoints": [{"line": line} for line in normalized_breakpoints[normalized]],
89
+ "sourceModified": False,
90
+ }, timeout=timeout)
91
+ client.request("configurationDone", timeout=timeout)
92
+ client.wait_response(pending, timeout=timeout)
93
+ except Exception:
94
+ client.close()
95
+ raise
96
+ executable = adapter_command[0]
97
+ session = DebugSession(
98
+ id=uuid.uuid4().hex,
99
+ root=root,
100
+ client=client,
101
+ adapter_command=tuple(adapter_command),
102
+ adapter_id=adapter.id if adapter else "custom",
103
+ adapter_version=adapter.version if adapter else "",
104
+ adapter_requests=adapter.requests if adapter else (request,),
105
+ request=request,
106
+ capabilities=capabilities,
107
+ source_fingerprint=source_fingerprint(root),
108
+ build_fingerprint=build_fingerprint(root, executable),
109
+ executable_hash=executable_hash(executable),
110
+ breakpoints=normalized_breakpoints,
111
+ )
112
+ with self._lock:
113
+ self._sessions[session.id] = session
114
+ return session
115
+
116
+ def get(self, session_id: str) -> DebugSession:
117
+ with self._lock:
118
+ session = self._sessions.get(session_id)
119
+ if session is None:
120
+ raise KeyError(f"unknown debug session: {session_id}")
121
+ return session
122
+
123
+ def list(self) -> list[dict[str, Any]]:
124
+ with self._lock:
125
+ return [session.as_dict() for session in self._sessions.values()]
126
+
127
+ def set_breakpoints(self, session_id: str, source: str, lines: Sequence[int]) -> dict[str, Any]:
128
+ session = self.get(session_id)
129
+ normalized = str(Path(source).expanduser().resolve())
130
+ body = session.client.request("setBreakpoints", {
131
+ "source": {"path": normalized},
132
+ "breakpoints": [{"line": int(line)} for line in lines],
133
+ "sourceModified": False,
134
+ })
135
+ session.breakpoints[normalized] = [int(line) for line in lines]
136
+ return body
137
+
138
+ def control(self, session_id: str, action: str, *, thread_id: int | None = None) -> dict[str, Any]:
139
+ session = self.get(session_id)
140
+ commands = {
141
+ "continue": "continue",
142
+ "pause": "pause",
143
+ "next": "next",
144
+ "stepIn": "stepIn",
145
+ "stepOut": "stepOut",
146
+ }
147
+ command = commands.get(action)
148
+ if command is None:
149
+ raise ValueError(f"unsupported debug action: {action}")
150
+ arguments = {"threadId": thread_id} if thread_id is not None else {}
151
+ return session.client.request(command, arguments)
152
+
153
+ def inspect(self, session_id: str, operation: str, arguments: dict[str, Any]) -> dict[str, Any]:
154
+ session = self.get(session_id)
155
+ allowed = {"threads", "stackTrace", "scopes", "variables", "source", "disassemble"}
156
+ if operation not in allowed:
157
+ raise ValueError(f"unsupported inspect operation: {operation}")
158
+ body = session.client.request(operation, arguments)
159
+ return cast(dict[str, Any], self._redact(body))
160
+
161
+ def evaluate(
162
+ self,
163
+ session_id: str,
164
+ expression: str,
165
+ *,
166
+ frame_id: int | None,
167
+ allow_side_effects: bool,
168
+ ) -> dict[str, Any]:
169
+ session = self.get(session_id)
170
+ return cast(dict[str, Any], self._redact(session.client.evaluate(
171
+ expression,
172
+ frame_id=frame_id,
173
+ allow_side_effects=allow_side_effects,
174
+ )))
175
+
176
+ def capture_stack(self, session_id: str, *, thread_id: int) -> dict[str, Any]:
177
+ session = self.get(session_id)
178
+ body = session.client.request("stackTrace", {"threadId": thread_id})
179
+ frames = list(body.get("stackFrames") or [])
180
+ observations = []
181
+ for caller, callee in zip(reversed(frames[1:]), reversed(frames[:-1])):
182
+ observations.append({
183
+ "source": self._frame_id(caller),
184
+ "target": self._frame_id(callee),
185
+ "kind": "sampled_stack",
186
+ "evidence": {"thread_id": thread_id, "provider": "dap-stack"},
187
+ })
188
+ store = get_codeintel_service(session.root).store
189
+ runtime_id = store.start_runtime_session(
190
+ provider="dap-stack",
191
+ source_fingerprint=session.source_fingerprint,
192
+ build_fingerprint=session.build_fingerprint,
193
+ executable_hash=session.executable_hash,
194
+ metadata={
195
+ "debug_session": session.id,
196
+ "thread_id": thread_id,
197
+ "adapter_id": session.adapter_id,
198
+ "adapter_version": session.adapter_version,
199
+ "adapter_requests": list(session.adapter_requests),
200
+ "adapter_capabilities": session.capabilities,
201
+ },
202
+ )
203
+ store.add_runtime_observations(runtime_id, observations)
204
+ store.end_runtime_session(runtime_id)
205
+ return {"session_id": runtime_id, "observations": observations, "frames": self._redact(frames)}
206
+
207
+ def stop(self, session_id: str, *, terminate_debuggee: bool = True) -> None:
208
+ session = self.get(session_id)
209
+ try:
210
+ session.client.request("disconnect", {"terminateDebuggee": terminate_debuggee}, timeout=5.0)
211
+ except Exception:
212
+ pass
213
+ session.client.close()
214
+ with self._lock:
215
+ self._sessions.pop(session_id, None)
216
+
217
+ @classmethod
218
+ def _redact(cls, value: Any) -> Any:
219
+ if isinstance(value, str):
220
+ return redact_value(value)
221
+ if isinstance(value, list):
222
+ return [cls._redact(item) for item in value]
223
+ if isinstance(value, dict):
224
+ return {key: cls._redact(item) for key, item in value.items()}
225
+ return value
226
+
227
+ @staticmethod
228
+ def _frame_id(frame: dict[str, Any]) -> str:
229
+ source = frame.get("source") or {}
230
+ path = source.get("path") or source.get("name") or "<unknown>"
231
+ return f"{path}:{frame.get('name', '<anonymous>')}:{frame.get('line', 0)}"
232
+
233
+
234
+ _MANAGER = DebugSessionManager()
235
+
236
+
237
+ def get_debug_manager() -> DebugSessionManager:
238
+ return _MANAGER
@@ -0,0 +1,201 @@
1
+ """Runtime trace providers independent from debugger control."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import shutil
7
+ import subprocess
8
+ import sys
9
+ import uuid
10
+ from pathlib import Path
11
+ from typing import Any, Sequence
12
+
13
+ from devcouncil.codeintel.debug.fingerprint import build_fingerprint, executable_hash, source_fingerprint
14
+ from devcouncil.codeintel.service import get_codeintel_service
15
+
16
+
17
+ class PythonTraceProvider:
18
+ def __init__(self, root: Path):
19
+ self.root = root.expanduser().resolve()
20
+
21
+ def run(self, script: Path, args: Sequence[str] = (), *, timeout: float = 300.0) -> dict[str, Any]:
22
+ script = script if script.is_absolute() else self.root / script
23
+ trace_path = self.root / ".devcouncil" / "codeintel" / "traces" / f"python-{uuid.uuid4().hex}.jsonl"
24
+ source = source_fingerprint(self.root)
25
+ build = build_fingerprint(self.root, sys.executable)
26
+ store = get_codeintel_service(self.root).store
27
+ session_id = store.start_runtime_session(
28
+ provider="python-sys-setprofile",
29
+ source_fingerprint=source,
30
+ build_fingerprint=build,
31
+ executable_hash=executable_hash(sys.executable),
32
+ metadata={"script": str(script), "args": list(args), "trace_path": str(trace_path)},
33
+ )
34
+ try:
35
+ proc = subprocess.run(
36
+ [
37
+ sys.executable,
38
+ "-m",
39
+ "devcouncil.codeintel.debug.python_trace_runner",
40
+ "--root",
41
+ str(self.root),
42
+ "--output",
43
+ str(trace_path),
44
+ str(script),
45
+ *args,
46
+ ],
47
+ cwd=self.root,
48
+ check=False,
49
+ timeout=timeout,
50
+ )
51
+ observations = load_jsonl_observations(trace_path)
52
+ store.add_runtime_observations(session_id, observations)
53
+ finally:
54
+ store.end_runtime_session(session_id)
55
+ return {
56
+ "session_id": session_id,
57
+ "provider": "python-sys-setprofile",
58
+ "exit_code": proc.returncode,
59
+ "observation_count": len(observations),
60
+ "trace_path": str(trace_path),
61
+ "source_fingerprint": source,
62
+ "build_fingerprint": build,
63
+ }
64
+
65
+
66
+ class NodeCpuProfileProvider:
67
+ def __init__(self, root: Path, executable: str | Path | None = None):
68
+ self.root = root.expanduser().resolve()
69
+ resolved = str(executable) if executable else shutil.which("node")
70
+ if not resolved:
71
+ raise FileNotFoundError("Node.js executable was not found")
72
+ self.executable = str(Path(resolved).expanduser().resolve())
73
+
74
+ def run(self, script: Path, args: Sequence[str] = (), *, timeout: float = 300.0) -> dict[str, Any]:
75
+ script = script if script.is_absolute() else self.root / script
76
+ trace_dir = self.root / ".devcouncil" / "codeintel" / "traces"
77
+ trace_dir.mkdir(parents=True, exist_ok=True)
78
+ trace_path = trace_dir / f"node-{uuid.uuid4().hex}.cpuprofile"
79
+ source = source_fingerprint(self.root)
80
+ build = build_fingerprint(self.root, self.executable)
81
+ executable_fingerprint = executable_hash(self.executable)
82
+ store = get_codeintel_service(self.root).store
83
+ session_id = store.start_runtime_session(
84
+ provider="node-cpu-profile",
85
+ source_fingerprint=source,
86
+ build_fingerprint=build,
87
+ executable_hash=executable_fingerprint,
88
+ metadata={
89
+ "script": str(script),
90
+ "args": list(args),
91
+ "trace_path": str(trace_path),
92
+ "runtime_version": _runtime_version(self.executable),
93
+ },
94
+ )
95
+ try:
96
+ proc = subprocess.run(
97
+ [
98
+ self.executable,
99
+ "--cpu-prof",
100
+ f"--cpu-prof-dir={trace_dir}",
101
+ f"--cpu-prof-name={trace_path.name}",
102
+ str(script),
103
+ *args,
104
+ ],
105
+ cwd=self.root,
106
+ check=False,
107
+ timeout=timeout,
108
+ )
109
+ observations = load_node_cpu_profile(trace_path)
110
+ store.add_runtime_observations(session_id, observations)
111
+ finally:
112
+ store.end_runtime_session(session_id)
113
+ return {
114
+ "session_id": session_id,
115
+ "provider": "node-cpu-profile",
116
+ "exit_code": proc.returncode,
117
+ "observation_count": len(observations),
118
+ "trace_path": str(trace_path),
119
+ "source_fingerprint": source,
120
+ "build_fingerprint": build,
121
+ "executable_hash": executable_fingerprint,
122
+ }
123
+
124
+
125
+ def load_jsonl_observations(path: Path) -> list[dict[str, Any]]:
126
+ rows: list[dict[str, Any]] = []
127
+ for line in path.read_text(encoding="utf-8").splitlines() if path.is_file() else []:
128
+ try:
129
+ value = json.loads(line)
130
+ except json.JSONDecodeError:
131
+ continue
132
+ if isinstance(value, dict) and value.get("source") and value.get("target"):
133
+ rows.append(value)
134
+ return rows
135
+
136
+
137
+ def load_node_cpu_profile(path: Path) -> list[dict[str, Any]]:
138
+ """Convert a Chrome/Node CPU profile's parent/child nodes to sampled edges."""
139
+
140
+ data = json.loads(path.read_text(encoding="utf-8"))
141
+ nodes = {int(node["id"]): node for node in data.get("nodes") or [] if isinstance(node, dict) and "id" in node}
142
+ samples = [int(value) for value in data.get("samples") or []]
143
+ counts: dict[int, int] = {}
144
+ for sample in samples:
145
+ counts[sample] = counts.get(sample, 0) + 1
146
+ rows: list[dict[str, Any]] = []
147
+ for parent_id, parent in nodes.items():
148
+ parent_name = _profile_frame(parent)
149
+ for child_id in parent.get("children") or []:
150
+ child = nodes.get(int(child_id))
151
+ if child is None:
152
+ continue
153
+ rows.append({
154
+ "source": parent_name,
155
+ "target": _profile_frame(child),
156
+ "kind": "sampled_calls",
157
+ "count": max(1, counts.get(int(child_id), 0)),
158
+ "evidence": {"provider": "node-cpu-profile", "parent_id": parent_id, "child_id": child_id},
159
+ })
160
+ return rows
161
+
162
+
163
+ def import_runtime_trace(root: Path, path: Path, *, provider: str = "jsonl") -> dict[str, Any]:
164
+ root = root.expanduser().resolve()
165
+ path = path.expanduser().resolve()
166
+ observations = load_node_cpu_profile(path) if path.suffix == ".cpuprofile" else load_jsonl_observations(path)
167
+ store = get_codeintel_service(root).store
168
+ source = source_fingerprint(root)
169
+ session_id = store.start_runtime_session(
170
+ provider=provider,
171
+ source_fingerprint=source,
172
+ build_fingerprint=build_fingerprint(root),
173
+ metadata={"imported_path": str(path)},
174
+ )
175
+ store.add_runtime_observations(session_id, observations)
176
+ store.end_runtime_session(session_id)
177
+ return {"session_id": session_id, "provider": provider, "observation_count": len(observations)}
178
+
179
+
180
+ def _profile_frame(node: dict[str, Any]) -> str:
181
+ frame = node.get("callFrame") or {}
182
+ url = frame.get("url") or "<unknown>"
183
+ function = frame.get("functionName") or "<anonymous>"
184
+ line = int(frame.get("lineNumber", -1)) + 1
185
+ return f"{url}:{function}:{line}"
186
+
187
+
188
+ def _runtime_version(executable: str) -> str:
189
+ try:
190
+ completed = subprocess.run(
191
+ [executable, "--version"],
192
+ stdin=subprocess.DEVNULL,
193
+ stdout=subprocess.PIPE,
194
+ stderr=subprocess.STDOUT,
195
+ check=False,
196
+ timeout=2.0,
197
+ text=True,
198
+ )
199
+ except (OSError, subprocess.TimeoutExpired):
200
+ return ""
201
+ return completed.stdout.strip().splitlines()[0][:128] if completed.stdout.strip() else ""
@@ -0,0 +1,17 @@
1
+ """Language manifests and Tree-sitter grammar diagnostics."""
2
+
3
+ from devcouncil.codeintel.languages.registry import (
4
+ LANGUAGE_SPECS,
5
+ LanguageSpec,
6
+ detect_language,
7
+ grammar_status,
8
+ supported_languages,
9
+ )
10
+
11
+ __all__ = [
12
+ "LANGUAGE_SPECS",
13
+ "LanguageSpec",
14
+ "detect_language",
15
+ "grammar_status",
16
+ "supported_languages",
17
+ ]