mixdog 0.9.2 → 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 (154) hide show
  1. package/package.json +2 -1
  2. package/scripts/anthropic-maxtokens-test.mjs +119 -0
  3. package/scripts/build-tui.mjs +13 -1
  4. package/scripts/explore-bench.mjs +124 -0
  5. package/scripts/hook-bus-test.mjs +191 -0
  6. package/scripts/path-suffix-test.mjs +57 -0
  7. package/scripts/recall-bench.mjs +207 -0
  8. package/scripts/tool-smoke.mjs +7 -4
  9. package/src/agents/debugger/AGENT.md +2 -2
  10. package/src/agents/heavy-worker/AGENT.md +20 -11
  11. package/src/agents/reviewer/AGENT.md +2 -2
  12. package/src/agents/worker/AGENT.md +17 -11
  13. package/src/mixdog-session-runtime.mjs +424 -1812
  14. package/src/repl.mjs +5 -5
  15. package/src/rules/agent/30-explorer.md +8 -11
  16. package/src/rules/lead/lead-tool.md +9 -5
  17. package/src/rules/shared/01-tool.md +11 -5
  18. package/src/runtime/agent/orchestrator/context/collect.mjs +51 -0
  19. package/src/runtime/agent/orchestrator/mcp/client.mjs +6 -2
  20. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +1 -1
  21. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
  22. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +22 -68
  23. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +46 -7
  24. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +1 -13
  25. package/src/runtime/agent/orchestrator/providers/gemini.mjs +1 -1
  26. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +1 -1
  27. package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
  28. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
  29. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +19 -12
  30. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +7 -5
  31. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +33 -23
  32. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +31 -14
  33. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +38 -12
  34. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +7 -8
  35. package/src/runtime/agent/orchestrator/session/loop/compact-debug.mjs +28 -0
  36. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +262 -0
  37. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
  38. package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
  39. package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
  40. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +49 -0
  41. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
  42. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
  43. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
  44. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
  45. package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
  46. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
  47. package/src/runtime/agent/orchestrator/session/loop.mjs +169 -918
  48. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +227 -0
  49. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
  50. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +137 -0
  51. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
  52. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
  53. package/src/runtime/agent/orchestrator/session/manager.mjs +65 -1032
  54. package/src/runtime/agent/orchestrator/stall-policy.mjs +3 -3
  55. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +2 -2
  56. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
  57. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +162 -0
  58. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
  59. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
  60. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +1 -1
  61. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
  62. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +5 -0
  63. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +50 -39
  64. package/src/runtime/agent/orchestrator/tools/builtin.mjs +11 -0
  65. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
  66. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
  67. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
  68. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +497 -0
  69. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
  70. package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
  71. package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
  72. package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
  73. package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
  74. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1192 -0
  75. package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
  76. package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
  77. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
  78. package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
  79. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +36 -4277
  80. package/src/runtime/agent/orchestrator/tools/patch.mjs +3 -3
  81. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -2
  82. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +1 -2
  83. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
  84. package/src/runtime/channels/index.mjs +14 -233
  85. package/src/runtime/channels/lib/boot-profile.mjs +23 -0
  86. package/src/runtime/channels/lib/crash-log.mjs +106 -0
  87. package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
  88. package/src/runtime/channels/lib/output-forwarder.mjs +8 -0
  89. package/src/runtime/channels/lib/telegram-format.mjs +19 -22
  90. package/src/runtime/channels/lib/whisper-language.mjs +42 -0
  91. package/src/runtime/memory/index.mjs +314 -359
  92. package/src/runtime/memory/lib/core-memory-store.mjs +351 -1
  93. package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
  94. package/src/runtime/memory/lib/http-wire.mjs +57 -0
  95. package/src/runtime/memory/lib/memory-cycle2.mjs +56 -3
  96. package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
  97. package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
  98. package/src/runtime/memory/lib/memory.mjs +20 -0
  99. package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
  100. package/src/runtime/memory/lib/recall-format.mjs +183 -0
  101. package/src/runtime/memory/tool-defs.mjs +4 -4
  102. package/src/runtime/shared/abort-controller.mjs +1 -1
  103. package/src/runtime/shared/background-tasks.mjs +2 -3
  104. package/src/runtime/shared/buffered-appender.mjs +149 -0
  105. package/src/runtime/shared/task-notification-envelope.mjs +98 -0
  106. package/src/runtime/shared/task-notification-envelope.test.mjs +107 -0
  107. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  108. package/src/runtime/shared/transcript-writer.mjs +29 -2
  109. package/src/session-runtime/config-helpers.mjs +209 -0
  110. package/src/session-runtime/effort.mjs +128 -0
  111. package/src/session-runtime/fs-utils.mjs +10 -0
  112. package/src/session-runtime/model-capabilities.mjs +130 -0
  113. package/src/session-runtime/output-styles.mjs +124 -0
  114. package/src/session-runtime/plugin-mcp.mjs +114 -0
  115. package/src/session-runtime/session-text.mjs +100 -0
  116. package/src/session-runtime/statusline-route.mjs +35 -0
  117. package/src/session-runtime/tool-catalog.mjs +720 -0
  118. package/src/session-runtime/workflow.mjs +358 -0
  119. package/src/standalone/agent-tool.mjs +49 -10
  120. package/src/standalone/channel-worker.mjs +3 -2
  121. package/src/standalone/explore-tool.mjs +10 -3
  122. package/src/standalone/hook-bus.mjs +165 -8
  123. package/src/standalone/opencode-go-login.mjs +121 -0
  124. package/src/standalone/provider-admin.mjs +14 -3
  125. package/src/tui/App.jsx +884 -143
  126. package/src/tui/components/PromptInput.jsx +272 -15
  127. package/src/tui/components/ToolExecution.jsx +20 -10
  128. package/src/tui/components/tool-output-format.mjs +2 -2
  129. package/src/tui/dist/index.mjs +1771 -1947
  130. package/src/tui/engine/agent-envelope.mjs +296 -0
  131. package/src/tui/engine/boot-profile.mjs +21 -0
  132. package/src/tui/engine/labels.mjs +67 -0
  133. package/src/tui/engine/notice-text.mjs +112 -0
  134. package/src/tui/engine/queue-helpers.mjs +161 -0
  135. package/src/tui/engine/session-stats.mjs +46 -0
  136. package/src/tui/engine/tool-call-fields.mjs +23 -0
  137. package/src/tui/engine/tool-result-text.mjs +126 -0
  138. package/src/tui/engine.mjs +311 -851
  139. package/src/tui/input-editing.mjs +58 -8
  140. package/src/tui/input-editing.selection.test.mjs +75 -0
  141. package/src/tui/keyboard-protocol.mjs +2 -2
  142. package/src/tui/lib/voice-recorder.mjs +35 -19
  143. package/src/tui/markdown/format-token.mjs +7 -8
  144. package/src/tui/markdown/format-token.test.mjs +3 -3
  145. package/src/tui/paste-attachments.mjs +38 -0
  146. package/src/tui/paste-fix.test.mjs +119 -0
  147. package/src/tui/themes/base.mjs +2 -2
  148. package/src/tui/themes/kanagawa.mjs +4 -4
  149. package/src/tui/themes/teal.mjs +4 -5
  150. package/src/tui/themes/utils.mjs +1 -1
  151. package/src/ui/statusline.mjs +49 -0
  152. package/src/workflows/default/WORKFLOW.md +16 -9
  153. package/src/workflows/sequential/WORKFLOW.md +16 -11
  154. package/src/workflows/solo/WORKFLOW.md +5 -1
