eminiar 0.13.1 → 0.13.2

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.
@@ -4,12 +4,12 @@ import {
4
4
  parseJob,
5
5
  runAsyncJob,
6
6
  spawnAsyncJob
7
- } from "./hook-chunk-RYZAFUTT.js";
8
- import "./hook-chunk-BECSRJFD.js";
7
+ } from "./hook-chunk-MVSMFCKL.js";
8
+ import "./hook-chunk-ETYHCPPH.js";
9
9
  export {
10
10
  ASYNC_JOB_MAX_MS,
11
11
  parseJob,
12
12
  runAsyncJob,
13
13
  spawnAsyncJob
14
14
  };
15
- //# sourceMappingURL=hook-async-J3UKDO62.js.map
15
+ //# sourceMappingURL=hook-async-3TLK23ZY.js.map
@@ -20217,8 +20217,11 @@ var BaseConfigSchema = external_exports.object({
20217
20217
  backend: external_exports.enum(["tree-sitter"]).default("tree-sitter")
20218
20218
  }).prefault({}),
20219
20219
  memories: external_exports.object({
20220
- /** Project-relative directory of markdown memory files (`<topic>/<name>.md`); commit it. */
20221
- dir: external_exports.string().default(".eminiar/memories")
20220
+ /**
20221
+ * Project-relative directory of markdown memory files (`<topic>/<name>.md`); commit it.
20222
+ * Absolute paths and `..` segments are refused: reconcile repairs the headers of files there.
20223
+ */
20224
+ dir: external_exports.string().regex(PROJECT_RELATIVE_PATH_RE, "must be a path inside the project (no leading / or ..)").default(".eminiar/memories")
20222
20225
  }).prefault({}),
