alysis-code 0.13.0__py3-none-any.whl

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 (458) hide show
  1. alysis_code/__init__.py +3 -0
  2. alysis_code/__main__.py +4 -0
  3. alysis_code/_build_info.py +14 -0
  4. alysis_code/account_login.py +468 -0
  5. alysis_code/agent/README.md +35 -0
  6. alysis_code/agent/__init__.py +11 -0
  7. alysis_code/agent/acceptance_contract.py +2217 -0
  8. alysis_code/agent/blast_radius.py +1403 -0
  9. alysis_code/agent/cache_keepalive.py +227 -0
  10. alysis_code/agent/completion_certificate.py +366 -0
  11. alysis_code/agent/completion_gate.py +306 -0
  12. alysis_code/agent/empty_response_stall.py +403 -0
  13. alysis_code/agent/errors.py +28 -0
  14. alysis_code/agent/llm_calls.py +475 -0
  15. alysis_code/agent/mutation_classification.py +227 -0
  16. alysis_code/agent/prompt_context.py +2508 -0
  17. alysis_code/agent/read_ledger.py +253 -0
  18. alysis_code/agent/regression_baseline.py +642 -0
  19. alysis_code/agent/reproduction_first.py +610 -0
  20. alysis_code/agent/sensitive_output.py +629 -0
  21. alysis_code/agent/session.py +3218 -0
  22. alysis_code/agent/steering.py +191 -0
  23. alysis_code/agent/subagent_execution.py +5177 -0
  24. alysis_code/agent/subagent_workspace.py +666 -0
  25. alysis_code/agent/tools_assembly.py +4728 -0
  26. alysis_code/agent/turn/__init__.py +101 -0
  27. alysis_code/agent/turn/core.py +8483 -0
  28. alysis_code/agent/turn/events.py +113 -0
  29. alysis_code/agent/turn/exploration.py +590 -0
  30. alysis_code/agent/turn/interventions.py +65 -0
  31. alysis_code/agent/turn/read_cache.py +420 -0
  32. alysis_code/agent/turn/snapshot.py +179 -0
  33. alysis_code/agent/turn_contract.py +661 -0
  34. alysis_code/agent/turn_path.py +129 -0
  35. alysis_code/agent/verification.py +2885 -0
  36. alysis_code/agent/verification_commands.py +512 -0
  37. alysis_code/agent/verification_evidence.py +738 -0
  38. alysis_code/agent_loop.py +693 -0
  39. alysis_code/agent_runtimes/__init__.py +51 -0
  40. alysis_code/agent_runtimes/base.py +114 -0
  41. alysis_code/agent_runtimes/builtins.py +129 -0
  42. alysis_code/agent_runtimes/codex_cli.py +664 -0
  43. alysis_code/agent_runtimes/host.py +263 -0
  44. alysis_code/agent_runtimes/registry.py +64 -0
  45. alysis_code/agent_runtimes/service.py +150 -0
  46. alysis_code/agentbox_client.py +416 -0
  47. alysis_code/agentbox_integration.py +310 -0
  48. alysis_code/alysis_cloud.py +152 -0
  49. alysis_code/approval_scope.py +276 -0
  50. alysis_code/assets/README.md +33 -0
  51. alysis_code/assets/__init__.py +126 -0
  52. alysis_code/assets/asset_read_core.py +281 -0
  53. alysis_code/assets/budget_allocator.py +456 -0
  54. alysis_code/assets/comprehender.py +759 -0
  55. alysis_code/assets/index.py +654 -0
  56. alysis_code/assets/ingestion.py +275 -0
  57. alysis_code/assets/legacy_migration.py +413 -0
  58. alysis_code/assets/models.py +263 -0
  59. alysis_code/assets/ocr.py +239 -0
  60. alysis_code/assets/owl/ascii/f-000.txt +13 -0
  61. alysis_code/assets/owl/ascii/f-001.txt +13 -0
  62. alysis_code/assets/owl/ascii/f-002.txt +13 -0
  63. alysis_code/assets/owl/ascii/f-003.txt +13 -0
  64. alysis_code/assets/owl/ascii/f-004.txt +13 -0
  65. alysis_code/assets/owl/ascii/f-005.txt +13 -0
  66. alysis_code/assets/owl/ascii/f-006.txt +13 -0
  67. alysis_code/assets/owl/ascii/f-007.txt +13 -0
  68. alysis_code/assets/owl/ascii/f-008.txt +13 -0
  69. alysis_code/assets/owl/ascii/f-009.txt +13 -0
  70. alysis_code/assets/owl/ascii/f-010.txt +13 -0
  71. alysis_code/assets/owl/ascii/f-011.txt +13 -0
  72. alysis_code/assets/owl/ascii/f-012.txt +13 -0
  73. alysis_code/assets/owl/ascii/f-013.txt +13 -0
  74. alysis_code/assets/owl/ascii/f-014.txt +13 -0
  75. alysis_code/assets/owl/ascii/f-015.txt +13 -0
  76. alysis_code/assets/owl/ascii/f-016.txt +13 -0
  77. alysis_code/assets/owl/ascii/f-017.txt +13 -0
  78. alysis_code/assets/owl/ascii/f-018.txt +13 -0
  79. alysis_code/assets/owl/ascii/f-019.txt +13 -0
  80. alysis_code/assets/owl/ascii/f-020.txt +13 -0
  81. alysis_code/assets/owl/index.html +98 -0
  82. alysis_code/assets/owl/show-owl.sh +761 -0
  83. alysis_code/assets/paths.py +49 -0
  84. alysis_code/assets/plan_binding.py +326 -0
  85. alysis_code/assets/planner_context.py +466 -0
  86. alysis_code/assets/planner_tools.py +184 -0
  87. alysis_code/assets/prompts.py +101 -0
  88. alysis_code/assets/replanner_context.py +239 -0
  89. alysis_code/assets/surface.py +521 -0
  90. alysis_code/assets/untrusted_content.py +48 -0
  91. alysis_code/assets/usage_logger.py +94 -0
  92. alysis_code/assets/worker_mirror.py +428 -0
  93. alysis_code/assets/worker_section.py +303 -0
  94. alysis_code/assets/worker_tools.py +468 -0
  95. alysis_code/atomic_io.py +83 -0
  96. alysis_code/auth_diagnostics.py +272 -0
  97. alysis_code/background_runner.py +366 -0
  98. alysis_code/branding.py +270 -0
  99. alysis_code/budget_policy.py +390 -0
  100. alysis_code/build_identity.py +465 -0
  101. alysis_code/builtin_hooks/__init__.py +7 -0
  102. alysis_code/builtin_hooks/notify_done_windows.py +65 -0
  103. alysis_code/bwrap_etc.py +76 -0
  104. alysis_code/cancellation.py +41 -0
  105. alysis_code/capabilities.py +137 -0
  106. alysis_code/chatgpt_codex_static_provider.py +133 -0
  107. alysis_code/cli.py +51 -0
  108. alysis_code/cli_impl/__init__.py +1 -0
  109. alysis_code/cli_impl/assets_cli.py +537 -0
  110. alysis_code/cli_impl/assets_modal.py +412 -0
  111. alysis_code/cli_impl/chat/__init__.py +156 -0
  112. alysis_code/cli_impl/chat/commands.py +2616 -0
  113. alysis_code/cli_impl/chat/loop.py +4508 -0
  114. alysis_code/cli_impl/chat/mid_turn_policy.py +125 -0
  115. alysis_code/cli_impl/chat/rendering.py +444 -0
  116. alysis_code/cli_impl/chat/state.py +124 -0
  117. alysis_code/cli_impl/chat_resume.py +830 -0
  118. alysis_code/cli_impl/chat_slash_completer.py +258 -0
  119. alysis_code/cli_impl/commands/__init__.py +11 -0
  120. alysis_code/cli_impl/commands/_shared.py +89 -0
  121. alysis_code/cli_impl/commands/auth.py +623 -0
  122. alysis_code/cli_impl/commands/chat_resume_helpers.py +1531 -0
  123. alysis_code/cli_impl/commands/chat_state.py +158 -0
  124. alysis_code/cli_impl/commands/chat_status.py +1248 -0
  125. alysis_code/cli_impl/commands/chat_terminal.py +942 -0
  126. alysis_code/cli_impl/commands/chat_tui_panels.py +1018 -0
  127. alysis_code/cli_impl/commands/cli_common.py +1223 -0
  128. alysis_code/cli_impl/commands/cli_surface.py +77 -0
  129. alysis_code/cli_impl/commands/config.py +131 -0
  130. alysis_code/cli_impl/commands/conventions.py +85 -0
  131. alysis_code/cli_impl/commands/execution_helpers.py +350 -0
  132. alysis_code/cli_impl/commands/extensions.py +401 -0
  133. alysis_code/cli_impl/commands/forge.py +1282 -0
  134. alysis_code/cli_impl/commands/forge_asset_view.py +121 -0
  135. alysis_code/cli_impl/commands/forge_helpers.py +1215 -0
  136. alysis_code/cli_impl/commands/hooks.py +737 -0
  137. alysis_code/cli_impl/commands/ide_bridge.py +31 -0
  138. alysis_code/cli_impl/commands/mcp.py +700 -0
  139. alysis_code/cli_impl/commands/profile.py +453 -0
  140. alysis_code/cli_impl/commands/prompt_helpers.py +307 -0
  141. alysis_code/cli_impl/commands/report.py +88 -0
  142. alysis_code/cli_impl/commands/root.py +1118 -0
  143. alysis_code/cli_impl/commands/sandbox.py +184 -0
  144. alysis_code/cli_impl/commands/server.py +54 -0
  145. alysis_code/cli_impl/commands/sessions.py +252 -0
  146. alysis_code/cli_impl/commands/skills.py +404 -0
  147. alysis_code/cli_impl/commands/startup.py +946 -0
  148. alysis_code/cli_impl/commands/tools.py +335 -0
  149. alysis_code/cli_impl/commands/update.py +364 -0
  150. alysis_code/cli_impl/commands/welcome.py +972 -0
  151. alysis_code/cli_impl/config_menu.py +3882 -0
  152. alysis_code/cli_impl/forge.py +4509 -0
  153. alysis_code/cli_impl/forge_recovery.py +485 -0
  154. alysis_code/cli_impl/setup_wizard.py +2409 -0
  155. alysis_code/cli_impl/tui/__init__.py +58 -0
  156. alysis_code/cli_impl/tui/app.py +4551 -0
  157. alysis_code/cli_impl/tui/config.py +32 -0
  158. alysis_code/cli_impl/tui/config_flow.py +2754 -0
  159. alysis_code/cli_impl/tui/config_overlay.py +566 -0
  160. alysis_code/cli_impl/tui/content.py +78 -0
  161. alysis_code/cli_impl/tui/footer.py +218 -0
  162. alysis_code/cli_impl/tui/forge_status.py +136 -0
  163. alysis_code/cli_impl/tui/markdown.py +244 -0
  164. alysis_code/cli_impl/tui/owl.py +109 -0
  165. alysis_code/cli_impl/tui/plan_meta.py +477 -0
  166. alysis_code/cli_impl/tui/setup_app.py +519 -0
  167. alysis_code/cli_impl/tui/setup_flow.py +1622 -0
  168. alysis_code/cli_impl/tui/state.py +101 -0
  169. alysis_code/cli_impl/tui/subagent_identity.py +66 -0
  170. alysis_code/cli_impl/tui/subagent_panel.py +186 -0
  171. alysis_code/cli_impl/tui/surface.py +796 -0
  172. alysis_code/cli_impl/tui/transcript.py +514 -0
  173. alysis_code/cli_impl/tui/update_prompt.py +79 -0
  174. alysis_code/cli_impl/tui/workspace_guard.py +384 -0
  175. alysis_code/clipboard.py +172 -0
  176. alysis_code/code_review.py +1211 -0
  177. alysis_code/compaction/__init__.py +28 -0
  178. alysis_code/compaction/conversation_compactor.py +2932 -0
  179. alysis_code/compaction/importance.py +177 -0
  180. alysis_code/compaction/settings.py +297 -0
  181. alysis_code/compaction/tool_output_offload.py +447 -0
  182. alysis_code/config.py +3509 -0
  183. alysis_code/conflict_auto_resolver.py +895 -0
  184. alysis_code/context/__init__.py +1 -0
  185. alysis_code/context/tool_schema_budgeter.py +220 -0
  186. alysis_code/crash_diagnostics.py +282 -0
  187. alysis_code/custom_tools/README.md +34 -0
  188. alysis_code/custom_tools/__init__.py +43 -0
  189. alysis_code/custom_tools/discovery.py +903 -0
  190. alysis_code/custom_tools/runtime.py +1516 -0
  191. alysis_code/custom_tools/session.py +227 -0
  192. alysis_code/custom_tools/trust.py +232 -0
  193. alysis_code/diff_paths.py +113 -0
  194. alysis_code/direction_change.py +293 -0
  195. alysis_code/dispatch_timing.py +306 -0
  196. alysis_code/durable_service_manager.py +1236 -0
  197. alysis_code/edit_discipline.py +659 -0
  198. alysis_code/error_text.py +73 -0
  199. alysis_code/execution_budget.py +411 -0
  200. alysis_code/execution_context.py +915 -0
  201. alysis_code/execution_deadline.py +1065 -0
  202. alysis_code/execution_shared.py +1904 -0
  203. alysis_code/extensions/README.md +30 -0
  204. alysis_code/extensions/__init__.py +93 -0
  205. alysis_code/extensions/activation.py +138 -0
  206. alysis_code/extensions/install.py +1436 -0
  207. alysis_code/extensions/manifest.py +487 -0
  208. alysis_code/extensions/models.py +74 -0
  209. alysis_code/extensions/paths.py +56 -0
  210. alysis_code/extensions/registry.json +4 -0
  211. alysis_code/extensions/registry.py +52 -0
  212. alysis_code/extensions/state.py +83 -0
  213. alysis_code/extensions/workspace_trust.py +101 -0
  214. alysis_code/failed_task_evidence.py +369 -0
  215. alysis_code/failure_category.py +315 -0
  216. alysis_code/feedback_report.py +1647 -0
  217. alysis_code/file_classification.py +485 -0
  218. alysis_code/forge.py +2064 -0
  219. alysis_code/forge_completion.py +362 -0
  220. alysis_code/forge_events.py +475 -0
  221. alysis_code/frontmatter_utils.py +95 -0
  222. alysis_code/git_evidence.py +1181 -0
  223. alysis_code/git_ops.py +560 -0
  224. alysis_code/git_safe.py +62 -0
  225. alysis_code/git_worktrees.py +190 -0
  226. alysis_code/hooks/README.md +33 -0
  227. alysis_code/hooks/__init__.py +67 -0
  228. alysis_code/hooks/audit.py +171 -0
  229. alysis_code/hooks/config.py +225 -0
  230. alysis_code/hooks/dispatcher.py +1110 -0
  231. alysis_code/hooks/models.py +447 -0
  232. alysis_code/hooks/trust.py +202 -0
  233. alysis_code/host_actions.py +543 -0
  234. alysis_code/host_browser.py +103 -0
  235. alysis_code/ide/__init__.py +5 -0
  236. alysis_code/ide/activity_events.py +399 -0
  237. alysis_code/ide/approvals.py +337 -0
  238. alysis_code/ide/artifacts.py +153 -0
  239. alysis_code/ide/browser_egress_proxy.py +1076 -0
  240. alysis_code/ide/cdp_websocket_transport.py +1192 -0
  241. alysis_code/ide/change_ledger.py +1721 -0
  242. alysis_code/ide/context_blocks.py +979 -0
  243. alysis_code/ide/event_stream.py +531 -0
  244. alysis_code/ide/forge_protocol.py +3112 -0
  245. alysis_code/ide/forge_request_ledger.py +737 -0
  246. alysis_code/ide/health.py +965 -0
  247. alysis_code/ide/managed_browser.py +2251 -0
  248. alysis_code/ide/management_protocol.py +3414 -0
  249. alysis_code/ide/mcp_oauth_coordinator.py +744 -0
  250. alysis_code/ide/mcp_oauth_lifecycle.py +1504 -0
  251. alysis_code/ide/prompt_queue.py +1070 -0
  252. alysis_code/ide/protocol.py +191 -0
  253. alysis_code/ide/resumable_swarm.py +1543 -0
  254. alysis_code/ide/session_search.py +295 -0
  255. alysis_code/ide/stdio_bridge.py +9935 -0
  256. alysis_code/ide/structured_state.py +1579 -0
  257. alysis_code/ide/swarm_protocol.py +816 -0
  258. alysis_code/integration_gate.py +506 -0
  259. alysis_code/interactive_input_guard.py +39 -0
  260. alysis_code/interactive_plan_mode.py +26 -0
  261. alysis_code/internal_artifacts.py +179 -0
  262. alysis_code/knowledge_base.py +1409 -0
  263. alysis_code/knowledge_capture.py +1190 -0
  264. alysis_code/knowledge_librarian.py +605 -0
  265. alysis_code/language_policy.py +34 -0
  266. alysis_code/litellm_static_provider.py +535 -0
  267. alysis_code/llm/__init__.py +1 -0
  268. alysis_code/llm/anthropic_messages.py +2288 -0
  269. alysis_code/llm/base.py +71 -0
  270. alysis_code/llm/cache_capabilities.py +985 -0
  271. alysis_code/llm/cache_control_blocks.py +244 -0
  272. alysis_code/llm/cache_policy.py +388 -0
  273. alysis_code/llm/factory.py +373 -0
  274. alysis_code/llm/gemini_generate_content.py +2652 -0
  275. alysis_code/llm/gemini_interactions.py +739 -0
  276. alysis_code/llm/metadata.py +450 -0
  277. alysis_code/llm/openai_compat.py +2947 -0
  278. alysis_code/llm/openai_responses.py +2604 -0
  279. alysis_code/llm/protocols.py +609 -0
  280. alysis_code/llm/provider_limits.py +525 -0
  281. alysis_code/llm/request_plan.py +389 -0
  282. alysis_code/llm/request_shape.py +238 -0
  283. alysis_code/llm/streaming.py +108 -0
  284. alysis_code/llm/temperature_compat.py +78 -0
  285. alysis_code/llm/types.py +195 -0
  286. alysis_code/llm/usage_normalization.py +222 -0
  287. alysis_code/llm_error_display.py +315 -0
  288. alysis_code/logging_redaction.py +326 -0
  289. alysis_code/managed_host_deadline.py +196 -0
  290. alysis_code/mcp/README.md +33 -0
  291. alysis_code/mcp/__init__.py +24 -0
  292. alysis_code/mcp/client.py +1137 -0
  293. alysis_code/mcp/config.py +597 -0
  294. alysis_code/mcp/errors.py +113 -0
  295. alysis_code/mcp/forge_scope.py +154 -0
  296. alysis_code/mcp/jsonrpc.py +214 -0
  297. alysis_code/mcp/manager.py +2308 -0
  298. alysis_code/mcp/models.py +666 -0
  299. alysis_code/mcp/oauth.py +972 -0
  300. alysis_code/mcp/oauth_runtime.py +310 -0
  301. alysis_code/mcp/oauth_store.py +276 -0
  302. alysis_code/mcp/prompts.py +329 -0
  303. alysis_code/mcp/resources.py +295 -0
  304. alysis_code/mcp/roots.py +106 -0
  305. alysis_code/mcp/server_requests.py +75 -0
  306. alysis_code/mcp/token_store.py +859 -0
  307. alysis_code/mcp/transport_http.py +1338 -0
  308. alysis_code/mcp/transport_stdio.py +1267 -0
  309. alysis_code/mcp/untrusted_content.py +119 -0
  310. alysis_code/merge_conflict_reviewer.py +729 -0
  311. alysis_code/model_catalog/__init__.py +1 -0
  312. alysis_code/model_catalog/chatgpt_codex_subscription_snapshot.json +186 -0
  313. alysis_code/model_catalog/litellm_model_prices_snapshot.json +44715 -0
  314. alysis_code/model_catalog/litellm_model_prices_snapshot.meta.json +17 -0
  315. alysis_code/model_metadata_policy.py +223 -0
  316. alysis_code/model_metadata_utils.py +103 -0
  317. alysis_code/model_registry.py +1420 -0
  318. alysis_code/model_router.py +147 -0
  319. alysis_code/permission_policy.py +1016 -0
  320. alysis_code/personas.py +451 -0
  321. alysis_code/pipeline_facts.py +233 -0
  322. alysis_code/plan_assistant.py +4763 -0
  323. alysis_code/plan_mode.py +393 -0
  324. alysis_code/plan_reconciliation.py +1228 -0
  325. alysis_code/plan_repair.py +652 -0
  326. alysis_code/plan_validation.py +1099 -0
  327. alysis_code/planning_constraints.py +904 -0
  328. alysis_code/policy.py +95 -0
  329. alysis_code/preview_server.py +457 -0
  330. alysis_code/process_reaping.py +566 -0
  331. alysis_code/profile_presets.py +1834 -0
  332. alysis_code/profiles.py +666 -0
  333. alysis_code/provider_auth/__init__.py +29 -0
  334. alysis_code/provider_auth/base.py +99 -0
  335. alysis_code/provider_auth/openai_codex.py +951 -0
  336. alysis_code/provider_auth/registry.py +76 -0
  337. alysis_code/provider_auth/store.py +125 -0
  338. alysis_code/provider_diagnostics.py +1209 -0
  339. alysis_code/provider_model_catalog.py +685 -0
  340. alysis_code/provider_telemetry.py +1699 -0
  341. alysis_code/provider_url.py +75 -0
  342. alysis_code/reasoning_contracts.py +911 -0
  343. alysis_code/remote_sync.py +350 -0
  344. alysis_code/replanning.py +1195 -0
  345. alysis_code/repo_scan.py +1152 -0
  346. alysis_code/request_estimation.py +296 -0
  347. alysis_code/review_gate.py +617 -0
  348. alysis_code/run_lock.py +1141 -0
  349. alysis_code/run_outcome.py +58 -0
  350. alysis_code/run_provenance.py +774 -0
  351. alysis_code/run_state.py +445 -0
  352. alysis_code/runtime_artifacts.py +116 -0
  353. alysis_code/runtime_context_features.py +78 -0
  354. alysis_code/runtime_kind.py +52 -0
  355. alysis_code/safety/__init__.py +11 -0
  356. alysis_code/safety/mcp_sanitize.py +29 -0
  357. alysis_code/safety/safe_http.py +297 -0
  358. alysis_code/safety/subagent_report.py +184 -0
  359. alysis_code/sandbox_doctor.py +682 -0
  360. alysis_code/sandbox_runner.py +1025 -0
  361. alysis_code/sandbox_settings.py +423 -0
  362. alysis_code/serialized_paths.py +355 -0
  363. alysis_code/server/__init__.py +3 -0
  364. alysis_code/server/app.py +367 -0
  365. alysis_code/server/auth.py +34 -0
  366. alysis_code/server/job_config.py +30 -0
  367. alysis_code/server/settings.py +215 -0
  368. alysis_code/server/store.py +193 -0
  369. alysis_code/server/worker_runner.py +657 -0
  370. alysis_code/service_persistence.py +355 -0
  371. alysis_code/session_artifacts.py +108 -0
  372. alysis_code/session_metrics.py +331 -0
  373. alysis_code/session_store.py +624 -0
  374. alysis_code/skills/README.md +34 -0
  375. alysis_code/skills/__init__.py +104 -0
  376. alysis_code/skills/conventions.py +84 -0
  377. alysis_code/skills/discovery.py +176 -0
  378. alysis_code/skills/eval_models.py +232 -0
  379. alysis_code/skills/eval_runner.py +372 -0
  380. alysis_code/skills/evals.py +1344 -0
  381. alysis_code/skills/install.py +293 -0
  382. alysis_code/skills/loader.py +118 -0
  383. alysis_code/skills/matching.py +103 -0
  384. alysis_code/skills/models.py +71 -0
  385. alysis_code/skills/paths.py +56 -0
  386. alysis_code/skills/prompting.py +500 -0
  387. alysis_code/skills/scaffold.py +142 -0
  388. alysis_code/skills/state.py +441 -0
  389. alysis_code/skills/transactions.py +125 -0
  390. alysis_code/skills/validation.py +304 -0
  391. alysis_code/step_budget.py +238 -0
  392. alysis_code/subagent_labels.py +49 -0
  393. alysis_code/subagents.py +1072 -0
  394. alysis_code/surface/__init__.py +80 -0
  395. alysis_code/surface/base.py +305 -0
  396. alysis_code/surface/console.py +387 -0
  397. alysis_code/surface/events.py +372 -0
  398. alysis_code/surface/hidden_surface.py +529 -0
  399. alysis_code/surface/noop_surface.py +219 -0
  400. alysis_code/surface/rich_surface.py +1555 -0
  401. alysis_code/surface/styles.py +67 -0
  402. alysis_code/surface/theme.py +455 -0
  403. alysis_code/surface/types.py +100 -0
  404. alysis_code/swarm_backend.py +926 -0
  405. alysis_code/swarm_orchestrator.py +4020 -0
  406. alysis_code/swarm_scheduler.py +441 -0
  407. alysis_code/swarm_trace.py +429 -0
  408. alysis_code/swarm_worker.py +2119 -0
  409. alysis_code/swarm_write_guard.py +348 -0
  410. alysis_code/task_dependencies.py +170 -0
  411. alysis_code/task_readiness.py +992 -0
  412. alysis_code/task_scope.py +2148 -0
  413. alysis_code/terminal_manager.py +762 -0
  414. alysis_code/terminal_ownership.py +460 -0
  415. alysis_code/text_normalization.py +30 -0
  416. alysis_code/token_budget.py +97 -0
  417. alysis_code/tools/README.md +34 -0
  418. alysis_code/tools/__init__.py +1 -0
  419. alysis_code/tools/artifacts.py +127 -0
  420. alysis_code/tools/availability.py +188 -0
  421. alysis_code/tools/fs.py +1456 -0
  422. alysis_code/tools/git.py +461 -0
  423. alysis_code/tools/history.py +229 -0
  424. alysis_code/tools/http_timeout.py +78 -0
  425. alysis_code/tools/image_generation.py +552 -0
  426. alysis_code/tools/registry.py +2936 -0
  427. alysis_code/tools/repo_map.py +476 -0
  428. alysis_code/tools/search.py +563 -0
  429. alysis_code/tools/shell.py +135 -0
  430. alysis_code/tools/symbols.py +1350 -0
  431. alysis_code/tools/test_discovery.py +643 -0
  432. alysis_code/tools/web.py +482 -0
  433. alysis_code/tools/web_search.py +2012 -0
  434. alysis_code/tools/web_search_dashscope.py +557 -0
  435. alysis_code/tools/web_search_ddgs.py +221 -0
  436. alysis_code/tools/web_search_provider_adapters.py +1429 -0
  437. alysis_code/tools/web_search_tavily.py +194 -0
  438. alysis_code/updates.py +933 -0
  439. alysis_code/usage_tracker.py +1990 -0
  440. alysis_code/verification_command_analysis.py +1004 -0
  441. alysis_code/verification_contract.py +574 -0
  442. alysis_code/verification_failure_summary.py +273 -0
  443. alysis_code/verification_repair.py +385 -0
  444. alysis_code/verify_gate.py +3129 -0
  445. alysis_code/web_research.py +1872 -0
  446. alysis_code/web_search_adapters.py +66 -0
  447. alysis_code/web_search_policy.py +27 -0
  448. alysis_code/workspace_binding.py +389 -0
  449. alysis_code/workspace_binding_ui.py +408 -0
  450. alysis_code/workspace_context.py +273 -0
  451. alysis_code/workspace_isolation.py +138 -0
  452. alysis_code/workspace_provisioning.py +455 -0
  453. alysis_code-0.13.0.dist-info/METADATA +507 -0
  454. alysis_code-0.13.0.dist-info/RECORD +458 -0
  455. alysis_code-0.13.0.dist-info/WHEEL +4 -0
  456. alysis_code-0.13.0.dist-info/entry_points.txt +3 -0
  457. alysis_code-0.13.0.dist-info/licenses/LICENSE +176 -0
  458. alysis_code-0.13.0.dist-info/licenses/NOTICE +4 -0
