token-goat 2.1.0 → 2.2.1

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.
@@ -981,8 +981,8 @@ var require_command = __commonJS({
981
981
  init_define_import_meta_env();
982
982
  var EventEmitter = __require("node:events").EventEmitter;
983
983
  var childProcess = __require("node:child_process");
984
- var path15 = __require("node:path");
985
- var fs15 = __require("node:fs");
984
+ var path17 = __require("node:path");
985
+ var fs16 = __require("node:fs");
986
986
  var process2 = __require("node:process");
987
987
  var { Argument: Argument2, humanReadableArgName } = require_argument();
988
988
  var { CommanderError: CommanderError2 } = require_error();
@@ -1914,11 +1914,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
1914
1914
  let launchWithNode = false;
1915
1915
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
1916
1916
  function findFile(baseDir, baseName) {
1917
- const localBin = path15.resolve(baseDir, baseName);
1918
- if (fs15.existsSync(localBin)) return localBin;
1919
- if (sourceExt.includes(path15.extname(baseName))) return void 0;
1917
+ const localBin = path17.resolve(baseDir, baseName);
1918
+ if (fs16.existsSync(localBin)) return localBin;
1919
+ if (sourceExt.includes(path17.extname(baseName))) return void 0;
1920
1920
  const foundExt = sourceExt.find(
1921
- (ext) => fs15.existsSync(`${localBin}${ext}`)
1921
+ (ext) => fs16.existsSync(`${localBin}${ext}`)
1922
1922
  );
1923
1923
  if (foundExt) return `${localBin}${foundExt}`;
1924
1924
  return void 0;
@@ -1930,21 +1930,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
1930
1930
  if (this._scriptPath) {
1931
1931
  let resolvedScriptPath;
1932
1932
  try {
1933
- resolvedScriptPath = fs15.realpathSync(this._scriptPath);
1933
+ resolvedScriptPath = fs16.realpathSync(this._scriptPath);
1934
1934
  } catch (err2) {
1935
1935
  resolvedScriptPath = this._scriptPath;
1936
1936
  }
1937
- executableDir = path15.resolve(
1938
- path15.dirname(resolvedScriptPath),
1937
+ executableDir = path17.resolve(
1938
+ path17.dirname(resolvedScriptPath),
1939
1939
  executableDir
1940
1940
  );
1941
1941
  }
1942
1942
  if (executableDir) {
1943
1943
  let localFile = findFile(executableDir, executableFile);
1944
1944
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
1945
- const legacyName = path15.basename(
1945
+ const legacyName = path17.basename(
1946
1946
  this._scriptPath,
1947
- path15.extname(this._scriptPath)
1947
+ path17.extname(this._scriptPath)
1948
1948
  );
1949
1949
  if (legacyName !== this._name) {
1950
1950
  localFile = findFile(
@@ -1955,7 +1955,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1955
1955
  }
1956
1956
  executableFile = localFile || executableFile;
1957
1957
  }
1958
- launchWithNode = sourceExt.includes(path15.extname(executableFile));
1958
+ launchWithNode = sourceExt.includes(path17.extname(executableFile));
1959
1959
  let proc;
1960
1960
  if (process2.platform !== "win32") {
1961
1961
  if (launchWithNode) {
@@ -2795,7 +2795,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2795
2795
  * @return {Command}
2796
2796
  */
2797
2797
  nameFromFilename(filename) {
2798
- this._name = path15.basename(filename, path15.extname(filename));
2798
+ this._name = path17.basename(filename, path17.extname(filename));
2799
2799
  return this;
2800
2800
  }
2801
2801
  /**
@@ -2809,9 +2809,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
2809
2809
  * @param {string} [path]
2810
2810
  * @return {(string|null|Command)}
2811
2811
  */
2812
- executableDir(path16) {
2813
- if (path16 === void 0) return this._executableDir;
2814
- this._executableDir = path16;
2812
+ executableDir(path18) {
2813
+ if (path18 === void 0) return this._executableDir;
2814
+ this._executableDir = path18;
2815
2815
  return this;
2816
2816
  }
2817
2817
  /**
@@ -3158,27 +3158,27 @@ var require_filesystem = __commonJS({
3158
3158
  "node_modules/detect-libc/lib/filesystem.js"(exports, module) {
3159
3159
  "use strict";
3160
3160
  init_define_import_meta_env();
3161
- var fs15 = __require("fs");
3161
+ var fs16 = __require("fs");
3162
3162
  var LDD_PATH = "/usr/bin/ldd";
3163
3163
  var SELF_PATH = "/proc/self/exe";
3164
3164
  var MAX_LENGTH = 2048;
3165
- var readFileSync11 = (path15) => {
3166
- const fd = fs15.openSync(path15, "r");
3165
+ var readFileSync12 = (path17) => {
3166
+ const fd = fs16.openSync(path17, "r");
3167
3167
  const buffer = Buffer.alloc(MAX_LENGTH);
3168
- const bytesRead = fs15.readSync(fd, buffer, 0, MAX_LENGTH, 0);
3169
- fs15.close(fd, () => {
3168
+ const bytesRead = fs16.readSync(fd, buffer, 0, MAX_LENGTH, 0);
3169
+ fs16.close(fd, () => {
3170
3170
  });
3171
3171
  return buffer.subarray(0, bytesRead);
3172
3172
  };
3173
- var readFile3 = (path15) => new Promise((resolve6, reject) => {
3174
- fs15.open(path15, "r", (err2, fd) => {
3173
+ var readFile3 = (path17) => new Promise((resolve6, reject) => {
3174
+ fs16.open(path17, "r", (err2, fd) => {
3175
3175
  if (err2) {
3176
3176
  reject(err2);
3177
3177
  } else {
3178
3178
  const buffer = Buffer.alloc(MAX_LENGTH);
3179
- fs15.read(fd, buffer, 0, MAX_LENGTH, 0, (_, bytesRead) => {
3179
+ fs16.read(fd, buffer, 0, MAX_LENGTH, 0, (_, bytesRead) => {
3180
3180
  resolve6(buffer.subarray(0, bytesRead));
3181
- fs15.close(fd, () => {
3181
+ fs16.close(fd, () => {
3182
3182
  });
3183
3183
  });
3184
3184
  }
@@ -3187,7 +3187,7 @@ var require_filesystem = __commonJS({
3187
3187
  module.exports = {
3188
3188
  LDD_PATH,
3189
3189
  SELF_PATH,
3190
- readFileSync: readFileSync11,
3190
+ readFileSync: readFileSync12,
3191
3191
  readFile: readFile3
3192
3192
  };
3193
3193
  }
@@ -3238,7 +3238,7 @@ var require_detect_libc = __commonJS({
3238
3238
  init_define_import_meta_env();
3239
3239
  var childProcess = __require("child_process");
3240
3240
  var { isLinux, getReport } = require_process();
3241
- var { LDD_PATH, SELF_PATH, readFile: readFile3, readFileSync: readFileSync11 } = require_filesystem();
3241
+ var { LDD_PATH, SELF_PATH, readFile: readFile3, readFileSync: readFileSync12 } = require_filesystem();
3242
3242
  var { interpreterPath } = require_elf();
3243
3243
  var cachedFamilyInterpreter;
3244
3244
  var cachedFamilyFilesystem;
@@ -3292,11 +3292,11 @@ var require_detect_libc = __commonJS({
3292
3292
  }
3293
3293
  return null;
3294
3294
  };
3295
- var familyFromInterpreterPath = (path15) => {
3296
- if (path15) {
3297
- if (path15.includes("/ld-musl-")) {
3295
+ var familyFromInterpreterPath = (path17) => {
3296
+ if (path17) {
3297
+ if (path17.includes("/ld-musl-")) {
3298
3298
  return MUSL;
3299
- } else if (path15.includes("/ld-linux-")) {
3299
+ } else if (path17.includes("/ld-linux-")) {
3300
3300
  return GLIBC;
3301
3301
  }
3302
3302
  }
@@ -3330,7 +3330,7 @@ var require_detect_libc = __commonJS({
3330
3330
  }
3331
3331
  cachedFamilyFilesystem = null;
3332
3332
  try {
3333
- const lddContent = readFileSync11(LDD_PATH);
3333
+ const lddContent = readFileSync12(LDD_PATH);
3334
3334
  cachedFamilyFilesystem = getFamilyFromLddContent(lddContent);
3335
3335
  } catch (e) {
3336
3336
  }
@@ -3343,8 +3343,8 @@ var require_detect_libc = __commonJS({
3343
3343
  cachedFamilyInterpreter = null;
3344
3344
  try {
3345
3345
  const selfContent = await readFile3(SELF_PATH);
3346
- const path15 = interpreterPath(selfContent);
3347
- cachedFamilyInterpreter = familyFromInterpreterPath(path15);
3346
+ const path17 = interpreterPath(selfContent);
3347
+ cachedFamilyInterpreter = familyFromInterpreterPath(path17);
3348
3348
  } catch (e) {
3349
3349
  }
3350
3350
  return cachedFamilyInterpreter;
@@ -3355,9 +3355,9 @@ var require_detect_libc = __commonJS({
3355
3355
  }
3356
3356
  cachedFamilyInterpreter = null;
3357
3357
  try {
3358
- const selfContent = readFileSync11(SELF_PATH);
3359
- const path15 = interpreterPath(selfContent);
3360
- cachedFamilyInterpreter = familyFromInterpreterPath(path15);
3358
+ const selfContent = readFileSync12(SELF_PATH);
3359
+ const path17 = interpreterPath(selfContent);
3360
+ cachedFamilyInterpreter = familyFromInterpreterPath(path17);
3361
3361
  } catch (e) {
3362
3362
  }
3363
3363
  return cachedFamilyInterpreter;
@@ -3419,7 +3419,7 @@ var require_detect_libc = __commonJS({
3419
3419
  }
3420
3420
  cachedVersionFilesystem = null;
3421
3421
  try {
3422
- const lddContent = readFileSync11(LDD_PATH);
3422
+ const lddContent = readFileSync12(LDD_PATH);
3423
3423
  const versionMatch = lddContent.match(RE_GLIBC_VERSION);
3424
3424
  if (versionMatch) {
3425
3425
  cachedVersionFilesystem = versionMatch[1];
@@ -4947,8 +4947,8 @@ var require_libvips = __commonJS({
4947
4947
  "node_modules/sharp/lib/libvips.js"(exports, module) {
4948
4948
  "use strict";
4949
4949
  init_define_import_meta_env();
4950
- var { spawnSync: spawnSync2 } = __require("node:child_process");
4951
- var { createHash: createHash2 } = __require("node:crypto");
4950
+ var { spawnSync: spawnSync3 } = __require("node:child_process");
4951
+ var { createHash: createHash3 } = __require("node:crypto");
4952
4952
  var semverCoerce = require_coerce();
4953
4953
  var semverGreaterThanOrEqualTo = require_gte();
4954
4954
  var semverSatisfies = require_satisfies();
@@ -5032,12 +5032,12 @@ var require_libvips = __commonJS({
5032
5032
  };
5033
5033
  var isRosetta = () => {
5034
5034
  if (process.platform === "darwin" && process.arch === "x64") {
5035
- const translated = spawnSync2("sysctl sysctl.proc_translated", spawnSyncOptions).stdout;
5035
+ const translated = spawnSync3("sysctl sysctl.proc_translated", spawnSyncOptions).stdout;
5036
5036
  return (translated || "").trim() === "sysctl.proc_translated: 1";
5037
5037
  }
5038
5038
  return false;
5039
5039
  };
5040
- var sha512 = (s) => createHash2("sha512").update(s).digest("hex");
5040
+ var sha512 = (s) => createHash3("sha512").update(s).digest("hex");
5041
5041
  var yarnLocator = () => {
5042
5042
  try {
5043
5043
  const identHash = sha512(`imgsharp-libvips-${buildPlatformArch()}`);
@@ -5047,13 +5047,13 @@ var require_libvips = __commonJS({
5047
5047
  }
5048
5048
  return "";
5049
5049
  };
5050
- var spawnRebuild = () => spawnSync2(`node-gyp rebuild --directory=src ${isEmscripten() ? "--nodedir=emscripten" : ""}`, {
5050
+ var spawnRebuild = () => spawnSync3(`node-gyp rebuild --directory=src ${isEmscripten() ? "--nodedir=emscripten" : ""}`, {
5051
5051
  ...spawnSyncOptions,
5052
5052
  stdio: "inherit"
5053
5053
  }).status;
5054
5054
  var globalLibvipsVersion = () => {
5055
5055
  if (process.platform !== "win32") {
5056
- const globalLibvipsVersion2 = spawnSync2("pkg-config --modversion vips-cpp", {
5056
+ const globalLibvipsVersion2 = spawnSync3("pkg-config --modversion vips-cpp", {
5057
5057
  ...spawnSyncOptions,
5058
5058
  env: {
5059
5059
  ...process.env,
@@ -5067,7 +5067,7 @@ var require_libvips = __commonJS({
5067
5067
  };
5068
5068
  var pkgConfigPath = () => {
5069
5069
  if (process.platform !== "win32") {
5070
- const brewPkgConfigPath = spawnSync2(
5070
+ const brewPkgConfigPath = spawnSync3(
5071
5071
  'which brew >/dev/null 2>&1 && brew environment --plain | grep PKG_CONFIG_LIBDIR | cut -d" " -f2',
5072
5072
  spawnSyncOptions
5073
5073
  ).stdout || "";
@@ -5138,9 +5138,9 @@ var require_sharp = __commonJS({
5138
5138
  ];
5139
5139
  var sharp;
5140
5140
  var errors = [];
5141
- for (const path15 of paths) {
5141
+ for (const path17 of paths) {
5142
5142
  try {
5143
- sharp = __require(path15);
5143
+ sharp = __require(path17);
5144
5144
  break;
5145
5145
  } catch (err2) {
5146
5146
  errors.push(err2);
@@ -5149,7 +5149,7 @@ var require_sharp = __commonJS({
5149
5149
  if (sharp) {
5150
5150
  module.exports = sharp;
5151
5151
  } else {
5152
- const [isLinux, isMacOs, isWindows] = ["linux", "darwin", "win32"].map((os3) => runtimePlatform.startsWith(os3));
5152
+ const [isLinux, isMacOs, isWindows2] = ["linux", "darwin", "win32"].map((os4) => runtimePlatform.startsWith(os4));
5153
5153
  const help = [`Could not load the "sharp" module using the ${runtimePlatform} runtime`];
5154
5154
  errors.forEach((err2) => {
5155
5155
  if (err2.code !== "MODULE_NOT_FOUND") {
@@ -5166,15 +5166,15 @@ var require_sharp = __commonJS({
5166
5166
  ` Requires ${expected}`
5167
5167
  );
5168
5168
  } else if (prebuiltPlatforms.includes(runtimePlatform)) {
5169
- const [os3, cpu] = runtimePlatform.split("-");
5170
- const libc = os3.endsWith("musl") ? " --libc=musl" : "";
5169
+ const [os4, cpu] = runtimePlatform.split("-");
5170
+ const libc = os4.endsWith("musl") ? " --libc=musl" : "";
5171
5171
  help.push(
5172
5172
  "- Ensure optional dependencies can be installed:",
5173
5173
  " npm install --include=optional sharp",
5174
5174
  "- Ensure your package manager supports multi-platform installation:",
5175
5175
  " See https://sharp.pixelplumbing.com/install#cross-platform",
5176
5176
  "- Add platform-specific dependencies:",
5177
- ` npm install --os=${os3.replace("musl", "")}${libc} --cpu=${cpu} sharp`
5177
+ ` npm install --os=${os4.replace("musl", "")}${libc} --cpu=${cpu} sharp`
5178
5178
  );
5179
5179
  } else {
5180
5180
  help.push(
@@ -5212,7 +5212,7 @@ var require_sharp = __commonJS({
5212
5212
  if (errors.some((err2) => err2.code === "ERR_DLOPEN_DISABLED")) {
5213
5213
  help.push("- Run Node.js without using the --no-addons flag");
5214
5214
  }
5215
- if (isWindows && /The specified procedure could not be found/.test(messages)) {
5215
+ if (isWindows2 && /The specified procedure could not be found/.test(messages)) {
5216
5216
  help.push(
5217
5217
  "- Using the canvas package on Windows?",
5218
5218
  " See https://sharp.pixelplumbing.com/install#canvas-and-windows",
@@ -6552,15 +6552,15 @@ var require_route = __commonJS({
6552
6552
  };
6553
6553
  }
6554
6554
  function wrapConversion(toModel, graph) {
6555
- const path15 = [graph[toModel].parent, toModel];
6555
+ const path17 = [graph[toModel].parent, toModel];
6556
6556
  let fn = conversions[graph[toModel].parent][toModel];
6557
6557
  let cur = graph[toModel].parent;
6558
6558
  while (graph[cur].parent) {
6559
- path15.unshift(graph[cur].parent);
6559
+ path17.unshift(graph[cur].parent);
6560
6560
  fn = link(conversions[graph[cur].parent][cur], fn);
6561
6561
  cur = graph[cur].parent;
6562
6562
  }
6563
- fn.conversion = path15;
6563
+ fn.conversion = path17;
6564
6564
  return fn;
6565
6565
  }
6566
6566
  module.exports = function(fromModel) {
@@ -8464,7 +8464,7 @@ var require_output = __commonJS({
8464
8464
  "node_modules/sharp/lib/output.js"(exports, module) {
8465
8465
  "use strict";
8466
8466
  init_define_import_meta_env();
8467
- var path15 = __require("node:path");
8467
+ var path17 = __require("node:path");
8468
8468
  var is = require_is();
8469
8469
  var sharp = require_sharp();
8470
8470
  var formats = /* @__PURE__ */ new Map([
@@ -8495,9 +8495,9 @@ var require_output = __commonJS({
8495
8495
  let err2;
8496
8496
  if (!is.string(fileOut)) {
8497
8497
  err2 = new Error("Missing output file path");
8498
- } else if (is.string(this.options.input.file) && path15.resolve(this.options.input.file) === path15.resolve(fileOut)) {
8498
+ } else if (is.string(this.options.input.file) && path17.resolve(this.options.input.file) === path17.resolve(fileOut)) {
8499
8499
  err2 = new Error("Cannot use same file for input and output");
8500
- } else if (jp2Regex.test(path15.extname(fileOut)) && !this.constructor.format.jp2k.output.file) {
8500
+ } else if (jp2Regex.test(path17.extname(fileOut)) && !this.constructor.format.jp2k.output.file) {
8501
8501
  err2 = errJp2Save();
8502
8502
  }
8503
8503
  if (err2) {
@@ -9475,13 +9475,13 @@ var {
9475
9475
  } = import_index.default;
9476
9476
 
9477
9477
  // src/cli.ts
9478
- import * as fs14 from "fs";
9479
- import * as path14 from "path";
9478
+ import * as fs15 from "fs";
9479
+ import * as path16 from "path";
9480
9480
 
9481
9481
  // src/baseline.ts
9482
9482
  init_define_import_meta_env();
9483
9483
  import * as fs2 from "node:fs";
9484
- import * as path4 from "node:path";
9484
+ import * as path5 from "node:path";
9485
9485
 
9486
9486
  // src/constants.ts
9487
9487
  init_define_import_meta_env();
@@ -9493,7 +9493,7 @@ init_define_import_meta_env();
9493
9493
  import { createRequire } from "node:module";
9494
9494
  function resolveVersion() {
9495
9495
  if (true) {
9496
- return "2.1.0";
9496
+ return "2.2.1";
9497
9497
  }
9498
9498
  const require2 = createRequire(import.meta.url);
9499
9499
  const pkg = require2("../package.json");
@@ -9543,9 +9543,42 @@ function configPath() {
9543
9543
  init_define_import_meta_env();
9544
9544
  import * as fs from "node:fs";
9545
9545
  import { createRequire as createRequire2 } from "node:module";
9546
- import * as path2 from "node:path";
9546
+ import * as path3 from "node:path";
9547
9547
  import Database from "better-sqlite3";
9548
9548
 
9549
+ // src/paths.ts
9550
+ init_define_import_meta_env();
9551
+ import * as path2 from "node:path";
9552
+ var WSL_PATH_RE = /^\/mnt\/([a-zA-Z])\/(.*)$/s;
9553
+ function normalizePath(p) {
9554
+ let s = p;
9555
+ if (s.includes("\\")) {
9556
+ s = s.replace(/\\/g, "/");
9557
+ }
9558
+ const m = WSL_PATH_RE.exec(s);
9559
+ if (m) {
9560
+ const driveLetter = m[1].toLowerCase();
9561
+ const rest = m[2];
9562
+ const restStripped = rest.replace(/^\/+/, "");
9563
+ s = `${driveLetter}:/${restStripped}`;
9564
+ }
9565
+ if (s.length >= 2 && s[1] === ":") {
9566
+ const c = s[0];
9567
+ if (/^[A-Z]$/.test(c)) {
9568
+ s = c.toLowerCase() + s.slice(1);
9569
+ }
9570
+ }
9571
+ return s;
9572
+ }
9573
+ function safeJoin(base, ...parts) {
9574
+ for (const part of parts) {
9575
+ if (part.includes(":")) {
9576
+ throw new Error(`safeJoin: path component contains colon: ${JSON.stringify(part)}`);
9577
+ }
9578
+ }
9579
+ return path2.join(base, ...parts);
9580
+ }
9581
+
9549
9582
  // src/reset.ts
9550
9583
  init_define_import_meta_env();
9551
9584
  var _resets = [];
@@ -9589,6 +9622,16 @@ CREATE TABLE IF NOT EXISTS refs (
9589
9622
  );
9590
9623
  CREATE INDEX IF NOT EXISTS idx_refs_name ON refs(name);
9591
9624
  CREATE INDEX IF NOT EXISTS idx_refs_file ON refs(file_path);
9625
+
9626
+ CREATE TABLE IF NOT EXISTS chunks (
9627
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
9628
+ file_path TEXT,
9629
+ start_line INTEGER,
9630
+ end_line INTEGER,
9631
+ text TEXT,
9632
+ kind TEXT
9633
+ );
9634
+ CREATE INDEX IF NOT EXISTS idx_chunks_file ON chunks(file_path);
9592
9635
  `;
9593
9636
  var FTS_SQL = `
9594
9637
  CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(
@@ -9636,15 +9679,15 @@ function initConnection(conn) {
9636
9679
  }
9637
9680
  }
9638
9681
  function resolveDbPath(dbPath) {
9639
- if (path2.isAbsolute(dbPath)) return dbPath;
9640
- if (dbPath.includes("/") || dbPath.includes("\\")) return path2.resolve(dbPath);
9641
- return path2.join(dataDir(), dbPath);
9682
+ if (path3.isAbsolute(dbPath)) return dbPath;
9683
+ if (dbPath.includes("/") || dbPath.includes("\\")) return path3.resolve(dbPath);
9684
+ return safeJoin(dataDir(), dbPath);
9642
9685
  }
9643
9686
  function getDb(dbPath) {
9644
9687
  const resolved = resolveDbPath(dbPath);
9645
9688
  const existing = _connections.get(resolved);
9646
9689
  if (existing !== void 0) return existing;
9647
- const dir = path2.dirname(resolved);
9690
+ const dir = path3.dirname(resolved);
9648
9691
  try {
9649
9692
  fs.mkdirSync(dir, { recursive: true });
9650
9693
  } catch (e) {
@@ -9668,7 +9711,7 @@ registerReset(closeAllDbs);
9668
9711
 
9669
9712
  // src/parser_types.ts
9670
9713
  init_define_import_meta_env();
9671
- import * as path3 from "node:path";
9714
+ import * as path4 from "node:path";
9672
9715
  var EXTENSION_LANGUAGE = /* @__PURE__ */ new Map([
9673
9716
  [".py", "python"],
9674
9717
  [".pyi", "python"],
@@ -9738,10 +9781,10 @@ var FILENAME_LANGUAGE = /* @__PURE__ */ new Map([
9738
9781
  [".envrc", "env_file"]
9739
9782
  ]);
9740
9783
  function detectLanguage(filePath) {
9741
- const base = path3.basename(filePath).toLowerCase();
9784
+ const base = path4.basename(filePath).toLowerCase();
9742
9785
  const byName = FILENAME_LANGUAGE.get(base);
9743
9786
  if (byName !== void 0) return byName;
9744
- const ext = path3.extname(base).toLowerCase();
9787
+ const ext = path4.extname(base).toLowerCase();
9745
9788
  return EXTENSION_LANGUAGE.get(ext) ?? "unknown";
9746
9789
  }
9747
9790
 
@@ -9782,7 +9825,7 @@ function walkProject(rootDir) {
9782
9825
  continue;
9783
9826
  }
9784
9827
  for (const entry of entries) {
9785
- const full = path4.join(dir, entry.name);
9828
+ const full = path5.join(dir, entry.name);
9786
9829
  if (entry.isDirectory()) {
9787
9830
  if (SKIP_DIRS.has(entry.name)) continue;
9788
9831
  if (entry.name.startsWith(".") && entry.name !== ".") {
@@ -9825,7 +9868,7 @@ function fetchTopSymbols(limit, dbPath) {
9825
9868
  }
9826
9869
  }
9827
9870
  function buildProjectMap(rootDir = process.cwd(), opts = {}) {
9828
- const root = path4.resolve(rootDir);
9871
+ const root = path5.resolve(rootDir);
9829
9872
  const { files, languages } = walkProject(root);
9830
9873
  const symbolLimit = opts.compact ? 10 : 30;
9831
9874
  const topSymbols = fetchTopSymbols(symbolLimit, globalDbPath());
@@ -9837,7 +9880,7 @@ function buildProjectMap(rootDir = process.cwd(), opts = {}) {
9837
9880
  mtime = 0;
9838
9881
  }
9839
9882
  return { f, mtime };
9840
- }).sort((a, b) => b.mtime - a.mtime).slice(0, opts.compact ? 5 : 15).map((x) => path4.relative(root, x.f));
9883
+ }).sort((a, b) => b.mtime - a.mtime).slice(0, opts.compact ? 5 : 15).map((x) => path5.relative(root, x.f));
9841
9884
  return {
9842
9885
  rootDir: root,
9843
9886
  fileCount: files.length,
@@ -9848,7 +9891,7 @@ function buildProjectMap(rootDir = process.cwd(), opts = {}) {
9848
9891
  }
9849
9892
  function formatProjectMap(map, compact = false) {
9850
9893
  const lines = [];
9851
- const rel = path4.basename(map.rootDir);
9894
+ const rel = path5.basename(map.rootDir);
9852
9895
  lines.push(`# Project map: ${rel}`);
9853
9896
  lines.push(`Files: ${map.fileCount}`);
9854
9897
  const langPairs = Object.entries(map.languages).sort((a, b) => b[1] - a[1]);
@@ -9861,7 +9904,7 @@ function formatProjectMap(map, compact = false) {
9861
9904
  if (compact) {
9862
9905
  lines.push(`- ${s.name} (${s.kind})`);
9863
9906
  } else {
9864
- const loc = `${path4.basename(s.filePath)}:${s.lineStart}-${s.lineEnd}`;
9907
+ const loc = `${path5.basename(s.filePath)}:${s.lineStart}-${s.lineEnd}`;
9865
9908
  lines.push(`- ${s.name} (${s.kind}) \u2014 ${loc}`);
9866
9909
  }
9867
9910
  }
@@ -9878,42 +9921,19 @@ function formatProjectMap(map, compact = false) {
9878
9921
 
9879
9922
  // src/repomap.ts
9880
9923
  init_define_import_meta_env();
9881
- import * as path5 from "path";
9924
+ import * as path6 from "path";
9882
9925
 
9883
9926
  // src/util.ts
9884
9927
  init_define_import_meta_env();
9885
9928
  import { spawnSync } from "node:child_process";
9886
9929
  import { closeSync, openSync, renameSync, unlinkSync, writeSync } from "node:fs";
9887
-
9888
- // src/paths.ts
9889
- init_define_import_meta_env();
9890
- var WSL_PATH_RE = /^\/mnt\/([a-zA-Z])\/(.*)$/s;
9891
- function normalizePath(p) {
9892
- let s = p;
9893
- if (s.includes("\\")) {
9894
- s = s.replace(/\\/g, "/");
9895
- }
9896
- const m = WSL_PATH_RE.exec(s);
9897
- if (m) {
9898
- const driveLetter = m[1].toLowerCase();
9899
- const rest = m[2];
9900
- const restStripped = rest.replace(/^\/+/, "");
9901
- s = `${driveLetter}:/${restStripped}`;
9902
- }
9903
- if (s.length >= 2 && s[1] === ":") {
9904
- const c = s[0];
9905
- if (/^[A-Z]$/.test(c)) {
9906
- s = c.toLowerCase() + s.slice(1);
9907
- }
9908
- }
9909
- return s;
9910
- }
9911
-
9912
- // src/util.ts
9913
9930
  function sleepSync(ms) {
9914
9931
  if (ms <= 0) return;
9915
9932
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
9916
9933
  }
9934
+ function isWindows() {
9935
+ return process.platform === "win32";
9936
+ }
9917
9937
  function runGit(args, opts = {}) {
9918
9938
  const fullArgs = ["-c", "core.fsmonitor=", ...args];
9919
9939
  const result = spawnSync("git", fullArgs, {
@@ -9979,6 +9999,21 @@ function atomicWriteText(filePath, content) {
9979
9999
  function atomicWriteBytes(filePath, content) {
9980
10000
  atomicWriteCore(filePath, content);
9981
10001
  }
10002
+ function ensureNewline(text) {
10003
+ return text.endsWith("\n") ? text : text + "\n";
10004
+ }
10005
+ function extractErrorMessage(err2, fallback = "") {
10006
+ return err2 instanceof Error ? err2.message : fallback || String(err2);
10007
+ }
10008
+ function isCodeFenceDelimiter(line) {
10009
+ const s = line.trim();
10010
+ return s.startsWith("```") || s.startsWith("~~~");
10011
+ }
10012
+ function normalizePathForwardSlash(p, toLowerCase) {
10013
+ let result = normalizePath(p).replace(/\\/g, "/");
10014
+ if (toLowerCase) result = result.toLowerCase();
10015
+ return result;
10016
+ }
9982
10017
 
9983
10018
  // src/index_reader.ts
9984
10019
  init_define_import_meta_env();
@@ -10124,7 +10159,7 @@ function isNoisePath(inputPath) {
10124
10159
  if (!inputPath) {
10125
10160
  return false;
10126
10161
  }
10127
- const p = normalizePath(inputPath).toLowerCase().replace(/\\/g, "/");
10162
+ const p = normalizePathForwardSlash(inputPath, true);
10128
10163
  for (const segment of NOISE_SEGMENTS) {
10129
10164
  if (p.includes(segment)) {
10130
10165
  return true;
@@ -10162,7 +10197,7 @@ function getTrackedFiles(cwd = process.cwd()) {
10162
10197
  if (result.exitCode !== 0 || !result.stdout) {
10163
10198
  return [];
10164
10199
  }
10165
- return result.stdout.split("\n").filter((line) => line.trim().length > 0).map((rel) => path5.join(cwd, rel));
10200
+ return result.stdout.split("\n").filter((line) => line.trim().length > 0).map((rel) => path6.join(cwd, rel));
10166
10201
  } catch {
10167
10202
  return [];
10168
10203
  }
@@ -10218,7 +10253,7 @@ function formatMap(entries, opts = {}) {
10218
10253
  lines.push("");
10219
10254
  lines.push("## Files");
10220
10255
  for (const e of shown) {
10221
- const rel = path5.relative(cwd, e.filePath);
10256
+ const rel = path6.relative(cwd, e.filePath);
10222
10257
  if (compact) {
10223
10258
  lines.push(`- ${rel} (${e.language})`);
10224
10259
  } else {
@@ -10236,11 +10271,13 @@ function formatMap(entries, opts = {}) {
10236
10271
 
10237
10272
  // src/session.ts
10238
10273
  init_define_import_meta_env();
10274
+ import { randomBytes, randomUUID } from "node:crypto";
10239
10275
  import * as fs3 from "node:fs";
10240
10276
  var _files = /* @__PURE__ */ new Map();
10241
10277
  var _hintsShown = /* @__PURE__ */ new Set();
10242
10278
  var _webFetches = /* @__PURE__ */ new Map();
10243
10279
  var _bashOutputs = /* @__PURE__ */ new Map();
10280
+ var _curlDownloads = /* @__PURE__ */ new Map();
10244
10281
  var _sessionId = null;
10245
10282
  function fileSize(absPath) {
10246
10283
  try {
@@ -10265,10 +10302,9 @@ function recordFileRead(filePath) {
10265
10302
  return;
10266
10303
  }
10267
10304
  _files.set(key, {
10268
- path: key,
10305
+ ...prev,
10269
10306
  readCount: prev.readCount + 1,
10270
10307
  lastReadAt: now,
10271
- wasEdited: prev.wasEdited,
10272
10308
  sizeBytes: size
10273
10309
  });
10274
10310
  }
@@ -10309,11 +10345,51 @@ function recordBashOutput(commandHash2, outputId, _sizeBytes) {
10309
10345
  function getBashOutputId(commandHash2) {
10310
10346
  return _bashOutputs.get(commandHash2) ?? null;
10311
10347
  }
10348
+ function recordCurlDownload(url, savedPath) {
10349
+ _curlDownloads.set(url, savedPath);
10350
+ }
10351
+ function getCurlDownloadPath(url) {
10352
+ return _curlDownloads.get(url) ?? null;
10353
+ }
10354
+ function markFileTruncated(filePath) {
10355
+ const key = normalizePath(filePath);
10356
+ const prev = _files.get(key);
10357
+ if (prev === void 0) {
10358
+ _files.set(key, {
10359
+ path: key,
10360
+ readCount: 1,
10361
+ lastReadAt: Date.now(),
10362
+ wasEdited: false,
10363
+ sizeBytes: fileSize(key),
10364
+ wasTruncated: true
10365
+ });
10366
+ return;
10367
+ }
10368
+ _files.set(key, { ...prev, wasTruncated: true });
10369
+ }
10370
+ function wasFileTruncatedThisSession(filePath) {
10371
+ const entry = _files.get(normalizePath(filePath));
10372
+ return entry?.wasTruncated === true;
10373
+ }
10374
+ function generateSessionId() {
10375
+ try {
10376
+ return randomUUID();
10377
+ } catch {
10378
+ return randomBytes(16).toString("hex");
10379
+ }
10380
+ }
10381
+ function getSessionId() {
10382
+ if (_sessionId !== null) return _sessionId;
10383
+ const fromEnv = process.env["CLAUDE_SESSION_ID"];
10384
+ _sessionId = fromEnv !== void 0 && fromEnv !== "" ? fromEnv : generateSessionId();
10385
+ return _sessionId;
10386
+ }
10312
10387
  registerReset(() => {
10313
10388
  _files = /* @__PURE__ */ new Map();
10314
10389
  _hintsShown = /* @__PURE__ */ new Set();
10315
10390
  _webFetches = /* @__PURE__ */ new Map();
10316
10391
  _bashOutputs = /* @__PURE__ */ new Map();
10392
+ _curlDownloads = /* @__PURE__ */ new Map();
10317
10393
  _sessionId = null;
10318
10394
  });
10319
10395
 
@@ -10373,8 +10449,8 @@ var HOOK_EVENTS = [
10373
10449
 
10374
10450
  // src/hooks_read.ts
10375
10451
  init_define_import_meta_env();
10376
- import * as fs4 from "node:fs";
10377
- import * as path7 from "node:path";
10452
+ import * as fs5 from "node:fs";
10453
+ import * as path9 from "node:path";
10378
10454
 
10379
10455
  // src/hooks_common.ts
10380
10456
  init_define_import_meta_env();
@@ -10398,6 +10474,166 @@ function contextOutput(context) {
10398
10474
  return { hookType: "context", context };
10399
10475
  }
10400
10476
 
10477
+ // src/snapshots.ts
10478
+ init_define_import_meta_env();
10479
+ import * as fs4 from "node:fs";
10480
+ import * as path7 from "node:path";
10481
+ import * as crypto from "node:crypto";
10482
+ import * as os2 from "node:os";
10483
+ var MAX_SNAPSHOTS_PER_SESSION = 150;
10484
+ var MAX_SNAPSHOT_BYTES = 256 * 1024;
10485
+ var SNAPSHOT_TRUNCATE_BYTES = 50 * 1024;
10486
+ var KIND_READ = "read";
10487
+ var KIND_PREDICTIVE = "predictive";
10488
+ var VALID_KINDS = /* @__PURE__ */ new Set([KIND_READ, KIND_PREDICTIVE]);
10489
+ var _TRUNCATED_MARKER = Buffer.from("\n<snapshot truncated at ");
10490
+ var SESSION_DIR_RE = /[^a-zA-Z0-9_-]/g;
10491
+ function sessionDir(sessionId) {
10492
+ if (!sessionId) return null;
10493
+ const safe = sessionId.replace(SESSION_DIR_RE, "_").slice(0, 64) || "anon";
10494
+ const base = path7.join(os2.homedir(), ".token-goat", "session_snapshots");
10495
+ const candidate = path7.join(base, safe);
10496
+ try {
10497
+ const rel = path7.relative(base, candidate);
10498
+ if (rel.startsWith("..")) return null;
10499
+ } catch {
10500
+ return null;
10501
+ }
10502
+ return candidate;
10503
+ }
10504
+ function pathKey(filePath) {
10505
+ return crypto.createHash("sha256").update(filePath, "utf8").digest("hex").slice(0, 32);
10506
+ }
10507
+ function snapshot_path(sessionId, filePath) {
10508
+ const d = sessionDir(sessionId);
10509
+ if (!d) return null;
10510
+ return path7.join(d, `${pathKey(filePath)}.bin`);
10511
+ }
10512
+ function kindSidecarPath(snapshotPath) {
10513
+ return snapshotPath + ".kind";
10514
+ }
10515
+ function writeSnapshotKind(sidecarPath, kind) {
10516
+ try {
10517
+ const safeKind = VALID_KINDS.has(kind) ? kind : KIND_READ;
10518
+ const dir = path7.dirname(sidecarPath);
10519
+ if (!fs4.existsSync(dir)) {
10520
+ fs4.mkdirSync(dir, { recursive: true });
10521
+ }
10522
+ fs4.writeFileSync(sidecarPath, safeKind, "utf8");
10523
+ return true;
10524
+ } catch {
10525
+ return false;
10526
+ }
10527
+ }
10528
+ function evictOldest(d, maxCount) {
10529
+ try {
10530
+ const entries = [];
10531
+ const files = fs4.readdirSync(d);
10532
+ for (const file of files) {
10533
+ const fullPath = path7.join(d, file);
10534
+ if (!file.endsWith(".bin")) continue;
10535
+ try {
10536
+ const stat3 = fs4.statSync(fullPath);
10537
+ entries.push([fullPath, stat3.mtimeMs]);
10538
+ } catch {
10539
+ continue;
10540
+ }
10541
+ }
10542
+ if (entries.length <= maxCount) return 0;
10543
+ entries.sort((a, b) => a[1] - b[1]);
10544
+ let removed = 0;
10545
+ const over = entries.length - maxCount;
10546
+ for (const [p] of entries.slice(0, over)) {
10547
+ try {
10548
+ fs4.unlinkSync(p);
10549
+ removed++;
10550
+ try {
10551
+ fs4.unlinkSync(kindSidecarPath(p));
10552
+ } catch {
10553
+ }
10554
+ } catch {
10555
+ continue;
10556
+ }
10557
+ }
10558
+ return removed;
10559
+ } catch {
10560
+ return 0;
10561
+ }
10562
+ }
10563
+ function store(sessionId, filePath, content, opts = {}) {
10564
+ const kind = opts.kind ?? KIND_READ;
10565
+ const origLen = content.length;
10566
+ if (origLen > MAX_SNAPSHOT_BYTES) {
10567
+ return null;
10568
+ }
10569
+ let stored = content;
10570
+ if (origLen > SNAPSHOT_TRUNCATE_BYTES) {
10571
+ const marker = Buffer.from(`
10572
+ <snapshot truncated at ${origLen} bytes>
10573
+ `);
10574
+ stored = Buffer.concat([content.slice(0, SNAPSHOT_TRUNCATE_BYTES), marker]);
10575
+ }
10576
+ const p = snapshot_path(sessionId, filePath);
10577
+ if (!p) return null;
10578
+ const sha = crypto.createHash("sha256").update(stored).digest("hex");
10579
+ try {
10580
+ if (fs4.existsSync(p)) {
10581
+ try {
10582
+ const existing = fs4.readFileSync(p);
10583
+ if (Buffer.from(existing).equals(stored)) {
10584
+ return {
10585
+ path: p,
10586
+ content_sha: sha,
10587
+ size_bytes: stored.length
10588
+ };
10589
+ }
10590
+ } catch {
10591
+ }
10592
+ }
10593
+ const dir = path7.dirname(p);
10594
+ if (!fs4.existsSync(dir)) {
10595
+ fs4.mkdirSync(dir, { recursive: true });
10596
+ }
10597
+ evictOldest(dir, MAX_SNAPSHOTS_PER_SESSION - 1);
10598
+ const tempPath = p + ".tmp";
10599
+ fs4.writeFileSync(tempPath, stored);
10600
+ fs4.renameSync(tempPath, p);
10601
+ const sidecar = kindSidecarPath(p);
10602
+ writeSnapshotKind(sidecar, kind);
10603
+ return {
10604
+ path: p,
10605
+ content_sha: sha,
10606
+ size_bytes: stored.length
10607
+ };
10608
+ } catch {
10609
+ return null;
10610
+ }
10611
+ }
10612
+ function load(sessionId, filePath, opts = {}) {
10613
+ const p = snapshot_path(sessionId, filePath);
10614
+ if (!p || !fs4.existsSync(p)) return null;
10615
+ try {
10616
+ const stat3 = fs4.statSync(p);
10617
+ if (stat3.size > MAX_SNAPSHOT_BYTES) {
10618
+ return null;
10619
+ }
10620
+ } catch {
10621
+ return null;
10622
+ }
10623
+ try {
10624
+ const data = fs4.readFileSync(p);
10625
+ if (opts.expected_sha) {
10626
+ const actualSha = crypto.createHash("sha256").update(data).digest("hex");
10627
+ if (actualSha.toLowerCase() !== opts.expected_sha.toLowerCase()) {
10628
+ return null;
10629
+ }
10630
+ }
10631
+ return data;
10632
+ } catch {
10633
+ return null;
10634
+ }
10635
+ }
10636
+
10401
10637
  // src/hints.ts
10402
10638
  init_define_import_meta_env();
10403
10639
  var HINT_PRIORITY_MEDIUM = 3;
@@ -10430,11 +10666,11 @@ function buildPackageManifestHint(options) {
10430
10666
  return null;
10431
10667
  }
10432
10668
  }
10433
- function _sanitizeHintPath(path15) {
10434
- if (typeof path15 !== "string") {
10669
+ function _sanitizeHintPath(path17) {
10670
+ if (typeof path17 !== "string") {
10435
10671
  return "???";
10436
10672
  }
10437
- return path15.replace(/[\x00]/g, "").slice(0, 200);
10673
+ return path17.replace(/[\x00]/g, "").slice(0, 200);
10438
10674
  }
10439
10675
 
10440
10676
  // src/hints/lang_patterns.ts
@@ -10550,8 +10786,9 @@ var BUILD_COMMAND_PATTERNS = [
10550
10786
  /^\s*cmake\s+--build\b/i,
10551
10787
  // Rake (Ruby)
10552
10788
  /^\s*rake\b/i,
10553
- // TypeScript compiler
10554
- /^\s*tsc\b/i,
10789
+ // TypeScript compiler (direct and via npx)
10790
+ /^\s*tsc(?:\s|$)/i,
10791
+ /^\s*npx\s+tsc(?:\s|$)/i,
10555
10792
  // Vite
10556
10793
  /^\s*vite\s+(build|dev|preview)\b/i,
10557
10794
  // Next.js
@@ -10636,6 +10873,7 @@ var MONITORING_COMMAND_PATTERNS = [
10636
10873
  // Linters / formatters run repeatedly
10637
10874
  { pattern: /^(?:npx\s+)?eslint(?:\s|$)/, recallHint: '--grep "error|warning|\u2716|problems"' },
10638
10875
  { pattern: /^(?:npx\s+)?prettier(?:\s|$)/, recallHint: '--grep "unchanged|reformatted|error"' },
10876
+ { pattern: /^npx\s+tsc(?:\s|$)/, recallHint: '--grep "error TS|Cannot find|Type "' },
10639
10877
  { pattern: /^ruff(?:\s|$)/, recallHint: '--grep "error|warning|Found"' },
10640
10878
  { pattern: /^(?:cargo\s+)?clippy/, recallHint: '--grep "error\\[|warning\\["' },
10641
10879
  // git diff (full diff output — can be very large; excludes --stat which is small)
@@ -10645,6 +10883,8 @@ var MONITORING_COMMAND_PATTERNS = [
10645
10883
  { pattern: /^npm run (?:test|spec)(?:\s|$)/, recallHint: '--grep "FAIL|PASS|Error|Tests:|\u2713|\u2717"' },
10646
10884
  { pattern: /^npm run build(?:\s|$)/, recallHint: '--grep "error|Built|Failed|\u2713|\u2717"' },
10647
10885
  { pattern: /^npm run (?:lint|typecheck|check|type-check)(?:\s|$)/, recallHint: '--grep "error|warning|\u2716|problems"' },
10886
+ // node scripts (migration runners, seed generators, etc. run repeatedly)
10887
+ { pattern: /^node\s+(?:scripts|src\/scripts)\/\S+\.m?js\b/, recallHint: '--tail 50 --grep "error|Error|done|complete|inserted|migrated"' },
10648
10888
  // External AI peer-review CLI tools (produce large outputs, run repeatedly per session)
10649
10889
  { pattern: /^codex(?:\s|$)/, recallHint: '--tail 100 --grep "error|suggestion|verdict|conclusion"' },
10650
10890
  { pattern: /^(?:~\/\.claude\/bin\/|\.claude\/bin\/)?glm\.sh(?:\s|$)/, recallHint: '--tail 100 --grep "error|verdict|conclusion|suggestion"' },
@@ -10874,7 +11114,7 @@ function dispatchFileTypeHandler(filePath, content, contentLengthHint) {
10874
11114
 
10875
11115
  // src/stats.ts
10876
11116
  init_define_import_meta_env();
10877
- import * as path6 from "node:path";
11117
+ import * as path8 from "node:path";
10878
11118
 
10879
11119
  // src/render/stats_renderer.ts
10880
11120
  init_define_import_meta_env();
@@ -11654,7 +11894,7 @@ CREATE INDEX IF NOT EXISTS idx_stats_kind ON stats(kind);
11654
11894
  var _globalSchemaApplied = /* @__PURE__ */ new Set();
11655
11895
  registerReset(() => _globalSchemaApplied.clear());
11656
11896
  function getGlobalDb() {
11657
- const dbPath = path6.join(dataDir(), "global.db");
11897
+ const dbPath = path8.join(dataDir(), "global.db");
11658
11898
  const db = getDb(dbPath);
11659
11899
  if (!_globalSchemaApplied.has(dbPath)) {
11660
11900
  db.exec(GLOBAL_SCHEMA_SQL);
@@ -11688,7 +11928,9 @@ function summarize(windowDays = 30, testDb) {
11688
11928
  const bytesSaved = row.bytes_saved ?? 0;
11689
11929
  const tokensSaved = row.tokens_saved ?? 0;
11690
11930
  const kind = row.kind;
11691
- const ts = row.ts;
11931
+ const tsRaw = row.ts;
11932
+ if (tsRaw === void 0) continue;
11933
+ const ts = tsRaw;
11692
11934
  totalEvents += 1;
11693
11935
  totalBytes += bytesSaved;
11694
11936
  totalTokens += tokensSaved;
@@ -11851,22 +12093,59 @@ function isTsConfigFile(basename7) {
11851
12093
  var LARGE_FILE_BYTES = 100 * 1024;
11852
12094
  var REREAD_DENY_BYTES = 50 * 1024;
11853
12095
  var LARGE_FILE_DENY_BYTES = 500 * 1024;
11854
- function isNodeModulesPath(path15) {
11855
- const isWindows = process.platform === "win32";
11856
- const check = isWindows ? path15.toLowerCase() : path15;
12096
+ function isNodeModulesPath(p) {
12097
+ const check = isWindows() ? p.toLowerCase() : p;
11857
12098
  return check.includes("/node_modules/") || check.includes("\\node_modules\\");
11858
12099
  }
11859
12100
  function _isDocFile(filePath) {
11860
12101
  const lower = filePath.toLowerCase();
11861
12102
  return lower.endsWith(".md") || lower.endsWith(".mdx") || lower.endsWith(".markdown") || lower.endsWith(".rst");
11862
12103
  }
12104
+ function isSessionArtifactFile(filePath) {
12105
+ if (/[/\\]tasks[/\\][a-z0-9]+\.output$/i.test(filePath)) return true;
12106
+ if (/[/\\]tool-results[/\\][a-z0-9]+\.txt$/i.test(filePath)) return true;
12107
+ return false;
12108
+ }
11863
12109
  function statSize(absPath) {
11864
12110
  try {
11865
- return fs4.statSync(absPath).size;
12111
+ return fs5.statSync(absPath).size;
11866
12112
  } catch {
11867
12113
  return null;
11868
12114
  }
11869
12115
  }
12116
+ function buildLineDiff(oldContent, newContent, label) {
12117
+ const oldLines = oldContent.split("\n");
12118
+ const newLines = newContent.split("\n");
12119
+ let prefix = 0;
12120
+ while (prefix < oldLines.length && prefix < newLines.length && oldLines[prefix] === newLines[prefix]) {
12121
+ prefix++;
12122
+ }
12123
+ let oldSuffix = oldLines.length;
12124
+ let newSuffix = newLines.length;
12125
+ while (oldSuffix > prefix && newSuffix > prefix && oldLines[oldSuffix - 1] === newLines[newSuffix - 1]) {
12126
+ oldSuffix--;
12127
+ newSuffix--;
12128
+ }
12129
+ if (prefix === oldLines.length && prefix === newLines.length) return "";
12130
+ const changedOld = oldLines.slice(prefix, oldSuffix);
12131
+ const changedNew = newLines.slice(prefix, newSuffix);
12132
+ const MAX_LINES = 50;
12133
+ const out2 = [
12134
+ `--- ${label} (prev)`,
12135
+ `+++ ${label} (current)`,
12136
+ `@@ -${prefix + 1},${changedOld.length} +${prefix + 1},${changedNew.length} @@`
12137
+ ];
12138
+ const removedLines = changedOld.map((l) => `-${l}`);
12139
+ const addedLines = changedNew.map((l) => `+${l}`);
12140
+ const allChanges = [...removedLines, ...addedLines];
12141
+ if (allChanges.length <= MAX_LINES) {
12142
+ out2.push(...allChanges);
12143
+ } else {
12144
+ out2.push(...allChanges.slice(0, MAX_LINES));
12145
+ out2.push(`... (${allChanges.length - MAX_LINES} more changed lines)`);
12146
+ }
12147
+ return out2.join("\n");
12148
+ }
11870
12149
  function preReadHandler(event) {
11871
12150
  const filePath = getFilePath(event);
11872
12151
  if (filePath === void 0) return passOutput();
@@ -11876,7 +12155,7 @@ function preReadHandler(event) {
11876
12155
  "node_modules is typically noise; use npm ls, npm outdated, or npm audit instead for dependency info. To force access, use: token-goat read node_modules/package/file.js::symbol-name or token-goat section node_modules/package/file.js::heading"
11877
12156
  );
11878
12157
  }
11879
- const basename7 = path7.basename(normalized);
12158
+ const basename7 = path9.basename(normalized);
11880
12159
  if (isLockFile(basename7)) {
11881
12160
  return denyOutput(
11882
12161
  'Lock files are rarely useful to read in full. Use `token-goat section "' + normalized + '::<section>"` to extract a specific dependency, or read the relevant manifest instead.'
@@ -11915,7 +12194,7 @@ function preReadHandler(event) {
11915
12194
  try {
11916
12195
  const sz = statSize(normalized);
11917
12196
  if (sz !== null && sz >= MARKDOWN_SIZE_THRESHOLD) {
11918
- fileContent = fs4.readFileSync(normalized, "utf8");
12197
+ fileContent = fs5.readFileSync(normalized, "utf8");
11919
12198
  }
11920
12199
  } catch {
11921
12200
  }
@@ -11931,6 +12210,22 @@ function preReadHandler(event) {
11931
12210
  }
11932
12211
  }
11933
12212
  }
12213
+ const isMemoryMd = normalized.toLowerCase().includes("memory/memory.md") || /[/\\]memory[/\\][^/\\]+\.md$/i.test(normalized);
12214
+ if (isMemoryMd && wasFileReadThisSession(normalized)) {
12215
+ recordFileRead(normalized);
12216
+ recordStat("session_hint", 0, 0);
12217
+ const isMainMemory = basename7.toLowerCase() === "memory.md";
12218
+ return denyOutput(
12219
+ isMainMemory ? "MEMORY.md was read this session. Its content is in the compact manifest as 'session memory'." : normalized + ' was already read this session. Memory files rarely change mid-session. Use `token-goat section "' + normalized + '::SectionHeading"` to extract one section.'
12220
+ );
12221
+ }
12222
+ if (/^\.improve-state-.*\.json$/.test(basename7) && wasFileReadThisSession(normalized)) {
12223
+ recordFileRead(normalized);
12224
+ recordStat("session_hint", 0, 0);
12225
+ return denyOutput(
12226
+ "Orchestrator state already read this session. Use `token-goat bash-output <id>` or recall it from the compact manifest."
12227
+ );
12228
+ }
11934
12229
  if (/^\.env(\.\w+)?$/.test(basename7) && wasFileReadThisSession(normalized)) {
11935
12230
  recordFileRead(normalized);
11936
12231
  recordStat("session_hint", 0, 0);
@@ -11938,14 +12233,124 @@ function preReadHandler(event) {
11938
12233
  normalized + " was already read this session. Environment files rarely change mid-session. Use `token-goat config-get " + normalized + " KEY_NAME` to extract a specific variable."
11939
12234
  );
11940
12235
  }
12236
+ if (isSessionArtifactFile(normalized)) {
12237
+ if (wasFileReadThisSession(normalized)) {
12238
+ if (wasFileTruncatedThisSession(normalized)) {
12239
+ recordFileRead(normalized);
12240
+ recordStat("session_hint", 0, 0);
12241
+ return denyOutput(
12242
+ "File was truncated on last read. Use `token-goat bash-output --tail N` or `--grep PATTERN` to read a slice."
12243
+ );
12244
+ }
12245
+ const artifactSessionId = getSessionId();
12246
+ const oldArtifactSnap = load(artifactSessionId, normalized);
12247
+ if (oldArtifactSnap !== null) {
12248
+ try {
12249
+ const sz = statSize(normalized);
12250
+ if (sz !== null && sz <= 256 * 1024) {
12251
+ const currentContent = fs5.readFileSync(normalized, "utf8");
12252
+ const TRUNC_MARKER = "\n<snapshot truncated at ";
12253
+ const oldRaw = oldArtifactSnap.toString("utf8");
12254
+ const truncIdx = oldRaw.indexOf(TRUNC_MARKER);
12255
+ const oldContent = truncIdx >= 0 ? oldRaw.slice(0, truncIdx) : oldRaw;
12256
+ if (oldContent === currentContent) {
12257
+ recordFileRead(normalized);
12258
+ recordStat("session_hint", 0, 0);
12259
+ return denyOutput(
12260
+ basename7 + " is unchanged since last read. Use `token-goat bash-output --tail N` or `--grep PATTERN` to read a slice."
12261
+ );
12262
+ }
12263
+ const diff = buildLineDiff(oldContent, currentContent, basename7);
12264
+ if (diff !== "") {
12265
+ recordFileRead(normalized);
12266
+ const savedBytes = Math.max(0, currentContent.length - diff.length);
12267
+ recordStat("session_hint", savedBytes, Math.round(savedBytes / 4));
12268
+ return denyOutput(
12269
+ "Content changed since last read of " + basename7 + ". Here is what changed:\n\n```diff\n" + diff + "\n```\n\nUse `token-goat bash-output --tail N` or `--grep PATTERN` to read a slice."
12270
+ );
12271
+ }
12272
+ }
12273
+ } catch {
12274
+ }
12275
+ }
12276
+ recordFileRead(normalized);
12277
+ recordStat("session_hint", 0, 0);
12278
+ return denyOutput(
12279
+ normalized + " was already read this session. Use `token-goat bash-output --tail N` or `--grep PATTERN` to read a slice."
12280
+ );
12281
+ }
12282
+ if (/[/\\]tasks[/\\][a-z0-9]+\.output$/i.test(normalized)) {
12283
+ recordFileRead(normalized);
12284
+ return contextOutput(
12285
+ "Session transcript: use `token-goat bash-output --tail N` or `--grep PATTERN` to read a slice instead of the full file."
12286
+ );
12287
+ }
12288
+ }
12289
+ if (/\.(md|mdx|rst|txt)$/i.test(basename7) && wasFileReadThisSession(normalized)) {
12290
+ if (wasFileTruncatedThisSession(normalized)) {
12291
+ recordFileRead(normalized);
12292
+ recordStat("session_hint", 0, 0);
12293
+ return denyOutput(
12294
+ 'File was truncated on last read (>33K tokens). Use `token-goat skeleton "' + normalized + '"` for structure or `token-goat read "' + normalized + '::SymbolName"` for one function.'
12295
+ );
12296
+ }
12297
+ const sessionId = getSessionId();
12298
+ const oldSnap = load(sessionId, normalized);
12299
+ if (oldSnap !== null) {
12300
+ try {
12301
+ const sz = statSize(normalized);
12302
+ if (sz !== null && sz <= 256 * 1024) {
12303
+ const currentContent = fs5.readFileSync(normalized, "utf8");
12304
+ const TRUNC_MARKER = "\n<snapshot truncated at ";
12305
+ const oldRaw = oldSnap.toString("utf8");
12306
+ const truncIdx = oldRaw.indexOf(TRUNC_MARKER);
12307
+ const oldContent = truncIdx >= 0 ? oldRaw.slice(0, truncIdx) : oldRaw;
12308
+ if (oldContent === currentContent) {
12309
+ recordFileRead(normalized);
12310
+ recordStat("session_hint", 0, 0);
12311
+ return denyOutput(
12312
+ basename7 + ' is unchanged since last read. Use `token-goat section "' + normalized + '::HeadingName"` to extract a part.'
12313
+ );
12314
+ }
12315
+ const diff = buildLineDiff(oldContent, currentContent, basename7);
12316
+ if (diff !== "") {
12317
+ recordFileRead(normalized);
12318
+ const savedBytes = Math.max(0, currentContent.length - diff.length);
12319
+ recordStat("session_hint", savedBytes, Math.round(savedBytes / 4));
12320
+ return denyOutput(
12321
+ "Content changed since last read of " + basename7 + ". Here is what changed:\n\n```diff\n" + diff + '\n```\n\nUse `token-goat section "' + normalized + '::HeadingName"` to read a specific section.'
12322
+ );
12323
+ }
12324
+ }
12325
+ } catch {
12326
+ }
12327
+ }
12328
+ }
11941
12329
  if (wasFileReadThisSession(normalized)) {
11942
12330
  const entry = getSessionFiles().get(normalized);
11943
12331
  const reads = entry?.readCount ?? 1;
11944
12332
  const plural = reads === 1 ? "read" : "reads";
11945
12333
  recordFileRead(normalized);
11946
- const hint = _isDocFile(normalized) ? 'Use `token-goat section "' + normalized + '::SectionName"` to read one section.' : "Use token-goat read/section/symbol to re-read surgically.";
11947
12334
  const rereadBytes = statSize(normalized) ?? 0;
11948
12335
  recordStat("session_hint", rereadBytes, Math.round(rereadBytes / 4));
12336
+ if (wasFileTruncatedThisSession(normalized)) {
12337
+ return denyOutput(
12338
+ 'File was truncated on last read (>33K tokens). Use `token-goat skeleton "' + normalized + '"` for structure or `token-goat read "' + normalized + '::SymbolName"` for one function.'
12339
+ );
12340
+ }
12341
+ if (/\.(md|mdx)$/i.test(basename7)) {
12342
+ return denyOutput(
12343
+ 'Markdown file already read this session. Use `token-goat section "' + normalized + '::HeadingName"` to read one section.'
12344
+ );
12345
+ }
12346
+ const isSourceExt = /\.(ts|tsx|js|jsx|py|go|rs|java|rb|php|swift|kt|cpp|c|h)$/i.test(basename7);
12347
+ if (isSourceExt && reads >= 2) {
12348
+ recordStat("read_count_deny", rereadBytes, Math.round(rereadBytes / 4));
12349
+ return denyOutput(
12350
+ "Read this file " + reads + ' times already \u2014 use `token-goat read "' + normalized + '::Symbol"`, `token-goat skeleton ' + normalized + "`, or `token-goat outline " + normalized + "` to pull just the part you need."
12351
+ );
12352
+ }
12353
+ const hint = _isDocFile(normalized) ? 'Use `token-goat section "' + normalized + '::SectionName"` to read one section.' : "Use token-goat read/section/symbol to re-read surgically.";
11949
12354
  if (rereadBytes >= REREAD_DENY_BYTES || reads >= 2) {
11950
12355
  return denyOutput(
11951
12356
  normalized + " was already read this session (" + reads + " " + plural + "). " + hint
@@ -11967,10 +12372,10 @@ function preReadHandler(event) {
11967
12372
  );
11968
12373
  }
11969
12374
  return contextOutput(
11970
- "Note: " + normalized + " is large (" + kb + "kb). " + hint
12375
+ "Note: " + normalized + " is large (" + kb + "KB). " + hint
11971
12376
  );
11972
12377
  }
11973
- const fileTypeExt = path7.extname(normalized).slice(1).toLowerCase();
12378
+ const fileTypeExt = path9.extname(normalized).slice(1).toLowerCase();
11974
12379
  const binaryExts = /* @__PURE__ */ new Set(["pdf", "docx", "xlsx", "pptx", "odt", "ods", "ott", "odp"]);
11975
12380
  const textTypeExts = /* @__PURE__ */ new Set(["html", "htm", "xhtml", "txt", "log", "out", "err", "trace", "csv", "tsv"]);
11976
12381
  const fileStatSize = size ?? statSize(normalized) ?? 0;
@@ -11979,7 +12384,7 @@ function preReadHandler(event) {
11979
12384
  let ftContent = "";
11980
12385
  if (!binaryExts.has(fileTypeExt)) {
11981
12386
  try {
11982
- ftContent = fs4.readFileSync(normalized, "utf8");
12387
+ ftContent = fs5.readFileSync(normalized, "utf8");
11983
12388
  } catch {
11984
12389
  }
11985
12390
  }
@@ -11994,29 +12399,67 @@ function preReadHandler(event) {
11994
12399
  }
11995
12400
  registerHook("pre_tool_use", preReadHandler, { toolName: "Read" });
11996
12401
  registerHook("pre_tool_use", preReadHandler, { toolName: "Grep" });
12402
+ function extractReadOutput(raw) {
12403
+ const resp = raw["tool_response"];
12404
+ if (typeof resp === "string") return resp;
12405
+ if (resp !== null && typeof resp === "object") {
12406
+ const r = resp;
12407
+ for (const key of ["output", "content", "text", "body"]) {
12408
+ if (typeof r[key] === "string") return r[key];
12409
+ }
12410
+ }
12411
+ return "";
12412
+ }
12413
+ function postReadHandler(event) {
12414
+ const filePath = getFilePath(event);
12415
+ if (filePath === void 0) return passOutput();
12416
+ const normalized = normalizePath(filePath);
12417
+ const respText = extractReadOutput(event.raw);
12418
+ if (respText.includes("[Truncated:") || respText.includes("Truncated: PARTIAL view")) {
12419
+ markFileTruncated(normalized);
12420
+ }
12421
+ const postBasename = path9.basename(normalized);
12422
+ if (/\.(md|mdx|rst|txt)$/i.test(postBasename) || isSessionArtifactFile(normalized)) {
12423
+ try {
12424
+ const sz = statSize(normalized);
12425
+ if (sz !== null && sz <= 256 * 1024) {
12426
+ const content = fs5.readFileSync(normalized);
12427
+ store(getSessionId(), normalized, content);
12428
+ }
12429
+ } catch {
12430
+ }
12431
+ }
12432
+ return passOutput();
12433
+ }
12434
+ registerHook("post_tool_use", postReadHandler, { toolName: "Read" });
11997
12435
 
11998
12436
  // src/hooks_edit.ts
11999
12437
  init_define_import_meta_env();
12000
- import * as path9 from "node:path";
12438
+ import * as path11 from "node:path";
12001
12439
 
12002
12440
  // src/hooks_index.ts
12003
12441
  init_define_import_meta_env();
12004
- import * as fs5 from "node:fs";
12005
- import * as path8 from "node:path";
12442
+ import * as fs6 from "node:fs";
12443
+ import * as path10 from "node:path";
12006
12444
  function dirtyQueuePath() {
12007
- return path8.join(dataDir(), "queue", "dirty.txt");
12445
+ return path10.join(dataDir(), "queue", "dirty.txt");
12008
12446
  }
12009
12447
  function appendDirtyPath(normalizedPath) {
12010
12448
  const queuePath = dirtyQueuePath();
12011
- fs5.mkdirSync(path8.dirname(queuePath), { recursive: true });
12012
- fs5.appendFileSync(queuePath, `${normalizedPath}
12449
+ const dir = path10.dirname(queuePath);
12450
+ try {
12451
+ fs6.mkdirSync(dir, { recursive: true });
12452
+ } catch (e) {
12453
+ if (e.code !== "EEXIST" || !fs6.existsSync(dir)) throw e;
12454
+ }
12455
+ fs6.appendFileSync(queuePath, `${normalizedPath}
12013
12456
  `);
12014
12457
  }
12015
12458
  function getDirtyPaths() {
12016
12459
  const queuePath = dirtyQueuePath();
12017
12460
  let raw;
12018
12461
  try {
12019
- raw = fs5.readFileSync(queuePath, "utf8");
12462
+ raw = fs6.readFileSync(queuePath, "utf8");
12020
12463
  } catch {
12021
12464
  return [];
12022
12465
  }
@@ -12032,21 +12475,21 @@ function getDirtyPaths() {
12032
12475
  }
12033
12476
  function clearDirtyQueue() {
12034
12477
  try {
12035
- fs5.rmSync(dirtyQueuePath(), { force: true });
12478
+ fs6.rmSync(dirtyQueuePath(), { force: true });
12036
12479
  } catch {
12037
12480
  }
12038
12481
  }
12039
12482
  function preCompactIndexHandler(_event) {
12040
12483
  const paths = getDirtyPaths();
12041
12484
  if (paths.length > 0) {
12042
- const sidecar = path8.join(dataDir(), "queue", "pending.txt");
12485
+ const sidecar = path10.join(dataDir(), "queue", "pending.txt");
12043
12486
  try {
12044
- fs5.mkdirSync(path8.dirname(sidecar), { recursive: true });
12487
+ fs6.mkdirSync(path10.dirname(sidecar), { recursive: true });
12045
12488
  atomicWriteBytes(sidecar, Buffer.from(`${paths.join("\n")}
12046
12489
  `, "utf8"));
12490
+ clearDirtyQueue();
12047
12491
  } catch {
12048
12492
  }
12049
- clearDirtyQueue();
12050
12493
  }
12051
12494
  return passOutput();
12052
12495
  }
@@ -12059,10 +12502,11 @@ function postEditHandler(event) {
12059
12502
  const normalized = normalizePath(filePath);
12060
12503
  recordFileEdit(normalized);
12061
12504
  appendDirtyPath(normalized);
12062
- const editedBasename = path9.basename(normalized);
12505
+ const editedBasename = path11.basename(normalized);
12063
12506
  if (/\.(md|mdx|markdown|rst)$/i.test(editedBasename)) {
12507
+ const escapedPath = normalized.replace(/`/g, "\\`");
12064
12508
  return contextOutput(
12065
- editedBasename + ' was edited. Use `token-goat section "' + normalized + '::HeadingName"` to re-read a specific section rather than the full file.'
12509
+ editedBasename + ' was edited. Use `token-goat section "' + escapedPath + '::HeadingName"` to re-read a specific section rather than the full file.'
12066
12510
  );
12067
12511
  }
12068
12512
  return passOutput();
@@ -12340,17 +12784,20 @@ init_define_import_meta_env();
12340
12784
 
12341
12785
  // src/fingerprint.ts
12342
12786
  init_define_import_meta_env();
12343
- import { createHash } from "node:crypto";
12344
- import * as fs6 from "node:fs";
12787
+ import { createHash as createHash2 } from "node:crypto";
12788
+ import * as fs7 from "node:fs";
12345
12789
  function fingerprintContent(content) {
12346
- const hash = createHash("sha256");
12790
+ const hash = createHash2("sha256");
12347
12791
  hash.update(typeof content === "string" ? Buffer.from(content, "utf-8") : content);
12348
12792
  return hash.digest("hex");
12349
12793
  }
12794
+ function shortFingerprint(content) {
12795
+ return fingerprintContent(content).slice(0, 16);
12796
+ }
12350
12797
  function fingerprintFile(filePath) {
12351
12798
  let data;
12352
12799
  try {
12353
- data = fs6.readFileSync(filePath);
12800
+ data = fs7.readFileSync(filePath);
12354
12801
  } catch {
12355
12802
  return null;
12356
12803
  }
@@ -12359,15 +12806,24 @@ function fingerprintFile(filePath) {
12359
12806
 
12360
12807
  // src/bash_output_cache.ts
12361
12808
  init_define_import_meta_env();
12362
- import * as fs7 from "fs/promises";
12809
+ import * as fs8 from "fs/promises";
12363
12810
  import { resolve as resolve3 } from "path";
12364
12811
  var _byId = /* @__PURE__ */ new Map();
12365
12812
  var _globsByKey = /* @__PURE__ */ new Map();
12366
12813
  var _grepsByKey = /* @__PURE__ */ new Map();
12367
- var GIT_MUTABLE_RE = /^\s*git\s+(diff|status)\b/i;
12368
- var LS_CMD_RE = /^\s*(?:ls|eza|exa|dir|Get-ChildItem|gci)\b/i;
12369
- var DEP_LIST_RE = /^\s*(?:npm\s+(?:-\S+\s+)*(?:ls|list)\b|pip\s+(?:-\S+\s+)*(?:list|freeze)\b|uv\s+pip\s+(?:-\S+\s+)*(?:list|freeze)\b|pnpm\s+(?:-\S+\s+)*(?:list|ls)\b|yarn\s+(?:-\S+\s+)*(?:list)\b|cargo\s+(?:-\S+\s+)*tree\b|bundle\s+(?:-\S+\s+)*(?:list|show)\b|composer\s+(?:-\S+\s+)*show\b)/i;
12370
- var NPM_INSTALL_RE = /^\s*npm\s+(?:-\S+\s+)*(?:install|ci)\b/i;
12814
+ var COMMAND_PATTERNS = {
12815
+ gitMutable: /^\s*git\s+(diff|status)\b/i,
12816
+ gitImmutable: /^\s*git\s+show\s+[0-9a-f]{40}\b/i,
12817
+ gitDiffUnscoped: /^\s*git\s+diff\b/i,
12818
+ gitDiffScoped: /\s--\s+\S/,
12819
+ dirListing: /^\s*(?:ls|eza|exa|dir|Get-ChildItem|gci)\b/i,
12820
+ depList: /^\s*(?:npm\s+(?:-\S+\s+)*(?:ls|list)\b|pip\s+(?:-\S+\s+)*(?:list|freeze)\b|uv\s+pip\s+(?:-\S+\s+)*(?:list|freeze)\b|pnpm\s+(?:-\S+\s+)*(?:list|ls)\b|yarn\s+(?:-\S+\s+)*(?:list)\b|cargo\s+(?:-\S+\s+)*tree\b|bundle\s+(?:-\S+\s+)*(?:list|show)\b|composer\s+(?:-\S+\s+)*show\b)/i,
12821
+ npmInstall: /^\s*npm\s+(?:-\S+\s+)*(?:install|ci)\b/i,
12822
+ npmAudit: /^\s*npm\s+(?:-\S+\s+)*audit\b(?!.*(?:--fix|fix)\b)/i,
12823
+ npmOutdated: /^\s*npm\s+(?:-\S+\s+)*outdated\b/i,
12824
+ envProbe: /^\s*(?:node\s+(?:-v|--version)|npm\s+(?:-v|--version)|python3?\s+(?:(?:-V)\b|--?version)|git\s+--version|uv\s+--version|go\s+version|rustc\s+--version|cargo\s+--version|java\s+--version|ruby\s+--version|gem\s+--version|php\s+--version|which\b|where\b)/i,
12825
+ npx: /^\s*npx\s+(?:--?yes\s+)?(?!.*\b(?:install|add|remove|uninstall|i|rm|update|upgrade|set|get|publish|link|ci|audit|shrinkwrap|dedupe|prune|rebuild)\b)/i
12826
+ };
12371
12827
  var DEP_LOCKFILES = {
12372
12828
  npm: ["package-lock.json", "yarn.lock"],
12373
12829
  pip: ["requirements.txt"],
@@ -12378,18 +12834,14 @@ var DEP_LOCKFILES = {
12378
12834
  bundle: ["Gemfile.lock"],
12379
12835
  composer: ["composer.lock"]
12380
12836
  };
12381
- function isGitMutableCommand(cmd) {
12382
- return GIT_MUTABLE_RE.test(cmd);
12383
- }
12384
- function isDirListingCommand(cmd) {
12385
- return LS_CMD_RE.test(cmd);
12386
- }
12387
- function isDepListCommand(cmd) {
12388
- return DEP_LIST_RE.test(cmd);
12389
- }
12390
- function isNpmInstallCommand(cmd) {
12391
- return NPM_INSTALL_RE.test(cmd);
12392
- }
12837
+ function isCommandOfType(cmd, type) {
12838
+ const pattern = COMMAND_PATTERNS[type];
12839
+ return pattern?.test(cmd) ?? false;
12840
+ }
12841
+ var isGitMutableCommand = (cmd) => isCommandOfType(cmd, "gitMutable");
12842
+ var isDirListingCommand = (cmd) => isCommandOfType(cmd, "dirListing");
12843
+ var isDepListCommand = (cmd) => isCommandOfType(cmd, "depList");
12844
+ var isNpmInstallCommand = (cmd) => isCommandOfType(cmd, "npmInstall");
12393
12845
  async function gitStateFingerprint(cwd) {
12394
12846
  try {
12395
12847
  const headResult = runGit(["rev-parse", "HEAD"], { cwd });
@@ -12397,21 +12849,21 @@ async function gitStateFingerprint(cwd) {
12397
12849
  const headSha = headResult.stdout.trim();
12398
12850
  let indexMtime = "";
12399
12851
  try {
12400
- const stat3 = await fs7.stat(resolve3(cwd, ".git", "index"));
12852
+ const stat3 = await fs8.stat(resolve3(cwd, ".git", "index"));
12401
12853
  indexMtime = stat3.mtimeMs.toString();
12402
12854
  } catch {
12403
12855
  }
12404
12856
  const key = `${headSha}\0${indexMtime}`;
12405
- return fingerprintContent(key).slice(0, 16);
12857
+ return shortFingerprint(key);
12406
12858
  } catch {
12407
12859
  return null;
12408
12860
  }
12409
12861
  }
12410
- async function dirStateFingerprint(path15) {
12862
+ async function dirStateFingerprint(path17) {
12411
12863
  try {
12412
- const stat3 = await fs7.stat(path15);
12864
+ const stat3 = await fs8.stat(path17);
12413
12865
  if (!stat3.isDirectory()) return null;
12414
- return fingerprintContent(stat3.mtimeMs.toString()).slice(0, 16);
12866
+ return shortFingerprint(stat3.mtimeMs.toString());
12415
12867
  } catch {
12416
12868
  return null;
12417
12869
  }
@@ -12425,8 +12877,8 @@ async function depLockfileFingerprint(cmd, cwd) {
12425
12877
  if (!candidates) return null;
12426
12878
  for (const lockfile of candidates) {
12427
12879
  try {
12428
- const content = await fs7.readFile(resolve3(cwd, lockfile));
12429
- return fingerprintContent(content).slice(0, 16);
12880
+ const content = await fs8.readFile(resolve3(cwd, lockfile));
12881
+ return shortFingerprint(content);
12430
12882
  } catch {
12431
12883
  continue;
12432
12884
  }
@@ -12474,7 +12926,7 @@ async function commandHash(command, cwd = null) {
12474
12926
  const fp = await depLockfileFingerprint(command, cwd);
12475
12927
  if (fp) key = `${key}\0npm-install:${fp}`;
12476
12928
  }
12477
- return fingerprintContent(key).slice(0, 16);
12929
+ return shortFingerprint(key);
12478
12930
  }
12479
12931
  function extractLsTarget(cmd, cwd) {
12480
12932
  const tokens = cmd.trim().split(/\s+/);
@@ -12508,6 +12960,10 @@ registerReset(() => {
12508
12960
  });
12509
12961
 
12510
12962
  // src/hooks_bash.ts
12963
+ function stripCdPrefix(cmd) {
12964
+ const stripped = cmd.replace(/^(?:cd\s+(?:"[^"]*"|'[^']*'|\S+)\s*&&\s*)+/, "");
12965
+ return stripped.trim() || cmd;
12966
+ }
12511
12967
  function extractCommand(event) {
12512
12968
  const cmd = event.toolInput["command"];
12513
12969
  return typeof cmd === "string" && cmd.trim() !== "" ? cmd.trim() : void 0;
@@ -12521,7 +12977,7 @@ function isOrchestratorStateFile(filePath) {
12521
12977
  return /^\.improve-state-/.test(basename7);
12522
12978
  }
12523
12979
  function extractCatSourceFile(cmd) {
12524
- const m = /^cat\s+(\S+\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj))\s*$/.exec(cmd);
12980
+ const m = /^cat\s+(\S+\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|css|scss|sass|less))\s*$/.exec(cmd);
12525
12981
  return m?.[1] ?? null;
12526
12982
  }
12527
12983
  function extractCatFile(cmd) {
@@ -12530,14 +12986,39 @@ function extractCatFile(cmd) {
12530
12986
  const filePath = m[1] ?? m[2] ?? m[3];
12531
12987
  if (filePath === void 0) return null;
12532
12988
  if (isTempPath(filePath)) return null;
12533
- const basename7 = filePath.includes("/") ? filePath.split("/").at(-1) : filePath.split("\\").at(-1) ?? filePath;
12989
+ const basename7 = (filePath.includes("/") ? filePath.split("/").at(-1) : filePath.split("\\").at(-1)) ?? filePath;
12534
12990
  const isEnvFile = /^\.env(\.\w+)?$/i.test(basename7);
12535
- const hasKnownExt = /\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|md|mdx|rst|txt|json|yaml|yml|toml|xml|conf|cfg|ini|properties|sql|env)$/i.test(filePath);
12991
+ const hasKnownExt = /\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|css|scss|sass|less|md|mdx|rst|txt|json|yaml|yml|toml|xml|conf|cfg|ini|properties|sql|env)$/i.test(filePath);
12536
12992
  if (!hasKnownExt && !isEnvFile) return null;
12537
- const isDoc = /\.(?:md|mdx|rst|txt|sql)$/i.test(filePath);
12993
+ const isSql = /\.sql$/i.test(filePath);
12994
+ const isDoc = /\.(?:md|mdx|rst|txt)$/i.test(filePath);
12538
12995
  const isEnv = isEnvFile || /\.env$/i.test(filePath);
12539
12996
  const isConfig = /\.(?:json|yaml|yml|toml|conf|cfg|ini|properties)$/i.test(filePath);
12540
- return { filePath, isDoc, isEnv, isConfig };
12997
+ return { filePath, isDoc, isEnv, isConfig, isSql };
12998
+ }
12999
+ function extractRgSymbolSearch(cmd) {
13000
+ if (!/^(?:rg|grep)\s+/.test(cmd)) return null;
13001
+ if (!/-n\b/.test(cmd)) return null;
13002
+ const patternMatch = /["']([^"']+)["']/.exec(cmd);
13003
+ const pattern = patternMatch?.[1];
13004
+ if (!pattern) return null;
13005
+ if (!/^[A-Za-z_][A-Za-z0-9_]*(\|[A-Za-z_][A-Za-z0-9_]*)*$/.test(pattern)) return null;
13006
+ const fileMatch = /(?:^|\s)(?:"([^"]+\.(?:ts|tsx|js|jsx|py|go|rs|java|rb|php|swift|kt|cpp|cc|cxx|c|h))"|'([^']+\.(?:ts|tsx|js|jsx|py|go|rs|java|rb|php|swift|kt|cpp|cc|cxx|c|h))'|([^\s"'|<>]+\.(?:ts|tsx|js|jsx|py|go|rs|java|rb|php|swift|kt|cpp|cc|cxx|c|h)))(?:\s|$|\|)/i.exec(cmd);
13007
+ if (!fileMatch) return null;
13008
+ const filePath = fileMatch[1] ?? fileMatch[2] ?? fileMatch[3];
13009
+ if (!filePath) return null;
13010
+ if (isTempPath(filePath)) return null;
13011
+ if (/-[a-zA-Z]*r[a-zA-Z]*\b/.test(cmd) || /--recursive\b/.test(cmd)) return null;
13012
+ return { filePath, identifier: pattern };
13013
+ }
13014
+ function extractCatJsonPipe(cmd) {
13015
+ const m = /^cat\s+(?:"([^"]+)"|'([^']+)'|(\S+))\s*\|\s*jq\b/.exec(cmd);
13016
+ if (!m) return null;
13017
+ const filePath = m[1] ?? m[2] ?? m[3];
13018
+ if (!filePath) return null;
13019
+ if (isTempPath(filePath)) return null;
13020
+ if (!/\.(?:json|yaml|yml|toml)$/i.test(filePath)) return null;
13021
+ return { filePath };
12541
13022
  }
12542
13023
  function extractWslCatFile(cmd) {
12543
13024
  const wslMatch = /^wsl(?:\s+-d\s+\S+)?\s+bash\s+-c\s+"cat(?:\s+(?:-[a-zA-Z]+|--[a-zA-Z-]+))*\s+\/mnt\/([a-z])\/([^"]*)"/.exec(cmd);
@@ -12547,18 +13028,44 @@ function extractWslCatFile(cmd) {
12547
13028
  if (!drive || !pathRest) return null;
12548
13029
  const filePath = drive + ":/" + pathRest;
12549
13030
  if (isTempPath(filePath)) return null;
12550
- const basename7 = filePath.includes("/") ? filePath.split("/").at(-1) : filePath.split("\\").at(-1) ?? filePath;
13031
+ const basename7 = (filePath.includes("/") ? filePath.split("/").at(-1) : filePath.split("\\").at(-1)) ?? filePath;
12551
13032
  const isEnvFile = /^\.env(\.\w+)?$/i.test(basename7);
12552
- const hasKnownExt = /\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|md|mdx|rst|txt|json|yaml|yml|toml|xml|conf|cfg|ini|properties|sql|env)$/i.test(filePath);
13033
+ const hasKnownExt = /\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|css|scss|sass|less|md|mdx|rst|txt|json|yaml|yml|toml|xml|conf|cfg|ini|properties|sql|env)$/i.test(filePath);
12553
13034
  if (!hasKnownExt && !isEnvFile) return null;
12554
- const isDoc = /\.(?:md|mdx|rst|txt|sql)$/i.test(filePath);
13035
+ const isSql = /\.sql$/i.test(filePath);
13036
+ const isDoc = /\.(?:md|mdx|rst|txt)$/i.test(filePath);
12555
13037
  const isEnv = isEnvFile || /\.env$/i.test(filePath);
12556
13038
  const isConfig = /\.(?:json|yaml|yml|toml|conf|cfg|ini|properties)$/i.test(filePath);
12557
- return { filePath, isDoc, isEnv, isConfig };
13039
+ return { filePath, isDoc, isEnv, isConfig, isSql };
12558
13040
  }
12559
13041
  function extractPythonFileRead(cmd) {
12560
13042
  if (!/python3?/.test(cmd)) return null;
12561
- const EXT = /\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|md|mdx|rst|txt|json|yaml|yml|toml|xml|conf|cfg|ini|properties)/i;
13043
+ if (/open\s*\([^)]*,\s*['"][wa]/i.test(cmd) || /\.write\s*\(/.test(cmd)) return null;
13044
+ const OPEN_EXT = /\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|md|mdx|rst|txt|json|yaml|yml|toml|xml|conf|cfg|ini|properties)/i;
13045
+ const heredocMatch = /^python3?\s+-\s+<<\s*'?(\w+)'?\s*\n([\s\S]*?)\n\1\s*$/.exec(cmd);
13046
+ if (heredocMatch) {
13047
+ const body = heredocMatch[2] ?? "";
13048
+ if (/open\s*\([^)]*,\s*['"][wa]/i.test(body) || /\.write\s*\(/.test(body) || /\.writelines\s*\(/.test(body)) return null;
13049
+ const heredocOpen = /open\s*\(\s*r?['"]([^'"]+\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|md|mdx|rst|txt|json|yaml|yml|toml|xml|conf|cfg|ini|properties))['"]/i.exec(body);
13050
+ if (heredocOpen?.[1]) {
13051
+ const filePath = heredocOpen[1];
13052
+ if (isOrchestratorStateFile(filePath)) return null;
13053
+ const isDoc = /\.(?:md|mdx|rst|txt)$/i.test(filePath);
13054
+ return { filePath, isDoc };
13055
+ }
13056
+ if (/open\s*\(/.test(body)) {
13057
+ const literal = /['"]([^'"]+\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|md|mdx|rst|txt|json|yaml|yml|toml|xml|conf|cfg|ini|properties))['"]/i.exec(body);
13058
+ if (literal?.[1]) {
13059
+ const filePath = literal[1];
13060
+ if (isOrchestratorStateFile(filePath)) return null;
13061
+ if (OPEN_EXT.test(filePath)) {
13062
+ const isDoc = /\.(?:md|mdx|rst|txt)$/i.test(filePath);
13063
+ return { filePath, isDoc };
13064
+ }
13065
+ }
13066
+ }
13067
+ return null;
13068
+ }
12562
13069
  const direct = /open\(['"]([^'"]+\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|md|mdx|rst|txt|json|yaml|yml|toml|xml|conf|cfg|ini|properties))['"]/i.exec(cmd);
12563
13070
  if (direct) {
12564
13071
  const filePath = direct[1] ?? "";
@@ -12573,7 +13080,7 @@ function extractPythonFileRead(cmd) {
12573
13080
  const filePath = literal[1] ?? "";
12574
13081
  if (filePath) {
12575
13082
  if (isOrchestratorStateFile(filePath)) return null;
12576
- if (EXT.test(filePath)) {
13083
+ if (OPEN_EXT.test(filePath)) {
12577
13084
  const isDoc = /\.(?:md|mdx|rst|txt)$/i.test(filePath);
12578
13085
  return { filePath, isDoc };
12579
13086
  }
@@ -12586,7 +13093,7 @@ function extractHeadFile(cmd) {
12586
13093
  const m = /^head(?:\s+-n\s+(\d+)|\s+-(\d+))?\s+(?:"([^"]+)"|'([^']+)'|(\S+))\s*$/.exec(cmd);
12587
13094
  if (!m) return null;
12588
13095
  const n = parseInt(m[1] ?? m[2] ?? "0", 10);
12589
- if (n > 0 && n < 10) return null;
13096
+ if (n < 10) return null;
12590
13097
  const filePath = m[3] ?? m[4] ?? m[5];
12591
13098
  if (filePath === void 0) return null;
12592
13099
  if (isTempPath(filePath)) return null;
@@ -12597,13 +13104,24 @@ function extractHeadFile(cmd) {
12597
13104
  }
12598
13105
  function extractNodeFileRead(cmd) {
12599
13106
  if (!/^node\s+-e/.test(cmd)) return null;
12600
- const m = /readFileSync\(['"]([^'"]+\.(?:ts|tsx|js|jsx|py|go|java|rs|rb|cs|md|mdx|rst|txt|json|yaml|yml|toml|xml|conf|cfg|ini|properties|sql))['"]/i.exec(cmd);
12601
- if (!m || !m[1]) return null;
12602
- const filePath = m[1];
12603
- if (isOrchestratorStateFile(filePath)) return null;
12604
- if (isTempPath(filePath)) return null;
12605
- const isDoc = /\.(?:md|mdx|rst|txt|sql)$/i.test(filePath);
12606
- return { filePath, isDoc };
13107
+ const readSync = /readFileSync\(['"]([^'"]+\.(?:ts|tsx|js|jsx|py|go|java|rs|rb|cs|md|mdx|rst|txt|json|yaml|yml|toml|xml|conf|cfg|ini|properties|sql))['"]/i.exec(cmd);
13108
+ if (readSync?.[1]) {
13109
+ const filePath = readSync[1];
13110
+ if (isOrchestratorStateFile(filePath)) return null;
13111
+ if (isTempPath(filePath)) return null;
13112
+ const isDoc = /\.(?:md|mdx|rst|txt|sql)$/i.test(filePath);
13113
+ const isConfig = /\.(?:json|yaml|yml|toml|conf|cfg|ini|properties)$/i.test(filePath);
13114
+ return { filePath, isDoc, isConfig };
13115
+ }
13116
+ const requireM = /require\(['"]([^'"]+\.json)['"]\)/i.exec(cmd);
13117
+ if (requireM?.[1]) {
13118
+ const filePath = requireM[1];
13119
+ if (filePath.includes("node_modules")) return null;
13120
+ if (isOrchestratorStateFile(filePath)) return null;
13121
+ if (isTempPath(filePath)) return null;
13122
+ return { filePath, isDoc: false, isConfig: true };
13123
+ }
13124
+ return null;
12607
13125
  }
12608
13126
  function extractTailFile(cmd) {
12609
13127
  if (/-f\b/.test(cmd)) return null;
@@ -12620,9 +13138,70 @@ function extractTailFile(cmd) {
12620
13138
  const isDoc = /\.(?:md|mdx|rst|txt|sql)$/i.test(filePath);
12621
13139
  return { filePath, isDoc };
12622
13140
  }
13141
+ function extractTasksOutput(cmd) {
13142
+ const taskOutputRe = /[/\\]tasks[/\\]([a-z0-9]+)\.output$/;
13143
+ const catM = /^cat(?:\s+(?:-[a-zA-Z]+|--[a-zA-Z-]+))*\s+(?:"([^"]+)"|'([^']+)'|(\S+))\s*$/.exec(cmd);
13144
+ if (catM) {
13145
+ const fp = catM[1] ?? catM[2] ?? catM[3];
13146
+ if (fp) {
13147
+ const m = taskOutputRe.exec(fp);
13148
+ if (m) return { id: m[1] };
13149
+ }
13150
+ }
13151
+ if (!/-f\b/.test(cmd) && !/-n\s*\+/.test(cmd)) {
13152
+ const tailM = /^tail(?:\s+-n\s+(\d+)|\s+-(\d+))?\s+(?:"([^"]+)"|'([^']+)'|(\S+))\s*$/.exec(cmd);
13153
+ if (tailM) {
13154
+ const fp = tailM[3] ?? tailM[4] ?? tailM[5];
13155
+ if (fp) {
13156
+ const m = taskOutputRe.exec(fp);
13157
+ if (m) {
13158
+ const nStr = tailM[1] ?? tailM[2];
13159
+ const n = nStr !== void 0 ? parseInt(nStr, 10) : void 0;
13160
+ return n !== void 0 ? { id: m[1], n } : { id: m[1] };
13161
+ }
13162
+ }
13163
+ }
13164
+ const byteTailM = /^tail\s+-c\s+\d+\s+(?:"([^"]+)"|'([^']+)'|(\S+))\s*$/.exec(cmd);
13165
+ if (byteTailM) {
13166
+ const fp = byteTailM[1] ?? byteTailM[2] ?? byteTailM[3];
13167
+ if (fp) {
13168
+ const m = taskOutputRe.exec(fp);
13169
+ if (m) return { id: m[1] };
13170
+ }
13171
+ }
13172
+ }
13173
+ return null;
13174
+ }
13175
+ function extractSedLineRange(cmd) {
13176
+ return /^sed\s+-n\s+['"]?\d+,\d+p['"]?/.test(cmd);
13177
+ }
13178
+ function extractDirectoryListing(cmd) {
13179
+ return /^eza\s+.*--long\s+\S+/.test(cmd) || /^eza\s+.*--tree/.test(cmd) || /^tree(\s|$)/.test(cmd) || /^ls\s+.*-[a-zA-Z]*R/.test(cmd) || /^ls\s+(?:-[la]+\s+)?(\S+)\s*[|]\s*head/.test(cmd) || /^ls\s+(?:-[la]+\s+)?(\S+)\s*[|]\s*grep/.test(cmd) || /^ls\s+(?:-[la]+\s+)?(\S+)\s*[|]\s*wc/.test(cmd);
13180
+ }
13181
+ function extractForLoopWcL(cmd) {
13182
+ return /^for\s+\w+\s+in\s+.*;\s*do\s+wc\s+-l/.test(cmd);
13183
+ }
13184
+ function extractFindCommand(cmd) {
13185
+ if (!/^find\b/.test(cmd)) return null;
13186
+ const isXargsGrepL = /[|]\s*xargs\s+(?:grep|rg)\s+.*-l\b/.test(cmd) || /[|]\s*xargs\s+grep\s+-l\b/.test(cmd);
13187
+ const nameMatch = /-name\s+['"]([^'"]+)['"]/i.exec(cmd);
13188
+ const extGlob = nameMatch ? nameMatch[1] ?? null : null;
13189
+ return { extGlob, isXargsGrepL };
13190
+ }
13191
+ function extractMarkdownHeadingGrep(cmd) {
13192
+ if (!/^(?:rg|grep)\s+/.test(cmd)) return null;
13193
+ if (!/-n\b/.test(cmd)) return null;
13194
+ const hasHeadingPattern = /["']?\^#{1,6}["']?/.test(cmd) || /["']?\^#\+["']?/.test(cmd) || /["']?\^#\\+["']?/.test(cmd);
13195
+ if (!hasHeadingPattern) return null;
13196
+ const fileMatch = /(?:^|\s)(?:"([^"]+\.(?:md|markdown))"|'([^']+\.(?:md|markdown))'|([^\s"'|<>]+\.(?:md|markdown)))\s*(?:\||$)/.exec(cmd);
13197
+ if (!fileMatch) return null;
13198
+ const filePath = fileMatch[1] ?? fileMatch[2] ?? fileMatch[3];
13199
+ if (!filePath) return null;
13200
+ return { filePath };
13201
+ }
12623
13202
  function extractRgStructuralSearch(cmd) {
12624
13203
  if (!/^(?:rg|grep)\s+/.test(cmd)) return null;
12625
- const hasStructural = /["']?\^?(?:def\s|class\s|function\s|func\s|fn\s|pub fn\s|import\s|from\s)/.test(cmd) || /["']\^(?:def|class|function|func|import|from)["']/.test(cmd) || /\\bdef\\b|\\bclass\\b/.test(cmd);
13204
+ const hasStructural = /["']?\^?(?:def\s|class\s|function\s|func\s|fn\s|pub fn\s|import\s|from\s)/.test(cmd) || /["']\^(?:def|class|function|func|import|from)["']/.test(cmd) || /\\bdef\\b|\\bclass\\b/.test(cmd) || /["']?\^[ \t]+def\b/.test(cmd);
12626
13205
  if (!hasStructural) return null;
12627
13206
  const fileMatch = /(?:^|\s)(?:"([^"]+\.(?:py|ts|tsx|js|jsx|go|rs|rb|cs|java|cpp|cc|cxx|c|h|sh|bash))"|('([^']+\.(?:py|ts|tsx|js|jsx|go|rs|rb|cs|java|cpp|cc|cxx|c|h|sh|bash))')|([^\s"']+\.(?:py|ts|tsx|js|jsx|go|rs|rb|cs|java|cpp|cc|cxx|c|h|sh|bash)))\s*$/.exec(cmd);
12628
13207
  if (!fileMatch) return null;
@@ -12631,6 +13210,37 @@ function extractRgStructuralSearch(cmd) {
12631
13210
  if (isTempPath(filePath)) return null;
12632
13211
  return { filePath };
12633
13212
  }
13213
+ function extractGrepPipeChain(cmd) {
13214
+ return /^(?:rg|grep)\b.*\|\s*(?:rg|grep)\b/.test(cmd);
13215
+ }
13216
+ function extractCurlUrl(cmd) {
13217
+ const m = /(https?:\/\/[^\s'"]+)/.exec(cmd);
13218
+ return m?.[1] ?? null;
13219
+ }
13220
+ function isCurlGetCommand(cmd) {
13221
+ if (!/^curl\b/.test(cmd)) return false;
13222
+ if (/-X\s+(?:POST|PUT|PATCH|DELETE|HEAD|OPTIONS)/i.test(cmd)) return false;
13223
+ if (/--request\s+(?:POST|PUT|PATCH|DELETE|HEAD|OPTIONS)/i.test(cmd)) return false;
13224
+ if (/\s(?:-d|--data(?:-raw|-binary|-urlencode)?|-F|--form)\b/.test(cmd)) return false;
13225
+ if (/\s(?:-u|--user)\b/.test(cmd)) return false;
13226
+ if (/-H\s+['"]?Authorization/i.test(cmd)) return false;
13227
+ return true;
13228
+ }
13229
+ function extractCurlDownload(cmd) {
13230
+ if (!/^curl\b/.test(cmd)) return null;
13231
+ const outputMatch = /(?:^|\s)(?:-o|--output)\s+(?:"([^"]+)"|'([^']+)'|(\S+))/.exec(cmd);
13232
+ if (!outputMatch) return null;
13233
+ const outputPath = outputMatch[1] ?? outputMatch[2] ?? outputMatch[3];
13234
+ if (!outputPath) return null;
13235
+ if (/-X\s+(?:POST|PUT|PATCH|DELETE|HEAD|OPTIONS)/i.test(cmd)) return null;
13236
+ if (/--request\s+(?:POST|PUT|PATCH|DELETE|HEAD|OPTIONS)/i.test(cmd)) return null;
13237
+ if (/(?:^|\s)(?:-d|--data(?:-raw|-binary|-urlencode)?|-F|--form)\b/.test(cmd)) return null;
13238
+ if (/(?:^|\s)(?:-u|--user)\b/.test(cmd)) return null;
13239
+ if (/-H\s+['"]?Authorization/i.test(cmd)) return null;
13240
+ const urlMatch = /(https?:\/\/[^\s'"]+)/.exec(cmd);
13241
+ if (!urlMatch?.[1]) return null;
13242
+ return { url: urlMatch[1], outputPath };
13243
+ }
12634
13244
  function isTscCommand(cmd) {
12635
13245
  return /^\s*tsc(\s|$)/i.test(cmd);
12636
13246
  }
@@ -12648,28 +13258,88 @@ function buildRecallHint(cmd, outputId) {
12648
13258
  return "Output from a prior `" + cmdPreview + "` run is cached. Use `token-goat bash-output " + outputId + "` (or `--tail 50`, `--grep ERROR`) to re-inspect it without re-running.";
12649
13259
  }
12650
13260
  function preBashHandler(event) {
12651
- const cmd = extractCommand(event);
12652
- if (cmd === void 0) return passOutput();
13261
+ const rawCmd = extractCommand(event);
13262
+ if (rawCmd === void 0) return passOutput();
13263
+ const cmd = stripCdPrefix(rawCmd);
13264
+ const cdStripped = cmd !== rawCmd;
13265
+ const taskOutput = extractTasksOutput(cmd);
13266
+ if (taskOutput !== null) {
13267
+ const { id } = taskOutput;
13268
+ recordStat("session_hint", 0, 0);
13269
+ return denyOutput(
13270
+ "Use `token-goat bash-output " + id + "` to recall this task output (already cached). Append `--tail <n>` or `--grep PATTERN` to slice it."
13271
+ );
13272
+ }
13273
+ const findResult = extractFindCommand(cmd);
13274
+ if (findResult !== null) {
13275
+ const { extGlob, isXargsGrepL } = findResult;
13276
+ recordStat("session_hint", 0, 0);
13277
+ if (isXargsGrepL) {
13278
+ return denyOutput(
13279
+ "`find | xargs grep -l` is a slow symbol search. Use `token-goat refs <symbol>` or `rg -l <symbol>` for faster symbol-file discovery."
13280
+ );
13281
+ }
13282
+ const fdHint = extGlob ? "Use `fd '" + extGlob + "'` for faster file discovery (respects .gitignore)." : "Use `fd` for faster file discovery (respects .gitignore).";
13283
+ return contextOutput(
13284
+ "`find` is slow and ignores .gitignore. " + fdHint + " For symbol definitions, use `token-goat symbol <Name>`."
13285
+ );
13286
+ }
13287
+ if (extractDirectoryListing(cmd)) {
13288
+ recordStat("session_hint", 0, 0);
13289
+ return contextOutput(
13290
+ "Use `token-goat map --compact` (~300 tokens) for a repo overview, or `token-goat map <dir>` for a subdirectory."
13291
+ );
13292
+ }
13293
+ if (extractForLoopWcL(cmd)) {
13294
+ recordStat("session_hint", 0, 0);
13295
+ return contextOutput(
13296
+ "Use `token-goat outline <file>` to see symbol names and line counts without loading files."
13297
+ );
13298
+ }
13299
+ if (extractSedLineRange(cmd)) {
13300
+ recordStat("session_hint", 0, 0);
13301
+ return contextOutput(
13302
+ 'Use `token-goat section "<file>::HeadingName"` to read one section instead of a line range.'
13303
+ );
13304
+ }
13305
+ const catJsonPipe = extractCatJsonPipe(cmd);
13306
+ if (catJsonPipe !== null) {
13307
+ const { filePath } = catJsonPipe;
13308
+ recordStat("session_hint", 0, 0);
13309
+ return contextOutput(
13310
+ '`cat | jq` loads the whole file. Use `token-goat config-get "' + filePath + '" KEY_NAME` or `token-goat section "' + filePath + '::sectionName"` to slice one value.'
13311
+ );
13312
+ }
12653
13313
  const catResult = extractCatFile(cmd);
12654
13314
  if (catResult !== null) {
12655
- const { filePath, isDoc, isEnv, isConfig } = catResult;
12656
- const hint = isEnv ? 'Use `token-goat config-get "' + filePath + '" KEY_NAME` to read a specific variable.' : isConfig ? 'Use `token-goat config-get "' + filePath + '" KEY_NAME` or `token-goat section "' + filePath + '::sectionName"` to read a specific value.' : isDoc ? 'Use `token-goat section "' + filePath + '::SectionHeading"` to read one section.' : 'Use `token-goat read "' + filePath + '::SymbolName"` to read one function or class.';
13315
+ const { filePath, isDoc, isEnv, isConfig, isSql } = catResult;
12657
13316
  recordStat("session_hint", 0, 0);
12658
- return denyOutput("`cat` loads the entire file into context. " + hint);
13317
+ if (isSql) {
13318
+ return contextOutput(
13319
+ '`cat` loads the entire file into context. Use `token-goat section "' + filePath + '::table_name"` to pull one CREATE TABLE / CREATE TYPE block.'
13320
+ );
13321
+ }
13322
+ const hint = isEnv ? 'Use `token-goat config-get "' + filePath + '" KEY_NAME` to read a specific variable.' : isConfig ? 'Use `token-goat config-get "' + filePath + '" KEY_NAME` or `token-goat section "' + filePath + '::sectionName"` to read a specific value.' : isDoc ? 'Use `token-goat section "' + filePath + '::SectionHeading"` to read one section.' : 'Use `token-goat read "' + filePath + '::SymbolName"` to read one function or class.';
13323
+ return cdStripped ? contextOutput("`cat` loads the entire file into context. " + hint) : denyOutput("`cat` loads the entire file into context. " + hint);
12659
13324
  }
12660
13325
  const wslCatResult = extractWslCatFile(cmd);
12661
13326
  if (wslCatResult !== null) {
12662
- const { filePath, isDoc, isEnv, isConfig } = wslCatResult;
12663
- const hint = isEnv ? 'Use `token-goat config-get "' + filePath + '" KEY_NAME` to read a specific variable.' : isConfig ? 'Use `token-goat config-get "' + filePath + '" KEY_NAME` or `token-goat section "' + filePath + '::sectionName"` to read a specific value.' : isDoc ? 'Use `token-goat section "' + filePath + '::SectionHeading"` to read one section.' : 'Use `token-goat read "' + filePath + '::SymbolName"` to read one function or class.';
13327
+ const { filePath, isDoc, isEnv, isConfig, isSql } = wslCatResult;
12664
13328
  recordStat("session_hint", 0, 0);
12665
- return denyOutput("`cat` loads the entire file into context. " + hint);
13329
+ if (isSql) {
13330
+ return contextOutput(
13331
+ '`cat` loads the entire file into context. Use `token-goat section "' + filePath + '::table_name"` to pull one CREATE TABLE / CREATE TYPE block.'
13332
+ );
13333
+ }
13334
+ const hint = isEnv ? 'Use `token-goat config-get "' + filePath + '" KEY_NAME` to read a specific variable.' : isConfig ? 'Use `token-goat config-get "' + filePath + '" KEY_NAME` or `token-goat section "' + filePath + '::sectionName"` to read a specific value.' : isDoc ? 'Use `token-goat section "' + filePath + '::SectionHeading"` to read one section.' : 'Use `token-goat read "' + filePath + '::SymbolName"` to read one function or class.';
13335
+ return cdStripped ? contextOutput("`cat` loads the entire file into context. " + hint) : denyOutput("`cat` loads the entire file into context. " + hint);
12666
13336
  }
12667
13337
  const pyRead = extractPythonFileRead(cmd);
12668
13338
  if (pyRead !== null) {
12669
13339
  const { filePath, isDoc } = pyRead;
12670
13340
  const hint = isDoc ? 'Use `token-goat section "' + filePath + '::SectionHeading"` to read one section.' : 'Use `token-goat read "' + filePath + '::SymbolName"` to extract a specific symbol.';
12671
13341
  recordStat("session_hint", 0, 0);
12672
- return denyOutput("Python `open()` file reads bypass read hooks. " + hint);
13342
+ return cdStripped ? contextOutput("Python `open()` file reads bypass read hooks. " + hint) : denyOutput("Python `open()` file reads bypass read hooks. " + hint);
12673
13343
  }
12674
13344
  const tailResult = extractTailFile(cmd);
12675
13345
  if (tailResult !== null) {
@@ -12687,10 +13357,32 @@ function preBashHandler(event) {
12687
13357
  }
12688
13358
  const nodeRead = extractNodeFileRead(cmd);
12689
13359
  if (nodeRead !== null) {
12690
- const { filePath, isDoc } = nodeRead;
12691
- const hint = isDoc ? 'Use `token-goat section "' + filePath + '::SectionHeading"` to read one section.' : 'Use `token-goat read "' + filePath + '::SymbolName"` to extract a specific symbol.';
13360
+ const { filePath, isDoc, isConfig } = nodeRead;
13361
+ const hint = isDoc ? 'Use `token-goat section "' + filePath + '::SectionHeading"` to read one section.' : isConfig ? 'Use `token-goat config-get "' + filePath + '" KEY_NAME` or `token-goat section "' + filePath + '::sectionName"` to read a specific value.' : 'Use `token-goat read "' + filePath + '::SymbolName"` to extract a specific symbol.';
12692
13362
  recordStat("session_hint", 0, 0);
12693
- return denyOutput("Node.js `fs.readFileSync()` bypasses read hooks. " + hint);
13363
+ return cdStripped ? contextOutput("Node.js `fs.readFileSync()` bypasses read hooks. " + hint) : denyOutput("Node.js `fs.readFileSync()` bypasses read hooks. " + hint);
13364
+ }
13365
+ if (extractGrepPipeChain(cmd)) {
13366
+ recordStat("session_hint", 0, 0);
13367
+ return contextOutput(
13368
+ "Collapse `grep | grep` into `rg -e PAT1 -e PAT2` (single pass). For symbol discovery: `token-goat refs <symbol>` or `token-goat semantic`."
13369
+ );
13370
+ }
13371
+ const mdHeadingGrep = extractMarkdownHeadingGrep(cmd);
13372
+ if (mdHeadingGrep !== null) {
13373
+ const { filePath } = mdHeadingGrep;
13374
+ recordStat("session_hint", 0, 0);
13375
+ return contextOutput(
13376
+ 'Use `token-goat outline "' + filePath + '"` to get all headings with line ranges \u2014 then `token-goat section "' + filePath + '::Heading"` to read one section.'
13377
+ );
13378
+ }
13379
+ const rgSymbol = extractRgSymbolSearch(cmd);
13380
+ if (rgSymbol !== null) {
13381
+ const { identifier } = rgSymbol;
13382
+ recordStat("session_hint", 0, 0);
13383
+ return contextOutput(
13384
+ "Use `token-goat symbol " + identifier + "` to jump directly to the definition without scanning the file."
13385
+ );
12694
13386
  }
12695
13387
  const rgStructural = extractRgStructuralSearch(cmd);
12696
13388
  if (rgStructural !== null) {
@@ -12702,7 +13394,7 @@ function preBashHandler(event) {
12702
13394
  }
12703
13395
  const monitoringHint = getMonitoringRecallHint(cmd);
12704
13396
  if (monitoringHint !== null) {
12705
- const monCmdHash = fingerprintContent(cmd).slice(0, 16);
13397
+ const monCmdHash = shortFingerprint(cmd);
12706
13398
  const monOutputId = getBashOutputId(monCmdHash);
12707
13399
  if (monOutputId !== null) {
12708
13400
  const monEntry = getBashOutput(monOutputId);
@@ -12721,8 +13413,32 @@ function preBashHandler(event) {
12721
13413
  );
12722
13414
  }
12723
13415
  }
13416
+ const curlDl = extractCurlDownload(cmd);
13417
+ if (curlDl !== null) {
13418
+ const prevPath = getCurlDownloadPath(curlDl.url);
13419
+ if (prevPath !== null) {
13420
+ recordStat("session_hint", 0, 0);
13421
+ return denyOutput(
13422
+ "Already downloaded to " + prevPath + " earlier this session. Use `rg '<pattern>' " + prevPath + '` to search it, or `token-goat read "' + prevPath + '::SectionName"` to read a part of it.'
13423
+ );
13424
+ }
13425
+ }
13426
+ if (isCurlGetCommand(cmd)) {
13427
+ const curlCacheKey = extractCurlUrl(cmd) ?? cmd;
13428
+ const curlHash = shortFingerprint(curlCacheKey);
13429
+ const curlOutputId = getBashOutputId(curlHash);
13430
+ if (curlOutputId !== null) {
13431
+ const curlEntry = getBashOutput(curlOutputId);
13432
+ const curlBytes = curlEntry?.sizeBytes ?? 0;
13433
+ recordStat("bash_compress:recall", curlBytes, Math.round(curlBytes / 4));
13434
+ const curlPreview = cmd.length > 60 ? cmd.slice(0, 57) + "..." : cmd;
13435
+ return contextOutput(
13436
+ "curl response cached (`" + curlPreview + "`). Use `token-goat bash-output " + curlOutputId + "` to recall it. Append `--grep PATTERN` to filter or `--section HeadingName` for a markdown section."
13437
+ );
13438
+ }
13439
+ }
12724
13440
  if (!isBuildCommand(cmd)) return passOutput();
12725
- const cmdHash = fingerprintContent(cmd).slice(0, 16);
13441
+ const cmdHash = shortFingerprint(cmd);
12726
13442
  const outputId = getBashOutputId(cmdHash);
12727
13443
  if (outputId === null) return passOutput();
12728
13444
  const entry = getBashOutput(outputId);
@@ -12745,14 +13461,20 @@ function extractBashOutput(raw) {
12745
13461
  }
12746
13462
  async function postBashHandler(event) {
12747
13463
  try {
12748
- const cmd = extractCommand(event);
12749
- if (cmd === void 0) return passOutput();
13464
+ const rawCmd = extractCommand(event);
13465
+ if (rawCmd === void 0) return passOutput();
13466
+ const cmd = stripCdPrefix(rawCmd);
13467
+ const curlDl = extractCurlDownload(cmd);
13468
+ if (curlDl !== null) {
13469
+ recordCurlDownload(curlDl.url, curlDl.outputPath);
13470
+ }
12750
13471
  const isMonitoring = getMonitoringRecallHint(cmd) !== null;
12751
- if (!isMonitoring && !isBuildCommand(cmd)) return passOutput();
13472
+ if (!isMonitoring && !isBuildCommand(cmd) && !isCurlGetCommand(cmd)) return passOutput();
12752
13473
  const output = extractBashOutput(event.raw);
12753
13474
  if (Buffer.byteLength(output, "utf-8") < MIN_CACHE_BYTES) return passOutput();
12754
13475
  const cwd = typeof event.raw["cwd"] === "string" ? event.raw["cwd"] : null;
12755
- const simpleHash = fingerprintContent(cmd).slice(0, 16);
13476
+ const cacheKey = isCurlGetCommand(cmd) ? extractCurlUrl(cmd) ?? cmd : cmd;
13477
+ const simpleHash = shortFingerprint(cacheKey);
12756
13478
  const id = await storeBashOutput(cmd, output, 0, cwd);
12757
13479
  recordBashOutput(simpleHash, id, Buffer.byteLength(output, "utf-8"));
12758
13480
  } catch {
@@ -12763,8 +13485,8 @@ registerHook("post_tool_use", postBashHandler, { toolName: "Bash" });
12763
13485
 
12764
13486
  // src/image_shrink.ts
12765
13487
  init_define_import_meta_env();
12766
- import * as fs8 from "node:fs";
12767
- import * as path10 from "node:path";
13488
+ import * as fs9 from "node:fs";
13489
+ import * as path12 from "node:path";
12768
13490
  var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
12769
13491
  ".png",
12770
13492
  ".jpg",
@@ -12791,7 +13513,7 @@ async function loadSharp() {
12791
13513
  return _sharpCache;
12792
13514
  }
12793
13515
  function isImagePath(p) {
12794
- return IMAGE_EXTENSIONS.has(path10.extname(p).toLowerCase());
13516
+ return IMAGE_EXTENSIONS.has(path12.extname(p).toLowerCase());
12795
13517
  }
12796
13518
  async function shrinkImage(input, opts) {
12797
13519
  const maxDimension = opts?.maxDimension ?? DEFAULT_MAX_DIMENSION;
@@ -12823,7 +13545,7 @@ async function shrinkImage(input, opts) {
12823
13545
  }
12824
13546
  function statSize2(absPath) {
12825
13547
  try {
12826
- const st = fs8.statSync(absPath);
13548
+ const st = fs9.statSync(absPath);
12827
13549
  return st.isFile() ? st.size : null;
12828
13550
  } catch {
12829
13551
  return null;
@@ -12837,7 +13559,7 @@ async function preReadImageHandler(event) {
12837
13559
  if (size === null || size < DEFAULT_SIZE_THRESHOLD_BYTES) return passOutput();
12838
13560
  let input;
12839
13561
  try {
12840
- input = fs8.readFileSync(filePath);
13562
+ input = fs9.readFileSync(filePath);
12841
13563
  } catch {
12842
13564
  return passOutput();
12843
13565
  }
@@ -12846,7 +13568,7 @@ async function preReadImageHandler(event) {
12846
13568
  const saved = result.originalBytes - result.shrunkBytes;
12847
13569
  const pct = Math.round(saved / result.originalBytes * 100);
12848
13570
  const dataUrl = `data:image/${result.format};base64,${result.data.toString("base64")}`;
12849
- const summary = `token-goat shrank ${path10.basename(filePath)}: ${Math.round(result.originalBytes / 1024)}kb -> ${Math.round(result.shrunkBytes / 1024)}kb (${pct}% smaller, ${result.width}x${result.height} ${result.format}).`;
13571
+ const summary = `token-goat shrank ${path12.basename(filePath)}: ${Math.round(result.originalBytes / 1024)}kb -> ${Math.round(result.shrunkBytes / 1024)}kb (${pct}% smaller, ${result.width}x${result.height} ${result.format}).`;
12850
13572
  return contextOutput(`${summary}
12851
13573
  ${dataUrl}`);
12852
13574
  }
@@ -12925,7 +13647,7 @@ async function relay(eventName) {
12925
13647
 
12926
13648
  // src/section_reader.ts
12927
13649
  init_define_import_meta_env();
12928
- import { readFileSync as readFileSync5 } from "node:fs";
13650
+ import { readFileSync as readFileSync6 } from "node:fs";
12929
13651
  function parseHeadingSpec(spec) {
12930
13652
  const m = /^(.*?)#(\d+)$/.exec(spec);
12931
13653
  if (m !== null && m[1] !== void 0 && m[2] !== void 0) {
@@ -12933,6 +13655,18 @@ function parseHeadingSpec(spec) {
12933
13655
  }
12934
13656
  return { base: spec.trim(), ordinal: null };
12935
13657
  }
13658
+ function normalizeHeading(s) {
13659
+ let n = s.replace(/[—–]/g, "-");
13660
+ n = n.replace(/\s*\([^)]+\)\s*$/, "");
13661
+ n = n.replace(/^\d+\.\s+/, "");
13662
+ return n.replace(/\s+/g, " ").trim();
13663
+ }
13664
+ function normalizeHeadingStrip(s) {
13665
+ let n = s.replace(/\s*[—–].*$/, "");
13666
+ n = n.replace(/\s*\([^)]+\)\s*$/, "");
13667
+ n = n.replace(/^\d+\.\s+/, "");
13668
+ return n.replace(/\s+/g, " ").trim();
13669
+ }
12936
13670
  var MARKDOWN_HEADER_RE = /^(#{1,6})\s+(.+?)\s*#*\s*$/;
12937
13671
  var TABLE_HEADER_RE = /^\s*\[+\s*([^\]]+?)\s*\]+\s*$/;
12938
13672
  var PYTHON_HEADER_RE = /^(\s*)(?:async\s+)?(?:def|class)\s+([A-Za-z_]\w*)/;
@@ -13007,7 +13741,7 @@ function sectionEndIndex(headers, headerPos, totalLines) {
13007
13741
  function readSection(filePath, headingSpec) {
13008
13742
  let text;
13009
13743
  try {
13010
- text = readFileSync5(filePath, "utf-8");
13744
+ text = readFileSync6(filePath, "utf-8");
13011
13745
  } catch {
13012
13746
  return null;
13013
13747
  }
@@ -13017,10 +13751,15 @@ function readSection(filePath, headingSpec) {
13017
13751
  const headers = findHeaders(text, language);
13018
13752
  const lines = text.split("\n");
13019
13753
  const target = base.toLowerCase();
13754
+ const normalizedTarget = normalizeHeading(base).toLowerCase();
13755
+ const strippedTarget = normalizeHeadingStrip(base).toLowerCase();
13020
13756
  const matches = [];
13021
13757
  for (let i = 0; i < headers.length; i++) {
13022
13758
  const h = headers[i];
13023
- if (h !== void 0 && h.heading.toLowerCase() === target) matches.push(i);
13759
+ if (h === void 0) continue;
13760
+ if (h.heading.toLowerCase() === target || normalizeHeading(h.heading).toLowerCase() === normalizedTarget || normalizeHeadingStrip(h.heading).toLowerCase() === strippedTarget) {
13761
+ matches.push(i);
13762
+ }
13024
13763
  }
13025
13764
  if (matches.length === 0) return null;
13026
13765
  const pick = ordinal === null ? 0 : ordinal - 1;
@@ -13044,9 +13783,9 @@ function readSection(filePath, headingSpec) {
13044
13783
 
13045
13784
  // src/install.ts
13046
13785
  init_define_import_meta_env();
13047
- import * as fs9 from "node:fs";
13048
- import * as os2 from "node:os";
13049
- import * as path11 from "node:path";
13786
+ import * as fs10 from "node:fs";
13787
+ import * as os3 from "node:os";
13788
+ import * as path13 from "node:path";
13050
13789
  var HOOK_EVENT_MAP = [
13051
13790
  ["PreToolUse", "pre_tool_use"],
13052
13791
  ["PostToolUse", "post_tool_use"],
@@ -13057,13 +13796,13 @@ function hookCommand(eventArg) {
13057
13796
  return `token-goat hook ${eventArg}`;
13058
13797
  }
13059
13798
  function settingsPath(scope) {
13060
- const base = scope === "user" ? path11.join(os2.homedir(), ".claude") : path11.join(process.cwd(), ".claude");
13061
- return path11.join(base, "settings.json");
13799
+ const base = scope === "user" ? path13.join(os3.homedir(), ".claude") : path13.join(process.cwd(), ".claude");
13800
+ return path13.join(base, "settings.json");
13062
13801
  }
13063
13802
  function readSettings(p) {
13064
13803
  let raw;
13065
13804
  try {
13066
- raw = fs9.readFileSync(p, "utf8");
13805
+ raw = fs10.readFileSync(p, "utf8");
13067
13806
  } catch {
13068
13807
  return {};
13069
13808
  }
@@ -13103,7 +13842,7 @@ function installHooks(scope = "user") {
13103
13842
  return { scope, settingsPath: p, alreadyInstalled: true };
13104
13843
  }
13105
13844
  settings.hooks = hooks;
13106
- fs9.mkdirSync(path11.dirname(p), { recursive: true });
13845
+ fs10.mkdirSync(path13.dirname(p), { recursive: true });
13107
13846
  atomicWriteText(p, `${JSON.stringify(settings, null, 2)}
13108
13847
  `);
13109
13848
  return { scope, settingsPath: p, alreadyInstalled: false };
@@ -13142,7 +13881,7 @@ function uninstallHooks(scope = "user") {
13142
13881
  } else {
13143
13882
  settings.hooks = hooks;
13144
13883
  }
13145
- fs9.mkdirSync(path11.dirname(p), { recursive: true });
13884
+ fs10.mkdirSync(path13.dirname(p), { recursive: true });
13146
13885
  atomicWriteText(p, `${JSON.stringify(settings, null, 2)}
13147
13886
  `);
13148
13887
  return true;
@@ -13151,21 +13890,21 @@ function uninstallHooks(scope = "user") {
13151
13890
  // src/worker.ts
13152
13891
  init_define_import_meta_env();
13153
13892
  import { spawn } from "node:child_process";
13154
- import * as fs10 from "node:fs";
13155
- import * as path12 from "node:path";
13893
+ import * as fs11 from "node:fs";
13894
+ import * as path14 from "node:path";
13156
13895
  import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads";
13157
13896
  import { fileURLToPath } from "node:url";
13158
13897
  var DEFAULT_POLL_INTERVAL_MS = 2e3;
13159
13898
  function dirtyQueuePathFor(dir) {
13160
- return path12.join(dir, "queue", "dirty.txt");
13899
+ return path14.join(dir, "queue", "dirty.txt");
13161
13900
  }
13162
13901
  function workerPidPath(dir = dataDir()) {
13163
- return path12.join(dir, "worker.pid");
13902
+ return path14.join(dir, "worker.pid");
13164
13903
  }
13165
13904
  function getDirtyPathsFor(dir) {
13166
13905
  let raw;
13167
13906
  try {
13168
- raw = fs10.readFileSync(dirtyQueuePathFor(dir), "utf8");
13907
+ raw = fs11.readFileSync(dirtyQueuePathFor(dir), "utf8");
13169
13908
  } catch {
13170
13909
  return [];
13171
13910
  }
@@ -13181,7 +13920,7 @@ function getDirtyPathsFor(dir) {
13181
13920
  }
13182
13921
  function clearDirtyQueueFor(dir) {
13183
13922
  try {
13184
- fs10.rmSync(dirtyQueuePathFor(dir), { force: true });
13923
+ fs11.rmSync(dirtyQueuePathFor(dir), { force: true });
13185
13924
  } catch {
13186
13925
  }
13187
13926
  }
@@ -13191,7 +13930,7 @@ function processDirtyBatch(paths, index = (absPath) => {
13191
13930
  }) {
13192
13931
  let indexed = 0;
13193
13932
  for (const p of paths) {
13194
- if (!fs10.existsSync(p)) continue;
13933
+ if (!p || !fs11.existsSync(p)) continue;
13195
13934
  const sha = fingerprintFile(p);
13196
13935
  if (sha === null) continue;
13197
13936
  index(p, sha);
@@ -13216,7 +13955,7 @@ function pidAlive(pid) {
13216
13955
  }
13217
13956
  function readPidFile(dir) {
13218
13957
  try {
13219
- const raw = fs10.readFileSync(workerPidPath(dir), "utf8").trim();
13958
+ const raw = fs11.readFileSync(workerPidPath(dir), "utf8").trim();
13220
13959
  if (!/^\d+$/.test(raw)) return null;
13221
13960
  return parseInt(raw, 10);
13222
13961
  } catch {
@@ -13239,7 +13978,7 @@ function stopWorker(dir = dataDir()) {
13239
13978
  }
13240
13979
  }
13241
13980
  try {
13242
- fs10.rmSync(workerPidPath(dir), { force: true });
13981
+ fs11.rmSync(workerPidPath(dir), { force: true });
13243
13982
  } catch {
13244
13983
  }
13245
13984
  return alive;
@@ -13247,7 +13986,11 @@ function stopWorker(dir = dataDir()) {
13247
13986
  function startDetachedWorker(opts) {
13248
13987
  const pollIntervalMs = opts?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
13249
13988
  const dir = opts?.dataDir ?? dataDir();
13250
- fs10.mkdirSync(dir, { recursive: true });
13989
+ try {
13990
+ fs11.mkdirSync(dir, { recursive: true });
13991
+ } catch (e) {
13992
+ if (e.code !== "EEXIST" || !fs11.existsSync(dir)) throw e;
13993
+ }
13251
13994
  const child = spawn(
13252
13995
  process.execPath,
13253
13996
  [fileURLToPath(import.meta.url), "--worker-daemon"],
@@ -13266,7 +14009,7 @@ function startDetachedWorker(opts) {
13266
14009
  if (pid === void 0) {
13267
14010
  throw new Error("startDetachedWorker: spawn produced no pid");
13268
14011
  }
13269
- fs10.writeFileSync(workerPidPath(dir), `${pid}
14012
+ fs11.writeFileSync(workerPidPath(dir), `${pid}
13270
14013
  `);
13271
14014
  child.unref();
13272
14015
  return pid;
@@ -13309,7 +14052,7 @@ workerEntry();
13309
14052
 
13310
14053
  // src/skill_cache.ts
13311
14054
  init_define_import_meta_env();
13312
- import * as fs11 from "fs/promises";
14055
+ import * as fs12 from "fs/promises";
13313
14056
  import { resolve as resolve4 } from "path";
13314
14057
  var COMPACT_END_MARKER = "<!-- COMPACT_END -->";
13315
14058
  var _skillOutputsDirOverride = null;
@@ -13322,7 +14065,7 @@ function skillOutputsDir() {
13322
14065
  }
13323
14066
  async function ensureSkillsDir() {
13324
14067
  try {
13325
- await fs11.mkdir(skillOutputsDir(), { recursive: true });
14068
+ await fs12.mkdir(skillOutputsDir(), { recursive: true });
13326
14069
  } catch {
13327
14070
  }
13328
14071
  }
@@ -13349,7 +14092,7 @@ function extractCompactFromMarker(body) {
13349
14092
  const lines = body.split("\n");
13350
14093
  for (let i = 0; i < lines.length; i++) {
13351
14094
  const stripped = lines[i].trim();
13352
- if (stripped.startsWith("```") || stripped.startsWith("~~~")) {
14095
+ if (isCodeFenceDelimiter(stripped)) {
13353
14096
  inCodeBlock = !inCodeBlock;
13354
14097
  continue;
13355
14098
  }
@@ -13364,14 +14107,14 @@ function extractCompactFromMarker(body) {
13364
14107
  async function listOutputs() {
13365
14108
  try {
13366
14109
  const dir = skillOutputsDir();
13367
- const entries = await fs11.readdir(dir, { withFileTypes: true });
14110
+ const entries = await fs12.readdir(dir, { withFileTypes: true });
13368
14111
  const metas = [];
13369
14112
  for (const entry of entries) {
13370
14113
  if (!entry.isFile() || !entry.name.endsWith(".meta")) {
13371
14114
  continue;
13372
14115
  }
13373
14116
  try {
13374
- const content = await fs11.readFile(resolve4(dir, entry.name), "utf-8");
14117
+ const content = await fs12.readFile(resolve4(dir, entry.name), "utf-8");
13375
14118
  const meta = JSON.parse(content);
13376
14119
  metas.push(meta);
13377
14120
  } catch {
@@ -13416,13 +14159,13 @@ async function listSkills(sessionId) {
13416
14159
  const compactFileId = `${safeSession}-${meta.skillName.replace(":", "_")}-compact`;
13417
14160
  let compactLen = 0;
13418
14161
  try {
13419
- const stat3 = await fs11.stat(resolve4(dir, compactFileId));
14162
+ const stat3 = await fs12.stat(resolve4(dir, compactFileId));
13420
14163
  compactLen = stat3.size;
13421
14164
  } catch {
13422
14165
  compactLen = 0;
13423
14166
  }
13424
14167
  const hasMarker = extractCompactFromMarker(
13425
- await fs11.readFile(resolve4(dir, `${meta.outputId}.txt`), "utf-8").catch(() => "")
14168
+ await fs12.readFile(resolve4(dir, `${meta.outputId}.txt`), "utf-8").catch(() => "")
13426
14169
  ) !== null;
13427
14170
  results.push({
13428
14171
  name: meta.skillName,
@@ -13454,7 +14197,7 @@ async function getSkillFilePath(skillName) {
13454
14197
 
13455
14198
  // src/config.ts
13456
14199
  init_define_import_meta_env();
13457
- import * as fs12 from "node:fs";
14200
+ import * as fs13 from "node:fs";
13458
14201
 
13459
14202
  // node_modules/smol-toml/dist/index.js
13460
14203
  init_define_import_meta_env();
@@ -14186,8 +14929,8 @@ function envInt(key, defaultVal) {
14186
14929
  }
14187
14930
 
14188
14931
  // src/config.ts
14189
- function defaultCompactAssistConfig() {
14190
- return {
14932
+ var CONFIG_DEFAULTS = {
14933
+ compact_assist: {
14191
14934
  enabled: true,
14192
14935
  triggers: ["manual", "auto"],
14193
14936
  min_events: 3,
@@ -14202,10 +14945,8 @@ function defaultCompactAssistConfig() {
14202
14945
  lazy_skill_injection: true,
14203
14946
  max_manifest_chars: 1600,
14204
14947
  harness: "auto"
14205
- };
14206
- }
14207
- function defaultBashCompressConfig() {
14208
- return {
14948
+ },
14949
+ bash_compress: {
14209
14950
  enabled: true,
14210
14951
  disabled_filters: [],
14211
14952
  max_lines: 1e3,
@@ -14215,32 +14956,22 @@ function defaultBashCompressConfig() {
14215
14956
  cache_max_file_count: 4096,
14216
14957
  cache_max_bytes: 16 * 1024 * 1024,
14217
14958
  cache_max_bytes_per_output: 50 * 1024 * 1024
14218
- };
14219
- }
14220
- function defaultBashDiffConfig() {
14221
- return {
14959
+ },
14960
+ bash_diff: {
14222
14961
  max_hunks_per_file: 10,
14223
14962
  hunk_density_cap: true
14224
- };
14225
- }
14226
- function defaultSeverityLogConfig() {
14227
- return {
14963
+ },
14964
+ bash_severity_log: {
14228
14965
  context_lines: 3,
14229
14966
  score_threshold: 0.5
14230
- };
14231
- }
14232
- function defaultCodeCompressConfig() {
14233
- return {
14967
+ },
14968
+ post_read_code_compress: {
14234
14969
  min_lines: 200
14235
- };
14236
- }
14237
- function defaultSessionBriefConfig() {
14238
- return {
14970
+ },
14971
+ session_brief: {
14239
14972
  enabled: true
14240
- };
14241
- }
14242
- function defaultSkillPreservationConfig() {
14243
- return {
14973
+ },
14974
+ skill_preservation: {
14244
14975
  enabled: true,
14245
14976
  max_cache_bytes: 5 * 1024 * 1024,
14246
14977
  orphan_sweep_enabled: true,
@@ -14252,25 +14983,19 @@ function defaultSkillPreservationConfig() {
14252
14983
  pre_skill_enabled: true,
14253
14984
  first_load_compact: false,
14254
14985
  post_compact_full_loads: false
14255
- };
14256
- }
14257
- function defaultCuratorConfig() {
14258
- return {
14986
+ },
14987
+ curator: {
14259
14988
  enabled: true,
14260
14989
  min_samples: 10,
14261
14990
  threshold_pct: 20
14262
- };
14263
- }
14264
- function defaultHintBudgetConfig() {
14265
- return {
14991
+ },
14992
+ hint_budget: {
14266
14993
  enabled: true,
14267
14994
  max_per_session: 100,
14268
14995
  max_structured_per_session: 30,
14269
14996
  max_index_only_per_session: 30
14270
- };
14271
- }
14272
- function defaultImageShrinkConfig() {
14273
- return {
14997
+ },
14998
+ image_shrink: {
14274
14999
  prefer_avif: true,
14275
15000
  avif_quality: 60,
14276
15001
  jpeg_quality: 75,
@@ -14278,27 +15003,19 @@ function defaultImageShrinkConfig() {
14278
15003
  orphan_sweep_enabled: true,
14279
15004
  orphan_age_secs: 604800,
14280
15005
  screenshot_redirect: true
14281
- };
14282
- }
14283
- function defaultRepomapConfig() {
14284
- return {
15006
+ },
15007
+ repomap: {
14285
15008
  compact_file_threshold: 50,
14286
15009
  exclude_tests: true
14287
- };
14288
- }
14289
- function defaultOverflowGuardConfig() {
14290
- return {
15010
+ },
15011
+ overflow_guard: {
14291
15012
  enabled: true,
14292
15013
  max_tokens: 25e3
14293
- };
14294
- }
14295
- function defaultStatsConfig() {
14296
- return {
15014
+ },
15015
+ stats: {
14297
15016
  record_zero_savings: false
14298
- };
14299
- }
14300
- function defaultHintsConfig() {
14301
- return {
15017
+ },
15018
+ hints: {
14302
15019
  suppress_after_ignored: 5,
14303
15020
  quiet_hours: "",
14304
15021
  json_sidecar: false,
@@ -14322,51 +15039,40 @@ function defaultHintsConfig() {
14322
15039
  truncated_read_min_lines: 200,
14323
15040
  protect_recent_reads: 4,
14324
15041
  prompt_triggers: []
14325
- };
14326
- }
14327
- function defaultHooksConfig() {
14328
- return {
15042
+ },
15043
+ hooks: {
14329
15044
  watchdog_ms: 700
14330
- };
14331
- }
14332
- function defaultWebFetchConfig() {
14333
- return {
15045
+ },
15046
+ webfetch: {
14334
15047
  allow: [],
14335
15048
  deny: [],
14336
15049
  max_file_count: 4096,
14337
15050
  max_bytes: 32 * 1024 * 1024,
14338
15051
  compress_bodies: true,
14339
15052
  compress_min_bytes: 16 * 1024
14340
- };
14341
- }
14342
- function defaultWorkerConfig() {
14343
- return {
15053
+ },
15054
+ worker: {
14344
15055
  watchdog_enabled: true,
14345
15056
  max_pool_workers: 4,
14346
15057
  blocked_roots: []
14347
- };
14348
- }
14349
- function defaultIndexingConfig() {
14350
- return {
15058
+ },
15059
+ indexing: {
14351
15060
  large_file_symbol_only_kb: 500,
14352
15061
  large_file_skip_kb: 2048,
14353
15062
  skip_dirs: []
14354
- };
14355
- }
14356
- function defaultCompressionConfig() {
14357
- return {
15063
+ },
15064
+ compression: {
14358
15065
  profile: "auto"
14359
- };
14360
- }
14361
- function defaultContextConfig() {
14362
- return {
15066
+ },
15067
+ context: {
14363
15068
  model_window_tokens: 2e5
14364
- };
14365
- }
14366
- function defaultInjectionConfig() {
14367
- return {
15069
+ },
15070
+ injection: {
14368
15071
  enabled: true
14369
- };
15072
+ }
15073
+ };
15074
+ function getDefaultConfig(section2) {
15075
+ return structuredClone(CONFIG_DEFAULTS[section2] ?? {});
14370
15076
  }
14371
15077
  function validatedBool(raw, def) {
14372
15078
  if (typeof raw === "boolean") return raw;
@@ -14439,7 +15145,7 @@ function loadConfig() {
14439
15145
  const p = configPath();
14440
15146
  let currentMtime = 0;
14441
15147
  try {
14442
- currentMtime = fs12.statSync(p).mtimeMs;
15148
+ currentMtime = fs13.statSync(p).mtimeMs;
14443
15149
  } catch {
14444
15150
  }
14445
15151
  const envFp = configEnvFingerprint();
@@ -14449,7 +15155,7 @@ function loadConfig() {
14449
15155
  let raw = {};
14450
15156
  if (currentMtime !== 0) {
14451
15157
  try {
14452
- const text = fs12.readFileSync(p, "utf8");
15158
+ const text = fs13.readFileSync(p, "utf8");
14453
15159
  raw = parse(text);
14454
15160
  } catch {
14455
15161
  }
@@ -14460,7 +15166,7 @@ function loadConfig() {
14460
15166
  }
14461
15167
  function _buildConfig(raw) {
14462
15168
  const ca_raw = section(raw, "compact_assist");
14463
- const ca = defaultCompactAssistConfig();
15169
+ const ca = getDefaultConfig("compact_assist");
14464
15170
  ca.enabled = validatedBool(ca_raw["enabled"], ca.enabled);
14465
15171
  ca.triggers = validatedStrList(ca_raw["triggers"], ca.triggers);
14466
15172
  ca.min_events = validatedInt(ca_raw["min_events"], ca.min_events, 0, 1e3);
@@ -14478,7 +15184,7 @@ function _buildConfig(raw) {
14478
15184
  ca.enabled = envBool("TOKEN_GOAT_COMPACT_ASSIST", envBool("TOKENWISE_COMPACT_ASSIST", ca.enabled));
14479
15185
  ca.lazy_skill_injection = envBool("TOKEN_GOAT_LAZY_SKILL_INJECTION", ca.lazy_skill_injection);
14480
15186
  const bc_raw = section(raw, "bash_compress");
14481
- const bc = defaultBashCompressConfig();
15187
+ const bc = getDefaultConfig("bash_compress");
14482
15188
  bc.enabled = validatedBool(bc_raw["enabled"], bc.enabled);
14483
15189
  bc.disabled_filters = validatedStrList(bc_raw["disabled_filters"], bc.disabled_filters);
14484
15190
  bc.max_lines = validatedInt(bc_raw["max_lines"], bc.max_lines, 50, 1e5);
@@ -14494,22 +15200,22 @@ function _buildConfig(raw) {
14494
15200
  bc.cache_max_bytes = envInt("TOKEN_GOAT_BASH_CACHE_MAX_BYTES", bc.cache_max_bytes);
14495
15201
  bc.cache_max_bytes_per_output = envInt("TOKEN_GOAT_BASH_CACHE_MAX_BYTES_PER_OUTPUT", bc.cache_max_bytes_per_output);
14496
15202
  const bd_raw = section(raw, "bash_diff");
14497
- const bd = defaultBashDiffConfig();
15203
+ const bd = getDefaultConfig("bash_diff");
14498
15204
  bd.max_hunks_per_file = validatedInt(bd_raw["max_hunks_per_file"], bd.max_hunks_per_file, 1, 1e4);
14499
15205
  bd.hunk_density_cap = validatedBool(bd_raw["hunk_density_cap"], bd.hunk_density_cap);
14500
15206
  const sl_raw = section(raw, "bash_severity_log");
14501
- const sl = defaultSeverityLogConfig();
15207
+ const sl = getDefaultConfig("bash_severity_log");
14502
15208
  sl.context_lines = validatedInt(sl_raw["context_lines"], sl.context_lines, 0, 100);
14503
15209
  sl.score_threshold = validatedFloat(sl_raw["score_threshold"], sl.score_threshold, 0, 1);
14504
15210
  const cc_raw = section(raw, "post_read_code_compress");
14505
- const cc = defaultCodeCompressConfig();
15211
+ const cc = getDefaultConfig("post_read_code_compress");
14506
15212
  cc.min_lines = validatedInt(cc_raw["min_lines"], cc.min_lines, 0, 1e6);
14507
15213
  const sb_raw = section(raw, "session_brief");
14508
- const sb = defaultSessionBriefConfig();
15214
+ const sb = getDefaultConfig("session_brief");
14509
15215
  sb.enabled = validatedBool(sb_raw["enabled"], sb.enabled);
14510
15216
  sb.enabled = envBool("TOKEN_GOAT_SESSION_BRIEF", sb.enabled);
14511
15217
  const sp_raw = section(raw, "skill_preservation");
14512
- const sp = defaultSkillPreservationConfig();
15218
+ const sp = getDefaultConfig("skill_preservation");
14513
15219
  sp.enabled = validatedBool(sp_raw["enabled"], sp.enabled);
14514
15220
  sp.max_cache_bytes = validatedInt(sp_raw["max_cache_bytes"], sp.max_cache_bytes, 64 * 1024, 512 * 1024 * 1024);
14515
15221
  sp.orphan_sweep_enabled = validatedBool(sp_raw["orphan_sweep_enabled"], sp.orphan_sweep_enabled);
@@ -14526,7 +15232,7 @@ function _buildConfig(raw) {
14526
15232
  sp.pre_skill_enabled = envBool("TOKEN_GOAT_PRE_SKILL", sp.pre_skill_enabled);
14527
15233
  sp.orphan_sweep_enabled = envBool("TOKEN_GOAT_ORPHAN_SWEEP", sp.orphan_sweep_enabled);
14528
15234
  const is_raw = section(raw, "image_shrink");
14529
- const is_cfg = defaultImageShrinkConfig();
15235
+ const is_cfg = getDefaultConfig("image_shrink");
14530
15236
  is_cfg.prefer_avif = validatedBool(is_raw["prefer_avif"], is_cfg.prefer_avif);
14531
15237
  is_cfg.avif_quality = validatedInt(is_raw["avif_quality"], is_cfg.avif_quality, 1, 100);
14532
15238
  is_cfg.jpeg_quality = validatedInt(is_raw["jpeg_quality"], is_cfg.jpeg_quality, 1, 100);
@@ -14537,33 +15243,33 @@ function _buildConfig(raw) {
14537
15243
  is_cfg.prefer_avif = envBool("TOKEN_GOAT_PREFER_AVIF", is_cfg.prefer_avif);
14538
15244
  is_cfg.max_image_pixels = envInt("TOKEN_GOAT_MAX_IMAGE_PIXELS", is_cfg.max_image_pixels);
14539
15245
  const cur_raw = section(raw, "curator");
14540
- const cur = defaultCuratorConfig();
15246
+ const cur = getDefaultConfig("curator");
14541
15247
  cur.enabled = validatedBool(cur_raw["enabled"], cur.enabled);
14542
15248
  cur.min_samples = validatedInt(cur_raw["min_samples"], cur.min_samples, 0, 1e4);
14543
15249
  cur.threshold_pct = validatedInt(cur_raw["threshold_pct"], cur.threshold_pct, 0, 100);
14544
15250
  cur.enabled = envBool("TOKEN_GOAT_CURATOR", cur.enabled);
14545
15251
  const hb_raw = section(raw, "hint_budget");
14546
- const hb = defaultHintBudgetConfig();
15252
+ const hb = getDefaultConfig("hint_budget");
14547
15253
  hb.enabled = validatedBool(hb_raw["enabled"], hb.enabled);
14548
15254
  hb.max_per_session = validatedInt(hb_raw["max_per_session"], hb.max_per_session, 0, 1e6);
14549
15255
  hb.max_structured_per_session = validatedInt(hb_raw["max_structured_per_session"], hb.max_structured_per_session, 0, 1e6);
14550
15256
  hb.max_index_only_per_session = validatedInt(hb_raw["max_index_only_per_session"], hb.max_index_only_per_session, 0, 1e6);
14551
15257
  hb.enabled = envBool("TOKEN_GOAT_HINT_BUDGET", hb.enabled);
14552
15258
  const rm_raw = section(raw, "repomap");
14553
- const rm = defaultRepomapConfig();
15259
+ const rm = getDefaultConfig("repomap");
14554
15260
  rm.compact_file_threshold = validatedInt(rm_raw["compact_file_threshold"], rm.compact_file_threshold, 0, 1e5);
14555
15261
  rm.exclude_tests = validatedBool(rm_raw["exclude_tests"], rm.exclude_tests);
14556
15262
  rm.compact_file_threshold = envInt("TOKEN_GOAT_REPOMAP_COMPACT_THRESHOLD", rm.compact_file_threshold);
14557
15263
  rm.exclude_tests = envBool("TOKEN_GOAT_REPOMAP_EXCLUDE_TESTS", rm.exclude_tests);
14558
15264
  const og_raw = section(raw, "overflow_guard");
14559
- const og = defaultOverflowGuardConfig();
15265
+ const og = getDefaultConfig("overflow_guard");
14560
15266
  og.enabled = validatedBool(og_raw["enabled"], og.enabled);
14561
15267
  og.max_tokens = validatedInt(og_raw["max_tokens"], og.max_tokens, 1e3, 1e6);
14562
15268
  const st_raw = section(raw, "stats");
14563
- const st = defaultStatsConfig();
15269
+ const st = getDefaultConfig("stats");
14564
15270
  st.record_zero_savings = validatedBool(st_raw["record_zero_savings"], st.record_zero_savings);
14565
15271
  const hi_raw = section(raw, "hints");
14566
- const hi = defaultHintsConfig();
15272
+ const hi = getDefaultConfig("hints");
14567
15273
  hi.suppress_after_ignored = validatedInt(hi_raw["suppress_after_ignored"], hi.suppress_after_ignored, 0, 1e3);
14568
15274
  hi.quiet_hours = validatedStr(hi_raw["quiet_hours"], hi.quiet_hours);
14569
15275
  hi.json_sidecar = validatedBool(hi_raw["json_sidecar"], hi.json_sidecar);
@@ -14596,11 +15302,11 @@ function _buildConfig(raw) {
14596
15302
  })).filter((t) => t.keywords.length > 0 && t.hint.length > 0);
14597
15303
  }
14598
15304
  const hk_raw = section(raw, "hooks");
14599
- const hk = defaultHooksConfig();
15305
+ const hk = getDefaultConfig("hooks");
14600
15306
  hk.watchdog_ms = validatedInt(hk_raw["watchdog_ms"], hk.watchdog_ms, 100, 3e4);
14601
15307
  hk.watchdog_ms = envInt("TOKEN_GOAT_HOOK_WATCHDOG_MS", hk.watchdog_ms);
14602
15308
  const wf_raw = section(raw, "webfetch");
14603
- const wf = defaultWebFetchConfig();
15309
+ const wf = getDefaultConfig("webfetch");
14604
15310
  wf.allow = validatedStrList(wf_raw["allow"], wf.allow);
14605
15311
  wf.deny = validatedStrList(wf_raw["deny"], wf.deny);
14606
15312
  wf.max_file_count = validatedInt(wf_raw["max_file_count"], wf.max_file_count, 0, 1e7);
@@ -14609,27 +15315,27 @@ function _buildConfig(raw) {
14609
15315
  wf.compress_min_bytes = validatedInt(wf_raw["compress_min_bytes"], wf.compress_min_bytes, 1024, 10 * 1024 * 1024);
14610
15316
  wf.compress_bodies = envBool("TOKEN_GOAT_WEB_COMPRESS", wf.compress_bodies);
14611
15317
  const wk_raw = section(raw, "worker");
14612
- const wk = defaultWorkerConfig();
15318
+ const wk = getDefaultConfig("worker");
14613
15319
  wk.watchdog_enabled = validatedBool(wk_raw["watchdog_enabled"], wk.watchdog_enabled);
14614
15320
  wk.max_pool_workers = validatedInt(wk_raw["max_pool_workers"], wk.max_pool_workers, 1, 8);
14615
15321
  wk.blocked_roots = validatedStrList(wk_raw["blocked_roots"], wk.blocked_roots);
14616
15322
  wk.watchdog_enabled = envBool("TOKEN_GOAT_WORKER_WATCHDOG", wk.watchdog_enabled);
14617
15323
  wk.max_pool_workers = envInt("TOKEN_GOAT_WORKER_MAX_POOL", wk.max_pool_workers);
14618
15324
  const ix_raw = section(raw, "indexing");
14619
- const ix = defaultIndexingConfig();
15325
+ const ix = getDefaultConfig("indexing");
14620
15326
  ix.large_file_symbol_only_kb = validatedInt(ix_raw["large_file_symbol_only_kb"], ix.large_file_symbol_only_kb, 1, 1048576);
14621
15327
  ix.large_file_skip_kb = validatedInt(ix_raw["large_file_skip_kb"], ix.large_file_skip_kb, 1, 1048576);
14622
15328
  ix.skip_dirs = validatedStrList(ix_raw["skip_dirs"], ix.skip_dirs);
14623
15329
  const cpr_raw = section(raw, "compression");
14624
- const cpr = defaultCompressionConfig();
15330
+ const cpr = getDefaultConfig("compression");
14625
15331
  cpr.profile = validatedStr(cpr_raw["profile"], cpr.profile);
14626
15332
  cpr.profile = envStr("TOKEN_GOAT_COMPRESS_PROFILE", cpr.profile);
14627
15333
  const ctx_raw = section(raw, "context");
14628
- const ctx = defaultContextConfig();
15334
+ const ctx = getDefaultConfig("context");
14629
15335
  ctx.model_window_tokens = validatedInt(ctx_raw["model_window_tokens"], ctx.model_window_tokens, 1e4, 1e7);
14630
15336
  ctx.model_window_tokens = envInt("TOKEN_GOAT_MODEL_WINDOW_TOKENS", ctx.model_window_tokens);
14631
15337
  const inj_raw = section(raw, "injection");
14632
- const inj = defaultInjectionConfig();
15338
+ const inj = getDefaultConfig("injection");
14633
15339
  inj.enabled = validatedBool(inj_raw["enabled"], inj.enabled);
14634
15340
  inj.enabled = envBool("TOKEN_GOAT_INJECTION_ENABLED", inj.enabled);
14635
15341
  return {
@@ -14659,9 +15365,9 @@ function _buildConfig(raw) {
14659
15365
 
14660
15366
  // src/cli_doctor.ts
14661
15367
  init_define_import_meta_env();
14662
- import * as fs13 from "fs";
14663
- import * as path13 from "path";
14664
- import { execSync } from "child_process";
15368
+ import * as fs14 from "fs";
15369
+ import * as path15 from "path";
15370
+ import { execSync, spawnSync as spawnSync2 } from "child_process";
14665
15371
  function checkWorkerRunning() {
14666
15372
  try {
14667
15373
  const output = execSync("tasklist", { encoding: "utf-8" });
@@ -14671,8 +15377,8 @@ function checkWorkerRunning() {
14671
15377
  }
14672
15378
  }
14673
15379
  function checkDbExists(dataDir2) {
14674
- const dbPath = path13.join(dataDir2, "index.db");
14675
- if (!fs13.existsSync(dbPath)) {
15380
+ const dbPath = path15.join(dataDir2, "index.db");
15381
+ if (!fs14.existsSync(dbPath)) {
14676
15382
  return {
14677
15383
  name: "Database",
14678
15384
  status: "warn",
@@ -14682,7 +15388,7 @@ function checkDbExists(dataDir2) {
14682
15388
  return {
14683
15389
  name: "Database",
14684
15390
  status: "ok",
14685
- message: `index.db exists (${Math.round(fs13.statSync(dbPath).size / 1024)} KB)`
15391
+ message: `index.db exists (${Math.round(fs14.statSync(dbPath).size / 1024)} KB)`
14686
15392
  };
14687
15393
  }
14688
15394
  function checkInstall() {
@@ -14702,7 +15408,7 @@ function checkInstall() {
14702
15408
  }
14703
15409
  }
14704
15410
  function checkConfigValid(configPath2) {
14705
- if (!fs13.existsSync(configPath2)) {
15411
+ if (!fs14.existsSync(configPath2)) {
14706
15412
  return {
14707
15413
  name: "Config",
14708
15414
  status: "warn",
@@ -14710,7 +15416,7 @@ function checkConfigValid(configPath2) {
14710
15416
  };
14711
15417
  }
14712
15418
  try {
14713
- const content = fs13.readFileSync(configPath2, "utf-8");
15419
+ const content = fs14.readFileSync(configPath2, "utf-8");
14714
15420
  JSON.parse(content);
14715
15421
  return {
14716
15422
  name: "Config",
@@ -14721,18 +15427,22 @@ function checkConfigValid(configPath2) {
14721
15427
  return {
14722
15428
  name: "Config",
14723
15429
  status: "fail",
14724
- message: `config invalid: ${err2 instanceof Error ? err2.message : "unknown error"}`
15430
+ message: `config invalid: ${extractErrorMessage(err2, "unknown error")}`
14725
15431
  };
14726
15432
  }
14727
15433
  }
14728
15434
  function checkDiskSpace(dataDir2) {
14729
15435
  try {
14730
- const statSync8 = execSync(`df -h ${dataDir2}`, { encoding: "utf-8" });
14731
- const lines = statSync8.trim().split("\n");
15436
+ const result = spawnSync2("df", ["-h", dataDir2], { encoding: "utf-8" });
15437
+ const stdout = typeof result.stdout === "string" ? result.stdout : "";
15438
+ if (result.error !== void 0 || result.status !== 0 || !stdout) {
15439
+ return { name: "Disk Space", status: "warn", message: "could not determine" };
15440
+ }
15441
+ const lines = stdout.trim().split("\n");
14732
15442
  if (lines.length < 2) {
14733
15443
  return { name: "Disk Space", status: "warn", message: "could not determine" };
14734
15444
  }
14735
- const parts = lines[1].split(/\s+/);
15445
+ const parts = lines[1].trim().split(/\s+/);
14736
15446
  const available = parts[3] || "unknown";
14737
15447
  return { name: "Disk Space", status: "ok", message: `${available} available` };
14738
15448
  } catch {
@@ -14744,9 +15454,9 @@ function runDoctor(dataDir2, configPath2) {
14744
15454
  results.push(checkInstall());
14745
15455
  results.push(checkWorkerRunning() ? { name: "Worker", status: "ok", message: "running" } : { name: "Worker", status: "warn", message: "not running" });
14746
15456
  const homeDir = process.env["HOME"] || process.env["USERPROFILE"] || "~";
14747
- const actualDataDir = dataDir2 || path13.join(homeDir, ".token-goat");
15457
+ const actualDataDir = dataDir2 || path15.join(homeDir, ".token-goat");
14748
15458
  results.push(checkDbExists(actualDataDir));
14749
- const actualConfigPath = configPath2 || path13.join(actualDataDir, "config.json");
15459
+ const actualConfigPath = configPath2 || path15.join(actualDataDir, "config.json");
14750
15460
  results.push(checkConfigValid(actualConfigPath));
14751
15461
  results.push(checkDiskSpace(actualDataDir));
14752
15462
  return results;
@@ -14785,7 +15495,7 @@ init_define_import_meta_env();
14785
15495
  var _byId2 = /* @__PURE__ */ new Map();
14786
15496
  var _urlIndex = /* @__PURE__ */ new Map();
14787
15497
  function cacheIdForUrl(url) {
14788
- return fingerprintContent(url).slice(0, 16);
15498
+ return shortFingerprint(url);
14789
15499
  }
14790
15500
  function storeWebOutput(url, content) {
14791
15501
  const cacheId = cacheIdForUrl(url);
@@ -14905,12 +15615,10 @@ async function getSectionContent(fileId, heading) {
14905
15615
  var CliError = class extends Error {
14906
15616
  };
14907
15617
  function out(text) {
14908
- process.stdout.write(text.endsWith("\n") ? text : `${text}
14909
- `);
15618
+ process.stdout.write(ensureNewline(text));
14910
15619
  }
14911
15620
  function err(text) {
14912
- process.stderr.write(text.endsWith("\n") ? text : `${text}
14913
- `);
15621
+ process.stderr.write(ensureNewline(text));
14914
15622
  }
14915
15623
  function previewLines(body, n) {
14916
15624
  return body.split(/\r?\n/).slice(0, n).join("\n");
@@ -15070,7 +15778,7 @@ function _applyFiltersAndPrint(content, opts) {
15070
15778
  const n = Number.parseInt(opts.tail, 10);
15071
15779
  return Number.isFinite(n) && n > 0 ? n : 80;
15072
15780
  })() : 80;
15073
- const applyElision = (lines2, headN2, tailN2) => lines2.length > headN2 + tailN2 ? [...lines2.slice(0, headN2), "...(elided)...", ...lines2.slice(lines2.length - tailN2)] : lines2;
15781
+ const applyElision = (lines2, headN2, tailN2) => lines2.length > headN2 + tailN2 + 1 ? [...lines2.slice(0, headN2), "...(elided)...", ...lines2.slice(lines2.length - tailN2)] : lines2;
15074
15782
  let result = lines;
15075
15783
  if (opts.head === void 0 && opts.tail === void 0 && opts.grep === void 0) {
15076
15784
  result = applyElision(lines, headN, tailN);
@@ -15085,10 +15793,21 @@ function _applyFiltersAndPrint(content, opts) {
15085
15793
  }
15086
15794
  function cmdBashOutput(id, opts) {
15087
15795
  if (opts.file !== void 0) {
15796
+ if (opts.file.includes("\0")) {
15797
+ throw new CliError("--file path contains a null byte");
15798
+ }
15799
+ if (!isWindows() && /^\/dev\/(stdin|fd\/0)$|^\/proc\/self\/fd\/0$/.test(opts.file) && process.stdin.isTTY) {
15800
+ throw new CliError("--file /dev/stdin requires piped input; redirect a file instead");
15801
+ }
15088
15802
  let content;
15089
15803
  try {
15090
- content = fs14.readFileSync(opts.file, "utf-8");
15091
- } catch {
15804
+ const st = fs15.statSync(opts.file);
15805
+ if (st.isFIFO() || st.isSocket()) {
15806
+ throw new CliError(`--file '${opts.file}' is a special file (FIFO or socket) \u2014 only regular files are supported`);
15807
+ }
15808
+ content = fs15.readFileSync(opts.file, "utf-8");
15809
+ } catch (e) {
15810
+ if (e instanceof CliError) throw e;
15092
15811
  throw new CliError(`cannot read file: ${opts.file}`);
15093
15812
  }
15094
15813
  _applyFiltersAndPrint(content, opts);
@@ -15108,12 +15827,12 @@ async function cmdSkillBody(name, opts) {
15108
15827
  if (filePath === null) {
15109
15828
  throw new CliError(`skill '${name}' not found`);
15110
15829
  }
15111
- const body = fs14.readFileSync(filePath, "utf-8");
15830
+ const body = fs15.readFileSync(filePath, "utf-8");
15112
15831
  if (opts.compact === true) {
15113
15832
  const lines = body.split("\n");
15114
15833
  const end = lines.findIndex((l) => l.includes("COMPACT_END"));
15115
15834
  if (end !== -1) {
15116
- out(lines.slice(0, end).join("\n"));
15835
+ out(lines.slice(end + 1).join("\n"));
15117
15836
  } else {
15118
15837
  out(body);
15119
15838
  }
@@ -15126,7 +15845,7 @@ async function cmdSkillCompact(name) {
15126
15845
  if (filePath === null) {
15127
15846
  throw new CliError(`skill '${name}' not found`);
15128
15847
  }
15129
- const body = fs14.readFileSync(filePath, "utf-8");
15848
+ const body = fs15.readFileSync(filePath, "utf-8");
15130
15849
  const sessionFiles = getSessionFiles();
15131
15850
  const sessionId = Array.from(sessionFiles.keys())[0] ?? "default";
15132
15851
  await storeCompact(sessionId, name, body);
@@ -15213,7 +15932,7 @@ function cmdConfigGet(file, key) {
15213
15932
  }
15214
15933
  function atomicWriteBuffer(dest, data) {
15215
15934
  try {
15216
- if (fs14.statSync(dest).isDirectory()) {
15935
+ if (fs15.statSync(dest).isDirectory()) {
15217
15936
  const e = Object.assign(new Error(`EISDIR: illegal operation on a directory, open '${dest}'`), { code: "EISDIR", path: dest });
15218
15937
  throw e;
15219
15938
  }
@@ -15221,16 +15940,16 @@ function atomicWriteBuffer(dest, data) {
15221
15940
  if (e.code !== "ENOENT") throw e;
15222
15941
  }
15223
15942
  const rnd = Math.random().toString(36).slice(2, 8);
15224
- const tmp = path14.join(path14.dirname(path14.resolve(dest)), `.tmp.${process.pid}.${rnd}`);
15943
+ const tmp = path16.join(path16.dirname(path16.resolve(dest)), `.tmp.${process.pid}.${rnd}`);
15225
15944
  try {
15226
- fs14.writeFileSync(tmp, data, { mode: 384 });
15945
+ fs15.writeFileSync(tmp, data, { mode: 384 });
15227
15946
  try {
15228
- fs14.renameSync(tmp, dest);
15947
+ fs15.renameSync(tmp, dest);
15229
15948
  } catch (e) {
15230
15949
  if (e.code === "EXDEV") {
15231
- fs14.copyFileSync(tmp, dest);
15950
+ fs15.copyFileSync(tmp, dest);
15232
15951
  try {
15233
- fs14.unlinkSync(tmp);
15952
+ fs15.unlinkSync(tmp);
15234
15953
  } catch (ue) {
15235
15954
  process.stderr.write(`token-goat write-file: warning: could not remove temp file ${tmp}: ${ue.message}
15236
15955
  `);
@@ -15241,7 +15960,7 @@ function atomicWriteBuffer(dest, data) {
15241
15960
  }
15242
15961
  } catch (e) {
15243
15962
  try {
15244
- fs14.unlinkSync(tmp);
15963
+ fs15.unlinkSync(tmp);
15245
15964
  } catch {
15246
15965
  }
15247
15966
  throw e;
@@ -15251,9 +15970,9 @@ function mapFsError(e, src, dest) {
15251
15970
  const fe = e;
15252
15971
  if (fe.code === "ENOENT") {
15253
15972
  const errPath = fe.path ?? "";
15254
- const isSource = src !== void 0 && path14.resolve(errPath) === path14.resolve(src);
15973
+ const isSource = src !== void 0 && path16.resolve(errPath) === path16.resolve(src);
15255
15974
  if (isSource) throw new CliError(`source file not found: ${src}`);
15256
- const destDir = dest ? path14.dirname(path14.resolve(dest)) : path14.dirname(path14.resolve(errPath || "."));
15975
+ const destDir = dest ? path16.dirname(path16.resolve(dest)) : path16.dirname(path16.resolve(errPath || "."));
15257
15976
  throw new CliError(`destination directory does not exist: ${destDir}`);
15258
15977
  }
15259
15978
  if (fe.code === "ENOTDIR") {
@@ -15261,7 +15980,7 @@ function mapFsError(e, src, dest) {
15261
15980
  }
15262
15981
  if (fe.code === "EISDIR") {
15263
15982
  const errPath = fe.path ?? "";
15264
- const isSource = src !== void 0 && (errPath === "" || path14.resolve(errPath) === path14.resolve(src));
15983
+ const isSource = src !== void 0 && (errPath === "" || path16.resolve(errPath) === path16.resolve(src));
15265
15984
  if (isSource) throw new CliError(`source is a directory, not a file: ${src}`);
15266
15985
  throw new CliError(`destination is a directory, not a file: ${dest ?? (errPath || "(unknown)")}`);
15267
15986
  }
@@ -15326,8 +16045,8 @@ function cmdWriteFile(dest, opts) {
15326
16045
  if (dest.includes("\0")) {
15327
16046
  throw new CliError("destination path contains a null byte");
15328
16047
  }
15329
- if (process.platform === "win32") {
15330
- const base = path14.basename(dest);
16048
+ if (isWindows()) {
16049
+ const base = path16.basename(dest);
15331
16050
  const stem = base.replace(/\.[^.]*$/, "").toUpperCase();
15332
16051
  if (WIN_RESERVED.has(stem)) {
15333
16052
  throw new CliError(`destination '${base}' is a reserved Windows device name`);
@@ -15346,20 +16065,23 @@ function cmdWriteFile(dest, opts) {
15346
16065
  if (opts.from.includes("\0")) {
15347
16066
  throw new CliError("--from path contains a null byte");
15348
16067
  }
15349
- if (process.platform !== "win32" && /^\/dev\/(stdin|fd\/0)$|^\/proc\/self\/fd\/0$/.test(opts.from) && process.stdin.isTTY) {
16068
+ if (!isWindows() && /^\/dev\/(stdin|fd\/0)$|^\/proc\/self\/fd\/0$/.test(opts.from) && process.stdin.isTTY) {
15350
16069
  throw new CliError("--from /dev/stdin requires piped input; use piped stdin mode or --b64 for interactive use");
15351
16070
  }
15352
16071
  try {
15353
- const st = fs14.statSync(opts.from);
16072
+ const st = fs15.statSync(opts.from);
15354
16073
  if (st.isFIFO() || st.isSocket()) {
15355
16074
  throw new CliError(`--from '${opts.from}' is a special file (FIFO or socket) \u2014 only regular files are supported`);
15356
16075
  }
15357
16076
  const maxFromMB = parseInt(process.env["TOKEN_GOAT_MAX_STDIN_MB"] ?? "512", 10);
15358
- const maxFromBytes = (Number.isFinite(maxFromMB) && maxFromMB > 0 ? maxFromMB : 512) * 1024 * 1024;
16077
+ if (!Number.isFinite(maxFromMB) || maxFromMB <= 0) {
16078
+ throw new CliError(`TOKEN_GOAT_MAX_STDIN_MB must be a positive integer; got '${process.env["TOKEN_GOAT_MAX_STDIN_MB"] ?? ""}'`);
16079
+ }
16080
+ const maxFromBytes = maxFromMB * 1024 * 1024;
15359
16081
  if (st.size > maxFromBytes) {
15360
16082
  throw new CliError(`--from source exceeds size limit (${Math.round(st.size / 1024 / 1024)} MB); set TOKEN_GOAT_MAX_STDIN_MB to override`);
15361
16083
  }
15362
- atomicWriteBuffer(dest, fs14.readFileSync(opts.from));
16084
+ atomicWriteBuffer(dest, fs15.readFileSync(opts.from));
15363
16085
  } catch (e) {
15364
16086
  if (e instanceof CliError) throw e;
15365
16087
  mapFsError(e, opts.from, dest);
@@ -15372,7 +16094,10 @@ function cmdWriteFile(dest, opts) {
15372
16094
  throw new CliError("--b64 payload contains only whitespace \u2014 likely a shell expansion error; pass an empty string explicitly for a zero-byte file");
15373
16095
  }
15374
16096
  const maxB64MB = parseInt(process.env["TOKEN_GOAT_MAX_STDIN_MB"] ?? "512", 10);
15375
- const maxB64Bytes = (Number.isFinite(maxB64MB) && maxB64MB > 0 ? maxB64MB : 512) * 1024 * 1024;
16097
+ if (!Number.isFinite(maxB64MB) || maxB64MB <= 0) {
16098
+ throw new CliError(`TOKEN_GOAT_MAX_STDIN_MB must be a positive integer; got '${process.env["TOKEN_GOAT_MAX_STDIN_MB"] ?? ""}'`);
16099
+ }
16100
+ const maxB64Bytes = maxB64MB * 1024 * 1024;
15376
16101
  const decodedSize = Math.floor(normalized.replace(/=+$/, "").length * 3 / 4);
15377
16102
  if (decodedSize > maxB64Bytes) {
15378
16103
  throw new CliError(`--b64 payload would decode to ${Math.round(decodedSize / 1024 / 1024)} MB which exceeds size limit; set TOKEN_GOAT_MAX_STDIN_MB to override`);
@@ -15473,7 +16198,7 @@ function buildProgram() {
15473
16198
  await fn(...args);
15474
16199
  process.exitCode = 0;
15475
16200
  } catch (e) {
15476
- const msg = e instanceof Error ? e.message : String(e);
16201
+ const msg = extractErrorMessage(e);
15477
16202
  err(`token-goat: ${msg}`);
15478
16203
  process.exitCode = 1;
15479
16204
  }
@@ -15525,7 +16250,7 @@ async function run(argv = process.argv) {
15525
16250
  process.exitCode = 1;
15526
16251
  return;
15527
16252
  }
15528
- const msg = e instanceof Error ? e.message : String(e);
16253
+ const msg = extractErrorMessage(e);
15529
16254
  err(`token-goat: ${msg}`);
15530
16255
  process.exitCode = 1;
15531
16256
  }