20223
20226
  recall: external_exports.object({
20224
20227
  /** Inject the memories and doc pages that match each prompt (UserPromptSubmit). */
@@ -20525,29 +20528,37 @@ function projectLayer(raw) {
20525
20528
  for (const k of Object.keys(adj)) if (projectKeeps(adj, k)) kept[k] = adj[k];
20526
20529
  return { ...ownJson(raw), adjudicator: kept };
20527
20530
  }
20528
- function withoutUnsafeDocsManifest(raw, source) {
20529
- const docs = raw.docs;
20530
- if (!isPlainObject2(docs) || !Object.hasOwn(docs, "manifest")) return raw;
20531
- const m = docs.manifest;
20532
- if (typeof m === "string" && PROJECT_RELATIVE_PATH_RE.test(m)) return raw;
20533
- process.stderr.write(
20534
- `eminiar: ignoring docs.manifest ${JSON.stringify(m)} in ${source}: it must be a path inside the project
20531
+ var PROJECT_PATH_KEYS = [
20532
+ ["docs", "manifest"],
20533
+ ["memories", "dir"]
20534
+ ];
20535
+ function withoutUnsafePaths(raw, source) {
20536
+ let out = raw;
20537
+ for (const [section, key] of PROJECT_PATH_KEYS) {
20538
+ const sec = out[section];
20539
+ if (!isPlainObject2(sec) || !Object.hasOwn(sec, key)) continue;
20540
+ const v = sec[key];
20541
+ if (typeof v === "string" && PROJECT_RELATIVE_PATH_RE.test(v)) continue;
20542
+ process.stderr.write(
20543
+ `eminiar: ignoring ${section}.${key} ${JSON.stringify(v)} in ${source}: it must be a path inside the project
20535
20544
  `
20536
- );
20537
- const { manifest: _dropped, ...rest } = ownJson(docs);
20538
- return { ...ownJson(raw), docs: rest };
20545
+ );
20546
+ const { [key]: _dropped, ...rest } = ownJson(sec);
20547
+ out = { ...ownJson(out), [section]: rest };
20548
+ }
20549
+ return out;
20539
20550
  }
20540
20551
  function personalLayer(projectRoot) {
20541
20552
  const file2 = paths().configFile;
20542
20553
  const raw = readJson(file2);
20543
20554
  const layer = !droppedProjectAdjudicatorKeys(raw).length || personalConfigTrusted(projectRoot, file2) ? raw : projectLayer(raw);
20544
- return withoutUnsafeDocsManifest(layer, file2);
20555
+ return withoutUnsafePaths(layer, file2);
20545
20556
  }
20546
20557
  function loadConfig(projectRoot) {
20547
20558
  const merged = deepMerge(
20548
20559
  deepMerge(
20549
20560
  personalLayer(projectRoot),
20550
- withoutUnsafeDocsManifest(
20561
+ withoutUnsafePaths(
20551
20562
  projectLayer(readJson(join2(projectRoot, "eminiar.config.json"))),
20552
20563
  "eminiar.config.json"
20553
20564
  )
@@ -24093,6 +24104,7 @@ export {
24093
24104
  paths,
24094
24105
  ensureDirs,
24095
24106
  projectDir,
24107
+ PROJECT_RELATIVE_PATH_RE,
24096
24108
  AdjudicatorConfigSchema,
24097
24109
  loadConfig,
24098
24110
  findProjectRoot,
@@ -24111,4 +24123,4 @@ export {
24111
24123
  classifyToolCall,
24112
24124
  classifyAssistant
24113
24125
  };
24114
- //# sourceMappingURL=hook-chunk-BECSRJFD.js.map
24126
+ //# sourceMappingURL=hook-chunk-ETYHCPPH.js.map
@@ -13,7 +13,7 @@ import {
13
13
  init_esm_shims,
14
14
  loadConfig,
15
15
  paths
16
- } from "./hook-chunk-BECSRJFD.js";
16
+ } from "./hook-chunk-ETYHCPPH.js";
17
17
 
18
18
  // src/adjudicator/async.ts
19
19
  init_esm_shims();
@@ -3777,4 +3777,4 @@ export {
3777
3777
  parseJob,
3778
3778
  runAsyncJob
3779
3779
  };
3780
- //# sourceMappingURL=hook-chunk-RYZAFUTT.js.map
3780
+ //# sourceMappingURL=hook-chunk-MVSMFCKL.js.map
@@ -1,5 +1,6 @@
1
1
  import { createRequire as __emCreateRequire } from "node:module"; const require = __emCreateRequire(import.meta.url);
2
2
  import {
3
+ PROJECT_RELATIVE_PATH_RE,
3
4
  TIERS,
4
5
  __commonJS,
5
6
  __require,
@@ -13,7 +14,7 @@ import {
13
14
  paths,
14
15
  resolveProjectRoot,
15
16
  tierOf
16
- } from "./hook-chunk-BECSRJFD.js";
17
+ } from "./hook-chunk-ETYHCPPH.js";
17
18
 
18
19
  // node_modules/fast-glob/out/utils/array.js
19
20
  var require_array = __commonJS({
@@ -5884,9 +5885,10 @@ function empty() {
5884
5885
  return { version: 1, sources: [] };
5885
5886
  }
5886
5887
  function normalize(raw) {
5887
- if (!raw || typeof raw !== "object") return { manifest: empty(), dropped: 0 };
5888
+ if (!raw || typeof raw !== "object") return { manifest: empty(), dropped: 0, outside: [] };
5888
5889
  const sources = Array.isArray(raw.sources) ? raw.sources : [];
5889
5890
  const out = [];
5891
+ const outside = [];
5890
5892
  const seen = /* @__PURE__ */ new Set();
5891
5893
  for (const s of sources) {
5892
5894
  if (!s || typeof s !== "object" || typeof s.name !== "string") continue;
@@ -5894,22 +5896,37 @@ function normalize(raw) {
5894
5896
  if (s.kind !== "url" && s.kind !== "path") continue;
5895
5897
  if (s.kind === "url" && !(Array.isArray(s.urls) && s.urls.length)) continue;
5896
5898
  if (s.kind === "path" && typeof s.path !== "string") continue;
5899
+ if (s.kind === "path" && !PROJECT_RELATIVE_PATH_RE.test(s.path)) {
5900
+ process.stderr.write(
5901
+ `eminiar: ignoring docs source ${JSON.stringify(s.name)}: its path ${JSON.stringify(s.path)} must be inside the project
5902
+ `
5903
+ );
5904
+ outside.push(s.name);
5905
+ continue;
5906
+ }
5897
5907
  seen.add(s.name);
5898
5908
  out.push({
5899
5909
  ...s,
5900
5910
  addedAt: typeof s.addedAt === "string" ? s.addedAt : (/* @__PURE__ */ new Date(0)).toISOString()
5901
5911
  });
5902
5912
  }
5903
- return { manifest: { version: 1, sources: out }, dropped: sources.length - out.length };
5913
+ return {
5914
+ manifest: { version: 1, sources: out },
5915
+ dropped: sources.length - out.length,
5916
+ outside
5917
+ };
5904
5918
  }
5905
- function lossyReason(raw, dropped) {
5919
+ function lossyReason(raw, dropped, outside, removing) {
5906
5920
  if (raw.version !== 1)
5907
5921
  return raw.version === void 0 ? "it has no version (this eminiar writes version 1)" : `it has version ${JSON.stringify(raw.version)}, and this eminiar only writes version 1`;
5908
5922
  const extra = Object.keys(raw).filter((k) => k !== "version" && k !== "sources");
5909
5923
  if (extra.length) return `it has keys this eminiar does not know (${extra.sort().join(", ")})`;
5910
5924
  if (Object.hasOwn(raw, "sources") && !Array.isArray(raw.sources))
5911
5925
  return "its sources is not an array";
5912
- if (dropped) return `${dropped} of its sources ${dropped === 1 ? "is" : "are"} not valid entries`;
5926
+ const rest = outside.filter((n) => n !== removing);
5927
+ const lost = dropped - (outside.length - rest.length);
5928
+ if (lost)
5929
+ return `${lost} of its sources ${lost === 1 ? "is" : "are"} not valid entries${rest.length ? ` (${rest.join(", ")}: path outside the project)` : ""}`;
5913
5930
  return void 0;
5914
5931
  }
5915
5932
  var MANIFEST_MAX_BYTES = 1024 * 1024;
@@ -5986,12 +6003,12 @@ function probeManifestAt(file, opts = {}) {
5986
6003
  }
5987
6004
  if (!raw || typeof raw !== "object" || Array.isArray(raw))
5988
6005
  return { state: "refused", reason: "it is not a JSON object" };
5989
- const { manifest, dropped } = normalize(raw);
6006
+ const { manifest, dropped, outside } = normalize(raw);
5990
6007
  if (opts.write) {
5991
- const lossy = lossyReason(raw, dropped);
6008
+ const lossy = lossyReason(raw, dropped, outside, opts.removing);
5992
6009
  if (lossy) return { state: "refused", reason: lossy };
5993
6010
  }
5994
- return { state: "ok", manifest };
6011
+ return { state: "ok", manifest, ...outside.length ? { outside } : {} };
5995
6012
  } catch (err) {
5996
6013
  const code = err.code;
5997
6014
  if (code === "ENOENT") return { state: "missing" };
@@ -6019,7 +6036,7 @@ function readManifest(ctx) {
6019
6036
  // src/swarm/hooks.ts
6020
6037
  init_esm_shims();
6021
6038
  import { execFileSync } from "child_process";
6022
- import { readFileSync as readFileSync7, realpathSync as realpathSync3, statSync as statSync5 } from "fs";
6039
+ import { readFileSync as readFileSync7, realpathSync as realpathSync5, statSync as statSync5 } from "fs";
6023
6040
  import { basename as basename2, dirname as dirname3, join as join8, resolve as resolve3 } from "path";
6024
6041
 
6025
6042
  // src/swarm/agents.ts
@@ -6354,7 +6371,7 @@ init_esm_shims();
6354
6371
  // src/index/indexer.ts
6355
6372
  init_esm_shims();
6356
6373
  var import_fast_glob = __toESM(require_out4(), 1);
6357
- import { readFileSync as readFileSync2, statSync as statSync2 } from "fs";
6374
+ import { existsSync, readFileSync as readFileSync2, realpathSync as realpathSync3, statSync as statSync2 } from "fs";
6358
6375
  import { relative as relative3, resolve } from "path";
6359
6376
 
6360
6377
  // src/chunker/index.ts
@@ -6735,7 +6752,7 @@ function readFileRisk(db) {
6735
6752
 
6736
6753
  // src/session/brief.ts
6737
6754
  init_esm_shims();
6738
- import { existsSync as existsSync2, readFileSync as readFileSync4 } from "fs";
6755
+ import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
6739
6756
  import { join as join4 } from "path";
6740
6757
 
6741
6758
  // src/memory/frontmatter.ts
@@ -6770,9 +6787,12 @@ function parseMemoryFile(text) {
6770
6787
  init_esm_shims();
6771
6788
  var import_fast_glob2 = __toESM(require_out4(), 1);
6772
6789
  import {
6773
- existsSync,
6790
+ existsSync as existsSync2,
6791
+ lstatSync as lstatSync3,
6774
6792
  mkdirSync as mkdirSync3,
6793
+ readdirSync as readdirSync2,
6775
6794
  readFileSync as readFileSync3,
6795
+ realpathSync as realpathSync4,
6776
6796
  renameSync as renameSync3,
6777
6797
  statSync as statSync3,
6778
6798
  unlinkSync as unlinkSync2,
@@ -6848,7 +6868,7 @@ function historyLine(db) {
6848
6868
  }
6849
6869
  function overviewParagraph(ctx) {
6850
6870
  const file = join4(memoriesDir(ctx), "onboarding", "overview.md");
6851
- if (!existsSync2(file)) return void 0;
6871
+ if (!existsSync3(file)) return void 0;
6852
6872
  try {
6853
6873
  const body = parseMemoryFile(readFileSync4(file, "utf8")).body.trim();
6854
6874
  const para = body.split(/\n\s*\n/).map(
@@ -7722,13 +7742,13 @@ init_esm_shims();
7722
7742
  // src/swarm/runtime.ts
7723
7743
  init_esm_shims();
7724
7744
  import { randomBytes as randomBytes2 } from "crypto";
7725
- import { existsSync as existsSync4, statSync as statSync4 } from "fs";
7745
+ import { existsSync as existsSync5, statSync as statSync4 } from "fs";
7726
7746
  import { isAbsolute as isAbsolute2, join as join6, relative as relative5 } from "path";
7727
7747
 
7728
7748
  // src/swarm/config.ts
7729
7749
  init_esm_shims();
7730
7750
  import { createHash as createHash3 } from "crypto";
7731
- import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
7751
+ import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
7732
7752
  import { createRequire as createRequire2 } from "module";
7733
7753
  import { join as join5 } from "path";
7734
7754
 
@@ -8106,7 +8126,7 @@ function roleRange(defaults, a, at, issue) {
8106
8126
  function swarmFile(root) {
8107
8127
  for (const rel of SWARM_FILES) {
8108
8128
  const file = join5(root, rel);
8109
- if (existsSync3(file)) return { file, rel, exists: true };
8129
+ if (existsSync4(file)) return { file, rel, exists: true };
8110
8130
  }
8111
8131
  return { file: join5(root, SWARM_FILES[0]), rel: SWARM_FILES[0], exists: false };
8112
8132
  }
@@ -9100,7 +9120,7 @@ function coordinationRoot(root) {
9100
9120
  } catch {
9101
9121
  return root;
9102
9122
  }
9103
- if (basename2(common) === ".git") return realpathSync3(dirname3(common));
9123
+ if (basename2(common) === ".git") return realpathSync5(dirname3(common));
9104
9124
  const out = execFileSync("git", ["worktree", "list", "--porcelain"], {
9105
9125
  cwd: root,
9106
9126
  encoding: "utf8",
@@ -9109,8 +9129,8 @@ function coordinationRoot(root) {
9109
9129
  });
9110
9130
  const first = /^worktree (.+)$/m.exec(out)?.[1];
9111
9131
  if (!first || /^bare$/m.test(out.split("\n\n")[0] ?? "")) return root;
9112
- const main = realpathSync3(first);
9113
- return main !== realpathSync3(common) && statSync5(join8(main, ".git"), { throwIfNoEntry: false }) ? main : root;
9132
+ const main = realpathSync5(first);
9133
+ return main !== realpathSync5(common) && statSync5(join8(main, ".git"), { throwIfNoEntry: false }) ? main : root;
9114
9134
  } catch {
9115
9135
  return root;
9116
9136
  }
@@ -9127,7 +9147,7 @@ function sessionRoot() {
9127
9147
  process.env.CLAUDE_PROJECT_DIR || process.env.EMINIAR_PROJECT_ROOT || process.cwd()
9128
9148
  );
9129
9149
  try {
9130
- return realpathSync3(root);
9150
+ return realpathSync5(root);
9131
9151
  } catch {
9132
9152
  return root;
9133
9153
  }
@@ -9800,12 +9820,12 @@ var FALLBACK_MODEL = "claude-sonnet-5-5";
9800
9820
  export {
9801
9821
  hookLog,
9802
9822
  agentFileRange,
9803
- indexContent,
9804
- git,
9805
9823
  DOCS_PREFIX,
9806
9824
  manifestPath,
9807
9825
  readManifestAt,
9808
9826
  readManifest,
9827
+ indexContent,
9828
+ git,
9809
9829
  memoriesDir,
9810
9830
  renderProjectBrief,
9811
9831
  recordShown,
@@ -9885,4 +9905,4 @@ queue-microtask/index.js:
9885
9905
  run-parallel/index.js:
9886
9906
  (*! run-parallel. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> *)
9887
9907
  */
9888
- //# sourceMappingURL=hook-chunk-DSLVDGOB.js.map
9908
+ //# sourceMappingURL=hook-chunk-ZLRLOPLR.js.map
@@ -8,7 +8,7 @@ import {
8
8
  manifestPath,
9
9
  readManifestAt,
10
10
  resolvePrice
11
- } from "./hook-chunk-DSLVDGOB.js";
11
+ } from "./hook-chunk-ZLRLOPLR.js";
12
12
  import {
13
13
  adjudicateOne,
14
14
  briefUnitsOf,
@@ -18,7 +18,7 @@ import {
18
18
  resolveAdjudicator,
19
19
  spawnAsyncJob,
20
20
  topIntent
21
- } from "./hook-chunk-RYZAFUTT.js";
21
+ } from "./hook-chunk-MVSMFCKL.js";
22
22
  import {
23
23
  AGENT_EFFORT_SINCE,
24
24
  KEPT_MODEL_FLAG,
@@ -30,7 +30,7 @@ import {
30
30
  init_esm_shims,
31
31
  keptModel,
32
32
  tierOf
33
- } from "./hook-chunk-BECSRJFD.js";
33
+ } from "./hook-chunk-ETYHCPPH.js";
34
34
 
35
35
  // src/adjudicator/hooks.ts
36
36
  init_esm_shims();
@@ -1826,4 +1826,4 @@ export {
1826
1826
  withModel,
1827
1827
  withNote
1828
1828
  };
1829
- //# sourceMappingURL=hook-hooks-IDTGXCFD.js.map
1829
+ //# sourceMappingURL=hook-hooks-5U2AI6W7.js.map
package/dist/hook.js CHANGED
@@ -35,7 +35,7 @@ import {
35
35
  swarmSubagentStop,
36
36
  swarmTouch,
37
37
  workerLacksTool
38
- } from "./hook-chunk-DSLVDGOB.js";
38
+ } from "./hook-chunk-ZLRLOPLR.js";
39
39
  import {
40
40
  GUIDE_ORDER,
41
41
  classifyAssistant,
@@ -52,7 +52,7 @@ import {
52
52
  projectDir,
53
53
  setStorageBusyTimeout,
54
54
  withPriority
55
- } from "./hook-chunk-BECSRJFD.js";
55
+ } from "./hook-chunk-ETYHCPPH.js";
56
56
 
57
57
  // src/hook.ts
58
58
  init_esm_shims();
@@ -1901,7 +1901,7 @@ var userPromptSubmit = async (input) => {
1901
1901
  let systemMessage;
1902
1902
  if (ctx.config.adjudicator.enabled && input.client !== "cursor" && hasBudget(ADJUDICATOR_NEEDS_MS)) {
1903
1903
  try {
1904
- const { onPrompt } = await import("./hook-hooks-IDTGXCFD.js");
1904
+ const { onPrompt } = await import("./hook-hooks-5U2AI6W7.js");
1905
1905
  const a = await onPrompt(ctx, db, input, sessionId);
1906
1906
  if (a?.context) parts.push(a.context);
1907
1907
  systemMessage = a?.systemMessage;
@@ -1959,7 +1959,7 @@ var postToolUse = async (input) => {
1959
1959
  const ctx = getContext(input.cwd);
1960
1960
  if (ctx.config.adjudicator.enabled) {
1961
1961
  try {
1962
- const { onPostAgent } = await import("./hook-hooks-IDTGXCFD.js");
1962
+ const { onPostAgent } = await import("./hook-hooks-5U2AI6W7.js");
1963
1963
  onPostAgent(ctx, db, input);
1964
1964
  } catch {
1965
1965
  }
@@ -2064,7 +2064,7 @@ var sessionEnd = async (input) => {
2064
2064
  ingestUsage(db, input, sessionId);
2065
2065
  if (ctx.config.adjudicator.enabled && input.client !== "cursor") {
2066
2066
  try {
2067
- const { onSessionEnd } = await import("./hook-hooks-IDTGXCFD.js");
2067
+ const { onSessionEnd } = await import("./hook-hooks-5U2AI6W7.js");
2068
2068
  onSessionEnd(ctx, db, input);
2069
2069
  } catch {
2070
2070
  }
@@ -2208,6 +2208,7 @@ import { join as join3 } from "path";
2208
2208
 
2209
2209
  // src/permissions/rules.ts
2210
2210
  init_esm_shims();
2211
+ import { posix } from "path";
2211
2212
  function parseRule(raw) {
2212
2213
  const m = /^([A-Za-z][\w-]*)(?:\((.*)\))?$/s.exec(raw.trim());
2213
2214
  if (!m) return void 0;
@@ -2216,24 +2217,83 @@ function parseRule(raw) {
2216
2217
  function escapeRe(s) {
2217
2218
  return s.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
2218
2219
  }
2220
+ function globSource(glob) {
2221
+ return glob.split("/**/").map(
2222
+ (part) => part.split("**").map(
2223
+ (seg) => seg.split("*").map((s) => s.split("?").map(escapeRe).join("[^/]")).join("[^/]*")
2224
+ ).join(".*")
2225
+ ).join("/(?:.*/)?");
2226
+ }
2227
+ function anchored(prefix, rest) {
2228
+ const full = posix.normalize(`${prefix}${rest}`);
2229
+ if (prefix && (full === prefix || full.startsWith(`${prefix}/`))) {
2230
+ return literal(prefix) + globSource(full.slice(prefix.length));
2231
+ }
2232
+ return globSource(full);
2233
+ }
2234
+ function literal(s) {
2235
+ return escapeRe(s).replace(/\*/g, "\\*");
2236
+ }
2237
+ function readSource(pattern, root, home) {
2238
+ const dir = pattern.endsWith("/") && pattern.length > 1;
2239
+ const p = dir ? pattern.slice(0, -1) : pattern;
2240
+ let body;
2241
+ if (p.startsWith("**")) body = globSource(p);
2242
+ else if (p.startsWith("//")) body = anchored("", p.slice(1));
2243
+ else if (p === "~" || p.startsWith("~/")) body = anchored(home, p.slice(1));
2244
+ else if (p.startsWith("/")) body = `(?:${anchored(root, p)}|${globSource(posix.normalize(p))})`;
2245
+ else if (p.startsWith("./")) body = anchored(root, p.slice(1));
2246
+ else if (p.includes("/")) body = anchored(root, `/${p}`);
2247
+ else body = `${literal(root)}/(?:.*/)?${globSource(p)}`;
2248
+ return `^${body}(?:/.*)?$`;
2249
+ }
2219
2250
  var readRegexCache = /* @__PURE__ */ new Map();
2220
- function readRegex(pattern) {
2221
- let re = readRegexCache.get(pattern);
2251
+ function readRegex(pattern, root, home) {
2252
+ const key = `${root}\0${home}\0${pattern}`;
2253
+ let re = readRegexCache.get(key);
2222
2254
  if (!re) {
2223
- if (readRegexCache.size >= 1024) readRegexCache.clear();
2224
- re = new RegExp(
2225
- `^${pattern.split("**").map((seg) => seg.split("*").map(escapeRe).join("[^/]*")).join(".*")}$`
2226
- );
2227
- readRegexCache.set(pattern, re);
2255
+ if (readRegexCache.size >= 4096) readRegexCache.clear();
2256
+ re = new RegExp(readSource(pattern, root, home));
2257
+ readRegexCache.set(key, re);
2228
2258
  }
2229
2259
  return re;
2230
2260
  }
2231
- function matchRead(rule, absPath) {
2232
- if (rule.tool !== "Read") return false;
2233
- if (rule.pattern === void 0) return true;
2234
- let p = rule.pattern;
2235
- if (p.startsWith("~")) p = (process.env.HOME ?? "") + p.slice(1);
2236
- return readRegex(p).test(absPath);
2261
+ function defaultRoot() {
2262
+ return process.cwd();
2263
+ }
2264
+ function needleOf(pattern) {
2265
+ const p = pattern.endsWith("/") && pattern.length > 1 ? pattern.slice(0, -1) : pattern;
2266
+ if (p.includes("..")) return "";
2267
+ let tail;
2268
+ if (p.startsWith("**") || !p.includes("/")) tail = p;
2269
+ else if (p.startsWith("//")) tail = posix.normalize(p.slice(1));
2270
+ else if (p === "~" || p.startsWith("~/")) tail = posix.normalize(p.slice(1) || "/");
2271
+ else if (p.startsWith("./")) tail = posix.normalize(p.slice(1));
2272
+ else tail = posix.normalize(p.startsWith("/") ? p : `/${p}`);
2273
+ let best = "";
2274
+ for (const piece of tail.split(/[*?]/)) if (piece.length > best.length) best = piece;
2275
+ return best;
2276
+ }
2277
+ var listCache = /* @__PURE__ */ new WeakMap();
2278
+ function readMatcher(rules, root) {
2279
+ const r = root ?? defaultRoot();
2280
+ const home = process.env.HOME ?? "";
2281
+ const key = `${r}\0${home}\0${rules.length}`;
2282
+ const hit = listCache.get(rules);
2283
+ if (hit && hit.key === key) return hit.matcher;
2284
+ const compiled = rules.filter((x) => x.tool === "Read").map(
2285
+ (rule) => rule.pattern === void 0 ? { rule, needle: "", re: /^/ } : { rule, needle: needleOf(rule.pattern), re: readRegex(rule.pattern, r, home) }
2286
+ );
2287
+ const which = (p) => {
2288
+ for (const c of compiled) {
2289
+ if (c.needle && !p.includes(c.needle)) continue;
2290
+ if (c.re.test(p)) return c.rule;
2291
+ }
2292
+ return void 0;
2293
+ };
2294
+ const matcher = { test: (p) => which(p) !== void 0, which };
2295
+ listCache.set(rules, { key, matcher });
2296
+ return matcher;
2237
2297
  }
2238
2298
 
2239
2299
  // src/permissions/split.ts
@@ -2267,7 +2327,7 @@ function loadPermissionRules(root, extraDeny = []) {
2267
2327
  const stamps = files.map(stamp);
2268
2328
  const hit = cache.get(key);
2269
2329
  if (hit?.stamps.every((s, i) => s === stamps[i])) return hit.rules;
2270
- const out = { allow: [], deny: [], ask: [] };
2330
+ const out = { allow: [], deny: [], ask: [], root };
2271
2331
  const push = (list, raws) => {
2272
2332
  for (const raw of raws ?? []) {
2273
2333
  const r = parseRule(raw);
@@ -2287,14 +2347,21 @@ function loadPermissionRules(root, extraDeny = []) {
2287
2347
  }
2288
2348
  function checkRead(rules, absPaths) {
2289
2349
  for (const kind of ["deny", "ask"]) {
2290
- for (const r of rules[kind]) {
2291
- if (absPaths.some((p) => matchRead(r, p))) return { allowed: false, rule: r.raw, kind };
2350
+ const m = readMatcher(rules[kind], rules.root);
2351
+ for (const p of absPaths) {
2352
+ const r = m.which(p);
2353
+ if (r) return { allowed: false, rule: r.raw, kind };
2292
2354
  }
2293
2355
  }
2294
2356
  return { allowed: true };
2295
2357
  }
2296
2358
  function checkReadPath(rules, abs) {
2297
- return checkRead(rules, [.../* @__PURE__ */ new Set([abs, realpathSync(abs), realpathSync.native(abs)])]);
2359
+ const asWritten = checkRead(rules, [abs]);
2360
+ if (!asWritten.allowed) return asWritten;
2361
+ const real = realpathSync(abs);
2362
+ const native = realpathSync.native(abs);
2363
+ const more = [real, native].filter((p, i, a) => p !== abs && a.indexOf(p) === i);
2364
+ return more.length ? checkRead(rules, more) : asWritten;
2298
2365
  }
2299
2366
 
2300
2367
  // src/session/routing-stats.ts
@@ -2894,7 +2961,7 @@ var preToolUse = async (input) => {
2894
2961
  const out = await route(ctx, input, spawn?.kind === "worker");
2895
2962
  if (!ctx.config.adjudicator.enabled || input.client === "cursor") return out;
2896
2963
  try {
2897
- const { afterRouting } = await import("./hook-hooks-IDTGXCFD.js");
2964
+ const { afterRouting } = await import("./hook-hooks-5U2AI6W7.js");
2898
2965
  return await afterRouting(ctx, input, spawn, out);
2899
2966
  } catch {
2900
2967
  return out;
@@ -3131,7 +3198,7 @@ ${guide}`);
3131
3198
  if (swarm) parts.push(swarm);
3132
3199
  if (ctx.config.adjudicator.enabled && input.client !== "cursor") {
3133
3200
  try {
3134
- const { onSessionStart } = await import("./hook-hooks-IDTGXCFD.js");
3201
+ const { onSessionStart } = await import("./hook-hooks-5U2AI6W7.js");
3135
3202
  const line2 = onSessionStart(ctx, db, input);
3136
3203
  if (line2) parts.push(line2);
3137
3204
  } catch {
@@ -3324,7 +3391,7 @@ async function main() {
3324
3391
  return;
3325
3392
  }
3326
3393
  if (process.argv.includes("--adjudicate")) {
3327
- const { ASYNC_JOB_MAX_MS, runAsyncJob } = await import("./hook-async-J3UKDO62.js");
3394
+ const { ASYNC_JOB_MAX_MS, runAsyncJob } = await import("./hook-async-3TLK23ZY.js");
3328
3395
  setTimeout(() => process.exit(0), ASYNC_JOB_MAX_MS).unref();
3329
3396
  await runAsyncJob(raw, { log: (line2) => hookLog("info", line2) });
3330
3397
  return;