token-goat 2.6.11 → 2.6.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/token-goat.mjs +231 -84
  2. package/package.json +1 -1
@@ -130752,7 +130752,7 @@ var init_cliui = __esm({
130752
130752
 
130753
130753
  // node_modules/escalade/sync/index.mjs
130754
130754
  import { dirname as dirname18, resolve as resolve11 } from "path";
130755
- import { readdirSync as readdirSync8, statSync as statSync19 } from "fs";
130755
+ import { readdirSync as readdirSync9, statSync as statSync19 } from "fs";
130756
130756
  function sync_default(start, callback) {
130757
130757
  let dir = resolve11(".", start);
130758
130758
  let tmp, stats = statSync19(dir);
@@ -130760,7 +130760,7 @@ function sync_default(start, callback) {
130760
130760
  dir = dirname18(dir);
130761
130761
  }
130762
130762
  while (true) {
130763
- tmp = callback(dir, readdirSync8(dir));
130763
+ tmp = callback(dir, readdirSync9(dir));
130764
130764
  if (tmp) return resolve11(dir, tmp);
130765
130765
  dir = dirname18(tmp = dir);
130766
130766
  if (tmp === dir) break;
@@ -178698,7 +178698,7 @@ init_define_import_meta_env();
178698
178698
  import { createRequire } from "node:module";
178699
178699
  function resolveVersion() {
178700
178700
  if (true) {
178701
- return "2.6.11";
178701
+ return "2.6.12";
178702
178702
  }
178703
178703
  const require3 = createRequire(import.meta.url);
178704
178704
  const pkg = require3("../package.json");
@@ -183058,6 +183058,21 @@ const FILE_PATH_ARG_KEY = {
183058
183058
  create: 'path',
183059
183059
  }
183060
183060
 
183061
+ // Copilot spawns a brand-new process for every single hook invocation (no long-lived plugin
183062
+ // process the way OpenClaw's is -- OPENCLAW_HOOK_SCRIPT's own \`copilot-\${process.pid}-\${Date.now()}\`
183063
+ // fallback is safe there specifically because that process lives for the whole session, so the
183064
+ // pid stays constant across calls). If Copilot ever omits \`sessionId\` from a payload, falling
183065
+ // back to \`process.pid\` here would mint a DIFFERENT id on every single call for what's really
183066
+ // the same session, since process.pid varies per invocation -- breaking token-goat's
183067
+ // session-based dedup/state ledger, which never accumulates across calls as a result. Derive a
183068
+ // stable id instead from the one thing that's actually constant across calls for the same
183069
+ // session: the working directory Copilot reports in \`payload.cwd\`.
183070
+ function stableFallbackSessionId(cwd) {
183071
+ const key = typeof cwd === 'string' && cwd ? cwd : process.cwd()
183072
+ const hash = require('node:crypto').createHash('sha256').update(key).digest('hex').slice(0, 16)
183073
+ return 'copilot-' + hash
183074
+ }
183075
+
183061
183076
  function remapToolInput(copilotToolName, input) {
183062
183077
  const pathKey = FILE_PATH_ARG_KEY[copilotToolName]
183063
183078
  if (pathKey === undefined || !input || typeof input !== 'object' || !(pathKey in input)) {
@@ -183100,7 +183115,7 @@ function main() {
183100
183115
 
183101
183116
  const toolName = payload && payload.toolName
183102
183117
  const canonical = {
183103
- session_id: (payload && payload.sessionId) || 'copilot-' + process.pid,
183118
+ session_id: (payload && payload.sessionId) || stableFallbackSessionId(payload && payload.cwd),
183104
183119
  cwd: payload && payload.cwd,
183105
183120
  }
183106
183121
  if (toolName) {
@@ -183757,6 +183772,7 @@ registerReset(() => {
183757
183772
  var SAFE_RE = /[^a-zA-Z0-9_-]/g;
183758
183773
  var MAX_FILES = 500;
183759
183774
  var SESSIONS_SUBDIR = "sessions";
183775
+ var AGENT_SALT_MARKER = ":agent:".replace(SAFE_RE, "_");
183760
183776
  function sessionPath(sessionId) {
183761
183777
  if (!sessionId) return null;
183762
183778
  const safe = sessionId.replace(SAFE_RE, "_").slice(0, 64);
@@ -183945,6 +183961,25 @@ function readSessionStateFile(sessionId) {
183945
183961
  if (!p) return null;
183946
183962
  return readDiskState(p);
183947
183963
  }
183964
+ function listSiblingSessionStates(sessionId) {
183965
+ if (!sessionId) return [];
183966
+ const safeSessionId = sessionId.replace(SAFE_RE, "_");
183967
+ if (!safeSessionId) return [];
183968
+ const prefix = `${safeSessionId}${AGENT_SALT_MARKER}`;
183969
+ const dir = path12.join(tokenGoatHome(), SESSIONS_SUBDIR);
183970
+ const out2 = [];
183971
+ try {
183972
+ if (!fs10.existsSync(dir)) return out2;
183973
+ for (const file2 of fs10.readdirSync(dir)) {
183974
+ if (!file2.endsWith(".json") || !file2.startsWith(prefix)) continue;
183975
+ const state = readDiskState(path12.join(dir, file2));
183976
+ if (state !== null) out2.push(state);
183977
+ }
183978
+ } catch {
183979
+ return out2;
183980
+ }
183981
+ return out2;
183982
+ }
183948
183983
  function loadSessionState(sessionId) {
183949
183984
  const p = sessionPath(sessionId);
183950
183985
  if (!p) return;
@@ -184189,7 +184224,7 @@ function findLatestSessionId() {
184189
184224
  return null;
184190
184225
  }
184191
184226
  const files = fs11.readdirSync(sessionsDir);
184192
- const jsonFiles = files.filter((f) => f.endsWith(".json"));
184227
+ const jsonFiles = files.filter((f) => f.endsWith(".json") && !f.includes(AGENT_SALT_MARKER));
184193
184228
  if (jsonFiles.length === 0) {
184194
184229
  return null;
184195
184230
  }
@@ -184843,7 +184878,7 @@ function rerankHits(hits, query, topK) {
184843
184878
  return { hit, index, adjusted: hit.distance - boost + penalty };
184844
184879
  });
184845
184880
  scored.sort((a, b) => a.adjusted - b.adjusted || a.index - b.index);
184846
- return scored.slice(0, topK).map((entry) => entry.hit);
184881
+ return scored.slice(0, topK).map((entry) => ({ ...entry.hit, adjustedDistance: entry.adjusted }));
184847
184882
  }
184848
184883
  function mergeNearbyHits(hits, proximity = 20) {
184849
184884
  if (hits.length <= 1) {
@@ -184868,6 +184903,7 @@ function mergeNearbyHits(hits, proximity = 20) {
184868
184903
  let curStart = current.startLine;
184869
184904
  let curEnd = current.endLine;
184870
184905
  let curDist = current.distance;
184906
+ let curAdjusted = current.adjustedDistance ?? current.distance;
184871
184907
  const mergedTexts = [current.text];
184872
184908
  for (let i = 1; i < fileHits.length; i++) {
184873
184909
  const hit = fileHits[i];
@@ -184878,6 +184914,7 @@ function mergeNearbyHits(hits, proximity = 20) {
184878
184914
  if (gap <= proximity) {
184879
184915
  curEnd = Math.max(curEnd, hit.endLine);
184880
184916
  curDist = Math.min(curDist, hit.distance);
184917
+ curAdjusted = Math.min(curAdjusted, hit.adjustedDistance ?? hit.distance);
184881
184918
  mergedTexts.push(hit.text);
184882
184919
  } else {
184883
184920
  merged.push({
@@ -184886,12 +184923,14 @@ function mergeNearbyHits(hits, proximity = 20) {
184886
184923
  endLine: curEnd,
184887
184924
  kind: current.kind,
184888
184925
  distance: curDist,
184926
+ adjustedDistance: curAdjusted,
184889
184927
  text: mergedTexts.join("\n---\n")
184890
184928
  });
184891
184929
  current = hit;
184892
184930
  curStart = hit.startLine;
184893
184931
  curEnd = hit.endLine;
184894
184932
  curDist = hit.distance;
184933
+ curAdjusted = hit.adjustedDistance ?? hit.distance;
184895
184934
  mergedTexts.length = 0;
184896
184935
  mergedTexts.push(hit.text);
184897
184936
  }
@@ -184902,10 +184941,11 @@ function mergeNearbyHits(hits, proximity = 20) {
184902
184941
  endLine: curEnd,
184903
184942
  kind: current.kind,
184904
184943
  distance: curDist,
184944
+ adjustedDistance: curAdjusted,
184905
184945
  text: mergedTexts.join("\n---\n")
184906
184946
  });
184907
184947
  }
184908
- merged.sort((a, b) => a.distance - b.distance);
184948
+ merged.sort((a, b) => (a.adjustedDistance ?? a.distance) - (b.adjustedDistance ?? b.distance));
184909
184949
  return merged;
184910
184950
  }
184911
184951
  async function indexFile(db, filePath, content, boundaries = []) {
@@ -185271,8 +185311,10 @@ function findMultilineCloser(line, from2, state) {
185271
185311
  return m ? { maskEnd: m[0].length } : null;
185272
185312
  }
185273
185313
  case "tripleQuote": {
185274
- const idx = line.indexOf('"""', from2);
185275
- return idx === -1 ? null : { maskEnd: idx + 3 };
185314
+ const n = state.identifier !== "" ? parseInt(state.identifier, 10) : 3;
185315
+ const re = new RegExp(`"{${n},}`);
185316
+ const m = re.exec(line.slice(from2));
185317
+ return m === null ? null : { maskEnd: from2 + m.index + m[0].length };
185276
185318
  }
185277
185319
  case "verbatim": {
185278
185320
  let j = from2;
@@ -185308,15 +185350,20 @@ function findMultilineOpener(line, from2, lang) {
185308
185350
  }
185309
185351
  if (lang === "kotlin") {
185310
185352
  const idx = line.indexOf('"""', from2);
185311
- if (idx === -1) return null;
185353
+ if (idx === -1 || isInsideStringLiteral(line, idx)) return null;
185312
185354
  const closeIdx = line.indexOf('"""', idx + 3);
185313
185355
  if (closeIdx !== -1) {
185314
- return { openStart: idx, closesSameLine: closeIdx + 3, state: { kind: "tripleQuote", identifier: "" } };
185356
+ return { openStart: idx, closesSameLine: closeIdx + 3, state: { kind: "tripleQuote", identifier: "3" } };
185315
185357
  }
185316
- return { openStart: idx, closesSameLine: null, state: { kind: "tripleQuote", identifier: "" } };
185358
+ return { openStart: idx, closesSameLine: null, state: { kind: "tripleQuote", identifier: "3" } };
185317
185359
  }
185318
185360
  if (lang === "csharp") {
185319
- const tripleIdx = line.indexOf('"""', from2);
185361
+ const tripleRe = /"{3,}/g;
185362
+ tripleRe.lastIndex = from2;
185363
+ const tripleM = tripleRe.exec(line);
185364
+ let tripleIdx = tripleM ? tripleM.index : -1;
185365
+ const tripleLen = tripleM ? tripleM[0].length : 0;
185366
+ if (tripleIdx !== -1 && isInsideStringLiteral(line, tripleIdx)) tripleIdx = -1;
185320
185367
  const verbRe = /\$?@\$?"/g;
185321
185368
  verbRe.lastIndex = from2;
185322
185369
  const verbM = verbRe.exec(line);
@@ -185324,11 +185371,13 @@ function findMultilineOpener(line, from2, lang) {
185324
185371
  if (tripleIdx === -1 && verbIdx === -1) return null;
185325
185372
  const useTriple = tripleIdx !== -1 && (verbIdx === -1 || tripleIdx < verbIdx);
185326
185373
  if (useTriple) {
185327
- const closeIdx = line.indexOf('"""', tripleIdx + 3);
185328
- if (closeIdx !== -1) {
185329
- return { openStart: tripleIdx, closesSameLine: closeIdx + 3, state: { kind: "tripleQuote", identifier: "" } };
185374
+ const closeRe = new RegExp(`"{${tripleLen},}`);
185375
+ const closeM = closeRe.exec(line.slice(tripleIdx + tripleLen));
185376
+ if (closeM !== null) {
185377
+ const closeIdx = tripleIdx + tripleLen + closeM.index;
185378
+ return { openStart: tripleIdx, closesSameLine: closeIdx + closeM[0].length, state: { kind: "tripleQuote", identifier: String(tripleLen) } };
185330
185379
  }
185331
- return { openStart: tripleIdx, closesSameLine: null, state: { kind: "tripleQuote", identifier: "" } };
185380
+ return { openStart: tripleIdx, closesSameLine: null, state: { kind: "tripleQuote", identifier: String(tripleLen) } };
185332
185381
  }
185333
185382
  const quoteIdx = verbIdx + (verbM?.[0].length ?? 1) - 1;
185334
185383
  const closer = findMultilineCloser(line, quoteIdx + 1, { kind: "verbatim", identifier: "" });
@@ -186009,6 +186058,16 @@ var KIND_MAP = /* @__PURE__ */ new Map([
186009
186058
  ]);
186010
186059
  var MAX_SYMBOLS = 500;
186011
186060
  var MAX_HEADING_LEN = 120;
186061
+ function stripGraphqlDescriptions(text) {
186062
+ let state = null;
186063
+ const outLines = [];
186064
+ for (const line of text.split("\n")) {
186065
+ const { code, state: nextState } = stripMultilineStringSpan(line, state, "kotlin");
186066
+ state = nextState;
186067
+ outLines.push(stripStringLiterals(code));
186068
+ }
186069
+ return outLines.join("\n");
186070
+ }
186012
186071
  function extractGraphql(content, filePath) {
186013
186072
  const symbols = [];
186014
186073
  const sections = [];
@@ -186022,7 +186081,7 @@ function extractGraphql(content, filePath) {
186022
186081
  imports.push({ kind: "import", target, line });
186023
186082
  }
186024
186083
  }
186025
- const stripped = stripHashComments(content);
186084
+ const stripped = stripGraphqlDescriptions(stripHashComments(content));
186026
186085
  const totalLines = content.split("\n").length;
186027
186086
  for (const m of stripped.matchAll(TYPE_RE)) {
186028
186087
  const keyword = m.groups?.["keyword"] ?? "";
@@ -186102,6 +186161,37 @@ var PATTERNS = [
186102
186161
  [TYPE_RE2, "sql_type"],
186103
186162
  [SCHEMA_RE3, "sql_schema"]
186104
186163
  ];
186164
+ function stripSqlStringLiterals(text) {
186165
+ let out2 = "";
186166
+ let i = 0;
186167
+ while (i < text.length) {
186168
+ const ch2 = text[i];
186169
+ if (ch2 === "'") {
186170
+ const quote = ch2;
186171
+ out2 += quote;
186172
+ i++;
186173
+ while (i < text.length) {
186174
+ const c = text[i];
186175
+ if (c === quote) {
186176
+ if (text[i + 1] === quote) {
186177
+ out2 += " ";
186178
+ i += 2;
186179
+ continue;
186180
+ }
186181
+ out2 += quote;
186182
+ i++;
186183
+ break;
186184
+ }
186185
+ out2 += c === "\n" ? "\n" : " ";
186186
+ i++;
186187
+ }
186188
+ continue;
186189
+ }
186190
+ out2 += ch2;
186191
+ i++;
186192
+ }
186193
+ return out2;
186194
+ }
186105
186195
  function unquote(name2) {
186106
186196
  if (name2.length >= 2 && (name2[0] === '"' && name2[name2.length - 1] === '"' || name2[0] === "`" && name2[name2.length - 1] === "`" || name2[0] === "[" && name2[name2.length - 1] === "]")) {
186107
186197
  return name2.slice(1, -1);
@@ -186116,9 +186206,10 @@ function extractSql(content, filePath) {
186116
186206
  let stripped = stripSqlLineComments(content);
186117
186207
  stripped = stripCstyleComments(stripped);
186118
186208
  const totalLines = content.split("\n").length;
186209
+ const noStrings = stripSqlStringLiterals(stripped);
186119
186210
  for (const [pattern, kind] of PATTERNS) {
186120
186211
  pattern.lastIndex = 0;
186121
- for (const m of stripped.matchAll(pattern)) {
186212
+ for (const m of noStrings.matchAll(pattern)) {
186122
186213
  const rawName = m[1];
186123
186214
  if (rawName) {
186124
186215
  const name2 = unquote(rawName).trim();
@@ -186590,8 +186681,8 @@ function extractApex(content, filePath) {
186590
186681
  const seen = /* @__PURE__ */ new Set();
186591
186682
  const lineIndex = buildLineIndex(content);
186592
186683
  const rawLines = content.split(/\r?\n/);
186593
- const commentFree = stripCstyleComments(content, /\/\/.*$/gm);
186594
- const code = stripStringLiterals(commentFree);
186684
+ const stringFree = stripStringLiterals(content);
186685
+ const code = stripCstyleComments(stringFree, /\/\/.*$/gm);
186595
186686
  const emit5 = (name2, kind, span, docstring = "") => {
186596
186687
  if (!name2 || symbols.length >= MAX_SYMBOLS6) return;
186597
186688
  const key = `${name2}\0${kind}\0${span.startLine}`;
@@ -187778,8 +187869,17 @@ function buildEmbeddingBoundaries(filePath, content, dbPath) {
187778
187869
  const symbols = querySymbols({ filePath, limit: 1e4 }, dbPath);
187779
187870
  return symbols.map((s) => ({ start: s.lineStart, end: s.lineEnd, kind: "symbol" }));
187780
187871
  }
187872
+ var DISABLED_EMBED_SHA_PREFIX = "disabled:";
187873
+ function disabledEmbedSha(sha) {
187874
+ return DISABLED_EMBED_SHA_PREFIX + sha;
187875
+ }
187781
187876
  async function indexFileEmbeddings(filePath, dbPath = globalDbPath(), sha) {
187782
- if (!loadConfig().indexing.embeddings_enabled) return;
187877
+ if (!loadConfig().indexing.embeddings_enabled) {
187878
+ if (sha !== void 0) {
187879
+ getDb(dbPath).prepare(`UPDATE files SET embed_sha = ? WHERE ${pathEqClause("path")}`).run(disabledEmbedSha(sha), foldPath(filePath));
187880
+ }
187881
+ return;
187882
+ }
187783
187883
  if (filePath.toLowerCase().endsWith(".profile-meta.xml")) {
187784
187884
  deleteFileEmbeddings(getDb(dbPath), filePath);
187785
187885
  return;
@@ -188285,7 +188385,8 @@ function makeIndexer(dbPath) {
188285
188385
  if (!parseUnchanged) {
188286
188386
  indexFileSync(absPath, dbPath);
188287
188387
  }
188288
- const embedUnchanged = parseUnchanged && entry?.embedSha === sha;
188388
+ const embeddingsEnabled = loadConfig().indexing?.embeddings_enabled ?? true;
188389
+ const embedUnchanged = parseUnchanged && entry?.embedSha === (embeddingsEnabled ? sha : disabledEmbedSha(sha));
188289
188390
  if (embedUnchanged) {
188290
188391
  return false;
188291
188392
  }
@@ -189461,7 +189562,7 @@ import * as fs19 from "fs/promises";
189461
189562
  import { createHash as createHash4 } from "crypto";
189462
189563
  import { resolve as resolve7 } from "path";
189463
189564
  import { homedir as homedir7 } from "os";
189464
- import { readdirSync as readdirSync6, readFileSync as readFileSync14, existsSync as existsSync13, statSync as statSync11, unlinkSync as unlinkSync7 } from "node:fs";
189565
+ import { readdirSync as readdirSync7, readFileSync as readFileSync14, existsSync as existsSync13, statSync as statSync11, unlinkSync as unlinkSync7 } from "node:fs";
189465
189566
  var COMPACT_END_MARKER = "<!-- COMPACT_END -->";
189466
189567
  var SKILLS_OUTPUT_SUBDIR = "skills";
189467
189568
  var _skillOutputsDirOverride = null;
@@ -189725,7 +189826,7 @@ function getCompactAnySessionSync(skillName) {
189725
189826
  const name2 = safeSkillName(skillName);
189726
189827
  if (!name2) return null;
189727
189828
  const dir = skillOutputsDir();
189728
- const entries = readdirSync6(dir, { withFileTypes: true });
189829
+ const entries = readdirSync7(dir, { withFileTypes: true });
189729
189830
  const suffix = `@${sanitizeSkillId(name2)}@compact`;
189730
189831
  for (const entry of entries) {
189731
189832
  if (!entry.isFile() || !entry.name.endsWith("@compact")) continue;
@@ -189904,7 +190005,7 @@ function pruneSkillOutputs(maxCount = DEFAULT_MAX_COUNT, maxAgeMs = DEFAULT_MAX_
189904
190005
  if (!existsSync13(dir)) return 0;
189905
190006
  const cutoff = Date.now() - maxAgeMs;
189906
190007
  const entries = [];
189907
- for (const file2 of readdirSync6(dir)) {
190008
+ for (const file2 of readdirSync7(dir)) {
189908
190009
  if (!file2.endsWith(".meta")) continue;
189909
190010
  const outputId = file2.slice(0, -".meta".length);
189910
190011
  let ts;
@@ -190655,7 +190756,6 @@ function preReadHandler(event) {
190655
190756
  const headings = extractMarkdownHeadings(fileContent);
190656
190757
  if (headings.length >= 3) {
190657
190758
  const alreadyRead = wasFileReadThisSession(normalized);
190658
- recordActualRead(event, normalized);
190659
190759
  const hintText = formatHeadingTree(headings, normalized);
190660
190760
  const wellKnown = getWellKnownSections(basename17);
190661
190761
  const wellKnownText = wellKnown.length > 0 ? "\nQuick access: " + wellKnown.map((s) => 'token-goat section "' + normalized + "::" + s + '"').join(" | ") : "";
@@ -190663,9 +190763,13 @@ function preReadHandler(event) {
190663
190763
  let message = hintText + wellKnownText + changelogExtra;
190664
190764
  const tooLargeForFirstRead = markdownSize !== null && markdownSize >= largeFileDenyBytes();
190665
190765
  if (alreadyRead || tooLargeForFirstRead) {
190766
+ if (alreadyRead) {
190767
+ recordActualRead(event, normalized);
190768
+ }
190666
190769
  message += ' To edit it anyway, use `token-goat replace "' + normalized + '" --old-from <oldfile> --new-from <newfile>` for a snippet edit, or `token-goat write-file "' + normalized + "\" --from <newfile>` to rewrite the whole file \u2014 Read/Edit's own precondition can't be satisfied after this deny.";
190667
190770
  return denyOutput(message);
190668
190771
  }
190772
+ recordActualRead(event, normalized);
190669
190773
  return contextOutput(message);
190670
190774
  }
190671
190775
  }
@@ -191023,8 +191127,31 @@ function renderReadRow(entry) {
191023
191127
  const edited = entry.wasEdited ? ", edited" : "";
191024
191128
  return `- ${entry.path} (${kb}kb, ${entry.readCount} ${plural2}${edited})`;
191025
191129
  }
191026
- function buildManifest2() {
191027
- const files = [...getSessionFiles().values()];
191130
+ function mergeManifestFiles(parent, siblingFiles) {
191131
+ const byPath = /* @__PURE__ */ new Map();
191132
+ for (const f of parent) byPath.set(foldPath(f.path), f);
191133
+ for (const f of siblingFiles) {
191134
+ const key = foldPath(f.path);
191135
+ const prev = byPath.get(key);
191136
+ if (prev === void 0) {
191137
+ byPath.set(key, f);
191138
+ continue;
191139
+ }
191140
+ byPath.set(key, {
191141
+ path: prev.path,
191142
+ readCount: Math.max(prev.readCount, f.readCount),
191143
+ lastReadAt: Math.max(prev.lastReadAt, f.lastReadAt),
191144
+ wasEdited: prev.wasEdited || f.wasEdited,
191145
+ sizeBytes: f.lastReadAt >= prev.lastReadAt ? f.sizeBytes : prev.sizeBytes,
191146
+ ...prev.wasTruncated || f.wasTruncated ? { wasTruncated: true } : {}
191147
+ });
191148
+ }
191149
+ return Array.from(byPath.values());
191150
+ }
191151
+ function buildManifest2(sessionId) {
191152
+ const ownFiles = [...getSessionFiles().values()];
191153
+ const siblingFiles = sessionId !== void 0 ? listSiblingSessionStates(sessionId).flatMap((s) => s.files) : [];
191154
+ const files = siblingFiles.length > 0 ? mergeManifestFiles(ownFiles, siblingFiles) : ownFiles;
191028
191155
  const editedFiles = files.filter((f) => f.wasEdited);
191029
191156
  const readFiles = files.filter((f) => f.readCount > 0 && !f.wasEdited);
191030
191157
  const webFetches = [...getSessionWebFetches().entries()];
@@ -191068,8 +191195,8 @@ function buildManifest2() {
191068
191195
  }
191069
191196
  return lines.join("\n");
191070
191197
  }
191071
- function preCompactHandler(_event) {
191072
- return contextOutput(buildManifest2());
191198
+ function preCompactHandler(event) {
191199
+ return contextOutput(buildManifest2(event.sessionId));
191073
191200
  }
191074
191201
  registerHook("pre_compact", preCompactHandler);
191075
191202
 
@@ -197334,7 +197461,6 @@ var _byId2 = /* @__PURE__ */ new Map();
197334
197461
  var COMMAND_PATTERNS = {
197335
197462
  gitMutable: /^\s*git\s+(diff|status)\b/i,
197336
197463
  gitImmutable: /^\s*git\s+show\s+[0-9a-f]{40}\b/i,
197337
- gitDiffUnscoped: /^\s*git\s+diff\b/i,
197338
197464
  gitDiffScoped: /\s--\s+\S/,
197339
197465
  dirListing: /^\s*(?:ls|eza|exa|dir|Get-ChildItem|gci)\b/i,
197340
197466
  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,
@@ -212436,6 +212562,9 @@ async function relay(eventName) {
212436
212562
  const rawPayload = await readStdinJson();
212437
212563
  const payload = eventName === "pre_tool_use" || eventName === "post_tool_use" ? normalizePayload(rawPayload, harnessForNormalization()) : rawPayload;
212438
212564
  const event = buildEvent(eventName, payload);
212565
+ if (!process.env["CLAUDE_CODE_SESSION_ID"] && event.sessionId) {
212566
+ process.env["CLAUDE_CODE_SESSION_ID"] = event.sessionId;
212567
+ }
212439
212568
  const stateKey = sessionStateKey(event);
212440
212569
  try {
212441
212570
  loadSessionState(stateKey);
@@ -238004,6 +238133,19 @@ function trimToBudget(text, budgetTokens, command2) {
238004
238133
  const marker = `[token-goat: output capped at ~${budgetTokens} tokens to protect context \u2014 showing ${shown} of ${totalLines} lines. ${hint}]`;
238005
238134
  return kept.join("\n") + "\n" + marker;
238006
238135
  }
238136
+ function capJsonRows(items, budgetTokens) {
238137
+ const totalCount = items.length;
238138
+ const charBudget = Math.max(1, budgetTokens * 3);
238139
+ const kept = [];
238140
+ let used = 0;
238141
+ for (const item of items) {
238142
+ const cost = JSON.stringify(item).length + 2;
238143
+ if (kept.length > 0 && used + cost > charBudget) break;
238144
+ kept.push(item);
238145
+ used += cost;
238146
+ }
238147
+ return { items: kept, truncated: kept.length < totalCount, totalCount };
238148
+ }
238007
238149
  function getHintFor(command2) {
238008
238150
  const cmd = (command2 || "").toLowerCase().trim();
238009
238151
  if (cmd === "symbol") {
@@ -238018,6 +238160,9 @@ function getHintFor(command2) {
238018
238160
  if (cmd === "bash-output" || cmd === "web-output") {
238019
238161
  return "Use --grep PATTERN, --section HEADING, or --tail N to narrow the cached output.";
238020
238162
  }
238163
+ if (cmd === "semantic") {
238164
+ return "Narrow your query text or pass --limit to reduce the number of matches returned.";
238165
+ }
238021
238166
  return "Narrow your query or raise overflow_guard max_tokens in config.";
238022
238167
  }
238023
238168
 
@@ -240781,6 +240926,11 @@ function guardText(text, command2) {
240781
240926
  const cfg = loadConfig();
240782
240927
  return cfg.overflow_guard.enabled ? trimToBudget(text, cfg.overflow_guard.max_tokens, command2) : text;
240783
240928
  }
240929
+ function guardJsonRows(items) {
240930
+ const cfg = loadConfig();
240931
+ if (!cfg.overflow_guard.enabled) return { items: [...items], truncated: false, totalCount: items.length };
240932
+ return capJsonRows(items, cfg.overflow_guard.max_tokens);
240933
+ }
240784
240934
  function findSpecSeparator(spec) {
240785
240935
  return spec.lastIndexOf("::");
240786
240936
  }
@@ -240819,7 +240969,9 @@ function runSymbol(opts) {
240819
240969
  return { text: `No matches for '${opts.name ?? "*"}'`, code: 1 };
240820
240970
  }
240821
240971
  if (opts.json === true) {
240822
- return { text: JSON.stringify(results, null, 2), code: 0 };
240972
+ const capped = guardJsonRows(results);
240973
+ const payload = capped.truncated ? { results: capped.items, truncated: true, totalCount: capped.totalCount } : results;
240974
+ return { text: JSON.stringify(payload, null, 2), code: 0 };
240823
240975
  }
240824
240976
  const blocks = results.map((sym) => {
240825
240977
  const header = `# ${sym.name} (${sym.kind}) \u2014 ${sym.filePath}:${sym.lineStart}-${sym.lineEnd}`;
@@ -241061,7 +241213,8 @@ function runRefs(opts) {
241061
241213
  const results = queryRefs(queryOpts);
241062
241214
  if (results.length > 0) anyFound = true;
241063
241215
  if (opts.json === true) {
241064
- jsonOut[sym] = results;
241216
+ const capped = guardJsonRows(results);
241217
+ jsonOut[sym] = capped.truncated ? { references: capped.items, truncated: true, totalCount: capped.totalCount } : capped.items;
241065
241218
  continue;
241066
241219
  }
241067
241220
  if (results.length === 0) {
@@ -241094,7 +241247,9 @@ function runRefsSingle(opts) {
241094
241247
  return 1;
241095
241248
  }
241096
241249
  if (opts.json === true) {
241097
- emit3(JSON.stringify(results, null, 2));
241250
+ const capped = guardJsonRows(results);
241251
+ const payload = capped.truncated ? { references: capped.items, truncated: true, totalCount: capped.totalCount } : results;
241252
+ emit3(JSON.stringify(payload, null, 2));
241098
241253
  return 0;
241099
241254
  }
241100
241255
  const lines = opts.callers === true ? renderCallerGroups(results) : results.map((ref) => `${ref.filePath}:${ref.line}: ${ref.context}`);
@@ -241132,20 +241287,16 @@ function runSkeleton(opts) {
241132
241287
  const filtered = opts.minLines !== void 0 ? symbols.filter((s) => s.lineEnd - s.lineStart + 1 >= (opts.minLines ?? 0)) : symbols;
241133
241288
  const refCounts = opts.stats === true ? queryRefCounts(filtered.map((s) => s.name)) : void 0;
241134
241289
  if (opts.json === true) {
241135
- return {
241136
- text: JSON.stringify(
241137
- filtered.map((s) => ({
241138
- name: s.name,
241139
- kind: s.kind,
241140
- lineStart: s.lineStart,
241141
- lineEnd: s.lineEnd,
241142
- ...refCounts !== void 0 ? { refCount: refCounts.get(s.name) ?? 0, hasDoc: s.docstring.trim().length > 0 } : {}
241143
- })),
241144
- null,
241145
- 2
241146
- ),
241147
- code: 0
241148
- };
241290
+ const rows = filtered.map((s) => ({
241291
+ name: s.name,
241292
+ kind: s.kind,
241293
+ lineStart: s.lineStart,
241294
+ lineEnd: s.lineEnd,
241295
+ ...refCounts !== void 0 ? { refCount: refCounts.get(s.name) ?? 0, hasDoc: s.docstring.trim().length > 0 } : {}
241296
+ }));
241297
+ const capped = guardJsonRows(rows);
241298
+ const payload = capped.truncated ? { symbols: capped.items, truncated: true, totalCount: capped.totalCount } : rows;
241299
+ return { text: JSON.stringify(payload, null, 2), code: 0 };
241149
241300
  }
241150
241301
  const totalLines = filtered.length > 0 ? Math.max(...filtered.map((s) => s.lineEnd)) : 0;
241151
241302
  const lines = [`# Skeleton: ${opts.file} (${filtered.length} symbols, ${totalLines} lines)`];
@@ -241168,18 +241319,14 @@ function runOutline(opts) {
241168
241319
  const filtered = opts.minLines !== void 0 ? symbols.filter((s) => s.lineEnd - s.lineStart + 1 >= (opts.minLines ?? 0)) : symbols;
241169
241320
  const refCounts = opts.stats === true ? queryRefCounts(filtered.map((s) => s.name)) : void 0;
241170
241321
  if (opts.json === true) {
241171
- return {
241172
- text: JSON.stringify(
241173
- refCounts !== void 0 ? filtered.map((s) => ({
241174
- ...s,
241175
- refCount: refCounts.get(s.name) ?? 0,
241176
- hasDoc: s.docstring.trim().length > 0
241177
- })) : filtered,
241178
- null,
241179
- 2
241180
- ),
241181
- code: 0
241182
- };
241322
+ const rows = refCounts !== void 0 ? filtered.map((s) => ({
241323
+ ...s,
241324
+ refCount: refCounts.get(s.name) ?? 0,
241325
+ hasDoc: s.docstring.trim().length > 0
241326
+ })) : filtered;
241327
+ const capped = guardJsonRows(rows);
241328
+ const payload = capped.truncated ? { symbols: capped.items, truncated: true, totalCount: capped.totalCount } : rows;
241329
+ return { text: JSON.stringify(payload, null, 2), code: 0 };
241183
241330
  }
241184
241331
  const lines = [`# Outline: ${opts.file} (${filtered.length} symbols)`];
241185
241332
  for (const sym of filtered) {
@@ -241852,7 +241999,7 @@ async function runSemantic(query, opts) {
241852
241999
  (h) => `# ${h.filePath}:${h.startLine}-${h.endLine} (distance ${h.distance.toFixed(3)})
241853
242000
  ${previewLines(h.text, 3)}`
241854
242001
  );
241855
- return { text: blocks2.join("\n\n"), code: 0 };
242002
+ return { text: guardText(blocks2.join("\n\n"), "semantic"), code: 0 };
241856
242003
  }
241857
242004
  const results = searchSymbolsFts(query, n);
241858
242005
  if (results.length === 0) {
@@ -241860,7 +242007,7 @@ ${previewLines(h.text, 3)}`
241860
242007
  }
241861
242008
  const blocks = results.map((s) => `${symbolHeader(s)}
241862
242009
  ${previewLines(s.body, 3)}`);
241863
- return { text: blocks.join("\n\n"), code: 0 };
242010
+ return { text: guardText(blocks.join("\n\n"), "semantic"), code: 0 };
241864
242011
  }
241865
242012
 
241866
242013
  // src/mcp_server.ts
@@ -246909,6 +247056,9 @@ var CACHE_ENV_GATES = [
246909
247056
  function pad(s, n) {
246910
247057
  return s.length >= n ? s : s + " ".repeat(n - s.length);
246911
247058
  }
247059
+ function listParentSessionBlobs() {
247060
+ return listBlobs(SESSIONS_SUBDIR).filter((b) => !b.id.includes(AGENT_SALT_MARKER)).sort((a, b) => b.mtime - a.mtime);
247061
+ }
246912
247062
  function cmdBashHistory(opts) {
246913
247063
  let limit = 30;
246914
247064
  if (opts.limit !== void 0) {
@@ -247111,7 +247261,7 @@ function cmdCompactHint(opts) {
247111
247261
  }
247112
247262
  }
247113
247263
  function cmdSessionSummary(opts) {
247114
- const blobs = listBlobs(SESSIONS_SUBDIR).sort((a, b) => b.mtime - a.mtime);
247264
+ const blobs = listParentSessionBlobs();
247115
247265
  if (blobs.length === 0) {
247116
247266
  if (opts.json === true) {
247117
247267
  process.stdout.write(JSON.stringify({ sessionCount: 0, message: "no session blobs found" }, null, 2) + "\n");
@@ -247144,7 +247294,7 @@ function cmdSessionSummary(opts) {
247144
247294
  }
247145
247295
  function cmdCost(opts) {
247146
247296
  if (opts.session === true) {
247147
- const blobs = listBlobs(SESSIONS_SUBDIR).sort((a, b) => b.mtime - a.mtime);
247297
+ const blobs = listParentSessionBlobs();
247148
247298
  if (blobs.length === 0) {
247149
247299
  if (opts.json === true) {
247150
247300
  process.stdout.write(JSON.stringify({ session: true, message: "no session blobs found" }, null, 2) + "\n");
@@ -247481,6 +247631,11 @@ function levenshtein2(a, b, cap = 3) {
247481
247631
  function closestKeys(unknown2, known) {
247482
247632
  return known.map((k) => ({ k, d: levenshtein2(unknown2, k) })).filter((x) => x.d <= 3).sort((a, b) => a.d - b.d).slice(0, 3).map((x) => x.k);
247483
247633
  }
247634
+ function didYouMeanKeySuffix(unknownKey) {
247635
+ const knownKeys = flattenConfig(defaultConfig()).map(([k]) => k);
247636
+ const suggestions = closestKeys(unknownKey, knownKeys);
247637
+ return suggestions.length > 0 ? ` (did you mean: ${suggestions.join(", ")}?)` : "";
247638
+ }
247484
247639
  function walkGet(obj, parts) {
247485
247640
  let cur = obj;
247486
247641
  for (const part of parts) {
@@ -247557,15 +247712,13 @@ function cmdConfig(opts) {
247557
247712
  }
247558
247713
  if (action === "get") {
247559
247714
  if (!opts.key) {
247560
- emitErr4("config get requires a key (e.g. compact_assist.enabled)");
247561
- throw new Error("missing key");
247715
+ throw new Error("config get requires a key (e.g. compact_assist.enabled)");
247562
247716
  }
247563
247717
  const parts = opts.key.split(".");
247564
247718
  const cfg = loadConfig();
247565
247719
  const result = walkGet(cfg, parts);
247566
247720
  if (!result.found) {
247567
- emitErr4(`key not found: ${opts.key}`);
247568
- throw new Error(`key not found: ${opts.key}`);
247721
+ throw new Error(`key not found: ${opts.key}${didYouMeanKeySuffix(opts.key)}`);
247569
247722
  }
247570
247723
  if (opts.json === true) {
247571
247724
  emit4(JSON.stringify({ key: opts.key, value: result.value }, null, 2));
@@ -247576,28 +247729,23 @@ function cmdConfig(opts) {
247576
247729
  }
247577
247730
  if (action === "set") {
247578
247731
  if (!opts.key) {
247579
- emitErr4("config set requires a key (e.g. compact_assist.enabled)");
247580
- throw new Error("missing key");
247732
+ throw new Error("config set requires a key (e.g. compact_assist.enabled)");
247581
247733
  }
247582
247734
  if (opts.value === void 0) {
247583
- emitErr4("config set requires a value");
247584
- throw new Error("missing value");
247735
+ throw new Error("config set requires a value");
247585
247736
  }
247586
247737
  const parts = opts.key.split(".");
247587
247738
  const cfg = loadPersistedConfig();
247588
247739
  const ref = walkParent(cfg, parts);
247589
247740
  if (!ref) {
247590
- emitErr4(`key not found: ${opts.key}`);
247591
- throw new Error(`key not found: ${opts.key}`);
247741
+ throw new Error(`key not found: ${opts.key}${didYouMeanKeySuffix(opts.key)}`);
247592
247742
  }
247593
247743
  const existing = ref.parent[ref.leaf];
247594
247744
  if (existing === void 0) {
247595
- emitErr4(`key not found: ${opts.key}`);
247596
- throw new Error(`key not found: ${opts.key}`);
247745
+ throw new Error(`key not found: ${opts.key}${didYouMeanKeySuffix(opts.key)}`);
247597
247746
  }
247598
247747
  if (typeof existing === "object" && existing !== null && !Array.isArray(existing)) {
247599
- emitErr4(`config set: '${opts.key}' is a section, not a settable field \u2014 set an individual key within it instead (e.g. ${opts.key}.<field>)`);
247600
- throw new Error(`cannot set a whole config section: ${opts.key}`);
247748
+ throw new Error(`config set: '${opts.key}' is a section, not a settable field \u2014 set an individual key within it instead (e.g. ${opts.key}.<field>)`);
247601
247749
  }
247602
247750
  const defaultAtKey = walkGet(defaultConfig(), parts);
247603
247751
  const coerced = coerce2(opts.value, existing, defaultAtKey.found ? defaultAtKey.value : void 0);
@@ -247606,8 +247754,7 @@ function cmdConfig(opts) {
247606
247754
  const revalidated = buildPersistedConfig(cfg);
247607
247755
  const revalidatedResult = walkGet(revalidated, parts);
247608
247756
  if (revalidatedResult.found && revalidatedResult.value !== coerced) {
247609
- emitErr4(`config set: ${opts.key} = ${coerced} is outside the allowed range (would be clamped to ${String(revalidatedResult.value)}); rejected`);
247610
- throw new Error(`value out of range for ${opts.key}: ${coerced}`);
247757
+ throw new Error(`config set: ${opts.key} = ${coerced} is outside the allowed range (would be clamped to ${String(revalidatedResult.value)}); rejected`);
247611
247758
  }
247612
247759
  }
247613
247760
  saveConfigSafe(cfg);
@@ -247687,8 +247834,7 @@ function cmdConfig(opts) {
247687
247834
  emit4(`config validate: ${findings.length} issue(s) found`);
247688
247835
  return;
247689
247836
  }
247690
- emitErr4(`config: unknown action '${action}'. Use list, get, set, or validate.`);
247691
- throw new Error(`unknown config action: ${action}`);
247837
+ throw new Error(`config: unknown action '${action}'. Use list, get, set, or validate.`);
247692
247838
  }
247693
247839
  function cmdProject(opts) {
247694
247840
  const { action } = opts;
@@ -247978,7 +248124,8 @@ async function cmdIndex(pathArg, opts = {}) {
247978
248124
  const sha = fingerprintFile(key);
247979
248125
  const entry = sha !== null ? getFileEntry(key, dbPath) : null;
247980
248126
  const parseUnchanged = sha !== null && entry?.sha === sha;
247981
- const embedUnchanged = parseUnchanged && entry?.embedSha === sha;
248127
+ const embeddingsEnabled = loadConfig().indexing?.embeddings_enabled ?? true;
248128
+ const embedUnchanged = parseUnchanged && sha !== null && entry?.embedSha === (embeddingsEnabled ? sha : disabledEmbedSha(sha));
247982
248129
  if (parseUnchanged && embedUnchanged) {
247983
248130
  skipped += 1;
247984
248131
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "token-goat",
3
- "version": "2.6.11",
3
+ "version": "2.6.12",
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",