@davidorex/pi-context 0.26.0 → 0.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 (123) hide show
  1. package/CHANGELOG.md +69 -15
  2. package/README.md +71 -18
  3. package/dist/block-api.d.ts +165 -18
  4. package/dist/block-api.d.ts.map +1 -1
  5. package/dist/block-api.js +800 -57
  6. package/dist/block-api.js.map +1 -1
  7. package/dist/citation-rot-scanner.d.ts +80 -0
  8. package/dist/citation-rot-scanner.d.ts.map +1 -0
  9. package/dist/citation-rot-scanner.js +421 -0
  10. package/dist/citation-rot-scanner.js.map +1 -0
  11. package/dist/content-hash.d.ts +25 -0
  12. package/dist/content-hash.d.ts.map +1 -0
  13. package/dist/content-hash.js +62 -0
  14. package/dist/content-hash.js.map +1 -0
  15. package/dist/context-dir.d.ts +126 -1
  16. package/dist/context-dir.d.ts.map +1 -1
  17. package/dist/context-dir.js +208 -4
  18. package/dist/context-dir.js.map +1 -1
  19. package/dist/context-registry.d.ts +122 -0
  20. package/dist/context-registry.d.ts.map +1 -0
  21. package/dist/context-registry.js +189 -0
  22. package/dist/context-registry.js.map +1 -0
  23. package/dist/context-sdk.d.ts +179 -12
  24. package/dist/context-sdk.d.ts.map +1 -1
  25. package/dist/context-sdk.js +536 -101
  26. package/dist/context-sdk.js.map +1 -1
  27. package/dist/context.d.ts +222 -4
  28. package/dist/context.d.ts.map +1 -1
  29. package/dist/context.js +244 -56
  30. package/dist/context.js.map +1 -1
  31. package/dist/git-env.d.ts +2 -0
  32. package/dist/git-env.d.ts.map +1 -0
  33. package/dist/git-env.js +29 -0
  34. package/dist/git-env.js.map +1 -0
  35. package/dist/index.d.ts +124 -2
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +440 -1125
  38. package/dist/index.js.map +1 -1
  39. package/dist/lens-view.d.ts.map +1 -1
  40. package/dist/lens-view.js +5 -3
  41. package/dist/lens-view.js.map +1 -1
  42. package/dist/migration-registry-loader.d.ts +121 -0
  43. package/dist/migration-registry-loader.d.ts.map +1 -0
  44. package/dist/migration-registry-loader.js +309 -0
  45. package/dist/migration-registry-loader.js.map +1 -0
  46. package/dist/migrations-store.d.ts +141 -0
  47. package/dist/migrations-store.d.ts.map +1 -0
  48. package/dist/migrations-store.js +206 -0
  49. package/dist/migrations-store.js.map +1 -0
  50. package/dist/object-store.d.ts +26 -0
  51. package/dist/object-store.d.ts.map +1 -0
  52. package/dist/object-store.js +104 -0
  53. package/dist/object-store.js.map +1 -0
  54. package/dist/ops-registry.d.ts +44 -0
  55. package/dist/ops-registry.d.ts.map +1 -0
  56. package/dist/ops-registry.js +1223 -0
  57. package/dist/ops-registry.js.map +1 -0
  58. package/dist/orientation.d.ts +16 -0
  59. package/dist/orientation.d.ts.map +1 -0
  60. package/dist/orientation.js +49 -0
  61. package/dist/orientation.js.map +1 -0
  62. package/dist/promote-item.d.ts +28 -0
  63. package/dist/promote-item.d.ts.map +1 -0
  64. package/dist/promote-item.js +257 -0
  65. package/dist/promote-item.js.map +1 -0
  66. package/dist/read-element.d.ts +129 -0
  67. package/dist/read-element.d.ts.map +1 -0
  68. package/dist/read-element.js +296 -0
  69. package/dist/read-element.js.map +1 -0
  70. package/dist/rename-canonical-id.d.ts.map +1 -1
  71. package/dist/rename-canonical-id.js +22 -13
  72. package/dist/rename-canonical-id.js.map +1 -1
  73. package/dist/roadmap-plan.d.ts.map +1 -1
  74. package/dist/roadmap-plan.js +23 -13
  75. package/dist/roadmap-plan.js.map +1 -1
  76. package/dist/schema-validator.d.ts +1 -0
  77. package/dist/schema-validator.d.ts.map +1 -1
  78. package/dist/schema-validator.js +7 -3
  79. package/dist/schema-validator.js.map +1 -1
  80. package/dist/schema-write.d.ts +78 -4
  81. package/dist/schema-write.d.ts.map +1 -1
  82. package/dist/schema-write.js +297 -34
  83. package/dist/schema-write.js.map +1 -1
  84. package/dist/status-vocab.d.ts +1 -0
  85. package/dist/status-vocab.d.ts.map +1 -1
  86. package/dist/status-vocab.js +4 -0
  87. package/dist/status-vocab.js.map +1 -1
  88. package/dist/truncate.d.ts +34 -0
  89. package/dist/truncate.d.ts.map +1 -0
  90. package/dist/truncate.js +85 -0
  91. package/dist/truncate.js.map +1 -0
  92. package/dist/write-schema-migration-tool.d.ts +46 -0
  93. package/dist/write-schema-migration-tool.d.ts.map +1 -0
  94. package/dist/write-schema-migration-tool.js +115 -0
  95. package/dist/write-schema-migration-tool.js.map +1 -0
  96. package/package.json +35 -2
  97. package/samples/blocks/work-orders.json +3 -0
  98. package/samples/conception.json +22 -3
  99. package/samples/migrations.json +133 -0
  100. package/samples/schemas/context-contracts.schema.json +19 -4
  101. package/samples/schemas/conventions.schema.json +68 -12
  102. package/samples/schemas/decisions.schema.json +125 -28
  103. package/samples/schemas/features.schema.json +140 -26
  104. package/samples/schemas/framework-gaps.schema.json +171 -26
  105. package/samples/schemas/issues.schema.json +86 -13
  106. package/samples/schemas/layer-plans.schema.json +195 -35
  107. package/samples/schemas/phase.schema.json +18 -3
  108. package/samples/schemas/rationale.schema.json +37 -7
  109. package/samples/schemas/requirements.schema.json +77 -10
  110. package/samples/schemas/research.schema.json +169 -36
  111. package/samples/schemas/spec-reviews.schema.json +88 -18
  112. package/samples/schemas/story.schema.json +18 -3
  113. package/samples/schemas/tasks.schema.json +66 -10
  114. package/samples/schemas/verification.schema.json +70 -12
  115. package/samples/schemas/work-orders.schema.json +195 -0
  116. package/schemas/bootstrap.schema.json +18 -6
  117. package/schemas/config.schema.json +58 -26
  118. package/schemas/context-registry.schema.json +46 -0
  119. package/schemas/migrations.schema.json +123 -0
  120. package/schemas/relations.schema.json +41 -10
  121. package/skill-narrative.md +42 -21
  122. package/skills/pi-context/SKILL.md +159 -65
  123. package/skills/pi-context/references/bundled-resources.md +7 -2
