token-goat 2.9.15 → 2.9.16

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 (37) hide show
  1. package/dist/{token-goat-chunk-ASYEPR3S.mjs → token-goat-chunk-2IEIBVVD.mjs} +7 -3
  2. package/dist/{token-goat-chunk-LCZBPOIN.mjs → token-goat-chunk-2PCFAQAS.mjs} +66 -51
  3. package/dist/{token-goat-chunk-PM76YS22.mjs → token-goat-chunk-3H6FQT62.mjs} +1 -1
  4. package/dist/{token-goat-chunk-C5JIO6HK.mjs → token-goat-chunk-4DC6DDIB.mjs} +5 -5
  5. package/dist/{token-goat-chunk-YQ7WI2CO.mjs → token-goat-chunk-5USHO7B6.mjs} +393 -3957
  6. package/dist/{token-goat-chunk-2X2EBBC6.mjs → token-goat-chunk-63QWJ6UM.mjs} +2 -2
  7. package/dist/{token-goat-chunk-NDRP4KJQ.mjs → token-goat-chunk-BFNTLGVA.mjs} +226 -50
  8. package/dist/token-goat-chunk-BGHSSJUA.mjs +102 -0
  9. package/dist/{token-goat-chunk-IVUQLQWN.mjs → token-goat-chunk-DMQ32ZSR.mjs} +5 -3
  10. package/dist/{token-goat-chunk-SFAS46RE.mjs → token-goat-chunk-EJCY6KHO.mjs} +2 -2
  11. package/dist/{token-goat-chunk-Z6UXPYJA.mjs → token-goat-chunk-F2ARFHLJ.mjs} +1 -1
  12. package/dist/{token-goat-chunk-YKG35VHC.mjs → token-goat-chunk-GIMGT34T.mjs} +1 -1
  13. package/dist/{token-goat-chunk-SZWYESBS.mjs → token-goat-chunk-GMXBQS6O.mjs} +155 -54
  14. package/dist/{token-goat-chunk-S4XRY446.mjs → token-goat-chunk-H3DXI6VD.mjs} +4 -3
  15. package/dist/{token-goat-chunk-6DLVZDB6.mjs → token-goat-chunk-LJL6FGDS.mjs} +13 -12
  16. package/dist/{token-goat-chunk-NEI4NC54.mjs → token-goat-chunk-LKSXAMJB.mjs} +7 -2
  17. package/dist/{token-goat-chunk-A4JYKD5H.mjs → token-goat-chunk-M2R4UQ3B.mjs} +2 -2
  18. package/dist/{token-goat-chunk-MZDIJJ3R.mjs → token-goat-chunk-MA5BZ2KI.mjs} +40 -3
  19. package/dist/{token-goat-chunk-GIIHUSZX.mjs → token-goat-chunk-MIL62GMN.mjs} +41 -11
  20. package/dist/{token-goat-chunk-NU7TLMQK.mjs → token-goat-chunk-NAMH252H.mjs} +10 -7
  21. package/dist/{token-goat-chunk-DQ4J5AFF.mjs → token-goat-chunk-NOBZV7PM.mjs} +69 -18
  22. package/dist/{token-goat-chunk-5V7DAC7V.mjs → token-goat-chunk-NQZCSVN2.mjs} +10 -3
  23. package/dist/{token-goat-chunk-7ZYK25AO.mjs → token-goat-chunk-OCI25AXY.mjs} +4 -4
  24. package/dist/{token-goat-chunk-NDPO7GAH.mjs → token-goat-chunk-QMZMHQRR.mjs} +21 -20
  25. package/dist/{token-goat-chunk-K7F2BFIK.mjs → token-goat-chunk-R3X5TCPB.mjs} +1 -1
  26. package/dist/token-goat-chunk-RRNZMM3A.mjs +3364 -0
  27. package/dist/{token-goat-chunk-ATIFTMRC.mjs → token-goat-chunk-ULA4WCED.mjs} +7 -7
  28. package/dist/{token-goat-chunk-OUGNPMDA.mjs → token-goat-chunk-UMXD2M7O.mjs} +30 -14
  29. package/dist/{token-goat-chunk-YZX7EFG4.mjs → token-goat-chunk-UXHDBVUR.mjs} +1 -1
  30. package/dist/{token-goat-chunk-LT7JRU6K.mjs → token-goat-chunk-VVIYEFBI.mjs} +4 -4
  31. package/dist/{token-goat-chunk-BL5LNGBG.mjs → token-goat-chunk-XOHTFRDH.mjs} +2966 -3869
  32. package/dist/{token-goat-chunk-XEPXYDPI.mjs → token-goat-chunk-YPS64ACE.mjs} +11 -2
  33. package/dist/{token-goat-chunk-GMQQA7E4.mjs → token-goat-chunk-Z5VPAXTC.mjs} +2442 -722
  34. package/dist/{token-goat-chunk-3NSDDTGL.mjs → token-goat-chunk-ZEHFYJ7Z.mjs} +5 -5
  35. package/dist/token-goat-hook.mjs +13 -12
  36. package/dist/token-goat.core.mjs +21 -20
  37. package/package.json +1 -1
