token-goat 2.9.15 → 2.9.17

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 (39) hide show
  1. package/README.md +2 -5
  2. package/dist/{token-goat-chunk-IVUQLQWN.mjs → token-goat-chunk-26YUO7MP.mjs} +5 -3
  3. package/dist/{token-goat-chunk-SZWYESBS.mjs → token-goat-chunk-2M33Q3O4.mjs} +172 -67
  4. package/dist/{token-goat-chunk-S4XRY446.mjs → token-goat-chunk-2VASS5VR.mjs} +4 -3
  5. package/dist/{token-goat-chunk-7ZYK25AO.mjs → token-goat-chunk-2XA5ZW4T.mjs} +4 -4
  6. package/dist/{token-goat-chunk-C5JIO6HK.mjs → token-goat-chunk-5WB7GDH4.mjs} +5 -5
  7. package/dist/{token-goat-chunk-MZDIJJ3R.mjs → token-goat-chunk-66224KCZ.mjs} +40 -3
  8. package/dist/{token-goat-chunk-NDRP4KJQ.mjs → token-goat-chunk-73GXYYZR.mjs} +248 -50
  9. package/dist/{token-goat-chunk-ATIFTMRC.mjs → token-goat-chunk-7EGKXBWT.mjs} +7 -7
  10. package/dist/{token-goat-chunk-GMQQA7E4.mjs → token-goat-chunk-7VQMOBPQ.mjs} +2453 -722
  11. package/dist/token-goat-chunk-BGHSSJUA.mjs +102 -0
  12. package/dist/{token-goat-chunk-2X2EBBC6.mjs → token-goat-chunk-BK3RPPTK.mjs} +2 -2
  13. package/dist/{token-goat-chunk-YQ7WI2CO.mjs → token-goat-chunk-C26ZBGBC.mjs} +393 -3957
  14. package/dist/{token-goat-chunk-SFAS46RE.mjs → token-goat-chunk-CICJFVIH.mjs} +2 -2
  15. package/dist/{token-goat-chunk-6DLVZDB6.mjs → token-goat-chunk-CKIJWTRQ.mjs} +13 -12
  16. package/dist/{token-goat-chunk-5V7DAC7V.mjs → token-goat-chunk-CPHVXKNU.mjs} +10 -3
  17. package/dist/{token-goat-chunk-LT7JRU6K.mjs → token-goat-chunk-EKGVCAES.mjs} +4 -4
  18. package/dist/{token-goat-chunk-Z6UXPYJA.mjs → token-goat-chunk-F2ARFHLJ.mjs} +1 -1
  19. package/dist/{token-goat-chunk-NDPO7GAH.mjs → token-goat-chunk-G75WIVFM.mjs} +21 -20
  20. package/dist/{token-goat-chunk-YZX7EFG4.mjs → token-goat-chunk-GLOM5W5C.mjs} +1 -1
  21. package/dist/{token-goat-chunk-GIIHUSZX.mjs → token-goat-chunk-GPCCYQSS.mjs} +41 -11
  22. package/dist/{token-goat-chunk-OUGNPMDA.mjs → token-goat-chunk-HRUGS6CO.mjs} +30 -14
  23. package/dist/{token-goat-chunk-LCZBPOIN.mjs → token-goat-chunk-IDHWKNKZ.mjs} +143 -51
  24. package/dist/{token-goat-chunk-YKG35VHC.mjs → token-goat-chunk-KZPO4DWC.mjs} +1 -1
  25. package/dist/{token-goat-chunk-NEI4NC54.mjs → token-goat-chunk-LKSXAMJB.mjs} +7 -2
  26. package/dist/{token-goat-chunk-K7F2BFIK.mjs → token-goat-chunk-MWSVUKVV.mjs} +1 -1
  27. package/dist/{token-goat-chunk-BL5LNGBG.mjs → token-goat-chunk-PKNOHVWP.mjs} +2966 -3869
  28. package/dist/{token-goat-chunk-A4JYKD5H.mjs → token-goat-chunk-RB24DBBS.mjs} +2 -2
  29. package/dist/{token-goat-chunk-3NSDDTGL.mjs → token-goat-chunk-RFFUCQK7.mjs} +5 -5
  30. package/dist/token-goat-chunk-RRNZMM3A.mjs +3364 -0
  31. package/dist/{token-goat-chunk-XEPXYDPI.mjs → token-goat-chunk-SWRKVA3J.mjs} +11 -2
  32. package/dist/{token-goat-chunk-NU7TLMQK.mjs → token-goat-chunk-THBLW56G.mjs} +10 -7
  33. package/dist/{token-goat-chunk-PM76YS22.mjs → token-goat-chunk-UBZCON6N.mjs} +1 -1
  34. package/dist/{token-goat-chunk-ASYEPR3S.mjs → token-goat-chunk-W2GUUQAQ.mjs} +7 -3
  35. package/dist/{token-goat-chunk-DQ4J5AFF.mjs → token-goat-chunk-YFGEUIEO.mjs} +69 -18
  36. package/dist/token-goat-hook.mjs +13 -12
  37. package/dist/token-goat.core.mjs +21 -20
  38. package/docs/cli.md +1 -0
  39. package/package.json +1 -1
