token-goat 2.6.33 → 2.6.35

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 (25) hide show
  1. package/README.md +4 -0
  2. package/SECURITY.md +11 -10
  3. package/dist/{token-goat-chunk-FN6WLFIX.mjs → token-goat-chunk-D5IZEGN2.mjs} +817 -53
  4. package/dist/{token-goat-hook-chunk-HXHFLKEL.mjs → token-goat-chunk-DQIT5JIF.mjs} +7 -2
  5. package/dist/{token-goat-chunk-72R7M5H7.mjs → token-goat-chunk-DRAIYVUI.mjs} +16 -14
  6. package/dist/{token-goat-chunk-RPOXYJWK.mjs → token-goat-chunk-HW4VUKJ5.mjs} +77 -26
  7. package/dist/{token-goat-chunk-TDYTBCT4.mjs → token-goat-chunk-I2KCEGIP.mjs} +704 -138
  8. package/dist/{token-goat-chunk-WFRR2DBG.mjs → token-goat-chunk-K7W3P2TJ.mjs} +583 -343
  9. package/dist/{token-goat-chunk-OHQ7RBLL.mjs → token-goat-chunk-SIHYQCTM.mjs} +2 -2
  10. package/dist/{token-goat-chunk-77CX3MCN.mjs → token-goat-chunk-UIZVX3HN.mjs} +2 -2
  11. package/dist/{token-goat-chunk-L6FVTDPD.mjs → token-goat-chunk-Y2NH6DGZ.mjs} +564 -31
  12. package/dist/{token-goat-chunk-HPWPUBAK.mjs → token-goat-chunk-YF67EWRH.mjs} +5 -5
  13. package/dist/{token-goat-hook-chunk-E6AIPKNF.mjs → token-goat-hook-chunk-2O7P4Z6Q.mjs} +579 -339
  14. package/dist/{token-goat-hook-chunk-WBBHV6TZ.mjs → token-goat-hook-chunk-3AAH72SO.mjs} +77 -26
  15. package/dist/{token-goat-hook-chunk-QD2DX2U7.mjs → token-goat-hook-chunk-4POU6OHK.mjs} +564 -31
  16. package/dist/{token-goat-hook-chunk-ZT47M3IA.mjs → token-goat-hook-chunk-CRBMPT74.mjs} +2 -2
  17. package/dist/{token-goat-hook-chunk-26DLRODR.mjs → token-goat-hook-chunk-D6UJEFHX.mjs} +2 -2
  18. package/dist/{token-goat-hook-chunk-XBOWDNJB.mjs → token-goat-hook-chunk-J4HKWUWZ.mjs} +5 -5
  19. package/dist/{token-goat-chunk-RQQ4SY2P.mjs → token-goat-hook-chunk-NIPXIAPA.mjs} +7 -2
  20. package/dist/{token-goat-hook-chunk-VDXQXYJK.mjs → token-goat-hook-chunk-NXVT4F3S.mjs} +817 -53
  21. package/dist/{token-goat-hook-chunk-EKGDLI4D.mjs → token-goat-hook-chunk-VU6VGZBM.mjs} +16 -14
  22. package/dist/{token-goat-hook-chunk-UWV4MTYI.mjs → token-goat-hook-chunk-XM4W3WCO.mjs} +704 -138
  23. package/dist/token-goat-hook.mjs +5 -5
  24. package/dist/token-goat.core.mjs +5 -5
  25. package/package.json +11 -10
@@ -5,20 +5,26 @@ import {
5
5
  PER_FILE_COUNTERFACTUAL_CEILING,
6
6
  SOURCE_HINT,
7
7
  SYMBOL_BODY_CHAR_CAP,
8
+ assignBraceBlockSpans,
8
9
  assignFlatEndLines,
9
10
  atomicWriteBytes,
10
11
  atomicWriteText,
11
12
  backupFile,
12
13
  buildLineIndex,
14
+ countContentLines,
13
15
  countNoun,
14
16
  dataDir,
17
+ decodeSource,
15
18
  detectHarness,
16
19
  detectLanguage,
20
+ displaySafePath,
21
+ displaySafeText,
17
22
  ensureDirSync,
18
23
  escapeRegExp,
19
24
  extractEnv,
20
25
  extractErrorMessage,
21
26
  extractIni,
27
+ fileIsAbsent,
22
28
  findHtmlHeadingMatches,
23
29
  findMatchingBraceEndLine,
24
30
  findProject,
@@ -82,7 +88,7 @@ import {
82
88
  withFileLock,
83
89
  writeIfDifferent,
84
90
  writeJsonSettings
85
- } from "./token-goat-chunk-WFRR2DBG.mjs";
91
+ } from "./token-goat-chunk-K7W3P2TJ.mjs";
86
92
  import {
87
93
  registerReset
88
94
  } from "./token-goat-chunk-AO2QD2AG.mjs";
