@davidorex/pi-context 0.26.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 (126) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +195 -0
  3. package/dist/block-api.d.ts +328 -0
  4. package/dist/block-api.d.ts.map +1 -0
  5. package/dist/block-api.js +1167 -0
  6. package/dist/block-api.js.map +1 -0
  7. package/dist/block-validation.d.ts +33 -0
  8. package/dist/block-validation.d.ts.map +1 -0
  9. package/dist/block-validation.js +213 -0
  10. package/dist/block-validation.js.map +1 -0
  11. package/dist/context-dir.d.ts +93 -0
  12. package/dist/context-dir.d.ts.map +1 -0
  13. package/dist/context-dir.js +224 -0
  14. package/dist/context-dir.js.map +1 -0
  15. package/dist/context-sdk.d.ts +428 -0
  16. package/dist/context-sdk.d.ts.map +1 -0
  17. package/dist/context-sdk.js +1327 -0
  18. package/dist/context-sdk.js.map +1 -0
  19. package/dist/context.d.ts +497 -0
  20. package/dist/context.d.ts.map +1 -0
  21. package/dist/context.js +1060 -0
  22. package/dist/context.js.map +1 -0
  23. package/dist/dispatch-context.d.ts +112 -0
  24. package/dist/dispatch-context.d.ts.map +1 -0
  25. package/dist/dispatch-context.js +119 -0
  26. package/dist/dispatch-context.js.map +1 -0
  27. package/dist/execution-context.d.ts +138 -0
  28. package/dist/execution-context.d.ts.map +1 -0
  29. package/dist/execution-context.js +151 -0
  30. package/dist/execution-context.js.map +1 -0
  31. package/dist/index.d.ts +34 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +1655 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/lens-validator.d.ts +50 -0
  36. package/dist/lens-validator.d.ts.map +1 -0
  37. package/dist/lens-validator.js +46 -0
  38. package/dist/lens-validator.js.map +1 -0
  39. package/dist/lens-view.d.ts +75 -0
  40. package/dist/lens-view.d.ts.map +1 -0
  41. package/dist/lens-view.js +253 -0
  42. package/dist/lens-view.js.map +1 -0
  43. package/dist/rename-canonical-id.d.ts +64 -0
  44. package/dist/rename-canonical-id.d.ts.map +1 -0
  45. package/dist/rename-canonical-id.js +378 -0
  46. package/dist/rename-canonical-id.js.map +1 -0
  47. package/dist/roadmap-plan.d.ts +186 -0
  48. package/dist/roadmap-plan.d.ts.map +1 -0
  49. package/dist/roadmap-plan.js +534 -0
  50. package/dist/roadmap-plan.js.map +1 -0
  51. package/dist/samples-catalog.d.ts +48 -0
  52. package/dist/samples-catalog.d.ts.map +1 -0
  53. package/dist/samples-catalog.js +128 -0
  54. package/dist/samples-catalog.js.map +1 -0
  55. package/dist/schema-migrations.d.ts +82 -0
  56. package/dist/schema-migrations.d.ts.map +1 -0
  57. package/dist/schema-migrations.js +106 -0
  58. package/dist/schema-migrations.js.map +1 -0
  59. package/dist/schema-validator.d.ts +76 -0
  60. package/dist/schema-validator.d.ts.map +1 -0
  61. package/dist/schema-validator.js +219 -0
  62. package/dist/schema-validator.js.map +1 -0
  63. package/dist/schema-write.d.ts +122 -0
  64. package/dist/schema-write.d.ts.map +1 -0
  65. package/dist/schema-write.js +210 -0
  66. package/dist/schema-write.js.map +1 -0
  67. package/dist/status-vocab.d.ts +60 -0
  68. package/dist/status-vocab.d.ts.map +1 -0
  69. package/dist/status-vocab.js +109 -0
  70. package/dist/status-vocab.js.map +1 -0
  71. package/dist/test-helpers.d.ts +12 -0
  72. package/dist/test-helpers.d.ts.map +1 -0
  73. package/dist/test-helpers.js +28 -0
  74. package/dist/test-helpers.js.map +1 -0
  75. package/dist/topo.d.ts +19 -0
  76. package/dist/topo.d.ts.map +1 -0
  77. package/dist/topo.js +99 -0
  78. package/dist/topo.js.map +1 -0
  79. package/dist/update-check.d.ts +6 -0
  80. package/dist/update-check.d.ts.map +1 -0
  81. package/dist/update-check.js +82 -0
  82. package/dist/update-check.js.map +1 -0
  83. package/package.json +115 -0
  84. package/samples/blocks/context-contracts.json +3 -0
  85. package/samples/blocks/conventions.json +3 -0
  86. package/samples/blocks/decisions.json +3 -0
  87. package/samples/blocks/features.json +3 -0
  88. package/samples/blocks/framework-gaps.json +3 -0
  89. package/samples/blocks/issues.json +3 -0
  90. package/samples/blocks/layer-plans.json +3 -0
  91. package/samples/blocks/phase.json +3 -0
  92. package/samples/blocks/rationale.json +3 -0
  93. package/samples/blocks/requirements.json +3 -0
  94. package/samples/blocks/research.json +3 -0
  95. package/samples/blocks/spec-reviews.json +3 -0
  96. package/samples/blocks/story.json +3 -0
  97. package/samples/blocks/tasks.json +3 -0
  98. package/samples/blocks/verification.json +3 -0
  99. package/samples/conception.json +467 -0
  100. package/samples/schemas/context-contracts.schema.json +89 -0
  101. package/samples/schemas/conventions.schema.json +34 -0
  102. package/samples/schemas/decisions.schema.json +87 -0
  103. package/samples/schemas/features.schema.json +53 -0
  104. package/samples/schemas/framework-gaps.schema.json +64 -0
  105. package/samples/schemas/issues.schema.json +39 -0
  106. package/samples/schemas/layer-plans.schema.json +92 -0
  107. package/samples/schemas/phase.schema.json +118 -0
  108. package/samples/schemas/rationale.schema.json +24 -0
  109. package/samples/schemas/requirements.schema.json +34 -0
  110. package/samples/schemas/research.schema.json +134 -0
  111. package/samples/schemas/spec-reviews.schema.json +62 -0
  112. package/samples/schemas/story.schema.json +73 -0
  113. package/samples/schemas/tasks.schema.json +34 -0
  114. package/samples/schemas/verification.schema.json +37 -0
  115. package/schemas/bootstrap.schema.json +24 -0
  116. package/schemas/config.schema.json +193 -0
  117. package/schemas/layer.schema.json +9 -0
  118. package/schemas/priority.schema.json +9 -0
  119. package/schemas/relations.schema.json +31 -0
  120. package/schemas/severity.schema.json +9 -0
  121. package/schemas/source.schema.json +9 -0
  122. package/schemas/status.schema.json +9 -0
  123. package/schemas/verification-method.schema.json +9 -0
  124. package/skill-narrative.md +130 -0
  125. package/skills/pi-context/SKILL.md +684 -0
  126. package/skills/pi-context/references/bundled-resources.md +47 -0
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Samples-catalog discovery surface (FGAP-068 closure; DEC-0037 packaged view).
3
+ *
4
+ * DEC-0037 (enacted) requires the conception present each block kind PACKAGED
5
+ * with its relation_types + invariants + lens. This module reads the
6
+ * extension's OWN bundled `samples/conception.json` + `samples/schemas/` and
7
+ * projects, per block kind: title, description, item shape, the relation_types
8
+ * for which the kind may be a source / target endpoint, the invariants ranging
9
+ * over it, and the lenses targeting it — plus the top-level relation_type /
10
+ * lens / invariant / layer / status_bucket registries.
11
+ *
12
+ * PACKAGE-INTRINSIC: there is no `cwd` parameter. The catalog reads the bundled
13
+ * samples directory resolved relative to this module (`../samples`), so it works
14
+ * the same from `src/` (tsx --test) and from `dist/` (post-build) and is
15
+ * independent of any installed project substrate.
16
+ *
17
+ * DEC-0019/0020 dual-surface: this library function backs both the Pi tool
18
+ * `read-samples-catalog` (in-pi LLM discovery) and the CLI script
19
+ * `scripts/orchestrator/read-samples-catalog.ts` (Claude-Code-side); all three
20
+ * ship as one unit.
21
+ *
22
+ * Never throws for unknown-kind / missing-description: defects surface as
23
+ * per-kind `warnings[]` and catalog-level `warnings[]` so a half-authored
24
+ * conception is diagnosable rather than fatal.
25
+ */
26
+ import fs from "node:fs";
27
+ import path from "node:path";
28
+ import { fileURLToPath } from "node:url";
29
+ import { schemaInfoFromPath } from "./context-sdk.js";
30
+ /** Bundled samples directory — resolved relative to this module so it works from
31
+ * both `src/` (tsx) and `dist/` (built); `samples/` sits one level above either. */
32
+ const SAMPLES_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "samples");
33
+ /**
34
+ * Build the packaged samples catalog. With `opts.kind` set, the catalog's
35
+ * `kinds[]` is filtered to the single matching block kind (empty + a warning
36
+ * when no kind matches). The top-level registries are always returned in full.
37
+ */
38
+ export function samplesCatalog(opts) {
39
+ const conception = JSON.parse(fs.readFileSync(path.join(SAMPLES_DIR, "conception.json"), "utf-8"));
40
+ const blockKinds = conception.block_kinds ?? [];
41
+ const relationTypes = conception.relation_types ?? [];
42
+ const lenses = conception.lenses ?? [];
43
+ const invariants = conception.invariants ?? [];
44
+ const warnings = [];
45
+ const knownKindIds = new Set(blockKinds.map((bk) => bk.canonical_id));
46
+ // Catalog-level: any source/target_kinds value that is neither "*" nor a
47
+ // known block_kind canonical_id is a misauthored endpoint declaration.
48
+ for (const rt of relationTypes) {
49
+ for (const k of rt.source_kinds ?? []) {
50
+ if (k !== "*" && !knownKindIds.has(k)) {
51
+ warnings.push(`relation_type '${rt.canonical_id}' names unknown kind '${k}' in source_kinds`);
52
+ }
53
+ }
54
+ for (const k of rt.target_kinds ?? []) {
55
+ if (k !== "*" && !knownKindIds.has(k)) {
56
+ warnings.push(`relation_type '${rt.canonical_id}' names unknown kind '${k}' in target_kinds`);
57
+ }
58
+ }
59
+ }
60
+ // Filter set: when opts.kind is given but unknown, emit a warning + empty kinds.
61
+ let selected = blockKinds;
62
+ if (opts?.kind !== undefined) {
63
+ selected = blockKinds.filter((bk) => bk.canonical_id === opts.kind);
64
+ if (selected.length === 0) {
65
+ warnings.push(`unknown kind '${opts.kind}' — not in block_kinds`);
66
+ }
67
+ }
68
+ const kinds = selected.map((bk) => buildSampleKind(bk, relationTypes, invariants, lenses));
69
+ return {
70
+ schema_version: String(conception.schema_version ?? ""),
71
+ kinds,
72
+ relationTypes,
73
+ lenses,
74
+ invariants,
75
+ layers: conception.layers ?? [],
76
+ status_buckets: conception.status_buckets ?? {},
77
+ warnings,
78
+ };
79
+ }
80
+ /** Project one block kind into a SampleKind, reading its bundled schema. */
81
+ function buildSampleKind(bk, relationTypes, invariants, lenses) {
82
+ const kindWarnings = [];
83
+ // Read the schema once for the root description; schemaInfoFromPath handles
84
+ // title + item shape (it does not capture the root `description` field).
85
+ const schemaFile = path.join(SAMPLES_DIR, "schemas", `${bk.canonical_id}.schema.json`);
86
+ let description = null;
87
+ try {
88
+ const rawSchema = JSON.parse(fs.readFileSync(schemaFile, "utf-8"));
89
+ description = typeof rawSchema.description === "string" ? rawSchema.description : null;
90
+ }
91
+ catch {
92
+ description = null;
93
+ }
94
+ const info = schemaInfoFromPath(schemaFile, bk.canonical_id);
95
+ const title = info?.title ?? null;
96
+ const shape = info
97
+ ? { arrayKeys: info.arrayKeys, itemProperties: info.itemProperties ?? {} }
98
+ : null;
99
+ if (title === null)
100
+ kindWarnings.push(`kind '${bk.canonical_id}': schema title missing or unreadable`);
101
+ if (description === null)
102
+ kindWarnings.push(`kind '${bk.canonical_id}': schema description missing or unreadable`);
103
+ if (shape === null)
104
+ kindWarnings.push(`kind '${bk.canonical_id}': schema shape missing or unreadable`);
105
+ // Endpoint participation: R is as_source for K iff R.source_kinds includes
106
+ // "*" or K.canonical_id; as_target analogously. A relation with no
107
+ // source_kinds/target_kinds contributes to neither (metadata-less case).
108
+ const as_source = relationTypes.filter((r) => r.source_kinds !== undefined && (r.source_kinds.includes("*") || r.source_kinds.includes(bk.canonical_id)));
109
+ const as_target = relationTypes.filter((r) => r.target_kinds !== undefined && (r.target_kinds.includes("*") || r.target_kinds.includes(bk.canonical_id)));
110
+ const kindInvariants = invariants.filter((inv) => inv.block === bk.canonical_id);
111
+ const kindLenses = lenses.filter((lens) => lens.target === bk.canonical_id || (Array.isArray(lens.targets) && lens.targets.includes(bk.canonical_id)));
112
+ return {
113
+ canonical_id: bk.canonical_id,
114
+ display_name: bk.display_name,
115
+ prefix: bk.prefix,
116
+ array_key: bk.array_key,
117
+ data_path: bk.data_path,
118
+ schema_path: bk.schema_path,
119
+ title,
120
+ description,
121
+ shape,
122
+ relation_types: { as_source, as_target },
123
+ invariants: kindInvariants,
124
+ lenses: kindLenses,
125
+ warnings: kindWarnings,
126
+ };
127
+ }
128
+ //# sourceMappingURL=samples-catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"samples-catalog.js","sourceRoot":"","sources":["../src/samples-catalog.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Schema migration registry — closes FGAP-006 (schema versioning + identity +
3
+ * composition + migration story) at the runtime-migration layer. Pairs with
4
+ * the `$id` + `version` schema-identity surface landed alongside this module
5
+ * in the framework schemas under `packages/pi-context/schemas/`.
6
+ *
7
+ * Aim: when a block file declares `schema_version: "1.0.0"` but the schema
8
+ * itself has advanced to `"2.0.0"`, a registered chain of MigrationFns walks
9
+ * the data forward one step at a time — `1.0.0 → 1.1.0 → 2.0.0` — before AJV
10
+ * validates against the current schema. Per-step migrations are pure data
11
+ * transforms that take and return `unknown`; the registry resolves the chain
12
+ * by looking up each (schemaName, fromVersion) entry and following its
13
+ * `toVersion` forward until it lands on `targetVersion` or fails.
14
+ *
15
+ * Design notes (intentions, not absolutes):
16
+ * - In-memory module via `createRegistry()` returning the typed surface;
17
+ * a singleton is not exported because per-process / per-test isolation
18
+ * simplifies coverage and avoids cross-test contamination.
19
+ * - Linear chain only — no branching / no diamond resolution. If a future
20
+ * schema has multiple `1.x → 2.x` paths the registry as written will
21
+ * follow the first registered edge from each (schemaName, fromVersion);
22
+ * callers should register exactly one outgoing edge per node.
23
+ * - Cycle detection is bounded by tracking visited (schemaName, version)
24
+ * tuples during `resolve`; an unbounded walk would hang on a misregistered
25
+ * `1.0.0 → 1.0.0` self-loop.
26
+ * - `runMigrations` with `currentVersion === targetVersion` is a no-op
27
+ * pass-through — no registry lookup, no copy, no error.
28
+ * - `register` rejects duplicate (schemaName, fromVersion) entries with a
29
+ * descriptive Error so accidental double-registration surfaces at startup
30
+ * rather than as silent override.
31
+ *
32
+ * Out of scope here:
33
+ * - On-disk migration of existing block files. Migration runs at READ time
34
+ * when a future `validateBlockWithMigration` (schema-validator.ts) is
35
+ * called; it does not rewrite the source file.
36
+ * - Cross-schema migrations (e.g. splitting one block into two). Those
37
+ * would compose at a higher layer than this per-schema chain.
38
+ */
39
+ /**
40
+ * A single forward migration step. Receives the block data as it exists at
41
+ * `fromVersion` and returns the data shaped to match `toVersion`. The function
42
+ * is expected to be pure; it should not perform I/O.
43
+ */
44
+ export type MigrationFn = (data: unknown) => unknown;
45
+ /**
46
+ * One registered edge in the migration graph. `schemaName` is the canonical
47
+ * schema id (matches the schema's `$id` minus the URN prefix, e.g. `"config"`,
48
+ * `"relations"`, `"priority"`). `fromVersion` and `toVersion` are semver
49
+ * strings matching the schema's `version` field at those points in time.
50
+ */
51
+ export interface MigrationRegistryEntry {
52
+ schemaName: string;
53
+ fromVersion: string;
54
+ toVersion: string;
55
+ migrate: MigrationFn;
56
+ }
57
+ /**
58
+ * Typed surface returned by `createRegistry()`. `register` adds an edge;
59
+ * `resolve` walks the registered edges to produce an ordered list of
60
+ * MigrationFns from `fromVersion` to `toVersion`. `resolve` throws when no
61
+ * path exists (including when the registry is empty for that schemaName).
62
+ */
63
+ export interface MigrationRegistry {
64
+ register(entry: MigrationRegistryEntry): void;
65
+ resolve(schemaName: string, fromVersion: string, toVersion: string): MigrationFn[];
66
+ }
67
+ /**
68
+ * Construct a fresh, empty migration registry. Each call returns an
69
+ * independent instance — there is no shared global state — so callers needing
70
+ * isolation (notably tests) get it for free.
71
+ */
72
+ export declare function createRegistry(): MigrationRegistry;
73
+ /**
74
+ * Resolve the migration chain for `schemaName` from `currentVersion` to
75
+ * `targetVersion` and apply each step in order to `data`. Returns the final
76
+ * migrated data. When `currentVersion === targetVersion` the input is
77
+ * returned unchanged (no allocation, no registry lookup).
78
+ *
79
+ * Throws when no migration path exists (`registry.resolve` propagates).
80
+ */
81
+ export declare function runMigrations(registry: MigrationRegistry, schemaName: string, currentVersion: string, targetVersion: string, data: unknown): unknown;
82
+ //# sourceMappingURL=schema-migrations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-migrations.d.ts","sourceRoot":"","sources":["../src/schema-migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC;AAErD;;;;;GAKG;AACH,MAAM,WAAW,sBAAsB;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,WAAW,CAAC;CACrB;AAED;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,KAAK,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAC9C,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW,EAAE,CAAC;CACnF;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,iBAAiB,CA0DlD;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC5B,QAAQ,EAAE,iBAAiB,EAC3B,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,EACtB,aAAa,EAAE,MAAM,EACrB,IAAI,EAAE,OAAO,GACX,OAAO,CAST"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Schema migration registry — closes FGAP-006 (schema versioning + identity +
3
+ * composition + migration story) at the runtime-migration layer. Pairs with
4
+ * the `$id` + `version` schema-identity surface landed alongside this module
5
+ * in the framework schemas under `packages/pi-context/schemas/`.
6
+ *
7
+ * Aim: when a block file declares `schema_version: "1.0.0"` but the schema
8
+ * itself has advanced to `"2.0.0"`, a registered chain of MigrationFns walks
9
+ * the data forward one step at a time — `1.0.0 → 1.1.0 → 2.0.0` — before AJV
10
+ * validates against the current schema. Per-step migrations are pure data
11
+ * transforms that take and return `unknown`; the registry resolves the chain
12
+ * by looking up each (schemaName, fromVersion) entry and following its
13
+ * `toVersion` forward until it lands on `targetVersion` or fails.
14
+ *
15
+ * Design notes (intentions, not absolutes):
16
+ * - In-memory module via `createRegistry()` returning the typed surface;
17
+ * a singleton is not exported because per-process / per-test isolation
18
+ * simplifies coverage and avoids cross-test contamination.
19
+ * - Linear chain only — no branching / no diamond resolution. If a future
20
+ * schema has multiple `1.x → 2.x` paths the registry as written will
21
+ * follow the first registered edge from each (schemaName, fromVersion);
22
+ * callers should register exactly one outgoing edge per node.
23
+ * - Cycle detection is bounded by tracking visited (schemaName, version)
24
+ * tuples during `resolve`; an unbounded walk would hang on a misregistered
25
+ * `1.0.0 → 1.0.0` self-loop.
26
+ * - `runMigrations` with `currentVersion === targetVersion` is a no-op
27
+ * pass-through — no registry lookup, no copy, no error.
28
+ * - `register` rejects duplicate (schemaName, fromVersion) entries with a
29
+ * descriptive Error so accidental double-registration surfaces at startup
30
+ * rather than as silent override.
31
+ *
32
+ * Out of scope here:
33
+ * - On-disk migration of existing block files. Migration runs at READ time
34
+ * when a future `validateBlockWithMigration` (schema-validator.ts) is
35
+ * called; it does not rewrite the source file.
36
+ * - Cross-schema migrations (e.g. splitting one block into two). Those
37
+ * would compose at a higher layer than this per-schema chain.
38
+ */
39
+ /**
40
+ * Construct a fresh, empty migration registry. Each call returns an
41
+ * independent instance — there is no shared global state — so callers needing
42
+ * isolation (notably tests) get it for free.
43
+ */
44
+ export function createRegistry() {
45
+ // Outer map: schemaName → inner map keyed by fromVersion. Each inner-map
46
+ // entry holds the single outgoing edge (toVersion + migrate). One outgoing
47
+ // edge per (schemaName, fromVersion) keeps the walk deterministic.
48
+ const edges = new Map();
49
+ function register(entry) {
50
+ let perSchema = edges.get(entry.schemaName);
51
+ if (!perSchema) {
52
+ perSchema = new Map();
53
+ edges.set(entry.schemaName, perSchema);
54
+ }
55
+ if (perSchema.has(entry.fromVersion)) {
56
+ throw new Error(`MigrationRegistry: duplicate edge for schema '${entry.schemaName}' fromVersion '${entry.fromVersion}'`);
57
+ }
58
+ perSchema.set(entry.fromVersion, { toVersion: entry.toVersion, migrate: entry.migrate });
59
+ }
60
+ function resolve(schemaName, fromVersion, toVersion) {
61
+ if (fromVersion === toVersion)
62
+ return [];
63
+ const perSchema = edges.get(schemaName);
64
+ if (!perSchema || perSchema.size === 0) {
65
+ throw new Error(`MigrationRegistry: no migrations registered for schema '${schemaName}' (need ${fromVersion} → ${toVersion})`);
66
+ }
67
+ const chain = [];
68
+ const visited = new Set();
69
+ let cursor = fromVersion;
70
+ // Walk forward step by step. Each step looks up the outgoing edge from
71
+ // `cursor`; if there is none, the requested `toVersion` is unreachable.
72
+ while (cursor !== toVersion) {
73
+ if (visited.has(cursor)) {
74
+ throw new Error(`MigrationRegistry: cycle detected at schema '${schemaName}' version '${cursor}' while resolving ${fromVersion} → ${toVersion}`);
75
+ }
76
+ visited.add(cursor);
77
+ const next = perSchema.get(cursor);
78
+ if (!next) {
79
+ throw new Error(`MigrationRegistry: no path from ${fromVersion} to ${toVersion} for schema '${schemaName}' (stuck at '${cursor}')`);
80
+ }
81
+ chain.push(next.migrate);
82
+ cursor = next.toVersion;
83
+ }
84
+ return chain;
85
+ }
86
+ return { register, resolve };
87
+ }
88
+ /**
89
+ * Resolve the migration chain for `schemaName` from `currentVersion` to
90
+ * `targetVersion` and apply each step in order to `data`. Returns the final
91
+ * migrated data. When `currentVersion === targetVersion` the input is
92
+ * returned unchanged (no allocation, no registry lookup).
93
+ *
94
+ * Throws when no migration path exists (`registry.resolve` propagates).
95
+ */
96
+ export function runMigrations(registry, schemaName, currentVersion, targetVersion, data) {
97
+ if (currentVersion === targetVersion)
98
+ return data;
99
+ const chain = registry.resolve(schemaName, currentVersion, targetVersion);
100
+ let cursor = data;
101
+ for (const step of chain) {
102
+ cursor = step(cursor);
103
+ }
104
+ return cursor;
105
+ }
106
+ //# sourceMappingURL=schema-migrations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-migrations.js","sourceRoot":"","sources":["../src/schema-migrations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ import type { ErrorObject } from "ajv";
2
+ import { type MigrationRegistry } from "./schema-migrations.js";
3
+ /**
4
+ * Error class for validation failures.
5
+ * Contains the original AJV errors and a formatted message.
6
+ */
7
+ export declare class ValidationError extends Error {
8
+ readonly label: string;
9
+ readonly errors: ErrorObject[];
10
+ constructor(label: string, errors: ErrorObject[]);
11
+ }
12
+ /**
13
+ * Validate data against a JSON Schema object.
14
+ * Throws ValidationError with formatted error messages on failure.
15
+ * Returns the validated data on success (pass-through).
16
+ *
17
+ * `$ref` to any pre-registered framework schema (URN form
18
+ * `pi-context://schemas/<name>`) resolves synchronously because those schemas
19
+ * were added to the shared AJV instance at module init.
20
+ *
21
+ * When the supplied schema carries an `$id` that already matches a registered
22
+ * schema on this AJV instance (e.g. the framework `config` / `relations`
23
+ * schemas pre-registered above), the cached compiled validator is reused
24
+ * rather than re-adding the schema — re-adding a duplicate `$id` throws
25
+ * "schema with key or id ... already exists".
26
+ */
27
+ export declare function validate(schema: Record<string, unknown>, data: unknown, label: string): unknown;
28
+ /**
29
+ * Validate that `schema` itself is a structurally-valid JSON Schema.
30
+ * Routes through AJV's bundled draft-07 meta-schema via `ajv.validateSchema`,
31
+ * which is the canonical "is this a schema?" check on the same AJV instance
32
+ * already in use for data validation. Throws `ValidationError` listing every
33
+ * meta-schema violation when the schema is malformed (e.g. unknown keyword
34
+ * combinations, invalid `type` value, malformed `properties`). Returns the
35
+ * input on success (pass-through, mirrors `validate`).
36
+ *
37
+ * Reusing the module-internal `ajv` instance is intentional per the rebuild
38
+ * arc — the schema-write surface (FGAP-011) must not stand up a parallel AJV
39
+ * instance with diverging strictness / format settings.
40
+ */
41
+ export declare function validateSchemaAgainstMeta(schema: unknown, label: string): unknown;
42
+ /**
43
+ * Load a JSON Schema from a file path and validate data against it.
44
+ * Throws if the schema file doesn't exist or is invalid JSON.
45
+ * Throws ValidationError on validation failure.
46
+ */
47
+ export declare function validateFromFile(schemaPath: string, data: unknown, label: string): unknown;
48
+ /**
49
+ * Migration-aware block validation (FGAP-006 read-time migration surface).
50
+ *
51
+ * Aim: read a block file from disk, compare its declared `schema_version`
52
+ * against the framework schema's `version`, run any registered migrations
53
+ * forward via `runMigrations`, then validate the migrated data against the
54
+ * current schema. This is the single entry point that lets a project on an
55
+ * older block format keep loading after a schema bump, without rewriting the
56
+ * file on disk.
57
+ *
58
+ * Behaviour:
59
+ * - `cwd` resolves to `<contextDir>/schemas/<schemaName>.schema.json` for
60
+ * the schema; user-state schemas under `.project/schemas/` win over
61
+ * framework schemas because that path is computed relative to `cwd`.
62
+ * - `data.schema_version` is read off the supplied data when present. When
63
+ * it is missing, the data is validated as-is (no migration attempted) —
64
+ * pre-FGAP-006 blocks have no version field and should pass through.
65
+ * - `registry` is optional; when omitted and the versions differ, the
66
+ * function throws (no chain to apply).
67
+ * - On match (`block.schema_version === schema.version`) or missing
68
+ * `schema_version` field, no migration runs and the data goes straight
69
+ * to `validate()`.
70
+ * - The migrated data is what gets validated; the input is not mutated.
71
+ *
72
+ * Out of scope: writing the migrated form back to disk — call sites that need
73
+ * persistence handle that themselves via the block-write surface.
74
+ */
75
+ export declare function validateBlockWithMigration(cwd: string, schemaName: string, data: unknown, registry?: MigrationRegistry): unknown;
76
+ //# sourceMappingURL=schema-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-validator.d.ts","sourceRoot":"","sources":["../src/schema-validator.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;AAIvC,OAAO,EAAE,KAAK,iBAAiB,EAAiB,MAAM,wBAAwB,CAAC;AAsE/E;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,KAAK;IACzC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC;gBAEnB,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE;CAOhD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAqB/F;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAMjF;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAgB1F;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,0BAA0B,CACzC,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,OAAO,EACb,QAAQ,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAgCT"}
@@ -0,0 +1,219 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import _Ajv from "ajv";
5
+ import _addFormats from "ajv-formats";
6
+ import { schemaPath as schemaPathHelper } from "./context-dir.js";
7
+ import { runMigrations } from "./schema-migrations.js";
8
+ // Node16 module resolution + CJS interop: default import is the module namespace
9
+ const Ajv = _Ajv.default ?? _Ajv;
10
+ const addFormats = _addFormats.default ?? _addFormats;
11
+ const ajv = new Ajv({ allErrors: true, strict: false });
12
+ addFormats(ajv);
13
+ /**
14
+ * Pre-register the eight framework schemas (`config`, `relations`, plus the
15
+ * six FGAP-016 shared enums: priority, status, severity, source, layer,
16
+ * verification-method) at AJV-instance construction so cross-schema `$ref`
17
+ * to `pi-context://schemas/<name>` resolves synchronously without an async
18
+ * `loadSchema` hook. Pre-registration also makes the URN identity surface
19
+ * (FGAP-006 closure) usable from any caller of this module without each
20
+ * caller restating the schemas.
21
+ *
22
+ * Pre-registration is best-effort: if a schema file is missing or malformed
23
+ * (which would only happen during local dev with a half-applied edit), the
24
+ * load throws synchronously at module init — the failure surfaces immediately
25
+ * rather than as a confusing late `$ref` resolution error.
26
+ *
27
+ * The schema directory is resolved relative to this file, which means the
28
+ * pre-registration works the same way whether the package is consumed from
29
+ * source (via tsx) or from `dist/` post-build, because the schemas/ directory
30
+ * sits one level above either `src/` or `dist/`.
31
+ */
32
+ const FRAMEWORK_SCHEMA_NAMES = [
33
+ "config",
34
+ "relations",
35
+ "bootstrap",
36
+ "priority",
37
+ "status",
38
+ "severity",
39
+ "source",
40
+ "layer",
41
+ "verification-method",
42
+ ];
43
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
44
+ // `__dirname` points at either `<pkg>/src/` (tsx) or `<pkg>/dist/` (built).
45
+ // Either way, `../schemas/` lands at `<pkg>/schemas/` because both `src/` and
46
+ // `dist/` are direct children of the package root.
47
+ const SCHEMAS_ROOT = path.resolve(__dirname, "..", "schemas");
48
+ for (const name of FRAMEWORK_SCHEMA_NAMES) {
49
+ const schemaPath = path.join(SCHEMAS_ROOT, `${name}.schema.json`);
50
+ if (!fs.existsSync(schemaPath)) {
51
+ throw new Error(`schema-validator: framework schema missing at module init: ${schemaPath}`);
52
+ }
53
+ const raw = fs.readFileSync(schemaPath, "utf-8");
54
+ let parsed;
55
+ try {
56
+ parsed = JSON.parse(raw);
57
+ }
58
+ catch (err) {
59
+ throw new Error(`schema-validator: failed to parse framework schema ${schemaPath}: ${err instanceof Error ? err.message : String(err)}`);
60
+ }
61
+ // addSchema is idempotent when the same key is registered twice with
62
+ // identical content, but we guard anyway so reloads in test contexts
63
+ // (which they are not currently doing, but might in future) do not throw
64
+ // on duplicate-id errors.
65
+ const id = parsed.$id;
66
+ if (id && ajv.getSchema(id)) {
67
+ continue;
68
+ }
69
+ ajv.addSchema(parsed, id);
70
+ }
71
+ /**
72
+ * Error class for validation failures.
73
+ * Contains the original AJV errors and a formatted message.
74
+ */
75
+ export class ValidationError extends Error {
76
+ label;
77
+ errors;
78
+ constructor(label, errors) {
79
+ const details = errors.map((e) => `${e.instancePath || ""}: ${e.message}`).join("; ");
80
+ super(`Validation failed for ${label}: ${details}`);
81
+ this.name = "ValidationError";
82
+ this.label = label;
83
+ this.errors = errors;
84
+ }
85
+ }
86
+ /**
87
+ * Validate data against a JSON Schema object.
88
+ * Throws ValidationError with formatted error messages on failure.
89
+ * Returns the validated data on success (pass-through).
90
+ *
91
+ * `$ref` to any pre-registered framework schema (URN form
92
+ * `pi-context://schemas/<name>`) resolves synchronously because those schemas
93
+ * were added to the shared AJV instance at module init.
94
+ *
95
+ * When the supplied schema carries an `$id` that already matches a registered
96
+ * schema on this AJV instance (e.g. the framework `config` / `relations`
97
+ * schemas pre-registered above), the cached compiled validator is reused
98
+ * rather than re-adding the schema — re-adding a duplicate `$id` throws
99
+ * "schema with key or id ... already exists".
100
+ */
101
+ export function validate(schema, data, label) {
102
+ const id = typeof schema.$id === "string" ? schema.$id : undefined;
103
+ let valid;
104
+ let errors;
105
+ if (id) {
106
+ const cached = ajv.getSchema(id);
107
+ if (cached) {
108
+ valid = cached(data);
109
+ errors = cached.errors;
110
+ }
111
+ else {
112
+ valid = ajv.validate(schema, data);
113
+ errors = ajv.errors;
114
+ }
115
+ }
116
+ else {
117
+ valid = ajv.validate(schema, data);
118
+ errors = ajv.errors;
119
+ }
120
+ if (!valid) {
121
+ throw new ValidationError(label, errors ?? []);
122
+ }
123
+ return data;
124
+ }
125
+ /**
126
+ * Validate that `schema` itself is a structurally-valid JSON Schema.
127
+ * Routes through AJV's bundled draft-07 meta-schema via `ajv.validateSchema`,
128
+ * which is the canonical "is this a schema?" check on the same AJV instance
129
+ * already in use for data validation. Throws `ValidationError` listing every
130
+ * meta-schema violation when the schema is malformed (e.g. unknown keyword
131
+ * combinations, invalid `type` value, malformed `properties`). Returns the
132
+ * input on success (pass-through, mirrors `validate`).
133
+ *
134
+ * Reusing the module-internal `ajv` instance is intentional per the rebuild
135
+ * arc — the schema-write surface (FGAP-011) must not stand up a parallel AJV
136
+ * instance with diverging strictness / format settings.
137
+ */
138
+ export function validateSchemaAgainstMeta(schema, label) {
139
+ const valid = ajv.validateSchema(schema);
140
+ if (valid !== true) {
141
+ throw new ValidationError(label, ajv.errors ?? []);
142
+ }
143
+ return schema;
144
+ }
145
+ /**
146
+ * Load a JSON Schema from a file path and validate data against it.
147
+ * Throws if the schema file doesn't exist or is invalid JSON.
148
+ * Throws ValidationError on validation failure.
149
+ */
150
+ export function validateFromFile(schemaPath, data, label) {
151
+ let content;
152
+ try {
153
+ content = fs.readFileSync(schemaPath, "utf-8");
154
+ }
155
+ catch {
156
+ throw new Error(`Schema file not found: ${schemaPath}`);
157
+ }
158
+ let schema;
159
+ try {
160
+ schema = JSON.parse(content);
161
+ }
162
+ catch (err) {
163
+ throw new Error(`Invalid JSON in schema file: ${schemaPath}: ${err instanceof Error ? err.message : String(err)}`);
164
+ }
165
+ return validate(schema, data, label);
166
+ }
167
+ /**
168
+ * Migration-aware block validation (FGAP-006 read-time migration surface).
169
+ *
170
+ * Aim: read a block file from disk, compare its declared `schema_version`
171
+ * against the framework schema's `version`, run any registered migrations
172
+ * forward via `runMigrations`, then validate the migrated data against the
173
+ * current schema. This is the single entry point that lets a project on an
174
+ * older block format keep loading after a schema bump, without rewriting the
175
+ * file on disk.
176
+ *
177
+ * Behaviour:
178
+ * - `cwd` resolves to `<contextDir>/schemas/<schemaName>.schema.json` for
179
+ * the schema; user-state schemas under `.project/schemas/` win over
180
+ * framework schemas because that path is computed relative to `cwd`.
181
+ * - `data.schema_version` is read off the supplied data when present. When
182
+ * it is missing, the data is validated as-is (no migration attempted) —
183
+ * pre-FGAP-006 blocks have no version field and should pass through.
184
+ * - `registry` is optional; when omitted and the versions differ, the
185
+ * function throws (no chain to apply).
186
+ * - On match (`block.schema_version === schema.version`) or missing
187
+ * `schema_version` field, no migration runs and the data goes straight
188
+ * to `validate()`.
189
+ * - The migrated data is what gets validated; the input is not mutated.
190
+ *
191
+ * Out of scope: writing the migrated form back to disk — call sites that need
192
+ * persistence handle that themselves via the block-write surface.
193
+ */
194
+ export function validateBlockWithMigration(cwd, schemaName, data, registry) {
195
+ const schemaPath = schemaPathHelper(cwd, schemaName);
196
+ if (!fs.existsSync(schemaPath)) {
197
+ throw new Error(`validateBlockWithMigration: schema file not found at ${schemaPath}`);
198
+ }
199
+ let schema;
200
+ try {
201
+ schema = JSON.parse(fs.readFileSync(schemaPath, "utf-8"));
202
+ }
203
+ catch (err) {
204
+ throw new Error(`validateBlockWithMigration: invalid JSON in ${schemaPath}: ${err instanceof Error ? err.message : String(err)}`);
205
+ }
206
+ const schemaVersion = typeof schema.version === "string" ? schema.version : undefined;
207
+ const blockVersion = data && typeof data === "object" && "schema_version" in data
208
+ ? data.schema_version
209
+ : undefined;
210
+ let toValidate = data;
211
+ if (schemaVersion && blockVersion && schemaVersion !== blockVersion) {
212
+ if (!registry) {
213
+ throw new Error(`validateBlockWithMigration: block at ${schemaName} declares schema_version '${blockVersion}' but schema is at '${schemaVersion}' and no MigrationRegistry was supplied`);
214
+ }
215
+ toValidate = runMigrations(registry, schemaName, blockVersion, schemaVersion, data);
216
+ }
217
+ return validate(schema, toValidate, schemaName);
218
+ }
219
+ //# sourceMappingURL=schema-validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-validator.js","sourceRoot":"","sources":["../src/schema-validator.ts"],"names":[],"mappings":"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"}