token-goat 2.9.8 → 2.9.10

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.
package/README.md CHANGED
@@ -37,7 +37,7 @@ Restart your AI sessions. Run `token-goat stats` a couple of minutes after your
37
37
 
38
38
  > **Built and continually improved, free, by one person. If it saves you tokens, drop a ⭐️ at the top of this page. One click. Makes my day. Also, if you'd like anything added, [drop me a line](mailto:token-goat@dfkhelper.com).**
39
39
 
40
- [Install](docs/install.md) · [CLI](docs/cli.md) · [What gets installed?](docs/install.md#what-gets-installed) · [Stats](#stats-display) · [Security & uninstall](docs/security.md)
40
+ [Install](docs/install.md) · [CLI](docs/cli.md) · [Architecture Q&A](docs/architecture-qa.html) · [What gets installed?](docs/install.md#what-gets-installed) · [Stats](#stats-display) · [Security & uninstall](docs/security.md)
41
41
 
42
42
  ---
43
43
 
@@ -323,7 +323,7 @@ corpus of captured shell output through the real compression path and reports ho
323
323
  result got, alongside a count of the lines each case is not allowed to lose. Deleting output makes
324
324
  the first number look better, so the second one is what keeps the first honest.
325
325
 
326
- Every command, with its flags, its output, and the comparison workflows that chain them: **[CLI reference](docs/cli.md)**.
326
+ Every command, with its flags, its output, and the comparison workflows that chain them: **[CLI reference](docs/cli.md)**. For architectural analysis, empirical evaluations, and technical answers to common engineering questions: **[Architecture & Technical Q&A](docs/architecture-qa.html)**.
327
327
 
328
328
  ## MCP server
329
329
 
package/SECURITY.md CHANGED
@@ -101,7 +101,7 @@ For a scanner that ingests a bill of materials rather than a lockfile, `npm run
101
101
 
102
102
  ## Verifying what you installed
103
103
 
104
- Every published version is built and pushed by one workflow, [`.github/workflows/publish.yml`](.github/workflows/publish.yml), which runs only when a GitHub release is published (or manually, and then only from `main`). It publishes with npm provenance, so npm holds a signed attestation tying the tarball to the commit and workflow run that produced it. Nothing is ever published from a laptop.
104
+ Every published version is built and pushed by one workflow, [`.github/workflows/publish.yml`](.github/workflows/publish.yml), which runs only when a GitHub release is published (or manually, and then only from `main`). It publishes with npm provenance, so npm holds a signed attestation tying the tarball to the commit and workflow run that produced it. Nothing is ever published from a developer workstation.
105
105
 
106
106
  To check a copy you already have:
107
107
 
@@ -4,14 +4,14 @@ import {
4
4
  buildEvent,
5
5
  relay,
6
6
  relayInProcess
7
- } from "./token-goat-chunk-XYLPMQ6I.mjs";
7
+ } from "./token-goat-chunk-ZQODJQTP.mjs";
8
8
  import {
9
9
  MAX_STDIN_BYTES,
10
10
  readStdinJson
11
- } from "./token-goat-chunk-VEEHPNBV.mjs";
12
- import "./token-goat-chunk-TXZY7C24.mjs";
13
- import "./token-goat-chunk-REHUP2OE.mjs";
14
- import "./token-goat-chunk-JZYJH76S.mjs";
11
+ } from "./token-goat-chunk-SLGOIXKF.mjs";
12
+ import "./token-goat-chunk-PA5UXNOT.mjs";
13
+ import "./token-goat-chunk-ATVQ6IIT.mjs";
14
+ import "./token-goat-chunk-VCEK77ED.mjs";
15
15
  import "./token-goat-chunk-EEIDFMEM.mjs";
16
16
  import "./token-goat-chunk-A37V4PBF.mjs";
17
17
  export {
@@ -86,7 +86,7 @@ import {
86
86
  runZipRead,
87
87
  symbolNamesInFile,
88
88
  upsertNote
89
- } from "./token-goat-chunk-6RAUVAKW.mjs";
89
+ } from "./token-goat-chunk-YFXFUG3L.mjs";
90
90
  import {
91
91
  DEFAULT_RECONCILE_BUDGET_MS,
92
92
  GEMINI_TOOL_NAME_MAP,
@@ -108,7 +108,7 @@ import {
108
108
  runReconcile,
109
109
  storeWebOutput,
110
110
  summarizeResidentContext
111
- } from "./token-goat-chunk-VEEHPNBV.mjs";
111
+ } from "./token-goat-chunk-SLGOIXKF.mjs";
112
112
  import {
113
113
  AGENT_SALT_MARKER,
114
114
  BASH_OUTPUT_SUBDIR,
@@ -238,7 +238,7 @@ import {
238
238
  visionTokensSavedByText,
239
239
  walkProject,
240
240
  writeCompact
241
- } from "./token-goat-chunk-TXZY7C24.mjs";
241
+ } from "./token-goat-chunk-PA5UXNOT.mjs";
242
242
  import {
243
243
  C,
244
244
  CONFIG_KEY_ENV_OVERRIDES,
@@ -372,7 +372,7 @@ import {
372
372
  withFileLock,
373
373
  withRetryOnLock,
374
374
  writeJsonSettings
375
- } from "./token-goat-chunk-JZYJH76S.mjs";
375
+ } from "./token-goat-chunk-VCEK77ED.mjs";
376
376
  import {
377
377
  __export,
378
378
  init_define_import_meta_env
@@ -15761,7 +15761,7 @@ async function cmdMcpServe() {
15761
15761
  let StdioServerTransport;
15762
15762
  try {
15763
15763
  ;
15764
- ({ createMcpServer } = await import("./token-goat-chunk-EZZ7IJYM.mjs"));
15764
+ ({ createMcpServer } = await import("./token-goat-chunk-DCYHAUW3.mjs"));
15765
15765
  ({ StdioServerTransport } = await import("./token-goat-chunk-EVC4TOLE.mjs"));
15766
15766
  } catch (err2) {
15767
15767
  process.stderr.write(
@@ -15786,7 +15786,7 @@ async function cmdHook(event, opts) {
15786
15786
  if (typeof opts.harness === "string" && opts.harness.length > 0) {
15787
15787
  process.env[ENV_KEYS.HARNESS_OVERRIDE] = opts.harness;
15788
15788
  }
15789
- const { relay } = await import("./token-goat-chunk-Q4VCAIBR.mjs");
15789
+ const { relay } = await import("./token-goat-chunk-2TJBY5RD.mjs");
15790
15790
  await relay(event);
15791
15791
  }
15792
15792
  async function cmdInstall(opts) {
@@ -16719,7 +16719,7 @@ function emitExtraFileArgsNote(command, first, extras, opts = {}) {
16719
16719
  }
16720
16720
  async function cmdCompress(opts) {
16721
16721
  try {
16722
- const bashRunner = await import("./token-goat-chunk-WT5OLTT6.mjs");
16722
+ const bashRunner = await import("./token-goat-chunk-JS44SRAX.mjs");
16723
16723
  if (opts.compress === false) {
16724
16724
  process.exitCode = bashRunner.runRaw(opts.cmd, parseTimeout(opts.timeout, bashRunner.DEFAULT_TIMEOUT_SECONDS));
16725
16725
  return;
@@ -2,7 +2,7 @@ import { createRequire as __cjsRequire } from 'node:module';
2
2
  const require = __cjsRequire(import.meta.url);
3
3
  import {
4
4
  detectHarness
5
- } from "./token-goat-chunk-JZYJH76S.mjs";
5
+ } from "./token-goat-chunk-VCEK77ED.mjs";
6
6
  import {
7
7
  init_define_import_meta_env
8
8
  } from "./token-goat-chunk-A37V4PBF.mjs";
@@ -25,7 +25,7 @@ import {
25
25
  runSkeleton,
26
26
  runSymbol,
27
27
  withPinnedReads
28
- } from "./token-goat-chunk-6RAUVAKW.mjs";
28
+ } from "./token-goat-chunk-YFXFUG3L.mjs";
29
29
  import {
30
30
  buildProjectMap,
31
31
  embeddingsDepsAvailable,
@@ -34,7 +34,7 @@ import {
34
34
  getProjectIndexCounts,
35
35
  isWorkerRunning,
36
36
  mapLookupBytesSaved
37
- } from "./token-goat-chunk-TXZY7C24.mjs";
37
+ } from "./token-goat-chunk-PA5UXNOT.mjs";
38
38
  import {
39
39
  ENV_KEYS,
40
40
  VERSION,
@@ -48,7 +48,7 @@ import {
48
48
  recordStat,
49
49
  resolveProjectRoot,
50
50
  savedTokensFromBytes
51
- } from "./token-goat-chunk-JZYJH76S.mjs";
51
+ } from "./token-goat-chunk-VCEK77ED.mjs";
52
52
  import "./token-goat-chunk-EEIDFMEM.mjs";
53
53
  import {
54
54
  init_define_import_meta_env
@@ -7,11 +7,11 @@ import {
7
7
  expandGlobs,
8
8
  leftoverIntegrations,
9
9
  run
10
- } from "./token-goat-chunk-2SBHUPBF.mjs";
11
- import "./token-goat-chunk-6RAUVAKW.mjs";
12
- import "./token-goat-chunk-VEEHPNBV.mjs";
13
- import "./token-goat-chunk-TXZY7C24.mjs";
14
- import "./token-goat-chunk-JZYJH76S.mjs";
10
+ } from "./token-goat-chunk-7ALFFPJW.mjs";
11
+ import "./token-goat-chunk-YFXFUG3L.mjs";
12
+ import "./token-goat-chunk-SLGOIXKF.mjs";
13
+ import "./token-goat-chunk-PA5UXNOT.mjs";
14
+ import "./token-goat-chunk-VCEK77ED.mjs";
15
15
  import "./token-goat-chunk-EEIDFMEM.mjs";
16
16
  import "./token-goat-chunk-A37V4PBF.mjs";
17
17
  export {
@@ -3,7 +3,7 @@ const require = __cjsRequire(import.meta.url);
3
3
  import {
4
4
  deliveredOutputBytes,
5
5
  wrappedShell
6
- } from "./token-goat-chunk-REHUP2OE.mjs";
6
+ } from "./token-goat-chunk-ATVQ6IIT.mjs";
7
7
  import {
8
8
  ToolFilter,
9
9
  capTokens,
@@ -15,7 +15,7 @@ import {
15
15
  recordStat,
16
16
  selectFilter,
17
17
  shlexSplit
18
- } from "./token-goat-chunk-JZYJH76S.mjs";
18
+ } from "./token-goat-chunk-VCEK77ED.mjs";
19
19
  import "./token-goat-chunk-EEIDFMEM.mjs";
20
20
  import {
21
21
  init_define_import_meta_env
@@ -101,7 +101,7 @@ import {
101
101
  toDisplayPath,
102
102
  toKB,
103
103
  withFileLock
104
- } from "./token-goat-chunk-JZYJH76S.mjs";
104
+ } from "./token-goat-chunk-VCEK77ED.mjs";
105
105
  import {
106
106
  registerReset
107
107
  } from "./token-goat-chunk-EEIDFMEM.mjs";
@@ -10268,6 +10268,51 @@ function modelDir() {
10268
10268
  function downloadUrl(file) {
10269
10269
  return `https://huggingface.co/${DEFAULT_MODEL}/resolve/${PINNED_MODEL_REVISION}/${file.name}`;
10270
10270
  }
10271
+ function sharedModelCacheDir() {
10272
+ const raw = process.env["TOKEN_GOAT_MODEL_CACHE_DIR"]?.trim();
10273
+ if (!raw) return null;
10274
+ return path7.join(raw, ...DEFAULT_MODEL.split("/"), PINNED_MODEL_REVISION);
10275
+ }
10276
+ async function copyFromSharedCache(shared, file, target) {
10277
+ const source = path7.join(shared, file.name);
10278
+ const temp = `${target}.${process.pid}.shared`;
10279
+ try {
10280
+ const info = fs11.lstatSync(source, { throwIfNoEntry: false });
10281
+ if (!info?.isFile() || info.size !== file.bytes) return false;
10282
+ fs11.copyFileSync(source, temp);
10283
+ if (await sha256Of(temp) !== file.sha256) {
10284
+ fs11.rmSync(temp, { force: true, maxRetries: 20, retryDelay: 25 });
10285
+ fs11.rmSync(source, { force: true, maxRetries: 20, retryDelay: 25 });
10286
+ return false;
10287
+ }
10288
+ fs11.renameSync(temp, target);
10289
+ return true;
10290
+ } catch {
10291
+ try {
10292
+ fs11.rmSync(temp, { force: true, maxRetries: 20, retryDelay: 25 });
10293
+ } catch {
10294
+ }
10295
+ return false;
10296
+ }
10297
+ }
10298
+ function publishToSharedCache(shared, file, target) {
10299
+ const destination = path7.join(shared, file.name);
10300
+ const temp = `${destination}.${process.pid}.partial`;
10301
+ let created = false;
10302
+ try {
10303
+ if (fs11.existsSync(destination)) return;
10304
+ fs11.mkdirSync(path7.dirname(destination), { recursive: true });
10305
+ fs11.copyFileSync(target, temp, fs11.constants.COPYFILE_EXCL);
10306
+ created = true;
10307
+ fs11.renameSync(temp, destination);
10308
+ } catch {
10309
+ if (!created) return;
10310
+ try {
10311
+ fs11.rmSync(temp, { force: true, maxRetries: 20, retryDelay: 25 });
10312
+ } catch {
10313
+ }
10314
+ }
10315
+ }
10271
10316
  var _ort = null;
10272
10317
  var _ortError = null;
10273
10318
  var _ortLoadAttempted = false;
@@ -10363,6 +10408,7 @@ async function ensureModelFiles(modelName = DEFAULT_MODEL) {
10363
10408
  ensureDataDirPrivate();
10364
10409
  const dir = modelDir();
10365
10410
  const offline = loadConfig().network.offline;
10411
+ const shared = sharedModelCacheDir();
10366
10412
  for (const file of MODEL_FILES) {
10367
10413
  const target = path7.join(dir, file.name);
10368
10414
  if (fs11.existsSync(target)) {
@@ -10370,6 +10416,10 @@ async function ensureModelFiles(modelName = DEFAULT_MODEL) {
10370
10416
  if (digest === file.sha256) continue;
10371
10417
  fs11.rmSync(target, { force: true });
10372
10418
  }
10419
+ if (shared) {
10420
+ fs11.mkdirSync(path7.dirname(target), { recursive: true });
10421
+ if (await copyFromSharedCache(shared, file, target)) continue;
10422
+ }
10373
10423
  if (offline) {
10374
10424
  throw new Error(
10375
10425
  `Offline mode is on (network.offline): refusing to download ${file.name} for the embedding model. Copy the pinned files into ${dir} on a connected machine to use semantic search here.`
@@ -10380,6 +10430,7 @@ async function ensureModelFiles(modelName = DEFAULT_MODEL) {
10380
10430
  `Downloading the embedding model, once (${file.name}, ${Math.round(file.bytes / 1024 / 1024)} MB) into ${dir}`
10381
10431
  );
10382
10432
  await download(file, target);
10433
+ if (shared) publishToSharedCache(shared, file, target);
10383
10434
  }
10384
10435
  return dir;
10385
10436
  }
@@ -20023,6 +20074,41 @@ function readIntToolInput(event, key) {
20023
20074
  }
20024
20075
  return void 0;
20025
20076
  }
20077
+ function readRequestedSliceWindow(event) {
20078
+ const rawOffset = readIntToolInput(event, "offset");
20079
+ const rawLimit = readIntToolInput(event, "limit");
20080
+ if (rawOffset !== void 0 || rawLimit !== void 0) {
20081
+ return {
20082
+ offset: rawOffset !== void 0 && rawOffset >= 1 ? Math.floor(rawOffset) : 1,
20083
+ limit: rawLimit !== void 0 && rawLimit > 0 ? Math.floor(rawLimit) : void 0,
20084
+ isExplicitSlice: true
20085
+ };
20086
+ }
20087
+ const rawRange = event.toolInput["view_range"] ?? event.toolInput["lines"] ?? event.toolInput["range"];
20088
+ if (Array.isArray(rawRange) && rawRange.length >= 1) {
20089
+ const rawStart = Number(rawRange[0]);
20090
+ const startVal = Number.isFinite(rawStart) && rawStart >= 1 ? Math.floor(rawStart) : 1;
20091
+ if (rawRange.length >= 2) {
20092
+ const rawEnd = Number(rawRange[1]);
20093
+ if (rawEnd === -1) {
20094
+ return { offset: startVal, isExplicitSlice: true };
20095
+ }
20096
+ if (Number.isFinite(rawEnd) && rawEnd >= startVal) {
20097
+ const lineCount = Math.floor(rawEnd - startVal + 1);
20098
+ return { offset: startVal, limit: lineCount, isExplicitSlice: true };
20099
+ }
20100
+ }
20101
+ return { offset: startVal, isExplicitSlice: true };
20102
+ }
20103
+ const startLine = readIntToolInput(event, "start_line") ?? readIntToolInput(event, "startLine") ?? readIntToolInput(event, "start");
20104
+ const endLine = readIntToolInput(event, "end_line") ?? readIntToolInput(event, "endLine") ?? readIntToolInput(event, "end");
20105
+ if (startLine !== void 0 || endLine !== void 0) {
20106
+ const effectiveStart = startLine !== void 0 && startLine >= 1 ? Math.floor(startLine) : 1;
20107
+ const effectiveLimit = endLine !== void 0 && endLine >= effectiveStart ? Math.floor(endLine - effectiveStart + 1) : void 0;
20108
+ return { offset: effectiveStart, limit: effectiveLimit, isExplicitSlice: true };
20109
+ }
20110
+ return { isExplicitSlice: false };
20111
+ }
20026
20112
  function readStartLine(event) {
20027
20113
  const resp = event.raw["tool_response"];
20028
20114
  if (resp === null || typeof resp !== "object") return 1;
@@ -20086,11 +20172,10 @@ function scanRequestedSlice(absPath, offset, limit) {
20086
20172
  }
20087
20173
  }
20088
20174
  function estimateRequestedSlice(event, absPath) {
20089
- const offset = readIntToolInput(event, "offset");
20090
- const limit = readIntToolInput(event, "limit");
20091
- if (limit === void 0 || limit <= 0) return { kind: "unbounded" };
20092
- const effectiveOffset = offset !== void 0 && offset >= 1 ? offset : 1;
20093
- const scan = scanRequestedSlice(absPath, effectiveOffset, limit);
20175
+ const window2 = readRequestedSliceWindow(event);
20176
+ if (window2.limit === void 0 || window2.limit <= 0) return { kind: "unbounded" };
20177
+ const effectiveOffset = window2.offset !== void 0 && window2.offset >= 1 ? window2.offset : 1;
20178
+ const scan = scanRequestedSlice(absPath, effectiveOffset, window2.limit);
20094
20179
  if (scan === null) return { kind: "unbounded" };
20095
20180
  if (scan.nearSingleLine) return { kind: "nearSingleLine" };
20096
20181
  if (scan.trustworthy) return { kind: "bytes", bytes: scan.bytes };
@@ -20330,7 +20415,7 @@ function preReadHandlerInner(event) {
20330
20415
  } catch {
20331
20416
  }
20332
20417
  }
20333
- const compactUnrangedRead = readIntToolInput(event, "offset") === void 0 && readIntToolInput(event, "limit") === void 0;
20418
+ const compactUnrangedRead = !readRequestedSliceWindow(event).isExplicitSlice;
20334
20419
  if (event.toolName !== "Grep" && compactUnrangedRead && loadConfig().hints.stable_doc_compacts && _isDocFile(normalized)) {
20335
20420
  const compactPath = compactPathFor(normalized);
20336
20421
  if (isCompactFresh(compactPath, normalized)) {
@@ -20413,7 +20498,7 @@ function preReadHandlerInner(event) {
20413
20498
  }
20414
20499
  return denyOutput(message);
20415
20500
  }
20416
- const unrangedRead = readIntToolInput(event, "offset") === void 0 && readIntToolInput(event, "limit") === void 0;
20501
+ const unrangedRead = !readRequestedSliceWindow(event).isExplicitSlice;
20417
20502
  if (loadConfig().hints.subagent_markdown_first_read_deny && event.agentId !== void 0 && unrangedRead && SUBAGENT_MD_FIRST_READ_DENY_EXT_RE.test(basename12) && markdownSize !== null && markdownSize >= SUBAGENT_MD_FIRST_READ_DENY_BYTES) {
20418
20503
  recordStat("subagent_markdown_first_read_deny", 0, 0);
20419
20504
  return denyOutput(
@@ -20783,9 +20868,9 @@ function readWindowFromDisk(event, normalized) {
20783
20868
  const size = statSize(normalized);
20784
20869
  if (size === null || size > SLICE_ESTIMATE_SCAN_CAP_BYTES) return null;
20785
20870
  const text = decodeSource(fs21.readFileSync(normalized));
20786
- const limit = readIntToolInput(event, "limit");
20787
- const offset = readIntToolInput(event, "offset");
20788
- const start = offset !== void 0 && offset >= 1 ? offset : 1;
20871
+ const window2 = readRequestedSliceWindow(event);
20872
+ const start = window2.offset !== void 0 && window2.offset >= 1 ? window2.offset : 1;
20873
+ const limit = window2.limit;
20789
20874
  if (start === 1 && (limit === void 0 || limit <= 0)) return text;
20790
20875
  const lines2 = text.split("\n");
20791
20876
  const from = start - 1;
@@ -20908,7 +20993,7 @@ function elideAlreadyServedLines(event, respText) {
20908
20993
  function structuralFoldInputs(event, respText) {
20909
20994
  const filePath = getFilePath(event);
20910
20995
  if (filePath === void 0) return null;
20911
- if (readIntToolInput(event, "offset") !== void 0 || readIntToolInput(event, "limit") !== void 0) return null;
20996
+ if (readRequestedSliceWindow(event).isExplicitSlice) return null;
20912
20997
  if (isTruncatedReadDelivery(event, respText)) return null;
20913
20998
  if (redactSecrets(respText).count > 0) return null;
20914
20999
  const parsed = parseNumberedReadResult(respText, readStartLine(event));
@@ -20943,14 +21028,15 @@ function foldCodeBodies(event, respText) {
20943
21028
  if (filePath === void 0) return null;
20944
21029
  const normalized = normalizePath(filePath);
20945
21030
  if (isImagePath(normalized)) return null;
20946
- const requestedOffset = readIntToolInput(event, "offset");
21031
+ const sliceWin = readRequestedSliceWindow(event);
21032
+ const requestedOffset = sliceWin.offset;
20947
21033
  if (requestedOffset !== void 0 && readStartLine(event) !== requestedOffset) return null;
20948
21034
  if (isTruncatedReadDelivery(event, respText)) return null;
20949
21035
  if (redactSecrets(respText).count > 0) return null;
20950
21036
  const parsed = parseNumberedReadResult(respText, readStartLine(event));
20951
21037
  if (parsed === null) return null;
20952
21038
  const shown = displaySafePath(toDisplayPath(findProject(getCwd(event) ?? process.cwd())?.root, normalized));
20953
- const folded = foldDelivery(parsed.rows, normalized, shown, requestedOffset !== void 0 || readIntToolInput(event, "limit") !== void 0);
21039
+ const folded = foldDelivery(parsed.rows, normalized, shown, sliceWin.isExplicitSlice);
20954
21040
  if (folded === null) return null;
20955
21041
  const rewritten = [...parsed.header, fenceUntrustedFileContent(folded.numbered.join("\n")), ...parsed.trailer].join("\n");
20956
21042
  const originalBytes = Buffer.byteLength(respText, "utf-8");
@@ -9,7 +9,7 @@ import {
9
9
  isBlobStale,
10
10
  loadBlob,
11
11
  storeBlob
12
- } from "./token-goat-chunk-TXZY7C24.mjs";
12
+ } from "./token-goat-chunk-PA5UXNOT.mjs";
13
13
  import {
14
14
  SYMBOL_BODY_CHAR_CAP,
15
15
  copilotCliMcpToolsDir,
@@ -25,7 +25,7 @@ import {
25
25
  resolveIndexPath,
26
26
  shortFingerprint,
27
27
  toDisplayPath
28
- } from "./token-goat-chunk-JZYJH76S.mjs";
28
+ } from "./token-goat-chunk-VCEK77ED.mjs";
29
29
  import {
30
30
  registerReset
31
31
  } from "./token-goat-chunk-EEIDFMEM.mjs";
@@ -12,7 +12,7 @@ init_define_import_meta_env();
12
12
  import { createRequire } from "node:module";
13
13
  function resolveVersion() {
14
14
  if (true) {
15
- return "2.9.8";
15
+ return "2.9.10";
16
16
  }
17
17
  const require2 = createRequire(import.meta.url);
18
18
  const pkg = require2("../package.json");
@@ -9636,6 +9636,25 @@ function remapToolInput(copilotToolName, input) {
9636
9636
  if (idKey !== undefined && idKey in out) {
9637
9637
  out = Object.assign({}, out, { bash_id: out[idKey] })
9638
9638
  }
9639
+ if (Array.isArray(out.view_range) && out.view_range.length >= 1) {
9640
+ const rawStart = Number(out.view_range[0])
9641
+ if (Number.isFinite(rawStart) && rawStart >= 1) {
9642
+ const mapped = { offset: Math.floor(rawStart) }
9643
+ if (out.view_range.length >= 2) {
9644
+ const rawEnd = Number(out.view_range[1])
9645
+ if (Number.isFinite(rawEnd) && rawEnd >= rawStart) {
9646
+ mapped.limit = Math.floor(rawEnd - rawStart + 1)
9647
+ }
9648
+ }
9649
+ out = Object.assign({}, out, mapped)
9650
+ }
9651
+ }
9652
+ if ('old_str' in out && !('old_string' in out)) {
9653
+ out = Object.assign({}, out, { old_string: out.old_str })
9654
+ }
9655
+ if ('new_str' in out && !('new_string' in out)) {
9656
+ out = Object.assign({}, out, { new_string: out.new_str })
9657
+ }
9639
9658
  return out
9640
9659
  }
9641
9660
 
@@ -67,7 +67,7 @@ import {
67
67
  walkProject,
68
68
  yamlLineClosesQuote,
69
69
  yamlOpenQuoteAfter
70
- } from "./token-goat-chunk-TXZY7C24.mjs";
70
+ } from "./token-goat-chunk-PA5UXNOT.mjs";
71
71
  import {
72
72
  Database,
73
73
  PER_FILE_COUNTERFACTUAL_CEILING,
@@ -125,7 +125,7 @@ import {
125
125
  unsupportedLanguageName,
126
126
  windowsCmdQuoteArg,
127
127
  withExtension
128
- } from "./token-goat-chunk-JZYJH76S.mjs";
128
+ } from "./token-goat-chunk-VCEK77ED.mjs";
129
129
  import {
130
130
  registerReset
131
131
  } from "./token-goat-chunk-EEIDFMEM.mjs";
@@ -18,7 +18,7 @@ import {
18
18
  storeWebOutput,
19
19
  summarizeResidentContext,
20
20
  taskListPruneHint
21
- } from "./token-goat-chunk-VEEHPNBV.mjs";
21
+ } from "./token-goat-chunk-SLGOIXKF.mjs";
22
22
  import {
23
23
  BASH_OUTPUT_SUBDIR,
24
24
  GENERIC_SERVED_OUTPUT_KEY,
@@ -125,12 +125,12 @@ import {
125
125
  wasCliReadThisSession,
126
126
  wasFileReadThisSession,
127
127
  wasHintShown
128
- } from "./token-goat-chunk-TXZY7C24.mjs";
128
+ } from "./token-goat-chunk-PA5UXNOT.mjs";
129
129
  import {
130
130
  bashOutputCapBytes,
131
131
  canRunWrappedShell,
132
132
  deliveredOutputBytes
133
- } from "./token-goat-chunk-REHUP2OE.mjs";
133
+ } from "./token-goat-chunk-ATVQ6IIT.mjs";
134
134
  import {
135
135
  BODY_FIRST_TOOL_RESPONSE_KEYS,
136
136
  ENV_KEYS,
@@ -194,7 +194,7 @@ import {
194
194
  stripAnsiEscapes,
195
195
  toDisplayPath,
196
196
  toKB
197
- } from "./token-goat-chunk-JZYJH76S.mjs";
197
+ } from "./token-goat-chunk-VCEK77ED.mjs";
198
198
  import {
199
199
  init_define_import_meta_env
200
200
  } from "./token-goat-chunk-A37V4PBF.mjs";
@@ -798,7 +798,7 @@ init_define_import_meta_env();
798
798
  var TRACKED_SKILL = "token-goat";
799
799
  var MAX_COMMANDS_SHOWN = 8;
800
800
  async function currentCommandNames() {
801
- const { buildProgram } = await import("./token-goat-chunk-3YANZKER.mjs");
801
+ const { buildProgram } = await import("./token-goat-chunk-DSHYDU53.mjs");
802
802
  return flattenCommandNames(buildCommandManifest(buildProgram()));
803
803
  }
804
804
  async function recordSkillVersionSnapshot(sessionId, skillName) {
@@ -9994,8 +9994,8 @@ registerHook("post_tool_use", postTaskOutputHandler, { toolName: "TaskOutput" })
9994
9994
 
9995
9995
  // src/hooks_tool_failure.ts
9996
9996
  init_define_import_meta_env();
9997
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
9998
- import { dirname as dirname2 } from "node:path";
9997
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, statSync as statSync4, writeFileSync as writeFileSync2 } from "node:fs";
9998
+ import { dirname as dirname2, relative } from "node:path";
9999
9999
  var FAILURE_SUFFIX = ".tool-failures.json";
10000
10000
  var MAX_TRACKED_FAILURES = 64;
10001
10001
  var SIGNATURE_ERROR_CHARS = 200;
@@ -10045,6 +10045,71 @@ function repeatFailureNotice(toolName) {
10045
10045
  const tool = toolName === void 0 || toolName === "" ? "This tool" : displaySafeText(toolName);
10046
10046
  return `[token-goat] ${tool} just failed with the same error as an earlier call this session. Retrying it unchanged will fail the same way -- change the arguments, the tool, or the approach.`;
10047
10047
  }
10048
+ var MAX_EDIT_DIAGNOSE_BYTES = 10 * 1024 * 1024;
10049
+ function diagnoseEditFailure(event, errorText) {
10050
+ const toolName = getToolName(event);
10051
+ if (!toolName || !/^(edit|str_replace_editor)$/i.test(toolName)) {
10052
+ return null;
10053
+ }
10054
+ const isMultiple = /multiple matches|not unique|found \d+ matches|matches \d+ times|more than one match/i.test(errorText);
10055
+ const isNotFound = /no match|not found|could not find|zero matches|string to replace.*not found/i.test(errorText);
10056
+ if (!isMultiple && !isNotFound) {
10057
+ return null;
10058
+ }
10059
+ const filePath = getFilePath(event) ?? (typeof event.toolInput["path"] === "string" ? event.toolInput["path"] : void 0);
10060
+ if (!filePath) return null;
10061
+ const oldString = typeof event.toolInput["old_string"] === "string" ? event.toolInput["old_string"] : typeof event.toolInput["old_str"] === "string" ? event.toolInput["old_str"] : typeof event.toolInput["target"] === "string" ? event.toolInput["target"] : void 0;
10062
+ if (oldString === void 0 || oldString === "") return null;
10063
+ const absPath = normalizePath(filePath);
10064
+ if (!existsSync2(absPath)) return null;
10065
+ try {
10066
+ const stat = statSync4(absPath);
10067
+ if (stat.size > MAX_EDIT_DIAGNOSE_BYTES) return null;
10068
+ const fileContent = readFileSync3(absPath, "utf8");
10069
+ const relDisplay = displaySafeText(relative(process.cwd(), absPath).replace(/\\/g, "/") || absPath);
10070
+ if (isMultiple) {
10071
+ const matchLines = [];
10072
+ let pos = 0;
10073
+ while (pos < fileContent.length) {
10074
+ const idx = fileContent.indexOf(oldString, pos);
10075
+ if (idx === -1) break;
10076
+ const line = fileContent.slice(0, idx).split("\n").length;
10077
+ matchLines.push(line);
10078
+ pos = idx + Math.max(1, oldString.length);
10079
+ }
10080
+ if (matchLines.length > 1) {
10081
+ const lineList = matchLines.slice(0, 5).join(", ");
10082
+ const overflow = matchLines.length > 5 ? ` (+${matchLines.length - 5} more)` : "";
10083
+ return `[token-goat] Edit failed: string matched ${matchLines.length} times in ${relDisplay} on lines ${lineList}${overflow}. Include 2-3 lines of surrounding context to make old_str unique.`;
10084
+ }
10085
+ }
10086
+ if (isNotFound) {
10087
+ const normOld = oldString.replace(/\r\n/g, "\n");
10088
+ const normFile = fileContent.replace(/\r\n/g, "\n");
10089
+ if (normFile.includes(normOld)) {
10090
+ return `[token-goat] Edit failed: string not found in ${relDisplay}, but matches with normalized line endings. Check CRLF vs LF line endings or whitespace.`;
10091
+ }
10092
+ const firstLine = oldString.split(/\r?\n/)[0]?.trim() ?? "";
10093
+ if (firstLine.length >= 10) {
10094
+ const fileLines = fileContent.split("\n");
10095
+ const similarLines = [];
10096
+ for (let i = 0; i < fileLines.length; i++) {
10097
+ const lineText = fileLines[i];
10098
+ if (lineText !== void 0 && lineText.includes(firstLine)) {
10099
+ similarLines.push(i + 1);
10100
+ if (similarLines.length >= 3) break;
10101
+ }
10102
+ }
10103
+ if (similarLines.length > 0) {
10104
+ return `[token-goat] Edit failed: string not found in ${relDisplay}. A similar line was found on line ${similarLines.join(", ")} \u2014 view that range to copy exact text.`;
10105
+ }
10106
+ }
10107
+ return `[token-goat] Edit failed: string not found in ${relDisplay}. View the target lines to copy the exact current content and indentation.`;
10108
+ }
10109
+ } catch {
10110
+ }
10111
+ return null;
10112
+ }
10048
10113
  function postToolUseFailureHandler(event) {
10049
10114
  try {
10050
10115
  if (!event.sessionId) return passOutput();
@@ -10056,6 +10121,7 @@ function postToolUseFailureHandler(event) {
10056
10121
  const signature = failureSignature(toolName, errorText);
10057
10122
  const ledger = readLedger(target);
10058
10123
  const priorState = ledger.seen[signature];
10124
+ const editDiagnostic = diagnoseEditFailure(event, errorText);
10059
10125
  if (priorState === void 0) {
10060
10126
  ledger.seen[signature] = false;
10061
10127
  ledger.order.push(signature);
@@ -10064,11 +10130,19 @@ function postToolUseFailureHandler(event) {
10064
10130
  if (evicted !== void 0) delete ledger.seen[evicted];
10065
10131
  }
10066
10132
  writeLedger(target, ledger);
10133
+ if (editDiagnostic !== null) {
10134
+ ledger.seen[signature] = true;
10135
+ writeLedger(target, ledger);
10136
+ return contextOutput(editDiagnostic);
10137
+ }
10067
10138
  return passOutput();
10068
10139
  }
10069
10140
  if (priorState) return passOutput();
10070
10141
  ledger.seen[signature] = true;
10071
10142
  writeLedger(target, ledger);
10143
+ if (editDiagnostic !== null) {
10144
+ return contextOutput(editDiagnostic);
10145
+ }
10072
10146
  return contextOutput(repeatFailureNotice(toolName));
10073
10147
  } catch {
10074
10148
  return passOutput();
@@ -2,11 +2,11 @@ import { createRequire as __cjsRequire } from 'node:module';
2
2
  const require = __cjsRequire(import.meta.url);
3
3
  import {
4
4
  relayInProcess
5
- } from "./token-goat-chunk-XYLPMQ6I.mjs";
6
- import "./token-goat-chunk-VEEHPNBV.mjs";
7
- import "./token-goat-chunk-TXZY7C24.mjs";
8
- import "./token-goat-chunk-REHUP2OE.mjs";
9
- import "./token-goat-chunk-JZYJH76S.mjs";
5
+ } from "./token-goat-chunk-ZQODJQTP.mjs";
6
+ import "./token-goat-chunk-SLGOIXKF.mjs";
7
+ import "./token-goat-chunk-PA5UXNOT.mjs";
8
+ import "./token-goat-chunk-ATVQ6IIT.mjs";
9
+ import "./token-goat-chunk-VCEK77ED.mjs";
10
10
  import "./token-goat-chunk-EEIDFMEM.mjs";
11
11
  import {
12
12
  init_define_import_meta_env
@@ -2,13 +2,13 @@ import { createRequire as __cjsRequire } from 'node:module';
2
2
  const require = __cjsRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./token-goat-chunk-2SBHUPBF.mjs";
6
- import "./token-goat-chunk-6RAUVAKW.mjs";
7
- import "./token-goat-chunk-VEEHPNBV.mjs";
8
- import "./token-goat-chunk-TXZY7C24.mjs";
5
+ } from "./token-goat-chunk-7ALFFPJW.mjs";
6
+ import "./token-goat-chunk-YFXFUG3L.mjs";
7
+ import "./token-goat-chunk-SLGOIXKF.mjs";
8
+ import "./token-goat-chunk-PA5UXNOT.mjs";
9
9
  import {
10
10
  installEpipeGuard
11
- } from "./token-goat-chunk-JZYJH76S.mjs";
11
+ } from "./token-goat-chunk-VCEK77ED.mjs";
12
12
  import "./token-goat-chunk-EEIDFMEM.mjs";
13
13
  import {
14
14
  init_define_import_meta_env
package/docs/security.md CHANGED
@@ -15,7 +15,7 @@ Outbound network is reserved to these explicit cases:
15
15
  - Google Drive API calls, only if you already authorized Drive in Claude Code. Token-goat never prompts for its own auth.
16
16
  - Image fetches from URLs: either explicit via `token-goat fetch-image <url>`, or when the AI agent issues a WebFetch call that returns image content — the hook intercepts and shrinks the image. The URL always originates from the agent's work, not from token-goat itself.
17
17
  - `token-goat screenshot <url>` navigates a headless browser to the URL you give it, subject to the target restrictions described below.
18
- - The first `token-goat semantic` run on a machine downloads the embedding model from `huggingface.co`, pinned to an immutable commit rather than a mutable branch and checked against a recorded SHA-256 and byte length before it is used, and only once `onnxruntime-node` has been installed (see below — it is not part of a default install). Subsequent runs use the local cache, re-verify it, and make no network call. Skip the download entirely by setting `indexing.embeddings_enabled = false` (it is on by default), in which case `semantic` falls back to full-text search.
18
+ - The first `token-goat semantic` run on a machine downloads the embedding model from `huggingface.co`, pinned to an immutable commit rather than a mutable branch and checked against a recorded SHA-256 and byte length before it is used, and only once `onnxruntime-node` has been installed (see below — it is not part of a default install). Subsequent runs use the local cache, re-verify it, and make no network call. Setting `TOKEN_GOAT_MODEL_CACHE_DIR` adds one more place to look before the network: a directory you name that outlives any single data root, which is how a continuous-integration job stops refetching the weights once per worker. A file taken from there is checked against the same recorded digest as a downloaded one and dropped from the shared directory if it fails, and an entry that is not a plain file of the expected size is passed over without being read at all, so a tampered copy costs a download and cannot substitute different weights, stall the read, or fill the disk. Token-goat also writes there, and treats the directory as somebody else's: it creates its temporary file exclusively rather than through whatever is already sitting at that name, and removes only what it created. Point it at a directory you control all the same, since nothing token-goat does can stop another writer filling it with entries that will simply be rejected. It is an environment variable only, so no project config file can point it anywhere. Skip the download entirely by setting `indexing.embeddings_enabled = false` (it is on by default), in which case `semantic` falls back to full-text search.
19
19
  - The first optical-character read of an image downloads the English language data (about 4 MB) from `cdn.jsdelivr.net`, at a fixed version path. Subsequent reads use the local cache. This happens for an explicit `token-goat image-text`, and also for the automatic text extraction the image-shrink hook performs when the agent reads a screenshot; turn the automatic one off with `image_shrink.ocr_enabled = false`.
20
20
 
21
21
  **One switch for all of it.** Set `network.offline = true` (env `TOKEN_GOAT_OFFLINE`) and every one of the paths above refuses instead of connecting, saying so rather than failing quietly. Anything already cached keeps working: a machine that has the embedding model still runs `semantic`, and one that has the language data still reads text out of images. This is one of the settings a per-project config file may not touch, so cloning a repository cannot switch it back off.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "token-goat",
3
- "version": "2.9.8",
3
+ "version": "2.9.10",
4
4
  "description": "Surgical token-reduction companion for Claude Code and other AI coding agents",
5
5
  "type": "module",
6
6
  "main": "./dist/token-goat.mjs",