devcouncil 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (365) hide show
  1. package/README.md +46 -30
  2. package/package.json +6 -2
  3. package/packages/codeintel-grammars/hatch_build.py +43 -0
  4. package/packages/codeintel-grammars/pyproject.toml +16 -0
  5. package/packages/codeintel-grammars/src/devcouncil_codeintel_grammars/__init__.py +93 -0
  6. package/pyproject.toml +99 -4
  7. package/src/devcouncil/app/config.py +512 -20
  8. package/src/devcouncil/app/events.py +4 -23
  9. package/src/devcouncil/app/orchestrator.py +5 -0
  10. package/src/devcouncil/app/run_context.py +3 -3
  11. package/src/devcouncil/assets/__init__.py +4 -1
  12. package/src/devcouncil/assets/vendor/force-graph.min.js +5 -0
  13. package/src/devcouncil/campaign/__init__.py +71 -0
  14. package/src/devcouncil/campaign/bloom.py +137 -0
  15. package/src/devcouncil/campaign/dashboard.py +123 -0
  16. package/src/devcouncil/campaign/mailbox.py +305 -0
  17. package/src/devcouncil/campaign/notify.py +91 -0
  18. package/src/devcouncil/campaign/orchestrator.py +592 -0
  19. package/src/devcouncil/campaign/prompts/coordinator.md +29 -0
  20. package/src/devcouncil/campaign/prompts/director.md +21 -0
  21. package/src/devcouncil/campaign/prompts/protocol.md +46 -0
  22. package/src/devcouncil/campaign/prompts/reviewer.md +24 -0
  23. package/src/devcouncil/campaign/prompts/worker.md +24 -0
  24. package/src/devcouncil/campaign/roles.py +202 -0
  25. package/src/devcouncil/campaign/watcher.py +153 -0
  26. package/src/devcouncil/cli/commands/agents.py +24 -17
  27. package/src/devcouncil/cli/commands/artifacts.py +36 -27
  28. package/src/devcouncil/cli/commands/ast.py +12 -3
  29. package/src/devcouncil/cli/commands/baseline.py +21 -12
  30. package/src/devcouncil/cli/commands/boot.py +218 -0
  31. package/src/devcouncil/cli/commands/campaign.py +302 -0
  32. package/src/devcouncil/cli/commands/check.py +225 -12
  33. package/src/devcouncil/cli/commands/config.py +221 -74
  34. package/src/devcouncil/cli/commands/cost.py +137 -28
  35. package/src/devcouncil/cli/commands/dashboard.py +12 -4
  36. package/src/devcouncil/cli/commands/debug_cmd.py +249 -0
  37. package/src/devcouncil/cli/commands/design.py +27 -17
  38. package/src/devcouncil/cli/commands/doctor.py +790 -8
  39. package/src/devcouncil/cli/commands/evidence.py +41 -20
  40. package/src/devcouncil/cli/commands/export.py +73 -0
  41. package/src/devcouncil/cli/commands/gaps.py +175 -0
  42. package/src/devcouncil/cli/commands/gated_write.py +76 -0
  43. package/src/devcouncil/cli/commands/go.py +220 -68
  44. package/src/devcouncil/cli/commands/graph_cmd.py +1192 -0
  45. package/src/devcouncil/cli/commands/handoff.py +45 -34
  46. package/src/devcouncil/cli/commands/hook.py +630 -85
  47. package/src/devcouncil/cli/commands/init.py +89 -30
  48. package/src/devcouncil/cli/commands/integrate.py +296 -1385
  49. package/src/devcouncil/cli/commands/lease.py +120 -0
  50. package/src/devcouncil/cli/commands/logs.py +12 -5
  51. package/src/devcouncil/cli/commands/lsp.py +40 -5
  52. package/src/devcouncil/cli/commands/map.py +317 -74
  53. package/src/devcouncil/cli/commands/mcp_server.py +12 -2
  54. package/src/devcouncil/cli/commands/okf.py +44 -6
  55. package/src/devcouncil/cli/commands/plan.py +184 -69
  56. package/src/devcouncil/cli/commands/prompt.py +26 -17
  57. package/src/devcouncil/cli/commands/provenance.py +79 -0
  58. package/src/devcouncil/cli/commands/repair.py +60 -49
  59. package/src/devcouncil/cli/commands/report.py +148 -40
  60. package/src/devcouncil/cli/commands/requirements.py +104 -0
  61. package/src/devcouncil/cli/commands/reset_demo_state.py +13 -4
  62. package/src/devcouncil/cli/commands/rollback.py +46 -35
  63. package/src/devcouncil/cli/commands/run.py +173 -8
  64. package/src/devcouncil/cli/commands/runs.py +298 -68
  65. package/src/devcouncil/cli/commands/scaffold.py +33 -12
  66. package/src/devcouncil/cli/commands/semantic.py +29 -14
  67. package/src/devcouncil/cli/commands/setup.py +103 -93
  68. package/src/devcouncil/cli/commands/shell.py +51 -42
  69. package/src/devcouncil/cli/commands/show.py +56 -42
  70. package/src/devcouncil/cli/commands/skills.py +29 -20
  71. package/src/devcouncil/cli/commands/status.py +80 -67
  72. package/src/devcouncil/cli/commands/task_gate.py +295 -0
  73. package/src/devcouncil/cli/commands/tasks.py +248 -19
  74. package/src/devcouncil/cli/commands/trace.py +14 -8
  75. package/src/devcouncil/cli/commands/verify.py +33 -8
  76. package/src/devcouncil/cli/commands/version.py +14 -6
  77. package/src/devcouncil/cli/commands/watch.py +49 -30
  78. package/src/devcouncil/cli/commands/watch_fs.py +30 -19
  79. package/src/devcouncil/cli/commands/wiki.py +278 -0
  80. package/src/devcouncil/cli/main.py +58 -1
  81. package/src/devcouncil/codeintel/__init__.py +16 -0
  82. package/src/devcouncil/codeintel/build_control.py +429 -0
  83. package/src/devcouncil/codeintel/build_worker.py +78 -0
  84. package/src/devcouncil/codeintel/debug/__init__.py +17 -0
  85. package/src/devcouncil/codeintel/debug/broker.py +114 -0
  86. package/src/devcouncil/codeintel/debug/broker_client.py +61 -0
  87. package/src/devcouncil/codeintel/debug/consent.py +36 -0
  88. package/src/devcouncil/codeintel/debug/discovery.py +132 -0
  89. package/src/devcouncil/codeintel/debug/fingerprint.py +85 -0
  90. package/src/devcouncil/codeintel/debug/protocol.py +259 -0
  91. package/src/devcouncil/codeintel/debug/python_trace_runner.py +81 -0
  92. package/src/devcouncil/codeintel/debug/session.py +238 -0
  93. package/src/devcouncil/codeintel/debug/tracing.py +201 -0
  94. package/src/devcouncil/codeintel/languages/__init__.py +17 -0
  95. package/src/devcouncil/codeintel/languages/generic_extractor.py +236 -0
  96. package/src/devcouncil/codeintel/languages/registry.py +149 -0
  97. package/src/devcouncil/codeintel/languages/workers.py +245 -0
  98. package/src/devcouncil/codeintel/query/__init__.py +5 -0
  99. package/src/devcouncil/codeintel/query/engine.py +289 -0
  100. package/src/devcouncil/codeintel/resolution/__init__.py +6 -0
  101. package/src/devcouncil/codeintel/resolution/abstract_state.py +301 -0
  102. package/src/devcouncil/codeintel/resolution/frameworks/__init__.py +33 -0
  103. package/src/devcouncil/codeintel/resolution/frameworks/base.py +46 -0
  104. package/src/devcouncil/codeintel/resolution/frameworks/di.py +56 -0
  105. package/src/devcouncil/codeintel/resolution/frameworks/events.py +45 -0
  106. package/src/devcouncil/codeintel/resolution/frameworks/routes.py +88 -0
  107. package/src/devcouncil/codeintel/resolution/semantic.py +887 -0
  108. package/src/devcouncil/codeintel/service.py +104 -0
  109. package/src/devcouncil/codeintel/store/__init__.py +15 -0
  110. package/src/devcouncil/codeintel/store/sqlite.py +1565 -0
  111. package/src/devcouncil/codeintel/sync/__init__.py +19 -0
  112. package/src/devcouncil/codeintel/sync/coordinator.py +430 -0
  113. package/src/devcouncil/codeintel/sync/incremental.py +484 -0
  114. package/src/devcouncil/codeintel/sync/lease.py +96 -0
  115. package/src/devcouncil/codeintel/sync/scope.py +98 -0
  116. package/src/devcouncil/council/__init__.py +4 -0
  117. package/src/devcouncil/council/prompts/__init__.py +4 -0
  118. package/src/devcouncil/domain/checkpoint_refs.py +17 -0
  119. package/src/devcouncil/domain/evidence.py +1 -0
  120. package/src/devcouncil/domain/gap.py +10 -0
  121. package/src/devcouncil/domain/requirement.py +5 -1
  122. package/src/devcouncil/domain/task.py +43 -2
  123. package/src/devcouncil/execution/checkpoints.py +25 -31
  124. package/src/devcouncil/execution/context_builder.py +15 -44
  125. package/src/devcouncil/execution/fs_watcher.py +64 -0
  126. package/src/devcouncil/execution/gated_write.py +203 -0
  127. package/src/devcouncil/execution/handoff.py +2 -1
  128. package/src/devcouncil/execution/hook_policy.py +19 -5
  129. package/src/devcouncil/execution/lease_ops.py +177 -0
  130. package/src/devcouncil/execution/lease_validation.py +71 -0
  131. package/src/devcouncil/execution/patch.py +3 -0
  132. package/src/devcouncil/execution/permissions.py +1 -0
  133. package/src/devcouncil/execution/policy_engine.py +205 -10
  134. package/src/devcouncil/execution/prompt_builder.py +278 -33
  135. package/src/devcouncil/execution/run_trace.py +356 -0
  136. package/src/devcouncil/execution/shell_session.py +46 -5
  137. package/src/devcouncil/execution/stop_gate.py +746 -0
  138. package/src/devcouncil/execution/stop_gate_history.py +113 -0
  139. package/src/devcouncil/execution/stop_gate_state.py +54 -0
  140. package/src/devcouncil/execution/stop_gate_verify_cache.py +69 -0
  141. package/src/devcouncil/execution/task_gate_ops.py +590 -0
  142. package/src/devcouncil/execution/task_runner.py +19 -0
  143. package/src/devcouncil/executors/advisor_tool.py +315 -0
  144. package/src/devcouncil/executors/agent_registry.py +125 -17
  145. package/src/devcouncil/executors/claude_sdk.py +376 -0
  146. package/src/devcouncil/executors/coding_cli.py +724 -25
  147. package/src/devcouncil/executors/mini_swe.py +50 -8
  148. package/src/devcouncil/executors/native/agent.py +224 -19
  149. package/src/devcouncil/executors/openhands.py +50 -8
  150. package/src/devcouncil/executors/transient_retry.py +99 -0
  151. package/src/devcouncil/gating/checks/clean_git.py +5 -2
  152. package/src/devcouncil/gating/checks/planned_files_check.py +38 -11
  153. package/src/devcouncil/gating/checks/secret_scan_check.py +2 -2
  154. package/src/devcouncil/gating/policy.py +46 -2
  155. package/src/devcouncil/indexing/ast_matcher.py +41 -4
  156. package/src/devcouncil/indexing/graph/__init__.py +78 -0
  157. package/src/devcouncil/indexing/graph/api_routes.py +522 -0
  158. package/src/devcouncil/indexing/graph/build.py +862 -0
  159. package/src/devcouncil/indexing/graph/cache.py +329 -0
  160. package/src/devcouncil/indexing/graph/communities.py +28 -0
  161. package/src/devcouncil/indexing/graph/cypher.py +107 -0
  162. package/src/devcouncil/indexing/graph/embeddings.py +194 -0
  163. package/src/devcouncil/indexing/graph/export.py +381 -0
  164. package/src/devcouncil/indexing/graph/export_links.py +81 -0
  165. package/src/devcouncil/indexing/graph/extract_python.py +307 -0
  166. package/src/devcouncil/indexing/graph/extract_ts.py +1205 -0
  167. package/src/devcouncil/indexing/graph/intel.py +668 -0
  168. package/src/devcouncil/indexing/graph/liveness.py +992 -0
  169. package/src/devcouncil/indexing/graph/okf_export.py +65 -0
  170. package/src/devcouncil/indexing/graph/pdg/__init__.py +67 -0
  171. package/src/devcouncil/indexing/graph/pdg/build.py +11 -0
  172. package/src/devcouncil/indexing/graph/pdg/cdg.py +41 -0
  173. package/src/devcouncil/indexing/graph/pdg/cfg.py +199 -0
  174. package/src/devcouncil/indexing/graph/pdg/query.py +21 -0
  175. package/src/devcouncil/indexing/graph/pdg/reaching_def.py +126 -0
  176. package/src/devcouncil/indexing/graph/pdg/schema.py +253 -0
  177. package/src/devcouncil/indexing/graph/pdg/taint.py +154 -0
  178. package/src/devcouncil/indexing/graph/query.py +302 -0
  179. package/src/devcouncil/indexing/graph/resolve.py +1020 -0
  180. package/src/devcouncil/indexing/graph/schema.py +103 -0
  181. package/src/devcouncil/indexing/graph_index.py +20 -29
  182. package/src/devcouncil/indexing/lsp.py +57 -25
  183. package/src/devcouncil/indexing/lsp_client.py +577 -0
  184. package/src/devcouncil/indexing/map_artifacts.py +355 -0
  185. package/src/devcouncil/indexing/map_refresh.py +141 -0
  186. package/src/devcouncil/indexing/repo_mapper.py +1509 -138
  187. package/src/devcouncil/indexing/semantic_index.py +12 -6
  188. package/src/devcouncil/indexing/subsystem_map.py +163 -0
  189. package/src/devcouncil/indexing/ts_imports.py +343 -0
  190. package/src/devcouncil/indexing/viz.py +960 -0
  191. package/src/devcouncil/indexing/walk.py +52 -0
  192. package/src/devcouncil/indexing/wiring.py +1776 -0
  193. package/src/devcouncil/integrations/actions.py +27 -4
  194. package/src/devcouncil/integrations/check.py +211 -16
  195. package/src/devcouncil/integrations/claude_assets.py +209 -12
  196. package/src/devcouncil/integrations/clients/__init__.py +1 -0
  197. package/src/devcouncil/integrations/clients/aider.py +52 -0
  198. package/src/devcouncil/integrations/clients/antigravity.py +87 -0
  199. package/src/devcouncil/integrations/clients/claude.py +339 -0
  200. package/src/devcouncil/integrations/clients/codex.py +39 -0
  201. package/src/devcouncil/integrations/clients/common.py +332 -0
  202. package/src/devcouncil/integrations/clients/cursor.py +164 -0
  203. package/src/devcouncil/integrations/clients/gemini.py +49 -0
  204. package/src/devcouncil/integrations/clients/grok.py +105 -0
  205. package/src/devcouncil/integrations/clients/hooks.py +500 -0
  206. package/src/devcouncil/integrations/clients/opencode.py +96 -0
  207. package/src/devcouncil/integrations/clients/warp.py +75 -0
  208. package/src/devcouncil/integrations/code_review_graph.py +2 -2
  209. package/src/devcouncil/integrations/github.py +73 -7
  210. package/src/devcouncil/integrations/integration_cli.py +197 -0
  211. package/src/devcouncil/integrations/mcp/handlers/__init__.py +1 -0
  212. package/src/devcouncil/integrations/mcp/handlers/ast_lsp.py +77 -0
  213. package/src/devcouncil/integrations/mcp/handlers/checkout.py +50 -0
  214. package/src/devcouncil/integrations/mcp/handlers/cli_gate.py +43 -0
  215. package/src/devcouncil/integrations/mcp/handlers/codeintel.py +182 -0
  216. package/src/devcouncil/integrations/mcp/handlers/debug.py +236 -0
  217. package/src/devcouncil/integrations/mcp/handlers/evidence.py +70 -0
  218. package/src/devcouncil/integrations/mcp/handlers/git.py +99 -0
  219. package/src/devcouncil/integrations/mcp/handlers/graph.py +36 -0
  220. package/src/devcouncil/integrations/mcp/handlers/handoff.py +53 -0
  221. package/src/devcouncil/integrations/mcp/handlers/knowledge.py +30 -0
  222. package/src/devcouncil/integrations/mcp/handlers/lease.py +70 -0
  223. package/src/devcouncil/integrations/mcp/handlers/live.py +108 -0
  224. package/src/devcouncil/integrations/mcp/handlers/map.py +676 -0
  225. package/src/devcouncil/integrations/mcp/handlers/next_task.py +35 -0
  226. package/src/devcouncil/integrations/mcp/handlers/policy.py +80 -0
  227. package/src/devcouncil/integrations/mcp/handlers/prompts.py +168 -0
  228. package/src/devcouncil/integrations/mcp/handlers/provenance.py +101 -0
  229. package/src/devcouncil/integrations/mcp/handlers/read.py +74 -0
  230. package/src/devcouncil/integrations/mcp/handlers/router_cache.py +53 -0
  231. package/src/devcouncil/integrations/mcp/handlers/run.py +53 -0
  232. package/src/devcouncil/integrations/mcp/handlers/runs.py +84 -0
  233. package/src/devcouncil/integrations/mcp/handlers/scope.py +56 -0
  234. package/src/devcouncil/integrations/mcp/handlers/status.py +114 -0
  235. package/src/devcouncil/integrations/mcp/handlers/task.py +100 -0
  236. package/src/devcouncil/integrations/mcp/handlers/tool_specs.py +904 -0
  237. package/src/devcouncil/integrations/mcp/handlers/trace.py +65 -0
  238. package/src/devcouncil/integrations/mcp/handlers/verify.py +45 -0
  239. package/src/devcouncil/integrations/mcp/handlers/wiki.py +60 -0
  240. package/src/devcouncil/integrations/mcp/handlers/write.py +69 -0
  241. package/src/devcouncil/integrations/mcp/server.py +265 -2391
  242. package/src/devcouncil/integrations/mcp/util.py +303 -0
  243. package/src/devcouncil/integrations/setup.py +152 -0
  244. package/src/devcouncil/knowledge/fetch.py +4 -0
  245. package/src/devcouncil/knowledge/knowledge_select.py +38 -0
  246. package/src/devcouncil/knowledge/okf.py +2 -1
  247. package/src/devcouncil/knowledge/resource_discovery.py +40 -0
  248. package/src/devcouncil/knowledge/wiki.py +643 -0
  249. package/src/devcouncil/knowledge/wiki_read.py +87 -0
  250. package/src/devcouncil/live/cards.py +7 -7
  251. package/src/devcouncil/live/models.py +4 -1
  252. package/src/devcouncil/live/reviewer.py +90 -11
  253. package/src/devcouncil/live/signals.py +4 -2
  254. package/src/devcouncil/live/tasks.py +12 -3
  255. package/src/devcouncil/live/transcripts.py +69 -2
  256. package/src/devcouncil/llm/cache.py +5 -6
  257. package/src/devcouncil/llm/model_defaults.yaml +10 -10
  258. package/src/devcouncil/llm/provider.py +647 -73
  259. package/src/devcouncil/llm/router.py +271 -46
  260. package/src/devcouncil/llm/semantic_bridge.py +614 -0
  261. package/src/devcouncil/optimization/gepa_agent.py +6 -4
  262. package/src/devcouncil/optimization/skillopt.py +9 -5
  263. package/src/devcouncil/planning/arbiter_service.py +12 -3
  264. package/src/devcouncil/planning/correction_manifest.py +107 -10
  265. package/src/devcouncil/planning/plan_difficulty.py +69 -0
  266. package/src/devcouncil/planning/plan_service.py +5 -2
  267. package/src/devcouncil/planning/planned_files_reconcile.py +191 -0
  268. package/src/devcouncil/planning/prompt_enhancer_service.py +6 -5
  269. package/src/devcouncil/planning/question_conversion.py +56 -0
  270. package/src/devcouncil/planning/spec_service.py +9 -3
  271. package/src/devcouncil/repo/ci_scaffold.py +197 -1
  272. package/src/devcouncil/repo/gitignore.py +1 -2
  273. package/src/devcouncil/reporting/evidence_export.py +124 -0
  274. package/src/devcouncil/reporting/evidence_html.py +210 -0
  275. package/src/devcouncil/reporting/json_report.py +16 -12
  276. package/src/devcouncil/reporting/markdown_report.py +38 -9
  277. package/src/devcouncil/reporting/mcp_resources.py +142 -0
  278. package/src/devcouncil/reporting/report_builder.py +40 -4
  279. package/src/devcouncil/reporting/task_provenance.py +42 -0
  280. package/src/devcouncil/reporting/verdict.py +75 -0
  281. package/src/devcouncil/skills/library/README.md +1 -0
  282. package/src/devcouncil/skills/library/devcouncil-hero-loop.md +109 -0
  283. package/src/devcouncil/skills/library/devcouncil-verification.md +109 -0
  284. package/src/devcouncil/skills/library/devcouncil.md +93 -0
  285. package/src/devcouncil/skills/registry.py +43 -12
  286. package/src/devcouncil/storage/db.py +57 -11
  287. package/src/devcouncil/storage/models.py +6 -0
  288. package/src/devcouncil/storage/native.py +5 -3
  289. package/src/devcouncil/storage/repositories.py +50 -18
  290. package/src/devcouncil/telemetry/context.py +28 -0
  291. package/src/devcouncil/telemetry/cost.py +4 -5
  292. package/src/devcouncil/telemetry/logging_setup.py +78 -11
  293. package/src/devcouncil/telemetry/model_pricing.yaml +7 -0
  294. package/src/devcouncil/telemetry/stages.py +27 -2
  295. package/src/devcouncil/telemetry/tracker.py +50 -13
  296. package/src/devcouncil/ui/dashboard.py +120 -8
  297. package/src/devcouncil/utils/fsio.py +58 -0
  298. package/src/devcouncil/utils/git_snapshot.py +112 -0
  299. package/src/devcouncil/utils/json_persist.py +53 -0
  300. package/src/devcouncil/utils/proc.py +89 -0
  301. package/src/devcouncil/verification/acceptance_compiler.py +36 -13
  302. package/src/devcouncil/verification/ad_hoc_check.py +95 -3
  303. package/src/devcouncil/verification/checks/__init__.py +41 -0
  304. package/src/devcouncil/verification/checks/acceptance.py +39 -0
  305. package/src/devcouncil/verification/checks/acceptance_corpus.py +194 -0
  306. package/src/devcouncil/verification/checks/acceptance_evidence.py +239 -0
  307. package/src/devcouncil/verification/checks/command_evidence.py +148 -0
  308. package/src/devcouncil/verification/checks/compiled_acceptance.py +179 -0
  309. package/src/devcouncil/verification/checks/corpus_stale.py +124 -0
  310. package/src/devcouncil/verification/checks/corpus_verification.py +9 -0
  311. package/src/devcouncil/verification/checks/dead_symbols.py +360 -0
  312. package/src/devcouncil/verification/checks/diff_coverage_gate.py +101 -0
  313. package/src/devcouncil/verification/checks/doc_code_ref.py +79 -0
  314. package/src/devcouncil/verification/checks/liveness_ratchet.py +336 -0
  315. package/src/devcouncil/verification/checks/orphan_diff.py +104 -0
  316. package/src/devcouncil/verification/checks/planned_files.py +98 -0
  317. package/src/devcouncil/verification/checks/semantic_diff.py +241 -0
  318. package/src/devcouncil/verification/checks/stale_map.py +80 -0
  319. package/src/devcouncil/verification/checks/stub_scan.py +71 -0
  320. package/src/devcouncil/verification/checks/subsystem_boundary.py +103 -0
  321. package/src/devcouncil/verification/checks/wiring.py +216 -0
  322. package/src/devcouncil/verification/claims/__init__.py +23 -0
  323. package/src/devcouncil/verification/claims/checks.py +395 -0
  324. package/src/devcouncil/verification/claims/mapper.py +168 -0
  325. package/src/devcouncil/verification/claims/models.py +39 -0
  326. package/src/devcouncil/verification/claims/transcript.py +92 -0
  327. package/src/devcouncil/verification/claims/verdict.py +88 -0
  328. package/src/devcouncil/verification/command_evidence.py +170 -0
  329. package/src/devcouncil/verification/command_malformation.py +147 -0
  330. package/src/devcouncil/verification/command_runner.py +164 -0
  331. package/src/devcouncil/verification/coverage_measurement.py +292 -0
  332. package/src/devcouncil/verification/diff_coverage.py +151 -0
  333. package/src/devcouncil/verification/difficulty.py +296 -0
  334. package/src/devcouncil/verification/effort_heuristics.py +178 -0
  335. package/src/devcouncil/verification/gap_ids.py +63 -0
  336. package/src/devcouncil/verification/gate_cache.py +194 -0
  337. package/src/devcouncil/verification/gate_selector.py +344 -0
  338. package/src/devcouncil/verification/git_diff_fallback.py +272 -0
  339. package/src/devcouncil/verification/implementation_reviewer.py +13 -0
  340. package/src/devcouncil/verification/incremental_check.py +241 -0
  341. package/src/devcouncil/verification/next_actions.py +60 -1
  342. package/src/devcouncil/verification/rigor_analytics.py +130 -0
  343. package/src/devcouncil/verification/sandbox.py +38 -11
  344. package/src/devcouncil/verification/stub_detector.py +369 -0
  345. package/src/devcouncil/verification/test_resolver.py +67 -1
  346. package/src/devcouncil/verification/verifier.py +137 -1666
  347. package/src/devcouncil/verification/verify_orchestration.py +610 -0
  348. package/src/devcouncil/verification/verify_setup.py +176 -0
  349. package/src/devcouncil/verification/wiki_refresh.py +208 -0
  350. package/src/semantic_layer/__init__.py +58 -0
  351. package/src/semantic_layer/benchmark.py +75 -0
  352. package/src/semantic_layer/cache.py +290 -0
  353. package/src/semantic_layer/compressor.py +137 -0
  354. package/src/semantic_layer/config.py +75 -0
  355. package/src/semantic_layer/embeddings.py +69 -0
  356. package/src/semantic_layer/llm_backends.py +99 -0
  357. package/src/semantic_layer/pipeline.py +111 -0
  358. package/src/semantic_layer/router.py +128 -0
  359. package/src/semantic_layer/tuner.py +72 -0
  360. package/uv.lock +973 -9
  361. package/src/devcouncil/artifacts/migrations.py +0 -20
  362. package/src/devcouncil/artifacts/schemas.py +0 -23
  363. package/src/devcouncil/artifacts/serializer.py +0 -21
  364. package/src/devcouncil/integrations/gitnexus.py +0 -70
  365. package/src/devcouncil/integrations/graphify.py +0 -34
