grepmax 0.7.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.

Potentially problematic release.


This version of grepmax might be problematic. Click here for more details.

Files changed (70) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +33 -0
  3. package/README.md +375 -0
  4. package/dist/commands/claude-code.js +60 -0
  5. package/dist/commands/codex.js +98 -0
  6. package/dist/commands/doctor.js +92 -0
  7. package/dist/commands/droid.js +189 -0
  8. package/dist/commands/index.js +125 -0
  9. package/dist/commands/list.js +120 -0
  10. package/dist/commands/mcp.js +572 -0
  11. package/dist/commands/opencode.js +199 -0
  12. package/dist/commands/search.js +539 -0
  13. package/dist/commands/serve.js +512 -0
  14. package/dist/commands/setup.js +162 -0
  15. package/dist/commands/skeleton.js +288 -0
  16. package/dist/commands/symbols.js +129 -0
  17. package/dist/commands/trace.js +50 -0
  18. package/dist/commands/verify.js +174 -0
  19. package/dist/config.js +120 -0
  20. package/dist/eval.js +618 -0
  21. package/dist/index.js +82 -0
  22. package/dist/lib/core/languages.js +237 -0
  23. package/dist/lib/graph/graph-builder.js +105 -0
  24. package/dist/lib/index/chunker.js +663 -0
  25. package/dist/lib/index/grammar-loader.js +110 -0
  26. package/dist/lib/index/ignore-patterns.js +63 -0
  27. package/dist/lib/index/index-config.js +86 -0
  28. package/dist/lib/index/sync-helpers.js +97 -0
  29. package/dist/lib/index/syncer.js +396 -0
  30. package/dist/lib/index/walker.js +164 -0
  31. package/dist/lib/index/watcher.js +245 -0
  32. package/dist/lib/output/formatter.js +161 -0
  33. package/dist/lib/output/json-formatter.js +6 -0
  34. package/dist/lib/search/intent.js +23 -0
  35. package/dist/lib/search/searcher.js +475 -0
  36. package/dist/lib/setup/model-loader.js +107 -0
  37. package/dist/lib/setup/setup-helpers.js +106 -0
  38. package/dist/lib/skeleton/body-fields.js +175 -0
  39. package/dist/lib/skeleton/index.js +24 -0
  40. package/dist/lib/skeleton/retriever.js +36 -0
  41. package/dist/lib/skeleton/skeletonizer.js +483 -0
  42. package/dist/lib/skeleton/summary-formatter.js +90 -0
  43. package/dist/lib/store/meta-cache.js +143 -0
  44. package/dist/lib/store/types.js +2 -0
  45. package/dist/lib/store/vector-db.js +340 -0
  46. package/dist/lib/utils/cleanup.js +33 -0
  47. package/dist/lib/utils/exit.js +38 -0
  48. package/dist/lib/utils/file-utils.js +131 -0
  49. package/dist/lib/utils/filter-builder.js +17 -0
  50. package/dist/lib/utils/formatter.js +230 -0
  51. package/dist/lib/utils/git.js +83 -0
  52. package/dist/lib/utils/lock.js +157 -0
  53. package/dist/lib/utils/project-root.js +107 -0
  54. package/dist/lib/utils/server-registry.js +97 -0
  55. package/dist/lib/workers/colbert-math.js +107 -0
  56. package/dist/lib/workers/colbert-tokenizer.js +113 -0
  57. package/dist/lib/workers/download-worker.js +169 -0
  58. package/dist/lib/workers/embeddings/colbert.js +213 -0
  59. package/dist/lib/workers/embeddings/granite.js +180 -0
  60. package/dist/lib/workers/embeddings/mlx-client.js +144 -0
  61. package/dist/lib/workers/orchestrator.js +350 -0
  62. package/dist/lib/workers/pool.js +373 -0
  63. package/dist/lib/workers/process-child.js +92 -0
  64. package/dist/lib/workers/worker.js +31 -0
  65. package/package.json +80 -0
  66. package/plugins/osgrep/.claude-plugin/plugin.json +20 -0
  67. package/plugins/osgrep/hooks/start.js +92 -0
  68. package/plugins/osgrep/hooks/stop.js +3 -0
  69. package/plugins/osgrep/hooks.json +26 -0
  70. package/plugins/osgrep/skills/osgrep/SKILL.md +82 -0
