mixdog 0.9.2 → 0.9.4

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 (417) hide show
  1. package/package.json +8 -3
  2. package/scripts/anthropic-maxtokens-test.mjs +119 -0
  3. package/scripts/bench/lead-review-tasks-r3.json +20 -0
  4. package/scripts/bench/lead-review-tasks.json +20 -0
  5. package/scripts/bench/r4-mixed-tasks.json +20 -0
  6. package/scripts/bench/review-tasks.json +20 -0
  7. package/scripts/bench/round-codex.json +114 -0
  8. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  9. package/scripts/bench/round-mixdog-lead.json +269 -0
  10. package/scripts/bench/round-mixdog.json +126 -0
  11. package/scripts/bench/round-r10-bigsample.json +679 -0
  12. package/scripts/bench/round-r11-codexalign.json +257 -0
  13. package/scripts/bench/round-r4-codex.json +114 -0
  14. package/scripts/bench/round-r4-mixed.json +225 -0
  15. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  16. package/scripts/bench/round-r6-codex.json +114 -0
  17. package/scripts/bench/round-r6-solo.json +257 -0
  18. package/scripts/bench/round-r7-full.json +254 -0
  19. package/scripts/bench/round-r8-fulldefault.json +255 -0
  20. package/scripts/bench-run.mjs +215 -29
  21. package/scripts/build-tui.mjs +13 -1
  22. package/scripts/explore-bench.mjs +124 -0
  23. package/scripts/freevar-smoke.mjs +95 -0
  24. package/scripts/hook-bus-test.mjs +191 -0
  25. package/scripts/internal-comms-bench.mjs +1 -0
  26. package/scripts/internal-comms-smoke.mjs +10 -9
  27. package/scripts/mouse-probe.mjs +45 -0
  28. package/scripts/output-style-bench.mjs +13 -6
  29. package/scripts/output-style-smoke.mjs +4 -4
  30. package/scripts/path-suffix-test.mjs +57 -0
  31. package/scripts/provider-toolcall-test.mjs +7 -3
  32. package/scripts/recall-bench.mjs +207 -0
  33. package/scripts/recall-usecase-cases.json +18 -0
  34. package/scripts/recall-usecase-probe.json +6 -0
  35. package/scripts/session-bench.mjs +152 -6
  36. package/scripts/tool-smoke.mjs +30 -67
  37. package/scripts/tui-render-smoke.mjs +90 -0
  38. package/scripts/webhook-smoke.mjs +208 -0
  39. package/src/agents/debugger/AGENT.md +5 -2
  40. package/src/agents/heavy-worker/AGENT.md +21 -11
  41. package/src/agents/maintainer/AGENT.md +4 -0
  42. package/src/agents/reviewer/AGENT.md +3 -2
  43. package/src/agents/worker/AGENT.md +21 -11
  44. package/src/lib/rules-builder.cjs +4 -0
  45. package/src/mixdog-session-runtime.mjs +933 -3731
  46. package/src/output-styles/default.md +34 -9
  47. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  48. package/src/output-styles/minimal.md +4 -1
  49. package/src/output-styles/simple.md +22 -7
  50. package/src/repl.mjs +5 -5
  51. package/src/rules/agent/00-common.md +2 -0
  52. package/src/rules/agent/30-explorer.md +8 -11
  53. package/src/rules/lead/lead-brief.md +12 -0
  54. package/src/rules/lead/lead-tool.md +2 -9
  55. package/src/rules/shared/01-tool.md +11 -5
  56. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  57. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  58. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  59. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  60. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  61. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  62. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  63. package/src/runtime/agent/orchestrator/context/collect.mjs +51 -0
  64. package/src/runtime/agent/orchestrator/mcp/client.mjs +6 -2
  65. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +63 -21
  66. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
  67. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  68. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  69. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +97 -1343
  70. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  71. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +78 -10
  72. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +1 -13
  73. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  74. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  75. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  76. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  77. package/src/runtime/agent/orchestrator/providers/gemini.mjs +44 -1014
  78. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +18 -4
  79. package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
  80. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
  81. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
  82. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
  83. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  84. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +19 -12
  85. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  86. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  87. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  88. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +41 -1142
  89. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
  90. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  91. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +303 -2119
  92. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +140 -995
  93. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
  94. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  95. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
  96. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  97. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  98. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +38 -12
  99. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +7 -8
  100. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  101. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  102. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  103. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  104. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  105. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  106. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  107. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  108. package/src/runtime/agent/orchestrator/session/loop/compact-debug.mjs +28 -0
  109. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +274 -0
  110. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  111. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
  112. package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
  113. package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
  114. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +47 -0
  115. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
  116. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  117. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
  118. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
  119. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  120. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
  121. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  122. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
  123. package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
  124. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
  125. package/src/runtime/agent/orchestrator/session/loop.mjs +409 -1304
  126. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  127. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +230 -0
  128. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
  129. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +149 -0
  130. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
  131. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  132. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  133. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
  134. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  135. package/src/runtime/agent/orchestrator/session/manager.mjs +226 -2114
  136. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  137. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  138. package/src/runtime/agent/orchestrator/stall-policy.mjs +3 -3
  139. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  140. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  141. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +33 -42
  142. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
  143. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
  144. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  145. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
  146. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +1 -1
  147. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
  148. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  149. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  150. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  151. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -87
  152. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  153. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  154. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +78 -304
  155. package/src/runtime/agent/orchestrator/tools/builtin.mjs +11 -6
  156. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
  157. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
  158. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
  159. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +551 -0
  160. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
  161. package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
  162. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  163. package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
  164. package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
  165. package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
  166. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1080 -0
  167. package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
  168. package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
  169. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
  170. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  171. package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
  172. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  173. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +36 -4277
  174. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  175. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  176. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  177. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  178. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  179. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  180. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  181. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  182. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  183. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  184. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -23
  185. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +10 -74
  186. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  187. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
  188. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  189. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  190. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  191. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  192. package/src/runtime/channels/backends/discord.mjs +12 -292
  193. package/src/runtime/channels/index.mjs +241 -894
  194. package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
  195. package/src/runtime/channels/lib/boot-profile.mjs +23 -0
  196. package/src/runtime/channels/lib/crash-log.mjs +106 -0
  197. package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
  198. package/src/runtime/channels/lib/event-queue.mjs +63 -4
  199. package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
  200. package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
  201. package/src/runtime/channels/lib/output-forwarder.mjs +9 -1
  202. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  203. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  204. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  205. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  206. package/src/runtime/channels/lib/telegram-format.mjs +19 -22
  207. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  208. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  209. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  210. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  211. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  212. package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
  213. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  214. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  215. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  216. package/src/runtime/channels/lib/webhook.mjs +36 -570
  217. package/src/runtime/channels/lib/whisper-language.mjs +42 -0
  218. package/src/runtime/channels/tool-defs.mjs +11 -130
  219. package/src/runtime/memory/index.mjs +258 -2050
  220. package/src/runtime/memory/lib/core-memory-store.mjs +351 -1
  221. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  222. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  223. package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
  224. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  225. package/src/runtime/memory/lib/http-wire.mjs +57 -0
  226. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  227. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  228. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  229. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  230. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  231. package/src/runtime/memory/lib/memory-cycle2.mjs +72 -837
  232. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  233. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  234. package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
  235. package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
  236. package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
  237. package/src/runtime/memory/lib/memory.mjs +20 -0
  238. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  239. package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
  240. package/src/runtime/memory/lib/query-handlers.mjs +780 -0
  241. package/src/runtime/memory/lib/recall-format.mjs +238 -0
  242. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  243. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  244. package/src/runtime/memory/tool-defs.mjs +6 -14
  245. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  246. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  247. package/src/runtime/search/lib/web-tools.mjs +24 -602
  248. package/src/runtime/shared/abort-controller.mjs +1 -1
  249. package/src/runtime/shared/atomic-file.mjs +26 -1
  250. package/src/runtime/shared/background-tasks.mjs +2 -3
  251. package/src/runtime/shared/buffered-appender.mjs +149 -0
  252. package/src/runtime/shared/launcher-control.mjs +2 -2
  253. package/src/runtime/shared/task-notification-envelope.mjs +98 -0
  254. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  255. package/src/runtime/shared/tool-primitives.mjs +308 -0
  256. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  257. package/src/runtime/shared/tool-surface.mjs +80 -898
  258. package/src/runtime/shared/transcript-writer.mjs +52 -2
  259. package/src/runtime/shared/update-checker.mjs +7 -4
  260. package/src/session-runtime/config-helpers.mjs +291 -0
  261. package/src/session-runtime/config-lifecycle.mjs +232 -0
  262. package/src/session-runtime/cwd-plugins.mjs +226 -0
  263. package/src/session-runtime/effort.mjs +128 -0
  264. package/src/session-runtime/fs-utils.mjs +10 -0
  265. package/src/session-runtime/mcp-glue.mjs +177 -0
  266. package/src/session-runtime/model-capabilities.mjs +130 -0
  267. package/src/session-runtime/model-recency.mjs +111 -0
  268. package/src/session-runtime/native-search.mjs +247 -0
  269. package/src/session-runtime/output-styles.mjs +126 -0
  270. package/src/session-runtime/plugin-mcp.mjs +114 -0
  271. package/src/session-runtime/prewarm.mjs +142 -0
  272. package/src/session-runtime/provider-models.mjs +278 -0
  273. package/src/session-runtime/provider-usage.mjs +120 -0
  274. package/src/session-runtime/quick-model-rows.mjs +170 -0
  275. package/src/session-runtime/quick-search-models.mjs +46 -0
  276. package/src/session-runtime/session-hooks.mjs +93 -0
  277. package/src/session-runtime/session-text.mjs +100 -0
  278. package/src/session-runtime/settings-api.mjs +319 -0
  279. package/src/session-runtime/statusline-route.mjs +35 -0
  280. package/src/session-runtime/tool-catalog.mjs +720 -0
  281. package/src/session-runtime/tool-defs.mjs +84 -0
  282. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  283. package/src/session-runtime/workflow.mjs +358 -0
  284. package/src/standalone/agent-tool/helpers.mjs +237 -0
  285. package/src/standalone/agent-tool/notify.mjs +107 -0
  286. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  287. package/src/standalone/agent-tool/render.mjs +152 -0
  288. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  289. package/src/standalone/agent-tool.mjs +155 -677
  290. package/src/standalone/channel-worker.mjs +7 -9
  291. package/src/standalone/explore-tool.mjs +40 -12
  292. package/src/standalone/hook-bus/config.mjs +207 -0
  293. package/src/standalone/hook-bus/constants.mjs +90 -0
  294. package/src/standalone/hook-bus/handlers.mjs +481 -0
  295. package/src/standalone/hook-bus/payload.mjs +31 -0
  296. package/src/standalone/hook-bus/rules.mjs +77 -0
  297. package/src/standalone/hook-bus.mjs +110 -746
  298. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  299. package/src/standalone/opencode-go-login.mjs +125 -0
  300. package/src/standalone/provider-admin.mjs +15 -19
  301. package/src/standalone/usage-dashboard.mjs +3 -1
  302. package/src/tui/App.jsx +1163 -7571
  303. package/src/tui/app/app-format.mjs +206 -0
  304. package/src/tui/app/channel-pickers.mjs +510 -0
  305. package/src/tui/app/clipboard.mjs +67 -0
  306. package/src/tui/app/core-memory-picker.mjs +210 -0
  307. package/src/tui/app/extension-pickers.mjs +506 -0
  308. package/src/tui/app/input-parsers.mjs +193 -0
  309. package/src/tui/app/maintenance-pickers.mjs +324 -0
  310. package/src/tui/app/model-options.mjs +330 -0
  311. package/src/tui/app/model-picker.mjs +365 -0
  312. package/src/tui/app/onboarding-steps.mjs +400 -0
  313. package/src/tui/app/project-picker.mjs +247 -0
  314. package/src/tui/app/provider-setup-picker.mjs +580 -0
  315. package/src/tui/app/resume-picker.mjs +55 -0
  316. package/src/tui/app/route-pickers.mjs +419 -0
  317. package/src/tui/app/settings-picker.mjs +490 -0
  318. package/src/tui/app/slash-commands.mjs +101 -0
  319. package/src/tui/app/slash-dispatch.mjs +427 -0
  320. package/src/tui/app/text-layout.mjs +46 -0
  321. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  322. package/src/tui/app/transcript-window.mjs +671 -0
  323. package/src/tui/app/use-mouse-input.mjs +460 -0
  324. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  325. package/src/tui/app/use-transcript-scroll.mjs +510 -0
  326. package/src/tui/app/use-transcript-window.mjs +589 -0
  327. package/src/tui/components/ConfirmBar.jsx +1 -1
  328. package/src/tui/components/Picker.jsx +32 -4
  329. package/src/tui/components/PromptInput.jsx +259 -80
  330. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  331. package/src/tui/components/StatusLine.jsx +63 -12
  332. package/src/tui/components/TextEntryPanel.jsx +11 -0
  333. package/src/tui/components/ToolExecution.jsx +56 -588
  334. package/src/tui/components/TranscriptItem.jsx +105 -0
  335. package/src/tui/components/UsagePanel.jsx +18 -4
  336. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  337. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  338. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  339. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  340. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  341. package/src/tui/components/tool-output-format.mjs +2 -2
  342. package/src/tui/display-width.mjs +20 -3
  343. package/src/tui/dist/index.mjs +18034 -17188
  344. package/src/tui/engine/agent-envelope.mjs +296 -0
  345. package/src/tui/engine/agent-job-feed.mjs +133 -0
  346. package/src/tui/engine/boot-profile.mjs +21 -0
  347. package/src/tui/engine/labels.mjs +67 -0
  348. package/src/tui/engine/notice-text.mjs +112 -0
  349. package/src/tui/engine/notification-plan.mjs +76 -0
  350. package/src/tui/engine/queue-helpers.mjs +161 -0
  351. package/src/tui/engine/render-timing.mjs +17 -0
  352. package/src/tui/engine/session-stats.mjs +46 -0
  353. package/src/tui/engine/tool-approval.mjs +94 -0
  354. package/src/tui/engine/tool-call-fields.mjs +23 -0
  355. package/src/tui/engine/tool-card-results.mjs +234 -0
  356. package/src/tui/engine/tool-result-status.mjs +135 -0
  357. package/src/tui/engine/tool-result-text.mjs +126 -0
  358. package/src/tui/engine.mjs +405 -1385
  359. package/src/tui/figures.mjs +5 -0
  360. package/src/tui/index.jsx +105 -0
  361. package/src/tui/input-editing.mjs +60 -10
  362. package/src/tui/keyboard-protocol.mjs +2 -2
  363. package/src/tui/lib/voice-recorder.mjs +35 -19
  364. package/src/tui/markdown/format-token.mjs +11 -9
  365. package/src/tui/paste-attachments.mjs +38 -0
  366. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  367. package/src/tui/theme.mjs +6 -0
  368. package/src/tui/themes/base.mjs +2 -2
  369. package/src/tui/themes/kanagawa.mjs +4 -4
  370. package/src/tui/themes/teal.mjs +4 -5
  371. package/src/tui/themes/utils.mjs +1 -1
  372. package/src/ui/statusline-agents.mjs +213 -0
  373. package/src/ui/statusline-format.mjs +146 -0
  374. package/src/ui/statusline-segments.mjs +148 -0
  375. package/src/ui/statusline.mjs +77 -462
  376. package/src/ui/tool-card.mjs +0 -1
  377. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  378. package/src/workflows/default/WORKFLOW.md +16 -9
  379. package/src/workflows/sequential/WORKFLOW.md +16 -11
  380. package/src/workflows/solo/WORKFLOW.md +5 -1
  381. package/vendor/ink/build/display-width.js +19 -3
  382. package/vendor/ink/build/ink.js +103 -6
  383. package/vendor/ink/build/log-update.js +17 -3
  384. package/vendor/ink/build/wrap-text.js +125 -0
  385. package/scripts/_test-folder-dialog.mjs +0 -30
  386. package/scripts/fix-brief-fn.mjs +0 -35
  387. package/scripts/fix-format-tool-surface.mjs +0 -24
  388. package/scripts/fix-tool-exec-visible.mjs +0 -42
  389. package/scripts/patch-agent-brief.mjs +0 -48
  390. package/scripts/patch-app.mjs +0 -21
  391. package/scripts/patch-app2.mjs +0 -18
  392. package/scripts/patch-dist-brief.mjs +0 -96
  393. package/scripts/patch-tool-exec.mjs +0 -70
  394. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  395. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  396. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  397. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  398. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  399. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  400. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  401. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  402. package/src/standalone/agent-task-status.test.mjs +0 -76
  403. package/src/tui/components/tool-output-format.test.mjs +0 -399
  404. package/src/tui/display-width.test.mjs +0 -35
  405. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  406. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  407. package/src/tui/markdown/format-token.test.mjs +0 -354
  408. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  409. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  410. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  411. package/src/tui/prompt-history-store.test.mjs +0 -52
  412. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  413. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  414. package/src/ui/markdown.test.mjs +0 -70
  415. package/src/ui/statusline-context-label.test.mjs +0 -15
  416. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  417. package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
