@opum-ai/lore 0.1.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 (91) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +306 -0
  3. package/bin/lore.cjs +109 -0
  4. package/package.json +67 -0
  5. package/src/adapters/backlog.ts +1084 -0
  6. package/src/adapters/git.ts +221 -0
  7. package/src/cli.ts +667 -0
  8. package/src/commands/agent.ts +301 -0
  9. package/src/commands/agents.ts +302 -0
  10. package/src/commands/args.ts +209 -0
  11. package/src/commands/changed.ts +70 -0
  12. package/src/commands/check.ts +1031 -0
  13. package/src/commands/codex-bridge.ts +49 -0
  14. package/src/commands/concurrency.ts +48 -0
  15. package/src/commands/context.ts +292 -0
  16. package/src/commands/discover.ts +89 -0
  17. package/src/commands/explorer.ts +253 -0
  18. package/src/commands/export.ts +93 -0
  19. package/src/commands/fswrite.ts +928 -0
  20. package/src/commands/graph.ts +291 -0
  21. package/src/commands/help.ts +151 -0
  22. package/src/commands/impact.ts +59 -0
  23. package/src/commands/init.ts +583 -0
  24. package/src/commands/instructions.ts +91 -0
  25. package/src/commands/link.ts +929 -0
  26. package/src/commands/new.ts +476 -0
  27. package/src/commands/orphans.ts +457 -0
  28. package/src/commands/path.ts +67 -0
  29. package/src/commands/provenance.ts +68 -0
  30. package/src/commands/query.ts +312 -0
  31. package/src/commands/reconcile-shared.ts +280 -0
  32. package/src/commands/rename.ts +585 -0
  33. package/src/commands/replace.ts +320 -0
  34. package/src/commands/scaffold.ts +346 -0
  35. package/src/commands/schema.ts +293 -0
  36. package/src/commands/snapshot.ts +130 -0
  37. package/src/commands/supersede.ts +400 -0
  38. package/src/commands/sync.ts +371 -0
  39. package/src/commands/tasks.ts +271 -0
  40. package/src/commands/traversal.ts +151 -0
  41. package/src/commands/validate.ts +226 -0
  42. package/src/config.ts +598 -0
  43. package/src/core/agent-bridge.ts +287 -0
  44. package/src/core/agent-context.ts +498 -0
  45. package/src/core/agent-profile.ts +447 -0
  46. package/src/core/bundle.ts +893 -0
  47. package/src/core/check.ts +853 -0
  48. package/src/core/codex-bridge.ts +100 -0
  49. package/src/core/concept.ts +597 -0
  50. package/src/core/consumer-scaffold.ts +433 -0
  51. package/src/core/context.ts +271 -0
  52. package/src/core/explorer-contract.ts +441 -0
  53. package/src/core/explorer-qualification.ts +58 -0
  54. package/src/core/explorer.ts +518 -0
  55. package/src/core/finding.ts +31 -0
  56. package/src/core/graph.ts +201 -0
  57. package/src/core/indexes.ts +436 -0
  58. package/src/core/instructions.ts +209 -0
  59. package/src/core/ladybug-driver.ts +1795 -0
  60. package/src/core/ladybug-lifecycle.ts +1178 -0
  61. package/src/core/ladybug-native.ts +95 -0
  62. package/src/core/ladybug-source.ts +667 -0
  63. package/src/core/links.ts +681 -0
  64. package/src/core/log.ts +253 -0
  65. package/src/core/managed-block.ts +540 -0
  66. package/src/core/manifest.ts +718 -0
  67. package/src/core/order.ts +13 -0
  68. package/src/core/profile.ts +1007 -0
  69. package/src/core/projection.ts +195 -0
  70. package/src/core/query.ts +542 -0
  71. package/src/core/reconcile.ts +236 -0
  72. package/src/core/replace.ts +419 -0
  73. package/src/core/retrieval.ts +213 -0
  74. package/src/core/rewrite.ts +940 -0
  75. package/src/core/scaffold.ts +255 -0
  76. package/src/core/schema.ts +366 -0
  77. package/src/core/snapshot-runtime.ts +52 -0
  78. package/src/core/snapshot-store.ts +287 -0
  79. package/src/core/snapshot.ts +711 -0
  80. package/src/core/template.ts +429 -0
  81. package/src/core/traversal.ts +487 -0
  82. package/src/core/validate.ts +517 -0
  83. package/src/core/workspace-contract.ts +473 -0
  84. package/src/core/workspace-projection.ts +365 -0
  85. package/src/core/workspace-retrieval.ts +196 -0
  86. package/src/core/workspace-source.ts +174 -0
  87. package/src/errors.ts +697 -0
  88. package/src/meta.ts +7 -0
  89. package/src/output.ts +589 -0
  90. package/src/scripts/upstream-backlog-watch.ts +288 -0
  91. package/src/state.ts +390 -0
