mixdog 0.9.1 → 0.9.3

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 (301) hide show
  1. package/package.json +9 -1
  2. package/scripts/_bench-cwc.json +20 -0
  3. package/scripts/agent-loop-policy-test.mjs +37 -0
  4. package/scripts/agent-parallel-smoke.mjs +54 -10
  5. package/scripts/anthropic-maxtokens-test.mjs +119 -0
  6. package/scripts/background-task-meta-smoke.mjs +1 -1
  7. package/scripts/bench-run.mjs +262 -0
  8. package/scripts/build-tui.mjs +13 -1
  9. package/scripts/compact-smoke.mjs +12 -0
  10. package/scripts/compact-trigger-migration-smoke.mjs +67 -1
  11. package/scripts/explore-bench.mjs +124 -0
  12. package/scripts/hook-bus-test.mjs +191 -0
  13. package/scripts/ingest-pure-conversation-smoke.mjs +148 -0
  14. package/scripts/internal-comms-bench.mjs +727 -0
  15. package/scripts/internal-comms-smoke.mjs +75 -0
  16. package/scripts/lead-workflow-smoke.mjs +4 -4
  17. package/scripts/live-worker-smoke.mjs +9 -9
  18. package/scripts/output-style-bench.mjs +285 -0
  19. package/scripts/output-style-smoke.mjs +13 -10
  20. package/scripts/patch-replay.mjs +90 -0
  21. package/scripts/path-suffix-test.mjs +57 -0
  22. package/scripts/provider-stream-stall-test.mjs +276 -0
  23. package/scripts/provider-toolcall-test.mjs +599 -1
  24. package/scripts/recall-bench.mjs +207 -0
  25. package/scripts/routing-corpus.mjs +281 -0
  26. package/scripts/session-bench.mjs +1526 -0
  27. package/scripts/session-diag.mjs +595 -0
  28. package/scripts/task-bench.mjs +207 -0
  29. package/scripts/tool-failures.mjs +6 -6
  30. package/scripts/tool-smoke.mjs +310 -67
  31. package/scripts/toolcall-args-test.mjs +81 -0
  32. package/src/agents/debugger/AGENT.md +4 -4
  33. package/src/agents/heavy-worker/AGENT.md +20 -9
  34. package/src/agents/reviewer/AGENT.md +4 -4
  35. package/src/agents/worker/AGENT.md +17 -9
  36. package/src/app.mjs +10 -6
  37. package/src/defaults/{hidden-roles.json → agents.json} +7 -7
  38. package/src/examples/schedules/SCHEDULE.example.md +32 -0
  39. package/src/examples/webhooks/WEBHOOK.example.md +40 -0
  40. package/src/headless-role.mjs +14 -14
  41. package/src/help.mjs +1 -0
  42. package/src/lib/rules-builder.cjs +32 -54
  43. package/src/mixdog-session-runtime.mjs +1040 -2036
  44. package/src/output-styles/default.md +12 -7
  45. package/src/output-styles/minimal.md +25 -0
  46. package/src/output-styles/oneline.md +21 -0
  47. package/src/output-styles/simple.md +10 -9
  48. package/src/repl.mjs +17 -7
  49. package/src/rules/agent/00-common.md +7 -5
  50. package/src/rules/agent/30-explorer.md +8 -12
  51. package/src/rules/lead/01-general.md +3 -1
  52. package/src/rules/lead/lead-tool.md +13 -2
  53. package/src/rules/shared/01-tool.md +23 -12
  54. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +90 -32
  55. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +32 -0
  56. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +18 -6
  57. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +23 -20
  58. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +48 -14
  59. package/src/runtime/agent/orchestrator/agent-trace.mjs +87 -12
  60. package/src/runtime/agent/orchestrator/config.mjs +3 -0
  61. package/src/runtime/agent/orchestrator/context/collect.mjs +182 -67
  62. package/src/runtime/agent/orchestrator/{internal-roles.mjs → internal-agents.mjs} +72 -72
  63. package/src/runtime/agent/orchestrator/internal-tools.mjs +13 -26
  64. package/src/runtime/agent/orchestrator/mcp/client.mjs +100 -18
  65. package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -0
  66. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +188 -0
  67. package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +444 -0
  68. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
  69. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +313 -84
  70. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +104 -38
  71. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +28 -33
  72. package/src/runtime/agent/orchestrator/providers/gemini.mjs +184 -17
  73. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +8 -1
  74. package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
  75. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
  76. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
  77. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +227 -31
  78. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +83 -6
  79. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +229 -115
  80. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +213 -33
  81. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
  82. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +49 -17
  83. package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
  84. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +22 -17
  85. package/src/runtime/agent/orchestrator/session/compact.mjs +560 -51
  86. package/src/runtime/agent/orchestrator/session/context-utils.mjs +250 -3
  87. package/src/runtime/agent/orchestrator/session/loop/compact-debug.mjs +28 -0
  88. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +262 -0
  89. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
  90. package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
  91. package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
  92. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +49 -0
  93. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
  94. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
  95. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
  96. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
  97. package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
  98. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
  99. package/src/runtime/agent/orchestrator/session/loop.mjs +513 -1000
  100. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +227 -0
  101. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
  102. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +137 -0
  103. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
  104. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
  105. package/src/runtime/agent/orchestrator/session/manager.mjs +232 -1152
  106. package/src/runtime/agent/orchestrator/session/store.mjs +4 -4
  107. package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +61 -0
  108. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +5 -0
  109. package/src/runtime/agent/orchestrator/stall-policy.mjs +63 -15
  110. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +194 -24
  111. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
  112. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
  113. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
  114. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +162 -0
  115. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
  116. package/src/runtime/agent/orchestrator/tools/builtin/list-formatting.mjs +10 -0
  117. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +5 -4
  118. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
  119. package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +15 -0
  120. package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +9 -44
  121. package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -1
  122. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +14 -5
  123. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
  124. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
  125. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +23 -2
  126. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +50 -39
  127. package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -2
  128. package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +10 -0
  129. package/src/runtime/agent/orchestrator/tools/builtin.mjs +70 -1
  130. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
  131. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
  132. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
  133. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +497 -0
  134. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
  135. package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
  136. package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
  137. package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
  138. package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
  139. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1192 -0
  140. package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
  141. package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
  142. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
  143. package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
  144. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
  145. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +39 -4189
  146. package/src/runtime/agent/orchestrator/tools/patch.mjs +119 -5
  147. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -2
  148. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +1 -2
  149. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
  150. package/src/runtime/channels/backends/discord.mjs +99 -9
  151. package/src/runtime/channels/backends/telegram.mjs +501 -0
  152. package/src/runtime/channels/index.mjs +437 -1439
  153. package/src/runtime/channels/lib/boot-profile.mjs +23 -0
  154. package/src/runtime/channels/lib/config.mjs +54 -2
  155. package/src/runtime/channels/lib/crash-log.mjs +106 -0
  156. package/src/runtime/channels/lib/format.mjs +4 -2
  157. package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
  158. package/src/runtime/channels/lib/output-forwarder.mjs +88 -67
  159. package/src/runtime/channels/lib/runtime-paths.mjs +29 -0
  160. package/src/runtime/channels/lib/scheduler.mjs +1 -1
  161. package/src/runtime/channels/lib/telegram-format.mjs +280 -0
  162. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  163. package/src/runtime/channels/lib/transcript-discovery.mjs +19 -1
  164. package/src/runtime/channels/lib/webhook.mjs +59 -31
  165. package/src/runtime/channels/lib/whisper-language.mjs +42 -0
  166. package/src/runtime/channels/tool-defs.mjs +1 -1
  167. package/src/runtime/memory/index.mjs +465 -345
  168. package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
  169. package/src/runtime/memory/lib/core-memory-store.mjs +352 -2
  170. package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
  171. package/src/runtime/memory/lib/http-wire.mjs +57 -0
  172. package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
  173. package/src/runtime/memory/lib/memory-cycle2.mjs +65 -9
  174. package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
  175. package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
  176. package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
  177. package/src/runtime/memory/lib/memory.mjs +121 -4
  178. package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
  179. package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
  180. package/src/runtime/memory/lib/recall-format.mjs +183 -0
  181. package/src/runtime/memory/lib/session-ingest.mjs +107 -0
  182. package/src/runtime/memory/lib/trace-store.mjs +69 -22
  183. package/src/runtime/memory/tool-defs.mjs +10 -7
  184. package/src/runtime/shared/abort-controller.mjs +1 -1
  185. package/src/runtime/shared/background-tasks.mjs +2 -3
  186. package/src/runtime/shared/buffered-appender.mjs +149 -0
  187. package/src/runtime/shared/channel-notification-routing.mjs +12 -0
  188. package/src/runtime/shared/channel-notification-routing.test.mjs +45 -0
  189. package/src/runtime/shared/config.mjs +9 -0
  190. package/src/runtime/shared/llm/http-agent.mjs +12 -5
  191. package/src/runtime/shared/schedules-store.mjs +21 -19
  192. package/src/runtime/shared/task-notification-envelope.mjs +98 -0
  193. package/src/runtime/shared/task-notification-envelope.test.mjs +107 -0
  194. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  195. package/src/runtime/shared/tool-surface.mjs +98 -13
  196. package/src/runtime/shared/transcript-writer.mjs +156 -0
  197. package/src/runtime/shared/update-checker.mjs +214 -0
  198. package/src/session-runtime/config-helpers.mjs +209 -0
  199. package/src/session-runtime/effort.mjs +128 -0
  200. package/src/session-runtime/fs-utils.mjs +10 -0
  201. package/src/session-runtime/model-capabilities.mjs +130 -0
  202. package/src/session-runtime/output-styles.mjs +124 -0
  203. package/src/session-runtime/plugin-mcp.mjs +114 -0
  204. package/src/session-runtime/session-text.mjs +100 -0
  205. package/src/session-runtime/statusline-route.mjs +35 -0
  206. package/src/session-runtime/tool-catalog.mjs +720 -0
  207. package/src/session-runtime/workflow.mjs +358 -0
  208. package/src/standalone/agent-tool.mjs +302 -117
  209. package/src/standalone/channel-admin.mjs +133 -40
  210. package/src/standalone/channel-worker.mjs +10 -292
  211. package/src/standalone/explore-tool.mjs +12 -5
  212. package/src/standalone/hook-bus.mjs +165 -8
  213. package/src/standalone/memory-runtime-proxy.mjs +3 -1
  214. package/src/standalone/opencode-go-login.mjs +121 -0
  215. package/src/standalone/provider-admin.mjs +25 -3
  216. package/src/standalone/usage-dashboard.mjs +1 -1
  217. package/src/tui/App.jsx +2883 -778
  218. package/src/tui/components/ConfirmBar.jsx +47 -0
  219. package/src/tui/components/ContextPanel.jsx +5 -3
  220. package/src/tui/components/ItemRightHintOverprint.jsx +54 -0
  221. package/src/tui/components/Markdown.jsx +22 -98
  222. package/src/tui/components/Message.jsx +14 -35
  223. package/src/tui/components/Picker.jsx +87 -12
  224. package/src/tui/components/PromptInput.jsx +355 -22
  225. package/src/tui/components/QueuedCommands.jsx +1 -1
  226. package/src/tui/components/SlashCommandPalette.jsx +8 -5
  227. package/src/tui/components/Spinner.jsx +7 -7
  228. package/src/tui/components/StatusLine.jsx +40 -21
  229. package/src/tui/components/TextEntryPanel.jsx +51 -7
  230. package/src/tui/components/ToolExecution.jsx +183 -101
  231. package/src/tui/components/TurnDone.jsx +4 -4
  232. package/src/tui/components/UsagePanel.jsx +1 -1
  233. package/src/tui/components/tool-output-format.mjs +161 -23
  234. package/src/tui/components/tool-output-format.test.mjs +87 -0
  235. package/src/tui/display-width.mjs +69 -0
  236. package/src/tui/display-width.test.mjs +35 -0
  237. package/src/tui/dist/index.mjs +6731 -2333
  238. package/src/tui/engine/agent-envelope.mjs +296 -0
  239. package/src/tui/engine/boot-profile.mjs +21 -0
  240. package/src/tui/engine/labels.mjs +67 -0
  241. package/src/tui/engine/notice-text.mjs +112 -0
  242. package/src/tui/engine/queue-helpers.mjs +161 -0
  243. package/src/tui/engine/session-stats.mjs +46 -0
  244. package/src/tui/engine/tool-call-fields.mjs +23 -0
  245. package/src/tui/engine/tool-result-text.mjs +126 -0
  246. package/src/tui/engine.mjs +569 -948
  247. package/src/tui/index.jsx +117 -7
  248. package/src/tui/input-editing.mjs +58 -8
  249. package/src/tui/input-editing.selection.test.mjs +75 -0
  250. package/src/tui/keyboard-protocol.mjs +42 -0
  251. package/src/tui/lib/voice-recorder.mjs +469 -0
  252. package/src/tui/markdown/format-token.mjs +128 -76
  253. package/src/tui/markdown/format-token.test.mjs +61 -19
  254. package/src/tui/markdown/measure-rendered-rows.mjs +85 -0
  255. package/src/tui/markdown/render-ansi.test.mjs +1 -1
  256. package/src/tui/markdown/streaming-markdown.mjs +167 -0
  257. package/src/tui/markdown/streaming-markdown.test.mjs +70 -0
  258. package/src/tui/markdown/table-layout.mjs +9 -9
  259. package/src/tui/paste-attachments.mjs +36 -9
  260. package/src/tui/paste-fix.test.mjs +119 -0
  261. package/src/tui/prompt-history-store.mjs +129 -0
  262. package/src/tui/prompt-history-store.test.mjs +52 -0
  263. package/src/tui/statusline-ansi-bridge.test.mjs +3 -3
  264. package/src/tui/theme.mjs +41 -657
  265. package/src/tui/themes/base.mjs +86 -0
  266. package/src/tui/themes/basic.mjs +85 -0
  267. package/src/tui/themes/catppuccin.mjs +72 -0
  268. package/src/tui/themes/dracula.mjs +70 -0
  269. package/src/tui/themes/everforest.mjs +71 -0
  270. package/src/tui/themes/gruvbox.mjs +71 -0
  271. package/src/tui/themes/index.mjs +71 -0
  272. package/src/tui/themes/indigo.mjs +78 -0
  273. package/src/tui/themes/kanagawa.mjs +80 -0
  274. package/src/tui/themes/light.mjs +81 -0
  275. package/src/tui/themes/nord.mjs +72 -0
  276. package/src/tui/themes/onedark.mjs +16 -0
  277. package/src/tui/themes/rosepine.mjs +70 -0
  278. package/src/tui/themes/teal.mjs +80 -0
  279. package/src/tui/themes/tokyonight.mjs +79 -0
  280. package/src/tui/themes/utils.mjs +106 -0
  281. package/src/tui/themes/warm.mjs +79 -0
  282. package/src/tui/transcript-tool-failures.mjs +13 -2
  283. package/src/ui/markdown.mjs +1 -1
  284. package/src/ui/model-display.mjs +2 -2
  285. package/src/ui/statusline.mjs +75 -27
  286. package/src/vendor/statusline/bin/statusline-route.mjs +5 -12
  287. package/src/vendor/statusline/src/gateway/claude-current.mjs +3 -3
  288. package/src/vendor/statusline/src/gateway/route-meta.mjs +30 -16
  289. package/src/workflows/default/WORKFLOW.md +46 -12
  290. package/src/workflows/sequential/WORKFLOW.md +51 -0
  291. package/src/workflows/solo/WORKFLOW.md +12 -1
  292. package/vendor/ink/build/display-width.js +62 -0
  293. package/vendor/ink/build/ink.js +100 -12
  294. package/vendor/ink/build/measure-text.js +4 -1
  295. package/vendor/ink/build/output.js +115 -9
  296. package/vendor/ink/build/render-node-to-output.js +4 -1
  297. package/vendor/ink/build/render.js +4 -0
  298. package/src/output-styles/extreme-simple.md +0 -20
  299. package/src/rules/lead/04-workflow.md +0 -51
  300. package/src/workflows/default/workflow.json +0 -13
  301. package/src/workflows/solo/workflow.json +0 -7
