token-goat 2.9.18 → 2.9.19

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 (42) hide show
  1. package/README.md +2 -0
  2. package/dist/{token-goat-chunk-V3ZWBEHM.mjs → token-goat-chunk-2GTB5RBZ.mjs} +365 -26
  3. package/dist/{token-goat-chunk-ARNRFR6M.mjs → token-goat-chunk-3W5P673V.mjs} +1 -1
  4. package/dist/{token-goat-chunk-YQZE5NS6.mjs → token-goat-chunk-62VJ47WN.mjs} +6 -6
  5. package/dist/{token-goat-chunk-CTOYJ232.mjs → token-goat-chunk-7DGTVBWP.mjs} +45 -8
  6. package/dist/{token-goat-chunk-5MMJAJF3.mjs → token-goat-chunk-AVRD7SBO.mjs} +60 -8
  7. package/dist/{token-goat-chunk-7VLWNQDQ.mjs → token-goat-chunk-BTKDZW7F.mjs} +1 -1
  8. package/dist/{token-goat-chunk-LMFO66YD.mjs → token-goat-chunk-DA43OE7L.mjs} +1 -1
  9. package/dist/{token-goat-chunk-6LGVCCMY.mjs → token-goat-chunk-DA6TIDLI.mjs} +21 -16
  10. package/dist/{token-goat-chunk-UENEL6KX.mjs → token-goat-chunk-DHIXE6GV.mjs} +2 -2
  11. package/dist/{token-goat-chunk-W3E65QOW.mjs → token-goat-chunk-EP7TCWHM.mjs} +304 -46
  12. package/dist/{token-goat-chunk-HIZXQVNU.mjs → token-goat-chunk-EXLLLIC3.mjs} +4 -4
  13. package/dist/{token-goat-chunk-H3FONFBO.mjs → token-goat-chunk-F24NE3FO.mjs} +3 -3
  14. package/dist/{token-goat-chunk-LA3O4JGD.mjs → token-goat-chunk-FJZ5MZK7.mjs} +3 -3
  15. package/dist/{token-goat-chunk-BFEWOMA3.mjs → token-goat-chunk-FQYMZASI.mjs} +632 -352
  16. package/dist/{token-goat-chunk-OWSJJY5R.mjs → token-goat-chunk-FX5DHKGF.mjs} +5 -5
  17. package/dist/{token-goat-chunk-K4DG6EEF.mjs → token-goat-chunk-GYC34EGE.mjs} +7 -8
  18. package/dist/{token-goat-chunk-WFX2V3OF.mjs → token-goat-chunk-HPMMLV37.mjs} +358 -116
  19. package/dist/{token-goat-chunk-I67CPTDD.mjs → token-goat-chunk-IBTFGZBX.mjs} +19 -14
  20. package/dist/{token-goat-chunk-L7VCX33I.mjs → token-goat-chunk-JNIFSVWM.mjs} +36 -10
  21. package/dist/{token-goat-chunk-EPXNOKIV.mjs → token-goat-chunk-KWM6KBPA.mjs} +2 -2
  22. package/dist/{token-goat-chunk-BWCRC23L.mjs → token-goat-chunk-L2UOJBV4.mjs} +21 -20
  23. package/dist/{token-goat-chunk-52IMXJ2L.mjs → token-goat-chunk-LACH3WKS.mjs} +4 -4
  24. package/dist/token-goat-chunk-MU6I4IDI.mjs +337 -0
  25. package/dist/{token-goat-chunk-GRI6HWX3.mjs → token-goat-chunk-NJKNAC37.mjs} +1 -1
  26. package/dist/{token-goat-chunk-ERTXEKB6.mjs → token-goat-chunk-NMTKNYGF.mjs} +2 -0
  27. package/dist/{token-goat-chunk-34OUJ2IE.mjs → token-goat-chunk-NYFYIRG3.mjs} +12 -12
  28. package/dist/{token-goat-chunk-4EXFN2AW.mjs → token-goat-chunk-OZRSREKR.mjs} +2 -2
  29. package/dist/token-goat-chunk-P46RTOAQ.mjs +27 -0
  30. package/dist/{token-goat-chunk-QML7ITHJ.mjs → token-goat-chunk-PIAESKVU.mjs} +139 -23
  31. package/dist/{token-goat-chunk-HPOFR6QX.mjs → token-goat-chunk-S7RFIX4Y.mjs} +180 -50
  32. package/dist/{token-goat-chunk-CA6V36KW.mjs → token-goat-chunk-SOKAPOOC.mjs} +23 -4
  33. package/dist/token-goat-chunk-VJQBYSXG.mjs +20 -0
  34. package/dist/{token-goat-chunk-LTYWLKAG.mjs → token-goat-chunk-WKO5TVNA.mjs} +922 -611
  35. package/dist/{token-goat-chunk-QIDLJGXZ.mjs → token-goat-chunk-ZGQXRYKE.mjs} +29 -5
  36. package/dist/token-goat-hook.mjs +12 -12
  37. package/dist/token-goat.core.mjs +21 -20
  38. package/docs/cli.md +2 -1
  39. package/docs/security.md +1 -1
  40. package/package.json +1 -1
  41. package/dist/token-goat-chunk-2KTZ6J7T.mjs +0 -173
  42. package/dist/token-goat-chunk-RHHFEXKF.mjs +0 -93
@@ -10,25 +10,25 @@ import {
10
10
  resolveMinNetSavingsBytes,
11
11
  storeBashOutputSync,
12
12
  stripAnsiCodes
13
- } from "./token-goat-chunk-CTOYJ232.mjs";
13
+ } from "./token-goat-chunk-7DGTVBWP.mjs";
14
14
  import {
15
15
  DEFAULT_MAX_AGE_MS,
16
16
  DEFAULT_MAX_COUNT,
17
17
  SOURCE_HINT,
18
18
  countRedactionPlaceholders,
19
+ hasPreciseSecret,
19
20
  recordStat,
20
21
  recordUnmappedTool,
21
22
  redactSecrets,
22
23
  savedTokensFromBytes,
23
24
  summarize,
24
25
  sweepCacheRoots
25
- } from "./token-goat-chunk-HPOFR6QX.mjs";
26
+ } from "./token-goat-chunk-S7RFIX4Y.mjs";
26
27
  import {
27
28
  parse
28
29
  } from "./token-goat-chunk-3BTK54F3.mjs";
29
30
  import {
30
31
  MARKDOWN_SIZE_THRESHOLD,
31
- PARSER_FINGERPRINT,
32
32
  deleteFileEmbeddings,
33
33
  deleteFileRows,
34
34
  eachUnfencedLine,
@@ -48,17 +48,20 @@ import {
48
48
  isAvailable,
49
49
  isEmbedFresh,
50
50
  isEmbeddableDocument,
51
+ isImagePath,
52
+ isNonTextAsset,
51
53
  isParseSkipEligible,
52
54
  isTreeSitterAvailable,
53
55
  loadRegexExtractors,
54
- parseSourceSymbolsTreeSitterOnly
55
- } from "./token-goat-chunk-W3E65QOW.mjs";
56
+ parseSourceSymbolsTreeSitterOnly,
57
+ parserFingerprintForLanguage
58
+ } from "./token-goat-chunk-EP7TCWHM.mjs";
56
59
  import {
57
60
  getFileEntry,
58
61
  pathEqClause,
59
62
  projectScopeClause,
60
63
  querySymbols
61
- } from "./token-goat-chunk-I67CPTDD.mjs";
64
+ } from "./token-goat-chunk-IBTFGZBX.mjs";
62
65
  import {
63
66
  detectLanguage,
64
67
  detectLanguageOfFile,
@@ -66,7 +69,7 @@ import {
66
69
  getDb,
67
70
  languageHasFlag,
68
71
  redactIfDotenv
69
- } from "./token-goat-chunk-L7VCX33I.mjs";
72
+ } from "./token-goat-chunk-JNIFSVWM.mjs";
70
73
  import {
71
74
  detectHarness,
72
75
  fileIsAbsent,
@@ -80,7 +83,7 @@ import {
80
83
  makeProjectAt,
81
84
  resolveProjectRoot,
82
85
  shortFingerprint
83
- } from "./token-goat-chunk-CA6V36KW.mjs";
86
+ } from "./token-goat-chunk-SOKAPOOC.mjs";
84
87
  import {
85
88
  registerReset
86
89
  } from "./token-goat-chunk-EEIDFMEM.mjs";
