@letrquan/book 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/CHANGELOG.md +2747 -40
  2. package/README.md +132 -1311
  3. package/dist/additional-roots-H7UNAEHS.js +31 -0
  4. package/dist/{app-MOXXWEOY.js → app-GE4OHJOC.js} +5740 -4045
  5. package/dist/app-GE4OHJOC.js.map +1 -0
  6. package/dist/{chunk-5GDZ22YP.js → chunk-224FJZLH.js} +99 -108
  7. package/dist/chunk-224FJZLH.js.map +1 -0
  8. package/dist/chunk-2AAOF5B5.js +28 -0
  9. package/dist/chunk-2AAOF5B5.js.map +1 -0
  10. package/dist/{chunk-CAGOB2N4.js → chunk-4AS7LKKQ.js} +6 -4
  11. package/dist/chunk-4AS7LKKQ.js.map +1 -0
  12. package/dist/chunk-525CAZPY.js +120 -0
  13. package/dist/chunk-525CAZPY.js.map +1 -0
  14. package/dist/{chunk-URLQAXTR.js → chunk-56SIEMDX.js} +2 -2
  15. package/dist/chunk-56SIEMDX.js.map +1 -0
  16. package/dist/{chunk-XIFFTATU.js → chunk-5YHTKWJN.js} +5420 -4031
  17. package/dist/chunk-5YHTKWJN.js.map +1 -0
  18. package/dist/chunk-62QL5QUP.js +283 -0
  19. package/dist/chunk-62QL5QUP.js.map +1 -0
  20. package/dist/{chunk-KWHQ2XXL.js → chunk-67UZEWWF.js} +3 -3
  21. package/dist/chunk-67UZEWWF.js.map +1 -0
  22. package/dist/chunk-6OQQOMZR.js +785 -0
  23. package/dist/chunk-6OQQOMZR.js.map +1 -0
  24. package/dist/{chunk-GTVB4HEM.js → chunk-AQEQX4FF.js} +344 -315
  25. package/dist/chunk-AQEQX4FF.js.map +1 -0
  26. package/dist/chunk-C7ADUNPR.js +273 -0
  27. package/dist/chunk-C7ADUNPR.js.map +1 -0
  28. package/dist/{chunk-E75IPST5.js → chunk-EFJ2NH6E.js} +12 -58
  29. package/dist/chunk-EFJ2NH6E.js.map +1 -0
  30. package/dist/{chunk-5KLVH3PY.js → chunk-HQI7NYGX.js} +5 -3
  31. package/dist/chunk-HQI7NYGX.js.map +1 -0
  32. package/dist/chunk-IME7R2UI.js +6541 -0
  33. package/dist/chunk-IME7R2UI.js.map +1 -0
  34. package/dist/chunk-J4F2QLM2.js +273 -0
  35. package/dist/chunk-J4F2QLM2.js.map +1 -0
  36. package/dist/{chunk-MAQXCUR4.js → chunk-J62VZYOB.js} +444 -112
  37. package/dist/chunk-J62VZYOB.js.map +1 -0
  38. package/dist/chunk-JTPOI3KU.js +826 -0
  39. package/dist/chunk-JTPOI3KU.js.map +1 -0
  40. package/dist/{chunk-WKBO5R4O.js → chunk-KBQS35OV.js} +2 -2
  41. package/dist/chunk-KBQS35OV.js.map +1 -0
  42. package/dist/chunk-KXAYUIKP.js +90 -0
  43. package/dist/chunk-KXAYUIKP.js.map +1 -0
  44. package/dist/{chunk-VZQXX3WW.js → chunk-QYBD22H5.js} +1 -1
  45. package/dist/chunk-QYBD22H5.js.map +1 -0
  46. package/dist/{chunk-V4BOYR52.js → chunk-RFJ6Q6LE.js} +763 -198
  47. package/dist/chunk-RFJ6Q6LE.js.map +1 -0
  48. package/dist/{chunk-7N4J557C.js → chunk-RT5SATF7.js} +1 -1
  49. package/dist/chunk-RT5SATF7.js.map +1 -0
  50. package/dist/{chunk-C7D4NJ2D.js → chunk-S4JMID4G.js} +68 -26
  51. package/dist/chunk-S4JMID4G.js.map +1 -0
  52. package/dist/{chunk-ANSJPYGI.js → chunk-SOW4WRQP.js} +3 -3
  53. package/dist/chunk-SOW4WRQP.js.map +1 -0
  54. package/dist/{chunk-J7MIZ5CA.js → chunk-TLX3H2CL.js} +518 -79
  55. package/dist/chunk-TLX3H2CL.js.map +1 -0
  56. package/dist/{chunk-DGMQEI6Y.js → chunk-VPNLCMVR.js} +22 -9
  57. package/dist/chunk-VPNLCMVR.js.map +1 -0
  58. package/dist/{chunk-BTZZUCLI.js → chunk-W3XFHEF4.js} +173 -29
  59. package/dist/chunk-W3XFHEF4.js.map +1 -0
  60. package/dist/{chunk-574C6MQR.js → chunk-X4NJT436.js} +130 -60
  61. package/dist/chunk-X4NJT436.js.map +1 -0
  62. package/dist/chunk-X66I2NQO.js +137 -0
  63. package/dist/chunk-X66I2NQO.js.map +1 -0
  64. package/dist/chunk-XHAO3K7I.js +18 -0
  65. package/dist/chunk-XHAO3K7I.js.map +1 -0
  66. package/dist/{chunk-3U5IBM24.js → chunk-Y2VHN7TC.js} +2 -2
  67. package/dist/chunk-Y2VHN7TC.js.map +1 -0
  68. package/dist/chunk-YC6W4EJH.js +947 -0
  69. package/dist/chunk-YC6W4EJH.js.map +1 -0
  70. package/dist/{chunk-24AXE6SP.js → chunk-ZPSZZUWF.js} +1 -1
  71. package/dist/chunk-ZPSZZUWF.js.map +1 -0
  72. package/dist/{command-approvals-6Y57VCWZ.js → command-approvals-VBIU6BLM.js} +6 -5
  73. package/dist/context-CBOBDY5Q.js +38 -0
  74. package/dist/{hook-approvals-CTUFKEJT.js → hook-approvals-ZVT4V7IM.js} +5 -5
  75. package/dist/index.js +996 -155
  76. package/dist/index.js.map +1 -1
  77. package/dist/{interactive-assets-CCAAWVKH.js → interactive-assets-T4I4SEHU.js} +13 -9
  78. package/dist/interactive-assets-T4I4SEHU.js.map +1 -0
  79. package/dist/job-runner.js +159 -111
  80. package/dist/job-runner.js.map +1 -1
  81. package/dist/job-supervisor.d.ts +2 -0
  82. package/dist/job-supervisor.js +112 -0
  83. package/dist/job-supervisor.js.map +1 -0
  84. package/dist/loader-S77OVTHH.js +18 -0
  85. package/dist/{mcp-approvals-WNPH65BK.js → mcp-approvals-HJ2VDYMC.js} +5 -5
  86. package/dist/{mcp-config-YIJIES2C.js → mcp-config-RNB5LEVE.js} +3 -3
  87. package/dist/memory-store-DZUT5HUH.js +75 -0
  88. package/dist/{model-window-store-YFVNXCIP.js → model-window-store-MTSQ5JDJ.js} +8 -4
  89. package/dist/openai-compatible-2OB54YST.js +14 -0
  90. package/dist/{permission-approvals-76PVREFD.js → permission-approvals-YLV6UC5W.js} +5 -5
  91. package/dist/permissions-DHOKF3W2.js +45 -0
  92. package/dist/sandbox-WRYTZH3D.js +41 -0
  93. package/dist/{scrollback-LUKLIBVI.js → scrollback-35ME2GFX.js} +32 -22
  94. package/dist/scrollback-35ME2GFX.js.map +1 -0
  95. package/dist/sdk.d.ts +2219 -2361
  96. package/dist/sdk.js +53 -38
  97. package/dist/sdk.js.map +1 -1
  98. package/dist/settings-loader-JUNPGAAM.js +35 -0
  99. package/dist/settings-loader-JUNPGAAM.js.map +1 -0
  100. package/dist/{shell-selection-V6QAYXPX.js → shell-selection-CDHGJ4GC.js} +2 -2
  101. package/dist/shell-selection-CDHGJ4GC.js.map +1 -0
  102. package/package.json +23 -22
  103. package/dist/app-MOXXWEOY.js.map +0 -1
  104. package/dist/chunk-24AXE6SP.js.map +0 -1
  105. package/dist/chunk-3U5IBM24.js.map +0 -1
  106. package/dist/chunk-574C6MQR.js.map +0 -1
  107. package/dist/chunk-5GDZ22YP.js.map +0 -1
  108. package/dist/chunk-5KLVH3PY.js.map +0 -1
  109. package/dist/chunk-7N4J557C.js.map +0 -1
  110. package/dist/chunk-A7JLBK2Y.js +0 -3506
  111. package/dist/chunk-A7JLBK2Y.js.map +0 -1
  112. package/dist/chunk-ANSJPYGI.js.map +0 -1
  113. package/dist/chunk-BTZZUCLI.js.map +0 -1
  114. package/dist/chunk-C7D4NJ2D.js.map +0 -1
  115. package/dist/chunk-CAGOB2N4.js.map +0 -1
  116. package/dist/chunk-DGMQEI6Y.js.map +0 -1
  117. package/dist/chunk-E75IPST5.js.map +0 -1
  118. package/dist/chunk-EYGD6X2T.js +0 -180
  119. package/dist/chunk-EYGD6X2T.js.map +0 -1
  120. package/dist/chunk-GTVB4HEM.js.map +0 -1
  121. package/dist/chunk-J7MIZ5CA.js.map +0 -1
  122. package/dist/chunk-KWHQ2XXL.js.map +0 -1
  123. package/dist/chunk-L6LK2QIQ.js +0 -657
  124. package/dist/chunk-L6LK2QIQ.js.map +0 -1
  125. package/dist/chunk-MAQXCUR4.js.map +0 -1
  126. package/dist/chunk-S4XL7HOM.js +0 -402
  127. package/dist/chunk-S4XL7HOM.js.map +0 -1
  128. package/dist/chunk-URLQAXTR.js.map +0 -1
  129. package/dist/chunk-V4BOYR52.js.map +0 -1
  130. package/dist/chunk-VZQXX3WW.js.map +0 -1
  131. package/dist/chunk-WKBO5R4O.js.map +0 -1
  132. package/dist/chunk-XIFFTATU.js.map +0 -1
  133. package/dist/context-ZC5IFEAX.js +0 -25
  134. package/dist/interactive-assets-CCAAWVKH.js.map +0 -1
  135. package/dist/loader-4IDS2RIE.js +0 -15
  136. package/dist/openai-compatible-RHZUBHV6.js +0 -10
  137. package/dist/permissions-CHCKTFQQ.js +0 -28
  138. package/dist/sandbox-CNVAQDA7.js +0 -23
  139. package/dist/scrollback-LUKLIBVI.js.map +0 -1
  140. package/dist/settings-loader-M2QP5TX2.js +0 -21
  141. package/patches/ink+6.8.0.patch +0 -13
  142. package/scripts/apply-ink-patch.mjs +0 -29
  143. package/scripts/ink-patch.mjs +0 -59
  144. package/scripts/verify-ink-patch.mjs +0 -11
  145. /package/dist/{command-approvals-6Y57VCWZ.js.map → additional-roots-H7UNAEHS.js.map} +0 -0
  146. /package/dist/{context-ZC5IFEAX.js.map → command-approvals-VBIU6BLM.js.map} +0 -0
  147. /package/dist/{hook-approvals-CTUFKEJT.js.map → context-CBOBDY5Q.js.map} +0 -0
  148. /package/dist/{loader-4IDS2RIE.js.map → hook-approvals-ZVT4V7IM.js.map} +0 -0
  149. /package/dist/{mcp-approvals-WNPH65BK.js.map → loader-S77OVTHH.js.map} +0 -0
  150. /package/dist/{mcp-config-YIJIES2C.js.map → mcp-approvals-HJ2VDYMC.js.map} +0 -0
  151. /package/dist/{model-window-store-YFVNXCIP.js.map → mcp-config-RNB5LEVE.js.map} +0 -0
  152. /package/dist/{openai-compatible-RHZUBHV6.js.map → memory-store-DZUT5HUH.js.map} +0 -0
  153. /package/dist/{permission-approvals-76PVREFD.js.map → model-window-store-MTSQ5JDJ.js.map} +0 -0
  154. /package/dist/{permissions-CHCKTFQQ.js.map → openai-compatible-2OB54YST.js.map} +0 -0
  155. /package/dist/{sandbox-CNVAQDA7.js.map → permission-approvals-YLV6UC5W.js.map} +0 -0
  156. /package/dist/{settings-loader-M2QP5TX2.js.map → permissions-DHOKF3W2.js.map} +0 -0
  157. /package/dist/{shell-selection-V6QAYXPX.js.map → sandbox-WRYTZH3D.js.map} +0 -0
