@gmickel/gno 1.27.0 → 1.28.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 (83) hide show
  1. package/README.md +2 -2
  2. package/assets/skill/SKILL.md +26 -1
  3. package/browser-extension/artifacts/{gno-browser-clipper-v1.27.0.zip → gno-browser-clipper-v1.28.0.zip} +0 -0
  4. package/browser-extension/artifacts/gno-browser-clipper-v1.28.0.zip.sha256 +1 -0
  5. package/browser-extension/dist/manifest.json +1 -1
  6. package/package.json +2 -2
  7. package/spec/cli.md +41 -4
  8. package/spec/db/schema.sql +12 -0
  9. package/spec/mcp.md +5 -0
  10. package/spec/output-schemas/ask.schema.json +125 -0
  11. package/spec/output-schemas/context-capsule-v1.schema.json +94 -1
  12. package/spec/output-schemas/get.schema.json +94 -0
  13. package/spec/output-schemas/mcp-job-status.schema.json +28 -0
  14. package/spec/output-schemas/multi-get.schema.json +128 -0
  15. package/spec/output-schemas/record-import.schema.json +193 -0
  16. package/spec/output-schemas/search-result.schema.json +94 -0
  17. package/spec/output-schemas/search-results.schema.json +125 -0
  18. package/spec/project-profile.schema.json +66 -0
  19. package/src/app/context-format.ts +1 -1
  20. package/src/cli/commands/get.ts +12 -2
  21. package/src/cli/commands/index-cmd.ts +13 -0
  22. package/src/cli/commands/multi-get.ts +9 -2
  23. package/src/cli/commands/shared.ts +28 -0
  24. package/src/cli/commands/update.ts +5 -0
  25. package/src/cli/program.ts +4 -0
  26. package/src/config/project-profile.ts +2 -0
  27. package/src/config/types.ts +16 -0
  28. package/src/converters/adapters/browser-export/adapter.ts +199 -0
  29. package/src/converters/adapters/browser-export/formats.ts +358 -0
  30. package/src/converters/adapters/email/adapter.ts +429 -0
  31. package/src/converters/adapters/email/html.ts +162 -0
  32. package/src/converters/adapters/email/mime.ts +454 -0
  33. package/src/converters/adapters/email/parameters.ts +77 -0
  34. package/src/converters/adapters/ical/adapter.ts +475 -0
  35. package/src/converters/adapters/ical/recurrence.ts +186 -0
  36. package/src/converters/adapters/jsonl/adapter.ts +238 -0
  37. package/src/converters/adapters/jsonl/config.ts +105 -0
  38. package/src/converters/adapters/shared/html-text.ts +165 -0
  39. package/src/converters/adapters/shared/record-utils.ts +79 -0
  40. package/src/converters/adapters/shared/utf8-lines.ts +141 -0
  41. package/src/converters/adapters/transcript/adapter.ts +295 -0
  42. package/src/converters/adapters/transcript/json.ts +184 -0
  43. package/src/converters/adapters/transcript/model.ts +171 -0
  44. package/src/converters/adapters/transcript/text.ts +58 -0
  45. package/src/converters/adapters/transcript/timed.ts +152 -0
  46. package/src/converters/index.ts +11 -1
  47. package/src/converters/mime.ts +8 -0
  48. package/src/converters/pipeline.ts +15 -1
  49. package/src/converters/registry.ts +37 -1
  50. package/src/converters/types.ts +136 -0
  51. package/src/core/context-capsule-schema.ts +87 -0
  52. package/src/core/context-capsule.ts +20 -0
  53. package/src/core/context-evidence.ts +7 -1
  54. package/src/core/document-capabilities.ts +12 -0
  55. package/src/core/project-profile-apply-state.ts +5 -0
  56. package/src/core/project-profile.ts +4 -0
  57. package/src/core/record-metadata.ts +49 -0
  58. package/src/ingestion/record-adapter-canonical.ts +433 -0
  59. package/src/ingestion/record-adapter.ts +437 -0
  60. package/src/ingestion/record-container.ts +688 -0
  61. package/src/ingestion/record-path.ts +20 -0
  62. package/src/ingestion/record-sync.ts +70 -0
  63. package/src/ingestion/sync.ts +228 -36
  64. package/src/ingestion/types.ts +69 -1
  65. package/src/ingestion/walker.ts +31 -11
  66. package/src/mcp/tools/get.ts +10 -2
  67. package/src/mcp/tools/multi-get.ts +9 -2
  68. package/src/mcp/tools/workspace-write.ts +2 -0
  69. package/src/pipeline/filters.ts +1 -1
  70. package/src/pipeline/graph-retrieval.ts +7 -4
  71. package/src/pipeline/hybrid.ts +7 -4
  72. package/src/pipeline/result-context.ts +12 -4
  73. package/src/pipeline/search.ts +11 -4
  74. package/src/pipeline/types.ts +3 -0
  75. package/src/pipeline/vsearch.ts +26 -8
  76. package/src/sdk/documents.ts +13 -5
  77. package/src/serve/browse-tree.ts +4 -2
  78. package/src/serve/routes/api.ts +63 -65
  79. package/src/store/migrations/022-record-export-lineage.ts +42 -0
  80. package/src/store/migrations/index.ts +2 -0
  81. package/src/store/sqlite/adapter.ts +114 -7
  82. package/src/store/types.ts +40 -0
  83. package/browser-extension/artifacts/gno-browser-clipper-v1.27.0.zip.sha256 +0 -1
