@davidorex/pi-context 0.26.0 → 0.28.1
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.
- package/CHANGELOG.md +71 -15
- package/README.md +71 -18
- package/dist/block-api.d.ts +165 -18
- package/dist/block-api.d.ts.map +1 -1
- package/dist/block-api.js +800 -57
- package/dist/block-api.js.map +1 -1
- package/dist/citation-rot-scanner.d.ts +80 -0
- package/dist/citation-rot-scanner.d.ts.map +1 -0
- package/dist/citation-rot-scanner.js +421 -0
- package/dist/citation-rot-scanner.js.map +1 -0
- package/dist/content-hash.d.ts +25 -0
- package/dist/content-hash.d.ts.map +1 -0
- package/dist/content-hash.js +62 -0
- package/dist/content-hash.js.map +1 -0
- package/dist/context-dir.d.ts +126 -1
- package/dist/context-dir.d.ts.map +1 -1
- package/dist/context-dir.js +208 -4
- package/dist/context-dir.js.map +1 -1
- package/dist/context-registry.d.ts +122 -0
- package/dist/context-registry.d.ts.map +1 -0
- package/dist/context-registry.js +189 -0
- package/dist/context-registry.js.map +1 -0
- package/dist/context-sdk.d.ts +179 -12
- package/dist/context-sdk.d.ts.map +1 -1
- package/dist/context-sdk.js +536 -101
- package/dist/context-sdk.js.map +1 -1
- package/dist/context.d.ts +222 -4
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +244 -56
- package/dist/context.js.map +1 -1
- package/dist/git-env.d.ts +2 -0
- package/dist/git-env.d.ts.map +1 -0
- package/dist/git-env.js +29 -0
- package/dist/git-env.js.map +1 -0
- package/dist/index.d.ts +124 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +440 -1125
- package/dist/index.js.map +1 -1
- package/dist/lens-view.d.ts.map +1 -1
- package/dist/lens-view.js +5 -3
- package/dist/lens-view.js.map +1 -1
- package/dist/migration-registry-loader.d.ts +121 -0
- package/dist/migration-registry-loader.d.ts.map +1 -0
- package/dist/migration-registry-loader.js +309 -0
- package/dist/migration-registry-loader.js.map +1 -0
- package/dist/migrations-store.d.ts +141 -0
- package/dist/migrations-store.d.ts.map +1 -0
- package/dist/migrations-store.js +206 -0
- package/dist/migrations-store.js.map +1 -0
- package/dist/object-store.d.ts +26 -0
- package/dist/object-store.d.ts.map +1 -0
- package/dist/object-store.js +104 -0
- package/dist/object-store.js.map +1 -0
- package/dist/ops-registry.d.ts +44 -0
- package/dist/ops-registry.d.ts.map +1 -0
- package/dist/ops-registry.js +1223 -0
- package/dist/ops-registry.js.map +1 -0
- package/dist/orientation.d.ts +16 -0
- package/dist/orientation.d.ts.map +1 -0
- package/dist/orientation.js +49 -0
- package/dist/orientation.js.map +1 -0
- package/dist/promote-item.d.ts +28 -0
- package/dist/promote-item.d.ts.map +1 -0
- package/dist/promote-item.js +257 -0
- package/dist/promote-item.js.map +1 -0
- package/dist/read-element.d.ts +129 -0
- package/dist/read-element.d.ts.map +1 -0
- package/dist/read-element.js +296 -0
- package/dist/read-element.js.map +1 -0
- package/dist/rename-canonical-id.d.ts.map +1 -1
- package/dist/rename-canonical-id.js +22 -13
- package/dist/rename-canonical-id.js.map +1 -1
- package/dist/roadmap-plan.d.ts.map +1 -1
- package/dist/roadmap-plan.js +23 -13
- package/dist/roadmap-plan.js.map +1 -1
- package/dist/schema-validator.d.ts +1 -0
- package/dist/schema-validator.d.ts.map +1 -1
- package/dist/schema-validator.js +7 -3
- package/dist/schema-validator.js.map +1 -1
- package/dist/schema-write.d.ts +78 -4
- package/dist/schema-write.d.ts.map +1 -1
- package/dist/schema-write.js +297 -34
- package/dist/schema-write.js.map +1 -1
- package/dist/status-vocab.d.ts +1 -0
- package/dist/status-vocab.d.ts.map +1 -1
- package/dist/status-vocab.js +4 -0
- package/dist/status-vocab.js.map +1 -1
- package/dist/truncate.d.ts +34 -0
- package/dist/truncate.d.ts.map +1 -0
- package/dist/truncate.js +85 -0
- package/dist/truncate.js.map +1 -0
- package/dist/write-schema-migration-tool.d.ts +46 -0
- package/dist/write-schema-migration-tool.d.ts.map +1 -0
- package/dist/write-schema-migration-tool.js +115 -0
- package/dist/write-schema-migration-tool.js.map +1 -0
- package/package.json +35 -2
- package/samples/blocks/work-orders.json +3 -0
- package/samples/conception.json +22 -3
- package/samples/migrations.json +133 -0
- package/samples/schemas/context-contracts.schema.json +19 -4
- package/samples/schemas/conventions.schema.json +68 -12
- package/samples/schemas/decisions.schema.json +125 -28
- package/samples/schemas/features.schema.json +140 -26
- package/samples/schemas/framework-gaps.schema.json +171 -26
- package/samples/schemas/issues.schema.json +86 -13
- package/samples/schemas/layer-plans.schema.json +195 -35
- package/samples/schemas/phase.schema.json +18 -3
- package/samples/schemas/rationale.schema.json +37 -7
- package/samples/schemas/requirements.schema.json +77 -10
- package/samples/schemas/research.schema.json +169 -36
- package/samples/schemas/spec-reviews.schema.json +88 -18
- package/samples/schemas/story.schema.json +18 -3
- package/samples/schemas/tasks.schema.json +66 -10
- package/samples/schemas/verification.schema.json +70 -12
- package/samples/schemas/work-orders.schema.json +195 -0
- package/schemas/bootstrap.schema.json +18 -6
- package/schemas/config.schema.json +58 -26
- package/schemas/context-registry.schema.json +46 -0
- package/schemas/migrations.schema.json +123 -0
- package/schemas/relations.schema.json +41 -10
- package/skill-narrative.md +42 -21
- package/skills/pi-context/SKILL.md +159 -65
- package/skills/pi-context/references/bundled-resources.md +7 -2
package/dist/context-dir.d.ts
CHANGED
|
@@ -48,6 +48,27 @@ export declare function resolveContextDir(cwd: string): string;
|
|
|
48
48
|
* boundaries under tsx/dist dual-load).
|
|
49
49
|
*/
|
|
50
50
|
export declare function tryResolveContextDir(cwd: string): string | null;
|
|
51
|
+
/**
|
|
52
|
+
* Optional pointer-history fields stamped onto the bootstrap pointer by
|
|
53
|
+
* `flipBootstrapPointer` (the /context switch family's mutation surface).
|
|
54
|
+
* Pre-fresh-init pointers carry NONE of these; switched pointers carry ALL
|
|
55
|
+
* three. The bootstrap schema declares each as optional so existing v1.0.0
|
|
56
|
+
* pointers remain valid against v1.1.0 — missing optional fields resolve to
|
|
57
|
+
* undefined in-process.
|
|
58
|
+
*
|
|
59
|
+
* `previous_contextDir` is the contextDir value the pointer held IMMEDIATELY
|
|
60
|
+
* BEFORE the most-recent flip; consumed by `/context switch -` to flip back.
|
|
61
|
+
*
|
|
62
|
+
* `switched_at` is the ISO 8601 timestamp of the most-recent flip.
|
|
63
|
+
*
|
|
64
|
+
* `switched_by` is the verified terminal-operator identity stamped by auth-gate
|
|
65
|
+
* on confirm; forensic attribution for the flip.
|
|
66
|
+
*/
|
|
67
|
+
export interface BootstrapPointerExtras {
|
|
68
|
+
previous_contextDir?: string;
|
|
69
|
+
switched_at?: string;
|
|
70
|
+
switched_by?: string;
|
|
71
|
+
}
|
|
51
72
|
/**
|
|
52
73
|
* Atomically write a `.pi-context.json` bootstrap pointer at `<cwd>/.pi-context.json`.
|
|
53
74
|
* `contextDir` is a required parameter chosen by the caller per DEC-0015 —
|
|
@@ -60,8 +81,48 @@ export declare function tryResolveContextDir(cwd: string): string | null;
|
|
|
60
81
|
* `bootstrapCache` entry for this cwd so the next `resolveContextDir(cwd)`
|
|
61
82
|
* call reads fresh data even if mtime granularity (1s on some filesystems)
|
|
62
83
|
* would otherwise mask the change.
|
|
84
|
+
*
|
|
85
|
+
* Backwards-compatible signature evolution: the optional `extras` parameter
|
|
86
|
+
* carries the v1.1.0 pointer-history fields. When `extras` is omitted (default,
|
|
87
|
+
* existing call sites) the writer behaves identically to its v1.0.0 form —
|
|
88
|
+
* pointer carries only `{contextDir, version, created_at}` and `version` stays
|
|
89
|
+
* at "1.0.0". When `extras` is provided, the pointer-history fields are
|
|
90
|
+
* merged in and `version` bumps to "1.1.0" so the on-disk pointer self-declares
|
|
91
|
+
* the format it carries. `created_at` is FRESH on every call (this primitive
|
|
92
|
+
* does not preserve created_at across writes — see `flipBootstrapPointer` for
|
|
93
|
+
* the preservation contract).
|
|
63
94
|
*/
|
|
64
|
-
export declare function writeBootstrapPointer(cwd: string, contextDir: string): void;
|
|
95
|
+
export declare function writeBootstrapPointer(cwd: string, contextDir: string, extras?: BootstrapPointerExtras): void;
|
|
96
|
+
/**
|
|
97
|
+
* Flip the bootstrap pointer to a new contextDir while preserving the original
|
|
98
|
+
* `created_at` timestamp and stamping pointer-history fields (previous_contextDir,
|
|
99
|
+
* switched_at, switched_by). The mutation surface for `/context switch` family
|
|
100
|
+
* + `context-switch` Pi tool.
|
|
101
|
+
*
|
|
102
|
+
* Behavior:
|
|
103
|
+
* 1. Reads the existing pointer (throws BootstrapNotFoundError when absent —
|
|
104
|
+
* flipping a pointer that does not exist is a programming error; callers
|
|
105
|
+
* bootstrapping a fresh substrate use `writeBootstrapPointer` directly).
|
|
106
|
+
* 2. Constructs the new pointer:
|
|
107
|
+
* - contextDir: `newContextDir` (the flip target)
|
|
108
|
+
* - version: "1.1.0" (pointer-history-bearing)
|
|
109
|
+
* - created_at: PRESERVED from the existing pointer (or stamped fresh when
|
|
110
|
+
* the existing pointer lacks created_at, which only happens for hand-
|
|
111
|
+
* authored pointers that omit the field)
|
|
112
|
+
* - previous_contextDir: the existing pointer's contextDir (so subsequent
|
|
113
|
+
* `/context switch -` can flip back)
|
|
114
|
+
* - switched_at: current ISO 8601 timestamp
|
|
115
|
+
* - switched_by: caller-supplied `writerIdentity` (auth-gate-verified
|
|
116
|
+
* terminal-operator identity at the Pi tool boundary)
|
|
117
|
+
* 3. AJV-validates against the URN-registered bootstrap schema.
|
|
118
|
+
* 4. Atomic tmp + rename write.
|
|
119
|
+
* 5. Invalidates `bootstrapCache` for the cwd.
|
|
120
|
+
*
|
|
121
|
+
* Does NOT validate that `newContextDir` exists / has a config.json — that is
|
|
122
|
+
* the caller's read-side check (slash command handler + Pi tool body each
|
|
123
|
+
* perform target-dir-shape validation appropriate to their mode).
|
|
124
|
+
*/
|
|
125
|
+
export declare function flipBootstrapPointer(cwd: string, newContextDir: string, writerIdentity: string): void;
|
|
65
126
|
/**
|
|
66
127
|
* Reject substrate names that are not bare path segments (FGAP-079 / DEC-0045).
|
|
67
128
|
*
|
|
@@ -87,7 +148,71 @@ export declare function assertSubstrateName(name: string): void;
|
|
|
87
148
|
* hardcoded.
|
|
88
149
|
*/
|
|
89
150
|
export declare function schemasDir(cwd: string): string;
|
|
151
|
+
/**
|
|
152
|
+
* Dir-targeted form of `schemaPath`: build the schema path against an
|
|
153
|
+
* explicit substrate directory rather than resolving one from `cwd`.
|
|
154
|
+
* `assertSubstrateName` still guards the block name (path-injection guard
|
|
155
|
+
* preserved). Cross-substrate consumers (Cycle F resolver / Cycle H
|
|
156
|
+
* migration) target a non-active substrate by passing its directory here.
|
|
157
|
+
*/
|
|
158
|
+
export declare function schemaPathForDir(substrateDir: string, blockName: string): string;
|
|
90
159
|
export declare function schemaPath(cwd: string, blockName: string): string;
|
|
91
160
|
export declare function agentsDir(cwd: string): string;
|
|
92
161
|
export declare function contextTemplatesDir(cwd: string): string;
|
|
162
|
+
/**
|
|
163
|
+
* `<resolveContextDir(cwd)>/migrations.json` — substrate-managed file holding
|
|
164
|
+
* operator-authored schema version migration declarations. Singleton file per
|
|
165
|
+
* cwd (no per-name guard); see `migrations-store.ts` for read / write helpers
|
|
166
|
+
* and `migration-registry-loader.ts` for the loader that converts declarations
|
|
167
|
+
* into a populated MigrationRegistry. Mirrors the relationsPath / configPath
|
|
168
|
+
* shape: pointer-resolved, substrate-dir-relative, no fallback default.
|
|
169
|
+
*/
|
|
170
|
+
/**
|
|
171
|
+
* Dir-targeted form of `migrationsPath`: build the `migrations.json` path
|
|
172
|
+
* against an explicit substrate directory rather than resolving one from
|
|
173
|
+
* `cwd`. Used by the ForDir migration-registry loader so a write into a
|
|
174
|
+
* non-active substrate validates/migrates against THAT substrate's
|
|
175
|
+
* declarations.
|
|
176
|
+
*/
|
|
177
|
+
export declare function migrationsPathForDir(substrateDir: string): string;
|
|
178
|
+
export declare function migrationsPath(cwd: string): string;
|
|
179
|
+
/**
|
|
180
|
+
* `^sub-[0-9a-f]{16}$` — the substrate_id shape. A substrate_id is the per-
|
|
181
|
+
* substrate root identity that `mintOid` (block-api.ts) salts an item OID with,
|
|
182
|
+
* so two substrates minting an item with the same birth nonce still produce
|
|
183
|
+
* distinct OIDs. Single source of truth for the on-disk `config.substrate_id`
|
|
184
|
+
* regex (mirrored as a literal `pattern` in config.schema.json — the two must
|
|
185
|
+
* not drift).
|
|
186
|
+
*/
|
|
187
|
+
export declare const SUBSTRATE_ID_PATTERN: RegExp;
|
|
188
|
+
/**
|
|
189
|
+
* Mint a fresh substrate_id: `"sub-" + sha256Hex(canonicalJson([Date.now(),
|
|
190
|
+
* randomUUID()])).slice(0, 16)`. The `[epoch-ms, uuid]` tuple makes the pre-
|
|
191
|
+
* image collision-free in practice (the uuid alone suffices; the timestamp is
|
|
192
|
+
* a readability/ordering aid in the pre-image only, never surfaced). The
|
|
193
|
+
* 16-hex-char slice keeps the id compact while leaving 64 bits of entropy —
|
|
194
|
+
* far beyond the substrate-count regime. Minted ONCE per substrate (at
|
|
195
|
+
* /context init in Cycle 4; established by hand for the active substrate +
|
|
196
|
+
* packaged samples this cycle) and then immutable on disk; never re-minted on
|
|
197
|
+
* an item write.
|
|
198
|
+
*/
|
|
199
|
+
export declare function mintSubstrateId(): string;
|
|
200
|
+
/**
|
|
201
|
+
* Read the `substrate_id` from `<substrateDir>/config.json`. Throws loudly when
|
|
202
|
+
* the config is absent / unreadable / lacks a `substrate_id` — there is NO
|
|
203
|
+
* degraded fallback and NO lazy mint-on-read (locked decision 2): a substrate
|
|
204
|
+
* that participates in identity stamping must carry an explicit substrate_id,
|
|
205
|
+
* established when its identity-declaring schemas were established. The
|
|
206
|
+
* schema-gate on `prepareItemIdentityForWrite` (block-api.ts) and this throw
|
|
207
|
+
* align by construction — stamping only fires for substrates whose schemas
|
|
208
|
+
* declare the identity fields, which are exactly the substrates given a
|
|
209
|
+
* substrate_id — so the throw is a loud guard against a mis-provisioned
|
|
210
|
+
* substrate, not an expected runtime branch.
|
|
211
|
+
*/
|
|
212
|
+
export declare function substrateIdForDir(substrateDir: string): string;
|
|
213
|
+
/**
|
|
214
|
+
* `substrateIdForDir(resolveContextDir(cwd))` — the cwd-resolved form for
|
|
215
|
+
* callers holding a working directory rather than an explicit substrate dir.
|
|
216
|
+
*/
|
|
217
|
+
export declare function substrateIdFor(cwd: string): string;
|
|
93
218
|
//# sourceMappingURL=context-dir.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-dir.d.ts","sourceRoot":"","sources":["../src/context-dir.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"context-dir.d.ts","sourceRoot":"","sources":["../src/context-dir.ts"],"names":[],"mappings":"AAiCA,mFAAmF;AACnF,eAAO,MAAM,WAAW,YAAY,CAAC;AAErC;;;;;;;GAOG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;IAChD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;gBACnB,GAAG,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM;CAQ9C;AA6BD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAuCrD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAO/D;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,sBAAsB;IACtC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,sBAAsB,GAAG,IAAI,CAuC5G;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,CA2DrG;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAMtD;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAGhF;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAQjE;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;;;GAOG;AACH;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAElD;AAID;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,QAAuB,CAAC;AAEzD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CA8B9D;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAElD"}
|
package/dist/context-dir.js
CHANGED
|
@@ -25,8 +25,10 @@
|
|
|
25
25
|
* workflow-executor.ts still import it as a bare segment); Phase 7 of
|
|
26
26
|
* FGAP-026 closure cascades those sites and removes the export.
|
|
27
27
|
*/
|
|
28
|
+
import { randomUUID } from "node:crypto";
|
|
28
29
|
import fs from "node:fs";
|
|
29
30
|
import path from "node:path";
|
|
31
|
+
import { canonicalJson, sha256Hex } from "./content-hash.js";
|
|
30
32
|
import { validate } from "./schema-validator.js";
|
|
31
33
|
/** @deprecated Same status as the removed PROJECT_DIR — Phase 7 cascade target. */
|
|
32
34
|
export const SCHEMAS_DIR = "schemas";
|
|
@@ -42,7 +44,7 @@ export class BootstrapNotFoundError extends Error {
|
|
|
42
44
|
cwd;
|
|
43
45
|
bootstrapPath;
|
|
44
46
|
constructor(cwd, bootstrapPath) {
|
|
45
|
-
super(`pi-context: no .pi-context.json bootstrap pointer at ${bootstrapPath}; run /context init to declare substrate dir per DEC-0015`);
|
|
47
|
+
super(`pi-context: no .pi-context.json bootstrap pointer at ${bootstrapPath}; run /context init <substrate-dir> to declare substrate dir per DEC-0015`);
|
|
46
48
|
this.name = "BootstrapNotFoundError";
|
|
47
49
|
this.cwd = cwd;
|
|
48
50
|
this.bootstrapPath = bootstrapPath;
|
|
@@ -149,13 +151,31 @@ export function tryResolveContextDir(cwd) {
|
|
|
149
151
|
* `bootstrapCache` entry for this cwd so the next `resolveContextDir(cwd)`
|
|
150
152
|
* call reads fresh data even if mtime granularity (1s on some filesystems)
|
|
151
153
|
* would otherwise mask the change.
|
|
154
|
+
*
|
|
155
|
+
* Backwards-compatible signature evolution: the optional `extras` parameter
|
|
156
|
+
* carries the v1.1.0 pointer-history fields. When `extras` is omitted (default,
|
|
157
|
+
* existing call sites) the writer behaves identically to its v1.0.0 form —
|
|
158
|
+
* pointer carries only `{contextDir, version, created_at}` and `version` stays
|
|
159
|
+
* at "1.0.0". When `extras` is provided, the pointer-history fields are
|
|
160
|
+
* merged in and `version` bumps to "1.1.0" so the on-disk pointer self-declares
|
|
161
|
+
* the format it carries. `created_at` is FRESH on every call (this primitive
|
|
162
|
+
* does not preserve created_at across writes — see `flipBootstrapPointer` for
|
|
163
|
+
* the preservation contract).
|
|
152
164
|
*/
|
|
153
|
-
export function writeBootstrapPointer(cwd, contextDir) {
|
|
165
|
+
export function writeBootstrapPointer(cwd, contextDir, extras) {
|
|
166
|
+
const hasExtras = extras !== undefined &&
|
|
167
|
+
(extras.previous_contextDir !== undefined || extras.switched_at !== undefined || extras.switched_by !== undefined);
|
|
154
168
|
const pointer = {
|
|
155
169
|
contextDir,
|
|
156
|
-
version: "1.0.0",
|
|
170
|
+
version: hasExtras ? "1.1.0" : "1.0.0",
|
|
157
171
|
created_at: new Date().toISOString(),
|
|
158
172
|
};
|
|
173
|
+
if (extras?.previous_contextDir !== undefined)
|
|
174
|
+
pointer.previous_contextDir = extras.previous_contextDir;
|
|
175
|
+
if (extras?.switched_at !== undefined)
|
|
176
|
+
pointer.switched_at = extras.switched_at;
|
|
177
|
+
if (extras?.switched_by !== undefined)
|
|
178
|
+
pointer.switched_by = extras.switched_by;
|
|
159
179
|
// Validate before write — AJV via canonical `validate()` against
|
|
160
180
|
// pre-registered bootstrap schema URN.
|
|
161
181
|
validate(BOOTSTRAP_REF_SCHEMA, pointer, `bootstrap pointer (writeBootstrapPointer for ${cwd})`);
|
|
@@ -180,6 +200,85 @@ export function writeBootstrapPointer(cwd, contextDir) {
|
|
|
180
200
|
// fresh pointer regardless of mtime-granularity edge cases.
|
|
181
201
|
bootstrapCache.delete(path.resolve(cwd));
|
|
182
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Flip the bootstrap pointer to a new contextDir while preserving the original
|
|
205
|
+
* `created_at` timestamp and stamping pointer-history fields (previous_contextDir,
|
|
206
|
+
* switched_at, switched_by). The mutation surface for `/context switch` family
|
|
207
|
+
* + `context-switch` Pi tool.
|
|
208
|
+
*
|
|
209
|
+
* Behavior:
|
|
210
|
+
* 1. Reads the existing pointer (throws BootstrapNotFoundError when absent —
|
|
211
|
+
* flipping a pointer that does not exist is a programming error; callers
|
|
212
|
+
* bootstrapping a fresh substrate use `writeBootstrapPointer` directly).
|
|
213
|
+
* 2. Constructs the new pointer:
|
|
214
|
+
* - contextDir: `newContextDir` (the flip target)
|
|
215
|
+
* - version: "1.1.0" (pointer-history-bearing)
|
|
216
|
+
* - created_at: PRESERVED from the existing pointer (or stamped fresh when
|
|
217
|
+
* the existing pointer lacks created_at, which only happens for hand-
|
|
218
|
+
* authored pointers that omit the field)
|
|
219
|
+
* - previous_contextDir: the existing pointer's contextDir (so subsequent
|
|
220
|
+
* `/context switch -` can flip back)
|
|
221
|
+
* - switched_at: current ISO 8601 timestamp
|
|
222
|
+
* - switched_by: caller-supplied `writerIdentity` (auth-gate-verified
|
|
223
|
+
* terminal-operator identity at the Pi tool boundary)
|
|
224
|
+
* 3. AJV-validates against the URN-registered bootstrap schema.
|
|
225
|
+
* 4. Atomic tmp + rename write.
|
|
226
|
+
* 5. Invalidates `bootstrapCache` for the cwd.
|
|
227
|
+
*
|
|
228
|
+
* Does NOT validate that `newContextDir` exists / has a config.json — that is
|
|
229
|
+
* the caller's read-side check (slash command handler + Pi tool body each
|
|
230
|
+
* perform target-dir-shape validation appropriate to their mode).
|
|
231
|
+
*/
|
|
232
|
+
export function flipBootstrapPointer(cwd, newContextDir, writerIdentity) {
|
|
233
|
+
const bootstrapPath = path.join(cwd, ".pi-context.json");
|
|
234
|
+
if (!fs.existsSync(bootstrapPath)) {
|
|
235
|
+
throw new BootstrapNotFoundError(cwd, bootstrapPath);
|
|
236
|
+
}
|
|
237
|
+
let existingRaw;
|
|
238
|
+
try {
|
|
239
|
+
existingRaw = fs.readFileSync(bootstrapPath, "utf-8");
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
throw new Error(`flipBootstrapPointer: failed to read existing ${bootstrapPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
243
|
+
}
|
|
244
|
+
let existing;
|
|
245
|
+
try {
|
|
246
|
+
existing = JSON.parse(existingRaw);
|
|
247
|
+
}
|
|
248
|
+
catch (err) {
|
|
249
|
+
throw new Error(`flipBootstrapPointer: invalid JSON in existing ${bootstrapPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
250
|
+
}
|
|
251
|
+
const previousContextDir = existing.contextDir;
|
|
252
|
+
if (typeof previousContextDir !== "string") {
|
|
253
|
+
throw new Error(`flipBootstrapPointer: existing pointer at ${bootstrapPath} lacks a string contextDir; refuses to flip an unreadable pointer`);
|
|
254
|
+
}
|
|
255
|
+
const preservedCreatedAt = typeof existing.created_at === "string" ? existing.created_at : new Date().toISOString();
|
|
256
|
+
const pointer = {
|
|
257
|
+
contextDir: newContextDir,
|
|
258
|
+
version: "1.1.0",
|
|
259
|
+
created_at: preservedCreatedAt,
|
|
260
|
+
previous_contextDir: previousContextDir,
|
|
261
|
+
switched_at: new Date().toISOString(),
|
|
262
|
+
switched_by: writerIdentity,
|
|
263
|
+
};
|
|
264
|
+
validate(BOOTSTRAP_REF_SCHEMA, pointer, `bootstrap pointer (flipBootstrapPointer for ${cwd})`);
|
|
265
|
+
const tmpPath = `${bootstrapPath}.bootstrap-${process.pid}.tmp`;
|
|
266
|
+
try {
|
|
267
|
+
fs.writeFileSync(tmpPath, JSON.stringify(pointer, null, 2), "utf-8");
|
|
268
|
+
fs.renameSync(tmpPath, bootstrapPath);
|
|
269
|
+
}
|
|
270
|
+
catch (err) {
|
|
271
|
+
try {
|
|
272
|
+
fs.unlinkSync(tmpPath);
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
/* ignore cleanup failure */
|
|
276
|
+
}
|
|
277
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
278
|
+
throw new Error(`flipBootstrapPointer: failed to write ${bootstrapPath}: ${msg}`);
|
|
279
|
+
}
|
|
280
|
+
bootstrapCache.delete(path.resolve(cwd));
|
|
281
|
+
}
|
|
183
282
|
/**
|
|
184
283
|
* Reject substrate names that are not bare path segments (FGAP-079 / DEC-0045).
|
|
185
284
|
*
|
|
@@ -211,9 +310,25 @@ export function assertSubstrateName(name) {
|
|
|
211
310
|
export function schemasDir(cwd) {
|
|
212
311
|
return path.join(resolveContextDir(cwd), SCHEMAS_DIR);
|
|
213
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* Dir-targeted form of `schemaPath`: build the schema path against an
|
|
315
|
+
* explicit substrate directory rather than resolving one from `cwd`.
|
|
316
|
+
* `assertSubstrateName` still guards the block name (path-injection guard
|
|
317
|
+
* preserved). Cross-substrate consumers (Cycle F resolver / Cycle H
|
|
318
|
+
* migration) target a non-active substrate by passing its directory here.
|
|
319
|
+
*/
|
|
320
|
+
export function schemaPathForDir(substrateDir, blockName) {
|
|
321
|
+
assertSubstrateName(blockName);
|
|
322
|
+
return path.join(substrateDir, SCHEMAS_DIR, `${blockName}.schema.json`);
|
|
323
|
+
}
|
|
214
324
|
export function schemaPath(cwd, blockName) {
|
|
325
|
+
// Assert the name BEFORE resolving the substrate dir so the FGAP-079
|
|
326
|
+
// path-traversal guard fires ahead of BootstrapNotFoundError (a traversal
|
|
327
|
+
// name must reject even when no `.pi-context.json` pointer exists). The
|
|
328
|
+
// ForDir body asserts again — harmless double-assert; the boundary guard
|
|
329
|
+
// is the point.
|
|
215
330
|
assertSubstrateName(blockName);
|
|
216
|
-
return
|
|
331
|
+
return schemaPathForDir(resolveContextDir(cwd), blockName);
|
|
217
332
|
}
|
|
218
333
|
export function agentsDir(cwd) {
|
|
219
334
|
return path.join(resolveContextDir(cwd), "agents");
|
|
@@ -221,4 +336,93 @@ export function agentsDir(cwd) {
|
|
|
221
336
|
export function contextTemplatesDir(cwd) {
|
|
222
337
|
return path.join(resolveContextDir(cwd), "templates");
|
|
223
338
|
}
|
|
339
|
+
/**
|
|
340
|
+
* `<resolveContextDir(cwd)>/migrations.json` — substrate-managed file holding
|
|
341
|
+
* operator-authored schema version migration declarations. Singleton file per
|
|
342
|
+
* cwd (no per-name guard); see `migrations-store.ts` for read / write helpers
|
|
343
|
+
* and `migration-registry-loader.ts` for the loader that converts declarations
|
|
344
|
+
* into a populated MigrationRegistry. Mirrors the relationsPath / configPath
|
|
345
|
+
* shape: pointer-resolved, substrate-dir-relative, no fallback default.
|
|
346
|
+
*/
|
|
347
|
+
/**
|
|
348
|
+
* Dir-targeted form of `migrationsPath`: build the `migrations.json` path
|
|
349
|
+
* against an explicit substrate directory rather than resolving one from
|
|
350
|
+
* `cwd`. Used by the ForDir migration-registry loader so a write into a
|
|
351
|
+
* non-active substrate validates/migrates against THAT substrate's
|
|
352
|
+
* declarations.
|
|
353
|
+
*/
|
|
354
|
+
export function migrationsPathForDir(substrateDir) {
|
|
355
|
+
return path.join(substrateDir, "migrations.json");
|
|
356
|
+
}
|
|
357
|
+
export function migrationsPath(cwd) {
|
|
358
|
+
return migrationsPathForDir(resolveContextDir(cwd));
|
|
359
|
+
}
|
|
360
|
+
// ── Substrate identity (content-addressed substrate identity, Cycle 3) ────────
|
|
361
|
+
/**
|
|
362
|
+
* `^sub-[0-9a-f]{16}$` — the substrate_id shape. A substrate_id is the per-
|
|
363
|
+
* substrate root identity that `mintOid` (block-api.ts) salts an item OID with,
|
|
364
|
+
* so two substrates minting an item with the same birth nonce still produce
|
|
365
|
+
* distinct OIDs. Single source of truth for the on-disk `config.substrate_id`
|
|
366
|
+
* regex (mirrored as a literal `pattern` in config.schema.json — the two must
|
|
367
|
+
* not drift).
|
|
368
|
+
*/
|
|
369
|
+
export const SUBSTRATE_ID_PATTERN = /^sub-[0-9a-f]{16}$/;
|
|
370
|
+
/**
|
|
371
|
+
* Mint a fresh substrate_id: `"sub-" + sha256Hex(canonicalJson([Date.now(),
|
|
372
|
+
* randomUUID()])).slice(0, 16)`. The `[epoch-ms, uuid]` tuple makes the pre-
|
|
373
|
+
* image collision-free in practice (the uuid alone suffices; the timestamp is
|
|
374
|
+
* a readability/ordering aid in the pre-image only, never surfaced). The
|
|
375
|
+
* 16-hex-char slice keeps the id compact while leaving 64 bits of entropy —
|
|
376
|
+
* far beyond the substrate-count regime. Minted ONCE per substrate (at
|
|
377
|
+
* /context init in Cycle 4; established by hand for the active substrate +
|
|
378
|
+
* packaged samples this cycle) and then immutable on disk; never re-minted on
|
|
379
|
+
* an item write.
|
|
380
|
+
*/
|
|
381
|
+
export function mintSubstrateId() {
|
|
382
|
+
return `sub-${sha256Hex(canonicalJson([Date.now(), randomUUID()])).slice(0, 16)}`;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Read the `substrate_id` from `<substrateDir>/config.json`. Throws loudly when
|
|
386
|
+
* the config is absent / unreadable / lacks a `substrate_id` — there is NO
|
|
387
|
+
* degraded fallback and NO lazy mint-on-read (locked decision 2): a substrate
|
|
388
|
+
* that participates in identity stamping must carry an explicit substrate_id,
|
|
389
|
+
* established when its identity-declaring schemas were established. The
|
|
390
|
+
* schema-gate on `prepareItemIdentityForWrite` (block-api.ts) and this throw
|
|
391
|
+
* align by construction — stamping only fires for substrates whose schemas
|
|
392
|
+
* declare the identity fields, which are exactly the substrates given a
|
|
393
|
+
* substrate_id — so the throw is a loud guard against a mis-provisioned
|
|
394
|
+
* substrate, not an expected runtime branch.
|
|
395
|
+
*/
|
|
396
|
+
export function substrateIdForDir(substrateDir) {
|
|
397
|
+
const configPath = path.join(substrateDir, "config.json");
|
|
398
|
+
if (!fs.existsSync(configPath)) {
|
|
399
|
+
throw new Error(`substrateIdForDir: no config.json at ${configPath}; a substrate that stamps identity must carry an explicit substrate_id (no lazy mint on write)`);
|
|
400
|
+
}
|
|
401
|
+
let raw;
|
|
402
|
+
try {
|
|
403
|
+
raw = fs.readFileSync(configPath, "utf-8");
|
|
404
|
+
}
|
|
405
|
+
catch (err) {
|
|
406
|
+
throw new Error(`substrateIdForDir: failed to read ${configPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
407
|
+
}
|
|
408
|
+
let data;
|
|
409
|
+
try {
|
|
410
|
+
data = JSON.parse(raw);
|
|
411
|
+
}
|
|
412
|
+
catch (err) {
|
|
413
|
+
throw new Error(`substrateIdForDir: invalid JSON in ${configPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
414
|
+
}
|
|
415
|
+
const substrateId = data && typeof data === "object" ? data.substrate_id : undefined;
|
|
416
|
+
if (typeof substrateId !== "string" || !SUBSTRATE_ID_PATTERN.test(substrateId)) {
|
|
417
|
+
throw new Error(`substrateIdForDir: config.json at ${configPath} has no valid substrate_id (expected ^sub-[0-9a-f]{16}$, got ${JSON.stringify(substrateId)}); establish one before stamping identity`);
|
|
418
|
+
}
|
|
419
|
+
return substrateId;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* `substrateIdForDir(resolveContextDir(cwd))` — the cwd-resolved form for
|
|
423
|
+
* callers holding a working directory rather than an explicit substrate dir.
|
|
424
|
+
*/
|
|
425
|
+
export function substrateIdFor(cwd) {
|
|
426
|
+
return substrateIdForDir(resolveContextDir(cwd));
|
|
427
|
+
}
|
|
224
428
|
//# sourceMappingURL=context-dir.js.map
|
package/dist/context-dir.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-dir.js","sourceRoot":"","sources":["../src/context-dir.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,mFAAmF;AACnF,MAAM,CAAC,MAAM,WAAW,GAAG,SAAS,CAAC;AAErC;;;;;;;GAOG;AACH,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IACvC,GAAG,CAAS;IACZ,aAAa,CAAS;IAC/B,YAAY,GAAW,EAAE,aAAqB;QAC7C,KAAK,CACJ,wDAAwD,aAAa,
|
|
1
|
+
{"version":3,"file":"context-dir.js","sourceRoot":"","sources":["../src/context-dir.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* context-registry — project-root substrate registry read / write surface for
|
|
3
|
+
* `<cwd>/.pi-context-registry.json`.
|
|
4
|
+
*
|
|
5
|
+
* The registry is a PROJECT-ROOT file (tracked in git, like `.pi-context.json`),
|
|
6
|
+
* NOT substrate-relative: it enumerates EVERY known substrate by its immutable
|
|
7
|
+
* content-addressed `substrate_id` (`^sub-[0-9a-f]{16}$`), mapping each to its
|
|
8
|
+
* on-disk directory + alias list. This is the substrate-locator layer the
|
|
9
|
+
* Cycle-8 F2 resolver consumes to resolve a foreign `<substrate_id>:<oid>` /
|
|
10
|
+
* `<alias>:<refname>` locator to a directory, and the surface the Phase-H
|
|
11
|
+
* migration registers each legacy substrate into.
|
|
12
|
+
*
|
|
13
|
+
* Distinct from the `.pi-context.json` bootstrap pointer: the pointer names the
|
|
14
|
+
* single ACTIVE substrate dir; this registry enumerates all of them. The
|
|
15
|
+
* SoT-drift invariant in `validateContext` (context-sdk.ts) requires the active
|
|
16
|
+
* `config.substrate_id` to have an entry here whose `dir` resolves to the active
|
|
17
|
+
* substrate.
|
|
18
|
+
*
|
|
19
|
+
* Mirrors `migrations-store.ts` atomic discipline one-for-one:
|
|
20
|
+
* - `loadRegistry` reads + JSON-parses + AJV-validates via `validateFromFile`
|
|
21
|
+
* against the bundled `context-registry.schema.json`; returns null when the
|
|
22
|
+
* file is absent (a pre-write project with no registry is a normal state,
|
|
23
|
+
* NOT an error).
|
|
24
|
+
* - `writeRegistry` delegates whole-file writes to block-api's atomic
|
|
25
|
+
* `writeTypedFile` (tmp + rename + schema-validate), so a failed write
|
|
26
|
+
* leaves the prior file byte-identical and a malformed registry never lands.
|
|
27
|
+
* - `registerSubstrate` wraps a load-or-empty → JSON deep-clone → upsert →
|
|
28
|
+
* write pattern (idempotent: re-registering the same substrate_id with the
|
|
29
|
+
* same dir is a no-op rewrite; a changed dir/aliases overwrites the entry).
|
|
30
|
+
*
|
|
31
|
+
* A small per-cwd cache (keyed by absolute cwd, invalidated on mtime change and
|
|
32
|
+
* proactively on write) mirrors the migrations-store cache so read-after-write
|
|
33
|
+
* within a process is consistent without restart.
|
|
34
|
+
*/
|
|
35
|
+
import type { DispatchContext } from "./dispatch-context.js";
|
|
36
|
+
/**
|
|
37
|
+
* Project-root path of the substrate registry. PROJECT-ROOT, not substrate-dir-
|
|
38
|
+
* relative (contrast `migrationsPath`, which resolves through the active
|
|
39
|
+
* substrate dir). The registry enumerates substrates above any single
|
|
40
|
+
* substrate, so it must live at a fixed root location independent of which
|
|
41
|
+
* substrate the bootstrap pointer currently names.
|
|
42
|
+
*/
|
|
43
|
+
export declare function contextRegistryPath(cwd: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* One registry entry — the directory + alias list for a substrate_id. Mirrors
|
|
46
|
+
* the `RegistryEntry` definition in context-registry.schema.json one-for-one.
|
|
47
|
+
* `dir` is resolved against the project root (`path.resolve(cwd, dir)`) when
|
|
48
|
+
* matched against the active substrate by the drift invariant.
|
|
49
|
+
*/
|
|
50
|
+
export interface RegistryEntry {
|
|
51
|
+
dir: string;
|
|
52
|
+
aliases: string[];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* On-disk registry shape. `substrates` is keyed by substrate_id
|
|
56
|
+
* (`^sub-[0-9a-f]{16}$`, enforced by the schema's `propertyNames`).
|
|
57
|
+
*/
|
|
58
|
+
export interface RegistryFile {
|
|
59
|
+
version: string;
|
|
60
|
+
substrates: Record<string, RegistryEntry>;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Current `version` emitted into newly-created `.pi-context-registry.json`
|
|
64
|
+
* files. Tracks the `version` field of context-registry.schema.json itself.
|
|
65
|
+
*/
|
|
66
|
+
export declare const REGISTRY_FILE_VERSION = "1.0.0";
|
|
67
|
+
/**
|
|
68
|
+
* Drop the cached registry for `cwd` so the next `loadRegistry(cwd)` re-reads
|
|
69
|
+
* from disk. Called internally by `writeRegistry`; exported for callers that
|
|
70
|
+
* mutate the file out-of-band (e.g. tests / the orchestrator seed).
|
|
71
|
+
*/
|
|
72
|
+
export declare function invalidateRegistry(cwd: string): void;
|
|
73
|
+
/**
|
|
74
|
+
* Load + AJV-validate the project-root registry. Returns null when the file is
|
|
75
|
+
* absent (a project with no registry yet — a normal pre-write condition, NOT an
|
|
76
|
+
* error). Throws on read / parse / schema failure. Caches by absolute cwd keyed
|
|
77
|
+
* on file mtime.
|
|
78
|
+
*/
|
|
79
|
+
export declare function loadRegistry(cwd: string): RegistryFile | null;
|
|
80
|
+
/**
|
|
81
|
+
* Atomic, AJV-validated whole-file write of `.pi-context-registry.json`.
|
|
82
|
+
* Delegates to block-api's `writeTypedFile` against the bundled registry schema
|
|
83
|
+
* (tmp + rename; a failed write leaves the prior file byte-identical). `ctx` is
|
|
84
|
+
* accepted for call-site parity with the rest of the substrate write surface;
|
|
85
|
+
* the registry schema declares no envelope author fields so stamping is a
|
|
86
|
+
* structural no-op today. Invalidates the per-cwd cache after the write so the
|
|
87
|
+
* next `loadRegistry(cwd)` reads the fresh file.
|
|
88
|
+
*/
|
|
89
|
+
export declare function writeRegistry(cwd: string, file: RegistryFile, ctx?: DispatchContext): void;
|
|
90
|
+
/**
|
|
91
|
+
* Upsert a substrate's registry entry. Load-or-empty → clone → set
|
|
92
|
+
* `substrates[substrate_id] = { dir, aliases }` → atomic write. Idempotent:
|
|
93
|
+
* re-registering the same substrate_id with the same dir + aliases produces a
|
|
94
|
+
* byte-identical file; a changed dir (e.g. a renamed substrate directory) or a
|
|
95
|
+
* changed alias list overwrites the prior entry in place. `aliases` defaults to
|
|
96
|
+
* `[]` when omitted (Cycle 4 registers with empty aliases; the Phase-H
|
|
97
|
+
* migration populates the legacy `project:` alias).
|
|
98
|
+
*
|
|
99
|
+
* Does NOT validate that `substrate_id` matches `^sub-[0-9a-f]{16}$` here — the
|
|
100
|
+
* schema's `propertyNames` pattern enforces that at the write boundary (an
|
|
101
|
+
* out-of-shape key fails `writeTypedFile`'s AJV validation, leaving the prior
|
|
102
|
+
* file intact).
|
|
103
|
+
*/
|
|
104
|
+
export declare function registerSubstrate(cwd: string, substrate_id: string, dir: string, aliases?: string[], ctx?: DispatchContext): void;
|
|
105
|
+
/**
|
|
106
|
+
* Resolve a substrate_id to its registered directory string (as stored — i.e.
|
|
107
|
+
* project-root-relative, the caller resolves against cwd when an absolute path
|
|
108
|
+
* is needed). Returns null on miss (substrate_id not registered, or no registry
|
|
109
|
+
* file at all) — never throws on a clean miss, mirroring the F2 resolver's
|
|
110
|
+
* expectation of a null-or-hit lookup.
|
|
111
|
+
*/
|
|
112
|
+
export declare function resolveSubstrateDir(cwd: string, substrate_id: string): string | null;
|
|
113
|
+
/**
|
|
114
|
+
* Resolve an alias to the substrate_id that declares it. Scans every entry's
|
|
115
|
+
* `aliases[]` for an exact string match and returns the owning substrate_id.
|
|
116
|
+
* Returns null on miss (alias unregistered, or no registry file) — never throws
|
|
117
|
+
* on a clean miss. First-match wins if (by misconfiguration) two substrates
|
|
118
|
+
* claim the same alias; the registry does not enforce alias uniqueness this
|
|
119
|
+
* cycle.
|
|
120
|
+
*/
|
|
121
|
+
export declare function resolveAlias(cwd: string, alias: string): string | null;
|
|
122
|
+
//# sourceMappingURL=context-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-registry.d.ts","sourceRoot":"","sources":["../src/context-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAMH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAa7D;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CAC1C;AAED;;;GAGG;AACH,eAAO,MAAM,qBAAqB,UAAU,CAAC;AAgB7C;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEpD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CA2B7D;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,CAAC,EAAE,eAAe,GAAG,IAAI,CAG1F;AAoBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAChC,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,MAAM,EACpB,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,MAAM,EAAO,EACtB,GAAG,CAAC,EAAE,eAAe,GACnB,IAAI,CAKN;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKpF;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOtE"}
|