sweet-search 2.4.2 → 2.5.1

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 (43) hide show
  1. package/core/cli.js +19 -5
  2. package/core/embedding/embedding-cache.js +177 -15
  3. package/core/embedding/embedding-service.js +18 -4
  4. package/core/graph/graph-expansion.js +52 -12
  5. package/core/graph/graph-extractor.js +30 -1
  6. package/core/indexing/ast-chunker.js +331 -16
  7. package/core/indexing/chunking/chunk-builder.js +34 -1
  8. package/core/indexing/index.js +6 -3
  9. package/core/indexing/indexer-ann.js +45 -6
  10. package/core/indexing/indexer-build.js +9 -1
  11. package/core/indexing/indexer-phases.js +6 -4
  12. package/core/indexing/indexing-file-policy.js +140 -0
  13. package/core/indexing/li-skip-policy.js +11 -220
  14. package/core/infrastructure/codebase-repository.js +21 -0
  15. package/core/infrastructure/config/embedding.js +20 -1
  16. package/core/infrastructure/config/graph.js +2 -2
  17. package/core/infrastructure/config/ranking.js +10 -0
  18. package/core/infrastructure/config/vector-store.js +1 -1
  19. package/core/infrastructure/coreml-cascade.js +236 -30
  20. package/core/infrastructure/coreml-cascade.json +25 -0
  21. package/core/infrastructure/index.js +15 -0
  22. package/core/infrastructure/init-config.js +78 -0
  23. package/core/infrastructure/language-patterns/registry-core.js +18 -0
  24. package/core/infrastructure/model-registry.js +12 -0
  25. package/core/infrastructure/native-inference.js +143 -51
  26. package/core/infrastructure/tree-sitter-provider.js +92 -2
  27. package/core/ranking/cascaded-scorer.js +6 -2
  28. package/core/ranking/file-kind-ranking.js +264 -0
  29. package/core/ranking/late-interaction-index.js +10 -4
  30. package/core/ranking/late-interaction-policy.js +304 -0
  31. package/core/search/context-expander.js +267 -28
  32. package/core/search/index.js +4 -0
  33. package/core/search/search-cli.js +3 -1
  34. package/core/search/search-pattern.js +4 -3
  35. package/core/search/search-postprocess.js +189 -8
  36. package/core/search/search-read-semantic.js +717 -0
  37. package/core/search/search-read.js +481 -0
  38. package/core/search/search-server.js +6 -4
  39. package/core/search/sweet-search.js +119 -15
  40. package/mcp/server.js +41 -0
  41. package/mcp/tool-handlers.js +117 -6
  42. package/package.json +9 -7
  43. package/scripts/init.js +386 -5
@@ -21,6 +21,8 @@ import { HNSWIndex } from '../vector-store/hnsw-index.js';
21
21
  import { BinaryHNSWIndex } from '../vector-store/binary-hnsw-index.js';
22
22
  import { Reranker } from '../ranking/flashrank.js';
23
23
  import { LateInteractionIndex } from '../ranking/late-interaction-index.js';
24
+ import { resolveSearchRerankPolicy } from '../ranking/late-interaction-policy.js';
25
+ import { readPersistedLiPolicy } from '../infrastructure/index.js';
24
26
  import { getEmbedding, getBinaryEmbedding, truncateForHNSW, int8CosineSimilarity, warmup as warmupEmbedding, isWarm, registerAutoPersistOnExit } from '../embedding/embedding-service.js';
25
27
  import { FloatVectorStore, getFloatStorePath } from '../vector-store/float-vector-store.js';
26
28
  import { recordQueryTelemetry } from '../embedding/embedding-cache.js';
@@ -41,6 +43,7 @@ import * as semantic from './search-semantic.js';
41
43
  import * as hybrid from './search-hybrid.js';
42
44
  import * as postprocess from './search-postprocess.js';
43
45
  import * as pattern from './search-pattern.js';
46
+ import { packageForAgent } from './context-expander.js';
44
47
 
45
48
  export { ROUTE_ALPHAS } from './search-fusion.js';
