@gmickel/gno 0.16.0 → 0.17.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.
Files changed (37) hide show
  1. package/README.md +36 -1
  2. package/package.json +4 -1
  3. package/src/cli/commands/ask.ts +9 -0
  4. package/src/cli/commands/query.ts +3 -2
  5. package/src/cli/pager.ts +1 -1
  6. package/src/cli/program.ts +89 -0
  7. package/src/core/links.ts +92 -20
  8. package/src/ingestion/sync.ts +267 -23
  9. package/src/ingestion/types.ts +2 -0
  10. package/src/ingestion/walker.ts +2 -1
  11. package/src/mcp/tools/index.ts +30 -1
  12. package/src/mcp/tools/query.ts +22 -2
  13. package/src/mcp/tools/search.ts +8 -0
  14. package/src/mcp/tools/vsearch.ts +8 -0
  15. package/src/pipeline/answer.ts +324 -7
  16. package/src/pipeline/expansion.ts +243 -7
  17. package/src/pipeline/explain.ts +93 -5
  18. package/src/pipeline/hybrid.ts +240 -57
  19. package/src/pipeline/query-modes.ts +125 -0
  20. package/src/pipeline/rerank.ts +34 -13
  21. package/src/pipeline/search.ts +41 -3
  22. package/src/pipeline/temporal.ts +257 -0
  23. package/src/pipeline/types.ts +58 -0
  24. package/src/pipeline/vsearch.ts +107 -9
  25. package/src/serve/public/app.tsx +1 -3
  26. package/src/serve/public/globals.built.css +2 -2
  27. package/src/serve/public/lib/retrieval-filters.ts +167 -0
  28. package/src/serve/public/pages/Ask.tsx +339 -109
  29. package/src/serve/public/pages/Browse.tsx +71 -5
  30. package/src/serve/public/pages/DocView.tsx +2 -21
  31. package/src/serve/public/pages/Search.tsx +507 -120
  32. package/src/serve/routes/api.ts +202 -2
  33. package/src/store/migrations/006-document-metadata.ts +104 -0
  34. package/src/store/migrations/007-document-date-fields.ts +24 -0
  35. package/src/store/migrations/index.ts +3 -1
  36. package/src/store/sqlite/adapter.ts +218 -5
  37. package/src/store/types.ts +46 -0
@@ -67,7 +67,7 @@ const MAX_CONCURRENCY = 16;
67
67
  * Increment when ingestion adds new derived data (tags, metadata, etc.)
68
68
  * Documents with ingestVersion < INGEST_VERSION will be re-processed.
69
69
  */
70
- export const INGEST_VERSION = 3;
70
+ export const INGEST_VERSION = 5;
71
71
 