@@ -1,15 +1,32 @@
1
+ import {
2
+ globToRegex,
3
+ globWalkPlan,
4
+ sandboxCoverage
5
+ } from "./chunk-JTPOI3KU.js";
6
+ import {
7
+ containsPath,
8
+ pathHoldsHome,
9
+ rootHoldsHome
10
+ } from "./chunk-525CAZPY.js";
11
+ import {
12
+ canonicalizePath,
13
+ isBookLocalSettingsPath,
14
+ isUnderRoot,
15
+ pathOutsideWorkspaceResult,
16
+ resolveMutationPath,
17
+ resolveReadablePathDetail,
18
+ resolveWorkspacePath
19
+ } from "./chunk-X66I2NQO.js";
1
20
  import {
2
21
  canonicalToolName,
3
22
  getPrimaryArg,
4
- observeFile,
5
- requireFreshObservation,
6
23
  toolFailure,
7
24
  toolSuccess
8
- } from "./chunk-5GDZ22YP.js";
9
- import {
10
- globToRegex,
11
- sandboxCoverage
12
- } from "./chunk-EYGD6X2T.js";
25
+ } from "./chunk-224FJZLH.js";
26
+
27
+ // src/permissions.ts
28
+ import { realpathSync } from "fs";
29
+ import { isAbsolute, relative as relative2, sep } from "path";
13
30
 