46
49
 
@@ -93,10 +96,13 @@ export class SweetSearch {
93
96
  const envOrProject = (envKey, cascadeKey, configKey) =>
94
97
  process.env[envKey] != null ? CASCADE_CONFIG[configKey] : projectCascade[cascadeKey];
95
98
 
96
- this.graphSearch = new GraphSearch(options.graphDbPath || DB_PATHS.codeGraph);
97
- this.codeGraphRepo = new CodeGraphRepository(options.graphDbPath || DB_PATHS.codeGraph);
98
- this.hnswIndex = new HNSWIndex({ indexPath: options.hnswPath || DB_PATHS.hnswIndex });
99
- this.binaryHnswIndex = new BinaryHNSWIndex({ indexPath: options.binaryHnswPath || DB_PATHS.binaryHnswIndex });
99
+ this.graphDbPath = options.graphDbPath || DB_PATHS.codeGraph;
100
+ this.graphSearch = new GraphSearch(this.graphDbPath);
101
+ this.codeGraphRepo = new CodeGraphRepository(this.graphDbPath);
102
+ this.hnswPath = options.hnswPath || DB_PATHS.hnswIndex;
103
+ this.binaryHnswPath = options.binaryHnswPath || DB_PATHS.binaryHnswIndex;
104
+ this.hnswIndex = new HNSWIndex({ indexPath: this.hnswPath });
105
+ this.binaryHnswIndex = new BinaryHNSWIndex({ indexPath: this.binaryHnswPath });
100
106
  this.reranker = new Reranker(options);
101
107
  this.lateInteractionIndex = new LateInteractionIndex(options.lateInteractionOptions || {});
102
108
  this.router = new QueryRouter();
@@ -108,7 +114,25 @@ export class SweetSearch {
108
114
  this.stage1Candidates = options.stage1Candidates ?? BINARY_HNSW_CONFIG.retrieval.stage1Candidates;
109
115
  this.stage2Candidates = options.stage2Candidates ?? BINARY_HNSW_CONFIG.retrieval.stage2Candidates;
110
116
  this.stage3Candidates = options.stage3Candidates ?? BINARY_HNSW_CONFIG.retrieval.stage3Candidates;
111
- this.useLateInteraction = options.useLateInteraction ?? LATE_INTERACTION_CONFIG.enabled;
117
+ // Late-interaction search-rerank gating — see core/ranking/late-interaction-policy.js
118
+ // for the full precedence ladder. The constructor records the inputs and
119
+ // computes a tentative value (so callers reading `useLateInteraction`
120
+ // before init() get sensible defaults); init() recomputes once the LI
121
+ // index header has been loaded so the manifest's modelId can drive the
122
+ // auto policy.
123
+ this._liPolicyOptionOverride = typeof options.useLateInteraction === 'boolean'
124
+ ? options.useLateInteraction
125
+ : undefined;
126
+ this._liPolicyPersisted = readPersistedLiPolicy(projectRoot);
127
+ const liInitial = resolveSearchRerankPolicy({
128
+ optionOverride: this._liPolicyOptionOverride,
129
+ env: process.env,
130
+ persisted: this._liPolicyPersisted,
131
+ indexManifest: null,
132
+ activeConfigModel: LATE_INTERACTION_CONFIG.model,
133
+ });
134
+ this.useLateInteraction = LATE_INTERACTION_CONFIG.enabled ? liInitial.effective : false;
135
+ this._liPolicyResolved = liInitial;
112
136
  this.lateInteractionBlendWeight = options.lateInteractionBlendWeight ?? LATE_INTERACTION_CONFIG.blendWeight ?? 0.3;
113
137
  this.returnSummaryFirst = options.returnSummaryFirst ?? HCGS_CONFIG.returnSummaryFirst;
114
138
  this.summaryTokenBudget = options.summaryTokenBudget ?? HCGS_CONFIG.summaryTokenBudget;
@@ -152,9 +176,9 @@ export class SweetSearch {
152
176
  if (this.initialized) return;
153
177
  const start = Date.now();
154
178
 
155
- this.hasGraphIndex = existsSync(DB_PATHS.codeGraph);
156
- this.hasHnswIndex = existsSync(DB_PATHS.hnswIndex.replace('.idx', '.meta.json'));
157
- this.hasBinaryHnswIndex = existsSync(DB_PATHS.binaryHnswIndex.replace('.idx', '.meta.json'));
179
+ this.hasGraphIndex = existsSync(this.graphDbPath);
180
+ this.hasHnswIndex = existsSync(this.hnswPath.replace('.idx', '.meta.json'));
181
+ this.hasBinaryHnswIndex = existsSync(this.binaryHnswPath.replace('.idx', '.meta.json'));
158
182
  this.hasCodebaseIndex = existsSync(this.codebaseDbPath);
159
183
  this.hasLateInteractionIndex = existsSync(this.lateInteractionIndex.indexPath);
160
184
  this.hasSparseGramIndex = existsSync(this.sparseGramIndexPath);
@@ -182,7 +206,7 @@ export class SweetSearch {
182
206
  // disable the entire 3-stage pipeline. Stage 2.5 falls back to SQLite.
183
207
  if (this.hasBinaryHnswIndex) {
184
208
  try {
185
- const floatStorePath = getFloatStorePath(DB_PATHS.binaryHnswIndex);
209
+ const floatStorePath = getFloatStorePath(this.binaryHnswPath);
186
210
  const floatLoaded = await this.floatVectorStore.load(floatStorePath);
187
211
  if (floatLoaded) {
188
212
  const fStats = this.floatVectorStore.getStats();
@@ -212,15 +236,65 @@ export class SweetSearch {
212
236
  const stats = this.lateInteractionIndex.getStats();
213
237
  this.log(`LateInteraction: Loaded ${stats.documents} documents (${stats.estimatedSizeMB} MB, ${stats.avgTokensPerDoc} avg tokens)`);
214
238
 
215
- // Preheat LI ONNX inference model (~900ms cold start otherwise).
216
- // The index loads token vectors; this loads the query encoder model.
217
- const { encodeQuery } = await import('../ranking/late-interaction-model.js');
218
- await encodeQuery('warmup');
219
- this.log('LateInteraction: ONNX model preheated');
239
+ // Re-resolve the rerank policy now that the index header has been
240
+ // loaded the manifest's modelId is the source of truth for the
241
+ // auto rule (edge index off, standard index → on, mismatch → off).
242
+ // The constructor only had the active LATE_INTERACTION_CONFIG.model
243
+ // to work with; this call corrects the decision when index and
244
+ // config disagree (env var changed, model bumped, etc.).
245
+ const manifest = {
246
+ modelId: this.lateInteractionIndex.modelId ?? null,
247
+ tokenDim: this.lateInteractionIndex.tokenDim ?? null,
248
+ modelMismatch: this.lateInteractionIndex.modelMismatch === true,
249
+ exists: true,
250
+ };
251
+ const resolved = resolveSearchRerankPolicy({
252
+ optionOverride: this._liPolicyOptionOverride,
253
+ env: process.env,
254
+ persisted: this._liPolicyPersisted,
255
+ indexManifest: manifest,
256
+ activeConfigModel: LATE_INTERACTION_CONFIG.model,
257
+ });
258
+ this._liPolicyResolved = resolved;
259
+ const previouslyOn = this.useLateInteraction;
260
+ this.useLateInteraction = LATE_INTERACTION_CONFIG.enabled && resolved.effective;
261
+ this.log(
262
+ `LateInteraction: rerank policy → ${this.useLateInteraction ? 'on' : 'off'} `
263
+ + `(${resolved.reason}${manifest.modelId ? `, index=${manifest.modelId}` : ''})`,
264
+ );
265
+ if (resolved.warning) {
266
+ // One-line warning — keeps the log digestible, full guidance is
267
+ // in docs/BENCH_TODO.md.
268
+ console.warn(`[SweetSearch] ${resolved.warning}`);
269
+ }
270
+
271
+ if (this.useLateInteraction) {
272
+ // Preheat LI ONNX inference model (~900ms cold start otherwise).
273
+ // Only when we will actually rerank — saves cold-start cost when
274
+ // policy resolves to off post-manifest-inspection.
275
+ const { encodeQuery } = await import('../ranking/late-interaction-model.js');
276
+ await encodeQuery('warmup');
277
+ this.log('LateInteraction: ONNX model preheated');
278
+ } else if (previouslyOn) {
279
+ // We loaded the index because the constructor's tentative
280
+ // resolution said on, but the manifest just told us otherwise —
281
+ // log so the user understands the gap between "index present"
282
+ // and "rerank active".
283
+ this.log('LateInteraction: index loaded but search rerank disabled by policy (read-semantic + ColGrep still use the index)');
284
+ }
220
285
  } catch (err) {
221
286
  this.log(`LateInteraction: Failed to load: ${err.message}`);
222
287
  this.hasLateInteractionIndex = false;
288
+ this.useLateInteraction = false;
223
289
  }
290
+ } else if (this.hasLateInteractionIndex && !this.useLateInteraction) {
291
+ // Index present but constructor-time policy resolved to off.
292
+ // Skip the (expensive) load + encoder warmup — read-semantic and
293
+ // ColGrep both lazy-load their own LI handle when actually invoked.
294
+ this.log(
295
+ `LateInteraction: index present, search rerank disabled by policy `
296
+ + `(${this._liPolicyResolved?.reason ?? 'unknown'})`,
297
+ );
224
298
  }
225
299
 
226
300
  if (this.hasSparseGramIndex) {
@@ -419,9 +493,39 @@ export class SweetSearch {
419
493
  }
420
494
 
421
495
  // Step 3: Post-retrieval processing (delegated to extracted module)
422
- return this._applyPostRetrieval(results, query, options, {
496
+ const postRetrievalResult = await this._applyPostRetrieval(results, query, options, {
423
497
  stats, semanticStats, searchMode, effectiveGraphExpand, intentPolicy, start,
424
498
  });
499
+
500
+ // Step 4: Agent packaging (lexical/semantic/hybrid/structural).
501
+ // The pattern (colgrep) branch already returns its own pre-packaged response
502
+ // and short-circuits earlier in this method. For non-pattern modes, apply the
503
+ // shared packager when the caller explicitly asked for an agent format.
504
+ // Default behavior (no agent format) is unchanged.
505
+ const agentFormats = new Set(['agent', 'agent_preview', 'agent_full', 'agent_full_xl']);
506
+ if (agentFormats.has(options.format)) {
507
+ const finalResults = postRetrievalResult.results || [];
508
+ const finalStats = postRetrievalResult.stats || {};
509
+ const agentResponse = packageForAgent(finalResults, {
510
+ ...finalStats,
511
+ candidatePoolSize: finalStats.results_count ?? finalResults.length,
512
+ }, {
513
+ query,
514
+ regex: regex || '',
515
+ mode: finalStats.path || searchMode,
516
+ format: options.format,
517
+ tokenBudget: options.tokenBudget,
518
+ codeGraphRepo: this.codeGraphRepo || null,
519
+ locationMap: null,
520
+ projectRoot: this.projectRoot,
521
+ ablations: options.ablations,
522
+ });
523
+ // Preserve the underlying retrieval stats so callers can inspect both layers
524
+ agentResponse.stats = finalStats;
525
+ return agentResponse;
526
+ }
527
+
528
+ return postRetrievalResult;
425
529
  }
426
530
 
427
531
  /** Structural search path (GraphRAG structural queries — opt-in via explicit flag) */
package/mcp/server.js CHANGED
@@ -18,11 +18,15 @@ import {
18
18
  HealthOutputSchema,
19
19
  RepoMapOutputSchema,
20
20
  VocabPrewarmOutputSchema,
21
+ ReadOutputSchema,
22
+ ReadSemanticOutputSchema,
21
23
  handleSearch,
22
24
  handleIndex,
23
25
  checkHealth,
24
26
  handleRepoMap,
25
27
  handleVocabPrewarm,
28
+ handleRead,
29
+ handleReadSemantic,
26
30
  } from './tool-handlers.js';
27
31
 
28
32
  const __filename = fileURLToPath(import.meta.url);
@@ -224,6 +228,43 @@ server.registerTool('vocab-prewarm', {
224
228
  },
225
229
  }, async (args) => handleVocabPrewarm(args, vocabDeps));
226
230
 
231
+ server.registerTool('read', {
232
+ description: 'Read one or more files for exact code understanding. Replaces the default Read tool for most code-reading workflows. Uses the filesystem as ground truth, supports line ranges and batching, and attaches symbol-aware chunk metadata when the file is indexed.',
233
+ inputSchema: {
234
+ files: z.array(z.object({
235
+ path: z.string().describe('File path relative to project root (or absolute)'),
236
+ startLine: z.number().int().min(1).optional().describe('Start line (1-based, inclusive)'),
237
+ endLine: z.number().int().min(1).optional().describe('End line (1-based, inclusive)'),
238
+ })).min(1).max(20).describe('Files to read (1-20)'),
239
+ includeMetadata: z.boolean().default(true).optional()
240
+ .describe('Attach symbol-aware chunk metadata when the file is indexed'),
241
+ },
242
+ outputSchema: ReadOutputSchema,
243
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
244
+ }, async (args) => handleRead(args, { PROJECT_ROOT }));
245
+
246
+ server.registerTool('read-semantic', {
247
+ description: 'Read only the spans of a file relevant to a query. Selects spans via hybrid retrieval (lexical + symbol + ColBERT-style late-interaction MaxSim) with RRF fusion and LI re-rank, then re-reads exact lines from disk. Returns 1-N small spans instead of the full file. Falls back to a plain read if the file is not indexed.',
248
+ inputSchema: {
249
+ file: z.string().describe('File path (project-relative or absolute)'),
250
+ query: z.string().min(1).max(500).describe('What you want to understand about this file'),
251
+ topK: z.number().int().min(1).max(20).default(5).optional()
252
+ .describe('Maximum spans before merging (default: 5)'),
253
+ threshold: z.number().min(0).max(1).default(0.4).optional()
254
+ .describe('MaxSim score floor (default: 0.4)'),
255
+ contextLines: z.number().int().min(0).max(20).default(2).optional()
256
+ .describe('Pre/post context lines per span (default: 2)'),
257
+ maxChars: z.number().int().min(200).max(64000).default(8000).optional()
258
+ .describe('Hard cap on returned text (default: 8000 chars)'),
259
+ maxTokens: z.number().int().min(50).max(16000).optional()
260
+ .describe('Convenience cap (~chars/4)'),
261
+ verbose: z.boolean().default(false).optional()
262
+ .describe('Include timings + per-signal scores'),
263
+ },
264
+ outputSchema: ReadSemanticOutputSchema,
265
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
266
+ }, async (args) => handleReadSemantic(args, { PROJECT_ROOT }));
267
+
227
268
  // ---------------------------------------------------------------------------
228
269
  // Resources
229
270
  // ---------------------------------------------------------------------------
@@ -88,6 +88,65 @@ export const VocabPrewarmOutputSchema = z.object({
88
88
  dryRun: z.boolean().optional(),
89
89
  });
90
90
 
91
+ const ReadFileResultSchema = z.object({
92
+ file: z.string(),
93
+ absolutePath: z.string().optional(),
94
+ ok: z.boolean(),
95
+ exact: z.boolean().optional(),
96
+ indexed: z.boolean().optional(),
97
+ language: z.string().nullable().optional(),
98
+ totalLines: z.number().int().optional(),
99
+ bytes: z.number().int().optional(),
100
+ mtimeMs: z.number().optional(),
101
+ range: z.object({
102
+ startLine: z.number().int(),
103
+ endLine: z.number().int(),
104
+ }).nullable().optional(),
105
+ text: z.string().optional(),
106
+ chunks: z.array(z.object({
107
+ id: z.string(),
108
+ symbol: z.string().nullable().optional(),
109
+ type: z.string().nullable().optional(),
110
+ startLine: z.number().int().nullable().optional(),
111
+ endLine: z.number().int().nullable().optional(),
112
+ signature: z.string().nullable().optional(),
113
+ })).optional(),
114
+ error: z.string().optional(),
115
+ timings: z.object({ totalMs: z.number() }).optional(),
116
+ });
117
+
118
+ export const ReadOutputSchema = z.object({
119
+ files: z.array(ReadFileResultSchema),
120
+ totalMs: z.number(),
121
+ });
122
+
123
+ const ReadSemanticSpanSchema = z.object({
124
+ startLine: z.number().int(),
125
+ endLine: z.number().int(),
126
+ score: z.number(),
127
+ symbols: z.array(z.string()).optional(),
128
+ types: z.array(z.string()).optional(),
129
+ chunkIds: z.array(z.string()).optional(),
130
+ text: z.string(),
131
+ truncated: z.boolean().optional(),
132
+ });
133
+
134
+ export const ReadSemanticOutputSchema = z.object({
135
+ file: z.string(),
136
+ query: z.string(),
137
+ ok: z.boolean(),
138
+ indexed: z.boolean(),
139
+ fellBack: z.boolean(),
140
+ reason: z.string().optional(),
141
+ language: z.string().nullable().optional(),
142
+ totalLines: z.number().int().optional(),
143
+ spans: z.array(ReadSemanticSpanSchema),
144
+ charsReturned: z.number().int().optional(),
145
+ approxTokensReturned: z.number().int().optional(),
146
+ signals: z.record(z.string(), z.any()).optional(),
147
+ timings: z.record(z.string(), z.number()).optional(),
148
+ });
149
+
91
150
  // ---------------------------------------------------------------------------
92
151
  // Internal state for health DB cache (module-scoped, not exported)
93
152
  // ---------------------------------------------------------------------------
@@ -104,11 +163,6 @@ let _healthDb = null;
104
163
  */
105
164
  export async function handleSearch({ query, k, mode, structural, regex, format, tokenBudget }, { getSearcher }) {
106
165
  try {
107
- // Agent format requires a regex (pattern search). If no regex, ignore the format
108
- // to avoid silent fallback to non-pattern search without agent packaging.
109
- const isAgentFormat = format && format.startsWith('agent');
110
- const effectiveFormat = (isAgentFormat && !regex) ? undefined : format;
111
-
112
166
  const searcher = await getSearcher();
113
167
  const searchMode = structural ? 'structural' : mode;
114
168
  const searchResult = await searcher.search(query, {
@@ -117,7 +171,7 @@ export async function handleSearch({ query, k, mode, structural, regex, format,
117
171
  expand: true,
118
172
  rerank: true,
119
173
  ...(regex && { regex }),
120
- ...(effectiveFormat && { format: effectiveFormat }),
174
+ ...(format && { format }),
121
175
  ...(tokenBudget && { tokenBudget }),
122
176
  });
123
177
 
@@ -474,3 +528,60 @@ export async function handleVocabPrewarm({ depth, modes, top, incremental, dryRu
474
528
  };
475
529
  }
476
530
  }
531
+
532
+ // ---------------------------------------------------------------------------
533
+ // read — filesystem-grounded reader
534
+ // ---------------------------------------------------------------------------
535
+
536
+ /**
537
+ * @param {{ files: Array<{path: string, startLine?: number, endLine?: number}>, includeMetadata?: boolean }} args
538
+ * @param {{ PROJECT_ROOT: string }} deps
539
+ */
540
+ export async function handleRead(args, deps) {
541
+ try {
542
+ const { readFiles, formatReadResults } = await import('../core/search/index.js');
543
+ const result = await readFiles(args.files || [], {
544
+ projectRoot: deps.PROJECT_ROOT,
545
+ includeMetadata: args.includeMetadata !== false,
546
+ });
547
+ return {
548
+ content: [{ type: 'text', text: formatReadResults(result, 'agent') }],
549
+ structuredContent: result,
550
+ };
551
+ } catch (err) {
552
+ const msg = (err.message || 'read failed').split('\n')[0];
553
+ return { content: [{ type: 'text', text: `read error: ${msg}` }], isError: true };
554
+ }
555
+ }
556
+
557
+ // ---------------------------------------------------------------------------
558
+ // read-semantic — hybrid span selection + filesystem-grounded re-read
559
+ // ---------------------------------------------------------------------------
560
+
561
+ /**
562
+ * @param {{ file: string, query: string, topK?: number, threshold?: number, contextLines?: number, maxChars?: number, maxTokens?: number, verbose?: boolean }} args
563
+ * @param {{ PROJECT_ROOT: string }} deps
564
+ */
565
+ export async function handleReadSemantic(args, deps) {
566
+ try {
567
+ const { readSemantic, formatReadSemanticResult } = await import('../core/search/index.js');
568
+ const result = await readSemantic({
569
+ path: args.file,
570
+ query: args.query,
571
+ topK: args.topK,
572
+ threshold: args.threshold,
573
+ contextLines: args.contextLines,
574
+ maxChars: args.maxChars,
575
+ maxTokens: args.maxTokens,
576
+ projectRoot: deps.PROJECT_ROOT,
577
+ verbose: args.verbose,
578
+ });
579
+ return {
580
+ content: [{ type: 'text', text: formatReadSemanticResult(result, 'agent') }],
581
+ structuredContent: result,
582
+ };
583
+ } catch (err) {
584
+ const msg = (err.message || 'read-semantic failed').split('\n')[0];
585
+ return { content: [{ type: 'text', text: `read-semantic error: ${msg}` }], isError: true };
586
+ }
587
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sweet-search",
3
- "version": "2.4.2",
3
+ "version": "2.5.1",
4
4
  "description": "Sweet Search - SOTA Hybrid Code Search Engine with WASM CatBoost Query Router, Semantic/Lexical/Structural Search, and Multilingual Support",
5
5
  "type": "module",
6
6
  "main": "core/search/sweet-search.js",
@@ -99,6 +99,8 @@
99
99
  "eval:latency": "node eval/scripts/latency-stress.js",
100
100
  "eval:multirepo": "node eval/scripts/multirepo-bench.js",
101
101
  "eval:multirepo:test": "node eval/scripts/multirepo-bench.js --split=test",
102
+ "bench:read-workflows": "node eval/read-workflows/run-bench.js",
103
+ "bench:agent-read-workflows": "node eval/agent-read-workflows/run-bench.js",
102
104
  "eval:fetch-repos": "node eval/scripts/fetch-benchmark-repos.js",
103
105
  "features": "node core/training/query-router/features/extractor.js",
104
106
  "features:benchmark": "node core/training/query-router/features/extractor.js --benchmark",
@@ -140,12 +142,12 @@
140
142
  "vitest": "^4.0.16"
141
143
  },
142
144
  "optionalDependencies": {
143
- "@sweet-search/native-darwin-arm64": "2.4.2",
144
- "@sweet-search/native-darwin-x64": "2.4.2",
145
- "@sweet-search/native-linux-arm64-gnu": "2.4.2",
146
- "@sweet-search/native-linux-arm64-gnu-cuda": "2.4.2",
147
- "@sweet-search/native-linux-x64-gnu": "2.4.2",
148
- "@sweet-search/native-linux-x64-gnu-cuda": "2.4.2"
145
+ "@sweet-search/native-darwin-arm64": "2.5.1",
146
+ "@sweet-search/native-darwin-x64": "2.5.1",
147
+ "@sweet-search/native-linux-arm64-gnu": "2.5.1",
148
+ "@sweet-search/native-linux-arm64-gnu-cuda": "2.5.1",
149
+ "@sweet-search/native-linux-x64-gnu": "2.5.1",
150
+ "@sweet-search/native-linux-x64-gnu-cuda": "2.5.1"
149
151
  },
150
152
  "engines": {
151
153
  "node": ">=18.0.0"