@gmickel/gno 1.17.0 → 1.19.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 (167) hide show
  1. package/README.md +18 -4
  2. package/assets/skill/SKILL.md +44 -1
  3. package/assets/skill/mcp-reference.md +21 -0
  4. package/package.json +3 -2
  5. package/spec/AGENTS.md +83 -0
  6. package/spec/CLAUDE.md +83 -0
  7. package/spec/bench-fixture.schema.json +137 -0
  8. package/spec/cli.md +2894 -0
  9. package/spec/db/schema.sql +442 -0
  10. package/spec/evals-agentic.md +510 -0
  11. package/spec/evals.md +1106 -0
  12. package/spec/mcp.md +2229 -0
  13. package/spec/output-schemas/activation-verification.schema.json +515 -0
  14. package/spec/output-schemas/ask.schema.json +366 -0
  15. package/spec/output-schemas/backlinks.schema.json +131 -0
  16. package/spec/output-schemas/bench-result.schema.json +120 -0
  17. package/spec/output-schemas/capture-receipt.schema.json +143 -0
  18. package/spec/output-schemas/collection-list.schema.json +45 -0
  19. package/spec/output-schemas/context-capsule-v1.schema.json +691 -0
  20. package/spec/output-schemas/context-capsule-verification.schema.json +1338 -0
  21. package/spec/output-schemas/context-list.schema.json +21 -0
  22. package/spec/output-schemas/doctor.schema.json +313 -0
  23. package/spec/output-schemas/error.schema.json +30 -0
  24. package/spec/output-schemas/expansion.schema.json +37 -0
  25. package/spec/output-schemas/get.schema.json +140 -0
  26. package/spec/output-schemas/graph-query.schema.json +99 -0
  27. package/spec/output-schemas/graph.schema.json +371 -0
  28. package/spec/output-schemas/links-list.schema.json +186 -0
  29. package/spec/output-schemas/mcp-add-collection-result.schema.json +23 -0
  30. package/spec/output-schemas/mcp-capture-result.schema.json +152 -0
  31. package/spec/output-schemas/mcp-http-error.schema.json +30 -0
  32. package/spec/output-schemas/mcp-job-list.schema.json +58 -0
  33. package/spec/output-schemas/mcp-job-status.schema.json +224 -0
  34. package/spec/output-schemas/mcp-remove-result.schema.json +39 -0
  35. package/spec/output-schemas/mcp-sync-result.schema.json +41 -0
  36. package/spec/output-schemas/mcp-tag-result.schema.json +33 -0
  37. package/spec/output-schemas/models-list.schema.json +93 -0
  38. package/spec/output-schemas/multi-get.schema.json +103 -0
  39. package/spec/output-schemas/process-status.schema.json +119 -0
  40. package/spec/output-schemas/query-diagnose.schema.json +123 -0
  41. package/spec/output-schemas/resident-status.schema.json +154 -0
  42. package/spec/output-schemas/retrieval-trace-common.schema.json +492 -0
  43. package/spec/output-schemas/retrieval-trace-delete.schema.json +16 -0
  44. package/spec/output-schemas/retrieval-trace-export.schema.json +61 -0
  45. package/spec/output-schemas/retrieval-trace-filters.schema.json +139 -0
  46. package/spec/output-schemas/retrieval-trace-judgment.schema.json +15 -0
  47. package/spec/output-schemas/retrieval-trace-list.schema.json +18 -0
  48. package/spec/output-schemas/retrieval-trace-payloads.schema.json +178 -0
  49. package/spec/output-schemas/retrieval-trace-purge.schema.json +31 -0
  50. package/spec/output-schemas/retrieval-trace-qrels.schema.json +303 -0
  51. package/spec/output-schemas/retrieval-trace-replay.schema.json +286 -0
  52. package/spec/output-schemas/retrieval-trace-show.schema.json +69 -0
  53. package/spec/output-schemas/retrieval-trace-summary.schema.json +65 -0
  54. package/spec/output-schemas/search-result.schema.json +154 -0
  55. package/spec/output-schemas/search-results.schema.json +338 -0
  56. package/spec/output-schemas/similar.schema.json +84 -0
  57. package/spec/output-schemas/status.schema.json +676 -0
  58. package/spec/output-schemas/tags-list.schema.json +48 -0
  59. package/src/app/context-runtime-types.ts +3 -0
  60. package/src/app/context-runtime.ts +15 -1
  61. package/src/cli/commands/ask.ts +106 -36
  62. package/src/cli/commands/context-build.ts +56 -9
  63. package/src/cli/commands/daemon.ts +69 -2
  64. package/src/cli/commands/get.ts +64 -3
  65. package/src/cli/commands/models/pull.ts +13 -3
  66. package/src/cli/commands/query.ts +62 -23
  67. package/src/cli/commands/replay.ts +140 -0
  68. package/src/cli/commands/search.ts +48 -3
  69. package/src/cli/commands/shared.ts +3 -1
  70. package/src/cli/commands/status.ts +2 -0
  71. package/src/cli/commands/trace.ts +200 -0
  72. package/src/cli/commands/vsearch.ts +75 -53
  73. package/src/cli/detach.ts +37 -20
  74. package/src/cli/program.ts +329 -27
  75. package/src/config/index.ts +12 -0
  76. package/src/config/retrieval-traces.ts +56 -0
  77. package/src/config/types.ts +41 -0
  78. package/src/core/context-compiler.ts +11 -4
  79. package/src/core/job-manager.ts +19 -0
  80. package/src/core/mutation-generations.ts +33 -0
  81. package/src/core/retrieval-qrels.ts +405 -0
  82. package/src/core/retrieval-replay-candidate.ts +368 -0
  83. package/src/core/retrieval-replay-types.ts +109 -0
  84. package/src/core/retrieval-replay-validation.ts +89 -0
  85. package/src/core/retrieval-replay.ts +441 -0
  86. package/src/core/retrieval-trace-evidence-origin.ts +175 -0
  87. package/src/core/retrieval-trace-export.ts +113 -0
  88. package/src/core/retrieval-trace-filter-normalization.ts +27 -0
  89. package/src/core/retrieval-trace-filters.ts +19 -0
  90. package/src/core/retrieval-trace-management-helpers.ts +247 -0
  91. package/src/core/retrieval-trace-management-types.ts +132 -0
  92. package/src/core/retrieval-trace-management.ts +422 -0
  93. package/src/core/retrieval-trace-request.ts +141 -0
  94. package/src/core/retrieval-trace-session.ts +494 -0
  95. package/src/core/retrieval-trace.ts +472 -0
  96. package/src/llm/cache.ts +13 -3
  97. package/src/llm/nodeLlamaCpp/adapter.ts +10 -1
  98. package/src/llm/nodeLlamaCpp/lifecycle.ts +71 -0
  99. package/src/mcp/context.ts +161 -0
  100. package/src/mcp/http-security.ts +477 -0
  101. package/src/mcp/http-session.ts +272 -0
  102. package/src/mcp/http-transport.ts +370 -0
  103. package/src/mcp/resources/index.ts +141 -134
  104. package/src/mcp/server.ts +19 -79
  105. package/src/mcp/tools/add-collection.ts +3 -1
  106. package/src/mcp/tools/capture.ts +3 -0
  107. package/src/mcp/tools/clear-collection-embeddings.ts +2 -0
  108. package/src/mcp/tools/context.ts +68 -16
  109. package/src/mcp/tools/embed.ts +62 -52
  110. package/src/mcp/tools/get.ts +35 -1
  111. package/src/mcp/tools/index-cmd.ts +88 -74
  112. package/src/mcp/tools/index.ts +96 -2
  113. package/src/mcp/tools/query.ts +95 -64
  114. package/src/mcp/tools/remove-collection.ts +2 -0
  115. package/src/mcp/tools/search.ts +36 -13
  116. package/src/mcp/tools/status.ts +11 -0
  117. package/src/mcp/tools/sync.ts +16 -14
  118. package/src/mcp/tools/trace.ts +143 -0
  119. package/src/mcp/tools/vsearch.ts +71 -38
  120. package/src/mcp/tools/workspace-write.ts +7 -3
  121. package/src/pipeline/answer.ts +167 -26
  122. package/src/pipeline/graph-retrieval.ts +15 -1
  123. package/src/pipeline/hybrid.ts +151 -43
  124. package/src/pipeline/search.ts +36 -3
  125. package/src/pipeline/trace-metadata.ts +47 -0
  126. package/src/pipeline/types.ts +43 -0
  127. package/src/pipeline/vsearch.ts +101 -38
  128. package/src/sdk/client.ts +380 -71
  129. package/src/sdk/documents.ts +48 -1
  130. package/src/sdk/index.ts +17 -0
  131. package/src/sdk/types.ts +28 -0
  132. package/src/serve/background-runtime.ts +12 -212
  133. package/src/serve/context-capsule.ts +67 -8
  134. package/src/serve/embed-scheduler.ts +74 -43
  135. package/src/serve/index.ts +9 -0
  136. package/src/serve/jobs.ts +78 -80
  137. package/src/serve/public/app.tsx +12 -1
  138. package/src/serve/public/components/HealthCenter.tsx +74 -1
  139. package/src/serve/public/globals.built.css +1 -1
  140. package/src/serve/public/lib/workspace-tabs.ts +2 -0
  141. package/src/serve/public/pages/Dashboard.tsx +11 -0
  142. package/src/serve/public/pages/TraceHistory.tsx +478 -0
  143. package/src/serve/public/pages/trace-history-detail.tsx +224 -0
  144. package/src/serve/resident-admission.ts +159 -0
  145. package/src/serve/resident-background-work.ts +39 -0
  146. package/src/serve/resident-request.ts +55 -0
  147. package/src/serve/resident-runtime.ts +490 -0
  148. package/src/serve/resident-status.ts +96 -0
  149. package/src/serve/retrieval-trace.ts +28 -0
  150. package/src/serve/routes/api.ts +629 -239
  151. package/src/serve/routes/mcp.ts +69 -0
  152. package/src/serve/routes/traces.ts +156 -0
  153. package/src/serve/server.ts +276 -37
  154. package/src/serve/status-model.ts +51 -0
  155. package/src/serve/status.ts +5 -0
  156. package/src/store/index.ts +31 -0
  157. package/src/store/migrations/014-retrieval-traces.ts +303 -0
  158. package/src/store/migrations/index.ts +2 -0
  159. package/src/store/retrieval-trace-codec.ts +384 -0
  160. package/src/store/sqlite/adapter.ts +179 -10
  161. package/src/store/sqlite/retrieval-trace-management-store.ts +341 -0
  162. package/src/store/sqlite/retrieval-trace-retention.ts +349 -0
  163. package/src/store/sqlite/retrieval-trace-rows.ts +267 -0
  164. package/src/store/sqlite/retrieval-trace-store.ts +515 -0
  165. package/src/store/types.ts +297 -0
  166. package/src/store/vector/sqlite-vec.ts +76 -1
  167. package/src/store/vector/types.ts +1 -1