@@ -0,0 +1,441 @@
1
+ /**
2
+ * Versioned, database-neutral read contract for the local graph explorer.
3
+ *
4
+ * The snapshot contains only source and derived health facts. Browser-local
5
+ * layout coordinates and interaction state use the separate presentation
6
+ * schema below and never participate in snapshot identity or serialization.
7
+ */
8
+
9
+ import { z } from "zod";
10
+ import { compareCodeUnits } from "./order";
11
+ import { type ChangedResult, compareRetainedSnapshots, parseRetainedSnapshot, type RetainedSnapshot } from "./snapshot";
12
+
13
+ export const EXPLORER_SNAPSHOT_SCHEMA_VERSION = "lore-explorer-snapshot/1" as const;
14
+ export const EXPLORER_CHANGE_SNAPSHOT_SCHEMA_VERSION = "lore-explorer-change-snapshot/1" as const;
15
+ export const EXPLORER_PRESENTATION_SCHEMA_VERSION = "lore-explorer-presentation/1" as const;
16
+
17
+ export const EXPLORER_RENDER_LIMITS = Object.freeze({
18
+ initialNodeLimit: 750,
19
+ initialEdgeLimit: 1_500,
20
+ maximumVisibleNodes: 5_000,
21
+ maximumVisibleEdges: 10_000,
22
+ maximumFocusDepth: 4,
23
+ });
24
+
25
+ export const EXPLORER_REFRESH_CONTRACT = Object.freeze({
26
+ hosts: Object.freeze(["127.0.0.1", "::1"] as const),
27
+ method: "GET",
28
+ responseSchemaVersion: EXPLORER_SNAPSHOT_SCHEMA_VERSION,
29
+ canonicalSnapshotBytes: true,
30
+ sameOriginOnly: true,
31
+ acceptsQueryLanguage: false,
32
+ acceptsDatabaseConfiguration: false,
33
+ acceptsWrites: false,
34
+ });
35
+
36
+ export const EXPLORER_INTERACTION_CONTRACT = Object.freeze({
37
+ keyboard: Object.freeze({
38
+ id: "KBD-01",
39
+ compositeNavigationKeys: Object.freeze(["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"] as const),
40
+ selectKey: "Enter",
41
+ returnFocusKey: "Escape",
42
+ pointerRequired: false,
43
+ }),
44
+ screenReader: Object.freeze({
45
+ id: "SR-01",
46
+ equivalentListRequired: true,
47
+ liveRegionPoliteness: "polite",
48
+ canvasOnlyTextAllowed: false,
49
+ }),
50
+ color: Object.freeze({
51
+ id: "COLOR-01",
52
+ minimumContrast: "WCAG 2.2 AA",
53
+ redundantNonColorCueRequired: true,
54
+ }),
55
+ responsive: Object.freeze({
56
+ id: "RESPONSIVE-01",
57
+ minimumViewportCssPixels: 320,
58
+ zoomPercent: 200,
59
+ twoDimensionalPageScrollAllowed: false,
60
+ }),
61
+ empty: Object.freeze({
62
+ id: "EMPTY-01",
63
+ healthState: "empty",
64
+ initialFocus: "status-heading",
65
+ navigationEnabled: false,
66
+ }),
67
+ corrupt: Object.freeze({
68
+ id: "CORRUPT-01",
69
+ healthState: "corrupt",
70
+ navigationEnabled: false,
71
+ destructiveActionAllowed: false,
72
+ }),
73
+ stale: Object.freeze({
74
+ id: "STALE-01",
75
+ healthState: "stale",
76
+ preservesLastCompleteViewOnRefreshFailure: true,
77
+ displayedProvenanceRequired: true,
78
+ }),
79
+ largeGraph: Object.freeze({
80
+ id: "SCALE-01",
81
+ announcesTotalAndVisibleCounts: true,
82
+ requiresExplicitExpansion: true,
83
+ limits: EXPLORER_RENDER_LIMITS,
84
+ }),
85
+ });
86
+
87
+ const digestSchema = z.string().regex(/^sha256:[0-9a-f]{64}$/u);
88
+ const commitSchema = z
89
+ .string()
90
+ .regex(/^[0-9a-f]{40}$/u)
91
+ .nullable();
92
+ const sourcePathSchema = z
93
+ .string()
94
+ .min(1)
95
+ .refine(
96
+ (path) =>
97
+ !path.startsWith("/") &&
98
+ !path.includes("\\") &&
99
+ path.split("/").every((segment) => segment !== "" && segment !== "." && segment !== ".."),
100
+ {
101
+ message: "source paths must be repository-relative POSIX paths",
102
+ },
103
+ );
104
+
105
+ const sourceIdentitySchema = z
106
+ .object({
107
+ repositoryScopeKey: digestSchema,
108
+ snapshotKey: digestSchema,
109
+ bundleId: digestSchema,
110
+ gitCommit: commitSchema,
111
+ exportDigest: digestSchema,
112
+ })
113
+ .strict();
114
+
115
+ const recordProvenanceSchema = sourceIdentitySchema
116
+ .extend({
117
+ recordKey: digestSchema,
118
+ sourcePath: sourcePathSchema.nullable(),
119
+ })
120
+ .strict();
121
+
122
+ const repositoryFactSchema = sourceIdentitySchema
123
+ .extend({
124
+ kind: z.literal("repository"),
125
+ docsRoot: sourcePathSchema,
126
+ displayName: z.string().min(1).max(256),
127
+ })
128
+ .strict();
129
+
130
+ const conceptFactSchema = recordProvenanceSchema
131
+ .extend({
132
+ kind: z.literal("concept"),
133
+ conceptId: z.string().min(1),
134
+ conceptType: z.string().min(1),
135
+ title: z.string().max(1_024).nullable(),
136
+ summary: z.string().max(4_096).nullable(),
137
+ status: z.string().max(256).nullable(),
138
+ tags: z.array(z.string().max(256)).max(256),
139
+ contentHash: digestSchema,
140
+ tokenEstimate: z.number().int().nonnegative(),
141
+ })
142
+ .strict();
143
+
144
+ const taskFactSchema = recordProvenanceSchema
145
+ .extend({
146
+ kind: z.literal("task"),
147
+ taskId: z.string().min(1),
148
+ title: z.string().min(1).max(1_024),
149
+ summary: z.string().max(4_096).nullable(),
150
+ status: z.string().min(1).max(256),
151
+ labels: z.array(z.string().max(256)).max(256),
152
+ priority: z.string().max(256).nullable(),
153
+ assignees: z.array(z.string().max(256)).max(256),
154
+ milestone: z.string().max(256).nullable(),
155
+ parentTaskId: z.string().max(256).nullable(),
156
+ })
157
+ .strict();
158
+
159
+ const authoredEdgeFactSchema = recordProvenanceSchema
160
+ .extend({
161
+ kind: z.literal("authored-edge"),
162
+ edgeKind: z.string().min(1).max(256),
163
+ fromRecordKey: digestSchema,
164
+ toRecordKey: digestSchema.nullable(),
165
+ target: z.string().min(1),
166
+ ordinal: z.number().int().nonnegative(),
167
+ dangling: z.boolean(),
168
+ })
169
+ .strict();
170
+
171
+ const graphCountsSchema = z
172
+ .object({
173
+ repositories: z.number().int().nonnegative(),
174
+ concepts: z.number().int().nonnegative(),
175
+ tasks: z.number().int().nonnegative(),
176
+ authoredEdges: z.number().int().nonnegative(),
177
+ danglingEdges: z.number().int().nonnegative(),
178
+ duplicateEdges: z.number().int().nonnegative(),
179
+ })
180
+ .strict();
181
+
182
+ export const explorerSnapshotSchema = z
183
+ .object({
184
+ schemaVersion: z.literal(EXPLORER_SNAPSHOT_SCHEMA_VERSION),
185
+ source: sourceIdentitySchema.extend({
186
+ docsRoot: sourcePathSchema,
187
+ sourceFingerprint: digestSchema,
188
+ generatedAt: z.null(),
189
+ }),
190
+ facts: z
191
+ .object({
192
+ repositories: z.array(repositoryFactSchema),
193
+ concepts: z.array(conceptFactSchema),
194
+ tasks: z.array(taskFactSchema),
195
+ authoredEdges: z.array(authoredEdgeFactSchema),
196
+ })
197
+ .strict(),
198
+ health: z
199
+ .object({
200
+ state: z.enum(["ready", "empty", "stale", "corrupt"]),
201
+ messageCode: z.string().max(256).nullable(),
202
+ counts: graphCountsSchema,
203
+ warnings: z.array(z.string().max(1_024)).max(64),
204
+ })
205
+ .strict(),
206
+ })
207
+ .strict();
208
+
209
+ export const explorerPresentationStateSchema = z
210
+ .object({
211
+ schemaVersion: z.literal(EXPLORER_PRESENTATION_SCHEMA_VERSION),
212
+ snapshotKey: digestSchema,
213
+ filters: z
214
+ .object({
215
+ search: z.string().max(256),
216
+ kinds: z.array(z.enum(["repository", "concept", "task"])),
217
+ statuses: z.array(z.string().max(256)),
218
+ edgeKinds: z.array(z.string().max(256)),
219
+ graphHealth: z.array(z.enum(["dangling", "duplicate", "supersession"])),
220
+ })
221
+ .strict(),
222
+ selection: z
223
+ .object({
224
+ selectedRecordKey: digestSchema.nullable(),
225
+ focusRecordKey: digestSchema.nullable(),
226
+ depth: z.number().int().min(0).max(EXPLORER_RENDER_LIMITS.maximumFocusDepth),
227
+ })
228
+ .strict(),
229
+ layout: z
230
+ .object({
231
+ algorithmVersion: z.string().min(1).max(256),
232
+ coordinates: z
233
+ .array(
234
+ z
235
+ .object({
236
+ recordKey: digestSchema,
237
+ x: z.number().finite(),
238
+ y: z.number().finite(),
239
+ })
240
+ .strict(),
241
+ )
242
+ .max(EXPLORER_RENDER_LIMITS.maximumVisibleNodes),
243
+ viewport: z
244
+ .object({
245
+ x: z.number().finite(),
246
+ y: z.number().finite(),
247
+ zoom: z.number().finite().positive(),
248
+ })
249
+ .strict(),
250
+ })
251
+ .strict(),
252
+ })
253
+ .strict();
254
+
255
+ export type ExplorerSnapshot = z.infer<typeof explorerSnapshotSchema>;
256
+ export type ExplorerPresentationState = z.infer<typeof explorerPresentationStateSchema>;
257
+
258
+ /** Retained facts plus a reproducible bounded delta for offline historical exploration. */
259
+ export interface ExplorerChangeSnapshot {
260
+ readonly schemaVersion: typeof EXPLORER_CHANGE_SNAPSHOT_SCHEMA_VERSION;
261
+ readonly mode: "snapshot" | "comparison";
262
+ readonly from: RetainedSnapshot;
263
+ readonly to: RetainedSnapshot;
264
+ readonly comparison: ChangedResult;
265
+ }
266
+
267
+ const explorerChangeEnvelopeSchema = z
268
+ .object({
269
+ schemaVersion: z.literal(EXPLORER_CHANGE_SNAPSHOT_SCHEMA_VERSION),
270
+ mode: z.enum(["snapshot", "comparison"]),
271
+ from: z.unknown(),
272
+ to: z.unknown(),
273
+ comparison: z.unknown(),
274
+ })
275
+ .strict();
276
+
277
+ const changedReplaySchema = z
278
+ .object({
279
+ filters: z
280
+ .object({
281
+ repositories: z.array(z.string()),
282
+ kinds: z.array(z.enum(["concept", "task", "edge"])),
283
+ })
284
+ .strict(),
285
+ limits: z.object({ result: z.number().int(), factScan: z.number().int() }).strict(),
286
+ })
287
+ .passthrough();
288
+
289
+ /** Parse historical explorer input and prove its delta is reproducible from embedded source facts. */
290
+ export function parseExplorerChangeSnapshot(value: unknown): ExplorerChangeSnapshot {
291
+ const envelope = explorerChangeEnvelopeSchema.parse(value);
292
+ const from = parseRetainedSnapshot(envelope.from);
293
+ const to = parseRetainedSnapshot(envelope.to);
294
+ const replay = changedReplaySchema.parse(envelope.comparison);
295
+ const comparison = compareRetainedSnapshots(from, to, {
296
+ limit: replay.limits.result,
297
+ repositories: replay.filters.repositories,
298
+ kinds: replay.filters.kinds,
299
+ });
300
+ if (canonicalJson(envelope.comparison) !== canonicalJson(comparison)) {
301
+ throw new Error(
302
+ `invalid ${EXPLORER_CHANGE_SNAPSHOT_SCHEMA_VERSION}: comparison does not match embedded retained facts`,
303
+ );
304
+ }
305
+ if (envelope.mode === "snapshot" && canonicalJson(from) !== canonicalJson(to)) {
306
+ throw new Error(
307
+ `invalid ${EXPLORER_CHANGE_SNAPSHOT_SCHEMA_VERSION}: snapshot mode requires one identical snapshot`,
308
+ );
309
+ }
310
+ return { schemaVersion: envelope.schemaVersion, mode: envelope.mode, from, to, comparison };
311
+ }
312
+
313
+ export function serializeExplorerChangeSnapshot(value: unknown): string {
314
+ return `${canonicalJson(parseExplorerChangeSnapshot(value))}\n`;
315
+ }
316
+
317
+ /** Parse the public snapshot and enforce cross-record determinism/provenance invariants. */
318
+ export function parseExplorerSnapshot(value: unknown): ExplorerSnapshot {
319
+ const snapshot = explorerSnapshotSchema.parse(value);
320
+ assertSortedUnique(snapshot.facts.repositories, (record) => record.repositoryScopeKey, "repositories");
321
+ assertSortedUnique(snapshot.facts.concepts, (record) => record.recordKey, "concepts");
322
+ assertSortedUnique(snapshot.facts.tasks, (record) => record.recordKey, "tasks");
323
+ assertSortedUnique(snapshot.health.warnings, (warning) => warning, "warnings");
324
+ assertEdgesSortedUnique(snapshot.facts.authoredEdges);
325
+
326
+ const sourceIdentity = identityTuple(snapshot.source);
327
+ const recordKeys = new Set<string>();
328
+ for (const repository of snapshot.facts.repositories) {
329
+ assertIdentity(repository, sourceIdentity, "repository");
330
+ }
331
+ for (const record of [...snapshot.facts.concepts, ...snapshot.facts.tasks]) {
332
+ assertIdentity(record, sourceIdentity, record.kind);
333
+ if (recordKeys.has(record.recordKey)) throw contractError(`duplicate record key ${record.recordKey}`);
334
+ recordKeys.add(record.recordKey);
335
+ }
336
+ const nodeKeys = new Set(recordKeys);
337
+
338
+ const edgeFingerprints = new Set<string>();
339
+ let duplicateEdges = 0;
340
+ let danglingEdges = 0;
341
+ for (const edge of snapshot.facts.authoredEdges) {
342
+ assertIdentity(edge, sourceIdentity, "authored edge");
343
+ if (recordKeys.has(edge.recordKey)) throw contractError(`duplicate record key ${edge.recordKey}`);
344
+ recordKeys.add(edge.recordKey);
345
+ if (!nodeKeys.has(edge.fromRecordKey)) throw contractError(`missing edge source ${edge.fromRecordKey}`);
346
+ if (edge.dangling !== (edge.toRecordKey === null)) {
347
+ throw contractError(`edge ${edge.recordKey} has inconsistent dangling state`);
348
+ }
349
+ if (edge.toRecordKey !== null && !nodeKeys.has(edge.toRecordKey)) {
350
+ throw contractError(`missing edge target ${edge.toRecordKey}`);
351
+ }
352
+ if (edge.dangling) danglingEdges++;
353
+ const fingerprint = [edge.fromRecordKey, edge.edgeKind, edge.target].join("\0");
354
+ if (edgeFingerprints.has(fingerprint)) duplicateEdges++;
355
+ edgeFingerprints.add(fingerprint);
356
+ }
357
+
358
+ const actualCounts = {
359
+ repositories: snapshot.facts.repositories.length,
360
+ concepts: snapshot.facts.concepts.length,
361
+ tasks: snapshot.facts.tasks.length,
362
+ authoredEdges: snapshot.facts.authoredEdges.length,
363
+ danglingEdges,
364
+ duplicateEdges,
365
+ };
366
+ if (canonicalJson(snapshot.health.counts) !== canonicalJson(actualCounts)) {
367
+ throw contractError("graph-health counts do not match source facts");
368
+ }
369
+ const factCount = actualCounts.repositories + actualCounts.concepts + actualCounts.tasks + actualCounts.authoredEdges;
370
+ if (snapshot.health.state === "empty" && factCount !== 0) {
371
+ throw contractError("empty graph-health state cannot carry source facts");
372
+ }
373
+ if (snapshot.health.state !== "empty" && actualCounts.repositories !== 1) {
374
+ throw contractError(`${snapshot.health.state} graph-health state requires exactly one M6 repository`);
375
+ }
376
+ if (
377
+ (snapshot.health.state === "stale" || snapshot.health.state === "corrupt") &&
378
+ snapshot.health.messageCode === null
379
+ ) {
380
+ throw contractError(`${snapshot.health.state} graph-health state requires a stable message code`);
381
+ }
382
+ return snapshot;
383
+ }
384
+
385
+ /** Serialize one validated snapshot as canonical UTF-8 JSON with a trailing newline. */
386
+ export function serializeExplorerSnapshot(value: unknown): string {
387
+ return `${canonicalJson(parseExplorerSnapshot(value))}\n`;
388
+ }
389
+
390
+ function identityTuple(identity: z.infer<typeof sourceIdentitySchema>): string {
391
+ return [
392
+ identity.repositoryScopeKey,
393
+ identity.snapshotKey,
394
+ identity.bundleId,
395
+ identity.gitCommit ?? "",
396
+ identity.exportDigest,
397
+ ].join("\0");
398
+ }
399
+
400
+ function assertIdentity(identity: z.infer<typeof sourceIdentitySchema>, expected: string, label: string): void {
401
+ if (identityTuple(identity) !== expected) throw contractError(`${label} provenance differs from snapshot source`);
402
+ }
403
+
404
+ function assertSortedUnique<T>(records: readonly T[], key: (record: T) => string, label: string): void {
405
+ for (let index = 1; index < records.length; index++) {
406
+ const previous = records[index - 1];
407
+ const current = records[index];
408
+ if (previous === undefined || current === undefined) continue;
409
+ if (compareCodeUnits(key(previous), key(current)) >= 0) {
410
+ throw contractError(`${label} must be unique and sorted by UTF-16 code units`);
411
+ }
412
+ }
413
+ }
414
+
415
+ function assertEdgesSortedUnique(edges: readonly z.infer<typeof authoredEdgeFactSchema>[]): void {
416
+ const key = (edge: z.infer<typeof authoredEdgeFactSchema>) =>
417
+ [edge.fromRecordKey, edge.edgeKind, edge.target, String(edge.ordinal).padStart(16, "0"), edge.recordKey].join("\0");
418
+ assertSortedUnique(edges, key, "authored edges");
419
+ }
420
+
421
+ function contractError(message: string): Error {
422
+ return new Error(`invalid ${EXPLORER_SNAPSHOT_SCHEMA_VERSION}: ${message}`);
423
+ }
424
+
425
+ /** RFC-8259-compatible deterministic JSON with lexicographically sorted object keys. */
426
+ function canonicalJson(value: unknown): string {
427
+ return JSON.stringify(canonicalValue(value));
428
+ }
429
+
430
+ function canonicalValue(value: unknown): unknown {
431
+ if (Array.isArray(value)) return value.map((entry) => canonicalValue(entry));
432
+ if (value === null || typeof value !== "object") return value;
433
+ const result: Record<string, unknown> = {};
434
+ for (const key of Object.keys(value).sort(compareCodeUnits)) {
435
+ const entry = (value as Record<string, unknown>)[key];
436
+ if (entry !== undefined && typeof entry !== "function" && typeof entry !== "symbol") {
437
+ result[key] = canonicalValue(entry);
438
+ }
439
+ }
440
+ return result;
441
+ }
@@ -0,0 +1,58 @@
1
+ /** Frozen browser and scale qualification for the static graph explorer. */
2
+
3
+ export const EXPLORER_QUALIFICATION_SCHEMA_VERSION = "lore-explorer-qualification/1" as const;
4
+
5
+ export const EXPLORER_SUPPORTED_BROWSERS = Object.freeze(["chromium", "firefox", "webkit"] as const);
6
+
7
+ export const EXPLORER_LARGE_FIXTURE = Object.freeze({
8
+ seed: "lore-explorer-large-v1",
9
+ concepts: 5_000,
10
+ tasks: 1_000,
11
+ authoredEdges: 10_000,
12
+ });
13
+
14
+ export const EXPLORER_QUALIFICATION_BUDGETS = Object.freeze({
15
+ artifactBytes: 32 * 1024 * 1024,
16
+ loadMilliseconds: 15_000,
17
+ interactionMilliseconds: 2_500,
18
+ mountedElements: 7_500,
19
+ heapBytes: 512 * 1024 * 1024,
20
+ });
21
+
22
+ export interface ExplorerQualificationFixture {
23
+ readonly schemaVersion: typeof EXPLORER_QUALIFICATION_SCHEMA_VERSION;
24
+ readonly playwrightVersion: "1.62.1";
25
+ readonly browsers: typeof EXPLORER_SUPPORTED_BROWSERS;
26
+ readonly seed: typeof EXPLORER_LARGE_FIXTURE.seed;
27
+ readonly records: {
28
+ readonly concepts: typeof EXPLORER_LARGE_FIXTURE.concepts;
29
+ readonly tasks: typeof EXPLORER_LARGE_FIXTURE.tasks;
30
+ readonly authoredEdges: typeof EXPLORER_LARGE_FIXTURE.authoredEdges;
31
+ };
32
+ readonly budgets: {
33
+ readonly artifactBytes: number;
34
+ readonly loadMilliseconds: number;
35
+ readonly interactionMilliseconds: number;
36
+ readonly mountedElements: number;
37
+ readonly heapBytes: number;
38
+ };
39
+ }
40
+
41
+ export function assertExplorerQualificationFixture(value: unknown): ExplorerQualificationFixture {
42
+ const expected: ExplorerQualificationFixture = {
43
+ schemaVersion: EXPLORER_QUALIFICATION_SCHEMA_VERSION,
44
+ playwrightVersion: "1.62.1",
45
+ browsers: EXPLORER_SUPPORTED_BROWSERS,
46
+ seed: EXPLORER_LARGE_FIXTURE.seed,
47
+ records: {
48
+ concepts: EXPLORER_LARGE_FIXTURE.concepts,
49
+ tasks: EXPLORER_LARGE_FIXTURE.tasks,
50
+ authoredEdges: EXPLORER_LARGE_FIXTURE.authoredEdges,
51
+ },
52
+ budgets: EXPLORER_QUALIFICATION_BUDGETS,
53
+ };
54
+ if (JSON.stringify(value) !== JSON.stringify(expected)) {
55
+ throw new Error(`explorer qualification fixture must match ${EXPLORER_QUALIFICATION_SCHEMA_VERSION}`);
56
+ }
57
+ return expected;
58
+ }