72
72
  /**
73
73
  * Decide whether to process a file or skip it.
@@ -141,6 +141,171 @@ function extractTags(markdown: string): string[] {
141
141
  return [...tags];
142
142
  }
143
143
 
144
+ interface DocumentMetadata {
145
+ contentType?: string;
146
+ categories?: string[];
147
+ author?: string;
148
+ frontmatterDate?: string;
149
+ dateFields?: Record<string, string>;
150
+ }
151
+
152
+ const CODE_EXTENSIONS = new Set([
153
+ ".c",
154
+ ".cc",
155
+ ".cpp",
156
+ ".cs",
157
+ ".go",
158
+ ".java",
159
+ ".js",
160
+ ".jsx",
161
+ ".m",
162
+ ".mm",
163
+ ".php",
164
+ ".py",
165
+ ".rb",
166
+ ".rs",
167
+ ".swift",
168
+ ".ts",
169
+ ".tsx",
170
+ ]);
171
+
172
+ const AUTHOR_KEYS = ["author", "by", "owner", "creator"] as const;
173
+ const DATE_KEYS = [
174
+ "date",
175
+ "published",
176
+ "published_at",
177
+ "created",
178
+ "created_at",
179
+ "updated",
180
+ "updated_at",
181
+ ] as const;
182
+ const DATE_FIELD_KEY_REGEX =
183
+ /(^|_)(date|time|created|updated|published|modified|deadline|expires|expiry|start|end)(_|$)/;
184
+
185
+ function normalizeMetadataKey(rawKey: string): string {
186
+ return rawKey
187
+ .trim()
188
+ .replace(/([a-z0-9])([A-Z])/g, "$1_$2")
189
+ .toLowerCase()
190
+ .replace(/[^a-z0-9]+/g, "_")
191
+ .replace(/^_+|_+$/g, "")
192
+ .replace(/_+/g, "_");
193
+ }
194
+
195
+ function normalizeDate(value: unknown): string | undefined {
196
+ if (value instanceof Date) {
197
+ return Number.isNaN(value.getTime()) ? undefined : value.toISOString();
198
+ }
199
+ if (typeof value !== "string" && typeof value !== "number") {
200
+ return undefined;
201
+ }
202
+ const normalizedValue =
203
+ typeof value === "string"
204
+ ? value.trim().replace(/^["'](.*)["']$/, "$1")
205
+ : value;
206
+ const parsed = new Date(normalizedValue);
207
+ if (Number.isNaN(parsed.getTime())) {
208
+ return undefined;
209
+ }
210
+ return parsed.toISOString();
211
+ }
212
+
213
+ function inferContentType(relPath: string, ext: string): string {
214
+ const lowerPath = relPath.toLowerCase();
215
+ if (CODE_EXTENSIONS.has(ext.toLowerCase())) {
216
+ return "code";
217
+ }
218
+ if (/(meeting|standup|retro|minutes)/.test(lowerPath)) {
219
+ return "meeting";
220
+ }
221
+ if (/(spec|rfc|adr|design)/.test(lowerPath)) {
222
+ return "spec";
223
+ }
224
+ if (/(notes|journal|log)/.test(lowerPath)) {
225
+ return "notes";
226
+ }
227
+ return "prose";
228
+ }
229
+
230
+ function parseCategories(input: unknown): string[] {
231
+ if (Array.isArray(input)) {
232
+ return input
233
+ .filter((v): v is string => typeof v === "string")
234
+ .map((v) => v.trim().toLowerCase())
235
+ .filter((v) => v.length > 0);
236
+ }
237
+ if (typeof input === "string") {
238
+ return input
239
+ .split(",")
240
+ .map((v) => v.trim().toLowerCase())
241
+ .filter((v) => v.length > 0);
242
+ }
243
+ return [];
244
+ }
245
+
246
+ function extractDocumentMetadata(
247
+ markdown: string,
248
+ relPath: string,
249
+ ext: string
250
+ ): DocumentMetadata {
251
+ const parsed = parseFrontmatter(markdown);
252
+ const metadata = parsed.metadata;
253
+ const contentType = inferContentType(relPath, ext);
254
+ const categories = new Set<string>([contentType]);
255
+
256
+ const fmCategories = parseCategories(
257
+ metadata.category ?? metadata.categories ?? metadata.type
258
+ );
259
+ for (const category of fmCategories) {
260
+ categories.add(category);
261
+ }
262
+
263
+ let author: string | undefined;
264
+ for (const key of AUTHOR_KEYS) {
265
+ const value = metadata[key];
266
+ if (typeof value === "string" && value.trim().length > 0) {
267
+ author = value.trim();
268
+ break;
269
+ }
270
+ }
271
+
272
+ const normalizedMetadata = new Map<string, unknown>();
273
+ for (const [rawKey, value] of Object.entries(metadata)) {
274
+ const key = normalizeMetadataKey(rawKey);
275
+ if (key.length > 0 && !normalizedMetadata.has(key)) {
276
+ normalizedMetadata.set(key, value);
277
+ }
278
+ }
279
+
280
+ let frontmatterDate: string | undefined;
281
+ for (const key of DATE_KEYS) {
282
+ const normalized = normalizeDate(normalizedMetadata.get(key));
283
+ if (normalized) {
284
+ frontmatterDate = normalized;
285
+ break;
286
+ }
287
+ }
288
+
289
+ const dateFields: Record<string, string> = {};
290
+ for (const [key, value] of normalizedMetadata.entries()) {
291
+ if (!DATE_FIELD_KEY_REGEX.test(key)) {
292
+ continue;
293
+ }
294
+ const normalized = normalizeDate(value);
295
+ if (normalized) {
296
+ dateFields[key] = normalized;
297
+ }
298
+ }
299
+
300
+ return {
301
+ contentType,
302
+ categories: [...categories],
303
+ author,
304
+ frontmatterDate,
305
+ dateFields: Object.keys(dateFields).length > 0 ? dateFields : undefined,
306
+ };
307
+ }
308
+
144
309
  /**
145
310
  * Check if path is a git repository (supports worktrees and submodules).
146
311
  * Uses git rev-parse which handles all git directory layouts.
@@ -268,15 +433,63 @@ export class SyncService {
268
433
  };
269
434
 
270
435
  try {
271
- // 1. Read file bytes
436
+ // 1. Re-stat before read to enforce maxBytes on current file size
437
+ let sourceSize = entry.size;
438
+ let sourceMtime = entry.mtime;
439
+ let sourceCtime = entry.ctime;
440
+ try {
441
+ const sourceStat = await stat(entry.absPath);
442
+ if (!sourceStat.isFile()) {
443
+ return {
444
+ relPath: entry.relPath,
445
+ status: "error",
446
+ errorCode: "NOT_FILE",
447
+ errorMessage: "Path is not a file",
448
+ };
449
+ }
450
+ sourceSize = sourceStat.size;
451
+ sourceMtime = sourceStat.mtime.toISOString();
452
+ sourceCtime = (
453
+ sourceStat.birthtime ??
454
+ sourceStat.ctime ??
455
+ sourceStat.mtime
456
+ ).toISOString();
457
+ } catch {
458
+ return {
459
+ relPath: entry.relPath,
460
+ status: "error",
461
+ errorCode: "NOT_FOUND",
462
+ errorMessage: "File not found",
463
+ };
464
+ }
465
+
466
+ if (sourceSize > limits.maxBytes) {
467
+ const message = `File size ${sourceSize} exceeds limit ${limits.maxBytes}`;
468
+ await store
469
+ .recordError({
470
+ collection: collection.name,
471
+ relPath: entry.relPath,
472
+ code: "TOO_LARGE",
473
+ message,
474
+ })
475
+ .catch(() => undefined);
476
+ return {
477
+ relPath: entry.relPath,
478
+ status: "skipped",
479
+ errorCode: "TOO_LARGE",
480
+ errorMessage: message,
481
+ };
482
+ }
483
+
484
+ // 2. Read file bytes
272
485
  const bytes = await Bun.file(entry.absPath).bytes();
273
486
 
274
- // 2. Compute sourceHash
487
+ // 3. Compute sourceHash
275
488
  const hasher = new Bun.CryptoHasher("sha256");
276
489
  hasher.update(bytes);
277
490
  const sourceHash = hasher.digest("hex");
278
491
 
279
- // 3. Check existing doc for skip/repair decision
492
+ // 4. Check existing doc for skip/repair decision
280
493
  const existingResult = await store.getDocument(
281
494
  collection.name,
282
495
  entry.relPath
@@ -288,10 +501,10 @@ export class SyncService {
288
501
  return { relPath: entry.relPath, status: "unchanged" };
289
502
  }
290
503
 
291
- // 4. Detect MIME (bytes is already Uint8Array from Bun.file().bytes())
504
+ // 5. Detect MIME (bytes is already Uint8Array from Bun.file().bytes())
292
505
  const mime = this.mimeDetector.detect(entry.absPath, bytes);
293
506
 
294
- // 5. Convert via pipeline
507
+ // 6. Convert via pipeline
295
508
  const convertResult = await this.pipeline.convert({
296
509
  sourcePath: entry.absPath,
297
510
  relativePath: entry.relPath,
@@ -323,8 +536,9 @@ export class SyncService {
323
536
  sourceHash,
324
537
  sourceMime: mime.mime,
325
538
  sourceExt: mime.ext,
326
- sourceSize: entry.size,
327
- sourceMtime: entry.mtime,
539
+ sourceSize,
540
+ sourceMtime,
541
+ sourceCtime,
328
542
  lastErrorCode: convertResult.error.code,
329
543
  lastErrorMessage: convertResult.error.message,
330
544
  // mirrorHash intentionally omitted (will be null)
@@ -348,21 +562,32 @@ export class SyncService {
348
562
  }
349
563
 
350
564
  const artifact = convertResult.value;
565
+ const extractedMetadata = extractDocumentMetadata(
566
+ artifact.markdown,
567
+ entry.relPath,
568
+ mime.ext
569
+ );
351
570
 
352
- // 6. Upsert document - EXPLICITLY clear error fields on success
571
+ // 7. Upsert document - EXPLICITLY clear error fields on success
353
572
  const docidResult = await store.upsertDocument({
354
573
  collection: collection.name,
355
574
  relPath: entry.relPath,
356
575
  sourceHash,
357
576
  sourceMime: mime.mime,
358
577
  sourceExt: mime.ext,
359
- sourceSize: entry.size,
360
- sourceMtime: entry.mtime,
578
+ sourceSize,
579
+ sourceMtime,
580
+ sourceCtime,
361
581
  title: artifact.title,
362
582
  mirrorHash: artifact.mirrorHash,
363
583
  converterId: artifact.meta.converterId,
364
584
  converterVersion: artifact.meta.converterVersion,
365
585
  languageHint: artifact.languageHint ?? collection.languageHint,
586
+ contentType: extractedMetadata.contentType,
587
+ categories: extractedMetadata.categories,
588
+ author: extractedMetadata.author,
589
+ frontmatterDate: extractedMetadata.frontmatterDate,
590
+ dateFields: extractedMetadata.dateFields,
366
591
  // Clear error fields on success (requires store to handle undefined → null)
367
592
  lastErrorCode: undefined,
368
593
  lastErrorMessage: undefined,
@@ -374,7 +599,7 @@ export class SyncService {
374
599
  relPath: entry.relPath,
375
600
  });
376
601
 
377
- // 7. Upsert content (content-addressed dedupe) - CHECKED
602
+ // 8. Upsert content (content-addressed dedupe) - CHECKED
378
603
  const contentResult = await store.upsertContent(
379
604
  artifact.mirrorHash,
380
605
  artifact.markdown
@@ -383,14 +608,14 @@ export class SyncService {
383
608
  mirrorHash: artifact.mirrorHash,
384
609
  });
385
610
 
386
- // 8. Chunk content
611
+ // 9. Chunk content
387
612
  const chunks = this.chunker.chunk(
388
613
  artifact.markdown,
389
614
  DEFAULT_CHUNK_PARAMS,
390
615
  artifact.languageHint ?? collection.languageHint
391
616
  );
392
617
 
393
- // 9. Convert to ChunkInput for store
618
+ // 10. Convert to ChunkInput for store
394
619
  const chunkInputs: ChunkInput[] = chunks.map((c) => ({
395
620
  seq: c.seq,
396
621
  pos: c.pos,
@@ -401,7 +626,7 @@ export class SyncService {
401
626
  tokenCount: c.tokenCount ?? undefined,
402
627
  }));
403
628
 
404
- // 10. Upsert chunks - CHECKED
629
+ // 11. Upsert chunks - CHECKED
405
630
  const chunksResult = await store.upsertChunks(
406
631
  artifact.mirrorHash,
407
632
  chunkInputs
@@ -411,13 +636,13 @@ export class SyncService {
411
636
  chunkCount: chunkInputs.length,
412
637
  });
413
638
 
414
- // 11. Rebuild FTS for this hash - CHECKED
639
+ // 12. Rebuild FTS for this hash - CHECKED
415
640
  const ftsResult = await store.rebuildFtsForHash(artifact.mirrorHash);
416
641
  mustOk(ftsResult, "rebuildFtsForHash", {
417
642
  mirrorHash: artifact.mirrorHash,
418
643
  });
419
644
 
420
- // 12. Extract and store tags from frontmatter and body hashtags
645
+ // 13. Extract and store tags from frontmatter and body hashtags
421
646
  // Always call setDocTags to clear removed tags on re-sync
422
647
  const extractedTags = extractTags(artifact.markdown);
423
648
  const tagsResult = await store.setDocTags(
@@ -430,7 +655,7 @@ export class SyncService {
430
655
  tagCount: extractedTags.length,
431
656
  });
432
657
 
433
- // 13. Extract and store links (wiki and markdown links)
658
+ // 14. Extract and store links (wiki and markdown links)
434
659
  const excludedRanges = getExcludedRanges(artifact.markdown);
435
660
  const lineOffsets = buildLineOffsets(artifact.markdown);
436
661
  const parsedLinks = parseLinks(
@@ -521,6 +746,9 @@ export class SyncService {
521
746
  sourceExt: existingResult.value.sourceExt,
522
747
  sourceSize: existingResult.value.sourceSize,
523
748
  sourceMtime: existingResult.value.sourceMtime,
749
+ sourceCtime:
750
+ existingResult.value.sourceCtime ??
751
+ existingResult.value.sourceMtime,
524
752
  lastErrorCode: code,
525
753
  lastErrorMessage: message,
526
754
  });
@@ -579,6 +807,7 @@ export class SyncService {
579
807
  relPath,
580
808
  size: stats.size,
581
809
  mtime: stats.mtime.toISOString(),
810
+ ctime: (stats.birthtime ?? stats.ctime ?? stats.mtime).toISOString(),
582
811
  };
583
812
 
584
813
  const result = await this.processFile(collection, entry, store, options);
@@ -661,6 +890,7 @@ export class SyncService {
661
890
  let updated = 0;
662
891
  let unchanged = 0;
663
892
  let errored = 0;
893
+ let dynamicSkipped = 0;
664
894
 
665
895
  if (concurrency === 1) {
666
896
  // Sequential processing with batched transactions (Windows perf)
@@ -696,8 +926,15 @@ export class SyncService {
696
926
  });
697
927
  }
698
928
  break;
699
- default:
700
- // 'skipped' status - already counted in filesSkipped
929
+ case "skipped":
930
+ dynamicSkipped += 1;
931
+ if (result.errorCode && result.errorMessage) {
932
+ errors.push({
933
+ relPath: result.relPath,
934
+ code: result.errorCode,
935
+ message: result.errorMessage,
936
+ });
937
+ }
701
938
  break;
702
939
  }
703
940
  }
@@ -763,8 +1000,15 @@ export class SyncService {
763
1000
  });
764
1001
  }
765
1002
  break;
766
- default:
767
- // 'skipped' status - already counted in filesSkipped
1003
+ case "skipped":
1004
+ dynamicSkipped += 1;
1005
+ if (result.errorCode && result.errorMessage) {
1006
+ errors.push({
1007
+ relPath: result.relPath,
1008
+ code: result.errorCode,
1009
+ message: result.errorMessage,
1010
+ });
1011
+ }
768
1012
  break;
769
1013
  }
770
1014
  }
@@ -796,7 +1040,7 @@ export class SyncService {
796
1040
  filesUpdated: updated,
797
1041
  filesUnchanged: unchanged,
798
1042
  filesErrored: errored,
799
- filesSkipped: skipped.length,
1043
+ filesSkipped: skipped.length + dynamicSkipped,
800
1044
  filesMarkedInactive: markedInactive,
801
1045
  durationMs: Date.now() - startTime,
802
1046
  errors,
@@ -21,6 +21,8 @@ export interface WalkEntry {
21
21
  size: number;
22
22
  /** Modification time (ISO 8601) */
