@sema-agent/core 1.294.0 → 1.296.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/subagent.d.ts.map +1 -1
- package/dist/agents/subagent.js +24 -3
- package/dist/agents/subagent.js.map +1 -1
- package/dist/bin/sema-tb.d.ts +20 -0
- package/dist/bin/sema-tb.d.ts.map +1 -1
- package/dist/bin/sema-tb.js +71 -23
- package/dist/bin/sema-tb.js.map +1 -1
- package/dist/brain/anthropic.d.ts.map +1 -1
- package/dist/brain/anthropic.js +17 -3
- package/dist/brain/anthropic.js.map +1 -1
- package/dist/brain/openai.d.ts +1 -0
- package/dist/brain/openai.d.ts.map +1 -1
- package/dist/brain/openai.js +328 -298
- package/dist/brain/openai.js.map +1 -1
- package/dist/brain/stream-engine.d.ts +7 -0
- package/dist/brain/stream-engine.d.ts.map +1 -1
- package/dist/brain/stream-engine.js +44 -11
- package/dist/brain/stream-engine.js.map +1 -1
- package/dist/brain/timeout.d.ts +2 -1
- package/dist/brain/timeout.d.ts.map +1 -1
- package/dist/brain/timeout.js +14 -1
- package/dist/brain/timeout.js.map +1 -1
- package/dist/core/ask-question.d.ts.map +1 -1
- package/dist/core/ask-question.js.map +1 -1
- package/dist/core/hooks.d.ts +3 -3
- package/dist/core/hooks.d.ts.map +1 -1
- package/dist/core/hooks.js +11 -4
- package/dist/core/hooks.js.map +1 -1
- package/dist/core/present-plan-tool.d.ts.map +1 -1
- package/dist/core/present-plan-tool.js +12 -13
- package/dist/core/present-plan-tool.js.map +1 -1
- package/dist/core/runner/assemble-result.d.ts +1 -0
- package/dist/core/runner/assemble-result.d.ts.map +1 -1
- package/dist/core/runner/assemble-result.js.map +1 -1
- package/dist/core/runner/call-cap.d.ts +4 -1
- package/dist/core/runner/call-cap.d.ts.map +1 -1
- package/dist/core/runner/call-cap.js +4 -1
- package/dist/core/runner/call-cap.js.map +1 -1
- package/dist/core/runner/cut-kill.d.ts +11 -0
- package/dist/core/runner/cut-kill.d.ts.map +1 -0
- package/dist/core/runner/cut-kill.js +38 -0
- package/dist/core/runner/cut-kill.js.map +1 -0
- package/dist/core/runner/prepare-task.d.ts +5 -0
- package/dist/core/runner/prepare-task.d.ts.map +1 -1
- package/dist/core/runner/prepare-task.js +70 -23
- package/dist/core/runner/prepare-task.js.map +1 -1
- package/dist/core/runner/runtask.d.ts.map +1 -1
- package/dist/core/runner/runtask.js +22 -6
- package/dist/core/runner/runtask.js.map +1 -1
- package/dist/core/sensitive-path-policy.d.ts +2 -1
- package/dist/core/sensitive-path-policy.d.ts.map +1 -1
- package/dist/core/sensitive-path-policy.js +22 -0
- package/dist/core/sensitive-path-policy.js.map +1 -1
- package/dist/core/task-registry.d.ts +4 -0
- package/dist/core/task-registry.d.ts.map +1 -1
- package/dist/core/task-registry.js +83 -15
- package/dist/core/task-registry.js.map +1 -1
- package/dist/core/tool-policy.d.ts +5 -2
- package/dist/core/tool-policy.d.ts.map +1 -1
- package/dist/core/tool-policy.js +9 -0
- package/dist/core/tool-policy.js.map +1 -1
- package/dist/core/trace.d.ts +3 -0
- package/dist/core/trace.d.ts.map +1 -1
- package/dist/core/trace.js.map +1 -1
- package/dist/core/types.d.ts +6 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/engine/compaction/utils.d.ts.map +1 -1
- package/dist/engine/compaction/utils.js +3 -1
- package/dist/engine/compaction/utils.js.map +1 -1
- package/dist/engine/execution-env/kill-tree.d.ts +1 -0
- package/dist/engine/execution-env/kill-tree.d.ts.map +1 -1
- package/dist/engine/execution-env/kill-tree.js +18 -2
- package/dist/engine/execution-env/kill-tree.js.map +1 -1
- package/dist/engine/execution-env/node-execution-env.d.ts +5 -0
- package/dist/engine/execution-env/node-execution-env.d.ts.map +1 -1
- package/dist/engine/execution-env/node-execution-env.js +19 -0
- package/dist/engine/execution-env/node-execution-env.js.map +1 -1
- package/dist/engine/harness/agent-harness.d.ts +1 -0
- package/dist/engine/harness/agent-harness.d.ts.map +1 -1
- package/dist/engine/harness/agent-harness.js +4 -0
- package/dist/engine/harness/agent-harness.js.map +1 -1
- package/dist/engine/harness/messages.d.ts +2 -0
- package/dist/engine/harness/messages.d.ts.map +1 -1
- package/dist/engine/harness/messages.js +52 -3
- package/dist/engine/harness/messages.js.map +1 -1
- package/dist/engine/harness/types.d.ts +7 -0
- package/dist/engine/harness/types.d.ts.map +1 -1
- package/dist/engine/harness/types.js.map +1 -1
- package/dist/engine/llm/types.d.ts +6 -0
- package/dist/engine/llm/types.d.ts.map +1 -1
- package/dist/engine/loop/agent-loop.js +3 -0
- package/dist/engine/loop/agent-loop.js.map +1 -1
- package/dist/engine/loop/types.d.ts +2 -0
- package/dist/engine/loop/types.d.ts.map +1 -1
- package/dist/engine/session/import-validate.d.ts.map +1 -1
- package/dist/engine/session/import-validate.js +64 -0
- package/dist/engine/session/import-validate.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/llm.d.ts +1 -1
- package/dist/internal/llm.d.ts.map +1 -1
- package/dist/scenarios/full-body.d.ts.map +1 -1
- package/dist/scenarios/full-body.js +3 -2
- package/dist/scenarios/full-body.js.map +1 -1
- package/dist/tools/fs/index.d.ts +8 -1
- package/dist/tools/fs/index.d.ts.map +1 -1
- package/dist/tools/fs/index.js +92 -30
- package/dist/tools/fs/index.js.map +1 -1
- package/dist/tools/fs/safety.d.ts +1 -1
- package/dist/tools/fs/safety.d.ts.map +1 -1
- package/dist/tools/fs/safety.js +3 -1
- package/dist/tools/fs/safety.js.map +1 -1
- package/dist/tools/fs/search.d.ts +7 -2
- package/dist/tools/fs/search.d.ts.map +1 -1
- package/dist/tools/fs/search.js +465 -57
- package/dist/tools/fs/search.js.map +1 -1
- package/dist/tools/monitor.d.ts.map +1 -1
- package/dist/tools/monitor.js +12 -1
- package/dist/tools/monitor.js.map +1 -1
- package/dist/tools/task-list.d.ts.map +1 -1
- package/dist/tools/task-list.js.map +1 -1
- package/dist/tools/worktree.d.ts.map +1 -1
- package/dist/tools/worktree.js +15 -2
- package/dist/tools/worktree.js.map +1 -1
- package/package.json +1 -1
package/dist/tools/fs/search.js
CHANGED
|
@@ -161,6 +161,7 @@ export function walkIsPartial(w) {
|
|
|
161
161
|
export async function walk(env, root, start, ignore, signal) {
|
|
162
162
|
const out = [];
|
|
163
163
|
const nameOnly = [];
|
|
164
|
+
const mtimes = new Map();
|
|
164
165
|
let incomplete = false;
|
|
165
166
|
let skippedLarge = 0;
|
|
166
167
|
let skippedUnreadable = 0;
|
|
@@ -169,7 +170,10 @@ export async function walk(env, root, start, ignore, signal) {
|
|
|
169
170
|
const rel = (abs) => (abs.startsWith(rootPrefix) ? abs.slice(rootPrefix.length) : abs);
|
|
170
171
|
const stack = [{ dir: start, depth: 0 }];
|
|
171
172
|
const visited = new Set();
|
|
173
|
+
let steps = 0;
|
|
172
174
|
while (stack.length > 0 && out.length < WALK_MAX_FILES) {
|
|
175
|
+
if (++steps % SCAN_YIELD_EVERY === 0)
|
|
176
|
+
await yieldEventLoop();
|
|
173
177
|
if (signal?.aborted) {
|
|
174
178
|
aborted = true;
|
|
175
179
|
break;
|
|
@@ -199,6 +203,8 @@ export async function walk(env, root, start, ignore, signal) {
|
|
|
199
203
|
else if (info.kind === "file") {
|
|
200
204
|
if (ignore(rel(info.path), false))
|
|
201
205
|
continue;
|
|
206
|
+
if (typeof info.mtimeMs === "number")
|
|
207
|
+
mtimes.set(info.path, info.mtimeMs);
|
|
202
208
|
if (hasBinaryExtension(info.path)) {
|
|
203
209
|
if (nameOnly.length < WALK_MAX_FILES)
|
|
204
210
|
nameOnly.push(info.path);
|
|
@@ -224,7 +230,7 @@ export async function walk(env, root, start, ignore, signal) {
|
|
|
224
230
|
}
|
|
225
231
|
if (stack.length > 0 && out.length >= WALK_MAX_FILES)
|
|
226
232
|
incomplete = true;
|
|
227
|
-
return { files: out, nameOnly, incomplete, skippedLarge, skippedUnreadable, aborted };
|
|
233
|
+
return { files: out, nameOnly, incomplete, skippedLarge, skippedUnreadable, aborted, mtimes };
|
|
228
234
|
}
|
|
229
235
|
function walkCaveat(w, forContentSearch) {
|
|
230
236
|
const parts = [];
|
|
@@ -240,42 +246,404 @@ function walkCaveat(w, forContentSearch) {
|
|
|
240
246
|
}
|
|
241
247
|
return parts.length > 0 ? `\n…[results may be incomplete: ${parts.join("; ")}]` : "";
|
|
242
248
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
249
|
+
const SCAN_YIELD_EVERY = 32;
|
|
250
|
+
const yieldEventLoop = () => new Promise((resolve) => (typeof setImmediate === "function" ? setImmediate(resolve) : setTimeout(resolve, 0)));
|
|
251
|
+
const REDOS_BOUNDED_CEILING = 100;
|
|
252
|
+
const REDOS_AMBIGUOUS_REPEAT_CEILING = 10;
|
|
253
|
+
function analyzeRedos(pattern, ignoreCase = false) {
|
|
254
|
+
const quantAt = (i) => {
|
|
255
|
+
const c = pattern[i];
|
|
256
|
+
if (c === "*" || c === "+")
|
|
257
|
+
return { len: 1, unbounded: true, max: Infinity, range: true, brace: false };
|
|
258
|
+
if (c === "?")
|
|
259
|
+
return { len: 1, unbounded: false, max: 1, range: true, brace: false };
|
|
260
|
+
if (c === "{") {
|
|
261
|
+
const m = /^\{(\d+)(?:,(\d*))?\}/.exec(pattern.slice(i));
|
|
262
|
+
if (!m)
|
|
263
|
+
return undefined;
|
|
264
|
+
const lo = Number(m[1]);
|
|
265
|
+
const hi = m[2] === "" || m[2] === undefined ? (m[2] === "" ? Infinity : lo) : Number(m[2]);
|
|
266
|
+
return { len: m[0].length, unbounded: hi > REDOS_BOUNDED_CEILING, max: hi, range: lo < hi, brace: true };
|
|
267
|
+
}
|
|
268
|
+
return undefined;
|
|
269
|
+
};
|
|
270
|
+
let hasNamedGroup = false;
|
|
271
|
+
const escTokenLen = (s, i) => {
|
|
272
|
+
const c = s[i + 1];
|
|
273
|
+
if (c === "x" && /^[0-9a-fA-F]{2}$/.test(s.slice(i + 2, i + 4)))
|
|
274
|
+
return 4;
|
|
275
|
+
if (c === "u") {
|
|
276
|
+
if (/^[0-9a-fA-F]{4}$/.test(s.slice(i + 2, i + 6)))
|
|
277
|
+
return 6;
|
|
278
|
+
return 2;
|
|
279
|
+
}
|
|
280
|
+
if (c === "c" && /^[a-zA-Z]$/.test(s[i + 2] ?? ""))
|
|
281
|
+
return 3;
|
|
282
|
+
if (c === "k" && hasNamedGroup && s[i + 2] === "<") {
|
|
283
|
+
const close = s.indexOf(">", i + 3);
|
|
284
|
+
if (close > i + 3)
|
|
285
|
+
return close - i + 1;
|
|
286
|
+
}
|
|
287
|
+
if (c === "0") {
|
|
288
|
+
let j = i + 2;
|
|
289
|
+
while (j < s.length && j < i + 4 && s[j] >= "0" && s[j] <= "7")
|
|
290
|
+
j++;
|
|
291
|
+
return j - i;
|
|
292
|
+
}
|
|
293
|
+
if (c !== undefined && c >= "1" && c <= "9") {
|
|
294
|
+
let j = i + 2;
|
|
295
|
+
while (j < s.length && j < i + 10 && s[j] >= "0" && s[j] <= "9")
|
|
296
|
+
j++;
|
|
297
|
+
if (Number(s.slice(i + 1, j)) <= captureGroups)
|
|
298
|
+
return j - i;
|
|
299
|
+
if (c >= "8")
|
|
300
|
+
return 2;
|
|
301
|
+
const maxLen = c <= "3" ? 3 : 2;
|
|
302
|
+
let k = i + 1;
|
|
303
|
+
while (k < j && k - i - 1 < maxLen && s[k] >= "0" && s[k] <= "7")
|
|
304
|
+
k++;
|
|
305
|
+
return k - i;
|
|
306
|
+
}
|
|
307
|
+
return 2;
|
|
308
|
+
};
|
|
309
|
+
let captureGroups = 0;
|
|
310
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
311
|
+
const ch = pattern[i];
|
|
312
|
+
if (ch === "\\") {
|
|
313
|
+
i++;
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
if (ch === "[") {
|
|
317
|
+
i++;
|
|
318
|
+
if (pattern[i] === "^")
|
|
319
|
+
i++;
|
|
320
|
+
while (i < pattern.length && pattern[i] !== "]") {
|
|
321
|
+
if (pattern[i] === "\\")
|
|
322
|
+
i++;
|
|
323
|
+
i++;
|
|
324
|
+
}
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
if (ch === "(") {
|
|
328
|
+
if (pattern[i + 1] !== "?")
|
|
329
|
+
captureGroups++;
|
|
330
|
+
else if (pattern[i + 2] === "<" && pattern[i + 3] !== "=" && pattern[i + 3] !== "!") {
|
|
331
|
+
captureGroups++;
|
|
332
|
+
hasNamedGroup = true;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
247
335
|
}
|
|
248
|
-
|
|
249
|
-
const
|
|
250
|
-
|
|
336
|
+
const readClassAtom = (s, pos) => {
|
|
337
|
+
const ch = s[pos];
|
|
338
|
+
if (ch === undefined)
|
|
339
|
+
return undefined;
|
|
340
|
+
if (ch !== "\\")
|
|
341
|
+
return { cp: ch, len: 1 };
|
|
342
|
+
const c = s[pos + 1];
|
|
343
|
+
if (c === undefined)
|
|
344
|
+
return undefined;
|
|
345
|
+
if (c === "x" && /^[0-9a-fA-F]{2}$/.test(s.slice(pos + 2, pos + 4)))
|
|
346
|
+
return { cp: String.fromCharCode(parseInt(s.slice(pos + 2, pos + 4), 16)), len: 4 };
|
|
347
|
+
if (c === "u" && /^[0-9a-fA-F]{4}$/.test(s.slice(pos + 2, pos + 6)))
|
|
348
|
+
return { cp: String.fromCharCode(parseInt(s.slice(pos + 2, pos + 6), 16)), len: 6 };
|
|
349
|
+
if (c === "c") {
|
|
350
|
+
if (/^[a-zA-Z0-9_]$/.test(s[pos + 2] ?? ""))
|
|
351
|
+
return { cp: String.fromCharCode(s[pos + 2].toUpperCase().charCodeAt(0) % 32), len: 3 };
|
|
352
|
+
return { cp: undefined, len: 2 };
|
|
353
|
+
}
|
|
354
|
+
if (c >= "0" && c <= "9") {
|
|
355
|
+
if (c === "8" || c === "9")
|
|
356
|
+
return { cp: c, len: 2 };
|
|
357
|
+
const maxLen = c <= "3" ? 3 : 2;
|
|
358
|
+
let j = pos + 1;
|
|
359
|
+
while (j < s.length && j - pos - 1 < maxLen && s[j] >= "0" && s[j] <= "7")
|
|
360
|
+
j++;
|
|
361
|
+
return { cp: String.fromCharCode(parseInt(s.slice(pos + 1, j), 8)), len: j - pos };
|
|
362
|
+
}
|
|
363
|
+
if (c === "b")
|
|
364
|
+
return { cp: "\b", len: 2 };
|
|
365
|
+
if (/[dDwWsS]/.test(c))
|
|
366
|
+
return { cp: undefined, len: 2 };
|
|
367
|
+
if (c === "t")
|
|
368
|
+
return { cp: "\t", len: 2 };
|
|
369
|
+
if (c === "n")
|
|
370
|
+
return { cp: "\n", len: 2 };
|
|
371
|
+
if (c === "r")
|
|
372
|
+
return { cp: "\r", len: 2 };
|
|
373
|
+
if (c === "f")
|
|
374
|
+
return { cp: "\f", len: 2 };
|
|
375
|
+
if (c === "v")
|
|
376
|
+
return { cp: "\v", len: 2 };
|
|
377
|
+
return { cp: c, len: 2 };
|
|
378
|
+
};
|
|
379
|
+
const atomKey = (src) => {
|
|
380
|
+
const canon = (ch) => {
|
|
381
|
+
if (!ignoreCase)
|
|
382
|
+
return ch;
|
|
383
|
+
const u = ch.toUpperCase();
|
|
384
|
+
if (u.length !== 1)
|
|
385
|
+
return ch;
|
|
386
|
+
if (ch.charCodeAt(0) >= 128 && u.charCodeAt(0) < 128)
|
|
387
|
+
return ch;
|
|
388
|
+
return u;
|
|
389
|
+
};
|
|
390
|
+
const lit = (ch) => `L${canon(ch)}`;
|
|
391
|
+
if (src.length === 0)
|
|
392
|
+
return src;
|
|
393
|
+
if (src[0] !== "\\") {
|
|
394
|
+
if (src.length === 1)
|
|
395
|
+
return src === "." ? src : lit(src);
|
|
396
|
+
if (src[0] === "[" && src.endsWith("]") && src[1] !== "^") {
|
|
397
|
+
const body = src.slice(1, -1);
|
|
398
|
+
const a1 = readClassAtom(body, 0);
|
|
399
|
+
if (a1?.cp !== undefined) {
|
|
400
|
+
if (a1.len === body.length)
|
|
401
|
+
return lit(a1.cp);
|
|
402
|
+
if (body[a1.len] === "-") {
|
|
403
|
+
const a2 = readClassAtom(body, a1.len + 1);
|
|
404
|
+
if (a2?.cp !== undefined && a1.len + 1 + a2.len === body.length && a1.cp === a2.cp) {
|
|
405
|
+
return lit(a1.cp);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return src;
|
|
410
|
+
}
|
|
411
|
+
if (src.startsWith("(?:") && src.endsWith(")")) {
|
|
412
|
+
const body = src.slice(3, -1);
|
|
413
|
+
if (body === ".")
|
|
414
|
+
return ".";
|
|
415
|
+
if (body.length === 1 && !"\\^$.|?*+()[]{}".includes(body))
|
|
416
|
+
return lit(body);
|
|
417
|
+
if (body[0] === "\\" && escTokenLen(body, 0) === body.length) {
|
|
418
|
+
const k = atomKey(body);
|
|
419
|
+
if (k.startsWith("L"))
|
|
420
|
+
return k;
|
|
421
|
+
}
|
|
422
|
+
if (body[0] === "[") {
|
|
423
|
+
const k = atomKey(body);
|
|
424
|
+
if (k.startsWith("L"))
|
|
425
|
+
return k;
|
|
426
|
+
}
|
|
427
|
+
return src;
|
|
428
|
+
}
|
|
429
|
+
return src;
|
|
430
|
+
}
|
|
431
|
+
const c = src[1];
|
|
432
|
+
if (c !== undefined && c >= "0" && c <= "9") {
|
|
433
|
+
const digits = src.slice(1);
|
|
434
|
+
if (c !== "0" && Number(digits) <= captureGroups)
|
|
435
|
+
return src;
|
|
436
|
+
if (c === "8" || c === "9")
|
|
437
|
+
return digits.length === 1 ? lit(c) : src;
|
|
438
|
+
if (/^[0-7]{1,3}$/.test(digits) && (digits.length < 3 || digits[0] <= "3")) {
|
|
439
|
+
return lit(String.fromCharCode(parseInt(digits, 8)));
|
|
440
|
+
}
|
|
441
|
+
return src;
|
|
442
|
+
}
|
|
443
|
+
if (c === "x" && src.length === 4)
|
|
444
|
+
return lit(String.fromCharCode(parseInt(src.slice(2), 16)));
|
|
445
|
+
if (c === "u" && src.length === 6)
|
|
446
|
+
return lit(String.fromCharCode(parseInt(src.slice(2), 16)));
|
|
447
|
+
if (c === "c" && src.length === 3)
|
|
448
|
+
return `L${String.fromCharCode(src[2].toUpperCase().charCodeAt(0) % 32)}`;
|
|
449
|
+
if (src.length === 2) {
|
|
450
|
+
if (!/[a-zA-Z]/.test(c))
|
|
451
|
+
return lit(c);
|
|
452
|
+
if (/[dDwWsSbB]/.test(c))
|
|
453
|
+
return src;
|
|
454
|
+
if (c === "t")
|
|
455
|
+
return lit("\t");
|
|
456
|
+
if (c === "n")
|
|
457
|
+
return lit("\n");
|
|
458
|
+
if (c === "r")
|
|
459
|
+
return lit("\r");
|
|
460
|
+
if (c === "f")
|
|
461
|
+
return lit("\f");
|
|
462
|
+
if (c === "v")
|
|
463
|
+
return lit("\v");
|
|
464
|
+
if (c === "c")
|
|
465
|
+
return src;
|
|
466
|
+
if (c === "k")
|
|
467
|
+
return hasNamedGroup ? src : lit("k");
|
|
468
|
+
return lit(c);
|
|
469
|
+
}
|
|
470
|
+
return src;
|
|
471
|
+
};
|
|
472
|
+
const stack = [{ start: 0, hasUnbounded: false, hasRange: false, hasAlt: false, maxCeil: 1 }];
|
|
473
|
+
const cur = () => stack[stack.length - 1];
|
|
474
|
+
const atomCtx = [{}];
|
|
475
|
+
const atom = () => atomCtx[atomCtx.length - 1];
|
|
476
|
+
let lastClosedGroup;
|
|
477
|
+
let quantifiedGroup = false;
|
|
478
|
+
let unboundedCount = 0;
|
|
479
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
480
|
+
const c = pattern[i];
|
|
481
|
+
const q = quantAt(i);
|
|
482
|
+
if (q) {
|
|
483
|
+
const lazy = pattern[i + q.len] === "?" ? 1 : 0;
|
|
484
|
+
if (q.unbounded)
|
|
485
|
+
unboundedCount++;
|
|
486
|
+
const g = lastClosedGroup;
|
|
487
|
+
const lvl = cur();
|
|
488
|
+
const ctx = atom();
|
|
489
|
+
if (g) {
|
|
490
|
+
quantifiedGroup = true;
|
|
491
|
+
const snippet = JSON.stringify(pattern.slice(g.start, i + q.len));
|
|
492
|
+
if (q.unbounded) {
|
|
493
|
+
if (g.hasUnbounded)
|
|
494
|
+
return { risk: `nested unbounded quantifiers in ${snippet} (an unbounded quantifier applied to a group that itself contains one)`, quantifiedGroup, unboundedCount };
|
|
495
|
+
if (g.hasAlt)
|
|
496
|
+
return { risk: `quantified alternation in ${snippet} (an unbounded quantifier applied to a group containing "|" — the branches can split the same text many ways)`, quantifiedGroup, unboundedCount };
|
|
497
|
+
if (g.hasRange)
|
|
498
|
+
return { risk: `ambiguous nested repetition in ${snippet} (an unbounded quantifier applied to a group whose own repetition count can vary)`, quantifiedGroup, unboundedCount };
|
|
499
|
+
}
|
|
500
|
+
else if (q.brace && q.max >= 2) {
|
|
501
|
+
if (g.hasUnbounded)
|
|
502
|
+
return { risk: `nested quantifiers in ${snippet} (an unbounded quantifier inside a group repeated up to ${q.max} times)`, quantifiedGroup, unboundedCount };
|
|
503
|
+
if ((g.hasAlt || g.hasRange) && q.max > REDOS_AMBIGUOUS_REPEAT_CEILING)
|
|
504
|
+
return { risk: `ambiguous repetition in ${snippet} (a group with ${g.hasAlt ? "alternation" : "variable-count content"} repeated up to ${q.max} times — candidate paths grow exponentially with the repeat count)`, quantifiedGroup, unboundedCount };
|
|
505
|
+
if (g.maxCeil >= 2 && g.maxCeil * q.max > REDOS_BOUNDED_CEILING)
|
|
506
|
+
return { risk: `compounded bounded repetition in ${snippet} (inner ceiling ${g.maxCeil} × outer ${q.max} exceeds the ${REDOS_BOUNDED_CEILING} safety ceiling)`, quantifiedGroup, unboundedCount };
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
const sideUnbounded = q.unbounded && q.range;
|
|
510
|
+
const atomStart = g ? g.start : ctx.lastAtomStart;
|
|
511
|
+
if (atomStart !== undefined) {
|
|
512
|
+
const atomSrc = pattern.slice(atomStart, i);
|
|
513
|
+
const key = atomKey(atomSrc);
|
|
514
|
+
const prev = ctx.prevQuant;
|
|
515
|
+
if (prev && prev.key === key && prev.unbounded && sideUnbounded) {
|
|
516
|
+
const snippet = JSON.stringify(pattern.slice(prev.start, i + q.len + lazy));
|
|
517
|
+
return {
|
|
518
|
+
risk: `adjacent equivalent quantified atoms in ${snippet} (two consecutive unbounded quantifiers apply to the identical atom ${JSON.stringify(atomSrc)} — every split of a matching run between them is a distinct backtracking path, quadratic in line length on non-matching input; merge them into a single quantifier)`,
|
|
519
|
+
quantifiedGroup,
|
|
520
|
+
unboundedCount,
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
ctx.prevQuant = { key, start: atomStart, unbounded: sideUnbounded };
|
|
524
|
+
}
|
|
525
|
+
else
|
|
526
|
+
ctx.prevQuant = undefined;
|
|
527
|
+
if (g) {
|
|
528
|
+
if (q.unbounded)
|
|
529
|
+
lvl.hasUnbounded = true;
|
|
530
|
+
else {
|
|
531
|
+
if (q.range)
|
|
532
|
+
lvl.hasRange = true;
|
|
533
|
+
lvl.maxCeil = Math.max(lvl.maxCeil, Math.max(g.maxCeil, 1) * q.max);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
else if (q.unbounded)
|
|
537
|
+
lvl.hasUnbounded = true;
|
|
538
|
+
else {
|
|
539
|
+
if (q.range)
|
|
540
|
+
lvl.hasRange = true;
|
|
541
|
+
lvl.maxCeil = Math.max(lvl.maxCeil, q.max);
|
|
542
|
+
}
|
|
543
|
+
i += q.len + lazy - 1;
|
|
544
|
+
ctx.lastAtomStart = undefined;
|
|
545
|
+
lastClosedGroup = undefined;
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
if (lastClosedGroup !== undefined || atom().lastAtomStart !== undefined)
|
|
549
|
+
atom().prevQuant = undefined;
|
|
550
|
+
lastClosedGroup = undefined;
|
|
551
|
+
if (c === "\\") {
|
|
552
|
+
atom().lastAtomStart = i;
|
|
553
|
+
i += escTokenLen(pattern, i) - 1;
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
if (c === "[") {
|
|
557
|
+
atom().lastAtomStart = i;
|
|
558
|
+
i++;
|
|
559
|
+
if (pattern[i] === "^")
|
|
560
|
+
i++;
|
|
561
|
+
while (i < pattern.length && pattern[i] !== "]") {
|
|
562
|
+
if (pattern[i] === "\\")
|
|
563
|
+
i++;
|
|
564
|
+
i++;
|
|
565
|
+
}
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
if (c === "|") {
|
|
569
|
+
cur().hasAlt = true;
|
|
570
|
+
const ctx = atom();
|
|
571
|
+
ctx.lastAtomStart = undefined;
|
|
572
|
+
ctx.prevQuant = undefined;
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
if (c === "(") {
|
|
576
|
+
stack.push({ start: i, hasUnbounded: false, hasRange: false, hasAlt: false, maxCeil: 1 });
|
|
577
|
+
atomCtx.push({});
|
|
578
|
+
if (pattern[i + 1] === "?") {
|
|
579
|
+
const c2 = pattern[i + 2];
|
|
580
|
+
if (c2 === ":" || c2 === "=" || c2 === "!")
|
|
581
|
+
i += 2;
|
|
582
|
+
else if (c2 === "<") {
|
|
583
|
+
const c3 = pattern[i + 3];
|
|
584
|
+
if (c3 === "=" || c3 === "!")
|
|
585
|
+
i += 3;
|
|
586
|
+
else {
|
|
587
|
+
const close = pattern.indexOf(">", i + 3);
|
|
588
|
+
if (close > 0)
|
|
589
|
+
i = close;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
if (c === ")") {
|
|
596
|
+
if (stack.length === 1)
|
|
597
|
+
continue;
|
|
598
|
+
const frame = stack.pop();
|
|
599
|
+
atomCtx.pop();
|
|
600
|
+
const lvl = cur();
|
|
601
|
+
lvl.hasUnbounded ||= frame.hasUnbounded;
|
|
602
|
+
lvl.hasRange ||= frame.hasRange;
|
|
603
|
+
lvl.hasAlt ||= frame.hasAlt;
|
|
604
|
+
lvl.maxCeil = Math.max(lvl.maxCeil, frame.maxCeil);
|
|
605
|
+
lastClosedGroup = frame;
|
|
606
|
+
atom().lastAtomStart = undefined;
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
atom().lastAtomStart = i;
|
|
251
610
|
}
|
|
252
|
-
return
|
|
611
|
+
return { quantifiedGroup, unboundedCount };
|
|
612
|
+
}
|
|
613
|
+
export function findRedosRisk(pattern, ignoreCase = false) {
|
|
614
|
+
return analyzeRedos(pattern, ignoreCase).risk;
|
|
615
|
+
}
|
|
616
|
+
function redosRejection(risk) {
|
|
617
|
+
return (`Error (grep): pattern rejected by the fallback scanner: ${risk}. ` +
|
|
618
|
+
`ripgrep is unavailable here, so this pattern would run on a backtracking regex engine where that shape ` +
|
|
619
|
+
`can hang the scan (catastrophic backtracking). Rewrite it so a repeated group cannot match the same text ` +
|
|
620
|
+
`in more than one way — remove the nested/variable quantifier or the alternation from the repeated group, ` +
|
|
621
|
+
`or lower the repetition counts (product ceiling: ${REDOS_BOUNDED_CEILING}).`);
|
|
622
|
+
}
|
|
623
|
+
const GREP_FILE_BUDGET_MS = 2_000;
|
|
624
|
+
const GREP_GRAY_LINE_MAX = 8_192;
|
|
625
|
+
const GREP_LINE_CHECK_EVERY = 256;
|
|
626
|
+
const monotonicNow = () => (typeof performance !== "undefined" ? performance.now() : Date.now());
|
|
627
|
+
class GrepBudgetExceeded extends Error {
|
|
628
|
+
}
|
|
629
|
+
function compilePattern(p) {
|
|
630
|
+
const re = new RegExp(p.pattern, p.ignore_case ? "i" : "");
|
|
631
|
+
return (l) => re.test(l);
|
|
253
632
|
}
|
|
254
633
|
function matchedParts(line, p) {
|
|
255
634
|
const parts = [];
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
break;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
else if (p.pattern.length > 0) {
|
|
268
|
-
const hay = p.ignore_case ? line.toLowerCase() : line;
|
|
269
|
-
const needle = p.ignore_case ? p.pattern.toLowerCase() : p.pattern;
|
|
270
|
-
for (let idx = hay.indexOf(needle); idx !== -1; idx = hay.indexOf(needle, idx + needle.length)) {
|
|
271
|
-
parts.push(line.slice(idx, idx + needle.length));
|
|
272
|
-
if (parts.length > 1000)
|
|
273
|
-
break;
|
|
274
|
-
}
|
|
635
|
+
const re = new RegExp(p.pattern, `g${p.ignore_case ? "i" : ""}`);
|
|
636
|
+
for (let m = re.exec(line); m !== null; m = re.exec(line)) {
|
|
637
|
+
if (m[0].length > 0)
|
|
638
|
+
parts.push(m[0]);
|
|
639
|
+
else
|
|
640
|
+
re.lastIndex++;
|
|
641
|
+
if (parts.length > 1000)
|
|
642
|
+
break;
|
|
275
643
|
}
|
|
276
644
|
return parts;
|
|
277
645
|
}
|
|
278
|
-
function multilineSpans(content, p, captureText = false) {
|
|
646
|
+
function multilineSpans(content, p, captureText = false, deadline = Infinity) {
|
|
279
647
|
const spans = [];
|
|
280
648
|
const starts = [0];
|
|
281
649
|
for (let i = 0; i < content.length; i++)
|
|
@@ -293,30 +661,33 @@ function multilineSpans(content, p, captureText = false) {
|
|
|
293
661
|
return lo + 1;
|
|
294
662
|
};
|
|
295
663
|
const MATCH_TEXT_MAX = 2_000;
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
const needle = p.ignore_case ? p.pattern.toLowerCase() : p.pattern;
|
|
309
|
-
if (needle.length > 0) {
|
|
310
|
-
for (let idx = hay.indexOf(needle); idx !== -1; idx = hay.indexOf(needle, idx + needle.length)) {
|
|
311
|
-
spans.push([lineAt(idx), lineAt(idx + needle.length - 1), captureText ? content.slice(idx, idx + Math.min(needle.length, MATCH_TEXT_MAX)) : undefined]);
|
|
312
|
-
if (spans.length > 10_000)
|
|
313
|
-
break;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
664
|
+
const re = new RegExp(p.pattern, `gs${p.ignore_case ? "i" : ""}`);
|
|
665
|
+
for (;;) {
|
|
666
|
+
if (monotonicNow() >= deadline)
|
|
667
|
+
throw new GrepBudgetExceeded();
|
|
668
|
+
const m = re.exec(content);
|
|
669
|
+
if (m === null)
|
|
670
|
+
break;
|
|
671
|
+
spans.push([lineAt(m.index), lineAt(m.index + Math.max(0, m[0].length - 1)), captureText ? m[0].slice(0, MATCH_TEXT_MAX) : undefined]);
|
|
672
|
+
if (m[0].length === 0)
|
|
673
|
+
re.lastIndex++;
|
|
674
|
+
if (spans.length > 10_000)
|
|
675
|
+
break;
|
|
316
676
|
}
|
|
317
677
|
return spans;
|
|
318
678
|
}
|
|
319
|
-
export async function jsGrep(env, root, p, signal) {
|
|
679
|
+
export async function jsGrep(env, root, p, signal, guards) {
|
|
680
|
+
const analysis = analyzeRedos(p.pattern, p.ignore_case === true);
|
|
681
|
+
if (analysis.risk)
|
|
682
|
+
return redosRejection(analysis.risk);
|
|
683
|
+
const budgetMs = guards?.budgetMs ?? GREP_FILE_BUDGET_MS;
|
|
684
|
+
const longLineLimit = guards?.longLineLimit ?? GREP_GRAY_LINE_MAX;
|
|
685
|
+
const grayPattern = analysis.quantifiedGroup || analysis.unboundedCount >= 2;
|
|
686
|
+
const budgetError = (file, elapsedMs) => `Error (grep): scan stopped after ${Math.round(elapsedMs)}ms inside ${file} — the pattern ` +
|
|
687
|
+
`${JSON.stringify(p.pattern)} is backtracking heavily on this content (fallback-engine budget: ` +
|
|
688
|
+
`${budgetMs}ms per file). Rewrite the pattern to be more specific, or rerun where ripgrep is available.`;
|
|
689
|
+
let skippedLongLines = 0;
|
|
690
|
+
let multilineSkippedFiles = 0;
|
|
320
691
|
let test;
|
|
321
692
|
try {
|
|
322
693
|
test = compilePattern(p);
|
|
@@ -332,7 +703,7 @@ export async function jsGrep(env, root, p, signal) {
|
|
|
332
703
|
let files = walked.files;
|
|
333
704
|
files.sort();
|
|
334
705
|
const cap = p.head_limit === 0 ? Infinity : Math.max(1, Math.floor(p.head_limit ?? GREP_DEFAULT_CAP));
|
|
335
|
-
const mode = p.output_mode ?? "
|
|
706
|
+
const mode = p.output_mode ?? "files_with_matches";
|
|
336
707
|
const rel = (abs) => (abs.startsWith(rootPrefix) ? abs.slice(rootPrefix.length) : abs);
|
|
337
708
|
const relOut = (abs) => {
|
|
338
709
|
const r = rel(abs);
|
|
@@ -366,7 +737,10 @@ export async function jsGrep(env, root, p, signal) {
|
|
|
366
737
|
let totalFiles = 0;
|
|
367
738
|
let skippedUnreadableFiles = 0;
|
|
368
739
|
let scanAborted = false;
|
|
740
|
+
let scanned = 0;
|
|
369
741
|
for (const f of files) {
|
|
742
|
+
if (++scanned % SCAN_YIELD_EVERY === 0)
|
|
743
|
+
await yieldEventLoop();
|
|
370
744
|
if (signal?.aborted) {
|
|
371
745
|
scanAborted = true;
|
|
372
746
|
break;
|
|
@@ -384,12 +758,19 @@ export async function jsGrep(env, root, p, signal) {
|
|
|
384
758
|
if (lines.length > 1 && lines.at(-1) === "")
|
|
385
759
|
lines.pop();
|
|
386
760
|
let fileCount = 0;
|
|
761
|
+
const fileMatchStart = monotonicNow();
|
|
387
762
|
if (p.multiline) {
|
|
763
|
+
if (grayPattern && read.value.length > longLineLimit) {
|
|
764
|
+
multilineSkippedFiles++;
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
388
767
|
let spans;
|
|
389
768
|
try {
|
|
390
|
-
spans = multilineSpans(read.value, p, mode === "content" && p.only_matching === true);
|
|
769
|
+
spans = multilineSpans(read.value, p, mode === "content" && p.only_matching === true, fileMatchStart + budgetMs);
|
|
391
770
|
}
|
|
392
771
|
catch (e) {
|
|
772
|
+
if (e instanceof GrepBudgetExceeded)
|
|
773
|
+
return budgetError(relOut(f), monotonicNow() - fileMatchStart);
|
|
393
774
|
return `Error (grep): invalid regex: ${e instanceof Error ? e.message : String(e)}`;
|
|
394
775
|
}
|
|
395
776
|
fileCount = spans.length;
|
|
@@ -412,6 +793,22 @@ export async function jsGrep(env, root, p, signal) {
|
|
|
412
793
|
}
|
|
413
794
|
else {
|
|
414
795
|
for (let i = 0; i < lines.length; i++) {
|
|
796
|
+
if (grayPattern || (i > 0 && i % GREP_LINE_CHECK_EVERY === 0)) {
|
|
797
|
+
const elapsed = monotonicNow() - fileMatchStart;
|
|
798
|
+
if (elapsed >= budgetMs)
|
|
799
|
+
return budgetError(relOut(f), elapsed);
|
|
800
|
+
}
|
|
801
|
+
if (i > 0 && i % GREP_LINE_CHECK_EVERY === 0) {
|
|
802
|
+
await yieldEventLoop();
|
|
803
|
+
if (signal?.aborted) {
|
|
804
|
+
scanAborted = true;
|
|
805
|
+
break;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
if (grayPattern && lines[i].length > longLineLimit) {
|
|
809
|
+
skippedLongLines++;
|
|
810
|
+
continue;
|
|
811
|
+
}
|
|
415
812
|
if (!test(lines[i]))
|
|
416
813
|
continue;
|
|
417
814
|
fileCount++;
|
|
@@ -434,6 +831,8 @@ export async function jsGrep(env, root, p, signal) {
|
|
|
434
831
|
}
|
|
435
832
|
}
|
|
436
833
|
}
|
|
834
|
+
if (scanAborted)
|
|
835
|
+
break;
|
|
437
836
|
if (mode === "content")
|
|
438
837
|
totalContent += fileCount;
|
|
439
838
|
if (fileCount > 0) {
|
|
@@ -444,12 +843,16 @@ export async function jsGrep(env, root, p, signal) {
|
|
|
444
843
|
counts.push(`${relOut(f)}:${fileCount}`);
|
|
445
844
|
}
|
|
446
845
|
}
|
|
447
|
-
if (scanAborted || skippedUnreadableFiles > 0) {
|
|
846
|
+
if (scanAborted || skippedUnreadableFiles > 0 || skippedLongLines > 0 || multilineSkippedFiles > 0) {
|
|
448
847
|
const extra = [];
|
|
449
848
|
if (scanAborted && !walked.aborted)
|
|
450
849
|
extra.push("the search was interrupted before finishing");
|
|
451
850
|
if (skippedUnreadableFiles > 0)
|
|
452
851
|
extra.push(`${skippedUnreadableFiles} file(s) could not be read and were skipped`);
|
|
852
|
+
if (skippedLongLines > 0)
|
|
853
|
+
extra.push(`${skippedLongLines} line(s) over ${longLineLimit} chars were not matched (the pattern's worst-case matching cost grows superlinearly with line length on the fallback engine)`);
|
|
854
|
+
if (multilineSkippedFiles > 0)
|
|
855
|
+
extra.push(`${multilineSkippedFiles} file(s) skipped by multiline matching (content over ${longLineLimit} chars; the pattern's worst-case matching cost grows superlinearly with input length on the fallback engine)`);
|
|
453
856
|
if (extra.length > 0) {
|
|
454
857
|
caveat = caveat.length > 0
|
|
455
858
|
? caveat.replace(/\]$/, `; ${extra.join("; ")}]`)
|
|
@@ -514,11 +917,9 @@ async function jsGrepFallback(env, root, p, signal, reason) {
|
|
|
514
917
|
};
|
|
515
918
|
}
|
|
516
919
|
export async function rgGrepDetailed(env, root, p, signal) {
|
|
517
|
-
const mode = p.output_mode ?? "
|
|
920
|
+
const mode = p.output_mode ?? "files_with_matches";
|
|
518
921
|
const flags = ["--no-messages", "--no-require-git"];
|
|
519
922
|
flags.push("--max-columns", "500", "--max-columns-preview");
|
|
520
|
-
if (!p.regex)
|
|
521
|
-
flags.push("-F");
|
|
522
923
|
if (p.ignore_case)
|
|
523
924
|
flags.push("-i");
|
|
524
925
|
if (p.multiline)
|
|
@@ -601,7 +1002,14 @@ export async function runGlobDetailed(env, root, pattern, opts = {}, signal) {
|
|
|
601
1002
|
const re = globTokenToRegExp(pattern, false, true);
|
|
602
1003
|
const rel = (abs) => (abs.startsWith(rootPrefix) ? abs.slice(rootPrefix.length) : abs);
|
|
603
1004
|
const cap = Math.max(1, Math.floor(opts.max ?? 500));
|
|
604
|
-
const
|
|
1005
|
+
const matchedAbs = files.filter((f) => re.test(rel(f)) || re.test(basename(f)));
|
|
1006
|
+
const haveMtimes = matchedAbs.length > 0 && matchedAbs.every((f) => walked.mtimes.has(f));
|
|
1007
|
+
const matchedAll = haveMtimes
|
|
1008
|
+
? matchedAbs
|
|
1009
|
+
.map((f) => ({ r: rel(f), m: walked.mtimes.get(f) }))
|
|
1010
|
+
.sort((a, b) => b.m - a.m || (a.r < b.r ? -1 : a.r > b.r ? 1 : 0))
|
|
1011
|
+
.map((x) => x.r)
|
|
1012
|
+
: matchedAbs.map(rel).sort();
|
|
605
1013
|
const matched = matchedAll.slice(0, cap);
|
|
606
1014
|
const totalMatches = matchedAll.length;
|
|
607
1015
|
const countIsComplete = !walkIsPartial(walked);
|