@@ -0,0 +1,347 @@
1
+ // Non-code text masker: blanks comment/string/regex bytes so downstream
2
+ // identifier scans never match inside them. Byte-offset preserving (fills
3
+ // with spaces, keeps newlines) so match.index maps back to the raw source.
4
+ // Extracted verbatim from code-graph.mjs.
5
+ import {
6
+ _supportsHashComments,
7
+ _supportsSlashComments,
8
+ _supportsSingleQuoteStrings,
9
+ _supportsBacktickStrings,
10
+ _supportsTripleSingleQuoteStrings,
11
+ _supportsTripleDoubleQuoteStrings,
12
+ _isJsLike,
13
+ _isWordStartChar,
14
+ _isWordChar,
15
+ REGEX_PRECEDENT_KEYWORDS,
16
+ REGEX_PRECEDENT_CHARS,
17
+ } from './lang-predicates.mjs';
18
+
19
+ // Mask a JS regex literal body starting at `start` (which points at `/`).
20
+ // Handles `\` escapes and `[...]` character classes per ECMAScript spec.
21
+ // Returns the index just past the closing `/flags`. Bytes between the
22
+ // delimiters are replaced with spaces in `out` so downstream identifier
23
+ // searches do not see them.
24
+ export function _maskJsRegexLiteral(src, out, start) {
25
+ if (src[start] !== '\n') out[start] = ' ';
26
+ let j = start + 1;
27
+ let inCharClass = false;
28
+ while (j < src.length) {
29
+ const c = src[j];
30
+ if (c === '\n') return j;
31
+ if (c === '\\') {
32
+ if (src[j] !== '\n') out[j] = ' ';
33
+ if (j + 1 < src.length && src[j + 1] !== '\n') out[j + 1] = ' ';
34
+ j += 2;
35
+ continue;
36
+ }
37
+ if (c === '[' && !inCharClass) {
38
+ inCharClass = true;
39
+ if (src[j] !== '\n') out[j] = ' ';
40
+ j++;
41
+ continue;
42
+ }
43
+ if (c === ']' && inCharClass) {
44
+ inCharClass = false;
45
+ if (src[j] !== '\n') out[j] = ' ';
46
+ j++;
47
+ continue;
48
+ }
49
+ if (c === '/' && !inCharClass) {
50
+ if (src[j] !== '\n') out[j] = ' ';
51
+ j++;
52
+ while (j < src.length && src[j] >= 'a' && src[j] <= 'z') {
53
+ if (src[j] !== '\n') out[j] = ' ';
54
+ j++;
55
+ }
56
+ return j;
57
+ }
58
+ if (src[j] !== '\n') out[j] = ' ';
59
+ j++;
60
+ }
61
+ return j;
62
+ }
63
+
64
+ export function _maskNonCodeText(text, lang) {
65
+ const src = String(text || '');
66
+ const out = src.split('');
67
+ let i = 0;
68
+ let blockComment = false;
69
+ // Stack of scanner frames. Top describes current state:
70
+ // { kind: 'string', delim } — inside single-line string literal (mask body)
71
+ // { kind: 'triple', delim } — inside triple-quote string (mask body)
72
+ // { kind: 'interp', braceDepth } — inside backtick `${...}` interpolation
73
+ // (code mode; bytes preserved so callers
74
+ // analysis can see fn-calls inside)
75
+ // Empty stack = top-level code.
76
+ const stack = [];
77
+ const top = () => (stack.length ? stack[stack.length - 1] : null);
78
+ // prevToken tracks ECMAScript token context for the `/`-disambiguation:
79
+ // 'expr' = expression-start (regex literal may follow)
80
+ // 'value' = value/operand (`/` is division)
81
+ // Start of file = expression context.
82
+ let prevToken = 'expr';
83
+ while (i < src.length) {
84
+ if (blockComment) {
85
+ if (src.startsWith('*/', i)) {
86
+ out[i] = ' ';
87
+ if (i + 1 < out.length) out[i + 1] = ' ';
88
+ i += 2;
89
+ blockComment = false;
90
+ continue;
91
+ }
92
+ if (src[i] !== '\n') out[i] = ' ';
93
+ i++;
94
+ continue;
95
+ }
96
+ const t = top();
97
+ if (t && t.kind === 'triple') {
98
+ if (src.startsWith(t.delim, i)) {
99
+ for (let j = 0; j < t.delim.length; j++) {
100
+ if (src[i + j] !== '\n') out[i + j] = ' ';
101
+ }
102
+ i += t.delim.length;
103
+ stack.pop();
104
+ prevToken = 'value';
105
+ continue;
106
+ }
107
+ if (src[i] !== '\n') out[i] = ' ';
108
+ i++;
109
+ continue;
110
+ }
111
+ if (t && t.kind === 'luablock') {
112
+ // Lua long-bracket comment `--[=*[ ... ]=*]` — mask until the EXACT
113
+ // matching close delimiter (`]` + same number of `=` + `]`) recorded
114
+ // on the frame, so `--[==[ ]] ]==]` closes only at `]==]`.
115
+ if (t.close && src.startsWith(t.close, i)) {
116
+ for (let j = 0; j < t.close.length; j++) {
117
+ if (src[i + j] !== '\n') out[i + j] = ' ';
118
+ }
119
+ i += t.close.length;
120
+ stack.pop();
121
+ prevToken = 'value';
122
+ continue;
123
+ }
124
+ if (src[i] !== '\n') out[i] = ' ';
125
+ i++;
126
+ continue;
127
+ }
128
+ if (t && t.kind === 'string') {
129
+ const d = t.delim;
130
+ if (d === '`' && src.startsWith('${', i)) {
131
+ // Enter interpolation. `${` itself is code-relevant — leave bytes intact.
132
+ stack.push({ kind: 'interp', braceDepth: 1 });
133
+ i += 2;
134
+ prevToken = 'expr';
135
+ continue;
136
+ }
137
+ // In bash single-quotes `'...'`, backslash is literal (no escape) — the
138
+ // string closes at the first `'`. Skip the escape consumption there so
139
+ // `'\'` is not mis-read as an escaped quote. bash `"..."` and all other
140
+ // langs keep backslash-escape handling.
141
+ const bashLiteralSingle = t.lang === 'bash' && d === '\'';
142
+ if (!bashLiteralSingle && src[i] === '\\' && (d === '\'' || d === '"' || d === '`')) {
143
+ if (src[i] !== '\n') out[i] = ' ';
144
+ if (i + 1 < src.length && src[i + 1] !== '\n') out[i + 1] = ' ';
145
+ i += 2;
146
+ continue;
147
+ }
148
+ if (src[i] === d) {
149
+ if (src[i] !== '\n') out[i] = ' ';
150
+ i++;
151
+ stack.pop();
152
+ prevToken = 'value';
153
+ continue;
154
+ }
155
+ // JS forbids a raw newline inside '...' or "..." — defensive reset. bash
156
+ // quoted strings legally span newlines, so do NOT reset bash frames.
157
+ if (src[i] === '\n' && t.lang !== 'bash' && (d === '\'' || d === '"')) {
158
+ stack.pop();
159
+ prevToken = 'value';
160
+ i++;
161
+ continue;
162
+ }
163
+ if (src[i] !== '\n') out[i] = ' ';
164
+ i++;
165
+ continue;
166
+ }
167
+ if (t && t.kind === 'interp') {
168
+ // Code mode inside `${...}`. Bytes preserved; track brace depth and
169
+ // nested constructs so masking resumes once interpolation closes.
170
+ if (src[i] === '{') {
171
+ t.braceDepth++;
172
+ prevToken = 'expr';
173
+ i++;
174
+ continue;
175
+ }
176
+ if (src[i] === '}') {
177
+ t.braceDepth--;
178
+ i++;
179
+ if (t.braceDepth === 0) {
180
+ stack.pop();
181
+ prevToken = 'value';
182
+ } else {
183
+ prevToken = 'value';
184
+ }
185
+ continue;
186
+ }
187
+ if (_supportsSlashComments(lang) && src.startsWith('/*', i)) {
188
+ out[i] = ' ';
189
+ if (i + 1 < out.length) out[i + 1] = ' ';
190
+ i += 2;
191
+ blockComment = true;
192
+ continue;
193
+ }
194
+ if (_supportsSlashComments(lang) && src.startsWith('//', i)) {
195
+ while (i < src.length && src[i] !== '\n') {
196
+ out[i] = ' ';
197
+ i++;
198
+ }
199
+ continue;
200
+ }
201
+ if (src[i] === '/' && _isJsLike(lang) && prevToken === 'expr') {
202
+ i = _maskJsRegexLiteral(src, out, i);
203
+ prevToken = 'value';
204
+ continue;
205
+ }
206
+ if (src[i] === '"' || (_supportsSingleQuoteStrings(lang) && src[i] === '\'') || (_supportsBacktickStrings(lang) && src[i] === '`')) {
207
+ if (src[i] !== '\n') out[i] = ' ';
208
+ stack.push({ kind: 'string', delim: src[i], lang });
209
+ i++;
210
+ continue;
211
+ }
212
+ if (_isWordStartChar(src[i])) {
213
+ const start = i;
214
+ while (i < src.length && _isWordChar(src[i])) i++;
215
+ const word = src.substring(start, i);
216
+ prevToken = REGEX_PRECEDENT_KEYWORDS.has(word) ? 'expr' : 'value';
217
+ continue;
218
+ }
219
+ if (src[i] >= '0' && src[i] <= '9') {
220
+ while (i < src.length && (src[i] === '.' || (src[i] >= '0' && src[i] <= '9'))) i++;
221
+ prevToken = 'value';
222
+ continue;
223
+ }
224
+ if (src[i] === ' ' || src[i] === '\t' || src[i] === '\r' || src[i] === '\n') {
225
+ i++;
226
+ continue;
227
+ }
228
+ if (REGEX_PRECEDENT_CHARS.has(src[i])) {
229
+ prevToken = 'expr';
230
+ } else {
231
+ prevToken = 'value';
232
+ }
233
+ i++;
234
+ continue;
235
+ }
236
+ // Top-level code.
237
+ if (_supportsSlashComments(lang) && src.startsWith('/*', i)) {
238
+ out[i] = ' ';
239
+ if (i + 1 < out.length) out[i + 1] = ' ';
240
+ i += 2;
241
+ blockComment = true;
242
+ continue;
243
+ }
244
+ if (_supportsSlashComments(lang) && src.startsWith('//', i)) {
245
+ while (i < src.length && src[i] !== '\n') {
246
+ out[i] = ' ';
247
+ i++;
248
+ }
249
+ continue;
250
+ }
251
+ if (_supportsHashComments(lang) && src[i] === '#') {
252
+ // Bash `#` is a comment ONLY at line start or after whitespace. When it
253
+ // follows a non-space char it is part of `${var#pat}` / `${var##pat}`
254
+ // parameter expansion (or `$#`, `arr[#]`, etc.), NOT a comment — masking
255
+ // there would erase the rest of the line. `#!` shebang sits at file
256
+ // start (a line start) so it is still masked.
257
+ if (lang === 'bash') {
258
+ const prev = i > 0 ? src[i - 1] : '\n';
259
+ const atCommentPos = prev === '\n' || prev === ' ' || prev === '\t' || prev === '\r';
260
+ if (!atCommentPos) {
261
+ prevToken = 'value';
262
+ i++;
263
+ continue;
264
+ }
265
+ }
266
+ while (i < src.length && src[i] !== '\n') {
267
+ out[i] = ' ';
268
+ i++;
269
+ }
270
+ continue;
271
+ }
272
+ // Lua comments: `--[=*[ ... ]=*]` long-bracket block and `--` line. Lua is
273
+ // neither slash nor hash (see comment predicates), so it needs this
274
+ // dedicated branch. Checked before number/operator handling so the leading
275
+ // `--` is consumed as a comment, not as two minus operators.
276
+ if (lang === 'lua' && src.startsWith('--', i)) {
277
+ // Long-bracket opener: `--` then `[` + zero-or-more `=` + `[`. The level
278
+ // (`=` count) selects the matching close `]` + same `=` + `]`.
279
+ const lb = /^--\[(=*)\[/.exec(src.slice(i, i + 64));
280
+ if (lb) {
281
+ const open = lb[0];
282
+ for (let j = 0; j < open.length; j++) {
283
+ if (src[i + j] !== '\n') out[i + j] = ' ';
284
+ }
285
+ i += open.length;
286
+ stack.push({ kind: 'luablock', close: `]${lb[1]}]` });
287
+ continue;
288
+ }
289
+ // Plain `--` line comment (no long-bracket opener follows).
290
+ while (i < src.length && src[i] !== '\n') {
291
+ out[i] = ' ';
292
+ i++;
293
+ }
294
+ continue;
295
+ }
296
+ if (_supportsTripleSingleQuoteStrings(lang) && src.startsWith("'''", i)) {
297
+ out[i] = ' ';
298
+ if (i + 1 < out.length) out[i + 1] = ' ';
299
+ if (i + 2 < out.length) out[i + 2] = ' ';
300
+ i += 3;
301
+ stack.push({ kind: 'triple', delim: "'''" });
302
+ continue;
303
+ }
304
+ if (_supportsTripleDoubleQuoteStrings(lang) && src.startsWith('"""', i)) {
305
+ out[i] = ' ';
306
+ if (i + 1 < out.length) out[i + 1] = ' ';
307
+ if (i + 2 < out.length) out[i + 2] = ' ';
308
+ i += 3;
309
+ stack.push({ kind: 'triple', delim: '"""' });
310
+ continue;
311
+ }
312
+ if (src[i] === '/' && _isJsLike(lang) && prevToken === 'expr') {
313
+ i = _maskJsRegexLiteral(src, out, i);
314
+ prevToken = 'value';
315
+ continue;
316
+ }
317
+ if (src[i] === '"' || (_supportsSingleQuoteStrings(lang) && src[i] === '\'') || (_supportsBacktickStrings(lang) && src[i] === '`')) {
318
+ if (src[i] !== '\n') out[i] = ' ';
319
+ stack.push({ kind: 'string', delim: src[i], lang });
320
+ i++;
321
+ continue;
322
+ }
323
+ if (_isWordStartChar(src[i])) {
324
+ const start = i;
325
+ while (i < src.length && _isWordChar(src[i])) i++;
326
+ const word = src.substring(start, i);
327
+ prevToken = REGEX_PRECEDENT_KEYWORDS.has(word) ? 'expr' : 'value';
328
+ continue;
329
+ }
330
+ if (src[i] >= '0' && src[i] <= '9') {
331
+ while (i < src.length && (src[i] === '.' || (src[i] >= '0' && src[i] <= '9'))) i++;
332
+ prevToken = 'value';
333
+ continue;
334
+ }
335
+ if (src[i] === ' ' || src[i] === '\t' || src[i] === '\r' || src[i] === '\n') {
336
+ i++;
337
+ continue;
338
+ }
339
+ if (REGEX_PRECEDENT_CHARS.has(src[i])) {
340
+ prevToken = 'expr';
341
+ } else {
342
+ prevToken = 'value';
343
+ }
344
+ i++;
345
+ }
346
+ return out.join('');
347
+ }