@letrquan/book 0.2.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 (103) hide show
  1. package/CHANGELOG.md +2504 -0
  2. package/LICENSE +123 -0
  3. package/README.md +1383 -0
  4. package/dist/app-MOXXWEOY.js +18945 -0
  5. package/dist/app-MOXXWEOY.js.map +1 -0
  6. package/dist/chunk-24AXE6SP.js +12 -0
  7. package/dist/chunk-24AXE6SP.js.map +1 -0
  8. package/dist/chunk-3U5IBM24.js +213 -0
  9. package/dist/chunk-3U5IBM24.js.map +1 -0
  10. package/dist/chunk-574C6MQR.js +743 -0
  11. package/dist/chunk-574C6MQR.js.map +1 -0
  12. package/dist/chunk-5GDZ22YP.js +449 -0
  13. package/dist/chunk-5GDZ22YP.js.map +1 -0
  14. package/dist/chunk-5KLVH3PY.js +167 -0
  15. package/dist/chunk-5KLVH3PY.js.map +1 -0
  16. package/dist/chunk-5RDRAO4B.js +92 -0
  17. package/dist/chunk-5RDRAO4B.js.map +1 -0
  18. package/dist/chunk-7N4J557C.js +10 -0
  19. package/dist/chunk-7N4J557C.js.map +1 -0
  20. package/dist/chunk-A7JLBK2Y.js +3506 -0
  21. package/dist/chunk-A7JLBK2Y.js.map +1 -0
  22. package/dist/chunk-ANSJPYGI.js +84 -0
  23. package/dist/chunk-ANSJPYGI.js.map +1 -0
  24. package/dist/chunk-BTZZUCLI.js +264 -0
  25. package/dist/chunk-BTZZUCLI.js.map +1 -0
  26. package/dist/chunk-C7D4NJ2D.js +217 -0
  27. package/dist/chunk-C7D4NJ2D.js.map +1 -0
  28. package/dist/chunk-CAGOB2N4.js +208 -0
  29. package/dist/chunk-CAGOB2N4.js.map +1 -0
  30. package/dist/chunk-DGMQEI6Y.js +98 -0
  31. package/dist/chunk-DGMQEI6Y.js.map +1 -0
  32. package/dist/chunk-E75IPST5.js +289 -0
  33. package/dist/chunk-E75IPST5.js.map +1 -0
  34. package/dist/chunk-EYGD6X2T.js +180 -0
  35. package/dist/chunk-EYGD6X2T.js.map +1 -0
  36. package/dist/chunk-GTVB4HEM.js +765 -0
  37. package/dist/chunk-GTVB4HEM.js.map +1 -0
  38. package/dist/chunk-J7MIZ5CA.js +1907 -0
  39. package/dist/chunk-J7MIZ5CA.js.map +1 -0
  40. package/dist/chunk-KWHQ2XXL.js +626 -0
  41. package/dist/chunk-KWHQ2XXL.js.map +1 -0
  42. package/dist/chunk-L6LK2QIQ.js +657 -0
  43. package/dist/chunk-L6LK2QIQ.js.map +1 -0
  44. package/dist/chunk-MAQXCUR4.js +1007 -0
  45. package/dist/chunk-MAQXCUR4.js.map +1 -0
  46. package/dist/chunk-S4XL7HOM.js +402 -0
  47. package/dist/chunk-S4XL7HOM.js.map +1 -0
  48. package/dist/chunk-URLQAXTR.js +60 -0
  49. package/dist/chunk-URLQAXTR.js.map +1 -0
  50. package/dist/chunk-V4BOYR52.js +3504 -0
  51. package/dist/chunk-V4BOYR52.js.map +1 -0
  52. package/dist/chunk-VZQXX3WW.js +210 -0
  53. package/dist/chunk-VZQXX3WW.js.map +1 -0
  54. package/dist/chunk-WKBO5R4O.js +34 -0
  55. package/dist/chunk-WKBO5R4O.js.map +1 -0
  56. package/dist/chunk-XIFFTATU.js +15753 -0
  57. package/dist/chunk-XIFFTATU.js.map +1 -0
  58. package/dist/command-approvals-6Y57VCWZ.js +28 -0
  59. package/dist/command-approvals-6Y57VCWZ.js.map +1 -0
  60. package/dist/context-ZC5IFEAX.js +25 -0
  61. package/dist/context-ZC5IFEAX.js.map +1 -0
  62. package/dist/hook-approvals-CTUFKEJT.js +22 -0
  63. package/dist/hook-approvals-CTUFKEJT.js.map +1 -0
  64. package/dist/index.d.ts +1 -0
  65. package/dist/index.js +2582 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/interactive-assets-CCAAWVKH.js +31 -0
  68. package/dist/interactive-assets-CCAAWVKH.js.map +1 -0
  69. package/dist/job-runner.d.ts +2 -0
  70. package/dist/job-runner.js +235 -0
  71. package/dist/job-runner.js.map +1 -0
  72. package/dist/loader-4IDS2RIE.js +15 -0
  73. package/dist/loader-4IDS2RIE.js.map +1 -0
  74. package/dist/mcp-approvals-WNPH65BK.js +18 -0
  75. package/dist/mcp-approvals-WNPH65BK.js.map +1 -0
  76. package/dist/mcp-config-YIJIES2C.js +16 -0
  77. package/dist/mcp-config-YIJIES2C.js.map +1 -0
  78. package/dist/model-window-store-YFVNXCIP.js +27 -0
  79. package/dist/model-window-store-YFVNXCIP.js.map +1 -0
  80. package/dist/openai-compatible-RHZUBHV6.js +10 -0
  81. package/dist/openai-compatible-RHZUBHV6.js.map +1 -0
  82. package/dist/permission-approvals-76PVREFD.js +14 -0
  83. package/dist/permission-approvals-76PVREFD.js.map +1 -0
  84. package/dist/permissions-CHCKTFQQ.js +28 -0
  85. package/dist/permissions-CHCKTFQQ.js.map +1 -0
  86. package/dist/sandbox-CNVAQDA7.js +23 -0
  87. package/dist/sandbox-CNVAQDA7.js.map +1 -0
  88. package/dist/scrollback-LUKLIBVI.js +136 -0
  89. package/dist/scrollback-LUKLIBVI.js.map +1 -0
  90. package/dist/sdk.d.ts +4574 -0
  91. package/dist/sdk.js +665 -0
  92. package/dist/sdk.js.map +1 -0
  93. package/dist/settings-loader-M2QP5TX2.js +21 -0
  94. package/dist/settings-loader-M2QP5TX2.js.map +1 -0
  95. package/dist/settings-removed-JWWPT4WA.js +19 -0
  96. package/dist/settings-removed-JWWPT4WA.js.map +1 -0
  97. package/dist/shell-selection-V6QAYXPX.js +17 -0
  98. package/dist/shell-selection-V6QAYXPX.js.map +1 -0
  99. package/package.json +143 -0
  100. package/patches/ink+6.8.0.patch +13 -0
  101. package/scripts/apply-ink-patch.mjs +29 -0
  102. package/scripts/ink-patch.mjs +59 -0
  103. package/scripts/verify-ink-patch.mjs +11 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/ink-scroll-renderer.ts","../src/cli/frame-buffer.ts"],"sourcesContent":["import { createRequire } from 'node:module';\r\nimport { dirname, join } from 'node:path';\r\nimport { pathToFileURL } from 'node:url';\r\n\r\nexport interface TranscriptScrollHint {\r\n top: number;\r\n bottom: number;\r\n delta: number;\r\n}\r\n\r\ninterface InkStream {\r\n isTTY?: boolean;\r\n rows?: number;\r\n write: (data: string) => unknown;\r\n}\r\n\r\ninterface LogUpdateRenderer {\r\n (output: string): boolean | void;\r\n clear: () => void;\r\n done: () => void;\r\n sync: (output: string) => void;\r\n setCursorPosition: (position: unknown) => void;\r\n isCursorDirty: () => boolean;\r\n willRender: (output: string) => boolean;\r\n}\r\n\r\ninterface LogUpdateModule {\r\n create: (stream: InkStream, options?: { incremental?: boolean }) => LogUpdateRenderer;\r\n}\r\n\r\nlet pendingHint: TranscriptScrollHint | null = null;\r\nlet rendererInstalled = false;\r\n\r\nexport function isInkScrollRendererEnabled(value?: string): boolean {\r\n return value === 'on';\r\n}\r\n\r\nexport function setTranscriptScrollHint(hint: TranscriptScrollHint): void {\r\n if (!Number.isFinite(hint.delta) || hint.delta === 0) return;\r\n if (\r\n pendingHint &&\r\n pendingHint.top === hint.top &&\r\n pendingHint.bottom === hint.bottom &&\r\n Math.sign(pendingHint.delta) === Math.sign(hint.delta)\r\n ) {\r\n pendingHint = { ...pendingHint, delta: pendingHint.delta + hint.delta };\r\n return;\r\n }\r\n pendingHint = { ...hint };\r\n}\r\n\r\nfunction splitFrame(frame: string): string[] {\r\n return frame.endsWith('\\n') ? frame.slice(0, -1).split('\\n') : frame.split('\\n');\r\n}\r\n\r\nfunction getFrameEndRow(frame: string, visibleLineCount: number): number {\r\n return visibleLineCount + (frame.endsWith('\\n') ? 1 : 0);\r\n}\r\n\r\nfunction cursorTo(row: number): string {\r\n return `\\x1b[${Math.max(1, Math.floor(row))};1H`;\r\n}\r\n\r\nfunction setScrollRegion(top: number, bottom: number): string {\r\n return `\\x1b[${Math.max(1, Math.floor(top))};${Math.max(1, Math.floor(bottom))}r`;\r\n}\r\n\r\nexport function isPureVerticalShift(\r\n previousLines: readonly string[],\r\n nextLines: readonly string[],\r\n hint: TranscriptScrollHint,\r\n): boolean {\r\n if (previousLines.length !== nextLines.length) return false;\r\n const top = Math.floor(hint.top) - 1;\r\n const bottom = Math.floor(hint.bottom);\r\n const delta = Math.trunc(hint.delta);\r\n if (top < 0 || bottom > nextLines.length || bottom - top < 2) return false;\r\n if (Math.abs(delta) >= bottom - top) return false;\r\n\r\n for (let row = 0; row < top; row++) {\r\n if (previousLines[row] !== nextLines[row]) return false;\r\n }\r\n for (let row = bottom; row < nextLines.length; row++) {\r\n if (previousLines[row] !== nextLines[row]) return false;\r\n }\r\n\r\n if (delta > 0) {\r\n for (let row = top; row < bottom - delta; row++) {\r\n if (nextLines[row] !== previousLines[row + delta]) return false;\r\n }\r\n } else {\r\n const distance = Math.abs(delta);\r\n for (let row = top + distance; row < bottom; row++) {\r\n if (nextLines[row] !== previousLines[row - distance]) return false;\r\n }\r\n }\r\n return true;\r\n}\r\n\r\nfunction writeExposedRows(\r\n stream: InkStream,\r\n lines: readonly string[],\r\n top: number,\r\n bottom: number,\r\n delta: number,\r\n): void {\r\n const distance = Math.abs(delta);\r\n const first = delta > 0 ? bottom - distance : top;\r\n for (let offset = 0; offset < distance; offset++) {\r\n const row = first + offset;\r\n stream.write(cursorTo(row + 1) + (lines[row] ?? '') + '\\x1b[K');\r\n }\r\n}\r\n\r\nfunction createScrollAwareRenderer(\r\n stream: InkStream,\r\n options: { incremental?: boolean } | undefined,\r\n createBase: LogUpdateModule['create'],\r\n): LogUpdateRenderer {\r\n const base = createBase(stream, options);\r\n let previousFrame = '';\r\n\r\n const render = ((output: string) => {\r\n const hint = pendingHint;\r\n pendingHint = null;\r\n const previousLines = splitFrame(previousFrame);\r\n const nextLines = splitFrame(output);\r\n const terminalRows = stream.rows ?? 0;\r\n const frameEndRow = getFrameEndRow(output, nextLines.length);\r\n const canScroll =\r\n Boolean(stream.isTTY) &&\r\n terminalRows > 0 &&\r\n frameEndRow <= terminalRows &&\r\n previousFrame.length > 0 &&\r\n hint !== null &&\r\n hint.bottom <= nextLines.length &&\r\n isPureVerticalShift(previousLines, nextLines, hint);\r\n\r\n if (canScroll && hint) {\r\n const top = Math.floor(hint.top);\r\n const bottom = Math.floor(hint.bottom);\r\n const delta = Math.trunc(hint.delta);\r\n stream.write('\\x1b[?25l' + setScrollRegion(top, bottom));\r\n stream.write(`\\x1b[${Math.abs(delta)}${delta > 0 ? 'S' : 'T'}`);\r\n writeExposedRows(stream, nextLines, top - 1, bottom, delta);\r\n // Ink's sync() positions the input cursor relative to the frame bottom.\r\n // Re-anchor there first so its cursor suffix cannot repaint the prompt on\r\n // a transcript row after a terminal-native scroll.\r\n stream.write(setScrollRegion(1, terminalRows) + cursorTo(frameEndRow));\r\n previousFrame = output;\r\n base.sync(output);\r\n return true;\r\n }\r\n\r\n previousFrame = output;\r\n return base(output);\r\n }) as LogUpdateRenderer;\r\n\r\n render.clear = () => {\r\n pendingHint = null;\r\n previousFrame = '';\r\n base.clear();\r\n };\r\n render.done = () => {\r\n pendingHint = null;\r\n previousFrame = '';\r\n base.done();\r\n };\r\n render.sync = (output: string) => {\r\n pendingHint = null;\r\n previousFrame = output;\r\n base.sync(output);\r\n };\r\n render.setCursorPosition = (position: unknown) => base.setCursorPosition(position);\r\n render.isCursorDirty = () => base.isCursorDirty();\r\n render.willRender = (output: string) => {\r\n const shouldRender = base.willRender(output);\r\n if (!shouldRender) pendingHint = null;\r\n return shouldRender;\r\n };\r\n return render;\r\n}\r\n\r\nexport async function installInkScrollRenderer(enabled?: boolean): Promise<void> {\r\n if (rendererInstalled) return;\r\n // DECSTBM/CSI S/T rendering remains available for profiling, but Windows\r\n // ConPTY can corrupt fixed footer rows after a later incremental repaint.\r\n if (!(enabled ?? isInkScrollRendererEnabled(process.env.BOOK_SCROLL_RENDERER))) return;\r\n rendererInstalled = true;\r\n\r\n try {\r\n const require = createRequire(import.meta.url);\r\n const inkEntry = require.resolve('ink');\r\n const logUpdateUrl = pathToFileURL(join(dirname(inkEntry), 'log-update.js')).href;\r\n const module = (await import(logUpdateUrl)) as { default: LogUpdateModule };\r\n const logUpdate = module.default;\r\n const createBase = logUpdate.create;\r\n logUpdate.create = (stream, options) => createScrollAwareRenderer(stream, options, createBase);\r\n } catch {\r\n // Keep Ink's built-in renderer when its internal module layout changes.\r\n }\r\n}\r\n","import { createRequire } from 'node:module';\r\nimport { dirname, join } from 'node:path';\r\nimport { pathToFileURL } from 'node:url';\r\n\r\nexport interface FrameCursorPosition {\r\n x: number;\r\n y: number;\r\n}\r\n\r\ninterface InkStream {\r\n isTTY?: boolean;\r\n rows?: number;\r\n write: (data: string) => unknown;\r\n}\r\n\r\ninterface LogUpdateRenderer {\r\n (output: string): boolean | void;\r\n clear: () => void;\r\n done: () => void;\r\n sync: (output: string) => void;\r\n setCursorPosition: (position: unknown) => void;\r\n isCursorDirty: () => boolean;\r\n willRender: (output: string) => boolean;\r\n}\r\n\r\ninterface LogUpdateModule {\r\n create: (stream: InkStream, options?: { incremental?: boolean }) => LogUpdateRenderer;\r\n}\r\n\r\nlet latestFrame = '';\r\nlet lastCursorPosition: FrameCursorPosition | null = null;\r\nlet captureInstalled = false;\r\nconst frameUpdateListeners = new Set<() => void>();\r\n\r\nfunction notifyFrameUpdate(): void {\r\n for (const listener of frameUpdateListeners) {\r\n try {\r\n listener();\r\n } catch {\r\n // A terminal overlay must never break Ink's renderer.\r\n }\r\n }\r\n}\r\n\r\nfunction splitFrame(frame: string): string[] {\r\n return frame.endsWith('\\n') ? frame.slice(0, -1).split('\\n') : frame.split('\\n');\r\n}\r\n\r\nfunction toCursorPosition(position: unknown): FrameCursorPosition | null {\r\n if (!position || typeof position !== 'object') return null;\r\n const candidate = position as { x?: unknown; y?: unknown };\r\n if (typeof candidate.x !== 'number' || typeof candidate.y !== 'number') return null;\r\n return { x: candidate.x, y: candidate.y };\r\n}\r\n\r\n/** Lines of the most recently rendered full-screen frame. */\r\nexport function getFrameLines(): string[] {\r\n return latestFrame.length === 0 ? [] : splitFrame(latestFrame);\r\n}\r\n\r\n/** Last cursor position Ink asked the renderer to restore, if any. */\r\nexport function getLastCursorPosition(): FrameCursorPosition | null {\r\n return lastCursorPosition;\r\n}\r\n\r\n/** Subscribe to completed Ink frame writes, used to preserve terminal overlays. */\r\nexport function subscribeToFrameUpdates(listener: () => void): () => void {\r\n frameUpdateListeners.add(listener);\r\n return () => frameUpdateListeners.delete(listener);\r\n}\r\n\r\n/** Override the captured snapshot. Used by focused selection tests. */\r\nexport function setFrameSnapshotForTesting(\r\n frame: string | null,\r\n cursor: FrameCursorPosition | null = null,\r\n): void {\r\n latestFrame = frame ?? '';\r\n lastCursorPosition = cursor;\r\n}\r\n\r\nexport function createFrameCapturingRenderer(\r\n stream: InkStream,\r\n options: { incremental?: boolean } | undefined,\r\n createBase: LogUpdateModule['create'],\r\n): LogUpdateRenderer {\r\n const base = createBase(stream, options);\r\n\r\n const render = ((output: string) => {\r\n latestFrame = output;\r\n const rendered = base(output);\r\n if (rendered !== false) notifyFrameUpdate();\r\n return rendered;\r\n }) as LogUpdateRenderer;\r\n\r\n render.clear = () => {\r\n latestFrame = '';\r\n base.clear();\r\n };\r\n render.done = () => {\r\n latestFrame = '';\r\n lastCursorPosition = null;\r\n base.done();\r\n };\r\n render.sync = (output: string) => {\r\n latestFrame = output;\r\n base.sync(output);\r\n notifyFrameUpdate();\r\n };\r\n render.setCursorPosition = (position: unknown) => {\r\n lastCursorPosition = toCursorPosition(position);\r\n base.setCursorPosition(position);\r\n };\r\n render.isCursorDirty = () => base.isCursorDirty();\r\n render.willRender = (output: string) => base.willRender(output);\r\n return render;\r\n}\r\n\r\n/** Capture Ink's latest full frame so mouse selection can extract visible text. */\r\nexport async function installFrameCapture(): Promise<void> {\r\n if (captureInstalled) return;\r\n captureInstalled = true;\r\n\r\n try {\r\n const require = createRequire(import.meta.url);\r\n const inkEntry = require.resolve('ink');\r\n const logUpdateUrl = pathToFileURL(join(dirname(inkEntry), 'log-update.js')).href;\r\n const module = (await import(logUpdateUrl)) as { default: LogUpdateModule };\r\n const logUpdate = module.default;\r\n const createBase = logUpdate.create;\r\n logUpdate.create = (stream, options) =>\r\n createFrameCapturingRenderer(stream, options, createBase);\r\n } catch {\r\n // Keep the TUI usable if Ink changes its private renderer layout.\r\n }\r\n}\r\n"],"mappings":"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","names":["require","createRequire","dirname","join","pathToFileURL","splitFrame","require","createRequire","pathToFileURL","join","dirname"]}
@@ -0,0 +1,208 @@
1
+ import {
2
+ writeFileAtomic
3
+ } from "./chunk-574C6MQR.js";
4
+ import {
5
+ resolveBookHome
6
+ } from "./chunk-24AXE6SP.js";
7
+
8
+ // src/model-window-store.ts
9
+ import { existsSync, readFileSync } from "fs";
10
+ import { join } from "path";
11
+ import { z } from "zod";
12
+ var MODEL_WINDOW_STORE_VERSION = 1;
13
+ var LEARNED_WINDOW_SAFETY_MARGIN = 0.8;
14
+ var MIN_LEARNED_CONTEXT_WINDOW = 16384;
15
+ var learnedModelWindowEntrySchema = z.object({
16
+ contextWindow: z.number().int().positive(),
17
+ learnedAt: z.number().int().nonnegative()
18
+ });
19
+ var looseModelWindowStoreSchema = z.object({
20
+ version: z.number().int().positive().default(MODEL_WINDOW_STORE_VERSION),
21
+ models: z.record(z.unknown()).default({})
22
+ });
23
+ function emptyStore() {
24
+ return { version: MODEL_WINDOW_STORE_VERSION, models: {} };
25
+ }
26
+ function defaultModelWindowStorePath(home) {
27
+ return home ? join(home, "model-windows.json") : join(resolveBookHome(), "model-windows.json");
28
+ }
29
+ function readModelWindowStore(options) {
30
+ const filePath = options?.path ?? defaultModelWindowStorePath(options?.home);
31
+ if (!existsSync(filePath)) {
32
+ return emptyStore();
33
+ }
34
+ try {
35
+ const raw = readFileSync(filePath, "utf-8").trim();
36
+ if (!raw) return emptyStore();
37
+ const parsed = JSON.parse(raw);
38
+ const validated = looseModelWindowStoreSchema.safeParse(parsed);
39
+ if (!validated.success) {
40
+ return emptyStore();
41
+ }
42
+ const models = {};
43
+ for (const [key, value] of Object.entries(validated.data.models)) {
44
+ const entryResult = learnedModelWindowEntrySchema.safeParse(value);
45
+ if (entryResult.success) {
46
+ models[key] = entryResult.data;
47
+ }
48
+ }
49
+ return {
50
+ version: validated.data.version,
51
+ models
52
+ };
53
+ } catch {
54
+ return emptyStore();
55
+ }
56
+ }
57
+ function writeModelWindowStore(data, options) {
58
+ if (data.version > MODEL_WINDOW_STORE_VERSION) {
59
+ return false;
60
+ }
61
+ const filePath = options?.path ?? defaultModelWindowStorePath(options?.home);
62
+ try {
63
+ if (existsSync(filePath)) {
64
+ const existing = readModelWindowStore(options);
65
+ if (existing.version > MODEL_WINDOW_STORE_VERSION) {
66
+ return false;
67
+ }
68
+ }
69
+ const serialized = JSON.stringify(data, null, 2) + "\n";
70
+ writeFileAtomic(filePath, serialized);
71
+ return true;
72
+ } catch {
73
+ return false;
74
+ }
75
+ }
76
+ function ratchetModelWindow(model, ceiling, options) {
77
+ return createModelWindowStore(options).ratchet(model, ceiling);
78
+ }
79
+ var FileModelWindowStore = class {
80
+ inner;
81
+ constructor(options) {
82
+ this.inner = createModelWindowStore(options);
83
+ }
84
+ get(model) {
85
+ return this.inner.get(model);
86
+ }
87
+ getEntry(model) {
88
+ return this.inner.getEntry(model);
89
+ }
90
+ ratchet(model, ceiling) {
91
+ return this.inner.ratchet(model, ceiling);
92
+ }
93
+ all() {
94
+ return this.inner.all();
95
+ }
96
+ };
97
+ var MemoryModelWindowStore = class {
98
+ models = /* @__PURE__ */ new Map();
99
+ now;
100
+ constructor(initial, now = Date.now) {
101
+ this.now = now;
102
+ if (initial) {
103
+ for (const [k, v] of Object.entries(initial)) {
104
+ this.models.set(k, { ...v });
105
+ }
106
+ }
107
+ }
108
+ get(model) {
109
+ return this.models.get(model)?.contextWindow;
110
+ }
111
+ getEntry(model) {
112
+ const entry = this.models.get(model);
113
+ return entry ? { ...entry } : void 0;
114
+ }
115
+ ratchet(model, ceiling) {
116
+ if (!model || typeof ceiling !== "number" || !Number.isFinite(ceiling) || ceiling < MIN_LEARNED_CONTEXT_WINDOW) {
117
+ return false;
118
+ }
119
+ const normalized = Math.floor(ceiling);
120
+ const existing = this.models.get(model);
121
+ if (existing && existing.contextWindow <= normalized) {
122
+ return false;
123
+ }
124
+ this.models.set(model, { contextWindow: normalized, learnedAt: this.now() });
125
+ return true;
126
+ }
127
+ all() {
128
+ const result = {};
129
+ for (const [k, v] of this.models.entries()) {
130
+ result[k] = { ...v };
131
+ }
132
+ return result;
133
+ }
134
+ };
135
+ function createModelWindowStore(options) {
136
+ let cachedData;
137
+ function load() {
138
+ if (cachedData === void 0) {
139
+ cachedData = readModelWindowStore(options);
140
+ }
141
+ return cachedData;
142
+ }
143
+ return {
144
+ get(model) {
145
+ if (!model) return void 0;
146
+ return load().models[model]?.contextWindow;
147
+ },
148
+ getEntry(model) {
149
+ if (!model) return void 0;
150
+ const entry = load().models[model];
151
+ return entry ? { ...entry } : void 0;
152
+ },
153
+ ratchet(model, ceiling) {
154
+ if (!model || typeof ceiling !== "number" || !Number.isFinite(ceiling) || ceiling < MIN_LEARNED_CONTEXT_WINDOW) {
155
+ return false;
156
+ }
157
+ const fresh = readModelWindowStore(options);
158
+ if (fresh.version > MODEL_WINDOW_STORE_VERSION) {
159
+ return false;
160
+ }
161
+ const normalizedCeiling = Math.floor(ceiling);
162
+ const existingEntry = fresh.models[model];
163
+ const targetCeiling = existingEntry ? Math.min(existingEntry.contextWindow, normalizedCeiling) : normalizedCeiling;
164
+ if (existingEntry && existingEntry.contextWindow === targetCeiling) {
165
+ return false;
166
+ }
167
+ const now = options?.now ? options.now() : Date.now();
168
+ const updated = {
169
+ version: fresh.version,
170
+ models: {
171
+ ...fresh.models,
172
+ [model]: {
173
+ contextWindow: targetCeiling,
174
+ learnedAt: now
175
+ }
176
+ }
177
+ };
178
+ const written = writeModelWindowStore(updated, options);
179
+ if (written) {
180
+ cachedData = updated;
181
+ return true;
182
+ }
183
+ return false;
184
+ },
185
+ all() {
186
+ const models = load().models;
187
+ const result = {};
188
+ for (const [k, v] of Object.entries(models)) {
189
+ result[k] = { ...v };
190
+ }
191
+ return result;
192
+ }
193
+ };
194
+ }
195
+
196
+ export {
197
+ MODEL_WINDOW_STORE_VERSION,
198
+ LEARNED_WINDOW_SAFETY_MARGIN,
199
+ MIN_LEARNED_CONTEXT_WINDOW,
200
+ defaultModelWindowStorePath,
201
+ readModelWindowStore,
202
+ writeModelWindowStore,
203
+ ratchetModelWindow,
204
+ FileModelWindowStore,
205
+ MemoryModelWindowStore,
206
+ createModelWindowStore
207
+ };
208
+ //# sourceMappingURL=chunk-CAGOB2N4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/model-window-store.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\r\nimport { join } from 'node:path';\r\nimport { z } from 'zod';\r\nimport { resolveBookHome } from './book-home.js';\r\nimport { writeFileAtomic } from './settings-repository.js';\r\n\r\nexport const MODEL_WINDOW_STORE_VERSION = 1;\r\n\r\n/**\r\n * Fraction of the refused prompt history recorded as the learned window ceiling.\r\n *\r\n * When a provider rejects a request for exceeding the context limit, the history\r\n * tokens at that moment represent a size that was REFUSED, not a size the provider\r\n * accepts. Recording the refused size directly would set a ceiling larger than the\r\n * true context window (e.g. true window 100k, refused history 150k -> ceiling 150k),\r\n * causing repeated user-visible failures. Applying a safety margin ensures the\r\n * recorded ceiling is strictly below the refused size and converges rapidly to a\r\n * viable operating limit.\r\n */\r\nexport const LEARNED_WINDOW_SAFETY_MARGIN = 0.8;\r\n\r\n/**\r\n * Minimum context window ceiling recorded from an overflow refusal.\r\n *\r\n * Sane coding sessions require at least 16k tokens for tools, system prompt,\r\n * and working context. Any refusal below this floor is far more likely to be an\r\n * upstream proxy error (e.g. 413 Request Entity Too Large) or bad telemetry\r\n * than a real context limit. Refusals below this floor are ignored rather than\r\n * recorded, preventing a transient error from permanently bricking the model.\r\n */\r\nexport const MIN_LEARNED_CONTEXT_WINDOW = 16_384;\r\n\r\nexport interface LearnedModelWindowEntry {\r\n contextWindow: number;\r\n learnedAt: number;\r\n}\r\n\r\nexport interface ModelWindowStoreData {\r\n version: number;\r\n models: Record<string, LearnedModelWindowEntry>;\r\n}\r\n\r\nexport interface ModelWindowStoreOptions {\r\n /** Explicit BOOK_HOME directory root (used by tests to isolate storage). */\r\n home?: string;\r\n /** Explicit file path override (used by tests). */\r\n path?: string;\r\n /** Optional clock override for timestamps (used by tests). */\r\n now?: () => number;\r\n}\r\n\r\nexport interface ModelWindowStore {\r\n get(model: string): number | undefined;\r\n getEntry(model: string): LearnedModelWindowEntry | undefined;\r\n ratchet(model: string, ceiling: number): boolean;\r\n all(): Record<string, LearnedModelWindowEntry>;\r\n}\r\n\r\nconst learnedModelWindowEntrySchema = z.object({\r\n contextWindow: z.number().int().positive(),\r\n learnedAt: z.number().int().nonnegative(),\r\n});\r\n\r\nconst looseModelWindowStoreSchema = z.object({\r\n version: z.number().int().positive().default(MODEL_WINDOW_STORE_VERSION),\r\n models: z.record(z.unknown()).default({}),\r\n});\r\n\r\nfunction emptyStore(): ModelWindowStoreData {\r\n return { version: MODEL_WINDOW_STORE_VERSION, models: {} };\r\n}\r\n\r\n/**\r\n * Resolved location for the user-global learned-window store.\r\n *\r\n * This file lives under BOOK_HOME (e.g. ~/.book/model-windows.json), NEVER inside\r\n * the workspace. A repository must never be able to declare or tamper with\r\n * learned window ceilings for itself.\r\n */\r\nexport function defaultModelWindowStorePath(home?: string): string {\r\n return home ? join(home, 'model-windows.json') : join(resolveBookHome(), 'model-windows.json');\r\n}\r\n\r\n/**\r\n * Read the learned-window store.\r\n * Tolerates missing, empty, or corrupt files by returning an empty store.\r\n * Never throws into a run.\r\n */\r\nexport function readModelWindowStore(options?: ModelWindowStoreOptions): ModelWindowStoreData {\r\n const filePath = options?.path ?? defaultModelWindowStorePath(options?.home);\r\n if (!existsSync(filePath)) {\r\n return emptyStore();\r\n }\r\n try {\r\n const raw = readFileSync(filePath, 'utf-8').trim();\r\n if (!raw) return emptyStore();\r\n const parsed = JSON.parse(raw);\r\n const validated = looseModelWindowStoreSchema.safeParse(parsed);\r\n if (!validated.success) {\r\n return emptyStore();\r\n }\r\n const models: Record<string, LearnedModelWindowEntry> = {};\r\n for (const [key, value] of Object.entries(validated.data.models)) {\r\n const entryResult = learnedModelWindowEntrySchema.safeParse(value);\r\n if (entryResult.success) {\r\n models[key] = entryResult.data;\r\n }\r\n }\r\n return {\r\n version: validated.data.version,\r\n models,\r\n };\r\n } catch {\r\n return emptyStore();\r\n }\r\n}\r\n\r\n/**\r\n * Atomically persist the learned-window store.\r\n * Never throws into a run.\r\n */\r\nexport function writeModelWindowStore(\r\n data: ModelWindowStoreData,\r\n options?: ModelWindowStoreOptions,\r\n): boolean {\r\n if (data.version > MODEL_WINDOW_STORE_VERSION) {\r\n return false;\r\n }\r\n const filePath = options?.path ?? defaultModelWindowStorePath(options?.home);\r\n try {\r\n if (existsSync(filePath)) {\r\n const existing = readModelWindowStore(options);\r\n if (existing.version > MODEL_WINDOW_STORE_VERSION) {\r\n return false;\r\n }\r\n }\r\n const serialized = JSON.stringify(data, null, 2) + '\\n';\r\n writeFileAtomic(filePath, serialized);\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Records a learned context window ceiling for a model.\r\n *\r\n * Ratchet, downward only:\r\n * - If no entry exists for the model, records the new ceiling.\r\n * - If an entry exists with a larger ceiling, lowers it.\r\n * - If an entry exists with an equal or smaller ceiling, changes nothing.\r\n *\r\n * Monotonicity is strict: a later overflow at a smaller size lowers it further;\r\n * an overflow at a larger size does not raise it.\r\n *\r\n * Returns true if the store was updated with a new ceiling, false otherwise.\r\n */\r\nexport function ratchetModelWindow(\r\n model: string,\r\n ceiling: number,\r\n options?: ModelWindowStoreOptions,\r\n): boolean {\r\n return createModelWindowStore(options).ratchet(model, ceiling);\r\n}\r\n\r\n/**\r\n * File-backed implementation of ModelWindowStore.\r\n * Delegates to createModelWindowStore for closure-based, freeze-safe caching.\r\n */\r\nexport class FileModelWindowStore implements ModelWindowStore {\r\n private readonly inner: ModelWindowStore;\r\n\r\n constructor(options?: ModelWindowStoreOptions) {\r\n this.inner = createModelWindowStore(options);\r\n }\r\n\r\n get(model: string): number | undefined {\r\n return this.inner.get(model);\r\n }\r\n\r\n getEntry(model: string): LearnedModelWindowEntry | undefined {\r\n return this.inner.getEntry(model);\r\n }\r\n\r\n ratchet(model: string, ceiling: number): boolean {\r\n return this.inner.ratchet(model, ceiling);\r\n }\r\n\r\n all(): Record<string, LearnedModelWindowEntry> {\r\n return this.inner.all();\r\n }\r\n}\r\n\r\n/**\r\n * In-memory implementation of ModelWindowStore, useful for unit tests.\r\n */\r\nexport class MemoryModelWindowStore implements ModelWindowStore {\r\n private readonly models = new Map<string, LearnedModelWindowEntry>();\r\n private readonly now: () => number;\r\n\r\n constructor(initial?: Record<string, LearnedModelWindowEntry>, now = Date.now) {\r\n this.now = now;\r\n if (initial) {\r\n for (const [k, v] of Object.entries(initial)) {\r\n this.models.set(k, { ...v });\r\n }\r\n }\r\n }\r\n\r\n get(model: string): number | undefined {\r\n return this.models.get(model)?.contextWindow;\r\n }\r\n\r\n getEntry(model: string): LearnedModelWindowEntry | undefined {\r\n const entry = this.models.get(model);\r\n return entry ? { ...entry } : undefined;\r\n }\r\n\r\n ratchet(model: string, ceiling: number): boolean {\r\n if (\r\n !model ||\r\n typeof ceiling !== 'number' ||\r\n !Number.isFinite(ceiling) ||\r\n ceiling < MIN_LEARNED_CONTEXT_WINDOW\r\n ) {\r\n return false;\r\n }\r\n const normalized = Math.floor(ceiling);\r\n const existing = this.models.get(model);\r\n if (existing && existing.contextWindow <= normalized) {\r\n return false;\r\n }\r\n this.models.set(model, { contextWindow: normalized, learnedAt: this.now() });\r\n return true;\r\n }\r\n\r\n all(): Record<string, LearnedModelWindowEntry> {\r\n const result: Record<string, LearnedModelWindowEntry> = {};\r\n for (const [k, v] of this.models.entries()) {\r\n result[k] = { ...v };\r\n }\r\n return result;\r\n }\r\n}\r\n\r\n/**\r\n * Create a freeze-safe ModelWindowStore instance.\r\n *\r\n * Cache state is held in a closure rather than as object properties, ensuring\r\n * that Object.freeze / deepFreeze(config) does not break subsequent memoization.\r\n */\r\nexport function createModelWindowStore(options?: ModelWindowStoreOptions): ModelWindowStore {\r\n let cachedData: ModelWindowStoreData | undefined;\r\n\r\n function load(): ModelWindowStoreData {\r\n if (cachedData === undefined) {\r\n cachedData = readModelWindowStore(options);\r\n }\r\n return cachedData;\r\n }\r\n\r\n return {\r\n get(model: string): number | undefined {\r\n if (!model) return undefined;\r\n return load().models[model]?.contextWindow;\r\n },\r\n\r\n getEntry(model: string): LearnedModelWindowEntry | undefined {\r\n if (!model) return undefined;\r\n const entry = load().models[model];\r\n return entry ? { ...entry } : undefined;\r\n },\r\n\r\n ratchet(model: string, ceiling: number): boolean {\r\n if (\r\n !model ||\r\n typeof ceiling !== 'number' ||\r\n !Number.isFinite(ceiling) ||\r\n ceiling < MIN_LEARNED_CONTEXT_WINDOW\r\n ) {\r\n return false;\r\n }\r\n const fresh = readModelWindowStore(options);\r\n if (fresh.version > MODEL_WINDOW_STORE_VERSION) {\r\n return false;\r\n }\r\n const normalizedCeiling = Math.floor(ceiling);\r\n const existingEntry = fresh.models[model];\r\n const targetCeiling = existingEntry\r\n ? Math.min(existingEntry.contextWindow, normalizedCeiling)\r\n : normalizedCeiling;\r\n\r\n if (existingEntry && existingEntry.contextWindow === targetCeiling) {\r\n // If the merged value equals what is already on disk, write nothing and return false.\r\n return false;\r\n }\r\n\r\n const now = options?.now ? options.now() : Date.now();\r\n const updated: ModelWindowStoreData = {\r\n version: fresh.version,\r\n models: {\r\n ...fresh.models,\r\n [model]: {\r\n contextWindow: targetCeiling,\r\n learnedAt: now,\r\n },\r\n },\r\n };\r\n const written = writeModelWindowStore(updated, options);\r\n if (written) {\r\n cachedData = updated;\r\n return true;\r\n }\r\n return false;\r\n },\r\n\r\n all(): Record<string, LearnedModelWindowEntry> {\r\n const models = load().models;\r\n const result: Record<string, LearnedModelWindowEntry> = {};\r\n for (const [k, v] of Object.entries(models)) {\r\n result[k] = { ...v };\r\n }\r\n return result;\r\n },\r\n };\r\n}\r\n"],"mappings":"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","names":[]}
@@ -0,0 +1,98 @@
1
+ import {
2
+ mcpProjectServerChoiceSchema,
3
+ projectAllowRuleChoiceSchema,
4
+ projectCommandChoiceSchema,
5
+ projectHookChoiceSchema,
6
+ writeFileAtomic
7
+ } from "./chunk-574C6MQR.js";
8
+ import {
9
+ resolveBookHome
10
+ } from "./chunk-24AXE6SP.js";
11
+
12
+ // src/workspace-trust.ts
13
+ import { existsSync, readFileSync } from "fs";
14
+ import { join, resolve } from "path";
15
+ import { z } from "zod";
16
+ var TRUST_STORE_VERSION = 2;
17
+ var workspaceTrustSchema = z.object({
18
+ /** Keyed by the exact rule text the project layer declared. */
19
+ permissionAllowRules: z.record(projectAllowRuleChoiceSchema).default({}),
20
+ /** Keyed by server name, carrying the fingerprint the decision was made against. */
21
+ mcpServers: z.record(mcpProjectServerChoiceSchema).default({}),
22
+ /** Keyed by a fingerprint of `{ event, matcher, command, env }`. */
23
+ hookEntries: z.record(projectHookChoiceSchema).default({}),
24
+ /** Keyed by command name, carrying the fingerprint of the shell its body runs. */
25
+ projectCommands: z.record(projectCommandChoiceSchema).default({})
26
+ });
27
+ function emptyWorkspaceTrust() {
28
+ return { permissionAllowRules: {}, mcpServers: {}, hookEntries: {}, projectCommands: {} };
29
+ }
30
+ function defaultTrustStorePath(home) {
31
+ return home ? join(home, ".book", "trust.json") : join(resolveBookHome(), "trust.json");
32
+ }
33
+ function workspaceKey(workspace) {
34
+ const absolute = resolve(workspace);
35
+ return process.platform === "win32" ? absolute.toLowerCase() : absolute;
36
+ }
37
+ function readRawStore(path) {
38
+ if (!existsSync(path)) return { store: { workspaces: {} } };
39
+ let parsed;
40
+ try {
41
+ parsed = JSON.parse(readFileSync(path, "utf-8"));
42
+ } catch (error) {
43
+ return {
44
+ store: { workspaces: {} },
45
+ unreadable: `${path} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
46
+ };
47
+ }
48
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
49
+ return { store: { workspaces: {} }, unreadable: `${path} must contain a JSON object` };
50
+ }
51
+ const record = parsed;
52
+ const version = typeof record.version === "number" ? record.version : TRUST_STORE_VERSION;
53
+ if (version > TRUST_STORE_VERSION) {
54
+ return {
55
+ store: { workspaces: {} },
56
+ unreadable: `${path} was written by a newer version of Book (trust store v${version}); upgrade before recording new decisions`
57
+ };
58
+ }
59
+ const workspaces = record.workspaces;
60
+ if (workspaces === void 0) return { store: { workspaces: {} } };
61
+ if (workspaces === null || typeof workspaces !== "object" || Array.isArray(workspaces)) {
62
+ return { store: { workspaces: {} }, unreadable: `${path} "workspaces" must be an object` };
63
+ }
64
+ return { store: { workspaces: { ...workspaces } } };
65
+ }
66
+ function loadWorkspaceTrust(workspace, storePath = defaultTrustStorePath()) {
67
+ const { store } = readRawStore(storePath);
68
+ const parsed = workspaceTrustSchema.safeParse(store.workspaces[workspaceKey(workspace)] ?? {});
69
+ return parsed.success ? parsed.data : emptyWorkspaceTrust();
70
+ }
71
+ function updateWorkspaceTrust(workspace, mutate, storePath = defaultTrustStorePath()) {
72
+ const { store, unreadable } = readRawStore(storePath);
73
+ if (unreadable) return { ok: false, error: unreadable };
74
+ const key = workspaceKey(workspace);
75
+ const existing = workspaceTrustSchema.safeParse(store.workspaces[key] ?? {});
76
+ const candidate = existing.success ? existing.data : emptyWorkspaceTrust();
77
+ mutate(candidate);
78
+ const validated = workspaceTrustSchema.safeParse(candidate);
79
+ if (!validated.success) return { ok: false, error: validated.error.message };
80
+ const next = {
81
+ version: TRUST_STORE_VERSION,
82
+ workspaces: { ...store.workspaces, [key]: validated.data }
83
+ };
84
+ try {
85
+ writeFileAtomic(storePath, `${JSON.stringify(next, null, 2)}
86
+ `);
87
+ } catch (error) {
88
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
89
+ }
90
+ return { ok: true };
91
+ }
92
+
93
+ export {
94
+ defaultTrustStorePath,
95
+ loadWorkspaceTrust,
96
+ updateWorkspaceTrust
97
+ };
98
+ //# sourceMappingURL=chunk-DGMQEI6Y.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/workspace-trust.ts"],"sourcesContent":["/**\r\n * Trust decisions the user makes about repository-controlled input.\r\n *\r\n * Book gates four kinds of repository-declared configuration behind a one-time\r\n * per-workspace decision: `permissions.allow` rules, MCP servers, hook entries,\r\n * and shell substitution in a project `.book/commands/*.md` body. Each gate is\r\n * only as strong as the store its decisions live in.\r\n *\r\n * That store used to be `<workspace>/.book/settings.local.json`, on the\r\n * reasoning that the file is gitignored and only Book writes it. `.gitignore`\r\n * does not stop a *tracked* file from reaching a clone: `git add -f\r\n * .book/settings.local.json` ships it with the repository, and every\r\n * fingerprint the store is keyed by is a digest of configuration the repository\r\n * already controls. A hostile project could therefore precompute its own\r\n * approvals and arrive pre-trusted, releasing its hooks on first run.\r\n *\r\n * Approval data lives outside the workspace instead, in\r\n * `<BOOK_HOME>/trust.json`, keyed by absolute workspace path. Nothing a\r\n * repository can write reaches it, so the gated party cannot self-approve\r\n * whatever it does to the files it ships.\r\n *\r\n * Reads are fail-closed: an unparseable or off-schema store resolves to \"no\r\n * decisions recorded\", which withholds the gated input rather than releasing\r\n * it. Writes refuse to touch a store they could not parse, so a corrupt or\r\n * newer-versioned file is reported rather than silently overwritten.\r\n */\r\nimport { existsSync, readFileSync } from 'fs';\r\nimport { join, resolve } from 'path';\r\nimport { z } from 'zod';\r\nimport { resolveBookHome } from './book-home.js';\r\nimport { writeFileAtomic } from './settings-repository.js';\r\nimport {\r\n mcpProjectServerChoiceSchema,\r\n projectAllowRuleChoiceSchema,\r\n projectCommandChoiceSchema,\r\n projectHookChoiceSchema,\r\n} from './settings.js';\r\n\r\n/**\r\n * Bumped only for a format change that older Book versions cannot read.\r\n *\r\n * v2 added `projectCommands`. The addition is *readable* by a v1 build, which\r\n * is exactly the problem: `updateWorkspaceTrust` writes the schema-parsed\r\n * value, and `z.object` strips unknown keys, so a v1 build recording any hook,\r\n * rule, or MCP decision for a workspace would silently drop that workspace's\r\n * command approvals on the way out. Refusing the whole store is the louder and\r\n * safer failure — a v1 build reports it as unreadable, withholds every gated\r\n * declaration, and declines to write, rather than quietly erasing decisions.\r\n */\r\nexport const TRUST_STORE_VERSION = 2;\r\n\r\nconst workspaceTrustSchema = z.object({\r\n /** Keyed by the exact rule text the project layer declared. */\r\n permissionAllowRules: z.record(projectAllowRuleChoiceSchema).default({}),\r\n /** Keyed by server name, carrying the fingerprint the decision was made against. */\r\n mcpServers: z.record(mcpProjectServerChoiceSchema).default({}),\r\n /** Keyed by a fingerprint of `{ event, matcher, command, env }`. */\r\n hookEntries: z.record(projectHookChoiceSchema).default({}),\r\n /** Keyed by command name, carrying the fingerprint of the shell its body runs. */\r\n projectCommands: z.record(projectCommandChoiceSchema).default({}),\r\n});\r\n\r\nexport type WorkspaceTrust = z.infer<typeof workspaceTrustSchema>;\r\n\r\nexport function emptyWorkspaceTrust(): WorkspaceTrust {\r\n return { permissionAllowRules: {}, mcpServers: {}, hookEntries: {}, projectCommands: {} };\r\n}\r\n\r\n/** Location of the user-global trust store. */\r\nexport function defaultTrustStorePath(home?: string): string {\r\n return home ? join(home, '.book', 'trust.json') : join(resolveBookHome(), 'trust.json');\r\n}\r\n\r\nfunction workspaceKey(workspace: string): string {\r\n const absolute = resolve(workspace);\r\n // Windows paths are case-insensitive: without folding, `D:\\repo` and `d:\\repo`\r\n // would accumulate two independent sets of decisions for one directory, and a\r\n // decision recorded under one spelling would read back as \"not decided\".\r\n return process.platform === 'win32' ? absolute.toLowerCase() : absolute;\r\n}\r\n\r\ninterface RawTrustStore {\r\n /** Other workspaces' entries, preserved verbatim so a write never drops them. */\r\n workspaces: Record<string, unknown>;\r\n}\r\n\r\ninterface RawTrustStoreRead {\r\n store: RawTrustStore;\r\n /** Set when the file exists but could not be understood; blocks writes. */\r\n unreadable?: string;\r\n}\r\n\r\nfunction readRawStore(path: string): RawTrustStoreRead {\r\n if (!existsSync(path)) return { store: { workspaces: {} } };\r\n\r\n let parsed: unknown;\r\n try {\r\n parsed = JSON.parse(readFileSync(path, 'utf-8'));\r\n } catch (error) {\r\n return {\r\n store: { workspaces: {} },\r\n unreadable: `${path} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,\r\n };\r\n }\r\n\r\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\r\n return { store: { workspaces: {} }, unreadable: `${path} must contain a JSON object` };\r\n }\r\n\r\n const record = parsed as Record<string, unknown>;\r\n const version = typeof record.version === 'number' ? record.version : TRUST_STORE_VERSION;\r\n if (version > TRUST_STORE_VERSION) {\r\n return {\r\n store: { workspaces: {} },\r\n unreadable: `${path} was written by a newer version of Book (trust store v${version}); upgrade before recording new decisions`,\r\n };\r\n }\r\n\r\n const workspaces = record.workspaces;\r\n if (workspaces === undefined) return { store: { workspaces: {} } };\r\n if (workspaces === null || typeof workspaces !== 'object' || Array.isArray(workspaces)) {\r\n return { store: { workspaces: {} }, unreadable: `${path} \"workspaces\" must be an object` };\r\n }\r\n return { store: { workspaces: { ...(workspaces as Record<string, unknown>) } } };\r\n}\r\n\r\n/**\r\n * Decisions recorded for one workspace. Anything unreadable resolves to no\r\n * decisions, which withholds the gated input.\r\n */\r\nexport function loadWorkspaceTrust(\r\n workspace: string,\r\n storePath: string = defaultTrustStorePath(),\r\n): WorkspaceTrust {\r\n const { store } = readRawStore(storePath);\r\n const parsed = workspaceTrustSchema.safeParse(store.workspaces[workspaceKey(workspace)] ?? {});\r\n return parsed.success ? parsed.data : emptyWorkspaceTrust();\r\n}\r\n\r\n/**\r\n * Apply a decision to one workspace's record, leaving every other workspace's\r\n * entry byte-identical.\r\n */\r\nexport function updateWorkspaceTrust(\r\n workspace: string,\r\n mutate: (trust: WorkspaceTrust) => void,\r\n storePath: string = defaultTrustStorePath(),\r\n): { ok: boolean; error?: string } {\r\n const { store, unreadable } = readRawStore(storePath);\r\n if (unreadable) return { ok: false, error: unreadable };\r\n\r\n const key = workspaceKey(workspace);\r\n const existing = workspaceTrustSchema.safeParse(store.workspaces[key] ?? {});\r\n const candidate = existing.success ? existing.data : emptyWorkspaceTrust();\r\n mutate(candidate);\r\n\r\n const validated = workspaceTrustSchema.safeParse(candidate);\r\n if (!validated.success) return { ok: false, error: validated.error.message };\r\n\r\n const next = {\r\n version: TRUST_STORE_VERSION,\r\n workspaces: { ...store.workspaces, [key]: validated.data },\r\n };\r\n try {\r\n writeFileAtomic(storePath, `${JSON.stringify(next, null, 2)}\\n`);\r\n } catch (error) {\r\n return { ok: false, error: error instanceof Error ? error.message : String(error) };\r\n }\r\n return { ok: true };\r\n}\r\n"],"mappings":"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","names":[]}