@@ -27,6 +27,8 @@ import {
27
27
  resolveTemporalRange,
28
28
  shouldSortByRecency,
29
29
  } from "./temporal";
30
+ import { attachSearchResultPlannerMetadata } from "./trace-metadata";
31
+ import { SEARCH_RESULT_PLANNER_METADATA } from "./types";
30
32
 
31
33
  // ─────────────────────────────────────────────────────────────────────────────
32
34
  // Score Normalization
@@ -113,7 +115,7 @@ function buildSearchResult(ctx: BuildResultContext): SearchResult {
113
115
  : undefined;
114
116
  }
115
117
 
116
- return {
118
+ const result: SearchResult = {
117
119
  docid: fts.docid ?? "",
118
120
  score: fts.score, // Raw score, normalized later as batch
119
121
  uri: fts.uri ?? "",
@@ -127,6 +129,19 @@ function buildSearchResult(ctx: BuildResultContext): SearchResult {
127
129
  source,
128
130
  conversion: fts.mirrorHash ? { mirrorHash: fts.mirrorHash } : undefined,
129
131
  };
132
+ if (!(chunk && fts.mirrorHash)) return result;
133
+ return attachSearchResultPlannerMetadata(result, {
134
+ retrievalRank: 0,
135
+ mirrorHash: fts.mirrorHash,
136
+ seq: chunk.seq,
137
+ sources: ["bm25"],
138
+ graphExpanded: false,
139
+ startLine: chunk.startLine,
140
+ endLine: chunk.endLine,
141
+ passageHash: new Bun.CryptoHasher("sha256")
142
+ .update(chunk.text)
143
+ .digest("hex"),
144
+ });
130
145
  }
131
146
 
132
147
  // ─────────────────────────────────────────────────────────────────────────────
@@ -142,6 +157,7 @@ export async function searchBm25(
142
157
  query: string,
143
158
  options: SearchOptions = {}
144
159
  ): Promise<ReturnType<typeof ok<SearchResults>>> {
160
+ const traceStartedAt = options.traceSession ? performance.now() : 0;
145
161
  const limit = options.limit ?? 20;
146
162
  const minScore = options.minScore ?? 0;
147
163
  const recencySort = shouldSortByRecency(query);
@@ -161,6 +177,7 @@ export async function searchBm25(
161
177
  const ftsResult = await store.searchFts(query, {
162
178
  limit: retrievalLimit,
163
179
  collection: options.collection,
180
+ relPathPrefix: options.retrievalScope?.relPathPrefix,
164
181
  language: options.lang,
165
182
  snippet: !(options.full || options.lineNumbers),
166
183
  tagsAll: options.tagsAll,
@@ -331,9 +348,13 @@ export async function searchBm25(
331
348
  }
332
349
 
333
350
  const finalResults = filteredResults.slice(0, limit);
351
+ for (const [index, result] of finalResults.entries()) {
352
+ const metadata = result[SEARCH_RESULT_PLANNER_METADATA];
353
+ if (metadata) metadata.retrievalRank = index + 1;
354
+ }
334
355
  await attachSearchResultContexts(store, finalResults);
335
356
 
336
- return ok({
357
+ const output: SearchResults = {
337
358
  results: finalResults,
338
359
  meta: {
339
360
  query,
@@ -349,5 +370,17 @@ export async function searchBm25(
349
370
  author: options.author,
350
371
  queryLanguage,
351
372
  },
352
- });
373
+ };
374
+ const traceResult = await options.traceSession?.recordRetrieval(
375
+ output,
376
+ performance.now() - traceStartedAt
377
+ );
378
+ if (traceResult && !traceResult.ok) {
379
+ return err(
380
+ "QUERY_FAILED",
381
+ `Trace recording failed: ${traceResult.error.message}`,
382
+ traceResult.error.cause
383
+ );
384
+ }
385
+ return ok(output);
353
386
  }
@@ -0,0 +1,47 @@
1
+ /** Non-canonical retrieval metadata attached without changing public payloads. */
2
+
3
+ import type {
4
+ Citation,
5
+ CitationTraceMetadata,
6
+ SearchResult,
7
+ SearchResultPlannerMetadata,
8
+ SearchResults,
9
+ SearchResultsTraceMetadata,
10
+ } from "./types";
11
+
12
+ import {
13
+ CITATION_TRACE_METADATA,
14
+ SEARCH_RESULT_PLANNER_METADATA,
15
+ SEARCH_RESULTS_TRACE_METADATA,
16
+ } from "./types";
17
+
18
+ const attachHiddenMetadata = <Value extends object, Metadata>(
19
+ value: Value,
20
+ key: symbol,
21
+ metadata: Metadata
22
+ ): Value => {
23
+ Object.defineProperty(value, key, {
24
+ configurable: false,
25
+ enumerable: false,
26
+ value: metadata,
27
+ writable: false,
28
+ });
29
+ return value;
30
+ };
31
+
32
+ export const attachSearchResultPlannerMetadata = <Result extends SearchResult>(
33
+ result: Result,
34
+ metadata: SearchResultPlannerMetadata
35
+ ): Result =>
36
+ attachHiddenMetadata(result, SEARCH_RESULT_PLANNER_METADATA, metadata);
37
+
38
+ export const attachCitationTraceMetadata = <Result extends Citation>(
39
+ citation: Result,
40
+ metadata: CitationTraceMetadata
41
+ ): Result => attachHiddenMetadata(citation, CITATION_TRACE_METADATA, metadata);
42
+
43
+ export const attachSearchResultsTraceMetadata = <Result extends SearchResults>(
44
+ results: Result,
45
+ metadata: SearchResultsTraceMetadata
46
+ ): Result =>
47
+ attachHiddenMetadata(results, SEARCH_RESULTS_TRACE_METADATA, metadata);
@@ -5,6 +5,7 @@
5
5
  * @module src/pipeline/types
6
6
  */
7
7
 
8
+ import type { RetrievalTraceSession } from "../core/retrieval-trace-session";
8
9
  import type { StoreResult } from "../store/types";
9
10
 
10
11
  // ─────────────────────────────────────────────────────────────────────────────
@@ -48,6 +49,11 @@ export interface SearchResultPlannerMetadata {
48
49
  seq: number;
49
50
  sources: FusionSource[];
50
51
  graphExpanded: boolean;
52
+ /** Exact canonical chunk coordinates, retained even for full-content output. */
53
+ startLine?: number;
54
+ endLine?: number;
55
+ /** SHA-256 of the complete canonical chunk lines. */
56
+ passageHash?: string;
51
57
  }
52
58
 
53
59
  /** Single search result matching output schema */
@@ -122,10 +128,26 @@ export interface SearchMeta {
122
128
  trace?: QueryDiagnoseTrace;
123
129
  }
124
130
 
131
+ export const SEARCH_RESULTS_TRACE_METADATA = Symbol(
132
+ "gno.searchResultsTraceMetadata"
133
+ );
134
+
135
+ export interface SearchCapabilityOutcome {
136
+ capability: string;
137
+ status: "attempted" | "used" | "unavailable" | "failed";
138
+ reasonCode?: string;
139
+ }
140
+
141
+ export interface SearchResultsTraceMetadata {
142
+ capabilityOutcomes: SearchCapabilityOutcome[];
143
+ fallbackCodes: string[];
144
+ }
145
+
125
146
  /** Complete search results wrapper */
126
147
  export interface SearchResults {
127
148
  results: SearchResult[];
128
149
  meta: SearchMeta;
150
+ [SEARCH_RESULTS_TRACE_METADATA]?: SearchResultsTraceMetadata;
129
151
  }
130
152
 
131
153
  // ─────────────────────────────────────────────────────────────────────────────
@@ -134,12 +156,19 @@ export interface SearchResults {
134
156
 
135
157
  /** Common options for all search commands */
136
158
  export interface SearchOptions {
159
+ /** Internal receipt seam; never serialized or included in public schemas. */
160
+ traceSession?: RetrievalTraceSession;
137
161
  /** Max results */
138
162
  limit?: number;
139
163
  /** Min score threshold (0-1) */
140
164
  minScore?: number;
141
165
  /** Filter by collection */
142
166
  collection?: string;
167
+ /** Internal exact corpus scope used by deterministic retrieval replay. */
168
+ retrievalScope?: {
169
+ relPathPrefix?: string;
170
+ allowedMirrorHashes: string[];
171
+ };
143
172
  /** Language filter/hint (BCP-47) */
144
173
  lang?: string;
145
174
  /** Include full content instead of snippet */
@@ -357,11 +386,25 @@ export const DEFAULT_PIPELINE_CONFIG: PipelineConfig = {
357
386
  // ─────────────────────────────────────────────────────────────────────────────
358
387
 
359
388
  /** Citation reference */
389
+ export const CITATION_TRACE_METADATA = Symbol("gno.citationTraceMetadata");
390
+
391
+ export interface CitationTraceMetadata {
392
+ sourceHash: string;
393
+ mirrorHash: string;
394
+ passageHash: string;
395
+ seq?: number;
396
+ rank: number;
397
+ plannerRank?: number;
398
+ sources?: FusionSource[];
399
+ graphExpanded?: boolean;
400
+ }
401
+
360
402
  export interface Citation {
361
403
  docid: string;
362
404
  uri: string;
363
405
  startLine?: number;
364
406
  endLine?: number;
407
+ [CITATION_TRACE_METADATA]?: CitationTraceMetadata;
365
408
  }
366
409
 
367
410
  /** Source selection entry for answer-generation explain */
@@ -26,6 +26,8 @@ import {
26
26
  shouldSortByRecency,
27
27
  type TemporalRange,
28
28
  } from "./temporal";
29
+ import { attachSearchResultPlannerMetadata } from "./trace-metadata";
30
+ import { SEARCH_RESULT_PLANNER_METADATA } from "./types";
29
31
 
30
32
  // ─────────────────────────────────────────────────────────────────────────────
31
33
  // Score Normalization
@@ -76,6 +78,7 @@ export async function searchVectorWithEmbedding(
76
78
  queryEmbedding: Float32Array,
77
79
  options: SearchOptions = {}
78
80
  ): Promise<ReturnType<typeof ok<SearchResults>>> {
81
+ const traceStartedAt = options.traceSession ? performance.now() : 0;
79
82
  const { store, vectorIndex } = deps;
80
83
  const limit = options.limit ?? 20;
81
84
  const minScore = options.minScore ?? 0;
@@ -102,6 +105,7 @@ export async function searchVectorWithEmbedding(
102
105
  retrievalLimit,
103
106
  {
104
107
  minScore,
108
+ allowedMirrorHashes: options.retrievalScope?.allowedMirrorHashes,
105
109
  }
106
110
  );
107
111
 
@@ -124,6 +128,7 @@ export async function searchVectorWithEmbedding(
124
128
  // Cache docs to avoid N+1 queries (filtered by collection and tags)
125
129
  const docByMirrorHash = await buildDocumentMap(store, {
126
130
  collection: options.collection,
131
+ relPathPrefix: options.retrievalScope?.relPathPrefix,
127
132
  tagsAll: options.tagsAll,
128
133
  tagsAny: options.tagsAny,
129
134
  since: temporalRange.since,
@@ -214,6 +219,7 @@ export async function searchVectorWithEmbedding(
214
219
  language: chunk.language,
215
220
  startLine: chunk.startLine,
216
221
  endLine: chunk.endLine,
222
+ seq: chunk.seq,
217
223
  },
218
224
  score,
219
225
  });
@@ -223,40 +229,56 @@ export async function searchVectorWithEmbedding(
223
229
 
224
230
  const collectionPath = collectionPaths.get(doc.collection);
225
231
 
226
- results.push({
227
- docid: doc.docid,
228
- score,
229
- uri: doc.uri,
230
- title: doc.title ?? undefined,
231
- contentType: doc.contentType ?? undefined,
232
- categories: doc.categories ?? undefined,
233
- line: chunk.startLine,
234
- snippet: chunk.text,
235
- snippetLanguage: chunk.language ?? undefined,
236
- snippetRange: {
237
- startLine: chunk.startLine,
238
- endLine: chunk.endLine,
239
- },
240
- source: {
241
- relPath: doc.relPath,
242
- absPath: collectionPath
243
- ? `${collectionPath}/${doc.relPath}`
244
- : undefined,
245
- mime: doc.sourceMime,
246
- ext: doc.sourceExt,
247
- modifiedAt: doc.sourceMtime,
248
- documentDate: doc.frontmatterDate ?? undefined,
249
- sizeBytes: doc.sourceSize,
250
- sourceHash: doc.sourceHash,
251
- },
252
- conversion: doc.mirrorHash
253
- ? {
254
- mirrorHash: doc.mirrorHash,
255
- converterId: doc.converterId ?? undefined,
256
- converterVersion: doc.converterVersion ?? undefined,
257
- }
258
- : undefined,
259
- });
232
+ results.push(
233
+ attachSearchResultPlannerMetadata(
234
+ {
235
+ docid: doc.docid,
236
+ score,
237
+ uri: doc.uri,
238
+ title: doc.title ?? undefined,
239
+ contentType: doc.contentType ?? undefined,
240
+ categories: doc.categories ?? undefined,
241
+ line: chunk.startLine,
242
+ snippet: chunk.text,
243
+ snippetLanguage: chunk.language ?? undefined,
244
+ snippetRange: {
245
+ startLine: chunk.startLine,
246
+ endLine: chunk.endLine,
247
+ },
248
+ source: {
249
+ relPath: doc.relPath,
250
+ absPath: collectionPath
251
+ ? `${collectionPath}/${doc.relPath}`
252
+ : undefined,
253
+ mime: doc.sourceMime,
254
+ ext: doc.sourceExt,
255
+ modifiedAt: doc.sourceMtime,
256
+ documentDate: doc.frontmatterDate ?? undefined,
257
+ sizeBytes: doc.sourceSize,
258
+ sourceHash: doc.sourceHash,
259
+ },
260
+ conversion: doc.mirrorHash
261
+ ? {
262
+ mirrorHash: doc.mirrorHash,
263
+ converterId: doc.converterId ?? undefined,
264
+ converterVersion: doc.converterVersion ?? undefined,
265
+ }
266
+ : undefined,
267
+ },
268
+ {
269
+ retrievalRank: 0,
270
+ mirrorHash: vec.mirrorHash,
271
+ seq: chunk.seq,
272
+ sources: ["vector"],
273
+ graphExpanded: false,
274
+ startLine: chunk.startLine,
275
+ endLine: chunk.endLine,
276
+ passageHash: new Bun.CryptoHasher("sha256")
277
+ .update(chunk.text)
278
+ .digest("hex"),
279
+ }
280
+ )
281
+ );
260
282
  }
261
283
 
262
284
  // For --full, fetch full content and build results
@@ -279,7 +301,7 @@ export async function searchVectorWithEmbedding(
279
301
 
280
302
  const collectionPath = collectionPaths.get(doc.collection);
281
303
 
282
- results.push({
304
+ const result: SearchResult = {
283
305
  docid: doc.docid,
284
306
  score,
285
307
  uri: doc.uri,
@@ -312,7 +334,23 @@ export async function searchVectorWithEmbedding(
312
334
  converterVersion: doc.converterVersion ?? undefined,
313
335
  }
314
336
  : undefined,
315
- });
337
+ };
338
+ results.push(
339
+ doc.mirrorHash
340
+ ? attachSearchResultPlannerMetadata(result, {
341
+ retrievalRank: 0,
342
+ mirrorHash: doc.mirrorHash,
343
+ seq: chunk.seq,
344
+ sources: ["vector"],
345
+ graphExpanded: false,
346
+ startLine: chunk.startLine,
347
+ endLine: chunk.endLine,
348
+ passageHash: new Bun.CryptoHasher("sha256")
349
+ .update(chunk.text)
350
+ .digest("hex"),
351
+ })
352
+ : result
353
+ );
316
354
  }
317
355
  }
318
356
 
@@ -334,9 +372,13 @@ export async function searchVectorWithEmbedding(
334
372
  }
335
373
 
336
374
  const finalResults = results.slice(0, limit);
375
+ for (const [index, result] of finalResults.entries()) {
376
+ const metadata = result[SEARCH_RESULT_PLANNER_METADATA];
377
+ if (metadata) metadata.retrievalRank = index + 1;
378
+ }
337
379
  await attachSearchResultContexts(store, finalResults);
338
380
 
339
- return ok({
381
+ const output: SearchResults = {
340
382
  results: finalResults,
341
383
  meta: {
342
384
  query,
@@ -353,7 +395,19 @@ export async function searchVectorWithEmbedding(
353
395
  author: options.author,
354
396
  queryLanguage,
355
397
  },
356
- });
398
+ };
399
+ const traceResult = await options.traceSession?.recordRetrieval(
400
+ output,
401
+ performance.now() - traceStartedAt
402
+ );
403
+ if (traceResult && !traceResult.ok) {
404
+ return err(
405
+ "QUERY_FAILED",
406
+ `Trace recording failed: ${traceResult.error.message}`,
407
+ traceResult.error.cause
408
+ );
409
+ }
410
+ return ok(output);
357
411
  }
358
412
 
359
413
  /**
@@ -397,6 +451,7 @@ interface ChunkInfo {
397
451
  language: string | null;
398
452
  startLine: number;
399
453
  endLine: number;
454
+ seq: number;
400
455
  }
401
456
 
402
457
  interface DocumentInfo {
@@ -425,6 +480,7 @@ interface DocumentInfo {
425
480
 
426
481
  interface DocumentMapOptions {
427
482
  collection?: string;
483
+ relPathPrefix?: string;
428
484
  tagsAll?: string[];
429
485
  tagsAny?: string[];
430
486
  since?: string;
@@ -513,6 +569,13 @@ async function buildDocumentMap(
513
569
  }
514
570
 
515
571
  for (const doc of activeDocs) {
572
+ if (
573
+ options.relPathPrefix !== undefined &&
574
+ doc.relPath !== options.relPathPrefix &&
575
+ !doc.relPath.startsWith(`${options.relPathPrefix}/`)
576
+ ) {
577
+ continue;
578
+ }
516
579
  if (!isWithinTemporalRange(doc.sourceMtime, temporalRange)) {
517
580
  continue;
518
581
  }