grepmax 0.17.23 → 0.18.0

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.
@@ -359,7 +359,13 @@ class TreeSitterChunker {
359
359
  if (t !== "lexical_declaration" && t !== "variable_declaration")
360
360
  return false;
361
361
  const parentType = ((_a = node.parent) === null || _a === void 0 ? void 0 : _a.type) || "";
362
- const allowedParents = ["program", "module", "source_file", "class_body", "export_statement"];
362
+ const allowedParents = [
363
+ "program",
364
+ "module",
365
+ "source_file",
366
+ "class_body",
367
+ "export_statement",
368
+ ];
363
369
  if (parentType && !allowedParents.includes(parentType))
364
370
  return false;
365
371
  // Exported declarations are part of the API surface, so always index
@@ -367,7 +373,8 @@ class TreeSitterChunker {
367
373
  // is `export const typeDefs = gql\`...\`` (template literals, object
368
374
  // literals, or non-PascalCase consts) had no defined_symbols entry
369
375
  // and peek/extract returned "Symbol not found".
370
- if (parentType === "export_statement" || parentType === "export_declaration") {
376
+ if (parentType === "export_statement" ||
377
+ parentType === "export_declaration") {
371
378
  return true;
372
379
  }
373
380
  const text = node.text || "";
@@ -553,6 +560,15 @@ class TreeSitterChunker {
553
560
  referencedSymbols.push(name);
554
561
  }
555
562
  };
563
+ // Type-position references go in a SEPARATE list so they never inflate
564
+ // referencedSymbols.length (which drives role classification below and
565
+ // the search structural boost). Navigation consumers union the two.
566
+ const typeReferencedSymbols = [];
567
+ const addTypeRef = (n) => {
568
+ if (n && !typeReferencedSymbols.includes(n)) {
569
+ typeReferencedSymbols.push(n);
570
+ }
571
+ };
556
572
  // Leaf identifier node types across grammars (a bare name with no
557
573
  // named children — `ErrorCodes`, not `a.ErrorCodes`).
558
574
  const LEAF_ID_TYPES = new Set([
@@ -623,8 +639,33 @@ class TreeSitterChunker {
623
639
  "struct_expression", // Rust
624
640
  "instance_expression", // Scala
625
641
  ]);
642
+ // Harvest Capitalized type names out of an annotation subtree, for the
643
+ // grammars whose type names are NOT `type_identifier` nodes (so Shape 4
644
+ // can't see them): Python/C# spell them `identifier`, PHP spells them
645
+ // `name`. We walk the WHOLE subtree and keep every Capitalized leaf of
646
+ // those kinds — safe because we only ever enter from a type position (a
647
+ // `type` / `return_type` / `returns` field, or a base/heritage list).
648
+ // Excludes the chunk's own name; lowercase builtins (`list`, `int`,
649
+ // `string`) and non-name leaves (`predefined_type`, `string_content`
650
+ // in `Literal["x"]`) fall out via the kind + Capitalized gates.
651
+ const TYPE_NAME_LEAF_TYPES = new Set(["identifier", "name"]);
652
+ const harvestTypeLeaves = (node) => {
653
+ var _a, _b, _c;
654
+ if (!node)
655
+ return;
656
+ if (((_b = (_a = node.namedChildren) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) === 0) {
657
+ if (TYPE_NAME_LEAF_TYPES.has(node.type) &&
658
+ /^[A-Z]/.test(node.text) &&
659
+ node.text !== name) {
660
+ addTypeRef(node.text);
661
+ }
662
+ return;
663
+ }
664
+ for (const c of (_c = node.namedChildren) !== null && _c !== void 0 ? _c : [])
665
+ harvestTypeLeaves(c);
666
+ };
626
667
  const extractRefs = (n) => {
627
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
668
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1;
628
669
  // Handle JS/TS (call_expression), Python (call), Lua (function_call)
629
670
  if (n.type === "call_expression" ||
630
671
  n.type === "call" ||
@@ -737,10 +778,110 @@ class TreeSitterChunker {
737
778
  addRef(head.text);
738
779
  }
739
780
  }
740
- for (const child of (_j = n.namedChildren) !== null && _j !== void 0 ? _j : []) {
781
+ // Shape 4 type-position reference (TS/TSX): `: T`, `<T>`, `T[]`,
782
+ // `as T`, `interface X extends T`, type aliases. TS uses a distinct
783
+ // `type_identifier` node for type names (primitives are
784
+ // `predefined_type`, so they never reach here). Capture Capitalized
785
+ // ones, skipping the chunk's own name and any locally-declared type
786
+ // parameter (`<T>` and its uses `value: T`). Constraints
787
+ // (`T extends Foo`) still yield `Foo`. Type list, never referencedSymbols.
788
+ if (n.type === "type_identifier" &&
789
+ /^[A-Z]/.test(n.text) &&
790
+ n.text !== name &&
791
+ !declaredTypeParams.has(n.text)) {
792
+ addTypeRef(n.text);
793
+ }
794
+ // Shape 5 — class heritage `class A extends B`: the superclass is an
795
+ // `identifier` (a runtime value), not a `type_identifier`, so Shape 4
796
+ // misses it. `implements I` and `interface X extends Y` use
797
+ // type_identifier and are already covered. Reduce `extends ns.Base`
798
+ // to `Base`; skip type arguments (Shape 4 handles those).
799
+ if (n.type === "extends_clause") {
800
+ for (const c of (_j = n.namedChildren) !== null && _j !== void 0 ? _j : []) {
801
+ if (c.type === "type_arguments")
802
+ continue;
803
+ const sup = simpleRefName(c);
804
+ if (sup && /^[A-Z]/.test(sup) && sup !== name)
805
+ addTypeRef(sup);
806
+ }
807
+ }
808
+ // Shape 6 — type-position references for grammars whose type names
809
+ // are plain `identifier` / `name` nodes (not `type_identifier`), so
810
+ // Shape 4 can't reach them. Read the annotation off the carrier
811
+ // node's type field and harvest from THAT subtree only — never the
812
+ // node as a whole — so value expressions stay out of the type list.
813
+ // Each grammar is gated by `lang` because the carrier node/field
814
+ // names collide across grammars (`function_definition`,
815
+ // `method_declaration`, `assignment`, …). The other typed grammars
816
+ // (Go/Rust/Java/Kotlin/Scala/Swift) spell type names as
817
+ // `type_identifier` and are already covered by Shape 4.
818
+ if (lang === "python") {
819
+ // `x: Foo`, `-> Bar`, `v: Baz = …`, and `class C(Base)` bases.
820
+ if (n.type === "typed_parameter" ||
821
+ n.type === "typed_default_parameter") {
822
+ harvestTypeLeaves((_l = (_k = n.childForFieldName) === null || _k === void 0 ? void 0 : _k.call(n, "type")) !== null && _l !== void 0 ? _l : null);
823
+ }
824
+ else if (n.type === "function_definition") {
825
+ harvestTypeLeaves((_o = (_m = n.childForFieldName) === null || _m === void 0 ? void 0 : _m.call(n, "return_type")) !== null && _o !== void 0 ? _o : null);
826
+ }
827
+ else if (n.type === "assignment") {
828
+ harvestTypeLeaves((_q = (_p = n.childForFieldName) === null || _p === void 0 ? void 0 : _p.call(n, "type")) !== null && _q !== void 0 ? _q : null);
829
+ }
830
+ else if (n.type === "class_definition") {
831
+ harvestTypeLeaves((_s = (_r = n.childForFieldName) === null || _r === void 0 ? void 0 : _r.call(n, "superclasses")) !== null && _s !== void 0 ? _s : null);
832
+ }
833
+ }
834
+ else if (lang === "c_sharp") {
835
+ // Method return (`returns`), parameter / property / variable
836
+ // (`type`), and the base/interface list.
837
+ if (n.type === "parameter" ||
838
+ n.type === "property_declaration" ||
839
+ n.type === "variable_declaration") {
840
+ harvestTypeLeaves((_u = (_t = n.childForFieldName) === null || _t === void 0 ? void 0 : _t.call(n, "type")) !== null && _u !== void 0 ? _u : null);
841
+ }
842
+ else if (n.type === "method_declaration") {
843
+ harvestTypeLeaves((_w = (_v = n.childForFieldName) === null || _v === void 0 ? void 0 : _v.call(n, "returns")) !== null && _w !== void 0 ? _w : null);
844
+ }
845
+ else if (n.type === "base_list") {
846
+ harvestTypeLeaves(n);
847
+ }
848
+ }
849
+ else if (lang === "php") {
850
+ // Types are `name` inside `named_type` / `optional_type`.
851
+ // Parameter / property (`type`), function & method return
852
+ // (`return_type`), and the extends / implements clauses.
853
+ if (n.type === "simple_parameter" ||
854
+ n.type === "property_declaration") {
855
+ harvestTypeLeaves((_y = (_x = n.childForFieldName) === null || _x === void 0 ? void 0 : _x.call(n, "type")) !== null && _y !== void 0 ? _y : null);
856
+ }
857
+ else if (n.type === "function_definition" ||
858
+ n.type === "method_declaration") {
859
+ harvestTypeLeaves((_0 = (_z = n.childForFieldName) === null || _z === void 0 ? void 0 : _z.call(n, "return_type")) !== null && _0 !== void 0 ? _0 : null);
860
+ }
861
+ else if (n.type === "base_clause" ||
862
+ n.type === "class_interface_clause") {
863
+ harvestTypeLeaves(n);
864
+ }
865
+ }
866
+ for (const child of (_1 = n.namedChildren) !== null && _1 !== void 0 ? _1 : []) {
741
867
  extractRefs(child);
742
868
  }
743
869
  };
870
+ // Collect locally-declared type-parameter names (`<T, K>`) so their
871
+ // *uses* (`value: T`) are excluded from type refs, not just their
872
+ // declarations. Chunk-scoped, which matches single-definition chunks.
873
+ const declaredTypeParams = new Set();
874
+ const collectTypeParams = (n) => {
875
+ var _a;
876
+ if (n.type === "type_parameter") {
877
+ const nm = firstNamed(n);
878
+ if ((nm === null || nm === void 0 ? void 0 : nm.type) === "type_identifier")
879
+ declaredTypeParams.add(nm.text);
880
+ }
881
+ for (const child of (_a = n.namedChildren) !== null && _a !== void 0 ? _a : [])
882
+ collectTypeParams(child);
883
+ };
884
+ collectTypeParams(effective);
744
885
  extractRefs(effective);
745
886
  // Classify role
746
887
  let role = "IMPLEMENTATION";
@@ -761,6 +902,7 @@ class TreeSitterChunker {
761
902
  isExported,
762
903
  definedSymbols,
763
904
  referencedSymbols,
905
+ typeReferencedSymbols,
764
906
  role,
765
907
  parentSymbol: stack.length > 1
766
908
  ? stack[stack.length - 1].replace(/^(Class|Method|Function|Interface|Type): /, "")
@@ -843,6 +985,9 @@ class TreeSitterChunker {
843
985
  if (sub.referencedSymbols) {
844
986
  sub.referencedSymbols = sub.referencedSymbols.filter(occurs);
845
987
  }
988
+ if (sub.typeReferencedSymbols) {
989
+ sub.typeReferencedSymbols = sub.typeReferencedSymbols.filter(occurs);
990
+ }
846
991
  return sub;
847
992
  }
848
993
  splitIfTooBig(chunk) {
@@ -61,10 +61,22 @@ exports.TOOLS = [
61
61
  parameters: {
62
62
  type: "object",
63
63
  properties: {
64
- query: { type: "string", description: "Natural language search query" },
65
- max_count: { type: "integer", description: "Max results (default 5)" },
66
- lang: { type: "string", description: "Filter by extension (e.g. 'ts', 'py')" },
67
- file: { type: "string", description: "Filter to files matching this name" },
64
+ query: {
65
+ type: "string",
66
+ description: "Natural language search query",
67
+ },
68
+ max_count: {
69
+ type: "integer",
70
+ description: "Max results (default 5)",
71
+ },
72
+ lang: {
73
+ type: "string",
74
+ description: "Filter by extension (e.g. 'ts', 'py')",
75
+ },
76
+ file: {
77
+ type: "string",
78
+ description: "Filter to files matching this name",
79
+ },
68
80
  },
69
81
  required: ["query"],
70
82
  },
@@ -79,7 +91,10 @@ exports.TOOLS = [
79
91
  type: "object",
80
92
  properties: {
81
93
  symbol: { type: "string", description: "Symbol name to trace" },
82
- depth: { type: "integer", description: "Caller depth 1-3 (default 1)" },
94
+ depth: {
95
+ type: "integer",
96
+ description: "Caller depth 1-3 (default 1)",
97
+ },
83
98
  },
84
99
  required: ["symbol"],
85
100
  },
@@ -94,7 +109,10 @@ exports.TOOLS = [
94
109
  type: "object",
95
110
  properties: {
96
111
  symbol: { type: "string", description: "Symbol name to peek at" },
97
- depth: { type: "integer", description: "Caller depth 1-3 (default 1)" },
112
+ depth: {
113
+ type: "integer",
114
+ description: "Caller depth 1-3 (default 1)",
115
+ },
98
116
  },
99
117
  required: ["symbol"],
100
118
  },
@@ -109,7 +127,10 @@ exports.TOOLS = [
109
127
  type: "object",
110
128
  properties: {
111
129
  target: { type: "string", description: "Symbol name or file path" },
112
- depth: { type: "integer", description: "Traversal depth 1-3 (default 1)" },
130
+ depth: {
131
+ type: "integer",
132
+ description: "Traversal depth 1-3 (default 1)",
133
+ },
113
134
  },
114
135
  required: ["target"],
115
136
  },
@@ -123,7 +144,10 @@ exports.TOOLS = [
123
144
  parameters: {
124
145
  type: "object",
125
146
  properties: {
126
- file: { type: "string", description: "File path relative to project root" },
147
+ file: {
148
+ type: "string",
149
+ description: "File path relative to project root",
150
+ },
127
151
  },
128
152
  required: ["file"],
129
153
  },
@@ -161,9 +185,13 @@ function executeSearch(args, ctx) {
161
185
  const rp = rel(absPath, ctx.projectRoot);
162
186
  const defs = (0, arrow_1.toArr)((_d = r.definedSymbols) !== null && _d !== void 0 ? _d : r.defined_symbols);
163
187
  const sym = defs[0] || "(anonymous)";
164
- const role = String((_e = r.role) !== null && _e !== void 0 ? _e : "IMPL").slice(0, 4).toUpperCase();
188
+ const role = String((_e = r.role) !== null && _e !== void 0 ? _e : "IMPL")
189
+ .slice(0, 4)
190
+ .toUpperCase();
165
191
  const startLine = (_j = (_h = (_f = r.startLine) !== null && _f !== void 0 ? _f : (_g = r.generated_metadata) === null || _g === void 0 ? void 0 : _g.start_line) !== null && _h !== void 0 ? _h : r.start_line) !== null && _j !== void 0 ? _j : 0;
166
- const hint = String((_l = (_k = r.text) !== null && _k !== void 0 ? _k : r.content) !== null && _l !== void 0 ? _l : "").split("\n")[0].slice(0, 100);
192
+ const hint = String((_l = (_k = r.text) !== null && _k !== void 0 ? _k : r.content) !== null && _l !== void 0 ? _l : "")
193
+ .split("\n")[0]
194
+ .slice(0, 100);
167
195
  return `${rp}:${startLine} ${sym} [${role}] — ${hint}`;
168
196
  });
169
197
  return lines.join("\n");
@@ -220,8 +248,12 @@ function executePeek(args, ctx) {
220
248
  .limit(1)
221
249
  .toArray();
222
250
  const exported = metaRows.length > 0 && Boolean(metaRows[0].is_exported);
223
- const startLine = metaRows.length > 0 ? Number(metaRows[0].start_line || 0) : center.line;
224
- const endLine = metaRows.length > 0 ? Number(metaRows[0].end_line || 0) : center.line;
251
+ const startLine = metaRows.length > 0
252
+ ? Number(metaRows[0].start_line || 0)
253
+ : center.line;
254
+ const endLine = metaRows.length > 0
255
+ ? Number(metaRows[0].end_line || 0)
256
+ : center.line;
225
257
  // Extract signature
226
258
  let sig = "(source not available)";
227
259
  try {
@@ -294,7 +326,11 @@ function executeRelated(args, ctx) {
294
326
  // Get file's symbols
295
327
  const fileChunks = yield table
296
328
  .query()
297
- .select(["defined_symbols", "referenced_symbols"])
329
+ .select([
330
+ "defined_symbols",
331
+ "referenced_symbols",
332
+ "type_referenced_symbols",
333
+ ])
298
334
  .where(`path = '${(0, filter_builder_1.escapeSqlString)(absPath)}'`)
299
335
  .toArray();
300
336
  if (fileChunks.length === 0)
@@ -306,6 +342,8 @@ function executeRelated(args, ctx) {
306
342
  definedHere.add(s);
307
343
  for (const s of (0, arrow_1.toArr)(chunk.referenced_symbols))
308
344
  referencedHere.add(s);
345
+ for (const s of (0, arrow_1.toArr)(chunk.type_referenced_symbols))
346
+ referencedHere.add(s);
309
347
  }
310
348
  // Dependencies: files that DEFINE symbols this file REFERENCES
311
349
  const depCounts = new Map();
@@ -331,7 +369,7 @@ function executeRelated(args, ctx) {
331
369
  const rows = yield table
332
370
  .query()
333
371
  .select(["path"])
334
- .where(`array_contains(referenced_symbols, '${(0, filter_builder_1.escapeSqlString)(sym)}')`)
372
+ .where(`(array_contains(referenced_symbols, '${(0, filter_builder_1.escapeSqlString)(sym)}') OR array_contains(type_referenced_symbols, '${(0, filter_builder_1.escapeSqlString)(sym)}'))`)
335
373
  .limit(20)
336
374
  .toArray();
337
375
  for (const row of rows) {
@@ -341,8 +379,12 @@ function executeRelated(args, ctx) {
341
379
  revCounts.set(p, (revCounts.get(p) || 0) + 1);
342
380
  }
343
381
  }
344
- const topDeps = [...depCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10);
345
- const topRevs = [...revCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10);
382
+ const topDeps = [...depCounts.entries()]
383
+ .sort((a, b) => b[1] - a[1])
384
+ .slice(0, 10);
385
+ const topRevs = [...revCounts.entries()]
386
+ .sort((a, b) => b[1] - a[1])
387
+ .slice(0, 10);
346
388
  if (topDeps.length === 0 && topRevs.length === 0)
347
389
  return "(none)";
348
390
  const lines = [];
@@ -48,9 +48,9 @@ const fs = __importStar(require("node:fs"));
48
48
  const lancedb = __importStar(require("@lancedb/lancedb"));
49
49
  const apache_arrow_1 = require("apache-arrow");
50
50
  const config_1 = require("../../config");
51
+ const cleanup_1 = require("../utils/cleanup");
51
52
  const filter_builder_1 = require("../utils/filter-builder");
52
53
  const logger_1 = require("../utils/logger");
53
- const cleanup_1 = require("../utils/cleanup");
54
54
  class DiskPressureError extends Error {
55
55
  constructor(message = "Disk critically low — writes suspended") {
56
56
  super(message);
@@ -266,6 +266,7 @@ class VectorDB {
266
266
  doc_token_ids: [],
267
267
  defined_symbols: [],
268
268
  referenced_symbols: [],
269
+ type_referenced_symbols: [],
269
270
  imports: [],
270
271
  exports: [],
271
272
  role: "",
@@ -308,6 +309,7 @@ class VectorDB {
308
309
  new apache_arrow_1.Field("doc_token_ids", new apache_arrow_1.List(new apache_arrow_1.Field("item", new apache_arrow_1.Int32(), true)), true),
309
310
  new apache_arrow_1.Field("defined_symbols", new apache_arrow_1.List(new apache_arrow_1.Field("item", new apache_arrow_1.Utf8(), true)), true),
310
311
  new apache_arrow_1.Field("referenced_symbols", new apache_arrow_1.List(new apache_arrow_1.Field("item", new apache_arrow_1.Utf8(), true)), true),
312
+ new apache_arrow_1.Field("type_referenced_symbols", new apache_arrow_1.List(new apache_arrow_1.Field("item", new apache_arrow_1.Utf8(), true)), true),
311
313
  new apache_arrow_1.Field("imports", new apache_arrow_1.List(new apache_arrow_1.Field("item", new apache_arrow_1.Utf8(), true)), true),
312
314
  new apache_arrow_1.Field("exports", new apache_arrow_1.List(new apache_arrow_1.Field("item", new apache_arrow_1.Utf8(), true)), true),
313
315
  new apache_arrow_1.Field("role", new apache_arrow_1.Utf8(), true),
@@ -316,12 +318,38 @@ class VectorDB {
316
318
  new apache_arrow_1.Field("summary", new apache_arrow_1.Utf8(), true),
317
319
  ]);
318
320
  }
321
+ /**
322
+ * In-place, non-breaking schema evolution for additive list columns. Older
323
+ * tables predate `type_referenced_symbols`; adding it via `addColumns` (rather
324
+ * than forcing `gmax index --reset`) keeps a live daemon's incremental writes
325
+ * working — existing rows read back as empty until their file is reindexed,
326
+ * which is when the new edges would populate anyway. Idempotent: re-adding an
327
+ * existing column throws, which we swallow.
328
+ */
329
+ evolveSchema(table) {
330
+ return __awaiter(this, void 0, void 0, function* () {
331
+ const schema = yield table.schema();
332
+ const fields = new Set(schema.fields.map((f) => f.name));
333
+ if (fields.has("type_referenced_symbols"))
334
+ return;
335
+ try {
336
+ yield table.addColumns(new apache_arrow_1.Field("type_referenced_symbols", new apache_arrow_1.List(new apache_arrow_1.Field("item", new apache_arrow_1.Utf8(), true)), true));
337
+ (0, logger_1.log)("db", "Added type_referenced_symbols column to existing table");
338
+ }
339
+ catch (err) {
340
+ // Lost a race with another writer that already added it, or a transient
341
+ // commit conflict — the next ensureTable() re-checks and no-ops.
342
+ (0, logger_1.debug)("vectordb", `evolveSchema skipped: ${err.message}`);
343
+ }
344
+ });
345
+ }
319
346
  ensureTable() {
320
347
  return __awaiter(this, void 0, void 0, function* () {
321
348
  const db = yield this.getDb();
322
349
  try {
323
350
  const table = yield db.openTable(TABLE_NAME);
324
351
  yield this.validateSchema(table);
352
+ yield this.evolveSchema(table);
325
353
  return table;
326
354
  }
327
355
  catch (_err) {
@@ -337,7 +365,7 @@ class VectorDB {
337
365
  }
338
366
  insertBatch(records) {
339
367
  return __awaiter(this, void 0, void 0, function* () {
340
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
368
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
341
369
  if (!records.length)
342
370
  return;
343
371
  this.ensureDiskOk();
@@ -410,12 +438,13 @@ class VectorDB {
410
438
  : null;
411
439
  rec.defined_symbols = (_g = rec.defined_symbols) !== null && _g !== void 0 ? _g : [];
412
440
  rec.referenced_symbols = (_h = rec.referenced_symbols) !== null && _h !== void 0 ? _h : [];
413
- rec.imports = (_j = rec.imports) !== null && _j !== void 0 ? _j : [];
414
- rec.exports = (_k = rec.exports) !== null && _k !== void 0 ? _k : [];
415
- rec.role = (_l = rec.role) !== null && _l !== void 0 ? _l : "";
416
- rec.parent_symbol = (_m = rec.parent_symbol) !== null && _m !== void 0 ? _m : "";
417
- rec.file_skeleton = (_o = rec.file_skeleton) !== null && _o !== void 0 ? _o : "";
418
- rec.summary = (_p = rec.summary) !== null && _p !== void 0 ? _p : null;
441
+ rec.type_referenced_symbols = (_j = rec.type_referenced_symbols) !== null && _j !== void 0 ? _j : [];
442
+ rec.imports = (_k = rec.imports) !== null && _k !== void 0 ? _k : [];
443
+ rec.exports = (_l = rec.exports) !== null && _l !== void 0 ? _l : [];
444
+ rec.role = (_m = rec.role) !== null && _m !== void 0 ? _m : "";
445
+ rec.parent_symbol = (_o = rec.parent_symbol) !== null && _o !== void 0 ? _o : "";
446
+ rec.file_skeleton = (_p = rec.file_skeleton) !== null && _p !== void 0 ? _p : "";
447
+ rec.summary = (_q = rec.summary) !== null && _q !== void 0 ? _q : null;
419
448
  }
420
449
  try {
421
450
  yield this.withWriteGate(() => table.add(records));
@@ -488,7 +517,9 @@ class VectorDB {
488
517
  const table = yield this.ensureTable();
489
518
  const cutoff = new Date(Date.now() - retentionMs);
490
519
  let resolveCompacting;
491
- this.compactingPromise = new Promise((r) => { resolveCompacting = r; });
520
+ this.compactingPromise = new Promise((r) => {
521
+ resolveCompacting = r;
522
+ });
492
523
  try {
493
524
  for (let attempt = 1; attempt <= retries; attempt++) {
494
525
  yield this.drainWrites();
@@ -519,7 +550,8 @@ class VectorDB {
519
550
  return;
520
551
  }
521
552
  // ENOSPC: return immediately — retrying will only make things worse
522
- if (msg.includes("No space left on device") || msg.includes("os error 28")) {
553
+ if (msg.includes("No space left on device") ||
554
+ msg.includes("os error 28")) {
523
555
  (0, logger_1.log)("vectordb", `Optimize failed (ENOSPC): disk full — skipping retries`);
524
556
  return;
525
557
  }
@@ -753,9 +785,7 @@ class VectorDB {
753
785
  yield this.withWriteGate(() => __awaiter(this, void 0, void 0, function* () {
754
786
  for (let i = 0; i < unique.length; i += batchSize) {
755
787
  const slice = unique.slice(i, i + batchSize);
756
- const values = slice
757
- .map((p) => `'${(0, filter_builder_1.escapeSqlString)(p)}'`)
758
- .join(",");
788
+ const values = slice.map((p) => `'${(0, filter_builder_1.escapeSqlString)(p)}'`).join(",");
759
789
  const where = `path IN (${values})${idExclusion}`;
760
790
  const existing = yield table
761
791
  .query()
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports._resetStaleHintForTests = _resetStaleHintForTests;
4
+ exports.maybeWarnStaleChunker = maybeWarnStaleChunker;
5
+ exports.maybeWarnStaleEmbedding = maybeWarnStaleEmbedding;
6
+ exports.maybeWarnCrossProjectDim = maybeWarnCrossProjectDim;
7
+ /**
8
+ * Query-time staleness nudges.
9
+ *
10
+ * When a graph/search command resolves a project whose index is out of date —
11
+ * built by an older chunker, or with a different embedding model/dim than the
12
+ * current config — emit a single concise line to STDERR. Writing to stderr
13
+ * (never stdout) keeps `--json` / `--agent` machine output byte-identical —
14
+ * agents parse stdout, so the hint never pollutes it. In `--agent` mode the line
15
+ * is rendered as a structured TSV record instead of prose so a tool that *does*
16
+ * capture stderr can parse it.
17
+ *
18
+ * Each concern fires at most once per process (independent latches, so a stale
19
+ * chunker does not mask a stale embedding). Suppress all with GMAX_NO_STALE_HINT=1.
20
+ */
21
+ const config_1 = require("../../config");
22
+ const index_config_1 = require("../index/index-config");
23
+ const project_registry_1 = require("./project-registry");
24
+ let chunkerEmitted = false;
25
+ let embeddingEmitted = false;
26
+ let crossDimEmitted = false;
27
+ /** Reset the once-per-process latches. Test-only. */
28
+ function _resetStaleHintForTests() {
29
+ chunkerEmitted = false;
30
+ embeddingEmitted = false;
31
+ crossDimEmitted = false;
32
+ }
33
+ function maybeWarnStaleChunker(projectRoot, opts) {
34
+ if (chunkerEmitted)
35
+ return;
36
+ if (!projectRoot)
37
+ return;
38
+ if (process.env.GMAX_NO_STALE_HINT === "1")
39
+ return;
40
+ const project = (0, project_registry_1.getProject)(projectRoot);
41
+ // Only nudge for an index that is supposed to be complete. A "pending"
42
+ // project is mid-index (will land current); "error" is ignored by the
43
+ // daemon; an unregistered root would already have errored upstream.
44
+ if (!project)
45
+ return;
46
+ if (project.status && project.status !== "indexed")
47
+ return;
48
+ const gap = (0, config_1.describeChunkerGap)(project.chunkerVersion);
49
+ if (!gap)
50
+ return;
51
+ // Latch only once we have something to say, so a suppressed-by-status call
52
+ // does not silence a later command in the same process (CLI is one command
53
+ // per process, but search resolves the root in two places).
54
+ chunkerEmitted = true;
55
+ const name = project.name || projectRoot;
56
+ if (opts === null || opts === void 0 ? void 0 : opts.agent) {
57
+ const fields = [
58
+ "stale_chunker",
59
+ `project=${name}`,
60
+ `indexed_v=${gap.fromVersion}`,
61
+ `current_v=${gap.toVersion}`,
62
+ `severity=${gap.severity}`,
63
+ `note=${gap.notes.join("; ")}`,
64
+ "fix=gmax index --reset",
65
+ ].join("\t");
66
+ process.stderr.write(`${fields}\n`);
67
+ return;
68
+ }
69
+ const label = gap.severity === "breaking" ? "WARN" : "hint";
70
+ process.stderr.write(`${label} gmax: '${name}' indexed by chunker v${gap.fromVersion} (now v${gap.toVersion}) — ${gap.notes.join(" ")} Run 'gmax index --reset' to refresh. (silence: GMAX_NO_STALE_HINT=1)\n`);
71
+ }
72
+ /**
73
+ * Nudge when a project's stored embedding model/dim differs from the current
74
+ * global config. A dim change is `breaking` (search scores are invalid until a
75
+ * re-embed); a same-dim model swap is `additive` (results just mix models).
76
+ * Mirrors maybeWarnStaleChunker's discipline: stderr-only, once-per-process,
77
+ * `--agent` TSV, GMAX_NO_STALE_HINT-suppressible.
78
+ */
79
+ function maybeWarnStaleEmbedding(projectRoot, opts) {
80
+ if (embeddingEmitted)
81
+ return;
82
+ if (!projectRoot)
83
+ return;
84
+ if (process.env.GMAX_NO_STALE_HINT === "1")
85
+ return;
86
+ const project = (0, project_registry_1.getProject)(projectRoot);
87
+ if (!project)
88
+ return;
89
+ if (project.status && project.status !== "indexed")
90
+ return;
91
+ const current = (0, index_config_1.readGlobalConfig)();
92
+ const gap = (0, config_1.describeEmbeddingGap)({ modelTier: project.modelTier, vectorDim: project.vectorDim }, { modelTier: current.modelTier, vectorDim: current.vectorDim });
93
+ if (!gap)
94
+ return;
95
+ embeddingEmitted = true;
96
+ const name = project.name || projectRoot;
97
+ if (opts === null || opts === void 0 ? void 0 : opts.agent) {
98
+ const fields = [
99
+ "stale_embedding",
100
+ `project=${name}`,
101
+ `indexed_model=${gap.fromModel}`,
102
+ `current_model=${gap.toModel}`,
103
+ `indexed_dim=${gap.fromDim}`,
104
+ `current_dim=${gap.toDim}`,
105
+ `dim_changed=${gap.dimChanged}`,
106
+ `severity=${gap.severity}`,
107
+ "fix=gmax index --reset",
108
+ ].join("\t");
109
+ process.stderr.write(`${fields}\n`);
110
+ return;
111
+ }
112
+ const label = gap.severity === "breaking" ? "WARN" : "hint";
113
+ const detail = gap.dimChanged
114
+ ? `vector dim ${gap.fromDim}→${gap.toDim} (incompatible — scores invalid until re-embed)`
115
+ : `model '${gap.fromModel}'→'${gap.toModel}' (same dim; results mix models until re-embed)`;
116
+ process.stderr.write(`${label} gmax: '${name}' indexed with embedding ${detail}. Run 'gmax index --reset'. (silence: GMAX_NO_STALE_HINT=1)\n`);
117
+ }
118
+ /**
119
+ * Cross-project guard: when a `--all-projects` / `--projects` search spans
120
+ * projects whose stored vector widths disagree (with each other or with the
121
+ * current query width), the shared fixed-dim table cannot answer all of them
122
+ * correctly — mismatched vectors were padded/truncated at insert and score as
123
+ * noise. Warn (stderr) so the caller can re-embed or exclude them, rather than
124
+ * silently returning invalid cross-project rankings.
125
+ */
126
+ function maybeWarnCrossProjectDim(roots, opts) {
127
+ var _a, _b, _c, _d, _e, _f, _g;
128
+ if (crossDimEmitted)
129
+ return;
130
+ if (process.env.GMAX_NO_STALE_HINT === "1")
131
+ return;
132
+ if (!roots || roots.length === 0)
133
+ return;
134
+ const current = (0, index_config_1.readGlobalConfig)();
135
+ const currentDim = (_d = (_a = current.vectorDim) !== null && _a !== void 0 ? _a : (_c = config_1.MODEL_TIERS[(_b = current.modelTier) !== null && _b !== void 0 ? _b : config_1.DEFAULT_MODEL_TIER]) === null || _c === void 0 ? void 0 : _c.vectorDim) !== null && _d !== void 0 ? _d : config_1.CONFIG.VECTOR_DIM;
136
+ const byRoot = new Map((0, project_registry_1.listProjects)().map((p) => [p.root, p]));
137
+ const dims = new Set([currentDim]);
138
+ const mismatched = [];
139
+ for (const r of roots) {
140
+ const p = byRoot.get(r.root);
141
+ if (!p)
142
+ continue;
143
+ const dim = (_g = (_e = p.vectorDim) !== null && _e !== void 0 ? _e : (_f = config_1.MODEL_TIERS[p.modelTier]) === null || _f === void 0 ? void 0 : _f.vectorDim) !== null && _g !== void 0 ? _g : currentDim;
144
+ dims.add(dim);
145
+ if (dim !== currentDim)
146
+ mismatched.push({ name: p.name || r.name, dim });
147
+ }
148
+ // All in-scope projects share the query width → nothing to warn about.
149
+ if (dims.size <= 1)
150
+ return;
151
+ crossDimEmitted = true;
152
+ if (opts === null || opts === void 0 ? void 0 : opts.agent) {
153
+ const fields = [
154
+ "stale_embedding_crossdim",
155
+ `query_dim=${currentDim}`,
156
+ `mismatched=${mismatched.map((m) => `${m.name}:${m.dim}`).join(",")}`,
157
+ "fix=re-embed or --exclude-projects",
158
+ ].join("\t");
159
+ process.stderr.write(`${fields}\n`);
160
+ return;
161
+ }
162
+ const list = mismatched.map((m) => `${m.name} (${m.dim}d)`).join(", ");
163
+ process.stderr.write(`WARN gmax: cross-project search mixes embedding dims (query ${currentDim}d) — ${list} return invalid scores. Re-embed them or drop with --exclude-projects. (silence: GMAX_NO_STALE_HINT=1)\n`);
164
+ }