@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
@@ -0,0 +1,437 @@
1
+ import type {
2
+ RecordAdapter,
3
+ RecordAdapterFailure,
4
+ RecordAdapterFailureCode,
5
+ RecordAdapterEvent,
6
+ RecordAdapterInput,
7
+ } from "../converters/types";
8
+
9
+ import {
10
+ type AccountedCanonicalRecord,
11
+ adapterFailureMessage,
12
+ canonicalRecord,
13
+ type CanonicalRecord,
14
+ normalizeRecordAdapterIdentity,
15
+ recordKeyFor,
16
+ recordAdapterFingerprint,
17
+ safeFailure,
18
+ } from "./record-adapter-canonical";
19
+
20
+ export type { CanonicalRecord } from "./record-adapter-canonical";
21
+ export {
22
+ recordAdapterFingerprint,
23
+ recordKeyFor,
24
+ } from "./record-adapter-canonical";
25
+
26
+ export interface RecordAdapterRunResult {
27
+ adapterId: string;
28
+ adapterVersion: string;
29
+ adapterFingerprint: string;
30
+ records: CanonicalRecord[];
31
+ failures: RecordAdapterFailure[];
32
+ failedRecordKeys: string[];
33
+ snapshotState: "complete" | "partial";
34
+ authoritative: boolean;
35
+ stoppedByCap: boolean;
36
+ sourceBytesRead: number;
37
+ }
38
+
39
+ class SourceLimitError extends Error {
40
+ constructor() {
41
+ super("Record adapter source exceeded its byte limit.");
42
+ this.name = "SourceLimitError";
43
+ }
44
+ }
45
+
46
+ const TIMEOUT = Symbol("record-adapter-timeout");
47
+
48
+ const nextBeforeDeadline = async <T>(
49
+ iterator: AsyncIterator<T>,
50
+ deadlineMs: number
51
+ ): Promise<IteratorResult<T> | typeof TIMEOUT> => {
52
+ const remainingMs = deadlineMs - Date.now();
53
+ if (remainingMs <= 0) return TIMEOUT;
54
+ return await new Promise<IteratorResult<T> | typeof TIMEOUT>(
55
+ (resolve, reject) => {
56
+ const timer = setTimeout(() => resolve(TIMEOUT), remainingMs);
57
+ void iterator.next().then(
58
+ (value) => {
59
+ clearTimeout(timer);
60
+ resolve(value);
61
+ },
62
+ (error: unknown) => {
63
+ clearTimeout(timer);
64
+ reject(error);
65
+ }
66
+ );
67
+ }
68
+ );
69
+ };
70
+
71
+ const closeIterator = async (
72
+ iterator: AsyncIterator<unknown>
73
+ ): Promise<boolean> => {
74
+ if (!iterator.return) return true;
75
+ try {
76
+ await iterator.return();
77
+ return true;
78
+ } catch {
79
+ return false;
80
+ }
81
+ };
82
+
83
+ /** Consume one adapter snapshot while centrally enforcing every global cap. */
84
+ export async function runRecordAdapter(
85
+ adapter: RecordAdapter,
86
+ input: RecordAdapterInput
87
+ ): Promise<RecordAdapterRunResult> {
88
+ const adapterIdentity = normalizeRecordAdapterIdentity(adapter);
89
+ const adapterFingerprint = recordAdapterFingerprint(adapter);
90
+ const accepted = new Map<string, AccountedCanonicalRecord>();
91
+ const seenKeys = new Set<string>();
92
+ const failedRecordKeys = new Set<string>();
93
+ const failures: RecordAdapterFailure[] = [];
94
+ let totalChars = 0;
95
+ let sourceBytesRead = 0;
96
+ let sourceOpened = false;
97
+ let sourceLimitExceeded = false;
98
+ let sourceReadFailed = false;
99
+ let stoppedByCap = false;
100
+ let terminalCount = 0;
101
+ let terminalState: "complete" | "partial" = "partial";
102
+ let iteratorCloseFailed = false;
103
+ let stoppedByFailureLimit = false;
104
+ let timedOut = false;
105
+ const abortController = new AbortController();
106
+ const deadlineMs = Date.now() + (input.limits.timeoutMs ?? 60_000);
107
+
108
+ const appendFailure = (failure: RecordAdapterFailure): boolean => {
109
+ if (stoppedByFailureLimit) return true;
110
+ if (input.limits.maxFailures > 0) failures.push(failure);
111
+ if (failures.length < input.limits.maxFailures) return false;
112
+ failures.push(
113
+ safeFailure(
114
+ input,
115
+ "FAILURE_LIMIT",
116
+ "Record snapshot reached its failure limit.",
117
+ true
118
+ )
119
+ );
120
+ stoppedByFailureLimit = true;
121
+ stoppedByCap = true;
122
+ return true;
123
+ };
124
+
125
+ const boundedOpen = (): AsyncIterable<Uint8Array> => ({
126
+ [Symbol.asyncIterator](): AsyncIterator<Uint8Array> {
127
+ if (sourceOpened)
128
+ throw new Error("Record adapter source may be opened once.");
129
+ sourceOpened = true;
130
+ let iterator: AsyncIterator<Uint8Array>;
131
+ try {
132
+ iterator = input.open(abortController.signal)[Symbol.asyncIterator]();
133
+ } catch {
134
+ sourceReadFailed = true;
135
+ throw new Error("Record adapter source could not be opened.");
136
+ }
137
+ return {
138
+ async next() {
139
+ let next: IteratorResult<Uint8Array>;
140
+ try {
141
+ next = await iterator.next();
142
+ } catch {
143
+ sourceReadFailed = true;
144
+ throw new Error("Record adapter source read failed.");
145
+ }
146
+ if (next.done) return next;
147
+ sourceBytesRead += next.value.byteLength;
148
+ if (sourceBytesRead > input.limits.maxSourceBytes) {
149
+ sourceLimitExceeded = true;
150
+ iteratorCloseFailed ||= !(await closeIterator(iterator));
151
+ throw new SourceLimitError();
152
+ }
153
+ return next;
154
+ },
155
+ async return() {
156
+ iteratorCloseFailed ||= !(await closeIterator(iterator));
157
+ return { done: true, value: undefined };
158
+ },
159
+ };
160
+ },
161
+ });
162
+
163
+ let iterator: AsyncIterator<RecordAdapterEvent> | undefined;
164
+ try {
165
+ iterator = adapter
166
+ .records({
167
+ ...input,
168
+ open: boundedOpen,
169
+ signal: abortController.signal,
170
+ })
171
+ [Symbol.asyncIterator]();
172
+ while (true) {
173
+ const next = await nextBeforeDeadline(iterator, deadlineMs);
174
+ if (next === TIMEOUT) {
175
+ timedOut = true;
176
+ stoppedByCap = true;
177
+ abortController.abort();
178
+ failures.push(
179
+ safeFailure(
180
+ input,
181
+ "TIMEOUT",
182
+ "Record adapter exceeded its time limit.",
183
+ true
184
+ )
185
+ );
186
+ break;
187
+ }
188
+ if (next.done) break;
189
+ const event = next.value;
190
+ if (event.type === "snapshot") {
191
+ terminalCount += 1;
192
+ if (terminalCount === 1) terminalState = event.state;
193
+ else {
194
+ const reachedFailureLimit = appendFailure(
195
+ safeFailure(
196
+ input,
197
+ "INVALID_SNAPSHOT",
198
+ "Record adapter emitted more than one terminal snapshot.",
199
+ false
200
+ )
201
+ );
202
+ if (reachedFailureLimit) break;
203
+ }
204
+ continue;
205
+ }
206
+ if (terminalCount > 0) {
207
+ const reachedFailureLimit = appendFailure(
208
+ safeFailure(
209
+ input,
210
+ "INVALID_SNAPSHOT",
211
+ "Record adapter emitted data after its terminal snapshot.",
212
+ false
213
+ )
214
+ );
215
+ if (reachedFailureLimit) break;
216
+ continue;
217
+ }
218
+ if (event.type === "failure") {
219
+ const reachedFailureLimit = appendFailure(
220
+ safeFailure(
221
+ input,
222
+ event.failure.code,
223
+ adapterFailureMessage(event.failure.code),
224
+ event.failure.retryable,
225
+ event.failure.stableId,
226
+ event.failure.sourceLocator
227
+ )
228
+ );
229
+ if (reachedFailureLimit) break;
230
+ } else {
231
+ let record: AccountedCanonicalRecord;
232
+ try {
233
+ record = canonicalRecord(adapter, event.record);
234
+ } catch (error) {
235
+ const code: RecordAdapterFailureCode =
236
+ error instanceof Error && error.message === "invalid source locator"
237
+ ? "INVALID_LOCATOR"
238
+ : error instanceof Error &&
239
+ error.message === "invalid source hash"
240
+ ? "INVALID_SOURCE_HASH"
241
+ : error instanceof Error &&
242
+ error.message === "record metadata out of bounds"
243
+ ? "RECORD_TOO_LARGE"
244
+ : "MISSING_ID";
245
+ const reachedFailureLimit = appendFailure(
246
+ safeFailure(
247
+ input,
248
+ code,
249
+ "Record identity or provenance was invalid.",
250
+ false,
251
+ event.record.stableId
252
+ )
253
+ );
254
+ if (reachedFailureLimit) break;
255
+ continue;
256
+ }
257
+ if (seenKeys.has(record.recordKey)) {
258
+ const previous = accepted.get(record.recordKey);
259
+ if (previous) totalChars -= previous.accountingChars;
260
+ accepted.delete(record.recordKey);
261
+ failedRecordKeys.add(record.recordKey);
262
+ const reachedFailureLimit = appendFailure(
263
+ safeFailure(
264
+ input,
265
+ "DUPLICATE_ID",
266
+ "Duplicate stable record identity; every duplicate was isolated.",
267
+ false,
268
+ record.stableId,
269
+ record.sourceLocator
270
+ )
271
+ );
272
+ if (reachedFailureLimit) break;
273
+ continue;
274
+ }
275
+ seenKeys.add(record.recordKey);
276
+ if (
277
+ record.markdown.length > input.limits.maxRecordChars ||
278
+ record.metadataChars > input.limits.maxMetadataChars
279
+ ) {
280
+ const reachedFailureLimit = appendFailure(
281
+ safeFailure(
282
+ input,
283
+ "RECORD_TOO_LARGE",
284
+ "Record exceeded the canonical character limit.",
285
+ false,
286
+ record.stableId,
287
+ record.sourceLocator
288
+ )
289
+ );
290
+ failedRecordKeys.add(record.recordKey);
291
+ if (reachedFailureLimit) break;
292
+ continue;
293
+ }
294
+ if (accepted.size >= input.limits.maxRecords) {
295
+ failures.push(
296
+ safeFailure(
297
+ input,
298
+ "RECORD_LIMIT",
299
+ "Record snapshot exceeded its record limit.",
300
+ true
301
+ )
302
+ );
303
+ stoppedByCap = true;
304
+ break;
305
+ }
306
+ if (totalChars + record.accountingChars > input.limits.maxTotalChars) {
307
+ failures.push(
308
+ safeFailure(
309
+ input,
310
+ "RECORD_LIMIT",
311
+ "Record snapshot exceeded its total character limit.",
312
+ true
313
+ )
314
+ );
315
+ stoppedByCap = true;
316
+ break;
317
+ }
318
+ accepted.set(record.recordKey, record);
319
+ totalChars += record.accountingChars;
320
+ }
321
+ }
322
+ } catch (error) {
323
+ failures.push(
324
+ safeFailure(
325
+ input,
326
+ error instanceof SourceLimitError
327
+ ? "SOURCE_TOO_LARGE"
328
+ : "ADAPTER_FAILURE",
329
+ error instanceof SourceLimitError
330
+ ? error.message
331
+ : "Record adapter stopped before a terminal snapshot.",
332
+ true
333
+ )
334
+ );
335
+ stoppedByCap ||= error instanceof SourceLimitError;
336
+ } finally {
337
+ if (iterator) {
338
+ if (timedOut) {
339
+ void closeIterator(iterator);
340
+ } else {
341
+ iteratorCloseFailed ||= !(await closeIterator(iterator));
342
+ }
343
+ }
344
+ }
345
+
346
+ if (iteratorCloseFailed) {
347
+ failures.push(
348
+ safeFailure(
349
+ input,
350
+ "ADAPTER_FAILURE",
351
+ "Record adapter iterator did not close cleanly.",
352
+ true
353
+ )
354
+ );
355
+ }
356
+
357
+ if (
358
+ sourceLimitExceeded &&
359
+ !failures.some((failure) => failure.code === "SOURCE_TOO_LARGE")
360
+ ) {
361
+ failures.push(
362
+ safeFailure(
363
+ input,
364
+ "SOURCE_TOO_LARGE",
365
+ "Record adapter source exceeded its byte limit.",
366
+ true
367
+ )
368
+ );
369
+ stoppedByCap = true;
370
+ }
371
+
372
+ if (
373
+ sourceReadFailed &&
374
+ !failures.some((failure) => failure.code === "ADAPTER_FAILURE")
375
+ ) {
376
+ failures.push(
377
+ safeFailure(
378
+ input,
379
+ "ADAPTER_FAILURE",
380
+ "Record adapter source could not be read completely.",
381
+ true
382
+ )
383
+ );
384
+ }
385
+
386
+ if (terminalCount !== 1) {
387
+ failures.push(
388
+ safeFailure(
389
+ input,
390
+ "INVALID_SNAPSHOT",
391
+ "Record adapter did not emit exactly one terminal snapshot.",
392
+ true
393
+ )
394
+ );
395
+ }
396
+
397
+ const authoritative =
398
+ terminalCount === 1 &&
399
+ terminalState === "complete" &&
400
+ failures.length === 0 &&
401
+ !stoppedByCap;
402
+ const failureLimit = Math.max(0, input.limits.maxFailures);
403
+ const boundedFailures =
404
+ failures.length <= failureLimit
405
+ ? failures
406
+ : failureLimit === 0
407
+ ? []
408
+ : [
409
+ ...failures.slice(0, failureLimit - 1),
410
+ safeFailure(
411
+ input,
412
+ "FAILURE_LIMIT",
413
+ "Record snapshot reached its failure limit.",
414
+ true
415
+ ),
416
+ ];
417
+ return {
418
+ adapterId: adapterIdentity.id,
419
+ adapterVersion: adapterIdentity.version,
420
+ adapterFingerprint,
421
+ records: [...accepted.values()]
422
+ .sort((left, right) => left.recordKey.localeCompare(right.recordKey))
423
+ .map(
424
+ ({
425
+ accountingChars: _accountingChars,
426
+ metadataChars: _metadataChars,
427
+ ...record
428
+ }) => record
429
+ ),
430
+ failures: boundedFailures,
431
+ failedRecordKeys: [...failedRecordKeys].sort(),
432
+ snapshotState: terminalState,
433
+ authoritative,
434
+ stoppedByCap,
435
+ sourceBytesRead,
436
+ };
437
+ }