eminiar 0.13.2 → 0.13.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/hook.js CHANGED
@@ -35,7 +35,7 @@ import {
35
35
  swarmSubagentStop,
36
36
  swarmTouch,
37
37
  workerLacksTool
38
- } from "./hook-chunk-ZLRLOPLR.js";
38
+ } from "./hook-chunk-6EFFDI3S.js";
39
39
  import {
40
40
  GUIDE_ORDER,
41
41
  classifyAssistant,
@@ -50,9 +50,10 @@ import {
50
50
  getStorage,
51
51
  init_esm_shims,
52
52
  projectDir,
53
+ sessionContext,
53
54
  setStorageBusyTimeout,
54
55
  withPriority
55
- } from "./hook-chunk-ETYHCPPH.js";
56
+ } from "./hook-chunk-G7UP4ZVA.js";
56
57
 
57
58
  // src/hook.ts
58
59
  init_esm_shims();
@@ -1834,7 +1835,7 @@ function persist(db, sessionId, events) {
1834
1835
  }
1835
1836
  }
1836
1837
  function open(input) {
1837
- const ctx = getContext(input.cwd);
1838
+ const ctx = sessionContext(input.cwd);
1838
1839
  const db = getStorage(ctx, { vectors: false });
1839
1840
  const sessionId = input.session_id || "unknown";
1840
1841
  db.sessionUpsert({
@@ -1847,7 +1848,7 @@ function open(input) {
1847
1848
  var usageFailureLogged = false;
1848
1849
  function ingestUsage(db, input, sessionId) {
1849
1850
  try {
1850
- const ctx = getContext(input.cwd);
1851
+ const ctx = sessionContext(input.cwd);
1851
1852
  if (!usageEnabled(ctx)) return;
1852
1853
  const overrides = ctx.config.usage.pricing;
1853
1854
  ingestSession(db, ctx.root, sessionId, input.transcript_path, { overrides });
@@ -1864,7 +1865,7 @@ function ingestUsage(db, input, sessionId) {
1864
1865
  }
1865
1866
  var userPromptSubmit = async (input) => {
1866
1867
  if (!input.prompt?.trim()) return void 0;
1867
- const ctx = getContext(input.cwd);
1868
+ const ctx = sessionContext(input.cwd);
1868
1869
  const db = getStorage(ctx, { vectors: false });
1869
1870
  const sessionId = input.session_id || "unknown";
1870
1871
  try {
@@ -1901,7 +1902,7 @@ var userPromptSubmit = async (input) => {
1901
1902
  let systemMessage;
1902
1903
  if (ctx.config.adjudicator.enabled && input.client !== "cursor" && hasBudget(ADJUDICATOR_NEEDS_MS)) {
1903
1904
  try {
1904
- const { onPrompt } = await import("./hook-hooks-5U2AI6W7.js");
1905
+ const { onPrompt } = await import("./hook-hooks-YGXLOZ6H.js");
1905
1906
  const a = await onPrompt(ctx, db, input, sessionId);
1906
1907
  if (a?.context) parts.push(a.context);
1907
1908
  systemMessage = a?.systemMessage;
@@ -1936,9 +1937,9 @@ function detectLoop(db, sessionId, key, summary) {
1936
1937
  }
1937
1938
  var postToolUse = async (input) => {
1938
1939
  if (!input.tool_name) return void 0;
1939
- if (input.agent_id) swarmActivity(getContext(input.cwd), input, false);
1940
+ if (input.agent_id) swarmActivity(sessionContext(input.cwd), input, false);
1940
1941
  const isAgent = input.tool_name === "Agent" || input.tool_name === "Task";
1941
- if (isAgent) swarmAgentReturned(getContext(input.cwd), input);
1942
+ if (isAgent) swarmAgentReturned(sessionContext(input.cwd), input);
1942
1943
  const { db, root, sessionId } = open(input);
1943
1944
  const drafts = classifyToolCall({
1944
1945
  toolName: input.tool_name,
@@ -1956,10 +1957,10 @@ var postToolUse = async (input) => {
1956
1957
  }
1957
1958
  }
1958
1959
  if (isAgent && input.client !== "cursor") {
1959
- const ctx = getContext(input.cwd);
1960
+ const ctx = sessionContext(input.cwd);
1960
1961
  if (ctx.config.adjudicator.enabled) {
1961
1962
  try {
1962
- const { onPostAgent } = await import("./hook-hooks-5U2AI6W7.js");
1963
+ const { onPostAgent } = await import("./hook-hooks-YGXLOZ6H.js");
1963
1964
  onPostAgent(ctx, db, input);
1964
1965
  } catch {
1965
1966
  }
@@ -1988,7 +1989,7 @@ function bestEffortWrites(db) {
1988
1989
  }
1989
1990
  var postToolUseFailure = async (input) => {
1990
1991
  if (!input.tool_name) return void 0;
1991
- const ctx = getContext(input.cwd);
1992
+ const ctx = sessionContext(input.cwd);
1992
1993
  if (input.tool_name === "Agent" || input.tool_name === "Task") swarmAgentReturned(ctx, input);
1993
1994
  const db = getStorage(ctx, { vectors: false });
1994
1995
  const sessionId = input.session_id || "unknown";
@@ -2028,7 +2029,7 @@ var postToolUseFailure = async (input) => {
2028
2029
  return recalled ? { hookSpecificOutput: { hookEventName: "PostToolUseFailure", additionalContext: recalled } } : void 0;
2029
2030
  };
2030
2031
  var subagentStop = (input) => {
2031
- if (input.agent_id) swarmSubagentStop(getContext(input.cwd), input);
2032
+ if (input.agent_id) swarmSubagentStop(sessionContext(input.cwd), input);
2032
2033
  const { db, sessionId } = open(input);
2033
2034
  const msg = input.last_assistant_message ?? "";
2034
2035
  if (msg.trim()) {
@@ -2054,7 +2055,7 @@ var stop = (input) => {
2054
2055
  return void 0;
2055
2056
  };
2056
2057
  var sessionEnd = async (input) => {
2057
- const ctx = getContext(input.cwd);
2058
+ const ctx = sessionContext(input.cwd);
2058
2059
  swarmSessionEnd(ctx, input);
2059
2060
  const { db, sessionId } = open(input);
2060
2061
  db.sessionUpsert({ id: sessionId, ended_at: Date.now() });
@@ -2064,7 +2065,7 @@ var sessionEnd = async (input) => {
2064
2065
  ingestUsage(db, input, sessionId);
2065
2066
  if (ctx.config.adjudicator.enabled && input.client !== "cursor") {
2066
2067
  try {
2067
- const { onSessionEnd } = await import("./hook-hooks-5U2AI6W7.js");
2068
+ const { onSessionEnd } = await import("./hook-hooks-YGXLOZ6H.js");
2068
2069
  onSessionEnd(ctx, db, input);
2069
2070
  } catch {
2070
2071
  }
@@ -2186,7 +2187,7 @@ function renderGuide(db, sessionId, budget = 2048) {
2186
2187
  // src/hooks/pre-compact.ts
2187
2188
  var SNAPSHOT_BUDGET = 2048;
2188
2189
  var preCompact = (input) => {
2189
- const ctx = getContext(input.cwd);
2190
+ const ctx = sessionContext(input.cwd);
2190
2191
  const db = getStorage(ctx, { vectors: false });
2191
2192
  const sessionId = input.session_id || "unknown";
2192
2193
  const body = renderGuide(db, sessionId, SNAPSHOT_BUDGET);
@@ -2197,9 +2198,9 @@ var preCompact = (input) => {
2197
2198
 
2198
2199
  // src/hooks/routing.ts
2199
2200
  init_esm_shims();
2200
- import { realpathSync as realpathSync2, statSync as statSync3 } from "fs";
2201
+ import { existsSync as existsSync7, readFileSync as readFileSync5, realpathSync as realpathSync3, statSync as statSync3 } from "fs";
2201
2202
  import { homedir as homedir2 } from "os";
2202
- import { extname, join as join7, relative, resolve as resolve2 } from "path";
2203
+ import { dirname as dirname3, extname, join as join7, relative as relative2, resolve as resolve3 } from "path";
2203
2204
 
2204
2205
  // src/permissions/settings.ts
2205
2206
  init_esm_shims();
@@ -2364,6 +2365,36 @@ function checkReadPath(rules, abs) {
2364
2365
  return more.length ? checkRead(rules, more) : asWritten;
2365
2366
  }
2366
2367
 
2368
+ // src/sandbox/file.ts
2369
+ init_esm_shims();
2370
+ import { existsSync as existsSync4, realpathSync as realpathSync2 } from "fs";
2371
+ import { isAbsolute, relative, resolve as resolve2 } from "path";
2372
+ function confinePath(root, target, given) {
2373
+ const abs = resolve2(root, target);
2374
+ const rules = given.root ? given : { ...given, root };
2375
+ refuse(checkRead(rules, [abs]), target);
2376
+ let real;
2377
+ try {
2378
+ real = realpathSync2(abs);
2379
+ } catch {
2380
+ throw new Error(`file not found: ${target}`);
2381
+ }
2382
+ refuse(checkReadPath(rules, abs), target);
2383
+ const rel = relative(root, real);
2384
+ const inside = rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
2385
+ if (inside) return real;
2386
+ if (readMatcher(rules.allow, rules.root).test(real)) return real;
2387
+ throw new Error(
2388
+ `${target} is outside the project root; add a Read(/${real}) rule to permissions.allow to opt it in`
2389
+ );
2390
+ }
2391
+ function refusal(check, target) {
2392
+ return `refused by permission rule ${check.rule} (${check.kind}) on path: ${target}`;
2393
+ }
2394
+ function refuse(check, target) {
2395
+ if (!check.allowed) throw new Error(refusal(check, target));
2396
+ }
2397
+
2367
2398
  // src/session/routing-stats.ts
2368
2399
  init_esm_shims();
2369
2400
  var ROUTING_CATEGORY = "routing";
@@ -2386,7 +2417,7 @@ function recordRouting(db, sessionId, e) {
2386
2417
 
2387
2418
  // src/session/server-alive.ts
2388
2419
  init_esm_shims();
2389
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync as readdirSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
2420
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync as readdirSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
2390
2421
  import { join as join4 } from "path";
2391
2422
  function markersDir(ctx) {
2392
2423
  return join4(projectDir(ctx.id), "servers");
@@ -2401,7 +2432,7 @@ function processAlive(pid) {
2401
2432
  }
2402
2433
  function liveServers(ctx) {
2403
2434
  const dir = markersDir(ctx);
2404
- if (!existsSync4(dir)) return [];
2435
+ if (!existsSync5(dir)) return [];
2405
2436
  const live = [];
2406
2437
  for (const name of readdirSync2(dir)) {
2407
2438
  const pid = Number(name);
@@ -2424,7 +2455,7 @@ import { join as join6 } from "path";
2424
2455
  init_esm_shims();
2425
2456
  import {
2426
2457
  copyFileSync,
2427
- existsSync as existsSync5,
2458
+ existsSync as existsSync6,
2428
2459
  mkdirSync as mkdirSync3,
2429
2460
  readdirSync as readdirSync3,
2430
2461
  readFileSync as readFileSync3,
@@ -2469,7 +2500,11 @@ function literalOf(pattern) {
2469
2500
  function symbolHints(db, pattern) {
2470
2501
  const lit = literalOf(pattern);
2471
2502
  if (!lit) return void 0;
2472
- const nodes = db.graphFindNodes({ query: lit, kinds: SYMBOL_KINDS, limit: 5 });
2503
+ const nodes = db.graphFindNodes({
2504
+ query: lit,
2505
+ kinds: SYMBOL_KINDS,
2506
+ limit: 5
2507
+ });
2473
2508
  if (!nodes.length) return void 0;
2474
2509
  const list = nodes.map((n) => `${n.kind} ${n.name} (${n.path}:${n.start_line ?? "?"})`).join("; ");
2475
2510
  return `eminiar graph: symbols matching "${lit}": ${list}. em_symbols {name_path, include_body: true} reads one; {mode:"references"} lists its callers.`;
@@ -2479,13 +2514,13 @@ function coverageHint(db, rel) {
2479
2514
  if (!row || row.status === "indexed") return void 0;
2480
2515
  return `eminiar graph: ${rel} is ${row.status}${row.reason ? ` (${row.reason})` : ""}; graph answers about it may be incomplete.`;
2481
2516
  }
2482
- var GIT_HISTORY = /(?:^|[;&|]\s*)git\s+(?:(?:-C|--git-dir|--work-tree)[= ]\S+\s+|--no-pager\s+)*(log|blame|shortlog|show|whatchanged|reflog)\b([^;&|]*)/;
2517
+ var GIT_HISTORY = /(?:^|[;&|]\s*)git\s+((?:(?:-C|--git-dir|--work-tree)[= ]\S+\s+|--no-pager\s+)*)(log|blame|shortlog|show|whatchanged|reflog)\b([^;&|]*)/;
2483
2518
  var ONE_COMMIT = /(^|\s)(-1|-n\s*1|-n1|--max-count[= ]1)(?=\s|$)/;
2484
2519
  function historyCall(cmd) {
2485
2520
  const m = GIT_HISTORY.exec(cmd);
2486
2521
  if (!m) return void 0;
2487
- const sub = m[1];
2488
- const rest = (m[2] ?? "").trim();
2522
+ const sub = m[2];
2523
+ const rest = (m[3] ?? "").trim();
2489
2524
  const args = rest.split(/\s+/).filter(Boolean);
2490
2525
  const q = (s) => JSON.stringify(s.replace(/^['"]|['"]$/g, ""));
2491
2526
  const positional = (a) => !a.startsWith("-");
@@ -2520,6 +2555,66 @@ function historyCall(cmd) {
2520
2555
  if (sub === "shortlog") return `{action:"timeline"}`;
2521
2556
  return `{action:"overview"}`;
2522
2557
  }
2558
+ function historyDir(segments, cwd) {
2559
+ let dir = cwd;
2560
+ let steps = 0;
2561
+ const step = (arg) => {
2562
+ dir = ++steps > MAX_STEPS ? void 0 : dir && changeDir(dir, arg);
2563
+ };
2564
+ for (const segment of segments) {
2565
+ const m = GIT_HISTORY.exec(segment);
2566
+ if (m) {
2567
+ const opts = m[1] ?? "";
2568
+ if (/--(git-dir|work-tree)\b/.test(opts)) return void 0;
2569
+ for (const c of opts.matchAll(/-C[= ](\S+)/g)) {
2570
+ step(c[1]);
2571
+ if (!dir) return void 0;
2572
+ }
2573
+ return dir;
2574
+ }
2575
+ const cd = CD.exec(segment);
2576
+ if (cd) step(cd[1]);
2577
+ if (!dir) return void 0;
2578
+ }
2579
+ return dir;
2580
+ }
2581
+ var CD = /^\(?\s*(?:cd|pushd)\b(.*)$/;
2582
+ var MAX_STEPS = 16;
2583
+ var MAX_RAW = 64 * 1024;
2584
+ var MAX_CLASSIFIED = 8 * 1024;
2585
+ var MAX_OPERANDS = 64;
2586
+ var MAX_SEGMENTS = 64;
2587
+ function changeDir(dir, arg) {
2588
+ const a = arg.trim().replace(/^(['"])(.*)\1$/, "$2") || "~";
2589
+ if (/^-|^~[^/]|[\s$`*?[\]{}()\\]/.test(a)) return void 0;
2590
+ return a === "~" || a.startsWith("~/") ? join7(homedir2(), a.slice(1)) : resolve3(dir, a);
2591
+ }
2592
+ function inRootRepo(root, dir) {
2593
+ try {
2594
+ const top = realpathSync3(root);
2595
+ let inside = false;
2596
+ for (let d = realpathSync3(dir); ; d = dirname3(d)) {
2597
+ if (d === top) inside = true;
2598
+ if (existsSync7(join7(d, ".git"))) return inside;
2599
+ if (dirname3(d) === d) return false;
2600
+ }
2601
+ } catch {
2602
+ return false;
2603
+ }
2604
+ }
2605
+ function repoOf(dir) {
2606
+ try {
2607
+ for (let d = realpathSync3(dir); ; d = dirname3(d)) {
2608
+ if (existsSync7(join7(d, ".git"))) return d;
2609
+ if (dirname3(d) === d) return void 0;
2610
+ }
2611
+ } catch {
2612
+ return void 0;
2613
+ }
2614
+ }
2615
+ function sameRepo(root, dir) {
2616
+ return repoOf(root) === repoOf(dir);
2617
+ }
2523
2618
  function docsSources(root) {
2524
2619
  try {
2525
2620
  return readManifestAt(join7(root, ".eminiar", "docs.json"), root).sources;
@@ -2562,7 +2657,34 @@ var BIG_TEXT_EXT = /* @__PURE__ */ new Set([
2562
2657
  ]);
2563
2658
  var VISUAL_EXT = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".pdf"]);
2564
2659
  function withoutHeredocs(cmd) {
2565
- return cmd.replace(/<<-?\s*(['"]?)(\w+)\1[^\n]*\n[\s\S]*?\n\s*\2\s*(?=\n|$)/g, "<<$2");
2660
+ if (!cmd.includes("<<")) return cmd;
2661
+ const head = /<<-?\s*(['"]?)(\w+)\1/y;
2662
+ const seen = /* @__PURE__ */ new Map();
2663
+ const terminator = (word, from) => {
2664
+ const c = seen.get(word);
2665
+ if (c && from >= c.from && (c.at === -1 || from <= c.at)) return c.end;
2666
+ const re = new RegExp(String.raw`\n[^\S\n]*${word}\s*(?=\n|$)`, "g");
2667
+ re.lastIndex = from;
2668
+ const m = re.exec(cmd);
2669
+ const rec = m ? { from, at: m.index, end: m.index + m[0].length } : { from, at: -1, end: -1 };
2670
+ seen.set(word, rec);
2671
+ return rec.end;
2672
+ };
2673
+ let out = "";
2674
+ let copied = 0;
2675
+ for (let k = cmd.indexOf("<<"); k !== -1; k = cmd.indexOf("<<", Math.max(k + 1, copied))) {
2676
+ head.lastIndex = k;
2677
+ const m = head.exec(cmd);
2678
+ if (!m) continue;
2679
+ const word = m[2];
2680
+ const eol = cmd.indexOf("\n", head.lastIndex);
2681
+ if (eol === -1) break;
2682
+ const end = terminator(word, eol + 1);
2683
+ if (end === -1) continue;
2684
+ out += `${cmd.slice(copied, k)}<<${word}${cmd.slice(head.lastIndex, eol)}`;
2685
+ copied = end;
2686
+ }
2687
+ return out + cmd.slice(copied);
2566
2688
  }
2567
2689
  function splitUnquoted(s, sep2) {
2568
2690
  const out = [];
@@ -2570,13 +2692,23 @@ function splitUnquoted(s, sep2) {
2570
2692
  let quote;
2571
2693
  for (let i = 0; i < s.length; i++) {
2572
2694
  const ch = s[i];
2695
+ if (quote === "'") {
2696
+ cur += ch;
2697
+ if (ch === "'") quote = void 0;
2698
+ continue;
2699
+ }
2700
+ if (ch === "\\") {
2701
+ cur += ch + (s[i + 1] ?? "");
2702
+ i++;
2703
+ continue;
2704
+ }
2573
2705
  if (quote) {
2574
2706
  cur += ch;
2575
- if (ch === quote && s[i - 1] !== "\\") quote = void 0;
2707
+ if (ch === quote[quote.length - 1]) quote = void 0;
2576
2708
  continue;
2577
2709
  }
2578
2710
  if (ch === "'" || ch === '"') {
2579
- quote = ch;
2711
+ quote = ch === "'" && s[i - 1] === "$" ? "$'" : ch;
2580
2712
  cur += ch;
2581
2713
  continue;
2582
2714
  }
@@ -2597,9 +2729,44 @@ function commandSegments(cmd) {
2597
2729
  const ch = s[i];
2598
2730
  if (ch === ";" || ch === "\n") return 1;
2599
2731
  if ((ch === "&" || ch === "|") && s[i + 1] === ch) return 2;
2732
+ if (ch === "&" && !/[&<>|]/.test(s[i - 1] ?? "") && !/[&>]/.test(s[i + 1] ?? "")) return 1;
2600
2733
  return 0;
2601
2734
  });
2602
2735
  }
2736
+ function shellWords(s) {
2737
+ const words = [];
2738
+ let cur = "";
2739
+ let inWord = false;
2740
+ for (let i = 0; i < s.length; i++) {
2741
+ const ch = s[i];
2742
+ if (ch === " " || ch === " " || ch === "\n") {
2743
+ if (inWord) words.push(cur);
2744
+ cur = "";
2745
+ inWord = false;
2746
+ continue;
2747
+ }
2748
+ inWord = true;
2749
+ if (ch === "\\") {
2750
+ cur += s[++i] ?? "";
2751
+ } else if (ch === "'" && s[i - 1] !== "$") {
2752
+ const end = s.indexOf("'", i + 1);
2753
+ const stop2 = end === -1 ? s.length : end;
2754
+ cur += s.slice(i + 1, stop2);
2755
+ i = stop2;
2756
+ } else if (ch === '"' || ch === "$" && s[i + 1] === "'") {
2757
+ const close = ch === '"' ? '"' : "'";
2758
+ if (ch === "$") i++;
2759
+ for (i++; i < s.length && s[i] !== close; i++) {
2760
+ if (s[i] === "\\" && i + 1 < s.length) i++;
2761
+ cur += s[i];
2762
+ }
2763
+ } else {
2764
+ cur += ch;
2765
+ }
2766
+ }
2767
+ if (inWord) words.push(cur);
2768
+ return words;
2769
+ }
2603
2770
  function pipeStages(segment) {
2604
2771
  return splitUnquoted(segment, (s, i) => s[i] === "|" && s[i + 1] !== "|" ? 1 : 0);
2605
2772
  }
@@ -2613,10 +2780,416 @@ function bounds(stage) {
2613
2780
  if (!/^\s*(head|tail)\b/.test(stage) || /(^|\s)-c\b|--bytes\b/.test(stage)) return false;
2614
2781
  return headLines(stage) <= BOUNDED_LINES;
2615
2782
  }
2616
- 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/;
2617
- var WRITES_FILE = /(?:^|\s|\d)>{1,2}\s*(?!&)(?!\/dev\/null)\S/;
2618
- function mutates(segment) {
2619
- return MUTATING.test(segment) || WRITES_FILE.test(segment);
2783
+ 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-])/;
2784
+ 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-])/;
2785
+ var MUTATING_WORDS = /* @__PURE__ */ new Set([
2786
+ "npm",
2787
+ "pnpm",
2788
+ "yarn",
2789
+ "pip",
2790
+ "pip3",
2791
+ "cargo",
2792
+ "brew",
2793
+ "gh",
2794
+ "terraform",
2795
+ "helm",
2796
+ "mvn",
2797
+ "mvnw",
2798
+ "gradle",
2799
+ "gradlew",
2800
+ "rm",
2801
+ "mv",
2802
+ "cp",
2803
+ "mkdir",
2804
+ "rmdir",
2805
+ "touch",
2806
+ "chmod",
2807
+ "chown",
2808
+ "ln",
2809
+ "sed",
2810
+ "tee",
2811
+ "eval",
2812
+ "sh",
2813
+ "bash",
2814
+ "zsh",
2815
+ "find"
2816
+ ]);
2817
+ var MUTATING = {
2818
+ test(cmd) {
2819
+ const word = /^[A-Za-z0-9]*/.exec(cmd)[0];
2820
+ if (word === "git") return GIT_MUTATING.test(cmd);
2821
+ return MUTATING_WORDS.has(word) && OTHER_MUTATING.test(cmd);
2822
+ }
2823
+ };
2824
+ var longOpts = (valued) => String.raw`--(?:${valued})\s+[^-\s]\S*|--(?!(?:${valued})(?:\s|$))\S*`;
2825
+ var RUNNERS = [
2826
+ 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")}))*`,
2827
+ String.raw`env(?:\s+(?:-[uC]\s*[^-\s]\S*|-(?![uC-])\S+|${longOpts("unset|chdir|split-string")}))*`,
2828
+ 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")}))*`,
2829
+ String.raw`timeout(?:\s+(?:-[sk]\s*[^-\s]\S*|-(?![sk-])\S+|${longOpts("signal|kill-after")}))*\s+\d\S*`,
2830
+ String.raw`nice(?:\s+-n\s*\S+|\s+-\d+|\s+--adjustment(?:=|\s+)\S+)?`,
2831
+ String.raw`stdbuf(?:\s+-[ioe]\s*\S+)*`,
2832
+ String.raw`(?:time|command)(?:\s+-p)?`,
2833
+ "nohup",
2834
+ String.raw`[A-Z_][A-Z0-9_]*=\S*`
2835
+ ];
2836
+ var SHELL_WORDS = /^(?:\s*(?:[({!]|(?:then|do|else|exec|if|elif|while|until)\s))*\s*/;
2837
+ var RUNNER = new RegExp(String.raw`^\s*(?:(?:${RUNNERS.join("|")})\s+)*`);
2838
+ var RUNNER_HEAD = /^\s*(?:sudo|env|xargs|parallel|timeout|nice|stdbuf|time|command|nohup|[A-Z_])/;
2839
+ var stripRunners = (s) => RUNNER_HEAD.test(s) ? s.replace(RUNNER, "") : s.replace(/^\s+/, "");
2840
+ var WRITES_FILE = /(?:^|[\s\d&])>{1,2}\s*(?!&)(?!\/dev\/null)\S/;
2841
+ var GLUED_WRITE = /(?<![-=>])>{1,2}\s*(?!&)(?!\/dev\/null)\S/;
2842
+ var RUNS_STDIN = /^(python3?|node|perl|ruby|deno|bun)(\s+-\S*)*$/;
2843
+ var FOLLOWS = /^((kubectl|docker)(\s.*)?\s(-f|-w|--follow)(?=[\s=]|$)|tail(\s.*)?\s(-[a-zA-Z]*[fF]|--follow)|.*\s--watch(All)?(?=[\s=]|$))/;
2844
+ 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))/;
2845
+ var WGET_PRINTS = /\s(?:-[a-zA-Z]*O\s*-|--output-document[= ]-|--spider)(?=\s|$)/;
2846
+ var WGET_SENDS = /\s(?:--(post-data|post-file|body-data|body-file|method|output-file|append-output|execute|config)\b|-[a-zA-Z]*[oae])/;
2847
+ 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/;
2848
+ 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/;
2849
+ var CHECK_SCRIPT = /^(t|test|lint|type-?check|check)(?=$|[:.-])(?![\w:.-]*(fix|write|update|install|deploy|publish|push|release|clean))/;
2850
+ var FIX_ARGS = /(?:^|\s)(--fix|--write|--apply(-unsafe)?|-u|--update(-?[sS]napshots?)?)(?=[\s=]|$)/;
2851
+ var NPX_CHECKS = /^(vitest|jest|tsc|eslint|biome|prettier)(?:@[\w.^~-]*)?$/;
2852
+ var PM_READS = /^(ls|list|outdated|why|info|view|audit|licenses|bin|root|help)$/;
2853
+ var VALUE_OPTS = {
2854
+ kubectl: /* @__PURE__ */ new Set([
2855
+ "-n",
2856
+ "--namespace",
2857
+ "--context",
2858
+ "--cluster",
2859
+ "--kubeconfig",
2860
+ "--user",
2861
+ "-s",
2862
+ "--server",
2863
+ "--token",
2864
+ "--as",
2865
+ "--as-group",
2866
+ "--as-uid",
2867
+ "--cache-dir",
2868
+ "--certificate-authority",
2869
+ "--client-certificate",
2870
+ "--client-key",
2871
+ "--password",
2872
+ "--username",
2873
+ "--profile",
2874
+ "--profile-output",
2875
+ "--request-timeout",
2876
+ "--tls-server-name",
2877
+ "--log-dir",
2878
+ "--log-file",
2879
+ "--log-file-max-size",
2880
+ "--vmodule",
2881
+ "-v",
2882
+ "--v"
2883
+ ]),
2884
+ docker: /* @__PURE__ */ new Set([
2885
+ "-H",
2886
+ "--host",
2887
+ "-c",
2888
+ "--context",
2889
+ "--config",
2890
+ "-l",
2891
+ "--log-level",
2892
+ "--tlscacert",
2893
+ "--tlscert",
2894
+ "--tlskey"
2895
+ ]),
2896
+ npm: /* @__PURE__ */ new Set(["--prefix", "-w", "--workspace", "--userconfig", "--registry", "--cache"]),
2897
+ pnpm: /* @__PURE__ */ new Set(["-C", "--dir", "--filter", "-F", "--workspace-dir"]),
2898
+ yarn: /* @__PURE__ */ new Set(["--cwd", "--cache-folder", "--registry"])
2899
+ };
2900
+ var FLAG_OPTS = {
2901
+ kubectl: /* @__PURE__ */ new Set([
2902
+ "--insecure-skip-tls-verify",
2903
+ "--match-server-version",
2904
+ "--warnings-as-errors",
2905
+ "--disable-compression",
2906
+ "--alsologtostderr",
2907
+ "--logtostderr",
2908
+ "--add-dir-header",
2909
+ "--skip-headers",
2910
+ "--skip-log-headers",
2911
+ "--one-output",
2912
+ "-h",
2913
+ "--help"
2914
+ ]),
2915
+ docker: /* @__PURE__ */ new Set(["-D", "--debug", "--tls", "--tlsverify", "-v", "--version", "--help"])
2916
+ };
2917
+ var MAX_READINGS = 4;
2918
+ function afterOptions(tool, args) {
2919
+ const words = args.trim().split(/\s+/);
2920
+ const valued = VALUE_OPTS[tool];
2921
+ let i = 0;
2922
+ while (words[i]?.startsWith("-")) i += valued?.has(words[i]) ? 2 : 1;
2923
+ return words.slice(i);
2924
+ }
2925
+ function subcommands(tool, args) {
2926
+ const words = args.trim().split(/\s+/);
2927
+ const valued = VALUE_OPTS[tool];
2928
+ const flags = FLAG_OPTS[tool];
2929
+ const starts = /* @__PURE__ */ new Set();
2930
+ const seen = /* @__PURE__ */ new Set();
2931
+ const todo = [0];
2932
+ while (todo.length) {
2933
+ const i = todo.pop();
2934
+ if (seen.has(i)) continue;
2935
+ seen.add(i);
2936
+ const w = words[i];
2937
+ if (w === void 0 || !w.startsWith("-")) {
2938
+ starts.add(i);
2939
+ if (starts.size > MAX_READINGS) return void 0;
2940
+ } else if (valued?.has(w)) todo.push(i + 2);
2941
+ else if (flags?.has(w) || w.includes("=") || /^-[^-]./.test(w)) todo.push(i + 1);
2942
+ else todo.push(i + 1, i + 2);
2943
+ }
2944
+ const tail = /\s--no-stream\b/.test(args) ? " --no-stream" : "";
2945
+ return [...starts].map((i) => words.slice(i, i + 3).join(" ") + tail);
2946
+ }
2947
+ function verbChanges(tool, reads, args) {
2948
+ const subs = subcommands(tool, args);
2949
+ return !subs || subs.some((s) => !reads.test(s));
2950
+ }
2951
+ function runsScript(tool, args) {
2952
+ const words = afterOptions(tool, args);
2953
+ let script = words.shift();
2954
+ if (script === "run" || script === "run-script") script = words.shift();
2955
+ else if (script === void 0 || PM_READS.test(script) || tool === "npm" && script !== "test")
2956
+ return false;
2957
+ if (script === void 0) return false;
2958
+ return !CHECK_SCRIPT.test(script) || FIX_ARGS.test(words.join(" "));
2959
+ }
2960
+ function makeChanges(args) {
2961
+ const words = shellWords(args);
2962
+ const targets = [];
2963
+ for (let i = 0; i < words.length; i++) {
2964
+ const w = words[i];
2965
+ if (/^(-E|--eval)/.test(w)) return true;
2966
+ if (w.startsWith("-")) {
2967
+ if (/^(-[CfIoW]|--(directory|file|makefile|include-dir|old-file|assume-old|what-if|new-file|assume-new))$/.test(
2968
+ w
2969
+ ) || /^(-[jl]|--jobs|--load-average|--max-load)$/.test(w) && /^\d+(\.\d+)?$/.test(words[i + 1] ?? ""))
2970
+ i++;
2971
+ continue;
2972
+ }
2973
+ if (!w.includes("=")) targets.push(w);
2974
+ }
2975
+ return !targets.length || !targets.every((t) => CHECK_SCRIPT.test(t)) || FIX_ARGS.test(args);
2976
+ }
2977
+ var GH_API_VALUED = /^(-[FfHXpqt]|--(method|field|raw-field|input|header|jq|template|preview|hostname|cache))$/;
2978
+ function ghApiSends(args) {
2979
+ const words = shellWords(args);
2980
+ if (words[0] !== "api") return false;
2981
+ let method;
2982
+ let fields = false;
2983
+ for (let i = 1; i < words.length; i++) {
2984
+ const w = words[i];
2985
+ const long = /^--(method|field|raw-field|input)(?:=|$)/.exec(w)?.[1];
2986
+ if (long) {
2987
+ if (long === "method")
2988
+ method = w.includes("=") ? w.slice(w.indexOf("=") + 1) : words[i + 1] ?? "";
2989
+ else fields = true;
2990
+ } else if (/^-[a-zA-Z]/.test(w)) {
2991
+ const c = w.slice(1).replace(/^i+/, "")[0];
2992
+ const value = w.slice(w.indexOf(c ?? "") + 1) || (words[i + 1] ?? "");
2993
+ if (c === "X") method = value;
2994
+ else if (c === "f" || c === "F") fields = true;
2995
+ }
2996
+ if (GH_API_VALUED.test(w)) i++;
2997
+ }
2998
+ if (method !== void 0) return method.toUpperCase() !== "GET";
2999
+ if (words.includes("graphql")) return /\bmutation\b/.test(words.join(" "));
3000
+ return fields;
3001
+ }
3002
+ function npxChanges(args, cwd) {
3003
+ const words = shellWords(args);
3004
+ let i = 0;
3005
+ for (; words[i]?.startsWith("-"); i++)
3006
+ if (/^(-p|--package|-c|--call)(=|$)/.test(words[i])) return true;
3007
+ const [name = "", ...rest] = words.slice(i);
3008
+ const tool = NPX_CHECKS.exec(name)?.[1];
3009
+ if (!tool || FIX_ARGS.test(args)) return true;
3010
+ const has = (re) => rest.some((w) => re.test(w));
3011
+ if (has(/^--(output-?file|outputFile|out-?dir|outFile|generateTrace)(=|$)/i)) return true;
3012
+ if (tool === "eslint" && has(/^-o/)) return true;
3013
+ if (tool === "prettier" && has(/^-[a-zA-Z]*w$/)) return true;
3014
+ return tool === "tsc" && !tscEmitsNothing(rest, cwd);
3015
+ }
3016
+ function tscEmitsNothing(words, cwd) {
3017
+ if (words.some((w) => /^(-b|--build|--generateTrace)(=|$)/i.test(w))) return false;
3018
+ const noEmit = words.findIndex((w) => /^--noEmit(=true)?$/i.test(w));
3019
+ if (noEmit !== -1) return words[noEmit + 1] !== "false";
3020
+ let project = "tsconfig.json";
3021
+ for (let i = 0; i < words.length; i++) {
3022
+ const w = words[i];
3023
+ if (/^(-p|--project)$/i.test(w)) project = words[++i] ?? "";
3024
+ else if (!w.startsWith("-")) return false;
3025
+ }
3026
+ try {
3027
+ let file2 = resolve3(cwd, project);
3028
+ if (statSync3(file2).isDirectory()) file2 = join7(file2, "tsconfig.json");
3029
+ const st = statSync3(file2);
3030
+ if (st.size > 256 * 1024) return false;
3031
+ return /"noEmit"\s*:\s*true\b/.test(readFileSync5(file2, "utf8"));
3032
+ } catch {
3033
+ return false;
3034
+ }
3035
+ }
3036
+ var CHANGES = /* @__PURE__ */ new Map([
3037
+ ["curl", (a) => CURL_SENDS.test(a)],
3038
+ ["wget", (a) => !WGET_PRINTS.test(a) || WGET_SENDS.test(a)],
3039
+ ["kubectl", (a) => verbChanges("kubectl", KUBECTL_READS, a)],
3040
+ ["docker", (a) => verbChanges("docker", DOCKER_READS, a)],
3041
+ ["gh", ghApiSends],
3042
+ ["make", makeChanges],
3043
+ ["npx", npxChanges],
3044
+ ["npm", (a) => runsScript("npm", a)],
3045
+ ["pnpm", (a) => runsScript("pnpm", a)],
3046
+ ["yarn", (a) => runsScript("yarn", a)]
3047
+ ]);
3048
+ function operands(words, valued) {
3049
+ let n = 0;
3050
+ for (let i = 0; i < words.length; i++) {
3051
+ const w = words[i];
3052
+ if (w === "--") return n + words.length - i - 1;
3053
+ if (w.startsWith("-") && w.length > 1) {
3054
+ if (valued.test(w)) i++;
3055
+ } else n++;
3056
+ }
3057
+ return n;
3058
+ }
3059
+ function awkRuns(words) {
3060
+ let program;
3061
+ for (let i = 0; i < words.length && program === void 0; i++) {
3062
+ const w = words[i];
3063
+ if (w === "--") program = words[i + 1];
3064
+ else if (/^(-[fiEl]|--(file|include|load|exec))/.test(w)) return true;
3065
+ else if (/^(-e|--source)$/.test(w)) program = words[i + 1];
3066
+ else if (/^(-[FvW]|--(field-separator|assign))$/.test(w)) i++;
3067
+ else if (!w.startsWith("-")) program = w;
3068
+ }
3069
+ return program !== void 0 && /\bsystem\s*\(|\|\s*&?\s*getline|\bprintf?\b[^;{}\n]*[>|]|@(load|include)\b/.test(program);
3070
+ }
3071
+ function sedWrites(words) {
3072
+ const scripts = [];
3073
+ let first;
3074
+ for (let i = 0; i < words.length; i++) {
3075
+ const w = words[i];
3076
+ if (w === "--") {
3077
+ first ??= words[i + 1];
3078
+ break;
3079
+ }
3080
+ if (w.startsWith("--")) {
3081
+ const [name, value] = w.split(/=(.*)/s);
3082
+ if (name === "--in-place" || name === "--file") return true;
3083
+ if (name === "--expression") scripts.push(value ?? words[++i] ?? "");
3084
+ else if (name === "--line-length" && value === void 0) i++;
3085
+ } else if (/^-./.test(w)) {
3086
+ for (let k = 1; k < w.length; k++) {
3087
+ const c = w[k];
3088
+ if (c === "i" || c === "f") return true;
3089
+ if (c === "e" || c === "l") {
3090
+ const value = w.slice(k + 1) || words[++i] || "";
3091
+ if (c === "e") scripts.push(value);
3092
+ break;
3093
+ }
3094
+ }
3095
+ } else if (first === void 0) first = w;
3096
+ }
3097
+ if (!scripts.length && first !== void 0) scripts.push(first);
3098
+ return scripts.some(sedScriptWrites);
3099
+ }
3100
+ function sedScriptWrites(script) {
3101
+ if (script.length > 2048) return true;
3102
+ let flagged = false;
3103
+ const t = script.replace(
3104
+ /(^|[^a-zA-Z\\])([sy])([^\\\n\sa-zA-Z0-9])((?:\\[\s\S]|(?!\3)[^\\])*)\3((?:\\[\s\S]|(?!\3)[^\\])*)\3([a-zA-Z0-9]*)/g,
3105
+ (_m, pre, cmd, _d, _a, _b, flags) => {
3106
+ if (cmd === "s" && /[we]/.test(flags)) flagged = true;
3107
+ return `${pre};`;
3108
+ }
3109
+ ).replace(/\/(?:\\[\s\S]|[^/\\\n])*\//g, ";");
3110
+ return flagged || /(?:^|[^a-zA-Z_.-])(?:[wW]|e(?![a-zA-Z]))/.test(t);
3111
+ }
3112
+ var FLAG_WRITES = /* @__PURE__ */ new Map([
3113
+ // `--pre cmd` runs cmd on every file searched
3114
+ ["rg", (w) => w.some((x) => /^--pre(-glob)?(=|$)/.test(x))],
3115
+ ["git", gitRunsOrWrites],
3116
+ ["find", (w) => w.some((x) => /^-(delete|exec|execdir|ok|okdir|fprint0?|fprintf|fls)$/.test(x))],
3117
+ ["tree", (w) => w.some((x) => /^(-[a-zA-Z]*o|--output)/.test(x))],
3118
+ ["sed", sedWrites],
3119
+ ["gsed", sedWrites],
3120
+ ["awk", awkRuns],
3121
+ ["gawk", awkRuns],
3122
+ ["mawk", awkRuns],
3123
+ ["nawk", awkRuns],
3124
+ ["sort", (w) => w.some((x) => /^(-[a-zA-Z]*o|--output(=|$))/.test(x))],
3125
+ // `uniq IN OUT` and `xxd IN OUT` write OUT
3126
+ ["uniq", (w) => operands(w, /^(-[fsw]|--(skip-fields|skip-chars|check-chars))$/) > 1],
3127
+ ["xxd", (w) => operands(w, /^-(c|g|l|o|s|n|cols|groupsize|len|offset|seek|name)$/) > 1],
3128
+ ["yq", (w) => w.some((x) => /^(-[a-zA-Z]*i[a-zA-Z]*|--inplace)(=|$)/.test(x))]
3129
+ ]);
3130
+ function gitRunsOrWrites(words) {
3131
+ let i = 0;
3132
+ for (; i < words.length && words[i].startsWith("-"); i++) {
3133
+ const w = words[i];
3134
+ if (/^(-c|--config-env|--exec-path=)/.test(w)) return true;
3135
+ if (/^(-C|--git-dir|--work-tree|--namespace|--super-prefix)$/.test(w)) i++;
3136
+ }
3137
+ const sub = words[i];
3138
+ const rest = words.slice(i + 1);
3139
+ if (sub === "difftool" || sub === "mergetool") return true;
3140
+ if (rest.some((w) => /^--(output|ext-diff)(=|$)/.test(w))) return true;
3141
+ return sub === "grep" && rest.some((w) => /^(-[a-zA-Z]*O|--open-files-in-pager)/.test(w));
3142
+ }
3143
+ 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)=/;
3144
+ var ARITHMETIC = /\$\(\(([^()`]*)\)\)/g;
3145
+ function substitutions(segment) {
3146
+ const found = [];
3147
+ const inner = /[$<>]\(([^()]*)\)|`([^`]*)`/g;
3148
+ let s = segment.replace(ARITHMETIC, "_");
3149
+ for (let level = 0; ; level++) {
3150
+ const next = s.replace(inner, (_, a, b) => {
3151
+ found.push(a ?? b ?? "");
3152
+ return "_";
3153
+ });
3154
+ if (next === s) break;
3155
+ if (level >= MAX_STEPS) return void 0;
3156
+ s = next;
3157
+ }
3158
+ return /[$<>]\(/.test(s) ? void 0 : found;
3159
+ }
3160
+ function staysNative(segment, cwd) {
3161
+ if (WRITES_FILE.test(segment)) return true;
3162
+ if (GLUED_WRITE.test(segment.replace(/'[^']*'|"(?:[^"\\]|\\.)*"/g, "''"))) return true;
3163
+ return pipeStages(segment).some((stage, i) => {
3164
+ const plain = stage.replace(SHELL_WORDS, "");
3165
+ if (plain.includes("GIT_") && GIT_ENV.test(plain)) return true;
3166
+ const cmd = stripRunners(plain).replace(/^\S*\//, "");
3167
+ const [, quoted = "", args = ""] = /^(\S+)([\s\S]*)$/.exec(cmd) ?? [];
3168
+ const tool = quoted.replace(/['"\\]/g, "");
3169
+ const named = tool + args;
3170
+ const bare = /^(npm|pnpm|yarn)$/.test(tool) ? `${tool} ${afterOptions(tool, args).join(" ")}` : named;
3171
+ 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;
3172
+ });
3173
+ }
3174
+ 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-])/;
3175
+ var TOOL_READS = {
3176
+ gh: (a) => /^\s+(?:api|(?:pr|issue|run|search|repo|release)\s+(?:list|view|diff|checks|status))\b/.test(a),
3177
+ npm: (a) => /^(?:test|t|run|run-script|ls|ll|list|outdated|view|show|info|why|explain|audit|root|bin|prefix|help)$/.test(
3178
+ afterOptions("npm", a)[0] ?? ""
3179
+ ),
3180
+ pnpm: (a) => afterOptions("pnpm", a).length > 0,
3181
+ yarn: (a) => afterOptions("yarn", a).length > 0
3182
+ // bare `yarn` installs
3183
+ };
3184
+ function readsOnly(segment, cwd) {
3185
+ return pipeStages(segment).every((stage) => {
3186
+ const cmd = stripRunners(stage.replace(SHELL_WORDS, "")).replace(/^\S*\//, "");
3187
+ const [, tool = "", args = ""] = /^(\S+)([\s\S]*)$/.exec(cmd) ?? [];
3188
+ if (/(?:^|\s)--output(?=[\s=]|$)/.test(args) || tool === "sort" && /\s-\w*o\b/.test(args))
3189
+ return false;
3190
+ if (CHANGES.has(tool)) return TOOL_READS[tool]?.(args) ?? !CHANGES.get(tool)(args, cwd);
3191
+ return READ_ONLY.test(cmd);
3192
+ });
2620
3193
  }
2621
3194
  var READ_LIMIT = 200 * 1024;
2622
3195
  var BIG_TEXT_LIMIT = 20 * 1024;
@@ -2665,13 +3238,13 @@ function fileOperands(stage, cwd) {
2665
3238
  }
2666
3239
  files.push(t);
2667
3240
  }
2668
- if (!files.length) return void 0;
3241
+ if (!files.length || files.length > MAX_OPERANDS) return void 0;
2669
3242
  const out = [];
2670
3243
  for (let f of files) {
2671
3244
  if (/[*?[\]{}$`\\]/.test(f)) return void 0;
2672
3245
  f = f.replace(/^['"]|['"]$/g, "");
2673
3246
  if (f.startsWith("~/")) f = join7(homedir2(), f.slice(2));
2674
- out.push(resolve2(cwd, f));
3247
+ out.push(resolve3(cwd, f));
2675
3248
  }
2676
3249
  return out;
2677
3250
  }
@@ -2709,66 +3282,99 @@ function firstStageBound(first, cwd) {
2709
3282
  const bytes = stageBytes(first, cwd);
2710
3283
  if (bytes === void 0) return void 0;
2711
3284
  if (bytes <= READ_LIMIT) return true;
2712
- return { rule: "big-read", text: `reads about ${Math.round(bytes / 1024)} KB` };
3285
+ return {
3286
+ rule: "big-read",
3287
+ text: `reads about ${Math.round(bytes / 1024)} KB`
3288
+ };
2713
3289
  }
2714
3290
  return void 0;
2715
3291
  }
3292
+ 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/;
3293
+ 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/;
3294
+ var UNBOUNDED_FILTERS = /^\s*(grep|rg|sort|uniq|jq|less)\b/;
2716
3295
  function heavySegment(segment, cwd) {
2717
3296
  if (TINY_BASH.test(segment)) return void 0;
2718
3297
  const stages = pipeStages(segment);
2719
3298
  const first = stages[0];
2720
3299
  if (!first) return void 0;
2721
3300
  if (stages.length > 1 && bounds(stages[stages.length - 1])) return void 0;
3301
+ const rest = stages.slice(1);
3302
+ if (!rest.every((s) => FILTERS.test(s))) return void 0;
2722
3303
  const bound = firstStageBound(first, cwd);
2723
3304
  if (bound === true) return void 0;
2724
3305
  if (bound) return bound;
2725
3306
  if (HEAVY_BASH.test(first)) return { rule: "heavy", text: "usually prints a lot" };
2726
- if (stages.slice(1).some((s) => /^\s*(grep|rg|sort|uniq|jq|less)\b/.test(s)))
2727
- return { rule: "pipe", text: "streams an unbounded output through a filter" };
3307
+ if (READ_SOURCE.test(first) && rest.some((s) => UNBOUNDED_FILTERS.test(s)))
3308
+ return {
3309
+ rule: "pipe",
3310
+ text: "streams an unbounded output through a filter"
3311
+ };
2728
3312
  return void 0;
2729
3313
  }
2730
3314
  function headOf(segment, n = 60) {
2731
3315
  const one = segment.replace(/\s+/g, " ").trim();
2732
3316
  return one.length > n ? `${one.slice(0, n - 1)}\u2026` : one;
2733
3317
  }
2734
- function bashSuggestion(raw, cwd) {
3318
+ function bashSuggestion(raw, root, cwd) {
3319
+ if (raw.length > MAX_RAW) return void 0;
2735
3320
  const cmd = withoutHeredocs(raw);
2736
3321
  if (!cmd.trim()) return void 0;
3322
+ if (cmd.length > MAX_CLASSIFIED) return void 0;
2737
3323
  const segments = commandSegments(cmd);
2738
- if (segments.some(mutates)) return void 0;
3324
+ if (segments.length > MAX_SEGMENTS) return void 0;
3325
+ const subs = substitutions(cmd);
3326
+ if (!subs) return void 0;
3327
+ if ([...segments, ...subs.flatMap(commandSegments)].some((s) => staysNative(s, cwd)))
3328
+ return void 0;
3329
+ if (segments.length > 1 && !segments.every((s) => readsOnly(s, cwd))) return void 0;
2739
3330
  const history = historyCall(cmd);
2740
- if (history) {
3331
+ const dir = history && historyDir(segments, cwd);
3332
+ if (history && dir && !sameRepo(root, dir)) return void 0;
3333
+ if (history && dir && inRootRepo(root, dir)) {
2741
3334
  return {
2742
3335
  tool: "em_history",
2743
3336
  rule: "history",
2744
3337
  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`
2745
3338
  };
2746
3339
  }
3340
+ let at = cwd;
3341
+ let steps = 0;
2747
3342
  for (const segment of segments) {
2748
- const heavy = heavySegment(segment, cwd);
2749
- if (!heavy) continue;
2750
- return {
2751
- tool: "em_execute",
2752
- rule: heavy.rule,
2753
- reason: `\`${headOf(segment)}\` ${heavy.text}; em_execute {language:"shell", code, intent} keeps the output out of context and returns only matching sections`
2754
- };
3343
+ const heavy = heavySegment(segment, at ?? cwd);
3344
+ if (heavy)
3345
+ return {
3346
+ tool: "em_execute",
3347
+ rule: heavy.rule,
3348
+ reason: `\`${headOf(segment)}\` ${heavy.text}; em_execute {language:"shell", code, intent} keeps the output out of context and returns only matching sections`
3349
+ };
3350
+ const cd = CD.exec(segment);
3351
+ if (cd) at = ++steps > MAX_STEPS ? void 0 : at && changeDir(at, cd[1]);
2755
3352
  }
2756
3353
  return void 0;
2757
3354
  }
3355
+ function executeFileTakes(root, fp) {
3356
+ try {
3357
+ const real = realpathSync3(root);
3358
+ confinePath(real, resolve3(root, fp), loadPermissionRules(real));
3359
+ return true;
3360
+ } catch {
3361
+ return false;
3362
+ }
3363
+ }
2758
3364
  function suggest(toolName, input, root, extra = {}) {
2759
3365
  switch (toolName) {
2760
3366
  case "Bash":
2761
- return bashSuggestion(String(input.command ?? ""), extra.cwd ?? root);
3367
+ return bashSuggestion(String(input.command ?? ""), root, extra.cwd ?? root);
2762
3368
  case "Read": {
2763
3369
  const fp = String(input.file_path ?? "");
2764
3370
  if (!fp) return void 0;
3371
+ if (input.offset != null || input.limit != null) return void 0;
2765
3372
  const ext = extname(fp).toLowerCase();
2766
3373
  if (VISUAL_EXT.has(ext)) return void 0;
2767
3374
  try {
2768
- const abs = resolve2(root, fp);
2769
- const st = statSync3(abs);
3375
+ const st = statSync3(resolve3(root, fp));
2770
3376
  if (st.size > 200 * 1024 || BIG_TEXT_EXT.has(ext) && st.size > 20 * 1024) {
2771
- if (!checkReadPath(loadPermissionRules(root), abs).allowed) return void 0;
3377
+ if (!executeFileTakes(root, fp)) return void 0;
2772
3378
  return {
2773
3379
  tool: "em_execute_file",
2774
3380
  rule: "big-read",
@@ -2835,12 +3441,12 @@ function subagentPrefix(shell) {
2835
3441
  }
2836
3442
  function projectRel(ctx, fp) {
2837
3443
  if (!fp) return void 0;
2838
- let abs = resolve2(ctx.root, fp);
3444
+ let abs = resolve3(ctx.root, fp);
2839
3445
  try {
2840
- abs = realpathSync2(abs);
3446
+ abs = realpathSync3(abs);
2841
3447
  } catch {
2842
3448
  }
2843
- const rel = relative(ctx.root, abs).split("\\").join("/");
3449
+ const rel = relative2(ctx.root, abs).split("\\").join("/");
2844
3450
  return rel.startsWith("..") ? void 0 : rel;
2845
3451
  }
2846
3452
  function graphContext(ctx, input) {
@@ -2871,7 +3477,11 @@ function fileKnowledge(ctx, input) {
2871
3477
  const repeatAfter = ctx.config.recall.repeatAfter;
2872
3478
  const edit = EDIT_TOOLS.has(input.tool_name ?? "");
2873
3479
  const results = [
2874
- memoriesForFile(db, rel, { sessionId, symbols: fileSymbols(db, rel), repeatAfter }),
3480
+ memoriesForFile(db, rel, {
3481
+ sessionId,
3482
+ symbols: fileSymbols(db, rel),
3483
+ repeatAfter
3484
+ }),
2875
3485
  fileContext(db, rel, { sessionId, edit, repeatAfter })
2876
3486
  ];
2877
3487
  const shown = results.filter((r) => r.text);
@@ -2888,12 +3498,14 @@ function fileKnowledge(ctx, input) {
2888
3498
  return void 0;
2889
3499
  }
2890
3500
  }
2891
- function commitFiles(root, cmd) {
2892
- const list = (args) => git(root, args, { allowFail: true }).split("\n").map((l) => l.trim()).filter(Boolean);
3501
+ function commitFiles(root, cwd, cmd) {
3502
+ const top = git(cwd, ["rev-parse", "--show-toplevel"], { allowFail: true }).trim();
3503
+ if (!top) return [];
3504
+ const list = (args) => git(cwd, [...args, "-z"], { allowFail: true }).split("\0").filter(Boolean);
2893
3505
  const files = new Set(list(["diff", "--cached", "--name-only"]));
2894
3506
  if (/\s(-[a-zA-Z]*a[a-zA-Z]*|--all)\b/.test(cmd))
2895
3507
  for (const f of list(["diff", "--name-only"])) files.add(f);
2896
- return [...files];
3508
+ return [...files].map((f) => relative2(root, join7(top, f)));
2897
3509
  }
2898
3510
  function commitKnowledge(ctx, input) {
2899
3511
  if (!ctx.config.recall.onCommit || input.tool_name !== "Bash") return void 0;
@@ -2901,7 +3513,7 @@ function commitKnowledge(ctx, input) {
2901
3513
  if (!/(^|[;&|]\s*)git\s+commit\b/.test(cmd)) return void 0;
2902
3514
  try {
2903
3515
  const db = getStorage(ctx, { vectors: false });
2904
- return commitContext(db, commitFiles(ctx.root, cmd)) || void 0;
3516
+ return commitContext(db, commitFiles(ctx.root, input.cwd || ctx.root, cmd)) || void 0;
2905
3517
  } catch {
2906
3518
  return void 0;
2907
3519
  }
@@ -2921,7 +3533,11 @@ async function webKnowledge(ctx, input) {
2921
3533
  if (!text.trim()) return void 0;
2922
3534
  try {
2923
3535
  const db = getStorage(ctx, { vectors: false });
2924
- const r = await recallForText(db, text, { maxMemories: 0, maxDocs: 3, path: "web" });
3536
+ const r = await recallForText(db, text, {
3537
+ maxMemories: 0,
3538
+ maxDocs: 3,
3539
+ path: "web"
3540
+ });
2925
3541
  if (!r.text) return void 0;
2926
3542
  const pages = r.text.split("\n").filter((l) => l.startsWith("- docs:"));
2927
3543
  return [
@@ -2939,7 +3555,12 @@ function joinHints(...hints) {
2939
3555
  function withContext(out, extra) {
2940
3556
  if (!extra) return out;
2941
3557
  if (!out)
2942
- return { hookSpecificOutput: { hookEventName: "PreToolUse", additionalContext: extra } };
3558
+ return {
3559
+ hookSpecificOutput: {
3560
+ hookEventName: "PreToolUse",
3561
+ additionalContext: extra
3562
+ }
3563
+ };
2943
3564
  const cur = out.hookSpecificOutput?.additionalContext;
2944
3565
  return {
2945
3566
  ...out,
@@ -2954,14 +3575,14 @@ ${extra}` : extra
2954
3575
  var preToolUse = async (input) => {
2955
3576
  if (!input.tool_name || !input.tool_input) return void 0;
2956
3577
  if (isSwarmTool(input.tool_name)) return swarmStampCaller(input);
2957
- const ctx = getContext(input.cwd);
3578
+ const ctx = sessionContext(input.cwd);
2958
3579
  if (input.agent_id) swarmTouch(ctx, input);
2959
3580
  const spawn = swarmPreAgent(ctx, input);
2960
3581
  if (spawn?.kind === "deny") return spawn.output;
2961
3582
  const out = await route(ctx, input, spawn?.kind === "worker");
2962
3583
  if (!ctx.config.adjudicator.enabled || input.client === "cursor") return out;
2963
3584
  try {
2964
- const { afterRouting } = await import("./hook-hooks-5U2AI6W7.js");
3585
+ const { afterRouting } = await import("./hook-hooks-YGXLOZ6H.js");
2965
3586
  return await afterRouting(ctx, input, spawn, out);
2966
3587
  } catch {
2967
3588
  return out;
@@ -2981,7 +3602,10 @@ async function route(ctx, input, swarmWorker) {
2981
3602
  const graph = graphContext(ctx, input);
2982
3603
  const hint = joinHints(graph, memo);
2983
3604
  const symbol = input.tool_name === "Grep" && graph ? literalOf(String(input.tool_input.pattern ?? "")) : void 0;
2984
- const s = suggest(input.tool_name, input.tool_input, ctx.root, { symbol, cwd: input.cwd });
3605
+ const s = suggest(input.tool_name, input.tool_input, ctx.root, {
3606
+ symbol,
3607
+ cwd: input.cwd
3608
+ });
2985
3609
  const record = routingRecorder(ctx, input, s);
2986
3610
  if (!s) {
2987
3611
  record("allow");
@@ -3101,12 +3725,12 @@ function annotate(s, note = "") {
3101
3725
 
3102
3726
  // src/hooks/session-start.ts
3103
3727
  init_esm_shims();
3104
- import { appendFileSync, existsSync as existsSync6, readdirSync as readdirSync4 } from "fs";
3728
+ import { appendFileSync, existsSync as existsSync8, readdirSync as readdirSync4 } from "fs";
3105
3729
  import { join as join9 } from "path";
3106
3730
 
3107
3731
  // src/session/current.ts
3108
3732
  init_esm_shims();
3109
- import { readFileSync as readFileSync5, statSync as statSync4, writeFileSync as writeFileSync4 } from "fs";
3733
+ import { readFileSync as readFileSync6, statSync as statSync4, writeFileSync as writeFileSync4 } from "fs";
3110
3734
  import { join as join8 } from "path";
3111
3735
  function file(ctx) {
3112
3736
  return join8(projectDir(ctx.id), "current-session");
@@ -3143,7 +3767,7 @@ function routingGuidance(mode) {
3143
3767
  ].join("\n");
3144
3768
  }
3145
3769
  var sessionStart = async (input) => {
3146
- const ctx = getContext(input.cwd);
3770
+ const ctx = sessionContext(input.cwd);
3147
3771
  const sessionId = input.session_id || "unknown";
3148
3772
  setCurrentSession(ctx, sessionId);
3149
3773
  const db = getStorage(ctx, { vectors: false });
@@ -3198,7 +3822,7 @@ ${guide}`);
3198
3822
  if (swarm) parts.push(swarm);
3199
3823
  if (ctx.config.adjudicator.enabled && input.client !== "cursor") {
3200
3824
  try {
3201
- const { onSessionStart } = await import("./hook-hooks-5U2AI6W7.js");
3825
+ const { onSessionStart } = await import("./hook-hooks-YGXLOZ6H.js");
3202
3826
  const line2 = onSessionStart(ctx, db, input);
3203
3827
  if (line2) parts.push(line2);
3204
3828
  } catch {
@@ -3220,7 +3844,7 @@ ${guide}`);
3220
3844
  }
3221
3845
  try {
3222
3846
  const onboarding = join9(memoriesDir(ctx), "onboarding");
3223
- const onboarded = existsSync6(onboarding) && readdirSync4(onboarding).some((f) => f.endsWith(".md"));
3847
+ const onboarded = existsSync8(onboarding) && readdirSync4(onboarding).some((f) => f.endsWith(".md"));
3224
3848
  if (!onboarded)
3225
3849
  parts.push(
3226
3850
  "No onboarding memories yet: run /eminiar:em-onboard once so future sessions start with the project's structure, build and test commands."
@@ -3234,7 +3858,7 @@ ${guide}`);
3234
3858
  } catch {
3235
3859
  }
3236
3860
  try {
3237
- if (existsSync6(join9(ctx.root, ".git")) && !db.metaGet("history_files"))
3861
+ if (existsSync8(join9(ctx.root, ".git")) && !db.metaGet("history_files"))
3238
3862
  parts.push(
3239
3863
  '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.'
3240
3864
  );
@@ -3279,8 +3903,8 @@ function ranLate() {
3279
3903
  }
3280
3904
  async function runWithin(handler, input, ms) {
3281
3905
  let timer;
3282
- const deadline = new Promise((resolve3) => {
3283
- timer = setTimeout(() => resolve3("late"), ms);
3906
+ const deadline = new Promise((resolve4) => {
3907
+ timer = setTimeout(() => resolve4("late"), ms);
3284
3908
  });
3285
3909
  try {
3286
3910
  const r = await Promise.race([handler(input), deadline]);
@@ -3335,7 +3959,7 @@ function failClosed(input, err) {
3335
3959
  if (input.hook_event_name !== "PreToolUse" || input.client === "cursor" || input.tool_name !== "Agent" && input.tool_name !== "Task" || !rawEnforced({ cwd: input.cwd }))
3336
3960
  return void 0;
3337
3961
  try {
3338
- swarmAgentReturned(getContext(input.cwd), input);
3962
+ swarmAgentReturned(sessionContext(input.cwd), input);
3339
3963
  } catch {
3340
3964
  }
3341
3965
  return JSON.stringify({
@@ -3367,7 +3991,7 @@ async function runCursor(parsed, budgetMs2) {
3367
3991
  }
3368
3992
  let root = input.cwd;
3369
3993
  try {
3370
- root = getContext(input.cwd).root;
3994
+ root = sessionContext(input.cwd).root;
3371
3995
  } catch {
3372
3996
  }
3373
3997
  try {
@@ -3391,7 +4015,7 @@ async function main() {
3391
4015
  return;
3392
4016
  }
3393
4017
  if (process.argv.includes("--adjudicate")) {
3394
- const { ASYNC_JOB_MAX_MS, runAsyncJob } = await import("./hook-async-3TLK23ZY.js");
4018
+ const { ASYNC_JOB_MAX_MS, runAsyncJob } = await import("./hook-async-7GK6H3OO.js");
3395
4019
  setTimeout(() => process.exit(0), ASYNC_JOB_MAX_MS).unref();
3396
4020
  await runAsyncJob(raw, { log: (line2) => hookLog("info", line2) });
3397
4021
  return;