@opum-ai/lore 0.1.0 → 0.2.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 (90) hide show
  1. package/README.md +33 -22
  2. package/bin/lore.cjs +35 -7
  3. package/package.json +17 -17
  4. package/src/adapters/backlog.ts +0 -1084
  5. package/src/adapters/git.ts +0 -221
  6. package/src/cli.ts +0 -667
  7. package/src/commands/agent.ts +0 -301
  8. package/src/commands/agents.ts +0 -302
  9. package/src/commands/args.ts +0 -209
  10. package/src/commands/changed.ts +0 -70
  11. package/src/commands/check.ts +0 -1031
  12. package/src/commands/codex-bridge.ts +0 -49
  13. package/src/commands/concurrency.ts +0 -48
  14. package/src/commands/context.ts +0 -292
  15. package/src/commands/discover.ts +0 -89
  16. package/src/commands/explorer.ts +0 -253
  17. package/src/commands/export.ts +0 -93
  18. package/src/commands/fswrite.ts +0 -928
  19. package/src/commands/graph.ts +0 -291
  20. package/src/commands/help.ts +0 -151
  21. package/src/commands/impact.ts +0 -59
  22. package/src/commands/init.ts +0 -583
  23. package/src/commands/instructions.ts +0 -91
  24. package/src/commands/link.ts +0 -929
  25. package/src/commands/new.ts +0 -476
  26. package/src/commands/orphans.ts +0 -457
  27. package/src/commands/path.ts +0 -67
  28. package/src/commands/provenance.ts +0 -68
  29. package/src/commands/query.ts +0 -312
  30. package/src/commands/reconcile-shared.ts +0 -280
  31. package/src/commands/rename.ts +0 -585
  32. package/src/commands/replace.ts +0 -320
  33. package/src/commands/scaffold.ts +0 -346
  34. package/src/commands/schema.ts +0 -293
  35. package/src/commands/snapshot.ts +0 -130
  36. package/src/commands/supersede.ts +0 -400
  37. package/src/commands/sync.ts +0 -371
  38. package/src/commands/tasks.ts +0 -271
  39. package/src/commands/traversal.ts +0 -151
  40. package/src/commands/validate.ts +0 -226
  41. package/src/config.ts +0 -598
  42. package/src/core/agent-bridge.ts +0 -287
  43. package/src/core/agent-context.ts +0 -498
  44. package/src/core/agent-profile.ts +0 -447
  45. package/src/core/bundle.ts +0 -893
  46. package/src/core/check.ts +0 -853
  47. package/src/core/codex-bridge.ts +0 -100
  48. package/src/core/concept.ts +0 -597
  49. package/src/core/consumer-scaffold.ts +0 -433
  50. package/src/core/context.ts +0 -271
  51. package/src/core/explorer-contract.ts +0 -441
  52. package/src/core/explorer-qualification.ts +0 -58
  53. package/src/core/explorer.ts +0 -518
  54. package/src/core/finding.ts +0 -31
  55. package/src/core/graph.ts +0 -201
  56. package/src/core/indexes.ts +0 -436
  57. package/src/core/instructions.ts +0 -209
  58. package/src/core/ladybug-driver.ts +0 -1795
  59. package/src/core/ladybug-lifecycle.ts +0 -1178
  60. package/src/core/ladybug-native.ts +0 -95
  61. package/src/core/ladybug-source.ts +0 -667
  62. package/src/core/links.ts +0 -681
  63. package/src/core/log.ts +0 -253
  64. package/src/core/managed-block.ts +0 -540
  65. package/src/core/manifest.ts +0 -718
  66. package/src/core/order.ts +0 -13
  67. package/src/core/profile.ts +0 -1007
  68. package/src/core/projection.ts +0 -195
  69. package/src/core/query.ts +0 -542
  70. package/src/core/reconcile.ts +0 -236
  71. package/src/core/replace.ts +0 -419
  72. package/src/core/retrieval.ts +0 -213
  73. package/src/core/rewrite.ts +0 -940
  74. package/src/core/scaffold.ts +0 -255
  75. package/src/core/schema.ts +0 -366
  76. package/src/core/snapshot-runtime.ts +0 -52
  77. package/src/core/snapshot-store.ts +0 -287
  78. package/src/core/snapshot.ts +0 -711
  79. package/src/core/template.ts +0 -429
  80. package/src/core/traversal.ts +0 -487
  81. package/src/core/validate.ts +0 -517
  82. package/src/core/workspace-contract.ts +0 -473
  83. package/src/core/workspace-projection.ts +0 -365
  84. package/src/core/workspace-retrieval.ts +0 -196
  85. package/src/core/workspace-source.ts +0 -174
  86. package/src/errors.ts +0 -697
  87. package/src/meta.ts +0 -7
  88. package/src/output.ts +0 -589
  89. package/src/scripts/upstream-backlog-watch.ts +0 -288
  90. package/src/state.ts +0 -390