@@ -88,6 +91,7 @@ import {
88
91
  IDENTICAL_READ_MIN_BODY_BYTES,
89
92
  LOCK_WAIT_MS_HARDENED,
90
93
  PER_FILE_COUNTERFACTUAL_CEILING,
94
+ VERSION,
91
95
  atomicWriteBytes,
92
96
  atomicWriteText,
93
97
  backupFile,
@@ -105,6 +109,7 @@ import {
105
109
  hookPowershellCommand,
106
110
  isCodeFenceDelimiter,
107
111
  isInsideRoot,
112
+ isNodeModulesPath,
108
113
  isTestFile,
109
114
  isUnderBlockedRoot,
110
115
  isWithinQuietHours,
@@ -126,8 +131,9 @@ import {
126
131
  withFileLock,
127
132
  withInstallScope,
128
133
  writeIfDifferent
129
- } from "./token-goat-chunk-QIDLJGXZ.mjs";
134
+ } from "./token-goat-chunk-ZGQXRYKE.mjs";
130
135
  import {
136
+ TOOL_RESULTS_ID_CHARS,
131
137
  displaySafeJson,
132
138
  displaySafePath,
133
139
  displaySafeText,
@@ -139,7 +145,7 @@ import {
139
145
  resolveIndexPath,
140
146
  scanForInjectionPatterns,
141
147
  toDisplayPath
142
- } from "./token-goat-chunk-ERTXEKB6.mjs";
148
+ } from "./token-goat-chunk-NMTKNYGF.mjs";
143
149
  import {
144
150
  applyExifOrientation,
145
151
  calculateFitInside,
@@ -156,6 +162,16 @@ import {
156
162
  init_define_import_meta_env
157
163
  } from "./token-goat-chunk-A37V4PBF.mjs";
158
164
 
165
+ // src/claude_config_dir.ts
166
+ init_define_import_meta_env();
167
+ import * as os from "node:os";
168
+ import * as path from "node:path";
169
+ function claudeConfigDir(homeDir = os.homedir()) {
170
+ const override = process.env["CLAUDE_CONFIG_DIR"];
171
+ if (override !== void 0 && override !== "") return override;
172
+ return path.join(homeDir, ".claude");
173
+ }
174
+
159
175
  // src/index_health.ts
160
176
  init_define_import_meta_env();
161
177
  import * as fs2 from "fs";
@@ -163,14 +179,14 @@ import * as fs2 from "fs";
163
179
  // src/walk_mode.ts
164
180
  init_define_import_meta_env();
165
181
  import * as fs from "fs";
166
- import * as path from "path";
182
+ import * as path2 from "path";
167
183
  function detectWalkMode(cwd) {
168
184
  const project = findProject(cwd);
169
185
  if (project?.marker === ".git") return "git";
170
186
  let cur = cwd;
171
187
  while (true) {
172
- if (fs.existsSync(path.join(cur, ".git"))) return "git";
173
- const parent = path.dirname(cur);
188
+ if (fs.existsSync(path2.join(cur, ".git"))) return "git";
189
+ const parent = path2.dirname(cur);
174
190
  if (parent === cur) break;
175
191
  cur = parent;
176
192
  }
@@ -206,17 +222,17 @@ function getEmbeddingCoverage(dbPath, rootDir) {
206
222
  embeddedFiles: countScoped("SELECT COUNT(DISTINCT file_path) as c FROM chunks", "file_path")
207
223
  };
208
224
  }
209
- function getParserFreshness(dbPath, parserSha, rootDir) {
225
+ function getParserFreshness(dbPath, expectedFor, rootDir) {
210
226
  const db = getDb(dbPath);
211
- const count = (extra, params) => {
212
- if (rootDir === void 0) {
213
- return db.prepare(`SELECT COUNT(*) as c FROM files${extra === "" ? "" : ` WHERE ${extra}`}`).get(...params).c;
214
- }
215
- const scope = projectScopeClause("path");
216
- const where = extra === "" ? scope.clause : `${scope.clause} AND ${extra}`;
217
- return db.prepare(`SELECT COUNT(*) as c FROM files WHERE ${where}`).get(...scope.params(rootDir), ...params).c;
218
- };
219
- return { indexedFiles: count("", []), currentFiles: count("parser_sha = ?", [parserSha]) };
227
+ const scope = rootDir === void 0 ? void 0 : projectScopeClause("path");
228
+ const rows = db.prepare(`SELECT language, parser_sha, COUNT(*) as c FROM files${scope === void 0 ? "" : ` WHERE ${scope.clause}`} GROUP BY language, parser_sha`).all(...scope === void 0 || rootDir === void 0 ? [] : scope.params(rootDir));
229
+ let indexedFiles = 0;
230
+ let currentFiles = 0;
231
+ for (const row of rows) {
232
+ indexedFiles += row.c;
233
+ if (row.parser_sha !== null && row.parser_sha === expectedFor(row.language ?? "unknown")) currentFiles += row.c;
234
+ }
235
+ return { indexedFiles, currentFiles };
220
236
  }
221
237
  function isIndexEmptyForProject(dbPath, rootDir) {
222
238
  if (!fs2.existsSync(dbPath)) return true;
@@ -237,8 +253,8 @@ function emptyIndexMessage(rootDir) {
237
253
  // src/bridges/visualstudio_install.ts
238
254
  init_define_import_meta_env();
239
255
  import * as fs4 from "node:fs";
240
- import * as os from "node:os";
241
- import * as path3 from "node:path";
256
+ import * as os2 from "node:os";
257
+ import * as path4 from "node:path";
242
258
 
243
259
  // src/bridges/guidance_block.ts
244
260
  init_define_import_meta_env();
@@ -256,6 +272,7 @@ function buildGuidanceBody(fallbackToolClause, opts = {}) {
256
272
  "Exemptions (gate passes, read directly): the file is under ~200 lines and you need all of it; it was never indexed (new, untracked, or generated this turn); it is a genuinely opaque binary (not an image); the target has no symbol handle (e.g. a literal mid-function).",
257
273
  "",
258
274
  "Failure shapes to catch yourself in, and the command that replaces each:",
275
+ '- not knowing which command answers a question you can already state in words \u2192 `answer "<question>"`, which resolves the subject in the index, runs the right command, prints `via: <command>`, and refuses rather than guess',
259
276
  '- a shell text search with context flags to find a function body \u2192 `read "file::symbol"`',
260
277
  '- paging one function with view/view_range \u2192 `read "file::symbol"`',
261
278
  '- reading a symbol plus chasing its callers and containing doc section as separate reads \u2192 `brief "file::symbol"`',
@@ -269,7 +286,7 @@ function buildGuidanceBody(fallbackToolClause, opts = {}) {
269
286
  "- opening a screenshot, diagram, or scan to read the text in it \u2192 `image-text file`",
270
287
  "- opening a PDF or Office document \u2192 inspect its format first, then read a narrow slice: PDF `pdf-meta`/`pdf-outline` then `pdf-locate` to find the pages and `pdf-extract` only those; Word `docx-outline` then `docx-tables`/`docx-text`; PowerPoint `pptx-outline` then `pptx-slide`/`pptx-notes`; Excel `xlsx-sheets` then `xlsx-columns`/`xlsx-head`/`xlsx-range`/`xlsx-query`",
271
288
  "",
272
- 'Commands: `symbol NAME`, `read "file::symbol"`, `brief "file::symbol"`, `section "file::Heading"`, `semantic "description"`, `outline file`/`skeleton file`, `map --compact`, `refs file::symbol --callers`, `changed --symbol`, `config-get file KEY`, `json-query file \'a.b.c\'`/`yaml-query`/`xml-query`, `json-outline file`/`yaml-outline`/`xml-outline`, `bash-output`/`web-output`/`mcp-output`, ' + (gdrive ? "`gdrive-sections <file-id>`, " : "") + "`image-meta file`/`image-text file`, `pdf-meta`/`pdf-outline`/`pdf-locate`/`pdf-extract`, `docx-outline`/`docx-tables`/`docx-text`, `pptx-outline`/`pptx-slide`/`pptx-notes`/`pptx-text`, `xlsx-sheets`/`xlsx-columns`/`xlsx-head`/`xlsx-range`/`xlsx-query`.",
289
+ 'Commands: `answer "<question>"`, `symbol NAME`, `read "file::symbol"`, `brief "file::symbol"`, `section "file::Heading"`, `semantic "description"`, `outline file`/`skeleton file`, `map --compact`, `refs file::symbol --callers`, `changed --symbol`, `config-get file KEY`, `json-query file \'a.b.c\'`/`yaml-query`/`xml-query`, `json-outline file`/`yaml-outline`/`xml-outline`, `bash-output`/`web-output`/`mcp-output`, ' + (gdrive ? "`gdrive-sections <file-id>`, " : "") + "`image-meta file`/`image-text file`, `pdf-meta`/`pdf-outline`/`pdf-locate`/`pdf-extract`, `docx-outline`/`docx-tables`/`docx-text`, `pptx-outline`/`pptx-slide`/`pptx-notes`/`pptx-text`, `xlsx-sheets`/`xlsx-columns`/`xlsx-head`/`xlsx-range`/`xlsx-query`.",
273
290
  "",
274
291
  "Sub-agent briefs must carry this gate verbatim: a sub-agent inherits none of this context and its reads spend the same token budget.",
275
292
  "",
@@ -286,7 +303,7 @@ function buildGuidanceBlock(h) {
286
303
  // src/bridges/mcp_servers_json.ts
287
304
  init_define_import_meta_env();
288
305
  import * as fs3 from "node:fs";
289
- import * as path2 from "node:path";
306
+ import * as path3 from "node:path";
290
307
  import { createRequire } from "node:module";
291
308
  import { fileURLToPath } from "node:url";
292
309
  var jsoncParser;
@@ -295,10 +312,10 @@ function jsonc() {
295
312
  return jsoncParser;
296
313
  }
297
314
  function bundledCliPath() {
298
- const here = path2.dirname(fileURLToPath(import.meta.url));
299
- const bundled = path2.join(here, "token-goat.mjs");
315
+ const here = path3.dirname(fileURLToPath(import.meta.url));
316
+ const bundled = path3.join(here, "token-goat.mjs");
300
317
  if (fs3.existsSync(bundled)) return bundled;
301
- return path2.resolve(here, "..", "..", "dist", "token-goat.mjs");
318
+ return path3.resolve(here, "..", "..", "dist", "token-goat.mjs");
302
319
  }
303
320
  function managedServer() {
304
321
  return {
@@ -311,7 +328,7 @@ function isManagedServer(value) {
311
328
  if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
312
329
  const entry = value;
313
330
  const args = entry["args"];
314
- return entry["type"] === "stdio" && Array.isArray(args) && args.length === 2 && typeof args[0] === "string" && path2.basename(args[0]).toLowerCase() === "token-goat.mjs" && args[1] === "mcp-serve";
331
+ return entry["type"] === "stdio" && Array.isArray(args) && args.length === 2 && typeof args[0] === "string" && path3.basename(args[0]).toLowerCase() === "token-goat.mjs" && args[1] === "mcp-serve";
315
332
  }
316
333
  function readServersJson(filePath, label) {
317
334
  const text = fs3.existsSync(filePath) ? fs3.readFileSync(filePath, "utf8") : "{}\n";
@@ -357,6 +374,16 @@ function holdsOnlyManagedServer(value) {
357
374
  const names = Object.keys(servers);
358
375
  return names.length === 1 && names[0] === "token-goat" && isManagedServer(servers["token-goat"]);
359
376
  }
377
+ function isResidueServersJson(text, rootKey = "servers") {
378
+ const trimmed = text.trim();
379
+ if (trimmed === "") return true;
380
+ const parsed = parseObject(trimmed);
381
+ if (parsed === null) return false;
382
+ const keys = Object.keys(parsed);
383
+ if (keys.length !== 1 || keys[0] !== rootKey) return false;
384
+ const servers = parsed[rootKey];
385
+ return servers !== null && typeof servers === "object" && !Array.isArray(servers) && Object.keys(servers).length === 0;
386
+ }
360
387
  function dropEmptyServers(text, rootKey = "servers") {
361
388
  const parsed = parseObject(text);
362
389
  if (parsed === null) return text;
@@ -399,10 +426,10 @@ var SIBLING_GATE_MARKERS = [
399
426
  ["<!-- token-goat-begin -->", "<!-- token-goat-end -->"]
400
427
  ];
401
428
  function visualStudioUserMcpPath() {
402
- return path3.join(os.homedir(), ".mcp.json");
429
+ return path4.join(os2.homedir(), ".mcp.json");
403
430
  }
404
431
  function visualStudioProjectMcpPath(projectRoot = process.cwd()) {
405
- return path3.join(path3.resolve(projectRoot), ".mcp.json");
432
+ return path4.join(path4.resolve(projectRoot), ".mcp.json");
406
433
  }
407
434
  function visualStudioMcpPath(opts = {}) {
408
435
  return opts.project === true ? visualStudioProjectMcpPath(opts.projectRoot) : visualStudioUserMcpPath();
@@ -411,7 +438,7 @@ function otherScopeMcpPath(opts) {
411
438
  return opts.project === true ? visualStudioUserMcpPath() : visualStudioProjectMcpPath(opts.projectRoot);
412
439
  }
413
440
  function visualStudioInstructionsPath(opts = {}) {
414
- return opts.project === true ? path3.join(path3.resolve(opts.projectRoot ?? process.cwd()), ".github", "copilot-instructions.md") : path3.join(os.homedir(), "copilot-instructions.md");
441
+ return opts.project === true ? path4.join(path4.resolve(opts.projectRoot ?? process.cwd()), ".github", "copilot-instructions.md") : path4.join(os2.homedir(), "copilot-instructions.md");
415
442
  }
416
443
  function visualStudioOtherScopeHasManagedServer(opts = {}) {
417
444
  return hasManagedServer(otherScopeMcpPath(opts), LABEL);
@@ -420,7 +447,7 @@ function isVisualStudioInstalled(opts = {}) {
420
447
  return hasManagedServer(visualStudioMcpPath(opts), LABEL);
421
448
  }
422
449
  function visualStudioSolutionVscodeMcpPath(projectRoot = process.cwd()) {
423
- return path3.join(path3.resolve(projectRoot), ".vscode", "mcp.json");
450
+ return path4.join(path4.resolve(projectRoot), ".vscode", "mcp.json");
424
451
  }
425
452
  function visualStudioManagedRegistrations(projectRoot = process.cwd()) {
426
453
  return [visualStudioUserMcpPath(), visualStudioProjectMcpPath(projectRoot), visualStudioSolutionVscodeMcpPath(projectRoot)].filter((p) => hasManagedServer(p, LABEL));
@@ -498,7 +525,7 @@ function installVisualStudioScoped(opts) {
498
525
  }
499
526
  const next = ensureMcpServersKey(setTokenGoatServer(config.text, managedServer()));
500
527
  if (config.text !== next) {
501
- ensureDirSync(path3.dirname(mcpPath));
528
+ ensureDirSync(path4.dirname(mcpPath));
502
529
  backupFile(mcpPath);
503
530
  atomicWriteText(mcpPath, next);
504
531
  if (!mcpExisted || ownedAlready) recordCreatedConfig(mcpPath);
@@ -536,8 +563,8 @@ function uninstallVisualStudioScoped(opts) {
536
563
  // src/bridges/copilot_cli_install.ts
537
564
  init_define_import_meta_env();
538
565
  import * as fs5 from "node:fs";
539
- import * as os2 from "node:os";
540
- import * as path4 from "node:path";
566
+ import * as os3 from "node:os";
567
+ import * as path5 from "node:path";
541
568
 
542
569
  // src/bridges/copilot_cli.ts
543
570
  init_define_import_meta_env();
@@ -1255,43 +1282,43 @@ var COPILOT_CLI_HOOK_EVENTS = [
1255
1282
  ];
1256
1283
  function copilotCliUserRoot() {
1257
1284
  const override = process.env["COPILOT_HOME"];
1258
- if (override !== void 0 && override.trim() !== "") return path4.resolve(override);
1259
- return path4.join(os2.homedir(), ".copilot");
1285
+ if (override !== void 0 && override.trim() !== "") return path5.resolve(override);
1286
+ return path5.join(os3.homedir(), ".copilot");
1260
1287
  }
1261
1288
  function copilotCliUserHooksDir() {
1262
- return path4.join(copilotCliUserRoot(), "hooks");
1289
+ return path5.join(copilotCliUserRoot(), "hooks");
1263
1290
  }
1264
1291
  function copilotCliProjectHooksDir() {
1265
- return path4.join(process.cwd(), ".github", "hooks");
1292
+ return path5.join(process.cwd(), ".github", "hooks");
1266
1293
  }
1267
1294
  function copilotCliCacheRoot() {
1268
1295
  const override = process.env["COPILOT_CACHE_HOME"];
1269
- if (override !== void 0 && override.trim() !== "") return path4.resolve(override);
1270
- const home = os2.homedir();
1271
- if (process.platform === "darwin") return path4.join(home, "Library", "Caches", "copilot");
1296
+ if (override !== void 0 && override.trim() !== "") return path5.resolve(override);
1297
+ const home = os3.homedir();
1298
+ if (process.platform === "darwin") return path5.join(home, "Library", "Caches", "copilot");
1272
1299
  if (process.platform === "win32") {
1273
1300
  const local = process.env["LOCALAPPDATA"];
1274
- const base2 = local !== void 0 && local.trim() !== "" ? local : path4.join(home, ".cache");
1275
- return path4.join(base2, "copilot");
1301
+ const base2 = local !== void 0 && local.trim() !== "" ? local : path5.join(home, ".cache");
1302
+ return path5.join(base2, "copilot");
1276
1303
  }
1277
1304
  const xdg = process.env["XDG_CACHE_HOME"];
1278
- const base = xdg !== void 0 && xdg.trim() !== "" ? xdg : path4.join(home, ".cache");
1279
- return path4.join(base, "copilot");
1305
+ const base = xdg !== void 0 && xdg.trim() !== "" ? xdg : path5.join(home, ".cache");
1306
+ return path5.join(base, "copilot");
1280
1307
  }
1281
1308
  function copilotCliMcpToolsDir() {
1282
- return path4.join(copilotCliCacheRoot(), "mcp-tools");
1309
+ return path5.join(copilotCliCacheRoot(), "mcp-tools");
1283
1310
  }
1284
1311
  function copilotCliHooksDir(opts = {}) {
1285
1312
  return opts.local === true ? copilotCliProjectHooksDir() : copilotCliUserHooksDir();
1286
1313
  }
1287
1314
  function copilotCliConfigPath(opts = {}) {
1288
- return path4.join(copilotCliHooksDir(opts), HOOKS_CONFIG_FILE);
1315
+ return path5.join(copilotCliHooksDir(opts), HOOKS_CONFIG_FILE);
1289
1316
  }
1290
1317
  function copilotCliScriptPath(opts = {}) {
1291
- return path4.join(copilotCliHooksDir(opts), HOOKS_SCRIPT_FILE);
1318
+ return path5.join(copilotCliHooksDir(opts), HOOKS_SCRIPT_FILE);
1292
1319
  }
1293
1320
  function copilotCliInstructionsPath(opts = {}) {
1294
- return path4.join(path4.dirname(copilotCliHooksDir(opts)), "copilot-instructions.md");
1321
+ return path5.join(path5.dirname(copilotCliHooksDir(opts)), "copilot-instructions.md");
1295
1322
  }
1296
1323
  var COPILOT_INSTRUCTIONS_BEGIN = "<!-- token-goat-begin -->";
1297
1324
  var COPILOT_INSTRUCTIONS_END = "<!-- token-goat-end -->";
@@ -1349,10 +1376,10 @@ var HOOKS_CONFIG_FILE = "token-goat.json";
1349
1376
  var HOOKS_SCRIPT_FILE = "token-goat-shim.js";
1350
1377
  var HOOKS_OWNERS_FILE = "token-goat.owners";
1351
1378
  function copilotHooksOwnersPath(hooksDir) {
1352
- return path4.join(hooksDir, HOOKS_OWNERS_FILE);
1379
+ return path5.join(hooksDir, HOOKS_OWNERS_FILE);
1353
1380
  }
1354
1381
  function copilotHooksFilePaths(hooksDir) {
1355
- return [path4.join(hooksDir, HOOKS_CONFIG_FILE), path4.join(hooksDir, HOOKS_SCRIPT_FILE), copilotHooksOwnersPath(hooksDir)];
1382
+ return [path5.join(hooksDir, HOOKS_CONFIG_FILE), path5.join(hooksDir, HOOKS_SCRIPT_FILE), copilotHooksOwnersPath(hooksDir)];
1356
1383
  }
1357
1384
  function readCopilotHooksOwners(hooksDir) {
1358
1385
  const owners = /* @__PURE__ */ new Set();
@@ -1360,7 +1387,7 @@ function readCopilotHooksOwners(hooksDir) {
1360
1387
  try {
1361
1388
  text = fs5.readFileSync(copilotHooksOwnersPath(hooksDir), "utf8");
1362
1389
  } catch {
1363
- if (fs5.existsSync(path4.join(hooksDir, HOOKS_CONFIG_FILE))) owners.add("copilot");
1390
+ if (fs5.existsSync(path5.join(hooksDir, HOOKS_CONFIG_FILE))) owners.add("copilot");
1364
1391
  return owners;
1365
1392
  }
1366
1393
  for (const line of text.split(/\r?\n/)) {
@@ -1370,8 +1397,8 @@ function readCopilotHooksOwners(hooksDir) {
1370
1397
  return owners;
1371
1398
  }
1372
1399
  function installCopilotHooksFile(hooksDir, owner) {
1373
- const configPath = path4.join(hooksDir, HOOKS_CONFIG_FILE);
1374
- const scriptPath = path4.join(hooksDir, HOOKS_SCRIPT_FILE);
1400
+ const configPath = path5.join(hooksDir, HOOKS_CONFIG_FILE);
1401
+ const scriptPath = path5.join(hooksDir, HOOKS_SCRIPT_FILE);
1375
1402
  const owners = readCopilotHooksOwners(hooksDir);
1376
1403
  const scriptChanged = writeIfDifferent(scriptPath, COPILOT_CLI_HOOK_SCRIPT);
1377
1404
  const desiredText = JSON.stringify(buildConfig(scriptPath), null, 2) + "\n";
@@ -1391,10 +1418,10 @@ function releaseCopilotHooksFile(hooksDir, owner, keepBackups = false) {
1391
1418
  writeIfDifferent(ownersPath, [...owners].sort().join("\n") + "\n");
1392
1419
  return true;
1393
1420
  }
1394
- const configRemoved = removeFileInScope(path4.join(hooksDir, HOOKS_CONFIG_FILE));
1395
- if (!keepBackups) removeCreatedBackups(path4.join(hooksDir, HOOKS_CONFIG_FILE));
1396
- takeCreatedConfig(path4.join(hooksDir, HOOKS_CONFIG_FILE));
1397
- const scriptRemoved = removeFileInScope(path4.join(hooksDir, HOOKS_SCRIPT_FILE));
1421
+ const configRemoved = removeFileInScope(path5.join(hooksDir, HOOKS_CONFIG_FILE));
1422
+ if (!keepBackups) removeCreatedBackups(path5.join(hooksDir, HOOKS_CONFIG_FILE));
1423
+ takeCreatedConfig(path5.join(hooksDir, HOOKS_CONFIG_FILE));
1424
+ const scriptRemoved = removeFileInScope(path5.join(hooksDir, HOOKS_SCRIPT_FILE));
1398
1425
  const ownersRemoved = removeFileInScope(ownersPath);
1399
1426
  return configRemoved || scriptRemoved || ownersRemoved;
1400
1427
  }
@@ -1453,7 +1480,7 @@ init_define_import_meta_env();
1453
1480
  // src/copilot_mcp_tools.ts
1454
1481
  init_define_import_meta_env();
1455
1482
  import fs6 from "node:fs";
1456
- import path5 from "node:path";
1483
+ import path6 from "node:path";
1457
1484
 
1458
1485
  // src/overflow_guard.ts
1459
1486
  init_define_import_meta_env();
@@ -1552,7 +1579,7 @@ function readCopilotMcpTools(dir = copilotCliMcpToolsDir()) {
1552
1579
  if (!entry.toLowerCase().endsWith(".json")) continue;
1553
1580
  let parsed;
1554
1581
  try {
1555
- parsed = JSON.parse(fs6.readFileSync(path5.join(dir, entry), "utf8"));
1582
+ parsed = JSON.parse(fs6.readFileSync(path6.join(dir, entry), "utf8"));
1556
1583
  } catch {
1557
1584
  unreadable += 1;
1558
1585
  continue;
@@ -1970,15 +1997,15 @@ function extractToolResponseField(raw, keys) {
1970
1997
  if (typeof resp === "string") return resp;
1971
1998
  if (resp !== null && typeof resp === "object") {
1972
1999
  const r = resp;
1973
- const path24 = resolveToolResponseFieldPath(r, keys);
1974
- if (path24 !== null) return readAtPath(r, path24);
2000
+ const path26 = resolveToolResponseFieldPath(r, keys);
2001
+ if (path26 !== null) return readAtPath(r, path26);
1975
2002
  }
1976
2003
  return "";
1977
2004
  }
1978
2005
  var NESTED_TOOL_RESPONSE_PATHS = [["file", "content"]];
1979
- function readAtPath(resp, path24) {
2006
+ function readAtPath(resp, path26) {
1980
2007
  let cur = resp;
1981
- for (const seg of path24) {
2008
+ for (const seg of path26) {
1982
2009
  if (cur === null || typeof cur !== "object") return "";
1983
2010
  cur = cur[seg];
1984
2011
  }
@@ -1988,25 +2015,25 @@ function resolveToolResponseFieldPath(resp, keys) {
1988
2015
  for (const key of keys) {
1989
2016
  if (typeof resp[key] === "string" && resp[key] !== "") return [key];
1990
2017
  }
1991
- for (const path24 of NESTED_TOOL_RESPONSE_PATHS) {
1992
- if (readAtPath(resp, path24) !== "") return path24;
2018
+ for (const path26 of NESTED_TOOL_RESPONSE_PATHS) {
2019
+ if (readAtPath(resp, path26) !== "") return path26;
1993
2020
  }
1994
2021
  return null;
1995
2022
  }
1996
2023
  function replaceToolResponseField(resp, keys, newText) {
1997
- const path24 = resolveToolResponseFieldPath(resp, keys);
1998
- if (path24 === null) return null;
2024
+ const path26 = resolveToolResponseFieldPath(resp, keys);
2025
+ if (path26 === null) return null;
1999
2026
  const root = { ...resp };
2000
2027
  let cur = root;
2001
- for (let i = 0; i < path24.length - 1; i++) {
2002
- const seg = path24[i];
2028
+ for (let i = 0; i < path26.length - 1; i++) {
2029
+ const seg = path26[i];
2003
2030
  const child = cur[seg];
2004
2031
  if (child === null || typeof child !== "object") return null;
2005
2032
  const cloned = { ...child };
2006
2033
  cur[seg] = cloned;
2007
2034
  cur = cloned;
2008
2035
  }
2009
- cur[path24[path24.length - 1]] = newText;
2036
+ cur[path26[path26.length - 1]] = newText;
2010
2037
  return root;
2011
2038
  }
2012
2039
  var OUTPUT_FIRST_TOOL_RESPONSE_KEYS = ["output", "content", "text", "body", "stdout", "stderr"];
@@ -2093,8 +2120,8 @@ function passOutput() {
2093
2120
  function denyOutput(message) {
2094
2121
  return { hookType: "deny", message: `[tg] ${neutralizeOutsideFences(message)}` };
2095
2122
  }
2096
- function contextOutput(context) {
2097
- return { hookType: "context", context };
2123
+ function contextOutput(context, correlators) {
2124
+ return correlators === void 0 ? { hookType: "context", context } : { hookType: "context", context, correlators };
2098
2125
  }
2099
2126
  function emitRewrite(updatedOutput, detail, savings, redaction = "count-here") {
2100
2127
  if (detectHarness() === "vscode") return passOutput();
@@ -2104,7 +2131,8 @@ function emitRewrite(updatedOutput, detail, savings, redaction = "count-here") {
2104
2131
  }
2105
2132
  if (savings !== void 0) {
2106
2133
  const bytesSaved = savings.originalBytes - Buffer.byteLength(updatedOutput, "utf-8");
2107
- if (bytesSaved > 0) recordStat(savings.kind, bytesSaved, savedTokensFromBytes(bytesSaved), void 0, savings.detail);
2134
+ const credited = Math.max(0, bytesSaved);
2135
+ recordStat(savings.kind, credited, savedTokensFromBytes(credited), void 0, savings.detail);
2108
2136
  }
2109
2137
  return { hookType: "rewriteOutput", updatedOutput };
2110
2138
  }
@@ -2163,8 +2191,8 @@ function makeDedupHintHandlers(opts) {
2163
2191
  init_define_import_meta_env();
2164
2192
  import * as crypto from "node:crypto";
2165
2193
  import * as fs7 from "node:fs";
2166
- import * as os3 from "node:os";
2167
- import * as path6 from "node:path";
2194
+ import * as os4 from "node:os";
2195
+ import * as path7 from "node:path";
2168
2196
  function vscodeToolName(event) {
2169
2197
  const name = event?.raw[VSCODE_TOOL_NAME_KEY];
2170
2198
  return typeof name === "string" ? name : void 0;
@@ -2174,10 +2202,10 @@ var MATERIALIZED_MAX_AGE_MS = 60 * 60 * 1e3;
2174
2202
  function pruneMaterialized() {
2175
2203
  const now = Date.now();
2176
2204
  try {
2177
- const dir = os3.tmpdir();
2205
+ const dir = os4.tmpdir();
2178
2206
  for (const file of fs7.readdirSync(dir)) {
2179
2207
  if (!file.startsWith(SHRINK_PREFIX)) continue;
2180
- const full = path6.join(dir, file);
2208
+ const full = path7.join(dir, file);
2181
2209
  try {
2182
2210
  const st = fs7.statSync(full);
2183
2211
  if (st.isFile() && now - st.mtimeMs > MATERIALIZED_MAX_AGE_MS) fs7.unlinkSync(full);
@@ -2194,7 +2222,7 @@ function materializeShrunkImageFile(context) {
2194
2222
  if (!match) return void 0;
2195
2223
  try {
2196
2224
  pruneMaterialized();
2197
- const file = path6.join(os3.tmpdir(), `${SHRINK_PREFIX}${process.pid}-${Date.now()}-${crypto.randomUUID()}.${match[1]}`);
2225
+ const file = path7.join(os4.tmpdir(), `${SHRINK_PREFIX}${process.pid}-${Date.now()}-${crypto.randomUUID()}.${match[1]}`);
2198
2226
  fs7.writeFileSync(file, Buffer.from(match[2], "base64"), { mode: 384 });
2199
2227
  return file;
2200
2228
  } catch {
@@ -2272,7 +2300,15 @@ function noteUnrecognizedTool(event, list) {
2272
2300
  const toolName = event.toolName;
2273
2301
  if (typeof toolName !== "string" || toolName === "") return;
2274
2302
  const named = [];
2275
- for (const { toolName: want } of list) {
2303
+ for (const { toolName: want, toolPattern } of list) {
2304
+ if (toolPattern !== void 0) {
2305
+ let matches = false;
2306
+ try {
2307
+ matches = new RegExp(toolPattern).test(toolName);
2308
+ } catch {
2309
+ }
2310
+ if (matches) return;
2311
+ }
2276
2312
  if (want === void 0) continue;
2277
2313
  if (want === toolName) return;
2278
2314
  named.push(want);
@@ -2319,11 +2355,7 @@ var CLAUDE_CODE_EVENT_NAMES = {
2319
2355
  user_prompt_submit: "UserPromptSubmit",
2320
2356
  subagent_stop: "SubagentStop",
2321
2357
  session_start: "SessionStart",
2322
- // Claude Code has no separate failure event -- a failed tool arrives on PostToolUse there,
2323
- // and only Copilot splits it out. This entry exists because the map is exhaustive over
2324
- // HookEventName, and it is a spelling for the response envelope rather than a claim that
2325
- // Claude Code will ever send this event. Nothing iterates this map to build install config,
2326
- // so naming an event Claude Code does not have cannot register one.
2358
+ // Claude Code has no separate failure event -- a failed tool arrives on PostToolUse there, and only Copilot splits it out. This entry exists because the map is exhaustive over HookEventName, and it is a spelling for the response envelope rather than a claim that Claude Code will ever send this event. Nothing iterates this map to build install config, so naming an event Claude Code does not have cannot register one.
2327
2359
  post_tool_use_failure: "PostToolUseFailure"
2328
2360
  };
2329
2361
  var EVENTS_WITHOUT_ADDITIONAL_CONTEXT = /* @__PURE__ */ new Set([
@@ -2400,6 +2432,8 @@ var _curlDownloads = /* @__PURE__ */ new Map();
2400
2432
  var _curlDownloadsAtLoad = /* @__PURE__ */ new Map();
2401
2433
  var _fileLineRanges = /* @__PURE__ */ new Map();
2402
2434
  var _fileServedOutputs = /* @__PURE__ */ new Map();
2435
+ var _fileLineRangesAtLoad = /* @__PURE__ */ new Map();
2436
+ var _fileServedOutputsAtLoad = /* @__PURE__ */ new Map();
2403
2437
  var _compactedAt = 0;
2404
2438
  var _cliReads = /* @__PURE__ */ new Set();
2405
2439
  var _pendingLargeFileHints = /* @__PURE__ */ new Map();
@@ -2478,7 +2512,6 @@ function recordFileEdit(filePath) {
2478
2512
  const normalized = normalizePath(filePath);
2479
2513
  const key = resolveFilesKey(normalized);
2480
2514
  _fileLineRanges.delete(foldPath(normalized));
2481
- _fileServedOutputs.delete(foldPath(normalized));
2482
2515
  const prev = _files.get(key);
2483
2516
  if (prev === void 0) {
2484
2517
  _files.set(key, {
@@ -2668,6 +2701,20 @@ function consumedCurlDownloadKeys() {
2668
2701
  }
2669
2702
  return consumed;
2670
2703
  }
2704
+ function consumedFileLineRangeKeys() {
2705
+ const consumed = [];
2706
+ for (const key of _fileLineRangesAtLoad.keys()) {
2707
+ if (!_fileLineRanges.has(key)) consumed.push(key);
2708
+ }
2709
+ return consumed;
2710
+ }
2711
+ function consumedFileServedOutputKeys() {
2712
+ const consumed = [];
2713
+ for (const key of _fileServedOutputsAtLoad.keys()) {
2714
+ if (!_fileServedOutputs.has(key)) consumed.push(key);
2715
+ }
2716
+ return consumed;
2717
+ }
2671
2718
  var MAX_RANGES_PER_FILE = 64;
2672
2719
  function statIdentity(absPath) {
2673
2720
  try {
@@ -2748,9 +2795,9 @@ function getSessionId() {
2748
2795
  _sessionId = fromEnv !== void 0 && fromEnv !== "" ? fromEnv : generateSessionId();
2749
2796
  return _sessionId;
2750
2797
  }
2751
- function setTranscriptPath(path24, sessionId) {
2752
- if (path24 === "") return;
2753
- _transcriptPath = { path: path24, sessionId };
2798
+ function setTranscriptPath(path26, sessionId) {
2799
+ if (path26 === "") return;
2800
+ _transcriptPath = { path: path26, sessionId };
2754
2801
  }
2755
2802
  function getTranscriptPath2() {
2756
2803
  if (_transcriptPath === null) return void 0;
@@ -2792,7 +2839,9 @@ function importSessionState(s) {
2792
2839
  _curlDownloads = new Map(s.curlDownloads);
2793
2840
  _curlDownloadsAtLoad = new Map(_curlDownloads);
2794
2841
  _fileLineRanges = new Map(s.fileLineRanges ?? []);
2842
+ _fileLineRangesAtLoad = new Map(_fileLineRanges);
2795
2843
  _fileServedOutputs = new Map(s.fileServedOutputs ?? []);
2844
+ _fileServedOutputsAtLoad = new Map(_fileServedOutputs);
2796
2845
  _cliReads = new Set(s.cliReads ?? []);
2797
2846
  _pendingLargeFileHints = new Map(s.pendingLargeFileHints ?? []);
2798
2847
  _pendingLargeFileHintsAtLoad = new Map(_pendingLargeFileHints);
@@ -2816,7 +2865,9 @@ registerReset(() => {
2816
2865
  _curlDownloads = /* @__PURE__ */ new Map();
2817
2866
  _curlDownloadsAtLoad = /* @__PURE__ */ new Map();
2818
2867
  _fileLineRanges = /* @__PURE__ */ new Map();
2868
+ _fileLineRangesAtLoad = /* @__PURE__ */ new Map();
2819
2869
  _fileServedOutputs = /* @__PURE__ */ new Map();
2870
+ _fileServedOutputsAtLoad = /* @__PURE__ */ new Map();
2820
2871
  _cliReads = /* @__PURE__ */ new Set();
2821
2872
  _pendingLargeFileHints = /* @__PURE__ */ new Map();
2822
2873
  _pendingLargeFileHintsAtLoad = /* @__PURE__ */ new Map();
@@ -2835,9 +2886,18 @@ registerReset(() => {
2835
2886
  init_define_import_meta_env();
2836
2887
  import { createHash } from "node:crypto";
2837
2888
  import * as fs9 from "node:fs";
2838
- import * as path7 from "node:path";
2839
- var MAX_FILES = 500;
2889
+ import * as path9 from "node:path";
2890
+
2891
+ // src/sessions_dir.ts
2892
+ init_define_import_meta_env();
2893
+ import * as path8 from "node:path";
2840
2894
  var SESSIONS_SUBDIR = "sessions";
2895
+ function sessionsDir() {
2896
+ return path8.join(tokenGoatHome(), SESSIONS_SUBDIR);
2897
+ }
2898
+
2899
+ // src/session_store.ts
2900
+ var MAX_FILES = 500;
2841
2901
  var AGENT_SALT_MARKER = sanitizeIdForFilename(":agent:");
2842
2902
  var AGENT_SALT_SEPARATOR = ":agent:";
2843
2903
  var AGENT_DIGEST_CHARS = 12;
@@ -2860,10 +2920,10 @@ function sessionSidecarPath(sessionId, suffix) {
2860
2920
  const safe = sessionFileStem(sessionId);
2861
2921
  if (!safe) return null;
2862
2922
  if (suffix.includes("/") || suffix.includes("\\") || suffix.includes("..")) return null;
2863
- const dir = path7.join(tokenGoatHome(), SESSIONS_SUBDIR);
2864
- const candidate = path7.join(dir, `${safe}${suffix}`);
2923
+ const dir = sessionsDir();
2924
+ const candidate = path9.join(dir, `${safe}${suffix}`);
2865
2925
  try {
2866
- const rel = path7.relative(dir, candidate);
2926
+ const rel = path9.relative(dir, candidate);
2867
2927
  if (rel.startsWith("..")) return null;
2868
2928
  } catch {
2869
2929
  return null;
@@ -3108,10 +3168,14 @@ function mergeSessionState(disk, mem) {
3108
3168
  byPath.set(key, prev ? mergeFileEntry(prev, e) : e);
3109
3169
  }
3110
3170
  const compactedAt = Math.max(disk.compactedAt ?? 0, mem.compactedAt ?? 0);
3111
- const diskRanges = (disk.compactedAt ?? 0) === compactedAt ? disk.fileLineRanges ?? [] : [];
3171
+ const rawDiskRanges = (disk.compactedAt ?? 0) === compactedAt ? disk.fileLineRanges ?? [] : [];
3112
3172
  const memRanges = (mem.compactedAt ?? 0) === compactedAt ? mem.fileLineRanges ?? [] : [];
3113
- const diskServed = (disk.compactedAt ?? 0) === compactedAt ? disk.fileServedOutputs ?? [] : [];
3173
+ const rawDiskServed = (disk.compactedAt ?? 0) === compactedAt ? disk.fileServedOutputs ?? [] : [];
3114
3174
  const memServed = (mem.compactedAt ?? 0) === compactedAt ? mem.fileServedOutputs ?? [] : [];
3175
+ const clearedRangeFiles = new Set(consumedFileLineRangeKeys());
3176
+ const clearedServedFiles = new Set(consumedFileServedOutputKeys());
3177
+ const diskRanges = rawDiskRanges.filter(([path26]) => !clearedRangeFiles.has(path26));
3178
+ const diskServed = rawDiskServed.filter(([path26]) => !clearedServedFiles.has(path26));
3115
3179
  return {
3116
3180
  files: Array.from(byPath.values()),
3117
3181
  hintsShown: Array.from(/* @__PURE__ */ new Set([...disk.hintsShown, ...mem.hintsShown])),
@@ -3128,10 +3192,7 @@ function mergeSessionState(disk, mem) {
3128
3192
  grepQueries: mergePairs(disk.grepQueries ?? [], mem.grepQueries ?? []),
3129
3193
  globQueries: mergePairs(disk.globQueries ?? [], mem.globQueries ?? []),
3130
3194
  outstandingAgentSpawns: mergeOutstandingAgentSpawns(disk.outstandingAgentSpawns ?? [], mem.outstandingAgentSpawns ?? []),
3131
- // An accumulating collection, so union rather than pick a winner: two hook processes can
3132
- // each see a screenshot the other never did. Disk first, then mem, so the order stays
3133
- // oldest-to-newest and the cap evicts the oldest -- the same policy recordSeenImage applies
3134
- // in memory, applied again here because a union of two capped lists can exceed the cap.
3195
+ // An accumulating collection, so union rather than pick a winner: two hook processes can each see a screenshot the other never did. Disk first, then mem, so the order stays oldest-to-newest and the cap evicts the oldest -- the same policy recordSeenImage applies in memory, applied again here because a union of two capped lists can exceed the cap.
3135
3196
  seenImageHashes: mergeSeenImageHashes(disk.seenImageHashes ?? [], mem.seenImageHashes ?? []),
3136
3197
  // Last-seen scalar, not an accumulating collection: prefer mem's value (this process's freshest observation) over disk's, since a newer write always supersedes an older one.
3137
3198
  ...mem.lastTabContextDigest !== void 0 ? { lastTabContextDigest: mem.lastTabContextDigest } : disk.lastTabContextDigest !== void 0 ? { lastTabContextDigest: disk.lastTabContextDigest } : {},
@@ -3165,13 +3226,13 @@ function listSiblingSessionStates(sessionId) {
3165
3226
  if (!sessionId) return [];
3166
3227
  const prefix = saltedStemPrefix(sessionId);
3167
3228
  if (prefix === AGENT_SALT_MARKER) return [];
3168
- const dir = path7.join(tokenGoatHome(), SESSIONS_SUBDIR);
3229
+ const dir = sessionsDir();
3169
3230
  const out = [];
3170
3231
  try {
3171
3232
  if (!fs9.existsSync(dir)) return out;
3172
3233
  for (const file of fs9.readdirSync(dir)) {
3173
3234
  if (!file.endsWith(".json") || !file.startsWith(prefix)) continue;
3174
- const state = readDiskState(path7.join(dir, file));
3235
+ const state = readDiskState(path9.join(dir, file));
3175
3236
  if (state !== null) out.push(state);
3176
3237
  }
3177
3238
  } catch {
@@ -3190,7 +3251,7 @@ function saveSessionState(sessionId) {
3190
3251
  const p = sessionPath(sessionId);
3191
3252
  if (!p) return;
3192
3253
  try {
3193
- const dir = path7.dirname(p);
3254
+ const dir = path9.dirname(p);
3194
3255
  if (!fs9.existsSync(dir)) ensureDirSync(dir);
3195
3256
  const mem = exportSessionState();
3196
3257
  const writeMerged = () => {
@@ -3215,7 +3276,6 @@ function saveSessionState(sessionId) {
3215
3276
  init_define_import_meta_env();
3216
3277
  import * as fs10 from "fs/promises";
3217
3278
  import { resolve as resolve4 } from "path";
3218
- import { homedir as homedir3 } from "os";
3219
3279
  import { readdirSync as readdirSync3, readFileSync as readFileSync5, existsSync as existsSync7, statSync as statSync3, unlinkSync as unlinkSync2 } from "node:fs";
3220
3280
  var COMPACT_END_MARKER = "<!-- COMPACT_END -->";
3221
3281
  var SKILLS_OUTPUT_SUBDIR = "skills";
@@ -3234,7 +3294,7 @@ function skillOutputsDir() {
3234
3294
  }
3235
3295
  function skillsSourceDir() {
3236
3296
  if (_skillsSourceDirOverride) return _skillsSourceDirOverride;
3237
- return resolve4(homedir3(), ".claude", "skills");
3297
+ return resolve4(claudeConfigDir(), "skills");
3238
3298
  }
3239
3299
  async function ensureSkillsDir() {
3240
3300
  try {
@@ -3718,7 +3778,7 @@ async function getSkillFilePath(skillName2) {
3718
3778
  }
3719
3779
  }
3720
3780
  async function resolvePluginSkillPath(pluginName, skillSlug) {
3721
- const manifestPath = _pluginsManifestPathOverride ?? resolve4(homedir3(), ".claude", "plugins", "installed_plugins.json");
3781
+ const manifestPath = _pluginsManifestPathOverride ?? resolve4(claudeConfigDir(), "plugins", "installed_plugins.json");
3722
3782
  let raw;
3723
3783
  try {
3724
3784
  raw = await fs10.readFile(manifestPath, "utf8");
@@ -3824,14 +3884,14 @@ function pruneSkillOutputs(maxCount = DEFAULT_MAX_COUNT, maxAgeMs = DEFAULT_MAX_
3824
3884
  init_define_import_meta_env();
3825
3885
  import { createHash as createHash2 } from "node:crypto";
3826
3886
  import * as fs11 from "node:fs";
3827
- import * as path9 from "node:path";
3887
+ import * as path11 from "node:path";
3828
3888
 
3829
3889
  // src/vscode_path_gate.ts
3830
3890
  init_define_import_meta_env();
3831
- import * as path8 from "node:path";
3891
+ import * as path10 from "node:path";
3832
3892
  function resolvesToUncPath(target, cwd) {
3833
3893
  try {
3834
- return isUncOrDevicePath(path8.resolve(cwd ?? process.cwd(), target));
3894
+ return isUncOrDevicePath(path10.resolve(cwd ?? process.cwd(), target));
3835
3895
  } catch {
3836
3896
  return false;
3837
3897
  }
@@ -3839,8 +3899,8 @@ function resolvesToUncPath(target, cwd) {
3839
3899
  function vscodePathAllowed(filePath, workspace) {
3840
3900
  if (isUncOrDevicePath(filePath)) return false;
3841
3901
  if (workspace === void 0 || isUncOrDevicePath(workspace)) return false;
3842
- const resolvedRoot = path8.resolve(workspace);
3843
- const resolvedTarget = path8.resolve(workspace, filePath);
3902
+ const resolvedRoot = path10.resolve(workspace);
3903
+ const resolvedTarget = path10.resolve(workspace, filePath);
3844
3904
  const root = foldPathForContainment(resolvedRoot.replace(/\\/g, "/"));
3845
3905
  const target = foldPathForContainment(resolvedTarget.replace(/\\/g, "/"));
3846
3906
  if (target !== root && !target.startsWith(root.endsWith("/") ? root : root + "/")) return false;
@@ -3858,19 +3918,6 @@ function preToolPathDeclined(event, target) {
3858
3918
  }
3859
3919
 
3860
3920
  // src/image_shrink.ts
3861
- var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
3862
- ".png",
3863
- ".jpg",
3864
- ".jpeg",
3865
- ".gif",
3866
- ".webp",
3867
- ".bmp",
3868
- ".tif",
3869
- ".tiff",
3870
- ".avif",
3871
- ".heic",
3872
- ".heif"
3873
- ]);
3874
3921
  var DEFAULT_MAX_DIMENSION = 1568;
3875
3922
  var VISION_PATCH_PX = 28;
3876
3923
  var VISION_TIER_LIMITS = {
@@ -3942,9 +3989,6 @@ function formatShrinkSummary(result, subject) {
3942
3989
  const dataUrl = `data:image/${result.format};base64,${result.data.toString("base64")}`;
3943
3990
  return { summary, dataUrl };
3944
3991
  }
3945
- function isImagePath(p) {
3946
- return IMAGE_EXTENSIONS.has(path9.extname(p).toLowerCase());
3947
- }
3948
3992
  var ImageDecodeError = class extends Error {
3949
3993
  };
3950
3994
  async function probeImageMeta(input) {
@@ -4054,7 +4098,7 @@ function statInfo(absPath) {
4054
4098
  }
4055
4099
  }
4056
4100
  function imageShrinkCacheDir() {
4057
- return path9.join(tokenGoatHome(), "image_shrink_cache");
4101
+ return path11.join(tokenGoatHome(), "image_shrink_cache");
4058
4102
  }
4059
4103
  var SHRINK_ENGINE_REVISION = 3;
4060
4104
  function shrinkCacheKeyForRevision(revision, originalPath, size, mtimeMs, quality) {
@@ -4090,7 +4134,7 @@ function findCachedShrink(originalPath, size, mtimeMs, quality) {
4090
4134
  const format = SHRINK_CACHE_FORMAT.get(m[3] ?? "");
4091
4135
  if (format === void 0) continue;
4092
4136
  return {
4093
- filePath: path9.join(dir, file),
4137
+ filePath: path11.join(dir, file),
4094
4138
  format,
4095
4139
  originalWidth: Number(m[1]),
4096
4140
  originalHeight: Number(m[2])
@@ -4105,7 +4149,7 @@ function writeCachedShrink(originalPath, result, mtimeMs, quality) {
4105
4149
  const key = shrinkCacheKey(originalPath, result.originalBytes, mtimeMs, quality);
4106
4150
  const ext = SHRINK_CACHE_EXTENSION.get(result.format);
4107
4151
  if (ext === void 0) return;
4108
- atomicWriteBytes(path9.join(dir, `token-goat-shrink-${key}-${result.originalWidth}x${result.originalHeight}${ext}`), result.data);
4152
+ atomicWriteBytes(path11.join(dir, `token-goat-shrink-${key}-${result.originalWidth}x${result.originalHeight}${ext}`), result.data);
4109
4153
  } catch {
4110
4154
  }
4111
4155
  }
@@ -4121,7 +4165,7 @@ function pruneShrinkCache() {
4121
4165
  const cutoff = now - DEFAULT_MAX_AGE_MS;
4122
4166
  for (const file of fs11.readdirSync(dir)) {
4123
4167
  if (!file.startsWith("token-goat-shrink-")) continue;
4124
- const full = path9.join(dir, file);
4168
+ const full = path11.join(dir, file);
4125
4169
  try {
4126
4170
  const st = fs11.statSync(full);
4127
4171
  if (st.mtimeMs < cutoff) fs11.unlinkSync(full);
@@ -4133,7 +4177,7 @@ function pruneShrinkCache() {
4133
4177
  }
4134
4178
  var HOST_MATERIALIZED_HARNESSES = /* @__PURE__ */ new Set(["copilot_cli", "pi", "opencode", "openclaw"]);
4135
4179
  async function finalizeShrinkResult(result, filePath, event) {
4136
- const basename11 = displaySafePath(path9.basename(filePath));
4180
+ const basename11 = displaySafePath(path11.basename(filePath));
4137
4181
  const shrinkSaved = result.originalBytes - result.shrunkBytes;
4138
4182
  const tier = loadConfig().image_shrink.vision_tier;
4139
4183
  const shrinkTokens = visionTokensSaved(result.originalWidth, result.originalHeight, result.width, result.height, tier);
@@ -4151,7 +4195,7 @@ ${dataUrl2}`);
4151
4195
  }
4152
4196
  recordStat("image_shrink", shrinkSaved, shrinkTokens, void 0, basename11);
4153
4197
  if (loadConfig().image_shrink.ocr_enabled) {
4154
- const { ocrImage, isTextHeavy, formatOcrSummary } = await import("./token-goat-chunk-YQZE5NS6.mjs");
4198
+ const { ocrImage, isTextHeavy, formatOcrSummary } = await import("./token-goat-chunk-62VJ47WN.mjs");
4155
4199
  const ocr = await ocrImage(result.data);
4156
4200
  if (ocr !== null && isTextHeavy(ocr, loadConfig().image_shrink.ocr_min_confidence)) {
4157
4201
  const emitted = formatOcrSummary(ocr, basename11, result.originalBytes);
@@ -4232,10 +4276,34 @@ async function preReadImageHandler(event) {
4232
4276
  }
4233
4277
  registerHook("pre_tool_use", preReadImageHandler, { toolName: "Read" });
4234
4278
 
4279
+ // src/embed_backfill.ts
4280
+ init_define_import_meta_env();
4281
+ var BACKFILL_META_KEY = "unembeddable_chunks_pruned_version";
4282
+ function ensureLedger(db) {
4283
+ db.exec("CREATE TABLE IF NOT EXISTS index_migrations (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
4284
+ }
4285
+ function selectUnembeddableChunkFiles(db, maxChunksPerFile) {
4286
+ const rows = db.prepare("SELECT file_path AS filePath, COUNT(*) AS chunks FROM chunks GROUP BY file_path").all();
4287
+ return rows.filter((r) => isNonTextAsset(r.filePath) || maxChunksPerFile > 0 && r.chunks > maxChunksPerFile);
4288
+ }
4289
+ function pruneUnembeddableChunks(db, maxChunksPerFile, deleteForFile) {
4290
+ ensureLedger(db);
4291
+ const row = db.prepare("SELECT value FROM index_migrations WHERE key = ?").get(BACKFILL_META_KEY);
4292
+ if (row?.value === VERSION) return { files: 0, chunks: 0, ran: false };
4293
+ const targets = selectUnembeddableChunkFiles(db, maxChunksPerFile);
4294
+ let chunks = 0;
4295
+ for (const target of targets) {
4296
+ deleteForFile(db, target.filePath);
4297
+ chunks += target.chunks;
4298
+ }
4299
+ db.prepare("INSERT INTO index_migrations (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(BACKFILL_META_KEY, VERSION);
4300
+ return { files: targets.length, chunks, ran: true };
4301
+ }
4302
+
4235
4303
  // src/index_prune.ts
4236
4304
  init_define_import_meta_env();
4237
4305
  import * as fs12 from "node:fs";
4238
- import * as path10 from "node:path";
4306
+ import * as path12 from "node:path";
4239
4307
  function removeFileFromIndex(db, filePath) {
4240
4308
  const tx = db.transaction(() => {
4241
4309
  deleteFileRows(db, filePath);
@@ -4367,7 +4435,7 @@ function pruneOrphanedChunks(dbPath = globalDbPath()) {
4367
4435
  return removed;
4368
4436
  }
4369
4437
  function recordKnownRoot(filePath, dbPath = globalDbPath()) {
4370
- const project = findProject(path10.dirname(filePath));
4438
+ const project = findProject(path12.dirname(filePath));
4371
4439
  if (project === null || isTooShallowToPrune(project.root)) return;
4372
4440
  const db = getDb(dbPath);
4373
4441
  db.prepare(
@@ -4425,7 +4493,7 @@ function sweepKnownRoots(dbPath = globalDbPath(), opts) {
4425
4493
  }
4426
4494
  var KNOWN_ROOT_RECORD_MIN_INTERVAL_MS = 60 * 60 * 1e3;
4427
4495
  function knownRootRecordMarkerPath(dir, filePath) {
4428
- return path10.join(dir, `known-root-record-${shortFingerprint(path10.dirname(filePath))}.marker`);
4496
+ return path12.join(dir, `known-root-record-${shortFingerprint(path12.dirname(filePath))}.marker`);
4429
4497
  }
4430
4498
  var KNOWN_ROOT_MARKER_PREFIX = "known-root-record-";
4431
4499
  function sweepExpiredKnownRootMarkers(dir = dataDir()) {
@@ -4434,7 +4502,7 @@ function sweepExpiredKnownRootMarkers(dir = dataDir()) {
4434
4502
  const cutoff = Date.now() - KNOWN_ROOT_RECORD_MIN_INTERVAL_MS;
4435
4503
  for (const file of fs12.readdirSync(dir)) {
4436
4504
  if (!file.startsWith(KNOWN_ROOT_MARKER_PREFIX) || !file.endsWith(".marker")) continue;
4437
- const full = path10.join(dir, file);
4505
+ const full = path12.join(dir, file);
4438
4506
  try {
4439
4507
  if (fs12.statSync(full).mtimeMs < cutoff) {
4440
4508
  fs12.unlinkSync(full);
@@ -4461,14 +4529,47 @@ function recordKnownRootThrottled(filePath, dir = dataDir(), dbPath = globalDbPa
4461
4529
  }
4462
4530
  recordKnownRoot(filePath, dbPath);
4463
4531
  }
4532
+ function findTopIndexedProjects(dbPath = globalDbPath(), limit = 3) {
4533
+ try {
4534
+ const db = getDb(dbPath);
4535
+ const hasRoots = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='known_roots'").get();
4536
+ if (!hasRoots) return [];
4537
+ const rawRoots = db.prepare("SELECT root FROM known_roots").all().map((r) => r.root);
4538
+ const seen = /* @__PURE__ */ new Map();
4539
+ for (const r of rawRoots) {
4540
+ const norm = normalizePath(r).replace(/\/+$/, "");
4541
+ const lower = foldPath(norm);
4542
+ if (!seen.has(lower)) seen.set(lower, norm);
4543
+ }
4544
+ const results = [];
4545
+ for (const root of seen.values()) {
4546
+ const count = db.prepare("SELECT count(*) as c FROM files WHERE path LIKE ? || '/%' OR path = ?").get(root, root).c;
4547
+ if (count > 0) results.push({ root, fileCount: count });
4548
+ }
4549
+ results.sort((a, b) => b.fileCount - a.fileCount);
4550
+ const unique = [];
4551
+ for (const res of results) {
4552
+ const isDuplicate = unique.some((u) => {
4553
+ const r1 = foldPath(u.root);
4554
+ const r2 = foldPath(res.root);
4555
+ return r1.startsWith(r2 + "/") || r2.startsWith(r1 + "/");
4556
+ });
4557
+ if (!isDuplicate) unique.push(res);
4558
+ if (unique.length >= limit) break;
4559
+ }
4560
+ return unique.slice(0, limit);
4561
+ } catch {
4562
+ return [];
4563
+ }
4564
+ }
4464
4565
 
4465
4566
  // src/process_priority.ts
4466
4567
  init_define_import_meta_env();
4467
- import * as os4 from "node:os";
4568
+ import * as os5 from "node:os";
4468
4569
  var PRIORITY_BY_NAME = {
4469
- normal: os4.constants.priority.PRIORITY_NORMAL,
4470
- below_normal: os4.constants.priority.PRIORITY_BELOW_NORMAL,
4471
- low: os4.constants.priority.PRIORITY_LOW
4570
+ normal: os5.constants.priority.PRIORITY_NORMAL,
4571
+ below_normal: os5.constants.priority.PRIORITY_BELOW_NORMAL,
4572
+ low: os5.constants.priority.PRIORITY_LOW
4472
4573
  };
4473
4574
  var DEFAULT_PRIORITY_NAME = "below_normal";
4474
4575
  function resolveWorkerPriority(name) {
@@ -4477,7 +4578,7 @@ function resolveWorkerPriority(name) {
4477
4578
  }
4478
4579
  function applyIndexingPriority() {
4479
4580
  try {
4480
- os4.setPriority(0, resolveWorkerPriority(loadConfig().worker.priority));
4581
+ os5.setPriority(0, resolveWorkerPriority(loadConfig().worker.priority));
4481
4582
  return true;
4482
4583
  } catch {
4483
4584
  return false;
@@ -4488,13 +4589,13 @@ function applyIndexingPriority() {
4488
4589
  init_define_import_meta_env();
4489
4590
  import { spawn } from "node:child_process";
4490
4591
  import * as fs15 from "node:fs";
4491
- import * as path13 from "node:path";
4592
+ import * as path15 from "node:path";
4492
4593
  import { fileURLToPath as fileURLToPath2 } from "node:url";
4493
4594
 
4494
4595
  // src/index_reclaim.ts
4495
4596
  init_define_import_meta_env();
4496
4597
  import * as fs13 from "node:fs";
4497
- import * as path11 from "node:path";
4598
+ import * as path13 from "node:path";
4498
4599
  var DERIVED_TABLES = ["chunk_vectors", "chunks", "refs", "symbols", "files"];
4499
4600
  function indexSizeBytes(dbPath) {
4500
4601
  let total = 0;
@@ -4578,7 +4679,7 @@ function mb(bytes) {
4578
4679
  }
4579
4680
  function cmdReclaimIndex(opts) {
4580
4681
  const dbPath = opts.dbPath ?? globalDbPath();
4581
- if (opts.force !== true && isWorkerRunning(path11.dirname(dbPath))) {
4682
+ if (opts.force !== true && isWorkerRunning(path13.dirname(dbPath))) {
4582
4683
  throw new Error(
4583
4684
  "reclaim-index: the worker daemon is running and writing to this index. Stop it first with 'token-goat worker stop', then re-run. Pass --force to proceed anyway (results may be inaccurate if the worker is really live)."
4584
4685
  );
@@ -4612,7 +4713,7 @@ function cmdReclaimIndex(opts) {
4612
4713
  }
4613
4714
  if (result.checkpointBusy) {
4614
4715
  process.stdout.write(
4615
- ` note: a concurrent reader blocked WAL truncation, so some space is still held in ${path11.basename(dbPath)}-wal and the figures above count the main file only. The next checkpoint releases it
4716
+ ` note: a concurrent reader blocked WAL truncation, so some space is still held in ${path13.basename(dbPath)}-wal and the figures above count the main file only. The next checkpoint releases it
4616
4717
  `
4617
4718
  );
4618
4719
  }
@@ -4621,7 +4722,7 @@ function cmdReclaimIndex(opts) {
4621
4722
  // src/snapshots.ts
4622
4723
  init_define_import_meta_env();
4623
4724
  import * as fs14 from "node:fs";
4624
- import * as path12 from "node:path";
4725
+ import * as path14 from "node:path";
4625
4726
  var MAX_SNAPSHOTS_PER_SESSION = 150;
4626
4727
  var MAX_SNAPSHOT_BYTES = 256 * 1024;
4627
4728
  var SNAPSHOT_TRUNCATE_BYTES = 50 * 1024;
@@ -4631,10 +4732,10 @@ var VALID_KINDS = /* @__PURE__ */ new Set([KIND_READ, KIND_PREDICTIVE]);
4631
4732
  function sessionDir(sessionId) {
4632
4733
  if (!sessionId) return null;
4633
4734
  const safe = sanitizeIdForFilename(sessionId, 64, "anon");
4634
- const base = path12.join(tokenGoatHome(), "session_snapshots");
4635
- const candidate = path12.join(base, safe);
4735
+ const base = path14.join(tokenGoatHome(), "session_snapshots");
4736
+ const candidate = path14.join(base, safe);
4636
4737
  try {
4637
- const rel = path12.relative(base, candidate);
4738
+ const rel = path14.relative(base, candidate);
4638
4739
  if (rel.startsWith("..")) return null;
4639
4740
  } catch {
4640
4741
  return null;
@@ -4647,7 +4748,7 @@ function pathKey(filePath) {
4647
4748
  function snapshot_path(sessionId, filePath) {
4648
4749
  const d = sessionDir(sessionId);
4649
4750
  if (!d) return null;
4650
- return path12.join(d, `${pathKey(filePath)}.bin`);
4751
+ return path14.join(d, `${pathKey(filePath)}.bin`);
4651
4752
  }
4652
4753
  function kindSidecarPath(snapshotPath) {
4653
4754
  return snapshotPath + ".kind";
@@ -4655,7 +4756,7 @@ function kindSidecarPath(snapshotPath) {
4655
4756
  function writeSnapshotKind(sidecarPath, kind) {
4656
4757
  try {
4657
4758
  const safeKind = VALID_KINDS.has(kind) ? kind : KIND_READ;
4658
- const dir = path12.dirname(sidecarPath);
4759
+ const dir = path14.dirname(sidecarPath);
4659
4760
  if (!fs14.existsSync(dir)) {
4660
4761
  ensureDirSync(dir);
4661
4762
  }
@@ -4670,7 +4771,7 @@ function evictOldest(d, maxCount) {
4670
4771
  const entries = [];
4671
4772
  const files = fs14.readdirSync(d);
4672
4773
  for (const file of files) {
4673
- const fullPath = path12.join(d, file);
4774
+ const fullPath = path14.join(d, file);
4674
4775
  if (!file.endsWith(".bin")) continue;
4675
4776
  try {
4676
4777
  const stat2 = fs14.statSync(fullPath);
@@ -4731,7 +4832,7 @@ function store(sessionId, filePath, content, opts = {}) {
4731
4832
  } catch {
4732
4833
  }
4733
4834
  }
4734
- const dir = path12.dirname(p);
4835
+ const dir = path14.dirname(p);
4735
4836
  if (!fs14.existsSync(dir)) {
4736
4837
  ensureDirSync(dir);
4737
4838
  }
@@ -4782,14 +4883,14 @@ function removeEligibleSnapshotFile(fullPath, file, cutoff) {
4782
4883
  return file.endsWith(".bin");
4783
4884
  }
4784
4885
  function cleanup_stale(maxAgeHours = 24) {
4785
- const base = path12.join(tokenGoatHome(), "session_snapshots");
4886
+ const base = path14.join(tokenGoatHome(), "session_snapshots");
4786
4887
  if (!fs14.existsSync(base)) return 0;
4787
4888
  const cutoff = Date.now() - maxAgeHours * 3600 * 1e3;
4788
4889
  let removed = 0;
4789
4890
  try {
4790
4891
  const sessionDirs = fs14.readdirSync(base);
4791
4892
  for (const sessionDir2 of sessionDirs) {
4792
- const sessionPath2 = path12.join(base, sessionDir2);
4893
+ const sessionPath2 = path14.join(base, sessionDir2);
4793
4894
  try {
4794
4895
  const stat2 = fs14.statSync(sessionPath2);
4795
4896
  if (!stat2.isDirectory()) continue;
@@ -4799,7 +4900,7 @@ function cleanup_stale(maxAgeHours = 24) {
4799
4900
  try {
4800
4901
  const files = fs14.readdirSync(sessionPath2);
4801
4902
  for (const file of files) {
4802
- const fullPath = path12.join(sessionPath2, file);
4903
+ const fullPath = path14.join(sessionPath2, file);
4803
4904
  try {
4804
4905
  if (removeEligibleSnapshotFile(fullPath, file, cutoff)) removed++;
4805
4906
  } catch {
@@ -4848,15 +4949,15 @@ function drainingSnapshotStamp(file, content) {
4848
4949
  var DRAINING_READ_ATTEMPTS = 5;
4849
4950
  var DRAINING_READ_RETRY_DELAY_MS = 50;
4850
4951
  function listDrainingFiles(queuePath) {
4851
- const dir = path13.dirname(queuePath);
4852
- const base = `${path13.basename(queuePath)}.draining`;
4952
+ const dir = path15.dirname(queuePath);
4953
+ const base = `${path15.basename(queuePath)}.draining`;
4853
4954
  let entries;
4854
4955
  try {
4855
4956
  entries = fs15.readdirSync(dir);
4856
4957
  } catch {
4857
4958
  return [];
4858
4959
  }
4859
- return entries.filter((name) => name === base || name.startsWith(`${base}.alt-`) && !name.includes(".corrupt-")).sort().map((name) => path13.join(dir, name));
4960
+ return entries.filter((name) => name === base || name.startsWith(`${base}.alt-`) && !name.includes(".corrupt-")).sort().map((name) => path15.join(dir, name));
4860
4961
  }
4861
4962
  var MAX_TRANSIENT_RETRIES = 5;
4862
4963
  function bumpRetryCount(dbPath, absPath) {
@@ -4884,16 +4985,16 @@ function clearRetryCount(dbPath, absPath) {
4884
4985
  }
4885
4986
  }
4886
4987
  function dirtyQueuePathFor(dir) {
4887
- return path13.join(dir, "queue", "dirty.txt");
4988
+ return path15.join(dir, "queue", "dirty.txt");
4888
4989
  }
4889
4990
  function drainHeartbeatPathFor(dir) {
4890
- return path13.join(dir, "queue", "drain-heartbeat");
4991
+ return path15.join(dir, "queue", "drain-heartbeat");
4891
4992
  }
4892
4993
  function writeDrainHeartbeat(dir, force = false) {
4893
4994
  const now = Date.now();
4894
4995
  if (!force && now - (heartbeatWriteTimes.get(dir) ?? 0) < WORKER_HEARTBEAT_REFRESH_MS) return;
4895
4996
  try {
4896
- ensureDirSync(path13.dirname(drainHeartbeatPathFor(dir)));
4997
+ ensureDirSync(path15.dirname(drainHeartbeatPathFor(dir)));
4897
4998
  fs15.writeFileSync(drainHeartbeatPathFor(dir), `${process.pid}
4898
4999
  `);
4899
5000
  heartbeatWriteTimes.set(dir, now);
@@ -4945,10 +5046,10 @@ function parseDirtyQueueLines(raw) {
4945
5046
  return out;
4946
5047
  }
4947
5048
  function workerPidPath(dir = dataDir()) {
4948
- return path13.join(dir, "worker.pid");
5049
+ return path15.join(dir, "worker.pid");
4949
5050
  }
4950
5051
  function workerStampPath(dir = dataDir()) {
4951
- return path13.join(dir, "worker.stamp");
5052
+ return path15.join(dir, "worker.stamp");
4952
5053
  }
4953
5054
  function getDirtyPathsFor(dir) {
4954
5055
  let raw;
@@ -4960,7 +5061,7 @@ function getDirtyPathsFor(dir) {
4960
5061
  return parseDirtyQueueLines(raw);
4961
5062
  }
4962
5063
  function workerErrorLogPath(dir) {
4963
- return path13.join(dir, "worker-errors.log");
5064
+ return path15.join(dir, "worker-errors.log");
4964
5065
  }
4965
5066
  var WORKER_ERROR_LOG_MAX_BYTES = 5 * 1024 * 1024;
4966
5067
  var CORRUPT_QUARANTINE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
@@ -4978,11 +5079,11 @@ function cleanupWorkerStateFiles(dir) {
4978
5079
  } catch {
4979
5080
  }
4980
5081
  try {
4981
- const queueDir = path13.dirname(dirtyQueuePathFor(dir));
5082
+ const queueDir = path15.dirname(dirtyQueuePathFor(dir));
4982
5083
  const cutoff = Date.now() - CORRUPT_QUARANTINE_MAX_AGE_MS;
4983
5084
  for (const file of fs15.readdirSync(queueDir)) {
4984
5085
  if (!file.includes(".corrupt-")) continue;
4985
- const full = path13.join(queueDir, file);
5086
+ const full = path15.join(queueDir, file);
4986
5087
  try {
4987
5088
  if (fs15.statSync(full).mtimeMs < cutoff) fs15.unlinkSync(full);
4988
5089
  } catch {
@@ -5018,7 +5119,7 @@ registerReset(() => {
5018
5119
  function embedFileSerialized(absPath, dbPath, sha) {
5019
5120
  const key = foldPath(absPath);
5020
5121
  const prior = inFlightEmbeddings.get(key);
5021
- const dir = path13.dirname(dbPath);
5122
+ const dir = path15.dirname(dbPath);
5022
5123
  const onEmbedError = (err) => {
5023
5124
  const message = extractErrorMessage(err);
5024
5125
  appendWorkerErrorLog(dir, `${(/* @__PURE__ */ new Date()).toISOString()} indexFileEmbeddings failed for ${absPath}: ${message}
@@ -5072,7 +5173,7 @@ function logTransientReadFailure(dir, absPath) {
5072
5173
  );
5073
5174
  }
5074
5175
  function bumpAndCheckRetry(dir, absPath) {
5075
- const dbPath = path13.join(dir, "global.db");
5176
+ const dbPath = path15.join(dir, "global.db");
5076
5177
  const attempts = bumpRetryCount(dbPath, absPath);
5077
5178
  if (attempts > MAX_TRANSIENT_RETRIES) {
5078
5179
  if (attempts === MAX_TRANSIENT_RETRIES + 1) {
@@ -5089,7 +5190,7 @@ function bumpAndCheckRetry(dir, absPath) {
5089
5190
  function appendToDirtyQueue(dir, absPath) {
5090
5191
  const queuePath = dirtyQueuePathFor(dir);
5091
5192
  try {
5092
- ensureDirSync(path13.dirname(queuePath));
5193
+ ensureDirSync(path15.dirname(queuePath));
5093
5194
  let leadingNewline = "";
5094
5195
  try {
5095
5196
  const existing = fs15.readFileSync(queuePath, "utf8");
@@ -5105,7 +5206,7 @@ function requeueDirtyPath(dir, absPath) {
5105
5206
  if (bumpAndCheckRetry(dir, absPath)) appendToDirtyQueue(dir, absPath);
5106
5207
  }
5107
5208
  function makeIndexer(dbPath) {
5108
- const dir = path13.dirname(dbPath);
5209
+ const dir = path15.dirname(dbPath);
5109
5210
  return (absPath, sha) => {
5110
5211
  try {
5111
5212
  const ixCfgForSkip = loadConfig().indexing;
@@ -5115,22 +5216,29 @@ function makeIndexer(dbPath) {
5115
5216
  }
5116
5217
  const embeddingsEnabled = loadConfig().indexing?.embeddings_enabled ?? true;
5117
5218
  const depsAvailable = embeddingsEnabled && embeddingsDepsAvailable(getDb(dbPath));
5118
- if (depsAvailable) ensureEmbeddingProvenance(getDb(dbPath));
5219
+ if (depsAvailable) {
5220
+ ensureEmbeddingProvenance(getDb(dbPath));
5221
+ pruneUnembeddableChunks(getDb(dbPath), loadConfig().indexing?.max_chunks_per_file ?? 0, deleteFileEmbeddings);
5222
+ }
5119
5223
  const entry = getFileEntry(absPath, dbPath);
5120
- const parseUnchanged = entry?.sha === sha && entry.parserSha === PARSER_FINGERPRINT && !indexedPathSpellingIsStale(entry.filePath, absPath);
5224
+ const spellingStale = entry !== null && indexedPathSpellingIsStale(entry.filePath, absPath);
5225
+ const parseUnchanged = entry?.sha === sha && entry.parserSha === parserFingerprintForLanguage(entry.language) && !spellingStale;
5121
5226
  if (!parseUnchanged) {
5122
5227
  indexFileSync(absPath, dbPath);
5123
5228
  }
5124
- const embedUnchanged = parseUnchanged && isEmbedFresh(
5125
- entry?.embedSha,
5229
+ const embedEntry = parseUnchanged ? entry : getFileEntry(absPath, dbPath);
5230
+ const embedUnchanged = !spellingStale && isEmbedFresh(
5231
+ embedEntry?.embedSha,
5126
5232
  sha,
5127
5233
  embeddingsEnabled,
5128
5234
  depsAvailable,
5129
5235
  // Same partial-config defensiveness as embeddingsEnabled above: 0 matches no stamped oversize marker (config floors this key at 1), so a mocked config re-examines the file rather than assuming it current. See isEmbedFresh.
5130
- loadConfig().indexing?.large_file_symbol_only_kb ?? 0
5236
+ loadConfig().indexing?.large_file_symbol_only_kb ?? 0,
5237
+ // Same reasoning and same 0 fallback for the chunk-count marker.
5238
+ loadConfig().indexing?.max_chunks_per_file ?? 0
5131
5239
  );
5132
5240
  if (embedUnchanged) {
5133
- return false;
5241
+ return parseUnchanged ? false : void 0;
5134
5242
  }
5135
5243
  return embedFileSerialized(absPath, dbPath, sha);
5136
5244
  } catch (err) {
@@ -5155,7 +5263,7 @@ function processDirtyBatch(paths, index = makeIndexer(globalDbPath()), remove =
5155
5263
  if (fileIsAbsent(p)) {
5156
5264
  try {
5157
5265
  remove(p);
5158
- clearRetryCount(path13.join(dir, "global.db"), p);
5266
+ clearRetryCount(path15.join(dir, "global.db"), p);
5159
5267
  } catch (err) {
5160
5268
  appendWorkerErrorLog(dir, `${(/* @__PURE__ */ new Date()).toISOString()} removeFileFromIndex failed for ${p}: ${extractErrorMessage(err)}
5161
5269
  `);
@@ -5170,7 +5278,7 @@ function processDirtyBatch(paths, index = makeIndexer(globalDbPath()), remove =
5170
5278
  continue;
5171
5279
  }
5172
5280
  try {
5173
- const dirname16 = path13.dirname(p);
5281
+ const dirname16 = path15.dirname(p);
5174
5282
  let root;
5175
5283
  if (projectRootCache.has(dirname16)) {
5176
5284
  root = projectRootCache.get(dirname16) ?? null;
@@ -5187,7 +5295,7 @@ function processDirtyBatch(paths, index = makeIndexer(globalDbPath()), remove =
5187
5295
  requeue(dir, p);
5188
5296
  continue;
5189
5297
  }
5190
- clearRetryCount(path13.join(dir, "global.db"), p);
5298
+ clearRetryCount(path15.join(dir, "global.db"), p);
5191
5299
  if (result !== false) indexed += 1;
5192
5300
  }
5193
5301
  return indexed;
@@ -5201,7 +5309,7 @@ function sleepSyncMs(ms) {
5201
5309
  function drainOnce(dir, index, remove) {
5202
5310
  const queuePath = dirtyQueuePathFor(dir);
5203
5311
  const draining = `${queuePath}.draining`;
5204
- const dbPath = path13.join(dir, "global.db");
5312
+ const dbPath = path15.join(dir, "global.db");
5205
5313
  const indexFn = index ?? makeIndexer(dbPath);
5206
5314
  const removeFn = remove ?? makeRemover(dbPath);
5207
5315
  let processed = 0;
@@ -5327,7 +5435,7 @@ function isWorkerRunning(dir = dataDir()) {
5327
5435
  return pidAlive(pid) && hasFreshWorkerHeartbeat(dir, pid);
5328
5436
  }
5329
5437
  function workerHealthCheckMarkerPath(dir) {
5330
- return path13.join(dir, "worker-healthcheck.marker");
5438
+ return path15.join(dir, "worker-healthcheck.marker");
5331
5439
  }
5332
5440
  var WORKER_HEALTHCHECK_MIN_INTERVAL_MS = 5 * 60 * 1e3;
5333
5441
  function ensureWorkerAlive(dir = dataDir()) {
@@ -5423,7 +5531,7 @@ function claimWorkerPidFile(dir, pid) {
5423
5531
  }
5424
5532
  function daemonEntryScript() {
5425
5533
  const self = fileURLToPath2(import.meta.url);
5426
- const launcher = path13.join(path13.dirname(self), "token-goat.mjs");
5534
+ const launcher = path15.join(path15.dirname(self), "token-goat.mjs");
5427
5535
  try {
5428
5536
  if (fs15.existsSync(launcher)) return launcher;
5429
5537
  } catch {
@@ -5541,7 +5649,7 @@ async function runWorkerLoop(dir, pollIntervalMs, shouldStop = () => false) {
5541
5649
  }
5542
5650
  if (Date.now() - lastKnownRootsSweepMs >= KNOWN_ROOTS_SWEEP_INTERVAL_MS) {
5543
5651
  try {
5544
- const result = sweepKnownRoots(path13.join(dir, "global.db"));
5652
+ const result = sweepKnownRoots(path15.join(dir, "global.db"));
5545
5653
  if (result.flaggedRoots.length > 0) {
5546
5654
  appendWorkerErrorLog(
5547
5655
  dir,
@@ -5556,7 +5664,7 @@ async function runWorkerLoop(dir, pollIntervalMs, shouldStop = () => false) {
5556
5664
  const now = Date.now();
5557
5665
  if (now - lastIdleVacuumMs >= IDLE_VACUUM_INTERVAL_MS && now - lastActiveMs >= IDLE_VACUUM_COOLDOWN_MS) {
5558
5666
  try {
5559
- const dbPath = path13.join(dir, "global.db");
5667
+ const dbPath = path15.join(dir, "global.db");
5560
5668
  if (fs15.existsSync(dbPath)) {
5561
5669
  const res = reclaimIndex(dbPath, { rebuild: false });
5562
5670
  if (!res.vacuumDeferred) {
@@ -5602,32 +5710,59 @@ function runDetachedWorkerDaemon() {
5602
5710
  // src/hooks_index.ts
5603
5711
  init_define_import_meta_env();
5604
5712
  import * as fs16 from "node:fs";
5605
- import * as path14 from "node:path";
5713
+ import * as path16 from "node:path";
5606
5714
  function dirtyQueuePath() {
5607
- return path14.join(dataDir(), "queue", "dirty.txt");
5715
+ return path16.join(dataDir(), "queue", "dirty.txt");
5608
5716
  }
5609
- function appendDirtyPath(normalizedPath) {
5717
+ function dirtyQueueLeadingNewline(queuePath) {
5718
+ let fd;
5719
+ try {
5720
+ const size = fs16.statSync(queuePath).size;
5721
+ if (size === 0) return "";
5722
+ fd = fs16.openSync(queuePath, "r");
5723
+ const tail = Buffer.allocUnsafe(1);
5724
+ fs16.readSync(fd, tail, 0, 1, size - 1);
5725
+ return tail[0] === 10 ? "" : "\n";
5726
+ } catch {
5727
+ return "";
5728
+ } finally {
5729
+ if (fd !== void 0) {
5730
+ try {
5731
+ fs16.closeSync(fd);
5732
+ } catch {
5733
+ }
5734
+ }
5735
+ }
5736
+ }
5737
+ function appendDirtyPaths(normalizedPaths) {
5738
+ if (normalizedPaths.length === 0) return;
5610
5739
  const queuePath = dirtyQueuePath();
5611
- const dir = path14.dirname(queuePath);
5740
+ const dir = path16.dirname(queuePath);
5612
5741
  try {
5613
5742
  ensureDirSync(dir);
5614
5743
  } catch (e) {
5615
5744
  if (e.code !== "EEXIST" || !fs16.existsSync(dir)) throw e;
5616
5745
  }
5617
- let leadingNewline = "";
5618
- try {
5619
- const existing = fs16.readFileSync(queuePath, "utf8");
5620
- if (existing.length > 0 && !existing.endsWith("\n")) leadingNewline = "\n";
5621
- } catch {
5622
- }
5623
- fs16.appendFileSync(queuePath, `${leadingNewline}${encodeDirtyQueueLine(normalizedPath)}
5624
- `);
5746
+ const body = normalizedPaths.map((p) => `${encodeDirtyQueueLine(p)}
5747
+ `).join("");
5748
+ fs16.appendFileSync(queuePath, `${dirtyQueueLeadingNewline(queuePath)}${body}`);
5749
+ }
5750
+ function appendDirtyPath(normalizedPath) {
5751
+ appendDirtyPaths([normalizedPath]);
5625
5752
  }
5626
5753
  function enqueueDirtyPathSafe(filePath, opts) {
5754
+ enqueueDirtyPathsSafe([filePath], opts);
5755
+ }
5756
+ function enqueueDirtyPathsSafe(filePaths, opts) {
5627
5757
  try {
5628
- const resolved = opts?.alreadyResolved === true ? filePath : resolveIndexPath(filePath);
5629
- if (isUnderSystemTemp(resolved)) return;
5630
- appendDirtyPath(resolved);
5758
+ const resolved = [];
5759
+ for (const filePath of filePaths) {
5760
+ const r = opts?.alreadyResolved === true ? filePath : resolveIndexPath(filePath);
5761
+ if (isUnderSystemTemp(r)) continue;
5762
+ resolved.push(r);
5763
+ }
5764
+ if (resolved.length === 0) return;
5765
+ appendDirtyPaths(resolved);
5631
5766
  } catch {
5632
5767
  return;
5633
5768
  }
@@ -5649,9 +5784,9 @@ function getDirtyPaths() {
5649
5784
  function preCompactIndexHandler(_event) {
5650
5785
  const paths = getDirtyPaths();
5651
5786
  if (paths.length > 0) {
5652
- const sidecar = path14.join(dataDir(), "queue", "pending.txt");
5787
+ const sidecar = path16.join(dataDir(), "queue", "pending.txt");
5653
5788
  try {
5654
- ensureDirSync(path14.dirname(sidecar));
5789
+ ensureDirSync(path16.dirname(sidecar));
5655
5790
  atomicWriteBytes(sidecar, Buffer.from(`${paths.join("\n")}
5656
5791
  `, "utf8"));
5657
5792
  } catch {
@@ -5664,7 +5799,7 @@ registerHook("pre_compact", preCompactIndexHandler, { advisory: true });
5664
5799
  // src/hooks_read.ts
5665
5800
  init_define_import_meta_env();
5666
5801
  import * as fs23 from "node:fs";
5667
- import * as path19 from "node:path";
5802
+ import * as path21 from "node:path";
5668
5803
 
5669
5804
  // src/hint_stats.ts
5670
5805
  init_define_import_meta_env();
@@ -5690,6 +5825,13 @@ var KNOWN_CORRELATOR_PLACEHOLDERS = /* @__PURE__ */ new Set([
5690
5825
  "table_name",
5691
5826
  "name"
5692
5827
  ]);
5828
+ function joinCorrelators(correlators) {
5829
+ const kept = correlators.filter((c) => c !== "" && !c.includes("\n"));
5830
+ return kept.length === 0 ? null : [...new Set(kept)].join("\n");
5831
+ }
5832
+ function splitCorrelators(correlator) {
5833
+ return correlator.split("\n").filter((c) => c !== "");
5834
+ }
5693
5835
  function extractPathCorrelator(text) {
5694
5836
  const m = /(?:[A-Za-z]:[\\/]|\.{1,2}\/|\/)[^\s"'`]+/.exec(text);
5695
5837
  if (m === null) return null;
@@ -5750,12 +5892,15 @@ function resetSuppressionStreak(category) {
5750
5892
  }
5751
5893
  function applyHintTracking(event, output, classify) {
5752
5894
  if (output.hookType !== "context") return output;
5753
- const { category, correlator } = classify(output.context);
5895
+ const classified = classify(output.context);
5896
+ const { category } = classified;
5897
+ const correlator = output.correlators === void 0 ? classified.correlator : joinCorrelators(output.correlators);
5754
5898
  const compensateSelfResolve = event.eventName === "pre_tool_use";
5755
5899
  if (shouldSuppress(category, event.sessionId)) {
5756
5900
  const occasion = bumpSuppressionStreak(category);
5757
5901
  const thresholds = loadConfig().hints.backoff_thresholds;
5758
5902
  if (!isProbeOccasion(occasion, thresholds)) {
5903
+ logSuppressedDetection(category, event.sessionId, correlator);
5759
5904
  return passOutput();
5760
5905
  }
5761
5906
  logHintEmission(category, event.sessionId, correlator, compensateSelfResolve, output.context.length);
@@ -5765,15 +5910,23 @@ function applyHintTracking(event, output, classify) {
5765
5910
  logHintEmission(category, event.sessionId, correlator, compensateSelfResolve, output.context.length);
5766
5911
  return output;
5767
5912
  }
5913
+ function logSuppressedDetection(category, sessionId, correlator) {
5914
+ try {
5915
+ getDb(globalDbPath()).prepare(
5916
+ `INSERT INTO hint_emissions (category, session_id, harness, correlator, emitted_at, resolved, acted_on, calls_remaining, bytes_emitted, observable, displayed)
5917
+ VALUES (@category, @sessionId, @harness, @correlator, @emittedAt, 1, 0, 0, 0, 0, 0)`
5918
+ ).run({ category, sessionId, harness: getHarnessName(), correlator, emittedAt: Date.now() });
5919
+ } catch {
5920
+ }
5921
+ }
5768
5922
  function logHintEmission(category, sessionId, correlator, compensateSelfResolve = false, bytesEmitted = null) {
5769
5923
  try {
5770
5924
  const db = getDb(globalDbPath());
5771
5925
  const resolved = correlator === null ? 1 : 0;
5772
- const actedOn = resolved === 1 && isSuppressionCategory(category) ? 1 : 0;
5773
5926
  const window = ACTED_ON_WINDOW + (compensateSelfResolve ? 1 : 0);
5774
5927
  db.prepare(
5775
- `INSERT INTO hint_emissions (category, session_id, harness, correlator, emitted_at, resolved, acted_on, calls_remaining, bytes_emitted)
5776
- VALUES (@category, @sessionId, @harness, @correlator, @emittedAt, @resolved, @actedOn, @callsRemaining, @bytesEmitted)`
5928
+ `INSERT INTO hint_emissions (category, session_id, harness, correlator, emitted_at, resolved, acted_on, calls_remaining, bytes_emitted, observable)
5929
+ VALUES (@category, @sessionId, @harness, @correlator, @emittedAt, @resolved, 0, @callsRemaining, @bytesEmitted, @observable)`
5777
5930
  ).run({
5778
5931
  category,
5779
5932
  sessionId,
@@ -5781,9 +5934,9 @@ function logHintEmission(category, sessionId, correlator, compensateSelfResolve
5781
5934
  correlator,
5782
5935
  emittedAt: Date.now(),
5783
5936
  resolved,
5784
- actedOn,
5785
5937
  callsRemaining: correlator === null ? 0 : window,
5786
- bytesEmitted
5938
+ bytesEmitted,
5939
+ observable: correlator === null ? 0 : 1
5787
5940
  });
5788
5941
  } catch {
5789
5942
  }
@@ -5803,7 +5956,8 @@ function commandMentionsCorrelator(command, correlator) {
5803
5956
  }
5804
5957
  var SUPPRESSION_HINT_CATEGORIES = /* @__PURE__ */ new Set([
5805
5958
  "read_reread_dedup",
5806
- "edit_reread_suggest"
5959
+ "edit_reread_suggest",
5960
+ "read_structural_nav"
5807
5961
  ]);
5808
5962
  function isSuppressionCategory(category) {
5809
5963
  return SUPPRESSION_HINT_CATEGORIES.has(category);
@@ -5823,14 +5977,13 @@ function eventTargetText(event) {
5823
5977
  function isDefiance(correlator, target) {
5824
5978
  if (target === "") return false;
5825
5979
  if (TOKEN_GOAT_INVOCATION_RE.test(target)) return false;
5826
- return commandMentionsCorrelator(target, correlator);
5980
+ return splitCorrelators(correlator).some((c) => commandMentionsCorrelator(target, c));
5827
5981
  }
5828
5982
  function isActedOn(category, correlator, command) {
5829
5983
  if (!TOKEN_GOAT_INVOCATION_RE.test(command)) return false;
5830
- if (category === "bash_recall") {
5831
- return command.includes("bash-output") && commandMentionsCorrelator(command, correlator);
5832
- }
5833
- return commandMentionsCorrelator(command, correlator);
5984
+ const anyMatch = splitCorrelators(correlator).some((c) => commandMentionsCorrelator(command, c));
5985
+ if (category === "bash_recall") return command.includes("bash-output") && anyMatch;
5986
+ return anyMatch;
5834
5987
  }
5835
5988
  function resolvePendingHintsForEvent(event) {
5836
5989
  try {
@@ -5840,8 +5993,7 @@ function resolvePendingHintsForEvent(event) {
5840
5993
  const pending = db.prepare(`SELECT id, category, correlator, calls_remaining FROM hint_emissions WHERE session_id = ? AND resolved = 0`).all(event.sessionId);
5841
5994
  for (const row of pending) {
5842
5995
  if (!isHintCategory(row.category) || row.correlator === null) {
5843
- const unobservable = isHintCategory(row.category) && isSuppressionCategory(row.category) ? 1 : 0;
5844
- db.prepare(`UPDATE hint_emissions SET acted_on = ?, resolved = 1 WHERE id = ?`).run(unobservable, row.id);
5996
+ db.prepare(`UPDATE hint_emissions SET acted_on = 0, resolved = 1, observable = 0 WHERE id = ?`).run(row.id);
5845
5997
  continue;
5846
5998
  }
5847
5999
  if (isSuppressionCategory(row.category)) {
@@ -5886,11 +6038,26 @@ registerHook(
5886
6038
  function categoryStats(category) {
5887
6039
  const db = getDb(globalDbPath());
5888
6040
  const row = db.prepare(
5889
- `SELECT COUNT(*) AS emitted, SUM(acted_on) AS actedOn, SUM(bytes_emitted) AS bytesEmitted,
6041
+ // Three populations, deliberately not pooled. `emitted` is what was shown AND could be
6042
+ // scored, so it is the only honest denominator for efficacy: including a row nothing could
6043
+ // ever have satisfied would divide a real numerator by partly-imaginary rows. `unobservable`
6044
+ // was shown but carried no pointer -- it spent bytes and earned no verdict. `detected` was
6045
+ // never shown at all (suppressed, or declined by a net-benefit gate) and spent nothing.
6046
+ // bytesEmitted and legacyEmissions span every row, which is correct in each case: the
6047
+ // unobservable rows really did cost the agent, and the never-displayed ones are zero-byte,
6048
+ // so neither distorts the spend figure.
6049
+ // COALESCE on the counts that stay `number`: SUM over no rows is NULL where the COUNT(*)
6050
+ // this replaced was 0, and an aggregate query always returns its row, so the `?? default`
6051
+ // below would not have caught it.
6052
+ `SELECT COALESCE(SUM(CASE WHEN displayed = 1 AND observable = 1 THEN 1 ELSE 0 END), 0) AS emitted,
6053
+ SUM(CASE WHEN displayed = 1 AND observable = 1 THEN acted_on ELSE 0 END) AS actedOn,
6054
+ COALESCE(SUM(CASE WHEN displayed = 1 AND observable = 0 THEN 1 ELSE 0 END), 0) AS unobservable,
6055
+ COALESCE(SUM(CASE WHEN displayed = 0 THEN 1 ELSE 0 END), 0) AS detected,
6056
+ SUM(bytes_emitted) AS bytesEmitted,
5890
6057
  SUM(CASE WHEN bytes_emitted IS NULL THEN 1 ELSE 0 END) AS legacyEmissions
5891
6058
  FROM hint_emissions WHERE category = ? AND harness = ?`
5892
6059
  ).get(category, getHarnessName());
5893
- return row ?? { emitted: 0, actedOn: 0, bytesEmitted: null, legacyEmissions: 0 };
6060
+ return row ?? { emitted: 0, actedOn: 0, unobservable: 0, detected: 0, bytesEmitted: null, legacyEmissions: 0 };
5894
6061
  }
5895
6062
  function shouldSuppress(category, _sessionId2) {
5896
6063
  try {
@@ -5898,6 +6065,7 @@ function shouldSuppress(category, _sessionId2) {
5898
6065
  const { emitted, actedOn } = categoryStats(category);
5899
6066
  if (emitted < cfg.min_sample_size) return false;
5900
6067
  const pct = 100 * (actedOn ?? 0) / emitted;
6068
+ if (isSuppressionCategory(category)) return 100 - pct > cfg.defiance_threshold_pct;
5901
6069
  return pct < cfg.suppress_threshold_pct;
5902
6070
  } catch {
5903
6071
  return false;
@@ -5918,7 +6086,7 @@ function manualMarks(category) {
5918
6086
  function getHintStatsSummary() {
5919
6087
  const probeThresholds = loadConfig().hints.backoff_thresholds.filter((t) => t > 0);
5920
6088
  return HINT_CATEGORIES.map((category) => {
5921
- const { emitted, actedOn, bytesEmitted, legacyEmissions } = categoryStats(category);
6089
+ const { emitted, actedOn, unobservable, detected, bytesEmitted, legacyEmissions } = categoryStats(category);
5922
6090
  const marks = manualMarks(category);
5923
6091
  const suppressed = shouldSuppress(category, "");
5924
6092
  return {
@@ -5926,6 +6094,8 @@ function getHintStatsSummary() {
5926
6094
  emitted,
5927
6095
  actedOn: actedOn ?? 0,
5928
6096
  efficacyPct: emitted === 0 ? null : Math.round(1e3 * (actedOn ?? 0) / emitted) / 10,
6097
+ unobservable,
6098
+ detected,
5929
6099
  suppressed,
5930
6100
  suppressionPermanent: suppressed && probeThresholds.length === 0,
5931
6101
  manualEffective: marks.effective,
@@ -6064,7 +6234,7 @@ function planServedElisions(rows, bodies) {
6064
6234
  // src/compact.ts
6065
6235
  init_define_import_meta_env();
6066
6236
  import * as fs18 from "node:fs";
6067
- import * as path15 from "node:path";
6237
+ import * as path17 from "node:path";
6068
6238
 
6069
6239
  // src/resident_context.ts
6070
6240
  init_define_import_meta_env();
@@ -6157,9 +6327,9 @@ function collectInvokedSkills(acc, record) {
6157
6327
  function skillName(skill) {
6158
6328
  const name = skill["name"];
6159
6329
  if (typeof name === "string" && name.trim() !== "") return name.trim();
6160
- const path24 = skill["path"];
6161
- if (typeof path24 !== "string") return null;
6162
- const segments = path24.split(/[\\/]/).filter((part) => part !== "");
6330
+ const path26 = skill["path"];
6331
+ if (typeof path26 !== "string") return null;
6332
+ const segments = path26.split(/[\\/]/).filter((part) => part !== "");
6163
6333
  const last = segments[segments.length - 1];
6164
6334
  return last === void 0 || last === "" ? null : last;
6165
6335
  }
@@ -6501,7 +6671,7 @@ function inferSessionGoal(cache, maxTokens = 80) {
6501
6671
  const dirCounts = new Counter();
6502
6672
  for (const fpath of editedPaths) {
6503
6673
  try {
6504
- let parent = path15.dirname(fpath);
6674
+ let parent = path17.dirname(fpath);
6505
6675
  if (parent === ".") {
6506
6676
  parent = "root";
6507
6677
  } else if (parent.startsWith("./")) {
@@ -6579,11 +6749,11 @@ function isNoisePath(inputPath) {
6579
6749
  }
6580
6750
  function findLatestSessionId() {
6581
6751
  try {
6582
- const sessionsDir = path15.join(tokenGoatHome(), "sessions");
6583
- if (!fs18.existsSync(sessionsDir)) {
6752
+ const sessionsDir2 = path17.join(tokenGoatHome(), "sessions");
6753
+ if (!fs18.existsSync(sessionsDir2)) {
6584
6754
  return null;
6585
6755
  }
6586
- const files = fs18.readdirSync(sessionsDir);
6756
+ const files = fs18.readdirSync(sessionsDir2);
6587
6757
  const jsonFiles = files.filter((f) => f.endsWith(".json") && !f.includes(AGENT_SALT_MARKER));
6588
6758
  if (jsonFiles.length === 0) {
6589
6759
  return null;
@@ -6593,9 +6763,9 @@ function findLatestSessionId() {
6593
6763
  return null;
6594
6764
  }
6595
6765
  let latestFile = firstFile;
6596
- let latestMtime = fs18.statSync(path15.join(sessionsDir, firstFile)).mtimeMs;
6766
+ let latestMtime = fs18.statSync(path17.join(sessionsDir2, firstFile)).mtimeMs;
6597
6767
  for (const file of jsonFiles) {
6598
- const mtime = fs18.statSync(path15.join(sessionsDir, file)).mtimeMs;
6768
+ const mtime = fs18.statSync(path17.join(sessionsDir2, file)).mtimeMs;
6599
6769
  if (mtime > latestMtime) {
6600
6770
  latestFile = file;
6601
6771
  latestMtime = mtime;
@@ -6617,28 +6787,28 @@ function eventCount(cache) {
6617
6787
  function writeSessionManifest(projectHash, sessionId, manifestJson) {
6618
6788
  const safeSessionId = sessionFileStem(sessionId);
6619
6789
  if (!safeSessionId) return;
6620
- const sessionsDir = path15.join(dataDir(), "projects", projectHash, "sessions");
6621
- if (!fs18.existsSync(sessionsDir)) {
6622
- ensureDirSync(sessionsDir);
6790
+ const sessionsDir2 = path17.join(dataDir(), "projects", projectHash, "sessions");
6791
+ if (!fs18.existsSync(sessionsDir2)) {
6792
+ ensureDirSync(sessionsDir2);
6623
6793
  }
6624
- const dest = path15.join(sessionsDir, `${safeSessionId}.json`);
6794
+ const dest = path17.join(sessionsDir2, `${safeSessionId}.json`);
6625
6795
  atomicWriteText(dest, JSON.stringify(manifestJson));
6626
6796
  }
6627
6797
  function readAllSessionManifests(projectHash, maxAgeSecs = 3600) {
6628
- const sessionsDir = path15.join(dataDir(), "projects", projectHash, "sessions");
6629
- if (!fs18.existsSync(sessionsDir)) {
6798
+ const sessionsDir2 = path17.join(dataDir(), "projects", projectHash, "sessions");
6799
+ if (!fs18.existsSync(sessionsDir2)) {
6630
6800
  return [];
6631
6801
  }
6632
6802
  const now = Date.now() / 1e3;
6633
6803
  const results = [];
6634
6804
  try {
6635
- const files = fs18.readdirSync(sessionsDir);
6805
+ const files = fs18.readdirSync(sessionsDir2);
6636
6806
  for (const file of files) {
6637
6807
  if (!file.endsWith(".json")) {
6638
6808
  continue;
6639
6809
  }
6640
6810
  try {
6641
- const fullPath = path15.join(sessionsDir, file);
6811
+ const fullPath = path17.join(sessionsDir2, file);
6642
6812
  const stat2 = fs18.statSync(fullPath);
6643
6813
  if (now - stat2.mtimeMs / 1e3 > maxAgeSecs) {
6644
6814
  try {
@@ -6831,6 +7001,7 @@ import * as fs19 from "node:fs";
6831
7001
 
6832
7002
  // src/hints/file_type_handler.ts
6833
7003
  init_define_import_meta_env();
7004
+ var LARGE_SYMBOL_LINE_THRESHOLD = 200;
6834
7005
  var FILE_TYPE_THRESHOLDS = {
6835
7006
  pdf: 0,
6836
7007
  // always intercept (any size)
@@ -7523,17 +7694,17 @@ function buildPackageManifestHint(options) {
7523
7694
  return null;
7524
7695
  }
7525
7696
  }
7526
- function _sanitizeHintPath(path24) {
7527
- if (typeof path24 !== "string") {
7697
+ function _sanitizeHintPath(path26) {
7698
+ if (typeof path26 !== "string") {
7528
7699
  return "???";
7529
7700
  }
7530
- return path24.replace(/[\x00]/g, "").slice(0, 200);
7701
+ return path26.replace(/[\x00]/g, "").slice(0, 200);
7531
7702
  }
7532
7703
 
7533
7704
  // src/doc_compact.ts
7534
7705
  init_define_import_meta_env();
7535
7706
  import * as fs20 from "fs";
7536
- import * as path16 from "path";
7707
+ import * as path18 from "path";
7537
7708
  var defaultSentencesPerSection = 2;
7538
7709
  var headerPrefix = "<!-- token-goat doc-compact source-hash:";
7539
7710
  var headerRegex = /^<!-- token-goat doc-compact source-hash:(\S+) source:(.+?) -->\r?$/;
@@ -7547,14 +7718,14 @@ function sourceHash(filePath) {
7547
7718
  }
7548
7719
  function _compactSlug(absPathStr) {
7549
7720
  const h = fingerprintContent(foldPath(absPathStr)).slice(0, 12);
7550
- const ext = path16.extname(absPathStr);
7551
- const stem = path16.basename(absPathStr, ext);
7721
+ const ext = path18.extname(absPathStr);
7722
+ const stem = path18.basename(absPathStr, ext);
7552
7723
  const safeStem = sanitizeIdForFilename(stem, 32);
7553
7724
  return `${h}_${safeStem}`;
7554
7725
  }
7555
7726
  function compactPathFor(sourcePath) {
7556
7727
  const abs = resolveIndexPath(sourcePath);
7557
- return path16.join(dataDir(), compactSubdir, `${_compactSlug(abs)}.md`);
7728
+ return path18.join(dataDir(), compactSubdir, `${_compactSlug(abs)}.md`);
7558
7729
  }
7559
7730
  function readCompactHeader(compactPath) {
7560
7731
  try {
@@ -7603,13 +7774,13 @@ function readCompactBody(compactPath) {
7603
7774
  }
7604
7775
  }
7605
7776
  function writeCompact(compactPath, sourcePath, compactBody, sourceRel) {
7606
- const srcPath = path16.resolve(sourcePath);
7777
+ const srcPath = path18.resolve(sourcePath);
7607
7778
  const sha = sourceHash(srcPath);
7608
- const displayRel = sourceRel || path16.basename(srcPath);
7779
+ const displayRel = sourceRel || path18.basename(srcPath);
7609
7780
  const header = `${headerPrefix}${sha} source:${displayRel} -->
7610
7781
  `;
7611
7782
  const fullText = header + compactBody.trimStart();
7612
- const dir = path16.dirname(compactPath);
7783
+ const dir = path18.dirname(compactPath);
7613
7784
  if (!fs20.existsSync(dir)) {
7614
7785
  ensureDirSync(dir);
7615
7786
  }
@@ -7746,12 +7917,12 @@ function compactDoc(filePath, heading) {
7746
7917
  init_define_import_meta_env();
7747
7918
  import crypto2 from "node:crypto";
7748
7919
  import fs21 from "node:fs";
7749
- import path17 from "node:path";
7920
+ import path19 from "node:path";
7750
7921
  var MAX_ENTRIES = 500;
7751
7922
  var MAX_TEXT_BYTES = 128 * 1024;
7752
7923
  var CACHE_FILE = "workspace-evidence.json";
7753
7924
  function cachePath() {
7754
- return path17.join(dataDir(), CACHE_FILE);
7925
+ return path19.join(dataDir(), CACHE_FILE);
7755
7926
  }
7756
7927
  function hash(text) {
7757
7928
  return crypto2.createHash("sha256").update(text).digest("hex");
@@ -7874,7 +8045,7 @@ ${changed.map((entry) => `- ${displaySafePath(entry.source)} (use a fresh surgic
7874
8045
  // src/notebook_compact.ts
7875
8046
  init_define_import_meta_env();
7876
8047
  import * as fs22 from "node:fs";
7877
- import * as path18 from "node:path";
8048
+ import * as path20 from "node:path";
7878
8049
  var NB_STRIP_MIN_SAVINGS = 4096;
7879
8050
  function stripNotebook(nbDict) {
7880
8051
  const cells = [];
@@ -7894,14 +8065,14 @@ function stripNotebook(nbDict) {
7894
8065
  var SIDECAR_DEFAULT_MAX_COUNT = 200;
7895
8066
  var SIDECAR_DEFAULT_MAX_AGE_MS = 24 * 3600 * 1e3;
7896
8067
  function pruneSidecars(cacheRoot, maxCount = SIDECAR_DEFAULT_MAX_COUNT, maxAgeMs = SIDECAR_DEFAULT_MAX_AGE_MS) {
7897
- const nbStripDir = path18.join(cacheRoot, "nb_strip");
8068
+ const nbStripDir = path20.join(cacheRoot, "nb_strip");
7898
8069
  let removed = 0;
7899
8070
  try {
7900
8071
  if (!fs22.existsSync(nbStripDir)) return 0;
7901
8072
  const cutoff = Date.now() - maxAgeMs;
7902
8073
  const kept = [];
7903
8074
  for (const entry of fs22.readdirSync(nbStripDir)) {
7904
- const dir = path18.join(nbStripDir, entry);
8075
+ const dir = path20.join(nbStripDir, entry);
7905
8076
  let mtime;
7906
8077
  try {
7907
8078
  mtime = fs22.statSync(dir).mtimeMs;
@@ -7937,8 +8108,8 @@ function pruneSidecars(cacheRoot, maxCount = SIDECAR_DEFAULT_MAX_COUNT, maxAgeMs
7937
8108
  }
7938
8109
  function getOrCreateSidecar(rawBytes, cacheRoot, opts = {}) {
7939
8110
  const sha = fingerprintContent(rawBytes);
7940
- const sidecarDir = path18.join(cacheRoot, "nb_strip", sha);
7941
- const sidecarPath = path18.join(sidecarDir, "stripped.ipynb");
8111
+ const sidecarDir = path20.join(cacheRoot, "nb_strip", sha);
8112
+ const sidecarPath = path20.join(sidecarDir, "stripped.ipynb");
7942
8113
  if (fs22.existsSync(sidecarPath)) {
7943
8114
  return [sidecarPath, false];
7944
8115
  }
@@ -8044,10 +8215,10 @@ var COMMENT_SYNTAX = new Map([
8044
8215
  ...["py", "rb", "sh", "bash", "zsh", "pl", "r"].map((ext) => [ext, HASH]),
8045
8216
  ...["sql", "lua", "hs"].map((ext) => [ext, DOUBLE_DASH])
8046
8217
  ]);
8047
- function commentSyntaxFor(path24) {
8048
- const dot = path24.lastIndexOf(".");
8218
+ function commentSyntaxFor(path26) {
8219
+ const dot = path26.lastIndexOf(".");
8049
8220
  if (dot < 0) return null;
8050
- return COMMENT_SYNTAX.get(path24.slice(dot + 1).toLowerCase()) ?? null;
8221
+ return COMMENT_SYNTAX.get(path26.slice(dot + 1).toLowerCase()) ?? null;
8051
8222
  }
8052
8223
  function planCommentFolds(rows, syntax, commentKeep, commentMinBlock, occupied) {
8053
8224
  if (syntax === null || rows.length === 0 || commentKeep < 1 || commentMinBlock < 1) return [];
@@ -8156,8 +8327,8 @@ function planProseFolds(rows, claimed) {
8156
8327
  }
8157
8328
  return folds;
8158
8329
  }
8159
- function foldDetail(path24, folds, maxLen = MAX_FOLD_DETAIL) {
8160
- const head = `${path24}::`;
8330
+ function foldDetail(path26, folds, maxLen = MAX_FOLD_DETAIL) {
8331
+ const head = `${path26}::`;
8161
8332
  const ids = folds.map((f) => f.kind === "body" ? f.name : `#${f.firstLine}-${f.lastLine}`);
8162
8333
  const budget = maxLen - head.length - FOLD_DETAIL_SUFFIX_BUDGET;
8163
8334
  const kept = [];
@@ -8175,7 +8346,7 @@ function foldDetail(path24, folds, maxLen = MAX_FOLD_DETAIL) {
8175
8346
 
8176
8347
  // src/fold_delivery.ts
8177
8348
  init_define_import_meta_env();
8178
- import { readFileSync as readFileSync13, statSync as statSync12 } from "node:fs";
8349
+ import { readFileSync as readFileSync13, statSync as statSync13 } from "node:fs";
8179
8350
 
8180
8351
  // src/indexed_source.ts
8181
8352
  init_define_import_meta_env();
@@ -8230,7 +8401,7 @@ function commentFoldNotice(firstLine, lastLine, shownPath) {
8230
8401
  var FOLD_SPAN_PARSE_MAX_BYTES = 4e5;
8231
8402
  function parseFoldSpansFromDisk(normalizedPath, rows) {
8232
8403
  try {
8233
- if (statSync12(normalizedPath).size > FOLD_SPAN_PARSE_MAX_BYTES) return [];
8404
+ if (statSync13(normalizedPath).size > FOLD_SPAN_PARSE_MAX_BYTES) return [];
8234
8405
  const language = detectLanguage(normalizedPath);
8235
8406
  if (!isTreeSitterAvailable(language)) return [];
8236
8407
  const fileText = readFileSync13(normalizedPath, "utf-8");
@@ -8249,7 +8420,7 @@ function parseFoldSpansFromDisk(normalizedPath, rows) {
8249
8420
  function resolveFoldSpans(normalizedPath, hasCommentSyntax, rows) {
8250
8421
  try {
8251
8422
  const entry = getFileEntry(normalizedPath);
8252
- if (entry !== null && entry.sha !== "" && !isVirtualIndexedPath(normalizedPath) && entry.sha === fingerprintFile(normalizedPath) && entry.parserSha === PARSER_FINGERPRINT) {
8423
+ if (entry !== null && entry.sha !== "" && !isVirtualIndexedPath(normalizedPath) && entry.sha === fingerprintFile(normalizedPath) && entry.parserSha === parserFingerprintForLanguage(entry.language)) {
8253
8424
  return querySymbols({ filePath: normalizedPath, limit: BODY_FOLD_SYMBOL_LIMIT });
8254
8425
  }
8255
8426
  if (hasCommentSyntax) enqueueDirtyPathSafe(normalizedPath);
@@ -8459,13 +8630,14 @@ function largeFileDenyBytes() {
8459
8630
  function counterfactualCredit(counterfactualBytes, emittedBytes = 0) {
8460
8631
  return Math.max(0, Math.min(counterfactualBytes, PER_FILE_COUNTERFACTUAL_CEILING) - emittedBytes);
8461
8632
  }
8462
- function isNodeModulesPath(p) {
8463
- const check = foldPath(p);
8464
- return check.includes("/node_modules/") || check.includes("\\node_modules\\");
8633
+ function diffHintCredit(counterfactualBytes, body) {
8634
+ const credit = counterfactualCredit(counterfactualBytes, body.length);
8635
+ if (savedTokensFromBytes(credit) < loadConfig().hints.diff_hint_min_tokens_saved) return null;
8636
+ return credit;
8465
8637
  }
8466
8638
  function relPathWithinRoot(root, target) {
8467
- const rel = path19.relative(root, target).replace(/\\/g, "/");
8468
- if (rel.startsWith("..") || path19.isAbsolute(rel)) return null;
8639
+ const rel = path21.relative(root, target).replace(/\\/g, "/");
8640
+ if (rel.startsWith("..") || path21.isAbsolute(rel)) return null;
8469
8641
  return rel;
8470
8642
  }
8471
8643
  function _isDocFile(filePath) {
@@ -8474,7 +8646,7 @@ function _isDocFile(filePath) {
8474
8646
  }
8475
8647
  function isSessionArtifactFile(filePath) {
8476
8648
  if (/[/\\]tasks[/\\][a-z0-9]+\.output$/i.test(filePath)) return true;
8477
- if (/[/\\]tool-results[/\\][a-z0-9]+\.txt$/i.test(filePath)) return true;
8649
+ if (new RegExp("[/\\\\]tool-results[/\\\\]" + TOOL_RESULTS_ID_CHARS + "\\.txt$", "i").test(filePath)) return true;
8478
8650
  return false;
8479
8651
  }
8480
8652
  function sessionArtifactRecall(rawPath) {
@@ -8491,7 +8663,7 @@ var BINARY_FILE_TYPE_EXTS = /* @__PURE__ */ new Set(["pdf", "docx", "xlsx", "ppt
8491
8663
  var TEXT_FILE_TYPE_EXTS = /* @__PURE__ */ new Set(["html", "htm", "xhtml", "txt", "log", "out", "err", "trace", "csv", "tsv", "vtt", "srt", "svg", "xml", "dtsx", "ampkg", "xaml", "json", "yaml", "yml", "jsonl"]);
8492
8664
  var DISPATCHED_FILE_TYPE_EXTS = /* @__PURE__ */ new Set([...BINARY_FILE_TYPE_EXTS, ...TEXT_FILE_TYPE_EXTS]);
8493
8665
  function isDispatchedFileType(basename11) {
8494
- return DISPATCHED_FILE_TYPE_EXTS.has(path19.extname(basename11).slice(1).toLowerCase());
8666
+ return DISPATCHED_FILE_TYPE_EXTS.has(path21.extname(basename11).slice(1).toLowerCase());
8495
8667
  }
8496
8668
  function extractQuickSymbolSamples(content, _filePath) {
8497
8669
  const symbols = [];
@@ -8541,11 +8713,6 @@ function surgicalHint(filePath, basename11, lineCount, fileContent) {
8541
8713
  const isHtmlFile = /\.(html|htm)$/i.test(basename11);
8542
8714
  const isSectionFile = /\.(css|scss|sass|less|toml)$/i.test(basename11);
8543
8715
  const isXmlFile = /\.(xml|dtsx|ampkg|xaml)$/i.test(basename11) && !basename11.toLowerCase().endsWith("-meta.xml");
8544
- const escapeHintName = (name) => {
8545
- const quoted = name.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
8546
- const safe = displaySafeText(quoted);
8547
- return safe !== quoted ? "" : safe.trim();
8548
- };
8549
8716
  if (isXmlFile) {
8550
8717
  return `Use \`token-goat xml-outline "${filePath}"\` for structure or \`token-goat xml-query "${filePath}" "<selector>"\` for nodes.`;
8551
8718
  } else if (isDocFile) {
@@ -8591,9 +8758,62 @@ function surgicalHint(filePath, basename11, lineCount, fileContent) {
8591
8758
  if (isShellScript && samples.length > 0) {
8592
8759
  return `Use \`token-goat section "${filePath}::${sym}"\`${avail} for a section, or \`token-goat skeleton "${filePath}"\` / \`token-goat outline "${filePath}"\` for structure.`;
8593
8760
  }
8761
+ if (samples.length > 0) {
8762
+ try {
8763
+ const indexed = querySymbols({ filePath, name: sym, limit: 1 })[0];
8764
+ if (indexed !== void 0 && isLargeSymbolSpan(indexed.lineStart, indexed.lineEnd, filePath)) {
8765
+ return `\`${sym}\` spans ${indexed.lineEnd - indexed.lineStart + 1} lines -- use \`token-goat grep "<pattern>" ${filePath} -C 15 --symbol\` for a slice inside it, or \`token-goat scope ${filePath}:${indexed.lineStart}\` to confirm the enclosing symbol.`;
8766
+ }
8767
+ } catch {
8768
+ }
8769
+ }
8594
8770
  return `Use \`token-goat read "${filePath}::${sym}"\`${avail} for one function, or \`token-goat skeleton "${filePath}"\` / \`token-goat outline "${filePath}"\` for structure.`;
8595
8771
  }
8596
8772
  }
8773
+ function escapeHintName(name) {
8774
+ const quoted = name.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
8775
+ const safe = displaySafeText(quoted);
8776
+ return safe !== quoted ? "" : safe.trim();
8777
+ }
8778
+ function isLargeSymbolSpan(lineStart, lineEnd, filePath) {
8779
+ const span = lineEnd - lineStart + 1;
8780
+ if (span > LARGE_SYMBOL_LINE_THRESHOLD) return true;
8781
+ const totalLines = lineCountForSurgicalHint(filePath);
8782
+ return totalLines > 0 && span > totalLines / 2;
8783
+ }
8784
+ function isWindowInsideMuchLargerSymbol(filePath, start, end) {
8785
+ try {
8786
+ const windowLines = end - start + 1;
8787
+ return querySymbols({ filePath, limit: 500 }).some((s) => {
8788
+ const span = s.lineEnd - s.lineStart + 1;
8789
+ return s.lineStart <= start && s.lineEnd >= end && windowLines * 3 <= span;
8790
+ });
8791
+ } catch {
8792
+ return false;
8793
+ }
8794
+ }
8795
+ function realSymbolReadHint(filePath, shown, range) {
8796
+ let candidates;
8797
+ try {
8798
+ const all = querySymbols({ filePath, limit: 500 }).map((s) => ({ name: escapeHintName(s.name), lineStart: s.lineStart, lineEnd: s.lineEnd }));
8799
+ const overlapping = range !== void 0 ? all.filter((s) => s.lineStart <= range.end && s.lineEnd >= range.start) : [];
8800
+ candidates = (overlapping.length > 0 ? overlapping : all).filter((s) => s.name !== "");
8801
+ } catch {
8802
+ candidates = [];
8803
+ }
8804
+ const top = candidates[0];
8805
+ if (top === void 0) {
8806
+ return range !== void 0 ? '`token-goat read "' + shown + "@" + range.start + "-" + range.end + '"`' : '`token-goat outline "' + shown + '"`';
8807
+ }
8808
+ if (isLargeSymbolSpan(top.lineStart, top.lineEnd, filePath)) {
8809
+ const line = range !== void 0 ? range.start : top.lineStart;
8810
+ const span = top.lineEnd - top.lineStart + 1;
8811
+ return '`token-goat grep "<pattern>" ' + shown + " -C 15 --symbol` for a slice inside `" + top.name + "` (" + span + " lines), or `token-goat scope " + shown + ":" + line + "` to confirm the enclosing symbol";
8812
+ }
8813
+ const names = candidates.map((s) => s.name).slice(0, 3);
8814
+ const rest = names.length > 1 ? " (or: " + names.slice(1).join(", ") + ")" : "";
8815
+ return '`token-goat read "' + shown + "::" + names[0] + '"`' + rest;
8816
+ }
8597
8817
  function lineCountForSurgicalHint(filePath, fileStatSize) {
8598
8818
  try {
8599
8819
  if (fileStatSize !== void 0 && fileStatSize > SLICE_ESTIMATE_SCAN_CAP_BYTES) {
@@ -8704,7 +8924,7 @@ function preReadHandlerInner(event) {
8704
8924
  } catch {
8705
8925
  }
8706
8926
  }
8707
- const basename11 = displaySafePath(path19.basename(normalized));
8927
+ const basename11 = displaySafePath(path21.basename(normalized));
8708
8928
  if (isLockFile(basename11)) {
8709
8929
  return denyOutput(
8710
8930
  'Lock files are rarely useful to read in full. Use `token-goat section "' + shown + '::<section>"` to extract a specific dependency, or read the relevant manifest instead.'
@@ -8825,12 +9045,13 @@ function preReadHandlerInner(event) {
8825
9045
  (basename11 + " is unchanged since last read. " + surgicalHint(normalized, basename11, countTextLines(snapDiff.currentContent), snapDiff.currentContent)).trimEnd()
8826
9046
  );
8827
9047
  }
8828
- if (snapDiff.kind === "diff" && savedTokensFromBytes(snapDiff.savedBytes) >= loadConfig().hints.diff_hint_min_tokens_saved) {
8829
- recordActualRead(event, normalized);
8830
- recordStat("session_hint", 0, 0);
8831
- return denyOutput(
8832
- ("Content changed since last read of " + basename11 + ". Here is what changed:\n\n" + fenceUntrustedFileContent("```diff\n" + snapDiff.diff + "\n```") + "\n\n" + surgicalHint(normalized, basename11, countTextLines(snapDiff.currentContent), snapDiff.currentContent)).trimEnd()
8833
- );
9048
+ if (snapDiff.kind === "diff") {
9049
+ const diffBody = ("Content changed since last read of " + basename11 + ". Here is what changed:\n\n" + fenceUntrustedFileContent("```diff\n" + snapDiff.diff + "\n```") + "\n\n" + surgicalHint(normalized, basename11, countTextLines(snapDiff.currentContent), snapDiff.currentContent)).trimEnd();
9050
+ if (diffHintCredit(snapDiff.currentContent.length, diffBody) !== null) {
9051
+ recordActualRead(event, normalized);
9052
+ recordStat("session_hint", 0, 0);
9053
+ return denyOutput(diffBody);
9054
+ }
8834
9055
  }
8835
9056
  recordActualRead(event, normalized);
8836
9057
  recordStat("session_hint", 0, 0);
@@ -8861,12 +9082,13 @@ function preReadHandlerInner(event) {
8861
9082
  (basename11 + " is unchanged since last read. " + surgicalHint(normalized, basename11, countTextLines(memSnapDiff.currentContent), memSnapDiff.currentContent)).trimEnd()
8862
9083
  );
8863
9084
  }
8864
- if (memSnapDiff.kind === "diff" && savedTokensFromBytes(memSnapDiff.savedBytes) >= loadConfig().hints.diff_hint_min_tokens_saved) {
8865
- recordActualRead(event, normalized);
8866
- recordStat("session_hint", 0, 0);
8867
- return denyOutput(
8868
- ("Content changed since last read of " + basename11 + ". Here is what changed:\n\n" + fenceUntrustedFileContent("```diff\n" + memSnapDiff.diff + "\n```") + "\n\n" + surgicalHint(normalized, basename11, countTextLines(memSnapDiff.currentContent), memSnapDiff.currentContent)).trimEnd()
8869
- );
9085
+ if (memSnapDiff.kind === "diff") {
9086
+ const diffBody = ("Content changed since last read of " + basename11 + ". Here is what changed:\n\n" + fenceUntrustedFileContent("```diff\n" + memSnapDiff.diff + "\n```") + "\n\n" + surgicalHint(normalized, basename11, countTextLines(memSnapDiff.currentContent), memSnapDiff.currentContent)).trimEnd();
9087
+ if (diffHintCredit(memSnapDiff.currentContent.length, diffBody) !== null) {
9088
+ recordActualRead(event, normalized);
9089
+ recordStat("session_hint", 0, 0);
9090
+ return denyOutput(diffBody);
9091
+ }
8870
9092
  }
8871
9093
  recordActualRead(event, normalized);
8872
9094
  recordStat("session_hint", 0, 0);
@@ -8915,12 +9137,13 @@ function preReadHandlerInner(event) {
8915
9137
  );
8916
9138
  }
8917
9139
  if (snapDiff.kind === "diff") {
8918
- recordActualRead(event, normalized);
8919
- const artifactDiffCredit = counterfactualCredit(snapDiff.currentContent.length, snapDiff.diff.length);
8920
- recordStat("session_hint", artifactDiffCredit, savedTokensFromBytes(artifactDiffCredit), void 0, "artifact-snapshot-diff");
8921
- return denyOutput(
8922
- "Content changed since last read of " + basename11 + ". Here is what changed:\n\n" + fenceUntrustedFileContent("```diff\n" + snapDiff.diff + "\n```") + "\n\n" + sessionArtifactRecall(normalized)
8923
- );
9140
+ const diffBody = "Content changed since last read of " + basename11 + ". Here is what changed:\n\n" + fenceUntrustedFileContent("```diff\n" + snapDiff.diff + "\n```") + "\n\n" + sessionArtifactRecall(normalized);
9141
+ const artifactDiffCredit = diffHintCredit(snapDiff.currentContent.length, diffBody);
9142
+ if (artifactDiffCredit !== null) {
9143
+ recordActualRead(event, normalized);
9144
+ recordStat("session_hint", artifactDiffCredit, savedTokensFromBytes(artifactDiffCredit), void 0, "artifact-snapshot-diff");
9145
+ return denyOutput(diffBody);
9146
+ }
8924
9147
  }
8925
9148
  recordActualRead(event, normalized);
8926
9149
  recordStat("session_hint", 0, 0);
@@ -8963,13 +9186,12 @@ function preReadHandlerInner(event) {
8963
9186
  );
8964
9187
  }
8965
9188
  if (snapDiff.kind === "diff") {
8966
- if (savedTokensFromBytes(snapDiff.savedBytes) >= loadConfig().hints.diff_hint_min_tokens_saved) {
9189
+ const diffBody = ("Content changed since last read of " + basename11 + ". Here is what changed:\n\n" + fenceUntrustedFileContent("```diff\n" + snapDiff.diff + "\n```") + "\n\n" + surgicalHint(normalized, basename11, countTextLines(snapDiff.currentContent), snapDiff.currentContent)).trimEnd();
9190
+ const diffCredit = diffHintCredit(snapDiff.currentContent.length, diffBody);
9191
+ if (diffCredit !== null) {
8967
9192
  recordActualRead(event, normalized);
8968
- const diffCredit = counterfactualCredit(snapDiff.currentContent.length, snapDiff.diff.length);
8969
9193
  recordStat("diff_hint", diffCredit, savedTokensFromBytes(diffCredit));
8970
- return denyOutput(
8971
- ("Content changed since last read of " + basename11 + ". Here is what changed:\n\n" + fenceUntrustedFileContent("```diff\n" + snapDiff.diff + "\n```") + "\n\n" + surgicalHint(normalized, basename11, countTextLines(snapDiff.currentContent), snapDiff.currentContent)).trimEnd()
8972
- );
9194
+ return denyOutput(diffBody);
8973
9195
  }
8974
9196
  }
8975
9197
  }
@@ -9019,7 +9241,7 @@ function preReadHandlerInner(event) {
9019
9241
  recordStat("read_served_deny", blocked, savedTokensFromBytes(blocked));
9020
9242
  recordStat("session_hint", 0, 0);
9021
9243
  return denyOutput(
9022
- "Every line of " + shown + " this read would return was already served in this session, byte for byte. Recall it with `token-goat bash-output " + alreadyServed.id + '`, or pull just the part you need with `token-goat read "' + shown + '::Symbol"`.'
9244
+ "Every line of " + shown + " this read would return was already served in this session, byte for byte. Recall it with `token-goat bash-output " + alreadyServed.id + "`, or pull just the part you need with " + realSymbolReadHint(normalized, shown) + "."
9023
9245
  );
9024
9246
  }
9025
9247
  const window = readRequestedSliceWindow(event);
@@ -9034,7 +9256,7 @@ function preReadHandlerInner(event) {
9034
9256
  recordStat("read_served_deny", blocked, savedTokensFromBytes(blocked));
9035
9257
  recordStat("session_hint", 0, 0);
9036
9258
  return denyOutput(
9037
- "Lines " + start + ".." + end + " of " + shown + ' was already read this session. Pull just the part you need with `token-goat read "' + shown + '::Symbol"`.'
9259
+ "Lines " + start + ".." + end + " of " + shown + " was already read this session. Pull just the part you need with " + realSymbolReadHint(normalized, shown, { start, end }) + "."
9038
9260
  );
9039
9261
  };
9040
9262
  if (snapDiff.kind === "unchanged") {
@@ -9060,11 +9282,14 @@ function preReadHandlerInner(event) {
9060
9282
  }
9061
9283
  const isPagingTracked = isSourceExt || /\.(md|mdx|markdown|rst|xml|dtsx|ampkg|xaml)$/i.test(basename11);
9062
9284
  if (isPagingTracked && window.isExplicitSlice && prevRanges.length >= 3) {
9063
- recordStat("read_count_deny", rereadCredit, savedTokensFromBytes(rereadCredit));
9064
- recordStat("session_hint", 0, 0);
9065
- return denyOutput(
9066
- "Sequential line-range paging detected on " + shown + " (" + (prevRanges.length + 1) + " slices read). " + surgicalHint(normalized, basename11, lineCountForSurgicalHint(normalized)) + " Inspect structure directly without manual chunk paging."
9067
- );
9285
+ const pagingWindowSpan = window.offset !== void 0 && window.limit !== void 0 ? { start: window.offset, end: window.offset + window.limit - 1 } : void 0;
9286
+ if (pagingWindowSpan === void 0 || !isWindowInsideMuchLargerSymbol(normalized, pagingWindowSpan.start, pagingWindowSpan.end)) {
9287
+ recordStat("read_count_deny", rereadCredit, savedTokensFromBytes(rereadCredit));
9288
+ recordStat("session_hint", 0, 0);
9289
+ return denyOutput(
9290
+ "Sequential line-range paging detected on " + shown + " (" + (prevRanges.length + 1) + " slices read). " + surgicalHint(normalized, basename11, lineCountForSurgicalHint(normalized)) + " Inspect structure directly without manual chunk paging."
9291
+ );
9292
+ }
9068
9293
  }
9069
9294
  }
9070
9295
  if (config2.hints.reread_deny && !protectedRead) {
@@ -9106,7 +9331,7 @@ function preReadHandlerInner(event) {
9106
9331
  }
9107
9332
  const pagingWindow = readRequestedSliceWindow(event);
9108
9333
  const activeRanges = getFileLineRanges(normalized);
9109
- const pagingNote = pagingWindow.isExplicitSlice && activeRanges.length >= 2 ? " Sequential line-range paging detected (" + (activeRanges.length + 1) + " slices read). Prefer `token-goat skeleton " + shown + '` or `token-goat read "' + shown + '::Symbol"`.' : "";
9334
+ const pagingNote = pagingWindow.isExplicitSlice && activeRanges.length >= 2 ? " Sequential line-range paging detected (" + (activeRanges.length + 1) + " slices read). Prefer `token-goat skeleton " + shown + "` or " + realSymbolReadHint(normalized, shown, pagingWindow.offset !== void 0 && pagingWindow.limit !== void 0 ? { start: pagingWindow.offset, end: pagingWindow.offset + pagingWindow.limit - 1 } : void 0) + "." : "";
9110
9335
  if (!isWithinQuietHours(config2.hints.quiet_hours)) {
9111
9336
  recordStat("session_hint", 0, 0);
9112
9337
  }
@@ -9149,7 +9374,7 @@ function preReadHandlerInner(event) {
9149
9374
  "Note: " + shown + " is large (" + kb + "KB). " + hint + contextPressureAdvisorySuffix()
9150
9375
  );
9151
9376
  }
9152
- const fileTypeExt = path19.extname(normalized).slice(1).toLowerCase();
9377
+ const fileTypeExt = path21.extname(normalized).slice(1).toLowerCase();
9153
9378
  const fileStatSize = size ?? statSize(normalized) ?? 0;
9154
9379
  const isKnownFileType = DISPATCHED_FILE_TYPE_EXTS.has(fileTypeExt);
9155
9380
  if (event.toolName !== "Grep" && !isImagePath(normalized) && (isKnownFileType || fileStatSize >= FILE_TYPE_THRESHOLDS.generic)) {
@@ -9230,7 +9455,7 @@ function postReadHandlerInner(event, suppressStructuralHint) {
9230
9455
  if (isTruncatedReadDelivery(event, respText)) {
9231
9456
  markFileTruncated(normalized, !readRequestedSliceWindow(event).isExplicitSlice);
9232
9457
  }
9233
- const postBasename = path19.basename(normalized);
9458
+ const postBasename = path21.basename(normalized);
9234
9459
  const diffSourcesEnabled = loadConfig().hints.serve_diff_on_reread;
9235
9460
  if (/\.(md|mdx|markdown|rst|txt)$/i.test(postBasename) || isSessionArtifactFile(normalized) || diffSourcesEnabled && isDiffableSource(postBasename)) {
9236
9461
  try {
@@ -9346,7 +9571,7 @@ function elideAlreadyServedLines(event, respText) {
9346
9571
  if (ids.length === 0) return null;
9347
9572
  const parsed = parseNumberedReadResult(respText, readStartLine(event));
9348
9573
  if (parsed === null) return null;
9349
- if (redactSecrets(respText).count > 0) return null;
9574
+ if (hasPreciseSecret(respText)) return null;
9350
9575
  const bodies = [];
9351
9576
  for (let i = ids.length - 1; i >= 0; i--) {
9352
9577
  const id = ids[i];
@@ -9386,7 +9611,7 @@ function structuralFoldInputs(event, respText) {
9386
9611
  if (filePath === void 0) return null;
9387
9612
  if (readRequestedSliceWindow(event).isExplicitSlice) return null;
9388
9613
  if (isTruncatedReadDelivery(event, respText)) return null;
9389
- if (redactSecrets(respText).count > 0) return null;
9614
+ if (hasPreciseSecret(respText)) return null;
9390
9615
  const parsed = parseNumberedReadResult(respText, readStartLine(event));
9391
9616
  if (parsed === null) return null;
9392
9617
  const normalized = normalizePath(filePath);
@@ -9423,7 +9648,7 @@ function foldCodeBodies(event, respText) {
9423
9648
  const requestedOffset = sliceWin.offset;
9424
9649
  if (requestedOffset !== void 0 && readStartLine(event) !== requestedOffset) return null;
9425
9650
  if (isTruncatedReadDelivery(event, respText)) return null;
9426
- if (redactSecrets(respText).count > 0) return null;
9651
+ if (hasPreciseSecret(respText)) return null;
9427
9652
  const parsed = parseNumberedReadResult(respText, readStartLine(event));
9428
9653
  if (parsed === null) return null;
9429
9654
  const shown = displaySafePath(toDisplayPath(findProject(getCwd(event) ?? process.cwd())?.root, normalized));
@@ -9460,13 +9685,13 @@ registerHook("post_tool_use", postReadHandler, { toolName: "Read" });
9460
9685
  // src/baseline.ts
9461
9686
  init_define_import_meta_env();
9462
9687
  import * as fs26 from "node:fs";
9463
- import * as path22 from "node:path";
9688
+ import * as path24 from "node:path";
9464
9689
 
9465
9690
  // src/cli_context_stats.ts
9466
9691
  init_define_import_meta_env();
9467
9692
  import * as fs25 from "node:fs";
9468
- import * as os5 from "node:os";
9469
- import * as path21 from "node:path";
9693
+ import * as os6 from "node:os";
9694
+ import * as path23 from "node:path";
9470
9695
 
9471
9696
  // src/confirm_apply.ts
9472
9697
  init_define_import_meta_env();
@@ -9523,7 +9748,7 @@ ${diff}
9523
9748
  // src/memory_prune.ts
9524
9749
  init_define_import_meta_env();
9525
9750
  import * as fs24 from "node:fs";
9526
- import * as path20 from "node:path";
9751
+ import * as path22 from "node:path";
9527
9752
  var ENTRY_RE = /^\s*-\s*\[(?<title>[^\]]+)\]\((?<target>[^)]+?\.md)\)/;
9528
9753
  var URL_SCHEME_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
9529
9754
  function parseIndex(text) {
@@ -9568,7 +9793,7 @@ function pruneIndex(memoryDir, opts) {
9568
9793
  changed: false,
9569
9794
  tokensSaved: 0
9570
9795
  };
9571
- const memoryMd = path20.join(memoryDir, "MEMORY.md");
9796
+ const memoryMd = path22.join(memoryDir, "MEMORY.md");
9572
9797
  let text;
9573
9798
  try {
9574
9799
  text = fs24.readFileSync(memoryMd, "utf-8");
@@ -9582,7 +9807,7 @@ function pruneIndex(memoryDir, opts) {
9582
9807
  const dups = [];
9583
9808
  for (const entry of entries) {
9584
9809
  const isUrl = URL_SCHEME_RE.test(entry.target);
9585
- const targetExists = isUrl ? true : path20.isAbsolute(entry.target) ? fs24.existsSync(entry.target) : fs24.existsSync(path20.join(memoryDir, entry.target));
9810
+ const targetExists = isUrl ? true : path22.isAbsolute(entry.target) ? fs24.existsSync(entry.target) : fs24.existsSync(path22.join(memoryDir, entry.target));
9586
9811
  const foldedTarget = foldPath(entry.target);
9587
9812
  if (!targetExists) {
9588
9813
  dead.push(entry);
@@ -9726,7 +9951,7 @@ async function tryEmbeddingClusters(siblings, snippets, threshold) {
9726
9951
  }
9727
9952
  async function findContentDuplicates(memoryDir, _opts) {
9728
9953
  const threshold = _opts?.threshold ?? 0.92;
9729
- const siblings = fs24.readdirSync(memoryDir).filter((name) => name.toLowerCase().endsWith(".md") && name.toLowerCase() !== "memory.md").map((name) => path20.join(memoryDir, name)).sort();
9954
+ const siblings = fs24.readdirSync(memoryDir).filter((name) => name.toLowerCase().endsWith(".md") && name.toLowerCase() !== "memory.md").map((name) => path22.join(memoryDir, name)).sort();
9730
9955
  if (siblings.length < 2) {
9731
9956
  return [];
9732
9957
  }
@@ -9810,7 +10035,7 @@ function auditClaudeMd(files) {
9810
10035
  const overlaps = [];
9811
10036
  for (const [stripped, filesSet] of lineToFiles) {
9812
10037
  if (filesSet.has(report.path) && filesSet.size > 1) {
9813
- const others = Array.from(filesSet).filter((p) => p !== report.path).map((p) => path20.basename(p));
10038
+ const others = Array.from(filesSet).filter((p) => p !== report.path).map((p) => path22.basename(p));
9814
10039
  if (others.length > 0) {
9815
10040
  if (stripped.length > 60) {
9816
10041
  overlaps.push(
@@ -9836,31 +10061,31 @@ function tok(filePath) {
9836
10061
  return 0;
9837
10062
  }
9838
10063
  }
9839
- function findClaudeMdFiles(projectRoot, homeDir = os5.homedir()) {
10064
+ function findClaudeMdFiles(projectRoot, homeDir = os6.homedir()) {
9840
10065
  const found = [];
9841
10066
  const seen = /* @__PURE__ */ new Set();
9842
- let current = path21.resolve(projectRoot);
10067
+ let current = path23.resolve(projectRoot);
9843
10068
  while (true) {
9844
- const candidate = path21.join(current, "CLAUDE.md");
10069
+ const candidate = path23.join(current, "CLAUDE.md");
9845
10070
  if (!seen.has(candidate) && fs25.existsSync(candidate)) {
9846
10071
  found.push(candidate);
9847
10072
  seen.add(candidate);
9848
10073
  }
9849
- const parent = path21.dirname(current);
10074
+ const parent = path23.dirname(current);
9850
10075
  if (parent === current) break;
9851
10076
  current = parent;
9852
10077
  }
9853
- const globalMd = path21.join(homeDir, ".claude", "CLAUDE.md");
10078
+ const globalMd = path23.join(claudeConfigDir(homeDir), "CLAUDE.md");
9854
10079
  if (!seen.has(globalMd) && fs25.existsSync(globalMd)) {
9855
10080
  found.push(globalMd);
9856
10081
  }
9857
10082
  return found;
9858
10083
  }
9859
- function findMemoryMd(projectRoot, homeDir = os5.homedir(), alternateRoots = []) {
10084
+ function findMemoryMd(projectRoot, homeDir = os6.homedir(), alternateRoots = []) {
9860
10085
  try {
9861
- const projectsDir = path21.join(homeDir, ".claude", "projects");
10086
+ const projectsDir = path23.join(claudeConfigDir(homeDir), "projects");
9862
10087
  if (!fs25.existsSync(projectsDir)) return null;
9863
- const rootStr = path21.resolve(projectRoot);
10088
+ const rootStr = path23.resolve(projectRoot);
9864
10089
  const candidateRoots = [rootStr];
9865
10090
  try {
9866
10091
  const realRoot = fs25.realpathSync.native(rootStr);
@@ -9868,12 +10093,12 @@ function findMemoryMd(projectRoot, homeDir = os5.homedir(), alternateRoots = [])
9868
10093
  } catch {
9869
10094
  }
9870
10095
  for (const alternate of alternateRoots) {
9871
- const resolved = path21.resolve(alternate);
10096
+ const resolved = path23.resolve(alternate);
9872
10097
  if (!candidateRoots.includes(resolved)) candidateRoots.push(resolved);
9873
10098
  }
9874
10099
  for (const root of candidateRoots) {
9875
10100
  const expectedSlug = root.replace(/[^A-Za-z0-9]/g, "-");
9876
- const candidate = path21.join(projectsDir, expectedSlug, "memory", "MEMORY.md");
10101
+ const candidate = path23.join(projectsDir, expectedSlug, "memory", "MEMORY.md");
9877
10102
  if (fs25.existsSync(candidate)) return candidate;
9878
10103
  }
9879
10104
  return null;
@@ -9881,17 +10106,17 @@ function findMemoryMd(projectRoot, homeDir = os5.homedir(), alternateRoots = [])
9881
10106
  return null;
9882
10107
  }
9883
10108
  }
9884
- function buildStats(projectRoot, homeDir = os5.homedir(), alternateRoots = []) {
10109
+ function buildStats(projectRoot, homeDir = os6.homedir(), alternateRoots = []) {
9885
10110
  const claudeMds = findClaudeMdFiles(projectRoot, homeDir);
9886
10111
  const claudeMdRows = [];
9887
10112
  let claudeMdTotal = 0;
9888
10113
  for (const p of claudeMds) {
9889
10114
  const t = tok(p);
9890
10115
  claudeMdTotal += t;
9891
- const parentDir = path21.basename(path21.dirname(p));
10116
+ const parentDir = path23.basename(path23.dirname(p));
9892
10117
  const label = parentDir === ".claude" ? "~/.claude/CLAUDE.md" : (() => {
9893
10118
  try {
9894
- return path21.relative(projectRoot, p);
10119
+ return path23.relative(projectRoot, p);
9895
10120
  } catch {
9896
10121
  return p;
9897
10122
  }
@@ -9955,7 +10180,7 @@ async function runContextStats(opts = {}) {
9955
10180
  process.stdout.write("[--fix] No MEMORY.md found; nothing to prune.\n");
9956
10181
  } else {
9957
10182
  const memPath = result.memory_md_path;
9958
- const pruneResult = pruneIndex(path21.dirname(memPath), { dryRun: true });
10183
+ const pruneResult = pruneIndex(path23.dirname(memPath), { dryRun: true });
9959
10184
  if (!pruneResult.changed || pruneResult.after === void 0) {
9960
10185
  process.stdout.write("[--fix] MEMORY.md already clean; nothing to prune.\n");
9961
10186
  } else {
@@ -10009,6 +10234,10 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
10009
10234
  ".idea",
10010
10235
  ".vscode"
10011
10236
  ]);
10237
+ function isIgnoredIndexPath(p) {
10238
+ const segments = foldPath(p).split(/[/\\]/);
10239
+ return segments.slice(0, -1).some((s) => SKIP_DIRS.has(s));
10240
+ }
10012
10241
  var MAX_FILES_SCANNED = 2e4;
10013
10242
  function walkProject(rootDir, opts = {}) {
10014
10243
  const files = [];
@@ -10028,7 +10257,7 @@ function walkProject(rootDir, opts = {}) {
10028
10257
  continue;
10029
10258
  }
10030
10259
  for (const entry of entries) {
10031
- const full = path22.join(dir, entry.name);
10260
+ const full = path24.join(dir, entry.name);
10032
10261
  if (entry.isDirectory()) {
10033
10262
  if (SKIP_DIRS.has(entry.name) || extraSkipDirs.includes(entry.name)) continue;
10034
10263
  if (entry.name.startsWith(".") && entry.name !== ".") {
@@ -10110,7 +10339,7 @@ function fetchTopSymbols(limit, dbPath, rootDir) {
10110
10339
  }
10111
10340
  }
10112
10341
  function buildProjectMap(rootDir = process.cwd(), opts = {}) {
10113
- const root = path22.resolve(rootDir);
10342
+ const root = path24.resolve(rootDir);
10114
10343
  const config = loadConfig();
10115
10344
  const { files, languages, truncated } = walkProject(root, { excludeTests: config.repomap.exclude_tests });
10116
10345
  const compact = opts.compact === true || files.length > config.repomap.compact_file_threshold;
@@ -10124,7 +10353,7 @@ function buildProjectMap(rootDir = process.cwd(), opts = {}) {
10124
10353
  mtime = 0;
10125
10354
  }
10126
10355
  return { f, mtime };
10127
- }).sort((a, b) => b.mtime - a.mtime).slice(0, compact ? 5 : 15).map((x) => path22.relative(root, x.f));
10356
+ }).sort((a, b) => b.mtime - a.mtime).slice(0, compact ? 5 : 15).map((x) => path24.relative(root, x.f));
10128
10357
  return {
10129
10358
  rootDir: root,
10130
10359
  fileCount: files.length,
@@ -10137,7 +10366,7 @@ function buildProjectMap(rootDir = process.cwd(), opts = {}) {
10137
10366
  }
10138
10367
  function formatProjectMap(map, compact = false) {
10139
10368
  const lines = [];
10140
- const rel = path22.basename(map.rootDir);
10369
+ const rel = path24.basename(map.rootDir);
10141
10370
  lines.push(`# Project map: ${rel}`);
10142
10371
  lines.push(map.fileCountTruncated === true ? `Files: at least ${map.fileCount} (walk stopped at the ${MAX_FILES_SCANNED}-file cap)` : `Files: ${map.fileCount}`);
10143
10372
  const langPairs = Object.entries(map.languages).sort((a, b) => b[1] - a[1]);
@@ -10166,7 +10395,7 @@ function formatProjectMap(map, compact = false) {
10166
10395
  }
10167
10396
  function mapLookupBytesSaved(map, emittedText) {
10168
10397
  const referencedFiles = /* @__PURE__ */ new Set([
10169
- ...map.recentFiles.map((f) => normalizePath(path22.resolve(map.rootDir, f))),
10398
+ ...map.recentFiles.map((f) => normalizePath(path24.resolve(map.rootDir, f))),
10170
10399
  ...map.topSymbols.map((s) => normalizePath(s.filePath))
10171
10400
  ]);
10172
10401
  const listingText = Array.from(referencedFiles).sort().join("\n");
@@ -10181,7 +10410,7 @@ function findMemSuggestionCandidates(projectRoot) {
10181
10410
  const claudeMdFiles = findClaudeMdFiles(projectRoot);
10182
10411
  const candidateFiles = new Set(claudeMdFiles);
10183
10412
  for (const claudeMd of claudeMdFiles) {
10184
- const agentsMd = path22.join(path22.dirname(claudeMd), "AGENTS.md");
10413
+ const agentsMd = path24.join(path24.dirname(claudeMd), "AGENTS.md");
10185
10414
  if (fs26.existsSync(agentsMd)) candidateFiles.add(agentsMd);
10186
10415
  }
10187
10416
  const suggestions = [];
@@ -10217,7 +10446,7 @@ function formatMemSuggestions(projectRoot) {
10217
10446
  if (suggestions.length === 0) return "";
10218
10447
  const lines = ["", "## mem suggestions"];
10219
10448
  for (const s of suggestions) {
10220
- const basename11 = path22.basename(s.path);
10449
+ const basename11 = path24.basename(s.path);
10221
10450
  lines.push(
10222
10451
  "Consider: mem import --from-md " + s.path + " # migrates " + s.count + " preference-shaped lines from " + basename11 + " as pending facts for review"
10223
10452
  );
@@ -10262,15 +10491,15 @@ function scanAndRecord(text) {
10262
10491
  // src/repomap.ts
10263
10492
  init_define_import_meta_env();
10264
10493
  import * as fs27 from "fs";
10265
- import * as path23 from "path";
10494
+ import * as path25 from "path";
10266
10495
  function getTrackedFiles(cwd = process.cwd()) {
10267
10496
  try {
10268
10497
  let dir = cwd;
10269
10498
  let onlyFile = "";
10270
10499
  try {
10271
10500
  if (fs27.statSync(cwd).isFile()) {
10272
- dir = path23.dirname(cwd);
10273
- onlyFile = path23.basename(cwd);
10501
+ dir = path25.dirname(cwd);
10502
+ onlyFile = path25.basename(cwd);
10274
10503
  }
10275
10504
  } catch {
10276
10505
  }
@@ -10279,7 +10508,7 @@ function getTrackedFiles(cwd = process.cwd()) {
10279
10508
  if (result.exitCode !== 0 || !result.stdout) {
10280
10509
  return [];
10281
10510
  }
10282
- return result.stdout.split("\n").filter((line) => line.trim().length > 0).map((rel) => path23.join(dir, rel));
10511
+ return result.stdout.split("\n").filter((line) => line.trim().length > 0).map((rel) => path25.join(dir, rel));
10283
10512
  } catch {
10284
10513
  return [];
10285
10514
  }
@@ -10505,7 +10734,49 @@ function embeddedIpv4Candidates(host) {
10505
10734
  return out;
10506
10735
  }
10507
10736
 
10737
+ // src/line_regions.ts
10738
+ init_define_import_meta_env();
10739
+ function resolveLineRegions(symbols, totalLines, start, end) {
10740
+ if (symbols.length === 0) return [];
10741
+ const firstStart = symbols.reduce((m, s) => Math.min(m, s.lineStart), Number.POSITIVE_INFINITY);
10742
+ const regionFor = (line) => {
10743
+ let best = null;
10744
+ for (const s of symbols) {
10745
+ if (s.lineStart > line || s.lineEnd < line) continue;
10746
+ if (best === null || s.lineEnd - s.lineStart < best.lineEnd - best.lineStart) best = s;
10747
+ }
10748
+ if (best !== null) {
10749
+ return { kind: "symbol", label: `${best.kind} ${best.name}`, start: best.lineStart, end: best.lineEnd };
10750
+ }
10751
+ if (line < firstStart) return { kind: "preamble", label: "file preamble", start: 1, end: firstStart - 1 };
10752
+ let prev = null;
10753
+ let next = null;
10754
+ for (const s of symbols) {
10755
+ if (s.lineEnd < line && (prev === null || s.lineEnd > prev.lineEnd)) prev = s;
10756
+ if (s.lineStart > line && (next === null || s.lineStart < next.lineStart)) next = s;
10757
+ }
10758
+ const prevName = prev === null ? "start of file" : prev.name;
10759
+ return {
10760
+ kind: "gap",
10761
+ label: next === null ? `gap after ${prevName}` : `gap between ${prevName} and ${next.name}`,
10762
+ start: prev === null ? 1 : prev.lineEnd + 1,
10763
+ end: next === null ? totalLines : next.lineStart - 1
10764
+ };
10765
+ };
10766
+ const bySpan = /* @__PURE__ */ new Map();
10767
+ for (let line = start; line <= Math.min(end, totalLines); line++) {
10768
+ const r = regionFor(line);
10769
+ const key = `${r.start}-${r.end}`;
10770
+ const seen = bySpan.get(key);
10771
+ if (seen === void 0) bySpan.set(key, r);
10772
+ else if (!seen.label.split(" / ").includes(r.label)) seen.label = `${seen.label} / ${r.label}`;
10773
+ }
10774
+ const picked = [...bySpan.values()];
10775
+ return picked.filter((r) => !picked.some((o) => o !== r && o.start <= r.start && o.end >= r.end)).sort((a, b) => a.start - b.start || a.end - b.end);
10776
+ }
10777
+
10508
10778
  export {
10779
+ claudeConfigDir,
10509
10780
  indexedSourceText,
10510
10781
  isVirtualIndexedPath,
10511
10782
  virtualIndexedScopeNote,
@@ -10547,6 +10818,7 @@ export {
10547
10818
  readServersJson,
10548
10819
  serversOf,
10549
10820
  setTokenGoatServer,
10821
+ isResidueServersJson,
10550
10822
  dropEmptyServers,
10551
10823
  hasManagedServer,
10552
10824
  visualStudioUserMcpPath,
@@ -10588,6 +10860,7 @@ export {
10588
10860
  classifyBashHint,
10589
10861
  classifyEditHint,
10590
10862
  applyHintTracking,
10863
+ logSuppressedDetection,
10591
10864
  isSuppressionCategory,
10592
10865
  meetsSavingsFloor,
10593
10866
  getHintStatsSummary,
@@ -10643,6 +10916,7 @@ export {
10643
10916
  getSessionId,
10644
10917
  setTranscriptPath,
10645
10918
  SESSIONS_SUBDIR,
10919
+ sessionsDir,
10646
10920
  AGENT_SALT_MARKER,
10647
10921
  sessionSidecarPath,
10648
10922
  listSiblingSessionStates,
@@ -10664,6 +10938,7 @@ export {
10664
10938
  loadSessionCache,
10665
10939
  computeAdaptiveBudget,
10666
10940
  buildManifestWithCount,
10941
+ LARGE_SYMBOL_LINE_THRESHOLD,
10667
10942
  buildLineDiff,
10668
10943
  SKILLS_OUTPUT_SUBDIR,
10669
10944
  skillOutputsDir,
@@ -10687,7 +10962,6 @@ export {
10687
10962
  visionTokensSavedByText,
10688
10963
  canShrinkFormat,
10689
10964
  formatShrinkSummary,
10690
- isImagePath,
10691
10965
  ImageDecodeError,
10692
10966
  probeImageMeta,
10693
10967
  imageQualifiesForShrink,
@@ -10702,6 +10976,7 @@ export {
10702
10976
  searchEvidenceSemantically,
10703
10977
  buildDeltaCapsule,
10704
10978
  foldDetail,
10979
+ pruneUnembeddableChunks,
10705
10980
  removeFileFromIndex,
10706
10981
  pruneDeletedFiles,
10707
10982
  pruneBlockedRoot,
@@ -10710,6 +10985,7 @@ export {
10710
10985
  findOrphanedChunkPaths,
10711
10986
  pruneOrphanedChunks,
10712
10987
  recordKnownRootThrottled,
10988
+ findTopIndexedProjects,
10713
10989
  cmdReclaimIndex,
10714
10990
  applyIndexingPriority,
10715
10991
  WORKER_HEARTBEAT_STALE_MS,
@@ -10724,6 +11000,7 @@ export {
10724
11000
  runDetachedWorkerDaemon,
10725
11001
  appendDirtyPath,
10726
11002
  enqueueDirtyPathSafe,
11003
+ enqueueDirtyPathsSafe,
10727
11004
  foldingEnabled,
10728
11005
  foldDelivery,
10729
11006
  OUTLINE_MIN_HEADINGS,
@@ -10731,6 +11008,7 @@ export {
10731
11008
  planMarkdownOutline,
10732
11009
  planSourceSkeleton,
10733
11010
  isStructuralRewriteAccepted,
11011
+ escapeHintName,
10734
11012
  confirmAndApply,
10735
11013
  findContentDuplicates,
10736
11014
  auditClaudeMd,
@@ -10739,6 +11017,7 @@ export {
10739
11017
  buildStats,
10740
11018
  runContextStats,
10741
11019
  SKIP_DIRS,
11020
+ isIgnoredIndexPath,
10742
11021
  MAX_FILES_SCANNED,
10743
11022
  walkProject,
10744
11023
  buildProjectMap,
@@ -10758,5 +11037,6 @@ export {
10758
11037
  parseIpv6Groups,
10759
11038
  isPrivateIpv4Octets,
10760
11039
  isPrivateIpv6Groups,
10761
- metadataEndpointRefusal
11040
+ metadataEndpointRefusal,
11041
+ resolveLineRegions
10762
11042
  };