23
23
  mtime: string;
24
+ /** Creation/change time (ISO 8601) */
25
+ ctime: string;
24
26
  }
25
27
 
26
28
  /** Walker configuration */
@@ -200,7 +200,7 @@ export class FileWalker implements WalkerPort {
200
200
 
201
201
  // Stat file
202
202
  const file = Bun.file(absPath);
203
- let stat: { size: number; mtime: Date };
203
+ let stat: { size: number; mtime: Date; ctime?: Date; birthtime?: Date };
204
204
  try {
205
205
  stat = await file.stat();
206
206
  } catch {
@@ -224,6 +224,7 @@ export class FileWalker implements WalkerPort {
224
224
  relPath,
225
225
  size: stat.size,
226
226
  mtime: stat.mtime.toISOString(),
227
+ ctime: (stat.birthtime ?? stat.ctime ?? stat.mtime).toISOString(),
227
228
  });
228
229
  }
229
230
 
@@ -56,6 +56,10 @@ const searchInputSchema = z.object({
56
56
  limit: z.number().int().min(1).max(100).default(5),
57
57
  minScore: z.number().min(0).max(1).optional(),
58
58
  lang: z.string().optional(),
59
+ since: z.string().optional(),
60
+ until: z.string().optional(),
61
+ categories: z.array(z.string()).optional(),
62
+ author: z.string().optional(),
59
63
  tagsAll: z.array(z.string()).optional(),
60
64
  tagsAny: z.array(z.string()).optional(),
61
65
  });
