@letrquan/book 0.3.0 → 0.4.0-beta.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 (148) hide show
  1. package/CHANGELOG.md +1024 -0
  2. package/README.md +6 -3
  3. package/dist/{additional-roots-H7UNAEHS.js → additional-roots-VAWRQ625.js} +8 -7
  4. package/dist/{app-GE4OHJOC.js → app-J7BVPSUH.js} +5397 -2953
  5. package/dist/app-J7BVPSUH.js.map +1 -0
  6. package/dist/chunk-32WBCUAN.js +660 -0
  7. package/dist/chunk-32WBCUAN.js.map +1 -0
  8. package/dist/{chunk-525CAZPY.js → chunk-4DKHLZRI.js} +6 -6
  9. package/dist/{chunk-4AS7LKKQ.js → chunk-62EEAUID.js} +2 -2
  10. package/dist/{chunk-AQEQX4FF.js → chunk-AHC7OU75.js} +385 -55
  11. package/dist/chunk-AHC7OU75.js.map +1 -0
  12. package/dist/{chunk-EFJ2NH6E.js → chunk-AJZWLKLQ.js} +63 -14
  13. package/dist/chunk-AJZWLKLQ.js.map +1 -0
  14. package/dist/chunk-B5FC3MYZ.js +395 -0
  15. package/dist/chunk-B5FC3MYZ.js.map +1 -0
  16. package/dist/{chunk-67UZEWWF.js → chunk-DNFQTSMJ.js} +53 -11
  17. package/dist/chunk-DNFQTSMJ.js.map +1 -0
  18. package/dist/{chunk-TLX3H2CL.js → chunk-E5WLQSKA.js} +231 -40
  19. package/dist/chunk-E5WLQSKA.js.map +1 -0
  20. package/dist/chunk-ECLC4CIJ.js +1181 -0
  21. package/dist/chunk-ECLC4CIJ.js.map +1 -0
  22. package/dist/chunk-EEX6QNXV.js +127 -0
  23. package/dist/chunk-EEX6QNXV.js.map +1 -0
  24. package/dist/chunk-EFXVSQ5J.js +931 -0
  25. package/dist/chunk-EFXVSQ5J.js.map +1 -0
  26. package/dist/{chunk-RFJ6Q6LE.js → chunk-ENLB7NYV.js} +577 -164
  27. package/dist/chunk-ENLB7NYV.js.map +1 -0
  28. package/dist/{chunk-IME7R2UI.js → chunk-FPR7SKIE.js} +1268 -883
  29. package/dist/chunk-FPR7SKIE.js.map +1 -0
  30. package/dist/{chunk-VPNLCMVR.js → chunk-HFB74CLM.js} +13 -5
  31. package/dist/chunk-HFB74CLM.js.map +1 -0
  32. package/dist/{chunk-5RDRAO4B.js → chunk-HOZXKOE3.js} +37 -1
  33. package/dist/chunk-HOZXKOE3.js.map +1 -0
  34. package/dist/chunk-IDYWN37T.js +36 -0
  35. package/dist/chunk-IDYWN37T.js.map +1 -0
  36. package/dist/{chunk-YC6W4EJH.js → chunk-JGF5HLSI.js} +193 -75
  37. package/dist/chunk-JGF5HLSI.js.map +1 -0
  38. package/dist/{chunk-5YHTKWJN.js → chunk-KDNWMKXV.js} +2447 -907
  39. package/dist/chunk-KDNWMKXV.js.map +1 -0
  40. package/dist/{chunk-Y2VHN7TC.js → chunk-MEOB2DZA.js} +11 -1
  41. package/dist/chunk-MEOB2DZA.js.map +1 -0
  42. package/dist/{chunk-KBQS35OV.js → chunk-MV5PQNIA.js} +2 -2
  43. package/dist/chunk-N2G4Q7ME.js +2672 -0
  44. package/dist/chunk-N2G4Q7ME.js.map +1 -0
  45. package/dist/{chunk-62QL5QUP.js → chunk-OJBMDM5H.js} +17 -1
  46. package/dist/chunk-OJBMDM5H.js.map +1 -0
  47. package/dist/{chunk-56SIEMDX.js → chunk-PTDC7ZUP.js} +2 -2
  48. package/dist/{chunk-X4NJT436.js → chunk-REASC2QG.js} +67 -28
  49. package/dist/chunk-REASC2QG.js.map +1 -0
  50. package/dist/chunk-SBTU5BEC.js +114 -0
  51. package/dist/chunk-SBTU5BEC.js.map +1 -0
  52. package/dist/{chunk-HQI7NYGX.js → chunk-SWPDKLNM.js} +2 -2
  53. package/dist/{chunk-W3XFHEF4.js → chunk-UCFJ43OS.js} +140 -28
  54. package/dist/chunk-UCFJ43OS.js.map +1 -0
  55. package/dist/{chunk-QYBD22H5.js → chunk-W7SLYS3M.js} +80 -16
  56. package/dist/chunk-W7SLYS3M.js.map +1 -0
  57. package/dist/{chunk-SOW4WRQP.js → chunk-WV3O5PGX.js} +3 -3
  58. package/dist/{chunk-JTPOI3KU.js → chunk-XJRJVXJR.js} +262 -38
  59. package/dist/chunk-XJRJVXJR.js.map +1 -0
  60. package/dist/{chunk-224FJZLH.js → chunk-YIL6HTJX.js} +228 -5
  61. package/dist/chunk-YIL6HTJX.js.map +1 -0
  62. package/dist/{command-approvals-VBIU6BLM.js → command-approvals-ZRY66LCW.js} +5 -4
  63. package/dist/context-C3RAP6K5.js +41 -0
  64. package/dist/{hook-approvals-ZVT4V7IM.js → hook-approvals-DPQD5H65.js} +5 -4
  65. package/dist/index.js +657 -754
  66. package/dist/index.js.map +1 -1
  67. package/dist/{interactive-assets-T4I4SEHU.js → interactive-assets-73AQO5WP.js} +11 -6
  68. package/dist/{interactive-assets-T4I4SEHU.js.map → interactive-assets-73AQO5WP.js.map} +1 -1
  69. package/dist/job-runner.js +10 -3
  70. package/dist/job-runner.js.map +1 -1
  71. package/dist/job-supervisor.js +13 -4
  72. package/dist/job-supervisor.js.map +1 -1
  73. package/dist/loader-DQNPDKOX.js +27 -0
  74. package/dist/{mcp-approvals-HJ2VDYMC.js → mcp-approvals-27LJYADQ.js} +5 -4
  75. package/dist/{mcp-config-RNB5LEVE.js → mcp-config-GXSODNCM.js} +3 -2
  76. package/dist/memory-store-I6Z54VUN.js +51 -0
  77. package/dist/{model-window-store-MTSQ5JDJ.js → model-window-store-6VKXXTPJ.js} +4 -3
  78. package/dist/{openai-compatible-2OB54YST.js → openai-compatible-IPUNBJ53.js} +5 -3
  79. package/dist/{permission-approvals-YLV6UC5W.js → permission-approvals-Q5IMFEZI.js} +5 -4
  80. package/dist/{permissions-DHOKF3W2.js → permissions-7Y35X6HZ.js} +26 -9
  81. package/dist/provider-approvals-5AGH3RVT.js +76 -0
  82. package/dist/{sandbox-WRYTZH3D.js → sandbox-N2RY72GN.js} +6 -5
  83. package/dist/{scrollback-35ME2GFX.js → scrollback-GD76GNM4.js} +36 -28
  84. package/dist/scrollback-GD76GNM4.js.map +1 -0
  85. package/dist/sdk.d.ts +876 -64
  86. package/dist/sdk.js +57 -37
  87. package/dist/sdk.js.map +1 -1
  88. package/dist/settings-loader-KDBH45AQ.js +43 -0
  89. package/dist/{settings-removed-JWWPT4WA.js → settings-removed-3ATWJEF3.js} +2 -2
  90. package/dist/shell-selection-Z5WORZU4.js +31 -0
  91. package/dist/shell-selection-Z5WORZU4.js.map +1 -0
  92. package/package.json +5 -5
  93. package/dist/app-GE4OHJOC.js.map +0 -1
  94. package/dist/chunk-224FJZLH.js.map +0 -1
  95. package/dist/chunk-5RDRAO4B.js.map +0 -1
  96. package/dist/chunk-5YHTKWJN.js.map +0 -1
  97. package/dist/chunk-62QL5QUP.js.map +0 -1
  98. package/dist/chunk-67UZEWWF.js.map +0 -1
  99. package/dist/chunk-6OQQOMZR.js +0 -785
  100. package/dist/chunk-6OQQOMZR.js.map +0 -1
  101. package/dist/chunk-AQEQX4FF.js.map +0 -1
  102. package/dist/chunk-C7ADUNPR.js +0 -273
  103. package/dist/chunk-C7ADUNPR.js.map +0 -1
  104. package/dist/chunk-EFJ2NH6E.js.map +0 -1
  105. package/dist/chunk-IME7R2UI.js.map +0 -1
  106. package/dist/chunk-J62VZYOB.js +0 -1339
  107. package/dist/chunk-J62VZYOB.js.map +0 -1
  108. package/dist/chunk-JTPOI3KU.js.map +0 -1
  109. package/dist/chunk-QYBD22H5.js.map +0 -1
  110. package/dist/chunk-RFJ6Q6LE.js.map +0 -1
  111. package/dist/chunk-S4JMID4G.js +0 -259
  112. package/dist/chunk-S4JMID4G.js.map +0 -1
  113. package/dist/chunk-TLX3H2CL.js.map +0 -1
  114. package/dist/chunk-VPNLCMVR.js.map +0 -1
  115. package/dist/chunk-W3XFHEF4.js.map +0 -1
  116. package/dist/chunk-X4NJT436.js.map +0 -1
  117. package/dist/chunk-X66I2NQO.js +0 -137
  118. package/dist/chunk-X66I2NQO.js.map +0 -1
  119. package/dist/chunk-Y2VHN7TC.js.map +0 -1
  120. package/dist/chunk-YC6W4EJH.js.map +0 -1
  121. package/dist/context-CBOBDY5Q.js +0 -38
  122. package/dist/loader-S77OVTHH.js +0 -18
  123. package/dist/memory-store-DZUT5HUH.js +0 -75
  124. package/dist/scrollback-35ME2GFX.js.map +0 -1
  125. package/dist/settings-loader-JUNPGAAM.js +0 -35
  126. package/dist/shell-selection-CDHGJ4GC.js +0 -17
  127. /package/dist/{additional-roots-H7UNAEHS.js.map → additional-roots-VAWRQ625.js.map} +0 -0
  128. /package/dist/{chunk-525CAZPY.js.map → chunk-4DKHLZRI.js.map} +0 -0
  129. /package/dist/{chunk-4AS7LKKQ.js.map → chunk-62EEAUID.js.map} +0 -0
  130. /package/dist/{chunk-KBQS35OV.js.map → chunk-MV5PQNIA.js.map} +0 -0
  131. /package/dist/{chunk-56SIEMDX.js.map → chunk-PTDC7ZUP.js.map} +0 -0
  132. /package/dist/{chunk-HQI7NYGX.js.map → chunk-SWPDKLNM.js.map} +0 -0
  133. /package/dist/{chunk-SOW4WRQP.js.map → chunk-WV3O5PGX.js.map} +0 -0
  134. /package/dist/{command-approvals-VBIU6BLM.js.map → command-approvals-ZRY66LCW.js.map} +0 -0
  135. /package/dist/{context-CBOBDY5Q.js.map → context-C3RAP6K5.js.map} +0 -0
  136. /package/dist/{hook-approvals-ZVT4V7IM.js.map → hook-approvals-DPQD5H65.js.map} +0 -0
  137. /package/dist/{loader-S77OVTHH.js.map → loader-DQNPDKOX.js.map} +0 -0
  138. /package/dist/{mcp-approvals-HJ2VDYMC.js.map → mcp-approvals-27LJYADQ.js.map} +0 -0
  139. /package/dist/{mcp-config-RNB5LEVE.js.map → mcp-config-GXSODNCM.js.map} +0 -0
  140. /package/dist/{memory-store-DZUT5HUH.js.map → memory-store-I6Z54VUN.js.map} +0 -0
  141. /package/dist/{model-window-store-MTSQ5JDJ.js.map → model-window-store-6VKXXTPJ.js.map} +0 -0
  142. /package/dist/{openai-compatible-2OB54YST.js.map → openai-compatible-IPUNBJ53.js.map} +0 -0
  143. /package/dist/{permission-approvals-YLV6UC5W.js.map → permission-approvals-Q5IMFEZI.js.map} +0 -0
  144. /package/dist/{permissions-DHOKF3W2.js.map → permissions-7Y35X6HZ.js.map} +0 -0
  145. /package/dist/{sandbox-WRYTZH3D.js.map → provider-approvals-5AGH3RVT.js.map} +0 -0
  146. /package/dist/{settings-loader-JUNPGAAM.js.map → sandbox-N2RY72GN.js.map} +0 -0
  147. /package/dist/{settings-removed-JWWPT4WA.js.map → settings-loader-KDBH45AQ.js.map} +0 -0
  148. /package/dist/{shell-selection-CDHGJ4GC.js.map → settings-removed-3ATWJEF3.js.map} +0 -0