@@ -0,0 +1,1080 @@
1
+ // Symbol search / callers / callees / references / impact query layer over a
2
+ // built graph. Pure over {graph,cwd,args}; owns no cache state. Extracted
3
+ // verbatim from code-graph.mjs.
4
+ import { readFileSync } from 'node:fs';
5
+ import { _isJsLike } from './lang-predicates.mjs';
6
+ import { _maskNonCodeText } from './text-mask.mjs';
7
+ import {
8
+ _graphRel,
9
+ _getSourceTextForNode,
10
+ _getSourceLinesForNode,
11
+ _getMaskedLinesForNode,
12
+ } from './source-access.mjs';
13
+ import {
14
+ _unicodeBoundaryPattern,
15
+ _lookupCandidateNodes,
16
+ _getTokenSymbolsForNode,
17
+ _collectCheapSymbols,
18
+ _capGraphList,
19
+ } from './symbol-index.mjs';
20
+ import { CODE_GRAPH_MAX_FILES } from './constants.mjs';
21
+ import { _inferSpanEndByIndent } from './span.mjs';
22
+ import {
23
+ _toByteColumn,
24
+ _byteColToCharCol,
25
+ _nearestEnclosingSymbol,
26
+ } from './text-columns.mjs';
27
+ import {
28
+ _keywordSymbolSortKey,
29
+ _tokenizeKeyword,
30
+ _keywordMatchesSymbolName,
31
+ } from './keyword-match.mjs';
32
+
33
+ export function _extractCallees(graph, declHit, _cwd, { cap = 200, callerSymbol = null, language = null } = {}) {
34
+ if (!declHit || !_CALLEES_BRACE_LANGS.has(declHit.lang)) return [];
35
+ const declNode = graph.nodes.get(declHit.rel);
36
+ if (!declNode) return [];
37
+ const sourceText = _getSourceTextForNode(graph, declNode);
38
+ if (!sourceText) return [];
39
+ let declLineIdx = Math.max(0, (declHit.line || 1) - 1);
40
+ let nativeStartCol = null;
41
+ if (callerSymbol && Array.isArray(declNode.symbols)) {
42
+ const rec = declNode.symbols
43
+ .filter((s) => s && s.name === callerSymbol
44
+ && Number.isFinite(Number(s.startLine)) && Number.isFinite(Number(s.startCol)))
45
+ .sort((a, b) => Math.abs(Number(a.startLine) - (declHit.line || 1))
46
+ - Math.abs(Number(b.startLine) - (declHit.line || 1)))[0];
47
+ if (rec) {
48
+ declLineIdx = Math.max(0, Number(rec.startLine) - 1);
49
+ nativeStartCol = Number(rec.startCol);
50
+ }
51
+ }
52
+ let i = 0;
53
+ {
54
+ let ln = 0;
55
+ while (i < sourceText.length && ln < declLineIdx) {
56
+ if (sourceText[i] === '\n') ln += 1;
57
+ i += 1;
58
+ }
59
+ }
60
+ let declColChar;
61
+ if (nativeStartCol != null) {
62
+ const lineEnd0 = sourceText.indexOf('\n', i);
63
+ const lineText0 = sourceText.slice(i, lineEnd0 < 0 ? sourceText.length : lineEnd0);
64
+ declColChar = _byteColToCharCol(lineText0, nativeStartCol);
65
+ } else {
66
+ declColChar = (Number.isFinite(declHit.col) && declHit.col > 1) ? declHit.col : 1;
67
+ }
68
+ if (declColChar > 1) {
69
+ const lineEnd = sourceText.indexOf('\n', i);
70
+ const maxI = lineEnd < 0 ? sourceText.length : lineEnd;
71
+ i = Math.min(i + (declColChar - 1), maxI);
72
+ }
73
+ let inLineComment = false;
74
+ let inBlockComment = false;
75
+ let quote = '';
76
+ let scanI = i;
77
+ let parenDepth = 0;
78
+ let bodyStart = -1;
79
+ while (scanI < sourceText.length) {
80
+ const ch = sourceText[scanI];
81
+ const next = sourceText[scanI + 1];
82
+ if (inLineComment) {
83
+ if (ch === '\n') inLineComment = false;
84
+ scanI += 1; continue;
85
+ }
86
+ if (inBlockComment) {
87
+ if (ch === '*' && next === '/') { inBlockComment = false; scanI += 2; continue; }
88
+ scanI += 1; continue;
89
+ }
90
+ if (quote) {
91
+ if (ch === '\\') { scanI += 2; continue; }
92
+ if (ch === quote) { quote = ''; }
93
+ scanI += 1; continue;
94
+ }
95
+ if (ch === '/' && next === '/') { inLineComment = true; scanI += 2; continue; }
96
+ if (ch === '/' && next === '*') { inBlockComment = true; scanI += 2; continue; }
97
+ if (ch === '"' || ch === "'" || ch === '`') { quote = ch; scanI += 1; continue; }
98
+ if (ch === '(') { parenDepth += 1; scanI += 1; continue; }
99
+ if (ch === ')') { if (parenDepth > 0) parenDepth -= 1; scanI += 1; continue; }
100
+ if (ch === '{' && parenDepth === 0) { bodyStart = scanI; break; }
101
+ if (ch === ';' && parenDepth === 0) break;
102
+ scanI += 1;
103
+ }
104
+ if (bodyStart < 0) return [];
105
+ let depth = 0;
106
+ let bodyEnd = -1;
107
+ inLineComment = false; inBlockComment = false; quote = '';
108
+ let j = bodyStart;
109
+ while (j < sourceText.length) {
110
+ const ch = sourceText[j];
111
+ const next = sourceText[j + 1];
112
+ if (inLineComment) {
113
+ if (ch === '\n') inLineComment = false;
114
+ j += 1; continue;
115
+ }
116
+ if (inBlockComment) {
117
+ if (ch === '*' && next === '/') { inBlockComment = false; j += 2; continue; }
118
+ j += 1; continue;
119
+ }
120
+ if (quote) {
121
+ if (ch === '\\') { j += 2; continue; }
122
+ if (ch === quote) { quote = ''; }
123
+ j += 1; continue;
124
+ }
125
+ if (ch === '/' && next === '/') { inLineComment = true; j += 2; continue; }
126
+ if (ch === '/' && next === '*') { inBlockComment = true; j += 2; continue; }
127
+ if (ch === '"' || ch === "'" || ch === '`') { quote = ch; j += 1; continue; }
128
+ if (ch === '{') depth += 1;
129
+ else if (ch === '}') {
130
+ depth -= 1;
131
+ if (depth === 0) { bodyEnd = j; break; }
132
+ }
133
+ j += 1;
134
+ }
135
+ if (bodyEnd < 0) bodyEnd = sourceText.length;
136
+ const rawBody = sourceText.slice(bodyStart + 1, bodyEnd);
137
+ const maskedBody = _maskNonCodeText(rawBody, declNode.lang);
138
+ const bodyStartLine = sourceText.slice(0, bodyStart + 1).split('\n').length;
139
+ const callRe = /(?<![\p{ID_Continue}$.])([\p{ID_Start}_][\p{ID_Continue}]*)(?=\s*\()/gu;
140
+ const memberCallRe = /\.\s*\??\.?\s*([\p{ID_Start}_][\p{ID_Continue}]*)(?=\s*\()/gu;
141
+ const seen = new Map();
142
+ const selfName = callerSymbol || null;
143
+ const _CALLEES_JS_METHODS = new Set([
144
+ 'trim','trimStart','trimEnd','slice','splice','substring','substr','split',
145
+ 'join','concat','includes','indexOf','lastIndexOf','startsWith','endsWith',
146
+ 'padStart','padEnd','repeat','charAt','charCodeAt','codePointAt','at',
147
+ 'toUpperCase','toLowerCase','normalize','match','matchAll','search',
148
+ 'replace','replaceAll','push','pop','shift','unshift','reverse','sort',
149
+ 'flat','flatMap','forEach','map','filter','every','some','reduce',
150
+ 'reduceRight','find','findIndex','findLast','findLastIndex','fill',
151
+ 'copyWithin','toString','valueOf','hasOwnProperty','keys','values',
152
+ 'entries','assign','freeze','then','catch','finally','resolve','reject',
153
+ 'all','allSettled','race','any','get','set','has','add','delete','clear',
154
+ 'max','min','floor','ceil','round','abs','sqrt','pow','log','sign','trunc',
155
+ 'random','hypot','parse','stringify','parseInt','parseFloat','isInteger',
156
+ 'isFinite','isNaN','toFixed','isArray','from','of','addEventListener',
157
+ 'removeEventListener','dispatchEvent','bind','call','apply',
158
+ ]);
159
+ const recordHit = (name, index, isMember) => {
160
+ if (!name) return;
161
+ if (_CALLEES_JS_KEYWORDS.has(name)) return;
162
+ if (_isJsLike(declHit.lang)) {
163
+ if (_CALLEES_JS_BUILTINS.has(name)) return;
164
+ if (isMember && _CALLEES_JS_METHODS.has(name)) return;
165
+ }
166
+ if (selfName && name === selfName) return;
167
+ if (seen.has(name)) return;
168
+ const upto = maskedBody.slice(0, index);
169
+ const lineInBody = upto.split('\n').length - 1;
170
+ const absLine = bodyStartLine + lineInBody;
171
+ const absIndex = bodyStart + 1 + index;
172
+ const lineStart = sourceText.lastIndexOf('\n', absIndex - 1) + 1;
173
+ const charCol = absIndex - lineStart + 1;
174
+ seen.set(name, { line: absLine, col: charCol, isMember });
175
+ };
176
+ let m = null;
177
+ while ((m = callRe.exec(maskedBody))) recordHit(m[1], m.index, false);
178
+ let mm = null;
179
+ while ((mm = memberCallRe.exec(maskedBody))) {
180
+ const methodStart = mm.index + mm[0].length - mm[1].length;
181
+ recordHit(mm[1], methodStart, true);
182
+ }
183
+ if (seen.size === 0) return [];
184
+ const allUnique = [...seen.entries()];
185
+ const sliced = allUnique.slice(0, cap);
186
+ const sourceLines = sourceText.split(/\r?\n/);
187
+ const rows = [];
188
+ for (const [name, info] of sliced) {
189
+ let resolvedPath = '';
190
+ let resolvedLine = 0;
191
+ let resolvedDecl = false;
192
+ try {
193
+ const calleeDecl = _resolveCalleeDeclaration(graph, name, { language, preferRel: declHit.rel });
194
+ if (calleeDecl && calleeDecl.declarationLike) {
195
+ const memberOk = !info.isMember
196
+ || calleeDecl.rel === declHit.rel
197
+ || (Array.isArray(declNode.resolvedImports)
198
+ && declNode.resolvedImports.some((p) => _graphRel(p, _cwd) === calleeDecl.rel));
199
+ if (memberOk) {
200
+ resolvedPath = calleeDecl.rel;
201
+ resolvedLine = calleeDecl.line || 0;
202
+ resolvedDecl = true;
203
+ }
204
+ }
205
+ } catch {
206
+ // Identifier shapes that trip the lookup regex fall through.
207
+ }
208
+ const snippetRaw = String(sourceLines[info.line - 1] || '').trim();
209
+ const snippet = snippetRaw.slice(0, 80);
210
+ let enclosing = '';
211
+ try {
212
+ const _encByteCol = _toByteColumn(sourceLines[info.line - 1] || '', info.col);
213
+ const enc = _nearestEnclosingSymbol(declNode, sourceText, info.line, _encByteCol);
214
+ enclosing = enc?.name || '';
215
+ } catch {
216
+ // Falls through to empty enclosing — non-fatal.
217
+ }
218
+ rows.push({
219
+ name,
220
+ callsitePath: declHit.rel,
221
+ callsiteLine: info.line,
222
+ declPath: resolvedPath,
223
+ declLine: resolvedLine,
224
+ external: !resolvedDecl,
225
+ enclosing,
226
+ snippet,
227
+ });
228
+ }
229
+ if (allUnique.length > sliced.length) {
230
+ rows.push({
231
+ name: '...',
232
+ callsitePath: '',
233
+ callsiteLine: 0,
234
+ declPath: '',
235
+ declLine: 0,
236
+ enclosing: '',
237
+ snippet: `+${allUnique.length - sliced.length} more callees (cap=${cap})`,
238
+ truncationFooter: true,
239
+ });
240
+ }
241
+ return rows;
242
+ }
243
+
244
+ export function _formatCalleeRow(row) {
245
+ if (row.truncationFooter) return `... ${row.snippet}`;
246
+ const callsite = row.callsitePath ? `callsite ${row.callsitePath}:${row.callsiteLine}` : 'callsite (unknown)';
247
+ if (row.external) {
248
+ const enclosingExt = row.enclosing ? `(in ${row.enclosing})` : '(in ?)';
249
+ return `${row.name}\t${callsite}\tdecl (external/builtin)\t${enclosingExt}`;
250
+ }
251
+ const decl = row.declPath ? `decl ${row.declPath}:${row.declLine}` : 'decl (unresolved)';
252
+ const enclosing = row.enclosing ? `(in ${row.enclosing})` : '(in ?)';
253
+ const next = `next: find_symbol({symbol:"${row.name}"})`;
254
+ return `${row.name}\t${callsite}\t${decl}\t${enclosing}\t${next}`;
255
+ }
256
+ export async function _prewarmReferenceSourceText(graph, symbol, language) {
257
+ const candidateNodes = _lookupCandidateNodes(graph, symbol, language);
258
+ if (!candidateNodes.length) return;
259
+ const uncached = [];
260
+ for (const node of candidateNodes) {
261
+ const cached = graph._sourceTextCache?.get(node.rel);
262
+ if (!cached || cached.fingerprint !== (node.fingerprint || '')) {
263
+ uncached.push(node);
264
+ }
265
+ }
266
+ if (uncached.length === 0) return;
267
+ const { readFile } = await import('fs/promises');
268
+ const concurrency = 64;
269
+ let next = 0;
270
+ async function worker() {
271
+ while (true) {
272
+ const index = next++;
273
+ if (index >= uncached.length) return;
274
+ const node = uncached[index];
275
+ try {
276
+ const text = await readFile(node.abs, 'utf8');
277
+ graph._sourceTextCache?.set(node.rel, { fingerprint: node.fingerprint || '', text });
278
+ } catch { /* skip unreadable file */ }
279
+ }
280
+ }
281
+ const workerCount = Math.min(Math.max(1, concurrency), uncached.length);
282
+ await Promise.all(Array.from({ length: workerCount }, () => worker()));
283
+ }
284
+
285
+ export function _cheapReferenceSearch(graph, symbol, cwd, { language = null, limit = null, fileRel = null, scopeRelPrefix = null } = {}) {
286
+ const escaped = String(symbol || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
287
+ if (!escaped) return '(no references)';
288
+ const cacheKey = `${language || '*'}|${symbol}|${Number.isFinite(limit) && limit > 0 ? String(Math.floor(limit)) : 'd'}|${fileRel || '*'}|${scopeRelPrefix || '*'}`;
289
+ const cached = graph?._referenceSearchCache?.get(cacheKey);
290
+ if (typeof cached === 'string') {
291
+ return cached;
292
+ }
293
+ const lines = [];
294
+ let candidateNodes = _lookupCandidateNodes(graph, symbol, language);
295
+ if (fileRel) candidateNodes = candidateNodes.filter((node) => node.rel === fileRel);
296
+ if (scopeRelPrefix) candidateNodes = candidateNodes.filter((node) => node.rel === scopeRelPrefix.slice(0, -1) || node.rel.startsWith(scopeRelPrefix));
297
+ const ENV_CAP = Math.max(1, Number(process.env.REFERENCE_HIT_CAP) || 200);
298
+ const REFERENCE_HIT_CAP = limit !== null && Number.isFinite(limit) && limit > 0
299
+ ? Math.min(Math.max(1, Math.floor(limit)), ENV_CAP)
300
+ : ENV_CAP;
301
+ const REFERENCE_LINE_CAP = Math.max(20, Number(process.env.REFERENCE_LINE_CAP) || 80);
302
+ let cappedOut = false;
303
+ outer: for (const node of candidateNodes) {
304
+ const sourceText = _getSourceTextForNode(graph, node);
305
+ if (!sourceText.includes(symbol)) continue;
306
+ const fileLines = _getMaskedLinesForNode(graph, node);
307
+ const rawLines = _getSourceLinesForNode(graph, node);
308
+ for (let i = 0; i < fileLines.length; i++) {
309
+ const line = fileLines[i];
310
+ if (!line.trim()) continue;
311
+ const boundaryLang = language || node.lang;
312
+ const re = new RegExp(_unicodeBoundaryPattern(escaped, boundaryLang, symbol), 'gu');
313
+ let match = null;
314
+ while ((match = re.exec(line))) {
315
+ if (lines.length < REFERENCE_HIT_CAP) {
316
+ const trimmed = (rawLines[i] ?? line).trim().slice(0, REFERENCE_LINE_CAP);
317
+ lines.push(`${node.rel}:${i + 1}:${match.index + 1} ${trimmed}`);
318
+ } else {
319
+ cappedOut = true;
320
+ break outer;
321
+ }
322
+ }
323
+ }
324
+ }
325
+ const result = lines.length ? lines.join('\n') : '(no references)';
326
+ const finalResult = cappedOut
327
+ ? `${result}\n\n[truncated — total hits exceeded ${REFERENCE_HIT_CAP * 4}, showing first ${REFERENCE_HIT_CAP}; raise REFERENCE_HIT_CAP env var for more]`
328
+ : result;
329
+ graph?._referenceSearchCache?.set(cacheKey, finalResult);
330
+ return finalResult;
331
+ }
332
+
333
+ function _nativeEndLineForDecl(node, symbolName, declLine) {
334
+ const symbols = Array.isArray(node?.symbols) ? node.symbols : [];
335
+ if (!symbols.length || !symbolName) return null;
336
+ const dl = Number(declLine);
337
+ if (!Number.isFinite(dl)) return null;
338
+ let exact = null;
339
+ let nearest = null;
340
+ let nearestDist = Infinity;
341
+ for (const s of symbols) {
342
+ if (!s || s.name !== symbolName) continue;
343
+ const sl = Number(s.startLine ?? s.line);
344
+ const el = Number(s.endLine);
345
+ if (!Number.isFinite(sl) || !Number.isFinite(el)) continue;
346
+ if (sl === dl && el >= dl) exact = el;
347
+ const dist = Math.abs(sl - dl);
348
+ if (dist < nearestDist) {
349
+ nearestDist = dist;
350
+ nearest = el >= sl ? el : null;
351
+ }
352
+ }
353
+ if (exact != null) return exact;
354
+ return nearestDist <= 2 ? nearest : null;
355
+ }
356
+
357
+ export function _formatSymbolHitLocation(hit) {
358
+ const line = Number(hit.line);
359
+ const col = Number(hit.col) || 1;
360
+ const end = Number(hit.endLine);
361
+ if (Number.isFinite(end) && end >= line) return `${hit.rel}:${line}-${end}:${col}`;
362
+ return `${hit.rel}:${line}:${col}`;
363
+ }
364
+
365
+ export function _sortSymbolHits(hits) {
366
+ if (!hits?.length) return hits;
367
+ const depthOf = (rel) => String(rel || '').split('/').length;
368
+ const isCanonicalSrc = (rel) => /^src\//.test(rel || '');
369
+ hits.sort((a, b) =>
370
+ Number(b.declarationLike) - Number(a.declarationLike)
371
+ || Number(isCanonicalSrc(b.rel)) - Number(isCanonicalSrc(a.rel))
372
+ || depthOf(a.rel) - depthOf(b.rel)
373
+ || b.matchCount - a.matchCount
374
+ || a.rel.localeCompare(b.rel)
375
+ || a.line - b.line
376
+ );
377
+ const declCount = hits.reduce((n, h) => n + (h.declarationLike ? 1 : 0), 0);
378
+ if (declCount > 1 && hits[0]) hits[0].ambiguousDeclaration = declCount;
379
+ return hits;
380
+ }
381
+
382
+ export function _findSymbolHits(graph, symbol, { language = null } = {}) {
383
+ const cleanSymbol = String(symbol || '').trim();
384
+ if (!cleanSymbol) return [];
385
+ const candidateNodes = _lookupCandidateNodes(graph, cleanSymbol, language);
386
+ return _findSymbolHitsOnNodes(graph, cleanSymbol, candidateNodes, { language });
387
+ }
388
+
389
+ function _findSymbolHitsOnNodes(graph, cleanSymbol, candidateNodes, { language = null } = {}) {
390
+ if (!cleanSymbol) return [];
391
+ const escaped = cleanSymbol.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
392
+ const declRe = new RegExp(
393
+ `(?:^|[\\s;{(,])(?:export\\s+(?:default\\s+)?)?(?:public\\s+|private\\s+|protected\\s+|internal\\s+|static\\s+|abstract\\s+|final\\s+|sealed\\s+|virtual\\s+|override\\s+|async\\s+|pub\\s+(?:\\([^)]*\\)\\s+)?)*(?:const|let|var|function\\*?|class|interface|type|enum|def|func|fn|struct|union|trait|impl|mod|record|object|typedef|namespace|package)\\s+${escaped}\\b`
394
+ );
395
+ const assignDeclRe = new RegExp(
396
+ `(?:^|[\\s;{(,])(?:export\\s+(?:default\\s+)?)?(?:const|let|var)\\s+${escaped}\\s*=\\s*(?:async\\s+)?(?:function\\b|(?:\\([^)]*\\)|[A-Za-z_$][\\w$]*)\\s*=>)`
397
+ );
398
+ const hits = [];
399
+ for (const node of candidateNodes) {
400
+ const sourceText = _getSourceTextForNode(graph, node);
401
+ if (!sourceText.includes(cleanSymbol)) continue;
402
+ const boundaryLang = language || node.lang;
403
+ const re = new RegExp(_unicodeBoundaryPattern(escaped, boundaryLang, cleanSymbol), 'gu');
404
+ const sourceLines = _getSourceLinesForNode(graph, node);
405
+ const lines = _getMaskedLinesForNode(graph, node);
406
+ let firstLine = null;
407
+ let firstCol = null;
408
+ let matchCount = 0;
409
+ let firstContent = '';
410
+ let contextLines = [];
411
+ let declarationLike = Array.isArray(node.topLevelTypes) && node.topLevelTypes.includes(cleanSymbol);
412
+ let declLine = null;
413
+ let declCol = null;
414
+ let declContent = '';
415
+ let declContext = [];
416
+ const hasNativeSymbols = Array.isArray(node.symbols) && node.symbols.length > 0;
417
+ const nativeDeclLines = new Set();
418
+ const nativeSymbolSource = hasNativeSymbols ? node.symbols : _collectCheapSymbols(sourceText, node.lang);
419
+ for (const sym of nativeSymbolSource) {
420
+ if (sym && sym.name === cleanSymbol) nativeDeclLines.add(sym.line);
421
+ }
422
+ let nativeDeclLine = null;
423
+ let nativeDeclCol = null;
424
+ let nativeDeclContent = '';
425
+ let nativeDeclContext = [];
426
+ for (let i = 0; i < lines.length; i++) {
427
+ const line = lines[i];
428
+ if (!line.trim()) continue;
429
+ re.lastIndex = 0;
430
+ let localHit = false;
431
+ let match = null;
432
+ while ((match = re.exec(line))) {
433
+ matchCount += 1;
434
+ localHit = true;
435
+ if (firstLine == null) {
436
+ firstLine = i + 1;
437
+ firstCol = match.index + 1;
438
+ firstContent = String(sourceLines[i] || '').trim();
439
+ contextLines = sourceLines.slice(i, i + 3).map((line) => String(line || '').trim()).filter(Boolean);
440
+ }
441
+ if (declLine == null && (assignDeclRe.test(line) || (!hasNativeSymbols && declRe.test(line)))) {
442
+ declLine = i + 1;
443
+ declCol = match.index + 1;
444
+ declContent = String(sourceLines[i] || '').trim();
445
+ declContext = sourceLines.slice(i, i + 3).map((l) => String(l || '').trim()).filter(Boolean);
446
+ }
447
+ if (nativeDeclLine == null && nativeDeclLines.has(i + 1)) {
448
+ nativeDeclLine = i + 1;
449
+ nativeDeclCol = match.index + 1;
450
+ nativeDeclContent = String(sourceLines[i] || '').trim();
451
+ nativeDeclContext = sourceLines.slice(i, i + 3).map((l) => String(l || '').trim()).filter(Boolean);
452
+ }
453
+ }
454
+ if (localHit && (nativeDeclLines.has(i + 1) || assignDeclRe.test(line) || (!hasNativeSymbols && declRe.test(line)))) declarationLike = true;
455
+ }
456
+ if (firstLine == null) continue;
457
+ if (nativeDeclLine != null) {
458
+ declLine = nativeDeclLine;
459
+ declCol = nativeDeclCol;
460
+ declContent = nativeDeclContent;
461
+ declContext = nativeDeclContext;
462
+ }
463
+ const hasDeclPos = declLine != null;
464
+ const declLineForEnd = hasDeclPos ? declLine : firstLine;
465
+ const endLine = _nativeEndLineForDecl(node, cleanSymbol, declLineForEnd);
466
+ hits.push({
467
+ rel: node.rel,
468
+ lang: node.lang,
469
+ line: hasDeclPos ? declLine : firstLine,
470
+ col: hasDeclPos ? declCol : (firstCol || 1),
471
+ ...(Number.isFinite(endLine) && endLine >= declLineForEnd ? { endLine } : {}),
472
+ declarationLike,
473
+ matchCount,
474
+ content: hasDeclPos ? declContent : firstContent,
475
+ context: hasDeclPos ? declContext : contextLines,
476
+ firstLine,
477
+ firstCol: firstCol || 1,
478
+ firstContent,
479
+ firstContext: contextLines,
480
+ });
481
+ }
482
+ if (!hits.length) return [];
483
+ return _sortSymbolHits(hits);
484
+ }
485
+
486
+ // Brace-delimited languages the callee body scanner supports. Non-brace
487
+ // languages get a deterministic skip downstream.
488
+ export const _CALLEES_BRACE_LANGS = new Set([
489
+ 'javascript', 'typescript', 'java', 'csharp', 'kotlin', 'go',
490
+ 'rust', 'c', 'cpp', 'php', 'swift', 'scala', 'dart', 'objc', 'zig',
491
+ ]);
492
+
493
+ // JS/TS reserved words / syntactic keywords that look like call expressions
494
+ // but are not function invocations.
495
+ const _CALLEES_JS_KEYWORDS = new Set([
496
+ 'if', 'else', 'for', 'while', 'do', 'switch', 'case', 'default',
497
+ 'return', 'yield', 'await', 'throw', 'try', 'catch', 'finally',
498
+ 'break', 'continue', 'with', 'in', 'of', 'new', 'delete', 'typeof',
499
+ 'void', 'instanceof', 'function', 'class', 'const', 'let', 'var',
500
+ 'this', 'super', 'extends', 'import', 'export', 'from', 'as',
501
+ 'static', 'async', 'true', 'false', 'null', 'undefined',
502
+ 'sizeof', 'using', 'namespace', 'interface', 'type', 'enum',
503
+ ]);
504
+
505
+ // JS/TS built-in globals / constructors / namespaces. Filtered only when
506
+ // scanning JS/TS bodies so Go/Rust/etc. callees named Map/Set/parse/get
507
+ // are not suppressed.
508
+ const _CALLEES_JS_BUILTINS = new Set([
509
+ 'Error', 'TypeError', 'RangeError', 'SyntaxError', 'ReferenceError',
510
+ 'EvalError', 'URIError', 'AggregateError',
511
+ 'String', 'Number', 'Boolean', 'Array', 'Object', 'Function',
512
+ 'Set', 'Map', 'WeakSet', 'WeakMap', 'WeakRef', 'FinalizationRegistry',
513
+ 'Promise', 'Symbol', 'BigInt', 'Date', 'RegExp', 'Proxy',
514
+ 'ArrayBuffer', 'SharedArrayBuffer', 'DataView', 'Int8Array', 'Uint8Array',
515
+ 'Uint8ClampedArray', 'Int16Array', 'Uint16Array', 'Int32Array', 'Uint32Array',
516
+ 'Float32Array', 'Float64Array', 'BigInt64Array', 'BigUint64Array',
517
+ 'parseInt', 'parseFloat', 'isNaN', 'isFinite', 'encodeURI',
518
+ 'encodeURIComponent', 'decodeURI', 'decodeURIComponent', 'eval',
519
+ 'globalThis', 'NaN', 'Infinity',
520
+ 'JSON', 'Math', 'Reflect', 'Atomics', 'Intl', 'console', 'process',
521
+ 'fetch', 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval',
522
+ 'queueMicrotask', 'structuredClone', 'requestAnimationFrame',
523
+ 'cancelAnimationFrame', 'alert', 'confirm', 'prompt',
524
+ 'require',
525
+ ]);
526
+
527
+ export function _pickCalleeDeclHit(hits, preferRel) {
528
+ if (!hits?.length) return null;
529
+ const sameFileDecl = preferRel ? hits.find((h) => h.rel === preferRel && h.declarationLike) : null;
530
+ if (sameFileDecl) return sameFileDecl;
531
+ const depthOf = (rel) => String(rel || '').split('/').length;
532
+ const isCanonicalSrc = (rel) => /^src\//.test(rel || '');
533
+ const sorted = [...hits].sort((a, b) =>
534
+ Number(b.declarationLike) - Number(a.declarationLike)
535
+ || Number(isCanonicalSrc(b.rel)) - Number(isCanonicalSrc(a.rel))
536
+ || depthOf(a.rel) - depthOf(b.rel)
537
+ || b.matchCount - a.matchCount
538
+ || a.rel.localeCompare(b.rel)
539
+ || a.line - b.line
540
+ );
541
+ return sorted.find((h) => h.declarationLike) || sorted[0];
542
+ }
543
+
544
+ function _resolveCalleeDeclaration(graph, name, { language = null, preferRel = null } = {}) {
545
+ return _pickCalleeDeclHit(_findSymbolHits(graph, name, { language }), preferRel);
546
+ }
547
+
548
+ function _nativeSymbolHit(node, sym) {
549
+ const line = Number(sym?.line ?? sym?.startLine);
550
+ if (!Number.isFinite(line) || line < 1) return null;
551
+ const endLine = Number(sym?.endLine);
552
+ return {
553
+ rel: node.rel,
554
+ lang: node.lang,
555
+ line,
556
+ col: Number(sym?.startCol) || Number(sym?.col) || 1,
557
+ endLine: Number.isFinite(endLine) && endLine >= line ? endLine : null,
558
+ declarationLike: true,
559
+ matchCount: 1,
560
+ content: '',
561
+ context: [],
562
+ };
563
+ }
564
+
565
+ function _collectNativeKeywordSymbolEntries(graph, keyword, { language = null } = {}) {
566
+ const lowerKey = String(keyword || '').toLowerCase();
567
+ if (!lowerKey) return [];
568
+ const keyTokens = _tokenizeKeyword(keyword);
569
+ const byName = new Map();
570
+ for (const node of graph?.nodes?.values?.() || []) {
571
+ if (language && node.lang !== language) continue;
572
+ const symbols = Array.isArray(node?.symbols) ? node.symbols : [];
573
+ if (!symbols.length) continue;
574
+ for (const sym of symbols) {
575
+ const name = String(sym?.name || '').trim();
576
+ if (!_keywordMatchesSymbolName(name, lowerKey, keyTokens)) continue;
577
+ const hit = _nativeSymbolHit(node, sym);
578
+ if (!hit) continue;
579
+ if (!byName.has(name)) byName.set(name, []);
580
+ byName.get(name).push(hit);
581
+ }
582
+ }
583
+ const entries = [];
584
+ for (const [name, hits] of byName.entries()) {
585
+ const sorted = _sortSymbolHits(hits);
586
+ entries.push({
587
+ name,
588
+ hit: _pickCalleeDeclHit(sorted) || sorted[0] || null,
589
+ resolved: sorted.length > 0,
590
+ });
591
+ }
592
+ entries.sort((a, b) => {
593
+ const ka = _keywordSymbolSortKey(a.name, keyword);
594
+ const kb = _keywordSymbolSortKey(b.name, keyword);
595
+ if (ka && !kb) return -1;
596
+ if (!ka && kb) return 1;
597
+ if (!ka && !kb) return a.name.localeCompare(b.name);
598
+ for (let i = 0; i < 3; i += 1) {
599
+ if (ka[i] !== kb[i]) return ka[i] - kb[i];
600
+ }
601
+ return a.name.localeCompare(b.name);
602
+ });
603
+ return entries;
604
+ }
605
+
606
+ function _collectCheapKeywordSymbolEntries(graph, keyword, { language = null } = {}) {
607
+ const lowerKey = String(keyword || '').toLowerCase();
608
+ if (!lowerKey) return [];
609
+ const keyTokens = _tokenizeKeyword(keyword);
610
+ const entries = [];
611
+ for (const node of graph?.nodes?.values?.() || []) {
612
+ if (language && node.lang !== language) continue;
613
+ if (Array.isArray(node?.symbols) && node.symbols.length) continue;
614
+ const sourceText = _getSourceTextForNode(graph, node);
615
+ for (const sym of _collectCheapSymbols(sourceText, node.lang)) {
616
+ const name = String(sym?.name || '').trim();
617
+ if (!_keywordMatchesSymbolName(name, lowerKey, keyTokens)) continue;
618
+ const hit = _nativeSymbolHit(node, sym);
619
+ if (!hit) continue;
620
+ entries.push({ name, hit, resolved: true });
621
+ }
622
+ }
623
+ return entries;
624
+ }
625
+
626
+ function _formatSearchSymbolRow(name, hit) {
627
+ const loc = hit ? _formatSymbolHitLocation(hit) : '(unresolved)';
628
+ const next = `next: find_symbol({symbol:"${name}"})`;
629
+ return `${name}\t${loc}\t${next}`;
630
+ }
631
+
632
+ export function _searchSymbolsByKeyword(graph, keyword, cwd, { language = null, limit = 30 } = {}) {
633
+ const clean = String(keyword || '').trim();
634
+ if (!clean) return '(no keyword)';
635
+ const cap = Math.max(1, Math.min(100, Math.floor(Number(limit) || 30)));
636
+ const nativeEntries = _collectNativeKeywordSymbolEntries(graph, clean, { language });
637
+ const cheapEntries = _collectCheapKeywordSymbolEntries(graph, clean, { language });
638
+ const entries = [...nativeEntries, ...cheapEntries];
639
+ if (!entries.length) {
640
+ const nodeCount = graph?.nodes?.size ?? 0;
641
+ return `(no symbol keyword matches in cwd=${cwd})\ngraph: nodes=${nodeCount}${language ? `, language=${language}` : ''}`;
642
+ }
643
+ entries.sort((a, b) => {
644
+ const rank = Number(b.resolved) - Number(a.resolved);
645
+ if (rank !== 0) return rank;
646
+ const ka = _keywordSymbolSortKey(a.name, keyword);
647
+ const kb = _keywordSymbolSortKey(b.name, keyword);
648
+ if (ka && !kb) return -1;
649
+ if (!ka && kb) return 1;
650
+ if (!ka && !kb) return a.name.localeCompare(b.name);
651
+ for (let i = 0; i < 3; i += 1) {
652
+ if (ka[i] !== kb[i]) return ka[i] - kb[i];
653
+ }
654
+ return a.name.localeCompare(b.name);
655
+ });
656
+ const resolvedEntries = entries.filter((e) => e.resolved);
657
+ const unresolvedNames = entries.filter((e) => !e.resolved).map((e) => e.name);
658
+ const shownResolved = resolvedEntries.slice(0, cap);
659
+ const lines = [`# search keyword=${clean} matches=${entries.length} shown=${shownResolved.length}`];
660
+ for (const { name, hit } of shownResolved) {
661
+ lines.push(_formatSearchSymbolRow(name, hit));
662
+ }
663
+ if (resolvedEntries.length > shownResolved.length) {
664
+ lines.push(`...+${resolvedEntries.length - shownResolved.length} more resolved (cap=${cap})`);
665
+ }
666
+ if (unresolvedNames.length) {
667
+ lines.push(`+${unresolvedNames.length} unresolved name variants (token-only, no declaration — find_symbol will miss these; grep to locate): ${unresolvedNames.join(', ')}`);
668
+ }
669
+ if (graph?.truncated) {
670
+ lines.push(`WARN: graph truncated at CODE_GRAPH_MAX_FILES=${CODE_GRAPH_MAX_FILES} — matches may be incomplete. Re-run with a narrower cwd.`);
671
+ }
672
+ return lines.join('\n');
673
+ }
674
+
675
+ function _parseReferenceEntries(referenceText) {
676
+ if (typeof referenceText !== 'string' || !referenceText.trim() || referenceText === '(no references)') {
677
+ return [];
678
+ }
679
+ const out = [];
680
+ for (const line of referenceText.split('\n')) {
681
+ const trimmed = line.trim();
682
+ if (!trimmed) continue;
683
+ const m = /^(.+?):(\d+):(\d+)(?:[\s\t]+(.*))?$/.exec(trimmed);
684
+ if (!m) continue;
685
+ out.push({ file: m[1], line: Number(m[2]), col: Number(m[3]), text: m[4] ? m[4].trim() : '' });
686
+ }
687
+ return out;
688
+ }
689
+
690
+ function _formatSymbolImpactLine(item) {
691
+ const callerSuffix = item.callers.length ? ` -> ${item.callers.join(', ')}` : '';
692
+ return `${item.symbol}\trefs=${item.references}\tcallers=${item.callers.length}${callerSuffix}`;
693
+ }
694
+
695
+ function _collectImpactSymbols(node, graph) {
696
+ const names = new Set();
697
+ for (const typeName of Array.isArray(node?.topLevelTypes) ? node.topLevelTypes : []) names.add(typeName);
698
+ const text = _getSourceTextForNode(graph, node);
699
+ for (const item of _collectCheapSymbols(text, node.lang)) names.add(item.name);
700
+ return [...names];
701
+ }
702
+
703
+ function _buildImpactSummary(node, graph, cwd, targetSymbol = '') {
704
+ const imports = node.resolvedImports.map((p) => _graphRel(p, cwd));
705
+ const dependents = [...(graph.reverse.get(node.rel) || [])].sort();
706
+ const related = [...new Set([...imports, ...dependents])].sort();
707
+ const symbols = targetSymbol ? [targetSymbol] : _collectImpactSymbols(node, graph).slice(0, 8);
708
+ const symbolImpact = [];
709
+ const externalCallers = new Set();
710
+ let externalReferences = 0;
711
+ for (const symbol of symbols) {
712
+ const refs = _parseReferenceEntries(_cheapReferenceSearch(graph, symbol, cwd, { language: node.lang }))
713
+ .filter((entry) => entry.file !== node.rel);
714
+ if (refs.length === 0) continue;
715
+ const callers = [...new Set(refs.map((entry) => entry.file))].sort();
716
+ for (const caller of callers) externalCallers.add(caller);
717
+ externalReferences += refs.length;
718
+ symbolImpact.push({ symbol, references: refs.length, callers });
719
+ }
720
+ symbolImpact.sort((a, b) => (b.references - a.references) || a.symbol.localeCompare(b.symbol));
721
+ return {
722
+ imports,
723
+ dependents,
724
+ related,
725
+ symbolImpact,
726
+ externalCallers: [...externalCallers].sort(),
727
+ externalReferences,
728
+ scannedSymbols: symbols.length,
729
+ };
730
+ }
731
+
732
+ export function _formatRelated(node, graph, cwd) {
733
+ const imports = node.resolvedImports.map((p) => _graphRel(p, cwd));
734
+ const dependents = [...(graph.reverse.get(node.rel) || [])].sort();
735
+ const related = [...new Set([...imports, ...dependents])].sort();
736
+ const lines = [
737
+ `file\t${node.rel}`,
738
+ `language\t${node.lang}`,
739
+ `imports\t${imports.length}`,
740
+ `dependents\t${dependents.length}`,
741
+ `related\t${related.length}`,
742
+ ];
743
+ lines.push('');
744
+ lines.push('# imports');
745
+ lines.push(imports.length ? _capGraphList(imports).join('\n') : '(none)');
746
+ lines.push('');
747
+ lines.push('# dependents');
748
+ lines.push(dependents.length ? _capGraphList(dependents).join('\n') : '(none)');
749
+ if (related.length) {
750
+ lines.push('');
751
+ lines.push('# related');
752
+ lines.push(..._capGraphList(related));
753
+ }
754
+ return lines.join('\n');
755
+ }
756
+
757
+ export function _formatImpact(node, graph, cwd, targetSymbol = '') {
758
+ const summary = _buildImpactSummary(node, graph, cwd, targetSymbol);
759
+ const lines = [
760
+ `file\t${node.rel}`,
761
+ `language\t${node.lang}`,
762
+ `imports\t${summary.imports.length}`,
763
+ `dependents\t${summary.dependents.length}`,
764
+ `related\t${summary.related.length}`,
765
+ `scanned_symbols\t${summary.scannedSymbols}`,
766
+ `external_references\t${summary.externalReferences}`,
767
+ `external_callers\t${summary.externalCallers.length}`,
768
+ ];
769
+ if (targetSymbol) lines.push(`symbol\t${targetSymbol}`);
770
+ if (summary.related.length) {
771
+ lines.push('');
772
+ lines.push('# structural');
773
+ lines.push(..._capGraphList(summary.related));
774
+ }
775
+ if (summary.symbolImpact.length) {
776
+ lines.push('');
777
+ lines.push(targetSymbol ? '# symbol impact' : '# top symbol impact');
778
+ lines.push(...summary.symbolImpact.slice(0, 5).map(_formatSymbolImpactLine));
779
+ }
780
+ if (summary.externalCallers.length) {
781
+ lines.push('');
782
+ lines.push('# external callers');
783
+ lines.push(..._capGraphList(summary.externalCallers));
784
+ }
785
+ return lines.join('\n');
786
+ }
787
+
788
+ export function _findSymbolAcrossGraph(graph, symbol, cwd, { language = null, limit = 5, fileRel = null, body = true } = {}) {
789
+ const allHits = _findSymbolHits(graph, symbol, { language });
790
+ const hits = fileRel ? allHits.filter((h) => h.rel === fileRel) : allHits;
791
+
792
+ if (!hits.length) {
793
+ const nodeCount = graph?.nodes?.size ?? 0;
794
+ const scopeNote = fileRel ? ` file=${fileRel}` : '';
795
+ const lines = [`(no symbol matches in cwd=${cwd}${scopeNote})`];
796
+ lines.push(`graph: nodes=${nodeCount}${language ? `, language=${language}` : ''}`);
797
+ if (graph?.truncated) {
798
+ lines.push(`WARN: graph truncated at CODE_GRAPH_MAX_FILES=${CODE_GRAPH_MAX_FILES} — symbol may exist in an un-indexed file. Re-run with a narrower cwd.`);
799
+ }
800
+ const lowerSym = symbol.toLowerCase();
801
+ const ciHits = [];
802
+ if (graph?._symbolTokenIndex && nodeCount > 0) {
803
+ for (const key of graph._symbolTokenIndex.keys()) {
804
+ const idx = key.indexOf('|');
805
+ if (idx < 0) continue;
806
+ const symPart = key.slice(idx + 1);
807
+ if (symPart !== symbol && symPart.toLowerCase() === lowerSym) {
808
+ if (!ciHits.includes(symPart)) ciHits.push(symPart);
809
+ if (ciHits.length >= 3) break;
810
+ }
811
+ }
812
+ }
813
+ return lines.join('\n');
814
+ }
815
+
816
+ const topHits = hits.slice(0, Math.max(1, limit));
817
+ const primary = topHits[0];
818
+ const declHits = hits.filter((h) => h.declarationLike);
819
+ const declCount = declHits.length;
820
+ const lines = [];
821
+ if (declCount > 1) {
822
+ lines.push(`⚠ ${declCount} declarations found — verify which one you intend`);
823
+ for (const h of declHits) lines.push(` ${_formatSymbolHitLocation(h)} [${h.lang}]`);
824
+ lines.push('');
825
+ }
826
+ if (primary?.declarationLike) {
827
+ lines.push(graph?.truncated
828
+ ? '# best declaration candidate (GRAPH TRUNCATED — may not be canonical; re-run with a narrower cwd to confirm)'
829
+ : '# best declaration candidate');
830
+ const multi = declCount > 1 ? `, declarations=${declCount}` : '';
831
+ lines.push(`${_formatSymbolHitLocation(primary)} (${primary.lang}, matches=${primary.matchCount}${multi})`);
832
+ let bodyEmitted = false;
833
+ if (body === true && Number.isFinite(Number(primary.line))) {
834
+ const node = graph.nodes.get(primary.rel);
835
+ const srcText = node ? _getSourceTextForNode(graph, node) : null;
836
+ if (srcText) {
837
+ const all = srcText.split('\n');
838
+ const start = Math.max(1, Number(primary.line));
839
+ let end = Number(primary.endLine);
840
+ if (!Number.isFinite(end) || end < start) {
841
+ end = _inferSpanEndByIndent(all, start) ?? start;
842
+ }
843
+ end = Math.min(end, start + 299);
844
+ const BODY_FULL_MAX = 120;
845
+ const BODY_HEAD = 90;
846
+ const BODY_TAIL = 20;
847
+ const fmt = (i) => `${start + i}: ${all[start - 1 + i]}`;
848
+ const span = end - start + 1;
849
+ if (span > BODY_FULL_MAX) {
850
+ const head = Array.from({ length: BODY_HEAD }, (_, i) => fmt(i));
851
+ const tail = Array.from({ length: BODY_TAIL }, (_, i) => fmt(span - BODY_TAIL + i));
852
+ const elided = span - BODY_HEAD - BODY_TAIL;
853
+ head.push(`... [${elided} lines elided — full body: read ${primary.rel} symbol=${symbol}]`);
854
+ lines.push([...head, ...tail].join('\n'));
855
+ } else {
856
+ lines.push(all.slice(start - 1, end).map((l, i) => `${start + i}: ${l}`).join('\n'));
857
+ }
858
+ bodyEmitted = true;
859
+ }
860
+ }
861
+ if (!bodyEmitted) {
862
+ if (primary.content) lines.push(primary.content.slice(0, 100));
863
+ if (Array.isArray(primary.context) && primary.context.length > 1) {
864
+ lines.push(`context: ${primary.context.slice(0, 2).join(' | ').slice(0, 120)}`);
865
+ }
866
+ }
867
+ if (declCount > 1) {
868
+ const others = declHits.slice(1, 3).map((h) => `${_formatSymbolHitLocation(h)} [${h.lang}]`);
869
+ if (others.length) lines.push(`other declarations: ${others.join(', ')}`);
870
+ }
871
+ lines.push('');
872
+ }
873
+ lines.push('# candidates');
874
+ lines.push(...topHits.map((hit, idx) => {
875
+ const kind = hit.declarationLike ? 'decl' : 'ref';
876
+ const suffix = hit.content ? ` — ${hit.content.slice(0, 100)}` : '';
877
+ return `${idx + 1}. ${_formatSymbolHitLocation(hit)} [${kind}, ${hit.lang}, matches=${hit.matchCount}]${suffix}`;
878
+ }));
879
+ if (declCount === 0 && hits.length > 0) {
880
+ lines.push('');
881
+ lines.push(`(no user declaration found; likely a global/builtin identifier — all ${hits.length} hits are references)`);
882
+ }
883
+ if (primary?.declarationLike) {
884
+ const calleeRows = _extractCallees(graph, primary, cwd, {
885
+ cap: 25,
886
+ callerSymbol: symbol,
887
+ language,
888
+ });
889
+ lines.push('');
890
+ lines.push('# callees');
891
+ if (calleeRows.length) {
892
+ for (const row of calleeRows) {
893
+ lines.push(_formatCalleeRow(row));
894
+ }
895
+ } else {
896
+ lines.push('(no callees)');
897
+ }
898
+ }
899
+ const _nodeCount = graph?.nodes?.size ?? 0;
900
+ const truncatedSuffix = graph?.truncated
901
+ ? ` [WARN: graph truncated at CODE_GRAPH_MAX_FILES=${CODE_GRAPH_MAX_FILES} — some files not indexed]`
902
+ : '';
903
+ const fileScopeSuffix = fileRel ? ` file=${fileRel}` : '';
904
+ lines.push(`\n# scope: cwd=${cwd} graph=${_nodeCount}-nodes${language ? ` language=${language}` : ''}${fileScopeSuffix}${truncatedSuffix}`);
905
+ return lines.join('\n');
906
+ }
907
+
908
+ export function _resolveReferenceLanguageNode(graph, symbol, rel, cwd, language = null) {
909
+ if (rel) {
910
+ const node = graph.nodes.get(rel);
911
+ if (!node) return { kind: 'file-not-found', node: null, file: rel };
912
+ const tokens = _getTokenSymbolsForNode(graph, node);
913
+ if (Array.isArray(tokens) && tokens.includes(String(symbol || ''))) return { kind: 'ok', node, file: rel };
914
+ const text = _getSourceTextForNode(graph, node);
915
+ if (typeof text === 'string' && text.includes(String(symbol || ''))) return { kind: 'ok', node, file: rel };
916
+ return { kind: 'symbol-not-present', node: null, file: rel };
917
+ }
918
+ const hits = _findSymbolHits(graph, symbol, { language });
919
+ if (!hits.length) return { kind: 'symbol-not-present', node: null, file: null };
920
+ const primary = hits.find((hit) => hit.declarationLike) || hits[0];
921
+ const node = primary?.rel ? graph.nodes.get(primary.rel) || null : null;
922
+ return node ? { kind: 'ok', node, file: node.rel } : { kind: 'symbol-not-present', node: null, file: null };
923
+ }
924
+
925
+ function _referenceKind(line, symbol, lang = null) {
926
+ const escaped = String(symbol || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
927
+ if (!escaped) return 'reference';
928
+ const text = String(line || '');
929
+ if (new RegExp(
930
+ `\\b(?:` +
931
+ `function|class|interface|type|enum|record|struct|union` +
932
+ `|namespace|module|package|trait|impl|object` +
933
+ `|const|let|var|val|typedef` +
934
+ `|def|fn|fun` +
935
+ `)\\s+${escaped}\\b`,
936
+ ).test(text)) return 'declaration';
937
+ if (new RegExp(`\\bfunc(?:\\s*\\([^)]*\\))?\\s+${escaped}\\b`).test(text)) return 'declaration';
938
+ if (new RegExp(`\\bimport\\b[\\s\\S]*${_unicodeBoundaryPattern(escaped, lang, symbol)}`, 'u').test(text)) return 'import';
939
+ if (new RegExp(`${_unicodeBoundaryPattern(escaped, lang, symbol)}\\s*\\(`, 'u').test(text)) return 'call';
940
+ return 'reference';
941
+ }
942
+
943
+ function _collectCallerEntries(graph, symbol, referenceText) {
944
+ const entries = _parseReferenceEntries(referenceText);
945
+ const detailed = [];
946
+ const declLinesCache = new Map();
947
+ for (const entry of entries) {
948
+ const node = graph.nodes.get(entry.file);
949
+ if (!node) continue;
950
+ const sourceText = _getSourceTextForNode(graph, node);
951
+ const sourceLines = sourceText.split(/\r?\n/);
952
+ const line = String(sourceLines[entry.line - 1] || '').trim();
953
+ if (!line) continue;
954
+ let kind = _referenceKind(line, symbol, node.lang);
955
+ if (kind === 'call') {
956
+ let declLines = declLinesCache.get(node.rel);
957
+ if (!declLines) {
958
+ declLines = new Set();
959
+ for (const sym of (Array.isArray(node.symbols) && node.symbols.length ? node.symbols : _collectCheapSymbols(sourceText, node.lang))) {
960
+ if (sym && sym.name === symbol) declLines.add(sym.line);
961
+ }
962
+ declLinesCache.set(node.rel, declLines);
963
+ }
964
+ if (declLines.has(entry.line)) kind = 'declaration';
965
+ }
966
+ const _encByteCol = _toByteColumn(sourceLines[entry.line - 1] || '', entry.col);
967
+ const enclosing = _nearestEnclosingSymbol(node, sourceText, entry.line, _encByteCol);
968
+ detailed.push({
969
+ ...entry,
970
+ kind,
971
+ caller: kind === 'call' ? (enclosing?.name || '') : '',
972
+ lineText: line,
973
+ });
974
+ }
975
+ return detailed;
976
+ }
977
+
978
+ export function _formatCallerReferences(graph, symbol, referenceText, { limit = 200 } = {}) {
979
+ const detailed = _collectCallerEntries(graph, symbol, referenceText);
980
+ if (!detailed.length) return '(no callers)';
981
+ const callSites = detailed.filter((entry) => entry.kind === 'call');
982
+ const format = (entry) => {
983
+ const caller = entry.caller ? `\tcaller=${entry.caller}` : '';
984
+ return `${entry.file}:${entry.line}:${entry.col}\t${entry.kind}${caller}\t${entry.lineText.slice(0, 80)}`;
985
+ };
986
+ if (callSites.length) {
987
+ const total = callSites.length;
988
+ const head = callSites.slice(0, limit).map(format);
989
+ const overflow = total > limit ? [`... +${total - limit} more call sites`] : [];
990
+ return ['# call sites', ...head, ...overflow].join('\n');
991
+ }
992
+ const NON_CALL_CAP = 40;
993
+ const nonCallEntries = detailed.slice(0, NON_CALL_CAP);
994
+ const overflow = detailed.length > NON_CALL_CAP
995
+ ? `\n... +${detailed.length - NON_CALL_CAP} more non-call references`
996
+ : '';
997
+ return [
998
+ '(no call sites)',
999
+ nonCallEntries.length ? `# non-call references\n${nonCallEntries.map(format).join('\n')}${overflow}` : '',
1000
+ ].filter(Boolean).join('\n');
1001
+ }
1002
+
1003
+ function _callerNamesOf(graph, symbol, cwd, language) {
1004
+ const refs = _cheapReferenceSearch(graph, symbol, cwd, { language });
1005
+ const byName = new Map();
1006
+ const leaves = new Map();
1007
+ for (const e of _collectCallerEntries(graph, symbol, refs)) {
1008
+ if (e.kind !== 'call') continue;
1009
+ if (e.caller && e.caller !== symbol) {
1010
+ if (!byName.has(e.caller)) byName.set(e.caller, { name: e.caller, loc: `${e.file}:${e.line}`, leaf: false });
1011
+ } else if (!e.caller) {
1012
+ const loc = `${e.file}:${e.line}`;
1013
+ if (!leaves.has(loc)) {
1014
+ const snippet = String(e.lineText || 'call').replace(/\s+/g, ' ').trim().slice(0, 48);
1015
+ leaves.set(loc, { name: `«${snippet}»`, loc, leaf: true });
1016
+ }
1017
+ }
1018
+ }
1019
+ const ANON_LEAF_MAX = 6;
1020
+ const leafList = leaves.size <= ANON_LEAF_MAX ? [...leaves.values()] : [];
1021
+ return [...byName.values(), ...leafList];
1022
+ }
1023
+
1024
+ export function _formatTransitiveCallers(graph, rootSymbol, cwd, { language = null, depth = 2, pageSize = 100, page = 1, hardMax = 1000 } = {}) {
1025
+ const expanded = new Set();
1026
+ const collected = [];
1027
+ let overflow = false;
1028
+ const walk = (symbol, level) => {
1029
+ if (overflow || level >= depth) return;
1030
+ if (expanded.has(symbol)) {
1031
+ collected.push({ indent: level + 1, label: `${symbol} … (callers expanded above)` });
1032
+ return;
1033
+ }
1034
+ expanded.add(symbol);
1035
+ for (const entry of _callerNamesOf(graph, symbol, cwd, language)) {
1036
+ if (collected.length >= hardMax) { overflow = true; return; }
1037
+ collected.push({ indent: level + 1, label: `${entry.name}\t${entry.loc}` });
1038
+ if (!entry.leaf) walk(entry.name, level + 1);
1039
+ }
1040
+ };
1041
+ walk(rootSymbol, 0);
1042
+ if (collected.length === 0) return _augmentNoHitDiagnostic('(no callers)', '(no callers)', graph, cwd, rootSymbol);
1043
+
1044
+ const size = Math.max(1, Math.floor(Number(pageSize) || 100));
1045
+ const pg = Math.max(1, Math.floor(Number(page) || 1));
1046
+ const total = collected.length;
1047
+ const lastPage = Math.ceil(total / size);
1048
+ const start = (pg - 1) * size;
1049
+ if (start >= total) {
1050
+ return `# transitive callers of ${rootSymbol} (depth=${depth}) — page ${pg} is past the end (total ${total}${overflow ? '+' : ''} node(s); last page is ${lastPage}).`;
1051
+ }
1052
+ const slice = collected.slice(start, start + size);
1053
+ const hasMore = overflow || (start + slice.length) < total;
1054
+ const lines = [
1055
+ `# transitive callers of ${rootSymbol} (depth=${depth}) — page ${pg}, nodes ${start + 1}-${start + slice.length} of ${total}${overflow ? '+' : ''}; INDENTED children are ITS callers`,
1056
+ rootSymbol,
1057
+ ...slice.map((e) => `${' '.repeat(e.indent)}${e.label}`),
1058
+ ];
1059
+ if (hasMore) {
1060
+ lines.push(`# NEXT — more callers remain; re-run callers with the SAME symbol + depth + page:${pg + 1} for the next ${size} node(s). Every node carries file:line — do NOT grep/read.`);
1061
+ } else {
1062
+ lines.push(`# END — complete caller set delivered (page ${pg} of ${lastPage}): named callers PLUS timer/event/module-level call sites (the «…» leaves), each with file:line. No further callers/grep/read is needed.`);
1063
+ }
1064
+ return lines.join('\n');
1065
+ }
1066
+
1067
+ // #4 UNSCOPED empty-result diagnostic. Distinguishes "defined but no edge"
1068
+ // from "not indexed at all" so the caller doesn't re-scope/grep needlessly.
1069
+ export function _augmentNoHitDiagnostic(result, emptyToken, graph, cwd, symbol) {
1070
+ if (typeof result !== 'string' || result.trim() !== emptyToken) return result;
1071
+ const n = graph?.nodes?.size || 0;
1072
+ const trunc = graph?.truncated ? `, graph truncated at ${CODE_GRAPH_MAX_FILES} files` : '';
1073
+ let declHit = null;
1074
+ try { declHit = (_sortSymbolHits(_findSymbolHits(graph, symbol, {})) || [])[0] || null; } catch {}
1075
+ if (declHit) {
1076
+ return `${emptyToken}\n# '${symbol}' IS defined (${_formatSymbolHitLocation(declHit)}) but is genuinely unreferenced in this graph — present, not missing. No re-scope / grep needed.`;
1077
+ }
1078
+ return `${emptyToken}\n# '${symbol}' not present in graph rooted at ${cwd} (${n} files indexed${trunc}). `
1079
+ + `If it should exist, the target is likely outside this cwd — pass an explicit 'cwd' (repo root) or 'file' anchor, or run 'cwd set <repo>'.`;
1080
+ }