@@ -4,11 +4,7 @@ import {
4
4
  pathEqClause,
5
5
  projectScopeClause,
6
6
  querySymbols
7
- } from "./token-goat-chunk-2X2EBBC6.mjs";
8
- import {
9
- DocumentRefusedError,
10
- MAX_DOCUMENT_WORK_MILLIS
11
- } from "./token-goat-chunk-Y4AFKTHK.mjs";
7
+ } from "./token-goat-chunk-BK3RPPTK.mjs";
12
8
  import {
13
9
  TREE_SITTER_LANGUAGES,
14
10
  _detectOpenQuote,
@@ -25,11 +21,11 @@ import {
25
21
  stripJsComments,
26
22
  stripStringLiterals,
27
23
  stripXmlComments
28
- } from "./token-goat-chunk-K7F2BFIK.mjs";
24
+ } from "./token-goat-chunk-MWSVUKVV.mjs";
29
25
  import {
30
26
  fingerprintContent,
31
27
  loadConfig
32
- } from "./token-goat-chunk-S4XRY446.mjs";
28
+ } from "./token-goat-chunk-2VASS5VR.mjs";
33
29
  import {
34
30
  registerReset
35
31
  } from "./token-goat-chunk-EEIDFMEM.mjs";
@@ -43,11 +39,15 @@ import {
43
39
  foldPath,
44
40
  globalDbPath,
45
41
  isCaseInsensitiveFs
46
- } from "./token-goat-chunk-PM76YS22.mjs";
42
+ } from "./token-goat-chunk-UBZCON6N.mjs";
47
43
  import {
48
44
  displaySafeText,
49
45
  normalizePath
50
46
  } from "./token-goat-chunk-ERTXEKB6.mjs";
47
+ import {
48
+ DocumentRefusedError,
49
+ MAX_DOCUMENT_WORK_MILLIS
50
+ } from "./token-goat-chunk-Y4AFKTHK.mjs";
51
51
  import {
52
52
  init_define_import_meta_env
53
53
  } from "./token-goat-chunk-A37V4PBF.mjs";
@@ -69,16 +69,16 @@ function isTransientDocumentRefusal(err) {
69
69
  async function extractEmbeddableDocumentText(filePath) {
70
70
  switch (path.extname(filePath).toLowerCase()) {
71
71
  case ".pdf": {
72
- const { extractPdfText, readPdfFileWithinBounds } = await import("./token-goat-chunk-Z6UXPYJA.mjs");
72
+ const { extractPdfText, readPdfFileWithinBounds } = await import("./token-goat-chunk-F2ARFHLJ.mjs");
73
73
  const { text } = await extractPdfText(await readPdfFileWithinBounds(filePath));
74
74
  return text;
75
75
  }
76
76
  case ".docx":
77
- return await (await import("./token-goat-chunk-LT7JRU6K.mjs")).docxText(filePath);
77
+ return await (await import("./token-goat-chunk-EKGVCAES.mjs")).docxText(filePath);
78
78
  case ".pptx":
79
- return await (await import("./token-goat-chunk-7ZYK25AO.mjs")).pptxAllSlidesText(filePath, true);
79
+ return await (await import("./token-goat-chunk-2XA5ZW4T.mjs")).pptxAllSlidesText(filePath, true);
80
80
  case ".xlsx":
81
- return await (await import("./token-goat-chunk-3NSDDTGL.mjs")).allSheetsHeadText(filePath, XLSX_SHEET_ROW_CAP);
81
+ return await (await import("./token-goat-chunk-RFFUCQK7.mjs")).allSheetsHeadText(filePath, XLSX_SHEET_ROW_CAP);
82
82
  default:
83
83
  return null;
84
84
  }
@@ -297,6 +297,12 @@ var BertWordPiece = class _BertWordPiece {
297
297
  ids.push(this.sepId);
298
298
  return ids.length > maxLength ? ids.slice(0, maxLength) : ids;
299
299
  }
300
+ /** How many wordpieces `text` becomes, markers excluded and nothing cut: what {@link encode} would read before its limit, for a caller sizing text to fit under it. Words never span whitespace, so the count of a newline-joined text is the sum of its lines' counts. */
301
+ countTokens(text) {
302
+ const ids = [];
303
+ for (const word of preTokenize(normalize(text))) this.wordToIds(word, ids);
304
+ return ids.length;
305
+ }
300
306
  };
301
307
 
302
308
  // src/embed_model.ts
@@ -558,6 +564,10 @@ var EmbeddingModel = class _EmbeddingModel {
558
564
  });
559
565
  return new _EmbeddingModel(tokenizer, session, ort.Tensor);
560
566
  }
567
+ /** Wordpieces `text` becomes under this model's own tokenizer, markers excluded. */
568
+ countTokens(text) {
569
+ return this.tokenizer.countTokens(text);
570
+ }
561
571
  /** Embed one text. Sequences are run singly, so there is no padding and no mask to get wrong. */