@@ -0,0 +1,81 @@
1
+ // Per-node source-text accessors with fingerprint-keyed runtime caching:
2
+ // raw text, raw lines, masked lines. Extracted verbatim from code-graph.mjs.
3
+ import { readFileSync } from 'node:fs';
4
+ import { toDisplayPath } from '../builtin.mjs';
5
+ import { _maskNonCodeText } from './text-mask.mjs';
6
+
7
+ export function _graphRel(absPath, cwd) {
8
+ return toDisplayPath(absPath, cwd);
9
+ }
10
+
11
+ export function _getSourceTextForNode(graph, node, fallbackText = null) {
12
+ const cached = graph?._sourceTextCache?.get(node.rel);
13
+ if (cached && cached.fingerprint === (node.fingerprint || '')) {
14
+ return cached.text;
15
+ }
16
+ if (typeof fallbackText === 'string') {
17
+ graph?._sourceTextCache?.set(node.rel, {
18
+ fingerprint: node.fingerprint || '',
19
+ text: fallbackText,
20
+ });
21
+ return fallbackText;
22
+ }
23
+ let text = '';
24
+ let readOk = false;
25
+ try { text = readFileSync(node.abs, 'utf8'); readOk = true; } catch { text = ''; readOk = false; }
26
+ if (readOk) {
27
+ graph?._sourceTextCache?.set(node.rel, {
28
+ fingerprint: node.fingerprint || '',
29
+ text,
30
+ });
31
+ }
32
+ return text;
33
+ }
34
+
35
+ export function _getSourceLinesForNode(graph, node) {
36
+ const cached = graph?._sourceLinesCache?.get(node.rel);
37
+ if (cached && cached.fingerprint === (node.fingerprint || '')) {
38
+ return cached.lines;
39
+ }
40
+ const text = _getSourceTextForNode(graph, node);
41
+ const lines = text.split(/\r?\n/);
42
+ graph?._sourceLinesCache?.set(node.rel, {
43
+ fingerprint: node.fingerprint || '',
44
+ lines,
45
+ });
46
+ return lines;
47
+ }
48
+
49
+ export function _getMaskedLinesForNode(graph, node) {
50
+ const cached = graph?._maskedLinesCache?.get(node.rel);
51
+ if (cached && cached.fingerprint === (node.fingerprint || '')) {
52
+ return cached.lines;
53
+ }
54
+ const text = _getSourceTextForNode(graph, node);
55
+ const lines = _maskNonCodeText(text, node.lang).split(/\r?\n/);
56
+ graph?._maskedLinesCache?.set(node.rel, {
57
+ fingerprint: node.fingerprint || '',
58
+ lines,
59
+ });
60
+ return lines;
61
+ }
62
+
63
+ // "file not found in graph" fires AFTER the dispatch-level existsSync check
64
+ // passed — the file EXISTS on disk but is not indexed. Say that explicitly,
65
+ // and offer same-basename indexed paths as a secondary pointer (capped at 3).
66
+ export function _appendSameBasenameHint(message, normFile, graph) {
67
+ const raw = String(normFile || '');
68
+ const base = raw.replace(/\\/g, '/').split('/').pop();
69
+ if (!base || !graph?.nodes) return message;
70
+ const baseLower = base.toLowerCase();
71
+ const matches = [];
72
+ for (const key of graph.nodes.keys()) {
73
+ if (key.split('/').pop().toLowerCase() === baseLower) {
74
+ matches.push(key);
75
+ if (matches.length >= 3) break;
76
+ }
77
+ }
78
+ const why = ' — the file exists on disk but is not indexed (excluded dir like dist/vendor, unsupported type, or graph file cap); use grep/read on it directly.';
79
+ if (!matches.length) return `${message}${why}`;
80
+ return `${message}${why} If you meant the indexed source, same filename is indexed at: ${matches.map((m) => `"${m}"`).join(', ')}.`;
81
+ }
@@ -0,0 +1,19 @@
1
+ // Declaration span-end inference from indentation. Recovers the end line of a
2
+ // brace-delimited declaration whose endLine the graph does not record
3
+ // (assignment-style decls). Extracted verbatim from code-graph.mjs.
4
+
5
+ const SYMBOL_SPAN_SCAN_MAX_LINES = 400;
6
+
7
+ export function _inferSpanEndByIndent(allLines, startLine) {
8
+ const decl = allLines[startLine - 1];
9
+ if (typeof decl !== 'string' || !/[{([]\s*$/.test(decl.trimEnd())) return null;
10
+ const declIndent = decl.match(/^[ \t]*/)[0].length;
11
+ const last = Math.min(allLines.length, startLine - 1 + SYMBOL_SPAN_SCAN_MAX_LINES);
12
+ for (let i = startLine; i < last; i++) {
13
+ const line = allLines[i];
14
+ if (!/^[ \t]*[})\]]/.test(line)) continue;
15
+ const indent = line.match(/^[ \t]*/)[0].length;
16
+ if (indent <= declIndent) return i + 1;
17
+ }
18
+ return null;
19
+ }
@@ -0,0 +1,280 @@
1
+ // Identifier tokenization, cheap regex symbol extraction, lazy per-symbol
2
+ // candidate-node lookup, and explainer anchor lines. Extracted verbatim
3
+ // from code-graph.mjs.
4
+ import {
5
+ _langUsesDollarInIdentifiers,
6
+ _langAllowsBangQuestionSuffix,
7
+ } from './lang-predicates.mjs';
8
+ import { _getSourceTextForNode } from './source-access.mjs';
9
+ import { _graphRel } from './source-access.mjs';
10
+
11
+ // Unicode-aware word-boundary wrapper for an already-regex-escaped
12
+ // symbol. JS `\b` only fires at ASCII [A-Za-z0-9_] transitions, so
13
+ // CJK / Cyrillic / Greek identifiers never matched the legacy shape.
14
+ // `$` is part of the boundary only for JS/TS/PHP; Ruby/Kotlin/Rust
15
+ // `!?` suffixes are kept distinct from the unsuffixed name when searching.
16
+ export function _unicodeBoundaryPattern(escaped, lang = null, symbol = null) {
17
+ const allowDollar = !lang || _langUsesDollarInIdentifiers(lang);
18
+ const before = allowDollar ? '(?<![\\p{ID_Continue}$])' : '(?<![\\p{ID_Continue}])';
19
+ let after = allowDollar ? '(?![\\p{ID_Continue}$])' : '(?![\\p{ID_Continue}])';
20
+ const sym = symbol == null ? '' : String(symbol);
21
+ if (lang && _langAllowsBangQuestionSuffix(lang) && sym && !/[!?]$/.test(sym)) {
22
+ after = allowDollar ? '(?![\\p{ID_Continue}$!?])' : '(?![\\p{ID_Continue}!?])';
23
+ }
24
+ return `${before}${escaped}${after}`;
25
+ }
26
+
27
+ export function _extractIdentifierTokens(text, lang = null) {
28
+ const out = new Set();
29
+ const allowDollar = !lang || _langUsesDollarInIdentifiers(lang);
30
+ const before = allowDollar ? '(?<![\\p{ID_Continue}$])' : '(?<![\\p{ID_Continue}])';
31
+ const suffix = lang && _langAllowsBangQuestionSuffix(lang) ? '[!?]?' : '';
32
+ const after = allowDollar ? '(?![\\p{ID_Continue}$])' : '(?![\\p{ID_Continue}])';
33
+ const re = new RegExp(`${before}[$@]?[\\p{ID_Start}_][\\p{ID_Continue}]*${suffix}${after}`, 'gu');
34
+ let match = null;
35
+ const src = String(text || '');
36
+ while ((match = re.exec(src))) {
37
+ out.add(match[0]);
38
+ }
39
+ return [...out];
40
+ }
41
+
42
+ export function _getTokenSymbolsForNode(graph, node) {
43
+ if (Array.isArray(node?.tokenSymbols)) return node.tokenSymbols;
44
+ const text = _getSourceTextForNode(graph, node);
45
+ const tokens = _extractIdentifierTokens(text, node.lang);
46
+ node.tokenSymbols = tokens;
47
+ return tokens;
48
+ }
49
+
50
+ // Lazy per-symbol candidate lookup. Caches the result back into
51
+ // `_symbolTokenIndex` so repeat lookups are O(1). Compared to a full
52
+ // _ensureSymbolTokenIndex sweep, the per-symbol scan is O(N) where N is
53
+ // the node count (~7000 on refs/), and each node's check is a cheap
54
+ // Array.includes on its pre-extracted tokenSymbols. Cold-process first
55
+ // lookup drops from ~1-2s to ~50ms.
56
+ export function _lookupCandidateNodes(graph, symbol, language = null) {
57
+ if (!graph?.nodes) return [];
58
+ const cacheKey = `${language || '*'}|${symbol}`;
59
+ if (graph._symbolTokenIndex?.has(cacheKey)) {
60
+ const rels = graph._symbolTokenIndex.get(cacheKey);
61
+ return rels.map((rel) => graph.nodes.get(rel)).filter(Boolean);
62
+ }
63
+ const candidates = [];
64
+ for (const node of graph.nodes.values()) {
65
+ if (language && node.lang !== language) continue;
66
+ const tokens = _getTokenSymbolsForNode(graph, node);
67
+ if (tokens.includes(symbol)) candidates.push(node);
68
+ }
69
+ if (candidates.length > 0) {
70
+ if (graph._symbolTokenIndex) {
71
+ graph._symbolTokenIndex.set(cacheKey, candidates.map((n) => n.rel));
72
+ }
73
+ return candidates;
74
+ }
75
+ // Token-index miss → fall back to language-filtered full graph scan.
76
+ // _extractIdentifierTokens uses ASCII `\b` word-boundary which misses
77
+ // unicode (Korean/CJK), $-prefixed identifiers in some positions, and
78
+ // certain multi-byte language tokens (Rust raw idents, Go method
79
+ // receivers). The downstream search loop's sourceText.includes()
80
+ // still catches these — we just need to give it the full node set.
81
+ // Not cached: caching the fallback would mask token-extractor
82
+ // improvements and would also keep returning the heavy scan after a
83
+ // future graph rebuild populated the token map for the symbol.
84
+ const fallback = [];
85
+ for (const node of graph.nodes.values()) {
86
+ if (language && node.lang !== language) continue;
87
+ fallback.push(node);
88
+ }
89
+ return fallback;
90
+ }
91
+
92
+ export function _extractSymbolsCheap(text, lang) {
93
+ const all = _collectCheapSymbols(text, lang).map((item) => `${item.kind} ${item.name} (L${item.line})`);
94
+ return all.length ? _capGraphList(all).join('\n') : '(no symbols)';
95
+ }
96
+
97
+ // Control-flow keywords that the bare `name(args) {?$` patterns below
98
+ // would otherwise mis-collect as function/method symbols (e.g. an
99
+ // `if (...) {` line). Excluding at the collection stage keeps the
100
+ // invariant out of every downstream label/summarizer.
101
+ const _CHEAP_SYMBOL_CONTROL_FLOW = new Set([
102
+ 'if', 'else', 'elif', 'for', 'foreach', 'while', 'do',
103
+ 'switch', 'case', 'default', 'when', 'select',
104
+ 'try', 'catch', 'finally', 'throw', 'throws',
105
+ 'return', 'yield', 'await', 'goto', 'break', 'continue',
106
+ 'with', 'using', 'lock', 'synchronized', 'unless',
107
+ ]);
108
+
109
+ export function _collectCheapSymbols(text, lang) {
110
+ const lines = String(text || '').split(/\r?\n/);
111
+ const out = [];
112
+ const push = (kind, name, idx) => {
113
+ if (!name) return;
114
+ // Skip control-flow keywords so `if(...) {`, `for(...) {`,
115
+ // `while(...) {`, `switch(...) {`, `catch(...) {` no longer leak
116
+ // as function/method symbols through the bare `name(args)` shapes.
117
+ if (_CHEAP_SYMBOL_CONTROL_FLOW.has(name)) return;
118
+ out.push({ kind, name, line: idx + 1 });
119
+ };
120
+ // Slash (`//` `/*`) comments: all C-family langs incl. new kotlin/swift/
121
+ // scala. Excluded: python/ruby (hash), bash (hash), lua (`--`; also `//`
122
+ // is lua integer-division, so slash-stripping would delete code). Second
123
+ // batch: dart/objc/zig are C-family slash-comment (kept by the default);
124
+ // elixir/r are hash-comment (excluded below).
125
+ const supportsSlash = lang !== 'python' && lang !== 'ruby'
126
+ && lang !== 'bash' && lang !== 'lua'
127
+ && lang !== 'elixir' && lang !== 'r';
128
+ // Hash (`#`) comments: python/ruby/php and bash. lua uses `--` (handled by
129
+ // _maskNonCodeText, not needed here since lua has no cheap-symbol matcher).
130
+ // Second batch: elixir and r are `#`-only line-comment langs → included.
131
+ const supportsHash = lang === 'python' || lang === 'ruby' || lang === 'php'
132
+ || lang === 'bash' || lang === 'elixir' || lang === 'r';
133
+ let inBlockComment = false;
134
+ for (let i = 0; i < lines.length; i++) {
135
+ // Per-line comment stripping at the collection stage so header/JSDoc
136
+ // words like "These", "side", "effects" cannot bleed into the
137
+ // overview `symbols:` token list or the cheap summarizer output.
138
+ // An unclosed `/*` keeps the code before it and flips the block flag
139
+ // so code-before-comment lines (and spaced generators like `* gen()`)
140
+ // still reach the per-language matchers below.
141
+ let line = lines[i];
142
+ if (supportsSlash) {
143
+ if (inBlockComment) {
144
+ const endIdx = line.indexOf('*/');
145
+ if (endIdx < 0) continue;
146
+ line = line.slice(endIdx + 2);
147
+ inBlockComment = false;
148
+ }
149
+ while (true) {
150
+ const startIdx = line.indexOf('/*');
151
+ if (startIdx < 0) break;
152
+ const endIdx = line.indexOf('*/', startIdx + 2);
153
+ if (endIdx < 0) {
154
+ line = line.slice(0, startIdx);
155
+ inBlockComment = true;
156
+ break;
157
+ }
158
+ line = line.slice(0, startIdx) + ' ' + line.slice(endIdx + 2);
159
+ }
160
+ const slashIdx = line.indexOf('//');
161
+ if (slashIdx >= 0) line = line.slice(0, slashIdx);
162
+ }
163
+ if (supportsHash) {
164
+ if (/^\s*#/.test(line)) continue;
165
+ }
166
+ if (!line.trim()) continue;
167
+ let m = null;
168
+ if (lang === 'typescript' || lang === 'javascript') {
169
+ if ((m = /\b(class|interface|type|enum)\s+([A-Za-z_][A-Za-z0-9_]*)/.exec(line))) push(m[1], m[2], i);
170
+ else if ((m = /\bfunction\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/.exec(line))) push('function', m[1], i);
171
+ else if ((m = /\b(?:const|let|var)\s+([A-Za-z_][A-Za-z0-9_]*)\b/.exec(line))) push('binding', m[1], i);
172
+ else if ((m = /^\s*(?:static\s+)?(?:async\s+)?(?:get\s+|set\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*\([^;]*\)\s*\{?$/.exec(line))) push('method', m[1], i);
173
+ } else if (lang === 'python') {
174
+ if ((m = /^\s*class\s+([A-Za-z_][A-Za-z0-9_]*)/.exec(line))) push('class', m[1], i);
175
+ else if ((m = /^\s*def\s+([A-Za-z_][A-Za-z0-9_]*)/.exec(line))) push('function', m[1], i);
176
+ } else if (lang === 'go') {
177
+ if ((m = /^\s*type\s+([A-Za-z_][A-Za-z0-9_]*)\s+struct\b/.exec(line))) push('struct', m[1], i);
178
+ else if ((m = /^\s*func(?:\s*\([^)]*\))?\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/.exec(line))) push('function', m[1], i);
179
+ } else if (lang === 'rust') {
180
+ if ((m = /^\s*(?:pub\s+)?struct\s+([A-Za-z_][A-Za-z0-9_]*)/.exec(line))) push('struct', m[1], i);
181
+ else if ((m = /^\s*(?:pub\s+)?fn\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/.exec(line))) push('function', m[1], i);
182
+ } else if (lang === 'kotlin') {
183
+ // Kotlin: `fun name(...)` is the canonical function declaration whether
184
+ // the body is a `{` block or an `= expr` expression body. The shared
185
+ // Java/C#-style `name(...) {` pattern misses expression bodies that
186
+ // end with the expression itself (no trailing `{` or `;`), so caller
187
+ // names disappear for those functions.
188
+ if ((m = /\b(class|interface|enum|object)\s+([A-Za-z_][A-Za-z0-9_]*)/.exec(line))) push(m[1], m[2], i);
189
+ else if ((m = /^\s*(?:public\s+|private\s+|protected\s+|internal\s+)?(?:open\s+|abstract\s+|final\s+)?(?:override\s+)?(?:suspend\s+)?(?:inline\s+)?fun\s+(?:<[^>]+>\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*\(/.exec(line))) push('function', m[1], i);
190
+ else if ((m = /^\s*(?:public\s+|private\s+|protected\s+|internal\s+)?(?:const\s+)?(?:val|var)\s+([A-Za-z_][A-Za-z0-9_]*)\b/.exec(line))) push('binding', m[1], i);
191
+ } else if (lang === 'java' || lang === 'csharp') {
192
+ if ((m = /\b(class|interface|enum|record)\s+([A-Za-z_][A-Za-z0-9_]*)/.exec(line))) push(m[1], m[2], i);
193
+ else if ((m = /\b([A-Za-z_][A-Za-z0-9_]*)\s*\([^;]*\)\s*\{?$/.exec(line))) push('function', m[1], i);
194
+ } else if (lang === 'c' || lang === 'cpp') {
195
+ if ((m = /\b(class|struct|enum)\s+([A-Za-z_][A-Za-z0-9_]*)/.exec(line))) push(m[1], m[2], i);
196
+ else if ((m = /^\s*[A-Za-z_][\w\s:*<>~]*\s+([A-Za-z_][A-Za-z0-9_]*)\s*\([^;]*\)\s*\{?$/.exec(line))) push('function', m[1], i);
197
+ } else if (lang === 'ruby' || lang === 'php') {
198
+ if ((m = /^\s*class\s+([A-Za-z_][A-Za-z0-9_:]*)/.exec(line))) push('class', m[1], i);
199
+ else if ((m = /^\s*def\s+([A-Za-z_][A-Za-z0-9_!?=]*)/.exec(line))) push('function', m[1], i);
200
+ }
201
+ // No cheap-regex matcher for swift/scala/bash/lua or the second batch
202
+ // (dart/objc/elixir/zig/r): the native indexer now emits symbols for these
203
+ // langs, so _collectCheapSymbols runs only as a fallback when native
204
+ // symbols are absent. They are deliberately left without a branch (yield no
205
+ // cheap anchors) rather than guessing with a loose pattern; callers
206
+ // (overview/anchors) fall back to native symbols.
207
+ }
208
+ return out;
209
+ }
210
+
211
+ // Raised from 6 to 50 after HS-A6 surfaced that overview on a ~46KB file
212
+ // returned only the first 6 anchors (all within the first 87 lines, 5%
213
+ // of the file). tail-trim still bounds output payload, so a higher cap
214
+ // surfaces full structure on large files without hurting small ones.
215
+ export function _extractExplainerAnchorLines(node, graph, { limit = 50, maxLineChars = 180 } = {}) {
216
+ const sourceLines = _getSourceTextForNode(graph, node).split(/\r?\n/);
217
+ const symbols = Array.isArray(node.symbols) && node.symbols.length
218
+ ? node.symbols
219
+ : _collectCheapSymbols(sourceLines.join('\n'), node.lang);
220
+ const out = [];
221
+ const seen = new Set();
222
+ for (const item of symbols) {
223
+ if (out.length >= limit) break;
224
+ const idx = item.line - 1;
225
+ const line = String(sourceLines[idx] || '').trim();
226
+ if (!line) continue;
227
+ const key = `${item.name}:${item.line}`;
228
+ if (seen.has(key)) continue;
229
+ seen.add(key);
230
+ out.push(`${item.kind} ${item.name} (L${item.line}): ${line.slice(0, maxLineChars)}`);
231
+ }
232
+ return out;
233
+ }
234
+
235
+ // Bound model-facing structural list output (imports/dependents/related,
236
+ // symbols, external callers) so a high fan-in/fan-out or symbol-dense file
237
+ // cannot inject an unbounded result — mirrors the find_imports/find_dependents
238
+ // cap.
239
+ export function _capGraphList(arr, cap = 200) {
240
+ return arr.length > cap
241
+ ? [...arr.slice(0, cap), `[truncated — showing first ${cap} of ${arr.length}]`]
242
+ : arr;
243
+ }
244
+
245
+ export function _buildExplainerFileSummary(node, graph, cwd) {
246
+ const topTypes = Array.isArray(node?.topLevelTypes) ? node.topLevelTypes.slice(0, 8) : [];
247
+ const importsAll = Array.isArray(node?.resolvedImports) ? node.resolvedImports.map((p) => _graphRel(p, cwd)) : [];
248
+ const imports = importsAll.slice(0, 8);
249
+ const tokensAll = _getTokenSymbolsForNode(graph, node);
250
+ // Prefer native tree-sitter symbol names; fall back to the regex token
251
+ // dump only when the native graph path didn't populate node.symbols.
252
+ const hasNativeSymbols = Array.isArray(node?.symbols) && node.symbols.length > 0;
253
+ const symbolsAll = hasNativeSymbols
254
+ ? [...new Set(node.symbols.map((s) => s.name))]
255
+ : tokensAll;
256
+ const symbolNames = symbolsAll.slice(0, hasNativeSymbols ? 30 : 20);
257
+ const anchors = _extractExplainerAnchorLines(node, graph);
258
+ const sourceHead = _getSourceTextForNode(graph, node)
259
+ .split(/\r?\n/)
260
+ .slice(0, 6)
261
+ .join('\n')
262
+ .trim()
263
+ .slice(0, 420);
264
+ const parts = [
265
+ `file: ${node.rel}`,
266
+ `language: ${node.lang}`,
267
+ ];
268
+ if (topTypes.length) parts.push(`top-level: ${topTypes.join(', ')}`);
269
+ if (symbolNames.length) {
270
+ const more = symbolsAll.length - symbolNames.length;
271
+ parts.push(`symbols: ${symbolNames.join(', ')}${more > 0 ? `, … +${more} more (mode:symbols for full list)` : ''}`);
272
+ }
273
+ if (imports.length) {
274
+ const more = importsAll.length - imports.length;
275
+ parts.push(`imports: ${imports.join(', ')}${more > 0 ? `, … +${more} more (mode:imports for full list)` : ''}`);
276
+ }
277
+ if (anchors.length) parts.push(`anchors:\n${anchors.join('\n')}`);
278
+ if (sourceHead) parts.push(`head:\n${sourceHead}`);
279
+ return parts.join('\n');
280
+ }