@@ -1,473 +0,0 @@
1
- /**
2
- * Versioned control-plane and identity contract for explicit local workspaces.
3
- *
4
- * Repository locators are intentionally confined to the manifest and member
5
- * observations. They never participate in durable identity or projection
6
- * records, so moving a checkout does not silently rename its source facts.
7
- */
8
-
9
- import { createHash } from "node:crypto";
10
- import { z } from "zod";
11
- import { compareCodeUnits } from "./order";
12
-
13
- export const WORKSPACE_MANIFEST_SCHEMA_VERSION = "lore-workspace-manifest/1" as const;
14
- export const WORKSPACE_IDENTITY_SCHEMA_VERSION = "lore-workspace-identity/1" as const;
15
-
16
- export const WORKSPACE_STORAGE_CONTRACT = Object.freeze({
17
- projectionRoot: ".lore/cache/workspaces/1/<workspaceKey>/",
18
- sourceOfTruth: "explicit-manifest-and-validated-exports",
19
- discoversMachineRepositories: false,
20
- storesRepositoryLocators: false,
21
- storesCredentials: false,
22
- exposesQueryLanguage: false,
23
- rebuildOnly: true,
24
- deletionScope: "one-workspace-projection",
25
- requiresContainmentAndSymlinkChecks: true,
26
- });
27
-
28
- export const WORKSPACE_LINK_CONTRACT = Object.freeze({
29
- repositoryLocalAuthoredLinksRemainLocal: true,
30
- infersCrossRepositoryLinksFromNames: false,
31
- explicitCrossRepositoryEndpointsRequired: true,
32
- conflictingCandidateDisposition: "reject-candidate",
33
- preservesLastVerifiedProjection: true,
34
- });
35
-
36
- const digestSchema = z.string().regex(/^sha256:[0-9a-f]{64}$/u);
37
- const commitSchema = z
38
- .string()
39
- .regex(/^[0-9a-f]{40}$/u)
40
- .nullable();
41
- const stableIdSchema = z
42
- .string()
43
- .min(1)
44
- .max(64)
45
- .regex(/^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/u);
46
- const boundedTextSchema = z
47
- .string()
48
- .min(1)
49
- .max(4_096)
50
- .refine((value) => value === value.trim() && !value.includes("\0"), {
51
- message: "values must be trimmed and contain no NUL bytes",
52
- });
53
- const sourcePathSchema = z
54
- .string()
55
- .min(1)
56
- .max(4_096)
57
- .refine(
58
- (path) =>
59
- !path.startsWith("/") &&
60
- !path.includes("\\") &&
61
- path.split("/").every((segment) => segment !== "" && segment !== "." && segment !== ".."),
62
- { message: "source paths must be repository-relative POSIX paths" },
63
- );
64
-
65
- const workspaceMemberSchema = z
66
- .object({
67
- memberId: stableIdSchema,
68
- locator: boundedTextSchema,
69
- displayName: z.string().min(1).max(256).nullable(),
70
- expectedRef: boundedTextSchema.max(1_024).nullable(),
71
- })
72
- .strict();
73
-
74
- const workspaceLinkEndpointSchema = z
75
- .object({
76
- memberId: stableIdSchema,
77
- kind: z.enum(["concept", "task"]),
78
- id: boundedTextSchema.max(1_024),
79
- })
80
- .strict();
81
-
82
- const workspaceLinkSchema = z
83
- .object({
84
- linkId: stableIdSchema,
85
- kind: boundedTextSchema.max(256),
86
- from: workspaceLinkEndpointSchema,
87
- to: workspaceLinkEndpointSchema,
88
- })
89
- .strict();
90
-
91
- export const workspaceManifestSchema = z
92
- .object({
93
- schemaVersion: z.literal(WORKSPACE_MANIFEST_SCHEMA_VERSION),
94
- workspaceId: stableIdSchema,
95
- members: z.array(workspaceMemberSchema).min(1).max(256),
96
- links: z.array(workspaceLinkSchema).max(10_000),
97
- })
98
- .strict();
99
-
100
- export type WorkspaceManifest = z.infer<typeof workspaceManifestSchema>;
101
- export type WorkspaceMember = z.infer<typeof workspaceMemberSchema>;
102
- export type WorkspaceLink = z.infer<typeof workspaceLinkSchema>;
103
-
104
- const workspaceSourceSnapshotSchema = z
105
- .object({
106
- memberId: stableIdSchema,
107
- repositoryScopeKey: digestSchema,
108
- bundleId: digestSchema,
109
- gitCommit: commitSchema,
110
- exportDigest: digestSchema,
111
- })
112
- .strict();
113
-
114
- export type WorkspaceSourceSnapshot = z.infer<typeof workspaceSourceSnapshotSchema>;
115
-
116
- const readyObservationSchema = workspaceSourceSnapshotSchema
117
- .extend({
118
- state: z.literal("ready"),
119
- locator: boundedTextSchema,
120
- gitRef: boundedTextSchema.max(1_024).nullable(),
121
- })
122
- .strict();
123
- const missingObservationSchema = z
124
- .object({
125
- state: z.literal("missing"),
126
- memberId: stableIdSchema,
127
- locator: boundedTextSchema,
128
- gitRef: boundedTextSchema.max(1_024).nullable(),
129
- })
130
- .strict();
131
- const workspaceMemberObservationSchema = z.discriminatedUnion("state", [
132
- readyObservationSchema,
133
- missingObservationSchema,
134
- ]);
135
-
136
- export type WorkspaceMemberObservation = z.infer<typeof workspaceMemberObservationSchema>;
137
- export type WorkspaceRecordKind = "concept" | "task" | "authored-edge";
138
-
139
- export interface WorkspaceRepositoryIdentity {
140
- readonly memberId: string;
141
- readonly memberKey: string;
142
- readonly repositoryKey: string;
143
- readonly bundleKey: string;
144
- readonly commitKey: string | null;
145
- readonly exportKey: string;
146
- readonly source: WorkspaceSourceSnapshot;
147
- }
148
-
149
- export interface WorkspaceLinkIdentity {
150
- readonly linkId: string;
151
- readonly workspaceLinkKey: string;
152
- readonly source: WorkspaceLink;
153
- }
154
-
155
- export interface WorkspaceIdentity {
156
- readonly schemaVersion: typeof WORKSPACE_IDENTITY_SCHEMA_VERSION;
157
- readonly workspaceKey: string;
158
- readonly snapshotKey: string;
159
- readonly repositories: readonly WorkspaceRepositoryIdentity[];
160
- readonly links: readonly WorkspaceLinkIdentity[];
161
- }
162
-
163
- export interface WorkspaceRecordIdentity {
164
- readonly workspaceRecordKey: string;
165
- readonly workspaceSourceKey: string | null;
166
- readonly sourceRecordKey: string;
167
- readonly sourcePath: string | null;
168
- }
169
-
170
- /** Derive the portable workspace storage identity without resolving a manifest locator. */
171
- export function workspaceKeyForId(workspaceId: string): string {
172
- stableIdSchema.parse(workspaceId);
173
- return digest(`lore-workspace/1\0${workspaceId}`);
174
- }
175
-
176
- /** Public, locator-free scope attached to every workspace command result. */
177
- export interface WorkspaceResultScope {
178
- readonly schemaVersion: "lore-workspace-result-scope/1";
179
- readonly workspaceId: string;
180
- readonly workspaceKey: string;
181
- readonly snapshotKey: string;
182
- readonly repositories: readonly WorkspaceResultRepository[];
183
- }
184
-
185
- /** One selected repository in a workspace command result. */
186
- export interface WorkspaceResultRepository {
187
- readonly memberId: string;
188
- readonly repositoryKey: string;
189
- readonly repositoryScopeKey: string;
190
- readonly bundleKey: string;
191
- readonly bundleId: string;
192
- readonly commitKey: string | null;
193
- readonly gitCommit: string | null;
194
- readonly exportKey: string;
195
- readonly exportDigest: string;
196
- }
197
-
198
- /** Complete namespaced and original provenance for one public workspace record. */
199
- export interface WorkspaceRecordProvenance extends WorkspaceResultRepository {
200
- readonly recordKind: WorkspaceRecordKind;
201
- readonly recordKey: string;
202
- readonly sourceRecordKey: string;
203
- readonly sourceKey: string | null;
204
- readonly sourcePath: string | null;
205
- readonly sourceId: string;
206
- }
207
-
208
- /** Qualify a repository-local identity for the public workspace id space. */
209
- export function qualifyWorkspaceId(memberId: string, sourceId: string): string {
210
- stableIdSchema.parse(memberId);
211
- if (sourceId.length === 0 || sourceId.includes("\0")) throw contractError("workspace source ids must be non-empty");
212
- return `${memberId}::${sourceId}`;
213
- }
214
-
215
- /** Split a public workspace id without guessing across repository members. */
216
- export function parseQualifiedWorkspaceId(value: string): { memberId: string; sourceId: string } {
217
- const separator = value.indexOf("::");
218
- if (separator <= 0 || separator === value.length - 2) {
219
- throw contractError("workspace ids must use <member-id>::<source-id>");
220
- }
221
- const memberId = value.slice(0, separator);
222
- stableIdSchema.parse(memberId);
223
- return { memberId, sourceId: value.slice(separator + 2) };
224
- }
225
-
226
- const workspaceRepositoryIdentitySchema = z
227
- .object({
228
- memberId: stableIdSchema,
229
- memberKey: digestSchema,
230
- repositoryKey: digestSchema,
231
- bundleKey: digestSchema,
232
- commitKey: digestSchema.nullable(),
233
- exportKey: digestSchema,
234
- source: workspaceSourceSnapshotSchema,
235
- })
236
- .strict();
237
- const workspaceLinkIdentitySchema = z
238
- .object({
239
- linkId: stableIdSchema,
240
- workspaceLinkKey: digestSchema,
241
- source: workspaceLinkSchema,
242
- })
243
- .strict();
244
- export const workspaceIdentitySchema = z
245
- .object({
246
- schemaVersion: z.literal(WORKSPACE_IDENTITY_SCHEMA_VERSION),
247
- workspaceKey: digestSchema,
248
- snapshotKey: digestSchema,
249
- repositories: z.array(workspaceRepositoryIdentitySchema).min(1).max(256),
250
- links: z.array(workspaceLinkIdentitySchema).max(10_000),
251
- })
252
- .strict();
253
-
254
- export type WorkspaceTransitionKind =
255
- | "unchanged"
256
- | "add"
257
- | "remove"
258
- | "branch-change"
259
- | "missing-repository"
260
- | "renamed-repository"
261
- | "repository-replaced"
262
- | "conflicting-link"
263
- | "stale-snapshot";
264
-
265
- export interface WorkspaceTransition {
266
- readonly kind: WorkspaceTransitionKind;
267
- readonly candidateDisposition: "reuse" | "rebuild" | "reject-candidate";
268
- readonly servePrevious: boolean;
269
- readonly removePriorEvidence: boolean;
270
- }
271
-
272
- /** Parse a canonical manifest and enforce explicit membership/link invariants. */
273
- export function parseWorkspaceManifest(value: unknown): WorkspaceManifest {
274
- const manifest = workspaceManifestSchema.parse(value);
275
- assertSortedUnique(manifest.members, (member) => member.memberId, "workspace members");
276
- assertSortedUnique(manifest.links, (link) => link.linkId, "workspace links");
277
-
278
- const members = new Set(manifest.members.map((member) => member.memberId));
279
- const linkFingerprints = new Set<string>();
280
- for (const link of manifest.links) {
281
- if (!members.has(link.from.memberId)) throw contractError(`unknown link source member ${link.from.memberId}`);
282
- if (!members.has(link.to.memberId)) throw contractError(`unknown link target member ${link.to.memberId}`);
283
- const fingerprint = [
284
- link.from.memberId,
285
- link.from.kind,
286
- link.from.id,
287
- link.kind,
288
- link.to.memberId,
289
- link.to.kind,
290
- link.to.id,
291
- ].join("\0");
292
- if (linkFingerprints.has(fingerprint)) throw contractError(`duplicate explicit link ${link.linkId}`);
293
- linkFingerprints.add(fingerprint);
294
- }
295
- return manifest;
296
- }
297
-
298
- /** Serialize a validated manifest as canonical UTF-8 JSON with a trailing newline. */
299
- export function serializeWorkspaceManifest(value: unknown): string {
300
- return `${canonicalJson(parseWorkspaceManifest(value))}\n`;
301
- }
302
-
303
- /**
304
- * Build locator-free workspace identities from a complete set of validated
305
- * repository export observations.
306
- */
307
- export function buildWorkspaceIdentity(manifestValue: unknown, sourceValues: readonly unknown[]): WorkspaceIdentity {
308
- const manifest = parseWorkspaceManifest(manifestValue);
309
- const sources = sourceValues.map((value) => workspaceSourceSnapshotSchema.parse(value));
310
- assertSortedUnique(sources, (source) => source.memberId, "workspace sources");
311
- const expectedMembers = manifest.members.map((member) => member.memberId);
312
- const observedMembers = sources.map((source) => source.memberId);
313
- if (canonicalJson(expectedMembers) !== canonicalJson(observedMembers)) {
314
- throw contractError("workspace sources must exactly match explicit manifest membership");
315
- }
316
-
317
- const workspaceKey = workspaceKeyForId(manifest.workspaceId);
318
- const repositories = sources.map((source): WorkspaceRepositoryIdentity => {
319
- const memberKey = digest(`lore-workspace-member/1\0${workspaceKey}\0${source.memberId}`);
320
- const repositoryKey = digest(
321
- `lore-workspace-repository/1\0${workspaceKey}\0${memberKey}\0${source.repositoryScopeKey}`,
322
- );
323
- return {
324
- memberId: source.memberId,
325
- memberKey,
326
- repositoryKey,
327
- bundleKey: digest(`lore-workspace-bundle/1\0${repositoryKey}\0${source.bundleId}`),
328
- commitKey:
329
- source.gitCommit === null ? null : digest(`lore-workspace-commit/1\0${repositoryKey}\0${source.gitCommit}`),
330
- exportKey: digest(`lore-workspace-export/1\0${repositoryKey}\0${source.exportDigest}`),
331
- source,
332
- };
333
- });
334
- const links = manifest.links.map(
335
- (link): WorkspaceLinkIdentity => ({
336
- linkId: link.linkId,
337
- workspaceLinkKey: digest(`lore-workspace-link/1\0${workspaceKey}\0${link.linkId}`),
338
- source: link,
339
- }),
340
- );
341
- const snapshotKey = digest(
342
- `lore-workspace-snapshot/1\0${workspaceKey}\0${canonicalJson({
343
- repositories: repositories.map((repository) => ({
344
- memberKey: repository.memberKey,
345
- repositoryKey: repository.repositoryKey,
346
- bundleKey: repository.bundleKey,
347
- gitCommit: repository.source.gitCommit,
348
- exportDigest: repository.source.exportDigest,
349
- })),
350
- links: links.map((link) => ({
351
- workspaceLinkKey: link.workspaceLinkKey,
352
- source: link.source,
353
- })),
354
- })}`,
355
- );
356
- return { schemaVersion: WORKSPACE_IDENTITY_SCHEMA_VERSION, workspaceKey, snapshotKey, repositories, links };
357
- }
358
-
359
- /** Namespace one repository-local source record without reinterpreting its key. */
360
- export function namespaceWorkspaceRecord(
361
- repository: WorkspaceRepositoryIdentity,
362
- kind: WorkspaceRecordKind,
363
- sourceRecordKeyValue: string,
364
- sourcePathValue: string | null,
365
- ): WorkspaceRecordIdentity {
366
- const sourceRecordKey = digestSchema.parse(sourceRecordKeyValue);
367
- const sourcePath = sourcePathValue === null ? null : sourcePathSchema.parse(sourcePathValue);
368
- return {
369
- workspaceRecordKey: digest(`lore-workspace-record/1\0${repository.repositoryKey}\0${kind}\0${sourceRecordKey}`),
370
- workspaceSourceKey:
371
- sourcePath === null ? null : digest(`lore-workspace-source/1\0${repository.repositoryKey}\0${sourcePath}`),
372
- sourceRecordKey,
373
- sourcePath,
374
- };
375
- }
376
-
377
- /**
378
- * Classify one explicit member transition. The precedence is part of the
379
- * public contract so simultaneous locator/source changes have one outcome.
380
- */
381
- export function classifyWorkspaceTransition(input: {
382
- readonly previous: WorkspaceMemberObservation | null;
383
- readonly current: WorkspaceMemberObservation | null;
384
- readonly linkConflict?: boolean;
385
- }): WorkspaceTransition {
386
- const previous = input.previous === null ? null : workspaceMemberObservationSchema.parse(input.previous);
387
- const current = input.current === null ? null : workspaceMemberObservationSchema.parse(input.current);
388
- if (previous === null && current === null) throw contractError("a transition requires a previous or current member");
389
- if (previous !== null && current !== null && previous.memberId !== current.memberId) {
390
- throw contractError("transition observations must use the same member id");
391
- }
392
- const hasPrevious = previous?.state === "ready";
393
- if (input.linkConflict === true) return transition("conflicting-link", "reject-candidate", hasPrevious, false);
394
- if (current === null) return transition("remove", "rebuild", hasPrevious, true);
395
- if (current.state === "missing") {
396
- return transition("missing-repository", "reject-candidate", hasPrevious, false);
397
- }
398
- if (previous === null || previous.state === "missing") return transition("add", "rebuild", false, false);
399
- if (previous.repositoryScopeKey !== current.repositoryScopeKey || previous.bundleId !== current.bundleId) {
400
- return transition("repository-replaced", "rebuild", true, true);
401
- }
402
- if (previous.gitCommit !== current.gitCommit || previous.gitRef !== current.gitRef) {
403
- return transition("branch-change", "rebuild", true, false);
404
- }
405
- if (previous.exportDigest !== current.exportDigest) {
406
- return transition("stale-snapshot", "rebuild", true, false);
407
- }
408
- if (previous.locator !== current.locator) {
409
- return transition("renamed-repository", "reuse", true, false);
410
- }
411
- return transition("unchanged", "reuse", true, false);
412
- }
413
-
414
- /** Canonical locator-free bytes suitable for projection metadata and evidence. */
415
- export function serializeWorkspaceIdentity(value: unknown): string {
416
- const identity = workspaceIdentitySchema.parse(value);
417
- assertSortedUnique(identity.repositories, (repository) => repository.memberId, "workspace identity repositories");
418
- assertSortedUnique(identity.links, (link) => link.linkId, "workspace identity links");
419
- for (const repository of identity.repositories) {
420
- if (repository.memberId !== repository.source.memberId) {
421
- throw contractError(`repository identity member differs from source ${repository.memberId}`);
422
- }
423
- }
424
- for (const link of identity.links) {
425
- if (link.linkId !== link.source.linkId) throw contractError(`link identity differs from source ${link.linkId}`);
426
- }
427
- return `${canonicalJson(identity)}\n`;
428
- }
429
-
430
- function transition(
431
- kind: WorkspaceTransitionKind,
432
- candidateDisposition: WorkspaceTransition["candidateDisposition"],
433
- servePrevious: boolean,
434
- removePriorEvidence: boolean,
435
- ): WorkspaceTransition {
436
- return { kind, candidateDisposition, servePrevious, removePriorEvidence };
437
- }
438
-
439
- function assertSortedUnique<T>(records: readonly T[], key: (record: T) => string, label: string): void {
440
- for (let index = 1; index < records.length; index++) {
441
- const previous = records[index - 1];
442
- const current = records[index];
443
- if (previous === undefined || current === undefined) continue;
444
- if (compareCodeUnits(key(previous), key(current)) >= 0) {
445
- throw contractError(`${label} must be unique and sorted by UTF-16 code units`);
446
- }
447
- }
448
- }
449
-
450
- function digest(value: string): string {
451
- return `sha256:${createHash("sha256").update(value).digest("hex")}`;
452
- }
453
-
454
- function contractError(message: string): Error {
455
- return new Error(`invalid ${WORKSPACE_MANIFEST_SCHEMA_VERSION}: ${message}`);
456
- }
457
-
458
- function canonicalJson(value: unknown): string {
459
- return JSON.stringify(canonicalValue(value));
460
- }
461
-
462
- function canonicalValue(value: unknown): unknown {
463
- if (Array.isArray(value)) return value.map((entry) => canonicalValue(entry));
464
- if (value === null || typeof value !== "object") return value;
465
- const result: Record<string, unknown> = {};
466
- for (const key of Object.keys(value).sort(compareCodeUnits)) {
467
- const entry = (value as Record<string, unknown>)[key];
468
- if (entry !== undefined && typeof entry !== "function" && typeof entry !== "symbol") {
469
- result[key] = canonicalValue(entry);
470
- }
471
- }
472
- return result;
473
- }