562
572
  async embed(text) {
563
573
  const ids = this.tokenizer.encode(text);
@@ -687,14 +697,14 @@ init_define_import_meta_env();
687
697
 
688
698
  // src/embed_fingerprint.ts
689
699
  init_define_import_meta_env();
690
- var EMBED_FINGERPRINT = "b1fd4a80b27f2ca1";
700
+ var EMBED_FINGERPRINT = "b7b2ff71de288d13";
691
701
 
692
702
  // src/embeddings.ts
693
703
  var QUERY_INSTRUCTION_PREFIX = "Represent this sentence for searching relevant passages: ";
694
704
  var _extractorCache = /* @__PURE__ */ new Map();
695
705
  var inHousePipelineFn = async (_task, modelName) => {
696
706
  const model = await EmbeddingModel.load(modelName);
697
- return async (text) => ({ data: await model.embed(text) });
707
+ return Object.assign(async (text) => ({ data: await model.embed(text) }), { countTokens: (text) => model.countTokens(text) });
698
708
  };
699
709
  var _pipelineFnOverride = null;
700
710
  registerReset(() => {
@@ -724,6 +734,7 @@ async function buildExtractorWithRetry(pipelineFn, modelName) {
724
734
  }
725
735
  var MIN_CHUNK_CHARS = 50;
726
736
  var MAX_CHUNK_CHARS = 8e3;
737
+ var MAX_CHUNK_TOKENS = MAX_SEQUENCE_TOKENS - 2;
727
738
  var DEFAULT_DISTANCE_THRESHOLD = 1.2;
728
739
  var _GENERATED_PATH_SEGMENTS = /* @__PURE__ */ new Set([
729
740
  "node_modules",
@@ -772,15 +783,7 @@ function isAvailable() {
772
783
  function embeddingBackendLoadError() {
773
784
  return runtimeLoadError();
774
785
  }
775
- async function embedTexts(texts, modelName = DEFAULT_MODEL) {
776
- if (!isAvailable()) {
777
- throw new Error(
778
- `Embedding backend not available: ${runtimeLoadError()?.message ?? "unknown error"}`
779
- );
780
- }
781
- if (texts.length === 0) {
782
- return [];
783
- }
786
+ function getExtractor(modelName) {
784
787
  let extractorPromise = _extractorCache.get(modelName);
785
788
  if (!extractorPromise) {
786
789
  const pipelineFn = _pipelineFnOverride ?? inHousePipelineFn;
@@ -790,7 +793,18 @@ async function embedTexts(texts, modelName = DEFAULT_MODEL) {
790
793
  if (_extractorCache.get(modelName) === extractorPromise) _extractorCache.delete(modelName);
791
794
  });
792
795
  }
793
- const extractor = await extractorPromise;
796
+ return extractorPromise;
797
+ }
798
+ async function embedTexts(texts, modelName = DEFAULT_MODEL) {
799
+ if (!isAvailable()) {
800
+ throw new Error(
801
+ `Embedding backend not available: ${runtimeLoadError()?.message ?? "unknown error"}`
802
+ );
803
+ }
804
+ if (texts.length === 0) {
805
+ return [];
806
+ }
807
+ const extractor = await getExtractor(modelName);
794
808
  const vecs = [];
795
809
  const expectedDim = DEFAULT_DIM;
796
810
  try {
@@ -840,17 +854,29 @@ function packVec(vec) {
840
854
  }
841
855
  return Buffer.from(view.buffer);
842
856
  }
843
- function splitRangeIntoChunks(filePath, lines2, rangeStart, rangeEnd, chunkSize, overlap, kind) {
857
+ function splitRangeIntoChunks(filePath, lines2, rangeStart, rangeEnd, chunkSize, overlap, kind, countTokens) {
844
858
  const chunks = [];
859
+ const lineTokenCache = /* @__PURE__ */ new Map();
860
+ const tokensOf = (lineNo) => {
861
+ if (countTokens === void 0) return 0;
862
+ let n = lineTokenCache.get(lineNo);
863
+ if (n === void 0) {
864
+ n = countTokens(lines2[lineNo - 1] ?? "");
865
+ lineTokenCache.set(lineNo, n);
866
+ }
867
+ return n;
868
+ };
845
869
  let currentChunk = "";
870
+ let currentTokens = 0;
846
871
  let startLine = rangeStart;
847
872
  let currentLine = rangeStart;
848
873
  for (let lineNo = rangeStart; lineNo <= rangeEnd; lineNo++) {
849
874
  const line = lines2[lineNo - 1] ?? "";
850
875
  const lineWithNewline = line + "\n";
851
- if (currentChunk.length + lineWithNewline.length > chunkSize && currentChunk.length > 0) {
876
+ const lineTokens = tokensOf(lineNo);
877
+ if ((currentChunk.length + lineWithNewline.length > chunkSize || currentTokens + lineTokens > MAX_CHUNK_TOKENS) && currentChunk.length > 0) {
852
878
  const trimmedLength = currentChunk.trim().length;
853
- const currentChunkTooSmall = trimmedLength < MIN_CHUNK_CHARS;
879
+ const currentChunkTooSmall = trimmedLength === 0 || trimmedLength < MIN_CHUNK_CHARS && countTokens === void 0;
854
880
  if (!currentChunkTooSmall) {
855
881
  chunks.push({
856
882
  filePath,
@@ -861,11 +887,15 @@ function splitRangeIntoChunks(filePath, lines2, rangeStart, rangeEnd, chunkSize,
861
887
  });
862
888
  }
863
889
  let overlapChars = 0;
890
+ let overlapTokens = 0;
864
891
  let computedOverlapStart = currentLine;
865
892
  while (computedOverlapStart > rangeStart) {
866
893
  const candidateChars = (lines2[computedOverlapStart - 2] ?? "").length + 1;
867
894
  if (overlapChars + candidateChars > overlap) break;
895
+ const candidateTokens = tokensOf(computedOverlapStart - 1);
896
+ if (overlapTokens + candidateTokens + lineTokens > MAX_CHUNK_TOKENS) break;
868
897
  overlapChars += candidateChars;
898
+ overlapTokens += candidateTokens;
869
899
  computedOverlapStart--;
870
900
  }
871
901
  const droppedChunkHadContent = currentChunkTooSmall && trimmedLength > 0;
@@ -873,10 +903,24 @@ function splitRangeIntoChunks(filePath, lines2, rangeStart, rangeEnd, chunkSize,
873
903
  if (overlapStart !== startLine) {
874
904
  const overlapText = lines2.slice(overlapStart - 1, currentLine - 1).join("\n");
875
905
  currentChunk = overlapText + "\n";
906
+ currentTokens = overlapTokens;
876
907
  startLine = overlapStart;
877
908
  }
878
909
  }
910
+ if (countTokens !== void 0 && (lineTokens > MAX_CHUNK_TOKENS || lineWithNewline.length > chunkSize)) {
911
+ const pieces = splitToFit(line, (piece) => piece.length + 1 <= chunkSize && countTokens(piece) <= MAX_CHUNK_TOKENS);
912
+ const last = pieces.pop() ?? "";
913
+ for (const piece of pieces) {
914
+ if (piece.trim().length > 0) chunks.push({ filePath, startLine: lineNo, endLine: lineNo, text: piece.trim(), kind });
915
+ }
916
+ currentChunk = last + "\n";
917
+ currentTokens = countTokens(last);
918
+ startLine = lineNo;
919
+ currentLine++;
920
+ continue;
921
+ }
879
922
  currentChunk += lineWithNewline;
923
+ currentTokens += lineTokens;
880
924
  currentLine++;
881
925
  }
882
926
  if (currentChunk.trim().length >= MIN_CHUNK_CHARS) {
@@ -889,17 +933,42 @@ function splitRangeIntoChunks(filePath, lines2, rangeStart, rangeEnd, chunkSize,
889
933
  });
890
934
  } else if (currentChunk.length > 0 && chunks.length > 0) {
891
935
  const last = chunks[chunks.length - 1];
892
- last.endLine = rangeEnd;
893
- last.text = lines2.slice(last.startLine - 1, rangeEnd).join("\n").trim();
936
+ let mergedTokens = 0;
937
+ for (let lineNo = last.startLine; lineNo <= rangeEnd; lineNo++) mergedTokens += tokensOf(lineNo);
938
+ if (mergedTokens <= MAX_CHUNK_TOKENS) {
939
+ last.endLine = rangeEnd;
940
+ last.text = lines2.slice(last.startLine - 1, rangeEnd).join("\n").trim();
941
+ } else if (currentChunk.trim().length > 0) {
942
+ chunks.push({ filePath, startLine, endLine: rangeEnd, text: currentChunk.trim(), kind });
943
+ }
894
944
  }
895
945
  return chunks;
896
946
  }
897
- function chunkFile(filePath, content, chunkSize = MAX_CHUNK_CHARS, overlap = 200, boundaries = []) {
947
+ function splitToFit(text, fits) {
948
+ if (text.length < 2 || fits(text)) return [text];
949
+ const mid = Math.floor(text.length / 2);
950
+ let cut = mid;
951
+ for (let d = 0; d <= Math.floor(text.length / 4); d++) {
952
+ if (/\s/.test(text[mid - d] ?? "")) {
953
+ cut = mid - d;
954
+ break;
955
+ }
956
+ if (/\s/.test(text[mid + d] ?? "")) {
957
+ cut = mid + d;
958
+ break;
959
+ }
960
+ }
961
+ const before = text.charCodeAt(cut - 1);
962
+ if (before >= 55296 && before <= 56319) cut++;
963
+ if (cut <= 0 || cut >= text.length) return [text];
964
+ return [...splitToFit(text.slice(0, cut), fits), ...splitToFit(text.slice(cut), fits)];
965
+ }
966
+ function chunkFile(filePath, content, chunkSize = MAX_CHUNK_CHARS, overlap = 200, boundaries = [], countTokens) {
898
967
  const lines2 = content.split(/\r?\n/);
899
968
  if (lines2.length > 1 && lines2[lines2.length - 1] === "") lines2.pop();
900
969
  const totalLines = lines2.length;
901
970
  if (boundaries.length === 0) {
902
- return splitRangeIntoChunks(filePath, lines2, 1, totalLines, chunkSize, overlap, "window");
971
+ return splitRangeIntoChunks(filePath, lines2, 1, totalLines, chunkSize, overlap, "window", countTokens);
903
972
  }
904
973
  const clipped = boundaries.map((b) => ({
905
974
  start: Math.max(1, Math.min(b.start, totalLines)),
@@ -907,7 +976,7 @@ function chunkFile(filePath, content, chunkSize = MAX_CHUNK_CHARS, overlap = 200
907
976
  kind: b.kind
908
977
  })).filter((b) => b.end >= b.start).sort((a, b) => a.start - b.start || b.end - a.end);
909
978
  if (clipped.length === 0) {
910
- return splitRangeIntoChunks(filePath, lines2, 1, totalLines, chunkSize, overlap, "window");
979
+ return splitRangeIntoChunks(filePath, lines2, 1, totalLines, chunkSize, overlap, "window", countTokens);
911
980
  }
912
981
  const flattened = [];
913
982
  let openEnd = 0;
@@ -974,7 +1043,7 @@ function chunkFile(filePath, content, chunkSize = MAX_CHUNK_CHARS, overlap = 200
974
1043
  }
975
1044
  const chunks = [];
976
1045
  for (const r of ranges) {
977
- chunks.push(...splitRangeIntoChunks(filePath, lines2, r.start, r.end, chunkSize, overlap, r.kind));
1046
+ chunks.push(...splitRangeIntoChunks(filePath, lines2, r.start, r.end, chunkSize, overlap, r.kind, countTokens));
978
1047
  }
979
1048
  return chunks;
980
1049
  }
@@ -1225,8 +1294,12 @@ function mergeNearbyHits(hits, proximity = 20) {
1225
1294
  return merged;
1226
1295
  }
1227
1296
  async function indexFile(db, filePath, content, boundaries = []) {
1228
- const chunks = chunkFile(filePath, content, void 0, void 0, boundaries);
1297
+ let chunks = chunkFile(filePath, content, void 0, void 0, boundaries);
1229
1298
  if (chunks.length > 0) {
1299
+ if (embeddingsDepsAvailable(db)) {
1300
+ const { countTokens } = await getExtractor(DEFAULT_MODEL);
1301
+ if (countTokens !== void 0) chunks = chunkFile(filePath, content, void 0, void 0, boundaries, countTokens);
1302
+ }
1230
1303
  return upsertChunks(db, chunks);
1231
1304
  }
1232
1305
  deleteFileEmbeddings(db, filePath);
@@ -1260,11 +1333,11 @@ function deleteFileEmbeddings(db, filePath) {
1260
1333
  }
1261
1334
  db.prepare(`DELETE FROM chunks WHERE ${pathEqClause("file_path")}`).run(folded);
1262
1335
  }
1263
- function resetAllEmbeddings(db) {
1336
+ function resetAllEmbeddings(db, keepVectors = false) {
1264
1337
  const paths = db.prepare("SELECT DISTINCT file_path FROM chunks").pluck().all();
1265
1338
  const clearEmbedSha = db.prepare(`UPDATE files SET embed_sha = NULL WHERE ${pathEqClause("path")}`);
1266
1339
  const tx = db.transaction(() => {
1267
- for (const p of paths) deleteFileEmbeddings(db, p);
1340
+ if (!keepVectors) for (const p of paths) deleteFileEmbeddings(db, p);
1268
1341
  for (const p of paths) clearEmbedSha.run(foldPath(p));
1269
1342
  });
1270
1343
  tx.immediate();
@@ -1282,17 +1355,22 @@ function majorMinor(version) {
1282
1355
  return parts.length >= 2 ? `${parts[0]}.${parts[1]}` : version;
1283
1356
  }
1284
1357
  var _provenanceChecked = /* @__PURE__ */ new WeakSet();
1358
+ function vectorSpaceOf(provenance) {
1359
+ const at = provenance.lastIndexOf("/embed-");
1360
+ return at === -1 ? provenance : provenance.slice(0, at);
1361
+ }
1285
1362
  function ensureEmbeddingProvenance(db, modelName = DEFAULT_MODEL) {
1286
1363
  if (_provenanceChecked.has(db)) return;
1287
1364
  _provenanceChecked.add(db);
1288
1365
  const current = embeddingProvenance(modelName);
1289
1366
  const stored = db.prepare("SELECT provenance FROM embedding_provenance WHERE id = 1").pluck().get();
1290
1367
  if (stored === current) return;
1291
- const cleared = resetAllEmbeddings(db);
1368
+ const keepVectors = stored !== void 0 && vectorSpaceOf(stored) === vectorSpaceOf(current);
1369
+ const cleared = resetAllEmbeddings(db, keepVectors);
1292
1370
  db.prepare(
1293
1371
  "INSERT INTO embedding_provenance (id, provenance) VALUES (1, ?) ON CONFLICT(id) DO UPDATE SET provenance = excluded.provenance"
1294
1372
  ).run(current);
1295
- if (cleared > 0) {
1373
+ if (cleared > 0 && !keepVectors) {
1296
1374
  console.warn(
1297
1375
  `Embedding stack changed (${stored ?? "unrecorded"} -> ${current}); discarded ${cleared} file${cleared === 1 ? "" : "s"} worth of vectors because they no longer reliably describe this file's current chunks. Run \`token-goat index\` to rebuild them.`
1298
1376
  );
@@ -1335,7 +1413,7 @@ function _pathPriorityPenalty(filePath) {
1335
1413
 
1336
1414
  // src/parser_fingerprint.ts
1337
1415
  init_define_import_meta_env();
1338
- var PARSER_FINGERPRINT = "ef41adb465538db6";
1416
+ var PARSER_FINGERPRINT = "0d80572d3abdfb19";
1339
1417
 
1340
1418
  // src/parser.ts
1341
1419
  init_define_import_meta_env();
@@ -1374,7 +1452,7 @@ function* eachUnfencedLine(lines2) {
1374
1452
  }
1375
1453
 
1376
1454
  // src/hints/markdown_hints.ts
1377
- var MARKDOWN_SIZE_THRESHOLD = 8e3;
1455
+ var MARKDOWN_SIZE_THRESHOLD = 4e3;
1378
1456
  var MAX_HEADINGS = 40;
1379
1457
  var MAX_OUTPUT_LINES = 60;
1380
1458
  function extractMarkdownHeadings(content, limit = MAX_HEADINGS) {
@@ -1918,6 +1996,28 @@ function extractTsJsSymbols(root, filePath, lines2) {
1918
1996
  out.push(makeSymbol(filePath, fieldName.text, "method", node, lines2, "c"));
1919
1997
  }
1920
1998
  }
1999
+ if (node.type === "method_definition" && node.parent?.type === "object") {
2000
+ const name = nodeName(node);
2001
+ if (name !== null && name !== "") {
2002
+ out.push(makeSymbol(filePath, name, "method", node, lines2, "c"));
2003
+ }
2004
+ }
2005
+ if (node.type === "pair" && node.parent?.type === "object") {
2006
+ const key = node.childForFieldName("key");
2007
+ const value = node.childForFieldName("value");
2008
+ if (key !== null && value !== null) {
2009
+ const keyName = key.type === "identifier" || key.type === "property_identifier" || key.type === "string" ? key.text.replace(/^['"]|['"]$/g, "") : null;
2010
+ if (keyName !== null && keyName !== "") {
2011
+ const isFn = value.type === "arrow_function" || value.type === "function_expression" || value.type === "function";
2012
+ const isObjectOrArray = value.type === "object" || value.type === "array";
2013
+ if (isFn) {
2014
+ out.push(makeSymbol(filePath, keyName, "method", node, lines2, "c"));
2015
+ } else if (isObjectOrArray && node.endPosition.row - node.startPosition.row >= 1) {
2016
+ out.push(makeSymbol(filePath, keyName, "config", node, lines2, "c"));
2017
+ }
2018
+ }
2019
+ }
2020
+ }
1921
2021
  const childInside = insideFunction || TSJS_FN_SCOPE_TYPES.has(node.type);
1922
2022
  for (const child of node.namedChildren) {
1923
2023
  visit(child, childInside);
@@ -3594,7 +3694,7 @@ function parseSourceSymbolsTreeSitterOnly(content, filePath, language) {
3594
3694
  var REGEX_ADAPTERS_SLOT = /* @__PURE__ */ Symbol.for("token-goat.regex-adapters");
3595
3695
  var adapterSlot = globalThis;
3596
3696
  async function loadRegexExtractors() {
3597
- adapterSlot[REGEX_ADAPTERS_SLOT] ??= await import("./token-goat-chunk-GIIHUSZX.mjs");
3697
+ adapterSlot[REGEX_ADAPTERS_SLOT] ??= await import("./token-goat-chunk-GPCCYQSS.mjs");
3598
3698
  }
3599
3699
  function regexAdapters() {
3600
3700
  const loaded = adapterSlot[REGEX_ADAPTERS_SLOT];
@@ -4166,7 +4266,7 @@ function tableSectionEndIndex(headers, headerPos, totalLines) {
4166
4266
  }
4167
4267
  return totalLines;
4168
4268
  }
4169
- function resolveHeaderPos(headers, base, ordinal) {
4269
+ function resolveHeaderPos(headers, base, ordinal, allowFuzzy = true) {
4170
4270
  const target = base.toLowerCase();
4171
4271
  const normalizedTarget = normalizeHeading(base).toLowerCase();
4172
4272
  const strippedTarget = normalizeHeadingStrip(base).toLowerCase();
@@ -4227,10 +4327,106 @@ function resolveHeaderPos(headers, base, ordinal) {
4227
4327
  const allWordsMatch = queryWords.every((qw) => headingWords.some((hw) => hw.includes(qw)));
4228
4328
  if (allWordsMatch) widenedMatches.push(i);
4229
4329
  }
4230
- if (widenedMatches.length !== 1) return null;
4231
- const widenedPos = widenedMatches[0];
4232
- if (widenedPos === void 0) return null;
4233
- return { headerPos: widenedPos, redirectedFrom: base, occurrences: null };
4330
+ if (widenedMatches.length === 1) {
4331
+ const widenedPos = widenedMatches[0];
4332
+ if (widenedPos !== void 0) {
4333
+ return { headerPos: widenedPos, redirectedFrom: base, occurrences: null };
4334
+ }
4335
+ }
4336
+ function sectionLevenshtein(a, b) {
4337
+ if (a === b) return 0;
4338
+ const la = a.length;
4339
+ const lb = b.length;
4340
+ if (la === 0) return lb;
4341
+ if (lb === 0) return la;
4342
+ const v0 = new Array(lb + 1);
4343
+ const v1 = new Array(lb + 1);
4344
+ for (let i = 0; i <= lb; i++) v0[i] = i;
4345
+ for (let i = 0; i < la; i++) {
4346
+ v1[0] = i + 1;
4347
+ for (let j = 0; j < lb; j++) {
4348
+ const cost = a.charCodeAt(i) === b.charCodeAt(j) ? 0 : 1;
4349
+ v1[j + 1] = Math.min(v1[j] + 1, v0[j + 1] + 1, v0[j] + cost);
4350
+ }
4351
+ for (let j = 0; j <= lb; j++) v0[j] = v1[j];
4352
+ }
4353
+ return v0[lb];
4354
+ }
4355
+ const STOP_WORDS = /* @__PURE__ */ new Set(["a", "an", "the", "and", "or", "in", "on", "at", "to", "for", "of", "with", "by", "is", "&"]);
4356
+ const cleanTokens = (str) => str.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 0);
4357
+ const rawQTokens = cleanTokens(base);
4358
+ const qTokens = rawQTokens.filter((t) => !STOP_WORDS.has(t) || rawQTokens.length <= 2);
4359
+ if (allowFuzzy && qTokens.length > 0) {
4360
+ const candidates = [];
4361
+ for (let i = 0; i < headers.length; i++) {
4362
+ const h = headers[i];
4363
+ if (h === void 0) continue;
4364
+ const rawHTokens = cleanTokens(h.heading);
4365
+ const hTokens = rawHTokens.filter((t) => !STOP_WORDS.has(t) || rawHTokens.length <= 2);
4366
+ if (hTokens.length === 0) continue;
4367
+ const normH = normalizeHeading(h.heading).toLowerCase();
4368
+ const maxLen = Math.max(normalizedTarget.length, normH.length);
4369
+ const editDist = sectionLevenshtein(normalizedTarget, normH);
4370
+ const strSim = maxLen > 0 ? 1 - editDist / maxLen : 0;
4371
+ let matchedQ = 0;
4372
+ for (const qw of qTokens) {
4373
+ let best = 0;
4374
+ for (const hw of hTokens) {
4375
+ if (qw === hw) {
4376
+ best = 1;
4377
+ break;
4378
+ }
4379
+ if (qw.length >= 3 && hw.length >= 3) {
4380
+ if (hw.startsWith(qw) || qw.startsWith(hw)) {
4381
+ best = Math.max(best, 0.9);
4382
+ } else if (sectionLevenshtein(qw, hw) <= 1) {
4383
+ best = Math.max(best, 0.85);
4384
+ }
4385
+ }
4386
+ }
4387
+ matchedQ += best;
4388
+ }
4389
+ let matchedH = 0;
4390
+ for (const hw of hTokens) {
4391
+ let best = 0;
4392
+ for (const qw of qTokens) {
4393
+ if (qw === hw) {
4394
+ best = 1;
4395
+ break;
4396
+ }
4397
+ if (qw.length >= 3 && hw.length >= 3) {
4398
+ if (hw.startsWith(qw) || qw.startsWith(hw)) {
4399
+ best = Math.max(best, 0.9);
4400
+ } else if (sectionLevenshtein(qw, hw) <= 1) {
4401
+ best = Math.max(best, 0.85);
4402
+ }
4403
+ }
4404
+ }
4405
+ matchedH += best;
4406
+ }
4407
+ const wordRecall = matchedQ / qTokens.length;
4408
+ const wordPrecision = matchedH / hTokens.length;
4409
+ const tokenDice = 2 * matchedQ / (qTokens.length + hTokens.length);
4410
+ let score = 0;
4411
+ if (strSim >= 0.8) {
4412
+ score = strSim;
4413
+ } else if (tokenDice >= 0.75 && wordRecall >= 0.7 && wordPrecision >= 0.7) {
4414
+ score = (tokenDice + wordRecall + wordPrecision) / 3;
4415
+ }
4416
+ if (score >= 0.75) {
4417
+ candidates.push({ index: i, score });
4418
+ }
4419
+ }
4420
+ if (candidates.length > 0) {
4421
+ candidates.sort((a, b) => b.score - a.score);
4422
+ const best = candidates[0];
4423
+ const runnerUp = candidates[1];
4424
+ if (candidates.length === 1 || runnerUp !== void 0 && best.score - runnerUp.score >= 0.08) {
4425
+ return { headerPos: best.index, redirectedFrom: base, occurrences: null };
4426
+ }
4427
+ }
4428
+ }
4429
+ return null;
4234
4430
  }
4235
4431
  function buildSectionResult(headers, kind, lines2, headerPos, redirectedFrom) {
4236
4432
  const header = headers[headerPos];
@@ -4249,12 +4445,12 @@ function buildSectionResult(headers, kind, lines2, headerPos, redirectedFrom) {
4249
4445
  };
4250
4446
  return redirectedFrom === null ? result : { ...result, redirectedFrom };
4251
4447
  }
4252
- function resolveSectionFromText(text, headingSpec, language) {
4448
+ function resolveSectionFromText(text, headingSpec, language, allowFuzzy = true) {
4253
4449
  const { headers, kind } = findHeaders(text, language);
4254
4450
  const { base, ordinal } = parseHeadingSpec(headingSpec, headers);
4255
4451
  if (base.length === 0) return null;
4256
4452
  const lines2 = text.split("\n");
4257
- const resolved = resolveHeaderPos(headers, base, ordinal);
4453
+ const resolved = resolveHeaderPos(headers, base, ordinal, allowFuzzy);
4258
4454
  if (resolved === null) return null;
4259
4455
  const built = buildSectionResult(headers, kind, lines2, resolved.headerPos, resolved.redirectedFrom);
4260
4456
  if (built === null || resolved.occurrences === null) return built;
@@ -4281,10 +4477,10 @@ function readTextForSections(filePath, readFn) {
4281
4477
  }
4282
4478
  return redactIfDotenv(filePath, text);
4283
4479
  }
4284
- function readSection(filePath, headingSpec, readFn) {
4480
+ function readSection(filePath, headingSpec, readFn, allowFuzzy = true) {
4285
4481
  const text = readTextForSections(filePath, readFn);
4286
4482
  if (text === null) return null;
4287
- return resolveSectionFromText(text, headingSpec, refineLanguageByContent(filePath, detectLanguage(filePath), text));
4483
+ return resolveSectionFromText(text, headingSpec, refineLanguageByContent(filePath, detectLanguage(filePath), text), allowFuzzy);
4288
4484
  }
4289
4485
  function findContainingSection(filePath, lineStart, lineEnd, readFn) {
4290
4486
  const text = readTextForSections(filePath, readFn);
@@ -4334,6 +4530,8 @@ export {
4334
4530
  isEmbeddableDocument,
4335
4531
  ipynbToVirtualSource,
4336
4532
  eachUnfencedLine,
4533
+ modelFilesPresent,
4534
+ ensureModelFiles,
4337
4535
  checkEmbeddingPreflight,
4338
4536
  OVER_FETCH_FACTOR,
4339
4537
  MAX_OVER_FETCH,
@@ -3,7 +3,7 @@ const require = __cjsRequire(import.meta.url);
3
3
  import {
4
4
  deliveredOutputBytes,
5
5
  wrappedShell
6
- } from "./token-goat-chunk-YZX7EFG4.mjs";
6
+ } from "./token-goat-chunk-GLOM5W5C.mjs";
7
7
  import {
8
8
  ToolFilter,
9
9
  capTokens,
@@ -14,18 +14,18 @@ import {
14
14
  selectFilter,
15
15
  shlexSplit,
16
16
  storeBashOutputSync
17
- } from "./token-goat-chunk-YQ7WI2CO.mjs";
18
- import "./token-goat-chunk-2X2EBBC6.mjs";
17
+ } from "./token-goat-chunk-C26ZBGBC.mjs";
19
18
  import {
20
19
  recordStat
21
- } from "./token-goat-chunk-IVUQLQWN.mjs";
22
- import "./token-goat-chunk-K7F2BFIK.mjs";
20
+ } from "./token-goat-chunk-26YUO7MP.mjs";
21
+ import "./token-goat-chunk-BK3RPPTK.mjs";
22
+ import "./token-goat-chunk-MWSVUKVV.mjs";
23
23
  import {
24
24
  loadConfig
25
- } from "./token-goat-chunk-S4XRY446.mjs";
25
+ } from "./token-goat-chunk-2VASS5VR.mjs";
26
26
  import "./token-goat-chunk-EEIDFMEM.mjs";
27
27
  import "./token-goat-chunk-OSUFN2FV.mjs";
28
- import "./token-goat-chunk-PM76YS22.mjs";
28
+ import "./token-goat-chunk-UBZCON6N.mjs";
29
29
  import "./token-goat-chunk-ERTXEKB6.mjs";
30
30
  import "./token-goat-chunk-GMOUBOX4.mjs";
31
31
  import {