@@ -0,0 +1,236 @@
1
+ """Generic Tree-sitter extraction used by the broad language matrix."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from typing import Any
7
+
8
+ from devcouncil.codeintel.languages.registry import detect_language
9
+ from devcouncil.codeintel.languages.workers import process_tree_sitter
10
+ from devcouncil.indexing.graph.extract_python import (
11
+ ExtractedCall,
12
+ ExtractedImport,
13
+ ExtractedSymbol,
14
+ FileExtraction,
15
+ )
16
+
17
+ _QUOTED = re.compile(r"['\"]([^'\"]+)['\"]")
18
+ _CALL = re.compile(r"(?<![\w$])(?:(?P<receiver>[A-Za-z_$][\w$.:]*)\s*[.>:]+\s*)?(?P<name>[A-Za-z_$][\w$]*)\s*\(")
19
+ _CALL_KEYWORDS = {"if", "for", "while", "switch", "catch", "return", "sizeof", "typeof", "match", "when"}
20
+
21
+
22
+ def extract_generic(path: str, source: str) -> FileExtraction:
23
+ spec = detect_language(path)
24
+ if spec is None:
25
+ return FileExtraction(path=path, language="")
26
+ output = FileExtraction(path=path, language=spec.grammar)
27
+ result = process_tree_sitter(spec.grammar, source)
28
+ if result is not None:
29
+ _append_result(output, source, result)
30
+ for language, region, line_offset in _embedded_regions(spec.name, source):
31
+ embedded = process_tree_sitter(language, region)
32
+ if embedded is not None:
33
+ _append_result(output, region, embedded, line_offset=line_offset)
34
+ return output
35
+
36
+
37
+ def _append_result(
38
+ output: FileExtraction,
39
+ source: str,
40
+ result: dict[str, Any],
41
+ *,
42
+ line_offset: int = 0,
43
+ ) -> None:
44
+ """Merge one container or embedded Tree-sitter result into a file extraction."""
45
+
46
+ exported_names = {
47
+ str(item.get("name", "")).strip()
48
+ for item in result.get("exports", [])
49
+ if str(item.get("name", "")).strip()
50
+ }
51
+
52
+ # Seen-sets instead of list scans: large generated files can carry
53
+ # thousands of symbols/calls, and per-append linear scans go quadratic.
54
+ seen_symbols = {(existing.qualname, existing.line) for existing in output.symbols}
55
+ seen_calls = {
56
+ (existing.name, existing.line, existing.receiver) for existing in output.calls
57
+ }
58
+
59
+ def visit(item: dict[str, Any], prefix: str = "") -> None:
60
+ name = str(item.get("name", "") or "").strip()
61
+ kind = _kind(str(item.get("kind", "") or ""))
62
+ if name and kind:
63
+ qualname = f"{prefix}.{name}" if prefix else name
64
+ symbol = ExtractedSymbol(
65
+ kind=kind,
66
+ name=name,
67
+ qualname=qualname,
68
+ line=int(item.get("start_line", 0)) + line_offset + 1,
69
+ end_line=int(item.get("end_line", 0)) + line_offset + 1,
70
+ decorators=[str(value) for value in item.get("decorators", [])],
71
+ exported=name in exported_names or not name.startswith("_"),
72
+ )
73
+ if (symbol.qualname, symbol.line) not in seen_symbols:
74
+ seen_symbols.add((symbol.qualname, symbol.line))
75
+ output.symbols.append(symbol)
76
+ child_prefix = qualname if kind in {"class", "interface", "struct", "trait", "enum"} else prefix
77
+ else:
78
+ child_prefix = prefix
79
+ for child in item.get("children", []) or []:
80
+ visit(child, child_prefix)
81
+
82
+ for item in result.get("structure", []) or []:
83
+ visit(item)
84
+
85
+ for item in result.get("imports", []) or []:
86
+ raw_source = str(item.get("source", "") or "")
87
+ match = _QUOTED.search(raw_source)
88
+ module = match.group(1) if match else raw_source.strip()
89
+ if not module:
90
+ continue
91
+ if module not in output.imports:
92
+ output.imports.append(module)
93
+ names = [str(value) for value in item.get("items", []) or []]
94
+ alias = str(item.get("alias", "") or "")
95
+ output.import_details.append(ExtractedImport(
96
+ module=module,
97
+ names=names,
98
+ alias_map={alias: module} if alias else {},
99
+ ))
100
+ output.all_exports = sorted(set(output.all_exports) | exported_names)
101
+
102
+ # ``qualname_hint`` is a CALLEE hint (``receiver.name`` / bare name) — the
103
+ # same convention as every dedicated extractor. resolve_calls resolves it
104
+ # against the same-file symbol index; the enclosing caller must never go
105
+ # here or bare in-function calls all bind back to their own caller.
106
+ if "calls" in result:
107
+ for item in result.get("calls", []) or []:
108
+ name = str(item.get("name", "") or "")
109
+ if not name or name in _CALL_KEYWORDS:
110
+ continue
111
+ line_no = int(item.get("line", 0)) + line_offset
112
+ receiver = str(item.get("receiver", "") or "")
113
+ call = ExtractedCall(
114
+ name=name,
115
+ line=line_no,
116
+ receiver=receiver,
117
+ qualname_hint=f"{receiver}.{name}" if receiver else name,
118
+ )
119
+ key = (call.name, call.line, call.receiver)
120
+ if key not in seen_calls:
121
+ seen_calls.add(key)
122
+ output.calls.append(call)
123
+ output.references.append(name)
124
+ return
125
+
126
+ # Compatibility for older companion packs that do not return call rows.
127
+ for line_no, line in enumerate(source.splitlines(), start=line_offset + 1):
128
+ for match in _CALL.finditer(line):
129
+ name = match.group("name")
130
+ if name in _CALL_KEYWORDS:
131
+ continue
132
+ receiver = match.group("receiver") or ""
133
+ output.calls.append(ExtractedCall(
134
+ name=name,
135
+ line=line_no,
136
+ receiver=receiver,
137
+ qualname_hint=f"{receiver}.{name}" if receiver else name,
138
+ ))
139
+ output.references.append(name)
140
+
141
+
142
+ def _embedded_regions(container: str, source: str) -> list[tuple[str, str, int]]:
143
+ regions: list[tuple[str, str, int]] = []
144
+ if container in {"Svelte", "Vue", "Astro", "Liquid"}:
145
+ for match in re.finditer(
146
+ r"<script\b(?P<attrs>[^>]*)>(?P<body>.*?)</script\s*>",
147
+ source,
148
+ re.IGNORECASE | re.DOTALL,
149
+ ):
150
+ attrs = match.group("attrs")
151
+ language = (
152
+ "typescript"
153
+ if re.search(r"\blang\s*=\s*['\"](?:ts|typescript)['\"]", attrs, re.I)
154
+ else "javascript"
155
+ )
156
+ regions.append((
157
+ language,
158
+ match.group("body"),
159
+ source[:match.start("body")].count("\n"),
160
+ ))
161
+ for match in re.finditer(
162
+ r"<style\b[^>]*>(?P<body>.*?)</style\s*>",
163
+ source,
164
+ re.IGNORECASE | re.DOTALL,
165
+ ):
166
+ regions.append((
167
+ "css",
168
+ match.group("body"),
169
+ source[:match.start("body")].count("\n"),
170
+ ))
171
+ if container in {"Svelte", "Vue", "Astro"}:
172
+ for match in re.finditer(
173
+ r"<template\b[^>]*>(?P<body>.*?)</template\s*>",
174
+ source,
175
+ re.IGNORECASE | re.DOTALL,
176
+ ):
177
+ regions.append((
178
+ "html",
179
+ match.group("body"),
180
+ source[:match.start("body")].count("\n"),
181
+ ))
182
+ if container == "Astro":
183
+ frontmatter = re.match(r"^---\s*\n(?P<body>.*?)\n---(?:\s*\n|$)", source, re.DOTALL)
184
+ if frontmatter is not None:
185
+ regions.append((
186
+ "typescript",
187
+ frontmatter.group("body"),
188
+ source[:frontmatter.start("body")].count("\n"),
189
+ ))
190
+ regions.append((
191
+ "html",
192
+ source[frontmatter.end():],
193
+ source[:frontmatter.end()].count("\n"),
194
+ ))
195
+ else:
196
+ regions.append(("html", source, 0))
197
+ if container == "Svelte":
198
+ regions.append(("html", source, 0))
199
+ if container == "Liquid":
200
+ for match in re.finditer(
201
+ r"{%\s*javascript\s*%}(?P<body>.*?){%\s*endjavascript\s*%}",
202
+ source,
203
+ re.IGNORECASE | re.DOTALL,
204
+ ):
205
+ regions.append((
206
+ "javascript",
207
+ match.group("body"),
208
+ source[:match.start("body")].count("\n"),
209
+ ))
210
+ regions.append(("html", source, 0))
211
+ return regions
212
+
213
+
214
+ def _kind(raw: str) -> str:
215
+ normalized = raw.lower().replace("_", "").replace(" ", "")
216
+ if "method" in normalized or "constructor" in normalized:
217
+ return "method"
218
+ if "function" in normalized or "procedure" in normalized or normalized in {"func", "subroutine"}:
219
+ return "function"
220
+ if "interface" in normalized or "protocol" in normalized:
221
+ return "interface"
222
+ if "struct" in normalized or "record" in normalized:
223
+ return "struct"
224
+ if "trait" in normalized:
225
+ return "trait"
226
+ if "enum" in normalized:
227
+ return "enum"
228
+ if "class" in normalized or "object" in normalized or "module" in normalized:
229
+ return "class"
230
+ if "type" in normalized or "alias" in normalized:
231
+ return "type"
232
+ if "property" in normalized or "field" in normalized:
233
+ return "property"
234
+ if "variable" in normalized or "constant" in normalized:
235
+ return "variable"
236
+ return ""
@@ -0,0 +1,149 @@
1
+ """Explicit language/grammar manifest for the code-intelligence release."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class LanguageSpec:
12
+ name: str
13
+ grammar: str
14
+ extensions: tuple[str, ...]
15
+ embedded: tuple[str, ...] = ()
16
+
17
+
18
+ def _spec(name: str, grammar: str, extensions: str, *, embedded: tuple[str, ...] = ()) -> LanguageSpec:
19
+ return LanguageSpec(name, grammar, tuple(extensions.split()), embedded)
20
+
21
+
22
+ LANGUAGE_SPECS: tuple[LanguageSpec, ...] = (
23
+ _spec("TypeScript", "typescript", ".ts .mts .cts"),
24
+ _spec("TSX", "tsx", ".tsx"),
25
+ _spec("JavaScript", "javascript", ".js .jsx .mjs .cjs"),
26
+ _spec("ArkTS", "typescript", ".ets"),
27
+ _spec("Python", "python", ".py .pyi"),
28
+ _spec("Go", "go", ".go"),
29
+ _spec("Rust", "rust", ".rs"),
30
+ _spec("Java", "java", ".java"),
31
+ _spec("C#", "csharp", ".cs"),
32
+ _spec("VB.NET", "vb", ".vb"),
33
+ _spec("PHP", "php", ".php .phtml"),
34
+ _spec("Ruby", "ruby", ".rb .rake"),
35
+ _spec("C", "c", ".c .h"),
36
+ _spec("C++", "cpp", ".cc .cpp .cxx .hh .hpp .hxx"),
37
+ _spec("Objective-C", "objc", ".m .mm"),
38
+ # Metal is a C++14-derived language; the language pack ships no separate
39
+ # Metal grammar, so use its maintained C++ parser plus Metal augmenters.
40
+ _spec("Metal", "cpp", ".metal"),
41
+ _spec("CUDA", "cuda", ".cu .cuh"),
42
+ _spec("Swift", "swift", ".swift"),
43
+ _spec("Kotlin", "kotlin", ".kt .kts"),
44
+ _spec("Scala", "scala", ".scala .sc"),
45
+ _spec("Dart", "dart", ".dart"),
46
+ _spec("Svelte", "svelte", ".svelte", embedded=("typescript", "javascript", "css", "html")),
47
+ _spec("Vue", "vue", ".vue", embedded=("typescript", "javascript", "css", "html")),
48
+ _spec("Astro", "astro", ".astro", embedded=("typescript", "javascript", "css", "html")),
49
+ _spec("Liquid", "liquid", ".liquid", embedded=("html", "javascript", "css")),
50
+ _spec("Pascal/Delphi", "pascal", ".pas .pp .dpr"),
51
+ _spec("Lua", "lua", ".lua"),
52
+ _spec("Luau", "luau", ".luau"),
53
+ _spec("R", "r", ".r .R"),
54
+ _spec("CFML", "cfml", ".cfm .cfc"),
55
+ _spec("COBOL", "cobol", ".cob .cbl .cpy"),
56
+ _spec("Erlang", "erlang", ".erl .hrl"),
57
+ _spec("Solidity", "solidity", ".sol"),
58
+ _spec("Terraform/OpenTofu", "hcl", ".tf .tfvars .hcl"),
59
+ _spec("Nix", "nix", ".nix"),
60
+ )
61
+
62
+ _BY_EXTENSION = {
63
+ extension.lower(): spec
64
+ for spec in LANGUAGE_SPECS
65
+ for extension in spec.extensions
66
+ }
67
+
68
+
69
+ def supported_languages() -> list[str]:
70
+ return [spec.name for spec in LANGUAGE_SPECS]
71
+
72
+
73
+ def detect_language(path: str | Path) -> LanguageSpec | None:
74
+ return _BY_EXTENSION.get(Path(path).suffix.lower())
75
+
76
+
77
+ def grammar_status() -> dict[str, Any]:
78
+ """Report local grammar availability without downloading anything."""
79
+
80
+ companion: dict[str, Any] = {"installed": False}
81
+ try:
82
+ try:
83
+ import devcouncil_codeintel_grammars as grammar_assets
84
+
85
+ companion = {"installed": True, **grammar_assets.activate()}
86
+ except ImportError:
87
+ pass
88
+ import tree_sitter_language_pack as pack
89
+
90
+ available = set(pack.available_languages())
91
+ pack_version = str(getattr(pack, "__version__", "unknown"))
92
+ cache_dir = str(pack.cache_dir())
93
+ error = ""
94
+ except Exception as exc:
95
+ available = set()
96
+ pack_version = "unavailable"
97
+ cache_dir = ""
98
+ error = f"{type(exc).__name__}: {exc}"
99
+ rows = []
100
+ for spec in LANGUAGE_SPECS:
101
+ required = (spec.grammar, *spec.embedded)
102
+ missing = sorted(set(required) - available)
103
+ rows.append({
104
+ "language": spec.name,
105
+ "grammar": spec.grammar,
106
+ "required_grammars": list(required),
107
+ "missing_grammars": missing,
108
+ "available": not missing,
109
+ "extensions": list(spec.extensions),
110
+ "embedded": list(spec.embedded),
111
+ })
112
+ missing_required = sorted({
113
+ grammar
114
+ for spec in LANGUAGE_SPECS
115
+ for grammar in (spec.grammar, *spec.embedded)
116
+ if grammar not in available
117
+ })
118
+ companion_installed = bool(companion.get("installed"))
119
+ companion_ok = bool(companion.get("ok"))
120
+ if missing_required and not companion_installed:
121
+ action = (
122
+ "Install the platform-matched devcouncil-codeintel-grammars wheel; "
123
+ "runtime grammar downloads are disabled."
124
+ )
125
+ elif missing_required and not companion_ok:
126
+ action = (
127
+ "Reinstall the platform-matched devcouncil-codeintel-grammars wheel; "
128
+ "its manifest or checksums are invalid."
129
+ )
130
+ elif missing_required:
131
+ action = (
132
+ "The installed companion wheel is incomplete for this platform; "
133
+ "install a wheel containing every required grammar."
134
+ )
135
+ else:
136
+ action = ""
137
+ return {
138
+ "ok": all(row["available"] for row in rows),
139
+ "pack_version": pack_version,
140
+ "cache_dir": cache_dir,
141
+ "available_count": sum(bool(row["available"]) for row in rows),
142
+ "required_count": len(rows),
143
+ "languages": rows,
144
+ "missing_grammars": missing_required,
145
+ "action": action,
146
+ "error": error,
147
+ "downloaded_at_runtime": False,
148
+ "companion": companion,
149
+ }
@@ -0,0 +1,245 @@
1
+ """Crash-isolated native Tree-sitter worker pool."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import atexit
6
+ import multiprocessing
7
+ import os
8
+ import re
9
+ import threading
10
+ from concurrent.futures import ProcessPoolExecutor
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+ _ACTIVATION_LOCK = threading.Lock()
15
+ _ACTIVATION_ATTEMPTED = False
16
+ _ACTIVATION_STATUS: dict[str, Any] = {"installed": False, "activated": False}
17
+
18
+
19
+ def _activate_companion_once() -> dict[str, Any]:
20
+ """Activate packaged assets once per process without fetching grammars."""
21
+
22
+ global _ACTIVATION_ATTEMPTED, _ACTIVATION_STATUS
23
+ with _ACTIVATION_LOCK:
24
+ if _ACTIVATION_ATTEMPTED:
25
+ return _ACTIVATION_STATUS
26
+ _ACTIVATION_ATTEMPTED = True
27
+ try:
28
+ import devcouncil_codeintel_grammars as grammar_assets
29
+ except ImportError:
30
+ return _ACTIVATION_STATUS
31
+ try:
32
+ _ACTIVATION_STATUS = {"installed": True, **grammar_assets.activate()}
33
+ except Exception as exc:
34
+ _ACTIVATION_STATUS = {
35
+ "installed": True,
36
+ "activated": False,
37
+ "ok": False,
38
+ "error": f"{type(exc).__name__}: {exc}",
39
+ }
40
+ return _ACTIVATION_STATUS
41
+
42
+
43
+ def _structure_row(item: Any) -> dict[str, Any]:
44
+ span = getattr(item, "span", None)
45
+ return {
46
+ "name": str(getattr(item, "name", "") or ""),
47
+ "kind": str(getattr(item, "kind", "") or ""),
48
+ "start_line": int(getattr(span, "start_line", 0)) if span is not None else 0,
49
+ "end_line": int(getattr(span, "end_line", 0)) if span is not None else 0,
50
+ "decorators": [str(value) for value in getattr(item, "decorators", []) or []],
51
+ "children": [_structure_row(child) for child in getattr(item, "children", []) or []],
52
+ }
53
+
54
+
55
+ def _declared_name(node: Any, raw: bytes) -> str:
56
+ """Resolve a definition's identifier through C-family declarator nesting."""
57
+ current = node
58
+ for _ in range(8):
59
+ candidate = None
60
+ for field in ("name", "declarator"):
61
+ try:
62
+ candidate = current.child_by_field_name(field)
63
+ except (AttributeError, TypeError):
64
+ candidate = None
65
+ if candidate is not None:
66
+ break
67
+ if candidate is None:
68
+ return ""
69
+ if str(getattr(candidate, "type", "")).endswith("identifier"):
70
+ return raw[candidate.start_byte:candidate.end_byte].decode("utf-8", errors="replace")
71
+ current = candidate
72
+ return ""
73
+
74
+
75
+ def _fill_missing_structure_names(tree: Any, raw: bytes, rows: list[dict[str, Any]]) -> None:
76
+ """The language pack's structure pass yields ``name=None`` for C-family
77
+ definitions — the identifier hides inside the declarator chain — and
78
+ downstream extraction drops nameless rows, losing every C/C++/ObjC symbol.
79
+ Recover names from the parse tree, matched by start line."""
80
+ pending: dict[int, list[dict[str, Any]]] = {}
81
+
82
+ def collect(items: list[dict[str, Any]]) -> None:
83
+ for row in items:
84
+ if not row["name"]:
85
+ pending.setdefault(int(row["start_line"]), []).append(row)
86
+ collect(row["children"])
87
+
88
+ collect(rows)
89
+ if not pending:
90
+ return
91
+ stack = [tree.root_node]
92
+ while stack:
93
+ node = stack.pop()
94
+ stack.extend(reversed(list(getattr(node, "children", ()) or ())))
95
+ point = getattr(node, "start_point", None)
96
+ waiting = pending.get(int(point[0])) if point is not None else None
97
+ if not waiting:
98
+ continue
99
+ name = _declared_name(node, raw)
100
+ if not name:
101
+ continue
102
+ row = waiting.pop(0)
103
+ row["name"] = name
104
+
105
+
106
+ def _call_rows(parser: Any, source: str, tree: Any | None = None) -> list[dict[str, Any]]:
107
+ raw = source.encode("utf-8")
108
+ if tree is None:
109
+ tree = parser.parse(raw)
110
+ rows: list[dict[str, Any]] = []
111
+ stack = [tree.root_node]
112
+ while stack:
113
+ node = stack.pop()
114
+ stack.extend(reversed(list(getattr(node, "children", ()) or ())))
115
+ node_type = str(getattr(node, "type", ""))
116
+ if "call" not in node_type or any(
117
+ value in node_type for value in ("callable", "calling_convention")
118
+ ):
119
+ continue
120
+ callee = None
121
+ for field in ("function", "name", "method"):
122
+ try:
123
+ callee = node.child_by_field_name(field)
124
+ except (AttributeError, TypeError):
125
+ callee = None
126
+ if callee is not None:
127
+ break
128
+ if callee is None:
129
+ named = list(getattr(node, "named_children", ()) or ())
130
+ callee = named[0] if named else None
131
+ if callee is None:
132
+ continue
133
+ text = raw[callee.start_byte:callee.end_byte].decode("utf-8", errors="replace")
134
+ identifiers = re.findall(r"[A-Za-z_$][\w$]*", text)
135
+ if not identifiers:
136
+ continue
137
+ name = identifiers[-1]
138
+ receiver = ".".join(identifiers[:-1])
139
+ rows.append({
140
+ "name": name,
141
+ "receiver": receiver,
142
+ "line": int(node.start_point[0]) + 1,
143
+ })
144
+ return rows
145
+
146
+
147
+ def _native_process(language: str, source: str) -> dict[str, Any] | None:
148
+ """Execute inside a child so a faulty native grammar cannot kill the server."""
149
+ import tree_sitter_language_pack as pack
150
+
151
+ if language not in set(pack.available_languages()):
152
+ return None
153
+ result = pack.process(
154
+ source,
155
+ pack.ProcessConfig(
156
+ language=language,
157
+ structure=True,
158
+ imports=True,
159
+ exports=True,
160
+ symbols=True,
161
+ diagnostics=True,
162
+ ),
163
+ )
164
+ parser = pack.get_parser(language)
165
+ raw = source.encode("utf-8")
166
+ tree = parser.parse(raw)
167
+ structure = [_structure_row(item) for item in getattr(result, "structure", []) or []]
168
+ _fill_missing_structure_names(tree, raw, structure)
169
+ return {
170
+ "structure": structure,
171
+ "imports": [
172
+ {
173
+ "source": str(getattr(item, "source", "") or ""),
174
+ "items": [str(value) for value in getattr(item, "items", []) or []],
175
+ "alias": str(getattr(item, "alias", "") or ""),
176
+ }
177
+ for item in getattr(result, "imports", []) or []
178
+ ],
179
+ "exports": [
180
+ {"name": str(getattr(item, "name", "") or "")}
181
+ for item in getattr(result, "exports", []) or []
182
+ ],
183
+ "calls": _call_rows(parser, source, tree),
184
+ }
185
+
186
+
187
+ class ParserWorkerPool:
188
+ """Small spawn-based pool that is discarded after a crash or timeout."""
189
+
190
+ def __init__(self, *, max_workers: int | None = None, timeout: float = 30.0):
191
+ cpu_bound = max(1, (os.cpu_count() or 2) // 2)
192
+ self.max_workers = max(1, min(4, max_workers or cpu_bound))
193
+ self.timeout = max(1.0, timeout)
194
+ self._pool: ProcessPoolExecutor | None = None
195
+ self._lock = threading.Lock()
196
+
197
+ def process(self, language: str, source: str) -> dict[str, Any] | None:
198
+ try:
199
+ pool = self._executor()
200
+ return pool.submit(_native_process, language, source).result(timeout=self.timeout)
201
+ except Exception:
202
+ self.restart()
203
+ return None
204
+
205
+ def _executor(self) -> ProcessPoolExecutor:
206
+ with self._lock:
207
+ if self._pool is None:
208
+ _activate_companion_once()
209
+ self._pool = ProcessPoolExecutor(
210
+ max_workers=self.max_workers,
211
+ mp_context=multiprocessing.get_context("spawn"),
212
+ initializer=_activate_companion_once,
213
+ )
214
+ return self._pool
215
+
216
+ def restart(self) -> None:
217
+ with self._lock:
218
+ pool, self._pool = self._pool, None
219
+ if pool is not None:
220
+ pool.shutdown(wait=False, cancel_futures=True)
221
+
222
+ def close(self) -> None:
223
+ with self._lock:
224
+ pool, self._pool = self._pool, None
225
+ if pool is not None:
226
+ pool.shutdown(wait=True, cancel_futures=True)
227
+
228
+
229
+ _POOL = ParserWorkerPool()
230
+ atexit.register(_POOL.close)
231
+
232
+
233
+ def process_tree_sitter(language: str, source: str) -> dict[str, Any] | None:
234
+ return _POOL.process(language, source)
235
+
236
+
237
+ def parser_worker_status() -> dict[str, object]:
238
+ return {
239
+ "start_method": "spawn",
240
+ "max_workers": _POOL.max_workers,
241
+ "timeout_seconds": _POOL.timeout,
242
+ "pid": os.getpid(),
243
+ "module": str(Path(__file__).resolve()),
244
+ "grammar_companion": dict(_ACTIVATION_STATUS),
245
+ }
@@ -0,0 +1,5 @@
1
+ """High-level code-intelligence queries."""
2
+
3
+ from devcouncil.codeintel.query.engine import CodeIntelQueryEngine
4
+
5
+ __all__ = ["CodeIntelQueryEngine"]