14
31
  // src/async.ts
15
32
  function throwIfAborted(signal) {
@@ -122,7 +139,9 @@ function hunkBounds(ops, context) {
122
139
  e++;
123
140
  ctxAfter++;
124
141
  }
125
- bounds.push([s + 1, e]);
142
+ const previous = bounds.at(-1);
143
+ if (previous && s + 1 <= previous[1]) previous[1] = e;
144
+ else bounds.push([s + 1, e]);
126
145
  k = e;
127
146
  }
128
147
  return bounds;
@@ -203,52 +222,109 @@ async function renderDiffWithStatsAsync(oldText, newText, context = 3, signal) {
203
222
  return { diff: out.join("\n"), stats: diffStatsFromHunks(hunks) };
204
223
  }
205
224
 
206
- // src/tools/path-utils.ts
207
- import { existsSync, realpathSync } from "fs";
208
- import { dirname, isAbsolute, relative, resolve, sep } from "path";
209
- function isOutside(root, candidate) {
210
- const rel = relative(root, candidate);
211
- return rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel);
212
- }
213
- function nearestExistingPath(inputPath) {
214
- let candidate = inputPath;
215
- while (!existsSync(candidate)) {
216
- const parent = dirname(candidate);
217
- if (parent === candidate) return null;
218
- candidate = parent;
219
- }
220
- return candidate;
221
- }
222
- function resolveWorkspacePath(workspaceRoot, inputPath) {
223
- const lexicalRoot = resolve(workspaceRoot);
224
- const filePath = resolve(isAbsolute(inputPath) ? inputPath : resolve(lexicalRoot, inputPath));
225
- if (isOutside(lexicalRoot, filePath)) return null;
226
- const existingPath = nearestExistingPath(filePath);
227
- if (!existingPath) return null;
228
- let realRoot;
229
- let realExisting;
225
+ // src/tools/file-provenance.ts
226
+ import { createHash } from "crypto";
227
+ import { readFile, stat } from "fs/promises";
228
+ import { normalize, relative, resolve } from "path";
229
+ function workspaceIdentity(workspaceRoot) {
230
+ const root = normalize(resolve(workspaceRoot));
231
+ const stable = process.platform === "win32" ? root.toLowerCase() : root;
232
+ return createHash("sha256").update(stable).digest("hex").slice(0, 24);
233
+ }
234
+ function normalizeObservedPath(path) {
235
+ const normalized = path.replace(/\\/g, "/");
236
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
237
+ }
238
+ function observationKey(workspaceId, path) {
239
+ return `${workspaceId}:${normalizeObservedPath(path)}`;
240
+ }
241
+ function observationPathFor(workspaceRoot, absolutePath) {
242
+ const root = canonicalizePath(resolve(workspaceRoot));
243
+ return relative(root, canonicalizePath(absolutePath)).replace(/\\/g, "/");
244
+ }
245
+ function isOutline(observation) {
246
+ return observation.operation === "outline";
247
+ }
248
+ function mayReplaceObservation(current, next) {
249
+ return !current || !isOutline(next) || isOutline(current);
250
+ }
251
+ function supersedesObservation(current, next) {
252
+ if (!current) return true;
253
+ if (isOutline(current) !== isOutline(next)) return isOutline(current);
254
+ return current.timestamp <= next.timestamp;
255
+ }
256
+ function seedObservationLedger(ledger, messages) {
257
+ for (const message of messages) {
258
+ for (const observation of message.fileObservations ?? []) {
259
+ const key = observationKey(observation.workspaceId, observation.path);
260
+ if (supersedesObservation(ledger.get(key), observation)) ledger.set(key, observation);
261
+ }
262
+ }
263
+ return ledger;
264
+ }
265
+ async function observeFile(ctx, absolutePath, operation, coverage) {
266
+ const bytes = await readFile(absolutePath);
267
+ const workspaceId = workspaceIdentity(ctx.workspaceRoot);
268
+ const path = observationPathFor(ctx.workspaceRoot, absolutePath);
269
+ const observation = {
270
+ path,
271
+ workspaceId,
272
+ sha256: createHash("sha256").update(bytes).digest("hex"),
273
+ byteSize: bytes.byteLength,
274
+ lineStart: coverage?.lineStart,
275
+ lineEnd: coverage?.lineEnd,
276
+ operation,
277
+ sourceRef: ctx.currentToolTraceId ?? "runtime-tool",
278
+ timestamp: Date.now()
279
+ };
280
+ const key = observationKey(workspaceId, path);
281
+ const ledger = ctx.fileObservationLedger;
282
+ if (ledger && mayReplaceObservation(ledger.get(key), observation)) ledger.set(key, observation);
283
+ return observation;
284
+ }
285
+ async function requireFreshObservation(ctx, absolutePath, relativePath) {
286
+ const workspaceId = workspaceIdentity(ctx.workspaceRoot);
287
+ const normalizedPath = relativePath.replace(/\\/g, "/");
288
+ const remembered = ctx.fileObservationLedger?.get(
289
+ observationKey(workspaceId, observationPathFor(ctx.workspaceRoot, absolutePath))
290
+ );
291
+ if (!remembered || isOutline(remembered)) return void 0;
230
292
  try {
231
- realRoot = realpathSync.native(lexicalRoot);
232
- realExisting = realpathSync.native(existingPath);
293
+ const info = await stat(absolutePath);
294
+ if (!info.isFile()) return staleMessage(normalizedPath);
295
+ const bytes = await readFile(absolutePath);
296
+ const currentHash = createHash("sha256").update(bytes).digest("hex");
297
+ return currentHash === remembered.sha256 ? void 0 : staleMessage(normalizedPath);
233
298
  } catch {
234
- return null;
299
+ return staleMessage(normalizedPath);
235
300
  }
236
- if (isOutside(realRoot, realExisting)) return null;
237
- const canonicalPath = resolve(realExisting, relative(existingPath, filePath));
238
- if (isOutside(realRoot, canonicalPath)) return null;
239
- const rel = relative(lexicalRoot, filePath);
240
- return { filePath, canonicalPath, relativePath: rel.replace(/\\/g, "/") };
241
301
  }
242
- function pathOutsideWorkspaceResult(inputPath) {
243
- return toolFailure(`Path outside workspace: ${inputPath}`, {
244
- code: "path_outside_workspace"
245
- });
302
+ function staleMessage(path) {
303
+ return `SKIPPED: ${path} changed or disappeared since it was last shown to the model. Call Read (or mention the file again) before modifying it.`;
304
+ }
305
+ function requireObservationForMutation(ctx, absolutePath, relativePath, retryVerb) {
306
+ const ledger = ctx.fileObservationLedger;
307
+ if (!ledger) return void 0;
308
+ const workspaceId = workspaceIdentity(ctx.workspaceRoot);
309
+ const normalizedPath = relativePath.replace(/\\/g, "/");
310
+ const remembered = ledger.get(
311
+ observationKey(workspaceId, observationPathFor(ctx.workspaceRoot, absolutePath))
312
+ );
313
+ if (remembered && !isOutline(remembered)) return void 0;
314
+ const seen = remembered ? "has only been outlined in this session, and an outline is not the file's content" : "has not been read in this session";
315
+ return toolFailure(
316
+ `SKIPPED: ${normalizedPath} ${seen}. Call Read (or mention the file) before modifying it.`,
317
+ {
318
+ code: "file_not_observed",
319
+ remediation: `Read the file first, then retry the ${retryVerb}.`
320
+ }
321
+ );
246
322
  }
247
323
 
248
324
  // src/tools/mutation.ts
249
325
  import { randomUUID } from "crypto";
250
- import { mkdir, readFile, rename, rm, stat, writeFile } from "fs/promises";
251
- import { dirname as dirname2 } from "path";
326
+ import { mkdir, readFile as readFile2, rename, rm, stat as stat2, writeFile } from "fs/promises";
327
+ import { dirname } from "path";
252
328
  var pathLocks = /* @__PURE__ */ new Map();
253
329
  function looksBinary(bytes) {
254
330
  const sample = bytes.subarray(0, 8192);
@@ -262,7 +338,7 @@ function looksBinary(bytes) {
262
338
  async function readTextSnapshot(filePath, allowMissing = false) {
263
339
  let bytes;
264
340
  try {
265
- bytes = await readFile(filePath);
341
+ bytes = await readFile2(filePath);
266
342
  } catch (error) {
267
343
  if (allowMissing && error.code === "ENOENT") {
268
344
  return {
@@ -292,7 +368,7 @@ async function readTextSnapshot(filePath, allowMissing = false) {
292
368
  const bareLfCount = (decoded.replace(/\r\n/g, "").match(/\n/g) ?? []).length;
293
369
  const bareCrCount = (decoded.replace(/\r\n/g, "").match(/\r/g) ?? []).length;
294
370
  const mixedLineEndings = crlfCount > 0 && bareLfCount > 0 || bareCrCount > 0;
295
- const mode = (await stat(filePath)).mode & 511;
371
+ const mode = (await stat2(filePath)).mode & 511;
296
372
  return {
297
373
  exists: true,
298
374
  bytes,
@@ -310,7 +386,7 @@ function restoreTextEncoding(text, snapshot) {
310
386
  }
311
387
  async function writeFileAtomically(filePath, bytes, signal, mode) {
312
388
  throwIfAborted(signal);
313
- await mkdir(dirname2(filePath), { recursive: true });
389
+ await mkdir(dirname(filePath), { recursive: true });
314
390
  const temporary = `${filePath}.book-tmp-${randomUUID()}`;
315
391
  try {
316
392
  await writeFile(temporary, bytes, { flag: "wx", mode });
@@ -378,6 +454,8 @@ function parsePatchUnsafe(patch) {
378
454
  if (lines.at(-1) === "") lines.pop();
379
455
  if (lines[0] !== "*** Begin Patch" || lines.at(-1) !== "*** End Patch")
380
456
  return invalid("Patch must start with *** Begin Patch and end with *** End Patch");
457
+ while (lines.length > 2 && lines[1] === "*** Begin Patch") lines.splice(1, 1);
458
+ while (lines.length > 2 && lines.at(-2) === "*** End Patch") lines.splice(-2, 1);
381
459
  const operations = [];
382
460
  let index = 1;
383
461
  let hunkCount = 0;
@@ -462,17 +540,13 @@ function linesOf(text) {
462
540
  if (lines.at(-1) === "") lines.pop();
463
541
  return lines;
464
542
  }
465
- function findUniqueSequence(haystack, needle) {
466
- if (needle.length === 0) return { index: 0, count: 0 };
467
- let index = -1;
468
- let count = 0;
543
+ function sequenceMatches(haystack, needle) {
544
+ if (needle.length === 0) return [];
545
+ const matches = [];
469
546
  for (let start = 0; start <= haystack.length - needle.length; start++) {
470
- if (needle.every((line, offset) => haystack[start + offset] === line)) {
471
- index = start;
472
- count++;
473
- }
547
+ if (needle.every((line, offset) => haystack[start + offset] === line)) matches.push(start);
474
548
  }
475
- return { index, count };
549
+ return matches;
476
550
  }
477
551
  function candidateLine(haystack, line) {
478
552
  const matches = haystack.reduce((found, value, index) => {
@@ -484,12 +558,15 @@ function candidateLine(haystack, line) {
484
558
  function applyHunks(text, hunks) {
485
559
  let lines = linesOf(text);
486
560
  let trailingNewline = text.endsWith("\n");
561
+ let cursor = 0;
487
562
  for (let hunkIndex = 0; hunkIndex < hunks.length; hunkIndex++) {
488
563
  const hunk = hunks[hunkIndex];
489
564
  const oldLines = hunk.lines.filter((line) => line.kind !== "add").map((line) => line.text);
490
565
  const newLines = hunk.lines.filter((line) => line.kind !== "remove").map((line) => line.text);
491
- const match = findUniqueSequence(lines, oldLines);
492
- if (match.count === 0)
566
+ const matches = sequenceMatches(lines, oldLines);
567
+ const afterPrevious = matches.filter((start) => start >= cursor);
568
+ const index = matches.length === 1 ? matches[0] : afterPrevious.length === 1 ? afterPrevious[0] : -1;
569
+ if (matches.length === 0)
493
570
  return {
494
571
  text,
495
572
  mismatch: toolFailure(`Hunk ${hunkIndex + 1}: patch context not found`, {
@@ -507,21 +584,23 @@ function applyHunks(text, hunks) {
507
584
  }
508
585
  })
509
586
  };
510
- if (match.count > 1)
587
+ if (index < 0)
511
588
  return {
512
589
  text,
513
590
  mismatch: toolFailure(`Hunk ${hunkIndex + 1}: patch context is ambiguous`, {
514
591
  code: "ambiguous_patch_context",
515
- remediation: "Reread a narrower range and include more exact context lines.",
516
- details: { hunkIndex: hunkIndex + 1, header: hunk.header, matches: match.count }
592
+ remediation: "Add surrounding lines, such as the enclosing function signature, until the hunk occurs once in the file or once after the previous hunk, then regenerate the patch.",
593
+ details: {
594
+ hunkIndex: hunkIndex + 1,
595
+ header: hunk.header,
596
+ matches: matches.length,
597
+ matchesAfterPreviousHunk: afterPrevious.length
598
+ }
517
599
  })
518
600
  };
519
- const touchesEndOfFile = match.index + oldLines.length === lines.length;
520
- lines = [
521
- ...lines.slice(0, match.index),
522
- ...newLines,
523
- ...lines.slice(match.index + oldLines.length)
524
- ];
601
+ const touchesEndOfFile = index + oldLines.length === lines.length;
602
+ lines = [...lines.slice(0, index), ...newLines, ...lines.slice(index + oldLines.length)];
603
+ cursor = index + newLines.length;
525
604
  if (touchesEndOfFile) {
526
605
  const oldNoNewline = hunk.lines.some((line) => line.kind !== "add" && line.noNewline);
527
606
  const newNoNewline = hunk.lines.some((line) => line.kind !== "remove" && line.noNewline);
@@ -547,7 +626,7 @@ async function applyPatch(args, ctx) {
547
626
  if (!("operations" in parsed)) return parsed;
548
627
  const paths = parsed.operations.map((operation) => operation.path);
549
628
  const resolved = parsed.operations.map((operation) => {
550
- const result = resolveWorkspacePath(ctx.workspaceRoot, operation.path);
629
+ const result = resolveMutationPath(ctx, operation.path);
551
630
  return result ? { operation, ...result } : null;
552
631
  });
553
632
  if (resolved.some((entry) => !entry))
@@ -770,7 +849,13 @@ ${diff}`);
770
849
  }
771
850
  }
772
851
  );
773
- return result;
852
+ if (!staged.every((file) => file.mutation.kind === "create")) return result;
853
+ return {
854
+ ...result,
855
+ maskedPlaceholder: `${staged.map(
856
+ (file, order) => `${order === 0 ? "Created" : "created"} ${file.relativePath} (${file.mutation.addedLines} lines, ${file.afterBytes?.byteLength ?? 0} bytes)`
857
+ ).join("; ")}.`
858
+ };
774
859
  } catch (error) {
775
860
  const code = errorCodeFor(error);
776
861
  return toolFailure(error instanceof Error ? error.message : String(error), {
@@ -809,6 +894,32 @@ var patchTools = [
809
894
  ];
810
895
 
811
896
  // src/permissions.ts
897
+ function isUnreadablePattern(target) {
898
+ return typeof target === "object" && target !== null && target.target === "invalid";
899
+ }
900
+ var WORKSPACE_READ_TOOLS = /* @__PURE__ */ new Set(["Read", "Glob", "Grep"]);
901
+ var PATH_RULE_TOOLS = /* @__PURE__ */ new Set([
902
+ "Read",
903
+ "Write",
904
+ "Edit",
905
+ "MultiEdit",
906
+ "NotebookEdit"
907
+ ]);
908
+ var WORKSPACE_READ_JUDGING_MODES = /* @__PURE__ */ new Set([
909
+ "default",
910
+ "accept-edits",
911
+ "plan"
912
+ ]);
913
+ var WORKSPACE_READ_REFUSAL_MODES = /* @__PURE__ */ new Set([
914
+ ...WORKSPACE_READ_JUDGING_MODES,
915
+ "dontAsk"
916
+ ]);
917
+ var WORKSPACE_READ_ONLY_GIT_TOOLS = /* @__PURE__ */ new Set([
918
+ "GitStatus",
919
+ "GitDiff",
920
+ "GitLog",
921
+ "GitBranch"
922
+ ]);
812
923
  function parseRule(rule) {
813
924
  const parenIdx = rule.indexOf("(");
814
925
  if (parenIdx === -1) {
@@ -834,12 +945,19 @@ function toolNameMatchesRule(ruleToolName, toolName) {
834
945
  serverName && !serverName.includes("__") && toolName.startsWith(`${ruleToolName}__`)
835
946
  );
836
947
  }
837
- function ruleMatches(rule, toolName, primaryArg) {
948
+ var caseFoldingPathRule = (value) => value.toLowerCase();
949
+ function ruleMatchesCandidate(rule, toolName, primaryArg, fold) {
838
950
  if (!toolNameMatchesRule(rule.toolName, toolName)) return false;
839
951
  if (rule.pattern === null) return true;
840
952
  const normalizedArg = normalizePathArg(primaryArg);
841
953
  const normalizedPattern = rule.pattern.startsWith("./") ? rule.pattern.slice(2) : rule.pattern;
842
- return globToRegex(normalizedPattern).test(normalizedArg);
954
+ if (!fold) return globToRegex(normalizedPattern).test(normalizedArg);
955
+ return globToRegex(caseFoldingPathRule(normalizedPattern)).test(
956
+ caseFoldingPathRule(normalizedArg)
957
+ );
958
+ }
959
+ function ruleMatches(rule, toolName, primaryArg) {
960
+ return ruleMatchesCandidate(rule, toolName, primaryArg, false);
843
961
  }
844
962
  function permissionRuleForToolCall(call) {
845
963
  const toolName = canonicalToolName(call.name);
@@ -852,7 +970,8 @@ function permissionRuleForToolCall(call) {
852
970
  } catch {
853
971
  }
854
972
  }
855
- return primaryArg ? `${toolName}(${primaryArg})` : toolName;
973
+ if (!primaryArg) return void 0;
974
+ return `${toolName}(${primaryArg})`;
856
975
  }
857
976
  function permissionResultOf(decision) {
858
977
  return typeof decision === "string" ? decision : decision.result;
@@ -860,10 +979,14 @@ function permissionResultOf(decision) {
860
979
  function permissionRuleOf(decision) {
861
980
  return typeof decision === "string" ? void 0 : decision.rule;
862
981
  }
982
+ function permissionReasonOf(decision) {
983
+ return typeof decision === "string" ? void 0 : decision.reason;
984
+ }
863
985
  var RULE_LADDER_MAX = 3;
864
986
  var SHELL_CHAINING = /[;&|><`$(){}\n]/;
865
987
  function permissionRuleLadder(call) {
866
988
  const exact = permissionRuleForToolCall(call);
989
+ if (!exact) return [];
867
990
  if (canonicalToolName(call.name) !== "Bash") return [exact];
868
991
  const command = getPrimaryArg(call.arguments).trim();
869
992
  if (!command || SHELL_CHAINING.test(command)) return [exact];
@@ -890,8 +1013,8 @@ function patchOperations(args) {
890
1013
  const parsed = parsePatch(args.patch);
891
1014
  return "operations" in parsed ? parsed.operations : [];
892
1015
  }
893
- function ruleMatchesPatch(rule, path) {
894
- return ruleMatches(rule, rule.toolName, path);
1016
+ function ruleMatchesPatch(rule, paths, fold) {
1017
+ return paths.some((path) => ruleMatchesCandidate(rule, rule.toolName, path, fold));
895
1018
  }
896
1019
  function patchRuleSupportsOperation(rule, operation) {
897
1020
  if (rule.toolName === "ApplyPatch") return true;
@@ -899,11 +1022,11 @@ function patchRuleSupportsOperation(rule, operation) {
899
1022
  if (rule.toolName === "Write") return operation.kind === "update" || operation.kind === "add";
900
1023
  return false;
901
1024
  }
902
- function compatiblePatchRuleMatches(rule, operations) {
1025
+ function compatiblePatchRuleMatches(rule, operations, spellingsOf, fold) {
903
1026
  if (!["ApplyPatch", "Edit", "Write"].includes(rule.toolName)) return false;
904
1027
  if (rule.toolName === "ApplyPatch" && rule.pattern === null) return true;
905
1028
  return operations.some(
906
- (operation) => patchRuleSupportsOperation(rule, operation) && (rule.pattern === null || ruleMatchesPatch(rule, operation.path))
1029
+ (operation) => patchRuleSupportsOperation(rule, operation) && (rule.pattern === null || ruleMatchesPatch(rule, spellingsOf(operation.path, fold), fold))
907
1030
  );
908
1031
  }
909
1032
  function primaryArgForRule(_toolName, args) {
@@ -920,63 +1043,260 @@ function sandboxAutoAllows(toolName, args, settings, options) {
920
1043
  if (!command) return false;
921
1044
  return sandboxCoverage(command, settings.sandbox, options.sandboxBackendAvailable).sandboxed;
922
1045
  }
1046
+ var toPosix = (value) => value.replace(/\\/g, "/");
1047
+ function realRootOf(scope) {
1048
+ if (scope.realRoot) return scope.realRoot;
1049
+ try {
1050
+ return realpathSync.native(scope.root);
1051
+ } catch {
1052
+ return void 0;
1053
+ }
1054
+ }
1055
+ function pathArgument(toolName, args) {
1056
+ const value = toolName === "NotebookEdit" ? args.notebook_path : args.filePath;
1057
+ return typeof value === "string" && value.trim() !== "" ? value : void 0;
1058
+ }
1059
+ function rulesName(lists, toolName) {
1060
+ return [...lists.deny, ...lists.ask, ...lists.allow].some(
1061
+ ({ parsed }) => parsed.toolName === toolName
1062
+ );
1063
+ }
1064
+ function resolvedPathScope(scope) {
1065
+ const detailCache = /* @__PURE__ */ new Map();
1066
+ const rootsFor = (includeReadOnly) => ({
1067
+ workspaceRoot: scope.root,
1068
+ readOnlyRoots: includeReadOnly ? scope.readOnlyRoots : void 0,
1069
+ additionalRoots: scope.additionalRoots
1070
+ });
1071
+ const holdsHomeByRoot = /* @__PURE__ */ new Map();
1072
+ const holdsHome = (root) => {
1073
+ let known = holdsHomeByRoot.get(root);
1074
+ if (known === void 0) {
1075
+ known = rootHoldsHome(root, scope.homeGuards ?? []);
1076
+ holdsHomeByRoot.set(root, known);
1077
+ }
1078
+ return known;
1079
+ };
1080
+ const guardedRoots = [scope.root, ...scope.additionalRoots ?? []].filter(holdsHome);
1081
+ const servingRootOf = (canonicalPath) => [realRootOf(scope), ...scope.additionalRoots ?? []].find(
1082
+ (root) => typeof root === "string" && root.length > 0 && isUnderRoot(canonicalPath, root)
1083
+ );
1084
+ return {
1085
+ scope,
1086
+ roots: rootsFor,
1087
+ detail: (raw, includeReadOnly) => {
1088
+ const key = `${includeReadOnly ? "r" : "w"}\0${raw}`;
1089
+ let known = detailCache.get(key);
1090
+ if (!known) {
1091
+ known = resolveReadablePathDetail(rootsFor(includeReadOnly), raw);
1092
+ detailCache.set(key, known);
1093
+ }
1094
+ return known;
1095
+ },
1096
+ isGuarded: (canonicalPath) => {
1097
+ const roots = [realRootOf(scope), ...scope.additionalRoots ?? []].filter(
1098
+ (root) => typeof root === "string" && root.length > 0
1099
+ );
1100
+ if (roots.some((root) => isBookLocalSettingsPath(canonicalPath, root, { directory: true })))
1101
+ return true;
1102
+ if (guardedRoots.length === 0) return false;
1103
+ if (!pathHoldsHome(canonicalPath, scope.homeGuards ?? [])) return false;
1104
+ const serving = servingRootOf(canonicalPath);
1105
+ return serving !== void 0 && holdsHome(serving);
1106
+ },
1107
+ /**
1108
+ * Whether a Grep or Glob *scope* can reach a guarded home. A scope is a subtree, not a file,
1109
+ * so it is guarded when the guarded home is inside it as well as when it is inside the home:
1110
+ * `Grep {path: "/home"}` reads `~/.ssh` without naming a single file under it (PR #334
1111
+ * finding 4). Compared canonically, both ways, because either side may be reached by a link.
1112
+ */
1113
+ scopeReachesHome: (canonicalScope) => {
1114
+ const homes = scope.homeGuards ?? [];
1115
+ if (homes.some((home) => containsPath(canonicalScope, home))) return true;
1116
+ return guardedRoots.some((root) => containsPath(root, canonicalScope));
1117
+ }
1118
+ };
1119
+ }
1120
+ function spellingsOfPath(paths, raw, includeReadOnly, restrictive) {
1121
+ const spellings = [];
1122
+ const detail = paths.detail(raw, includeReadOnly);
1123
+ if (!("path" in detail)) return [];
1124
+ const resolved = detail.path;
1125
+ const inWorkspace = resolveWorkspacePath(paths.scope.root, raw) !== null;
1126
+ if (inWorkspace) {
1127
+ spellings.push(
1128
+ resolved.relativePath,
1129
+ toPosix(resolved.filePath),
1130
+ toPosix(resolved.canonicalPath)
1131
+ );
1132
+ const realRoot = realRootOf(paths.scope);
1133
+ if (realRoot) spellings.push(toPosix(relative2(realRoot, resolved.canonicalPath)));
1134
+ } else {
1135
+ spellings.push(toPosix(resolved.filePath), toPosix(resolved.canonicalPath));
1136
+ if (restrictive) {
1137
+ for (const root of paths.scope.additionalRoots ?? []) {
1138
+ const fromRoot = relative2(canonicalizePath(root), resolved.canonicalPath);
1139
+ if (!fromRoot || isAbsolute(fromRoot) || fromRoot === ".." || fromRoot.startsWith(`..${sep}`)) {
1140
+ continue;
1141
+ }
1142
+ spellings.push(toPosix(fromRoot));
1143
+ }
1144
+ }
1145
+ }
1146
+ return [...new Set(spellings)].filter((spelling) => spelling !== "" && spelling !== raw);
1147
+ }
1148
+ function pathRuleSpellings(paths, toolName, args, restrictive) {
1149
+ const raw = pathArgument(toolName, args);
1150
+ return raw ? spellingsOfPath(paths, raw, toolName === "Read", restrictive) : [];
1151
+ }
1152
+ function globTarget(pattern, paths) {
1153
+ const { refusal, scopes } = globWalkPlan(pattern, paths.scope.root);
1154
+ if (refusal) return { target: "invalid", reason: refusal };
1155
+ if (scopes === null) return "outside";
1156
+ let guarded = false;
1157
+ for (const scope of scopes) {
1158
+ const detail = paths.detail(scope, false);
1159
+ if (!("path" in detail)) return "outside";
1160
+ if (paths.scopeReachesHome(detail.path.canonicalPath)) guarded = true;
1161
+ }
1162
+ return guarded ? "guarded" : "servable";
1163
+ }
1164
+ function readToolTarget(toolName, args, paths) {
1165
+ if (toolName === "Read") {
1166
+ const raw2 = pathArgument(toolName, args);
1167
+ if (!raw2) return "none";
1168
+ const detail2 = paths.detail(raw2, true);
1169
+ if (!("path" in detail2)) return detail2.reason === "excluded" ? "hidden" : "outside";
1170
+ return paths.isGuarded(detail2.path.canonicalPath) ? "guarded" : "servable";
1171
+ }
1172
+ if (toolName === "Glob") {
1173
+ const pattern = typeof args.pattern === "string" ? args.pattern.trim() : "";
1174
+ if (!pattern) return "none";
1175
+ return globTarget(pattern, paths);
1176
+ }
1177
+ const raw = typeof args.path === "string" ? args.path.trim() : "";
1178
+ if (raw === "" || raw === ".") return "servable";
1179
+ const detail = paths.detail(raw, false);
1180
+ if (!("path" in detail)) return detail.reason === "excluded" ? "hidden" : "outside";
1181
+ if (paths.isGuarded(detail.path.canonicalPath)) return "guarded";
1182
+ return paths.scopeReachesHome(detail.path.canonicalPath) ? "guarded" : "servable";
1183
+ }
1184
+ function parseRuleLists(settings) {
1185
+ const list = (texts) => texts.map((text) => ({ text, parsed: parseRule(text) }));
1186
+ return {
1187
+ deny: list(settings.permissions.deny),
1188
+ ask: list(settings.permissions.ask),
1189
+ allow: list(settings.permissions.allow)
1190
+ };
1191
+ }
1192
+ function hasReadAdjudication(lists) {
1193
+ return [...lists.deny, ...lists.ask].some(
1194
+ ({ parsed }) => WORKSPACE_READ_TOOLS.has(parsed.toolName)
1195
+ );
1196
+ }
923
1197
  function evaluatePermission(toolName, args, settings, options = {}) {
924
1198
  return evaluatePermissionDetail(toolName, args, settings, options).decision;
925
1199
  }
926
1200
  function evaluatePermissionDetail(toolName, args, settings, options = {}) {
927
- const { deny, ask, allow } = settings.permissions;
1201
+ const scope = options.workspace;
1202
+ const paths = scope ? resolvedPathScope(scope) : void 0;
1203
+ const parsed = parseRuleLists(settings);
928
1204
  if (toolName === "ApplyPatch") {
929
1205
  const operations = patchOperations(args);
930
- const compatible = (ruleStr) => {
931
- const rule = parseRule(ruleStr);
932
- return rule.toolName === "ApplyPatch" || rule.toolName === "Edit" || rule.toolName === "Write";
1206
+ const spellings = /* @__PURE__ */ new Map();
1207
+ const restrictiveSpellings = /* @__PURE__ */ new Map();
1208
+ const spellingsOf = (path, restrictive) => {
1209
+ const cache = restrictive ? restrictiveSpellings : spellings;
1210
+ let known = cache.get(path);
1211
+ if (!known) {
1212
+ known = paths ? [path, ...spellingsOfPath(paths, path, false, restrictive)] : [path];
1213
+ cache.set(path, known);
1214
+ }
1215
+ return known;
933
1216
  };
934
- for (const ruleStr of deny) {
935
- if (!compatible(ruleStr)) continue;
936
- const rule = parseRule(ruleStr);
937
- if (compatiblePatchRuleMatches(rule, operations))
938
- return { decision: "deny", matchedRule: ruleStr, source: "deny" };
939
- }
940
- for (const ruleStr of ask) {
941
- if (!compatible(ruleStr)) continue;
942
- const rule = parseRule(ruleStr);
943
- if (compatiblePatchRuleMatches(rule, operations))
944
- return { decision: "ask", matchedRule: ruleStr, source: "ask" };
1217
+ const isCompatible = (rule) => rule.toolName === "ApplyPatch" || rule.toolName === "Edit" || rule.toolName === "Write";
1218
+ for (const list of ["deny", "ask"]) {
1219
+ for (const entry of parsed[list]) {
1220
+ if (!isCompatible(entry.parsed)) continue;
1221
+ if (compatiblePatchRuleMatches(entry.parsed, operations, spellingsOf, true)) {
1222
+ return { decision: list, matchedRule: entry.text, source: list };
1223
+ }
1224
+ }
945
1225
  }
946
- const allowRules = allow.filter(compatible).map(parseRule);
1226
+ const allowRules = parsed.allow.filter(({ parsed: rule }) => isCompatible(rule));
1227
+ const firstCompatible = allowRules[0]?.text;
947
1228
  if (operations.length > 0 && operations.every(
948
1229
  (operation) => allowRules.some(
949
- (rule) => patchRuleSupportsOperation(rule, operation) && (rule.pattern === null || ruleMatchesPatch(rule, operation.path))
1230
+ ({ parsed: rule }) => patchRuleSupportsOperation(rule, operation) && (rule.pattern === null || ruleMatchesPatch(rule, spellingsOf(operation.path, false), false))
950
1231
  )
951
1232
  )) {
952
- return { decision: "allow", matchedRule: allow.find(compatible), source: "allow" };
1233
+ return { decision: "allow", matchedRule: firstCompatible, source: "allow" };
953
1234
  }
954
- if (allowRules.some((rule) => rule.toolName === "ApplyPatch" && rule.pattern === null)) {
955
- return { decision: "allow", matchedRule: allow.find(compatible), source: "allow" };
1235
+ if (allowRules.some(({ parsed: rule }) => rule.toolName === "ApplyPatch" && rule.pattern === null)) {
1236
+ return { decision: "allow", matchedRule: firstCompatible, source: "allow" };
956
1237
  }
957
1238
  return { decision: "ask", source: "default" };
958
1239
  }
959
- const call = { id: "permission-evaluation", name: toolName, arguments: args };
960
- for (const ruleStr of deny) {
961
- if (permissionRuleMatchesCall(ruleStr, call)) {
962
- return { decision: "deny", matchedRule: ruleStr, source: "deny" };
1240
+ const tool = canonicalToolName(toolName);
1241
+ const candidates = [getPrimaryArg(args)];
1242
+ let restrictiveCandidates = candidates;
1243
+ if (scope && paths && PATH_RULE_TOOLS.has(tool) && rulesName(parsed, tool)) {
1244
+ restrictiveCandidates = [...candidates, ...pathRuleSpellings(paths, tool, args, true)];
1245
+ candidates.push(...pathRuleSpellings(paths, tool, args, false));
1246
+ }
1247
+ const matchesRule = (rule, fold) => {
1248
+ const pool = fold ? restrictiveCandidates : candidates;
1249
+ if (tool === "WebFetch") {
1250
+ try {
1251
+ const url = new URL(candidates[0]).toString();
1252
+ return ruleMatchesCandidate(rule, tool, url, false);
1253
+ } catch {
1254
+ }
1255
+ }
1256
+ return pool.some((candidate) => ruleMatchesCandidate(rule, tool, candidate, fold));
1257
+ };
1258
+ const readTarget = scope && paths && (scope.judgeReads || scope.refuseUnreachableReads) && WORKSPACE_READ_TOOLS.has(tool) ? readToolTarget(tool, args, paths) : void 0;
1259
+ const unreadablePattern = readTarget !== void 0 && isUnreadablePattern(readTarget) ? readTarget.reason : void 0;
1260
+ const outside = readTarget === "outside" ? { outsideWorkspace: true } : {};
1261
+ for (const entry of parsed.deny) {
1262
+ if (matchesRule(entry.parsed, true)) {
1263
+ return { decision: "deny", matchedRule: entry.text, source: "deny" };
963
1264
  }
964
1265
  }
965
- for (const ruleStr of ask) {
966
- if (permissionRuleMatchesCall(ruleStr, call)) {
967
- return { decision: "ask", matchedRule: ruleStr, source: "ask" };
1266
+ for (const entry of parsed.ask) {
1267
+ if (matchesRule(entry.parsed, true)) {
1268
+ return { decision: "ask", matchedRule: entry.text, source: "ask", ...outside };
968
1269
  }
969
1270
  }
970
- for (const ruleStr of allow) {
971
- if (permissionRuleMatchesCall(ruleStr, call)) {
972
- return { decision: "allow", matchedRule: ruleStr, source: "allow" };
1271
+ for (const entry of parsed.allow) {
1272
+ if (matchesRule(entry.parsed, false)) {
1273
+ return { decision: "allow", matchedRule: entry.text, source: "allow" };
973
1274
  }
974
1275
  }
975
1276
  if (sandboxAutoAllows(toolName, args, settings, options)) {
976
1277
  return { decision: "allow", source: "sandbox" };
977
1278
  }
1279
+ if (scope?.autoAllowReads && (readTarget === "none" || readTarget === "servable" && (tool === "Read" || !hasReadAdjudication(parsed)))) {
1280
+ return { decision: "allow", source: "workspace" };
1281
+ }
1282
+ if (scope?.autoAllowReads && WORKSPACE_READ_ONLY_GIT_TOOLS.has(tool)) {
1283
+ return { decision: "allow", source: "workspace" };
1284
+ }
1285
+ if (ALWAYS_ALLOWED_TOOLS.has(tool)) {
1286
+ return { decision: "allow", source: "default" };
1287
+ }
1288
+ if (unreadablePattern !== void 0) {
1289
+ return { decision: "refuse", source: "default", unreadablePattern };
1290
+ }
1291
+ if (readTarget === "outside") {
1292
+ return { decision: "refuse", source: "default", outsideWorkspace: true };
1293
+ }
1294
+ if (readTarget === "hidden") {
1295
+ return { decision: "refuse", source: "default", excludedPath: true };
1296
+ }
978
1297
  return { decision: "ask", source: "default" };
979
1298
  }
1299
+ var ALWAYS_ALLOWED_TOOLS = /* @__PURE__ */ new Set(["MemorySave", "TodoWrite"]);
980
1300
 
981
1301
  export {
982
1302
  throwIfAborted,
@@ -984,8 +1304,14 @@ export {
984
1304
  delay,
985
1305
  yieldToEventLoop,
986
1306
  renderDiffWithStatsAsync,
987
- resolveWorkspacePath,
988
- pathOutsideWorkspaceResult,
1307
+ workspaceIdentity,
1308
+ normalizeObservedPath,
1309
+ observationKey,
1310
+ supersedesObservation,
1311
+ seedObservationLedger,
1312
+ observeFile,
1313
+ requireFreshObservation,
1314
+ requireObservationForMutation,
989
1315
  readTextSnapshot,
990
1316
  restoreTextEncoding,
991
1317
  writeFileAtomically,
@@ -993,15 +1319,21 @@ export {
993
1319
  parsePatch,
994
1320
  applyHunks,
995
1321
  patchTools,
1322
+ WORKSPACE_READ_TOOLS,
1323
+ WORKSPACE_READ_JUDGING_MODES,
1324
+ WORKSPACE_READ_REFUSAL_MODES,
1325
+ WORKSPACE_READ_ONLY_GIT_TOOLS,
996
1326
  parseRule,
997
1327
  permissionRuleForToolCall,
998
1328
  permissionResultOf,
999
1329
  permissionRuleOf,
1330
+ permissionReasonOf,
1000
1331
  permissionRuleLadder,
1001
1332
  permissionRuleMatchesCall,
1002
1333
  primaryArgForRule,
1003
1334
  hasAdjudicationPolicy,
1004
1335
  evaluatePermission,
1005
- evaluatePermissionDetail
1336
+ evaluatePermissionDetail,
1337
+ ALWAYS_ALLOWED_TOOLS
1006
1338
  };
1007
- //# sourceMappingURL=chunk-MAQXCUR4.js.map
1339
+ //# sourceMappingURL=chunk-J62VZYOB.js.map