@@ -15,6 +15,10 @@ import {
15
15
  type DocumentCapabilities,
16
16
  } from "../../core/document-capabilities";
17
17
  import { resolveEffectiveIndex } from "../../core/indexed-reference";
18
+ import {
19
+ projectRecordEvidenceMetadata,
20
+ type RecordEvidenceMetadata,
21
+ } from "../../core/record-metadata";
18
22
  import { parseRef } from "../../core/ref-parser";
19
23
  import {
20
24
  attachRetrievalTraceMetadata,
@@ -54,6 +58,7 @@ interface GetResponse {
54
58
  converterVersion?: string;
55
59
  mirrorHash?: string;
56
60
  };
61
+ record?: RecordEvidenceMetadata;
57
62
  capabilities: DocumentCapabilities;
58
63
  }
59
64
 
@@ -210,13 +215,14 @@ export function handleGet(
210
215
 
211
216
  // Build absPath
212
217
  const uriParsed = parseUri(doc.uri);
218
+ const sourceRelPath = doc.recordSourcePath ?? doc.relPath;
213
219
  let absPath: string | undefined;
214
220
  if (uriParsed) {
215
221
  const collection = ctx.collections.find(
216
222
  (c) => c.name === uriParsed.collection
217
223
  );
218
224
  if (collection) {
219
- absPath = pathJoin(collection.path, doc.relPath);
225
+ absPath = pathJoin(collection.path, sourceRelPath);
220
226
  }
221
227
  }
222
228
 
@@ -230,7 +236,7 @@ export function handleGet(
230
236
  language: doc.languageHint ?? undefined,
231
237
  source: {
232
238
  absPath,
233
- relPath: doc.relPath,
239
+ relPath: sourceRelPath,
234
240
  mime: doc.sourceMime,
235
241
  ext: doc.sourceExt,
236
242
  modifiedAt: doc.sourceMtime,
@@ -244,10 +250,12 @@ export function handleGet(
244
250
  mirrorHash: doc.mirrorHash,
245
251
  }
246
252
  : undefined,
253
+ record: projectRecordEvidenceMetadata(doc),
247
254
  capabilities: getDocumentCapabilities({
248
255
  sourceExt: doc.sourceExt,
249
256
  sourceMime: doc.sourceMime,
250
257
  contentAvailable: doc.mirrorHash !== null,
258
+ recordKey: doc.recordKey,
251
259
  }),
252
260
  };
253
261
 
@@ -11,6 +11,10 @@ import type { ToolContext } from "../server";
11
11
 
12
12
  import { decorateUriForIndex, parseUri } from "../../app/constants";
13
13
  import { resolveEffectiveIndex } from "../../core/indexed-reference";
14
+ import {
15
+ projectRecordEvidenceMetadata,
16
+ type RecordEvidenceMetadata,
17
+ } from "../../core/record-metadata";
14
18
  import { parseRef } from "../../core/ref-parser";
15
19
  import { openScopedIndexStore } from "../../store/sqlite/scoped-index";
16
20
  import { runTool, type ToolResult } from "./index";
@@ -37,6 +41,7 @@ interface DocumentResult {
37
41
  modifiedAt?: string;
38
42
  sizeBytes?: number;
39
43
  };
44
+ record?: RecordEvidenceMetadata;
40
45
  }
41
46
 
42
47
  interface MultiGetResponse {
@@ -234,13 +239,14 @@ export function handleMultiGet(
234
239
 
235
240
  // Build absPath
236
241
  const uriParsed = parseUri(doc.uri);
242
+ const sourceRelPath = doc.recordSourcePath ?? doc.relPath;
237
243
  let absPath: string | undefined;
238
244
  if (uriParsed) {
239
245
  const collection = ctx.collections.find(
240
246
  (c) => c.name === uriParsed.collection
241
247
  );
242
248
  if (collection) {
243
- absPath = pathJoin(collection.path, doc.relPath);
249
+ absPath = pathJoin(collection.path, sourceRelPath);
244
250
  }
245
251
  }
246
252
 
@@ -253,12 +259,13 @@ export function handleMultiGet(
253
259
  truncated,
254
260
  source: {
255
261
  absPath,
256
- relPath: doc.relPath,
262
+ relPath: sourceRelPath,
257
263
  mime: doc.sourceMime,
258
264
  ext: doc.sourceExt,
259
265
  modifiedAt: doc.sourceMtime,
260
266
  sizeBytes: doc.sourceSize,
261
267
  },
268
+ record: projectRecordEvidenceMetadata(doc),
262
269
  });
263
270
  }
264
271
 
@@ -141,11 +141,13 @@ function ensureEditable(doc: {
141
141
  sourceExt: string;
142
142
  sourceMime: string;
143
143
  mirrorHash: string | null;
144
+ recordKey?: string | null;
144
145
  }) {
145
146
  const capabilities = getDocumentCapabilities({
146
147
  sourceExt: doc.sourceExt,
147
148
  sourceMime: doc.sourceMime,
148
149
  contentAvailable: doc.mirrorHash !== null,
150
+ recordKey: doc.recordKey,
149
151
  });
150
152
  if (!capabilities.editable) {
151
153
  throw new Error(
@@ -62,7 +62,7 @@ export function evaluateDocumentChunkFilters(
62
62
  matchesExcludedText(
63
63
  [
64
64
  doc.title ?? "",
65
- doc.relPath,
65
+ doc.recordSourcePath ?? doc.relPath,
66
66
  doc.author ?? "",
67
67
  doc.contentType ?? "",
68
68
  ...(doc.categories ?? []),
@@ -42,6 +42,9 @@ const EMPTY_EDGE_CONFIDENCE: Record<GraphEdgeConfidence, number> = {
42
42
  similarity: 0,
43
43
  };
44
44
 
45
+ const sourceRelPath = (doc: DocumentRow): string =>
46
+ doc.recordSourcePath ?? doc.relPath;
47
+
45
48
  const confidenceWeight = (confidence: GraphEdgeConfidence): number => {
46
49
  switch (confidence) {
47
50
  case "explicit":
@@ -253,8 +256,8 @@ export async function expandGraphCandidates(
253
256
  }
254
257
  if (
255
258
  options.relPathPrefix !== undefined &&
256
- doc.relPath !== options.relPathPrefix &&
257
- !doc.relPath.startsWith(`${options.relPathPrefix}/`)
259
+ sourceRelPath(doc) !== options.relPathPrefix &&
260
+ !sourceRelPath(doc).startsWith(`${options.relPathPrefix}/`)
258
261
  ) {
259
262
  continue;
260
263
  }
@@ -337,8 +340,8 @@ export async function expandGraphCandidates(
337
340
  (doc) =>
338
341
  doc.mirrorHash &&
339
342
  (options.relPathPrefix === undefined ||
340
- doc.relPath === options.relPathPrefix ||
341
- doc.relPath.startsWith(`${options.relPathPrefix}/`)) &&
343
+ sourceRelPath(doc) === options.relPathPrefix ||
344
+ sourceRelPath(doc).startsWith(`${options.relPathPrefix}/`)) &&
342
345
  matchesDocumentFilters(doc, options)
343
346
  );
344
347
  const docs = await filterDocsByTags(store, metadataFilteredDocs, options);
@@ -21,6 +21,7 @@ import type {
21
21
  } from "./types";
22
22
 
23
23
  import { normalizeContentTypes } from "../config/content-types";
24
+ import { projectRecordEvidenceMetadata } from "../core/record-metadata";
24
25
  import { embedTextsWithRecovery } from "../embed/batch";
25
26
  import { err, ok } from "../store/types";
26
27
  import { createChunkLookup } from "./chunk-lookup";
@@ -840,10 +841,11 @@ export async function searchHybrid(
840
841
  };
841
842
  const matchesMetadataFilters = (doc: DocumentRow): boolean => {
842
843
  const relPathPrefix = options.retrievalScope?.relPathPrefix;
844
+ const sourceRelPath = doc.recordSourcePath ?? doc.relPath;
843
845
  if (
844
846
  relPathPrefix !== undefined &&
845
- doc.relPath !== relPathPrefix &&
846
- !doc.relPath.startsWith(`${relPathPrefix}/`)
847
+ sourceRelPath !== relPathPrefix &&
848
+ !sourceRelPath.startsWith(`${relPathPrefix}/`)
847
849
  ) {
848
850
  return false;
849
851
  }
@@ -1052,9 +1054,9 @@ export async function searchHybrid(
1052
1054
  snippetLanguage: chunk.language ?? undefined,
1053
1055
  snippetRange,
1054
1056
  source: {
1055
- relPath: doc.relPath,
1057
+ relPath: doc.recordSourcePath ?? doc.relPath,
1056
1058
  absPath: collectionPath
1057
- ? `${collectionPath}/${doc.relPath}`
1059
+ ? `${collectionPath}/${doc.recordSourcePath ?? doc.relPath}`
1058
1060
  : undefined,
1059
1061
  mime: doc.sourceMime,
1060
1062
  ext: doc.sourceExt,
@@ -1068,6 +1070,7 @@ export async function searchHybrid(
1068
1070
  converterId: doc.converterId ?? undefined,
1069
1071
  converterVersion: doc.converterVersion ?? undefined,
1070
1072
  },
1073
+ record: projectRecordEvidenceMetadata(doc),
1071
1074
  };
1072
1075
  const auxiliaryBaseScore =
1073
1076
  auxiliaryBaseScores.get(`${candidate.mirrorHash}:${candidate.seq}`) ??
@@ -16,10 +16,18 @@ export async function attachSearchResultContexts(
16
16
  results: SearchResult[]
17
17
  ): Promise<void> {
18
18
  const validResults = results
19
- .map((result) => ({
20
- identity: contextIdentityFromUri(result.uri),
21
- result,
22
- }))
19
+ .map((result) => {
20
+ const uriIdentity = contextIdentityFromUri(result.uri);
21
+ return {
22
+ identity: uriIdentity
23
+ ? {
24
+ collection: uriIdentity.collection,
25
+ relPath: result.source.relPath,
26
+ }
27
+ : null,
28
+ result,
29
+ };
30
+ })
23
31
  .filter(
24
32
  (
25
33
  entry
@@ -15,6 +15,7 @@ import type {
15
15
  SearchResults,
16
16
  } from "./types";
17
17
 
18
+ import { projectRecordEvidenceMetadata } from "../core/record-metadata";
18
19
  import { getContentBatch } from "../store/content-batch";
19
20
  import { err, ok } from "../store/types";
20
21
  import { createChunkLookup } from "./chunk-lookup";
@@ -85,8 +86,9 @@ interface BuildResultContext {
85
86
  /** Build SearchResult from FtsResult and related data */
86
87
  function buildSearchResult(ctx: BuildResultContext): SearchResult {
87
88
  const { fts, chunk, collectionPath, options, fullContent } = ctx;
89
+ const sourceRelPath = fts.recordSourcePath ?? fts.relPath ?? "";
88
90
  const source: SearchResultSource = {
89
- relPath: fts.relPath ?? "",
91
+ relPath: sourceRelPath,
90
92
  // Use actual source metadata with fallback to markdown defaults
91
93
  mime: fts.sourceMime ?? "text/markdown",
92
94
  ext: fts.sourceExt ?? ".md",
@@ -97,8 +99,8 @@ function buildSearchResult(ctx: BuildResultContext): SearchResult {
97
99
  };
98
100
 
99
101
  // Add absPath if we have collection path (cross-platform safe)
100
- if (collectionPath && fts.relPath) {
101
- source.absPath = pathJoin(collectionPath, fts.relPath);
102
+ if (collectionPath && sourceRelPath) {
103
+ source.absPath = pathJoin(collectionPath, sourceRelPath);
102
104
  }
103
105
 
104
106
  // Determine snippet content and range
@@ -134,6 +136,7 @@ function buildSearchResult(ctx: BuildResultContext): SearchResult {
134
136
  snippetRange,
135
137
  source,
136
138
  conversion: fts.mirrorHash ? { mirrorHash: fts.mirrorHash } : undefined,
139
+ record: projectRecordEvidenceMetadata(fts),
137
140
  };
138
141
  if (!(chunk && fts.mirrorHash)) return result;
139
142
  return attachSearchResultPlannerMetadata(result, {
@@ -287,7 +290,11 @@ export async function searchBm25(
287
290
 
288
291
  const excluded =
289
292
  matchesExcludedText(
290
- [fts.title ?? "", fts.relPath ?? "", fts.snippet ?? ""],
293
+ [
294
+ fts.title ?? "",
295
+ fts.recordSourcePath ?? fts.relPath ?? "",
296
+ fts.snippet ?? "",
297
+ ],
291
298
  options.exclude
292
299
  ) ||
293
300
  matchesExcludedChunks(
@@ -10,6 +10,7 @@ import type {
10
10
  ContextCapsuleV1,
11
11
  ContextCapsuleVerification,
12
12
  } from "../core/context-capsule";
13
+ import type { RecordEvidenceMetadata } from "../core/record-metadata";
13
14
  import type { RetrievalTraceSession } from "../core/retrieval-trace-session";
14
15
  import type { StoreResult } from "../store/types";
15
16
  import type { ClaimVerificationResult } from "./claim-verification";
@@ -86,6 +87,8 @@ export interface SearchResult {
86
87
  context?: string;
87
88
  source: SearchResultSource;
88
89
  conversion?: SearchResultConversion;
90
+ /** Bounded logical-record provenance for file/export adapters. */
91
+ record?: RecordEvidenceMetadata;
89
92
  [SEARCH_RESULT_PLANNER_METADATA]?: SearchResultPlannerMetadata;
90
93
  }
91
94
 
@@ -7,11 +7,12 @@
7
7
 
8
8
  import type { Config } from "../config/types";
9
9
  import type { EmbeddingPort } from "../llm/types";
10
- import type { StorePort } from "../store/types";
10
+ import type { DocumentRow, StorePort } from "../store/types";
11
11
  import type { VectorIndexPort } from "../store/vector/types";
12
12
  import type { SearchOptions, SearchResult, SearchResults } from "./types";
13
13
 
14
14
  import { normalizeContentTypes } from "../config/content-types";
15
+ import { projectRecordEvidenceMetadata } from "../core/record-metadata";
15
16
  import { getContentBatch } from "../store/content-batch";
16
17
  import { err, ok } from "../store/types";
17
18
  import { createChunkLookup } from "./chunk-lookup";
@@ -213,11 +214,12 @@ export async function searchVectorWithEmbedding(
213
214
  const docs = auxiliaryRankingActive ? matchingDocs : [matchingDocs.at(-1)!];
214
215
  for (const doc of docs) {
215
216
  const collectionPath = collectionPaths.get(doc.collection);
217
+ const sourceRelPath = doc.recordSourcePath ?? doc.relPath;
216
218
  const excluded =
217
219
  matchesExcludedText(
218
220
  [
219
221
  doc.title ?? "",
220
- doc.relPath,
222
+ sourceRelPath,
221
223
  doc.author ?? "",
222
224
  doc.contentType ?? "",
223
225
  ...(doc.categories ?? []),
@@ -248,9 +250,9 @@ export async function searchVectorWithEmbedding(
248
250
  endLine: chunk.endLine,
249
251
  },
250
252
  source: {
251
- relPath: doc.relPath,
253
+ relPath: sourceRelPath,
252
254
  absPath: collectionPath
253
- ? `${collectionPath}/${doc.relPath}`
255
+ ? `${collectionPath}/${sourceRelPath}`
254
256
  : undefined,
255
257
  mime: doc.sourceMime,
256
258
  ext: doc.sourceExt,
@@ -266,6 +268,7 @@ export async function searchVectorWithEmbedding(
266
268
  converterVersion: doc.converterVersion ?? undefined,
267
269
  }
268
270
  : undefined,
271
+ record: projectRecordEvidenceMetadata(doc),
269
272
  },
270
273
  doc.collection,
271
274
  contentTypeRules,
@@ -332,6 +335,7 @@ export async function searchVectorWithEmbedding(
332
335
  : undefined;
333
336
 
334
337
  const collectionPath = collectionPaths.get(doc.collection);
338
+ const sourceRelPath = doc.recordSourcePath ?? doc.relPath;
335
339
 
336
340
  const result = applyContentTypeBoost(
337
341
  {
@@ -349,9 +353,9 @@ export async function searchVectorWithEmbedding(
349
353
  ? undefined
350
354
  : { startLine: chunk.startLine, endLine: chunk.endLine },
351
355
  source: {
352
- relPath: doc.relPath,
356
+ relPath: sourceRelPath,
353
357
  absPath: collectionPath
354
- ? `${collectionPath}/${doc.relPath}`
358
+ ? `${collectionPath}/${sourceRelPath}`
355
359
  : undefined,
356
360
  mime: doc.sourceMime,
357
361
  ext: doc.sourceExt,
@@ -367,6 +371,7 @@ export async function searchVectorWithEmbedding(
367
371
  converterVersion: doc.converterVersion ?? undefined,
368
372
  }
369
373
  : undefined,
374
+ record: projectRecordEvidenceMetadata(doc),
370
375
  },
371
376
  doc.collection,
372
377
  contentTypeRules,
@@ -514,6 +519,12 @@ interface DocumentInfo {
514
519
  mirrorHash: string | null;
515
520
  converterId: string | null;
516
521
  converterVersion: string | null;
522
+ recordKey: DocumentRow["recordKey"];
523
+ recordSourcePath: DocumentRow["recordSourcePath"];
524
+ recordSourceLocator: DocumentRow["recordSourceLocator"];
525
+ recordMetadata: DocumentRow["recordMetadata"];
526
+ recordAnchors: DocumentRow["recordAnchors"];
527
+ recordAdapterFingerprint: DocumentRow["recordAdapterFingerprint"];
517
528
  }
518
529
 
519
530
  // ─────────────────────────────────────────────────────────────────────────────
@@ -611,10 +622,11 @@ async function buildDocumentMap(
611
622
  }
612
623
 
613
624
  for (const doc of activeDocs) {
625
+ const sourceRelPath = doc.recordSourcePath ?? doc.relPath;
614
626
  if (
615
627
  options.relPathPrefix !== undefined &&
616
- doc.relPath !== options.relPathPrefix &&
617
- !doc.relPath.startsWith(`${options.relPathPrefix}/`)
628
+ sourceRelPath !== options.relPathPrefix &&
629
+ !sourceRelPath.startsWith(`${options.relPathPrefix}/`)
618
630
  ) {
619
631
  continue;
620
632
  }
@@ -655,6 +667,12 @@ async function buildDocumentMap(
655
667
  mirrorHash: doc.mirrorHash,
656
668
  converterId: doc.converterId,
657
669
  converterVersion: doc.converterVersion,
670
+ recordKey: doc.recordKey,
671
+ recordSourcePath: doc.recordSourcePath,
672
+ recordSourceLocator: doc.recordSourceLocator,
673
+ recordMetadata: doc.recordMetadata,
674
+ recordAnchors: doc.recordAnchors,
675
+ recordAdapterFingerprint: doc.recordAdapterFingerprint,
658
676
  };
659
677
  const matchingDocuments = result.get(doc.mirrorHash!) ?? [];
660
678
  matchingDocuments.push(documentInfo);
@@ -23,6 +23,7 @@ import type {
23
23
  } from "./types";
24
24
 
25
25
  import { getDocumentCapabilities } from "../core/document-capabilities";
26
+ import { projectRecordEvidenceMetadata } from "../core/record-metadata";
26
27
  import { isGlobPattern, parseRef, splitRefs } from "../core/ref-parser";
27
28
  import {
28
29
  attachRetrievalTraceMetadata,
@@ -53,9 +54,10 @@ function buildSourceMeta(
53
54
  config: Config
54
55
  ): GetResponse["source"] {
55
56
  const coll = config.collections.find((c) => c.name === doc.collection);
57
+ const relPath = doc.recordSourcePath ?? doc.relPath;
56
58
  return {
57
- absPath: coll ? `${coll.path}/${doc.relPath}` : undefined,
58
- relPath: doc.relPath,
59
+ absPath: coll ? `${coll.path}/${relPath}` : undefined,
60
+ relPath,
59
61
  mime: doc.sourceMime,
60
62
  ext: doc.sourceExt,
61
63
  sizeBytes: doc.sourceSize,
@@ -129,10 +131,12 @@ async function getDocumentByRefUntraced(
129
131
  language: doc.languageHint ?? undefined,
130
132
  source: buildSourceMeta(doc, config),
131
133
  conversion: buildConversionMeta(doc),
134
+ record: projectRecordEvidenceMetadata(doc),
132
135
  capabilities: getDocumentCapabilities({
133
136
  sourceExt: doc.sourceExt,
134
137
  sourceMime: doc.sourceMime,
135
138
  contentAvailable: doc.mirrorHash !== null,
139
+ recordKey: doc.recordKey,
136
140
  }),
137
141
  };
138
142
  }
@@ -156,10 +160,12 @@ async function getDocumentByRefUntraced(
156
160
  language: doc.languageHint ?? undefined,
157
161
  source: buildSourceMeta(doc, config),
158
162
  conversion: buildConversionMeta(doc),
163
+ record: projectRecordEvidenceMetadata(doc),
159
164
  capabilities: getDocumentCapabilities({
160
165
  sourceExt: doc.sourceExt,
161
166
  sourceMime: doc.sourceMime,
162
167
  contentAvailable: doc.mirrorHash !== null,
168
+ recordKey: doc.recordKey,
163
169
  }),
164
170
  };
165
171
  }
@@ -318,6 +324,7 @@ export async function multiGetDocuments(
318
324
  maxBytes
319
325
  );
320
326
  const coll = config.collections.find((c) => c.name === doc.collection);
327
+ const sourceRelPath = doc.recordSourcePath ?? doc.relPath;
321
328
  documents.push({
322
329
  docid: doc.docid,
323
330
  uri: doc.uri,
@@ -326,11 +333,12 @@ export async function multiGetDocuments(
326
333
  truncated: truncated || undefined,
327
334
  totalLines: content.split("\n").length,
328
335
  source: {
329
- absPath: coll ? `${coll.path}/${doc.relPath}` : undefined,
330
- relPath: doc.relPath,
336
+ absPath: coll ? `${coll.path}/${sourceRelPath}` : undefined,
337
+ relPath: sourceRelPath,
331
338
  mime: doc.sourceMime,
332
339
  ext: doc.sourceExt,
333
340
  },
341
+ record: projectRecordEvidenceMetadata(doc),
334
342
  });
335
343
  }
336
344
 
@@ -385,7 +393,7 @@ export async function listDocuments(
385
393
  uri: d.uri,
386
394
  title: d.title ?? undefined,
387
395
  source: {
388
- relPath: d.relPath,
396
+ relPath: d.recordSourcePath ?? d.relPath,
389
397
  mime: d.sourceMime,
390
398
  ext: d.sourceExt,
391
399
  },
@@ -15,7 +15,7 @@ export interface BrowseTreeNode {
15
15
  type BrowseCollectionLike = Pick<CollectionRow, "name">;
16
16
  type BrowseDocumentLike = Pick<
17
17
  DocumentRow,
18
- "collection" | "relPath" | "active"
18
+ "collection" | "relPath" | "recordSourcePath" | "active"
19
19
  >;
20
20
  type BrowseFolderLike = {
21
21
  collection: string;
@@ -128,7 +128,9 @@ export function buildBrowseTree(
128
128
 
129
129
  root.documentCount += 1;
130
130
 
131
- const normalizedRelPath = normalizeBrowsePath(doc.relPath);
131
+ const normalizedRelPath = normalizeBrowsePath(
132
+ doc.recordSourcePath ?? doc.relPath
133
+ );
132
134
  const parts = normalizedRelPath.split("/").filter(Boolean);
133
135
  const folderParts = parts.slice(0, -1);
134
136