@@ -154,7 +160,12 @@ function decodeZipEntry(entries, entryPath) {
154
160
  async function parseOoxmlPart(xmlText2) {
155
161
  const fxp = await loadXmlParser();
156
162
  if (!fxp) throw new Error("fast-xml-parser is not installed; run `npm install fast-xml-parser` to enable this command");
157
- const parser = new fxp.XMLParser({ ignoreAttributes: false, preserveOrder: false, trimValues: false });
163
+ const parser = new fxp.XMLParser({
164
+ ignoreAttributes: false,
165
+ preserveOrder: false,
166
+ trimValues: false,
167
+ parseTagValue: false
168
+ });
158
169
  return parser.parse(xmlText2);
159
170
  }
160
171
  function pushTextValue(runs, val) {
@@ -514,7 +525,7 @@ var ResizeableBuffer = class {
514
525
  }
515
526
  toString(encoding) {
516
527
  if (encoding) {
517
- return this.buf.slice(0, this.length).toString(encoding);
528
+ return this.buf.toString(encoding, 0, this.length);
518
529
  } else {
519
530
  return Uint8Array.prototype.slice.call(this.buf.slice(0, this.length));
520
531
  }
@@ -1766,14 +1777,19 @@ var transform = function(original_options = {}) {
1766
1777
  const obj = {};
1767
1778
  for (let i = 0, l = record.length; i < l; i++) {
1768
1779
  if (columns[i] === void 0 || columns[i].disabled) continue;
1769
- if (group_columns_by_name === true && obj[columns[i].name] !== void 0) {
1780
+ if (group_columns_by_name === true && Object.hasOwn(obj, columns[i].name)) {
1770
1781
  if (Array.isArray(obj[columns[i].name])) {
1771
1782
  obj[columns[i].name] = obj[columns[i].name].concat(record[i]);
1772
1783
  } else {
1773
1784
  obj[columns[i].name] = [obj[columns[i].name], record[i]];
1774
1785
  }
1775
1786
  } else {
1776
- obj[columns[i].name] = record[i];
1787
+ Object.defineProperty(obj, columns[i].name, {
1788
+ value: record[i],
1789
+ enumerable: true,
1790
+ writable: true,
1791
+ configurable: true
1792
+ });
1777
1793
  }
1778
1794
  }
1779
1795
  if (raw === true || info2 === true) {
@@ -2268,35 +2284,264 @@ function profileCsv(content, opts = {}) {
2268
2284
  }
2269
2285
  function formatCsvProfile(profiles) {
2270
2286
  return profiles.map((p) => {
2271
- const lines2 = [`${p.name} (${p.inferredType})`, ` nulls: ${p.nullCount} distinct: ${p.distinctCount}`];
2272
- if (p.min !== void 0) lines2.push(` range: ${p.min} .. ${p.max}`);
2273
- if (p.topValues !== void 0) lines2.push(` values: ${p.topValues.map((t) => `${t.value} (${t.count})`).join(", ")}`);
2287
+ const lines2 = [
2288
+ `${displaySafeText(p.name)} (${p.inferredType})`,
2289
+ ` nulls: ${p.nullCount} distinct: ${p.distinctCount}`
2290
+ ];
2291
+ if (p.min !== void 0) lines2.push(` range: ${displaySafeText(p.min)} .. ${displaySafeText(p.max ?? "")}`);
2292
+ if (p.topValues !== void 0) lines2.push(` values: ${p.topValues.map((t) => `${displaySafeText(t.value)} (${t.count})`).join(", ")}`);
2274
2293
  return lines2.join("\n");
2275
2294
  }).join("\n\n");
2276
2295
  }
2277
2296
 
2278
- // src/xlsx_extract.ts
2279
- var loadExcelJs = createLazyModuleLoader(async () => {
2280
- const mod = await import("exceljs");
2281
- return mod.default ?? mod;
2282
- }, "xlsx reading disabled (exceljs package unavailable)");
2283
- async function requireExcelJs() {
2284
- const mod = await loadExcelJs();
2285
- if (!mod) throw new Error("exceljs is not installed; run `npm install exceljs` to enable this command");
2286
- return mod;
2297
+ // src/xlsx_reader.ts
2298
+ var EMPTY_CELL = Object.freeze({ value: null, text: "" });
2299
+ function asArray(val) {
2300
+ if (val === void 0 || val === null) return [];
2301
+ if (Array.isArray(val)) return val.filter((v) => v !== null && typeof v === "object");
2302
+ if (typeof val === "object") return [val];
2303
+ return [];
2304
+ }
2305
+ function textOf(node) {
2306
+ if (node === void 0 || node === null) return "";
2307
+ if (typeof node === "string") return node;
2308
+ if (typeof node === "number" || typeof node === "boolean") return String(node);
2309
+ if (Array.isArray(node)) return node.map(textOf).join("");
2310
+ const t = node["#text"];
2311
+ return t === void 0 ? "" : String(t);
2312
+ }
2313
+ function attr(node, name) {
2314
+ if (node === void 0) return void 0;
2315
+ const v = node[`@_${name}`];
2316
+ return v === void 0 || v === null ? void 0 : String(v);
2317
+ }
2318
+ function isTruthyAttr(v) {
2319
+ return v === "1" || v === "true";
2320
+ }
2321
+ function refToColumn(ref2) {
2322
+ let n = 0;
2323
+ for (const ch of ref2) {
2324
+ const code = ch.toUpperCase().charCodeAt(0);
2325
+ if (code < 65 || code > 90) break;
2326
+ n = n * 26 + (code - 64);
2327
+ }
2328
+ return n;
2287
2329
  }
2288
- async function loadWorkbook(filePath) {
2289
- const ExcelJS = await requireExcelJs();
2290
- const wb = new ExcelJS.Workbook();
2291
- try {
2292
- await wb.xlsx.readFile(filePath);
2293
- } catch (err) {
2294
- const msg = extractErrorMessage(err);
2295
- if (msg.startsWith("File not found:")) throw err;
2296
- throw new Error(`not a valid .xlsx file: ${filePath}`, { cause: err });
2330
+ function refToRow(ref2) {
2331
+ const m = /(\d+)\s*$/.exec(ref2);
2332
+ return m ? parseInt(m[1], 10) : 0;
2333
+ }
2334
+ var BUILTIN_DATE_FORMAT_IDS = /* @__PURE__ */ new Set([14, 15, 16, 17, 18, 19, 20, 21, 22, 45, 46, 47]);
2335
+ function formatCodeIsDate(formatCode) {
2336
+ const stripped = formatCode.replace(/"[^"]*"/g, "").replace(/\[[^\]]*\]/g, "").replace(/\\./g, "");
2337
+ return /[ymdhs]/i.test(stripped);
2338
+ }
2339
+ function serialToDate(serial, date1904) {
2340
+ if (date1904) return new Date(Date.UTC(1904, 0, 1) + Math.round(serial * 864e5));
2341
+ const adjusted = serial >= 61 ? serial - 1 : serial;
2342
+ return new Date(Date.UTC(1899, 11, 31) + Math.round(adjusted * 864e5));
2343
+ }
2344
+ function numberToText(n) {
2345
+ return String(n);
2346
+ }
2347
+ function parseStyles(xml, parsed) {
2348
+ if (xml === null || parsed === null || typeof parsed !== "object") return { dateStyles: [] };
2349
+ const sheet = parsed["styleSheet"];
2350
+ if (sheet === void 0 || sheet === null || typeof sheet !== "object") return { dateStyles: [] };
2351
+ const root = sheet;
2352
+ const customDateFormats = /* @__PURE__ */ new Set();
2353
+ for (const fmt of asArray(root["numFmts"]?.["numFmt"])) {
2354
+ const id = Number(attr(fmt, "numFmtId"));
2355
+ const code = attr(fmt, "formatCode");
2356
+ if (Number.isFinite(id) && code !== void 0 && formatCodeIsDate(code)) customDateFormats.add(id);
2357
+ }
2358
+ const dateStyles = [];
2359
+ for (const xf of asArray(root["cellXfs"]?.["xf"])) {
2360
+ const id = Number(attr(xf, "numFmtId") ?? "0");
2361
+ dateStyles.push(Number.isFinite(id) && (BUILTIN_DATE_FORMAT_IDS.has(id) || customDateFormats.has(id)));
2362
+ }
2363
+ return { dateStyles };
2364
+ }
2365
+ function parseSharedStrings(parsed) {
2366
+ if (parsed === null || typeof parsed !== "object") return [];
2367
+ const sst = parsed["sst"];
2368
+ if (sst === void 0 || sst === null || typeof sst !== "object") return [];
2369
+ return asArray(sst["si"]).map((si) => {
2370
+ if (si["t"] !== void 0) return textOf(si["t"]);
2371
+ const runs = asArray(si["r"]);
2372
+ if (runs.length > 0) return runs.map((r) => textOf(r["t"])).join("");
2373
+ return "";
2374
+ });
2375
+ }
2376
+ function parseWorkbookRels(parsed) {
2377
+ const out = /* @__PURE__ */ new Map();
2378
+ if (parsed === null || typeof parsed !== "object") return out;
2379
+ const rels = parsed["Relationships"];
2380
+ if (rels === void 0 || rels === null || typeof rels !== "object") return out;
2381
+ for (const rel of asArray(rels["Relationship"])) {
2382
+ const id = attr(rel, "Id");
2383
+ const target = attr(rel, "Target");
2384
+ if (id === void 0 || target === void 0) continue;
2385
+ const normalized = target.startsWith("/") ? target.slice(1) : `xl/${target.replace(/^\.\//, "")}`;
2386
+ out.set(id, normalized);
2297
2387
  }
2298
- return wb;
2388
+ return out;
2299
2389
  }
2390
+ function buildCell(c, shared, styles, date1904) {
2391
+ const type = attr(c, "t") ?? "n";
2392
+ const styleIdx = Number(attr(c, "s") ?? "-1");
2393
+ const isDateStyle = Number.isInteger(styleIdx) && styleIdx >= 0 && styles.dateStyles[styleIdx] === true;
2394
+ const hasV = c["v"] !== void 0;
2395
+ const hasIs = c["is"] !== void 0;
2396
+ const fNode = c["f"];
2397
+ const formula = fNode === void 0 ? "" : textOf(fNode);
2398
+ if (!hasV && !hasIs && formula === "") return null;
2399
+ let raw;
2400
+ let text;
2401
+ if (type === "s") {
2402
+ const idx = Number(textOf(c["v"]));
2403
+ const s = Number.isInteger(idx) ? shared[idx] ?? "" : "";
2404
+ raw = s;
2405
+ text = s;
2406
+ } else if (type === "inlineStr") {
2407
+ const isNode = c["is"];
2408
+ const s = isNode !== void 0 && isNode !== null && typeof isNode === "object" ? (() => {
2409
+ const node = isNode;
2410
+ if (node["t"] !== void 0) return textOf(node["t"]);
2411
+ return asArray(node["r"]).map((r) => textOf(r["t"])).join("");
2412
+ })() : textOf(isNode);
2413
+ raw = s;
2414
+ text = s;
2415
+ } else if (type === "str") {
2416
+ const s = textOf(c["v"]);
2417
+ raw = s;
2418
+ text = s;
2419
+ } else if (type === "b") {
2420
+ const b = textOf(c["v"]).trim() === "1";
2421
+ raw = b;
2422
+ text = b ? "TRUE" : "FALSE";
2423
+ } else if (type === "e") {
2424
+ const e = textOf(c["v"]);
2425
+ raw = { error: e };
2426
+ text = e;
2427
+ } else {
2428
+ const n = Number(textOf(c["v"]));
2429
+ if (!Number.isFinite(n)) {
2430
+ raw = null;
2431
+ text = "";
2432
+ } else if (isDateStyle) {
2433
+ const d = serialToDate(n, date1904);
2434
+ raw = d;
2435
+ text = d.toISOString();
2436
+ } else {
2437
+ raw = n;
2438
+ text = numberToText(n);
2439
+ }
2440
+ }
2441
+ if (formula !== "") return { value: { formula, result: raw }, text, formula };
2442
+ return { value: raw, text };
2443
+ }
2444
+ function parseSheet(parsed, shared, styles, date1904) {
2445
+ const cells = /* @__PURE__ */ new Map();
2446
+ let rowCount = 0;
2447
+ let columnCount = 0;
2448
+ let populatedRows = 0;
2449
+ if (parsed === null || typeof parsed !== "object") return { cells, rowCount, columnCount, populatedRows };
2450
+ const ws = parsed["worksheet"];
2451
+ if (ws === void 0 || ws === null || typeof ws !== "object") return { cells, rowCount, columnCount, populatedRows };
2452
+ const sheetData = ws["sheetData"];
2453
+ if (sheetData === void 0 || sheetData === null || typeof sheetData !== "object") {
2454
+ return { cells, rowCount, columnCount, populatedRows };
2455
+ }
2456
+ let fallbackRow = 0;
2457
+ for (const row of asArray(sheetData["row"])) {
2458
+ const declaredRow = Number(attr(row, "r"));
2459
+ const rowIdx = Number.isInteger(declaredRow) && declaredRow > 0 ? declaredRow : fallbackRow + 1;
2460
+ fallbackRow = rowIdx;
2461
+ let fallbackCol = 0;
2462
+ const rowCells = /* @__PURE__ */ new Map();
2463
+ for (const c of asArray(row["c"])) {
2464
+ const ref2 = attr(c, "r");
2465
+ const declaredCol = ref2 === void 0 ? 0 : refToColumn(ref2);
2466
+ const colIdx = declaredCol > 0 ? declaredCol : fallbackCol + 1;
2467
+ fallbackCol = colIdx;
2468
+ const cell = buildCell(c, shared, styles, date1904);
2469
+ if (cell === null) continue;
2470
+ rowCells.set(colIdx, cell);
2471
+ if (colIdx > columnCount) columnCount = colIdx;
2472
+ }
2473
+ if (rowCells.size === 0) continue;
2474
+ cells.set(rowIdx, rowCells);
2475
+ populatedRows++;
2476
+ if (rowIdx > rowCount) rowCount = rowIdx;
2477
+ }
2478
+ return { cells, rowCount, columnCount, populatedRows };
2479
+ }
2480
+ function makeWorksheet(name, data) {
2481
+ function getRow(r) {
2482
+ const rowCells = data.cells.get(r);
2483
+ return {
2484
+ get values() {
2485
+ const out = [];
2486
+ if (rowCells !== void 0) for (const [col, cell] of rowCells) out[col] = cell.value;
2487
+ return out;
2488
+ },
2489
+ eachCell(opts, cb) {
2490
+ if (opts.includeEmpty) {
2491
+ const maxCol = data.columnCount;
2492
+ for (let c = 1; c <= maxCol; c++) cb(rowCells?.get(c) ?? EMPTY_CELL, c);
2493
+ return;
2494
+ }
2495
+ if (rowCells === void 0) return;
2496
+ for (const col of [...rowCells.keys()].sort((a, b) => a - b)) cb(rowCells.get(col), col);
2497
+ }
2498
+ };
2499
+ }
2500
+ return {
2501
+ name,
2502
+ rowCount: data.rowCount,
2503
+ columnCount: data.columnCount,
2504
+ actualRowCount: data.populatedRows,
2505
+ getRow,
2506
+ getCell(addr) {
2507
+ return data.cells.get(refToRow(addr))?.get(refToColumn(addr)) ?? EMPTY_CELL;
2508
+ }
2509
+ };
2510
+ }
2511
+ async function readXlsxWorkbook(filePath) {
2512
+ const entries = await readOoxmlZip(filePath, ".xlsx");
2513
+ const workbookXml = decodeZipEntry(entries, "xl/workbook.xml");
2514
+ if (workbookXml === null) throw new Error(`not a valid .xlsx file: ${filePath}`);
2515
+ const workbookRoot = await parseOoxmlPart(workbookXml);
2516
+ const wbNode = workbookRoot?.["workbook"];
2517
+ if (wbNode === void 0 || wbNode === null || typeof wbNode !== "object") {
2518
+ throw new Error(`not a valid .xlsx file: ${filePath}`);
2519
+ }
2520
+ const wb = wbNode;
2521
+ const date1904 = isTruthyAttr(attr(wb["workbookPr"], "date1904"));
2522
+ const relsXml = decodeZipEntry(entries, "xl/_rels/workbook.xml.rels");
2523
+ const rels = parseWorkbookRels(relsXml === null ? null : await parseOoxmlPart(relsXml));
2524
+ const sharedXml = decodeZipEntry(entries, "xl/sharedStrings.xml");
2525
+ const shared = sharedXml === null ? [] : parseSharedStrings(await parseOoxmlPart(sharedXml));
2526
+ const stylesXml = decodeZipEntry(entries, "xl/styles.xml");
2527
+ const styles = parseStyles(stylesXml, stylesXml === null ? null : await parseOoxmlPart(stylesXml));
2528
+ const worksheets = [];
2529
+ for (const sheet of asArray(wb["sheets"]?.["sheet"])) {
2530
+ const name = attr(sheet, "name") ?? "";
2531
+ const rid = attr(sheet, "r:id") ?? attr(sheet, "relationshipId");
2532
+ const partPath = rid === void 0 ? void 0 : rels.get(rid);
2533
+ const sheetXml = partPath === void 0 ? null : decodeZipEntry(entries, partPath);
2534
+ const data = sheetXml === null ? { cells: /* @__PURE__ */ new Map(), rowCount: 0, columnCount: 0, populatedRows: 0 } : parseSheet(await parseOoxmlPart(sheetXml), shared, styles, date1904);
2535
+ worksheets.push(makeWorksheet(name, data));
2536
+ }
2537
+ return {
2538
+ worksheets,
2539
+ getWorksheet: (name) => worksheets.find((ws) => ws.name === name)
2540
+ };
2541
+ }
2542
+
2543
+ // src/xlsx_extract.ts
2544
+ var loadWorkbook = readXlsxWorkbook;
2300
2545
  function requireSheet(wb, sheetName) {
2301
2546
  const ws = wb.getWorksheet(sheetName);
2302
2547
  if (ws === void 0) {
@@ -3035,10 +3280,11 @@ function logHintEmission(category, sessionId, correlator, compensateSelfResolve
3035
3280
  try {
3036
3281
  const db = getDb(globalDbPath());
3037
3282
  const resolved = correlator === null ? 1 : 0;
3283
+ const actedOn = resolved === 1 && isSuppressionCategory(category) ? 1 : 0;
3038
3284
  const window = ACTED_ON_WINDOW + (compensateSelfResolve ? 1 : 0);
3039
3285
  db.prepare(
3040
3286
  `INSERT INTO hint_emissions (category, session_id, harness, correlator, emitted_at, resolved, acted_on, calls_remaining, bytes_emitted)
3041
- VALUES (@category, @sessionId, @harness, @correlator, @emittedAt, @resolved, 0, @callsRemaining, @bytesEmitted)`
3287
+ VALUES (@category, @sessionId, @harness, @correlator, @emittedAt, @resolved, @actedOn, @callsRemaining, @bytesEmitted)`
3042
3288
  ).run({
3043
3289
  category,
3044
3290
  sessionId,
@@ -3046,13 +3292,14 @@ function logHintEmission(category, sessionId, correlator, compensateSelfResolve
3046
3292
  correlator,
3047
3293
  emittedAt: Date.now(),
3048
3294
  resolved,
3295
+ actedOn,
3049
3296
  callsRemaining: correlator === null ? 0 : window,
3050
3297
  bytesEmitted
3051
3298
  });
3052
3299
  } catch {
3053
3300
  }
3054
3301
  }
3055
- var TOKEN_GOAT_INVOCATION_RE = /(?:^|[\s;&|])token-goat(?=[\s]|$)/;
3302
+ var TOKEN_GOAT_INVOCATION_RE = /(?:^|[\s;&|])(?:token-goat|tg)(?=[\s]|$)/;
3056
3303
  function commandMentionsCorrelator(command, correlator) {
3057
3304
  let idx = command.indexOf(correlator);
3058
3305
  while (idx !== -1) {
@@ -3065,6 +3312,30 @@ function commandMentionsCorrelator(command, correlator) {
3065
3312
  }
3066
3313
  return false;
3067
3314
  }
3315
+ var SUPPRESSION_HINT_CATEGORIES = /* @__PURE__ */ new Set([
3316
+ "read_reread_dedup",
3317
+ "edit_reread_suggest"
3318
+ ]);
3319
+ function isSuppressionCategory(category) {
3320
+ return SUPPRESSION_HINT_CATEGORIES.has(category);
3321
+ }
3322
+ var EVENT_PATH_KEYS = ["file_path", "filePath", "notebook_path", "path"];
3323
+ function eventTargetText(event) {
3324
+ if (event.toolName === "Bash") {
3325
+ const c = event.toolInput["command"];
3326
+ return typeof c === "string" ? c : "";
3327
+ }
3328
+ for (const key of EVENT_PATH_KEYS) {
3329
+ const v = event.toolInput[key];
3330
+ if (typeof v === "string" && v !== "") return v;
3331
+ }
3332
+ return "";
3333
+ }
3334
+ function isDefiance(correlator, target) {
3335
+ if (target === "") return false;
3336
+ if (TOKEN_GOAT_INVOCATION_RE.test(target)) return false;
3337
+ return commandMentionsCorrelator(target, correlator);
3338
+ }
3068
3339
  function isActedOn(category, correlator, command) {
3069
3340
  if (!TOKEN_GOAT_INVOCATION_RE.test(command)) return false;
3070
3341
  if (category === "bash_recall") {
@@ -3076,10 +3347,29 @@ function resolvePendingHintsForEvent(event) {
3076
3347
  try {
3077
3348
  const db = getDb(globalDbPath());
3078
3349
  const command = event.toolName === "Bash" && typeof event.toolInput["command"] === "string" ? event.toolInput["command"] : "";
3350
+ const target = eventTargetText(event);
3079
3351
  const pending = db.prepare(`SELECT id, category, correlator, calls_remaining FROM hint_emissions WHERE session_id = ? AND resolved = 0`).all(event.sessionId);
3080
3352
  for (const row of pending) {
3081
3353
  if (!isHintCategory(row.category) || row.correlator === null) {
3082
- db.prepare(`UPDATE hint_emissions SET resolved = 1 WHERE id = ?`).run(row.id);
3354
+ const unobservable = isHintCategory(row.category) && isSuppressionCategory(row.category) ? 1 : 0;
3355
+ db.prepare(`UPDATE hint_emissions SET acted_on = ?, resolved = 1 WHERE id = ?`).run(unobservable, row.id);
3356
+ continue;
3357
+ }
3358
+ if (isSuppressionCategory(row.category)) {
3359
+ if (isDefiance(row.correlator, target)) {
3360
+ db.prepare(`UPDATE hint_emissions SET acted_on = 0, resolved = 1 WHERE id = ?`).run(row.id);
3361
+ continue;
3362
+ }
3363
+ if (command !== "" && isActedOn(row.category, row.correlator, command)) {
3364
+ db.prepare(`UPDATE hint_emissions SET acted_on = 1, resolved = 1 WHERE id = ?`).run(row.id);
3365
+ continue;
3366
+ }
3367
+ const left = row.calls_remaining - 1;
3368
+ if (left <= 0) {
3369
+ db.prepare(`UPDATE hint_emissions SET acted_on = 1, resolved = 1 WHERE id = ?`).run(row.id);
3370
+ } else {
3371
+ db.prepare(`UPDATE hint_emissions SET calls_remaining = ? WHERE id = ?`).run(left, row.id);
3372
+ }
3083
3373
  continue;
3084
3374
  }
3085
3375
  if (command !== "" && isActedOn(row.category, row.correlator, command)) {
@@ -3137,15 +3427,18 @@ function manualMarks(category) {
3137
3427
  }
3138
3428
  }
3139
3429
  function getHintStatsSummary() {
3430
+ const probeThresholds = loadConfig().hints.backoff_thresholds.filter((t) => t > 0);
3140
3431
  return HINT_CATEGORIES.map((category) => {
3141
3432
  const { emitted, actedOn, bytesEmitted, legacyEmissions } = categoryStats(category);
3142
3433
  const marks = manualMarks(category);
3434
+ const suppressed = shouldSuppress(category, "");
3143
3435
  return {
3144
3436
  category,
3145
3437
  emitted,
3146
3438
  actedOn: actedOn ?? 0,
3147
3439
  efficacyPct: emitted === 0 ? null : Math.round(1e3 * (actedOn ?? 0) / emitted) / 10,
3148
- suppressed: shouldSuppress(category, ""),
3440
+ suppressed,
3441
+ suppressionPermanent: suppressed && probeThresholds.length === 0,
3149
3442
  manualEffective: marks.effective,
3150
3443
  manualIneffective: marks.ineffective,
3151
3444
  bytesEmitted,
@@ -3709,12 +4002,12 @@ function buildGuidanceBody(fallbackToolClause, opts = {}) {
3709
4002
  '- searching for a *concept* rather than a literal string \u2192 `semantic "description"`',
3710
4003
  "- re-reading output you already captured \u2192 `bash-output`/`web-output`/`mcp-output` by ID",
3711
4004
  "- a directory listing or recursive wildcard walk to orient in an unfamiliar repo \u2192 `map --compact`",
3712
- "- pulling one value or subtree out of a JSON/YAML file (manifest, lockfile, spec, config) \u2192 `json-query file 'a.b.c'` / `yaml-query file 'a.b.c'`",
4005
+ "- pulling one value or subtree out of a JSON/YAML/XML file (manifest, lockfile, spec, config) \u2192 `json-query file 'a.b.c'` / `yaml-query file 'a.b.c'` / `xml-query file 'a.b.c'`",
3713
4006
  "- opening an image to check its dimensions, format, or size \u2192 `image-meta file`",
3714
4007
  "- opening a screenshot, diagram, or scan to read the text in it \u2192 `image-text file`",
3715
4008
  "- opening a PDF or Office document \u2192 inspect its format first, then read a narrow slice: PDF `pdf-meta`/`pdf-outline` then `pdf-extract`; Word `docx-outline` then `docx-text`; PowerPoint `pptx-outline` then `pptx-slide`/`pptx-notes`; Excel `xlsx-sheets` then `xlsx-head`/`xlsx-range`/`xlsx-query`",
3716
4009
  "",
3717
- '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`, `json-outline file`/`yaml-outline`, `bash-output`/`web-output`/`mcp-output`, ' + (gdrive ? "`gdrive-sections <file-id>`, " : "") + "`image-meta file`/`image-text file`, `pdf-meta`/`pdf-outline`/`pdf-extract`, `docx-outline`/`docx-text`, `pptx-outline`/`pptx-slide`/`pptx-notes`/`pptx-text`, `xlsx-sheets`/`xlsx-head`/`xlsx-range`/`xlsx-query`.",
4010
+ '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-extract`, `docx-outline`/`docx-text`, `pptx-outline`/`pptx-slide`/`pptx-notes`/`pptx-text`, `xlsx-sheets`/`xlsx-head`/`xlsx-range`/`xlsx-query`.",
3718
4011
  "",
3719
4012
  "Sub-agent briefs must carry this gate verbatim: a sub-agent inherits none of this context and its reads spend the same token budget.",
3720
4013
  "",
@@ -6666,7 +6959,7 @@ async function ocrImage(input) {
6666
6959
  const entryPath = resolveTesseractEntry();
6667
6960
  if (entryPath === null) return null;
6668
6961
  if (ocrBlockedOffline()) return null;
6669
- return new Promise((resolve9) => {
6962
+ return new Promise((resolve10) => {
6670
6963
  let settled = false;
6671
6964
  let child;
6672
6965
  try {
@@ -6675,7 +6968,7 @@ async function ocrImage(input) {
6675
6968
  });
6676
6969
  } catch {
6677
6970
  _ocrUnavailableThisProcess = true;
6678
- resolve9(null);
6971
+ resolve10(null);
6679
6972
  return;
6680
6973
  }
6681
6974
  const chunks = [];
@@ -6688,7 +6981,7 @@ async function ocrImage(input) {
6688
6981
  child.kill();
6689
6982
  } catch {
6690
6983
  }
6691
- resolve9(result);
6984
+ resolve10(result);
6692
6985
  };
6693
6986
  const timer = setTimeout(() => finish(null, true), _ocrTimeoutMs);
6694
6987
  child.stdout?.on("data", (c) => chunks.push(c));
@@ -6813,11 +7106,11 @@ function statInfo(absPath) {
6813
7106
  function imageShrinkCacheDir() {
6814
7107
  return path12.join(tokenGoatHome(), "image_shrink_cache");
6815
7108
  }
6816
- function shrinkCacheKey(originalPath, size, mtimeMs) {
6817
- return createHash2("sha256").update(`${originalPath}:${size}:${mtimeMs}`).digest("hex").slice(0, 16);
7109
+ function shrinkCacheKey(originalPath, size, mtimeMs, quality) {
7110
+ return createHash2("sha256").update(`${originalPath}:${size}:${mtimeMs}:${quality}`).digest("hex").slice(0, 16);
6818
7111
  }
6819
- function findCachedShrink(originalPath, size, mtimeMs) {
6820
- const key = shrinkCacheKey(originalPath, size, mtimeMs);
7112
+ function findCachedShrink(originalPath, size, mtimeMs, quality) {
7113
+ const key = shrinkCacheKey(originalPath, size, mtimeMs, quality);
6821
7114
  const dir = imageShrinkCacheDir();
6822
7115
  const candidates = [
6823
7116
  { ext: ".webp", format: "webp" },
@@ -6829,11 +7122,11 @@ function findCachedShrink(originalPath, size, mtimeMs) {
6829
7122
  }
6830
7123
  return null;
6831
7124
  }
6832
- function writeCachedShrink(originalPath, result, mtimeMs) {
7125
+ function writeCachedShrink(originalPath, result, mtimeMs, quality) {
6833
7126
  try {
6834
7127
  const dir = imageShrinkCacheDir();
6835
7128
  ensureDirSync(dir);
6836
- const key = shrinkCacheKey(originalPath, result.originalBytes, mtimeMs);
7129
+ const key = shrinkCacheKey(originalPath, result.originalBytes, mtimeMs, quality);
6837
7130
  const ext = result.format === "jpeg" ? ".jpg" : ".webp";
6838
7131
  atomicWriteBytes(path12.join(dir, `token-goat-shrink-${key}${ext}`), result.data);
6839
7132
  } catch {
@@ -6887,7 +7180,8 @@ async function preReadImageHandler(event) {
6887
7180
  const stat2 = statInfo(filePath);
6888
7181
  if (stat2 === null) return passOutput();
6889
7182
  const size = stat2.size;
6890
- const cached = findCachedShrink(filePath, stat2.size, stat2.mtimeMs);
7183
+ const quality = loadConfig().image_shrink.jpeg_quality;
7184
+ const cached = findCachedShrink(filePath, stat2.size, stat2.mtimeMs, quality);
6891
7185
  if (cached !== null) {
6892
7186
  let cachedData;
6893
7187
  try {
@@ -6940,12 +7234,12 @@ async function preReadImageHandler(event) {
6940
7234
  return passOutput();
6941
7235
  }
6942
7236
  }
6943
- const result = await shrinkImage(input, { sizeThresholdBytes: 0 });
7237
+ const result = await shrinkImage(input, { quality, sizeThresholdBytes: 0 });
6944
7238
  if (result === null) {
6945
7239
  recordStat("image_shrink_skipped");
6946
7240
  return passOutput();
6947
7241
  }
6948
- writeCachedShrink(filePath, result, stat2.mtimeMs);
7242
+ writeCachedShrink(filePath, result, stat2.mtimeMs, quality);
6949
7243
  return finalizeShrinkResult(result, filePath);
6950
7244
  }
6951
7245
  registerHook("pre_tool_use", preReadImageHandler, { toolName: "Read" });
@@ -6986,7 +7280,7 @@ registerReset(() => {
6986
7280
  PIPELINE_RETRY_DELAY_MS = DEFAULT_PIPELINE_RETRY_DELAY_MS;
6987
7281
  });
6988
7282
  function sleep(ms) {
6989
- return new Promise((resolve9) => setTimeout(resolve9, ms));
7283
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
6990
7284
  }
6991
7285
  async function buildExtractorWithRetry(pipelineFn, modelName) {
6992
7286
  let lastError;
@@ -7096,6 +7390,12 @@ async function embedTexts(texts, modelName = DEFAULT_MODEL) {
7096
7390
  `Dimension mismatch: model returned ${vec.length}-dim vector, expected ${expectedDim}`
7097
7391
  );
7098
7392
  }
7393
+ const badIndex = vec.findIndex((component) => !Number.isFinite(component));
7394
+ if (badIndex !== -1) {
7395
+ throw new Error(
7396
+ `Non-finite embedding component at index ${badIndex}: model returned ${String(vec[badIndex])}`
7397
+ );
7398
+ }
7099
7399
  vecs.push(vec);
7100
7400
  }
7101
7401
  } finally {
@@ -7106,6 +7406,11 @@ function packVec(vec) {
7106
7406
  const view = new Float32Array(vec.length);
7107
7407
  for (const [i, val] of vec.entries()) {
7108
7408
  view[i] = val;
7409
+ if (!Number.isFinite(view[i])) {
7410
+ throw new Error(
7411
+ `Non-finite embedding component at index ${i}: ${String(val)} is not representable as a 32-bit float`
7412
+ );
7413
+ }
7109
7414
  }
7110
7415
  return Buffer.from(view.buffer);
7111
7416
  }
@@ -7306,7 +7611,7 @@ function fetchScopedHits(db, queryVec, k, maxDistance, rootDir) {
7306
7611
  if (!row) {
7307
7612
  continue;
7308
7613
  }
7309
- if (row.distance <= maxDistance) {
7614
+ if (typeof row.distance === "number" && Number.isFinite(row.distance) && row.distance <= maxDistance) {
7310
7615
  const chunk = scopeParams !== void 0 ? chunkStmt.get(row.rowid, ...scopeParams) : chunkStmt.get(row.rowid);
7311
7616
  if (chunk) {
7312
7617
  hits.push({
@@ -7536,6 +7841,7 @@ function trimToBudget(text, budgetTokens, command) {
7536
7841
  return text;
7537
7842
  }
7538
7843
  const lines2 = text.split("\n");
7844
+ if (lines2.length > 1 && lines2[lines2.length - 1] === "") lines2.pop();
7539
7845
  const totalLines = lines2.length;
7540
7846
  const bodyBudget = Math.max(1, budgetTokens - markerMarginTokens);
7541
7847
  const charBudget = bodyBudget * 3;
@@ -8575,9 +8881,9 @@ var MONITORING_COMMAND_PATTERNS = [
8575
8881
  recallHint: "--tail 50"
8576
8882
  },
8577
8883
  // token-goat section/outline/symbol repeat calls — output is stable until the file changes
8578
- { pattern: /^token-goat\s+section\s+["'][^"']+["']/, recallHint: "" },
8579
- { pattern: /^token-goat\s+outline\s+\S+/, recallHint: "" },
8580
- { pattern: /^token-goat\s+symbol\s+\S+/, recallHint: "" }
8884
+ { pattern: /^(?:token-goat|tg)\s+section\s+["'][^"']+["']/, recallHint: "" },
8885
+ { pattern: /^(?:token-goat|tg)\s+outline\s+\S+/, recallHint: "" },
8886
+ { pattern: /^(?:token-goat|tg)\s+symbol\s+\S+/, recallHint: "" }
8581
8887
  ];
8582
8888
  function isPsMultilineSystemQuery(cmd) {
8583
8889
  if (!/^(?:powershell(?:\.exe)?|pwsh(?:\.exe)?)\s+/i.test(cmd)) return false;
@@ -8806,6 +9112,7 @@ ${recall}`
8806
9112
  if (htmlResult.shouldBlock) return htmlResult;
8807
9113
  }
8808
9114
  const lines2 = content.split("\n");
9115
+ if (lines2.length > 1 && lines2[lines2.length - 1] === "") lines2.pop();
8809
9116
  const preview = [
8810
9117
  "--- first 5 lines ---",
8811
9118
  ...lines2.slice(0, 5),
@@ -9421,7 +9728,8 @@ function isSessionArtifactFile(filePath) {
9421
9728
  if (/[/\\]tool-results[/\\][a-z0-9]+\.txt$/i.test(filePath)) return true;
9422
9729
  return false;
9423
9730
  }
9424
- function sessionArtifactRecall(filePath) {
9731
+ function sessionArtifactRecall(rawPath) {
9732
+ const filePath = displaySafePath(rawPath);
9425
9733
  return 'Use `token-goat bash-output --file "' + filePath + '" --tail 50` (or `--grep PATTERN`) to read a slice instead of the full file.';
9426
9734
  }
9427
9735
  function readIntToolInput(event, key) {
@@ -9488,7 +9796,8 @@ function estimateRequestedSlice(event, absPath) {
9488
9796
  if (scan.trustworthy) return { kind: "bytes", bytes: scan.bytes };
9489
9797
  return { kind: "unbounded" };
9490
9798
  }
9491
- function describeSliceAdvice(slice, absPath) {
9799
+ function describeSliceAdvice(slice, rawAbsPath) {
9800
+ const absPath = displaySafePath(rawAbsPath);
9492
9801
  if (slice.kind === "nearSingleLine") {
9493
9802
  return BYTE_RANGE_ADVICE(absPath);
9494
9803
  }
@@ -9647,6 +9956,7 @@ function preReadHandlerInner(event) {
9647
9956
  }
9648
9957
  if (filePath === void 0) return passOutput();
9649
9958
  const normalized = normalizePath(filePath);
9959
+ const shown = displaySafePath(normalized);
9650
9960
  if (isNodeModulesPath(normalized)) {
9651
9961
  return denyOutput(
9652
9962
  "node_modules is typically noise; use npm ls, npm outdated, or npm audit instead for dependency info. To force access, use: token-goat read node_modules/package/file.js::symbol-name or token-goat section node_modules/package/file.js::heading"
@@ -9670,7 +9980,7 @@ function preReadHandlerInner(event) {
9670
9980
  const basename12 = path18.basename(normalized);
9671
9981
  if (isLockFile(basename12)) {
9672
9982
  return denyOutput(
9673
- 'Lock files are rarely useful to read in full. Use `token-goat section "' + normalized + '::<section>"` to extract a specific dependency, or read the relevant manifest instead.'
9983
+ '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.'
9674
9984
  );
9675
9985
  }
9676
9986
  if (normalized.toLowerCase().endsWith(".tsbuildinfo")) {
@@ -9694,13 +10004,13 @@ function preReadHandlerInner(event) {
9694
10004
  if (isTsConfigFile(basename12) && wasFileReadThisSession(normalized)) {
9695
10005
  recordActualRead(event, normalized);
9696
10006
  return quietContextOutput(
9697
- "Already read " + basename12 + '. Use `token-goat section "' + normalized + '::compilerOptions"` to extract compiler options, or `token-goat config-get ' + normalized + " compilerOptions.target` for a single value."
10007
+ "Already read " + basename12 + '. Use `token-goat section "' + shown + '::compilerOptions"` to extract compiler options, or `token-goat config-get ' + shown + " compilerOptions.target` for a single value."
9698
10008
  );
9699
10009
  }
9700
10010
  if (isManifestFile(basename12) && wasFileReadThisSession(normalized)) {
9701
10011
  recordActualRead(event, normalized);
9702
10012
  return quietContextOutput(
9703
- "You've already read " + basename12 + '. Use `token-goat section "' + normalized + '::<field>"` or `token-goat config-get ' + normalized + " <key>` to extract just the value you need."
10013
+ "You've already read " + basename12 + '. Use `token-goat section "' + shown + '::<field>"` or `token-goat config-get ' + shown + " <key>` to extract just the value you need."
9704
10014
  );
9705
10015
  }
9706
10016
  const skillName = detectSkillFile(normalized);
@@ -9729,7 +10039,7 @@ function preReadHandlerInner(event) {
9729
10039
  const savedBytes = Math.max(0, fullSize - compactBody.length);
9730
10040
  recordStat("session_hint", savedBytes, Math.round(savedBytes / 4));
9731
10041
  return denyOutput(
9732
- "Serving the extractive compact sidecar in place of the full file (source unchanged since the last `compact-doc` build):\n\n" + fenceUntrustedFileContent(compactBody) + '\n\nUse `token-goat compact-doc "' + normalized + '" --force` to rebuild it, or `token-goat compact-doc "' + normalized + '" --show` to view it directly. ' + editAnywayHint(normalized)
10042
+ "Serving the extractive compact sidecar in place of the full file (source unchanged since the last `compact-doc` build):\n\n" + fenceUntrustedFileContent(compactBody) + '\n\nUse `token-goat compact-doc "' + shown + '" --force` to rebuild it, or `token-goat compact-doc "' + shown + '" --show` to view it directly. ' + editAnywayHint(normalized)
9733
10043
  );
9734
10044
  }
9735
10045
  }
@@ -9770,7 +10080,7 @@ function preReadHandlerInner(event) {
9770
10080
  const hintText = formatHeadingTree(headings, normalized);
9771
10081
  const headingTextsLower = new Set(headings.map((h) => h.text.trim().toLowerCase()));
9772
10082
  const wellKnown = getWellKnownSections(basename12).filter((s) => headingTextsLower.has(s.trim().toLowerCase()));
9773
- const wellKnownText = wellKnown.length > 0 ? "\nQuick access: " + wellKnown.map((s) => 'token-goat section "' + normalized + "::" + s + '"').join(" | ") : "";
10083
+ const wellKnownText = wellKnown.length > 0 ? "\nQuick access: " + wellKnown.map((s) => 'token-goat section "' + shown + "::" + s + '"').join(" | ") : "";
9774
10084
  const changelogExtra = basename12.toLowerCase() === "changelog.md" ? extractChangelogVersionHint(fileContent, normalized) : "";
9775
10085
  let message = fenceUntrustedFileContent(hintText + changelogExtra) + wellKnownText;
9776
10086
  const slice = estimateRequestedSlice(event, normalized);
@@ -9794,7 +10104,7 @@ function preReadHandlerInner(event) {
9794
10104
  recordStat("session_hint", 0, 0);
9795
10105
  const isMainMemory = basename12.toLowerCase() === "memory.md";
9796
10106
  return denyOutput(
9797
- isMainMemory ? "MEMORY.md was read this session. Its content is in the compact manifest as 'session memory'." : normalized + ' was already read this session. Memory files rarely change mid-session. Use `token-goat section "' + normalized + '::SectionHeading"` to extract one section.'
10107
+ isMainMemory ? "MEMORY.md was read this session. Its content is in the compact manifest as 'session memory'." : shown + ' was already read this session. Memory files rarely change mid-session. Use `token-goat section "' + shown + '::SectionHeading"` to extract one section.'
9798
10108
  );
9799
10109
  }
9800
10110
  if (/^\.improve-state-.*\.json$/.test(basename12) && wasFileReadThisSession(normalized)) {
@@ -9808,7 +10118,7 @@ function preReadHandlerInner(event) {
9808
10118
  recordActualRead(event, normalized);
9809
10119
  recordStat("session_hint", 0, 0);
9810
10120
  return denyOutput(
9811
- normalized + " was already read this session. Environment files rarely change mid-session. Use `token-goat config-get " + normalized + " KEY_NAME` to extract a specific variable."
10121
+ shown + " was already read this session. Environment files rarely change mid-session. Use `token-goat config-get " + shown + " KEY_NAME` to extract a specific variable."
9812
10122
  );
9813
10123
  }
9814
10124
  if (isSessionArtifactFile(normalized)) {
@@ -9839,7 +10149,7 @@ function preReadHandlerInner(event) {
9839
10149
  recordActualRead(event, normalized);
9840
10150
  recordStat("session_hint", 0, 0);
9841
10151
  return denyOutput(
9842
- normalized + " was already read this session. " + sessionArtifactRecall(normalized)
10152
+ shown + " was already read this session. " + sessionArtifactRecall(normalized)
9843
10153
  );
9844
10154
  }
9845
10155
  {
@@ -9898,7 +10208,7 @@ function preReadHandlerInner(event) {
9898
10208
  if (scanCrossSessionManifests(project.root, project.hash, normalized, ttlSecs)) {
9899
10209
  recordActualRead(event, normalized);
9900
10210
  return quietContextOutput(
9901
- "This file may have already been read by another agent/session working in this project recently. If you are a subagent continuing shared work, consider whether you already have this content from context, or use `token-goat read " + normalized + "::SymbolName` for a narrower slice instead of a full re-read."
10211
+ "This file may have already been read by another agent/session working in this project recently. If you are a subagent continuing shared work, consider whether you already have this content from context, or use `token-goat read " + shown + "::SymbolName` for a narrower slice instead of a full re-read."
9902
10212
  );
9903
10213
  }
9904
10214
  }
@@ -9930,7 +10240,7 @@ function preReadHandlerInner(event) {
9930
10240
  if (/\.(md|mdx|markdown|rst)$/i.test(basename12)) {
9931
10241
  recordStat("session_hint", rereadCredit, Math.round(rereadCredit / 4));
9932
10242
  return denyOutput(
9933
- 'Markdown file already read this session. Use `token-goat section "' + normalized + '::HeadingName"` to read one section. ' + editAnywayHint(normalized)
10243
+ 'Markdown file already read this session. Use `token-goat section "' + shown + '::HeadingName"` to read one section. ' + editAnywayHint(normalized)
9934
10244
  );
9935
10245
  }
9936
10246
  const isSourceExt = isSourceExtension(basename12);
@@ -9938,22 +10248,22 @@ function preReadHandlerInner(event) {
9938
10248
  recordStat("read_count_deny", rereadCredit, Math.round(rereadCredit / 4));
9939
10249
  recordStat("session_hint", rereadCredit, Math.round(rereadCredit / 4));
9940
10250
  return denyOutput(
9941
- "Read this file " + reads + ' times already \u2014 use `token-goat read "' + normalized + '::Symbol"`, `token-goat skeleton ' + normalized + "`, or `token-goat outline " + normalized + "` to pull just the part you need. " + editAnywayHint(normalized)
10251
+ "Read this file " + reads + ' times already \u2014 use `token-goat read "' + shown + '::Symbol"`, `token-goat skeleton ' + shown + "`, or `token-goat outline " + shown + "` to pull just the part you need. " + editAnywayHint(normalized)
9942
10252
  );
9943
10253
  }
9944
10254
  }
9945
- const hint = _isDocFile(normalized) ? 'Use `token-goat section "' + normalized + '::SectionName"` to read one section.' : "Use token-goat read/section/symbol to re-read surgically.";
10255
+ const hint = _isDocFile(normalized) ? 'Use `token-goat section "' + shown + '::SectionName"` to read one section.' : "Use token-goat read/section/symbol to re-read surgically.";
9946
10256
  if (config2.hints.reread_deny && !protectedRead && (rereadBytes >= config2.hints.reread_deny_min_bytes || reads >= 2)) {
9947
10257
  recordStat("session_hint", rereadCredit, Math.round(rereadCredit / 4));
9948
10258
  return denyOutput(
9949
- normalized + " was already read this session (" + reads + " " + plural + "). " + hint + " " + editAnywayHint(normalized)
10259
+ shown + " was already read this session (" + reads + " " + plural + "). " + hint + " " + editAnywayHint(normalized)
9950
10260
  );
9951
10261
  }
9952
10262
  if (!isWithinQuietHours(config2.hints.quiet_hours)) {
9953
10263
  recordStat("session_hint", 0, 0);
9954
10264
  }
9955
10265
  return quietContextOutput(
9956
- "Note: " + normalized + " was already read this session (" + reads + " " + plural + "). " + hint
10266
+ "Note: " + shown + " was already read this session (" + reads + " " + plural + "). " + hint
9957
10267
  );
9958
10268
  }
9959
10269
  const size = statSize(normalized);
@@ -9966,12 +10276,12 @@ function preReadHandlerInner(event) {
9966
10276
  }
9967
10277
  const kb = toKB(size);
9968
10278
  const config2 = loadConfig();
9969
- const hint = _isDocFile(normalized) ? 'Use `token-goat section "' + normalized + '::SectionName"` to read one section.' : "Consider token-goat skeleton or token-goat section.";
10279
+ const hint = _isDocFile(normalized) ? 'Use `token-goat section "' + shown + '::SectionName"` to read one section.' : "Consider token-goat skeleton or token-goat section.";
9970
10280
  if (gateSize >= largeFileDenyBytes()) {
9971
10281
  const denyCredit = Math.min(size, PER_FILE_COUNTERFACTUAL_CEILING);
9972
10282
  recordStat("session_hint", denyCredit, Math.round(denyCredit / 4));
9973
10283
  return denyOutput(
9974
- normalized + " is very large (" + kb + "KB). " + hint + " " + describeSliceAdvice(slice, normalized) + " " + editAnywayHint(normalized)
10284
+ shown + " is very large (" + kb + "KB). " + hint + " " + describeSliceAdvice(slice, normalized) + " " + editAnywayHint(normalized)
9975
10285
  );
9976
10286
  }
9977
10287
  recordActualRead(event, normalized);
@@ -9985,7 +10295,7 @@ function preReadHandlerInner(event) {
9985
10295
  recordStat("session_hint", 0, 0);
9986
10296
  }
9987
10297
  return quietContextOutput(
9988
- "Note: " + normalized + " is large (" + kb + "KB). " + hint + contextPressureAdvisorySuffix()
10298
+ "Note: " + shown + " is large (" + kb + "KB). " + hint + contextPressureAdvisorySuffix()
9989
10299
  );
9990
10300
  }
9991
10301
  const fileTypeExt = path18.extname(normalized).slice(1).toLowerCase();
@@ -10033,16 +10343,19 @@ function estimateTruncatedLineCount(normalized) {
10033
10343
  }
10034
10344
  return Infinity;
10035
10345
  }
10036
- function editAnywayHint(normalized) {
10346
+ function editAnywayHint(rawPath) {
10347
+ const normalized = displaySafePath(rawPath);
10037
10348
  return 'To edit it anyway, use `token-goat replace "' + normalized + '" --old-b64 <base64> --new-b64 <base64>` (preferred \u2014 no temp files needed) or `--old-from <oldfile> --new-from <newfile>` for a snippet edit, or `token-goat write-file "' + normalized + "\" --b64 <base64>` (or `--from <newfile>`) to rewrite the whole file \u2014 Read/Edit's own precondition can't be satisfied after this deny.";
10038
10349
  }
10039
- function truncatedReadDenyMessage(normalized) {
10350
+ function truncatedReadDenyMessage(rawPath) {
10351
+ const normalized = displaySafePath(rawPath);
10040
10352
  return 'File was truncated on last read (>33K tokens). Use `token-goat skeleton "' + normalized + '"` for structure or `token-goat read "' + normalized + '::SymbolName"` for one function. ' + editAnywayHint(normalized);
10041
10353
  }
10042
10354
  function postReadHandlerInner(event) {
10043
10355
  const filePath = getFilePath(event);
10044
10356
  if (filePath === void 0) return passOutput();
10045
10357
  const normalized = normalizePath(filePath);
10358
+ const shown = displaySafePath(normalized);
10046
10359
  const respText = extractReadOutput(event.raw);
10047
10360
  if (respText.includes("[Truncated:") || respText.includes("Truncated: PARTIAL view")) {
10048
10361
  markFileTruncated(normalized);
@@ -10063,7 +10376,7 @@ function postReadHandlerInner(event) {
10063
10376
  try {
10064
10377
  const cwd = getCwd(event) ?? process.cwd();
10065
10378
  const project = findProject(cwd) ?? makeProjectAt(cwd);
10066
- const source = fs22.readFileSync(normalized, "utf8");
10379
+ const source = decodeSource(fs22.readFileSync(normalized));
10067
10380
  recordEvidence({ projectRoot: project.root, source: normalized, representation: "file", text: source });
10068
10381
  } catch {
10069
10382
  }
@@ -10099,7 +10412,7 @@ function postReadHandlerInner(event) {
10099
10412
  if (lineCount >= minLines && meetsSavingsFloor(sz)) {
10100
10413
  recordStat("session_hint", 0, 0);
10101
10414
  return quietContextOutput(
10102
- normalized + " is " + lineCount + ' lines. Use `token-goat skeleton "' + normalized + '"` or `token-goat outline "' + normalized + '"` for structural navigation instead of a future full re-read.'
10415
+ shown + " is " + lineCount + ' lines. Use `token-goat skeleton "' + shown + '"` or `token-goat outline "' + shown + '"` for structural navigation instead of a future full re-read.'
10103
10416
  );
10104
10417
  }
10105
10418
  }
@@ -10123,7 +10436,7 @@ import * as readline from "node:readline";
10123
10436
  async function defaultConfirm(question) {
10124
10437
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
10125
10438
  try {
10126
- const answer = await new Promise((resolve9) => rl.question(question, resolve9));
10439
+ const answer = await new Promise((resolve10) => rl.question(question, resolve10));
10127
10440
  return /^y(es)?$/i.test(answer.trim());
10128
10441
  } finally {
10129
10442
  rl.close();
@@ -11412,7 +11725,7 @@ function extractHtml(content, filePath) {
11412
11725
  }
11413
11726
  }
11414
11727
  sections.sort((a, b) => a.line - b.line);
11415
- const totalLines = code.split("\n").length;
11728
+ const totalLines = countContentLines(code);
11416
11729
  assignFlatEndLines(sections, totalLines);
11417
11730
  const seenId = /* @__PURE__ */ new Set();
11418
11731
  const seenClass = /* @__PURE__ */ new Set();
@@ -11515,7 +11828,7 @@ function extractLiquid(content, filePath, relPath) {
11515
11828
  const stem = path22.basename(resolvedRel, path22.extname(resolvedRel));
11516
11829
  symbols.push({ filePath, name: stem, kind: "liquid_section_file", lineStart: 1, lineEnd: 1, body: "", docstring: "", parent: "" });
11517
11830
  }
11518
- const totalLines = content.split("\n").length;
11831
+ const totalLines = countContentLines(content);
11519
11832
  for (const hm of findHtmlHeadingMatches(content)) {
11520
11833
  if (hm.heading) {
11521
11834
  const line = offsetToLine(lineIndex, hm.offset);
@@ -12096,7 +12409,7 @@ function extractLua(content, filePath) {
12096
12409
  symbols.push(makeLineSymbol(filePath, baseName, "function", lineNum, stripped.slice(0, 200)));
12097
12410
  }
12098
12411
  if (!lineClosesItself(stripped)) {
12099
- funcStack.push({ name: baseName, endKeywordNeeded: true, isBlock: false });
12412
+ funcStack.push({ name: baseName, endKeywordNeeded: true, isBlock: false, symbolIndex: symbols.length - 1 });
12100
12413
  }
12101
12414
  continue;
12102
12415
  }
@@ -12110,7 +12423,7 @@ function extractLua(content, filePath) {
12110
12423
  symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200)));
12111
12424
  }
12112
12425
  if (!lineClosesItself(stripped)) {
12113
- funcStack.push({ name: fname, endKeywordNeeded: true, isBlock: false });
12426
+ funcStack.push({ name: fname, endKeywordNeeded: true, isBlock: false, symbolIndex: symbols.length - 1 });
12114
12427
  }
12115
12428
  continue;
12116
12429
  }
@@ -12125,7 +12438,7 @@ function extractLua(content, filePath) {
12125
12438
  symbols.push(makeLineSymbol(filePath, baseName, "function", lineNum, stripped.slice(0, 200)));
12126
12439
  }
12127
12440
  if (!lineClosesItself(stripped)) {
12128
- funcStack.push({ name: baseName, endKeywordNeeded: true, isBlock: false });
12441
+ funcStack.push({ name: baseName, endKeywordNeeded: true, isBlock: false, symbolIndex: symbols.length - 1 });
12129
12442
  }
12130
12443
  continue;
12131
12444
  }
@@ -12146,7 +12459,13 @@ function extractLua(content, filePath) {
12146
12459
  if (/^(?:\bend\b[\s),;}]*)+$/.test(stripped)) {
12147
12460
  const popCount = (stripped.match(/\bend\b/g) ?? []).length;
12148
12461
  for (let k = 0; k < popCount && funcStack.length > 0; k++) {
12149
- funcStack.pop();
12462
+ const popped = funcStack.pop();
12463
+ if (popped !== void 0 && popped.symbolIndex !== void 0) {
12464
+ const open = symbols[popped.symbolIndex];
12465
+ if (open !== void 0 && lineNum > open.lineStart) {
12466
+ symbols[popped.symbolIndex] = { ...open, lineEnd: lineNum, body: lines2.slice(open.lineStart - 1, lineNum).join("\n") };
12467
+ }
12468
+ }
12150
12469
  }
12151
12470
  }
12152
12471
  }
@@ -12188,14 +12507,14 @@ function extractElixir(content, filePath) {
12188
12507
  if (modM) {
12189
12508
  const modName = modM[1] ?? "";
12190
12509
  symbols.push(makeLineSymbol(filePath, modName, "class", lineNum, stripped.slice(0, 200), void 0, lines2, "hash"));
12191
- moduleStack.push({ name: modName, endKeywordNeeded: true, isBlock: false });
12510
+ moduleStack.push({ name: modName, endKeywordNeeded: true, isBlock: false, symbolIndex: symbols.length - 1 });
12192
12511
  continue;
12193
12512
  }
12194
12513
  const protoM = PROTOCOL_RE.exec(stripped);
12195
12514
  if (protoM) {
12196
12515
  const protoName = protoM[1] ?? "";
12197
12516
  symbols.push(makeLineSymbol(filePath, protoName, "protocol", lineNum, stripped.slice(0, 200), void 0, lines2, "hash"));
12198
- moduleStack.push({ name: protoName, endKeywordNeeded: true, isBlock: false });
12517
+ moduleStack.push({ name: protoName, endKeywordNeeded: true, isBlock: false, symbolIndex: symbols.length - 1 });
12199
12518
  continue;
12200
12519
  }
12201
12520
  const fm = FUNC_RE4.exec(stripped);
@@ -12208,7 +12527,7 @@ function extractElixir(content, filePath) {
12208
12527
  symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200), void 0, lines2, "hash"));
12209
12528
  }
12210
12529
  if (opensDoBlock) {
12211
- moduleStack.push({ name: fname, endKeywordNeeded: true, isBlock: false });
12530
+ moduleStack.push({ name: fname, endKeywordNeeded: true, isBlock: false, symbolIndex: symbols.length - 1 });
12212
12531
  }
12213
12532
  continue;
12214
12533
  }
@@ -12222,7 +12541,7 @@ function extractElixir(content, filePath) {
12222
12541
  symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200), void 0, lines2, "hash"));
12223
12542
  }
12224
12543
  if (opensDoBlock) {
12225
- moduleStack.push({ name: fname, endKeywordNeeded: true, isBlock: false });
12544
+ moduleStack.push({ name: fname, endKeywordNeeded: true, isBlock: false, symbolIndex: symbols.length - 1 });
12226
12545
  }
12227
12546
  continue;
12228
12547
  }
@@ -12239,7 +12558,13 @@ function extractElixir(content, filePath) {
12239
12558
  }
12240
12559
  if (stripped === "end" || /^end\s/.test(stripped) || /^end$/.test(stripped)) {
12241
12560
  if (moduleStack.length > 0) {
12242
- moduleStack.pop();
12561
+ const popped = moduleStack.pop();
12562
+ if (popped !== void 0 && popped.symbolIndex !== void 0) {
12563
+ const open = symbols[popped.symbolIndex];
12564
+ if (open !== void 0 && lineNum > open.lineStart) {
12565
+ symbols[popped.symbolIndex] = { ...open, lineEnd: lineNum, body: lines2.slice(open.lineStart - 1, lineNum).join("\n") };
12566
+ }
12567
+ }
12243
12568
  }
12244
12569
  }
12245
12570
  }
@@ -12492,12 +12817,64 @@ function extractZig(content, filePath) {
12492
12817
 
12493
12818
  // src/languages/r.ts
12494
12819
  var FUNC_ASSIGN_RE = /^([A-Za-z_][A-Za-z0-9_.]*)\s*(?:<-|=)\s*(?:function|\\)\s*\(/;
12495
- var SETCLASS_RE = /setClass\s*\(\s*["']([A-Za-z_][A-Za-z0-9_.]*)/;
12496
- var SETMETHOD_RE = /setMethod\s*\(\s*["']([A-Za-z_][A-Za-z0-9_.]*)/;
12820
+ var SETCLASS_RE = /^(?:[A-Za-z_][A-Za-z0-9_.]*\s*(?:<-|=)\s*)?setClass\s*\(\s*["']([A-Za-z_][A-Za-z0-9_.]*)/;
12821
+ var SETMETHOD_RE = /^(?:[A-Za-z_][A-Za-z0-9_.]*\s*(?:<-|=)\s*)?setMethod\s*\(\s*["']([A-Za-z_][A-Za-z0-9_.]*)/;
12822
+ function matchingParenIndex(content, openIndex) {
12823
+ let depth = 0;
12824
+ let quote = null;
12825
+ for (let i = openIndex; i < content.length; i++) {
12826
+ const ch = content[i];
12827
+ if (quote !== null) {
12828
+ if (ch === "\\") {
12829
+ i++;
12830
+ continue;
12831
+ }
12832
+ if (ch === quote) quote = null;
12833
+ continue;
12834
+ }
12835
+ if (ch === "#") {
12836
+ while (i < content.length && content[i] !== "\n") i++;
12837
+ continue;
12838
+ }
12839
+ if (ch === '"' || ch === "'" || ch === "`") {
12840
+ quote = ch;
12841
+ continue;
12842
+ }
12843
+ if (ch === "(") depth++;
12844
+ else if (ch === ")") {
12845
+ depth--;
12846
+ if (depth === 0) return i;
12847
+ }
12848
+ }
12849
+ return null;
12850
+ }
12851
+ function bracedBodyEndLine(content, lineIndex, parenIndex, totalLines, fallback) {
12852
+ const close = matchingParenIndex(content, parenIndex);
12853
+ if (close === null) return fallback;
12854
+ let j = close + 1;
12855
+ while (j < content.length) {
12856
+ const ch = content[j];
12857
+ if (ch === "#") {
12858
+ while (j < content.length && content[j] !== "\n") j++;
12859
+ continue;
12860
+ }
12861
+ if (ch !== " " && ch !== " " && ch !== "\r" && ch !== "\n") break;
12862
+ j++;
12863
+ }
12864
+ if (content[j] !== "{") return fallback;
12865
+ return findMatchingBraceEndLine(content, j, totalLines, lineIndex, "#");
12866
+ }
12867
+ function callEndLine(content, lineIndex, parenIndex, fallback) {
12868
+ const close = matchingParenIndex(content, parenIndex);
12869
+ return close === null ? fallback : offsetToLine(lineIndex, close);
12870
+ }
12497
12871
  function extractR(content, filePath) {
12498
12872
  const symbols = [];
12499
12873
  const imports = [];
12500
12874
  const lines2 = content.split(/\r?\n/);
12875
+ const lineIndex = buildLineIndex(content);
12876
+ const totalLines = countContentLines(content);
12877
+ const spanBody = (startLine, endLine) => lines2.slice(startLine - 1, endLine).join("\n");
12501
12878
  for (let i = 0; i < lines2.length; i++) {
12502
12879
  const rawLine = lines2[i] ?? "";
12503
12880
  const lineNum = i + 1;
@@ -12510,17 +12887,21 @@ function extractR(content, filePath) {
12510
12887
  if (!isIndented) {
12511
12888
  const fm = FUNC_ASSIGN_RE.exec(stripped);
12512
12889
  if (fm) {
12513
- symbols.push(makeLineSymbol(filePath, fm[1] ?? "", "function", lineNum, stripped.slice(0, 200), void 0, lines2, "hash"));
12890
+ const parenIndex = (lineIndex[i] ?? 0) + fm[0].length - 1;
12891
+ const endLine = bracedBodyEndLine(content, lineIndex, parenIndex, totalLines, lineNum);
12892
+ symbols.push(makeSpanSymbol(filePath, fm[1] ?? "", "function", { startLine: lineNum, endLine, body: spanBody(lineNum, endLine) }, void 0, lines2, "hash"));
12514
12893
  continue;
12515
12894
  }
12516
12895
  const cm = SETCLASS_RE.exec(stripped);
12517
12896
  if (cm) {
12518
- symbols.push(makeLineSymbol(filePath, cm[1] ?? "", "class", lineNum, stripped.slice(0, 200), void 0, lines2, "hash"));
12897
+ const endLine = callEndLine(content, lineIndex, (lineIndex[i] ?? 0) + (cm.index + cm[0].indexOf("(")), lineNum);
12898
+ symbols.push(makeSpanSymbol(filePath, cm[1] ?? "", "class", { startLine: lineNum, endLine, body: spanBody(lineNum, endLine) }, void 0, lines2, "hash"));
12519
12899
  continue;
12520
12900
  }
12521
12901
  const mm = SETMETHOD_RE.exec(stripped);
12522
12902
  if (mm) {
12523
- symbols.push(makeLineSymbol(filePath, mm[1] ?? "", "function", lineNum, stripped.slice(0, 200), void 0, lines2, "hash"));
12903
+ const endLine = callEndLine(content, lineIndex, (lineIndex[i] ?? 0) + (mm.index + mm[0].indexOf("(")), lineNum);
12904
+ symbols.push(makeSpanSymbol(filePath, mm[1] ?? "", "function", { startLine: lineNum, endLine, body: spanBody(lineNum, endLine) }, void 0, lines2, "hash"));
12524
12905
  }
12525
12906
  }
12526
12907
  }
@@ -12587,7 +12968,7 @@ function extractGraphql(content, filePath) {
12587
12968
  }
12588
12969
  }
12589
12970
  const stripped = stripHashComments(descriptionsStripped);
12590
- const totalLines = content.split("\n").length;
12971
+ const totalLines = countContentLines(content);
12591
12972
  const lineIndex = buildLineIndex(stripped);
12592
12973
  for (const m of stripped.matchAll(TYPE_RE)) {
12593
12974
  const keyword = m.groups?.["keyword"] ?? "";
@@ -12863,7 +13244,7 @@ function extractSql(content, filePath) {
12863
13244
  const sections = [];
12864
13245
  const seen = /* @__PURE__ */ new Set();
12865
13246
  const emit2 = makeSymbolEmitter(symbols, sections, seen, filePath, MAX_SYMBOLS3, MAX_HEADING_LEN2);
12866
- const totalLines = content.split("\n").length;
13247
+ const totalLines = countContentLines(content);
12867
13248
  const lineIndex = buildLineIndex(content);
12868
13249
  const noStrings = stripSqlStringLiterals(content);
12869
13250
  const singleLineEndLines = /* @__PURE__ */ new Map();
@@ -12910,25 +13291,53 @@ function stripBashComment(line) {
12910
13291
  }
12911
13292
  return line;
12912
13293
  }
12913
- function findHeredocOpeners(line) {
13294
+ function maskArithmeticSpans(line, carryDepth) {
13295
+ const chars = line.split("");
13296
+ let depth = carryDepth;
13297
+ for (let i = 0; i < chars.length; i++) {
13298
+ if (depth > 0) {
13299
+ if (chars[i] === "(") depth++;
13300
+ else if (chars[i] === ")") depth--;
13301
+ chars[i] = " ";
13302
+ continue;
13303
+ }
13304
+ const isDollar = chars[i] === "$" && chars[i + 1] === "(" && chars[i + 2] === "(";
13305
+ const isBare = chars[i] === "(" && chars[i + 1] === "(";
13306
+ if (!isDollar && !isBare) continue;
13307
+ if (isInsideStringLiteral(line, i)) continue;
13308
+ const open = isDollar ? i + 1 : i;
13309
+ for (let j = open; j < chars.length; j++) {
13310
+ if (chars[j] === "(") depth++;
13311
+ else if (chars[j] === ")") depth--;
13312
+ chars[j] = " ";
13313
+ i = j;
13314
+ if (depth === 0) break;
13315
+ }
13316
+ }
13317
+ return { masked: chars.join(""), depth };
13318
+ }
13319
+ function findHeredocOpeners(line, carryDepth) {
12914
13320
  const terminators = [];
13321
+ const { masked, depth } = maskArithmeticSpans(line, carryDepth);
12915
13322
  HEREDOC_RE.lastIndex = 0;
12916
13323
  let m;
12917
- while ((m = HEREDOC_RE.exec(line)) !== null) {
13324
+ while ((m = HEREDOC_RE.exec(masked)) !== null) {
12918
13325
  if (isInsideStringLiteral(line, m.index)) continue;
12919
13326
  const terminator = m[2] ?? "";
12920
13327
  if (terminator) terminators.push(terminator);
12921
13328
  }
12922
- return terminators;
13329
+ return { terminators, depth };
12923
13330
  }
12924
13331
  function extractBash(content, filePath) {
12925
13332
  const symbols = [];
12926
13333
  const lines2 = content.split(/\r?\n/);
12927
13334
  const heredocs = [];
13335
+ let arithmeticDepth = 0;
12928
13336
  let braceDepth = 0;
12929
13337
  let inFunction = false;
12930
13338
  let functionBraceDepth = 0;
12931
13339
  let awaitingFunctionBrace = false;
13340
+ let openFunctionIndex = null;
12932
13341
  for (let i = 0; i < lines2.length; i++) {
12933
13342
  const rawLine = lines2[i] ?? "";
12934
13343
  const lineNum = i + 1;
@@ -12938,7 +13347,9 @@ function extractBash(content, filePath) {
12938
13347
  }
12939
13348
  const noComment = stripBashComment(rawLine);
12940
13349
  const stripped = noComment.trim();
12941
- heredocs.push(...findHeredocOpeners(noComment));
13350
+ const opened = findHeredocOpeners(noComment, arithmeticDepth);
13351
+ arithmeticDepth = opened.depth;
13352
+ heredocs.push(...opened.terminators);
12942
13353
  if (!stripped) continue;
12943
13354
  if (!inFunction && !awaitingFunctionBrace && braceDepth === 0) {
12944
13355
  const kwMatch = FUNC_KEYWORD_RE.exec(stripped);
@@ -12946,7 +13357,9 @@ function extractBash(content, filePath) {
12946
13357
  const funcMatch = kwMatch ?? posixMatch;
12947
13358
  if (funcMatch) {
12948
13359
  const fname = funcMatch[1] ?? "";
13360
+ let pushedIndex = null;
12949
13361
  if (fname && symbols.length < MAX_SYMBOLS4) {
13362
+ pushedIndex = symbols.length;
12950
13363
  symbols.push(makeLineSymbol(filePath, fname, "function", lineNum, stripped.slice(0, 200), void 0, lines2, "hash"));
12951
13364
  }
12952
13365
  if (fname) {
@@ -12958,9 +13371,11 @@ function extractBash(content, filePath) {
12958
13371
  } else if (openCount > closeCount) {
12959
13372
  inFunction = true;
12960
13373
  functionBraceDepth = braceDepth;
13374
+ openFunctionIndex = pushedIndex;
12961
13375
  }
12962
13376
  } else {
12963
13377
  awaitingFunctionBrace = true;
13378
+ openFunctionIndex = pushedIndex;
12964
13379
  }
12965
13380
  }
12966
13381
  } else {
@@ -12981,6 +13396,13 @@ function extractBash(content, filePath) {
12981
13396
  braceDepth += (braceLine.match(/\{/g) ?? []).length - (braceLine.match(/\}/g) ?? []).length;
12982
13397
  if (inFunction && braceDepth <= functionBraceDepth) {
12983
13398
  inFunction = false;
13399
+ if (openFunctionIndex !== null) {
13400
+ const open = symbols[openFunctionIndex];
13401
+ if (open !== void 0 && lineNum > open.lineStart) {
13402
+ symbols[openFunctionIndex] = { ...open, lineEnd: lineNum, body: lines2.slice(open.lineStart - 1, lineNum).join("\n") };
13403
+ }
13404
+ openFunctionIndex = null;
13405
+ }
12984
13406
  }
12985
13407
  }
12986
13408
  return symbols;
@@ -13065,7 +13487,7 @@ function extractMakefile(content, filePath) {
13065
13487
  const seen = /* @__PURE__ */ new Set();
13066
13488
  const emit2 = makeSymbolEmitter(symbols, sections, seen, filePath, MAX_SYMBOLS5, MAX_HEADING_LEN3);
13067
13489
  const stripped = stripComments(content);
13068
- const totalLines = content.split("\n").length;
13490
+ const totalLines = countContentLines(content);
13069
13491
  const lineIndex = buildLineIndex(stripped);
13070
13492
  const { noContinuation: strippedNoContinuation, forTargets: strippedForTargets } = maskContinuationAndDefines(stripped);
13071
13493
  for (const m of strippedForTargets.matchAll(TARGET_RE)) {
@@ -13145,7 +13567,7 @@ function extractProto(content, filePath) {
13145
13567
  const imports = [];
13146
13568
  const emit2 = makeSymbolEmitter(symbols, sections, seen, filePath, MAX_SYMBOLS6, MAX_HEADING_LEN4);
13147
13569
  const stripped = stripComments2(content);
13148
- const totalLines = content.split("\n").length;
13570
+ const totalLines = countContentLines(content);
13149
13571
  const lineIndex = buildLineIndex(stripped);
13150
13572
  const blockEndLines = /* @__PURE__ */ new Map();
13151
13573
  for (const m of stripped.matchAll(IMPORT_RE4)) {
@@ -13278,7 +13700,7 @@ function extractTerraform(content, filePath) {
13278
13700
  const seen = /* @__PURE__ */ new Set();
13279
13701
  const emit2 = makeSymbolEmitter(symbols, sections, seen, filePath, MAX_SYMBOLS7, MAX_HEADING_LEN5);
13280
13702
  const stripped = stripComments3(maskHeredocs(content));
13281
- const totalLines = content.split("\n").length;
13703
+ const totalLines = countContentLines(content);
13282
13704
  const lineIndex = buildLineIndex(stripped);
13283
13705
  const blockEndLines = /* @__PURE__ */ new Map();
13284
13706
  const matches = [
@@ -13565,7 +13987,7 @@ var TRIGGER_RE2 = new RegExp(
13565
13987
  var RETURN_TYPE = "(?:[A-Za-z_][A-Za-z0-9_.<>?,\\[\\] ]*[ \\t]+)";
13566
13988
  var STATEMENT_KEYWORD_GUARD = "(?!(?:return|throw|new|yield|else|do|try|finally|break|continue)\\b)";
13567
13989
  var METHOD_RE3 = new RegExp(
13568
- `^[ \\t]*(?:@${IDENT3}(?:\\([^\\n)]*\\))?[ \\t]+)*(?:(?:${MODIFIER}[ \\t]+)+(${RETURN_TYPE})?|(?:${MODIFIER}[ \\t]+)*${STATEMENT_KEYWORD_GUARD}(${RETURN_TYPE}))(${IDENT3})[ \\t]*\\([^;{}]*\\)[ \\t\\r\\n]*(?:\\{|;)`,
13990
+ `^[ \\t]*(?:@${IDENT3}(?:\\([^\\n)]*\\))?[ \\t]+)*(?=[^\\n]*\\()(?:(?:${MODIFIER}[ \\t]+)+(${RETURN_TYPE})?|(?:${MODIFIER}[ \\t]+)*${STATEMENT_KEYWORD_GUARD}(${RETURN_TYPE}))(${IDENT3})[ \\t]*\\([^;{}]*\\)[ \\t\\r\\n]*(?:\\{|;)`,
13569
13991
  "gm"
13570
13992
  );
13571
13993
  var CONTROL_NAMES = /* @__PURE__ */ new Set([
@@ -14076,8 +14498,8 @@ function extractLwcJavaScript(content, filePath) {
14076
14498
  const sourceLines = lines(content);
14077
14499
  const bundle = bundleName(filePath);
14078
14500
  const symbols = [
14079
- symbol(filePath, bundle, "lwc_bundle", 1, sourceLines.length),
14080
- symbol(filePath, lwcTagAlias(bundle), "lwc_component_alias", 1, sourceLines.length)
14501
+ symbol(filePath, bundle, "lwc_bundle", 1, countContentLines(content)),
14502
+ symbol(filePath, lwcTagAlias(bundle), "lwc_component_alias", 1, countContentLines(content))
14081
14503
  ];
14082
14504
  const refs = [];
14083
14505
  const commentFree = stripJsComments(content);
@@ -14175,10 +14597,9 @@ function attributeRefs(refs, content, filePath, attribute, split = false) {
14175
14597
  function extractSalesforceMarkup(content, filePath) {
14176
14598
  const normalized = filePath.replaceAll("\\", "/");
14177
14599
  const extension = path24.posix.extname(normalized).toLowerCase();
14178
- const sourceLines = lines(content);
14179
14600
  const kind = MARKUP_KIND[extension] ?? "salesforce_markup";
14180
14601
  const symbols = [
14181
- symbol(filePath, markupArtifactName(normalized, extension), kind, 1, sourceLines.length)
14602
+ symbol(filePath, markupArtifactName(normalized, extension), kind, 1, countContentLines(content))
14182
14603
  ];
14183
14604
  const refs = [];
14184
14605
  const isAura = [".cmp", ".app", ".evt", ".intf", ".design", ".auradoc", ".tokens"].includes(extension);
@@ -14330,7 +14751,7 @@ function componentSymbol(filePath, name, kind, totalLines) {
14330
14751
  return { filePath, name, kind, lineStart: 1, lineEnd: totalLines, body: "", docstring: "", parent: "" };
14331
14752
  }
14332
14753
  function extractVue(content, filePath) {
14333
- const totalLines = content.split("\n").length;
14754
+ const totalLines = countContentLines(content);
14334
14755
  const lineIndex = buildLineIndex(content);
14335
14756
  const name = componentName(filePath);
14336
14757
  const symbols = [componentSymbol(filePath, name, "vue_component", totalLines)];
@@ -14345,7 +14766,7 @@ function extractVue(content, filePath) {
14345
14766
  return finalize(symbols, refs);
14346
14767
  }
14347
14768
  function extractSvelte(content, filePath) {
14348
- const totalLines = content.split("\n").length;
14769
+ const totalLines = countContentLines(content);
14349
14770
  const lineIndex = buildLineIndex(content);
14350
14771
  const name = componentName(filePath);
14351
14772
  const symbols = [componentSymbol(filePath, name, "svelte_component", totalLines)];
@@ -14378,7 +14799,7 @@ function detectAstroFrontmatter(content) {
14378
14799
  return null;
14379
14800
  }
14380
14801
  function extractAstro(content, filePath) {
14381
- const totalLines = content.split("\n").length;
14802
+ const totalLines = countContentLines(content);
14382
14803
  const lineIndex = buildLineIndex(content);
14383
14804
  const name = componentName(filePath);
14384
14805
  const symbols = [componentSymbol(filePath, name, "astro_component", totalLines)];
@@ -14593,6 +15014,12 @@ var TSJS_KIND_BY_TYPE = /* @__PURE__ */ new Map([
14593
15014
  ["method_signature", "method"],
14594
15015
  ["property_signature", "var"],
14595
15016
  ["abstract_method_signature", "method"],
15017
+ // `declare function f(): void` parses as a `function_signature` inside an
15018
+ // ambient_declaration -- a distinct node type from `function_declaration`, which always
15019
+ // carries a body. Without an entry here every ambient function in a .d.ts file was
15020
+ // missing from the index entirely, the same container-drop shape as the module entries
15021
+ // below rather than a wrong span.
15022
+ ["function_signature", "function"],
14596
15023
  // `namespace Foo { ... }` (and the legacy `module Foo { ... }` synonym) parses as
14597
15024
  // `internal_module`; `declare module "some-string" { ... }` (an ambient module declaration,
14598
15025
  // common in .d.ts files) parses as `module` -- a distinct node type from either. Neither had a
@@ -14641,15 +15068,41 @@ function nodeName(node) {
14641
15068
  if (named !== null) return named.text;
14642
15069
  return null;
14643
15070
  }
15071
+ var SPEC_DECLARATION_OWNER = /* @__PURE__ */ new Map([
15072
+ ["variable_declarator", /* @__PURE__ */ new Set(["lexical_declaration", "variable_declaration"])],
15073
+ ["var_spec", /* @__PURE__ */ new Set(["var_declaration"])],
15074
+ ["const_spec", /* @__PURE__ */ new Set(["const_declaration"])],
15075
+ ["type_spec", /* @__PURE__ */ new Set(["type_declaration"])]
15076
+ ]);
15077
+ var PREFIX_WRAPPER_TYPES = /* @__PURE__ */ new Set(["export_statement", "ambient_declaration"]);
15078
+ function widenToDeclaration(node) {
15079
+ let widened = node;
15080
+ const owner = SPEC_DECLARATION_OWNER.get(widened.type);
15081
+ if (owner !== void 0) {
15082
+ const decl = widened.parent;
15083
+ if (decl === null || !owner.has(decl.type)) return widened;
15084
+ let specs = 0;
15085
+ for (const c of decl.namedChildren) if (c.type === widened.type) specs++;
15086
+ if (specs !== 1) return widened;
15087
+ if (decl.startPosition.row !== widened.startPosition.row) return widened;
15088
+ widened = decl;
15089
+ }
15090
+ for (; ; ) {
15091
+ const parent = widened.parent;
15092
+ if (parent === null || !PREFIX_WRAPPER_TYPES.has(parent.type)) return widened;
15093
+ widened = parent;
15094
+ }
15095
+ }
14644
15096
  function makeSymbol(filePath, name, kind, node, lines2, style) {
14645
- const lineStart = node.startPosition.row + 1;
15097
+ const ranged = widenToDeclaration(node);
15098
+ const lineStart = ranged.startPosition.row + 1;
14646
15099
  return {
14647
15100
  filePath,
14648
15101
  name,
14649
15102
  kind,
14650
15103
  lineStart,
14651
- lineEnd: node.endPosition.row + 1,
14652
- body: node.text,
15104
+ lineEnd: ranged.endPosition.row + 1,
15105
+ body: ranged.text,
14653
15106
  docstring: lines2 !== void 0 && style !== void 0 ? precedingDocComment(lines2, lineStart, style) : "",
14654
15107
  parent: ""
14655
15108
  };
@@ -14688,13 +15141,23 @@ function extractTsJsSymbols(root, filePath, lines2) {
14688
15141
  out.push(makeSymbol(filePath, name, kind, node, lines2, "c"));
14689
15142
  } else {
14690
15143
  const lineStart = decorators[0].startPosition.row + 1;
15144
+ const decoratedEnd = widenToDeclaration(node).endPosition.row + 1;
14691
15145
  out.push({
14692
15146
  filePath,
14693
15147
  name,
14694
15148
  kind,
14695
15149
  lineStart,
14696
- lineEnd: node.endPosition.row + 1,
14697
- body: [...decorators, node].map((n) => n.text).join("\n"),
15150
+ lineEnd: decoratedEnd,
15151
+ // Read the body off the file rather than gluing the decorator and the node
15152
+ // together with a newline: `@dec export class X {}` has no newline between
15153
+ // them, and the glued form both invents one and drops the `export` that sits
15154
+ // between the two nodes. A decorated declaration yields one symbol, so taking
15155
+ // its whole span cannot fan out.
15156
+ // The trailing replace keeps a convention the rest of the index follows: a
15157
+ // tree-sitter node never carries the indentation of its own first line,
15158
+ // because it starts at the first real character. Reading the span off the
15159
+ // file would otherwise make decorated symbols the one shape that does.
15160
+ body: lines2.slice(lineStart - 1, decoratedEnd).join("\n").replace(/^[ \t]+/, ""),
14698
15161
  docstring: precedingDocComment(lines2, lineStart, "c"),
14699
15162
  parent: ""
14700
15163
  });
@@ -14711,8 +15174,11 @@ function extractTsJsSymbols(root, filePath, lines2) {
14711
15174
  const isFn = value !== null && (value.type === "arrow_function" || value.type === "function_expression" || value.type === "function");
14712
15175
  out.push(makeSymbol(filePath, name.text, isFn ? "function" : "variable", child, lines2, "c"));
14713
15176
  } else {
14714
- for (const bound of collectPatternBindings(name)) {
14715
- out.push(makeSymbol(filePath, bound, "variable", child, lines2, "c"));
15177
+ const bindings = collectPatternBindings(name);
15178
+ const elideBodies = bindings.length > 1 && bindings.length * child.text.length > MAX_SYMBOL_BODY_CHARS;
15179
+ for (const bound of bindings) {
15180
+ const sym = makeSymbol(filePath, bound, "variable", child, lines2, "c");
15181
+ out.push(elideBodies ? { ...sym, body: "" } : sym);
14716
15182
  }
14717
15183
  }
14718
15184
  }
@@ -16097,8 +16563,8 @@ var NO_TREE_SITTER_EXTRACTORS = {
16097
16563
  toml: extractTomlSymbols,
16098
16564
  css: extractCssSymbols,
16099
16565
  dockerfile: extractDockerfileSymbols,
16100
- csharp: (content, filePath) => extractCsharp(content, filePath).symbols,
16101
- php: (content, filePath) => extractPhp(content, filePath).symbols,
16566
+ csharp: (content, filePath) => assignBraceBlockSpans(extractCsharp(content, filePath).symbols, content, "//"),
16567
+ php: (content, filePath) => assignBraceBlockSpans(extractPhp(content, filePath).symbols, content, "//"),
16102
16568
  html: (content, filePath) => {
16103
16569
  const r = extractHtml(content, filePath);
16104
16570
  return [...r.symbols, ...sectionsToHeadingSymbols(r.sections, filePath)];
@@ -16107,13 +16573,13 @@ var NO_TREE_SITTER_EXTRACTORS = {
16107
16573
  const r = extractLiquid(content, filePath);
16108
16574
  return [...r.symbols, ...sectionsToHeadingSymbols(r.sections, filePath)];
16109
16575
  },
16110
- kotlin: (content, filePath) => extractKotlin(content, filePath).symbols,
16111
- swift: (content, filePath) => extractSwift(content, filePath).symbols,
16112
- scala: (content, filePath) => extractScala(content, filePath).symbols,
16576
+ kotlin: (content, filePath) => assignBraceBlockSpans(extractKotlin(content, filePath).symbols, content, "//"),
16577
+ swift: (content, filePath) => assignBraceBlockSpans(extractSwift(content, filePath).symbols, content, "//"),
16578
+ scala: (content, filePath) => assignBraceBlockSpans(extractScala(content, filePath).symbols, content, "//"),
16113
16579
  lua: (content, filePath) => extractLua(content, filePath).symbols,
16114
16580
  elixir: (content, filePath) => extractElixir(content, filePath).symbols,
16115
- dart: (content, filePath) => extractDart(content, filePath).symbols,
16116
- zig: (content, filePath) => extractZig(content, filePath).symbols,
16581
+ dart: (content, filePath) => assignBraceBlockSpans(extractDart(content, filePath).symbols, content, "//"),
16582
+ zig: (content, filePath) => assignBraceBlockSpans(extractZig(content, filePath).symbols, content, "//"),
16117
16583
  r: (content, filePath) => extractR(content, filePath).symbols,
16118
16584
  graphql: (content, filePath) => extractGraphql(content, filePath).symbols,
16119
16585
  sql: extractSql,
@@ -16121,7 +16587,7 @@ var NO_TREE_SITTER_EXTRACTORS = {
16121
16587
  makefile: extractMakefile,
16122
16588
  proto: (content, filePath) => extractProto(content, filePath).symbols,
16123
16589
  terraform: extractTerraform,
16124
- powershell: (content, filePath) => extractPowershell(content, filePath).symbols,
16590
+ powershell: (content, filePath) => assignBraceBlockSpans(extractPowershell(content, filePath).symbols, content, "#"),
16125
16591
  apex: (content, filePath) => extractApex(content, filePath).symbols,
16126
16592
  salesforce_metadata: (content, filePath) => extractSalesforceMetadata(content, filePath).symbols,
16127
16593
  env_file: extractEnv,
@@ -16224,7 +16690,7 @@ function indexFileSync(filePath, dbPath = globalDbPath(), preReadBytes) {
16224
16690
  throw err;
16225
16691
  }
16226
16692
  }
16227
- const content = raw.toString("utf8");
16693
+ const content = decodeSource(raw);
16228
16694
  const { symbols, refs } = parseContent(content, filePath, language);
16229
16695
  writeParseResult(filePath, raw, { symbols, refs, language, duration: 0 }, dbPath);
16230
16696
  }
@@ -16249,6 +16715,35 @@ var UNAVAILABLE_EMBED_SHA_PREFIX = "unavailable:";
16249
16715
  function unavailableEmbedSha(sha) {
16250
16716
  return UNAVAILABLE_EMBED_SHA_PREFIX + sha;
16251
16717
  }
16718
+ function indexedPathSpellingIsStale(storedPath, absPath) {
16719
+ if (!isCaseInsensitiveFs()) return false;
16720
+ const stored = normalizePath(storedPath);
16721
+ const candidate = normalizePath(path26.resolve(absPath));
16722
+ if (foldPath(stored) !== foldPath(candidate)) return false;
16723
+ let real;
16724
+ try {
16725
+ real = normalizePath(fs26.realpathSync.native(absPath));
16726
+ } catch {
16727
+ return false;
16728
+ }
16729
+ if (real === stored) return false;
16730
+ if (foldPath(real) !== foldPath(stored)) return false;
16731
+ const storedSegments = stored.split("/");
16732
+ const realSegments = real.split("/");
16733
+ if (storedSegments.length !== realSegments.length) return real !== stored;
16734
+ const storedBase = storedSegments[storedSegments.length - 1];
16735
+ const realBase = realSegments[realSegments.length - 1];
16736
+ if (storedBase !== realBase) return true;
16737
+ for (let i = storedSegments.length - 2; i >= 0; i--) {
16738
+ if (storedSegments[i] !== realSegments[i]) {
16739
+ if (storedSegments.slice(0, i + 1).join("/") === candidate.split("/").slice(0, i + 1).join("/")) {
16740
+ continue;
16741
+ }
16742
+ return true;
16743
+ }
16744
+ }
16745
+ return false;
16746
+ }
16252
16747
  function isEmbedFresh(storedEmbedSha, sha, embeddingsEnabled, depsAvailable) {
16253
16748
  if (storedEmbedSha === void 0) return false;
16254
16749
  if (!embeddingsEnabled) return storedEmbedSha === disabledEmbedSha(sha);
@@ -16293,7 +16788,7 @@ async function indexFileEmbeddings(filePath, dbPath = globalDbPath(), sha, onErr
16293
16788
  }
16294
16789
  let content;
16295
16790
  try {
16296
- content = await fs26.promises.readFile(filePath, "utf8");
16791
+ content = decodeSource(await fs26.promises.readFile(filePath));
16297
16792
  } catch {
16298
16793
  return;
16299
16794
  }
@@ -16431,6 +16926,41 @@ function pruneSystemTempFiles(dbPath = globalDbPath()) {
16431
16926
  const db = getDb(dbPath);
16432
16927
  return removeFilesBestEffort(db, findSystemTempFiles(dbPath));
16433
16928
  }
16929
+ function findOrphanedChunkPaths(dbPath = globalDbPath()) {
16930
+ return orphanedChunkGroups(getDb(dbPath)).map((g) => g.representative);
16931
+ }
16932
+ function orphanedChunkGroups(db) {
16933
+ const known = new Set(
16934
+ db.prepare("SELECT DISTINCT path FROM files").all().map(
16935
+ (r) => foldPath(normalizePath(r.path))
16936
+ )
16937
+ );
16938
+ const rows = db.prepare("SELECT DISTINCT file_path FROM chunks").all();
16939
+ const byFolded = /* @__PURE__ */ new Map();
16940
+ for (const { file_path: raw } of rows) {
16941
+ const folded = foldPath(normalizePath(raw));
16942
+ if (known.has(folded)) continue;
16943
+ const group = byFolded.get(folded);
16944
+ if (group === void 0) byFolded.set(folded, { representative: raw, spellings: [raw] });
16945
+ else group.spellings.push(raw);
16946
+ }
16947
+ return [...byFolded.values()];
16948
+ }
16949
+ function pruneOrphanedChunks(dbPath = globalDbPath()) {
16950
+ const db = getDb(dbPath);
16951
+ const removed = [];
16952
+ const run = db.transaction(() => {
16953
+ for (const group of orphanedChunkGroups(db)) {
16954
+ try {
16955
+ for (const spelling of group.spellings) deleteFileEmbeddings(db, spelling);
16956
+ removed.push(group.representative);
16957
+ } catch {
16958
+ }
16959
+ }
16960
+ });
16961
+ run.immediate();
16962
+ return removed;
16963
+ }
16434
16964
  function recordKnownRoot(filePath, dbPath = globalDbPath()) {
16435
16965
  const project = findProject(path27.dirname(filePath));
16436
16966
  if (project === null || isTooShallowToPrune(project.root)) return;
@@ -16485,7 +17015,8 @@ function sweepKnownRoots(dbPath = globalDbPath(), opts) {
16485
17015
  prunedRows += removeDeletedFilesBestEffort(db, deletable).length;
16486
17016
  prunedRoots.push(root);
16487
17017
  }
16488
- return { prunedRows, prunedRoots, flaggedRoots };
17018
+ const prunedOrphanChunkPaths = pruneOrphanedChunks(dbPath);
17019
+ return { prunedRows, prunedRoots, flaggedRoots, prunedOrphanChunkPaths };
16489
17020
  }
16490
17021
  var KNOWN_ROOT_RECORD_MIN_INTERVAL_MS = 60 * 60 * 1e3;
16491
17022
  function knownRootRecordMarkerPath(dir, filePath) {
@@ -16626,11 +17157,25 @@ function pidFileIsWithinStartupGrace(dir) {
16626
17157
  return false;
16627
17158
  }
16628
17159
  }
17160
+ var ENCODED_LINE_MARKER = "!";
17161
+ function encodeDirtyQueueLine(absPath) {
17162
+ const needsEncoding = /[\r\n]/.test(absPath) || absPath !== absPath.trim();
17163
+ return needsEncoding ? ENCODED_LINE_MARKER + JSON.stringify(absPath) : absPath;
17164
+ }
17165
+ function decodeDirtyQueueLine(line) {
17166
+ if (!line.startsWith(ENCODED_LINE_MARKER)) return line;
17167
+ try {
17168
+ const decoded = JSON.parse(line.slice(ENCODED_LINE_MARKER.length));
17169
+ return typeof decoded === "string" ? decoded : line;
17170
+ } catch {
17171
+ return line;
17172
+ }
17173
+ }
16629
17174
  function parseDirtyQueueLines(raw) {
16630
17175
  const seen = /* @__PURE__ */ new Set();
16631
17176
  const out = [];
16632
17177
  for (const line of raw.split("\n")) {
16633
- const trimmed = line.trim();
17178
+ const trimmed = decodeDirtyQueueLine(line.trim());
16634
17179
  if (trimmed === "") continue;
16635
17180
  const normalized = normalizePath(trimmed);
16636
17181
  const dedupeKey = foldPath(normalized);
@@ -16689,14 +17234,21 @@ var INDEX_FAILED = /* @__PURE__ */ Symbol("indexFailed");
16689
17234
  var inFlightEmbeddings = /* @__PURE__ */ new Map();
16690
17235
  var activeEmbedSlots = 0;
16691
17236
  var embedSlotWaiters = [];
16692
- function releaseEmbedSlot() {
16693
- activeEmbedSlots -= 1;
16694
- const next = embedSlotWaiters.shift();
16695
- if (next) next();
17237
+ var embedSlotEpoch = 0;
17238
+ function makeReleaseEmbedSlot() {
17239
+ const epoch = embedSlotEpoch;
17240
+ return () => {
17241
+ if (epoch !== embedSlotEpoch) return;
17242
+ activeEmbedSlots -= 1;
17243
+ const next = embedSlotWaiters.shift();
17244
+ if (next) next();
17245
+ };
16696
17246
  }
16697
17247
  registerReset(() => {
17248
+ embedSlotEpoch += 1;
16698
17249
  activeEmbedSlots = 0;
16699
17250
  embedSlotWaiters.length = 0;
17251
+ inFlightEmbeddings.clear();
16700
17252
  });
16701
17253
  function embedFileSerialized(absPath, dbPath, sha) {
16702
17254
  const key = foldPath(absPath);
@@ -16710,7 +17262,8 @@ function embedFileSerialized(absPath, dbPath, sha) {
16710
17262
  const limit = loadConfig().worker.max_pool_workers ?? 4;
16711
17263
  const dispatchEmbed = () => {
16712
17264
  const result = indexFileEmbeddings(absPath, dbPath, sha, onEmbedError);
16713
- result.then(releaseEmbedSlot, releaseEmbedSlot);
17265
+ const release = makeReleaseEmbedSlot();
17266
+ result.then(release, release);
16714
17267
  return result;
16715
17268
  };
16716
17269
  const runEmbed = () => {
@@ -16718,10 +17271,10 @@ function embedFileSerialized(absPath, dbPath, sha) {
16718
17271
  activeEmbedSlots += 1;
16719
17272
  return dispatchEmbed();
16720
17273
  }
16721
- return new Promise((resolve9) => {
17274
+ return new Promise((resolve10) => {
16722
17275
  embedSlotWaiters.push(() => {
16723
17276
  activeEmbedSlots += 1;
16724
- resolve9(dispatchEmbed());
17277
+ resolve10(dispatchEmbed());
16725
17278
  });
16726
17279
  });
16727
17280
  };
@@ -16775,7 +17328,7 @@ function appendToDirtyQueue(dir, absPath) {
16775
17328
  if (existing.length > 0 && !existing.endsWith("\n")) leadingNewline = "\n";
16776
17329
  } catch {
16777
17330
  }
16778
- fs28.appendFileSync(queuePath, `${leadingNewline}${absPath}
17331
+ fs28.appendFileSync(queuePath, `${leadingNewline}${encodeDirtyQueueLine(absPath)}
16779
17332
  `);
16780
17333
  } catch {
16781
17334
  }
@@ -16793,7 +17346,7 @@ function makeIndexer(dbPath) {
16793
17346
  return true;
16794
17347
  }
16795
17348
  const entry = getFileEntry(absPath, dbPath);
16796
- const parseUnchanged = entry?.sha === sha;
17349
+ const parseUnchanged = entry?.sha === sha && !(entry !== null && indexedPathSpellingIsStale(entry.filePath, absPath));
16797
17350
  if (!parseUnchanged) {
16798
17351
  indexFileSync(absPath, dbPath);
16799
17352
  }
@@ -16826,7 +17379,7 @@ function processDirtyBatch(paths, index = makeIndexer(globalDbPath()), remove =
16826
17379
  if (!p) continue;
16827
17380
  writeDrainHeartbeat(dir);
16828
17381
  if (isUnderBlockedRoot(p, blockedRoots)) continue;
16829
- if (!fs28.existsSync(p)) {
17382
+ if (fileIsAbsent(p)) {
16830
17383
  remove(p);
16831
17384
  continue;
16832
17385
  }
@@ -17058,6 +17611,12 @@ function claimWorkerPidFile(dir, pid) {
17058
17611
  if (existingPid !== null && pidAlive(existingPid) && (hasFreshWorkerHeartbeat(dir, existingPid) || pidFileIsWithinStartupGrace(dir))) {
17059
17612
  return false;
17060
17613
  }
17614
+ if (existingPid !== null && pidAlive(existingPid) && existingPid !== pid && existingPid !== process.pid) {
17615
+ try {
17616
+ process.kill(existingPid, "SIGTERM");
17617
+ } catch {
17618
+ }
17619
+ }
17061
17620
  try {
17062
17621
  fs28.rmSync(pidPath, { force: true });
17063
17622
  } catch {
@@ -17121,8 +17680,12 @@ var KNOWN_ROOTS_SWEEP_INTERVAL_MS = 24 * 60 * 60 * 1e3;
17121
17680
  async function runWorkerLoop(dir, pollIntervalMs, shouldStop = () => false) {
17122
17681
  let lastSnapshotCleanupMs = 0;
17123
17682
  let lastKnownRootsSweepMs = 0;
17683
+ let ownedPidFile = false;
17124
17684
  while (!shouldStop()) {
17125
17685
  if (!fs28.existsSync(dir)) break;
17686
+ const pidOwner = readPidFile(dir);
17687
+ if (pidOwner === process.pid) ownedPidFile = true;
17688
+ else if (ownedPidFile && pidOwner !== null) break;
17126
17689
  try {
17127
17690
  drainOnce(dir);
17128
17691
  } catch {
@@ -17157,7 +17720,7 @@ async function runWorkerLoop(dir, pollIntervalMs, shouldStop = () => false) {
17157
17720
  lastKnownRootsSweepMs = Date.now();
17158
17721
  }
17159
17722
  if (shouldStop()) break;
17160
- await new Promise((resolve9) => setTimeout(resolve9, pollIntervalMs));
17723
+ await new Promise((resolve10) => setTimeout(resolve10, pollIntervalMs));
17161
17724
  }
17162
17725
  }
17163
17726
  function runDetachedWorkerDaemon() {
@@ -17196,7 +17759,7 @@ function appendDirtyPath(normalizedPath2) {
17196
17759
  if (existing.length > 0 && !existing.endsWith("\n")) leadingNewline = "\n";
17197
17760
  } catch {
17198
17761
  }
17199
- fs29.appendFileSync(queuePath, `${leadingNewline}${normalizedPath2}
17762
+ fs29.appendFileSync(queuePath, `${leadingNewline}${encodeDirtyQueueLine(normalizedPath2)}
17200
17763
  `);
17201
17764
  }
17202
17765
  function enqueueDirtyPathSafe(filePath, opts) {
@@ -17552,6 +18115,7 @@ export {
17552
18115
  tomlBracketDelta,
17553
18116
  isParseSkipEligible,
17554
18117
  indexFileSync,
18118
+ indexedPathSpellingIsStale,
17555
18119
  isEmbedFresh,
17556
18120
  indexFileEmbeddings,
17557
18121
  removeFileFromIndex,
@@ -17559,6 +18123,8 @@ export {
17559
18123
  pruneBlockedRoot,
17560
18124
  findSystemTempFiles,
17561
18125
  pruneSystemTempFiles,
18126
+ findOrphanedChunkPaths,
18127
+ pruneOrphanedChunks,
17562
18128
  recordKnownRootThrottled,
17563
18129
  WORKER_HEARTBEAT_STALE_MS,
17564
18130
  dirtyQueuePathFor,