eminiar 0.13.1 → 0.13.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.
package/dist/hook.js CHANGED
@@ -35,7 +35,7 @@ import {
35
35
  swarmSubagentStop,
36
36
  swarmTouch,
37
37
  workerLacksTool
38
- } from "./hook-chunk-DSLVDGOB.js";
38
+ } from "./hook-chunk-ZLRLOPLR.js";
39
39
  import {
40
40
  GUIDE_ORDER,
41
41
  classifyAssistant,
@@ -52,7 +52,7 @@ import {
52
52
  projectDir,
53
53
  setStorageBusyTimeout,
54
54
  withPriority
55
- } from "./hook-chunk-BECSRJFD.js";
55
+ } from "./hook-chunk-ETYHCPPH.js";
56
56
 
57
57
  // src/hook.ts
58
58
  init_esm_shims();
@@ -1901,7 +1901,7 @@ var userPromptSubmit = async (input) => {
1901
1901
  let systemMessage;
1902
1902
  if (ctx.config.adjudicator.enabled && input.client !== "cursor" && hasBudget(ADJUDICATOR_NEEDS_MS)) {
1903
1903
  try {
1904
- const { onPrompt } = await import("./hook-hooks-IDTGXCFD.js");
1904
+ const { onPrompt } = await import("./hook-hooks-5U2AI6W7.js");
1905
1905
  const a = await onPrompt(ctx, db, input, sessionId);
1906
1906
  if (a?.context) parts.push(a.context);
1907
1907
  systemMessage = a?.systemMessage;
@@ -1959,7 +1959,7 @@ var postToolUse = async (input) => {
1959
1959
  const ctx = getContext(input.cwd);
1960
1960
  if (ctx.config.adjudicator.enabled) {
1961
1961
  try {
1962
- const { onPostAgent } = await import("./hook-hooks-IDTGXCFD.js");
1962
+ const { onPostAgent } = await import("./hook-hooks-5U2AI6W7.js");
1963
1963
  onPostAgent(ctx, db, input);
1964
1964
  } catch {
1965
1965
  }
@@ -2064,7 +2064,7 @@ var sessionEnd = async (input) => {
2064
2064
  ingestUsage(db, input, sessionId);
2065
2065
  if (ctx.config.adjudicator.enabled && input.client !== "cursor") {
2066
2066
  try {
2067
- const { onSessionEnd } = await import("./hook-hooks-IDTGXCFD.js");
2067
+ const { onSessionEnd } = await import("./hook-hooks-5U2AI6W7.js");
2068
2068
  onSessionEnd(ctx, db, input);
2069
2069
  } catch {
2070
2070
  }
@@ -2197,9 +2197,9 @@ var preCompact = (input) => {
2197
2197
 
2198
2198
  // src/hooks/routing.ts
2199
2199
  init_esm_shims();
2200
- import { realpathSync as realpathSync2, statSync as statSync3 } from "fs";
2200
+ import { existsSync as existsSync7, readFileSync as readFileSync5, realpathSync as realpathSync3, statSync as statSync3 } from "fs";
2201
2201
  import { homedir as homedir2 } from "os";
2202
- import { extname, join as join7, relative, resolve as resolve2 } from "path";
2202
+ import { dirname as dirname3, extname, join as join7, relative as relative2, resolve as resolve3 } from "path";
2203
2203
 
2204
2204
  // src/permissions/settings.ts
2205
2205
  init_esm_shims();
@@ -2208,6 +2208,7 @@ import { join as join3 } from "path";
2208
2208
 
2209
2209
  // src/permissions/rules.ts
2210
2210
  init_esm_shims();
2211
+ import { posix } from "path";
2211
2212
  function parseRule(raw) {
2212
2213
  const m = /^([A-Za-z][\w-]*)(?:\((.*)\))?$/s.exec(raw.trim());
2213
2214
  if (!m) return void 0;
@@ -2216,24 +2217,83 @@ function parseRule(raw) {
2216
2217
  function escapeRe(s) {
2217
2218
  return s.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
2218
2219
  }
2220
+ function globSource(glob) {
2221
+ return glob.split("/**/").map(
2222
+ (part) => part.split("**").map(
2223
+ (seg) => seg.split("*").map((s) => s.split("?").map(escapeRe).join("[^/]")).join("[^/]*")
2224
+ ).join(".*")
2225
+ ).join("/(?:.*/)?");
2226
+ }
2227
+ function anchored(prefix, rest) {
2228
+ const full = posix.normalize(`${prefix}${rest}`);
2229
+ if (prefix && (full === prefix || full.startsWith(`${prefix}/`))) {
2230
+ return literal(prefix) + globSource(full.slice(prefix.length));
2231
+ }
2232
+ return globSource(full);
2233
+ }
2234
+ function literal(s) {
2235
+ return escapeRe(s).replace(/\*/g, "\\*");
2236
+ }
2237
+ function readSource(pattern, root, home) {
2238
+ const dir = pattern.endsWith("/") && pattern.length > 1;
2239
+ const p = dir ? pattern.slice(0, -1) : pattern;
2240
+ let body;
2241
+ if (p.startsWith("**")) body = globSource(p);
2242
+ else if (p.startsWith("//")) body = anchored("", p.slice(1));
2243
+ else if (p === "~" || p.startsWith("~/")) body = anchored(home, p.slice(1));
2244
+ else if (p.startsWith("/")) body = `(?:${anchored(root, p)}|${globSource(posix.normalize(p))})`;
2245
+ else if (p.startsWith("./")) body = anchored(root, p.slice(1));
2246
+ else if (p.includes("/")) body = anchored(root, `/${p}`);
2247
+ else body = `${literal(root)}/(?:.*/)?${globSource(p)}`;
2248
+ return `^${body}(?:/.*)?$`;
2249
+ }
2219
2250
  var readRegexCache = /* @__PURE__ */ new Map();
2220
- function readRegex(pattern) {
2221
- let re = readRegexCache.get(pattern);
2251
+ function readRegex(pattern, root, home) {
2252
+ const key = `${root}\0${home}\0${pattern}`;
2253
+ let re = readRegexCache.get(key);
2222
2254
  if (!re) {
2223
- if (readRegexCache.size >= 1024) readRegexCache.clear();
2224
- re = new RegExp(
2225
- `^${pattern.split("**").map((seg) => seg.split("*").map(escapeRe).join("[^/]*")).join(".*")}$`
2226
- );
2227
- readRegexCache.set(pattern, re);
2255
+ if (readRegexCache.size >= 4096) readRegexCache.clear();
2256
+ re = new RegExp(readSource(pattern, root, home));
2257
+ readRegexCache.set(key, re);
2228
2258
  }
2229
2259
  return re;
2230
2260
  }
2231
- function matchRead(rule, absPath) {
2232
- if (rule.tool !== "Read") return false;
2233
- if (rule.pattern === void 0) return true;
2234
- let p = rule.pattern;
2235
- if (p.startsWith("~")) p = (process.env.HOME ?? "") + p.slice(1);
2236
- return readRegex(p).test(absPath);
2261
+ function defaultRoot() {
2262
+ return process.cwd();
2263
+ }
2264
+ function needleOf(pattern) {
2265
+ const p = pattern.endsWith("/") && pattern.length > 1 ? pattern.slice(0, -1) : pattern;
2266
+ if (p.includes("..")) return "";
2267
+ let tail;
2268
+ if (p.startsWith("**") || !p.includes("/")) tail = p;
2269
+ else if (p.startsWith("//")) tail = posix.normalize(p.slice(1));
2270
+ else if (p === "~" || p.startsWith("~/")) tail = posix.normalize(p.slice(1) || "/");
2271
+ else if (p.startsWith("./")) tail = posix.normalize(p.slice(1));
2272
+ else tail = posix.normalize(p.startsWith("/") ? p : `/${p}`);
2273
+ let best = "";
2274
+ for (const piece of tail.split(/[*?]/)) if (piece.length > best.length) best = piece;
2275
+ return best;
2276
+ }
2277
+ var listCache = /* @__PURE__ */ new WeakMap();
2278
+ function readMatcher(rules, root) {
2279
+ const r = root ?? defaultRoot();
2280
+ const home = process.env.HOME ?? "";
2281
+ const key = `${r}\0${home}\0${rules.length}`;
2282
+ const hit = listCache.get(rules);
2283
+ if (hit && hit.key === key) return hit.matcher;
2284
+ const compiled = rules.filter((x) => x.tool === "Read").map(
2285
+ (rule) => rule.pattern === void 0 ? { rule, needle: "", re: /^/ } : { rule, needle: needleOf(rule.pattern), re: readRegex(rule.pattern, r, home) }
2286
+ );
2287
+ const which = (p) => {
2288
+ for (const c of compiled) {
2289
+ if (c.needle && !p.includes(c.needle)) continue;
2290
+ if (c.re.test(p)) return c.rule;
2291
+ }
2292
+ return void 0;
2293
+ };
2294
+ const matcher = { test: (p) => which(p) !== void 0, which };
2295
+ listCache.set(rules, { key, matcher });
2296
+ return matcher;
2237
2297
  }
2238
2298
 
2239
2299
  // src/permissions/split.ts
@@ -2267,7 +2327,7 @@ function loadPermissionRules(root, extraDeny = []) {
2267
2327
  const stamps = files.map(stamp);
2268
2328
  const hit = cache.get(key);
2269
2329
  if (hit?.stamps.every((s, i) => s === stamps[i])) return hit.rules;
2270
- const out = { allow: [], deny: [], ask: [] };
2330
+ const out = { allow: [], deny: [], ask: [], root };
2271
2331
  const push = (list, raws) => {
2272
2332
  for (const raw of raws ?? []) {
2273
2333
  const r = parseRule(raw);
@@ -2287,14 +2347,51 @@ function loadPermissionRules(root, extraDeny = []) {
2287
2347
  }
2288
2348
  function checkRead(rules, absPaths) {
2289
2349
  for (const kind of ["deny", "ask"]) {
2290
- for (const r of rules[kind]) {
2291
- if (absPaths.some((p) => matchRead(r, p))) return { allowed: false, rule: r.raw, kind };
2350
+ const m = readMatcher(rules[kind], rules.root);
2351
+ for (const p of absPaths) {
2352
+ const r = m.which(p);
2353
+ if (r) return { allowed: false, rule: r.raw, kind };
2292
2354
  }
2293
2355
  }
2294
2356
  return { allowed: true };
2295
2357
  }
2296
2358
  function checkReadPath(rules, abs) {
2297
- return checkRead(rules, [.../* @__PURE__ */ new Set([abs, realpathSync(abs), realpathSync.native(abs)])]);
2359
+ const asWritten = checkRead(rules, [abs]);
2360
+ if (!asWritten.allowed) return asWritten;
2361
+ const real = realpathSync(abs);
2362
+ const native = realpathSync.native(abs);
2363
+ const more = [real, native].filter((p, i, a) => p !== abs && a.indexOf(p) === i);
2364
+ return more.length ? checkRead(rules, more) : asWritten;
2365
+ }
2366
+
2367
+ // src/sandbox/file.ts
2368
+ init_esm_shims();
2369
+ import { existsSync as existsSync4, realpathSync as realpathSync2 } from "fs";
2370
+ import { isAbsolute, relative, resolve as resolve2 } from "path";
2371
+ function confinePath(root, target, given) {
2372
+ const abs = resolve2(root, target);
2373
+ const rules = given.root ? given : { ...given, root };
2374
+ refuse(checkRead(rules, [abs]), target);
2375
+ let real;
2376
+ try {
2377
+ real = realpathSync2(abs);
2378
+ } catch {
2379
+ throw new Error(`file not found: ${target}`);
2380
+ }
2381
+ refuse(checkReadPath(rules, abs), target);
2382
+ const rel = relative(root, real);
2383
+ const inside = rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
2384
+ if (inside) return real;
2385
+ if (readMatcher(rules.allow, rules.root).test(real)) return real;
2386
+ throw new Error(
2387
+ `${target} is outside the project root; add a Read(/${real}) rule to permissions.allow to opt it in`
2388
+ );
2389
+ }
2390
+ function refusal(check, target) {
2391
+ return `refused by permission rule ${check.rule} (${check.kind}) on path: ${target}`;
2392
+ }
2393
+ function refuse(check, target) {
2394
+ if (!check.allowed) throw new Error(refusal(check, target));
2298
2395
  }
2299
2396
 
2300
2397
  // src/session/routing-stats.ts
@@ -2319,7 +2416,7 @@ function recordRouting(db, sessionId, e) {
2319
2416
 
2320
2417
  // src/session/server-alive.ts
2321
2418
  init_esm_shims();
2322
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync as readdirSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
2419
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync as readdirSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
2323
2420
  import { join as join4 } from "path";
2324
2421
  function markersDir(ctx) {
2325
2422
  return join4(projectDir(ctx.id), "servers");
@@ -2334,7 +2431,7 @@ function processAlive(pid) {
2334
2431
  }
2335
2432
  function liveServers(ctx) {
2336
2433
  const dir = markersDir(ctx);
2337
- if (!existsSync4(dir)) return [];
2434
+ if (!existsSync5(dir)) return [];
2338
2435
  const live = [];
2339
2436
  for (const name of readdirSync2(dir)) {
2340
2437
  const pid = Number(name);
@@ -2357,7 +2454,7 @@ import { join as join6 } from "path";
2357
2454
  init_esm_shims();
2358
2455
  import {
2359
2456
  copyFileSync,
2360
- existsSync as existsSync5,
2457
+ existsSync as existsSync6,
2361
2458
  mkdirSync as mkdirSync3,
2362
2459
  readdirSync as readdirSync3,
2363
2460
  readFileSync as readFileSync3,
@@ -2402,7 +2499,11 @@ function literalOf(pattern) {
2402
2499
  function symbolHints(db, pattern) {
2403
2500
  const lit = literalOf(pattern);
2404
2501
  if (!lit) return void 0;
2405
- const nodes = db.graphFindNodes({ query: lit, kinds: SYMBOL_KINDS, limit: 5 });
2502
+ const nodes = db.graphFindNodes({
2503
+ query: lit,
2504
+ kinds: SYMBOL_KINDS,
2505
+ limit: 5
2506
+ });
2406
2507
  if (!nodes.length) return void 0;
2407
2508
  const list = nodes.map((n) => `${n.kind} ${n.name} (${n.path}:${n.start_line ?? "?"})`).join("; ");
2408
2509
  return `eminiar graph: symbols matching "${lit}": ${list}. em_symbols {name_path, include_body: true} reads one; {mode:"references"} lists its callers.`;
@@ -2412,13 +2513,13 @@ function coverageHint(db, rel) {
2412
2513
  if (!row || row.status === "indexed") return void 0;
2413
2514
  return `eminiar graph: ${rel} is ${row.status}${row.reason ? ` (${row.reason})` : ""}; graph answers about it may be incomplete.`;
2414
2515
  }
2415
- var GIT_HISTORY = /(?:^|[;&|]\s*)git\s+(?:(?:-C|--git-dir|--work-tree)[= ]\S+\s+|--no-pager\s+)*(log|blame|shortlog|show|whatchanged|reflog)\b([^;&|]*)/;
2516
+ var GIT_HISTORY = /(?:^|[;&|]\s*)git\s+((?:(?:-C|--git-dir|--work-tree)[= ]\S+\s+|--no-pager\s+)*)(log|blame|shortlog|show|whatchanged|reflog)\b([^;&|]*)/;
2416
2517
  var ONE_COMMIT = /(^|\s)(-1|-n\s*1|-n1|--max-count[= ]1)(?=\s|$)/;
2417
2518
  function historyCall(cmd) {
2418
2519
  const m = GIT_HISTORY.exec(cmd);
2419
2520
  if (!m) return void 0;
2420
- const sub = m[1];
2421
- const rest = (m[2] ?? "").trim();
2521
+ const sub = m[2];
2522
+ const rest = (m[3] ?? "").trim();
2422
2523
  const args = rest.split(/\s+/).filter(Boolean);
2423
2524
  const q = (s) => JSON.stringify(s.replace(/^['"]|['"]$/g, ""));
2424
2525
  const positional = (a) => !a.startsWith("-");
@@ -2453,6 +2554,66 @@ function historyCall(cmd) {
2453
2554
  if (sub === "shortlog") return `{action:"timeline"}`;
2454
2555
  return `{action:"overview"}`;
2455
2556
  }
2557
+ function historyDir(segments, cwd) {
2558
+ let dir = cwd;
2559
+ let steps = 0;
2560
+ const step = (arg) => {
2561
+ dir = ++steps > MAX_STEPS ? void 0 : dir && changeDir(dir, arg);
2562
+ };
2563
+ for (const segment of segments) {
2564
+ const m = GIT_HISTORY.exec(segment);
2565
+ if (m) {
2566
+ const opts = m[1] ?? "";
2567
+ if (/--(git-dir|work-tree)\b/.test(opts)) return void 0;
2568
+ for (const c of opts.matchAll(/-C[= ](\S+)/g)) {
2569
+ step(c[1]);
2570
+ if (!dir) return void 0;
2571
+ }
2572
+ return dir;
2573
+ }
2574
+ const cd = CD.exec(segment);
2575
+ if (cd) step(cd[1]);
2576
+ if (!dir) return void 0;
2577
+ }
2578
+ return dir;
2579
+ }
2580
+ var CD = /^\(?\s*(?:cd|pushd)\b(.*)$/;
2581
+ var MAX_STEPS = 16;
2582
+ var MAX_RAW = 64 * 1024;
2583
+ var MAX_CLASSIFIED = 8 * 1024;
2584
+ var MAX_OPERANDS = 64;
2585
+ var MAX_SEGMENTS = 64;
2586
+ function changeDir(dir, arg) {
2587
+ const a = arg.trim().replace(/^(['"])(.*)\1$/, "$2") || "~";
2588
+ if (/^-|^~[^/]|[\s$`*?[\]{}()\\]/.test(a)) return void 0;
2589
+ return a === "~" || a.startsWith("~/") ? join7(homedir2(), a.slice(1)) : resolve3(dir, a);
2590
+ }
2591
+ function inRootRepo(root, dir) {
2592
+ try {
2593
+ const top = realpathSync3(root);
2594
+ let inside = false;
2595
+ for (let d = realpathSync3(dir); ; d = dirname3(d)) {
2596
+ if (d === top) inside = true;
2597
+ if (existsSync7(join7(d, ".git"))) return inside;
2598
+ if (dirname3(d) === d) return false;
2599
+ }
2600
+ } catch {
2601
+ return false;
2602
+ }
2603
+ }
2604
+ function repoOf(dir) {
2605
+ try {
2606
+ for (let d = realpathSync3(dir); ; d = dirname3(d)) {
2607
+ if (existsSync7(join7(d, ".git"))) return d;
2608
+ if (dirname3(d) === d) return void 0;
2609
+ }
2610
+ } catch {
2611
+ return void 0;
2612
+ }
2613
+ }
2614
+ function sameRepo(root, dir) {
2615
+ return repoOf(root) === repoOf(dir);
2616
+ }
2456
2617
  function docsSources(root) {
2457
2618
  try {
2458
2619
  return readManifestAt(join7(root, ".eminiar", "docs.json"), root).sources;
@@ -2495,7 +2656,34 @@ var BIG_TEXT_EXT = /* @__PURE__ */ new Set([
2495
2656
  ]);
2496
2657
  var VISUAL_EXT = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".pdf"]);
2497
2658
  function withoutHeredocs(cmd) {
2498
- return cmd.replace(/<<-?\s*(['"]?)(\w+)\1[^\n]*\n[\s\S]*?\n\s*\2\s*(?=\n|$)/g, "<<$2");
2659
+ if (!cmd.includes("<<")) return cmd;
2660
+ const head = /<<-?\s*(['"]?)(\w+)\1/y;
2661
+ const seen = /* @__PURE__ */ new Map();
2662
+ const terminator = (word, from) => {
2663
+ const c = seen.get(word);
2664
+ if (c && from >= c.from && (c.at === -1 || from <= c.at)) return c.end;
2665
+ const re = new RegExp(String.raw`\n[^\S\n]*${word}\s*(?=\n|$)`, "g");
2666
+ re.lastIndex = from;
2667
+ const m = re.exec(cmd);
2668
+ const rec = m ? { from, at: m.index, end: m.index + m[0].length } : { from, at: -1, end: -1 };
2669
+ seen.set(word, rec);
2670
+ return rec.end;
2671
+ };
2672
+ let out = "";
2673
+ let copied = 0;
2674
+ for (let k = cmd.indexOf("<<"); k !== -1; k = cmd.indexOf("<<", Math.max(k + 1, copied))) {
2675
+ head.lastIndex = k;
2676
+ const m = head.exec(cmd);
2677
+ if (!m) continue;
2678
+ const word = m[2];
2679
+ const eol = cmd.indexOf("\n", head.lastIndex);
2680
+ if (eol === -1) break;
2681
+ const end = terminator(word, eol + 1);
2682
+ if (end === -1) continue;
2683
+ out += `${cmd.slice(copied, k)}<<${word}${cmd.slice(head.lastIndex, eol)}`;
2684
+ copied = end;
2685
+ }
2686
+ return out + cmd.slice(copied);
2499
2687
  }
2500
2688
  function splitUnquoted(s, sep2) {
2501
2689
  const out = [];
@@ -2503,13 +2691,23 @@ function splitUnquoted(s, sep2) {
2503
2691
  let quote;
2504
2692
  for (let i = 0; i < s.length; i++) {
2505
2693
  const ch = s[i];
2694
+ if (quote === "'") {
2695
+ cur += ch;
2696
+ if (ch === "'") quote = void 0;
2697
+ continue;
2698
+ }
2699
+ if (ch === "\\") {
2700
+ cur += ch + (s[i + 1] ?? "");
2701
+ i++;
2702
+ continue;
2703
+ }
2506
2704
  if (quote) {
2507
2705
  cur += ch;
2508
- if (ch === quote && s[i - 1] !== "\\") quote = void 0;
2706
+ if (ch === quote[quote.length - 1]) quote = void 0;
2509
2707
  continue;
2510
2708
  }
2511
2709
  if (ch === "'" || ch === '"') {
2512
- quote = ch;
2710
+ quote = ch === "'" && s[i - 1] === "$" ? "$'" : ch;
2513
2711
  cur += ch;
2514
2712
  continue;
2515
2713
  }
@@ -2530,9 +2728,44 @@ function commandSegments(cmd) {
2530
2728
  const ch = s[i];
2531
2729
  if (ch === ";" || ch === "\n") return 1;
2532
2730
  if ((ch === "&" || ch === "|") && s[i + 1] === ch) return 2;
2731
+ if (ch === "&" && !/[&<>|]/.test(s[i - 1] ?? "") && !/[&>]/.test(s[i + 1] ?? "")) return 1;
2533
2732
  return 0;
2534
2733
  });
2535
2734
  }
2735
+ function shellWords(s) {
2736
+ const words = [];
2737
+ let cur = "";
2738
+ let inWord = false;
2739
+ for (let i = 0; i < s.length; i++) {
2740
+ const ch = s[i];
2741
+ if (ch === " " || ch === " " || ch === "\n") {
2742
+ if (inWord) words.push(cur);
2743
+ cur = "";
2744
+ inWord = false;
2745
+ continue;
2746
+ }
2747
+ inWord = true;
2748
+ if (ch === "\\") {
2749
+ cur += s[++i] ?? "";
2750
+ } else if (ch === "'" && s[i - 1] !== "$") {
2751
+ const end = s.indexOf("'", i + 1);
2752
+ const stop2 = end === -1 ? s.length : end;
2753
+ cur += s.slice(i + 1, stop2);
2754
+ i = stop2;
2755
+ } else if (ch === '"' || ch === "$" && s[i + 1] === "'") {
2756
+ const close = ch === '"' ? '"' : "'";
2757
+ if (ch === "$") i++;
2758
+ for (i++; i < s.length && s[i] !== close; i++) {
2759
+ if (s[i] === "\\" && i + 1 < s.length) i++;
2760
+ cur += s[i];
2761
+ }
2762
+ } else {
2763
+ cur += ch;
2764
+ }
2765
+ }
2766
+ if (inWord) words.push(cur);
2767
+ return words;
2768
+ }
2536
2769
  function pipeStages(segment) {
2537
2770
  return splitUnquoted(segment, (s, i) => s[i] === "|" && s[i + 1] !== "|" ? 1 : 0);
2538
2771
  }
@@ -2546,10 +2779,416 @@ function bounds(stage) {
2546
2779
  if (!/^\s*(head|tail)\b/.test(stage) || /(^|\s)-c\b|--bytes\b/.test(stage)) return false;
2547
2780
  return headLines(stage) <= BOUNDED_LINES;
2548
2781
  }
2549
- var MUTATING = /^\s*(?:(?:sudo|env|[A-Z_][A-Z0-9_]*=\S*)\s+)*(git\s+(add|commit|checkout|switch|push|pull|fetch|merge|rebase|stash|reset|restore|cherry-pick|revert|tag|init|clone|worktree|mv|rm|am|apply|submodule)|npm\s+(install|i|ci|uninstall|un|link|publish|version|update|up|dedupe)|pnpm\s+(install|i|add|remove|rm|update|up|link)|yarn\s+(add|remove|install|upgrade)|pip3?\s+(install|uninstall)|cargo\s+(add|install)|brew\s+(install|uninstall|upgrade)|gh\s+(pr|issue|release|repo)\s+(create|edit|merge|close|comment|delete|ready)|rm|mv|cp|mkdir|rmdir|touch|chmod|chown|ln|sed\s+(?:-\S+\s+)*-i|tee)\b/;
2550
- var WRITES_FILE = /(?:^|\s|\d)>{1,2}\s*(?!&)(?!\/dev\/null)\S/;
2551
- function mutates(segment) {
2552
- return MUTATING.test(segment) || WRITES_FILE.test(segment);
2782
+ var GIT_MUTATING = /^(git\s+(?:(?:-[Cc]\s*[^-\s]\S*|--(?:git-dir|work-tree)[= ]\S+|--no-pager)\s+)*(add|commit|checkout|switch|push|pull|fetch|merge|rebase|stash|reset|restore|cherry-pick|revert|tag|init|clone|worktree|mv|rm|am|apply|submodule|clean|branch\s(?:.*\s)?(-[a-zA-Z]*[dDmM][a-zA-Z]*|--delete|--move)|config(?:\s+--(?:global|local|system|worktree))*\s+(?:set|unset|--(?:unset|unset-all|add|replace-all|rename-section|remove-section|edit)|-e|\w[\w.-]*\s+\S+)))(?![\w-])/;
2783
+ var OTHER_MUTATING = /^(npm\s+(install|i|ci|uninstall|un|link|publish|version|update|up|dedupe|audit\s+fix)|pnpm\s+(install|i|add|remove|rm|update|up|link|audit\s.*--fix)|yarn\s+(add|remove|install|upgrade)|pip3?\s+(install|uninstall)|cargo\s+(add|install)|brew\s+(install|uninstall|upgrade)|gh\s+(pr|issue|release|repo)\s+(create|edit|merge|close|comment|delete|ready)|terraform\s+(?:-\S+\s+)*(apply|destroy|import|taint|untaint|state\s+(rm|mv|push))|helm\s+(?:-\S+\s+(?:[^-\s]\S*\s+)?)*(install|upgrade|uninstall|rollback|delete)|mvnw?(?:\s.*)?\s(deploy|install|release:\S*)|gradlew?(?:\s.*)?[\s:](publish|upload)\w*|rm|mv|cp|mkdir|rmdir|touch|chmod|chown|ln|sed\s+(?:-\S+\s+)*-i|tee|eval|sh|bash|zsh|find\s.*\s-(delete|exec|execdir|ok|okdir))(?![\w-])/;
2784
+ var MUTATING_WORDS = /* @__PURE__ */ new Set([
2785
+ "npm",
2786
+ "pnpm",
2787
+ "yarn",
2788
+ "pip",
2789
+ "pip3",
2790
+ "cargo",
2791
+ "brew",
2792
+ "gh",
2793
+ "terraform",
2794
+ "helm",
2795
+ "mvn",
2796
+ "mvnw",
2797
+ "gradle",
2798
+ "gradlew",
2799
+ "rm",
2800
+ "mv",
2801
+ "cp",
2802
+ "mkdir",
2803
+ "rmdir",
2804
+ "touch",
2805
+ "chmod",
2806
+ "chown",
2807
+ "ln",
2808
+ "sed",
2809
+ "tee",
2810
+ "eval",
2811
+ "sh",
2812
+ "bash",
2813
+ "zsh",
2814
+ "find"
2815
+ ]);
2816
+ var MUTATING = {
2817
+ test(cmd) {
2818
+ const word = /^[A-Za-z0-9]*/.exec(cmd)[0];
2819
+ if (word === "git") return GIT_MUTATING.test(cmd);
2820
+ return MUTATING_WORDS.has(word) && OTHER_MUTATING.test(cmd);
2821
+ }
2822
+ };
2823
+ var longOpts = (valued) => String.raw`--(?:${valued})\s+[^-\s]\S*|--(?!(?:${valued})(?:\s|$))\S*`;
2824
+ var RUNNERS = [
2825
+ String.raw`sudo(?:\s+(?:-[ugCDhpRrtTU]\s*[^-\s]\S*|-(?![ugCDhpRrtTU-])\S+|${longOpts("user|group|chdir|host|prompt|close-from|role|type|other-user|chroot|command-timeout")}))*`,
2826
+ String.raw`env(?:\s+(?:-[uC]\s*[^-\s]\S*|-(?![uC-])\S+|${longOpts("unset|chdir|split-string")}))*`,
2827
+ String.raw`(?:xargs|parallel)(?:\s+(?:-[nILPsdEaj]\s*[^-\s]\S*|-(?![nILPsdEaj-])\S+|${longOpts("max-args|max-procs|max-lines|max-chars|delimiter|arg-file|eof|jobs")}))*`,
2828
+ String.raw`timeout(?:\s+(?:-[sk]\s*[^-\s]\S*|-(?![sk-])\S+|${longOpts("signal|kill-after")}))*\s+\d\S*`,
2829
+ String.raw`nice(?:\s+-n\s*\S+|\s+-\d+|\s+--adjustment(?:=|\s+)\S+)?`,
2830
+ String.raw`stdbuf(?:\s+-[ioe]\s*\S+)*`,
2831
+ String.raw`(?:time|command)(?:\s+-p)?`,
2832
+ "nohup",
2833
+ String.raw`[A-Z_][A-Z0-9_]*=\S*`
2834
+ ];
2835
+ var SHELL_WORDS = /^(?:\s*(?:[({!]|(?:then|do|else|exec|if|elif|while|until)\s))*\s*/;
2836
+ var RUNNER = new RegExp(String.raw`^\s*(?:(?:${RUNNERS.join("|")})\s+)*`);
2837
+ var RUNNER_HEAD = /^\s*(?:sudo|env|xargs|parallel|timeout|nice|stdbuf|time|command|nohup|[A-Z_])/;
2838
+ var stripRunners = (s) => RUNNER_HEAD.test(s) ? s.replace(RUNNER, "") : s.replace(/^\s+/, "");
2839
+ var WRITES_FILE = /(?:^|[\s\d&])>{1,2}\s*(?!&)(?!\/dev\/null)\S/;
2840
+ var GLUED_WRITE = /(?<![-=>])>{1,2}\s*(?!&)(?!\/dev\/null)\S/;
2841
+ var RUNS_STDIN = /^(python3?|node|perl|ruby|deno|bun)(\s+-\S*)*$/;
2842
+ var FOLLOWS = /^((kubectl|docker)(\s.*)?\s(-f|-w|--follow)(?=[\s=]|$)|tail(\s.*)?\s(-[a-zA-Z]*[fF]|--follow)|.*\s--watch(All)?(?=[\s=]|$))/;
2843
+ var CURL_SENDS = /\s(?:-(?:(?!X)[a-zA-Z])*[dFTOoKDc]|(?:-[a-zA-Z]*X|--request)(?![\s=]*['"]?(?:GET|HEAD)\b)|--(?:data|json|form|upload-file|output|remote-name|config|dump-header|cookie-jar|trace|stderr|create-dirs|libcurl|etag-save|hsts|alt-svc))/;
2844
+ var WGET_PRINTS = /\s(?:-[a-zA-Z]*O\s*-|--output-document[= ]-|--spider)(?=\s|$)/;
2845
+ var WGET_SENDS = /\s(?:--(post-data|post-file|body-data|body-file|method|output-file|append-output|execute|config)\b|-[a-zA-Z]*[oae])/;
2846
+ var KUBECTL_READS = /^(get|describe|logs|top|explain|api-resources|api-versions|events|version|cluster-info|config\s+(view|get-contexts|current-context)|rollout\s+(status|history)|auth\s+can-i)\b/;
2847
+ var DOCKER_READS = /^(ps|logs|images|inspect|history|version|info|stats\s.*--no-stream|(container|image|network|volume)\s+(ls|inspect)|compose\s+(ps|logs|config|images|ls)|system\s+df)\b/;
2848
+ var CHECK_SCRIPT = /^(t|test|lint|type-?check|check)(?=$|[:.-])(?![\w:.-]*(fix|write|update|install|deploy|publish|push|release|clean))/;
2849
+ var FIX_ARGS = /(?:^|\s)(--fix|--write|--apply(-unsafe)?|-u|--update(-?[sS]napshots?)?)(?=[\s=]|$)/;
2850
+ var NPX_CHECKS = /^(vitest|jest|tsc|eslint|biome|prettier)(?:@[\w.^~-]*)?$/;
2851
+ var PM_READS = /^(ls|list|outdated|why|info|view|audit|licenses|bin|root|help)$/;
2852
+ var VALUE_OPTS = {
2853
+ kubectl: /* @__PURE__ */ new Set([
2854
+ "-n",
2855
+ "--namespace",
2856
+ "--context",
2857
+ "--cluster",
2858
+ "--kubeconfig",
2859
+ "--user",
2860
+ "-s",
2861
+ "--server",
2862
+ "--token",
2863
+ "--as",
2864
+ "--as-group",
2865
+ "--as-uid",
2866
+ "--cache-dir",
2867
+ "--certificate-authority",
2868
+ "--client-certificate",
2869
+ "--client-key",
2870
+ "--password",
2871
+ "--username",
2872
+ "--profile",
2873
+ "--profile-output",
2874
+ "--request-timeout",
2875
+ "--tls-server-name",
2876
+ "--log-dir",
2877
+ "--log-file",
2878
+ "--log-file-max-size",
2879
+ "--vmodule",
2880
+ "-v",
2881
+ "--v"
2882
+ ]),
2883
+ docker: /* @__PURE__ */ new Set([
2884
+ "-H",
2885
+ "--host",
2886
+ "-c",
2887
+ "--context",
2888
+ "--config",
2889
+ "-l",
2890
+ "--log-level",
2891
+ "--tlscacert",
2892
+ "--tlscert",
2893
+ "--tlskey"
2894
+ ]),
2895
+ npm: /* @__PURE__ */ new Set(["--prefix", "-w", "--workspace", "--userconfig", "--registry", "--cache"]),
2896
+ pnpm: /* @__PURE__ */ new Set(["-C", "--dir", "--filter", "-F", "--workspace-dir"]),
2897
+ yarn: /* @__PURE__ */ new Set(["--cwd", "--cache-folder", "--registry"])
2898
+ };
2899
+ var FLAG_OPTS = {
2900
+ kubectl: /* @__PURE__ */ new Set([
2901
+ "--insecure-skip-tls-verify",
2902
+ "--match-server-version",
2903
+ "--warnings-as-errors",
2904
+ "--disable-compression",
2905
+ "--alsologtostderr",
2906
+ "--logtostderr",
2907
+ "--add-dir-header",
2908
+ "--skip-headers",
2909
+ "--skip-log-headers",
2910
+ "--one-output",
2911
+ "-h",
2912
+ "--help"
2913
+ ]),
2914
+ docker: /* @__PURE__ */ new Set(["-D", "--debug", "--tls", "--tlsverify", "-v", "--version", "--help"])
2915
+ };
2916
+ var MAX_READINGS = 4;
2917
+ function afterOptions(tool, args) {
2918
+ const words = args.trim().split(/\s+/);
2919
+ const valued = VALUE_OPTS[tool];
2920
+ let i = 0;
2921
+ while (words[i]?.startsWith("-")) i += valued?.has(words[i]) ? 2 : 1;
2922
+ return words.slice(i);
2923
+ }
2924
+ function subcommands(tool, args) {
2925
+ const words = args.trim().split(/\s+/);
2926
+ const valued = VALUE_OPTS[tool];
2927
+ const flags = FLAG_OPTS[tool];
2928
+ const starts = /* @__PURE__ */ new Set();
2929
+ const seen = /* @__PURE__ */ new Set();
2930
+ const todo = [0];
2931
+ while (todo.length) {
2932
+ const i = todo.pop();
2933
+ if (seen.has(i)) continue;
2934
+ seen.add(i);
2935
+ const w = words[i];
2936
+ if (w === void 0 || !w.startsWith("-")) {
2937
+ starts.add(i);
2938
+ if (starts.size > MAX_READINGS) return void 0;
2939
+ } else if (valued?.has(w)) todo.push(i + 2);
2940
+ else if (flags?.has(w) || w.includes("=") || /^-[^-]./.test(w)) todo.push(i + 1);
2941
+ else todo.push(i + 1, i + 2);
2942
+ }
2943
+ const tail = /\s--no-stream\b/.test(args) ? " --no-stream" : "";
2944
+ return [...starts].map((i) => words.slice(i, i + 3).join(" ") + tail);
2945
+ }
2946
+ function verbChanges(tool, reads, args) {
2947
+ const subs = subcommands(tool, args);
2948
+ return !subs || subs.some((s) => !reads.test(s));
2949
+ }
2950
+ function runsScript(tool, args) {
2951
+ const words = afterOptions(tool, args);
2952
+ let script = words.shift();
2953
+ if (script === "run" || script === "run-script") script = words.shift();
2954
+ else if (script === void 0 || PM_READS.test(script) || tool === "npm" && script !== "test")
2955
+ return false;
2956
+ if (script === void 0) return false;
2957
+ return !CHECK_SCRIPT.test(script) || FIX_ARGS.test(words.join(" "));
2958
+ }
2959
+ function makeChanges(args) {
2960
+ const words = shellWords(args);
2961
+ const targets = [];
2962
+ for (let i = 0; i < words.length; i++) {
2963
+ const w = words[i];
2964
+ if (/^(-E|--eval)/.test(w)) return true;
2965
+ if (w.startsWith("-")) {
2966
+ if (/^(-[CfIoW]|--(directory|file|makefile|include-dir|old-file|assume-old|what-if|new-file|assume-new))$/.test(
2967
+ w
2968
+ ) || /^(-[jl]|--jobs|--load-average|--max-load)$/.test(w) && /^\d+(\.\d+)?$/.test(words[i + 1] ?? ""))
2969
+ i++;
2970
+ continue;
2971
+ }
2972
+ if (!w.includes("=")) targets.push(w);
2973
+ }
2974
+ return !targets.length || !targets.every((t) => CHECK_SCRIPT.test(t)) || FIX_ARGS.test(args);
2975
+ }
2976
+ var GH_API_VALUED = /^(-[FfHXpqt]|--(method|field|raw-field|input|header|jq|template|preview|hostname|cache))$/;
2977
+ function ghApiSends(args) {
2978
+ const words = shellWords(args);
2979
+ if (words[0] !== "api") return false;
2980
+ let method;
2981
+ let fields = false;
2982
+ for (let i = 1; i < words.length; i++) {
2983
+ const w = words[i];
2984
+ const long = /^--(method|field|raw-field|input)(?:=|$)/.exec(w)?.[1];
2985
+ if (long) {
2986
+ if (long === "method")
2987
+ method = w.includes("=") ? w.slice(w.indexOf("=") + 1) : words[i + 1] ?? "";
2988
+ else fields = true;
2989
+ } else if (/^-[a-zA-Z]/.test(w)) {
2990
+ const c = w.slice(1).replace(/^i+/, "")[0];
2991
+ const value = w.slice(w.indexOf(c ?? "") + 1) || (words[i + 1] ?? "");
2992
+ if (c === "X") method = value;
2993
+ else if (c === "f" || c === "F") fields = true;
2994
+ }
2995
+ if (GH_API_VALUED.test(w)) i++;
2996
+ }
2997
+ if (method !== void 0) return method.toUpperCase() !== "GET";
2998
+ if (words.includes("graphql")) return /\bmutation\b/.test(words.join(" "));
2999
+ return fields;
3000
+ }
3001
+ function npxChanges(args, cwd) {
3002
+ const words = shellWords(args);
3003
+ let i = 0;
3004
+ for (; words[i]?.startsWith("-"); i++)
3005
+ if (/^(-p|--package|-c|--call)(=|$)/.test(words[i])) return true;
3006
+ const [name = "", ...rest] = words.slice(i);
3007
+ const tool = NPX_CHECKS.exec(name)?.[1];
3008
+ if (!tool || FIX_ARGS.test(args)) return true;
3009
+ const has = (re) => rest.some((w) => re.test(w));
3010
+ if (has(/^--(output-?file|outputFile|out-?dir|outFile|generateTrace)(=|$)/i)) return true;
3011
+ if (tool === "eslint" && has(/^-o/)) return true;
3012
+ if (tool === "prettier" && has(/^-[a-zA-Z]*w$/)) return true;
3013
+ return tool === "tsc" && !tscEmitsNothing(rest, cwd);
3014
+ }
3015
+ function tscEmitsNothing(words, cwd) {
3016
+ if (words.some((w) => /^(-b|--build|--generateTrace)(=|$)/i.test(w))) return false;
3017
+ const noEmit = words.findIndex((w) => /^--noEmit(=true)?$/i.test(w));
3018
+ if (noEmit !== -1) return words[noEmit + 1] !== "false";
3019
+ let project = "tsconfig.json";
3020
+ for (let i = 0; i < words.length; i++) {
3021
+ const w = words[i];
3022
+ if (/^(-p|--project)$/i.test(w)) project = words[++i] ?? "";
3023
+ else if (!w.startsWith("-")) return false;
3024
+ }
3025
+ try {
3026
+ let file2 = resolve3(cwd, project);
3027
+ if (statSync3(file2).isDirectory()) file2 = join7(file2, "tsconfig.json");
3028
+ const st = statSync3(file2);
3029
+ if (st.size > 256 * 1024) return false;
3030
+ return /"noEmit"\s*:\s*true\b/.test(readFileSync5(file2, "utf8"));
3031
+ } catch {
3032
+ return false;
3033
+ }
3034
+ }
3035
+ var CHANGES = /* @__PURE__ */ new Map([
3036
+ ["curl", (a) => CURL_SENDS.test(a)],
3037
+ ["wget", (a) => !WGET_PRINTS.test(a) || WGET_SENDS.test(a)],
3038
+ ["kubectl", (a) => verbChanges("kubectl", KUBECTL_READS, a)],
3039
+ ["docker", (a) => verbChanges("docker", DOCKER_READS, a)],
3040
+ ["gh", ghApiSends],
3041
+ ["make", makeChanges],
3042
+ ["npx", npxChanges],
3043
+ ["npm", (a) => runsScript("npm", a)],
3044
+ ["pnpm", (a) => runsScript("pnpm", a)],
3045
+ ["yarn", (a) => runsScript("yarn", a)]
3046
+ ]);
3047
+ function operands(words, valued) {
3048
+ let n = 0;
3049
+ for (let i = 0; i < words.length; i++) {
3050
+ const w = words[i];
3051
+ if (w === "--") return n + words.length - i - 1;
3052
+ if (w.startsWith("-") && w.length > 1) {
3053
+ if (valued.test(w)) i++;
3054
+ } else n++;
3055
+ }
3056
+ return n;
3057
+ }
3058
+ function awkRuns(words) {
3059
+ let program;
3060
+ for (let i = 0; i < words.length && program === void 0; i++) {
3061
+ const w = words[i];
3062
+ if (w === "--") program = words[i + 1];
3063
+ else if (/^(-[fiEl]|--(file|include|load|exec))/.test(w)) return true;
3064
+ else if (/^(-e|--source)$/.test(w)) program = words[i + 1];
3065
+ else if (/^(-[FvW]|--(field-separator|assign))$/.test(w)) i++;
3066
+ else if (!w.startsWith("-")) program = w;
3067
+ }
3068
+ return program !== void 0 && /\bsystem\s*\(|\|\s*&?\s*getline|\bprintf?\b[^;{}\n]*[>|]|@(load|include)\b/.test(program);
3069
+ }
3070
+ function sedWrites(words) {
3071
+ const scripts = [];
3072
+ let first;
3073
+ for (let i = 0; i < words.length; i++) {
3074
+ const w = words[i];
3075
+ if (w === "--") {
3076
+ first ??= words[i + 1];
3077
+ break;
3078
+ }
3079
+ if (w.startsWith("--")) {
3080
+ const [name, value] = w.split(/=(.*)/s);
3081
+ if (name === "--in-place" || name === "--file") return true;
3082
+ if (name === "--expression") scripts.push(value ?? words[++i] ?? "");
3083
+ else if (name === "--line-length" && value === void 0) i++;
3084
+ } else if (/^-./.test(w)) {
3085
+ for (let k = 1; k < w.length; k++) {
3086
+ const c = w[k];
3087
+ if (c === "i" || c === "f") return true;
3088
+ if (c === "e" || c === "l") {
3089
+ const value = w.slice(k + 1) || words[++i] || "";
3090
+ if (c === "e") scripts.push(value);
3091
+ break;
3092
+ }
3093
+ }
3094
+ } else if (first === void 0) first = w;
3095
+ }
3096
+ if (!scripts.length && first !== void 0) scripts.push(first);
3097
+ return scripts.some(sedScriptWrites);
3098
+ }
3099
+ function sedScriptWrites(script) {
3100
+ if (script.length > 2048) return true;
3101
+ let flagged = false;
3102
+ const t = script.replace(
3103
+ /(^|[^a-zA-Z\\])([sy])([^\\\n\sa-zA-Z0-9])((?:\\[\s\S]|(?!\3)[^\\])*)\3((?:\\[\s\S]|(?!\3)[^\\])*)\3([a-zA-Z0-9]*)/g,
3104
+ (_m, pre, cmd, _d, _a, _b, flags) => {
3105
+ if (cmd === "s" && /[we]/.test(flags)) flagged = true;
3106
+ return `${pre};`;
3107
+ }
3108
+ ).replace(/\/(?:\\[\s\S]|[^/\\\n])*\//g, ";");
3109
+ return flagged || /(?:^|[^a-zA-Z_.-])(?:[wW]|e(?![a-zA-Z]))/.test(t);
3110
+ }
3111
+ var FLAG_WRITES = /* @__PURE__ */ new Map([
3112
+ // `--pre cmd` runs cmd on every file searched
3113
+ ["rg", (w) => w.some((x) => /^--pre(-glob)?(=|$)/.test(x))],
3114
+ ["git", gitRunsOrWrites],
3115
+ ["find", (w) => w.some((x) => /^-(delete|exec|execdir|ok|okdir|fprint0?|fprintf|fls)$/.test(x))],
3116
+ ["tree", (w) => w.some((x) => /^(-[a-zA-Z]*o|--output)/.test(x))],
3117
+ ["sed", sedWrites],
3118
+ ["gsed", sedWrites],
3119
+ ["awk", awkRuns],
3120
+ ["gawk", awkRuns],
3121
+ ["mawk", awkRuns],
3122
+ ["nawk", awkRuns],
3123
+ ["sort", (w) => w.some((x) => /^(-[a-zA-Z]*o|--output(=|$))/.test(x))],
3124
+ // `uniq IN OUT` and `xxd IN OUT` write OUT
3125
+ ["uniq", (w) => operands(w, /^(-[fsw]|--(skip-fields|skip-chars|check-chars))$/) > 1],
3126
+ ["xxd", (w) => operands(w, /^-(c|g|l|o|s|n|cols|groupsize|len|offset|seek|name)$/) > 1],
3127
+ ["yq", (w) => w.some((x) => /^(-[a-zA-Z]*i[a-zA-Z]*|--inplace)(=|$)/.test(x))]
3128
+ ]);
3129
+ function gitRunsOrWrites(words) {
3130
+ let i = 0;
3131
+ for (; i < words.length && words[i].startsWith("-"); i++) {
3132
+ const w = words[i];
3133
+ if (/^(-c|--config-env|--exec-path=)/.test(w)) return true;
3134
+ if (/^(-C|--git-dir|--work-tree|--namespace|--super-prefix)$/.test(w)) i++;
3135
+ }
3136
+ const sub = words[i];
3137
+ const rest = words.slice(i + 1);
3138
+ if (sub === "difftool" || sub === "mergetool") return true;
3139
+ if (rest.some((w) => /^--(output|ext-diff)(=|$)/.test(w))) return true;
3140
+ return sub === "grep" && rest.some((w) => /^(-[a-zA-Z]*O|--open-files-in-pager)/.test(w));
3141
+ }
3142
+ var GIT_ENV = /(?:^|\s)GIT_(?:EXTERNAL_DIFF|PAGER|EDITOR|SEQUENCE_EDITOR|SSH|SSH_COMMAND|ASKPASS|PROXY_COMMAND|EXEC_PATH|CONFIG\w*|TRACE\w*|DIFF_OPTS|TEMPLATE_DIR)=/;
3143
+ var ARITHMETIC = /\$\(\(([^()`]*)\)\)/g;
3144
+ function substitutions(segment) {
3145
+ const found = [];
3146
+ const inner = /[$<>]\(([^()]*)\)|`([^`]*)`/g;
3147
+ let s = segment.replace(ARITHMETIC, "_");
3148
+ for (let level = 0; ; level++) {
3149
+ const next = s.replace(inner, (_, a, b) => {
3150
+ found.push(a ?? b ?? "");
3151
+ return "_";
3152
+ });
3153
+ if (next === s) break;
3154
+ if (level >= MAX_STEPS) return void 0;
3155
+ s = next;
3156
+ }
3157
+ return /[$<>]\(/.test(s) ? void 0 : found;
3158
+ }
3159
+ function staysNative(segment, cwd) {
3160
+ if (WRITES_FILE.test(segment)) return true;
3161
+ if (GLUED_WRITE.test(segment.replace(/'[^']*'|"(?:[^"\\]|\\.)*"/g, "''"))) return true;
3162
+ return pipeStages(segment).some((stage, i) => {
3163
+ const plain = stage.replace(SHELL_WORDS, "");
3164
+ if (plain.includes("GIT_") && GIT_ENV.test(plain)) return true;
3165
+ const cmd = stripRunners(plain).replace(/^\S*\//, "");
3166
+ const [, quoted = "", args = ""] = /^(\S+)([\s\S]*)$/.exec(cmd) ?? [];
3167
+ const tool = quoted.replace(/['"\\]/g, "");
3168
+ const named = tool + args;
3169
+ const bare = /^(npm|pnpm|yarn)$/.test(tool) ? `${tool} ${afterOptions(tool, args).join(" ")}` : named;
3170
+ return MUTATING.test(named) || MUTATING.test(bare) || (/^(?:kubectl|docker|tail)(?![^\s])/.test(named) || named.includes("--watch")) && FOLLOWS.test(named) || i > 0 && RUNS_STDIN.test(named) || CHANGES.get(tool)?.(args, cwd) === true || FLAG_WRITES.get(tool)?.(shellWords(args)) === true;
3171
+ });
3172
+ }
3173
+ var READ_ONLY = /^(?:(?:cd|pushd|popd|pwd|true|false|:|test|\[|sleep|echo|printf|ls|tree|cat|head|tail|wc|grep|egrep|fgrep|rg|sort|uniq|cut|tr|nl|column|awk|sed|jq|yq|diff|cmp|du|df|stat|file|which|type|date|basename|dirname|realpath|readlink|ps|lsof|strings|xxd|od|find)|git\s+(?:(?:-[Cc]\s*[^-\s]\S*|--no-pager)\s+)*(?:diff|log|show|status|ls-files|ls-tree|grep|blame|branch(?=\s*$|\s+(?:-[avr]+|--(?:list|show-current|merged|no-merged|contains))(?:\s|$))|rev-list|rev-parse|for-each-ref|cat-file|describe|shortlog|reflog|merge-base|name-rev|config\s+(?:--(?:global|local|system|worktree)\s+)*(?:--get\S*|--list|-l)))(?![\w-])/;
3174
+ var TOOL_READS = {
3175
+ gh: (a) => /^\s+(?:api|(?:pr|issue|run|search|repo|release)\s+(?:list|view|diff|checks|status))\b/.test(a),
3176
+ npm: (a) => /^(?:test|t|run|run-script|ls|ll|list|outdated|view|show|info|why|explain|audit|root|bin|prefix|help)$/.test(
3177
+ afterOptions("npm", a)[0] ?? ""
3178
+ ),
3179
+ pnpm: (a) => afterOptions("pnpm", a).length > 0,
3180
+ yarn: (a) => afterOptions("yarn", a).length > 0
3181
+ // bare `yarn` installs
3182
+ };
3183
+ function readsOnly(segment, cwd) {
3184
+ return pipeStages(segment).every((stage) => {
3185
+ const cmd = stripRunners(stage.replace(SHELL_WORDS, "")).replace(/^\S*\//, "");
3186
+ const [, tool = "", args = ""] = /^(\S+)([\s\S]*)$/.exec(cmd) ?? [];
3187
+ if (/(?:^|\s)--output(?=[\s=]|$)/.test(args) || tool === "sort" && /\s-\w*o\b/.test(args))
3188
+ return false;
3189
+ if (CHANGES.has(tool)) return TOOL_READS[tool]?.(args) ?? !CHANGES.get(tool)(args, cwd);
3190
+ return READ_ONLY.test(cmd);
3191
+ });
2553
3192
  }
2554
3193
  var READ_LIMIT = 200 * 1024;
2555
3194
  var BIG_TEXT_LIMIT = 20 * 1024;
@@ -2598,13 +3237,13 @@ function fileOperands(stage, cwd) {
2598
3237
  }
2599
3238
  files.push(t);
2600
3239
  }
2601
- if (!files.length) return void 0;
3240
+ if (!files.length || files.length > MAX_OPERANDS) return void 0;
2602
3241
  const out = [];
2603
3242
  for (let f of files) {
2604
3243
  if (/[*?[\]{}$`\\]/.test(f)) return void 0;
2605
3244
  f = f.replace(/^['"]|['"]$/g, "");
2606
3245
  if (f.startsWith("~/")) f = join7(homedir2(), f.slice(2));
2607
- out.push(resolve2(cwd, f));
3246
+ out.push(resolve3(cwd, f));
2608
3247
  }
2609
3248
  return out;
2610
3249
  }
@@ -2642,66 +3281,99 @@ function firstStageBound(first, cwd) {
2642
3281
  const bytes = stageBytes(first, cwd);
2643
3282
  if (bytes === void 0) return void 0;
2644
3283
  if (bytes <= READ_LIMIT) return true;
2645
- return { rule: "big-read", text: `reads about ${Math.round(bytes / 1024)} KB` };
3284
+ return {
3285
+ rule: "big-read",
3286
+ text: `reads about ${Math.round(bytes / 1024)} KB`
3287
+ };
2646
3288
  }
2647
3289
  return void 0;
2648
3290
  }
3291
+ var READ_SOURCE = /^\s*(ls|cat|sed|awk|cut|sort|uniq|tr|nl|echo|printf|ps|lsof|jq|yq|diff|strings|xxd|od|git\s+(diff|ls-files|ls-tree|grep|status|show|log|blame|branch|rev-list|for-each-ref|cat-file)|gh\s+(api|pr\s+(list|view|diff|checks)|issue\s+(list|view)|run\s+(list|view)|search))\b/;
3292
+ var FILTERS = /^\s*(grep|egrep|fgrep|rg|sort|uniq|jq|head|tail|wc|cut|tr|column|less|awk|sed|python3?\s+-m\s+json\.tool)\b/;
3293
+ var UNBOUNDED_FILTERS = /^\s*(grep|rg|sort|uniq|jq|less)\b/;
2649
3294
  function heavySegment(segment, cwd) {
2650
3295
  if (TINY_BASH.test(segment)) return void 0;
2651
3296
  const stages = pipeStages(segment);
2652
3297
  const first = stages[0];
2653
3298
  if (!first) return void 0;
2654
3299
  if (stages.length > 1 && bounds(stages[stages.length - 1])) return void 0;
3300
+ const rest = stages.slice(1);
3301
+ if (!rest.every((s) => FILTERS.test(s))) return void 0;
2655
3302
  const bound = firstStageBound(first, cwd);
2656
3303
  if (bound === true) return void 0;
2657
3304
  if (bound) return bound;
2658
3305
  if (HEAVY_BASH.test(first)) return { rule: "heavy", text: "usually prints a lot" };
2659
- if (stages.slice(1).some((s) => /^\s*(grep|rg|sort|uniq|jq|less)\b/.test(s)))
2660
- return { rule: "pipe", text: "streams an unbounded output through a filter" };
3306
+ if (READ_SOURCE.test(first) && rest.some((s) => UNBOUNDED_FILTERS.test(s)))
3307
+ return {
3308
+ rule: "pipe",
3309
+ text: "streams an unbounded output through a filter"
3310
+ };
2661
3311
  return void 0;
2662
3312
  }
2663
3313
  function headOf(segment, n = 60) {
2664
3314
  const one = segment.replace(/\s+/g, " ").trim();
2665
3315
  return one.length > n ? `${one.slice(0, n - 1)}\u2026` : one;
2666
3316
  }
2667
- function bashSuggestion(raw, cwd) {
3317
+ function bashSuggestion(raw, root, cwd) {
3318
+ if (raw.length > MAX_RAW) return void 0;
2668
3319
  const cmd = withoutHeredocs(raw);
2669
3320
  if (!cmd.trim()) return void 0;
3321
+ if (cmd.length > MAX_CLASSIFIED) return void 0;
2670
3322
  const segments = commandSegments(cmd);
2671
- if (segments.some(mutates)) return void 0;
3323
+ if (segments.length > MAX_SEGMENTS) return void 0;
3324
+ const subs = substitutions(cmd);
3325
+ if (!subs) return void 0;
3326
+ if ([...segments, ...subs.flatMap(commandSegments)].some((s) => staysNative(s, cwd)))
3327
+ return void 0;
3328
+ if (segments.length > 1 && !segments.every((s) => readsOnly(s, cwd))) return void 0;
2672
3329
  const history = historyCall(cmd);
2673
- if (history) {
3330
+ const dir = history && historyDir(segments, cwd);
3331
+ if (history && dir && !sameRepo(root, dir)) return void 0;
3332
+ if (history && dir && inRootRepo(root, dir)) {
2674
3333
  return {
2675
3334
  tool: "em_history",
2676
3335
  rule: "history",
2677
3336
  reason: `the git history is already classified (commit kinds, fixes traced to the change that caused them, risk per file); em_history ${history} answers this without the raw log`
2678
3337
  };
2679
3338
  }
3339
+ let at = cwd;
3340
+ let steps = 0;
2680
3341
  for (const segment of segments) {
2681
- const heavy = heavySegment(segment, cwd);
2682
- if (!heavy) continue;
2683
- return {
2684
- tool: "em_execute",
2685
- rule: heavy.rule,
2686
- reason: `\`${headOf(segment)}\` ${heavy.text}; em_execute {language:"shell", code, intent} keeps the output out of context and returns only matching sections`
2687
- };
3342
+ const heavy = heavySegment(segment, at ?? cwd);
3343
+ if (heavy)
3344
+ return {
3345
+ tool: "em_execute",
3346
+ rule: heavy.rule,
3347
+ reason: `\`${headOf(segment)}\` ${heavy.text}; em_execute {language:"shell", code, intent} keeps the output out of context and returns only matching sections`
3348
+ };
3349
+ const cd = CD.exec(segment);
3350
+ if (cd) at = ++steps > MAX_STEPS ? void 0 : at && changeDir(at, cd[1]);
2688
3351
  }
2689
3352
  return void 0;
2690
3353
  }
3354
+ function executeFileTakes(root, fp) {
3355
+ try {
3356
+ const real = realpathSync3(root);
3357
+ confinePath(real, resolve3(root, fp), loadPermissionRules(real));
3358
+ return true;
3359
+ } catch {
3360
+ return false;
3361
+ }
3362
+ }
2691
3363
  function suggest(toolName, input, root, extra = {}) {
2692
3364
  switch (toolName) {
2693
3365
  case "Bash":
2694
- return bashSuggestion(String(input.command ?? ""), extra.cwd ?? root);
3366
+ return bashSuggestion(String(input.command ?? ""), root, extra.cwd ?? root);
2695
3367
  case "Read": {
2696
3368
  const fp = String(input.file_path ?? "");
2697
3369
  if (!fp) return void 0;
3370
+ if (input.offset != null || input.limit != null) return void 0;
2698
3371
  const ext = extname(fp).toLowerCase();
2699
3372
  if (VISUAL_EXT.has(ext)) return void 0;
2700
3373
  try {
2701
- const abs = resolve2(root, fp);
2702
- const st = statSync3(abs);
3374
+ const st = statSync3(resolve3(root, fp));
2703
3375
  if (st.size > 200 * 1024 || BIG_TEXT_EXT.has(ext) && st.size > 20 * 1024) {
2704
- if (!checkReadPath(loadPermissionRules(root), abs).allowed) return void 0;
3376
+ if (!executeFileTakes(root, fp)) return void 0;
2705
3377
  return {
2706
3378
  tool: "em_execute_file",
2707
3379
  rule: "big-read",
@@ -2768,12 +3440,12 @@ function subagentPrefix(shell) {
2768
3440
  }
2769
3441
  function projectRel(ctx, fp) {
2770
3442
  if (!fp) return void 0;
2771
- let abs = resolve2(ctx.root, fp);
3443
+ let abs = resolve3(ctx.root, fp);
2772
3444
  try {
2773
- abs = realpathSync2(abs);
3445
+ abs = realpathSync3(abs);
2774
3446
  } catch {
2775
3447
  }
2776
- const rel = relative(ctx.root, abs).split("\\").join("/");
3448
+ const rel = relative2(ctx.root, abs).split("\\").join("/");
2777
3449
  return rel.startsWith("..") ? void 0 : rel;
2778
3450
  }
2779
3451
  function graphContext(ctx, input) {
@@ -2804,7 +3476,11 @@ function fileKnowledge(ctx, input) {
2804
3476
  const repeatAfter = ctx.config.recall.repeatAfter;
2805
3477
  const edit = EDIT_TOOLS.has(input.tool_name ?? "");
2806
3478
  const results = [
2807
- memoriesForFile(db, rel, { sessionId, symbols: fileSymbols(db, rel), repeatAfter }),
3479
+ memoriesForFile(db, rel, {
3480
+ sessionId,
3481
+ symbols: fileSymbols(db, rel),
3482
+ repeatAfter
3483
+ }),
2808
3484
  fileContext(db, rel, { sessionId, edit, repeatAfter })
2809
3485
  ];
2810
3486
  const shown = results.filter((r) => r.text);
@@ -2854,7 +3530,11 @@ async function webKnowledge(ctx, input) {
2854
3530
  if (!text.trim()) return void 0;
2855
3531
  try {
2856
3532
  const db = getStorage(ctx, { vectors: false });
2857
- const r = await recallForText(db, text, { maxMemories: 0, maxDocs: 3, path: "web" });
3533
+ const r = await recallForText(db, text, {
3534
+ maxMemories: 0,
3535
+ maxDocs: 3,
3536
+ path: "web"
3537
+ });
2858
3538
  if (!r.text) return void 0;
2859
3539
  const pages = r.text.split("\n").filter((l) => l.startsWith("- docs:"));
2860
3540
  return [
@@ -2872,7 +3552,12 @@ function joinHints(...hints) {
2872
3552
  function withContext(out, extra) {
2873
3553
  if (!extra) return out;
2874
3554
  if (!out)
2875
- return { hookSpecificOutput: { hookEventName: "PreToolUse", additionalContext: extra } };
3555
+ return {
3556
+ hookSpecificOutput: {
3557
+ hookEventName: "PreToolUse",
3558
+ additionalContext: extra
3559
+ }
3560
+ };
2876
3561
  const cur = out.hookSpecificOutput?.additionalContext;
2877
3562
  return {
2878
3563
  ...out,
@@ -2894,7 +3579,7 @@ var preToolUse = async (input) => {
2894
3579
  const out = await route(ctx, input, spawn?.kind === "worker");
2895
3580
  if (!ctx.config.adjudicator.enabled || input.client === "cursor") return out;
2896
3581
  try {
2897
- const { afterRouting } = await import("./hook-hooks-IDTGXCFD.js");
3582
+ const { afterRouting } = await import("./hook-hooks-5U2AI6W7.js");
2898
3583
  return await afterRouting(ctx, input, spawn, out);
2899
3584
  } catch {
2900
3585
  return out;
@@ -2914,7 +3599,10 @@ async function route(ctx, input, swarmWorker) {
2914
3599
  const graph = graphContext(ctx, input);
2915
3600
  const hint = joinHints(graph, memo);
2916
3601
  const symbol = input.tool_name === "Grep" && graph ? literalOf(String(input.tool_input.pattern ?? "")) : void 0;
2917
- const s = suggest(input.tool_name, input.tool_input, ctx.root, { symbol, cwd: input.cwd });
3602
+ const s = suggest(input.tool_name, input.tool_input, ctx.root, {
3603
+ symbol,
3604
+ cwd: input.cwd
3605
+ });
2918
3606
  const record = routingRecorder(ctx, input, s);
2919
3607
  if (!s) {
2920
3608
  record("allow");
@@ -3034,12 +3722,12 @@ function annotate(s, note = "") {
3034
3722
 
3035
3723
  // src/hooks/session-start.ts
3036
3724
  init_esm_shims();
3037
- import { appendFileSync, existsSync as existsSync6, readdirSync as readdirSync4 } from "fs";
3725
+ import { appendFileSync, existsSync as existsSync8, readdirSync as readdirSync4 } from "fs";
3038
3726
  import { join as join9 } from "path";
3039
3727
 
3040
3728
  // src/session/current.ts
3041
3729
  init_esm_shims();
3042
- import { readFileSync as readFileSync5, statSync as statSync4, writeFileSync as writeFileSync4 } from "fs";
3730
+ import { readFileSync as readFileSync6, statSync as statSync4, writeFileSync as writeFileSync4 } from "fs";
3043
3731
  import { join as join8 } from "path";
3044
3732
  function file(ctx) {
3045
3733
  return join8(projectDir(ctx.id), "current-session");
@@ -3131,7 +3819,7 @@ ${guide}`);
3131
3819
  if (swarm) parts.push(swarm);
3132
3820
  if (ctx.config.adjudicator.enabled && input.client !== "cursor") {
3133
3821
  try {
3134
- const { onSessionStart } = await import("./hook-hooks-IDTGXCFD.js");
3822
+ const { onSessionStart } = await import("./hook-hooks-5U2AI6W7.js");
3135
3823
  const line2 = onSessionStart(ctx, db, input);
3136
3824
  if (line2) parts.push(line2);
3137
3825
  } catch {
@@ -3153,7 +3841,7 @@ ${guide}`);
3153
3841
  }
3154
3842
  try {
3155
3843
  const onboarding = join9(memoriesDir(ctx), "onboarding");
3156
- const onboarded = existsSync6(onboarding) && readdirSync4(onboarding).some((f) => f.endsWith(".md"));
3844
+ const onboarded = existsSync8(onboarding) && readdirSync4(onboarding).some((f) => f.endsWith(".md"));
3157
3845
  if (!onboarded)
3158
3846
  parts.push(
3159
3847
  "No onboarding memories yet: run /eminiar:em-onboard once so future sessions start with the project's structure, build and test commands."
@@ -3167,7 +3855,7 @@ ${guide}`);
3167
3855
  } catch {
3168
3856
  }
3169
3857
  try {
3170
- if (existsSync6(join9(ctx.root, ".git")) && !db.metaGet("history_files"))
3858
+ if (existsSync8(join9(ctx.root, ".git")) && !db.metaGet("history_files"))
3171
3859
  parts.push(
3172
3860
  'Git history is not classified yet: em_history {action:"overview"} builds it (the server also does this in the background shortly after start); bug-prone files are flagged before you read or edit them once it exists.'
3173
3861
  );
@@ -3212,8 +3900,8 @@ function ranLate() {
3212
3900
  }
3213
3901
  async function runWithin(handler, input, ms) {
3214
3902
  let timer;
3215
- const deadline = new Promise((resolve3) => {
3216
- timer = setTimeout(() => resolve3("late"), ms);
3903
+ const deadline = new Promise((resolve4) => {
3904
+ timer = setTimeout(() => resolve4("late"), ms);
3217
3905
  });
3218
3906
  try {
3219
3907
  const r = await Promise.race([handler(input), deadline]);
@@ -3324,7 +4012,7 @@ async function main() {
3324
4012
  return;
3325
4013
  }
3326
4014
  if (process.argv.includes("--adjudicate")) {
3327
- const { ASYNC_JOB_MAX_MS, runAsyncJob } = await import("./hook-async-J3UKDO62.js");
4015
+ const { ASYNC_JOB_MAX_MS, runAsyncJob } = await import("./hook-async-3TLK23ZY.js");
3328
4016
  setTimeout(() => process.exit(0), ASYNC_JOB_MAX_MS).unref();
3329
4017
  await runAsyncJob(raw, { log: (line2) => hookLog("info", line2) });
3330
4018
  return;