token-goat 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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.0";
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
@@ -10874,7 +11110,7 @@ function dispatchFileTypeHandler(filePath, content, contentLengthHint) {
10874
11110
 
10875
11111
  // src/stats.ts
10876
11112
  init_define_import_meta_env();
10877
- import * as path6 from "node:path";
11113
+ import * as path8 from "node:path";
10878
11114
 
10879
11115
  // src/render/stats_renderer.ts
10880
11116
  init_define_import_meta_env();
@@ -11654,7 +11890,7 @@ CREATE INDEX IF NOT EXISTS idx_stats_kind ON stats(kind);
11654
11890
  var _globalSchemaApplied = /* @__PURE__ */ new Set();
11655
11891
  registerReset(() => _globalSchemaApplied.clear());
11656
11892
  function getGlobalDb() {
11657
- const dbPath = path6.join(dataDir(), "global.db");
11893
+ const dbPath = path8.join(dataDir(), "global.db");
11658
11894
  const db = getDb(dbPath);
11659
11895
  if (!_globalSchemaApplied.has(dbPath)) {
11660
11896
  db.exec(GLOBAL_SCHEMA_SQL);
@@ -11688,7 +11924,9 @@ function summarize(windowDays = 30, testDb) {
11688
11924
  const bytesSaved = row.bytes_saved ?? 0;
11689
11925
  const tokensSaved = row.tokens_saved ?? 0;
11690
11926
  const kind = row.kind;
11691
- const ts = row.ts;
11927
+ const tsRaw = row.ts;
11928
+ if (tsRaw === void 0) continue;
11929
+ const ts = tsRaw;
11692
11930
  totalEvents += 1;
11693
11931
  totalBytes += bytesSaved;
11694
11932
  totalTokens += tokensSaved;
@@ -11851,22 +12089,59 @@ function isTsConfigFile(basename7) {
11851
12089
  var LARGE_FILE_BYTES = 100 * 1024;
11852
12090
  var REREAD_DENY_BYTES = 50 * 1024;
11853
12091
  var LARGE_FILE_DENY_BYTES = 500 * 1024;
11854
- function isNodeModulesPath(path15) {
11855
- const isWindows = process.platform === "win32";
11856
- const check = isWindows ? path15.toLowerCase() : path15;
12092
+ function isNodeModulesPath(p) {
12093
+ const check = isWindows() ? p.toLowerCase() : p;
11857
12094
  return check.includes("/node_modules/") || check.includes("\\node_modules\\");
11858
12095
  }
11859
12096
  function _isDocFile(filePath) {
11860
12097
  const lower = filePath.toLowerCase();
11861
12098
  return lower.endsWith(".md") || lower.endsWith(".mdx") || lower.endsWith(".markdown") || lower.endsWith(".rst");
11862
12099
  }
12100
+ function isSessionArtifactFile(filePath) {
12101
+ if (/[/\\]tasks[/\\][a-z0-9]+\.output$/i.test(filePath)) return true;
12102
+ if (/[/\\]tool-results[/\\][a-z0-9]+\.txt$/i.test(filePath)) return true;
12103
+ return false;
12104
+ }
11863
12105
  function statSize(absPath) {
11864
12106
  try {
11865
- return fs4.statSync(absPath).size;
12107
+ return fs5.statSync(absPath).size;
11866
12108
  } catch {
11867
12109
  return null;
11868
12110
  }
11869
12111
  }
12112
+ function buildLineDiff(oldContent, newContent, label) {
12113
+ const oldLines = oldContent.split("\n");
12114
+ const newLines = newContent.split("\n");
12115
+ let prefix = 0;
12116
+ while (prefix < oldLines.length && prefix < newLines.length && oldLines[prefix] === newLines[prefix]) {
12117
+ prefix++;
12118
+ }
12119
+ let oldSuffix = oldLines.length;
12120
+ let newSuffix = newLines.length;
12121
+ while (oldSuffix > prefix && newSuffix > prefix && oldLines[oldSuffix - 1] === newLines[newSuffix - 1]) {
12122
+ oldSuffix--;
12123
+ newSuffix--;
12124
+ }
12125
+ if (prefix === oldLines.length && prefix === newLines.length) return "";
12126
+ const changedOld = oldLines.slice(prefix, oldSuffix);
12127
+ const changedNew = newLines.slice(prefix, newSuffix);
12128
+ const MAX_LINES = 50;
12129
+ const out2 = [
12130
+ `--- ${label} (prev)`,
12131
+ `+++ ${label} (current)`,
12132
+ `@@ -${prefix + 1},${changedOld.length} +${prefix + 1},${changedNew.length} @@`
12133
+ ];
12134
+ const removedLines = changedOld.map((l) => `-${l}`);
12135
+ const addedLines = changedNew.map((l) => `+${l}`);
12136
+ const allChanges = [...removedLines, ...addedLines];
12137
+ if (allChanges.length <= MAX_LINES) {
12138
+ out2.push(...allChanges);
12139
+ } else {
12140
+ out2.push(...allChanges.slice(0, MAX_LINES));
12141
+ out2.push(`... (${allChanges.length - MAX_LINES} more changed lines)`);
12142
+ }
12143
+ return out2.join("\n");
12144
+ }
11870
12145
  function preReadHandler(event) {
11871
12146
  const filePath = getFilePath(event);
11872
12147
  if (filePath === void 0) return passOutput();
@@ -11876,7 +12151,7 @@ function preReadHandler(event) {
11876
12151
  "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
12152
  );
11878
12153
  }
11879
- const basename7 = path7.basename(normalized);
12154
+ const basename7 = path9.basename(normalized);
11880
12155
  if (isLockFile(basename7)) {
11881
12156
  return denyOutput(
11882
12157
  '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 +12190,7 @@ function preReadHandler(event) {
11915
12190
  try {
11916
12191
  const sz = statSize(normalized);
11917
12192
  if (sz !== null && sz >= MARKDOWN_SIZE_THRESHOLD) {
11918
- fileContent = fs4.readFileSync(normalized, "utf8");
12193
+ fileContent = fs5.readFileSync(normalized, "utf8");
11919
12194
  }
11920
12195
  } catch {
11921
12196
  }
@@ -11931,6 +12206,22 @@ function preReadHandler(event) {
11931
12206
  }
11932
12207
  }
11933
12208
  }
12209
+ const isMemoryMd = normalized.toLowerCase().includes("memory/memory.md") || /[/\\]memory[/\\][^/\\]+\.md$/i.test(normalized);
12210
+ if (isMemoryMd && wasFileReadThisSession(normalized)) {
12211
+ recordFileRead(normalized);
12212
+ recordStat("session_hint", 0, 0);
12213
+ const isMainMemory = basename7.toLowerCase() === "memory.md";
12214
+ return denyOutput(
12215
+ 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.'
12216
+ );
12217
+ }
12218
+ if (/^\.improve-state-.*\.json$/.test(basename7) && wasFileReadThisSession(normalized)) {
12219
+ recordFileRead(normalized);
12220
+ recordStat("session_hint", 0, 0);
12221
+ return denyOutput(
12222
+ "Orchestrator state already read this session. Use `token-goat bash-output <id>` or recall it from the compact manifest."
12223
+ );
12224
+ }
11934
12225
  if (/^\.env(\.\w+)?$/.test(basename7) && wasFileReadThisSession(normalized)) {
11935
12226
  recordFileRead(normalized);
11936
12227
  recordStat("session_hint", 0, 0);
@@ -11938,14 +12229,124 @@ function preReadHandler(event) {
11938
12229
  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
12230
  );
11940
12231
  }
12232
+ if (isSessionArtifactFile(normalized)) {
12233
+ if (wasFileReadThisSession(normalized)) {
12234
+ if (wasFileTruncatedThisSession(normalized)) {
12235
+ recordFileRead(normalized);
12236
+ recordStat("session_hint", 0, 0);
12237
+ return denyOutput(
12238
+ "File was truncated on last read. Use `token-goat bash-output --tail N` or `--grep PATTERN` to read a slice."
12239
+ );
12240
+ }
12241
+ const artifactSessionId = getSessionId();
12242
+ const oldArtifactSnap = load(artifactSessionId, normalized);
12243
+ if (oldArtifactSnap !== null) {
12244
+ try {
12245
+ const sz = statSize(normalized);
12246
+ if (sz !== null && sz <= 256 * 1024) {
12247
+ const currentContent = fs5.readFileSync(normalized, "utf8");
12248
+ const TRUNC_MARKER = "\n<snapshot truncated at ";
12249
+ const oldRaw = oldArtifactSnap.toString("utf8");
12250
+ const truncIdx = oldRaw.indexOf(TRUNC_MARKER);
12251
+ const oldContent = truncIdx >= 0 ? oldRaw.slice(0, truncIdx) : oldRaw;
12252
+ if (oldContent === currentContent) {
12253
+ recordFileRead(normalized);
12254
+ recordStat("session_hint", 0, 0);
12255
+ return denyOutput(
12256
+ basename7 + " is unchanged since last read. Use `token-goat bash-output --tail N` or `--grep PATTERN` to read a slice."
12257
+ );
12258
+ }
12259
+ const diff = buildLineDiff(oldContent, currentContent, basename7);
12260
+ if (diff !== "") {
12261
+ recordFileRead(normalized);
12262
+ const savedBytes = Math.max(0, currentContent.length - diff.length);
12263
+ recordStat("session_hint", savedBytes, Math.round(savedBytes / 4));
12264
+ return denyOutput(
12265
+ "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."
12266
+ );
12267
+ }
12268
+ }
12269
+ } catch {
12270
+ }
12271
+ }
12272
+ recordFileRead(normalized);
12273
+ recordStat("session_hint", 0, 0);
12274
+ return denyOutput(
12275
+ normalized + " was already read this session. Use `token-goat bash-output --tail N` or `--grep PATTERN` to read a slice."
12276
+ );
12277
+ }
12278
+ if (/[/\\]tasks[/\\][a-z0-9]+\.output$/i.test(normalized)) {
12279
+ recordFileRead(normalized);
12280
+ return contextOutput(
12281
+ "Session transcript: use `token-goat bash-output --tail N` or `--grep PATTERN` to read a slice instead of the full file."
12282
+ );
12283
+ }
12284
+ }
12285
+ if (/\.(md|mdx|rst|txt)$/i.test(basename7) && wasFileReadThisSession(normalized)) {
12286
+ if (wasFileTruncatedThisSession(normalized)) {
12287
+ recordFileRead(normalized);
12288
+ recordStat("session_hint", 0, 0);
12289
+ return denyOutput(
12290
+ 'File was truncated on last read (>33K tokens). Use `token-goat skeleton "' + normalized + '"` for structure or `token-goat read "' + normalized + '::SymbolName"` for one function.'
12291
+ );
12292
+ }
12293
+ const sessionId = getSessionId();
12294
+ const oldSnap = load(sessionId, normalized);
12295
+ if (oldSnap !== null) {
12296
+ try {
12297
+ const sz = statSize(normalized);
12298
+ if (sz !== null && sz <= 256 * 1024) {
12299
+ const currentContent = fs5.readFileSync(normalized, "utf8");
12300
+ const TRUNC_MARKER = "\n<snapshot truncated at ";
12301
+ const oldRaw = oldSnap.toString("utf8");
12302
+ const truncIdx = oldRaw.indexOf(TRUNC_MARKER);
12303
+ const oldContent = truncIdx >= 0 ? oldRaw.slice(0, truncIdx) : oldRaw;
12304
+ if (oldContent === currentContent) {
12305
+ recordFileRead(normalized);
12306
+ recordStat("session_hint", 0, 0);
12307
+ return denyOutput(
12308
+ basename7 + ' is unchanged since last read. Use `token-goat section "' + normalized + '::HeadingName"` to extract a part.'
12309
+ );
12310
+ }
12311
+ const diff = buildLineDiff(oldContent, currentContent, basename7);
12312
+ if (diff !== "") {
12313
+ recordFileRead(normalized);
12314
+ const savedBytes = Math.max(0, currentContent.length - diff.length);
12315
+ recordStat("session_hint", savedBytes, Math.round(savedBytes / 4));
12316
+ return denyOutput(
12317
+ "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.'
12318
+ );
12319
+ }
12320
+ }
12321
+ } catch {
12322
+ }
12323
+ }
12324
+ }
11941
12325
  if (wasFileReadThisSession(normalized)) {
11942
12326
  const entry = getSessionFiles().get(normalized);
11943
12327
  const reads = entry?.readCount ?? 1;
11944
12328
  const plural = reads === 1 ? "read" : "reads";
11945
12329
  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
12330
  const rereadBytes = statSize(normalized) ?? 0;
11948
12331
  recordStat("session_hint", rereadBytes, Math.round(rereadBytes / 4));
12332
+ if (wasFileTruncatedThisSession(normalized)) {
12333
+ return denyOutput(
12334
+ 'File was truncated on last read (>33K tokens). Use `token-goat skeleton "' + normalized + '"` for structure or `token-goat read "' + normalized + '::SymbolName"` for one function.'
12335
+ );
12336
+ }
12337
+ if (/\.(md|mdx)$/i.test(basename7)) {
12338
+ return denyOutput(
12339
+ 'Markdown file already read this session. Use `token-goat section "' + normalized + '::HeadingName"` to read one section.'
12340
+ );
12341
+ }
12342
+ const isSourceExt = /\.(ts|tsx|js|jsx|py|go|rs|java|rb|php|swift|kt|cpp|c|h)$/i.test(basename7);
12343
+ if (isSourceExt && reads >= 2) {
12344
+ recordStat("read_count_deny", rereadBytes, Math.round(rereadBytes / 4));
12345
+ return denyOutput(
12346
+ "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."
12347
+ );
12348
+ }
12349
+ 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
12350
  if (rereadBytes >= REREAD_DENY_BYTES || reads >= 2) {
11950
12351
  return denyOutput(
11951
12352
  normalized + " was already read this session (" + reads + " " + plural + "). " + hint
@@ -11967,10 +12368,10 @@ function preReadHandler(event) {
11967
12368
  );
11968
12369
  }
11969
12370
  return contextOutput(
11970
- "Note: " + normalized + " is large (" + kb + "kb). " + hint
12371
+ "Note: " + normalized + " is large (" + kb + "KB). " + hint
11971
12372
  );
11972
12373
  }
11973
- const fileTypeExt = path7.extname(normalized).slice(1).toLowerCase();
12374
+ const fileTypeExt = path9.extname(normalized).slice(1).toLowerCase();
11974
12375
  const binaryExts = /* @__PURE__ */ new Set(["pdf", "docx", "xlsx", "pptx", "odt", "ods", "ott", "odp"]);
11975
12376
  const textTypeExts = /* @__PURE__ */ new Set(["html", "htm", "xhtml", "txt", "log", "out", "err", "trace", "csv", "tsv"]);
11976
12377
  const fileStatSize = size ?? statSize(normalized) ?? 0;
@@ -11979,7 +12380,7 @@ function preReadHandler(event) {
11979
12380
  let ftContent = "";
11980
12381
  if (!binaryExts.has(fileTypeExt)) {
11981
12382
  try {
11982
- ftContent = fs4.readFileSync(normalized, "utf8");
12383
+ ftContent = fs5.readFileSync(normalized, "utf8");
11983
12384
  } catch {
11984
12385
  }
11985
12386
  }
@@ -11994,29 +12395,67 @@ function preReadHandler(event) {
11994
12395
  }
11995
12396
  registerHook("pre_tool_use", preReadHandler, { toolName: "Read" });
11996
12397
  registerHook("pre_tool_use", preReadHandler, { toolName: "Grep" });
12398
+ function extractReadOutput(raw) {
12399
+ const resp = raw["tool_response"];
12400
+ if (typeof resp === "string") return resp;
12401
+ if (resp !== null && typeof resp === "object") {
12402
+ const r = resp;
12403
+ for (const key of ["output", "content", "text", "body"]) {
12404
+ if (typeof r[key] === "string") return r[key];
12405
+ }
12406
+ }
12407
+ return "";
12408
+ }
12409
+ function postReadHandler(event) {
12410
+ const filePath = getFilePath(event);
12411
+ if (filePath === void 0) return passOutput();
12412
+ const normalized = normalizePath(filePath);
12413
+ const respText = extractReadOutput(event.raw);
12414
+ if (respText.includes("[Truncated:") || respText.includes("Truncated: PARTIAL view")) {
12415
+ markFileTruncated(normalized);
12416
+ }
12417
+ const postBasename = path9.basename(normalized);
12418
+ if (/\.(md|mdx|rst|txt)$/i.test(postBasename) || isSessionArtifactFile(normalized)) {
12419
+ try {
12420
+ const sz = statSize(normalized);
12421
+ if (sz !== null && sz <= 256 * 1024) {
12422
+ const content = fs5.readFileSync(normalized);
12423
+ store(getSessionId(), normalized, content);
12424
+ }
12425
+ } catch {
12426
+ }
12427
+ }
12428
+ return passOutput();
12429
+ }
12430
+ registerHook("post_tool_use", postReadHandler, { toolName: "Read" });
11997
12431
 
11998
12432
  // src/hooks_edit.ts
11999
12433
  init_define_import_meta_env();
12000
- import * as path9 from "node:path";
12434
+ import * as path11 from "node:path";
12001
12435
 
12002
12436
  // src/hooks_index.ts
12003
12437
  init_define_import_meta_env();
12004
- import * as fs5 from "node:fs";
12005
- import * as path8 from "node:path";
12438
+ import * as fs6 from "node:fs";
12439
+ import * as path10 from "node:path";
12006
12440
  function dirtyQueuePath() {
12007
- return path8.join(dataDir(), "queue", "dirty.txt");
12441
+ return path10.join(dataDir(), "queue", "dirty.txt");
12008
12442
  }
12009
12443
  function appendDirtyPath(normalizedPath) {
12010
12444
  const queuePath = dirtyQueuePath();
12011
- fs5.mkdirSync(path8.dirname(queuePath), { recursive: true });
12012
- fs5.appendFileSync(queuePath, `${normalizedPath}
12445
+ const dir = path10.dirname(queuePath);
12446
+ try {
12447
+ fs6.mkdirSync(dir, { recursive: true });
12448
+ } catch (e) {
12449
+ if (e.code !== "EEXIST" || !fs6.existsSync(dir)) throw e;
12450
+ }
12451
+ fs6.appendFileSync(queuePath, `${normalizedPath}
12013
12452
  `);
12014
12453
  }
12015
12454
  function getDirtyPaths() {
12016
12455
  const queuePath = dirtyQueuePath();
12017
12456
  let raw;
12018
12457
  try {
12019
- raw = fs5.readFileSync(queuePath, "utf8");
12458
+ raw = fs6.readFileSync(queuePath, "utf8");
12020
12459
  } catch {
12021
12460
  return [];
12022
12461
  }
@@ -12032,21 +12471,21 @@ function getDirtyPaths() {
12032
12471
  }
12033
12472
  function clearDirtyQueue() {
12034
12473
  try {
12035
- fs5.rmSync(dirtyQueuePath(), { force: true });
12474
+ fs6.rmSync(dirtyQueuePath(), { force: true });
12036
12475
  } catch {
12037
12476
  }
12038
12477
  }
12039
12478
  function preCompactIndexHandler(_event) {
12040
12479
  const paths = getDirtyPaths();
12041
12480
  if (paths.length > 0) {
12042
- const sidecar = path8.join(dataDir(), "queue", "pending.txt");
12481
+ const sidecar = path10.join(dataDir(), "queue", "pending.txt");
12043
12482
  try {
12044
- fs5.mkdirSync(path8.dirname(sidecar), { recursive: true });
12483
+ fs6.mkdirSync(path10.dirname(sidecar), { recursive: true });
12045
12484
  atomicWriteBytes(sidecar, Buffer.from(`${paths.join("\n")}
12046
12485
  `, "utf8"));
12486
+ clearDirtyQueue();
12047
12487
  } catch {
12048
12488
  }
12049
- clearDirtyQueue();
12050
12489
  }
12051
12490
  return passOutput();
12052
12491
  }
@@ -12059,10 +12498,11 @@ function postEditHandler(event) {
12059
12498
  const normalized = normalizePath(filePath);
12060
12499
  recordFileEdit(normalized);
12061
12500
  appendDirtyPath(normalized);
12062
- const editedBasename = path9.basename(normalized);
12501
+ const editedBasename = path11.basename(normalized);
12063
12502
  if (/\.(md|mdx|markdown|rst)$/i.test(editedBasename)) {
12503
+ const escapedPath = normalized.replace(/`/g, "\\`");
12064
12504
  return contextOutput(
12065
- editedBasename + ' was edited. Use `token-goat section "' + normalized + '::HeadingName"` to re-read a specific section rather than the full file.'
12505
+ editedBasename + ' was edited. Use `token-goat section "' + escapedPath + '::HeadingName"` to re-read a specific section rather than the full file.'
12066
12506
  );
12067
12507
  }
12068
12508
  return passOutput();
@@ -12340,17 +12780,20 @@ init_define_import_meta_env();
12340
12780
 
12341
12781
  // src/fingerprint.ts
12342
12782
  init_define_import_meta_env();
12343
- import { createHash } from "node:crypto";
12344
- import * as fs6 from "node:fs";
12783
+ import { createHash as createHash2 } from "node:crypto";
12784
+ import * as fs7 from "node:fs";
12345
12785
  function fingerprintContent(content) {
12346
- const hash = createHash("sha256");
12786
+ const hash = createHash2("sha256");
12347
12787
  hash.update(typeof content === "string" ? Buffer.from(content, "utf-8") : content);
12348
12788
  return hash.digest("hex");
12349
12789
  }
12790
+ function shortFingerprint(content) {
12791
+ return fingerprintContent(content).slice(0, 16);
12792
+ }
12350
12793
  function fingerprintFile(filePath) {
12351
12794
  let data;
12352
12795
  try {
12353
- data = fs6.readFileSync(filePath);
12796
+ data = fs7.readFileSync(filePath);
12354
12797
  } catch {
12355
12798
  return null;
12356
12799
  }
@@ -12359,15 +12802,24 @@ function fingerprintFile(filePath) {
12359
12802
 
12360
12803
  // src/bash_output_cache.ts
12361
12804
  init_define_import_meta_env();
12362
- import * as fs7 from "fs/promises";
12805
+ import * as fs8 from "fs/promises";
12363
12806
  import { resolve as resolve3 } from "path";
12364
12807
  var _byId = /* @__PURE__ */ new Map();
12365
12808
  var _globsByKey = /* @__PURE__ */ new Map();
12366
12809
  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;
12810
+ var COMMAND_PATTERNS = {
12811
+ gitMutable: /^\s*git\s+(diff|status)\b/i,
12812
+ gitImmutable: /^\s*git\s+show\s+[0-9a-f]{40}\b/i,
12813
+ gitDiffUnscoped: /^\s*git\s+diff\b/i,
12814
+ gitDiffScoped: /\s--\s+\S/,
12815
+ dirListing: /^\s*(?:ls|eza|exa|dir|Get-ChildItem|gci)\b/i,
12816
+ 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,
12817
+ npmInstall: /^\s*npm\s+(?:-\S+\s+)*(?:install|ci)\b/i,
12818
+ npmAudit: /^\s*npm\s+(?:-\S+\s+)*audit\b(?!.*(?:--fix|fix)\b)/i,
12819
+ npmOutdated: /^\s*npm\s+(?:-\S+\s+)*outdated\b/i,
12820
+ 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,
12821
+ 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
12822
+ };
12371
12823
  var DEP_LOCKFILES = {
12372
12824
  npm: ["package-lock.json", "yarn.lock"],
12373
12825
  pip: ["requirements.txt"],
@@ -12378,18 +12830,14 @@ var DEP_LOCKFILES = {
12378
12830
  bundle: ["Gemfile.lock"],
12379
12831
  composer: ["composer.lock"]
12380
12832
  };
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
- }
12833
+ function isCommandOfType(cmd, type) {
12834
+ const pattern = COMMAND_PATTERNS[type];
12835
+ return pattern?.test(cmd) ?? false;
12836
+ }
12837
+ var isGitMutableCommand = (cmd) => isCommandOfType(cmd, "gitMutable");
12838
+ var isDirListingCommand = (cmd) => isCommandOfType(cmd, "dirListing");
12839
+ var isDepListCommand = (cmd) => isCommandOfType(cmd, "depList");
12840
+ var isNpmInstallCommand = (cmd) => isCommandOfType(cmd, "npmInstall");
12393
12841
  async function gitStateFingerprint(cwd) {
12394
12842
  try {
12395
12843
  const headResult = runGit(["rev-parse", "HEAD"], { cwd });
@@ -12397,21 +12845,21 @@ async function gitStateFingerprint(cwd) {
12397
12845
  const headSha = headResult.stdout.trim();
12398
12846
  let indexMtime = "";
12399
12847
  try {
12400
- const stat3 = await fs7.stat(resolve3(cwd, ".git", "index"));
12848
+ const stat3 = await fs8.stat(resolve3(cwd, ".git", "index"));
12401
12849
  indexMtime = stat3.mtimeMs.toString();
12402
12850
  } catch {
12403
12851
  }
12404
12852
  const key = `${headSha}\0${indexMtime}`;
12405
- return fingerprintContent(key).slice(0, 16);
12853
+ return shortFingerprint(key);
12406
12854
  } catch {
12407
12855
  return null;
12408
12856
  }
12409
12857
  }
12410
- async function dirStateFingerprint(path15) {
12858
+ async function dirStateFingerprint(path17) {
12411
12859
  try {
12412
- const stat3 = await fs7.stat(path15);
12860
+ const stat3 = await fs8.stat(path17);
12413
12861
  if (!stat3.isDirectory()) return null;
12414
- return fingerprintContent(stat3.mtimeMs.toString()).slice(0, 16);
12862
+ return shortFingerprint(stat3.mtimeMs.toString());
12415
12863
  } catch {
12416
12864
  return null;
12417
12865
  }
@@ -12425,8 +12873,8 @@ async function depLockfileFingerprint(cmd, cwd) {
12425
12873
  if (!candidates) return null;
12426
12874
  for (const lockfile of candidates) {
12427
12875
  try {
12428
- const content = await fs7.readFile(resolve3(cwd, lockfile));
12429
- return fingerprintContent(content).slice(0, 16);
12876
+ const content = await fs8.readFile(resolve3(cwd, lockfile));
12877
+ return shortFingerprint(content);
12430
12878
  } catch {
12431
12879
  continue;
12432
12880
  }
@@ -12474,7 +12922,7 @@ async function commandHash(command, cwd = null) {
12474
12922
  const fp = await depLockfileFingerprint(command, cwd);
12475
12923
  if (fp) key = `${key}\0npm-install:${fp}`;
12476
12924
  }
12477
- return fingerprintContent(key).slice(0, 16);
12925
+ return shortFingerprint(key);
12478
12926
  }
12479
12927
  function extractLsTarget(cmd, cwd) {
12480
12928
  const tokens = cmd.trim().split(/\s+/);
@@ -12521,7 +12969,7 @@ function isOrchestratorStateFile(filePath) {
12521
12969
  return /^\.improve-state-/.test(basename7);
12522
12970
  }
12523
12971
  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);
12972
+ 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
12973
  return m?.[1] ?? null;
12526
12974
  }
12527
12975
  function extractCatFile(cmd) {
@@ -12530,14 +12978,39 @@ function extractCatFile(cmd) {
12530
12978
  const filePath = m[1] ?? m[2] ?? m[3];
12531
12979
  if (filePath === void 0) return null;
12532
12980
  if (isTempPath(filePath)) return null;
12533
- const basename7 = filePath.includes("/") ? filePath.split("/").at(-1) : filePath.split("\\").at(-1) ?? filePath;
12981
+ const basename7 = (filePath.includes("/") ? filePath.split("/").at(-1) : filePath.split("\\").at(-1)) ?? filePath;
12534
12982
  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);
12983
+ 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
12984
  if (!hasKnownExt && !isEnvFile) return null;
12537
- const isDoc = /\.(?:md|mdx|rst|txt|sql)$/i.test(filePath);
12985
+ const isSql = /\.sql$/i.test(filePath);
12986
+ const isDoc = /\.(?:md|mdx|rst|txt)$/i.test(filePath);
12538
12987
  const isEnv = isEnvFile || /\.env$/i.test(filePath);
12539
12988
  const isConfig = /\.(?:json|yaml|yml|toml|conf|cfg|ini|properties)$/i.test(filePath);
12540
- return { filePath, isDoc, isEnv, isConfig };
12989
+ return { filePath, isDoc, isEnv, isConfig, isSql };
12990
+ }
12991
+ function extractRgSymbolSearch(cmd) {
12992
+ if (!/^(?:rg|grep)\s+/.test(cmd)) return null;
12993
+ if (!/-n\b/.test(cmd)) return null;
12994
+ const patternMatch = /["']([^"']+)["']/.exec(cmd);
12995
+ const pattern = patternMatch?.[1];
12996
+ if (!pattern) return null;
12997
+ if (!/^[A-Za-z_][A-Za-z0-9_]*(\|[A-Za-z_][A-Za-z0-9_]*)*$/.test(pattern)) return null;
12998
+ 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);
12999
+ if (!fileMatch) return null;
13000
+ const filePath = fileMatch[1] ?? fileMatch[2] ?? fileMatch[3];
13001
+ if (!filePath) return null;
13002
+ if (isTempPath(filePath)) return null;
13003
+ if (/-[a-zA-Z]*r[a-zA-Z]*\b/.test(cmd) || /--recursive\b/.test(cmd)) return null;
13004
+ return { filePath, identifier: pattern };
13005
+ }
13006
+ function extractCatJsonPipe(cmd) {
13007
+ const m = /^cat\s+(?:"([^"]+)"|'([^']+)'|(\S+))\s*\|\s*jq\b/.exec(cmd);
13008
+ if (!m) return null;
13009
+ const filePath = m[1] ?? m[2] ?? m[3];
13010
+ if (!filePath) return null;
13011
+ if (isTempPath(filePath)) return null;
13012
+ if (!/\.(?:json|yaml|yml|toml)$/i.test(filePath)) return null;
13013
+ return { filePath };
12541
13014
  }
12542
13015
  function extractWslCatFile(cmd) {
12543
13016
  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 +13020,44 @@ function extractWslCatFile(cmd) {
12547
13020
  if (!drive || !pathRest) return null;
12548
13021
  const filePath = drive + ":/" + pathRest;
12549
13022
  if (isTempPath(filePath)) return null;
12550
- const basename7 = filePath.includes("/") ? filePath.split("/").at(-1) : filePath.split("\\").at(-1) ?? filePath;
13023
+ const basename7 = (filePath.includes("/") ? filePath.split("/").at(-1) : filePath.split("\\").at(-1)) ?? filePath;
12551
13024
  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);
13025
+ 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
13026
  if (!hasKnownExt && !isEnvFile) return null;
12554
- const isDoc = /\.(?:md|mdx|rst|txt|sql)$/i.test(filePath);
13027
+ const isSql = /\.sql$/i.test(filePath);
13028
+ const isDoc = /\.(?:md|mdx|rst|txt)$/i.test(filePath);
12555
13029
  const isEnv = isEnvFile || /\.env$/i.test(filePath);
12556
13030
  const isConfig = /\.(?:json|yaml|yml|toml|conf|cfg|ini|properties)$/i.test(filePath);
12557
- return { filePath, isDoc, isEnv, isConfig };
13031
+ return { filePath, isDoc, isEnv, isConfig, isSql };
12558
13032
  }
12559
13033
  function extractPythonFileRead(cmd) {
12560
13034
  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;
13035
+ if (/open\s*\([^)]*,\s*['"][wa]/i.test(cmd) || /\.write\s*\(/.test(cmd)) return null;
13036
+ 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;
13037
+ const heredocMatch = /^python3?\s+-\s+<<\s*'?(\w+)'?\s*\n([\s\S]*?)\n\1\s*$/.exec(cmd);
13038
+ if (heredocMatch) {
13039
+ const body = heredocMatch[2] ?? "";
13040
+ if (/open\s*\([^)]*,\s*['"][wa]/i.test(body) || /\.write\s*\(/.test(body) || /\.writelines\s*\(/.test(body)) return null;
13041
+ 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);
13042
+ if (heredocOpen?.[1]) {
13043
+ const filePath = heredocOpen[1];
13044
+ if (isOrchestratorStateFile(filePath)) return null;
13045
+ const isDoc = /\.(?:md|mdx|rst|txt)$/i.test(filePath);
13046
+ return { filePath, isDoc };
13047
+ }
13048
+ if (/open\s*\(/.test(body)) {
13049
+ 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);
13050
+ if (literal?.[1]) {
13051
+ const filePath = literal[1];
13052
+ if (isOrchestratorStateFile(filePath)) return null;
13053
+ if (OPEN_EXT.test(filePath)) {
13054
+ const isDoc = /\.(?:md|mdx|rst|txt)$/i.test(filePath);
13055
+ return { filePath, isDoc };
13056
+ }
13057
+ }
13058
+ }
13059
+ return null;
13060
+ }
12562
13061
  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
13062
  if (direct) {
12564
13063
  const filePath = direct[1] ?? "";
@@ -12573,7 +13072,7 @@ function extractPythonFileRead(cmd) {
12573
13072
  const filePath = literal[1] ?? "";
12574
13073
  if (filePath) {
12575
13074
  if (isOrchestratorStateFile(filePath)) return null;
12576
- if (EXT.test(filePath)) {
13075
+ if (OPEN_EXT.test(filePath)) {
12577
13076
  const isDoc = /\.(?:md|mdx|rst|txt)$/i.test(filePath);
12578
13077
  return { filePath, isDoc };
12579
13078
  }
@@ -12586,7 +13085,7 @@ function extractHeadFile(cmd) {
12586
13085
  const m = /^head(?:\s+-n\s+(\d+)|\s+-(\d+))?\s+(?:"([^"]+)"|'([^']+)'|(\S+))\s*$/.exec(cmd);
12587
13086
  if (!m) return null;
12588
13087
  const n = parseInt(m[1] ?? m[2] ?? "0", 10);
12589
- if (n > 0 && n < 10) return null;
13088
+ if (n < 10) return null;
12590
13089
  const filePath = m[3] ?? m[4] ?? m[5];
12591
13090
  if (filePath === void 0) return null;
12592
13091
  if (isTempPath(filePath)) return null;
@@ -12597,13 +13096,24 @@ function extractHeadFile(cmd) {
12597
13096
  }
12598
13097
  function extractNodeFileRead(cmd) {
12599
13098
  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 };
13099
+ 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);
13100
+ if (readSync?.[1]) {
13101
+ const filePath = readSync[1];
13102
+ if (isOrchestratorStateFile(filePath)) return null;
13103
+ if (isTempPath(filePath)) return null;
13104
+ const isDoc = /\.(?:md|mdx|rst|txt|sql)$/i.test(filePath);
13105
+ const isConfig = /\.(?:json|yaml|yml|toml|conf|cfg|ini|properties)$/i.test(filePath);
13106
+ return { filePath, isDoc, isConfig };
13107
+ }
13108
+ const requireM = /require\(['"]([^'"]+\.json)['"]\)/i.exec(cmd);
13109
+ if (requireM?.[1]) {
13110
+ const filePath = requireM[1];
13111
+ if (filePath.includes("node_modules")) return null;
13112
+ if (isOrchestratorStateFile(filePath)) return null;
13113
+ if (isTempPath(filePath)) return null;
13114
+ return { filePath, isDoc: false, isConfig: true };
13115
+ }
13116
+ return null;
12607
13117
  }
12608
13118
  function extractTailFile(cmd) {
12609
13119
  if (/-f\b/.test(cmd)) return null;
@@ -12620,9 +13130,67 @@ function extractTailFile(cmd) {
12620
13130
  const isDoc = /\.(?:md|mdx|rst|txt|sql)$/i.test(filePath);
12621
13131
  return { filePath, isDoc };
12622
13132
  }
13133
+ function extractTasksOutput(cmd) {
13134
+ const taskOutputRe = /[/\\]tasks[/\\]([a-z0-9]+)\.output$/;
13135
+ const catM = /^cat(?:\s+(?:-[a-zA-Z]+|--[a-zA-Z-]+))*\s+(?:"([^"]+)"|'([^']+)'|(\S+))\s*$/.exec(cmd);
13136
+ if (catM) {
13137
+ const fp = catM[1] ?? catM[2] ?? catM[3];
13138
+ if (fp) {
13139
+ const m = taskOutputRe.exec(fp);
13140
+ if (m) return { id: m[1] };
13141
+ }
13142
+ }
13143
+ if (!/-f\b/.test(cmd) && !/-n\s*\+/.test(cmd)) {
13144
+ const tailM = /^tail(?:\s+-n\s+(\d+)|\s+-(\d+))?\s+(?:"([^"]+)"|'([^']+)'|(\S+))\s*$/.exec(cmd);
13145
+ if (tailM) {
13146
+ const fp = tailM[3] ?? tailM[4] ?? tailM[5];
13147
+ if (fp) {
13148
+ const m = taskOutputRe.exec(fp);
13149
+ if (m) {
13150
+ const nStr = tailM[1] ?? tailM[2];
13151
+ const n = nStr !== void 0 ? parseInt(nStr, 10) : void 0;
13152
+ return n !== void 0 ? { id: m[1], n } : { id: m[1] };
13153
+ }
13154
+ }
13155
+ }
13156
+ const byteTailM = /^tail\s+-c\s+\d+\s+(?:"([^"]+)"|'([^']+)'|(\S+))\s*$/.exec(cmd);
13157
+ if (byteTailM) {
13158
+ const fp = byteTailM[1] ?? byteTailM[2] ?? byteTailM[3];
13159
+ if (fp) {
13160
+ const m = taskOutputRe.exec(fp);
13161
+ if (m) return { id: m[1] };
13162
+ }
13163
+ }
13164
+ }
13165
+ return null;
13166
+ }
13167
+ function extractSedLineRange(cmd) {
13168
+ return /^sed\s+-n\s+['"]?\d+,\d+p['"]?/.test(cmd);
13169
+ }
13170
+ function extractDirectoryListing(cmd) {
13171
+ 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);
13172
+ }
13173
+ function extractFindCommand(cmd) {
13174
+ if (!/^find\b/.test(cmd)) return null;
13175
+ const isXargsGrepL = /[|]\s*xargs\s+(?:grep|rg)\s+.*-l\b/.test(cmd) || /[|]\s*xargs\s+grep\s+-l\b/.test(cmd);
13176
+ const nameMatch = /-name\s+['"]([^'"]+)['"]/i.exec(cmd);
13177
+ const extGlob = nameMatch ? nameMatch[1] ?? null : null;
13178
+ return { extGlob, isXargsGrepL };
13179
+ }
13180
+ function extractMarkdownHeadingGrep(cmd) {
13181
+ if (!/^(?:rg|grep)\s+/.test(cmd)) return null;
13182
+ if (!/-n\b/.test(cmd)) return null;
13183
+ const hasHeadingPattern = /["']?\^#{1,6}["']?/.test(cmd) || /["']?\^#\+["']?/.test(cmd) || /["']?\^#\\+["']?/.test(cmd);
13184
+ if (!hasHeadingPattern) return null;
13185
+ const fileMatch = /(?:^|\s)(?:"([^"]+\.(?:md|markdown))"|'([^']+\.(?:md|markdown))'|([^\s"'|<>]+\.(?:md|markdown)))\s*(?:\||$)/.exec(cmd);
13186
+ if (!fileMatch) return null;
13187
+ const filePath = fileMatch[1] ?? fileMatch[2] ?? fileMatch[3];
13188
+ if (!filePath) return null;
13189
+ return { filePath };
13190
+ }
12623
13191
  function extractRgStructuralSearch(cmd) {
12624
13192
  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);
13193
+ 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
13194
  if (!hasStructural) return null;
12627
13195
  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
13196
  if (!fileMatch) return null;
@@ -12631,6 +13199,37 @@ function extractRgStructuralSearch(cmd) {
12631
13199
  if (isTempPath(filePath)) return null;
12632
13200
  return { filePath };
12633
13201
  }
13202
+ function extractGrepPipeChain(cmd) {
13203
+ return /^(?:rg|grep)\b.*\|\s*(?:rg|grep)\b/.test(cmd);
13204
+ }
13205
+ function extractCurlUrl(cmd) {
13206
+ const m = /(https?:\/\/[^\s'"]+)/.exec(cmd);
13207
+ return m?.[1] ?? null;
13208
+ }
13209
+ function isCurlGetCommand(cmd) {
13210
+ if (!/^curl\b/.test(cmd)) return false;
13211
+ if (/-X\s+(?:POST|PUT|PATCH|DELETE|HEAD|OPTIONS)/i.test(cmd)) return false;
13212
+ if (/--request\s+(?:POST|PUT|PATCH|DELETE|HEAD|OPTIONS)/i.test(cmd)) return false;
13213
+ if (/\s(?:-d|--data(?:-raw|-binary|-urlencode)?|-F|--form)\b/.test(cmd)) return false;
13214
+ if (/\s(?:-u|--user)\b/.test(cmd)) return false;
13215
+ if (/-H\s+['"]?Authorization/i.test(cmd)) return false;
13216
+ return true;
13217
+ }
13218
+ function extractCurlDownload(cmd) {
13219
+ if (!/^curl\b/.test(cmd)) return null;
13220
+ const outputMatch = /(?:^|\s)(?:-o|--output)\s+(?:"([^"]+)"|'([^']+)'|(\S+))/.exec(cmd);
13221
+ if (!outputMatch) return null;
13222
+ const outputPath = outputMatch[1] ?? outputMatch[2] ?? outputMatch[3];
13223
+ if (!outputPath) return null;
13224
+ if (/-X\s+(?:POST|PUT|PATCH|DELETE|HEAD|OPTIONS)/i.test(cmd)) return null;
13225
+ if (/--request\s+(?:POST|PUT|PATCH|DELETE|HEAD|OPTIONS)/i.test(cmd)) return null;
13226
+ if (/(?:^|\s)(?:-d|--data(?:-raw|-binary|-urlencode)?|-F|--form)\b/.test(cmd)) return null;
13227
+ if (/(?:^|\s)(?:-u|--user)\b/.test(cmd)) return null;
13228
+ if (/-H\s+['"]?Authorization/i.test(cmd)) return null;
13229
+ const urlMatch = /(https?:\/\/[^\s'"]+)/.exec(cmd);
13230
+ if (!urlMatch?.[1]) return null;
13231
+ return { url: urlMatch[1], outputPath };
13232
+ }
12634
13233
  function isTscCommand(cmd) {
12635
13234
  return /^\s*tsc(\s|$)/i.test(cmd);
12636
13235
  }
@@ -12650,18 +13249,70 @@ function buildRecallHint(cmd, outputId) {
12650
13249
  function preBashHandler(event) {
12651
13250
  const cmd = extractCommand(event);
12652
13251
  if (cmd === void 0) return passOutput();
13252
+ const taskOutput = extractTasksOutput(cmd);
13253
+ if (taskOutput !== null) {
13254
+ const { id } = taskOutput;
13255
+ recordStat("session_hint", 0, 0);
13256
+ return denyOutput(
13257
+ "Use `token-goat bash-output " + id + "` to recall this task output (already cached). Append `--tail <n>` or `--grep PATTERN` to slice it."
13258
+ );
13259
+ }
13260
+ const findResult = extractFindCommand(cmd);
13261
+ if (findResult !== null) {
13262
+ const { extGlob, isXargsGrepL } = findResult;
13263
+ recordStat("session_hint", 0, 0);
13264
+ if (isXargsGrepL) {
13265
+ return denyOutput(
13266
+ "`find | xargs grep -l` is a slow symbol search. Use `token-goat refs <symbol>` or `rg -l <symbol>` for faster symbol-file discovery."
13267
+ );
13268
+ }
13269
+ const fdHint = extGlob ? "Use `fd '" + extGlob + "'` for faster file discovery (respects .gitignore)." : "Use `fd` for faster file discovery (respects .gitignore).";
13270
+ return contextOutput(
13271
+ "`find` is slow and ignores .gitignore. " + fdHint + " For symbol definitions, use `token-goat symbol <Name>`."
13272
+ );
13273
+ }
13274
+ if (extractDirectoryListing(cmd)) {
13275
+ recordStat("session_hint", 0, 0);
13276
+ return contextOutput(
13277
+ "Use `token-goat map --compact` (~300 tokens) for a repo overview, or `token-goat map <dir>` for a subdirectory."
13278
+ );
13279
+ }
13280
+ if (extractSedLineRange(cmd)) {
13281
+ recordStat("session_hint", 0, 0);
13282
+ return contextOutput(
13283
+ 'Use `token-goat section "<file>::HeadingName"` to read one section instead of a line range.'
13284
+ );
13285
+ }
13286
+ const catJsonPipe = extractCatJsonPipe(cmd);
13287
+ if (catJsonPipe !== null) {
13288
+ const { filePath } = catJsonPipe;
13289
+ recordStat("session_hint", 0, 0);
13290
+ return contextOutput(
13291
+ '`cat | jq` loads the whole file. Use `token-goat config-get "' + filePath + '" KEY_NAME` or `token-goat section "' + filePath + '::sectionName"` to slice one value.'
13292
+ );
13293
+ }
12653
13294
  const catResult = extractCatFile(cmd);
12654
13295
  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.';
13296
+ const { filePath, isDoc, isEnv, isConfig, isSql } = catResult;
12657
13297
  recordStat("session_hint", 0, 0);
13298
+ if (isSql) {
13299
+ return contextOutput(
13300
+ '`cat` loads the entire file into context. Use `token-goat section "' + filePath + '::table_name"` to pull one CREATE TABLE / CREATE TYPE block.'
13301
+ );
13302
+ }
13303
+ 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.';
12658
13304
  return denyOutput("`cat` loads the entire file into context. " + hint);
12659
13305
  }
12660
13306
  const wslCatResult = extractWslCatFile(cmd);
12661
13307
  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.';
13308
+ const { filePath, isDoc, isEnv, isConfig, isSql } = wslCatResult;
12664
13309
  recordStat("session_hint", 0, 0);
13310
+ if (isSql) {
13311
+ return contextOutput(
13312
+ '`cat` loads the entire file into context. Use `token-goat section "' + filePath + '::table_name"` to pull one CREATE TABLE / CREATE TYPE block.'
13313
+ );
13314
+ }
13315
+ 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.';
12665
13316
  return denyOutput("`cat` loads the entire file into context. " + hint);
12666
13317
  }
12667
13318
  const pyRead = extractPythonFileRead(cmd);
@@ -12687,11 +13338,33 @@ function preBashHandler(event) {
12687
13338
  }
12688
13339
  const nodeRead = extractNodeFileRead(cmd);
12689
13340
  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.';
13341
+ const { filePath, isDoc, isConfig } = nodeRead;
13342
+ 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
13343
  recordStat("session_hint", 0, 0);
12693
13344
  return denyOutput("Node.js `fs.readFileSync()` bypasses read hooks. " + hint);
12694
13345
  }
13346
+ if (extractGrepPipeChain(cmd)) {
13347
+ recordStat("session_hint", 0, 0);
13348
+ return contextOutput(
13349
+ "Collapse `grep | grep` into `rg -e PAT1 -e PAT2` (single pass). For symbol discovery: `token-goat refs <symbol>` or `token-goat semantic`."
13350
+ );
13351
+ }
13352
+ const mdHeadingGrep = extractMarkdownHeadingGrep(cmd);
13353
+ if (mdHeadingGrep !== null) {
13354
+ const { filePath } = mdHeadingGrep;
13355
+ recordStat("session_hint", 0, 0);
13356
+ return contextOutput(
13357
+ 'Use `token-goat outline "' + filePath + '"` to get all headings with line ranges \u2014 then `token-goat section "' + filePath + '::Heading"` to read one section.'
13358
+ );
13359
+ }
13360
+ const rgSymbol = extractRgSymbolSearch(cmd);
13361
+ if (rgSymbol !== null) {
13362
+ const { identifier } = rgSymbol;
13363
+ recordStat("session_hint", 0, 0);
13364
+ return contextOutput(
13365
+ "Use `token-goat symbol " + identifier + "` to jump directly to the definition without scanning the file."
13366
+ );
13367
+ }
12695
13368
  const rgStructural = extractRgStructuralSearch(cmd);
12696
13369
  if (rgStructural !== null) {
12697
13370
  const { filePath } = rgStructural;
@@ -12702,7 +13375,7 @@ function preBashHandler(event) {
12702
13375
  }
12703
13376
  const monitoringHint = getMonitoringRecallHint(cmd);
12704
13377
  if (monitoringHint !== null) {
12705
- const monCmdHash = fingerprintContent(cmd).slice(0, 16);
13378
+ const monCmdHash = shortFingerprint(cmd);
12706
13379
  const monOutputId = getBashOutputId(monCmdHash);
12707
13380
  if (monOutputId !== null) {
12708
13381
  const monEntry = getBashOutput(monOutputId);
@@ -12721,8 +13394,32 @@ function preBashHandler(event) {
12721
13394
  );
12722
13395
  }
12723
13396
  }
13397
+ const curlDl = extractCurlDownload(cmd);
13398
+ if (curlDl !== null) {
13399
+ const prevPath = getCurlDownloadPath(curlDl.url);
13400
+ if (prevPath !== null) {
13401
+ recordStat("session_hint", 0, 0);
13402
+ return denyOutput(
13403
+ "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.'
13404
+ );
13405
+ }
13406
+ }
13407
+ if (isCurlGetCommand(cmd)) {
13408
+ const curlCacheKey = extractCurlUrl(cmd) ?? cmd;
13409
+ const curlHash = shortFingerprint(curlCacheKey);
13410
+ const curlOutputId = getBashOutputId(curlHash);
13411
+ if (curlOutputId !== null) {
13412
+ const curlEntry = getBashOutput(curlOutputId);
13413
+ const curlBytes = curlEntry?.sizeBytes ?? 0;
13414
+ recordStat("bash_compress:recall", curlBytes, Math.round(curlBytes / 4));
13415
+ const curlPreview = cmd.length > 60 ? cmd.slice(0, 57) + "..." : cmd;
13416
+ return contextOutput(
13417
+ "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."
13418
+ );
13419
+ }
13420
+ }
12724
13421
  if (!isBuildCommand(cmd)) return passOutput();
12725
- const cmdHash = fingerprintContent(cmd).slice(0, 16);
13422
+ const cmdHash = shortFingerprint(cmd);
12726
13423
  const outputId = getBashOutputId(cmdHash);
12727
13424
  if (outputId === null) return passOutput();
12728
13425
  const entry = getBashOutput(outputId);
@@ -12747,12 +13444,17 @@ async function postBashHandler(event) {
12747
13444
  try {
12748
13445
  const cmd = extractCommand(event);
12749
13446
  if (cmd === void 0) return passOutput();
13447
+ const curlDl = extractCurlDownload(cmd);
13448
+ if (curlDl !== null) {
13449
+ recordCurlDownload(curlDl.url, curlDl.outputPath);
13450
+ }
12750
13451
  const isMonitoring = getMonitoringRecallHint(cmd) !== null;
12751
- if (!isMonitoring && !isBuildCommand(cmd)) return passOutput();
13452
+ if (!isMonitoring && !isBuildCommand(cmd) && !isCurlGetCommand(cmd)) return passOutput();
12752
13453
  const output = extractBashOutput(event.raw);
12753
13454
  if (Buffer.byteLength(output, "utf-8") < MIN_CACHE_BYTES) return passOutput();
12754
13455
  const cwd = typeof event.raw["cwd"] === "string" ? event.raw["cwd"] : null;
12755
- const simpleHash = fingerprintContent(cmd).slice(0, 16);
13456
+ const cacheKey = isCurlGetCommand(cmd) ? extractCurlUrl(cmd) ?? cmd : cmd;
13457
+ const simpleHash = shortFingerprint(cacheKey);
12756
13458
  const id = await storeBashOutput(cmd, output, 0, cwd);
12757
13459
  recordBashOutput(simpleHash, id, Buffer.byteLength(output, "utf-8"));
12758
13460
  } catch {
@@ -12763,8 +13465,8 @@ registerHook("post_tool_use", postBashHandler, { toolName: "Bash" });
12763
13465
 
12764
13466
  // src/image_shrink.ts
12765
13467
  init_define_import_meta_env();
12766
- import * as fs8 from "node:fs";
12767
- import * as path10 from "node:path";
13468
+ import * as fs9 from "node:fs";
13469
+ import * as path12 from "node:path";
12768
13470
  var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
12769
13471
  ".png",
12770
13472
  ".jpg",
@@ -12791,7 +13493,7 @@ async function loadSharp() {
12791
13493
  return _sharpCache;
12792
13494
  }
12793
13495
  function isImagePath(p) {
12794
- return IMAGE_EXTENSIONS.has(path10.extname(p).toLowerCase());
13496
+ return IMAGE_EXTENSIONS.has(path12.extname(p).toLowerCase());
12795
13497
  }
12796
13498
  async function shrinkImage(input, opts) {
12797
13499
  const maxDimension = opts?.maxDimension ?? DEFAULT_MAX_DIMENSION;
@@ -12823,7 +13525,7 @@ async function shrinkImage(input, opts) {
12823
13525
  }
12824
13526
  function statSize2(absPath) {
12825
13527
  try {
12826
- const st = fs8.statSync(absPath);
13528
+ const st = fs9.statSync(absPath);
12827
13529
  return st.isFile() ? st.size : null;
12828
13530
  } catch {
12829
13531
  return null;
@@ -12837,7 +13539,7 @@ async function preReadImageHandler(event) {
12837
13539
  if (size === null || size < DEFAULT_SIZE_THRESHOLD_BYTES) return passOutput();
12838
13540
  let input;
12839
13541
  try {
12840
- input = fs8.readFileSync(filePath);
13542
+ input = fs9.readFileSync(filePath);
12841
13543
  } catch {
12842
13544
  return passOutput();
12843
13545
  }
@@ -12846,7 +13548,7 @@ async function preReadImageHandler(event) {
12846
13548
  const saved = result.originalBytes - result.shrunkBytes;
12847
13549
  const pct = Math.round(saved / result.originalBytes * 100);
12848
13550
  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}).`;
13551
+ 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
13552
  return contextOutput(`${summary}
12851
13553
  ${dataUrl}`);
12852
13554
  }
@@ -12925,7 +13627,7 @@ async function relay(eventName) {
12925
13627
 
12926
13628
  // src/section_reader.ts
12927
13629
  init_define_import_meta_env();
12928
- import { readFileSync as readFileSync5 } from "node:fs";
13630
+ import { readFileSync as readFileSync6 } from "node:fs";
12929
13631
  function parseHeadingSpec(spec) {
12930
13632
  const m = /^(.*?)#(\d+)$/.exec(spec);
12931
13633
  if (m !== null && m[1] !== void 0 && m[2] !== void 0) {
@@ -12933,6 +13635,18 @@ function parseHeadingSpec(spec) {
12933
13635
  }
12934
13636
  return { base: spec.trim(), ordinal: null };
12935
13637
  }
13638
+ function normalizeHeading(s) {
13639
+ let n = s.replace(/[—–]/g, "-");
13640
+ n = n.replace(/\s*\([^)]+\)\s*$/, "");
13641
+ n = n.replace(/^\d+\.\s+/, "");
13642
+ return n.replace(/\s+/g, " ").trim();
13643
+ }
13644
+ function normalizeHeadingStrip(s) {
13645
+ let n = s.replace(/\s*[—–].*$/, "");
13646
+ n = n.replace(/\s*\([^)]+\)\s*$/, "");
13647
+ n = n.replace(/^\d+\.\s+/, "");
13648
+ return n.replace(/\s+/g, " ").trim();
13649
+ }
12936
13650
  var MARKDOWN_HEADER_RE = /^(#{1,6})\s+(.+?)\s*#*\s*$/;
12937
13651
  var TABLE_HEADER_RE = /^\s*\[+\s*([^\]]+?)\s*\]+\s*$/;
12938
13652
  var PYTHON_HEADER_RE = /^(\s*)(?:async\s+)?(?:def|class)\s+([A-Za-z_]\w*)/;
@@ -13007,7 +13721,7 @@ function sectionEndIndex(headers, headerPos, totalLines) {
13007
13721
  function readSection(filePath, headingSpec) {
13008
13722
  let text;
13009
13723
  try {
13010
- text = readFileSync5(filePath, "utf-8");
13724
+ text = readFileSync6(filePath, "utf-8");
13011
13725
  } catch {
13012
13726
  return null;
13013
13727
  }
@@ -13017,10 +13731,15 @@ function readSection(filePath, headingSpec) {
13017
13731
  const headers = findHeaders(text, language);
13018
13732
  const lines = text.split("\n");
13019
13733
  const target = base.toLowerCase();
13734
+ const normalizedTarget = normalizeHeading(base).toLowerCase();
13735
+ const strippedTarget = normalizeHeadingStrip(base).toLowerCase();
13020
13736
  const matches = [];
13021
13737
  for (let i = 0; i < headers.length; i++) {
13022
13738
  const h = headers[i];
13023
- if (h !== void 0 && h.heading.toLowerCase() === target) matches.push(i);
13739
+ if (h === void 0) continue;
13740
+ if (h.heading.toLowerCase() === target || normalizeHeading(h.heading).toLowerCase() === normalizedTarget || normalizeHeadingStrip(h.heading).toLowerCase() === strippedTarget) {
13741
+ matches.push(i);
13742
+ }
13024
13743
  }
13025
13744
  if (matches.length === 0) return null;
13026
13745
  const pick = ordinal === null ? 0 : ordinal - 1;
@@ -13044,9 +13763,9 @@ function readSection(filePath, headingSpec) {
13044
13763
 
13045
13764
  // src/install.ts
13046
13765
  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";
13766
+ import * as fs10 from "node:fs";
13767
+ import * as os3 from "node:os";
13768
+ import * as path13 from "node:path";
13050
13769
  var HOOK_EVENT_MAP = [
13051
13770
  ["PreToolUse", "pre_tool_use"],
13052
13771
  ["PostToolUse", "post_tool_use"],
@@ -13057,13 +13776,13 @@ function hookCommand(eventArg) {
13057
13776
  return `token-goat hook ${eventArg}`;
13058
13777
  }
13059
13778
  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");
13779
+ const base = scope === "user" ? path13.join(os3.homedir(), ".claude") : path13.join(process.cwd(), ".claude");
13780
+ return path13.join(base, "settings.json");
13062
13781
  }
13063
13782
  function readSettings(p) {
13064
13783
  let raw;
13065
13784
  try {
13066
- raw = fs9.readFileSync(p, "utf8");
13785
+ raw = fs10.readFileSync(p, "utf8");
13067
13786
  } catch {
13068
13787
  return {};
13069
13788
  }
@@ -13103,7 +13822,7 @@ function installHooks(scope = "user") {
13103
13822
  return { scope, settingsPath: p, alreadyInstalled: true };
13104
13823
  }
13105
13824
  settings.hooks = hooks;
13106
- fs9.mkdirSync(path11.dirname(p), { recursive: true });
13825
+ fs10.mkdirSync(path13.dirname(p), { recursive: true });
13107
13826
  atomicWriteText(p, `${JSON.stringify(settings, null, 2)}
13108
13827
  `);
13109
13828
  return { scope, settingsPath: p, alreadyInstalled: false };
@@ -13142,7 +13861,7 @@ function uninstallHooks(scope = "user") {
13142
13861
  } else {
13143
13862
  settings.hooks = hooks;
13144
13863
  }
13145
- fs9.mkdirSync(path11.dirname(p), { recursive: true });
13864
+ fs10.mkdirSync(path13.dirname(p), { recursive: true });
13146
13865
  atomicWriteText(p, `${JSON.stringify(settings, null, 2)}
13147
13866
  `);
13148
13867
  return true;
@@ -13151,21 +13870,21 @@ function uninstallHooks(scope = "user") {
13151
13870
  // src/worker.ts
13152
13871
  init_define_import_meta_env();
13153
13872
  import { spawn } from "node:child_process";
13154
- import * as fs10 from "node:fs";
13155
- import * as path12 from "node:path";
13873
+ import * as fs11 from "node:fs";
13874
+ import * as path14 from "node:path";
13156
13875
  import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads";
13157
13876
  import { fileURLToPath } from "node:url";
13158
13877
  var DEFAULT_POLL_INTERVAL_MS = 2e3;
13159
13878
  function dirtyQueuePathFor(dir) {
13160
- return path12.join(dir, "queue", "dirty.txt");
13879
+ return path14.join(dir, "queue", "dirty.txt");
13161
13880
  }
13162
13881
  function workerPidPath(dir = dataDir()) {
13163
- return path12.join(dir, "worker.pid");
13882
+ return path14.join(dir, "worker.pid");
13164
13883
  }
13165
13884
  function getDirtyPathsFor(dir) {
13166
13885
  let raw;
13167
13886
  try {
13168
- raw = fs10.readFileSync(dirtyQueuePathFor(dir), "utf8");
13887
+ raw = fs11.readFileSync(dirtyQueuePathFor(dir), "utf8");
13169
13888
  } catch {
13170
13889
  return [];
13171
13890
  }
@@ -13181,7 +13900,7 @@ function getDirtyPathsFor(dir) {
13181
13900
  }
13182
13901
  function clearDirtyQueueFor(dir) {
13183
13902
  try {
13184
- fs10.rmSync(dirtyQueuePathFor(dir), { force: true });
13903
+ fs11.rmSync(dirtyQueuePathFor(dir), { force: true });
13185
13904
  } catch {
13186
13905
  }
13187
13906
  }
@@ -13191,7 +13910,7 @@ function processDirtyBatch(paths, index = (absPath) => {
13191
13910
  }) {
13192
13911
  let indexed = 0;
13193
13912
  for (const p of paths) {
13194
- if (!fs10.existsSync(p)) continue;
13913
+ if (!p || !fs11.existsSync(p)) continue;
13195
13914
  const sha = fingerprintFile(p);
13196
13915
  if (sha === null) continue;
13197
13916
  index(p, sha);
@@ -13216,7 +13935,7 @@ function pidAlive(pid) {
13216
13935
  }
13217
13936
  function readPidFile(dir) {
13218
13937
  try {
13219
- const raw = fs10.readFileSync(workerPidPath(dir), "utf8").trim();
13938
+ const raw = fs11.readFileSync(workerPidPath(dir), "utf8").trim();
13220
13939
  if (!/^\d+$/.test(raw)) return null;
13221
13940
  return parseInt(raw, 10);
13222
13941
  } catch {
@@ -13239,7 +13958,7 @@ function stopWorker(dir = dataDir()) {
13239
13958
  }
13240
13959
  }
13241
13960
  try {
13242
- fs10.rmSync(workerPidPath(dir), { force: true });
13961
+ fs11.rmSync(workerPidPath(dir), { force: true });
13243
13962
  } catch {
13244
13963
  }
13245
13964
  return alive;
@@ -13247,7 +13966,11 @@ function stopWorker(dir = dataDir()) {
13247
13966
  function startDetachedWorker(opts) {
13248
13967
  const pollIntervalMs = opts?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
13249
13968
  const dir = opts?.dataDir ?? dataDir();
13250
- fs10.mkdirSync(dir, { recursive: true });
13969
+ try {
13970
+ fs11.mkdirSync(dir, { recursive: true });
13971
+ } catch (e) {
13972
+ if (e.code !== "EEXIST" || !fs11.existsSync(dir)) throw e;
13973
+ }
13251
13974
  const child = spawn(
13252
13975
  process.execPath,
13253
13976
  [fileURLToPath(import.meta.url), "--worker-daemon"],
@@ -13266,7 +13989,7 @@ function startDetachedWorker(opts) {
13266
13989
  if (pid === void 0) {
13267
13990
  throw new Error("startDetachedWorker: spawn produced no pid");
13268
13991
  }
13269
- fs10.writeFileSync(workerPidPath(dir), `${pid}
13992
+ fs11.writeFileSync(workerPidPath(dir), `${pid}
13270
13993
  `);
13271
13994
  child.unref();
13272
13995
  return pid;
@@ -13309,7 +14032,7 @@ workerEntry();
13309
14032
 
13310
14033
  // src/skill_cache.ts
13311
14034
  init_define_import_meta_env();
13312
- import * as fs11 from "fs/promises";
14035
+ import * as fs12 from "fs/promises";
13313
14036
  import { resolve as resolve4 } from "path";
13314
14037
  var COMPACT_END_MARKER = "<!-- COMPACT_END -->";
13315
14038
  var _skillOutputsDirOverride = null;
@@ -13322,7 +14045,7 @@ function skillOutputsDir() {
13322
14045
  }
13323
14046
  async function ensureSkillsDir() {
13324
14047
  try {
13325
- await fs11.mkdir(skillOutputsDir(), { recursive: true });
14048
+ await fs12.mkdir(skillOutputsDir(), { recursive: true });
13326
14049
  } catch {
13327
14050
  }
13328
14051
  }
@@ -13349,7 +14072,7 @@ function extractCompactFromMarker(body) {
13349
14072
  const lines = body.split("\n");
13350
14073
  for (let i = 0; i < lines.length; i++) {
13351
14074
  const stripped = lines[i].trim();
13352
- if (stripped.startsWith("```") || stripped.startsWith("~~~")) {
14075
+ if (isCodeFenceDelimiter(stripped)) {
13353
14076
  inCodeBlock = !inCodeBlock;
13354
14077
  continue;
13355
14078
  }
@@ -13364,14 +14087,14 @@ function extractCompactFromMarker(body) {
13364
14087
  async function listOutputs() {
13365
14088
  try {
13366
14089
  const dir = skillOutputsDir();
13367
- const entries = await fs11.readdir(dir, { withFileTypes: true });
14090
+ const entries = await fs12.readdir(dir, { withFileTypes: true });
13368
14091
  const metas = [];
13369
14092
  for (const entry of entries) {
13370
14093
  if (!entry.isFile() || !entry.name.endsWith(".meta")) {
13371
14094
  continue;
13372
14095
  }
13373
14096
  try {
13374
- const content = await fs11.readFile(resolve4(dir, entry.name), "utf-8");
14097
+ const content = await fs12.readFile(resolve4(dir, entry.name), "utf-8");
13375
14098
  const meta = JSON.parse(content);
13376
14099
  metas.push(meta);
13377
14100
  } catch {
@@ -13416,13 +14139,13 @@ async function listSkills(sessionId) {
13416
14139
  const compactFileId = `${safeSession}-${meta.skillName.replace(":", "_")}-compact`;
13417
14140
  let compactLen = 0;
13418
14141
  try {
13419
- const stat3 = await fs11.stat(resolve4(dir, compactFileId));
14142
+ const stat3 = await fs12.stat(resolve4(dir, compactFileId));
13420
14143
  compactLen = stat3.size;
13421
14144
  } catch {
13422
14145
  compactLen = 0;
13423
14146
  }
13424
14147
  const hasMarker = extractCompactFromMarker(
13425
- await fs11.readFile(resolve4(dir, `${meta.outputId}.txt`), "utf-8").catch(() => "")
14148
+ await fs12.readFile(resolve4(dir, `${meta.outputId}.txt`), "utf-8").catch(() => "")
13426
14149
  ) !== null;
13427
14150
  results.push({
13428
14151
  name: meta.skillName,
@@ -13454,7 +14177,7 @@ async function getSkillFilePath(skillName) {
13454
14177
 
13455
14178
  // src/config.ts
13456
14179
  init_define_import_meta_env();
13457
- import * as fs12 from "node:fs";
14180
+ import * as fs13 from "node:fs";
13458
14181
 
13459
14182
  // node_modules/smol-toml/dist/index.js
13460
14183
  init_define_import_meta_env();
@@ -14186,8 +14909,8 @@ function envInt(key, defaultVal) {
14186
14909
  }
14187
14910
 
14188
14911
  // src/config.ts
14189
- function defaultCompactAssistConfig() {
14190
- return {
14912
+ var CONFIG_DEFAULTS = {
14913
+ compact_assist: {
14191
14914
  enabled: true,
14192
14915
  triggers: ["manual", "auto"],
14193
14916
  min_events: 3,
@@ -14202,10 +14925,8 @@ function defaultCompactAssistConfig() {
14202
14925
  lazy_skill_injection: true,
14203
14926
  max_manifest_chars: 1600,
14204
14927
  harness: "auto"
14205
- };
14206
- }
14207
- function defaultBashCompressConfig() {
14208
- return {
14928
+ },
14929
+ bash_compress: {
14209
14930
  enabled: true,
14210
14931
  disabled_filters: [],
14211
14932
  max_lines: 1e3,
@@ -14215,32 +14936,22 @@ function defaultBashCompressConfig() {
14215
14936
  cache_max_file_count: 4096,
14216
14937
  cache_max_bytes: 16 * 1024 * 1024,
14217
14938
  cache_max_bytes_per_output: 50 * 1024 * 1024
14218
- };
14219
- }
14220
- function defaultBashDiffConfig() {
14221
- return {
14939
+ },
14940
+ bash_diff: {
14222
14941
  max_hunks_per_file: 10,
14223
14942
  hunk_density_cap: true
14224
- };
14225
- }
14226
- function defaultSeverityLogConfig() {
14227
- return {
14943
+ },
14944
+ bash_severity_log: {
14228
14945
  context_lines: 3,
14229
14946
  score_threshold: 0.5
14230
- };
14231
- }
14232
- function defaultCodeCompressConfig() {
14233
- return {
14947
+ },
14948
+ post_read_code_compress: {
14234
14949
  min_lines: 200
14235
- };
14236
- }
14237
- function defaultSessionBriefConfig() {
14238
- return {
14950
+ },
14951
+ session_brief: {
14239
14952
  enabled: true
14240
- };
14241
- }
14242
- function defaultSkillPreservationConfig() {
14243
- return {
14953
+ },
14954
+ skill_preservation: {
14244
14955
  enabled: true,
14245
14956
  max_cache_bytes: 5 * 1024 * 1024,
14246
14957
  orphan_sweep_enabled: true,
@@ -14252,25 +14963,19 @@ function defaultSkillPreservationConfig() {
14252
14963
  pre_skill_enabled: true,
14253
14964
  first_load_compact: false,
14254
14965
  post_compact_full_loads: false
14255
- };
14256
- }
14257
- function defaultCuratorConfig() {
14258
- return {
14966
+ },
14967
+ curator: {
14259
14968
  enabled: true,
14260
14969
  min_samples: 10,
14261
14970
  threshold_pct: 20
14262
- };
14263
- }
14264
- function defaultHintBudgetConfig() {
14265
- return {
14971
+ },
14972
+ hint_budget: {
14266
14973
  enabled: true,
14267
14974
  max_per_session: 100,
14268
14975
  max_structured_per_session: 30,
14269
14976
  max_index_only_per_session: 30
14270
- };
14271
- }
14272
- function defaultImageShrinkConfig() {
14273
- return {
14977
+ },
14978
+ image_shrink: {
14274
14979
  prefer_avif: true,
14275
14980
  avif_quality: 60,
14276
14981
  jpeg_quality: 75,
@@ -14278,27 +14983,19 @@ function defaultImageShrinkConfig() {
14278
14983
  orphan_sweep_enabled: true,
14279
14984
  orphan_age_secs: 604800,
14280
14985
  screenshot_redirect: true
14281
- };
14282
- }
14283
- function defaultRepomapConfig() {
14284
- return {
14986
+ },
14987
+ repomap: {
14285
14988
  compact_file_threshold: 50,
14286
14989
  exclude_tests: true
14287
- };
14288
- }
14289
- function defaultOverflowGuardConfig() {
14290
- return {
14990
+ },
14991
+ overflow_guard: {
14291
14992
  enabled: true,
14292
14993
  max_tokens: 25e3
14293
- };
14294
- }
14295
- function defaultStatsConfig() {
14296
- return {
14994
+ },
14995
+ stats: {
14297
14996
  record_zero_savings: false
14298
- };
14299
- }
14300
- function defaultHintsConfig() {
14301
- return {
14997
+ },
14998
+ hints: {
14302
14999
  suppress_after_ignored: 5,
14303
15000
  quiet_hours: "",
14304
15001
  json_sidecar: false,
@@ -14322,51 +15019,40 @@ function defaultHintsConfig() {
14322
15019
  truncated_read_min_lines: 200,
14323
15020
  protect_recent_reads: 4,
14324
15021
  prompt_triggers: []
14325
- };
14326
- }
14327
- function defaultHooksConfig() {
14328
- return {
15022
+ },
15023
+ hooks: {
14329
15024
  watchdog_ms: 700
14330
- };
14331
- }
14332
- function defaultWebFetchConfig() {
14333
- return {
15025
+ },
15026
+ webfetch: {
14334
15027
  allow: [],
14335
15028
  deny: [],
14336
15029
  max_file_count: 4096,
14337
15030
  max_bytes: 32 * 1024 * 1024,
14338
15031
  compress_bodies: true,
14339
15032
  compress_min_bytes: 16 * 1024
14340
- };
14341
- }
14342
- function defaultWorkerConfig() {
14343
- return {
15033
+ },
15034
+ worker: {
14344
15035
  watchdog_enabled: true,
14345
15036
  max_pool_workers: 4,
14346
15037
  blocked_roots: []
14347
- };
14348
- }
14349
- function defaultIndexingConfig() {
14350
- return {
15038
+ },
15039
+ indexing: {
14351
15040
  large_file_symbol_only_kb: 500,
14352
15041
  large_file_skip_kb: 2048,
14353
15042
  skip_dirs: []
14354
- };
14355
- }
14356
- function defaultCompressionConfig() {
14357
- return {
15043
+ },
15044
+ compression: {
14358
15045
  profile: "auto"
14359
- };
14360
- }
14361
- function defaultContextConfig() {
14362
- return {
15046
+ },
15047
+ context: {
14363
15048
  model_window_tokens: 2e5
14364
- };
14365
- }
14366
- function defaultInjectionConfig() {
14367
- return {
15049
+ },
15050
+ injection: {
14368
15051
  enabled: true
14369
- };
15052
+ }
15053
+ };
15054
+ function getDefaultConfig(section2) {
15055
+ return structuredClone(CONFIG_DEFAULTS[section2] ?? {});
14370
15056
  }
14371
15057
  function validatedBool(raw, def) {
14372
15058
  if (typeof raw === "boolean") return raw;
@@ -14439,7 +15125,7 @@ function loadConfig() {
14439
15125
  const p = configPath();
14440
15126
  let currentMtime = 0;
14441
15127
  try {
14442
- currentMtime = fs12.statSync(p).mtimeMs;
15128
+ currentMtime = fs13.statSync(p).mtimeMs;
14443
15129
  } catch {
14444
15130
  }
14445
15131
  const envFp = configEnvFingerprint();
@@ -14449,7 +15135,7 @@ function loadConfig() {
14449
15135
  let raw = {};
14450
15136
  if (currentMtime !== 0) {
14451
15137
  try {
14452
- const text = fs12.readFileSync(p, "utf8");
15138
+ const text = fs13.readFileSync(p, "utf8");
14453
15139
  raw = parse(text);
14454
15140
  } catch {
14455
15141
  }
@@ -14460,7 +15146,7 @@ function loadConfig() {
14460
15146
  }
14461
15147
  function _buildConfig(raw) {
14462
15148
  const ca_raw = section(raw, "compact_assist");
14463
- const ca = defaultCompactAssistConfig();
15149
+ const ca = getDefaultConfig("compact_assist");
14464
15150
  ca.enabled = validatedBool(ca_raw["enabled"], ca.enabled);
14465
15151
  ca.triggers = validatedStrList(ca_raw["triggers"], ca.triggers);
14466
15152
  ca.min_events = validatedInt(ca_raw["min_events"], ca.min_events, 0, 1e3);
@@ -14478,7 +15164,7 @@ function _buildConfig(raw) {
14478
15164
  ca.enabled = envBool("TOKEN_GOAT_COMPACT_ASSIST", envBool("TOKENWISE_COMPACT_ASSIST", ca.enabled));
14479
15165
  ca.lazy_skill_injection = envBool("TOKEN_GOAT_LAZY_SKILL_INJECTION", ca.lazy_skill_injection);
14480
15166
  const bc_raw = section(raw, "bash_compress");
14481
- const bc = defaultBashCompressConfig();
15167
+ const bc = getDefaultConfig("bash_compress");
14482
15168
  bc.enabled = validatedBool(bc_raw["enabled"], bc.enabled);
14483
15169
  bc.disabled_filters = validatedStrList(bc_raw["disabled_filters"], bc.disabled_filters);
14484
15170
  bc.max_lines = validatedInt(bc_raw["max_lines"], bc.max_lines, 50, 1e5);
@@ -14494,22 +15180,22 @@ function _buildConfig(raw) {
14494
15180
  bc.cache_max_bytes = envInt("TOKEN_GOAT_BASH_CACHE_MAX_BYTES", bc.cache_max_bytes);
14495
15181
  bc.cache_max_bytes_per_output = envInt("TOKEN_GOAT_BASH_CACHE_MAX_BYTES_PER_OUTPUT", bc.cache_max_bytes_per_output);
14496
15182
  const bd_raw = section(raw, "bash_diff");
14497
- const bd = defaultBashDiffConfig();
15183
+ const bd = getDefaultConfig("bash_diff");
14498
15184
  bd.max_hunks_per_file = validatedInt(bd_raw["max_hunks_per_file"], bd.max_hunks_per_file, 1, 1e4);
14499
15185
  bd.hunk_density_cap = validatedBool(bd_raw["hunk_density_cap"], bd.hunk_density_cap);
14500
15186
  const sl_raw = section(raw, "bash_severity_log");
14501
- const sl = defaultSeverityLogConfig();
15187
+ const sl = getDefaultConfig("bash_severity_log");
14502
15188
  sl.context_lines = validatedInt(sl_raw["context_lines"], sl.context_lines, 0, 100);
14503
15189
  sl.score_threshold = validatedFloat(sl_raw["score_threshold"], sl.score_threshold, 0, 1);
14504
15190
  const cc_raw = section(raw, "post_read_code_compress");
14505
- const cc = defaultCodeCompressConfig();
15191
+ const cc = getDefaultConfig("post_read_code_compress");
14506
15192
  cc.min_lines = validatedInt(cc_raw["min_lines"], cc.min_lines, 0, 1e6);
14507
15193
  const sb_raw = section(raw, "session_brief");
14508
- const sb = defaultSessionBriefConfig();
15194
+ const sb = getDefaultConfig("session_brief");
14509
15195
  sb.enabled = validatedBool(sb_raw["enabled"], sb.enabled);
14510
15196
  sb.enabled = envBool("TOKEN_GOAT_SESSION_BRIEF", sb.enabled);
14511
15197
  const sp_raw = section(raw, "skill_preservation");
14512
- const sp = defaultSkillPreservationConfig();
15198
+ const sp = getDefaultConfig("skill_preservation");
14513
15199
  sp.enabled = validatedBool(sp_raw["enabled"], sp.enabled);
14514
15200
  sp.max_cache_bytes = validatedInt(sp_raw["max_cache_bytes"], sp.max_cache_bytes, 64 * 1024, 512 * 1024 * 1024);
14515
15201
  sp.orphan_sweep_enabled = validatedBool(sp_raw["orphan_sweep_enabled"], sp.orphan_sweep_enabled);
@@ -14526,7 +15212,7 @@ function _buildConfig(raw) {
14526
15212
  sp.pre_skill_enabled = envBool("TOKEN_GOAT_PRE_SKILL", sp.pre_skill_enabled);
14527
15213
  sp.orphan_sweep_enabled = envBool("TOKEN_GOAT_ORPHAN_SWEEP", sp.orphan_sweep_enabled);
14528
15214
  const is_raw = section(raw, "image_shrink");
14529
- const is_cfg = defaultImageShrinkConfig();
15215
+ const is_cfg = getDefaultConfig("image_shrink");
14530
15216
  is_cfg.prefer_avif = validatedBool(is_raw["prefer_avif"], is_cfg.prefer_avif);
14531
15217
  is_cfg.avif_quality = validatedInt(is_raw["avif_quality"], is_cfg.avif_quality, 1, 100);
14532
15218
  is_cfg.jpeg_quality = validatedInt(is_raw["jpeg_quality"], is_cfg.jpeg_quality, 1, 100);
@@ -14537,33 +15223,33 @@ function _buildConfig(raw) {
14537
15223
  is_cfg.prefer_avif = envBool("TOKEN_GOAT_PREFER_AVIF", is_cfg.prefer_avif);
14538
15224
  is_cfg.max_image_pixels = envInt("TOKEN_GOAT_MAX_IMAGE_PIXELS", is_cfg.max_image_pixels);
14539
15225
  const cur_raw = section(raw, "curator");
14540
- const cur = defaultCuratorConfig();
15226
+ const cur = getDefaultConfig("curator");
14541
15227
  cur.enabled = validatedBool(cur_raw["enabled"], cur.enabled);
14542
15228
  cur.min_samples = validatedInt(cur_raw["min_samples"], cur.min_samples, 0, 1e4);
14543
15229
  cur.threshold_pct = validatedInt(cur_raw["threshold_pct"], cur.threshold_pct, 0, 100);
14544
15230
  cur.enabled = envBool("TOKEN_GOAT_CURATOR", cur.enabled);
14545
15231
  const hb_raw = section(raw, "hint_budget");
14546
- const hb = defaultHintBudgetConfig();
15232
+ const hb = getDefaultConfig("hint_budget");
14547
15233
  hb.enabled = validatedBool(hb_raw["enabled"], hb.enabled);
14548
15234
  hb.max_per_session = validatedInt(hb_raw["max_per_session"], hb.max_per_session, 0, 1e6);
14549
15235
  hb.max_structured_per_session = validatedInt(hb_raw["max_structured_per_session"], hb.max_structured_per_session, 0, 1e6);
14550
15236
  hb.max_index_only_per_session = validatedInt(hb_raw["max_index_only_per_session"], hb.max_index_only_per_session, 0, 1e6);
14551
15237
  hb.enabled = envBool("TOKEN_GOAT_HINT_BUDGET", hb.enabled);
14552
15238
  const rm_raw = section(raw, "repomap");
14553
- const rm = defaultRepomapConfig();
15239
+ const rm = getDefaultConfig("repomap");
14554
15240
  rm.compact_file_threshold = validatedInt(rm_raw["compact_file_threshold"], rm.compact_file_threshold, 0, 1e5);
14555
15241
  rm.exclude_tests = validatedBool(rm_raw["exclude_tests"], rm.exclude_tests);
14556
15242
  rm.compact_file_threshold = envInt("TOKEN_GOAT_REPOMAP_COMPACT_THRESHOLD", rm.compact_file_threshold);
14557
15243
  rm.exclude_tests = envBool("TOKEN_GOAT_REPOMAP_EXCLUDE_TESTS", rm.exclude_tests);
14558
15244
  const og_raw = section(raw, "overflow_guard");
14559
- const og = defaultOverflowGuardConfig();
15245
+ const og = getDefaultConfig("overflow_guard");
14560
15246
  og.enabled = validatedBool(og_raw["enabled"], og.enabled);
14561
15247
  og.max_tokens = validatedInt(og_raw["max_tokens"], og.max_tokens, 1e3, 1e6);
14562
15248
  const st_raw = section(raw, "stats");
14563
- const st = defaultStatsConfig();
15249
+ const st = getDefaultConfig("stats");
14564
15250
  st.record_zero_savings = validatedBool(st_raw["record_zero_savings"], st.record_zero_savings);
14565
15251
  const hi_raw = section(raw, "hints");
14566
- const hi = defaultHintsConfig();
15252
+ const hi = getDefaultConfig("hints");
14567
15253
  hi.suppress_after_ignored = validatedInt(hi_raw["suppress_after_ignored"], hi.suppress_after_ignored, 0, 1e3);
14568
15254
  hi.quiet_hours = validatedStr(hi_raw["quiet_hours"], hi.quiet_hours);
14569
15255
  hi.json_sidecar = validatedBool(hi_raw["json_sidecar"], hi.json_sidecar);
@@ -14596,11 +15282,11 @@ function _buildConfig(raw) {
14596
15282
  })).filter((t) => t.keywords.length > 0 && t.hint.length > 0);
14597
15283
  }
14598
15284
  const hk_raw = section(raw, "hooks");
14599
- const hk = defaultHooksConfig();
15285
+ const hk = getDefaultConfig("hooks");
14600
15286
  hk.watchdog_ms = validatedInt(hk_raw["watchdog_ms"], hk.watchdog_ms, 100, 3e4);
14601
15287
  hk.watchdog_ms = envInt("TOKEN_GOAT_HOOK_WATCHDOG_MS", hk.watchdog_ms);
14602
15288
  const wf_raw = section(raw, "webfetch");
14603
- const wf = defaultWebFetchConfig();
15289
+ const wf = getDefaultConfig("webfetch");
14604
15290
  wf.allow = validatedStrList(wf_raw["allow"], wf.allow);
14605
15291
  wf.deny = validatedStrList(wf_raw["deny"], wf.deny);
14606
15292
  wf.max_file_count = validatedInt(wf_raw["max_file_count"], wf.max_file_count, 0, 1e7);
@@ -14609,27 +15295,27 @@ function _buildConfig(raw) {
14609
15295
  wf.compress_min_bytes = validatedInt(wf_raw["compress_min_bytes"], wf.compress_min_bytes, 1024, 10 * 1024 * 1024);
14610
15296
  wf.compress_bodies = envBool("TOKEN_GOAT_WEB_COMPRESS", wf.compress_bodies);
14611
15297
  const wk_raw = section(raw, "worker");
14612
- const wk = defaultWorkerConfig();
15298
+ const wk = getDefaultConfig("worker");
14613
15299
  wk.watchdog_enabled = validatedBool(wk_raw["watchdog_enabled"], wk.watchdog_enabled);
14614
15300
  wk.max_pool_workers = validatedInt(wk_raw["max_pool_workers"], wk.max_pool_workers, 1, 8);
14615
15301
  wk.blocked_roots = validatedStrList(wk_raw["blocked_roots"], wk.blocked_roots);
14616
15302
  wk.watchdog_enabled = envBool("TOKEN_GOAT_WORKER_WATCHDOG", wk.watchdog_enabled);
14617
15303
  wk.max_pool_workers = envInt("TOKEN_GOAT_WORKER_MAX_POOL", wk.max_pool_workers);
14618
15304
  const ix_raw = section(raw, "indexing");
14619
- const ix = defaultIndexingConfig();
15305
+ const ix = getDefaultConfig("indexing");
14620
15306
  ix.large_file_symbol_only_kb = validatedInt(ix_raw["large_file_symbol_only_kb"], ix.large_file_symbol_only_kb, 1, 1048576);
14621
15307
  ix.large_file_skip_kb = validatedInt(ix_raw["large_file_skip_kb"], ix.large_file_skip_kb, 1, 1048576);
14622
15308
  ix.skip_dirs = validatedStrList(ix_raw["skip_dirs"], ix.skip_dirs);
14623
15309
  const cpr_raw = section(raw, "compression");
14624
- const cpr = defaultCompressionConfig();
15310
+ const cpr = getDefaultConfig("compression");
14625
15311
  cpr.profile = validatedStr(cpr_raw["profile"], cpr.profile);
14626
15312
  cpr.profile = envStr("TOKEN_GOAT_COMPRESS_PROFILE", cpr.profile);
14627
15313
  const ctx_raw = section(raw, "context");
14628
- const ctx = defaultContextConfig();
15314
+ const ctx = getDefaultConfig("context");
14629
15315
  ctx.model_window_tokens = validatedInt(ctx_raw["model_window_tokens"], ctx.model_window_tokens, 1e4, 1e7);
14630
15316
  ctx.model_window_tokens = envInt("TOKEN_GOAT_MODEL_WINDOW_TOKENS", ctx.model_window_tokens);
14631
15317
  const inj_raw = section(raw, "injection");
14632
- const inj = defaultInjectionConfig();
15318
+ const inj = getDefaultConfig("injection");
14633
15319
  inj.enabled = validatedBool(inj_raw["enabled"], inj.enabled);
14634
15320
  inj.enabled = envBool("TOKEN_GOAT_INJECTION_ENABLED", inj.enabled);
14635
15321
  return {
@@ -14659,9 +15345,9 @@ function _buildConfig(raw) {
14659
15345
 
14660
15346
  // src/cli_doctor.ts
14661
15347
  init_define_import_meta_env();
14662
- import * as fs13 from "fs";
14663
- import * as path13 from "path";
14664
- import { execSync } from "child_process";
15348
+ import * as fs14 from "fs";
15349
+ import * as path15 from "path";
15350
+ import { execSync, spawnSync as spawnSync2 } from "child_process";
14665
15351
  function checkWorkerRunning() {
14666
15352
  try {
14667
15353
  const output = execSync("tasklist", { encoding: "utf-8" });
@@ -14671,8 +15357,8 @@ function checkWorkerRunning() {
14671
15357
  }
14672
15358
  }
14673
15359
  function checkDbExists(dataDir2) {
14674
- const dbPath = path13.join(dataDir2, "index.db");
14675
- if (!fs13.existsSync(dbPath)) {
15360
+ const dbPath = path15.join(dataDir2, "index.db");
15361
+ if (!fs14.existsSync(dbPath)) {
14676
15362
  return {
14677
15363
  name: "Database",
14678
15364
  status: "warn",
@@ -14682,7 +15368,7 @@ function checkDbExists(dataDir2) {
14682
15368
  return {
14683
15369
  name: "Database",
14684
15370
  status: "ok",
14685
- message: `index.db exists (${Math.round(fs13.statSync(dbPath).size / 1024)} KB)`
15371
+ message: `index.db exists (${Math.round(fs14.statSync(dbPath).size / 1024)} KB)`
14686
15372
  };
14687
15373
  }
14688
15374
  function checkInstall() {
@@ -14702,7 +15388,7 @@ function checkInstall() {
14702
15388
  }
14703
15389
  }
14704
15390
  function checkConfigValid(configPath2) {
14705
- if (!fs13.existsSync(configPath2)) {
15391
+ if (!fs14.existsSync(configPath2)) {
14706
15392
  return {
14707
15393
  name: "Config",
14708
15394
  status: "warn",
@@ -14710,7 +15396,7 @@ function checkConfigValid(configPath2) {
14710
15396
  };
14711
15397
  }
14712
15398
  try {
14713
- const content = fs13.readFileSync(configPath2, "utf-8");
15399
+ const content = fs14.readFileSync(configPath2, "utf-8");
14714
15400
  JSON.parse(content);
14715
15401
  return {
14716
15402
  name: "Config",
@@ -14721,18 +15407,22 @@ function checkConfigValid(configPath2) {
14721
15407
  return {
14722
15408
  name: "Config",
14723
15409
  status: "fail",
14724
- message: `config invalid: ${err2 instanceof Error ? err2.message : "unknown error"}`
15410
+ message: `config invalid: ${extractErrorMessage(err2, "unknown error")}`
14725
15411
  };
14726
15412
  }
14727
15413
  }
14728
15414
  function checkDiskSpace(dataDir2) {
14729
15415
  try {
14730
- const statSync8 = execSync(`df -h ${dataDir2}`, { encoding: "utf-8" });
14731
- const lines = statSync8.trim().split("\n");
15416
+ const result = spawnSync2("df", ["-h", dataDir2], { encoding: "utf-8" });
15417
+ const stdout = typeof result.stdout === "string" ? result.stdout : "";
15418
+ if (result.error !== void 0 || result.status !== 0 || !stdout) {
15419
+ return { name: "Disk Space", status: "warn", message: "could not determine" };
15420
+ }
15421
+ const lines = stdout.trim().split("\n");
14732
15422
  if (lines.length < 2) {
14733
15423
  return { name: "Disk Space", status: "warn", message: "could not determine" };
14734
15424
  }
14735
- const parts = lines[1].split(/\s+/);
15425
+ const parts = lines[1].trim().split(/\s+/);
14736
15426
  const available = parts[3] || "unknown";
14737
15427
  return { name: "Disk Space", status: "ok", message: `${available} available` };
14738
15428
  } catch {
@@ -14744,9 +15434,9 @@ function runDoctor(dataDir2, configPath2) {
14744
15434
  results.push(checkInstall());
14745
15435
  results.push(checkWorkerRunning() ? { name: "Worker", status: "ok", message: "running" } : { name: "Worker", status: "warn", message: "not running" });
14746
15436
  const homeDir = process.env["HOME"] || process.env["USERPROFILE"] || "~";
14747
- const actualDataDir = dataDir2 || path13.join(homeDir, ".token-goat");
15437
+ const actualDataDir = dataDir2 || path15.join(homeDir, ".token-goat");
14748
15438
  results.push(checkDbExists(actualDataDir));
14749
- const actualConfigPath = configPath2 || path13.join(actualDataDir, "config.json");
15439
+ const actualConfigPath = configPath2 || path15.join(actualDataDir, "config.json");
14750
15440
  results.push(checkConfigValid(actualConfigPath));
14751
15441
  results.push(checkDiskSpace(actualDataDir));
14752
15442
  return results;
@@ -14785,7 +15475,7 @@ init_define_import_meta_env();
14785
15475
  var _byId2 = /* @__PURE__ */ new Map();
14786
15476
  var _urlIndex = /* @__PURE__ */ new Map();
14787
15477
  function cacheIdForUrl(url) {
14788
- return fingerprintContent(url).slice(0, 16);
15478
+ return shortFingerprint(url);
14789
15479
  }
14790
15480
  function storeWebOutput(url, content) {
14791
15481
  const cacheId = cacheIdForUrl(url);
@@ -14905,12 +15595,10 @@ async function getSectionContent(fileId, heading) {
14905
15595
  var CliError = class extends Error {
14906
15596
  };
14907
15597
  function out(text) {
14908
- process.stdout.write(text.endsWith("\n") ? text : `${text}
14909
- `);
15598
+ process.stdout.write(ensureNewline(text));
14910
15599
  }
14911
15600
  function err(text) {
14912
- process.stderr.write(text.endsWith("\n") ? text : `${text}
14913
- `);
15601
+ process.stderr.write(ensureNewline(text));
14914
15602
  }
14915
15603
  function previewLines(body, n) {
14916
15604
  return body.split(/\r?\n/).slice(0, n).join("\n");
@@ -15070,7 +15758,7 @@ function _applyFiltersAndPrint(content, opts) {
15070
15758
  const n = Number.parseInt(opts.tail, 10);
15071
15759
  return Number.isFinite(n) && n > 0 ? n : 80;
15072
15760
  })() : 80;
15073
- const applyElision = (lines2, headN2, tailN2) => lines2.length > headN2 + tailN2 ? [...lines2.slice(0, headN2), "...(elided)...", ...lines2.slice(lines2.length - tailN2)] : lines2;
15761
+ const applyElision = (lines2, headN2, tailN2) => lines2.length > headN2 + tailN2 + 1 ? [...lines2.slice(0, headN2), "...(elided)...", ...lines2.slice(lines2.length - tailN2)] : lines2;
15074
15762
  let result = lines;
15075
15763
  if (opts.head === void 0 && opts.tail === void 0 && opts.grep === void 0) {
15076
15764
  result = applyElision(lines, headN, tailN);
@@ -15085,10 +15773,21 @@ function _applyFiltersAndPrint(content, opts) {
15085
15773
  }
15086
15774
  function cmdBashOutput(id, opts) {
15087
15775
  if (opts.file !== void 0) {
15776
+ if (opts.file.includes("\0")) {
15777
+ throw new CliError("--file path contains a null byte");
15778
+ }
15779
+ if (!isWindows() && /^\/dev\/(stdin|fd\/0)$|^\/proc\/self\/fd\/0$/.test(opts.file) && process.stdin.isTTY) {
15780
+ throw new CliError("--file /dev/stdin requires piped input; redirect a file instead");
15781
+ }
15088
15782
  let content;
15089
15783
  try {
15090
- content = fs14.readFileSync(opts.file, "utf-8");
15091
- } catch {
15784
+ const st = fs15.statSync(opts.file);
15785
+ if (st.isFIFO() || st.isSocket()) {
15786
+ throw new CliError(`--file '${opts.file}' is a special file (FIFO or socket) \u2014 only regular files are supported`);
15787
+ }
15788
+ content = fs15.readFileSync(opts.file, "utf-8");
15789
+ } catch (e) {
15790
+ if (e instanceof CliError) throw e;
15092
15791
  throw new CliError(`cannot read file: ${opts.file}`);
15093
15792
  }
15094
15793
  _applyFiltersAndPrint(content, opts);
@@ -15108,12 +15807,12 @@ async function cmdSkillBody(name, opts) {
15108
15807
  if (filePath === null) {
15109
15808
  throw new CliError(`skill '${name}' not found`);
15110
15809
  }
15111
- const body = fs14.readFileSync(filePath, "utf-8");
15810
+ const body = fs15.readFileSync(filePath, "utf-8");
15112
15811
  if (opts.compact === true) {
15113
15812
  const lines = body.split("\n");
15114
15813
  const end = lines.findIndex((l) => l.includes("COMPACT_END"));
15115
15814
  if (end !== -1) {
15116
- out(lines.slice(0, end).join("\n"));
15815
+ out(lines.slice(end + 1).join("\n"));
15117
15816
  } else {
15118
15817
  out(body);
15119
15818
  }
@@ -15126,7 +15825,7 @@ async function cmdSkillCompact(name) {
15126
15825
  if (filePath === null) {
15127
15826
  throw new CliError(`skill '${name}' not found`);
15128
15827
  }
15129
- const body = fs14.readFileSync(filePath, "utf-8");
15828
+ const body = fs15.readFileSync(filePath, "utf-8");
15130
15829
  const sessionFiles = getSessionFiles();
15131
15830
  const sessionId = Array.from(sessionFiles.keys())[0] ?? "default";
15132
15831
  await storeCompact(sessionId, name, body);
@@ -15213,7 +15912,7 @@ function cmdConfigGet(file, key) {
15213
15912
  }
15214
15913
  function atomicWriteBuffer(dest, data) {
15215
15914
  try {
15216
- if (fs14.statSync(dest).isDirectory()) {
15915
+ if (fs15.statSync(dest).isDirectory()) {
15217
15916
  const e = Object.assign(new Error(`EISDIR: illegal operation on a directory, open '${dest}'`), { code: "EISDIR", path: dest });
15218
15917
  throw e;
15219
15918
  }
@@ -15221,16 +15920,16 @@ function atomicWriteBuffer(dest, data) {
15221
15920
  if (e.code !== "ENOENT") throw e;
15222
15921
  }
15223
15922
  const rnd = Math.random().toString(36).slice(2, 8);
15224
- const tmp = path14.join(path14.dirname(path14.resolve(dest)), `.tmp.${process.pid}.${rnd}`);
15923
+ const tmp = path16.join(path16.dirname(path16.resolve(dest)), `.tmp.${process.pid}.${rnd}`);
15225
15924
  try {
15226
- fs14.writeFileSync(tmp, data, { mode: 384 });
15925
+ fs15.writeFileSync(tmp, data, { mode: 384 });
15227
15926
  try {
15228
- fs14.renameSync(tmp, dest);
15927
+ fs15.renameSync(tmp, dest);
15229
15928
  } catch (e) {
15230
15929
  if (e.code === "EXDEV") {
15231
- fs14.copyFileSync(tmp, dest);
15930
+ fs15.copyFileSync(tmp, dest);
15232
15931
  try {
15233
- fs14.unlinkSync(tmp);
15932
+ fs15.unlinkSync(tmp);
15234
15933
  } catch (ue) {
15235
15934
  process.stderr.write(`token-goat write-file: warning: could not remove temp file ${tmp}: ${ue.message}
15236
15935
  `);
@@ -15241,7 +15940,7 @@ function atomicWriteBuffer(dest, data) {
15241
15940
  }
15242
15941
  } catch (e) {
15243
15942
  try {
15244
- fs14.unlinkSync(tmp);
15943
+ fs15.unlinkSync(tmp);
15245
15944
  } catch {
15246
15945
  }
15247
15946
  throw e;
@@ -15251,9 +15950,9 @@ function mapFsError(e, src, dest) {
15251
15950
  const fe = e;
15252
15951
  if (fe.code === "ENOENT") {
15253
15952
  const errPath = fe.path ?? "";
15254
- const isSource = src !== void 0 && path14.resolve(errPath) === path14.resolve(src);
15953
+ const isSource = src !== void 0 && path16.resolve(errPath) === path16.resolve(src);
15255
15954
  if (isSource) throw new CliError(`source file not found: ${src}`);
15256
- const destDir = dest ? path14.dirname(path14.resolve(dest)) : path14.dirname(path14.resolve(errPath || "."));
15955
+ const destDir = dest ? path16.dirname(path16.resolve(dest)) : path16.dirname(path16.resolve(errPath || "."));
15257
15956
  throw new CliError(`destination directory does not exist: ${destDir}`);
15258
15957
  }
15259
15958
  if (fe.code === "ENOTDIR") {
@@ -15261,7 +15960,7 @@ function mapFsError(e, src, dest) {
15261
15960
  }
15262
15961
  if (fe.code === "EISDIR") {
15263
15962
  const errPath = fe.path ?? "";
15264
- const isSource = src !== void 0 && (errPath === "" || path14.resolve(errPath) === path14.resolve(src));
15963
+ const isSource = src !== void 0 && (errPath === "" || path16.resolve(errPath) === path16.resolve(src));
15265
15964
  if (isSource) throw new CliError(`source is a directory, not a file: ${src}`);
15266
15965
  throw new CliError(`destination is a directory, not a file: ${dest ?? (errPath || "(unknown)")}`);
15267
15966
  }
@@ -15326,8 +16025,8 @@ function cmdWriteFile(dest, opts) {
15326
16025
  if (dest.includes("\0")) {
15327
16026
  throw new CliError("destination path contains a null byte");
15328
16027
  }
15329
- if (process.platform === "win32") {
15330
- const base = path14.basename(dest);
16028
+ if (isWindows()) {
16029
+ const base = path16.basename(dest);
15331
16030
  const stem = base.replace(/\.[^.]*$/, "").toUpperCase();
15332
16031
  if (WIN_RESERVED.has(stem)) {
15333
16032
  throw new CliError(`destination '${base}' is a reserved Windows device name`);
@@ -15346,20 +16045,23 @@ function cmdWriteFile(dest, opts) {
15346
16045
  if (opts.from.includes("\0")) {
15347
16046
  throw new CliError("--from path contains a null byte");
15348
16047
  }
15349
- if (process.platform !== "win32" && /^\/dev\/(stdin|fd\/0)$|^\/proc\/self\/fd\/0$/.test(opts.from) && process.stdin.isTTY) {
16048
+ if (!isWindows() && /^\/dev\/(stdin|fd\/0)$|^\/proc\/self\/fd\/0$/.test(opts.from) && process.stdin.isTTY) {
15350
16049
  throw new CliError("--from /dev/stdin requires piped input; use piped stdin mode or --b64 for interactive use");
15351
16050
  }
15352
16051
  try {
15353
- const st = fs14.statSync(opts.from);
16052
+ const st = fs15.statSync(opts.from);
15354
16053
  if (st.isFIFO() || st.isSocket()) {
15355
16054
  throw new CliError(`--from '${opts.from}' is a special file (FIFO or socket) \u2014 only regular files are supported`);
15356
16055
  }
15357
16056
  const maxFromMB = parseInt(process.env["TOKEN_GOAT_MAX_STDIN_MB"] ?? "512", 10);
15358
- const maxFromBytes = (Number.isFinite(maxFromMB) && maxFromMB > 0 ? maxFromMB : 512) * 1024 * 1024;
16057
+ if (!Number.isFinite(maxFromMB) || maxFromMB <= 0) {
16058
+ throw new CliError(`TOKEN_GOAT_MAX_STDIN_MB must be a positive integer; got '${process.env["TOKEN_GOAT_MAX_STDIN_MB"] ?? ""}'`);
16059
+ }
16060
+ const maxFromBytes = maxFromMB * 1024 * 1024;
15359
16061
  if (st.size > maxFromBytes) {
15360
16062
  throw new CliError(`--from source exceeds size limit (${Math.round(st.size / 1024 / 1024)} MB); set TOKEN_GOAT_MAX_STDIN_MB to override`);
15361
16063
  }
15362
- atomicWriteBuffer(dest, fs14.readFileSync(opts.from));
16064
+ atomicWriteBuffer(dest, fs15.readFileSync(opts.from));
15363
16065
  } catch (e) {
15364
16066
  if (e instanceof CliError) throw e;
15365
16067
  mapFsError(e, opts.from, dest);
@@ -15372,7 +16074,10 @@ function cmdWriteFile(dest, opts) {
15372
16074
  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
16075
  }
15374
16076
  const maxB64MB = parseInt(process.env["TOKEN_GOAT_MAX_STDIN_MB"] ?? "512", 10);
15375
- const maxB64Bytes = (Number.isFinite(maxB64MB) && maxB64MB > 0 ? maxB64MB : 512) * 1024 * 1024;
16077
+ if (!Number.isFinite(maxB64MB) || maxB64MB <= 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 maxB64Bytes = maxB64MB * 1024 * 1024;
15376
16081
  const decodedSize = Math.floor(normalized.replace(/=+$/, "").length * 3 / 4);
15377
16082
  if (decodedSize > maxB64Bytes) {
15378
16083
  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 +16178,7 @@ function buildProgram() {
15473
16178
  await fn(...args);
15474
16179
  process.exitCode = 0;
15475
16180
  } catch (e) {
15476
- const msg = e instanceof Error ? e.message : String(e);
16181
+ const msg = extractErrorMessage(e);
15477
16182
  err(`token-goat: ${msg}`);
15478
16183
  process.exitCode = 1;
15479
16184
  }
@@ -15525,7 +16230,7 @@ async function run(argv = process.argv) {
15525
16230
  process.exitCode = 1;
15526
16231
  return;
15527
16232
  }
15528
- const msg = e instanceof Error ? e.message : String(e);
16233
+ const msg = extractErrorMessage(e);
15529
16234
  err(`token-goat: ${msg}`);
15530
16235
  process.exitCode = 1;
15531
16236
  }