@@ -2,12 +2,12 @@ import { createRequire as __cjsRequire } from 'node:module';
2
2
  const require = __cjsRequire(import.meta.url);
3
3
  import {
4
4
  getDb
5
- } from "./token-goat-chunk-K7F2BFIK.mjs";
5
+ } from "./token-goat-chunk-R3X5TCPB.mjs";
6
6
  import {
7
7
  foldPath,
8
8
  globalDbPath,
9
9
  isCaseInsensitiveFs
10
- } from "./token-goat-chunk-PM76YS22.mjs";
10
+ } from "./token-goat-chunk-3H6FQT62.mjs";
11
11
  import {
12
12
  normalizePath
13
13
  } from "./token-goat-chunk-ERTXEKB6.mjs";
@@ -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-63QWJ6UM.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-R3X5TCPB.mjs";
29
25
  import {
30
26
  fingerprintContent,
31
27
  loadConfig
32
- } from "./token-goat-chunk-S4XRY446.mjs";
28
+ } from "./token-goat-chunk-H3DXI6VD.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-3H6FQT62.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-VVIYEFBI.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-OCI25AXY.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-ZEHFYJ7Z.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 = "e70e91822f151d92";
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) {
@@ -3594,7 +3672,7 @@ function parseSourceSymbolsTreeSitterOnly(content, filePath, language) {
3594
3672
  var REGEX_ADAPTERS_SLOT = /* @__PURE__ */ Symbol.for("token-goat.regex-adapters");
3595
3673
  var adapterSlot = globalThis;
3596
3674
  async function loadRegexExtractors() {
3597
- adapterSlot[REGEX_ADAPTERS_SLOT] ??= await import("./token-goat-chunk-GIIHUSZX.mjs");
3675
+ adapterSlot[REGEX_ADAPTERS_SLOT] ??= await import("./token-goat-chunk-MIL62GMN.mjs");
3598
3676
  }
3599
3677
  function regexAdapters() {
3600
3678
  const loaded = adapterSlot[REGEX_ADAPTERS_SLOT];
@@ -4166,7 +4244,7 @@ function tableSectionEndIndex(headers, headerPos, totalLines) {
4166
4244
  }
4167
4245
  return totalLines;
4168
4246
  }
4169
- function resolveHeaderPos(headers, base, ordinal) {
4247
+ function resolveHeaderPos(headers, base, ordinal, allowFuzzy = true) {
4170
4248
  const target = base.toLowerCase();
4171
4249
  const normalizedTarget = normalizeHeading(base).toLowerCase();
4172
4250
  const strippedTarget = normalizeHeadingStrip(base).toLowerCase();
@@ -4227,10 +4305,106 @@ function resolveHeaderPos(headers, base, ordinal) {
4227
4305
  const allWordsMatch = queryWords.every((qw) => headingWords.some((hw) => hw.includes(qw)));
4228
4306
  if (allWordsMatch) widenedMatches.push(i);
4229
4307
  }
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 };
4308
+ if (widenedMatches.length === 1) {
4309
+ const widenedPos = widenedMatches[0];
4310
+ if (widenedPos !== void 0) {
4311
+ return { headerPos: widenedPos, redirectedFrom: base, occurrences: null };
4312
+ }
4313
+ }
4314
+ function sectionLevenshtein(a, b) {
4315
+ if (a === b) return 0;
4316
+ const la = a.length;
4317
+ const lb = b.length;
4318
+ if (la === 0) return lb;
4319
+ if (lb === 0) return la;
4320
+ const v0 = new Array(lb + 1);
4321
+ const v1 = new Array(lb + 1);
4322
+ for (let i = 0; i <= lb; i++) v0[i] = i;
4323
+ for (let i = 0; i < la; i++) {
4324
+ v1[0] = i + 1;
4325
+ for (let j = 0; j < lb; j++) {
4326
+ const cost = a.charCodeAt(i) === b.charCodeAt(j) ? 0 : 1;
4327
+ v1[j + 1] = Math.min(v1[j] + 1, v0[j + 1] + 1, v0[j] + cost);
4328
+ }
4329
+ for (let j = 0; j <= lb; j++) v0[j] = v1[j];
4330
+ }
4331
+ return v0[lb];
4332
+ }
4333
+ const STOP_WORDS = /* @__PURE__ */ new Set(["a", "an", "the", "and", "or", "in", "on", "at", "to", "for", "of", "with", "by", "is", "&"]);
4334
+ const cleanTokens = (str) => str.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 0);
4335
+ const rawQTokens = cleanTokens(base);
4336
+ const qTokens = rawQTokens.filter((t) => !STOP_WORDS.has(t) || rawQTokens.length <= 2);
4337
+ if (allowFuzzy && qTokens.length > 0) {
4338
+ const candidates = [];
4339
+ for (let i = 0; i < headers.length; i++) {
4340
+ const h = headers[i];
4341
+ if (h === void 0) continue;
4342
+ const rawHTokens = cleanTokens(h.heading);
4343
+ const hTokens = rawHTokens.filter((t) => !STOP_WORDS.has(t) || rawHTokens.length <= 2);
4344
+ if (hTokens.length === 0) continue;
4345
+ const normH = normalizeHeading(h.heading).toLowerCase();
4346
+ const maxLen = Math.max(normalizedTarget.length, normH.length);
4347
+ const editDist = sectionLevenshtein(normalizedTarget, normH);
4348
+ const strSim = maxLen > 0 ? 1 - editDist / maxLen : 0;
4349
+ let matchedQ = 0;
4350
+ for (const qw of qTokens) {
4351
+ let best = 0;
4352
+ for (const hw of hTokens) {
4353
+ if (qw === hw) {
4354
+ best = 1;
4355
+ break;
4356
+ }
4357
+ if (qw.length >= 3 && hw.length >= 3) {
4358
+ if (hw.startsWith(qw) || qw.startsWith(hw)) {
4359
+ best = Math.max(best, 0.9);
4360
+ } else if (sectionLevenshtein(qw, hw) <= 1) {
4361
+ best = Math.max(best, 0.85);
4362
+ }
4363
+ }
4364
+ }
4365
+ matchedQ += best;
4366
+ }
4367
+ let matchedH = 0;
4368
+ for (const hw of hTokens) {
4369
+ let best = 0;
4370
+ for (const qw of qTokens) {
4371
+ if (qw === hw) {
4372
+ best = 1;
4373
+ break;
4374
+ }
4375
+ if (qw.length >= 3 && hw.length >= 3) {
4376
+ if (hw.startsWith(qw) || qw.startsWith(hw)) {
4377
+ best = Math.max(best, 0.9);
4378
+ } else if (sectionLevenshtein(qw, hw) <= 1) {
4379
+ best = Math.max(best, 0.85);
4380
+ }
4381
+ }
4382
+ }
4383
+ matchedH += best;
4384
+ }
4385
+ const wordRecall = matchedQ / qTokens.length;
4386
+ const wordPrecision = matchedH / hTokens.length;
4387
+ const tokenDice = 2 * matchedQ / (qTokens.length + hTokens.length);
4388
+ let score = 0;
4389
+ if (strSim >= 0.8) {
4390
+ score = strSim;
4391
+ } else if (tokenDice >= 0.75 && wordRecall >= 0.7 && wordPrecision >= 0.7) {
4392
+ score = (tokenDice + wordRecall + wordPrecision) / 3;
4393
+ }
4394
+ if (score >= 0.75) {
4395
+ candidates.push({ index: i, score });
4396
+ }
4397
+ }
4398
+ if (candidates.length > 0) {
4399
+ candidates.sort((a, b) => b.score - a.score);
4400
+ const best = candidates[0];
4401
+ const runnerUp = candidates[1];
4402
+ if (candidates.length === 1 || runnerUp !== void 0 && best.score - runnerUp.score >= 0.08) {
4403
+ return { headerPos: best.index, redirectedFrom: base, occurrences: null };
4404
+ }
4405
+ }
4406
+ }
4407
+ return null;
4234
4408
  }
4235
4409
  function buildSectionResult(headers, kind, lines2, headerPos, redirectedFrom) {
4236
4410
  const header = headers[headerPos];
@@ -4249,12 +4423,12 @@ function buildSectionResult(headers, kind, lines2, headerPos, redirectedFrom) {
4249
4423
  };
4250
4424
  return redirectedFrom === null ? result : { ...result, redirectedFrom };
4251
4425
  }
4252
- function resolveSectionFromText(text, headingSpec, language) {
4426
+ function resolveSectionFromText(text, headingSpec, language, allowFuzzy = true) {
4253
4427
  const { headers, kind } = findHeaders(text, language);
4254
4428
  const { base, ordinal } = parseHeadingSpec(headingSpec, headers);
4255
4429
  if (base.length === 0) return null;
4256
4430
  const lines2 = text.split("\n");
4257
- const resolved = resolveHeaderPos(headers, base, ordinal);
4431
+ const resolved = resolveHeaderPos(headers, base, ordinal, allowFuzzy);
4258
4432
  if (resolved === null) return null;
4259
4433
  const built = buildSectionResult(headers, kind, lines2, resolved.headerPos, resolved.redirectedFrom);
4260
4434
  if (built === null || resolved.occurrences === null) return built;
@@ -4281,10 +4455,10 @@ function readTextForSections(filePath, readFn) {
4281
4455
  }
4282
4456
  return redactIfDotenv(filePath, text);
4283
4457
  }
4284
- function readSection(filePath, headingSpec, readFn) {
4458
+ function readSection(filePath, headingSpec, readFn, allowFuzzy = true) {
4285
4459
  const text = readTextForSections(filePath, readFn);
4286
4460
  if (text === null) return null;
4287
- return resolveSectionFromText(text, headingSpec, refineLanguageByContent(filePath, detectLanguage(filePath), text));
4461
+ return resolveSectionFromText(text, headingSpec, refineLanguageByContent(filePath, detectLanguage(filePath), text), allowFuzzy);
4288
4462
  }
4289
4463
  function findContainingSection(filePath, lineStart, lineEnd, readFn) {
4290
4464
  const text = readTextForSections(filePath, readFn);
@@ -4334,6 +4508,8 @@ export {
4334
4508
  isEmbeddableDocument,
4335
4509
  ipynbToVirtualSource,
4336
4510
  eachUnfencedLine,
4511
+ modelFilesPresent,
4512
+ ensureModelFiles,
4337
4513
  checkEmbeddingPreflight,
4338
4514
  OVER_FETCH_FACTOR,
4339
4515
  MAX_OVER_FETCH,
@@ -0,0 +1,102 @@
1
+ import { createRequire as __cjsRequire } from 'node:module';
2
+ const require = __cjsRequire(import.meta.url);
3
+ import {
4
+ GIF_WEB_PALETTE,
5
+ assertDecodableSize,
6
+ quantizeRgbaToIndexed,
7
+ require_omggif,
8
+ resizeRgba
9
+ } from "./token-goat-chunk-RRNZMM3A.mjs";
10
+ import {
11
+ __toESM,
12
+ init_define_import_meta_env
13
+ } from "./token-goat-chunk-A37V4PBF.mjs";
14
+
15
+ // src/image_gif_encode.ts
16
+ init_define_import_meta_env();
17
+ var import_omggif = __toESM(require_omggif(), 1);
18
+ function unionRects(a, b) {
19
+ const x = Math.min(a.x, b.x);
20
+ const y = Math.min(a.y, b.y);
21
+ return { x, y, width: Math.max(a.x + a.width, b.x + b.width) - x, height: Math.max(a.y + a.height, b.y + b.height) - y };
22
+ }
23
+ function gifFrameDelta(prev, prevTransparentIndex, cur, curTransparentIndex, width, height) {
24
+ let minX = width;
25
+ let minY = height;
26
+ let maxX = -1;
27
+ let maxY = -1;
28
+ let cleared = false;
29
+ for (let y = 0; y < height; y++) {
30
+ const row = y * width;
31
+ for (let x = 0; x < width; x++) {
32
+ const i = row + x;
33
+ const curIdx = cur[i] ?? 0;
34
+ const prevIdx = prev[i] ?? 0;
35
+ const curClear = curTransparentIndex !== null && curIdx === curTransparentIndex;
36
+ if (curClear === (prevTransparentIndex !== null && prevIdx === prevTransparentIndex) && (curClear || curIdx === prevIdx)) continue;
37
+ if (curClear) cleared = true;
38
+ if (x < minX) minX = x;
39
+ if (x > maxX) maxX = x;
40
+ if (y < minY) minY = y;
41
+ if (y > maxY) maxY = y;
42
+ }
43
+ }
44
+ if (maxX < 0) return { changed: { x: 0, y: 0, width: 1, height: 1 }, cleared: false };
45
+ return { changed: { x: minX, y: minY, width: maxX - minX + 1, height: maxY - minY + 1 }, cleared };
46
+ }
47
+ function sliceIndexedRect(indexed, canvasWidth, rect) {
48
+ if (rect.x === 0 && rect.y === 0 && rect.width === canvasWidth && rect.height * canvasWidth === indexed.length) return indexed;
49
+ const out = new Array(rect.width * rect.height);
50
+ for (let row = 0; row < rect.height; row++) {
51
+ const src = (rect.y + row) * canvasWidth + rect.x;
52
+ const dst = row * rect.width;
53
+ for (let col = 0; col < rect.width; col++) out[dst + col] = indexed[src + col] ?? 0;
54
+ }
55
+ return out;
56
+ }
57
+ function encodeAnimatedGifDelta(decoded, targetW, targetH) {
58
+ assertDecodableSize("GIF output", targetW, targetH, 5, decoded.frames.length);
59
+ const outBuf = Buffer.alloc(targetW * targetH * 5 * decoded.frames.length + 4096);
60
+ const gifWriter = new import_omggif.default.GifWriter(outBuf, targetW, targetH, { loop: 0, palette: GIF_WEB_PALETTE });
61
+ let pending = null;
62
+ const writePending = (disposal) => {
63
+ if (pending === null) return;
64
+ gifWriter.addFrame(pending.rect.x, pending.rect.y, pending.rect.width, pending.rect.height, pending.pixels, {
65
+ delay: pending.delay,
66
+ disposal,
67
+ ...pending.transparent === null ? {} : { transparent: pending.transparent }
68
+ });
69
+ pending = null;
70
+ };
71
+ for (const frame of decoded.frames) {
72
+ const resizedFrameRgba = resizeRgba(frame.data, frame.width, frame.height, targetW, targetH);
73
+ const { indexedPixels, transparentIndex } = quantizeRgbaToIndexed(resizedFrameRgba, targetW, targetH);
74
+ let rect = { x: 0, y: 0, width: targetW, height: targetH };
75
+ if (pending !== null) {
76
+ const delta = gifFrameDelta(pending.canvas, pending.transparent, indexedPixels, transparentIndex, targetW, targetH);
77
+ rect = delta.changed;
78
+ if (delta.cleared) {
79
+ rect = unionRects(pending.rect, delta.changed);
80
+ pending = { ...pending, rect, pixels: sliceIndexedRect(pending.canvas, targetW, rect) };
81
+ writePending(2);
82
+ } else {
83
+ writePending(1);
84
+ }
85
+ }
86
+ pending = {
87
+ rect,
88
+ pixels: sliceIndexedRect(indexedPixels, targetW, rect),
89
+ canvas: indexedPixels,
90
+ transparent: transparentIndex,
91
+ delay: frame.delay
92
+ };
93
+ }
94
+ writePending(1);
95
+ return outBuf.subarray(0, gifWriter.end());
96
+ }
97
+ export {
98
+ encodeAnimatedGifDelta,
99
+ gifFrameDelta,
100
+ sliceIndexedRect,
101
+ unionRects
102
+ };
@@ -2,11 +2,11 @@ import { createRequire as __cjsRequire } from 'node:module';
2
2
  const require = __cjsRequire(import.meta.url);
3
3
  import {
4
4
  getDb
5
- } from "./token-goat-chunk-K7F2BFIK.mjs";
5
+ } from "./token-goat-chunk-R3X5TCPB.mjs";
6
6
  import {
7
7
  getHarnessName,
8
8
  loadConfig
9
- } from "./token-goat-chunk-S4XRY446.mjs";
9
+ } from "./token-goat-chunk-H3DXI6VD.mjs";
10
10
  import {
11
11
  registerReset
12
12
  } from "./token-goat-chunk-EEIDFMEM.mjs";
@@ -19,7 +19,7 @@ import {
19
19
  ensureDirSync,
20
20
  sanitizeIdForFilename,
21
21
  tokenGoatHome
22
- } from "./token-goat-chunk-PM76YS22.mjs";
22
+ } from "./token-goat-chunk-3H6FQT62.mjs";
23
23
  import {
24
24
  displaySafeText
25
25
  } from "./token-goat-chunk-ERTXEKB6.mjs";
@@ -473,6 +473,7 @@ var _KIND_GROUPS = [
473
473
  "gdrive_image",
474
474
  "webfetch_image",
475
475
  "image_shrink_skipped",
476
+ "image_shrink_over_pixel_limit",
476
477
  "image_shrink_cache_hit",
477
478
  "image_ocr"
478
479
  ])
@@ -872,6 +873,7 @@ var KIND_TO_SOURCE = {
872
873
  image_shrink: SOURCE_IMAGE,
873
874
  image_shrink_cache_hit: SOURCE_IMAGE,
874
875
  image_shrink_skipped: SOURCE_IMAGE,
876
+ image_shrink_over_pixel_limit: SOURCE_IMAGE,
875
877
  image_ocr: SOURCE_IMAGE,
876
878
  webfetch_image: SOURCE_IMAGE,
877
879
  gdrive_image: SOURCE_IMAGE,