eminiar 0.13.2 → 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/.claude-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/README.md +9 -6
- package/dist/hook.js +669 -48
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eminiar",
|
|
3
3
|
"displayName": "Eminiar",
|
|
4
|
-
"version": "0.13.
|
|
4
|
+
"version": "0.13.3",
|
|
5
5
|
"description": "Local-first context, memory and knowledge graph for Claude Code: sandboxed execution, FTS5 + vector search, session continuity, code graph.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Ryan Dick"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eminiar",
|
|
3
3
|
"displayName": "Eminiar",
|
|
4
|
-
"version": "0.13.
|
|
4
|
+
"version": "0.13.3",
|
|
5
5
|
"description": "Local-first context, memory and knowledge graph for coding agents: sandboxed execution, FTS5 + vector search, session continuity, code graph.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Ryan Dick"
|
package/README.md
CHANGED
|
@@ -60,14 +60,17 @@ Everything runs locally in one SQLite database per project.
|
|
|
60
60
|
|
|
61
61
|
`em_execute`, `em_batch_execute` and `em_execute_file` run shell, JavaScript and TypeScript, plus
|
|
62
62
|
Python, Ruby, Go, PHP, Perl, R, Elixir, Rust and C# when they are installed. Output over 5 KB is
|
|
63
|
-
indexed. Claude gets back only the sections that match its `intent`.
|
|
64
|
-
Code `Bash` deny rules, and `em_execute_file` refuses a `path` that
|
|
65
|
-
matches
|
|
63
|
+
indexed. Claude gets back only the sections that match its `intent`. Shell snippets are checked
|
|
64
|
+
against your Claude Code `Bash` deny and ask rules, and `em_execute_file` refuses a `path` that
|
|
65
|
+
one of your `Read` deny rules matches. Snippets in other languages are not rule-checked, and
|
|
66
|
+
the sandbox is not an isolation boundary
|
|
67
|
+
([what is enforced](docs/how-it-works.md#what-the-sandbox-enforces)).
|
|
66
68
|
|
|
67
69
|
Hooks route noisy commands for Claude. A Bash line is judged one command at a time: output that
|
|
68
|
-
is already bounded (`| head`, `| wc`, `grep -l`, `cat` of a small file) and commands
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
is already bounded (`| head`, `| wc`, `grep -l`, `cat` of a small file) and commands it
|
|
71
|
+
recognises as changing something (files, a cluster, a remote API) stay native. `git log`,
|
|
72
|
+
`git blame` and `git show` go to `em_history`, and a `Grep` for a symbol the graph knows goes to
|
|
73
|
+
`em_symbols`. `em_stats` shows each routing decision and the rule
|
|
71
74
|
behind it, so you can see what was sent where.
|
|
72
75
|
|
|
73
76
|
### 🔎 Hybrid search — keyword and meaning together
|
package/dist/hook.js
CHANGED
|
@@ -2197,9 +2197,9 @@ var preCompact = (input) => {
|
|
|
2197
2197
|
|
|
2198
2198
|
// src/hooks/routing.ts
|
|
2199
2199
|
init_esm_shims();
|
|
2200
|
-
import { realpathSync as
|
|
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
|
|
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();
|
|
@@ -2364,6 +2364,36 @@ function checkReadPath(rules, abs) {
|
|
|
2364
2364
|
return more.length ? checkRead(rules, more) : asWritten;
|
|
2365
2365
|
}
|
|
2366
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));
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2367
2397
|
// src/session/routing-stats.ts
|
|
2368
2398
|
init_esm_shims();
|
|
2369
2399
|
var ROUTING_CATEGORY = "routing";
|
|
@@ -2386,7 +2416,7 @@ function recordRouting(db, sessionId, e) {
|
|
|
2386
2416
|
|
|
2387
2417
|
// src/session/server-alive.ts
|
|
2388
2418
|
init_esm_shims();
|
|
2389
|
-
import { existsSync as
|
|
2419
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync as readdirSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
2390
2420
|
import { join as join4 } from "path";
|
|
2391
2421
|
function markersDir(ctx) {
|
|
2392
2422
|
return join4(projectDir(ctx.id), "servers");
|
|
@@ -2401,7 +2431,7 @@ function processAlive(pid) {
|
|
|
2401
2431
|
}
|
|
2402
2432
|
function liveServers(ctx) {
|
|
2403
2433
|
const dir = markersDir(ctx);
|
|
2404
|
-
if (!
|
|
2434
|
+
if (!existsSync5(dir)) return [];
|
|
2405
2435
|
const live = [];
|
|
2406
2436
|
for (const name of readdirSync2(dir)) {
|
|
2407
2437
|
const pid = Number(name);
|
|
@@ -2424,7 +2454,7 @@ import { join as join6 } from "path";
|
|
|
2424
2454
|
init_esm_shims();
|
|
2425
2455
|
import {
|
|
2426
2456
|
copyFileSync,
|
|
2427
|
-
existsSync as
|
|
2457
|
+
existsSync as existsSync6,
|
|
2428
2458
|
mkdirSync as mkdirSync3,
|
|
2429
2459
|
readdirSync as readdirSync3,
|
|
2430
2460
|
readFileSync as readFileSync3,
|
|
@@ -2469,7 +2499,11 @@ function literalOf(pattern) {
|
|
|
2469
2499
|
function symbolHints(db, pattern) {
|
|
2470
2500
|
const lit = literalOf(pattern);
|
|
2471
2501
|
if (!lit) return void 0;
|
|
2472
|
-
const nodes = db.graphFindNodes({
|
|
2502
|
+
const nodes = db.graphFindNodes({
|
|
2503
|
+
query: lit,
|
|
2504
|
+
kinds: SYMBOL_KINDS,
|
|
2505
|
+
limit: 5
|
|
2506
|
+
});
|
|
2473
2507
|
if (!nodes.length) return void 0;
|
|
2474
2508
|
const list = nodes.map((n) => `${n.kind} ${n.name} (${n.path}:${n.start_line ?? "?"})`).join("; ");
|
|
2475
2509
|
return `eminiar graph: symbols matching "${lit}": ${list}. em_symbols {name_path, include_body: true} reads one; {mode:"references"} lists its callers.`;
|
|
@@ -2479,13 +2513,13 @@ function coverageHint(db, rel) {
|
|
|
2479
2513
|
if (!row || row.status === "indexed") return void 0;
|
|
2480
2514
|
return `eminiar graph: ${rel} is ${row.status}${row.reason ? ` (${row.reason})` : ""}; graph answers about it may be incomplete.`;
|
|
2481
2515
|
}
|
|
2482
|
-
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([^;&|]*)/;
|
|
2483
2517
|
var ONE_COMMIT = /(^|\s)(-1|-n\s*1|-n1|--max-count[= ]1)(?=\s|$)/;
|
|
2484
2518
|
function historyCall(cmd) {
|
|
2485
2519
|
const m = GIT_HISTORY.exec(cmd);
|
|
2486
2520
|
if (!m) return void 0;
|
|
2487
|
-
const sub = m[
|
|
2488
|
-
const rest = (m[
|
|
2521
|
+
const sub = m[2];
|
|
2522
|
+
const rest = (m[3] ?? "").trim();
|
|
2489
2523
|
const args = rest.split(/\s+/).filter(Boolean);
|
|
2490
2524
|
const q = (s) => JSON.stringify(s.replace(/^['"]|['"]$/g, ""));
|
|
2491
2525
|
const positional = (a) => !a.startsWith("-");
|
|
@@ -2520,6 +2554,66 @@ function historyCall(cmd) {
|
|
|
2520
2554
|
if (sub === "shortlog") return `{action:"timeline"}`;
|
|
2521
2555
|
return `{action:"overview"}`;
|
|
2522
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
|
+
}
|
|
2523
2617
|
function docsSources(root) {
|
|
2524
2618
|
try {
|
|
2525
2619
|
return readManifestAt(join7(root, ".eminiar", "docs.json"), root).sources;
|
|
@@ -2562,7 +2656,34 @@ var BIG_TEXT_EXT = /* @__PURE__ */ new Set([
|
|
|
2562
2656
|
]);
|
|
2563
2657
|
var VISUAL_EXT = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".pdf"]);
|
|
2564
2658
|
function withoutHeredocs(cmd) {
|
|
2565
|
-
|
|
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);
|
|
2566
2687
|
}
|
|
2567
2688
|
function splitUnquoted(s, sep2) {
|
|
2568
2689
|
const out = [];
|
|
@@ -2570,13 +2691,23 @@ function splitUnquoted(s, sep2) {
|
|
|
2570
2691
|
let quote;
|
|
2571
2692
|
for (let i = 0; i < s.length; i++) {
|
|
2572
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
|
+
}
|
|
2573
2704
|
if (quote) {
|
|
2574
2705
|
cur += ch;
|
|
2575
|
-
if (ch === quote
|
|
2706
|
+
if (ch === quote[quote.length - 1]) quote = void 0;
|
|
2576
2707
|
continue;
|
|
2577
2708
|
}
|
|
2578
2709
|
if (ch === "'" || ch === '"') {
|
|
2579
|
-
quote = ch;
|
|
2710
|
+
quote = ch === "'" && s[i - 1] === "$" ? "$'" : ch;
|
|
2580
2711
|
cur += ch;
|
|
2581
2712
|
continue;
|
|
2582
2713
|
}
|
|
@@ -2597,9 +2728,44 @@ function commandSegments(cmd) {
|
|
|
2597
2728
|
const ch = s[i];
|
|
2598
2729
|
if (ch === ";" || ch === "\n") return 1;
|
|
2599
2730
|
if ((ch === "&" || ch === "|") && s[i + 1] === ch) return 2;
|
|
2731
|
+
if (ch === "&" && !/[&<>|]/.test(s[i - 1] ?? "") && !/[&>]/.test(s[i + 1] ?? "")) return 1;
|
|
2600
2732
|
return 0;
|
|
2601
2733
|
});
|
|
2602
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
|
+
}
|
|
2603
2769
|
function pipeStages(segment) {
|
|
2604
2770
|
return splitUnquoted(segment, (s, i) => s[i] === "|" && s[i + 1] !== "|" ? 1 : 0);
|
|
2605
2771
|
}
|
|
@@ -2613,10 +2779,416 @@ function bounds(stage) {
|
|
|
2613
2779
|
if (!/^\s*(head|tail)\b/.test(stage) || /(^|\s)-c\b|--bytes\b/.test(stage)) return false;
|
|
2614
2780
|
return headLines(stage) <= BOUNDED_LINES;
|
|
2615
2781
|
}
|
|
2616
|
-
var
|
|
2617
|
-
var
|
|
2618
|
-
|
|
2619
|
-
|
|
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
|
+
});
|
|
2620
3192
|
}
|
|
2621
3193
|
var READ_LIMIT = 200 * 1024;
|
|
2622
3194
|
var BIG_TEXT_LIMIT = 20 * 1024;
|
|
@@ -2665,13 +3237,13 @@ function fileOperands(stage, cwd) {
|
|
|
2665
3237
|
}
|
|
2666
3238
|
files.push(t);
|
|
2667
3239
|
}
|
|
2668
|
-
if (!files.length) return void 0;
|
|
3240
|
+
if (!files.length || files.length > MAX_OPERANDS) return void 0;
|
|
2669
3241
|
const out = [];
|
|
2670
3242
|
for (let f of files) {
|
|
2671
3243
|
if (/[*?[\]{}$`\\]/.test(f)) return void 0;
|
|
2672
3244
|
f = f.replace(/^['"]|['"]$/g, "");
|
|
2673
3245
|
if (f.startsWith("~/")) f = join7(homedir2(), f.slice(2));
|
|
2674
|
-
out.push(
|
|
3246
|
+
out.push(resolve3(cwd, f));
|
|
2675
3247
|
}
|
|
2676
3248
|
return out;
|
|
2677
3249
|
}
|
|
@@ -2709,66 +3281,99 @@ function firstStageBound(first, cwd) {
|
|
|
2709
3281
|
const bytes = stageBytes(first, cwd);
|
|
2710
3282
|
if (bytes === void 0) return void 0;
|
|
2711
3283
|
if (bytes <= READ_LIMIT) return true;
|
|
2712
|
-
return {
|
|
3284
|
+
return {
|
|
3285
|
+
rule: "big-read",
|
|
3286
|
+
text: `reads about ${Math.round(bytes / 1024)} KB`
|
|
3287
|
+
};
|
|
2713
3288
|
}
|
|
2714
3289
|
return void 0;
|
|
2715
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/;
|
|
2716
3294
|
function heavySegment(segment, cwd) {
|
|
2717
3295
|
if (TINY_BASH.test(segment)) return void 0;
|
|
2718
3296
|
const stages = pipeStages(segment);
|
|
2719
3297
|
const first = stages[0];
|
|
2720
3298
|
if (!first) return void 0;
|
|
2721
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;
|
|
2722
3302
|
const bound = firstStageBound(first, cwd);
|
|
2723
3303
|
if (bound === true) return void 0;
|
|
2724
3304
|
if (bound) return bound;
|
|
2725
3305
|
if (HEAVY_BASH.test(first)) return { rule: "heavy", text: "usually prints a lot" };
|
|
2726
|
-
if (
|
|
2727
|
-
return {
|
|
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
|
+
};
|
|
2728
3311
|
return void 0;
|
|
2729
3312
|
}
|
|
2730
3313
|
function headOf(segment, n = 60) {
|
|
2731
3314
|
const one = segment.replace(/\s+/g, " ").trim();
|
|
2732
3315
|
return one.length > n ? `${one.slice(0, n - 1)}\u2026` : one;
|
|
2733
3316
|
}
|
|
2734
|
-
function bashSuggestion(raw, cwd) {
|
|
3317
|
+
function bashSuggestion(raw, root, cwd) {
|
|
3318
|
+
if (raw.length > MAX_RAW) return void 0;
|
|
2735
3319
|
const cmd = withoutHeredocs(raw);
|
|
2736
3320
|
if (!cmd.trim()) return void 0;
|
|
3321
|
+
if (cmd.length > MAX_CLASSIFIED) return void 0;
|
|
2737
3322
|
const segments = commandSegments(cmd);
|
|
2738
|
-
if (segments.
|
|
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;
|
|
2739
3329
|
const history = historyCall(cmd);
|
|
2740
|
-
|
|
3330
|
+
const dir = history && historyDir(segments, cwd);
|
|
3331
|
+
if (history && dir && !sameRepo(root, dir)) return void 0;
|
|
3332
|
+
if (history && dir && inRootRepo(root, dir)) {
|
|
2741
3333
|
return {
|
|
2742
3334
|
tool: "em_history",
|
|
2743
3335
|
rule: "history",
|
|
2744
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`
|
|
2745
3337
|
};
|
|
2746
3338
|
}
|
|
3339
|
+
let at = cwd;
|
|
3340
|
+
let steps = 0;
|
|
2747
3341
|
for (const segment of segments) {
|
|
2748
|
-
const heavy = heavySegment(segment, cwd);
|
|
2749
|
-
if (
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
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]);
|
|
2755
3351
|
}
|
|
2756
3352
|
return void 0;
|
|
2757
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
|
+
}
|
|
2758
3363
|
function suggest(toolName, input, root, extra = {}) {
|
|
2759
3364
|
switch (toolName) {
|
|
2760
3365
|
case "Bash":
|
|
2761
|
-
return bashSuggestion(String(input.command ?? ""), extra.cwd ?? root);
|
|
3366
|
+
return bashSuggestion(String(input.command ?? ""), root, extra.cwd ?? root);
|
|
2762
3367
|
case "Read": {
|
|
2763
3368
|
const fp = String(input.file_path ?? "");
|
|
2764
3369
|
if (!fp) return void 0;
|
|
3370
|
+
if (input.offset != null || input.limit != null) return void 0;
|
|
2765
3371
|
const ext = extname(fp).toLowerCase();
|
|
2766
3372
|
if (VISUAL_EXT.has(ext)) return void 0;
|
|
2767
3373
|
try {
|
|
2768
|
-
const
|
|
2769
|
-
const st = statSync3(abs);
|
|
3374
|
+
const st = statSync3(resolve3(root, fp));
|
|
2770
3375
|
if (st.size > 200 * 1024 || BIG_TEXT_EXT.has(ext) && st.size > 20 * 1024) {
|
|
2771
|
-
if (!
|
|
3376
|
+
if (!executeFileTakes(root, fp)) return void 0;
|
|
2772
3377
|
return {
|
|
2773
3378
|
tool: "em_execute_file",
|
|
2774
3379
|
rule: "big-read",
|
|
@@ -2835,12 +3440,12 @@ function subagentPrefix(shell) {
|
|
|
2835
3440
|
}
|
|
2836
3441
|
function projectRel(ctx, fp) {
|
|
2837
3442
|
if (!fp) return void 0;
|
|
2838
|
-
let abs =
|
|
3443
|
+
let abs = resolve3(ctx.root, fp);
|
|
2839
3444
|
try {
|
|
2840
|
-
abs =
|
|
3445
|
+
abs = realpathSync3(abs);
|
|
2841
3446
|
} catch {
|
|
2842
3447
|
}
|
|
2843
|
-
const rel =
|
|
3448
|
+
const rel = relative2(ctx.root, abs).split("\\").join("/");
|
|
2844
3449
|
return rel.startsWith("..") ? void 0 : rel;
|
|
2845
3450
|
}
|
|
2846
3451
|
function graphContext(ctx, input) {
|
|
@@ -2871,7 +3476,11 @@ function fileKnowledge(ctx, input) {
|
|
|
2871
3476
|
const repeatAfter = ctx.config.recall.repeatAfter;
|
|
2872
3477
|
const edit = EDIT_TOOLS.has(input.tool_name ?? "");
|
|
2873
3478
|
const results = [
|
|
2874
|
-
memoriesForFile(db, rel, {
|
|
3479
|
+
memoriesForFile(db, rel, {
|
|
3480
|
+
sessionId,
|
|
3481
|
+
symbols: fileSymbols(db, rel),
|
|
3482
|
+
repeatAfter
|
|
3483
|
+
}),
|
|
2875
3484
|
fileContext(db, rel, { sessionId, edit, repeatAfter })
|
|
2876
3485
|
];
|
|
2877
3486
|
const shown = results.filter((r) => r.text);
|
|
@@ -2921,7 +3530,11 @@ async function webKnowledge(ctx, input) {
|
|
|
2921
3530
|
if (!text.trim()) return void 0;
|
|
2922
3531
|
try {
|
|
2923
3532
|
const db = getStorage(ctx, { vectors: false });
|
|
2924
|
-
const r = await recallForText(db, text, {
|
|
3533
|
+
const r = await recallForText(db, text, {
|
|
3534
|
+
maxMemories: 0,
|
|
3535
|
+
maxDocs: 3,
|
|
3536
|
+
path: "web"
|
|
3537
|
+
});
|
|
2925
3538
|
if (!r.text) return void 0;
|
|
2926
3539
|
const pages = r.text.split("\n").filter((l) => l.startsWith("- docs:"));
|
|
2927
3540
|
return [
|
|
@@ -2939,7 +3552,12 @@ function joinHints(...hints) {
|
|
|
2939
3552
|
function withContext(out, extra) {
|
|
2940
3553
|
if (!extra) return out;
|
|
2941
3554
|
if (!out)
|
|
2942
|
-
return {
|
|
3555
|
+
return {
|
|
3556
|
+
hookSpecificOutput: {
|
|
3557
|
+
hookEventName: "PreToolUse",
|
|
3558
|
+
additionalContext: extra
|
|
3559
|
+
}
|
|
3560
|
+
};
|
|
2943
3561
|
const cur = out.hookSpecificOutput?.additionalContext;
|
|
2944
3562
|
return {
|
|
2945
3563
|
...out,
|
|
@@ -2981,7 +3599,10 @@ async function route(ctx, input, swarmWorker) {
|
|
|
2981
3599
|
const graph = graphContext(ctx, input);
|
|
2982
3600
|
const hint = joinHints(graph, memo);
|
|
2983
3601
|
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, {
|
|
3602
|
+
const s = suggest(input.tool_name, input.tool_input, ctx.root, {
|
|
3603
|
+
symbol,
|
|
3604
|
+
cwd: input.cwd
|
|
3605
|
+
});
|
|
2985
3606
|
const record = routingRecorder(ctx, input, s);
|
|
2986
3607
|
if (!s) {
|
|
2987
3608
|
record("allow");
|
|
@@ -3101,12 +3722,12 @@ function annotate(s, note = "") {
|
|
|
3101
3722
|
|
|
3102
3723
|
// src/hooks/session-start.ts
|
|
3103
3724
|
init_esm_shims();
|
|
3104
|
-
import { appendFileSync, existsSync as
|
|
3725
|
+
import { appendFileSync, existsSync as existsSync8, readdirSync as readdirSync4 } from "fs";
|
|
3105
3726
|
import { join as join9 } from "path";
|
|
3106
3727
|
|
|
3107
3728
|
// src/session/current.ts
|
|
3108
3729
|
init_esm_shims();
|
|
3109
|
-
import { readFileSync as
|
|
3730
|
+
import { readFileSync as readFileSync6, statSync as statSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
3110
3731
|
import { join as join8 } from "path";
|
|
3111
3732
|
function file(ctx) {
|
|
3112
3733
|
return join8(projectDir(ctx.id), "current-session");
|
|
@@ -3220,7 +3841,7 @@ ${guide}`);
|
|
|
3220
3841
|
}
|
|
3221
3842
|
try {
|
|
3222
3843
|
const onboarding = join9(memoriesDir(ctx), "onboarding");
|
|
3223
|
-
const onboarded =
|
|
3844
|
+
const onboarded = existsSync8(onboarding) && readdirSync4(onboarding).some((f) => f.endsWith(".md"));
|
|
3224
3845
|
if (!onboarded)
|
|
3225
3846
|
parts.push(
|
|
3226
3847
|
"No onboarding memories yet: run /eminiar:em-onboard once so future sessions start with the project's structure, build and test commands."
|
|
@@ -3234,7 +3855,7 @@ ${guide}`);
|
|
|
3234
3855
|
} catch {
|
|
3235
3856
|
}
|
|
3236
3857
|
try {
|
|
3237
|
-
if (
|
|
3858
|
+
if (existsSync8(join9(ctx.root, ".git")) && !db.metaGet("history_files"))
|
|
3238
3859
|
parts.push(
|
|
3239
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.'
|
|
3240
3861
|
);
|
|
@@ -3279,8 +3900,8 @@ function ranLate() {
|
|
|
3279
3900
|
}
|
|
3280
3901
|
async function runWithin(handler, input, ms) {
|
|
3281
3902
|
let timer;
|
|
3282
|
-
const deadline = new Promise((
|
|
3283
|
-
timer = setTimeout(() =>
|
|
3903
|
+
const deadline = new Promise((resolve4) => {
|
|
3904
|
+
timer = setTimeout(() => resolve4("late"), ms);
|
|
3284
3905
|
});
|
|
3285
3906
|
try {
|
|
3286
3907
|
const r = await Promise.race([handler(input), deadline]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eminiar",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.3",
|
|
4
4
|
"description": "Local-first context, memory and knowledge-graph plugin for Claude Code and Cursor. Sandboxed execution, FTS5 + vector search, session continuity, and a code graph — nothing leaves your machine.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|