@@ -0,0 +1,309 @@
1
+ /**
2
+ * migration-registry-loader — builds a populated MigrationRegistry from the
3
+ * substrate-persisted migrations.json declarations.
4
+ *
5
+ * Layering:
6
+ * - schema-migrations.ts owns the in-memory registry abstraction
7
+ * (MigrationFn, MigrationRegistry, createRegistry, runMigrations). It
8
+ * knows nothing of persistence — that boundary is intentional so the
9
+ * registry can be exercised by tests with ad-hoc functions.
10
+ * - migrations-store.ts owns the on-disk read/write surface (MigrationDecl,
11
+ * TransformOp shapes; AJV-validated load/write; op-correct mutation
12
+ * helpers).
13
+ * - This module is the bridge: it consumes MigrationDecl values from the
14
+ * store and produces MigrationFn closures that the registry registers.
15
+ * Caching (keyed on the resolved substrate dir) + invalidation hooks live
16
+ * here so the substrate writers can punch a single function call to mark
17
+ * the cached registry stale.
18
+ *
19
+ * Path-walker semantics (TransformSpec dotted paths):
20
+ * - All paths are anchored on '$' and use simple dotted notation:
21
+ * '$' → the data root, '$.field' → root.field, '$.nested.field' →
22
+ * root.nested.field. Bracket-indexed array access ('$.arr[0]') is OUT
23
+ * of initial scope; the schema does not constrain the string form
24
+ * beyond a free-form `type: "string"`, but the walker rejects '[' in
25
+ * a path segment so a future array-extension is not accidentally
26
+ * bypassed at the runtime layer.
27
+ * - 'set' creates intermediate object parents as needed (a 'set' at
28
+ * '$.a.b.c' on `{}` produces `{ a: { b: { c: value } } }`). 'rename'
29
+ * is read + delete + set: a no-op when the source field is absent.
30
+ * 'delete' is a no-op when the field is absent. 'coerce' is a no-op
31
+ * when the field is absent; when present it casts via the explicit
32
+ * primitive constructors String(v) / Number(v) / Boolean(v); for
33
+ * 'array' coercion uses [].concat(v) so a non-array value becomes a
34
+ * single-element array (and an existing array passes through
35
+ * unchanged); for 'object' coercion uses Object(v).
36
+ *
37
+ * Identity migrations are the pass-through case the operator declares when
38
+ * the bumped schema is shape-compatible with the prior version; the closure
39
+ * returned by migrationFnFor for kind='identity' is byte-cheap (returns the
40
+ * input reference unchanged).
41
+ */
42
+ import path from "node:path";
43
+ import { resolveContextDir, tryResolveContextDir } from "./context-dir.js";
44
+ import { loadMigrationsFileForDir } from "./migrations-store.js";
45
+ import { createRegistry } from "./schema-migrations.js";
46
+ /**
47
+ * Cache of populated MigrationRegistry instances. ONE cache, keyed by the
48
+ * RESOLVED SUBSTRATE DIR (`path.resolve(substrateDir)`) — not by cwd. The cwd
49
+ * and dir forms converge on this single key: the cwd forms first resolve
50
+ * `cwd → substrateDir` via the bootstrap pointer, then read/invalidate the
51
+ * dir-keyed entry. Invalidated by `invalidateMigrationRegistry(cwd)` /
52
+ * `invalidateMigrationRegistryForDir(substrateDir)` after a successful
53
+ * migrations.json mutation so the next consumer sees the new declarations
54
+ * without restarting the process.
55
+ *
56
+ * Keying on the resolved dir (rather than cwd) is load-bearing for coherence:
57
+ * the block writers read the registry via `getProjectMigrationRegistryForDir`
58
+ * (dir-keyed); a migration declaration invalidates via the cwd form. If the
59
+ * read key (resolved dir) and the invalidate key (raw cwd) diverged — which
60
+ * they do whenever cwd ≠ substrateDir, e.g. `<root>` vs `<root>/.project` —
61
+ * the post-declaration read would serve a stale pre-declaration registry. The
62
+ * single resolved-dir key keeps read and invalidate coherent.
63
+ */
64
+ const registryCache = new Map();
65
+ /**
66
+ * Apply a single dotted path traversal, returning {parent, key} where parent
67
+ * is the object containing the addressed leaf and key is the final segment.
68
+ * When `createParents` is true, intermediate missing parents are created as
69
+ * empty objects (used by 'set' / 'rename' write side). When false, missing
70
+ * intermediates yield `null` parent (used by 'delete' / 'coerce' / 'rename'
71
+ * read side to no-op silently on absent paths).
72
+ *
73
+ * Rejects '[' / ']' in any segment — array-element addressing is explicitly
74
+ * out of scope so a future extension cannot be accidentally bypassed.
75
+ */
76
+ function walkPath(root, dottedPath, createParents) {
77
+ if (!dottedPath.startsWith("$")) {
78
+ throw new Error(`migration path must start with '$' (got '${dottedPath}')`);
79
+ }
80
+ const tail = dottedPath.slice(1); // strip leading '$'
81
+ const segments = tail.length === 0 ? [] : tail.split(".").slice(1); // first split element is '' from leading '.'
82
+ if (segments.length === 0) {
83
+ throw new Error(`migration path '${dottedPath}' has no segments; cannot address the root for write/delete`);
84
+ }
85
+ for (const seg of segments) {
86
+ if (seg.length === 0) {
87
+ throw new Error(`migration path '${dottedPath}' contains an empty segment`);
88
+ }
89
+ if (seg.includes("[") || seg.includes("]")) {
90
+ throw new Error(`migration path '${dottedPath}' contains array-element addressing ('[...]'); not supported in v1`);
91
+ }
92
+ }
93
+ if (typeof root !== "object" || root === null || Array.isArray(root)) {
94
+ if (!createParents)
95
+ return { parent: null, key: segments[segments.length - 1] };
96
+ throw new Error(`migration path '${dottedPath}' cannot create parents on a non-object root`);
97
+ }
98
+ let cursor = root;
99
+ for (let i = 0; i < segments.length - 1; i++) {
100
+ const seg = segments[i];
101
+ const next = cursor[seg];
102
+ if (typeof next === "object" && next !== null && !Array.isArray(next)) {
103
+ cursor = next;
104
+ }
105
+ else if (createParents) {
106
+ const fresh = {};
107
+ cursor[seg] = fresh;
108
+ cursor = fresh;
109
+ }
110
+ else {
111
+ return { parent: null, key: segments[segments.length - 1] };
112
+ }
113
+ }
114
+ return { parent: cursor, key: segments[segments.length - 1] };
115
+ }
116
+ /**
117
+ * Apply one TransformOp to `data` in place. Returns the (possibly same) data
118
+ * reference so chained ops compose cleanly. The mutation is intentionally
119
+ * in-place because the migrationFnFor closure deep-clones at the entry of
120
+ * the composed function — chaining sees a private copy throughout.
121
+ */
122
+ function applyOp(data, op) {
123
+ switch (op.op) {
124
+ case "rename": {
125
+ const read = walkPath(data, op.from, false);
126
+ if (read.parent === null || !(read.key in read.parent))
127
+ return data; // no-op on absent source
128
+ const value = read.parent[read.key];
129
+ delete read.parent[read.key];
130
+ const write = walkPath(data, op.to, true);
131
+ if (write.parent === null)
132
+ return data;
133
+ write.parent[write.key] = value;
134
+ return data;
135
+ }
136
+ case "set": {
137
+ const write = walkPath(data, op.path, true);
138
+ if (write.parent === null)
139
+ return data;
140
+ write.parent[write.key] = op.value;
141
+ return data;
142
+ }
143
+ case "delete": {
144
+ const read = walkPath(data, op.path, false);
145
+ if (read.parent === null)
146
+ return data;
147
+ delete read.parent[read.key];
148
+ return data;
149
+ }
150
+ case "coerce": {
151
+ const read = walkPath(data, op.path, false);
152
+ if (read.parent === null || !(read.key in read.parent))
153
+ return data;
154
+ const current = read.parent[read.key];
155
+ let next;
156
+ switch (op.type) {
157
+ case "string":
158
+ next = String(current);
159
+ break;
160
+ case "number":
161
+ next = Number(current);
162
+ break;
163
+ case "boolean":
164
+ next = Boolean(current);
165
+ break;
166
+ case "array":
167
+ next = Array.isArray(current) ? current : [].concat(current);
168
+ break;
169
+ case "object":
170
+ next = Object(current);
171
+ break;
172
+ }
173
+ read.parent[read.key] = next;
174
+ return data;
175
+ }
176
+ }
177
+ }
178
+ /**
179
+ * Convert one MigrationDecl into a MigrationFn closure. kind='identity'
180
+ * returns the input unchanged; kind='declarative-transform' deep-clones
181
+ * the input then applies each TransformOp in declaration order to the
182
+ * private copy, returning it.
183
+ *
184
+ * The kind='declarative-transform' branch throws when `transform` is
185
+ * missing — the schema declares the field optional only because the
186
+ * identity branch never carries one; runtime-time the loader trusts the
187
+ * write-schema-migration tool's presence/absence guard, but a hand-edited
188
+ * malformed file would surface here rather than silently no-op.
189
+ */
190
+ export function migrationFnFor(decl) {
191
+ if (decl.kind === "identity") {
192
+ return (data) => data;
193
+ }
194
+ if (decl.kind === "declarative-transform") {
195
+ const ops = decl.transform?.operations;
196
+ if (!ops) {
197
+ throw new Error(`migrationFnFor: declarative-transform decl for schema '${decl.schemaName}' ${decl.fromVersion}→${decl.toVersion} is missing transform.operations`);
198
+ }
199
+ return (data) => {
200
+ let cursor = JSON.parse(JSON.stringify(data));
201
+ for (const op of ops) {
202
+ cursor = applyOp(cursor, op);
203
+ }
204
+ return cursor;
205
+ };
206
+ }
207
+ // Unreachable per the TS discriminated union — present as a defensive
208
+ // throw so a future schema-shape additive change surfaces here.
209
+ throw new Error(`migrationFnFor: unknown decl.kind '${decl.kind}'`);
210
+ }
211
+ /**
212
+ * Build a fresh MigrationRegistry populated from migrations.json. When the
213
+ * file is absent or empty (no declarations), returns an empty registry that
214
+ * `runMigrations` exercises happy-path-only (currentVersion === targetVersion
215
+ * cases pass through without registry consultation).
216
+ *
217
+ * Throws when a declaration is malformed in a way the loader catches at
218
+ * MigrationFn-construction time (e.g. declarative-transform with no
219
+ * transform.operations), preserving the fail-fast aim. The store's AJV
220
+ * validation at load time catches schema-shape failures upstream.
221
+ */
222
+ export function buildRegistryFromSubstrateForDir(substrateDir) {
223
+ const reg = createRegistry();
224
+ const file = loadMigrationsFileForDir(substrateDir);
225
+ if (file === null)
226
+ return reg;
227
+ for (const decl of file.migrations) {
228
+ const migrate = migrationFnFor(decl);
229
+ reg.register({
230
+ schemaName: decl.schemaName,
231
+ fromVersion: decl.fromVersion,
232
+ toVersion: decl.toVersion,
233
+ migrate,
234
+ });
235
+ }
236
+ return reg;
237
+ }
238
+ export function buildRegistryFromSubstrate(cwd) {
239
+ return buildRegistryFromSubstrateForDir(resolveContextDir(cwd));
240
+ }
241
+ /**
242
+ * Return a cached MigrationRegistry for `cwd`, building it on first call (or
243
+ * after invalidation). Resolves the cwd to its substrate dir via the bootstrap
244
+ * pointer, then delegates to `getProjectMigrationRegistryForDir` so the cwd
245
+ * read path and the dir read path share ONE cache entry (keyed by the resolved
246
+ * substrate dir). This convergence is what keeps reads coherent with the
247
+ * cwd-form invalidation.
248
+ */
249
+ export function getProjectMigrationRegistry(cwd) {
250
+ return getProjectMigrationRegistryForDir(resolveContextDir(cwd));
251
+ }
252
+ /**
253
+ * Dir-targeted form of `getProjectMigrationRegistry` and the SINGLE registry
254
+ * builder/cacher: return a cached MigrationRegistry for an explicit substrate
255
+ * directory, building it on first call. Keyed on `path.resolve(substrateDir)`
256
+ * — the one cache key both read forms and both invalidate forms agree on. Used
257
+ * by `writeBlockForDir` so a write into a non-active substrate
258
+ * validates/migrates against THAT substrate's `migrations.json`, never the
259
+ * active dir's.
260
+ *
261
+ * The cwd form (`getProjectMigrationRegistry`) resolves `cwd → substrateDir`
262
+ * and delegates here, so when `substrateDir === resolveContextDir(cwd)` both
263
+ * forms hit the SAME entry. The migrations-store mutation helpers invalidate
264
+ * via `invalidateMigrationRegistry(cwd)`, which resolves the same dir key and
265
+ * deletes the entry this builder populates — so a declare-then-read sequence
266
+ * (mutate migrations.json, then `writeBlockForDir` re-reads) rebuilds against
267
+ * the fresh declarations rather than serving a stale registry.
268
+ */
269
+ export function getProjectMigrationRegistryForDir(substrateDir) {
270
+ const key = path.resolve(substrateDir);
271
+ const cached = registryCache.get(key);
272
+ if (cached)
273
+ return cached;
274
+ const reg = buildRegistryFromSubstrateForDir(substrateDir);
275
+ registryCache.set(key, reg);
276
+ return reg;
277
+ }
278
+ /**
279
+ * Drop the cached MigrationRegistry for an explicit substrate directory (if
280
+ * any). Deletes `path.resolve(substrateDir)` — the SAME key the read forms
281
+ * populate, so an invalidation here is observed by the next read.
282
+ *
283
+ * No-op when no entry is cached — invalidation is cheap and write-after-no-
284
+ * cache is a normal pre-warming state.
285
+ */
286
+ export function invalidateMigrationRegistryForDir(substrateDir) {
287
+ registryCache.delete(path.resolve(substrateDir));
288
+ }
289
+ /**
290
+ * Drop the cached MigrationRegistry for `cwd` (if any). Called by each
291
+ * migrations-store mutation helper after a successful write so the next
292
+ * consumer reads the fresh declarations without process restart.
293
+ *
294
+ * Resolves `cwd → substrateDir` and delegates to the ForDir form so the
295
+ * deleted key MATCHES the resolved-substrate-dir key the read path uses (the
296
+ * Regression-B fix: pre-fix this deleted `path.resolve(cwd)`, a different key
297
+ * from the dir-keyed read entry, leaving the post-declaration read stale). The
298
+ * mutation helpers always call this after a successful `writeMigrationsFile`,
299
+ * which itself required the pointer to resolve — so resolution is normally
300
+ * safe; `tryResolveContextDir` guards the absent-pointer edge with a no-op
301
+ * rather than throwing during invalidation.
302
+ */
303
+ export function invalidateMigrationRegistry(cwd) {
304
+ const substrateDir = tryResolveContextDir(cwd);
305
+ if (substrateDir === null)
306
+ return;
307
+ invalidateMigrationRegistryForDir(substrateDir);
308
+ }
309
+ //# sourceMappingURL=migration-registry-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration-registry-loader.js","sourceRoot":"","sources":["../src/migration-registry-loader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,141 @@
1
+ /**
2
+ * migrations-store — substrate-managed read / write surface for
3
+ * `<resolveContextDir(cwd)>/migrations.json`.
4
+ *
5
+ * Mirrors the config / relations precedent in context.ts: load + AJV-validate
6
+ * via `validateFromFile` against the bundled migrations.schema.json; whole-file
7
+ * writes delegate to block-api's atomic `writeTypedFile` (tmp + rename) so a
8
+ * failed write leaves the prior file byte-identical.
9
+ *
10
+ * Three op-correct mutation helpers — append / replace / remove — wrap a
11
+ * load → JSON deep-clone → mutate → write pattern. Identity of a MigrationDecl
12
+ * is the (schemaName, fromVersion) pair, mirroring the registry's
13
+ * one-outgoing-edge-per-(schemaName, fromVersion) discipline from
14
+ * `schema-migrations.ts`:
15
+ * - appendMigrationDecl requires the (schemaName, fromVersion) pair to be
16
+ * ABSENT; collision throws with a descriptive message so an unintended
17
+ * double-declaration surfaces at the write site rather than as silent
18
+ * registry duplicate-registration at load time.
19
+ * - replaceMigrationDecl requires the pair to be PRESENT; a missing target
20
+ * throws (use append to introduce a new declaration).
21
+ * - removeMigrationDecl requires the pair to be PRESENT; a missing target
22
+ * throws.
23
+ *
24
+ * After each successful mutation the helper invokes
25
+ * `invalidateMigrationRegistry(cwd)` (migration-registry-loader.ts) so the
26
+ * next `getProjectMigrationRegistry(cwd)` consumer reads the fresh
27
+ * declarations without process restart. The store↔loader edge is mutually
28
+ * cyclic (loader reads from store; store writes invalidate loader cache);
29
+ * ESM tolerates the cycle because both imports are function-level uses, not
30
+ * top-level evaluations.
31
+ *
32
+ * MigrationDecl + TransformSpec + TransformOp types mirror the on-disk schema
33
+ * shape one-for-one. They live here (rather than schema-migrations.ts) because
34
+ * schema-migrations.ts is the in-memory registry abstraction (knows nothing of
35
+ * persistence); the persisted-shape vocabulary belongs at the substrate-store
36
+ * boundary.
37
+ */
38
+ import type { DispatchContext } from "./dispatch-context.js";
39
+ /**
40
+ * One TransformOp variant — discriminated by the `op` field. Mirrors the
41
+ * `definitions/TransformOp` `oneOf` in migrations.schema.json. Keeping the
42
+ * variants in TypeScript lets the loader's `migrationFnFor` switch on the
43
+ * discriminator with type-narrowing rather than blind property access.
44
+ */
45
+ export type TransformOp = {
46
+ op: "rename";
47
+ from: string;
48
+ to: string;
49
+ } | {
50
+ op: "set";
51
+ path: string;
52
+ value?: unknown;
53
+ } | {
54
+ op: "delete";
55
+ path: string;
56
+ } | {
57
+ op: "coerce";
58
+ path: string;
59
+ type: "string" | "number" | "boolean" | "array" | "object";
60
+ };
61
+ export interface TransformSpec {
62
+ operations: TransformOp[];
63
+ }
64
+ export interface MigrationDecl {
65
+ schemaName: string;
66
+ fromVersion: string;
67
+ toVersion: string;
68
+ kind: "identity" | "declarative-transform";
69
+ transform?: TransformSpec;
70
+ created_by: string;
71
+ created_at: string;
72
+ }
73
+ export interface MigrationsFile {
74
+ schema_version: string;
75
+ migrations: MigrationDecl[];
76
+ }
77
+ /**
78
+ * Current schema_version emitted into newly-created migrations.json files.
79
+ * Tracks the `version` field of migrations.schema.json itself.
80
+ */
81
+ export declare const MIGRATIONS_FILE_VERSION = "1.0.0";
82
+ /**
83
+ * Load + AJV-validate migrations.json. Returns null when the file is absent
84
+ * (pre-write state is a normal condition, NOT an error — empty/missing yields
85
+ * an empty registry in the loader). Throws on read / parse / schema failure.
86
+ */
87
+ export declare function loadMigrationsFileForDir(substrateDir: string): MigrationsFile | null;
88
+ export declare function loadMigrationsFile(cwd: string): MigrationsFile | null;
89
+ /**
90
+ * Atomic, AJV-validated whole-file write of migrations.json. Delegates to
91
+ * block-api's writeTypedFile against the bundled migrations schema. `ctx` is
92
+ * accepted for call-site parity with the rest of the substrate write surface;
93
+ * the migrations schema declares no envelope author fields so stamping is a
94
+ * structural no-op today (the loader-cache invalidation upstream of this call
95
+ * is the side-effect that matters for read-after-write parity).
96
+ */
97
+ export declare function writeMigrationsFile(cwd: string, file: MigrationsFile, ctx?: DispatchContext): void;
98
+ /**
99
+ * Dir-targeted twin of {@link writeMigrationsFile} (Cycle-1 `*ForDir` pattern).
100
+ * Atomic, AJV-validated whole-file write of `<substrateDir>/migrations.json`
101
+ * against the bundled migrations schema — takes the ALREADY-RESOLVED substrate
102
+ * dir directly (no `.pi-context.json` pointer resolution). The cwd form is a
103
+ * thin wrapper resolving the active dir; behaviour is byte-identical when called
104
+ * via cwd. Same attestation-parity no-op semantics as the cwd form (the
105
+ * migrations schema declares no envelope author fields). Does NOT invalidate any
106
+ * loader cache — the cache-invalidation side-effect lives on the
107
+ * appendMigrationDecl mutation helpers, not the raw whole-file write.
108
+ */
109
+ export declare function writeMigrationsFileForDir(substrateDir: string, file: MigrationsFile, ctx?: DispatchContext): void;
110
+ /**
111
+ * Append a new MigrationDecl to the substrate. Op-correctness: the
112
+ * (schemaName, fromVersion) pair must be ABSENT on-disk; collision throws.
113
+ * Invalidates the loader cache for `cwd` after a successful write so the
114
+ * next registry consumer reads the fresh declaration.
115
+ */
116
+ export declare function appendMigrationDecl(cwd: string, decl: MigrationDecl, ctx?: DispatchContext): void;
117
+ /**
118
+ * Dir-targeted twin of {@link appendMigrationDecl} (Cycle-1 `*ForDir` pattern).
119
+ * Append a new MigrationDecl to the substrate at `substrateDir`. Op-correctness:
120
+ * the (schemaName, fromVersion) pair must be ABSENT on-disk; collision throws.
121
+ * Invalidates the loader cache for `substrateDir` after a successful write so
122
+ * the next registry consumer reads the fresh declaration. The cwd form is a thin
123
+ * wrapper resolving the active dir; behaviour is byte-identical when called via
124
+ * cwd (resolveContextDir(cwd) → invalidateMigrationRegistry(cwd) routes through
125
+ * invalidateMigrationRegistryForDir on the same dir).
126
+ */
127
+ export declare function appendMigrationDeclForDir(substrateDir: string, decl: MigrationDecl, ctx?: DispatchContext): void;
128
+ /**
129
+ * Replace an existing MigrationDecl identified by (schemaName, fromVersion).
130
+ * Op-correctness: target must be PRESENT; missing target throws. The whole
131
+ * decl is replaced (no per-field merge). Invalidates the loader cache after a
132
+ * successful write.
133
+ */
134
+ export declare function replaceMigrationDecl(cwd: string, decl: MigrationDecl, ctx?: DispatchContext): void;
135
+ /**
136
+ * Remove an existing MigrationDecl identified by (schemaName, fromVersion).
137
+ * Op-correctness: target must be PRESENT; missing target throws. Invalidates
138
+ * the loader cache after a successful write.
139
+ */
140
+ export declare function removeMigrationDecl(cwd: string, schemaName: string, fromVersion: string, ctx?: DispatchContext): void;
141
+ //# sourceMappingURL=migrations-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrations-store.d.ts","sourceRoot":"","sources":["../src/migrations-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAOH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAc7D;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GACpB;IAAE,EAAE,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAC5C;IAAE,EAAE,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,EAAE,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAA;CAAE,CAAC;AAE9F,MAAM,WAAW,aAAa;IAC7B,UAAU,EAAE,WAAW,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,UAAU,GAAG,uBAAuB,CAAC;IAC3C,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,aAAa,EAAE,CAAC;CAC5B;AAED;;;GAGG;AACH,eAAO,MAAM,uBAAuB,UAAU,CAAC;AAE/C;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,YAAY,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAiBpF;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAErE;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,GAAG,CAAC,EAAE,eAAe,GAAG,IAAI,CAElG;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,yBAAyB,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,GAAG,CAAC,EAAE,eAAe,GAAG,IAAI,CAEjH;AA6BD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,CAAC,EAAE,eAAe,GAAG,IAAI,CAEjG;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,CAAC,EAAE,eAAe,GAAG,IAAI,CAYhH;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,CAAC,EAAE,eAAe,GAAG,IAAI,CAiBlG;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,eAAe,GAAG,IAAI,CAerH"}