@davidorex/pi-context 0.30.0 → 0.32.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.
- package/CHANGELOG.md +127 -0
- package/README.md +27 -11
- package/dist/block-api.d.ts +13 -0
- package/dist/block-api.d.ts.map +1 -1
- package/dist/block-api.js +28 -3
- package/dist/block-api.js.map +1 -1
- package/dist/content-hash.d.ts +13 -0
- package/dist/content-hash.d.ts.map +1 -1
- package/dist/content-hash.js +16 -0
- package/dist/content-hash.js.map +1 -1
- package/dist/context-dir.d.ts +12 -0
- package/dist/context-dir.d.ts.map +1 -1
- package/dist/context-dir.js +14 -0
- package/dist/context-dir.js.map +1 -1
- package/dist/context-sdk.d.ts +71 -33
- package/dist/context-sdk.d.ts.map +1 -1
- package/dist/context-sdk.js +547 -149
- package/dist/context-sdk.js.map +1 -1
- package/dist/context.d.ts +213 -2
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +119 -5
- package/dist/context.js.map +1 -1
- package/dist/index.d.ts +595 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2225 -55
- package/dist/index.js.map +1 -1
- package/dist/lens-view.d.ts +0 -5
- package/dist/lens-view.d.ts.map +1 -1
- package/dist/lens-view.js +43 -1
- package/dist/lens-view.js.map +1 -1
- package/dist/migration-registry-loader.d.ts +36 -12
- package/dist/migration-registry-loader.d.ts.map +1 -1
- package/dist/migration-registry-loader.js +79 -17
- package/dist/migration-registry-loader.js.map +1 -1
- package/dist/migrations-store.d.ts +45 -18
- package/dist/migrations-store.d.ts.map +1 -1
- package/dist/migrations-store.js +56 -22
- package/dist/migrations-store.js.map +1 -1
- package/dist/ops-registry.d.ts +16 -0
- package/dist/ops-registry.d.ts.map +1 -1
- package/dist/ops-registry.js +352 -117
- package/dist/ops-registry.js.map +1 -1
- package/dist/pending-blocked-store.d.ts +83 -0
- package/dist/pending-blocked-store.d.ts.map +1 -0
- package/dist/pending-blocked-store.js +93 -0
- package/dist/pending-blocked-store.js.map +1 -0
- package/dist/promote-item.d.ts.map +1 -1
- package/dist/promote-item.js +41 -12
- package/dist/promote-item.js.map +1 -1
- package/dist/roadmap-plan.d.ts +121 -99
- package/dist/roadmap-plan.d.ts.map +1 -1
- package/dist/roadmap-plan.js +281 -345
- package/dist/roadmap-plan.js.map +1 -1
- package/dist/schema-merge.d.ts +26 -0
- package/dist/schema-merge.d.ts.map +1 -0
- package/dist/schema-merge.js +176 -0
- package/dist/schema-merge.js.map +1 -0
- package/dist/status-vocab.d.ts +12 -2
- package/dist/status-vocab.d.ts.map +1 -1
- package/dist/status-vocab.js +14 -1
- package/dist/status-vocab.js.map +1 -1
- package/package.json +2 -1
- package/samples/blocks/milestone.json +3 -0
- package/samples/blocks/session-notes.json +1 -0
- package/samples/conception.json +358 -15
- package/samples/migrations.json +8 -0
- package/samples/schemas/context-contracts.schema.json +4 -0
- package/samples/schemas/conventions.schema.json +4 -0
- package/samples/schemas/decisions.schema.json +4 -0
- package/samples/schemas/features.schema.json +4 -0
- package/samples/schemas/framework-gaps.schema.json +5 -1
- package/samples/schemas/issues.schema.json +30 -2
- package/samples/schemas/layer-plans.schema.json +6 -2
- package/samples/schemas/milestone.schema.json +79 -0
- package/samples/schemas/phase.schema.json +4 -0
- package/samples/schemas/rationale.schema.json +4 -0
- package/samples/schemas/requirements.schema.json +4 -0
- package/samples/schemas/research.schema.json +5 -1
- package/samples/schemas/session-notes.schema.json +89 -0
- package/samples/schemas/spec-reviews.schema.json +4 -0
- package/samples/schemas/story.schema.json +8 -0
- package/samples/schemas/tasks.schema.json +4 -0
- package/samples/schemas/verification.schema.json +4 -0
- package/samples/schemas/work-orders.schema.json +6 -2
- package/schemas/config.schema.json +101 -3
- package/schemas/migrations.schema.json +25 -0
- package/schemas/pending-blocked.schema.json +190 -0
- package/skill-narrative.md +14 -10
- package/skills/pi-context/SKILL.md +127 -49
- package/skills/pi-context/references/bundled-resources.md +7 -2
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "pi-context://schemas/pending-blocked",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"title": "pi-context substrate-persisted pending-blocked resync records",
|
|
6
|
+
"description": "Records of catalog-ahead schema resyncs that `update` refused (blocked) on the last live run. Each entry pins the target catalog schema (by content_hash into the object store) plus the migration chain reaching it, so `resolve-blocked` can re-validate the corrected block against the SAME pinned target the run blocked on, then — on pass — register the chain, write the target schema, advance the merge base, and clear the entry. Replaced wholesale each live `update` run (an empty set removes the file); never an append-only log.",
|
|
7
|
+
"type": "object",
|
|
8
|
+
"required": ["schema_version", "entries"],
|
|
9
|
+
"additionalProperties": false,
|
|
10
|
+
"properties": {
|
|
11
|
+
"schema_version": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"description": "Schema version of this pending-blocked.json file itself. Decoupled from any block schema version it references."
|
|
14
|
+
},
|
|
15
|
+
"entries": {
|
|
16
|
+
"type": "array",
|
|
17
|
+
"description": "One PendingBlockedEntry per schema the last live update run refused (blocked). The set is replaced wholesale each live run; an empty set means the file is removed.",
|
|
18
|
+
"items": { "$ref": "#/definitions/PendingBlockedEntry" }
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"definitions": {
|
|
22
|
+
"PendingBlockedEntry": {
|
|
23
|
+
"type": "object",
|
|
24
|
+
"required": ["name", "reason", "target_hash", "chain", "blocked_at"],
|
|
25
|
+
"additionalProperties": false,
|
|
26
|
+
"properties": {
|
|
27
|
+
"name": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "Canonical schema id (block_kind canonical_id) the resync was refused for."
|
|
30
|
+
},
|
|
31
|
+
"from": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"description": "Installed schema version the resync would walk forward FROM (the block's declared version pre-resync). Omitted when unreadable."
|
|
34
|
+
},
|
|
35
|
+
"to": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"description": "Target catalog schema version the resync would advance TO. Omitted when unreadable."
|
|
38
|
+
},
|
|
39
|
+
"reason": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"enum": ["no-migration-chain", "validation-failed"],
|
|
42
|
+
"description": "Why the live resync refused: no-migration-chain = no shipped chain reaches the catalog version; validation-failed = the forward-migrated items fail the catalog schema."
|
|
43
|
+
},
|
|
44
|
+
"target_hash": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"pattern": "^[0-9a-f]{64}$",
|
|
47
|
+
"description": "content_hash of the pinned target catalog schema body, stored in the object store. resolve-blocked re-validates the corrected block against THIS body."
|
|
48
|
+
},
|
|
49
|
+
"chain": {
|
|
50
|
+
"type": "array",
|
|
51
|
+
"description": "Ordered MigrationDecl list reaching the target version (one per hop). Empty when reason='no-migration-chain' (no chain reaches the target).",
|
|
52
|
+
"items": { "$ref": "#/definitions/MigrationDecl" }
|
|
53
|
+
},
|
|
54
|
+
"failures": {
|
|
55
|
+
"type": "array",
|
|
56
|
+
"description": "For reason='validation-failed', the per-item constraint failures the live resync produced. Omitted for no-migration-chain.",
|
|
57
|
+
"items": { "$ref": "#/definitions/BlockValidationFailure" }
|
|
58
|
+
},
|
|
59
|
+
"premarker_hash": {
|
|
60
|
+
"type": "string",
|
|
61
|
+
"pattern": "^[0-9a-f]{64}$",
|
|
62
|
+
"description": "content_hash of the wrapped pre-marker block-file bytes, stored in the object store, written when a live update inscribed git-style failure markers into the validation-blocked block file. Present only on marker-bearing entries; lets the caller restore the block file to its pre-marker bytes. Omitted when no markers were written."
|
|
63
|
+
},
|
|
64
|
+
"blocked_at": {
|
|
65
|
+
"type": "string",
|
|
66
|
+
"format": "date-time",
|
|
67
|
+
"description": "ISO-8601 timestamp the live update run recorded the block refusal."
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"BlockValidationFailure": {
|
|
72
|
+
"type": "object",
|
|
73
|
+
"required": ["instancePath", "keyword", "message"],
|
|
74
|
+
"additionalProperties": false,
|
|
75
|
+
"properties": {
|
|
76
|
+
"itemId": {
|
|
77
|
+
"type": "string",
|
|
78
|
+
"description": "Failing block item's `id` when the instancePath resolves to one; omitted for envelope-level errors."
|
|
79
|
+
},
|
|
80
|
+
"instancePath": {
|
|
81
|
+
"type": "string",
|
|
82
|
+
"description": "AJV raw JSON pointer of the failing constraint."
|
|
83
|
+
},
|
|
84
|
+
"keyword": {
|
|
85
|
+
"type": "string",
|
|
86
|
+
"description": "AJV constraint keyword that failed (or 'error' for a synthetic non-AJV throw)."
|
|
87
|
+
},
|
|
88
|
+
"message": {
|
|
89
|
+
"type": "string",
|
|
90
|
+
"description": "AJV constraint message text."
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"MigrationDecl": {
|
|
95
|
+
"type": "object",
|
|
96
|
+
"required": ["schemaName", "fromVersion", "toVersion", "kind", "created_by", "created_at"],
|
|
97
|
+
"additionalProperties": false,
|
|
98
|
+
"properties": {
|
|
99
|
+
"schemaName": {
|
|
100
|
+
"type": "string",
|
|
101
|
+
"description": "Canonical schema id (matches the schema's $id minus the URN prefix)."
|
|
102
|
+
},
|
|
103
|
+
"fromVersion": {
|
|
104
|
+
"type": "string",
|
|
105
|
+
"description": "Semver string of the source schema version this migration walks forward from."
|
|
106
|
+
},
|
|
107
|
+
"toVersion": {
|
|
108
|
+
"type": "string",
|
|
109
|
+
"description": "Semver string of the destination schema version this migration produces."
|
|
110
|
+
},
|
|
111
|
+
"kind": {
|
|
112
|
+
"type": "string",
|
|
113
|
+
"enum": ["identity", "declarative-transform"],
|
|
114
|
+
"description": "identity = data shape unchanged across the bump (no transform). declarative-transform = transform.operations[] applies to the data."
|
|
115
|
+
},
|
|
116
|
+
"transform": {
|
|
117
|
+
"$ref": "#/definitions/TransformSpec",
|
|
118
|
+
"description": "Required when kind='declarative-transform'; forbidden when kind='identity'."
|
|
119
|
+
},
|
|
120
|
+
"created_by": {
|
|
121
|
+
"type": "string",
|
|
122
|
+
"description": "Operator identity (writer.user) recorded at declaration write."
|
|
123
|
+
},
|
|
124
|
+
"created_at": {
|
|
125
|
+
"type": "string",
|
|
126
|
+
"format": "date-time",
|
|
127
|
+
"description": "ISO-8601 timestamp recorded at declaration write."
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
"TransformSpec": {
|
|
132
|
+
"type": "object",
|
|
133
|
+
"required": ["operations"],
|
|
134
|
+
"additionalProperties": false,
|
|
135
|
+
"properties": {
|
|
136
|
+
"operations": {
|
|
137
|
+
"type": "array",
|
|
138
|
+
"description": "Ordered TransformOp sequence applied in order to the block-item data shape.",
|
|
139
|
+
"items": { "$ref": "#/definitions/TransformOp" }
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
"TransformOp": {
|
|
144
|
+
"oneOf": [
|
|
145
|
+
{
|
|
146
|
+
"type": "object",
|
|
147
|
+
"required": ["op", "from", "to"],
|
|
148
|
+
"additionalProperties": false,
|
|
149
|
+
"properties": {
|
|
150
|
+
"op": { "type": "string", "enum": ["rename"] },
|
|
151
|
+
"from": { "type": "string" },
|
|
152
|
+
"to": { "type": "string" }
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"type": "object",
|
|
157
|
+
"required": ["op", "path"],
|
|
158
|
+
"additionalProperties": false,
|
|
159
|
+
"properties": {
|
|
160
|
+
"op": { "type": "string", "enum": ["set"] },
|
|
161
|
+
"path": { "type": "string" },
|
|
162
|
+
"value": {}
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
"type": "object",
|
|
167
|
+
"required": ["op", "path"],
|
|
168
|
+
"additionalProperties": false,
|
|
169
|
+
"properties": {
|
|
170
|
+
"op": { "type": "string", "enum": ["delete"] },
|
|
171
|
+
"path": { "type": "string" }
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"type": "object",
|
|
176
|
+
"required": ["op", "path", "type"],
|
|
177
|
+
"additionalProperties": false,
|
|
178
|
+
"properties": {
|
|
179
|
+
"op": { "type": "string", "enum": ["coerce"] },
|
|
180
|
+
"path": { "type": "string" },
|
|
181
|
+
"type": {
|
|
182
|
+
"type": "string",
|
|
183
|
+
"enum": ["string", "number", "boolean", "array", "object"]
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
]
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
package/skill-narrative.md
CHANGED
|
@@ -13,7 +13,7 @@ pi-context manages structured project state in the substrate directory — a dir
|
|
|
13
13
|
</objective>
|
|
14
14
|
|
|
15
15
|
<block_files>
|
|
16
|
-
Blocks are JSON files under the substrate root (one per block kind, each an array of items). Each block has a corresponding schema in `<root>/schemas/`. When you write to a block via the tools, the data is validated against its schema before persisting. Writes are atomic (tmp file + rename) and serialised per block via `withBlockLock`. The substrate root is the dir chosen at init (recorded in the `.pi-context.json` bootstrap pointer) and written to `config.json`'s `root` field by `/context accept-all`; the framework ships no default substrate-dir name. block-api routes through `resolveContextDir(cwd)` — which resolves `config.root` when set and otherwise falls back to the pointer — so a relocated root reaches every read/write site.
|
|
16
|
+
Blocks are JSON files under the substrate root (one per block kind, each an array of items). Each block has a corresponding schema in `<root>/schemas/`. When you write to a block via the tools, the data is validated against its schema before persisting. Writes are atomic (tmp file + rename) and serialised per block via `withBlockLock`. The substrate root is the dir chosen at init (recorded in the `.pi-context.json` bootstrap pointer) and written to `config.json`'s `root` field at init (skeleton config) and again by `/context accept-all`; the framework ships no default substrate-dir name. block-api routes through `resolveContextDir(cwd)` — which resolves `config.root` when set and otherwise falls back to the pointer — so a relocated root reaches every read/write site.
|
|
17
17
|
|
|
18
18
|
Each item in an identity-bearing block carries a three-layer identity, not a refname alone: `id` (the mutable human label, e.g. a kind-prefixed refname), `oid` (a content-independent 32-hex identity minted once at the item's birth and immutable thereafter, salted by the substrate's `substrate_id`), `content_hash` (a SHA-256 over the item's content projection — the item minus its metadata fields — so identical content deduplicates), and `content_parent` (the prior version's `content_hash`, forming a per-item version chain that advances only when content actually changed). A write carrying a different incoming `oid` is rejected. Identity stamping is a no-op unless the block's array subschema declares all three identity fields, scoping content-addressing to canonical schemas and leaving bespoke/test schemas untouched.
|
|
19
19
|
</block_files>
|
|
@@ -27,21 +27,23 @@ Every block write validates against `<root>/schemas/<blockname>.schema.json`. If
|
|
|
27
27
|
</schema_validation>
|
|
28
28
|
|
|
29
29
|
<context_init>
|
|
30
|
-
`/context init <substrate-dir>` creates the substrate skeleton: the `.pi-context.json` bootstrap pointer (declaring the chosen substrate-dir name)
|
|
30
|
+
`/context init <substrate-dir>` creates the substrate skeleton: the `.pi-context.json` bootstrap pointer (declaring the chosen substrate-dir name), the substrate root and its `schemas/` directory, a minimal SKELETON `config.json` (`schema_version`, empty `block_kinds`, `root`, and a minted + project-registered `substrate_id` — no vocabulary), and the catalog's `config` migration declarations seeded into `migrations.json`. No catalog vocabulary, schemas, or starter blocks are imposed (ship-no-defaults). Idempotent: re-running preserves existing dirs and never clobbers an existing `config.json`. Populate the substrate next with `/context accept-all` (adopt the canonical conception) followed by `/context install`.
|
|
31
31
|
</context_init>
|
|
32
32
|
|
|
33
33
|
<context_accept_all>
|
|
34
|
-
`/context accept-all` adopts the package's canonical packaged conception (`samples/conception.json`) as the substrate's `config.json` — the full vocabulary (`block_kinds`, `relation_types`, `lenses`, `invariants`) plus the `installed_schemas` / `installed_blocks` manifest — with `root` set to the actual substrate dir. It writes `config.json` only (run `/context install` after to materialize the schemas + starter blocks) and is idempotent: it never overwrites
|
|
34
|
+
`/context accept-all` adopts the package's canonical packaged conception (`samples/conception.json`) as the substrate's `config.json` — the full vocabulary (`block_kinds`, `relation_types`, `lenses`, `invariants`) plus the `installed_schemas` / `installed_blocks` manifest — with `root` set to the actual substrate dir. It writes `config.json` only (run `/context install` after to materialize the schemas + starter blocks) and is idempotent: it never overwrites a populated `config.json` (offer, don't impose); the vocabulary-empty skeleton `config.json` written by init IS overwritten by the catalog, with the skeleton's on-disk `substrate_id` preserved. This is the accept-all path; per-entry step-through curation is a separate surface.
|
|
35
35
|
</context_accept_all>
|
|
36
36
|
|
|
37
37
|
<context_install>
|
|
38
|
-
`/context install` reconciles the substrate against the `installed_schemas` and `installed_blocks` lists declared in `config.json`. For each declared name it copies the matching asset from the package-shipped samples catalog (`samples/schemas/` for schemas, `samples/blocks/` for starter blocks) into the substrate. Default behavior is skip-if-exists (preserves user edits)
|
|
38
|
+
`/context install` (reflected CLI op: `pi-context context-install [--update]`, `authGated`) reconciles the substrate against the `installed_schemas` and `installed_blocks` lists declared in `config.json`. The slash command and the CLI op run the same install engine — the op's `--update` maps to the same migration-aware overwrite the slash command's `--update` does. For each declared name it copies the matching asset from the package-shipped samples catalog (`samples/schemas/` for schemas, `samples/blocks/` for starter blocks) into the substrate. Default behavior is skip-if-exists (preserves user edits). Pass `--update` to re-sync installed SCHEMAS through the migration registry: a same-version body change (or a non-versioned schema) is a verbatim re-sync (reported `resynced`); a schema `version` bump forward-migrates the populated block's items through the shipped catalog migration chain and re-validates them against the new schema (reported `migrated`); a bump with no safe migration — no shipped chain reaching the catalog version, or items that would fail the new schema — is refused, leaving BOTH the installed schema file AND the block file byte-unchanged (reported `blocked`). Preview drift first with `/context check-status`. Populated block data is never overwritten — a block holding items is preserved (reported as `preserved`) regardless of `--update`, while empty or absent blocks receive the catalog starter. Sources missing from the catalog are reported as `notFound`. Empty install lists are not an error — the result is a clean no-op message instructing the user to edit `config.json`. Install also records an install baseline in `config.installed_from`: the catalog source (`catalog` = pi-context `name@version`, `catalog_version` = conception `schema_version`), a baseline timestamp, and a per-schema fingerprint (`assets[canonical_id]` = content hash + the schema's own version) of the installed SCHEMAS — the basis for installed-vs-catalog drift detection. The baseline covers schemas only (blocks are user data). A re-install on an unchanged substrate is idempotent: the existing baseline is preserved verbatim (timestamp included) so `config.json` stays byte-identical. `/context check-status` is a separate read-only command that previews drift between the installed schemas and the catalog: it compares each installed schema against its recorded baseline, the catalog's current schema, and the currently-installed file, and reports `in-sync` / `catalog-ahead` (the package shipped a newer schema) / `locally-modified` (the installed file was edited) / `both-diverged` / `no-baseline` / `missing-catalog` / `missing-installed`. For each schema behind the catalog (`catalog-ahead` / `both-diverged`) it additionally surfaces which schema is behind and by what version gap — `behind` plus a `version_delta` carrying the baseline → catalog version pair (a declared version bump) or a content-only basis when the catalog body moved with the version string unchanged. It writes nothing. The reflected CLI op is `pi-context context-check-status --json`.
|
|
39
39
|
|
|
40
|
-
The installable catalog IS the packaged conception (`samples/conception.json`): its `block_kinds` enumerate the available kinds, each carrying its schema (`samples/schemas/`) and starter block (`samples/blocks/`). The generated installable-catalog table below lists the authoritative names — declare any subset in `installed_*` and run `/context install`, or take the whole conception via `/context accept-all`.
|
|
40
|
+
The installable catalog IS the packaged conception (`samples/conception.json`): its `block_kinds` enumerate the available kinds, each carrying its schema (`samples/schemas/`) and starter block (`samples/blocks/`). The generated installable-catalog table below lists the authoritative names — declare any subset in `installed_*` and run `/context install`, or take the whole conception via `/context accept-all`. To inspect a catalog schema body itself, `pi-context read-catalog-schema --kind <canonical_id>` fetches and prints the verbatim bundled `*.schema.json` (the raw JSON Schema — `properties` / `definitions` / `$id`, distinct from the `read-samples-catalog` summary projection), so after `/context check-status` flags a schema behind the catalog you can diff that body locally against the installed `<substrate>/schemas/<name>.schema.json` without hunting through `node_modules`. It is read-only and package-intrinsic (reads the bundled catalog, mutates nothing).
|
|
41
41
|
</context_install>
|
|
42
42
|
|
|
43
43
|
<substrate_config>
|
|
44
|
-
`<substrate-dir>/config.json` is the substrate bootstrap. Its `root` field declares where every other block, schema, agent, and template lives — consumers resolve that dir via the `.pi-context.json` pointer plus `config.root`, never by assuming a fixed directory name. Its `substrate_id` field is the per-substrate root identity (pattern `sub-` followed by 16 hex), minted once and immutable on disk; it salts oid minting and identifies the substrate in the project-root registry. `naming` aliases canonical block ids to display names (used by `/context view` rendering). `hierarchy` declares legal closure-table edges (parent block → child block via relation_type). `lenses` declares named projections over a target block. `installed_schemas` / `installed_blocks` are the install manifest consumed by `/context install`.
|
|
44
|
+
`<substrate-dir>/config.json` is the substrate bootstrap. Its `root` field declares where every other block, schema, agent, and template lives — consumers resolve that dir via the `.pi-context.json` pointer plus `config.root`, never by assuming a fixed directory name. Its `substrate_id` field is the per-substrate root identity (pattern `sub-` followed by 16 hex), minted once and immutable on disk; it salts oid minting and identifies the substrate in the project-root registry. `naming` aliases canonical block ids to display names (used by `/context view` rendering). `hierarchy` declares legal closure-table edges (parent block → child block via relation_type). `lenses` declares named projections over a target block. `installed_schemas` / `installed_blocks` are the install manifest consumed by `/context install`. `installed_from` is the optional install baseline `/context install` records — the catalog source plus a per-schema content fingerprint of the installed schemas — for installed-vs-catalog drift detection.
|
|
45
|
+
|
|
46
|
+
A fresh substrate adopted via `/context accept-all` carries advisory (severity-`warning`) convention-articulation invariants: every decision, feature, and task should carry an `item_governed_by_convention` edge to a convention it follows, or an `item_acknowledges_missing_convention` edge to a missing-convention gap. `context-validate` reports an artifact that articulates neither as a warning (it does not error), so the advice surfaces without blocking writes; satisfy it by adding one of those two edges (`append-relation`) when filing or amending the artifact.
|
|
45
47
|
|
|
46
48
|
`config.json` and `relations.json` are exempt from `config.root` redirection — they always live at the substrate-dir root (the dir chosen at bootstrap, resolved via the `.pi-context.json` pointer, suggested `.context`) because they are the substrate that defines `root`. The substrate-dir root is whatever was chosen at bootstrap, not necessarily `.project`. All other state lives under `<config.root>/...` per `resolveContextDir(cwd)`. The package ships their schemas in `schemas/` (config.schema.json, relations.schema.json) and resolves them via three-tier search: project override > user override > package-shipped.
|
|
47
49
|
|
|
@@ -57,7 +59,7 @@ Source-of-truth-drift invariant: `validateContext` requires the active `config.s
|
|
|
57
59
|
</cross_substrate>
|
|
58
60
|
|
|
59
61
|
<schema_versioning>
|
|
60
|
-
Schemas are draft-07 JSON-Schema, one per block kind, under `<substrate-dir>/schemas/`. Package-shipped substrate-singleton schemas carry a `pi-context://schemas/<name>` `$id` plus a `version`. `<substrate-dir>/migrations.json` is the per-substrate schema-version migration registry. A schema `version` bump REQUIRES a companion migration declared via the `write-schema-migration` tool; without one, reading or writing an item that declares an older `schema_version` throws a version mismatch. Migration kinds are `identity` (shape-compatible, no transform) or `declarative-transform` (a TransformSpec of rename/set/delete/coerce on dotted paths). The loaded registry resolves the migration edge at the next read/write, so items walk forward without a process restart. A `block:<name>` reference resolves to `<contextDir>/schemas/<name>.schema.json`.
|
|
62
|
+
Schemas are draft-07 JSON-Schema, one per block kind, under `<substrate-dir>/schemas/`. Package-shipped substrate-singleton schemas carry a `pi-context://schemas/<name>` `$id` plus a `version`. `<substrate-dir>/migrations.json` is the per-substrate schema-version migration registry. A schema `version` bump REQUIRES a companion migration declared via the `write-schema-migration` tool; without one, reading or writing an item that declares an older `schema_version` throws a version mismatch. Migration kinds are `identity` (shape-compatible, no transform) or `declarative-transform` (a TransformSpec of rename/set/delete/coerce/map_each on dotted paths; `map_each` addresses an array — table mode maps each string element through a lookup, with unmatched elements becoming `{relation_type, item_endpoint}` under a parent/child fallback, and set-on-each mode sets a field on every object element). The loaded registry resolves the migration edge at the next read/write, so items walk forward without a process restart. Config loading is migration-aware: a `config.json` whose `schema_version` lags the bundled config schema is walked forward through the `config` migration chain in memory at load (the on-disk file is never rewritten); every substrate-lifecycle ceremony — init / accept-all / install / update / check-status / switch (existing-target and switch-back forms; the target substrate is seeded right after the pointer flip) / resolve-conflict / resolve-blocked — seeds the catalog's `config` identity declaration into `migrations.json` (idempotent) before its first config read, and a version mismatch with no resolvable chain throws. The ceremonies that reach identity-stamping writes (update, install, resolve-blocked) also establish substrate identity at entry: a config carrying no `substrate_id` gets one minted, persisted, and registered before the first stamping write — a pre-identity substrate heals on the sanctioned ceremony instead of refusing — reported in the ceremony result under `substrateIdEstablished` (live runs only; an established identity is never re-minted). On the write side, versioned-document envelopes converge: every block schema declares an optional top-level `schema_version`, and the write path stamps it (config.json's and migrations.json's included) to the owning schema's current `version` on every sanctioned write — an incoming envelope claiming an older version is first walked forward through the registered chain (or refused with the file left byte-unchanged when no chain reaches the current version), then persisted at the current version; reads of a stamped block validate the whole envelope migration-aware. A substrate whose installed schemas predate the `schema_version` property keeps writing unchanged until `/context update` lands it. A `block:<name>` reference resolves to `<contextDir>/schemas/<name>.schema.json`.
|
|
61
63
|
</schema_versioning>
|
|
62
64
|
|
|
63
65
|
<lens_views>
|
|
@@ -65,6 +67,8 @@ Lenses are named projections over a target block. A lens declares `id`, `target`
|
|
|
65
67
|
|
|
66
68
|
Edges live in `<substrate-dir>/relations.json` as a closure table — each row is `{ parent, child, relation_type, ordinal? }`. `relation_type` is a lens id, a hierarchy edge type, or a registered `relation_types[].canonical_id`; `ordinal` orders siblings within `(parent, relation_type)`. Endpoints (both `parent` and `child`) are dual-form: a legacy string (a canonical id, a lens bin name, or an `<alias>:<refname>` cross-substrate sentinel; disambiguation lives in `validateRelations`), OR a structured item endpoint `{ kind: "item", oid, refname?, substrate_id?, content_hash? }` where a present `substrate_id` marks a foreign endpoint and `content_hash` is carried for drift detection, OR a structured lens-bin endpoint `{ kind: "lens_bin", bin }` — a virtual parent that never resolves to an item.
|
|
67
69
|
|
|
70
|
+
Edge orientation is declared once, read everywhere. Storage is uniform (`edge.parent` = source endpoint, `edge.child` = target endpoint); which endpoint holds a relation's PRIMARY semantic role (prerequisite/predecessor/gate for `ordering`, container for `membership`, source for `data_flow`) is `config.relation_types[].role_direction` — `as_parent` (primary at `edge.parent`) or `as_child` (primary at `edge.child`), optional and set only for relations with a per-role consumer. The `primaryEndpoint(edge, role_direction)` / `counterEndpoint(edge, role_direction)` helpers read the endpoint under that declaration, and the blocked/ready deriver (`state_derivation.blocked_by` gate-vs-dependency split), the milestone rollup, the derived roadmap (precedes + membership), and `promote-item`'s lineage edge all route through it rather than hardcoding parent/child. Authoring: `append-relation` / `append-relations` take EITHER raw `--parent`/`--child` OR role-typed `--primary`/`--counter` (mapped to parent/child via `role_direction`; mutually exclusive) — a bare `--parent`/`--child` append of a relation that is BOTH role-bearing and orientation-ambiguous (its source/target kinds overlap, incl. `"*"`) is rejected in favor of `--primary`/`--counter`, while a role-less or disjoint-kind relation appends bare unchanged. Reading: `context-walk-descendants` / `walk-ancestors` on a disjoint-kind relation queried from the wrong endpoint THROWS naming the correct op instead of returning an ambiguous `[]`. `replace-relation` writes raw endpoints verbatim (bypassing the orientation gate) — the re-orient affordance; run `context-validate` after.
|
|
71
|
+
|
|
68
72
|
The single-form rule: ALL inter-item relationships are closure-table edges. Embedded nested id-bearing arrays and FK-as-field are forbidden — a nested id-bearing array in a schema is flagged `nested_id_bearing_array` by `validateContext` with the remediation "promote to a top-level entity + membership edge". Containment is a membership edge carrying `ordinal`; the nested id-bearing array → top-level entity block + ordinal-bearing membership edges promotion is performed by the canonicalizer (the context-dir-migration `canonicalizeSubstrate` machinery, run as a repo-side migration script under `scripts/migration/` — not a packaged pi-context tool). (Distinct from the `promote-item` tool, which is a cross-substrate derivation: it promotes a substrate item INTO another registered substrate as a new content-addressed item, recording an `item_derived_from_item` lineage edge in the destination.)
|
|
69
73
|
|
|
70
74
|
The lens-view algorithm: `edgesForLens(lens, items, authoredEdges)` returns synthetic edges (when `derived_from_field` is set) or filtered authored edges (otherwise). `groupByLens(items, lens, lensEdges)` produces a `Map<binName, ItemRecord[]>`. `walkDescendants(parentId, relationType, edges)` traverses the closure table from any parent.
|
|
@@ -85,7 +89,7 @@ The lens-view algorithm: `edgesForLens(lens, items, authoredEdges)` returns synt
|
|
|
85
89
|
|
|
86
90
|
`validateContext(cwd)` (the `context-validate` tool) layers the registry/identity invariants over cross-block referential integrity: `substrate_id_unregistered` and `substrate_id_registry_mismatch` (the source-of-truth-drift guard on the active substrate), `edge_endpoint_dangling` and `edge_endpoint_unregistered` (a structured endpoint naming a registered-but-absent or unregistered substrate), and `nested_id_bearing_array` (a schema embedding an id-bearing array instead of using a membership edge). Config-declared `invariants[]` and registered lens-validators are checked in the same pass.
|
|
87
91
|
|
|
88
|
-
|
|
92
|
+
Three derived substrate tools complement validation: `context-edges-for-lens` returns the materialized `Edge[]` for a named lens (synthetic from `derived_from_field` or filtered authored edges); `context-lens-view` projects a config-declared lens as a binned item-view — a bin→count summary, or one bin's items paged by `offset`/`limit`; `context-walk-descendants` returns the transitive descendant id list from a parent under a given relation_type.
|
|
89
93
|
</substrate_validation>
|
|
90
94
|
|
|
91
95
|
<block_item_reads>
|
|
@@ -138,8 +142,8 @@ On `session_start`, checks npm registry for newer versions of `@davidorex/pi-pro
|
|
|
138
142
|
</update_check>
|
|
139
143
|
|
|
140
144
|
<success_criteria>
|
|
141
|
-
- `<substrate-dir>/`, `<substrate-dir>/schemas/`,
|
|
142
|
-
- `installed_schemas` / `installed_blocks` declared in `config.json` are reified by `/context install`; `--update`
|
|
145
|
+
- `<substrate-dir>/`, `<substrate-dir>/schemas/`, the `.pi-context.json` bootstrap pointer, a skeleton `config.json`, and a seeded `migrations.json` exist after `/context init <substrate-dir>` (init imposes no catalog: no vocabulary, no schemas, no blocks until accept-all + install). Phases are not a directory — they live as an in-block array under `phase.json` (plural `phases` key); there is no `phases/` dir.
|
|
146
|
+
- `installed_schemas` / `installed_blocks` declared in `config.json` are reified by `/context install`; `--update` re-syncs installed schemas through the migration registry (forward-migrate block items on a version bump, or refuse and leave unchanged when items can't be safely migrated), but populated block data is never overwritten (preserved) — only empty or absent blocks receive the catalog starter
|
|
143
147
|
- Block writes validate against schemas — invalid data rejected with specific error
|
|
144
148
|
- `/context status` returns current derived state without errors
|
|
145
149
|
- `/context validate` returns no errors for well-formed cross-block references
|