@@ -0,0 +1,1350 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ import fnmatch
5
+ import os
6
+ import re
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from ..file_classification import CODE_SCAN_SKIP_DIR_NAMES, symbol_search_backend_for_path
12
+
13
+
14
+ class SymbolSearchError(RuntimeError):
15
+ pass
16
+
17
+
18
+ _SUPPORTED_KINDS = {"function", "class", "method", "constant"}
19
+ _DEFAULT_MAX_RESULTS = 100
20
+ _MAX_RESULTS = 200
21
+ _MAX_FILE_BYTES = 512 * 1024
22
+ _MAX_INLINE_CHARS = 240
23
+ _MAX_NOTE_PATHS = 3
24
+ _DEFAULT_SKIP_DIRS = set(CODE_SCAN_SKIP_DIR_NAMES)
25
+ _JS_TS_BLOCK_KEYWORDS = {"if", "for", "while", "switch", "catch", "function", "class"}
26
+ _JS_TS_FUNCTION_RE = re.compile(
27
+ r"^(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)\s*\("
28
+ )
29
+ _JS_TS_CLASS_RE = re.compile(r"^(?:export\s+)?(?:default\s+)?class\s+([A-Za-z_$][\w$]*)\b")
30
+ _JS_TS_CONST_DECL_RE = re.compile(r"^(?:export\s+)?const\s+([A-Za-z_$][\w$]*)\b(.*)$")
31
+ _JS_TS_FUNCTION_EXPR_RE = re.compile(r"^(?:async\s+)?function\b")
32
+ _JS_TS_METHOD_RE = re.compile(
33
+ r"^(?:(?:public|private|protected|static|async|readonly|override|get|set)\s+)*"
34
+ r"(?:\*\s*)?([A-Za-z_$][\w$]*)\s*(?:<[^>{}]*>)?\s*\("
35
+ )
36
+ _JS_TS_CLASS_FIELD_RE = re.compile(
37
+ r"^(?:(?:public|private|protected|readonly|static|async|declare|override)\s+)*"
38
+ r"([A-Za-z_$][\w$]*)\s*(?:\?)?(.*)$"
39
+ )
40
+ _JS_TS_PENDING_ASSIGNMENT_MAX_LINES = 8
41
+ _JAVA_IDENTIFIER = r"[A-Za-z_$][\w$]*"
42
+ _JAVA_ANNOTATION_PREFIX = r"(?:@[A-Za-z_$][\w$.]*(?:\([^)]*\))?\s+)*"
43
+ _JAVA_MODIFIER_PREFIX = (
44
+ r"(?:(?:public|protected|private|abstract|static|final|sealed|non-sealed|strictfp|"
45
+ r"synchronized|native|default|transient|volatile)\s+)*"
46
+ )
47
+ _JAVA_GENERIC_METHOD_PREFIX = r"(?:<[^;{}()]+>\s+)?"
48
+ _JAVA_DECL_PREFIX = _JAVA_ANNOTATION_PREFIX + _JAVA_MODIFIER_PREFIX
49
+ _JAVA_TYPE_RE = re.compile(
50
+ rf"^{_JAVA_DECL_PREFIX}(?P<kind>class|interface|enum|record)\s+"
51
+ rf"(?P<name>{_JAVA_IDENTIFIER})\b"
52
+ )
53
+ _JAVA_METHOD_RE = re.compile(
54
+ rf"^{_JAVA_DECL_PREFIX}{_JAVA_GENERIC_METHOD_PREFIX}"
55
+ rf"(?P<return>[\w$<>\[\].?,\s]+?)\s+(?P<name>{_JAVA_IDENTIFIER})\s*\("
56
+ )
57
+ _JAVA_CONSTRUCTOR_RE = re.compile(rf"^{_JAVA_DECL_PREFIX}(?P<name>{_JAVA_IDENTIFIER})\s*\(")
58
+ _JAVA_BLOCK_KEYWORDS = {
59
+ "catch",
60
+ "do",
61
+ "else",
62
+ "for",
63
+ "if",
64
+ "new",
65
+ "return",
66
+ "switch",
67
+ "synchronized",
68
+ "throw",
69
+ "try",
70
+ "while",
71
+ }
72
+
73
+
74
+ @dataclass(frozen=True)
75
+ class _SymbolEntry:
76
+ path: str
77
+ line: int
78
+ kind: str
79
+ name: str
80
+ simple_name: str
81
+ signature: str
82
+ end_line: int | None = None
83
+ parent: str = ""
84
+
85
+
86
+ @dataclass(frozen=True)
87
+ class _JsTsClassScope:
88
+ name: str
89
+ body_depth: int
90
+
91
+
92
+ @dataclass
93
+ class _JsTsPendingAssignment:
94
+ line: int
95
+ kind: str
96
+ name: str
97
+ simple_name: str
98
+ signature_lines: list[str]
99
+ rhs_fragments: list[str]
100
+ scope_depth: int
101
+ fallback_kind: str | None = None
102
+ parent: str = ""
103
+
104
+
105
+ @dataclass(frozen=True)
106
+ class _JavaTypeScope:
107
+ name: str
108
+ body_depth: int
109
+
110
+
111
+ def _clip_inline(text: str, *, max_chars: int = _MAX_INLINE_CHARS) -> str:
112
+ compact = " ".join(str(text).split())
113
+ if len(compact) <= max_chars:
114
+ return compact
115
+ return compact[:max_chars].rstrip() + "...(truncated)"
116
+
117
+
118
+ def _resolve_under_root(root: Path, user_path: str) -> Path:
119
+ root_abs = root.resolve()
120
+ target = (root_abs / user_path).resolve()
121
+ try:
122
+ target.relative_to(root_abs)
123
+ except ValueError as e:
124
+ raise SymbolSearchError(f"root_path escapes root: {user_path}") from e
125
+ return target
126
+
127
+
128
+ def _rel_path(root: Path, path: Path) -> str:
129
+ try:
130
+ rel = path.resolve().relative_to(root.resolve())
131
+ except ValueError:
132
+ return os.fspath(path).replace("\\", "/")
133
+ return os.fspath(rel).replace("\\", "/")
134
+
135
+
136
+ def _matches_globs(rel_path: str, globs: list[str] | None) -> bool:
137
+ if not globs:
138
+ return True
139
+ return any(fnmatch.fnmatchcase(rel_path, pattern) for pattern in globs)
140
+
141
+
142
+ def _iter_candidate_files(*, root: Path, base: Path, globs: list[str] | None) -> list[Path]:
143
+ if base.is_file():
144
+ rel = _rel_path(root, base)
145
+ return [base] if _matches_globs(rel, globs) else []
146
+
147
+ candidates: list[Path] = []
148
+ for current_root, dirnames, filenames in os.walk(base):
149
+ dirnames[:] = sorted(dirname for dirname in dirnames if dirname not in _DEFAULT_SKIP_DIRS)
150
+ for filename in sorted(filenames):
151
+ path = Path(current_root) / filename
152
+ rel = _rel_path(root, path)
153
+ if _matches_globs(rel, globs):
154
+ candidates.append(path)
155
+ return candidates
156
+
157
+
158
+ def _safe_unparse(node: ast.AST) -> str:
159
+ try:
160
+ return ast.unparse(node)
161
+ except Exception:
162
+ return "..."
163
+
164
+
165
+ def _format_arg(arg: ast.arg) -> str:
166
+ text = arg.arg
167
+ if arg.annotation is not None:
168
+ text += f": {_safe_unparse(arg.annotation)}"
169
+ return text
170
+
171
+
172
+ def _format_function_signature(
173
+ node: ast.FunctionDef | ast.AsyncFunctionDef,
174
+ *,
175
+ display_name: str,
176
+ ) -> str:
177
+ params: list[str] = []
178
+ posonlyargs = getattr(node.args, "posonlyargs", [])
179
+ for arg in posonlyargs:
180
+ params.append(_format_arg(arg))
181
+ if posonlyargs:
182
+ params.append("/")
183
+
184
+ for arg in node.args.args:
185
+ params.append(_format_arg(arg))
186
+
187
+ if node.args.vararg is not None:
188
+ params.append(f"*{_format_arg(node.args.vararg)}")
189
+ elif node.args.kwonlyargs:
190
+ params.append("*")
191
+
192
+ for arg in node.args.kwonlyargs:
193
+ params.append(_format_arg(arg))
194
+
195
+ if node.args.kwarg is not None:
196
+ params.append(f"**{_format_arg(node.args.kwarg)}")
197
+
198
+ prefix = "async def" if isinstance(node, ast.AsyncFunctionDef) else "def"
199
+ signature = f"{prefix} {display_name}({', '.join(params)})"
200
+ if node.returns is not None:
201
+ signature += f" -> {_safe_unparse(node.returns)}"
202
+ return _clip_inline(signature)
203
+
204
+
205
+ def _format_class_signature(node: ast.ClassDef) -> str:
206
+ bases = [_safe_unparse(base) for base in node.bases]
207
+ signature = f"class {node.name}"
208
+ if bases:
209
+ signature += f"({', '.join(bases)})"
210
+ return _clip_inline(signature)
211
+
212
+
213
+ def _extract_assign_names(target: ast.expr) -> list[str]:
214
+ if isinstance(target, ast.Name):
215
+ return [target.id]
216
+ if isinstance(target, (ast.Tuple, ast.List)):
217
+ names: list[str] = []
218
+ for element in target.elts:
219
+ names.extend(_extract_assign_names(element))
220
+ return names
221
+ return []
222
+
223
+
224
+ def _extract_constant_entries(
225
+ *,
226
+ rel_path: str,
227
+ node: ast.Assign | ast.AnnAssign,
228
+ source_lines: list[str],
229
+ ) -> list[_SymbolEntry]:
230
+ if isinstance(node, ast.Assign):
231
+ names: list[str] = []
232
+ for target in node.targets:
233
+ names.extend(_extract_assign_names(target))
234
+ else:
235
+ names = _extract_assign_names(node.target)
236
+
237
+ signature = ""
238
+ if 1 <= node.lineno <= len(source_lines):
239
+ signature = _clip_inline(source_lines[node.lineno - 1].strip())
240
+
241
+ out: list[_SymbolEntry] = []
242
+ for name in names:
243
+ if not name.isupper():
244
+ continue
245
+ out.append(
246
+ _SymbolEntry(
247
+ path=rel_path,
248
+ line=node.lineno,
249
+ kind="constant",
250
+ name=name,
251
+ simple_name=name,
252
+ signature=signature,
253
+ end_line=getattr(node, "end_lineno", node.lineno),
254
+ )
255
+ )
256
+ return out
257
+
258
+
259
+ def _extract_python_symbols(*, root: Path, path: Path, text: str) -> list[_SymbolEntry]:
260
+ rel_path = _rel_path(root, path)
261
+ tree = ast.parse(text, filename=rel_path)
262
+ source_lines = text.splitlines()
263
+ symbols: list[_SymbolEntry] = []
264
+
265
+ for node in tree.body:
266
+ if isinstance(node, ast.ClassDef):
267
+ symbols.append(
268
+ _SymbolEntry(
269
+ path=rel_path,
270
+ line=node.lineno,
271
+ kind="class",
272
+ name=node.name,
273
+ simple_name=node.name,
274
+ signature=_format_class_signature(node),
275
+ end_line=getattr(node, "end_lineno", node.lineno),
276
+ )
277
+ )
278
+ for body_item in node.body:
279
+ if not isinstance(body_item, (ast.FunctionDef, ast.AsyncFunctionDef)):
280
+ continue
281
+ qualified_name = f"{node.name}.{body_item.name}"
282
+ symbols.append(
283
+ _SymbolEntry(
284
+ path=rel_path,
285
+ line=body_item.lineno,
286
+ kind="method",
287
+ name=qualified_name,
288
+ simple_name=body_item.name,
289
+ signature=_format_function_signature(
290
+ body_item,
291
+ display_name=qualified_name,
292
+ ),
293
+ end_line=getattr(body_item, "end_lineno", body_item.lineno),
294
+ parent=node.name,
295
+ )
296
+ )
297
+ continue
298
+
299
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
300
+ symbols.append(
301
+ _SymbolEntry(
302
+ path=rel_path,
303
+ line=node.lineno,
304
+ kind="function",
305
+ name=node.name,
306
+ simple_name=node.name,
307
+ signature=_format_function_signature(node, display_name=node.name),
308
+ end_line=getattr(node, "end_lineno", node.lineno),
309
+ )
310
+ )
311
+ continue
312
+
313
+ if isinstance(node, ast.Assign):
314
+ symbols.extend(
315
+ _extract_constant_entries(
316
+ rel_path=rel_path,
317
+ node=node,
318
+ source_lines=source_lines,
319
+ )
320
+ )
321
+ continue
322
+
323
+ if isinstance(node, ast.AnnAssign):
324
+ symbols.extend(
325
+ _extract_constant_entries(
326
+ rel_path=rel_path,
327
+ node=node,
328
+ source_lines=source_lines,
329
+ )
330
+ )
331
+
332
+ return symbols
333
+
334
+
335
+ def _sanitize_js_ts_line(
336
+ line: str,
337
+ *,
338
+ in_block_comment: bool,
339
+ in_backtick_string: bool,
340
+ ) -> tuple[str, bool, bool]:
341
+ out: list[str] = []
342
+ index = 0
343
+ quote: str | None = None
344
+ line_len = len(line)
345
+ while index < line_len:
346
+ ch = line[index]
347
+ nxt = line[index + 1] if index + 1 < line_len else ""
348
+
349
+ if in_block_comment:
350
+ if ch == "*" and nxt == "/":
351
+ in_block_comment = False
352
+ index += 2
353
+ continue
354
+ index += 1
355
+ continue
356
+
357
+ if in_backtick_string:
358
+ if ch == "\\":
359
+ index += 2
360
+ continue
361
+ if ch == "`":
362
+ in_backtick_string = False
363
+ index += 1
364
+ continue
365
+
366
+ if quote is not None:
367
+ if ch == "\\":
368
+ index += 2
369
+ continue
370
+ if ch == quote:
371
+ quote = None
372
+ index += 1
373
+ continue
374
+
375
+ if ch == "/" and nxt == "*":
376
+ in_block_comment = True
377
+ index += 2
378
+ continue
379
+ if ch == "/" and nxt == "/":
380
+ break
381
+ if ch == "`":
382
+ in_backtick_string = True
383
+ index += 1
384
+ continue
385
+ if ch in {"'", '"'}:
386
+ quote = ch
387
+ index += 1
388
+ continue
389
+
390
+ out.append(ch)
391
+ index += 1
392
+
393
+ return "".join(out), in_block_comment, in_backtick_string
394
+
395
+
396
+ def _consume_js_ts_identifier(text: str, start: int) -> int | None:
397
+ if start >= len(text):
398
+ return None
399
+ ch = text[start]
400
+ if ch != "_" and ch != "$" and not ch.isalpha():
401
+ return None
402
+ index = start + 1
403
+ while index < len(text):
404
+ ch = text[index]
405
+ if ch != "_" and ch != "$" and not ch.isalnum():
406
+ break
407
+ index += 1
408
+ return index
409
+
410
+
411
+ def _consume_js_ts_parenthesized(text: str, start: int) -> int | None:
412
+ if start >= len(text) or text[start] != "(":
413
+ return None
414
+
415
+ depth = 0
416
+ for index in range(start, len(text)):
417
+ ch = text[index]
418
+ if ch == "(":
419
+ depth += 1
420
+ continue
421
+ if ch == ")":
422
+ depth -= 1
423
+ if depth == 0:
424
+ return index + 1
425
+
426
+ return None
427
+
428
+
429
+ def _consume_js_ts_generic_prefix(text: str, start: int) -> int | None:
430
+ if start >= len(text) or text[start] != "<":
431
+ return start
432
+
433
+ angle_depth = 0
434
+ paren_depth = 0
435
+ bracket_depth = 0
436
+ brace_depth = 0
437
+
438
+ for index in range(start, len(text)):
439
+ ch = text[index]
440
+ prev = text[index - 1] if index > start else ""
441
+ if ch == "<":
442
+ angle_depth += 1
443
+ continue
444
+ if ch == ">" and angle_depth > 0 and prev != "=":
445
+ angle_depth -= 1
446
+ if angle_depth == 0 and paren_depth == 0 and bracket_depth == 0 and brace_depth == 0:
447
+ return index + 1
448
+ continue
449
+ if ch == "(":
450
+ paren_depth += 1
451
+ continue
452
+ if ch == ")" and paren_depth > 0:
453
+ paren_depth -= 1
454
+ continue
455
+ if ch == "[":
456
+ bracket_depth += 1
457
+ continue
458
+ if ch == "]" and bracket_depth > 0:
459
+ bracket_depth -= 1
460
+ continue
461
+ if ch == "{":
462
+ brace_depth += 1
463
+ continue
464
+ if ch == "}" and brace_depth > 0:
465
+ brace_depth -= 1
466
+
467
+ return None
468
+
469
+
470
+ def _has_js_ts_top_level_arrow(text: str, start: int) -> bool:
471
+ paren_depth = 0
472
+ bracket_depth = 0
473
+ brace_depth = 0
474
+ angle_depth = 0
475
+
476
+ for index in range(start, len(text)):
477
+ ch = text[index]
478
+ nxt = text[index + 1] if index + 1 < len(text) else ""
479
+ prev = text[index - 1] if index > start else ""
480
+
481
+ if ch == "(":
482
+ paren_depth += 1
483
+ continue
484
+ if ch == ")" and paren_depth > 0:
485
+ paren_depth -= 1
486
+ continue
487
+ if ch == "[":
488
+ bracket_depth += 1
489
+ continue
490
+ if ch == "]" and bracket_depth > 0:
491
+ bracket_depth -= 1
492
+ continue
493
+ if ch == "{":
494
+ brace_depth += 1
495
+ continue
496
+ if ch == "}" and brace_depth > 0:
497
+ brace_depth -= 1
498
+ continue
499
+ if ch == "<":
500
+ angle_depth += 1
501
+ continue
502
+ if ch == ">" and angle_depth > 0 and prev != "=":
503
+ angle_depth -= 1
504
+ continue
505
+ if (
506
+ ch == "="
507
+ and nxt == ">"
508
+ and paren_depth == 0
509
+ and bracket_depth == 0
510
+ and brace_depth == 0
511
+ and angle_depth == 0
512
+ ):
513
+ return True
514
+
515
+ return False
516
+
517
+
518
+ def _looks_like_js_ts_arrow_rhs(value: str) -> bool:
519
+ candidate = value.strip()
520
+ if not candidate:
521
+ return False
522
+
523
+ if candidate.startswith("async "):
524
+ candidate = candidate[6:].lstrip()
525
+
526
+ generic_end = _consume_js_ts_generic_prefix(candidate, 0)
527
+ if generic_end is None:
528
+ return False
529
+ candidate = candidate[generic_end:].lstrip()
530
+ if not candidate:
531
+ return False
532
+
533
+ if candidate.startswith("("):
534
+ params_end = _consume_js_ts_parenthesized(candidate, 0)
535
+ else:
536
+ params_end = _consume_js_ts_identifier(candidate, 0)
537
+ if params_end is None:
538
+ return False
539
+
540
+ return _has_js_ts_top_level_arrow(candidate, params_end)
541
+
542
+
543
+ def _is_js_ts_function_rhs(value: str) -> bool:
544
+ candidate = value.strip().rstrip(";").strip()
545
+ if _looks_like_js_ts_arrow_rhs(candidate):
546
+ return True
547
+ if _JS_TS_FUNCTION_EXPR_RE.match(candidate):
548
+ tail = candidate.partition("function")[2]
549
+ return ")" in tail or "{" in tail
550
+ return False
551
+
552
+
553
+ def _looks_like_js_ts_multiline_function_rhs(value: str) -> bool:
554
+ candidate = value.strip()
555
+ if not candidate:
556
+ return True
557
+ prefixes = (
558
+ "(",
559
+ "async (",
560
+ "function",
561
+ "async function",
562
+ "<",
563
+ "async <",
564
+ )
565
+ return candidate.startswith(prefixes)
566
+
567
+
568
+ def _split_js_ts_assignment(value: str) -> tuple[str, str] | None:
569
+ paren_depth = 0
570
+ bracket_depth = 0
571
+ brace_depth = 0
572
+ angle_depth = 0
573
+
574
+ for index, ch in enumerate(value):
575
+ nxt = value[index + 1] if index + 1 < len(value) else ""
576
+ prev = value[index - 1] if index > 0 else ""
577
+
578
+ if ch == "(":
579
+ paren_depth += 1
580
+ continue
581
+ if ch == ")" and paren_depth > 0:
582
+ paren_depth -= 1
583
+ continue
584
+ if ch == "[":
585
+ bracket_depth += 1
586
+ continue
587
+ if ch == "]" and bracket_depth > 0:
588
+ bracket_depth -= 1
589
+ continue
590
+ if ch == "{":
591
+ brace_depth += 1
592
+ continue
593
+ if ch == "}" and brace_depth > 0:
594
+ brace_depth -= 1
595
+ continue
596
+ if ch == "<":
597
+ angle_depth += 1
598
+ continue
599
+ if ch == ">" and angle_depth > 0:
600
+ angle_depth -= 1
601
+ continue
602
+ if (
603
+ ch == "="
604
+ and paren_depth == 0
605
+ and bracket_depth == 0
606
+ and brace_depth == 0
607
+ and angle_depth == 0
608
+ and nxt != ">"
609
+ and nxt != "="
610
+ and prev not in {"!", "<", ">", "="}
611
+ ):
612
+ lhs = value[:index].strip()
613
+ rhs = value[index + 1 :].strip()
614
+ return lhs, rhs
615
+
616
+ return None
617
+
618
+
619
+ def _parse_js_ts_const_assignment(line: str) -> tuple[str, str] | None:
620
+ match = _JS_TS_CONST_DECL_RE.match(line)
621
+ if match is None:
622
+ return None
623
+ assignment = _split_js_ts_assignment(match.group(2))
624
+ if assignment is None:
625
+ return None
626
+ _lhs, rhs = assignment
627
+ return match.group(1), rhs
628
+
629
+
630
+ def _parse_js_ts_class_field_assignment(line: str) -> tuple[str, str] | None:
631
+ match = _JS_TS_CLASS_FIELD_RE.match(line)
632
+ if match is None:
633
+ return None
634
+ assignment = _split_js_ts_assignment(match.group(2))
635
+ if assignment is None:
636
+ return None
637
+ _lhs, rhs = assignment
638
+ return match.group(1), rhs
639
+
640
+
641
+ def _js_ts_pending_signature(signature_lines: list[str]) -> str:
642
+ parts = [part for part in signature_lines if part]
643
+ return _clip_inline(" ".join(parts))
644
+
645
+
646
+ def _js_ts_assignment_entry(
647
+ *,
648
+ rel_path: str,
649
+ line: int,
650
+ kind: str,
651
+ name: str,
652
+ simple_name: str,
653
+ signature_lines: list[str],
654
+ parent: str = "",
655
+ ) -> _SymbolEntry:
656
+ return _SymbolEntry(
657
+ path=rel_path,
658
+ line=line,
659
+ kind=kind,
660
+ name=name,
661
+ simple_name=simple_name,
662
+ signature=_js_ts_pending_signature(signature_lines),
663
+ end_line=line + max(0, len(signature_lines) - 1),
664
+ parent=parent,
665
+ )
666
+
667
+
668
+ def _start_js_ts_pending_assignment(
669
+ *,
670
+ line: int,
671
+ kind: str,
672
+ name: str,
673
+ simple_name: str,
674
+ signature_line: str,
675
+ rhs_fragment: str,
676
+ scope_depth: int,
677
+ fallback_kind: str | None = None,
678
+ parent: str = "",
679
+ ) -> _JsTsPendingAssignment:
680
+ return _JsTsPendingAssignment(
681
+ line=line,
682
+ kind=kind,
683
+ name=name,
684
+ simple_name=simple_name,
685
+ signature_lines=[signature_line] if signature_line else [],
686
+ rhs_fragments=[rhs_fragment] if rhs_fragment else [],
687
+ scope_depth=scope_depth,
688
+ fallback_kind=fallback_kind,
689
+ parent=parent,
690
+ )
691
+
692
+
693
+ def _finalize_js_ts_pending_assignment(
694
+ *,
695
+ rel_path: str,
696
+ symbols: list[_SymbolEntry],
697
+ pending: _JsTsPendingAssignment | None,
698
+ resolved: bool,
699
+ ) -> None:
700
+ if pending is None:
701
+ return
702
+ if resolved:
703
+ symbols.append(
704
+ _js_ts_assignment_entry(
705
+ rel_path=rel_path,
706
+ line=pending.line,
707
+ kind=pending.kind,
708
+ name=pending.name,
709
+ simple_name=pending.simple_name,
710
+ signature_lines=pending.signature_lines,
711
+ parent=pending.parent,
712
+ )
713
+ )
714
+ return
715
+ if pending.fallback_kind is not None:
716
+ symbols.append(
717
+ _js_ts_assignment_entry(
718
+ rel_path=rel_path,
719
+ line=pending.line,
720
+ kind=pending.fallback_kind,
721
+ name=pending.name,
722
+ simple_name=pending.simple_name,
723
+ signature_lines=pending.signature_lines,
724
+ parent=pending.parent,
725
+ )
726
+ )
727
+
728
+
729
+ def _append_js_ts_pending_assignment_line(
730
+ *,
731
+ rel_path: str,
732
+ symbols: list[_SymbolEntry],
733
+ pending: _JsTsPendingAssignment,
734
+ raw_signature_line: str,
735
+ rhs_fragment: str,
736
+ ) -> _JsTsPendingAssignment | None:
737
+ if raw_signature_line:
738
+ pending.signature_lines.append(raw_signature_line)
739
+ if rhs_fragment:
740
+ pending.rhs_fragments.append(rhs_fragment)
741
+
742
+ combined_rhs = " ".join(fragment for fragment in pending.rhs_fragments if fragment)
743
+ if _is_js_ts_function_rhs(combined_rhs):
744
+ _finalize_js_ts_pending_assignment(
745
+ rel_path=rel_path,
746
+ symbols=symbols,
747
+ pending=pending,
748
+ resolved=True,
749
+ )
750
+ return None
751
+
752
+ signature_line_count = len(pending.signature_lines)
753
+ if (
754
+ signature_line_count >= _JS_TS_PENDING_ASSIGNMENT_MAX_LINES
755
+ or rhs_fragment.rstrip().endswith(";")
756
+ ):
757
+ _finalize_js_ts_pending_assignment(
758
+ rel_path=rel_path,
759
+ symbols=symbols,
760
+ pending=pending,
761
+ resolved=False,
762
+ )
763
+ return None
764
+ return pending
765
+
766
+
767
+ def _extract_js_ts_symbols(*, root: Path, path: Path, text: str) -> list[_SymbolEntry]:
768
+ rel_path = _rel_path(root, path)
769
+ symbols: list[_SymbolEntry] = []
770
+ brace_depth = 0
771
+ in_block_comment = False
772
+ in_backtick_string = False
773
+ pending_class_name: str | None = None
774
+ class_stack: list[_JsTsClassScope] = []
775
+ pending_assignment: _JsTsPendingAssignment | None = None
776
+
777
+ for line_number, raw_line in enumerate(text.splitlines(), start=1):
778
+ sanitized, in_block_comment, in_backtick_string = _sanitize_js_ts_line(
779
+ raw_line,
780
+ in_block_comment=in_block_comment,
781
+ in_backtick_string=in_backtick_string,
782
+ )
783
+ stripped = sanitized.strip()
784
+ if not stripped:
785
+ continue
786
+
787
+ depth_before = brace_depth
788
+ while class_stack and depth_before < class_stack[-1].body_depth:
789
+ class_stack.pop()
790
+
791
+ if pending_class_name is not None and depth_before == 0 and "{" in stripped:
792
+ class_stack.append(_JsTsClassScope(name=pending_class_name, body_depth=1))
793
+ pending_class_name = None
794
+
795
+ source_signature = _clip_inline(raw_line.strip())
796
+ consumed_by_pending = False
797
+
798
+ if pending_assignment is not None:
799
+ if depth_before != pending_assignment.scope_depth:
800
+ _finalize_js_ts_pending_assignment(
801
+ rel_path=rel_path,
802
+ symbols=symbols,
803
+ pending=pending_assignment,
804
+ resolved=False,
805
+ )
806
+ pending_assignment = None
807
+ else:
808
+ pending_assignment = _append_js_ts_pending_assignment_line(
809
+ rel_path=rel_path,
810
+ symbols=symbols,
811
+ pending=pending_assignment,
812
+ raw_signature_line=raw_line.strip(),
813
+ rhs_fragment=stripped,
814
+ )
815
+ consumed_by_pending = True
816
+
817
+ if not consumed_by_pending and depth_before == 0:
818
+ function_match = _JS_TS_FUNCTION_RE.match(stripped)
819
+ if function_match:
820
+ function_name = function_match.group(1)
821
+ symbols.append(
822
+ _SymbolEntry(
823
+ path=rel_path,
824
+ line=line_number,
825
+ kind="function",
826
+ name=function_name,
827
+ simple_name=function_name,
828
+ signature=source_signature,
829
+ end_line=line_number,
830
+ )
831
+ )
832
+ else:
833
+ class_match = _JS_TS_CLASS_RE.match(stripped)
834
+ if class_match:
835
+ class_name = class_match.group(1)
836
+ symbols.append(
837
+ _SymbolEntry(
838
+ path=rel_path,
839
+ line=line_number,
840
+ kind="class",
841
+ name=class_name,
842
+ simple_name=class_name,
843
+ signature=source_signature,
844
+ end_line=line_number,
845
+ )
846
+ )
847
+ class_tail = stripped[class_match.end() :]
848
+ if "{" in class_tail:
849
+ class_stack.append(_JsTsClassScope(name=class_name, body_depth=1))
850
+ pending_class_name = None
851
+ else:
852
+ pending_class_name = class_name
853
+ else:
854
+ const_assignment = _parse_js_ts_const_assignment(stripped)
855
+ if const_assignment is not None:
856
+ const_name, const_value = const_assignment
857
+ if _is_js_ts_function_rhs(const_value):
858
+ symbols.append(
859
+ _SymbolEntry(
860
+ path=rel_path,
861
+ line=line_number,
862
+ kind="function",
863
+ name=const_name,
864
+ simple_name=const_name,
865
+ signature=source_signature,
866
+ end_line=line_number,
867
+ )
868
+ )
869
+ elif _looks_like_js_ts_multiline_function_rhs(const_value):
870
+ pending_assignment = _start_js_ts_pending_assignment(
871
+ line=line_number,
872
+ kind="function",
873
+ name=const_name,
874
+ simple_name=const_name,
875
+ signature_line=raw_line.strip(),
876
+ rhs_fragment=const_value,
877
+ scope_depth=depth_before,
878
+ fallback_kind="constant",
879
+ )
880
+ else:
881
+ symbols.append(
882
+ _SymbolEntry(
883
+ path=rel_path,
884
+ line=line_number,
885
+ kind="constant",
886
+ name=const_name,
887
+ simple_name=const_name,
888
+ signature=source_signature,
889
+ end_line=line_number,
890
+ )
891
+ )
892
+
893
+ if not consumed_by_pending and class_stack and depth_before == class_stack[-1].body_depth:
894
+ class_name = class_stack[-1].name
895
+ field_assignment = _parse_js_ts_class_field_assignment(stripped)
896
+ if field_assignment is not None:
897
+ field_name, field_value = field_assignment
898
+ qualified_name = f"{class_name}.{field_name}"
899
+ if _is_js_ts_function_rhs(field_value):
900
+ symbols.append(
901
+ _SymbolEntry(
902
+ path=rel_path,
903
+ line=line_number,
904
+ kind="method",
905
+ name=qualified_name,
906
+ simple_name=field_name,
907
+ signature=source_signature,
908
+ end_line=line_number,
909
+ parent=class_name,
910
+ )
911
+ )
912
+ elif _looks_like_js_ts_multiline_function_rhs(field_value):
913
+ pending_assignment = _start_js_ts_pending_assignment(
914
+ line=line_number,
915
+ kind="method",
916
+ name=qualified_name,
917
+ simple_name=field_name,
918
+ signature_line=raw_line.strip(),
919
+ rhs_fragment=field_value,
920
+ scope_depth=depth_before,
921
+ parent=class_name,
922
+ )
923
+ if pending_assignment is None:
924
+ method_match = _JS_TS_METHOD_RE.match(stripped)
925
+ if method_match:
926
+ method_name = method_match.group(1)
927
+ if method_name not in _JS_TS_BLOCK_KEYWORDS:
928
+ qualified_name = f"{class_name}.{method_name}"
929
+ symbols.append(
930
+ _SymbolEntry(
931
+ path=rel_path,
932
+ line=line_number,
933
+ kind="method",
934
+ name=qualified_name,
935
+ simple_name=method_name,
936
+ signature=source_signature,
937
+ end_line=line_number,
938
+ parent=class_name,
939
+ )
940
+ )
941
+
942
+ brace_depth = max(0, brace_depth + stripped.count("{") - stripped.count("}"))
943
+ while class_stack and brace_depth < class_stack[-1].body_depth:
944
+ class_stack.pop()
945
+
946
+ if pending_assignment is not None:
947
+ _finalize_js_ts_pending_assignment(
948
+ rel_path=rel_path,
949
+ symbols=symbols,
950
+ pending=pending_assignment,
951
+ resolved=False,
952
+ )
953
+
954
+ return symbols
955
+
956
+
957
+ def _sanitize_java_line(line: str, *, in_block_comment: bool) -> tuple[str, bool]:
958
+ out: list[str] = []
959
+ index = 0
960
+ quote: str | None = None
961
+ line_len = len(line)
962
+ while index < line_len:
963
+ ch = line[index]
964
+ nxt = line[index + 1] if index + 1 < line_len else ""
965
+
966
+ if in_block_comment:
967
+ if ch == "*" and nxt == "/":
968
+ in_block_comment = False
969
+ index += 2
970
+ continue
971
+ index += 1
972
+ continue
973
+
974
+ if quote is not None:
975
+ if ch == "\\":
976
+ index += 2
977
+ continue
978
+ if ch == quote:
979
+ quote = None
980
+ index += 1
981
+ continue
982
+
983
+ if ch == "/" and nxt == "*":
984
+ in_block_comment = True
985
+ index += 2
986
+ continue
987
+ if ch == "/" and nxt == "/":
988
+ break
989
+ if ch in {"'", '"'}:
990
+ quote = ch
991
+ index += 1
992
+ continue
993
+
994
+ out.append(ch)
995
+ index += 1
996
+
997
+ return "".join(out), in_block_comment
998
+
999
+
1000
+ def _extract_java_symbols(*, root: Path, path: Path, text: str) -> list[_SymbolEntry]:
1001
+ rel_path = _rel_path(root, path)
1002
+ symbols: list[_SymbolEntry] = []
1003
+ brace_depth = 0
1004
+ in_block_comment = False
1005
+ pending_type_name: str | None = None
1006
+ type_stack: list[_JavaTypeScope] = []
1007
+
1008
+ for line_number, raw_line in enumerate(text.splitlines(), start=1):
1009
+ sanitized, in_block_comment = _sanitize_java_line(
1010
+ raw_line,
1011
+ in_block_comment=in_block_comment,
1012
+ )
1013
+ stripped = sanitized.strip()
1014
+ if not stripped:
1015
+ continue
1016
+
1017
+ depth_before = brace_depth
1018
+ while type_stack and depth_before < type_stack[-1].body_depth:
1019
+ type_stack.pop()
1020
+
1021
+ if pending_type_name is not None and "{" in stripped:
1022
+ type_stack.append(_JavaTypeScope(name=pending_type_name, body_depth=depth_before + 1))
1023
+ pending_type_name = None
1024
+
1025
+ source_signature = _clip_inline(raw_line.strip())
1026
+ type_match = _JAVA_TYPE_RE.match(stripped)
1027
+ if type_match:
1028
+ type_name = type_match.group("name")
1029
+ symbols.append(
1030
+ _SymbolEntry(
1031
+ path=rel_path,
1032
+ line=line_number,
1033
+ kind="class",
1034
+ name=type_name,
1035
+ simple_name=type_name,
1036
+ signature=source_signature,
1037
+ end_line=line_number,
1038
+ )
1039
+ )
1040
+ type_tail = stripped[type_match.end() :]
1041
+ if "{" in type_tail:
1042
+ type_stack.append(_JavaTypeScope(name=type_name, body_depth=depth_before + 1))
1043
+ else:
1044
+ pending_type_name = type_name
1045
+ elif type_stack and depth_before == type_stack[-1].body_depth:
1046
+ class_name = type_stack[-1].name
1047
+ method_match = _JAVA_METHOD_RE.match(stripped)
1048
+ method_name = method_match.group("name") if method_match else ""
1049
+ if method_name and method_name not in _JAVA_BLOCK_KEYWORDS:
1050
+ qualified_name = f"{class_name}.{method_name}"
1051
+ symbols.append(
1052
+ _SymbolEntry(
1053
+ path=rel_path,
1054
+ line=line_number,
1055
+ kind="method",
1056
+ name=qualified_name,
1057
+ simple_name=method_name,
1058
+ signature=source_signature,
1059
+ end_line=line_number,
1060
+ parent=class_name,
1061
+ )
1062
+ )
1063
+ elif not method_match:
1064
+ constructor_match = _JAVA_CONSTRUCTOR_RE.match(stripped)
1065
+ constructor_name = constructor_match.group("name") if constructor_match else ""
1066
+ if constructor_name == class_name:
1067
+ qualified_name = f"{class_name}.{constructor_name}"
1068
+ symbols.append(
1069
+ _SymbolEntry(
1070
+ path=rel_path,
1071
+ line=line_number,
1072
+ kind="method",
1073
+ name=qualified_name,
1074
+ simple_name=constructor_name,
1075
+ signature=source_signature,
1076
+ end_line=line_number,
1077
+ parent=class_name,
1078
+ )
1079
+ )
1080
+
1081
+ brace_depth = max(0, brace_depth + stripped.count("{") - stripped.count("}"))
1082
+ while type_stack and brace_depth < type_stack[-1].body_depth:
1083
+ type_stack.pop()
1084
+
1085
+ return symbols
1086
+
1087
+
1088
+ def _matches_query(entry: _SymbolEntry, *, query: str, exact: bool) -> bool:
1089
+ if exact:
1090
+ return query == entry.name or query == entry.simple_name
1091
+
1092
+ query_cf = query.casefold()
1093
+ return query_cf in entry.name.casefold() or query_cf in entry.simple_name.casefold()
1094
+
1095
+
1096
+ def _add_note(notes: list[str], message: str) -> None:
1097
+ if message and message not in notes:
1098
+ notes.append(message)
1099
+
1100
+
1101
+ def _entry_end_line(entry: _SymbolEntry) -> int:
1102
+ end_line = entry.end_line if entry.end_line is not None else entry.line
1103
+ return max(entry.line, int(end_line))
1104
+
1105
+
1106
+ def _entry_parent(entry: _SymbolEntry) -> str:
1107
+ if entry.parent:
1108
+ return entry.parent
1109
+ if entry.kind == "method" and "." in entry.name:
1110
+ return entry.name.rsplit(".", 1)[0]
1111
+ return ""
1112
+
1113
+
1114
+ def _symbol_snippet(
1115
+ *,
1116
+ source_lines: list[str],
1117
+ start_line: int,
1118
+ end_line: int,
1119
+ max_lines: int = 8,
1120
+ ) -> dict[str, Any] | None:
1121
+ if start_line < 1 or start_line > len(source_lines):
1122
+ return None
1123
+ bounded_end = min(max(start_line, end_line), len(source_lines), start_line + max_lines - 1)
1124
+ lines = [
1125
+ {
1126
+ "line": line_number,
1127
+ "text": _clip_inline(source_lines[line_number - 1], max_chars=160),
1128
+ }
1129
+ for line_number in range(start_line, bounded_end + 1)
1130
+ ]
1131
+ return {
1132
+ "start_line": start_line,
1133
+ "end_line": bounded_end,
1134
+ "truncated": bounded_end < min(max(start_line, end_line), len(source_lines)),
1135
+ "lines": lines,
1136
+ }
1137
+
1138
+
1139
+ def _reference_hints_for_entry(
1140
+ *,
1141
+ entry: _SymbolEntry,
1142
+ source_lines_by_path: dict[str, list[str]],
1143
+ max_references: int = 5,
1144
+ ) -> list[dict[str, Any]]:
1145
+ needle = entry.simple_name or entry.name
1146
+ if not needle:
1147
+ return []
1148
+ out: list[dict[str, Any]] = []
1149
+ defining_range = range(entry.line, _entry_end_line(entry) + 1)
1150
+ for rel_path, source_lines in source_lines_by_path.items():
1151
+ for line_number, line in enumerate(source_lines, start=1):
1152
+ if rel_path == entry.path and line_number in defining_range:
1153
+ continue
1154
+ if needle not in line:
1155
+ continue
1156
+ out.append(
1157
+ {
1158
+ "path": rel_path,
1159
+ "line": line_number,
1160
+ "text": _clip_inline(line.strip(), max_chars=200),
1161
+ }
1162
+ )
1163
+ if len(out) >= max_references:
1164
+ return out
1165
+ return out
1166
+
1167
+
1168
+ def symbol_search(
1169
+ *,
1170
+ root: Path,
1171
+ query: str,
1172
+ kind: str | None = None,
1173
+ root_path: str = ".",
1174
+ globs: list[str] | None = None,
1175
+ max_results: int = _DEFAULT_MAX_RESULTS,
1176
+ exact: bool = False,
1177
+ include_details: bool = False,
1178
+ include_snippet: bool = False,
1179
+ include_references: bool = False,
1180
+ ) -> dict[str, Any]:
1181
+ cleaned_query = str(query or "").strip()
1182
+ if not cleaned_query:
1183
+ raise SymbolSearchError("query must be a non-empty string")
1184
+
1185
+ cleaned_kind = str(kind or "").strip().lower() or None
1186
+ if cleaned_kind is not None and cleaned_kind not in _SUPPORTED_KINDS:
1187
+ allowed = ", ".join(sorted(_SUPPORTED_KINDS))
1188
+ raise SymbolSearchError(f"Unsupported kind: {kind!r}. Expected one of: {allowed}")
1189
+
1190
+ base = _resolve_under_root(root, root_path)
1191
+ if not base.exists():
1192
+ raise SymbolSearchError(f"Not found: {root_path}")
1193
+
1194
+ cleaned_globs = [str(pattern) for pattern in (globs or []) if str(pattern).strip()]
1195
+ safe_max_results = max(1, min(int(max_results), _MAX_RESULTS))
1196
+ candidate_files = _iter_candidate_files(
1197
+ root=root,
1198
+ base=base,
1199
+ globs=cleaned_globs or None,
1200
+ )
1201
+
1202
+ matches: list[dict[str, Any]] = []
1203
+ matched_entries: list[_SymbolEntry] = []
1204
+ source_lines_by_path: dict[str, list[str]] = {}
1205
+ notes: list[str] = []
1206
+ truncated = False
1207
+ parsed_files = 0
1208
+ parsed_backends: set[str] = set()
1209
+ skipped_unsupported: list[str] = []
1210
+ skipped_large: list[str] = []
1211
+ skipped_unparsable: list[str] = []
1212
+
1213
+ for path in candidate_files:
1214
+ rel_path = _rel_path(root, path)
1215
+ backend = symbol_search_backend_for_path(rel_path)
1216
+ if backend is None:
1217
+ if len(skipped_unsupported) < _MAX_NOTE_PATHS:
1218
+ skipped_unsupported.append(rel_path)
1219
+ continue
1220
+
1221
+ try:
1222
+ size = path.stat().st_size
1223
+ except OSError:
1224
+ if len(skipped_large) < _MAX_NOTE_PATHS:
1225
+ skipped_large.append(rel_path)
1226
+ continue
1227
+ if size > _MAX_FILE_BYTES:
1228
+ if len(skipped_large) < _MAX_NOTE_PATHS:
1229
+ skipped_large.append(rel_path)
1230
+ continue
1231
+
1232
+ try:
1233
+ text = path.read_text(encoding="utf-8", errors="replace")
1234
+ except OSError:
1235
+ if len(skipped_unparsable) < _MAX_NOTE_PATHS:
1236
+ skipped_unparsable.append(rel_path)
1237
+ continue
1238
+ source_lines = text.splitlines()
1239
+ if include_snippet or include_references:
1240
+ source_lines_by_path[rel_path] = source_lines
1241
+
1242
+ if backend == "python_ast":
1243
+ try:
1244
+ symbols = _extract_python_symbols(root=root, path=path, text=text)
1245
+ except SyntaxError:
1246
+ if len(skipped_unparsable) < _MAX_NOTE_PATHS:
1247
+ skipped_unparsable.append(rel_path)
1248
+ continue
1249
+ elif backend == "js_ts_heuristic":
1250
+ symbols = _extract_js_ts_symbols(root=root, path=path, text=text)
1251
+ else:
1252
+ symbols = _extract_java_symbols(root=root, path=path, text=text)
1253
+
1254
+ parsed_files += 1
1255
+ parsed_backends.add(backend)
1256
+ for entry in symbols:
1257
+ if cleaned_kind is not None and entry.kind != cleaned_kind:
1258
+ continue
1259
+ if not _matches_query(entry, query=cleaned_query, exact=exact):
1260
+ continue
1261
+ match = {
1262
+ "path": entry.path,
1263
+ "line": entry.line,
1264
+ "kind": entry.kind,
1265
+ "name": entry.name,
1266
+ "signature": entry.signature,
1267
+ }
1268
+ if include_details:
1269
+ match["end_line"] = _entry_end_line(entry)
1270
+ parent = _entry_parent(entry)
1271
+ if parent:
1272
+ match["parent"] = parent
1273
+ if include_snippet:
1274
+ snippet = _symbol_snippet(
1275
+ source_lines=source_lines,
1276
+ start_line=entry.line,
1277
+ end_line=_entry_end_line(entry),
1278
+ )
1279
+ if snippet is not None:
1280
+ match["snippet"] = snippet
1281
+ matches.append(match)
1282
+ matched_entries.append(entry)
1283
+ if len(matches) > safe_max_results:
1284
+ truncated = True
1285
+ break
1286
+ if truncated:
1287
+ break
1288
+
1289
+ if parsed_files == 0 and skipped_unsupported:
1290
+ paths = ", ".join(skipped_unsupported)
1291
+ _add_note(
1292
+ notes,
1293
+ f"Skipped unsupported file(s): {paths}",
1294
+ )
1295
+ if skipped_large:
1296
+ paths = ", ".join(skipped_large)
1297
+ _add_note(notes, f"Skipped large or unreadable source file(s): {paths}")
1298
+ if skipped_unparsable:
1299
+ paths = ", ".join(skipped_unparsable)
1300
+ _add_note(notes, f"Skipped unparsable source file(s): {paths}")
1301
+ if not candidate_files:
1302
+ _add_note(notes, "No files matched the requested scope.")
1303
+ elif parsed_files == 0 and not skipped_unsupported:
1304
+ _add_note(notes, "No supported source files matched the requested scope.")
1305
+
1306
+ if parsed_backends == {"python_ast"}:
1307
+ backend_name = "python_ast"
1308
+ elif parsed_backends == {"js_ts_heuristic"}:
1309
+ backend_name = "js_ts_heuristic"
1310
+ elif parsed_backends == {"java_heuristic"}:
1311
+ backend_name = "java_heuristic"
1312
+ elif parsed_backends:
1313
+ backend_name = "mixed_static"
1314
+ else:
1315
+ backend_name = "python_ast"
1316
+
1317
+ root_abs = root.resolve()
1318
+ scope_display = "."
1319
+ try:
1320
+ rel_base = base.resolve().relative_to(root_abs)
1321
+ except ValueError:
1322
+ scope_display = root_path
1323
+ else:
1324
+ if os.fspath(rel_base):
1325
+ scope_display = os.fspath(rel_base).replace("\\", "/")
1326
+
1327
+ if include_references and matches:
1328
+ for match, entry in zip(matches, matched_entries, strict=False):
1329
+ references = _reference_hints_for_entry(
1330
+ entry=entry,
1331
+ source_lines_by_path=source_lines_by_path,
1332
+ )
1333
+ if references:
1334
+ match["references"] = references
1335
+
1336
+ return {
1337
+ "query": cleaned_query,
1338
+ "kind": cleaned_kind,
1339
+ "root_path": scope_display,
1340
+ "globs": cleaned_globs or None,
1341
+ "exact": bool(exact),
1342
+ "include_details": bool(include_details),
1343
+ "include_snippet": bool(include_snippet),
1344
+ "include_references": bool(include_references),
1345
+ "matches": matches[:safe_max_results],
1346
+ "truncated": truncated,
1347
+ "notes": notes,
1348
+ "backend": backend_name,
1349
+ "parsed_files": parsed_files,
1350
+ }