package/dist/eval.js ADDED
@@ -0,0 +1,618 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var _a;
12
+ var _b;
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.cases = void 0;
15
+ exports.evaluateCase = evaluateCase;
16
+ // Reduce worker pool fan-out during eval to avoid ONNX concurrency issues
17
+ (_a = (_b = process.env).OSGREP_WORKER_COUNT) !== null && _a !== void 0 ? _a : (_b.OSGREP_WORKER_COUNT = "1");
18
+ const searcher_1 = require("./lib/search/searcher");
19
+ const vector_db_1 = require("./lib/store/vector-db");
20
+ const exit_1 = require("./lib/utils/exit");
21
+ const project_root_1 = require("./lib/utils/project-root");
22
+ exports.cases = [
23
+ // --- Search & Ranking ---
24
+ {
25
+ query: "How do we merge vector and keyword results before rerank?",
26
+ expectedPath: "src/lib/search/searcher.ts",
27
+ note: "Hybrid search path that stitches LanceDB vector search with FTS.",
28
+ },
29
+ {
30
+ query: "Where do we dedupe overlapping vector and FTS candidates?",
31
+ expectedPath: "src/lib/search/searcher.ts",
32
+ note: "Combines results and removes duplicates ahead of rerank.",
33
+ },
34
+ {
35
+ query: "How do we boost functions and downweight tests or docs?",
36
+ expectedPath: "src/lib/search/searcher.ts",
37
+ note: "applyStructureBoost handles path/type based adjustments.",
38
+ },
39
+ {
40
+ query: "What controls the pre-rerank candidate fanout?",
41
+ expectedPath: "src/lib/search/searcher.ts",
42
+ note: "PRE_RERANK_K calculation before ColBERT scoring.",
43
+ },
44
+ {
45
+ query: "How do we filter searches to a path prefix?",
46
+ expectedPath: "src/lib/search/searcher.ts",
47
+ note: "Path prefix WHERE clause for scoped queries.",
48
+ },
49
+ {
50
+ query: "How do we apply ColBERT rerank scoring to candidates?",
51
+ expectedPath: "src/lib/workers/orchestrator.ts",
52
+ note: "Worker-side rerank that feeds query/docs into maxSim.",
53
+ },
54
+ {
55
+ query: "ColBERT maxSim scoring implementation",
56
+ expectedPath: "src/lib/workers/colbert-math.ts",
57
+ note: "Summed max dot products between query and doc token grids.",
58
+ },
59
+ // --- Worker Pool & Embeddings ---
60
+ {
61
+ query: "Why are ONNX workers child processes instead of threads?",
62
+ expectedPath: "src/lib/workers/pool.ts",
63
+ note: "Process pool choice to isolate runtime crashes.",
64
+ },
65
+ {
66
+ query: "How do we timeout and restart stuck worker tasks?",
67
+ expectedPath: "src/lib/workers/pool.ts",
68
+ note: "Task timeout handling that kills and respawns workers.",
69
+ },
70
+ {
71
+ query: "Which script does the worker pool fork at runtime?",
72
+ expectedPath: "src/lib/workers/pool.ts",
73
+ note: "resolveProcessWorker chooses process-child entrypoint.",
74
+ },
75
+ {
76
+ query: "How does worker pool shutdown terminate children?",
77
+ expectedPath: "src/lib/workers/pool.ts",
78
+ note: "destroy() kills processes with SIGTERM/SIGKILL fallback.",
79
+ },
80
+ {
81
+ query: "Where are Granite embeddings loaded from onnx cache?",
82
+ expectedPath: "src/lib/workers/embeddings/granite.ts",
83
+ note: "resolvePaths + load selecting ONNX weights and tokenizer.",
84
+ },
85
+ {
86
+ query: "How do we mean-pool Granite outputs to 384 dimensions?",
87
+ expectedPath: "src/lib/workers/embeddings/granite.ts",
88
+ note: "meanPool normalizes and pads vectors to CONFIG.VECTOR_DIM.",
89
+ },
90
+ {
91
+ query: "How does ColBERT quantize token grids to int8 with a scale?",
92
+ expectedPath: "src/lib/workers/embeddings/colbert.ts",
93
+ note: "runBatch builds int8 arrays and records maxVal scale.",
94
+ },
95
+ {
96
+ query: "Where do we compute pooled_colbert_48d summaries?",
97
+ expectedPath: "src/lib/workers/embeddings/colbert.ts",
98
+ note: "Per-chunk pooled embedding stored alongside dense vectors.",
99
+ },
100
+ {
101
+ query: "How do we normalize ColBERT query embeddings before rerank?",
102
+ expectedPath: "src/lib/workers/orchestrator.ts",
103
+ note: "encodeQuery builds normalized matrix from ONNX output.",
104
+ },
105
+ {
106
+ query: "How are dense and ColBERT embeddings combined for each chunk?",
107
+ expectedPath: "src/lib/workers/orchestrator.ts",
108
+ note: "computeHybrid pairs Granite dense vectors with ColBERT grids.",
109
+ },
110
+ {
111
+ query: "Where do we build anchor chunks with imports and preamble?",
112
+ expectedPath: "src/lib/index/chunker.ts",
113
+ note: "buildAnchorChunk prepends metadata-heavy anchor blocks.",
114
+ },
115
+ {
116
+ query: "How is breadcrumb formatting added to chunk text?",
117
+ expectedPath: "src/lib/index/chunker.ts",
118
+ note: "formatChunkText injects file + context headers.",
119
+ },
120
+ {
121
+ query: "What are the chunk overlap and max size settings?",
122
+ expectedPath: "src/lib/index/chunker.ts",
123
+ note: "MAX_CHUNK_LINES/CHARS and OVERLAP tuning in TreeSitterChunker.",
124
+ },
125
+ {
126
+ query: "How do we fall back when a Tree-sitter grammar is missing?",
127
+ expectedPath: "src/lib/index/chunker.ts",
128
+ note: "chunk() fallback path when parser/grammar cannot load.",
129
+ },
130
+ {
131
+ query: "Where are grammars downloaded and cached?",
132
+ expectedPath: "src/lib/index/grammar-loader.ts",
133
+ note: "GRAMMARS_DIR and ensureGrammars downloader.",
134
+ },
135
+ {
136
+ query: "Which languages and grammars are supported for chunking?",
137
+ expectedPath: "src/lib/core/languages.ts",
138
+ note: "LANGUAGES table that maps extensions to grammars.",
139
+ },
140
+ // --- Indexing & Sync ---
141
+ {
142
+ query: "Where do we enforce a writer lock to prevent concurrent indexing?",
143
+ expectedPath: "src/lib/utils/lock.ts",
144
+ note: "LOCK file acquisition and stale process detection.",
145
+ },
146
+ {
147
+ query: "Where is DEFAULT_IGNORE_PATTERNS defined for indexing?",
148
+ expectedPath: "src/lib/index/ignore-patterns.ts",
149
+ note: "DEFAULT_IGNORE_PATTERNS with lockfiles and secrets.",
150
+ },
151
+ {
152
+ query: "Which INDEXABLE_EXTENSIONS are allowed and what is the 10MB limit?",
153
+ expectedPath: "src/config.ts",
154
+ note: "INDEXABLE_EXTENSIONS and MAX_FILE_SIZE_BYTES.",
155
+ },
156
+ {
157
+ query: "How do we reset when VectorDB and meta cache disagree?",
158
+ expectedPath: "src/lib/index/syncer.ts",
159
+ note: "Inconsistency detection that forces drop + rebuild.",
160
+ },
161
+ {
162
+ query: "How are batches flushed to LanceDB before updating meta cache?",
163
+ expectedPath: "src/lib/index/syncer.ts",
164
+ note: "flushBatch writes VectorDB first, then meta entries.",
165
+ },
166
+ {
167
+ query: "How do we remove stale or deleted paths from the index?",
168
+ expectedPath: "src/lib/index/syncer.ts",
169
+ note: "Cleanup of stale paths after scanning is finished.",
170
+ },
171
+ {
172
+ query: "How do we skip unchanged files using mtime/size hashes?",
173
+ expectedPath: "src/lib/index/syncer.ts",
174
+ note: "Meta cache check to bypass re-embedding identical files.",
175
+ },
176
+ {
177
+ query: "When does processFile mark shouldDelete for binary, empty, or too-big files?",
178
+ expectedPath: "src/lib/workers/orchestrator.ts",
179
+ note: "processFile returns shouldDelete for non-indexable snapshots.",
180
+ },
181
+ {
182
+ query: "How is the file hash computed for change detection?",
183
+ expectedPath: "src/lib/utils/file-utils.ts",
184
+ note: "computeBufferHash SHA-256 helper.",
185
+ },
186
+ {
187
+ query: "How do we snapshot a file and verify it didn't change during read?",
188
+ expectedPath: "src/lib/utils/file-utils.ts",
189
+ note: "readFileSnapshot double-checks size/mtime before returning.",
190
+ },
191
+ // --- Storage & Schema ---
192
+ {
193
+ query: "Where is the LanceDB schema defined, including pooled_colbert_48d?",
194
+ expectedPath: "src/lib/store/vector-db.ts",
195
+ note: "Schema with dense, colbert, and pooled embeddings.",
196
+ },
197
+ {
198
+ query: "How do we warn about schema mismatches and ask for a reindex?",
199
+ expectedPath: "src/lib/store/vector-db.ts",
200
+ note: "insertBatch error message for field mismatches.",
201
+ },
202
+ {
203
+ query: "Where do we create the full-text index on chunk content?",
204
+ expectedPath: "src/lib/store/vector-db.ts",
205
+ note: "createFTSIndex invoking LanceDB FTS.",
206
+ },
207
+ // --- CLI Commands ---
208
+ {
209
+ query: "How does the search command trigger initial indexing when the store is empty?",
210
+ expectedPath: "src/commands/search.ts",
211
+ note: "Checks hasAnyRows and runs initialSync + spinner.",
212
+ },
213
+ {
214
+ query: "Where does search --dry-run print formatDryRunSummary?",
215
+ expectedPath: "src/commands/search.ts",
216
+ note: "formatDryRunSummary usage for dry-run summaries.",
217
+ },
218
+ {
219
+ query: "How does the index command handle --reset then call createFTSIndex?",
220
+ expectedPath: "src/commands/index.ts",
221
+ note: "Indexing workflow before createFTSIndex.",
222
+ },
223
+ {
224
+ query: "How does serve reject search paths outside the project root?",
225
+ expectedPath: "src/commands/serve.ts",
226
+ note: "Path normalization rejecting traversal outside projectRoot.",
227
+ },
228
+ {
229
+ query: "Where does the server enforce a 1MB payload size limit?",
230
+ expectedPath: "src/commands/serve.ts",
231
+ note: "Request body guard that 413s payloads over 1MB.",
232
+ },
233
+ {
234
+ query: "How does serve --background redirect logs to ~/.osgrep/logs/server.log?",
235
+ expectedPath: "src/commands/serve.ts",
236
+ note: "Background flag redirecting stdio to server.log.",
237
+ },
238
+ {
239
+ query: "Where does setup ensureSetup runs and grammars get downloaded?",
240
+ expectedPath: "src/commands/setup.ts",
241
+ note: "Setup command invoking ensureSetup and ensureGrammars.",
242
+ },
243
+ {
244
+ query: "How does doctor check PATHS.models for missing model directories?",
245
+ expectedPath: "src/commands/doctor.ts",
246
+ note: "Health checks for PATHS.models and MODEL_IDS.",
247
+ },
248
+ {
249
+ query: "Where is Claude Code plugin installation defined?",
250
+ expectedPath: "src/commands/claude-code.ts",
251
+ note: "Marketplace add + install flow.",
252
+ },
253
+ // --- Paths, Config, Environment ---
254
+ {
255
+ query: "How do we create .osgrep directories and add them to .gitignore?",
256
+ expectedPath: "src/lib/utils/project-root.ts",
257
+ note: "ensureProjectPaths scaffolds directories and gitignore entry.",
258
+ },
259
+ {
260
+ query: "How is the project root detected via .git or existing .osgrep?",
261
+ expectedPath: "src/lib/utils/project-root.ts",
262
+ note: "findProjectRoot walking parents and honoring repo roots.",
263
+ },
264
+ {
265
+ query: "Where are PATHS.globalRoot, models, and grammars defined?",
266
+ expectedPath: "src/config.ts",
267
+ note: "PATHS pointing to ~/.osgrep directories.",
268
+ },
269
+ {
270
+ query: "How do workers prefer a local ./models directory when present?",
271
+ expectedPath: "src/lib/workers/orchestrator.ts",
272
+ note: "env.localModelPath override when repo ships models.",
273
+ },
274
+ {
275
+ query: "Where are VECTOR_DIM, COLBERT_DIM, and WORKER_THREADS configured?",
276
+ expectedPath: "src/config.ts",
277
+ note: "CONFIG with VECTOR_DIM, COLBERT_DIM, WORKER_THREADS.",
278
+ },
279
+ // --- Extended Coverage ---
280
+ {
281
+ query: "Where do we read WORKER_TIMEOUT_MS from OSGREP_WORKER_TIMEOUT_MS?",
282
+ expectedPath: "src/config.ts",
283
+ note: "WORKER_TIMEOUT_MS env override.",
284
+ },
285
+ {
286
+ query: "Where is TASK_TIMEOUT_MS set for worker tasks?",
287
+ expectedPath: "src/lib/workers/pool.ts",
288
+ note: "OSGREP_WORKER_TASK_TIMEOUT_MS guarded timeout.",
289
+ },
290
+ {
291
+ query: "How do we cap worker threads from OSGREP_WORKER_THREADS with a HARD_CAP of 4?",
292
+ expectedPath: "src/config.ts",
293
+ note: "DEFAULT_WORKER_THREADS calculation.",
294
+ },
295
+ {
296
+ query: "Where do we set HF transformers cacheDir and allowLocalModels?",
297
+ expectedPath: "src/lib/workers/orchestrator.ts",
298
+ note: "env.cacheDir and env.allowLocalModels toggles.",
299
+ },
300
+ {
301
+ query: "Where do we load Granite ONNX with CPU execution providers?",
302
+ expectedPath: "src/lib/workers/embeddings/granite.ts",
303
+ note: "load() builds sessionOptions for cpu backend.",
304
+ },
305
+ {
306
+ query: "Where do we limit ColBERT ONNX runtime threads to 1?",
307
+ expectedPath: "src/lib/workers/embeddings/colbert.ts",
308
+ note: "ONNX_THREADS constant and session options.",
309
+ },
310
+ {
311
+ query: "How do we normalize ColBERT doc vectors and quantize to int8 scale?",
312
+ expectedPath: "src/lib/workers/embeddings/colbert.ts",
313
+ note: "runBatch builds normalized grid and scale factor.",
314
+ },
315
+ {
316
+ query: "Where do we normalize ColBERT query rows before building matrix?",
317
+ expectedPath: "src/lib/workers/orchestrator.ts",
318
+ note: "encodeQuery normalizes ONNX output rows.",
319
+ },
320
+ {
321
+ query: "Where do we convert serialized Buffer objects to Int8Array for rerank?",
322
+ expectedPath: "src/lib/workers/orchestrator.ts",
323
+ note: "rerank converts Buffer/object into Int8Array.",
324
+ },
325
+ {
326
+ query: "Where do we build UUIDs for chunk ids before inserting to LanceDB?",
327
+ expectedPath: "src/lib/workers/orchestrator.ts",
328
+ note: "toPreparedChunks uses uuidv4 for chunk IDs.",
329
+ },
330
+ {
331
+ query: "How do we include imports, exports, and top comments in anchor chunks?",
332
+ expectedPath: "src/lib/index/chunker.ts",
333
+ note: "buildAnchorChunk composes sections with metadata.",
334
+ },
335
+ {
336
+ query: "Where do we warn about missing tree-sitter grammars and fall back?",
337
+ expectedPath: "src/lib/index/chunker.ts",
338
+ note: "chunk() logs and falls back when getLanguage fails.",
339
+ },
340
+ {
341
+ query: "Where do we split oversized chunks with line and char overlaps?",
342
+ expectedPath: "src/lib/index/chunker.ts",
343
+ note: "splitIfTooBig uses OVERLAP_LINES and OVERLAP_CHARS.",
344
+ },
345
+ {
346
+ query: "Where is GRAMMARS_DIR set to ~/.osgrep/grammars?",
347
+ expectedPath: "src/lib/index/grammar-loader.ts",
348
+ note: "GRAMMARS_DIR constant.",
349
+ },
350
+ {
351
+ query: "Where do we download grammars with fetch and a custom User-Agent?",
352
+ expectedPath: "src/lib/index/grammar-loader.ts",
353
+ note: "ensureGrammars downloadFile helper.",
354
+ },
355
+ {
356
+ query: "Where do we guard against files changing during read?",
357
+ expectedPath: "src/lib/utils/file-utils.ts",
358
+ note: "readFileSnapshot compares pre/post stats.",
359
+ },
360
+ {
361
+ query: "Where do we detect null bytes before indexing content?",
362
+ expectedPath: "src/lib/utils/file-utils.ts",
363
+ note: "hasNullByte check in processFile path.",
364
+ },
365
+ {
366
+ query: "Where do we register cleanup tasks and execute them at exit?",
367
+ expectedPath: "src/lib/utils/cleanup.ts",
368
+ note: "registerCleanup and runCleanup functions.",
369
+ },
370
+ {
371
+ query: "Where does gracefulExit destroy the worker pool before exiting?",
372
+ expectedPath: "src/lib/utils/exit.ts",
373
+ note: "gracefulExit calls destroyWorkerPool and runCleanup.",
374
+ },
375
+ {
376
+ query: "Where is the LMDB meta cache opened with compression?",
377
+ expectedPath: "src/lib/store/meta-cache.ts",
378
+ note: "MetaCache constructor uses lmdb open() with compression.",
379
+ },
380
+ {
381
+ query: "Where do we connect to LanceDB and seed the table schema?",
382
+ expectedPath: "src/lib/store/vector-db.ts",
383
+ note: "ensureTable creates schema and deletes seed row.",
384
+ },
385
+ {
386
+ query: "Where do we drop the LanceDB table during resets?",
387
+ expectedPath: "src/lib/store/vector-db.ts",
388
+ note: "drop() helper invoked on reset.",
389
+ },
390
+ {
391
+ query: "Where do we close LanceDB connections and unregister cleanup?",
392
+ expectedPath: "src/lib/store/vector-db.ts",
393
+ note: "close() method clears connections and cleanup hook.",
394
+ },
395
+ {
396
+ query: "Where do we use fast-glob to stream files for indexing?",
397
+ expectedPath: "src/lib/index/syncer.ts",
398
+ note: "fg.stream with glob options for repo walk.",
399
+ },
400
+ {
401
+ query: "Where do we skip duplicate real paths and broken symlinks?",
402
+ expectedPath: "src/lib/index/syncer.ts",
403
+ note: "visitedRealPaths plus try/catch around realpathSync.",
404
+ },
405
+ {
406
+ query: "Where do we abort indexing when AbortSignal is triggered?",
407
+ expectedPath: "src/lib/index/syncer.ts",
408
+ note: "Checks signal.aborted to stop scheduling.",
409
+ },
410
+ {
411
+ query: "Where do we flush batches when batch/deletes/meta reach batchLimit?",
412
+ expectedPath: "src/lib/index/syncer.ts",
413
+ note: "flush() checks batchLimit based on EMBED_BATCH_SIZE.",
414
+ },
415
+ {
416
+ query: "Where do we detect stale cached paths and delete them after indexing?",
417
+ expectedPath: "src/lib/index/syncer.ts",
418
+ note: "Removes stale paths from VectorDB and meta cache.",
419
+ },
420
+ {
421
+ query: "Where do we detect inconsistent VectorDB vs meta cache and force rebuild?",
422
+ expectedPath: "src/lib/index/syncer.ts",
423
+ note: "isInconsistent triggers drop and meta reset.",
424
+ },
425
+ {
426
+ query: "Where is createIndexingSpinner updating text for scanning and indexing files?",
427
+ expectedPath: "src/lib/index/sync-helpers.ts",
428
+ note: "createIndexingSpinner onProgress formatting.",
429
+ },
430
+ {
431
+ query: "Where does ensureSetup create ~/.osgrep directories with ora spinner?",
432
+ expectedPath: "src/lib/setup/setup-helpers.ts",
433
+ note: "ensureSetup directory creation feedback.",
434
+ },
435
+ {
436
+ query: "Where do we download models via a worker thread to avoid ONNX in main thread?",
437
+ expectedPath: "src/lib/setup/model-loader.ts",
438
+ note: "downloadModels spawns worker with ts-node/register when dev.",
439
+ },
440
+ {
441
+ query: "Where do we check areModelsDownloaded before running setup?",
442
+ expectedPath: "src/lib/setup/model-loader.ts",
443
+ note: "areModelsDownloaded verifies cache directories.",
444
+ },
445
+ {
446
+ query: "Where does setup command list model and grammar status after finishing?",
447
+ expectedPath: "src/commands/setup.ts",
448
+ note: "Setup command status output with model IDs.",
449
+ },
450
+ {
451
+ query: "Where does doctor print system platform, arch, and Node version?",
452
+ expectedPath: "src/commands/doctor.ts",
453
+ note: "Doctor command system info logging.",
454
+ },
455
+ {
456
+ query: "Where does the list command calculate directory sizes recursively?",
457
+ expectedPath: "src/commands/list.ts",
458
+ note: "getDirectorySize walk.",
459
+ },
460
+ {
461
+ query: "Where does the list command format sizes and time ago text?",
462
+ expectedPath: "src/commands/list.ts",
463
+ note: "formatSize and formatDate helpers.",
464
+ },
465
+ {
466
+ query: "Where does serve register running servers to servers.json?",
467
+ expectedPath: "src/lib/utils/server-registry.ts",
468
+ note: "registerServer writes to ~/.osgrep/servers.json.",
469
+ },
470
+ {
471
+ query: "How does serve status enumerate active servers?",
472
+ expectedPath: "src/commands/serve.ts",
473
+ note: "serve status subcommand uses listServers().",
474
+ },
475
+ {
476
+ query: "How does serve stop --all kill background servers?",
477
+ expectedPath: "src/commands/serve.ts",
478
+ note: "serve stop iterates listServers and SIGTERMs.",
479
+ },
480
+ {
481
+ query: "Where is the LOCK file written and stale PID detection handled?",
482
+ expectedPath: "src/lib/utils/lock.ts",
483
+ note: "acquireWriterLock parses existing lock with pid/start time.",
484
+ },
485
+ {
486
+ query: "Where do we parse .git worktree files to find the main repo root?",
487
+ expectedPath: "src/lib/utils/git.ts",
488
+ note: "getMainRepoRoot and getGitCommonDir for worktrees.",
489
+ },
490
+ {
491
+ query: "Where do we format search results in plain mode for agents?",
492
+ expectedPath: "src/lib/utils/formatter.ts",
493
+ note: "formatTextResults plain mode with agent tags.",
494
+ },
495
+ {
496
+ query: "Where do we apply syntax highlighting for human output?",
497
+ expectedPath: "src/lib/utils/formatter.ts",
498
+ note: "formatTextResults uses cli-highlight when not plain.",
499
+ },
500
+ {
501
+ query: "Where do we merge nearby snippets from the same file before printing?",
502
+ expectedPath: "src/lib/utils/formatter.ts",
503
+ note: "Smart stitching merges overlapping chunks per file.",
504
+ },
505
+ {
506
+ query: "Where are search CLI options like --scores, --compact, --per-file handled?",
507
+ expectedPath: "src/commands/search.ts",
508
+ note: "Commander options declared for search command.",
509
+ },
510
+ {
511
+ query: "Where is the search path argument normalized against project root?",
512
+ expectedPath: "src/commands/search.ts",
513
+ note: "Relative path handling before searcher.search.",
514
+ },
515
+ ];
516
+ const topK = 20;
517
+ function evaluateCase(response, evalCase, timeMs) {
518
+ const expectedPaths = evalCase.expectedPath
519
+ .split("|")
520
+ .map((p) => p.trim().toLowerCase())
521
+ .filter(Boolean);
522
+ const rank = response.data.findIndex((chunk) => {
523
+ var _a, _b;
524
+ const path = ((_b = (_a = chunk.metadata) === null || _a === void 0 ? void 0 : _a.path) === null || _b === void 0 ? void 0 : _b.toLowerCase()) || "";
525
+ return expectedPaths.some((expected) => path.includes(expected));
526
+ });
527
+ const avoidRank = response.data.findIndex((chunk) => {
528
+ var _a, _b, _c;
529
+ return (_b = (_a = chunk.metadata) === null || _a === void 0 ? void 0 : _a.path) === null || _b === void 0 ? void 0 : _b.toLowerCase().includes(((_c = evalCase.avoidPath) === null || _c === void 0 ? void 0 : _c.toLowerCase()) || "_____");
530
+ });
531
+ const hitAvoid = evalCase.avoidPath && avoidRank >= 0 && (rank === -1 || avoidRank < rank);
532
+ const found = rank >= 0 && !hitAvoid;
533
+ const rr = found ? 1 / (rank + 1) : 0;
534
+ const recall = found && rank < 10 ? 1 : 0;
535
+ return {
536
+ rr,
537
+ found,
538
+ recall,
539
+ path: evalCase.expectedPath,
540
+ query: evalCase.query,
541
+ note: evalCase.note,
542
+ timeMs,
543
+ };
544
+ }
545
+ function run() {
546
+ return __awaiter(this, void 0, void 0, function* () {
547
+ var _a;
548
+ const root = process.cwd();
549
+ const searchRoot = root;
550
+ const projectRoot = (_a = (0, project_root_1.findProjectRoot)(searchRoot)) !== null && _a !== void 0 ? _a : searchRoot;
551
+ const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
552
+ const vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
553
+ const searcher = new searcher_1.Searcher(vectorDb);
554
+ // 1. Ensure the store exists (VectorDB handles creation, but we check for data)
555
+ const hasRows = yield vectorDb.hasAnyRows();
556
+ if (!hasRows) {
557
+ console.error(`❌ Store appears to be empty!`);
558
+ console.error(` Run "osgrep index" to populate the store with data.`);
559
+ process.exit(1);
560
+ }
561
+ // 2. Check if store has data (redundant but good for sanity)
562
+ try {
563
+ const testResult = yield searcher.search("test", 1, { rerank: true });
564
+ if (testResult.data.length === 0) {
565
+ console.error(`⚠️ Store appears to be empty (search returned 0 results)!`);
566
+ console.error(` Run "osgrep index" to populate the store with data.`);
567
+ process.exit(1);
568
+ }
569
+ }
570
+ catch (err) {
571
+ console.error(`❌ Error checking store data:`, err);
572
+ process.exit(1);
573
+ }
574
+ const results = [];
575
+ console.log("Starting evaluation...\n");
576
+ const startTime = performance.now();
577
+ for (const c of exports.cases) {
578
+ const queryStart = performance.now();
579
+ const res = yield searcher.search(c.query, topK, { rerank: false });
580
+ const queryEnd = performance.now();
581
+ const timeMs = queryEnd - queryStart;
582
+ results.push(evaluateCase(res, c, timeMs));
583
+ }
584
+ const totalTime = performance.now() - startTime;
585
+ const mrr = results.reduce((sum, r) => sum + r.rr, 0) / results.length;
586
+ const recallAt10 = results.reduce((sum, r) => sum + r.recall, 0) / results.length;
587
+ const avgTime = results.reduce((sum, r) => sum + r.timeMs, 0) / results.length;
588
+ console.log("=".repeat(80));
589
+ console.log(`Eval results for store at: ${paths.lancedbDir}`);
590
+ console.log("=".repeat(80));
591
+ results.forEach((r) => {
592
+ const status = r.found ? `rank ${(1 / r.rr).toFixed(0)}` : "❌ missed";
593
+ const emoji = r.found ? (r.rr === 1 ? "🎯" : "✓") : "❌";
594
+ console.log(`${emoji} ${r.query}`);
595
+ console.log(` => ${status} (target: ${r.path}) [${r.timeMs.toFixed(0)}ms]`);
596
+ if (r.note) {
597
+ console.log(` // ${r.note}`);
598
+ }
599
+ });
600
+ console.log("=".repeat(80));
601
+ console.log(`MRR: ${mrr.toFixed(3)}`);
602
+ console.log(`Recall@10: ${recallAt10.toFixed(3)}`);
603
+ console.log(`Avg query time: ${avgTime.toFixed(0)}ms`);
604
+ console.log(`Total time: ${totalTime.toFixed(0)}ms`);
605
+ console.log(`Found: ${results.filter((r) => r.found).length}/${results.length}`);
606
+ console.log("=".repeat(80));
607
+ yield (0, exit_1.gracefulExit)(0);
608
+ });
609
+ }
610
+ if (
611
+ // Only auto-run when executed directly (not when imported for experiments/tests)
612
+ require.main === module &&
613
+ process.env.OSGREP_EVAL_AUTORUN !== "0") {
614
+ run().catch((err) => {
615
+ console.error("Eval failed:", err);
616
+ (0, exit_1.gracefulExit)(1);
617
+ });
618
+ }