@@ -1,3 +1,7 @@
1
+ import {
2
+ projectFileWriteRefusal
3
+ } from "./chunk-IDYWN37T.js";
4
+
1
5
  // src/debug-log.ts
2
6
  import {
3
7
  appendFileSync,
@@ -15,6 +19,7 @@ var RENDER_DEBUG_ENABLED = process.env.BOOK_DEBUG_RENDER === "1";
15
19
  var FLOW_DEBUG_ENABLED = process.env.BOOK_DEBUG_FLOW === "1";
16
20
  var debugLogPath;
17
21
  var debugLogSizes = /* @__PURE__ */ new Map();
22
+ var refusedDebugLogTargets = /* @__PURE__ */ new Set();
18
23
  var DEFAULT_DEBUG_LOG_MAX_BYTES = 5 * 1024 * 1024;
19
24
  var MIN_DEBUG_LOG_MAX_BYTES = 4;
20
25
  var DEFAULT_DEBUG_LOG_BACKUPS = 3;
@@ -59,6 +64,7 @@ function getDebugLogPath() {
59
64
  }
60
65
  function cleanupDebugLogs(days = DEFAULT_LOCAL_DATA_RETENTION_DAYS, target = getDebugLogPath(), now = Date.now()) {
61
66
  if (!target) return 0;
67
+ if (target === defaultDebugLogPath() && projectFileWriteRefusal(target)) return 0;
62
68
  const cutoff = now - Math.max(1, days) * 864e5;
63
69
  const directory = dirname(target);
64
70
  const prefix = `${target.split(/[\\/]/).pop()}.`;
@@ -76,12 +82,22 @@ function cleanupDebugLogs(days = DEFAULT_LOCAL_DATA_RETENTION_DAYS, target = get
76
82
  }
77
83
  return removed;
78
84
  }
85
+ function usableDebugLogTarget(target) {
86
+ if (refusedDebugLogTargets.has(target)) return false;
87
+ const refusal = target === defaultDebugLogPath() ? projectFileWriteRefusal(target) : void 0;
88
+ if (!refusal) return true;
89
+ refusedDebugLogTargets.add(target);
90
+ process.stderr.write(`Debug logging is off for this session: ${target}: ${refusal}.
91
+ `);
92
+ return false;
93
+ }
79
94
  function writeDebugLine(line) {
80
95
  const target = debugLogPath ?? (debugLogPath = getDebugLogPath());
81
96
  if (!target) {
82
97
  process.stderr.write(line);
83
98
  return;
84
99
  }
100
+ if (!usableDebugLogTarget(target)) return;
85
101
  try {
86
102
  mkdirSync(dirname(target), { recursive: true });
87
103
  const lineBytes = Buffer.byteLength(line);
@@ -280,4 +296,4 @@ export {
280
296
  createUiDebugLogger,
281
297
  createRenderDebugLogger
282
298
  };
283
- //# sourceMappingURL=chunk-62QL5QUP.js.map
299
+ //# sourceMappingURL=chunk-OJBMDM5H.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/debug-log.ts"],"sourcesContent":["/**\n * Lightweight debug logger controlled by environment variables.\n *\n * Writes timestamped messages to a log file when stderr is an interactive TTY,\n * because Ink renders to the same terminal and raw stderr writes corrupt the UI.\n * In non-TTY contexts (redirected stderr, CI), logs continue to use stderr.\n * Most functions are no-ops when the relevant env var is not set to '1'.\n *\n * Gated by separate flags so output volume stays controllable:\n * BOOK_DEBUG=1 — provider + agent + TUI lifecycle events\n * BOOK_DEBUG_UI=1 — input handling, modal toggles, permission prompts,\n * component mount/unmount, layout state changes\n * BOOK_DEBUG_RENDER=1 — per-render details (noisy; can flood during streaming)\n * BOOK_DEBUG_FLOW=1 — enter/exit timing around expensive flows\n * BOOK_DEBUG_FILE=path — write logs to a specific file\n * BOOK_DEBUG_STDERR=1 — force stderr even when attached to a TTY\n *\n * `BOOK_DEBUG=1` covers the union of \"core + UI\" (lifecycle). UI hooks read\n * `isUiDebugEnabled()`, which returns true if either BOOK_DEBUG=1 or\n * BOOK_DEBUG_UI=1 so the default mode surfaces UI events too.\n *\n * Usage:\n * import { createDebugLogger } from '../debug-log.js';\n * const log = createDebugLogger('provider');\n * log.debug('Sending request', { model: 'gpt-4' });\n */\n\nimport {\n appendFileSync,\n existsSync,\n mkdirSync,\n readdirSync,\n renameSync,\n statSync,\n unlinkSync,\n} from 'fs';\nimport { dirname, join } from 'path';\nimport { projectFileWriteRefusal } from './workspace-files.js';\n\n// Core flag — provider, agent loop, and TUI lifecycle.\nconst DEBUG_ENABLED = process.env.BOOK_DEBUG === '1';\n// UI flag — input, modals, permissions, mount/unmount, layout changes.\n// Falls back to core flag so default debug mode surfaces UI events too.\nconst UI_DEBUG_ENABLED = process.env.BOOK_DEBUG_UI === '1' || DEBUG_ENABLED;\n// Noisy per-render flag — opt-in only.\nconst RENDER_DEBUG_ENABLED = process.env.BOOK_DEBUG_RENDER === '1';\n// Flow/tracing flag — enter/exit pairs with timing — opt-in only.\nconst FLOW_DEBUG_ENABLED = process.env.BOOK_DEBUG_FLOW === '1';\n\nlet debugLogPath: string | undefined;\nconst debugLogSizes = new Map<string, number>();\n/** Log targets found unwritable: debug logging to them is off for the rest of the session. */\nconst refusedDebugLogTargets = new Set<string>();\nconst DEFAULT_DEBUG_LOG_MAX_BYTES = 5 * 1024 * 1024;\nconst MIN_DEBUG_LOG_MAX_BYTES = 4;\nconst DEFAULT_DEBUG_LOG_BACKUPS = 3;\nexport const DEFAULT_LOCAL_DATA_RETENTION_DAYS = 30;\n\nexport function positiveInteger(value: string | undefined, fallback: number): number {\n const parsed = Number(value);\n return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : fallback;\n}\n\nfunction debugLogMaxBytes(): number {\n // Four bytes is the largest UTF-8 encoding for a single Unicode code point.\n return Math.max(\n MIN_DEBUG_LOG_MAX_BYTES,\n positiveInteger(process.env.BOOK_DEBUG_MAX_BYTES, DEFAULT_DEBUG_LOG_MAX_BYTES),\n );\n}\n\nfunction rotateDebugLog(target: string, incomingBytes: number): void {\n if (!existsSync(target)) {\n debugLogSizes.set(target, 0);\n return;\n }\n const maxBytes = debugLogMaxBytes();\n const currentBytes = debugLogSizes.get(target) ?? statSync(target).size;\n debugLogSizes.set(target, currentBytes);\n if (currentBytes + incomingBytes <= maxBytes) return;\n\n const backups = positiveInteger(process.env.BOOK_DEBUG_BACKUPS, DEFAULT_DEBUG_LOG_BACKUPS);\n const oldest = `${target}.${backups}`;\n if (existsSync(oldest)) unlinkSync(oldest);\n for (let index = backups - 1; index >= 1; index--) {\n const source = `${target}.${index}`;\n if (existsSync(source)) renameSync(source, `${target}.${index + 1}`);\n }\n renameSync(target, `${target}.1`);\n debugLogSizes.set(target, 0);\n}\n\nfunction defaultDebugLogPath(): string {\n return join(process.env.BOOK_WORKSPACE || process.cwd(), '.book', 'debug.log');\n}\n\nexport function getDebugLogPath(): string | undefined {\n if (process.env.BOOK_DEBUG_STDERR === '1') return undefined;\n if (process.env.BOOK_DEBUG_FILE) return process.env.BOOK_DEBUG_FILE;\n if (process.stderr.isTTY) return defaultDebugLogPath();\n return undefined;\n}\n\n/** Remove expired rotated logs while always preserving the active log file. */\nexport function cleanupDebugLogs(\n days = DEFAULT_LOCAL_DATA_RETENTION_DAYS,\n target = getDebugLogPath(),\n now = Date.now(),\n): number {\n if (!target) return 0;\n // The default log's folder is a workspace one, held to the same rule as the log itself.\n if (target === defaultDebugLogPath() && projectFileWriteRefusal(target)) return 0;\n const cutoff = now - Math.max(1, days) * 86_400_000;\n const directory = dirname(target);\n const prefix = `${target.split(/[\\\\/]/).pop()}.`;\n let removed = 0;\n try {\n for (const entry of readdirSync(directory, { withFileTypes: true })) {\n if (!entry.isFile() || !entry.name.startsWith(prefix)) continue;\n const path = join(directory, entry.name);\n if (statSync(path).mtimeMs >= cutoff) continue;\n unlinkSync(path);\n debugLogSizes.delete(path);\n removed++;\n }\n } catch {\n // Retention cleanup is best effort and must never block startup.\n }\n return removed;\n}\n\n/**\n * The default `.book/debug.log` is one of Book's own workspace files, so it is written only while\n * it is a regular file; otherwise debug logging stops for the session, said once on stderr.\n * `BOOK_DEBUG_FILE` is the user's own choice, a FIFO or `/dev/stderr` included, and is used as\n * given.\n */\nfunction usableDebugLogTarget(target: string): boolean {\n if (refusedDebugLogTargets.has(target)) return false;\n // Checked before every append, not once: a checkout during the session can put something else\n // at the path.\n const refusal = target === defaultDebugLogPath() ? projectFileWriteRefusal(target) : undefined;\n if (!refusal) return true;\n refusedDebugLogTargets.add(target);\n process.stderr.write(`Debug logging is off for this session: ${target}: ${refusal}.\\n`);\n return false;\n}\n\nfunction writeDebugLine(line: string): void {\n const target = debugLogPath ?? (debugLogPath = getDebugLogPath());\n if (!target) {\n process.stderr.write(line);\n return;\n }\n if (!usableDebugLogTarget(target)) return;\n\n try {\n mkdirSync(dirname(target), { recursive: true });\n const lineBytes = Buffer.byteLength(line);\n rotateDebugLog(target, lineBytes);\n appendFileSync(target, line, 'utf8');\n debugLogSizes.set(target, (debugLogSizes.get(target) ?? 0) + lineBytes);\n } catch {\n process.stderr.write(line);\n }\n}\n\nfunction writeSizeBoundedDebugLine(line: string, maxBytes: number): void {\n if (Buffer.byteLength(line) <= maxBytes) {\n writeDebugLine(line);\n return;\n }\n\n // Split oversized render events on UTF-8 character boundaries so one burst\n // cannot bypass the configured active-log cap.\n let chunk = '';\n let chunkBytes = 0;\n for (const character of line) {\n const characterBytes = Buffer.byteLength(character);\n if (chunk && chunkBytes + characterBytes > maxBytes) {\n writeDebugLine(chunk);\n chunk = '';\n chunkBytes = 0;\n }\n chunk += character;\n chunkBytes += characterBytes;\n }\n if (chunk) writeDebugLine(chunk);\n}\n\nlet pendingRenderDebug: string[] = [];\nlet renderDebugFlush: ReturnType<typeof setImmediate> | null = null;\n\nfunction writeRenderDebugLine(line: string): void {\n pendingRenderDebug.push(line);\n if (renderDebugFlush !== null) return;\n renderDebugFlush = setImmediate(() => {\n renderDebugFlush = null;\n const batch = pendingRenderDebug;\n pendingRenderDebug = [];\n const maxBytes = debugLogMaxBytes();\n let chunk = '';\n let chunkBytes = 0;\n\n for (const pendingLine of batch) {\n const lineBytes = Buffer.byteLength(pendingLine);\n if (chunk && chunkBytes + lineBytes > maxBytes) {\n writeDebugLine(chunk);\n chunk = '';\n chunkBytes = 0;\n }\n if (lineBytes > maxBytes) {\n writeSizeBoundedDebugLine(pendingLine, maxBytes);\n continue;\n }\n chunk += pendingLine;\n chunkBytes += lineBytes;\n }\n if (chunk) writeDebugLine(chunk);\n });\n}\n\n// Per-namespace monotonic event counter, used when a logger is created with\n// `createDebugLoggerWithCounter`. Useful for tracing event ordering across\n// concurrent sources (streaming flushes vs. user input).\nconst namespaceCounters = new Map<string, number>();\n\nfunction nextEventId(namespace: string): number {\n const n = (namespaceCounters.get(namespace) ?? 0) + 1;\n namespaceCounters.set(namespace, n);\n return n;\n}\n\n/** True if core debug (provider/agent/TUI lifecycle) is on. */\nexport function isDebugEnabled(): boolean {\n return DEBUG_ENABLED;\n}\n\n/** True if UI debug is on (BOOK_DEBUG=1 already enables this). */\nexport function isUiDebugEnabled(): boolean {\n return UI_DEBUG_ENABLED;\n}\n\n/** True if per-render debug is on (opt-in via BOOK_DEBUG_RENDER=1). */\nexport function isRenderDebugEnabled(): boolean {\n return RENDER_DEBUG_ENABLED;\n}\n\n/** True if flow tracing is on (opt-in via BOOK_DEBUG_FLOW=1). */\nexport function isFlowDebugEnabled(): boolean {\n return FLOW_DEBUG_ENABLED;\n}\n\nfunction timestamp(): string {\n const now = new Date();\n const h = String(now.getHours()).padStart(2, '0');\n const m = String(now.getMinutes()).padStart(2, '0');\n const s = String(now.getSeconds()).padStart(2, '0');\n const ms = String(now.getMilliseconds()).padStart(3, '0');\n return `${h}:${m}:${s}.${ms}`;\n}\n\n/** Truncate a string for safe preview in logs. */\nfunction truncate(value: string, max = 80): string {\n const single = value.replace(/\\s+/g, ' ').trim();\n if (single.length <= max) return single;\n return `${single.slice(0, max)}…`;\n}\n\nfunction formatValue(a: unknown): string {\n if (typeof a === 'string') return a;\n if (a instanceof Error) return a.message;\n try {\n return JSON.stringify(a);\n } catch {\n return String(a);\n }\n}\n\nfunction formatMessage(...args: unknown[]): string {\n return args.map(formatValue).join(' ');\n}\n\n/** Format a metadata object, truncating any long string values. */\nfunction formatMeta(meta: Record<string, unknown>): string {\n const parts: string[] = [];\n for (const [key, value] of Object.entries(meta)) {\n let repr: string;\n if (typeof value === 'string') {\n repr = truncate(value, 120);\n } else if (value === null) {\n repr = 'null';\n } else if (value === undefined) {\n repr = 'undefined';\n } else {\n try {\n repr = JSON.stringify(value);\n } catch {\n repr = String(value);\n }\n }\n parts.push(`${key}=${repr}`);\n }\n return parts.join(' ');\n}\n\nexport interface DebugLogger {\n debug: (...args: unknown[]) => void;\n info: (...args: unknown[]) => void;\n warn: (...args: unknown[]) => void;\n /** Structured log with key=value metadata (long strings are truncated). */\n event: (event: string, meta?: Record<string, unknown>) => void;\n}\n\nfunction noop(): void {}\n\nconst NOOP_LOGGER: DebugLogger = {\n debug: noop,\n info: noop,\n warn: noop,\n event: noop,\n};\n\nfunction makePrefix(namespace: string, level: string): string {\n return `[${timestamp()}] [${namespace}] [${level}]`;\n}\n\n/** Build a writing logger for an enabled namespace. */\nfunction buildLogger(\n namespace: string,\n write: (line: string) => void = writeDebugLine,\n): DebugLogger {\n return {\n debug: (...args: unknown[]) => {\n write(`${makePrefix(namespace, 'DEBUG')} ${formatMessage(...args)}\\n`);\n },\n info: (...args: unknown[]) => {\n write(`${makePrefix(namespace, 'INFO')} ${formatMessage(...args)}\\n`);\n },\n warn: (...args: unknown[]) => {\n write(`${makePrefix(namespace, 'WARN')} ${formatMessage(...args)}\\n`);\n },\n event: (event, meta) => {\n const suffix = meta && Object.keys(meta).length > 0 ? ` ${formatMeta(meta)}` : '';\n write(`${makePrefix(namespace, 'DEBUG')} ${event}${suffix}\\n`);\n },\n };\n}\n\n/**\n * Create a debug logger gated by `BOOK_DEBUG=1`. Returns a no-op logger when\n * debug is off, so callers can call freely without env checks at call sites.\n */\nexport function createDebugLogger(namespace: string): DebugLogger {\n if (!DEBUG_ENABLED) return NOOP_LOGGER;\n return buildLogger(namespace);\n}\n\n/**\n * Like {@link createDebugLogger} but emits a monotonic event id per namespace\n * in every message, e.g. `[tui:input] [DEBUG#42] key=Escape`. Helpful when\n * tracing event ordering across concurrent sources.\n *\n * Activation matches {@link createDebugLogger}: gated by `BOOK_DEBUG=1`.\n */\nexport function createDebugLoggerWithCounter(namespace: string): DebugLogger {\n if (!DEBUG_ENABLED) return NOOP_LOGGER;\n const wrap =\n (level: 'debug' | 'info' | 'warn') =>\n (...args: unknown[]) => {\n const id = nextEventId(namespace);\n writeDebugLine(\n `[${timestamp()}] [${namespace}] [${level.toUpperCase()}#${id}] ${formatMessage(...args)}\\n`,\n );\n };\n return {\n debug: wrap('debug'),\n info: wrap('info'),\n warn: wrap('warn'),\n event: (event, meta) => {\n const id = nextEventId(namespace);\n const suffix = meta && Object.keys(meta).length > 0 ? ` ${formatMeta(meta)}` : '';\n writeDebugLine(`[${timestamp()}] [${namespace}] [DEBUG#${id}] ${event}${suffix}\\n`);\n },\n };\n}\n\n/**\n * Create a debug logger gated by `BOOK_DEBUG_UI=1` (also active when\n * `BOOK_DEBUG=1`). No-op otherwise. Used for input, modal, permission, and\n * mount/unmount events.\n */\nexport function createUiDebugLogger(namespace: string): DebugLogger {\n if (!UI_DEBUG_ENABLED) return NOOP_LOGGER;\n return buildLogger(namespace);\n}\n\n/**\n * Create a debug logger gated by `BOOK_DEBUG_RENDER=1` only. No-op otherwise.\n * Used for noisy per-render logs. Render logs must be opt-in to keep stderr\n * readable during streaming.\n */\nexport function createRenderDebugLogger(namespace: string): DebugLogger {\n if (!RENDER_DEBUG_ENABLED) return NOOP_LOGGER;\n return buildLogger(namespace, writeRenderDebugLine);\n}\n\n/**\n * Create a debug logger gated by `BOOK_DEBUG_FLOW=1` only. No-op otherwise.\n * Used for enter/exit trace pairs around expensive flows.\n */\nexport function createFlowDebugLogger(namespace: string): DebugLogger {\n if (!FLOW_DEBUG_ENABLED) return NOOP_LOGGER;\n return buildLogger(namespace);\n}\n\n/** Throttle a callback to at most one invocation per `intervalMs`. */\nexport function throttle<A extends unknown[]>(\n fn: (...args: A) => void,\n intervalMs: number,\n): (...args: A) => void {\n let last = 0;\n return (...args: A) => {\n const now = Date.now();\n if (now - last < intervalMs) return;\n last = now;\n fn(...args);\n };\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  updateWorkspaceTrust
3
- } from "./chunk-VPNLCMVR.js";
3
+ } from "./chunk-HFB74CLM.js";
4
4
 
5
5
  // src/mcp-approvals.ts
6
6
  import { createHash } from "crypto";
@@ -57,4 +57,4 @@ export {
57
57
  mcpServersToRecord,
58
58
  persistMcpProjectServerChoice
59
59
  };
60
- //# sourceMappingURL=chunk-56SIEMDX.js.map
60
+ //# sourceMappingURL=chunk-PTDC7ZUP.js.map
@@ -1,3 +1,7 @@
1
+ import {
2
+ projectFileWriteRefusal
3
+ } from "./chunk-IDYWN37T.js";
4
+
1
5
  // src/settings.ts
2
6
  import { z } from "zod";
3
7
  var permissionRuleSchema = z.string().min(1);
@@ -17,7 +21,20 @@ var sandboxSchema = z.object({
17
21
  filesystem: z.object({
18
22
  allowWrite: z.array(z.string()).default([]),
19
23
  denyWrite: z.array(z.string()).default([]),
20
- denyRead: z.array(z.string()).default([])
24
+ denyRead: z.array(z.string()).default([]),
25
+ /**
26
+ * Off (the default) binds every git directory of the workspace read-only inside
27
+ * the sandbox, which also closes the pointer-file redirect a writable git dir
28
+ * leaves open (issue 373): a sandboxed command can create `.git/commondir` in a
29
+ * plain repository, and the host's git reads it on its next command. Sandboxed
30
+ * git writes — commit, checkout, add, stash, fetch — fail while it is off, and
31
+ * the failed output names this setting among the remedies. On restores the
32
+ * previous shape (git directories writable, their hooks, config and pointer
33
+ * files read-only, the top-level `.git` pinned) and reopens the pointer-file
34
+ * gap. Only a trusted layer may set it: a workspace file may tighten it to
35
+ * `false` and never back to `true`.
36
+ */
37
+ allowGitWrites: z.boolean().default(false)
21
38
  }).prefault({}),
22
39
  network: z.object({
23
40
  allowedDomains: z.array(z.string()).default([]),
@@ -92,6 +109,18 @@ var retrySettingsSchema = z.object({
92
109
  * timeout to it cancels a healthy request and reports `stream_stall`.
93
110
  */
94
111
  thinkingStallTimeoutMs: z.number().int().min(1e4).max(18e5).default(9e5),
112
+ /**
113
+ * First-delta ceiling for a model that may think silently (#379).
114
+ *
115
+ * A router that buffers the model's whole thinking block sends a role-only
116
+ * first chunk and then nothing until the thinking is done — up to about 80 s —
117
+ * which the chat ceiling reads as a fault. While a stream has shown no
118
+ * content, tool call or reasoning delta yet, and neither `reasoning_effort`
119
+ * nor the model's catalog `effort` entry settles what it is, silence is
120
+ * judged by this ceiling instead. The first meaningful delta hands the stream
121
+ * back to the per-phase ceilings.
122
+ */
123
+ firstDeltaStallTimeoutMs: z.number().int().min(5e3).max(18e5).default(12e4),
95
124
  toolRetries: z.number().int().min(0).max(3).default(1),
96
125
  watchdog: z.boolean().default(false),
97
126
  /**
@@ -118,6 +147,12 @@ var effortLevelSchema = z.enum(["low", "medium", "high", "xhigh", "max"]);
118
147
  var providerModelSchema = z.object({
119
148
  label: z.string().optional(),
120
149
  contextWindow: unboundedInt().positive().optional(),
150
+ /**
151
+ * The input limit the endpoint's model list reported. Written by discovery and overwritten on
152
+ * every refresh; ranks below a declared `contextWindow` and a learned ceiling, so a figure that
153
+ * is too large can still be corrected.
154
+ */
155
+ reportedContextWindow: unboundedInt().positive().optional(),
121
156
  maxOutputTokens: unboundedInt().positive().optional(),
122
157
  /** Whether this model accepts image input. Unknown models remain optimistic. */
123
158
  vision: z.boolean().optional(),
@@ -137,6 +172,7 @@ var providerModelSchema = z.object({
137
172
  })
138
173
  ]).optional()
139
174
  });
175
+ var ADVISOR_OFF_VALUE = "off";
140
176
  var providerConfigSchema = z.object({
141
177
  type: z.enum(["openai", "anthropic"]).default("openai"),
142
178
  baseURL: z.string().min(1).optional(),
@@ -176,21 +212,8 @@ var continuationSettingsSchema = z.object({
176
212
  });
177
213
  var memorySettingsSchema = z.object({
178
214
  enabled: z.boolean().default(true),
179
- autoSave: z.boolean().default(true),
180
- requireApproval: z.boolean().default(false),
181
- quarantineExternal: z.boolean().default(true),
182
- /**
183
- * Phase 1b: at the next session start, extract memories the model missed from idle sessions.
184
- * Off by default since 2026-10: on the owner's machine it wrote 8 of the 10 memories and all the
185
- * wrong ones, copied from delegated spec runs; it stays available as an opt-in.
186
- */
187
- extraction: z.object({
188
- enabled: z.boolean().default(false),
189
- idleHours: z.number().min(0).default(3),
190
- minMessages: unboundedInt().min(1).default(10),
191
- maxPerSession: z.number().int().min(1).max(20).default(5),
192
- maxSessionsPerRun: z.number().int().min(1).max(20).default(3)
193
- }).prefault({})
215
+ /** False makes memory read-only: it still loads, and the model cannot write it. */
216
+ autoSave: z.boolean().default(true)
194
217
  });
195
218
  var agentSettingsSchema = z.object({
196
219
  mode: z.enum(["adaptive", "manual", "off"]).default("adaptive"),
@@ -302,6 +325,10 @@ var commandSettingsSchema = z.object({
302
325
  projectCommands: z.record(z.string(), projectCommandChoiceSchema).default({})
303
326
  });
304
327
  var projectDirectoryChoiceSchema = z.enum(["approved", "rejected"]);
328
+ var workspaceProviderChoiceSchema = z.object({
329
+ fingerprint: z.string().min(1),
330
+ choice: z.enum(["approved", "rejected"])
331
+ });
305
332
  var uiSettingsSchema = z.object({
306
333
  /** Show provider-native and embedded model reasoning in the interactive transcript. */
307
334
  showThinking: z.boolean().default(true),
@@ -327,6 +354,12 @@ var bookSettingsSchema = z.object({
327
354
  compactModel: z.string().min(1).optional(),
328
355
  /** Reasoning effort for the compaction reducer; defaults to the session effort capped at medium. */
329
356
  compactEffort: effortLevelSchema.optional(),
357
+ /**
358
+ * A stronger model the agent may consult through the `Advisor` tool (`/advisor`).
359
+ * Unset, the tool is not offered; {@link ADVISOR_OFF_VALUE} is the explicit "off" that a higher
360
+ * layer writes to outrank a lower layer's advisor.
361
+ */
362
+ advisorModel: z.string().min(1).optional(),
330
363
  /** Max agent turns per user message. Omit for unlimited. */
331
364
  maxTurns: unboundedInt().min(1).optional(),
332
365
  maxTokens: unboundedInt().min(1e3).optional(),
@@ -360,6 +393,12 @@ var bookSettingsSchema = z.object({
360
393
  * as a project allow rule with no entry is.
361
394
  */
362
395
  projectDirectories: z.record(z.string(), projectDirectoryChoiceSchema).default({}),
396
+ /**
397
+ * Decisions about providers only a workspace layer declares, keyed by provider id (and by
398
+ * `.bookrc.json` for its legacy `baseUrl`). A workspace-declared provider with no matching
399
+ * `approved` entry is withheld: it is absent from `provider`, and nothing it names is resolved.
400
+ */
401
+ workspaceProviders: z.record(z.string(), workspaceProviderChoiceSchema).default({}),
363
402
  env: z.record(z.string(), z.string()).default({}),
364
403
  provider: z.record(z.string(), providerConfigSchema).default({}),
365
404
  permissions: permissionsSchema.prefault({}),
@@ -384,7 +423,7 @@ var DEFAULT_SETTINGS = {
384
423
  autoAllowBashIfSandboxed: true,
385
424
  excludedCommands: [],
386
425
  allowUnsandboxedCommands: true,
387
- filesystem: { allowWrite: [], denyWrite: [], denyRead: [] },
426
+ filesystem: { allowWrite: [], denyWrite: [], denyRead: [], allowGitWrites: false },
388
427
  network: { allowedDomains: [], deniedDomains: [] }
389
428
  },
390
429
  hooks: {
@@ -403,6 +442,7 @@ var DEFAULT_SETTINGS = {
403
442
  },
404
443
  additionalDirectories: [],
405
444
  projectDirectories: {},
445
+ workspaceProviders: {},
406
446
  env: {},
407
447
  provider: {},
408
448
  ui: { showThinking: true, startupAnimation: true },
@@ -415,6 +455,7 @@ var DEFAULT_SETTINGS = {
415
455
  requestTimeoutMs: 6e5,
416
456
  streamStallTimeoutMs: 2e4,
417
457
  thinkingStallTimeoutMs: 9e5,
458
+ firstDeltaStallTimeoutMs: 12e4,
418
459
  toolRetries: 1,
419
460
  watchdog: false,
420
461
  streamReissueAttempts: 3,
@@ -430,16 +471,7 @@ var DEFAULT_SETTINGS = {
430
471
  },
431
472
  memory: {
432
473
  enabled: true,
433
- autoSave: true,
434
- requireApproval: false,
435
- quarantineExternal: true,
436
- extraction: {
437
- enabled: false,
438
- idleHours: 3,
439
- minMessages: 10,
440
- maxPerSession: 5,
441
- maxSessionsPerRun: 3
442
- }
474
+ autoSave: true
443
475
  },
444
476
  agents: {
445
477
  mode: "adaptive",
@@ -693,6 +725,10 @@ var SettingsRepository = class {
693
725
  return this.mutate(mutation, displayValues);
694
726
  }
695
727
  mutate(mutation, values) {
728
+ const refusal = projectFileWriteRefusal(this.path);
729
+ if (refusal) {
730
+ return { ok: false, path: this.path, diagnostics: [{ path: this.path, message: refusal }] };
731
+ }
696
732
  let lock;
697
733
  try {
698
734
  lock = this.acquireLock();
@@ -791,14 +827,17 @@ export {
791
827
  projectHookChoiceSchema,
792
828
  HOOK_EVENTS,
793
829
  effortLevelSchema,
830
+ ADVISOR_OFF_VALUE,
794
831
  providerConfigSchema,
795
832
  mcpProjectServerChoiceSchema,
796
833
  projectCommandChoiceSchema,
797
834
  projectDirectoryChoiceSchema,
835
+ workspaceProviderChoiceSchema,
798
836
  bookSettingsSchema,
799
837
  DEFAULT_SETTINGS,
800
838
  getNestedValue,
801
839
  setNestedValue,
840
+ deleteNestedValue,
802
841
  parseNumericFlag,
803
842
  resolvePrintPrompt,
804
843
  redactSettingValue,
@@ -810,4 +849,4 @@ export {
810
849
  SettingsRepository,
811
850
  formatSettingsDiagnostics
812
851
  };
813
- //# sourceMappingURL=chunk-X4NJT436.js.map
852
+ //# sourceMappingURL=chunk-REASC2QG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/settings.ts","../src/cli/utils.ts","../src/settings-repository.ts","../src/settings-redaction.ts"],"sourcesContent":["import { z } from 'zod';\n\n/**\n * Permission rule: \"Tool\" or \"Tool(specifier)\" where specifier is a glob pattern\n * matched against the tool's primary argument (e.g. bash command, file path).\n */\nexport const permissionRuleSchema = z.string().min(1);\n\n// An object schema defaulted to `{}` uses `.prefault({})`, not `.default({})`: Zod 4's `.default()`\n// returns its value without parsing it, so the object's own field defaults would never be applied.\n// Records and arrays keep `.default({})` / `.default([])`, whose values are already final.\n\n/**\n * An integer setting with no ceiling of its own.\n *\n * Zod 4's `.int()` also rejects anything above `Number.MAX_SAFE_INTEGER`, which Zod 3 accepted. A\n * settings file that loaded before the upgrade, such as one holding an \"effectively unlimited\"\n * `maxTurns`, must keep loading: an invalid document stops Book from starting. So these settings\n * check integrality themselves, reporting the same `invalid_type` issue `.int()` does. Bounded\n * ones keep `.int()`, since their `.max()` is far lower.\n */\nexport function unboundedInt() {\n return z.number().check((ctx) => {\n if (!Number.isInteger(ctx.value)) {\n ctx.issues.push({ code: 'invalid_type', expected: 'int', input: ctx.value });\n }\n });\n}\n\n/**\n * Bash sandbox configuration (matches CC's sandbox.* keys).\n */\nexport const sandboxSchema = z.object({\n enabled: z.boolean().default(false),\n failIfUnavailable: z.boolean().default(false),\n autoAllowBashIfSandboxed: z.boolean().default(true),\n excludedCommands: z.array(z.string()).default([]),\n allowUnsandboxedCommands: z.boolean().default(true),\n filesystem: z\n .object({\n allowWrite: z.array(z.string()).default([]),\n denyWrite: z.array(z.string()).default([]),\n denyRead: z.array(z.string()).default([]),\n /**\n * Off (the default) binds every git directory of the workspace read-only inside\n * the sandbox, which also closes the pointer-file redirect a writable git dir\n * leaves open (issue 373): a sandboxed command can create `.git/commondir` in a\n * plain repository, and the host's git reads it on its next command. Sandboxed\n * git writes — commit, checkout, add, stash, fetch — fail while it is off, and\n * the failed output names this setting among the remedies. On restores the\n * previous shape (git directories writable, their hooks, config and pointer\n * files read-only, the top-level `.git` pinned) and reopens the pointer-file\n * gap. Only a trusted layer may set it: a workspace file may tighten it to\n * `false` and never back to `true`.\n */\n allowGitWrites: z.boolean().default(false),\n })\n .prefault({}),\n network: z\n .object({\n allowedDomains: z.array(z.string()).default([]),\n deniedDomains: z.array(z.string()).default([]),\n })\n .prefault({}),\n});\n\n/**\n * Permission configuration (matches CC's permissions key).\n */\n/**\n * Per-rule trust decisions for `permissions.allow` entries declared by the\n * checked-in project layer.\n *\n * Unlike an MCP server, a permission rule *is* its own identity: change the\n * rule and it is a different key, so no fingerprint is needed to detect drift.\n * Decisions are stored per workspace in `.book/settings.local.json`, which the\n * repository cannot write.\n */\nexport const projectAllowRuleChoiceSchema = z.enum(['approved', 'rejected']);\n\nexport type ProjectAllowRuleChoice = z.infer<typeof projectAllowRuleChoiceSchema>;\n\nexport const permissionsSchema = z.object({\n allow: z.array(permissionRuleSchema).default([]),\n ask: z.array(permissionRuleSchema).default([]),\n deny: z.array(permissionRuleSchema).default([]),\n /**\n * Decisions about `allow` rules the project layer declared. A project rule\n * with no `approved` entry here never reaches the resolved allow list.\n */\n projectAllowRules: z.record(z.string(), projectAllowRuleChoiceSchema).default({}),\n});\n\n/**\n * Hook entry: a shell command that runs at a lifecycle event.\n * Matches Claude Code's hook configuration format.\n */\nexport const hookEntrySchema = z.object({\n /** Tool(specifier) pattern to filter which events trigger this hook. */\n matcher: z.string().optional(),\n /** Shell command to run (passed through system shell). */\n command: z.string().min(1),\n /** Extra environment variables for this hook. */\n env: z.record(z.string(), z.string()).default({}),\n});\n\nexport type HookEntry = z.infer<typeof hookEntrySchema>;\n\n/**\n * Per-entry trust decisions for hook entries declared by the checked-in project\n * layer, keyed by a fingerprint of `{ event, matcher, command, env }`.\n *\n * A hook is a shell command Book executes at lifecycle events, so a project\n * hook with no `approved` entry here never reaches the resolved hooks. Unlike\n * an MCP server record, a hook carries no name — the fingerprint is the key.\n */\nexport const projectHookChoiceSchema = z.enum(['approved', 'rejected']);\n\nexport type ProjectHookChoice = z.infer<typeof projectHookChoiceSchema>;\n\n/** Hook events supported by book. */\nexport const HOOK_EVENTS = [\n 'SessionStart',\n 'SessionEnd',\n 'UserPromptSubmit',\n 'PreToolUse',\n 'PostToolUse',\n 'Stop',\n 'PreCompact',\n 'PostCompact',\n 'SubagentStart',\n 'SubagentStop',\n /**\n * Something a human should know about while nobody is watching: a run parked on\n * a rejected credential, a budget crossing its ceiling, a refused spawn because\n * the disk is nearly full. Wire ntfy/Slack/SMS here as an ordinary shell hook.\n *\n * Only `severity: 'alarm'` is meant to wake anyone; everything else is a line to\n * tail. `hooksSchema` is generated from this array, so the schema cost is one\n * entry.\n */\n 'Notification',\n] as const;\n\nexport type HookEvent = (typeof HOOK_EVENTS)[number];\n\n/**\n * Hooks configuration — a map from event name to an array of hook entries.\n */\nexport const hooksSchema = z.object({\n ...(Object.fromEntries(HOOK_EVENTS.map((e) => [e, z.array(hookEntrySchema).default([])])) as {\n [K in HookEvent]: z.ZodDefault<z.ZodArray<typeof hookEntrySchema>>;\n }),\n /**\n * Decisions about hook entries the checked-in project layer declared. An\n * entry whose fingerprint has no `approved` record here never reaches the\n * resolved hooks.\n */\n projectEntries: z.record(z.string(), projectHookChoiceSchema).default({}),\n});\n\nexport type HooksConfig = z.infer<typeof hooksSchema>;\n\n/**\n * Retry configuration schema — mirrors Claude Code's retry tunables.\n */\nexport const retrySettingsSchema = z.object({\n maxAttempts: z.number().int().min(0).max(15).default(10),\n baseDelayMs: z.number().int().min(100).max(60000).default(1000),\n maxDelayMs: z.number().int().min(100).max(300000).default(30000),\n totalBudgetMs: z.number().int().min(0).max(600000).default(0),\n requestTimeoutMs: z.number().int().min(5000).max(600000).default(600000),\n streamStallTimeoutMs: z.number().int().min(5000).max(120000).default(20000),\n /**\n * Stall ceiling while the model is thinking, which is a different regime.\n *\n * `streamStallTimeoutMs` is tuned for a chat: 20s of silence means something\n * broke. But with adaptive thinking on — the default for every Opus and Sonnet\n * model here, at `high` effort unless told otherwise — a long quiet stretch\n * before the first token is the model working, not a fault. Applying the chat\n * timeout to it cancels a healthy request and reports `stream_stall`.\n */\n thinkingStallTimeoutMs: z.number().int().min(10_000).max(1_800_000).default(900_000),\n /**\n * First-delta ceiling for a model that may think silently (#379).\n *\n * A router that buffers the model's whole thinking block sends a role-only\n * first chunk and then nothing until the thinking is done — up to about 80 s —\n * which the chat ceiling reads as a fault. While a stream has shown no\n * content, tool call or reasoning delta yet, and neither `reasoning_effort`\n * nor the model's catalog `effort` entry settles what it is, silence is\n * judged by this ceiling instead. The first meaningful delta hands the stream\n * back to the per-phase ceilings.\n */\n firstDeltaStallTimeoutMs: z.number().int().min(5_000).max(1_800_000).default(120_000),\n toolRetries: z.number().int().min(0).max(3).default(1),\n watchdog: z.boolean().default(false),\n /**\n * How many times a turn may be re-sent after a transport fault that left the\n * work intact — a stalled stream, a dropped socket — and how many times an\n * empty completion may be re-asked. The two are counted separately per turn;\n * this only says how many of each a turn gets.\n *\n * `maxAttempts` covers connection setup only; once a 200 response is streaming\n * it is out of scope, so without this a single 20-second provider silence ended\n * the run. Set to 0 to restore that behavior exactly for transport faults. An\n * empty completion still gets one retry, the floor every earlier version spent,\n * so a run does not end on the first blank answer.\n */\n streamReissueAttempts: z.number().int().min(0).max(10).default(3),\n /**\n * Separate allowance for continuing after the provider's output cap. A large\n * generated file legitimately hits the cap on consecutive turns, and sharing\n * the transport budget would leave nothing for a real socket drop afterwards.\n */\n outputCapContinuations: z.number().int().min(0).max(50).default(10),\n});\n\nexport type RetrySettings = z.infer<typeof retrySettingsSchema>;\n\nexport const effortLevelSchema = z.enum(['low', 'medium', 'high', 'xhigh', 'max']);\n\nexport const providerModelSchema = z.object({\n label: z.string().optional(),\n contextWindow: unboundedInt().positive().optional(),\n /**\n * The input limit the endpoint's model list reported. Written by discovery and overwritten on\n * every refresh; ranks below a declared `contextWindow` and a learned ceiling, so a figure that\n * is too large can still be corrected.\n */\n reportedContextWindow: unboundedInt().positive().optional(),\n maxOutputTokens: unboundedInt().positive().optional(),\n /** Whether this model accepts image input. Unknown models remain optimistic. */\n vision: z.boolean().optional(),\n /**\n * Entered by hand rather than returned by the endpoint's model-list API.\n * Refreshing the catalog keeps these, since they exist precisely because\n * discovery does not list them.\n */\n manual: z.boolean().optional(),\n /** Mutation-tool preference for this model; defaults to a family-level prior. */\n editFormat: z.enum(['patch', 'replace', 'whole']).optional(),\n effort: z\n .union([\n z.literal(false),\n z.object({\n default: effortLevelSchema.optional(),\n levels: z.array(effortLevelSchema).optional(),\n }),\n ])\n .optional(),\n});\n\n/**\n * The `advisorModel` value that means \"no advisor\". `/advisor off` removes the user layer's key,\n * but a checked-in `.book/settings.json` that declares an advisor would come back at the next\n * launch, so the local layer (which outranks it) gets this instead (#418).\n */\nexport const ADVISOR_OFF_VALUE = 'off';\n\nexport const providerConfigSchema = z.object({\n type: z.enum(['openai', 'anthropic']).default('openai'),\n baseURL: z.string().min(1).optional(),\n baseUrl: z.string().min(1).optional(),\n apiKey: z.string().min(1).optional(),\n models: z.record(z.string(), providerModelSchema).default({}),\n});\n\n/**\n * Continuation: whether a turn that stops with no tool calls may be re-driven.\n *\n * `runAgentLoop` ends as soon as a turn produces no tool calls, so one user\n * message is the whole run and a model that writes \"I've finished\" stops there.\n * Enabling this lets the loop append a host-authored user turn and keep going\n * while there is demonstrably work left.\n *\n * Off by default: it changes when a run ends, and continuation without the\n * no-progress brake below would let a stalled run spin and bill silently, where\n * today it stops and a human notices.\n */\nexport const continuationSettingsSchema = z.object({\n enabled: z.boolean().default(false),\n /** Consecutive host-authored continuations before the run stops regardless. */\n maxConsecutive: z.number().int().min(1).max(1000).default(50),\n /**\n * Identical progress witnesses in a row before the run stops as no-progress.\n * Minimum 1: a 0 here would fire on the first boundary, before any witness\n * exists, and label a run that never got a chance to spin as spinning.\n */\n noProgressLimit: z.number().int().min(1).max(20).default(3),\n /**\n * Consecutive turns in which EVERY tool call was refused before the run stops.\n *\n * Enforced even when `enabled` is false, deliberately. The spin it catches\n * predates continuation and needs no continuation to happen: a headless run in\n * the default permission mode answers every prompt `deny`, so the model can\n * re-issue a refused call forever while `toolCalls.length > 0` keeps the\n * turn-end gate from ever firing. None of the existing anti-loop signals stop it\n * either — the repeated-failure breaker only annotates a repeated call and never\n * sees the loop's own permission refusals, and `toolCallStats.failures` excludes\n * `blocked` by construction.\n *\n * 0 disables the guard.\n */\n blockedToolTurnLimit: z.number().int().min(0).max(100).default(3),\n /** Turns between host-authored work-state messages; 0 disables them. */\n planRefreshTurns: z.number().int().min(0).max(500).default(25),\n /** Wall-clock ceiling for one continued run; 0 means no ceiling. */\n maxWallClockMs: unboundedInt().min(0).default(0),\n});\n\nexport type ContinuationSettings = z.infer<typeof continuationSettingsSchema>;\n\nexport const memorySettingsSchema = z.object({\n enabled: z.boolean().default(true),\n /** False makes memory read-only: it still loads, and the model cannot write it. */\n autoSave: z.boolean().default(true),\n});\n\nexport const agentSettingsSchema = z.object({\n mode: z.enum(['adaptive', 'manual', 'off']).default('adaptive'),\n maxConcurrent: z.number().int().min(1).max(16).default(3),\n maxSpawned: z.number().int().min(1).max(64).default(8),\n maxDepth: z.literal(1).default(1),\n persist: z.boolean().default(true),\n includeUntrackedInSnapshot: z.boolean().default(true),\n telemetry: z.boolean().default(true),\n retentionDays: z.number().int().min(1).max(3650).default(30),\n checks: z\n .record(z.string(), z.union([z.string().min(1), z.array(z.string().min(1)).min(1)]))\n .default({}),\n /**\n * Wall-clock ceiling for one `Check` run. The 120s default suits a focused\n * suite; a full `npm test` on a large repository routinely exceeds it, and a\n * check that always times out is worse than no check because the timeout was\n * reported as a failing suite. Raise it per project rather than per invocation.\n */\n checkTimeoutMs: z.number().int().min(1_000).max(7_200_000).default(120_000),\n /** Ceiling on a foreground delegation via the Task tool; overrides the 1800s default. */\n taskTimeoutMs: unboundedInt().min(1000).optional(),\n /**\n * Simultaneous agent worktrees per repository; 0 disables the check.\n *\n * Nothing reclaims a worktree automatically outside the TUI, and the store's\n * retention sweep runs once at startup with a 30-day default — so on a long\n * unattended run the count only grows. Sized above `maxConcurrent` so ordinary\n * fan-out is unaffected and only accumulation trips it.\n */\n maxWorktrees: z.number().int().min(0).max(256).default(24),\n /**\n * Re-drive agents that were interrupted by process death on the next start.\n *\n * A restart otherwise converts the whole pending backlog into terminal records\n * nothing picks up, silently losing hours of child work on a wide fan-out. The\n * re-drive is contained: explorers are read-only and patchers/validators run in\n * their own worktree, so nothing reaches the parent workspace without the usual\n * evidence gate. Only `process_exit` interruptions qualify — a user stop stays\n * stopped.\n */\n resumeInterrupted: z.boolean().default(true),\n /**\n * Refuse a new worktree when free disk would fall below this; 0 disables.\n * Worktrees share the filesystem with the workspace, so exhausting it breaks\n * the root agent's own Edit and Bash, not just the child's.\n */\n minFreeDiskBytes: unboundedInt()\n .min(0)\n .default(2 * 1024 * 1024 * 1024),\n profiles: z\n .record(\n z.string(),\n z.object({\n model: z.string().min(1).optional(),\n effort: effortLevelSchema.optional(),\n maxTurns: unboundedInt().min(1).optional(),\n color: z.string().optional(),\n }),\n )\n .default({}),\n ui: z.object({ enabled: z.boolean().default(true) }).prefault({}),\n routing: z\n .object({\n inlineSearchBudget: z.number().int().min(1).max(20).default(3),\n exploreReminder: z.boolean().default(true),\n })\n .prefault({}),\n forwardTextEvents: z.boolean().default(false),\n});\n\nexport const toolDiscoverySettingsSchema = z.object({\n mode: z.enum(['auto', 'eager', 'deferred']).default('auto'),\n eagerToolCount: z.number().int().min(1).max(100).default(10),\n schemaTokenBudget: z.number().int().min(1000).max(100000).default(8000),\n maxLoadedTools: z.number().int().min(1).max(100).default(15),\n searchLimit: z.number().int().min(1).max(5).default(5),\n});\n\nexport type ToolDiscoverySettings = z.infer<typeof toolDiscoverySettingsSchema>;\n\nexport const toolExecutionSettingsSchema = z.object({\n /** Shared across the root loop and managed children for parallel-safe tools. */\n maxConcurrent: z.number().int().min(1).max(8).default(4),\n});\n\nexport type ToolExecutionSettings = z.infer<typeof toolExecutionSettingsSchema>;\n\nexport const observabilitySettingsSchema = z.object({\n /** Persist a per-tool-call JSONL record for `book tool-stats`. */\n toolTelemetry: z.boolean().default(true),\n /**\n * Default reporting window (in days) for `book tool-stats` and the target for\n * `book tool-stats --prune`. Disk use is bounded by size-based log rotation;\n * records are only deleted when they rotate out or are explicitly pruned.\n */\n toolTelemetryRetentionDays: z.number().int().min(1).max(3650).default(30),\n});\n\nexport type ObservabilitySettings = z.infer<typeof observabilitySettingsSchema>;\n\nexport const mcpProjectServerChoiceSchema = z.object({\n /** Hash of the server's command/args/env at decision time; a mismatch re-prompts. */\n fingerprint: z.string().min(1),\n choice: z.enum(['approved', 'rejected']),\n});\n\nexport type McpProjectServerChoice = z.infer<typeof mcpProjectServerChoiceSchema>;\n\nexport const mcpSettingsSchema = z.object({\n /**\n * Per-server trust decisions for workspace `.mcp.json` declarations.\n * User-global servers (<BOOK_HOME>/.book/mcp.json) never require approval.\n */\n projectServers: z.record(z.string(), mcpProjectServerChoiceSchema).default({}),\n});\n\nexport type McpSettings = z.infer<typeof mcpSettingsSchema>;\n\nexport const projectCommandChoiceSchema = z.object({\n /** Digest of the shell the body substitutes at decision time; a mismatch re-prompts. */\n fingerprint: z.string().min(1),\n choice: z.enum(['approved', 'rejected']),\n});\n\nexport type ProjectCommandChoice = z.infer<typeof projectCommandChoiceSchema>;\n\nexport const commandSettingsSchema = z.object({\n /**\n * Per-command trust decisions for `<workspace>/.book/commands/*.md` files\n * that substitute shell into their prompt. That substitution runs outside the\n * permission system and outside the sandbox, so a repository-controlled file\n * needs a decision before it runs. User-global commands\n * (<BOOK_HOME>/commands) were written by the user and never require one.\n */\n projectCommands: z.record(z.string(), projectCommandChoiceSchema).default({}),\n});\n\nexport type CommandSettings = z.infer<typeof commandSettingsSchema>;\n\n/**\n * Per-directory trust decisions for `additionalDirectories` declared by the checked-in project\n * layer, keyed by the directory's real path.\n *\n * `additionalDirectories` widens the set of roots the file tools serve, so honouring one from a\n * clone would hand that clone a read (and a write path) over whatever the path resolves to. A\n * project directory with no `approved` entry here is withheld, exactly as a project allow rule\n * is. Unlike a rule, the key cannot be the declared text: a repository controls that text and\n * could declare a link, so the decision is recorded against where the path really goes.\n */\nexport const projectDirectoryChoiceSchema = z.enum(['approved', 'rejected']);\n\nexport type ProjectDirectoryChoice = z.infer<typeof projectDirectoryChoiceSchema>;\n\n/**\n * One trust decision about a provider a workspace layer declared: where its requests go and which\n * credential they carry. The fingerprint digests the declared type, origin and `apiKey` reference\n * (see `provider-approvals.ts`), so changing either after the decision withholds the provider\n * again.\n */\nexport const workspaceProviderChoiceSchema = z.object({\n fingerprint: z.string().min(1),\n choice: z.enum(['approved', 'rejected']),\n});\n\nexport type WorkspaceProviderChoice = z.infer<typeof workspaceProviderChoiceSchema>;\n\nexport const uiSettingsSchema = z.object({\n /** Show provider-native and embedded model reasoning in the interactive transcript. */\n showThinking: z.boolean().default(true),\n /** Play the full-screen fire sequence on a newly created interactive startup session. */\n startupAnimation: z.boolean().default(true),\n});\n\nexport type UiSettings = z.infer<typeof uiSettingsSchema>;\n\nexport const skillActivationSchema = z.enum(['auto', 'name-only', 'manual', 'off']);\nexport type SkillActivation = z.infer<typeof skillActivationSchema>;\n\nexport const skillExecutionSchema = z.enum(['inherit', 'ask', 'deny']);\nexport type SkillExecution = z.infer<typeof skillExecutionSchema>;\n\nexport const skillSettingsSchema = z.object({\n /** Emergency switch that removes skill prompt and runtime effects without deleting packages. */\n enabled: z.boolean().default(true),\n /** Per-skill visibility overrides keyed by the skill's declared name. */\n overrides: z.record(z.string(), skillActivationSchema).default({}),\n /** Per-skill activation consent policy. Tool calls still use the normal permission system. */\n execution: z.record(z.string(), skillExecutionSchema).default({}),\n});\n\nexport type SkillSettings = z.infer<typeof skillSettingsSchema>;\n\nexport type ProviderModelConfig = z.infer<typeof providerModelSchema>;\nexport type ProviderConfig = z.infer<typeof providerConfigSchema>;\n\n/** The supported production strategy. */\nexport const compactStrategySchema = z.literal('summary');\nexport type CompactStrategy = z.infer<typeof compactStrategySchema>;\n\n/**\n * Full settings.json schema — all keys that book supports.\n * New keys added in later milestones extend this schema.\n */\nexport const bookSettingsSchema = z.object({\n model: z.string().optional(),\n /** Default strategy used to reduce historical conversation context. */\n compactStrategy: compactStrategySchema.optional(),\n /** Optional model used only to generate historical conversation checkpoints. */\n compactModel: z.string().min(1).optional(),\n /** Reasoning effort for the compaction reducer; defaults to the session effort capped at medium. */\n compactEffort: effortLevelSchema.optional(),\n /**\n * A stronger model the agent may consult through the `Advisor` tool (`/advisor`).\n * Unset, the tool is not offered; {@link ADVISOR_OFF_VALUE} is the explicit \"off\" that a higher\n * layer writes to outrank a lower layer's advisor.\n */\n advisorModel: z.string().min(1).optional(),\n /** Max agent turns per user message. Omit for unlimited. */\n maxTurns: unboundedInt().min(1).optional(),\n maxTokens: unboundedInt().min(1000).optional(),\n effort: effortLevelSchema.optional(),\n /** TUI color theme: rubric (default), folio, apple, or a custom theme filename. */\n theme: z.string().min(1).optional(),\n /**\n * The shell the Bash tool spawns: `bash`, `pwsh`, `powershell`, `cmd`, `sh`,\n * or an executable path. Honoured from the user-global layer, an explicit\n * `--settings` document, or `BOOK_SHELL` only: a workspace file cannot pick\n * the program every command is handed to.\n */\n shell: z.string().min(1).optional(),\n ui: uiSettingsSchema.prefault({}),\n skills: skillSettingsSchema.prefault({}),\n autoCompactEnabled: z.boolean().optional(),\n /** Permission mode used by each host when no invocation-specific mode is supplied. */\n defaultMode: z\n .enum(['default', 'acceptEdits', 'plan', 'auto', 'dontAsk', 'bypassPermissions'])\n .optional(),\n disableBypassPermissionsMode: z.boolean().optional(),\n /**\n * Extra roots the file tools serve, for paths outside the workspace. An honored entry is a root\n * for reads *and* writes: `Write`, `Edit`, `MultiEdit`, `ApplyPatch` and `NotebookEdit` accept an\n * absolute path inside one and treat the entry as they would the workspace, while a relative path\n * stays anchored to the workspace. A project-declared entry is withheld until `book trust dir`.\n */\n additionalDirectories: z.array(z.string()).default([]),\n /**\n * Decisions about `additionalDirectories` the project layer declared, keyed by the directory's\n * real path (the declared text is only what the repository wrote; the path is what the file\n * tools would serve). A project directory with no `approved` entry here is withheld, exactly\n * as a project allow rule with no entry is.\n */\n projectDirectories: z.record(z.string(), projectDirectoryChoiceSchema).default({}),\n /**\n * Decisions about providers only a workspace layer declares, keyed by provider id (and by\n * `.bookrc.json` for its legacy `baseUrl`). A workspace-declared provider with no matching\n * `approved` entry is withheld: it is absent from `provider`, and nothing it names is resolved.\n */\n workspaceProviders: z.record(z.string(), workspaceProviderChoiceSchema).default({}),\n env: z.record(z.string(), z.string()).default({}),\n provider: z.record(z.string(), providerConfigSchema).default({}),\n permissions: permissionsSchema.prefault({}),\n sandbox: sandboxSchema.prefault({}),\n hooks: hooksSchema.prefault({}),\n retry: retrySettingsSchema.prefault({}),\n memory: memorySettingsSchema.prefault({}),\n continuation: continuationSettingsSchema.prefault({}),\n agents: agentSettingsSchema.prefault({}),\n toolDiscovery: toolDiscoverySettingsSchema.prefault({}),\n toolExecution: toolExecutionSettingsSchema.prefault({}),\n observability: observabilitySettingsSchema.prefault({}),\n mcp: mcpSettingsSchema.prefault({}),\n commands: commandSettingsSchema.prefault({}),\n});\n\nexport type BookSettings = z.infer<typeof bookSettingsSchema>;\n\n/**\n * Resolved settings after layering (all defaults filled).\n */\nexport type ResolvedSettings = Required<\n Omit<\n BookSettings,\n | 'model'\n | 'compactModel'\n | 'compactEffort'\n | 'advisorModel'\n | 'maxTurns'\n | 'maxTokens'\n | 'effort'\n | 'theme'\n | 'shell'\n | 'autoCompactEnabled'\n | 'defaultMode'\n | 'disableBypassPermissionsMode'\n >\n> &\n Pick<\n BookSettings,\n | 'model'\n | 'compactModel'\n | 'compactEffort'\n | 'advisorModel'\n | 'maxTurns'\n | 'maxTokens'\n | 'effort'\n | 'theme'\n | 'shell'\n | 'autoCompactEnabled'\n | 'defaultMode'\n | 'disableBypassPermissionsMode'\n >;\n\n/**\n * Default settings used as the base layer before user/project/local override.\n */\nexport const DEFAULT_SETTINGS: ResolvedSettings = {\n compactStrategy: 'summary',\n permissions: { allow: [], ask: [], deny: [], projectAllowRules: {} },\n sandbox: {\n enabled: false,\n failIfUnavailable: false,\n autoAllowBashIfSandboxed: true,\n excludedCommands: [],\n allowUnsandboxedCommands: true,\n filesystem: { allowWrite: [], denyWrite: [], denyRead: [], allowGitWrites: false },\n network: { allowedDomains: [], deniedDomains: [] },\n },\n hooks: {\n SessionStart: [],\n SessionEnd: [],\n UserPromptSubmit: [],\n PreToolUse: [],\n PostToolUse: [],\n Stop: [],\n PreCompact: [],\n PostCompact: [],\n SubagentStart: [],\n SubagentStop: [],\n Notification: [],\n projectEntries: {},\n },\n additionalDirectories: [],\n projectDirectories: {},\n workspaceProviders: {},\n env: {},\n provider: {},\n ui: { showThinking: true, startupAnimation: true },\n skills: { enabled: true, overrides: {}, execution: {} },\n retry: {\n maxAttempts: 10,\n baseDelayMs: 1000,\n maxDelayMs: 30000,\n totalBudgetMs: 0,\n requestTimeoutMs: 600000,\n streamStallTimeoutMs: 20000,\n thinkingStallTimeoutMs: 900_000,\n firstDeltaStallTimeoutMs: 120_000,\n toolRetries: 1,\n watchdog: false,\n streamReissueAttempts: 3,\n outputCapContinuations: 10,\n },\n continuation: {\n enabled: false,\n maxConsecutive: 50,\n noProgressLimit: 3,\n blockedToolTurnLimit: 3,\n planRefreshTurns: 25,\n maxWallClockMs: 0,\n },\n memory: {\n enabled: true,\n autoSave: true,\n },\n agents: {\n mode: 'adaptive',\n maxConcurrent: 3,\n maxSpawned: 8,\n maxDepth: 1,\n persist: true,\n includeUntrackedInSnapshot: true,\n telemetry: true,\n retentionDays: 30,\n checks: {},\n checkTimeoutMs: 120_000,\n maxWorktrees: 24,\n resumeInterrupted: true,\n minFreeDiskBytes: 2 * 1024 * 1024 * 1024,\n profiles: {},\n ui: { enabled: true },\n routing: { inlineSearchBudget: 3, exploreReminder: true },\n forwardTextEvents: false,\n },\n toolDiscovery: {\n mode: 'auto',\n eagerToolCount: 10,\n schemaTokenBudget: 8000,\n maxLoadedTools: 15,\n searchLimit: 5,\n },\n toolExecution: {\n maxConcurrent: 4,\n },\n observability: {\n toolTelemetry: true,\n toolTelemetryRetentionDays: 30,\n },\n mcp: {\n projectServers: {},\n },\n commands: {\n projectCommands: {},\n },\n};\n","/** Deeply nested value access by dot-separated key path. */\nexport function getNestedValue(obj: Record<string, unknown>, path: string): unknown {\n const parts = path.split('.');\n let current: unknown = obj;\n for (const part of parts) {\n if (current === null || current === undefined || typeof current !== 'object') return undefined;\n current = (current as Record<string, unknown>)[part];\n }\n return current;\n}\n\n/** Deeply nested value setter, creating intermediate objects as needed. */\nexport function setNestedValue(obj: Record<string, unknown>, path: string, value: unknown): void {\n const parts = path.split('.');\n let current = obj;\n for (let i = 0; i < parts.length - 1; i++) {\n if (\n !(parts[i] in current) ||\n typeof current[parts[i]] !== 'object' ||\n current[parts[i]] === null\n ) {\n current[parts[i]] = {};\n }\n current = current[parts[i]] as Record<string, unknown>;\n }\n current[parts[parts.length - 1]] = value;\n}\n\n/** Delete a dot-path and prune only empty ancestor objects on that path. */\nexport function deleteNestedValue(\n obj: Record<string, unknown>,\n path: string | readonly string[],\n): boolean {\n const parts = typeof path === 'string' ? path.split('.') : [...path];\n const ancestors: Array<{ parent: Record<string, unknown>; key: string }> = [];\n let current = obj;\n\n for (let i = 0; i < parts.length - 1; i++) {\n const key = parts[i];\n if (!Object.prototype.hasOwnProperty.call(current, key)) return false;\n const next = current[key];\n if (typeof next !== 'object' || next === null || Array.isArray(next)) return false;\n ancestors.push({ parent: current, key });\n current = next as Record<string, unknown>;\n }\n\n const leaf = parts[parts.length - 1];\n if (!Object.prototype.hasOwnProperty.call(current, leaf)) return false;\n delete current[leaf];\n\n for (let i = ancestors.length - 1; i >= 0; i--) {\n const { parent, key } = ancestors[i];\n const child = parent[key];\n if (\n typeof child === 'object' &&\n child !== null &&\n !Array.isArray(child) &&\n Object.keys(child).length === 0\n ) {\n delete parent[key];\n } else {\n break;\n }\n }\n return true;\n}\n\n/**\n * Parse a numeric CLI flag, refusing anything that is not a usable number.\n *\n * `parseInt('none', 10)` is `NaN`, and the old guard was truthiness — `'none'` is\n * truthy, so the typo sailed through. Downstream, `NaN` loses every comparison it\n * takes part in, which is the worst possible failure for a limit: `--max-turns`\n * ran zero turns and reported `completed`, and `--max-budget-usd` read as\n * *configured* while permitting unbounded spend. Reject it at the boundary, and\n * test presence with `!== undefined` so an explicit `0` is a real zero rather than\n * a missing flag.\n */\nexport function parseNumericFlag(\n raw: unknown,\n flag: string,\n opts: { integer?: boolean; min?: number } = {},\n): number | undefined {\n if (raw === undefined || raw === null || raw === '') return undefined;\n const text = String(raw).trim();\n const value = opts.integer ? Number.parseInt(text, 10) : Number.parseFloat(text);\n // Number.parse* stops at the first bad character, so '12abc' would otherwise\n // silently become 12.\n if (!Number.isFinite(value) || !/^[+-]?(\\d+\\.?\\d*|\\.\\d+)$/.test(text)) {\n throw new Error(`${flag} expects a number, but received \"${text}\".`);\n }\n // `Number.parseInt('2.5', 10)` is 2, so testing the parsed value would never\n // catch a fractional input for an integer flag - it would silently truncate.\n if (opts.integer && text.includes('.')) {\n throw new Error(`${flag} expects a whole number, but received \"${text}\".`);\n }\n const min = opts.min ?? 0;\n if (value < min) {\n throw new Error(`${flag} must be at least ${min}, but received \"${text}\".`);\n }\n return value;\n}\n\n/**\n * Merge the root positional argument into `-p/--print [prompt]`.\n *\n * `--print` carries its prompt as its own optional value, so a prompt written\n * after a later flag (`book -p --model m \"fix it\"`) is a stray positional that\n * commander rejected with \"too many arguments\" (#226). Claude Code accepts the\n * prompt in either place, and so does Book now: the positional is the prompt\n * when the flag has no value of its own. Both at once is an error rather than a\n * silent choice, and a positional without `--print` is an error too, because the\n * TUI has nowhere to put it — and it is the shape a mistyped subcommand takes.\n *\n * Returns the resolved `--print` value, or `{ error }` for commander to report.\n */\nexport function resolvePrintPrompt(\n print: string | boolean | undefined,\n positional: string | undefined,\n): { print: string | boolean | undefined } | { error: string } {\n if (positional === undefined) return { print };\n if (print === undefined) {\n return {\n error: `unexpected argument '${positional}'. A prompt on the command line needs -p/--print (book -p \"${positional}\"); run book --help for the subcommands.`,\n };\n }\n if (typeof print === 'string') {\n return {\n error: `the prompt was given twice: --print '${print}' and the argument '${positional}'. Pass it once, either as the --print value or as the argument.`,\n };\n }\n return { print: positional };\n}\n","import {\n closeSync,\n existsSync,\n fsyncSync,\n mkdirSync,\n openSync,\n readFileSync,\n renameSync,\n statSync,\n unlinkSync,\n writeSync,\n} from 'fs';\nimport { basename, dirname, join } from 'path';\nimport type { core } from 'zod';\nimport { deleteNestedValue, setNestedValue } from './cli/utils.js';\nimport { bookSettingsSchema } from './settings.js';\nimport { redactSettingValue } from './settings-redaction.js';\nimport { projectFileWriteRefusal } from './workspace-files.js';\n\nexport type SettingsDocumentReadResult =\n | { status: 'absent'; path: string }\n | { status: 'valid'; path: string; document: Record<string, unknown> }\n | { status: 'malformed'; path: string; error: string }\n | { status: 'non-object'; path: string; error: string };\n\nexport interface SettingsDiagnostic {\n path: string;\n issuePath?: string;\n message: string;\n}\n\nexport type SettingsMutationResult =\n | {\n ok: true;\n path: string;\n document: Record<string, unknown>;\n values: Record<string, unknown>;\n changed: boolean;\n }\n | { ok: false; path: string; diagnostics: SettingsDiagnostic[] };\n\nexport interface SettingsRepositoryOptions {\n writeAtomic?: (path: string, contents: string) => void;\n}\n\nexport const SETTINGS_TOP_LEVEL_KEYS = Object.freeze(Object.keys(bookSettingsSchema.shape).sort());\n\nexport function formatSettingsKeyHelp(heading = 'Supported top-level settings:'): string {\n return `${heading}\\n${SETTINGS_TOP_LEVEL_KEYS.map((key) => ` ${key}`).join('\\n')}`;\n}\n\nfunction issueDiagnostic(path: string, issue: core.$ZodIssue): SettingsDiagnostic {\n return {\n path,\n issuePath: issue.path.join('.'),\n message: issue.message,\n };\n}\n\nfunction safeMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nexport function readSettingsDocument(path: string): SettingsDocumentReadResult {\n if (!existsSync(path)) return { status: 'absent', path };\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(readFileSync(path, 'utf-8'));\n } catch (error) {\n return { status: 'malformed', path, error: safeMessage(error) };\n }\n\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n return { status: 'non-object', path, error: 'Settings must contain a JSON object.' };\n }\n\n return { status: 'valid', path, document: parsed as Record<string, unknown> };\n}\n\nexport function writeFileAtomic(path: string, contents: string): void {\n const directory = dirname(path);\n mkdirSync(directory, { recursive: true });\n const temporaryPath = join(\n directory,\n `.${basename(path)}.${process.pid}.${crypto.randomUUID()}.tmp`,\n );\n let descriptor: number | undefined;\n\n try {\n descriptor = openSync(temporaryPath, 'wx', 0o600);\n writeSync(descriptor, contents, undefined, 'utf-8');\n fsyncSync(descriptor);\n closeSync(descriptor);\n descriptor = undefined;\n renameSync(temporaryPath, path);\n } catch (error) {\n if (descriptor !== undefined) closeSync(descriptor);\n try {\n unlinkSync(temporaryPath);\n } catch {\n // The temporary file may not have been created.\n }\n throw error;\n }\n}\n\nfunction validateTopLevelKeys(\n path: string,\n document: Record<string, unknown>,\n): SettingsDiagnostic[] {\n const supported = new Set(SETTINGS_TOP_LEVEL_KEYS);\n return Object.keys(document)\n .filter((key) => !supported.has(key))\n .map((key) => ({\n path,\n issuePath: key,\n message: `Unknown top-level setting. Supported keys: ${SETTINGS_TOP_LEVEL_KEYS.join(', ')}`,\n }));\n}\n\nexport class SettingsRepository {\n private readonly writeAtomic: (path: string, contents: string) => void;\n\n constructor(\n readonly path: string,\n options: SettingsRepositoryOptions = {},\n ) {\n this.writeAtomic = options.writeAtomic ?? writeFileAtomic;\n }\n\n read(): SettingsDocumentReadResult {\n return readSettingsDocument(this.path);\n }\n\n set(values: Record<string, unknown>): SettingsMutationResult {\n return this.mutate((candidate) => {\n for (const [key, value] of Object.entries(values)) setNestedValue(candidate, key, value);\n }, values);\n }\n\n remove(paths: string[]): SettingsMutationResult {\n return this.mutate((candidate) => {\n for (const path of paths) deleteNestedValue(candidate, path.split('.'));\n }, {});\n }\n\n update(\n mutation: (candidate: Record<string, unknown>) => void,\n displayValues: Record<string, unknown> = {},\n ): SettingsMutationResult {\n return this.mutate(mutation, displayValues);\n }\n\n private mutate(\n mutation: (candidate: Record<string, unknown>) => void,\n values: Record<string, unknown>,\n ): SettingsMutationResult {\n const refusal = projectFileWriteRefusal(this.path);\n if (refusal) {\n return { ok: false, path: this.path, diagnostics: [{ path: this.path, message: refusal }] };\n }\n let lock: number | undefined;\n try {\n lock = this.acquireLock();\n return this.mutateLocked(mutation, values);\n } catch (error) {\n return {\n ok: false,\n path: this.path,\n diagnostics: [{ path: this.path, message: safeMessage(error) }],\n };\n } finally {\n if (lock !== undefined) this.releaseLock(lock);\n }\n }\n\n private acquireLock(): number {\n mkdirSync(dirname(this.path), { recursive: true });\n const lockPath = `${this.path}.lock`;\n for (let attempt = 0; attempt < 50; attempt++) {\n try {\n return openSync(lockPath, 'wx', 0o600);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;\n try {\n if (Date.now() - statSync(lockPath).mtimeMs > 10_000) unlinkSync(lockPath);\n } catch {\n // Another process may have released or replaced the lock.\n }\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);\n }\n }\n throw new Error(`Timed out waiting for settings lock: ${lockPath}`);\n }\n\n private releaseLock(descriptor: number): void {\n closeSync(descriptor);\n try {\n unlinkSync(`${this.path}.lock`);\n } catch {\n // A stale-lock recovery may already have removed it.\n }\n }\n\n private mutateLocked(\n mutation: (candidate: Record<string, unknown>) => void,\n values: Record<string, unknown>,\n ): SettingsMutationResult {\n const source = this.read();\n if (source.status === 'malformed' || source.status === 'non-object') {\n return {\n ok: false,\n path: this.path,\n diagnostics: [{ path: this.path, message: source.error }],\n };\n }\n\n const candidate = structuredClone(source.status === 'valid' ? source.document : {});\n try {\n mutation(candidate);\n } catch (error) {\n return {\n ok: false,\n path: this.path,\n diagnostics: [{ path: this.path, message: safeMessage(error) }],\n };\n }\n\n const keyDiagnostics = validateTopLevelKeys(this.path, candidate);\n const validation = bookSettingsSchema.safeParse(candidate);\n const diagnostics = [\n ...keyDiagnostics,\n ...(validation.success\n ? []\n : validation.error.issues.map((issue) => issueDiagnostic(this.path, issue))),\n ];\n if (diagnostics.length > 0) return { ok: false, path: this.path, diagnostics };\n\n try {\n const changed =\n source.status !== 'valid' || JSON.stringify(source.document) !== JSON.stringify(candidate);\n if (changed) this.writeAtomic(this.path, JSON.stringify(candidate, null, 2) + '\\n');\n return {\n ok: true,\n path: this.path,\n document: candidate,\n changed,\n values: Object.fromEntries(\n Object.entries(values).map(([key, value]) => [key, redactSettingValue(key, value)]),\n ),\n };\n } catch (error) {\n return {\n ok: false,\n path: this.path,\n diagnostics: [{ path: this.path, message: safeMessage(error) }],\n };\n }\n }\n}\n\nexport function formatSettingsDiagnostics(diagnostics: SettingsDiagnostic[]): string {\n return diagnostics\n .map((diagnostic) => {\n const location = diagnostic.issuePath\n ? `${diagnostic.path} (${diagnostic.issuePath})`\n : diagnostic.path;\n return `${location}: ${diagnostic.message}`;\n })\n .join('\\n');\n}\n","const REDACTED_SECRET = '*** (stored)';\n\nfunction redactProviderRecord(value: unknown): unknown {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return value;\n const clone = structuredClone(value) as Record<string, unknown>;\n if ('apiKey' in clone) clone.apiKey = REDACTED_SECRET;\n return clone;\n}\n\nfunction redactProviderMap(value: unknown): unknown {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return value;\n const clone = structuredClone(value) as Record<string, unknown>;\n for (const [id, provider] of Object.entries(clone)) clone[id] = redactProviderRecord(provider);\n return clone;\n}\n\n/**\n * Removed blocks that may still carry a credential in a real settings file.\n *\n * Subscription authentication is gone, but `auth.profiles.<id>.clientSecret`\n * and the free-form `headers` that rode along with the bearer token are still\n * on disk wherever it was configured — and `book config list` prints the *raw*\n * document, not the validated one, so those keys survive into terminal\n * scrollback and pasted bug reports. Validation discarding a block is not the\n * same as a display path being safe, so the whole subtree is masked rather than\n * its individual leaves: nothing reads it any more, so there is nothing in it\n * worth showing.\n */\nconst REMOVED_SECRET_BEARING_BLOCKS = ['auth'];\n\nexport function redactSettingValue(path: string, value: unknown): unknown {\n const parts = path.split('.');\n if (parts[0] && REMOVED_SECRET_BEARING_BLOCKS.includes(parts[0])) return REDACTED_SECRET;\n if (parts[0] !== 'provider') return value;\n if (parts.length === 1) return redactProviderMap(value);\n if (parts.length === 2) return redactProviderRecord(value);\n return parts[parts.length - 1] === 'apiKey' ? REDACTED_SECRET : value;\n}\n\nexport function redactSettingsForDisplay<T>(value: T): T {\n if (!value || typeof value !== 'object') return value;\n const clone = structuredClone(value) as Record<string, unknown>;\n clone.provider = redactProviderMap(clone.provider);\n for (const key of REMOVED_SECRET_BEARING_BLOCKS) {\n if (clone[key] !== undefined) clone[key] = REDACTED_SECRET;\n }\n return clone as T;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,114 @@
1
+ import {
2
+ isOutside
3
+ } from "./chunk-YIL6HTJX.js";
4
+ import {
5
+ reachesUncPath
6
+ } from "./chunk-EEX6QNXV.js";
7
+ import {
8
+ renderUntrustedText
9
+ } from "./chunk-WV3O5PGX.js";
10
+
11
+ // src/repo-containment.ts
12
+ import { existsSync, realpathSync } from "fs";
13
+ import { homedir } from "os";
14
+ import { basename, dirname, join, parse, relative, resolve } from "path";
15
+ function tryRealPath(path) {
16
+ if (reachesUncPath(path)) return void 0;
17
+ try {
18
+ return realpathSync.native(path);
19
+ } catch {
20
+ }
21
+ try {
22
+ return realpathSync(path);
23
+ } catch {
24
+ return void 0;
25
+ }
26
+ }
27
+ function ancestorsFromRoot(workspace) {
28
+ const dirs = [];
29
+ const root = parse(resolve(workspace)).root;
30
+ let current = resolve(workspace);
31
+ while (true) {
32
+ dirs.push(current);
33
+ if (current === root) break;
34
+ current = dirname(current);
35
+ }
36
+ return dirs.reverse();
37
+ }
38
+ function findRepositoryRoot(dir, home) {
39
+ if (reachesUncPath(dir)) return void 0;
40
+ for (const candidate of ancestorsFromRoot(tryRealPath(dir) ?? resolve(dir)).reverse()) {
41
+ if (existsSync(join(candidate, ".git"))) {
42
+ return isUserHome(candidate, home) ? void 0 : candidate;
43
+ }
44
+ }
45
+ return void 0;
46
+ }
47
+ function userHomeReal(home) {
48
+ let path = home;
49
+ if (path === void 0) {
50
+ try {
51
+ path = homedir();
52
+ } catch {
53
+ return void 0;
54
+ }
55
+ }
56
+ return path ? tryRealPath(path) : void 0;
57
+ }
58
+ function samePath(left, right) {
59
+ return relative(left, right) === "";
60
+ }
61
+ function isUserHome(real, home) {
62
+ const homeReal = userHomeReal(home);
63
+ return homeReal !== void 0 && samePath(homeReal, real);
64
+ }
65
+ function ignoresHomeRepository(top, workspace, home) {
66
+ return ignoresRealHomeRepository(
67
+ tryRealPath(top) ?? resolve(top),
68
+ tryRealPath(workspace) ?? resolve(workspace),
69
+ home
70
+ );
71
+ }
72
+ function ignoresRealHomeRepository(topReal, workspaceReal, home) {
73
+ const homeReal = userHomeReal(home);
74
+ return homeReal !== void 0 && samePath(homeReal, topReal) && !samePath(homeReal, workspaceReal);
75
+ }
76
+ function isInGitRepository(workspace, exists = existsSync, home) {
77
+ const start = tryRealPath(workspace) ?? resolve(workspace);
78
+ for (const dir of ancestorsFromRoot(start).reverse()) {
79
+ if (exists(join(dir, ".git"))) return !ignoresRealHomeRepository(dir, start, home);
80
+ }
81
+ return false;
82
+ }
83
+ function homeGitCeiling(home) {
84
+ const homeReal = userHomeReal(home);
85
+ return homeReal !== void 0 && existsSync(join(homeReal, ".git")) ? homeReal : void 0;
86
+ }
87
+ function projectContainmentRoot(workspace, home) {
88
+ return findRepositoryRoot(workspace, home) ?? resolve(workspace);
89
+ }
90
+ function refusalFor(rootReal, fileReal) {
91
+ if (isOutside(rootReal, fileReal)) return "outside";
92
+ if (!basename(fileReal).toLowerCase().endsWith(".md")) return "not-markdown";
93
+ return void 0;
94
+ }
95
+ function formatSkippedNotice(kind, workspace, entry) {
96
+ const workspaceRoot = resolve(workspace);
97
+ const shown = !isOutside(workspaceRoot, entry.path) ? relative(workspaceRoot, entry.path) : entry.path;
98
+ const head = `\u26A0 Skipped ${kind} ${renderUntrustedText(shown, 400)}: it links to ` + renderUntrustedText(entry.target, 400);
99
+ return entry.reason === "not-markdown" ? `${head}, which is not a Markdown (.md) file.` : `${head}, outside ${renderUntrustedText(entry.root, 400)}.`;
100
+ }
101
+
102
+ export {
103
+ tryRealPath,
104
+ ancestorsFromRoot,
105
+ findRepositoryRoot,
106
+ isUserHome,
107
+ ignoresHomeRepository,
108
+ isInGitRepository,
109
+ homeGitCeiling,
110
+ projectContainmentRoot,
111
+ refusalFor,
112
+ formatSkippedNotice
113
+ };
114
+ //# sourceMappingURL=chunk-SBTU5BEC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/repo-containment.ts"],"sourcesContent":["import { existsSync, realpathSync } from 'fs';\nimport { homedir } from 'os';\nimport { basename, dirname, join, parse, relative, resolve } from 'path';\nimport { renderUntrustedText } from './hook-approvals.js';\nimport { isOutside } from './tools/path-utils.js';\nimport { reachesUncPath } from './unc-path.js';\n\n/**\n * Repository containment for the project files a repository can commit as links (#539, #543):\n * instruction files, REVIEW.md and project command files. A repository can commit any of them as\n * a link to `~/.ssh/id_rsa`, and the target would be sent to the provider as prompt text. Each\n * loader checks the real path of what it found against the real path of the repository root, and\n * reports what it refused through `formatSkippedNotice`. An `@path` mention follows Read's\n * boundary instead (`src/input/input-expansion.ts`, #567). This module imports no loader.\n */\n\n/** Why a project file must not load: its real path left the root, or it is not Markdown. */\nexport type RefusalReason = 'outside' | 'not-markdown';\n\n/** A project file that was not loaded. */\nexport interface SkippedProjectFile {\n /** Where the file was found. */\n path: string;\n /** Its real path. */\n target: string;\n /** The real path of the directory it had to stay inside. */\n root: string;\n reason: RefusalReason;\n}\n\n/**\n * The native real path, else the JS one (`realpathSync.native` throws on some Windows volumes\n * where the JS version works), else `undefined`. Also `undefined`, before anything is resolved,\n * for a Windows path that is, or reaches through a link, a UNC location (`reachesUncPath`), so no\n * containment check sends a request to a host a repository named (#567).\n */\nexport function tryRealPath(path: string): string | undefined {\n if (reachesUncPath(path)) return undefined;\n try {\n return realpathSync.native(path);\n } catch {\n // Fall through to the JS implementation.\n }\n try {\n return realpathSync(path);\n } catch {\n return undefined;\n }\n}\n\n/** `resolve(workspace)` and each directory above it, from the filesystem root down. */\nexport function ancestorsFromRoot(workspace: string): string[] {\n const dirs: string[] = [];\n const root = parse(resolve(workspace)).root;\n let current = resolve(workspace);\n\n while (true) {\n dirs.push(current);\n if (current === root) break;\n current = dirname(current);\n }\n\n return dirs.reverse();\n}\n\n/**\n * The git repository root, as a real path: the nearest directory, walking up the real path of\n * `dir`, with a `.git` entry (a directory, or a file for a linked worktree or a submodule). Like\n * `git rev-parse --show-toplevel`, which starts from the real working directory, so the root\n * always contains the real path of `dir`, and a workspace opened through a link finds the\n * repository it is really in. A linked worktree is its own root, not its main checkout (which\n * `findGitRoot` in `memory-store.ts` maps it to, on purpose, for memory keying). Only stats\n * files; git is never spawned.\n *\n * A `.git` at the real path of `home` (the user's home directory by default, read on every call)\n * is a dotfiles repository, not a project: it is no repository (`undefined`, and the walk stops),\n * so a project under the home directory without its own `.git` is measured as one in no\n * repository, not from the whole home directory. Unlike the git consumers'\n * {@link ignoresHomeRepository}, this holds for `dir` being the home directory too: a file found\n * in the home directory while the workspace is in a repository of its own is the user's, not\n * checked against the home directory (`claude-md.ts`).\n */\nexport function findRepositoryRoot(dir: string, home?: string): string | undefined {\n // Its ancestors are probed for `.git` below, which would reach the share as well.\n if (reachesUncPath(dir)) return undefined;\n for (const candidate of ancestorsFromRoot(tryRealPath(dir) ?? resolve(dir)).reverse()) {\n if (existsSync(join(candidate, '.git'))) {\n return isUserHome(candidate, home) ? undefined : candidate;\n }\n }\n return undefined;\n}\n\n/**\n * The real path of `home`, the user's home directory when not given (read on every call, from\n * `USERPROFILE` on Windows), or `undefined` when there is none or it does not resolve.\n */\nfunction userHomeReal(home: string | undefined): string | undefined {\n let path = home;\n if (path === undefined) {\n try {\n path = homedir();\n } catch {\n return undefined;\n }\n }\n return path ? tryRealPath(path) : undefined;\n}\n\n/** Whether two paths name the same directory, ignoring case where the platform does (Windows). */\nfunction samePath(left: string, right: string): boolean {\n return relative(left, right) === '';\n}\n\n/** Whether the real path `real` is that of `home`, the user's home directory when not given. */\nexport function isUserHome(real: string, home: string | undefined): boolean {\n const homeReal = userHomeReal(home);\n return homeReal !== undefined && samePath(homeReal, real);\n}\n\n/**\n * The home-directory rule for the repository a workspace is in (#567, #579): whether the\n * repository whose top directory git or a `.git` walk found at `top`, starting from `workspace`,\n * is a dotfiles repository to ignore. It is when `top` is the user's home directory (`home` when\n * given) and `workspace` is not, which is a project below the home directory with no `.git` of its\n * own (the walk would have stopped there). A workspace that is the home directory itself is in\n * that repository. Both paths are compared as real paths, as `isUserHome` does.\n *\n * This is the rule git applies to Book's own calls through {@link homeGitCeiling}, for the\n * callers that decide without git: the prompt's git line and the TUI status poll\n * ({@link isInGitRepository}), and managed-agent isolation. Containment (`findRepositoryRoot`)\n * asks about every directory a file was found in, the home directory among them, so it keeps its\n * own rule: a `.git` at the home directory is never a repository there.\n */\nexport function ignoresHomeRepository(top: string, workspace: string, home?: string): boolean {\n return ignoresRealHomeRepository(\n tryRealPath(top) ?? resolve(top),\n tryRealPath(workspace) ?? resolve(workspace),\n home,\n );\n}\n\n/** {@link ignoresHomeRepository} for paths that are already real. */\nfunction ignoresRealHomeRepository(topReal: string, workspaceReal: string, home?: string): boolean {\n const homeReal = userHomeReal(home);\n return (\n homeReal !== undefined && samePath(homeReal, topReal) && !samePath(homeReal, workspaceReal)\n );\n}\n\n/**\n * Whether `workspace` is in a git repository, for the callers that decide without spawning git:\n * the prompt's git line (`context.ts`) and the TUI status poll (`useGitStatus.ts`). The nearest\n * directory with a `.git` entry (a directory, or a file for a linked worktree or a submodule),\n * walking up the real path of `workspace`, as git does from the working directory it is started\n * in, so a workspace opened through a link is answered for the repository it is really in; under\n * the home-directory rule ({@link ignoresHomeRepository}), which Book's git applies too\n * ({@link homeGitCeiling}). Only stats files. `exists` is the probe, replaceable in tests.\n */\nexport function isInGitRepository(\n workspace: string,\n exists: (path: string) => boolean = existsSync,\n home?: string,\n): boolean {\n const start = tryRealPath(workspace) ?? resolve(workspace);\n for (const dir of ancestorsFromRoot(start).reverse()) {\n // An ancestor of a real path is a real path, so it is compared as it is.\n if (exists(join(dir, '.git'))) return !ignoresRealHomeRepository(dir, start, home);\n }\n return false;\n}\n\n/**\n * The `GIT_CEILING_DIRECTORIES` entry Book's own git calls carry (`hardenedGitEnv`): the real path\n * of the user's home directory (`home` when given) when it holds a `.git`, else `undefined`, so a\n * repository above a home directory without one is still found. Git never stops at the directory\n * it starts in, so a workspace that is the home directory still finds its repository, while a\n * project below it with no `.git` of its own finds none: {@link ignoresHomeRepository}, applied\n * by git. A real path, so the entry matches whether or not the list starts with the empty entry\n * that turns off git's own resolution of the entries after it.\n */\nexport function homeGitCeiling(home?: string): string | undefined {\n const homeReal = userHomeReal(home);\n return homeReal !== undefined && existsSync(join(homeReal, '.git')) ? homeReal : undefined;\n}\n\n/** The directory project files are measured from: the repository root, else the workspace. */\nexport function projectContainmentRoot(workspace: string, home?: string): string {\n return findRepositoryRoot(workspace, home) ?? resolve(workspace);\n}\n\n/**\n * Why a project file must not load: its real path leaves `rootReal`, or it is not Markdown. The\n * second check is what stops a link to a secret inside the repository (`CLAUDE.md -> .env`,\n * `-> .book/settings.local.json`); a regular `CLAUDE.md`, `REVIEW.md` or command file always\n * passes it.\n */\nexport function refusalFor(rootReal: string, fileReal: string): RefusalReason | undefined {\n if (isOutside(rootReal, fileReal)) return 'outside';\n if (!basename(fileReal).toLowerCase().endsWith('.md')) return 'not-markdown';\n return undefined;\n}\n\n/**\n * The one-line start-up warning for a skipped file, `kind` being what it is (\"project\n * instructions\", \"project command\", \"review instructions\"). The path is shown relative to the\n * workspace when it is inside it. The target and the names along the way are\n * repository-authored: no control characters.\n */\nexport function formatSkippedNotice(\n kind: string,\n workspace: string,\n entry: SkippedProjectFile,\n): string {\n const workspaceRoot = resolve(workspace);\n const shown = !isOutside(workspaceRoot, entry.path)\n ? relative(workspaceRoot, entry.path)\n : entry.path;\n const head =\n `⚠ Skipped ${kind} ${renderUntrustedText(shown, 400)}: it links to ` +\n renderUntrustedText(entry.target, 400);\n return entry.reason === 'not-markdown'\n ? `${head}, which is not a Markdown (.md) file.`\n : `${head}, outside ${renderUntrustedText(entry.root, 400)}.`;\n}\n"],"mappings":"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","names":[]}
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-2AAOF5B5.js";
4
4
  import {
5
5
  updateWorkspaceTrust
6
- } from "./chunk-VPNLCMVR.js";
6
+ } from "./chunk-HFB74CLM.js";
7
7
 
8
8
  // src/command-approvals.ts
9
9
  import { createHash } from "crypto";
@@ -166,4 +166,4 @@ export {
166
166
  assertProjectCommandApproved,
167
167
  persistProjectCommandChoice
168
168
  };
169
- //# sourceMappingURL=chunk-HQI7NYGX.js.map
169
+ //# sourceMappingURL=chunk-SWPDKLNM.js.map