devcouncil 0.3.1 → 0.4.1

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 (366) hide show
  1. package/README.md +92 -33
  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 +56 -40
  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 +964 -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 +281 -0
  219. package/src/devcouncil/integrations/mcp/handlers/graph.py +34 -0
  220. package/src/devcouncil/integrations/mcp/handlers/handoff.py +53 -0
  221. package/src/devcouncil/integrations/mcp/handlers/knowledge.py +28 -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 +87 -0
  229. package/src/devcouncil/integrations/mcp/handlers/read.py +103 -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 +69 -0
  233. package/src/devcouncil/integrations/mcp/handlers/scope.py +56 -0
  234. package/src/devcouncil/integrations/mcp/handlers/status.py +199 -0
  235. package/src/devcouncil/integrations/mcp/handlers/task.py +88 -0
  236. package/src/devcouncil/integrations/mcp/handlers/tool_specs.py +922 -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 +53 -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 +325 -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/summary.py +21 -3
  255. package/src/devcouncil/live/tasks.py +12 -3
  256. package/src/devcouncil/live/transcripts.py +69 -2
  257. package/src/devcouncil/llm/cache.py +5 -6
  258. package/src/devcouncil/llm/model_defaults.yaml +10 -10
  259. package/src/devcouncil/llm/provider.py +647 -73
  260. package/src/devcouncil/llm/router.py +271 -46
  261. package/src/devcouncil/llm/semantic_bridge.py +614 -0
  262. package/src/devcouncil/optimization/gepa_agent.py +6 -4
  263. package/src/devcouncil/optimization/skillopt.py +9 -5
  264. package/src/devcouncil/planning/arbiter_service.py +12 -3
  265. package/src/devcouncil/planning/correction_manifest.py +107 -10
  266. package/src/devcouncil/planning/plan_difficulty.py +69 -0
  267. package/src/devcouncil/planning/plan_service.py +5 -2
  268. package/src/devcouncil/planning/planned_files_reconcile.py +191 -0
  269. package/src/devcouncil/planning/prompt_enhancer_service.py +6 -5
  270. package/src/devcouncil/planning/question_conversion.py +56 -0
  271. package/src/devcouncil/planning/spec_service.py +9 -3
  272. package/src/devcouncil/repo/ci_scaffold.py +197 -1
  273. package/src/devcouncil/repo/gitignore.py +1 -2
  274. package/src/devcouncil/reporting/evidence_export.py +124 -0
  275. package/src/devcouncil/reporting/evidence_html.py +210 -0
  276. package/src/devcouncil/reporting/json_report.py +16 -12
  277. package/src/devcouncil/reporting/markdown_report.py +38 -9
  278. package/src/devcouncil/reporting/mcp_resources.py +142 -0
  279. package/src/devcouncil/reporting/report_builder.py +40 -4
  280. package/src/devcouncil/reporting/task_provenance.py +42 -0
  281. package/src/devcouncil/reporting/verdict.py +75 -0
  282. package/src/devcouncil/skills/library/README.md +1 -0
  283. package/src/devcouncil/skills/library/devcouncil-hero-loop.md +109 -0
  284. package/src/devcouncil/skills/library/devcouncil-verification.md +109 -0
  285. package/src/devcouncil/skills/library/devcouncil.md +93 -0
  286. package/src/devcouncil/skills/registry.py +43 -12
  287. package/src/devcouncil/storage/db.py +57 -11
  288. package/src/devcouncil/storage/models.py +6 -0
  289. package/src/devcouncil/storage/native.py +5 -3
  290. package/src/devcouncil/storage/repositories.py +50 -18
  291. package/src/devcouncil/telemetry/context.py +28 -0
  292. package/src/devcouncil/telemetry/cost.py +4 -5
  293. package/src/devcouncil/telemetry/logging_setup.py +78 -11
  294. package/src/devcouncil/telemetry/model_pricing.yaml +7 -0
  295. package/src/devcouncil/telemetry/stages.py +27 -2
  296. package/src/devcouncil/telemetry/tracker.py +50 -13
  297. package/src/devcouncil/ui/dashboard.py +120 -8
  298. package/src/devcouncil/utils/fsio.py +58 -0
  299. package/src/devcouncil/utils/git_snapshot.py +112 -0
  300. package/src/devcouncil/utils/json_persist.py +53 -0
  301. package/src/devcouncil/utils/proc.py +89 -0
  302. package/src/devcouncil/verification/acceptance_compiler.py +36 -13
  303. package/src/devcouncil/verification/ad_hoc_check.py +95 -3
  304. package/src/devcouncil/verification/checks/__init__.py +41 -0
  305. package/src/devcouncil/verification/checks/acceptance.py +39 -0
  306. package/src/devcouncil/verification/checks/acceptance_corpus.py +194 -0
  307. package/src/devcouncil/verification/checks/acceptance_evidence.py +239 -0
  308. package/src/devcouncil/verification/checks/command_evidence.py +148 -0
  309. package/src/devcouncil/verification/checks/compiled_acceptance.py +179 -0
  310. package/src/devcouncil/verification/checks/corpus_stale.py +124 -0
  311. package/src/devcouncil/verification/checks/corpus_verification.py +9 -0
  312. package/src/devcouncil/verification/checks/dead_symbols.py +360 -0
  313. package/src/devcouncil/verification/checks/diff_coverage_gate.py +101 -0
  314. package/src/devcouncil/verification/checks/doc_code_ref.py +79 -0
  315. package/src/devcouncil/verification/checks/liveness_ratchet.py +336 -0
  316. package/src/devcouncil/verification/checks/orphan_diff.py +104 -0
  317. package/src/devcouncil/verification/checks/planned_files.py +98 -0
  318. package/src/devcouncil/verification/checks/semantic_diff.py +241 -0
  319. package/src/devcouncil/verification/checks/stale_map.py +80 -0
  320. package/src/devcouncil/verification/checks/stub_scan.py +71 -0
  321. package/src/devcouncil/verification/checks/subsystem_boundary.py +103 -0
  322. package/src/devcouncil/verification/checks/wiring.py +216 -0
  323. package/src/devcouncil/verification/claims/__init__.py +23 -0
  324. package/src/devcouncil/verification/claims/checks.py +395 -0
  325. package/src/devcouncil/verification/claims/mapper.py +168 -0
  326. package/src/devcouncil/verification/claims/models.py +39 -0
  327. package/src/devcouncil/verification/claims/transcript.py +92 -0
  328. package/src/devcouncil/verification/claims/verdict.py +88 -0
  329. package/src/devcouncil/verification/command_evidence.py +170 -0
  330. package/src/devcouncil/verification/command_malformation.py +147 -0
  331. package/src/devcouncil/verification/command_runner.py +164 -0
  332. package/src/devcouncil/verification/coverage_measurement.py +292 -0
  333. package/src/devcouncil/verification/diff_coverage.py +151 -0
  334. package/src/devcouncil/verification/difficulty.py +296 -0
  335. package/src/devcouncil/verification/effort_heuristics.py +178 -0
  336. package/src/devcouncil/verification/gap_ids.py +63 -0
  337. package/src/devcouncil/verification/gate_cache.py +194 -0
  338. package/src/devcouncil/verification/gate_selector.py +344 -0
  339. package/src/devcouncil/verification/git_diff_fallback.py +272 -0
  340. package/src/devcouncil/verification/implementation_reviewer.py +13 -0
  341. package/src/devcouncil/verification/incremental_check.py +241 -0
  342. package/src/devcouncil/verification/next_actions.py +60 -1
  343. package/src/devcouncil/verification/rigor_analytics.py +130 -0
  344. package/src/devcouncil/verification/sandbox.py +38 -11
  345. package/src/devcouncil/verification/stub_detector.py +369 -0
  346. package/src/devcouncil/verification/test_resolver.py +67 -1
  347. package/src/devcouncil/verification/verifier.py +137 -1666
  348. package/src/devcouncil/verification/verify_orchestration.py +610 -0
  349. package/src/devcouncil/verification/verify_setup.py +176 -0
  350. package/src/devcouncil/verification/wiki_refresh.py +208 -0
  351. package/src/semantic_layer/__init__.py +58 -0
  352. package/src/semantic_layer/benchmark.py +75 -0
  353. package/src/semantic_layer/cache.py +290 -0
  354. package/src/semantic_layer/compressor.py +137 -0
  355. package/src/semantic_layer/config.py +75 -0
  356. package/src/semantic_layer/embeddings.py +69 -0
  357. package/src/semantic_layer/llm_backends.py +99 -0
  358. package/src/semantic_layer/pipeline.py +111 -0
  359. package/src/semantic_layer/router.py +128 -0
  360. package/src/semantic_layer/tuner.py +72 -0
  361. package/uv.lock +973 -9
  362. package/src/devcouncil/artifacts/migrations.py +0 -20
  363. package/src/devcouncil/artifacts/schemas.py +0 -23
  364. package/src/devcouncil/artifacts/serializer.py +0 -21
  365. package/src/devcouncil/integrations/gitnexus.py +0 -70
  366. package/src/devcouncil/integrations/graphify.py +0 -34