@@ -101,16 +105,41 @@ const vsearchInputSchema = z.object({
101
105
  limit: z.number().int().min(1).max(100).default(5),
102
106
  minScore: z.number().min(0).max(1).optional(),
103
107
  lang: z.string().optional(),
108
+ since: z.string().optional(),
109
+ until: z.string().optional(),
110
+ categories: z.array(z.string()).optional(),
111
+ author: z.string().optional(),
104
112
  tagsAll: z.array(z.string()).optional(),
105
113
  tagsAny: z.array(z.string()).optional(),
106
114
  });
107
115
 
108
- const queryInputSchema = z.object({
116
+ const queryModeInputSchema = z.object({
117
+ mode: z.enum(["term", "intent", "hyde"]),
118
+ text: z.string().trim().min(1, "Query mode text cannot be empty"),
119
+ });
120
+
121
+ export const queryInputSchema = z.object({
109
122
  query: z.string().min(1, "Query cannot be empty"),
110
123
  collection: z.string().optional(),
111
124
  limit: z.number().int().min(1).max(100).default(5),
112
125
  minScore: z.number().min(0).max(1).optional(),
113
126
  lang: z.string().optional(),
127
+ since: z.string().optional(),
128
+ until: z.string().optional(),
129
+ categories: z.array(z.string()).optional(),
130
+ author: z.string().optional(),
131
+ queryModes: z
132
+ .array(queryModeInputSchema)
133
+ .superRefine((entries, ctx) => {
134
+ const hydeCount = entries.filter((entry) => entry.mode === "hyde").length;
135
+ if (hydeCount > 1) {
136
+ ctx.addIssue({
137
+ code: z.ZodIssueCode.custom,
138
+ message: "Only one hyde mode is allowed in queryModes",
139
+ });
140
+ }
141
+ })
142
+ .optional(),
114
143
  fast: z.boolean().default(false),
115
144
  thorough: z.boolean().default(false),
116
145
  expand: z.boolean().default(false), // Default: skip expansion
@@ -11,7 +11,11 @@ import type {
11
11
  GenerationPort,
12
12
  RerankPort,
13
13
  } from "../../llm/types";
14
- import type { SearchResult, SearchResults } from "../../pipeline/types";
14
+ import type {
15
+ QueryModeInput,
16
+ SearchResult,
17
+ SearchResults,
18
+ } from "../../pipeline/types";
15
19
  import type { ToolContext } from "../server";
16
20
 
17
21
  import { parseUri } from "../../app/constants";
@@ -32,6 +36,11 @@ interface QueryInput {
32
36
  limit?: number;
33
37
  minScore?: number;
34
38
  lang?: string;
39
+ since?: string;
40
+ until?: string;
41
+ categories?: string[];
42
+ author?: string;
43
+ queryModes?: QueryModeInput[];
35
44
  fast?: boolean;
36
45
  thorough?: boolean;
37
46
  expand?: boolean;
@@ -158,6 +167,7 @@ export function handleQuery(
158
167
  // Determine noExpand/noRerank based on mode flags
159
168
  // Priority: fast > thorough > expand/rerank params > defaults
160
169
  // Default: noExpand=true (skip expansion), noRerank=false (with reranking)
170
+ const hasStructuredModes = Boolean(args.queryModes?.length);
161
171
  let noExpand = true;
162
172
  let noRerank = false;
163
173
 
@@ -177,8 +187,13 @@ export function handleQuery(
177
187
  }
178
188
  }
179
189
 
190
+ // Structured query modes replace generated expansion.
191
+ if (hasStructuredModes) {
192
+ noExpand = true;
193
+ }
194
+
180
195
  // Create generation port (for expansion) - optional
181
- if (!noExpand) {
196
+ if (!noExpand && !hasStructuredModes) {
182
197
  const genResult = await llm.createGenerationPort(preset.gen, {
183
198
  policy,
184
199
  onProgress: (progress) => downloadProgress("gen", progress),
@@ -232,8 +247,13 @@ export function handleQuery(
232
247
  minScore: args.minScore,
233
248
  collection: args.collection,
234
249
  queryLanguageHint: args.lang, // Affects expansion prompt, not retrieval
250
+ since: args.since,
251
+ until: args.until,
252
+ categories: args.categories,
253
+ author: args.author,
235
254
  noExpand,
236
255
  noRerank,
256
+ queryModes: args.queryModes,
237
257
  tagsAll: normalizeTagFilters(args.tagsAll),
238
258
  tagsAny: normalizeTagFilters(args.tagsAny),
239
259
  });
@@ -19,6 +19,10 @@ interface SearchInput {
19
19
  limit?: number;
20
20
  minScore?: number;
21
21
  lang?: string;
22
+ since?: string;
23
+ until?: string;
24
+ categories?: string[];
25
+ author?: string;
22
26
  tagsAll?: string[];
23
27
  tagsAny?: string[];
24
28
  }
@@ -104,6 +108,10 @@ export function handleSearch(
104
108
  minScore: args.minScore,
105
109
  collection: args.collection,
106
110
  lang: args.lang,
111
+ since: args.since,
112
+ until: args.until,
113
+ categories: args.categories,
114
+ author: args.author,
107
115
  tagsAll: normalizeTagFilters(args.tagsAll),
108
116
  tagsAny: normalizeTagFilters(args.tagsAny),
109
117
  });
@@ -28,6 +28,10 @@ interface VsearchInput {
28
28
  limit?: number;
29
29
  minScore?: number;
30
30
  lang?: string;
31
+ since?: string;
32
+ until?: string;
33
+ categories?: string[];
34
+ author?: string;
31
35
  tagsAll?: string[];
32
36
  tagsAny?: string[];
33
37
  }
@@ -188,6 +192,10 @@ export function handleVsearch(
188
192
  limit: args.limit ?? 5,
189
193
  minScore: args.minScore,
190
194
  collection: args.collection,
195
+ since: args.since,
196
+ until: args.until,
197
+ categories: args.categories,
198
+ author: args.author,
191
199
  tagsAll: normalizeTagFilters(args.tagsAll),
192
200
  tagsAny: normalizeTagFilters(args.tagsAny),
193
201
  }