@@ -0,0 +1,114 @@
1
+ """Loopback JSON broker that keeps CLI debug sessions alive across invocations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import os
8
+ import secrets
9
+ import socketserver
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ from devcouncil.codeintel.debug.session import get_debug_manager
14
+
15
+
16
+ class BrokerServer(socketserver.ThreadingTCPServer):
17
+ allow_reuse_address = False
18
+ daemon_threads = True
19
+
20
+ def __init__(self, root: Path, token: str):
21
+ super().__init__(("127.0.0.1", 0), BrokerHandler)
22
+ self.root = root
23
+ self.token = token
24
+
25
+
26
+ class BrokerHandler(socketserver.StreamRequestHandler):
27
+ def handle(self) -> None:
28
+ try:
29
+ request = json.loads(self.rfile.readline(4 * 1024 * 1024).decode("utf-8"))
30
+ if request.get("token") != self.server.token: # type: ignore[attr-defined]
31
+ raise PermissionError("invalid debug broker token")
32
+ result = dispatch(self.server.root, str(request.get("action", "")), dict(request.get("params") or {})) # type: ignore[attr-defined]
33
+ response = {"ok": True, "result": result}
34
+ except Exception as exc:
35
+ response = {"ok": False, "error": f"{type(exc).__name__}: {exc}"}
36
+ self.wfile.write((json.dumps(response, default=str) + "\n").encode("utf-8"))
37
+
38
+
39
+ def dispatch(root: Path, action: str, params: dict[str, Any]) -> Any:
40
+ manager = get_debug_manager()
41
+ if action == "ping":
42
+ return {"pid": os.getpid(), "sessions": manager.list()}
43
+ if action == "start":
44
+ session = manager.start(
45
+ root,
46
+ [str(value) for value in params["adapter_command"]],
47
+ request=str(params.get("request", "launch")),
48
+ arguments=dict(params.get("arguments") or {}),
49
+ initial_breakpoints={
50
+ str(source): [int(line) for line in lines]
51
+ for source, lines in dict(params.get("initial_breakpoints") or {}).items()
52
+ },
53
+ timeout=float(params.get("timeout", 30.0)),
54
+ )
55
+ return session.as_dict()
56
+ if action == "list":
57
+ return manager.list()
58
+ if action == "breakpoints":
59
+ return manager.set_breakpoints(str(params["session_id"]), str(params["source"]), params.get("lines") or [])
60
+ if action == "control":
61
+ return manager.control(
62
+ str(params["session_id"]),
63
+ str(params["debug_action"]),
64
+ thread_id=int(params["thread_id"]) if params.get("thread_id") is not None else None,
65
+ )
66
+ if action == "inspect":
67
+ return manager.inspect(str(params["session_id"]), str(params["operation"]), dict(params.get("arguments") or {}))
68
+ if action == "evaluate":
69
+ return manager.evaluate(
70
+ str(params["session_id"]),
71
+ str(params["expression"]),
72
+ frame_id=int(params["frame_id"]) if params.get("frame_id") is not None else None,
73
+ allow_side_effects=bool(params.get("allow_side_effects")),
74
+ )
75
+ if action == "capture_stack":
76
+ return manager.capture_stack(str(params["session_id"]), thread_id=int(params["thread_id"]))
77
+ if action == "stop":
78
+ manager.stop(str(params["session_id"]), terminate_debuggee=bool(params.get("terminate_debuggee", True)))
79
+ return {"stopped": params["session_id"]}
80
+ raise ValueError(f"unknown broker action: {action}")
81
+
82
+
83
+ def main() -> int:
84
+ parser = argparse.ArgumentParser()
85
+ parser.add_argument("--root", type=Path, required=True)
86
+ ns = parser.parse_args()
87
+ root = ns.root.expanduser().resolve()
88
+ credential = secrets.token_urlsafe(32)
89
+ server = BrokerServer(root, credential)
90
+ state_path = root / ".devcouncil" / "codeintel" / "debug-broker.json"
91
+ state_path.parent.mkdir(parents=True, exist_ok=True)
92
+ state_path.write_text(json.dumps({
93
+ "pid": os.getpid(),
94
+ "host": "127.0.0.1",
95
+ "port": server.server_address[1],
96
+ "token": credential,
97
+ }), encoding="utf-8")
98
+ try:
99
+ state_path.chmod(0o600)
100
+ except OSError:
101
+ pass
102
+ try:
103
+ server.serve_forever(poll_interval=0.25)
104
+ finally:
105
+ server.server_close()
106
+ try:
107
+ state_path.unlink()
108
+ except OSError:
109
+ pass
110
+ return 0
111
+
112
+
113
+ if __name__ == "__main__":
114
+ raise SystemExit(main())
@@ -0,0 +1,61 @@
1
+ """Client/startup helper for the persistent local debug broker."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import socket
7
+ import subprocess
8
+ import sys
9
+ import time
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+
14
+ class DebugBrokerClient:
15
+ def __init__(self, root: Path):
16
+ self.root = root.expanduser().resolve()
17
+ self.state_path = self.root / ".devcouncil" / "codeintel" / "debug-broker.json"
18
+
19
+ def ensure_started(self, *, timeout: float = 5.0) -> None:
20
+ try:
21
+ self.call("ping", timeout=0.5)
22
+ return
23
+ except Exception:
24
+ pass
25
+ try:
26
+ self.state_path.unlink()
27
+ except OSError:
28
+ pass
29
+ subprocess.Popen(
30
+ [sys.executable, "-m", "devcouncil.codeintel.debug.broker", "--root", str(self.root)],
31
+ cwd=self.root,
32
+ stdin=subprocess.DEVNULL,
33
+ stdout=subprocess.DEVNULL,
34
+ stderr=subprocess.DEVNULL,
35
+ start_new_session=True,
36
+ )
37
+ deadline = time.monotonic() + timeout
38
+ while time.monotonic() < deadline:
39
+ try:
40
+ self.call("ping", timeout=0.5)
41
+ return
42
+ except Exception:
43
+ time.sleep(0.05)
44
+ raise TimeoutError("debug broker did not start")
45
+
46
+ def call(self, action: str, params: dict[str, Any] | None = None, *, timeout: float = 30.0) -> Any:
47
+ state = json.loads(self.state_path.read_text(encoding="utf-8"))
48
+ request = json.dumps({"token": state["token"], "action": action, "params": params or {}}) + "\n"
49
+ with socket.create_connection((str(state["host"]), int(state["port"])), timeout=timeout) as sock:
50
+ sock.sendall(request.encode("utf-8"))
51
+ sock.settimeout(timeout)
52
+ chunks = bytearray()
53
+ while not chunks.endswith(b"\n"):
54
+ chunk = sock.recv(65536)
55
+ if not chunk:
56
+ break
57
+ chunks.extend(chunk)
58
+ response = json.loads(bytes(chunks).decode("utf-8"))
59
+ if not response.get("ok"):
60
+ raise RuntimeError(str(response.get("error") or "debug broker request failed"))
61
+ return response.get("result")
@@ -0,0 +1,36 @@
1
+ """Explicit one-time debugger consent stored in project configuration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import yaml
8
+
9
+
10
+ def debug_consent_enabled(root: Path) -> bool:
11
+ try:
12
+ from devcouncil.app.config import load_config
13
+
14
+ return bool(load_config(root).code_intelligence.debug.auto_discover)
15
+ except (FileNotFoundError, ValueError):
16
+ return False
17
+
18
+
19
+ def set_debug_consent(root: Path, enabled: bool = True) -> Path:
20
+ path = root.expanduser().resolve() / ".devcouncil" / "config.yaml"
21
+ if not path.is_file():
22
+ raise FileNotFoundError(f"Config not found at {path}. Run 'dev init' first.")
23
+ raw = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
24
+ codeintel = raw.setdefault("code_intelligence", {})
25
+ debug = codeintel.setdefault("debug", {})
26
+ debug["auto_discover"] = bool(enabled)
27
+ path.write_text(yaml.safe_dump(raw, sort_keys=False), encoding="utf-8")
28
+ return path
29
+
30
+
31
+ def require_debug_consent(root: Path) -> None:
32
+ if not debug_consent_enabled(root):
33
+ raise PermissionError(
34
+ "Debugger discovery/execution is disabled. Run `dev debug discover --consent` "
35
+ "or set code_intelligence.debug.auto_discover: true."
36
+ )
@@ -0,0 +1,132 @@
1
+ """Side-effect-free discovery of installed Debug Adapter Protocol adapters."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib.util
6
+ import importlib.metadata
7
+ import os
8
+ import shutil
9
+ import subprocess
10
+ import sys
11
+ from dataclasses import asdict, dataclass
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+ from devcouncil.codeintel.debug.fingerprint import executable_hash
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class AdapterInfo:
20
+ id: str
21
+ name: str
22
+ command: tuple[str, ...]
23
+ path: str
24
+ executable_hash: str
25
+ version: str = ""
26
+ transport: str = "stdio"
27
+ languages: tuple[str, ...] = ()
28
+ requests: tuple[str, ...] = ("launch", "attach")
29
+
30
+ def as_dict(self) -> dict[str, Any]:
31
+ data = asdict(self)
32
+ data["command"] = list(self.command)
33
+ data["languages"] = list(self.languages)
34
+ data["requests"] = list(self.requests)
35
+ return data
36
+
37
+
38
+ _EXECUTABLES = (
39
+ ("lldb-dap", "LLDB DAP", ("c", "cpp", "objective-c", "swift", "rust"), ("--version",)),
40
+ ("codelldb", "CodeLLDB", ("c", "cpp", "objective-c", "swift", "rust"), ("--version",)),
41
+ ("dlv", "Delve", ("go",), ("version",)),
42
+ ("netcoredbg", ".NET Core Debugger", ("csharp", "vbnet"), ("--version",)),
43
+ ("js-debug-adapter", "JavaScript Debugger", ("javascript", "typescript"), ("--version",)),
44
+ ("node-debug2", "Node Debug2", ("javascript", "typescript"), ("--version",)),
45
+ ("php-debug-adapter", "PHP Debug Adapter", ("php",), ("--version",)),
46
+ )
47
+
48
+
49
+ def _command_version(command: tuple[str, ...]) -> str:
50
+ """Run an adapter's non-mutating version command with a strict bound."""
51
+ try:
52
+ completed = subprocess.run(
53
+ list(command),
54
+ stdin=subprocess.DEVNULL,
55
+ stdout=subprocess.PIPE,
56
+ stderr=subprocess.STDOUT,
57
+ timeout=2.0,
58
+ check=False,
59
+ text=True,
60
+ )
61
+ except (OSError, subprocess.TimeoutExpired):
62
+ return ""
63
+ lines = [line.strip() for line in completed.stdout.splitlines() if line.strip()]
64
+ return lines[0][:256] if lines else ""
65
+
66
+
67
+ def discover_adapters() -> list[AdapterInfo]:
68
+ adapters: list[AdapterInfo] = []
69
+ if importlib.util.find_spec("debugpy") is not None:
70
+ # Preserve the virtual-environment interpreter path. Resolving its
71
+ # symlink can escape the environment that actually contains debugpy.
72
+ path = str(Path(sys.executable).absolute())
73
+ try:
74
+ version = importlib.metadata.version("debugpy")
75
+ except importlib.metadata.PackageNotFoundError:
76
+ version = ""
77
+ adapters.append(AdapterInfo(
78
+ id="debugpy",
79
+ name="Python debugpy",
80
+ command=(path, "-m", "debugpy.adapter"),
81
+ path=path,
82
+ executable_hash=executable_hash(path),
83
+ version=version,
84
+ languages=("python",),
85
+ ))
86
+ configured_node = os.environ.get("DEVCOUNCIL_NODE_DEBUG2_PATH")
87
+ node = shutil.which("node")
88
+ if configured_node and node:
89
+ adapter_path = str(Path(configured_node).expanduser().resolve())
90
+ if Path(adapter_path).is_file():
91
+ adapters.append(AdapterInfo(
92
+ id="node-debug2",
93
+ name="Node Debug2",
94
+ command=(str(Path(node).resolve()), adapter_path),
95
+ path=adapter_path,
96
+ executable_hash=executable_hash(adapter_path),
97
+ version=os.environ.get("DEVCOUNCIL_NODE_DEBUG2_VERSION", ""),
98
+ languages=("javascript", "typescript"),
99
+ ))
100
+ for executable, name, languages, version_args in _EXECUTABLES:
101
+ resolved = shutil.which(executable)
102
+ if resolved:
103
+ path = str(Path(resolved).resolve())
104
+ adapters.append(AdapterInfo(
105
+ id=executable,
106
+ name=name,
107
+ command=(path,),
108
+ path=path,
109
+ executable_hash=executable_hash(path),
110
+ version=_command_version((path, *version_args)),
111
+ languages=languages,
112
+ ))
113
+ return adapters
114
+
115
+
116
+ def adapter_by_id(adapter_id: str) -> AdapterInfo | None:
117
+ return next((adapter for adapter in discover_adapters() if adapter.id == adapter_id), None)
118
+
119
+
120
+ def adapter_by_command(command: tuple[str, ...] | list[str]) -> AdapterInfo | None:
121
+ if not command:
122
+ return None
123
+ executable = str(Path(command[0]).expanduser().resolve())
124
+ return next(
125
+ (
126
+ adapter
127
+ for adapter in discover_adapters()
128
+ if str(Path(adapter.command[0]).expanduser().resolve()) == executable
129
+ and tuple(command[1:]) == adapter.command[1:]
130
+ ),
131
+ None,
132
+ )
@@ -0,0 +1,85 @@
1
+ """Source, build, and executable fingerprints for runtime evidence."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import subprocess
7
+ from pathlib import Path
8
+
9
+
10
+ def _sha256(data: bytes) -> str:
11
+ return hashlib.sha256(data).hexdigest()
12
+
13
+
14
+ def executable_hash(path: str | Path) -> str:
15
+ try:
16
+ return _sha256(Path(path).resolve().read_bytes())
17
+ except OSError:
18
+ return ""
19
+
20
+
21
+ _GIT_TIMEOUT_SECONDS = 15.0
22
+ _FALLBACK_SKIP_PARTS = frozenset({
23
+ ".git", ".devcouncil", "node_modules", "vendor", ".venv", "venv",
24
+ "dist", "build", "target", "__pycache__",
25
+ })
26
+
27
+
28
+ def source_fingerprint(root: Path) -> str:
29
+ root = root.expanduser().resolve()
30
+ head = b""
31
+ dirty = b""
32
+ try:
33
+ head = subprocess.check_output(
34
+ ["git", "rev-parse", "HEAD"],
35
+ cwd=root,
36
+ stderr=subprocess.DEVNULL,
37
+ timeout=_GIT_TIMEOUT_SECONDS,
38
+ )
39
+ dirty = subprocess.check_output(
40
+ ["git", "diff", "--no-ext-diff", "--binary", "HEAD"],
41
+ cwd=root,
42
+ stderr=subprocess.DEVNULL,
43
+ timeout=_GIT_TIMEOUT_SECONDS,
44
+ )
45
+ untracked = subprocess.check_output(
46
+ ["git", "ls-files", "--others", "--exclude-standard", "-z"],
47
+ cwd=root,
48
+ stderr=subprocess.DEVNULL,
49
+ timeout=_GIT_TIMEOUT_SECONDS,
50
+ )
51
+ for raw_path in sorted(value for value in untracked.split(b"\0") if value):
52
+ path = root / raw_path.decode("utf-8", errors="surrogateescape")
53
+ try:
54
+ dirty += b"\0untracked:" + raw_path + b"\0" + _sha256(path.read_bytes()).encode()
55
+ except OSError:
56
+ continue
57
+ except (OSError, subprocess.SubprocessError):
58
+ for path in sorted(root.rglob("*")):
59
+ if not path.is_file() or _FALLBACK_SKIP_PARTS.intersection(path.parts):
60
+ continue
61
+ try:
62
+ stat = path.stat()
63
+ dirty += f"{path.relative_to(root).as_posix()}\0{stat.st_size}\0{stat.st_mtime_ns}\n".encode()
64
+ except OSError:
65
+ continue
66
+ return _sha256(head + b"\0" + dirty)
67
+
68
+
69
+ def build_fingerprint(root: Path, executable: str | Path = "") -> str:
70
+ parts = [source_fingerprint(root).encode()]
71
+ for rel in (
72
+ ".devcouncil/config.yaml",
73
+ "pyproject.toml",
74
+ "package.json",
75
+ "Cargo.toml",
76
+ "go.mod",
77
+ "CMakeLists.txt",
78
+ ):
79
+ try:
80
+ parts.append((root / rel).read_bytes())
81
+ except OSError:
82
+ pass
83
+ if executable:
84
+ parts.append(executable_hash(executable).encode())
85
+ return _sha256(b"\0".join(parts))
@@ -0,0 +1,259 @@
1
+ """Capability-negotiated Debug Adapter Protocol client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import queue
7
+ import socket
8
+ import subprocess
9
+ import threading
10
+ import time
11
+ from dataclasses import dataclass
12
+ from typing import Any, BinaryIO, Sequence, cast
13
+
14
+
15
+ class DAPError(RuntimeError):
16
+ pass
17
+
18
+
19
+ def encode_message(message: dict[str, Any]) -> bytes:
20
+ body = json.dumps(message, separators=(",", ":")).encode("utf-8")
21
+ return f"Content-Length: {len(body)}\r\n\r\n".encode("ascii") + body
22
+
23
+
24
+ def read_message(stream: BinaryIO) -> dict[str, Any] | None:
25
+ headers: dict[str, str] = {}
26
+ while True:
27
+ line = stream.readline()
28
+ if not line:
29
+ return None
30
+ if line in {b"\r\n", b"\n"}:
31
+ break
32
+ key, _, value = line.decode("ascii", errors="replace").partition(":")
33
+ headers[key.strip().lower()] = value.strip()
34
+ try:
35
+ length = int(headers["content-length"])
36
+ except (KeyError, ValueError) as exc:
37
+ raise DAPError("DAP message is missing a valid Content-Length header") from exc
38
+ body = stream.read(length)
39
+ if len(body) != length:
40
+ raise DAPError("DAP stream ended mid-message")
41
+ parsed = json.loads(body.decode("utf-8"))
42
+ if not isinstance(parsed, dict):
43
+ raise DAPError("DAP message body must be an object")
44
+ return parsed
45
+
46
+
47
+ @dataclass
48
+ class PendingRequest:
49
+ sequence: int
50
+ responses: "queue.Queue[dict[str, Any]]"
51
+
52
+
53
+ class DAPClient:
54
+ def __init__(
55
+ self,
56
+ reader: BinaryIO,
57
+ writer: BinaryIO,
58
+ *,
59
+ process: subprocess.Popen[bytes] | None = None,
60
+ sock: socket.socket | None = None,
61
+ ):
62
+ self.reader = reader
63
+ self.writer = writer
64
+ self.process = process
65
+ self.sock = sock
66
+ self.capabilities: dict[str, Any] = {}
67
+ self._sequence = 0
68
+ self._write_lock = threading.Lock()
69
+ self._pending: dict[int, queue.Queue[dict[str, Any]]] = {}
70
+ self._events: "queue.Queue[dict[str, Any]]" = queue.Queue()
71
+ self._closed = threading.Event()
72
+ self._reader_thread = threading.Thread(target=self._read_loop, name="devcouncil-dap-reader", daemon=True)
73
+ self._reader_thread.start()
74
+
75
+ @classmethod
76
+ def start_stdio(cls, command: Sequence[str], *, cwd: str | None = None) -> "DAPClient":
77
+ process = subprocess.Popen(
78
+ list(command),
79
+ cwd=cwd,
80
+ stdin=subprocess.PIPE,
81
+ stdout=subprocess.PIPE,
82
+ stderr=subprocess.DEVNULL,
83
+ )
84
+ if process.stdin is None or process.stdout is None:
85
+ process.kill()
86
+ raise DAPError("debug adapter did not expose stdio pipes")
87
+ return cls(cast(BinaryIO, process.stdout), cast(BinaryIO, process.stdin), process=process)
88
+
89
+ @classmethod
90
+ def connect_tcp(cls, host: str, port: int, *, timeout: float = 10.0) -> "DAPClient":
91
+ sock = socket.create_connection((host, port), timeout=timeout)
92
+ reader = sock.makefile("rb")
93
+ writer = sock.makefile("wb")
94
+ return cls(reader, writer, sock=sock)
95
+
96
+ def begin_request(self, command: str, arguments: dict[str, Any] | None = None) -> PendingRequest:
97
+ with self._write_lock:
98
+ self._sequence += 1
99
+ sequence = self._sequence
100
+ responses: "queue.Queue[dict[str, Any]]" = queue.Queue(maxsize=1)
101
+ self._pending[sequence] = responses
102
+ message = {
103
+ "seq": sequence,
104
+ "type": "request",
105
+ "command": command,
106
+ "arguments": arguments or {},
107
+ }
108
+ self.writer.write(encode_message(message))
109
+ self.writer.flush()
110
+ return PendingRequest(sequence, responses)
111
+
112
+ def wait_response(self, pending: PendingRequest, *, timeout: float = 15.0) -> dict[str, Any]:
113
+ try:
114
+ response = pending.responses.get(timeout=timeout)
115
+ except queue.Empty as exc:
116
+ self._pending.pop(pending.sequence, None)
117
+ raise TimeoutError(f"DAP request {pending.sequence} timed out") from exc
118
+ if not response.get("success", False):
119
+ message = response.get("message") or response.get("body", {}).get("error", {}).get("format")
120
+ raise DAPError(str(message or "debug adapter rejected request"))
121
+ return dict(response.get("body") or {})
122
+
123
+ def request(self, command: str, arguments: dict[str, Any] | None = None, *, timeout: float = 15.0) -> dict[str, Any]:
124
+ return self.wait_response(self.begin_request(command, arguments), timeout=timeout)
125
+
126
+ def initialize(self, *, client_name: str = "DevCouncil", adapter_id: str = "devcouncil") -> dict[str, Any]:
127
+ self.capabilities = self.request("initialize", {
128
+ "clientID": "devcouncil",
129
+ "clientName": client_name,
130
+ "adapterID": adapter_id,
131
+ "pathFormat": "path",
132
+ "linesStartAt1": True,
133
+ "columnsStartAt1": True,
134
+ "supportsVariableType": True,
135
+ "supportsVariablePaging": True,
136
+ "supportsRunInTerminalRequest": False,
137
+ })
138
+ return self.capabilities
139
+
140
+ def wait_event(
141
+ self,
142
+ event: str | None = None,
143
+ *,
144
+ timeout: float = 15.0,
145
+ fail_on: PendingRequest | None = None,
146
+ ) -> dict[str, Any]:
147
+ deadline = time.monotonic() + timeout
148
+ deadline_items: list[dict[str, Any]] = []
149
+ while True:
150
+ if fail_on is not None:
151
+ try:
152
+ response = fail_on.responses.get_nowait()
153
+ except queue.Empty:
154
+ pass
155
+ else:
156
+ fail_on.responses.put(response)
157
+ if not response.get("success", False):
158
+ self.wait_response(fail_on, timeout=0)
159
+ remaining = deadline - time.monotonic()
160
+ if remaining <= 0:
161
+ for item in deadline_items:
162
+ self._events.put(item)
163
+ raise TimeoutError(f"DAP event {event or '*'} timed out")
164
+ try:
165
+ message = self._events.get(timeout=min(0.05, remaining) if fail_on is not None else remaining)
166
+ except queue.Empty as exc:
167
+ if fail_on is not None:
168
+ continue
169
+ for item in deadline_items:
170
+ self._events.put(item)
171
+ raise TimeoutError(f"DAP event {event or '*'} timed out") from exc
172
+ if event is None or message.get("event") == event:
173
+ for item in deadline_items:
174
+ self._events.put(item)
175
+ return message
176
+ deadline_items.append(message)
177
+
178
+ def evaluate(
179
+ self,
180
+ expression: str,
181
+ *,
182
+ frame_id: int | None = None,
183
+ context: str = "repl",
184
+ allow_side_effects: bool = False,
185
+ ) -> dict[str, Any]:
186
+ if not allow_side_effects:
187
+ raise PermissionError("DAP evaluate may execute code; set allow_side_effects=True explicitly")
188
+ arguments: dict[str, Any] = {"expression": expression, "context": context}
189
+ if frame_id is not None:
190
+ arguments["frameId"] = frame_id
191
+ return self.request("evaluate", arguments)
192
+
193
+ def close(self) -> None:
194
+ if self._closed.is_set():
195
+ return
196
+ self._closed.set()
197
+ if self.sock is not None:
198
+ try:
199
+ self.sock.shutdown(socket.SHUT_RDWR)
200
+ except OSError:
201
+ pass
202
+ try:
203
+ self.writer.close()
204
+ except OSError:
205
+ pass
206
+ try:
207
+ self.reader.close()
208
+ except OSError:
209
+ pass
210
+ if self.sock is not None:
211
+ self.sock.close()
212
+ if self.process is not None and self.process.poll() is None:
213
+ self.process.terminate()
214
+ try:
215
+ self.process.wait(timeout=3)
216
+ except subprocess.TimeoutExpired:
217
+ self.process.kill()
218
+
219
+ def _read_loop(self) -> None:
220
+ try:
221
+ while not self._closed.is_set():
222
+ message = read_message(self.reader)
223
+ if message is None:
224
+ break
225
+ message_type = message.get("type")
226
+ if message_type == "response":
227
+ request_seq = int(message.get("request_seq", -1))
228
+ responses = self._pending.pop(request_seq, None)
229
+ if responses is not None:
230
+ responses.put(message)
231
+ elif message_type == "event":
232
+ self._events.put(message)
233
+ elif message_type == "request":
234
+ self._respond_unsupported(message)
235
+ except Exception as exc:
236
+ error = {
237
+ "type": "response",
238
+ "success": False,
239
+ "message": f"DAP transport failed: {type(exc).__name__}: {exc}",
240
+ }
241
+ for responses in list(self._pending.values()):
242
+ responses.put(error)
243
+ self._pending.clear()
244
+ finally:
245
+ self._closed.set()
246
+
247
+ def _respond_unsupported(self, request: dict[str, Any]) -> None:
248
+ with self._write_lock:
249
+ self._sequence += 1
250
+ response = {
251
+ "seq": self._sequence,
252
+ "type": "response",
253
+ "request_seq": request.get("seq"),
254
+ "success": False,
255
+ "command": request.get("command"),
256
+ "message": "DevCouncil does not support this reverse request",
257
+ }
258
+ self.writer.write(encode_message(response))
259
+ self.writer.flush()