@davidorex/pi-context 0.29.0 → 0.31.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 (62) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/README.md +48 -21
  3. package/dist/block-api.d.ts +25 -3
  4. package/dist/block-api.d.ts.map +1 -1
  5. package/dist/block-api.js +17 -6
  6. package/dist/block-api.js.map +1 -1
  7. package/dist/content-hash.d.ts +13 -0
  8. package/dist/content-hash.d.ts.map +1 -1
  9. package/dist/content-hash.js +16 -0
  10. package/dist/content-hash.js.map +1 -1
  11. package/dist/context-dir.d.ts +12 -0
  12. package/dist/context-dir.d.ts.map +1 -1
  13. package/dist/context-dir.js +14 -0
  14. package/dist/context-dir.js.map +1 -1
  15. package/dist/context-sdk.d.ts +99 -4
  16. package/dist/context-sdk.d.ts.map +1 -1
  17. package/dist/context-sdk.js +183 -13
  18. package/dist/context-sdk.js.map +1 -1
  19. package/dist/context.d.ts +103 -0
  20. package/dist/context.d.ts.map +1 -1
  21. package/dist/context.js +85 -0
  22. package/dist/context.js.map +1 -1
  23. package/dist/index.d.ts +534 -0
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +1879 -20
  26. package/dist/index.js.map +1 -1
  27. package/dist/migration-registry-loader.d.ts +16 -0
  28. package/dist/migration-registry-loader.d.ts.map +1 -1
  29. package/dist/migration-registry-loader.js +33 -0
  30. package/dist/migration-registry-loader.js.map +1 -1
  31. package/dist/ops-registry.d.ts +196 -4
  32. package/dist/ops-registry.d.ts.map +1 -1
  33. package/dist/ops-registry.js +795 -110
  34. package/dist/ops-registry.js.map +1 -1
  35. package/dist/pending-blocked-store.d.ts +83 -0
  36. package/dist/pending-blocked-store.d.ts.map +1 -0
  37. package/dist/pending-blocked-store.js +93 -0
  38. package/dist/pending-blocked-store.js.map +1 -0
  39. package/dist/read-element.d.ts +48 -0
  40. package/dist/read-element.d.ts.map +1 -1
  41. package/dist/read-element.js +88 -30
  42. package/dist/read-element.js.map +1 -1
  43. package/dist/schema-merge.d.ts +26 -0
  44. package/dist/schema-merge.d.ts.map +1 -0
  45. package/dist/schema-merge.js +176 -0
  46. package/dist/schema-merge.js.map +1 -0
  47. package/dist/write-schema-migration-tool.d.ts +2 -1
  48. package/dist/write-schema-migration-tool.d.ts.map +1 -1
  49. package/dist/write-schema-migration-tool.js +13 -10
  50. package/dist/write-schema-migration-tool.js.map +1 -1
  51. package/package.json +2 -1
  52. package/samples/conception.json +50 -0
  53. package/samples/schemas/framework-gaps.schema.json +1 -1
  54. package/samples/schemas/issues.schema.json +2 -2
  55. package/samples/schemas/layer-plans.schema.json +2 -2
  56. package/samples/schemas/research.schema.json +1 -1
  57. package/samples/schemas/work-orders.schema.json +2 -2
  58. package/schemas/config.schema.json +25 -1
  59. package/schemas/pending-blocked.schema.json +190 -0
  60. package/skill-narrative.md +7 -5
  61. package/skills/pi-context/SKILL.md +146 -8
  62. package/skills/pi-context/references/bundled-resources.md +2 -1
@@ -3,7 +3,7 @@
3
3
  "$id": "pi-context://schemas/layer-plans",
4
4
  "version": "1.0.1",
5
5
  "title": "Layer Restructure Plans",
6
- "description": "Plans for restructuring the .project/ directory from flat-block storage into a layered artifact-ownership model. Each plan names its conceptual source (e.g. Muni five-layer), describes each layer's purpose and its current-vs-target block mapping, and sequences the enactment into phases with exit criteria. Layer plans are themselves L2 specification-layer artifacts — they are spec-level decisions about how the framework organizes project state.",
6
+ "description": "Plans for restructuring substrate storage into a layered artifact-ownership model. Each plan names its conceptual source (e.g. a layered architecture model), describes each layer's purpose and its current-vs-target block mapping, and sequences the enactment into phases with exit criteria. Layer plans are themselves L2 specification-layer artifacts — they are spec-level decisions about how the framework organizes project state.",
7
7
  "type": "object",
8
8
  "required": [
9
9
  "plans"
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "model": {
47
47
  "type": "string",
48
- "description": "Conceptual source — e.g. 'Muni five-layer'"
48
+ "description": "Conceptual source — e.g. 'a layered architecture model'"
49
49
  },
50
50
  "description": {
51
51
  "type": "string",
@@ -55,7 +55,7 @@
55
55
  "L4",
56
56
  "L5"
57
57
  ],
58
- "description": "Which Muni layer the research informs. L1 identity/domain; L2 specification; L3 work; L4 execution; L5 memory."
58
+ "description": "Which architecture layer the research informs. L1 identity/domain; L2 specification; L3 work; L4 execution; L5 memory."
59
59
  },
60
60
  "type": {
61
61
  "type": "string",
@@ -3,7 +3,7 @@
3
3
  "$id": "pi-context://schemas/work-orders",
4
4
  "version": "1.0.1",
5
5
  "title": "Work Orders",
6
- "description": "Orchestrator-authored work-order spec for the privileged JIT-agent that drives the in-pi bounded code-change loop. A work-order names the target agent, the typed input/output contracts, the substrate-block context handoff, the bounded scope within which the agent may operate, and the deterministic real-check criteria gating acceptance (deterministic verification; never LLM self-report — verdict comes from real checks the agent cannot fake). Substrate-block handoff to the consuming agent uses the ContextBlockRef shape: each context_blocks entry is either a string for whole-block injection, or an object { name, item?, depth?, focus? } for item-specific injection. To handoff a single work-order item to the consuming agent, the agent's .agent.yaml contextBlocks declares { name: \"work-orders\", item: \"WO-NNN\" }; the compiler injects the resolved item under template key _work_orders_item (with companion _work_orders_raw / _work_orders_depth / _work_orders_focus). Authorship: orchestrator is sole spec author; capability-widening edits are writer.kind=human.",
6
+ "description": "Orchestrator-authored work-order spec for the privileged JIT-agent that drives the bounded code-change loop. A work-order names the target agent, the typed input/output contracts, the substrate-block context handoff, the bounded scope within which the agent may operate, and the deterministic real-check criteria gating acceptance (deterministic verification; never LLM self-report — verdict comes from real checks the agent cannot fake). Substrate-block handoff to the consuming agent uses the ContextBlockRef shape: each context_blocks entry is either a string for whole-block injection, or an object { name, item?, depth?, focus? } for item-specific injection. To handoff a single work-order item to the consuming agent, the agent's .agent.yaml contextBlocks declares { name: \"work-orders\", item: \"WO-NNN\" }; the compiler injects the resolved item under template key _work_orders_item (with companion _work_orders_raw / _work_orders_depth / _work_orders_focus). Authorship: orchestrator is sole spec author; capability-widening edits are writer.kind=human.",
7
7
  "type": "object",
8
8
  "required": [
9
9
  "work_orders"
@@ -57,7 +57,7 @@
57
57
  },
58
58
  "context_blocks": {
59
59
  "type": "array",
60
- "description": "Substrate-block references the privileged agent's .agent.yaml will consume via contextBlocks. Each entry: string for whole-block injection (_<name>), OR object { name, item?, depth?, focus? } for item-specific injection (per the existing ContextBlockRef shape in packages/pi-jit-agents/src/types.ts).",
60
+ "description": "Substrate-block references the privileged agent's .agent.yaml will consume via contextBlocks. Each entry: string for whole-block injection (_<name>), OR object { name, item?, depth?, focus? } for item-specific injection (per the ContextBlockRef shape defined by your agent layer).",
61
61
  "items": {
62
62
  "oneOf": [
63
63
  {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
3
  "$id": "pi-context://schemas/config",
4
- "version": "1.5.0",
4
+ "version": "1.6.0",
5
5
  "title": "pi-context substrate config",
6
6
  "description": "Self-bootstrapping config block. Validates the config that configures the substrate. ConfigBlock is the canonical registry for block kinds, status buckets, relation types, display strings, layers, naming aliases, hierarchy declarations, and lens specs.",
7
7
  "type": "object",
@@ -194,6 +194,30 @@
194
194
  "description": "Blocks the project has opted into via /context install.",
195
195
  "items": { "type": "string" }
196
196
  },
197
+ "installed_from": {
198
+ "type": "object",
199
+ "description": "Install baseline of the installed schemas: catalog source + per-schema content fingerprint recorded at install time so installed-vs-catalog drift can be detected. Blocks are user data and are not baselined. Written by /context install; preserved verbatim (including `at`) on an idempotent re-install whose catalog + assets are unchanged.",
200
+ "required": ["catalog", "catalog_version", "at", "assets"],
201
+ "additionalProperties": false,
202
+ "properties": {
203
+ "catalog": { "type": "string", "description": "pi-context package \"name@version\" the install baseline was taken from." },
204
+ "catalog_version": { "type": "string", "description": "samples/conception.json schema_version at baseline time." },
205
+ "at": { "type": "string", "description": "ISO-8601 timestamp of when this baseline content was established." },
206
+ "assets": {
207
+ "type": "object",
208
+ "description": "Per installed schema (keyed by canonical_id): content fingerprint + the schema file's own declared version.",
209
+ "additionalProperties": {
210
+ "type": "object",
211
+ "required": ["content_hash", "version"],
212
+ "additionalProperties": false,
213
+ "properties": {
214
+ "content_hash": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
215
+ "version": { "type": "string" }
216
+ }
217
+ }
218
+ }
219
+ }
220
+ },
197
221
  "tool_operations": {
198
222
  "type": "array",
199
223
  "description": "Operation-granular tool grant vocabulary. Each entry names a Pi tool (or coarser operation) that can be granted to a privileged JIT-agent. Operation-granular, opt-in from empty default.",
@@ -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
+ }
@@ -35,13 +35,15 @@ Every block write validates against `<root>/schemas/<blockname>.schema.json`. If
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); pass `--update` to overwrite and report the asset as `updated`. 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`.
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
 
@@ -85,7 +87,7 @@ The lens-view algorithm: `edgesForLens(lens, items, authoredEdges)` returns synt
85
87
 
86
88
  `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
89
 
88
- Two 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-walk-descendants` returns the transitive descendant id list from a parent under a given relation_type.
90
+ 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
91
  </substrate_validation>
90
92
 
91
93
  <block_item_reads>
@@ -139,7 +141,7 @@ On `session_start`, checks npm registry for newer versions of `@davidorex/pi-pro
139
141
 
140
142
  <success_criteria>
141
143
  - `<substrate-dir>/`, `<substrate-dir>/schemas/`, and the `.pi-context.json` bootstrap pointer exist after `/context init <substrate-dir>` (init is skeleton-only: no `config.json`, 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.
142
- - `installed_schemas` / `installed_blocks` declared in `config.json` are reified by `/context install`; `--update` overwrites
144
+ - `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
145
  - Block writes validate against schemas — invalid data rejected with specific error
144
146
  - `/context status` returns current derived state without errors
145
147
  - `/context validate` returns no errors for well-formed cross-block references
@@ -46,6 +46,62 @@ Append a closure-table relation (edge: parent, child, relation_type, optional or
46
46
  | `child` | string | yes | Canonical id of the child endpoint |
47
47
  | `relation_type` | string | yes | Registered relation_type canonical_id / hierarchy edge type / lens id |
48
48
  | `ordinal` | integer | no | Optional sibling-ordering within (parent, relation_type) |
49
+ | `dryRun` | boolean | no | Preview without writing relations.json |
50
+ </tool>
51
+
52
+ <tool name="remove-relation">
53
+ Remove the single closure-table relation (edge) matching parent+child+relation_type from relations.json. Matches on the SAME (parent, child, relation_type) dedup identity append-relation uses, so it is the symmetric inverse of append-relation (ordinal is NOT part of identity). An absent edge is an idempotent no-op. Reference integrity is NOT checked here — run context-validate after if the removal changes resolvability.
54
+
55
+ *Remove a relation/edge between two items (the inverse of append-relation)*
56
+
57
+ | Parameter | Type | Required | Description |
58
+ |-----------|------|----------|-------------|
59
+ | `parent` | string | yes | Canonical id (or lens bin name) of the parent endpoint |
60
+ | `child` | string | yes | Canonical id of the child endpoint |
61
+ | `relation_type` | string | yes | Registered relation_type canonical_id / hierarchy edge type / lens id |
62
+ | `dryRun` | boolean | no | Preview without writing relations.json |
63
+ </tool>
64
+
65
+ <tool name="replace-relation">
66
+ Atomically replace one closure-table relation with another in a SINGLE write (no half-state: the old edge and the new edge never coexist on disk). The old edge is matched on the (parent, child, relation_type) dedup identity; the new edge is written with its optional ordinal. If the old edge is absent the call is effectively an append of the new edge. Reference integrity is NOT checked here — run context-validate after.
67
+
68
+ *Atomically swap one relation/edge for another in a single write*
69
+
70
+ | Parameter | Type | Required | Description |
71
+ |-----------|------|----------|-------------|
72
+ | `old_parent` | string | yes | Parent endpoint selector of the edge to remove |
73
+ | `old_child` | string | yes | Child endpoint selector of the edge to remove |
74
+ | `old_relation_type` | string | yes | relation_type of the edge to remove |
75
+ | `parent` | string | yes | Parent endpoint selector of the replacement edge |
76
+ | `child` | string | yes | Child endpoint selector of the replacement edge |
77
+ | `relation_type` | string | yes | relation_type of the replacement edge |
78
+ | `ordinal` | integer | no | Optional sibling-ordering within (parent, relation_type) for the new edge |
79
+ | `dryRun` | boolean | no | Preview without writing relations.json |
80
+ </tool>
81
+
82
+ <tool name="append-relations">
83
+ Append MANY closure-table relations to relations.json in a single write. Each edge is an object { parent, child, relation_type, ordinal? }. Per-(parent, child, relation_type) duplicates are skipped (against on-disk edges AND earlier edges in the same batch). Returns appended/skipped counts. Reference integrity is NOT checked here — run context-validate after. Creates relations.json if absent.
84
+
85
+ *Create many relations/edges between items in one write*
86
+
87
+ | Parameter | Type | Required | Description |
88
+ |-----------|------|----------|-------------|
89
+ | `edges` | unknown | yes | JSON array of { parent, child, relation_type, ordinal? } selector objects (parent/child are id/lens-bin selectors) |
90
+ | `dryRun` | boolean | no | Preview without writing relations.json |
91
+ </tool>
92
+
93
+ <tool name="upsert-block-item">
94
+ Append-or-replace an item in a project block array by id: if an item with the same idField value exists it is REPLACED (full-shape replacement, not shallow-merge — use update-block-item for merge); otherwise the item is appended. Schema validation is automatic. idField defaults to 'id'.
95
+
96
+ *Append-or-replace a full block item by id (replacement, not merge)*
97
+
98
+ | Parameter | Type | Required | Description |
99
+ |-----------|------|----------|-------------|
100
+ | `block` | string | yes | Block name (e.g., 'issues', 'decisions') |
101
+ | `arrayKey` | string | yes | Array key in the block (e.g., 'issues', 'decisions') |
102
+ | `item` | unknown | yes | Full item object to upsert — must conform to block schema |
103
+ | `idField` | string | no | Field used as the upsert key (default 'id') |
104
+ | `dryRun` | boolean | no | Preview the upsert without writing |
49
105
  </tool>
50
106
 
51
107
  <tool name="promote-item">
@@ -155,6 +211,13 @@ Get derived context state — source metrics, block summaries, planning lifecycl
155
211
 
156
212
  </tool>
157
213
 
214
+ <tool name="context-check-status">
215
+ Read-only installed-vs-catalog schema drift report — per installed schema the drift state, the baseline and catalog versions, and for behind schemas (catalog-ahead / both-diverged) the version delta (baseline -> catalog) or the content-only basis when the version string is unchanged. The front of the check-status -> update --dryRun -> update sequence; writes nothing.
216
+
217
+ *Report installed-vs-catalog schema drift + the version gap for behind schemas (read-only)*
218
+
219
+ </tool>
220
+
158
221
  <tool name="context-validate">
159
222
  Validate cross-block referential integrity — check that IDs referenced across blocks exist.
160
223
 
@@ -193,6 +256,16 @@ Enumerate installable sample block kinds (packaged view): per kind — title, de
193
256
  | `kind` | string | no | Filter to one block_kind canonical_id (e.g. 'tasks') |
194
257
  </tool>
195
258
 
259
+ <tool name="read-catalog-schema">
260
+ Fetch and print the verbatim catalog schema body (raw JSON Schema: properties/definitions/$id) for a named block kind — diffable locally against the installed `<substrate>/schemas/<name>.schema.json` without touching node_modules. Read-only; the projection-returning sibling is read-samples-catalog.
261
+
262
+ *Fetch and print the verbatim catalog schema body for a named block kind (raw JSON Schema, diffable locally)*
263
+
264
+ | Parameter | Type | Required | Description |
265
+ |-----------|------|----------|-------------|
266
+ | `kind` | string | yes | Catalog block_kind canonical_id (e.g. 'tasks') |
267
+ </tool>
268
+
196
269
  <tool name="context-current-state">
197
270
  Derive 'where are we + what's next' purely from the substrate — focus, in-flight tasks, ranked atomic-next actions (open framework-gaps then unblocked planned tasks), and blocked tasks. No writes; nothing hand-stored.
198
271
 
@@ -258,6 +331,27 @@ Create or replace a substrate block-kind JSON Schema. operation 'create' require
258
331
  | `dryRun` | boolean | no | Meta-validate without writing |
259
332
  </tool>
260
333
 
334
+ <tool name="resolve-conflict">
335
+ Commit the reconciliation of a schema merge conflict surfaced by update. Run this AFTER reconciling a both-diverged conflict update reported: it writes the reconciled schema body (meta-validated, atomic, operation 'replace') AND advances the merge base for that schema to the packaged catalog body. Advancing the base is the step a bare write-schema lacks — without it, update's 3-way merge re-derives the SAME conflict on every subsequent run because the base never moves off the original pre-conflict body. With the base advanced to the catalog, the next update sees the schema as locally-modified (base === catalog ≠ your body) and the deterministic merge takes your reconciled body (base === theirs → ours) — auto-merging with zero conflicts and preserving your resolution. If schema is omitted, the current on-disk schema is treated as already reconciled and only the base is advanced. The calling agent runs this; no subordinate resolver is spawned.
336
+
337
+ *Commit a reconciled schema conflict: write the resolved body + advance the merge base to the catalog so update stops re-reporting it (run after reconciling an update conflict)*
338
+
339
+ | Parameter | Type | Required | Description |
340
+ |-----------|------|----------|-------------|
341
+ | `schemaName` | string | yes | Schema name without extension (e.g., 'tasks') |
342
+ | `schema` | unknown | no | The reconciled schema body R (whole JSON Schema object, draft-07; accepts a JSON string). If omitted, the current on-disk schema is treated as already reconciled and only the merge base is advanced. |
343
+ </tool>
344
+
345
+ <tool name="resolve-blocked">
346
+ Commit the resolution of a blocked schema surfaced by update. Run AFTER fixing the block's items (or widening the local schema): when the block file carries git-style failure markers (written by update), strips the full-line marker sentinels first, then re-validates the corrected block against the pinned target schema from the pending-blocked record; on pass registers the migration chain, writes the target schema, advances the merge base to the target (so a subsequent update converges instead of re-blocking), and clears the pending entry; on fail reports the remaining per-item failures and writes nothing.
347
+
348
+ *Commit a blocked schema's resolution: strip any git-style failure markers, re-validate the corrected block against the pinned target, then write the target schema + advance the base + clear the pending record (run after fixing the items update reported blocked)*
349
+
350
+ | Parameter | Type | Required | Description |
351
+ |-----------|------|----------|-------------|
352
+ | `schemaName` | string | yes | Schema name with a pending-blocked entry (from update's blocked report) |
353
+ </tool>
354
+
261
355
  <tool name="write-schema-migration">
262
356
  Declare a schema version-bump migration into substrate (migrations.json). operation 'create' appends a new declaration; 'replace' overwrites an existing declaration matched by (schemaName, fromVersion); 'remove' drops a declaration. kind='identity' asserts the bump is shape-compatible (no data transform); kind='declarative-transform' carries a TransformSpec of rename/set/delete/coerce operations on dotted JSON paths. The loaded MigrationRegistry resolves the recorded edge at next read/write so block items declaring an older schema_version walk forward without process restart. Requires user authorization via interactive confirmation at the pi-dispatch auth-gate; on confirm, the verified terminal-operator identity is stamped as writer.
263
357
 
@@ -291,6 +385,36 @@ Adopt the canonical packaged conception (samples/conception.json) as this substr
291
385
 
292
386
  </tool>
293
387
 
388
+ <tool name="context-install">
389
+ Install (materialize) the schemas and starter blocks declared in config.json's installed_schemas / installed_blocks from the package samples catalog. Default skip-if-exists (installed files never overwritten without --update); populated block data is always preserved (even with --update); empty or absent blocks get the catalog starter. Records the install baseline (config.installed_from: catalog source + per-schema fingerprint) for installed-vs-catalog drift detection (schemas only). A re-install on an unchanged substrate is idempotent.
390
+
391
+ *Install declared schemas + starter blocks from the samples catalog (skip-if-exists; --update re-syncs schemas + replaces empty blocks; records the config.installed_from baseline)*
392
+
393
+ | Parameter | Type | Required | Description |
394
+ |-----------|------|----------|-------------|
395
+ | `update` | boolean | no | When true, re-sync existing installed schemas (migration-aware) and replace empty blocks with the catalog starter; populated block data is never overwritten. When false (default), skip existing files. |
396
+ </tool>
397
+
398
+ <tool name="update">
399
+ Bring the installed substrate model (schemas) current with the packaged catalog. Per installed schema, consults the read-only drift check and routes by state: an already-current (in-sync) schema is a no-op; a schema the package shipped a newer version of (catalog-ahead) is re-synced through the migration-aware path; a schema edited locally (locally-modified / both-diverged) is reconciled by a deterministic 3-way merge of base (the as-installed body in the object store, keyed by the recorded baseline content_hash) × ours (the installed schema) × theirs (the catalog schema) — disjoint edits auto-merge so both the user's and the catalog's changes survive (required / enum / array-valued type nodes merge as sets), and a schema with irreconcilable per-path conflicts is left unmodified — the conflict set is returned in the op output (under conflicts) alongside a readable report, and the calling agent reconciles it then commits via resolve-conflict — which writes the reconciled body AND advances the merge base to the catalog so update stops re-reporting it (no subordinate resolver is spawned); undecidable / absent schemas (no-baseline / missing-catalog / missing-installed) are reported, not touched. Update also additively propagates catalog-new config-registry entries (relation_types / invariants / block_kinds / lenses) that are absent from the substrate config, preserving every user-authored entry and any locally-diverged body of an existing entry (additive-only — present entries are never overwritten). Update reports, under migrationsRegistered, the migration declarations a version-bump resync registers into migrations.json (each as schema / from / to). A blocked (refused) catalog-ahead schema additionally carries its diagnostic detail under blockedDetail (one entry per blocked schema): the refusal reason — no-migration-chain (no shipped chain reaches the catalog version) vs validation-failed (the forward-migrated items fail the catalog schema) — the installed -> catalog version pair, and for a validation failure the per-item failures naming the failing item id, field, and constraint. A live blocked resync also persists a pending-blocked record (pinning the target catalog schema + the chain reaching it) consumable by resolve-blocked, which commits the resolution once the block's items are fixed. Pass dryRun to preview the per-schema action plan; dryRun predicts the precise per-schema catalog-ahead outcome (resync / migrate / block / merge / conflict) by running the forward-migration + re-validation in memory, the per-blocked-schema diagnostic detail, the config-registry entries that would be added, AND the migration declarations that would be registered, writing nothing. When a catalog-ahead resync is blocked because the block's items fail the catalog schema (validation-failed), update inscribes git-style failure markers INTO the block file at the offending items (full-line `<<<<<<< BLOCKED …` / `>>>>>>> target: …` sentinels), pinning the pre-marker bytes so resolve-blocked can strip the markers and re-validate; the schema and migrations.json stay byte-unchanged. A dryRun preview writes no markers.
400
+
401
+ *Update the installed schema model from the catalog (3-way merges locally-modified schemas, preserving non-conflicting edits; conflicts → returned in the op output + a report for the calling agent to reconcile and commit via resolve-conflict; a blocked resync carries blockedDetail — reason, version pair, per-item failures — and persists a pending-blocked record (target catalog schema + the chain reaching it) resolved via resolve-blocked once the block's items are fixed; a validation-failed block is marked in place with git-style failure markers (recoverable; stripped + re-validated by resolve-blocked); --dry-run predicts the precise per-schema outcome — resync / migrate / block / merge / conflict — via in-memory forward-migration + re-validation, writing nothing)*
402
+
403
+ | Parameter | Type | Required | Description |
404
+ |-----------|------|----------|-------------|
405
+ | `dryRun` | boolean | no | Preview the per-schema action plan without writing anything. |
406
+ </tool>
407
+
408
+ <tool name="validate-block-items">
409
+ Validate a block's items against the catalog schema version — returns the per-item failures (item id, field, constraint) without writing. Resolves the block's catalog block_kind, loads the installed block, forward-migrates its items in memory through the shipped chain when the block lags the catalog version (a fresh registry; never warms the project's cache), and validates against the catalog schema body. Returns block / from (the block's declared version) / to (the catalog version) / valid / failures[] (each: itemId — the failing item's id when the instancePath resolves to one — instancePath, keyword, message). Read-only: never overwrites the schema, the block, or migrations.json. An unknown block or a missing installed block file throws.
410
+
411
+ *Validate a block's items against the catalog schema version — returns the per-item failures (item id, field, constraint) without writing*
412
+
413
+ | Parameter | Type | Required | Description |
414
+ |-----------|------|----------|-------------|
415
+ | `block` | string | yes | Block name (e.g. 'tasks') |
416
+ </tool>
417
+
294
418
  <tool name="context-switch">
295
419
  Flip the bootstrap pointer to a different substrate dir (parallel to git switch). Default: flip to an existing substrate at target_dir (requires config.json present). create_new=true: bootstrap a fresh substrate at target_dir AND flip in one operation. to_previous=true: flip back to the pointer's previous_contextDir (target_dir ignored).
296
420
 
@@ -319,7 +443,6 @@ Move a non-active substrate dir to archive/<dir>/. Refuses to archive the active
319
443
  | Parameter | Type | Required | Description |
320
444
  |-----------|------|----------|-------------|
321
445
  | `target_dir` | string | yes | Substrate dir name to archive (e.g. '.project'). Refused if it is the active substrate. |
322
- | `writer` | object | no | DispatchContext.writer — stamped by auth-gate on operator confirm. |
323
446
  </tool>
324
447
 
325
448
  <tool name="filter-block-items">
@@ -424,6 +547,19 @@ Materialize the Edge[] for a named lens — synthetic edges from derived_from_fi
424
547
  | `lensId` | string | yes | Lens id from config.lenses[].id |
425
548
  </tool>
426
549
 
550
+ <tool name="context-lens-view">
551
+ Project a config-declared lens (config.lenses[]) as a binned item-view. Without --bin, a bin->count summary (always under the read cap). With --bin, that bin's items paged by --offset/--limit. Serves target, composition, and hand-curated lenses.
552
+
553
+ *Project a config-declared lens as a binned item-view — bin->count summary, or one bin's items paged*
554
+
555
+ | Parameter | Type | Required | Description |
556
+ |-----------|------|----------|-------------|
557
+ | `lensId` | string | yes | Lens id from config.lenses[].id |
558
+ | `bin` | string | no | Return this bin's items paged; omit for a bin->count summary |
559
+ | `offset` | integer | no | Per-bin page start index (default 0) |
560
+ | `limit` | integer | no | Per-bin page size (default 50) |
561
+ </tool>
562
+
427
563
  <tool name="context-walk-descendants">
428
564
  Walk closure-table descendants of a parent id under a given relation_type. Returns string[] of descendant ids (may be empty if no children or relations.json absent).
429
565
 
@@ -512,7 +648,7 @@ List every roadmap in <config.root>/roadmap.json with id, title, optional status
512
648
  <command name="/context">
513
649
  Context state management
514
650
 
515
- Subcommands: `init`, `switch`, `list`, `archive`, `install`, `accept-all`, `view`, `lens-curate`, `roadmap-list`, `roadmap-view`, `roadmap-validate`, `status`, `add-work`, `validate`, `help`
651
+ Subcommands: `init`, `switch`, `list`, `archive`, `install`, `check-status`, `accept-all`, `view`, `lens-curate`, `roadmap-list`, `roadmap-view`, `roadmap-validate`, `status`, `add-work`, `validate`, `help`
516
652
  </command>
517
653
 
518
654
  </commands_reference>
@@ -522,7 +658,7 @@ Subcommands: `init`, `switch`, `list`, `archive`, `install`, `accept-all`, `view
522
658
  </events>
523
659
 
524
660
  <bundled_resources>
525
- 11 schemas, 34 samples bundled.
661
+ 12 schemas, 34 samples bundled.
526
662
  See references/bundled-resources.md for full inventory.
527
663
  </bundled_resources>
528
664
 
@@ -660,13 +796,15 @@ Every block write validates against `<root>/schemas/<blockname>.schema.json`. If
660
796
  </context_accept_all>
661
797
 
662
798
  <context_install>
663
- `/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); pass `--update` to overwrite and report the asset as `updated`. 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`.
799
+ `/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`.
664
800
 
665
- 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`.
801
+ 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).
666
802
  </context_install>
667
803
 
668
804
  <substrate_config>
669
- `<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`.
805
+ `<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.
806
+
807
+ 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.
670
808
 
671
809
  `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.
672
810
 
@@ -710,7 +848,7 @@ The lens-view algorithm: `edgesForLens(lens, items, authoredEdges)` returns synt
710
848
 
711
849
  `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.
712
850
 
713
- Two 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-walk-descendants` returns the transitive descendant id list from a parent under a given relation_type.
851
+ 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.
714
852
  </substrate_validation>
715
853
 
716
854
  <block_item_reads>
@@ -764,7 +902,7 @@ On `session_start`, checks npm registry for newer versions of `@davidorex/pi-pro
764
902
 
765
903
  <success_criteria>
766
904
  - `<substrate-dir>/`, `<substrate-dir>/schemas/`, and the `.pi-context.json` bootstrap pointer exist after `/context init <substrate-dir>` (init is skeleton-only: no `config.json`, 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.
767
- - `installed_schemas` / `installed_blocks` declared in `config.json` are reified by `/context install`; `--update` overwrites
905
+ - `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
768
906
  - Block writes validate against schemas — invalid data rejected with specific error
769
907
  - `/context status` returns current derived state without errors
770
908
  - `/context validate` returns no errors for well-formed cross-block references
@@ -1,12 +1,13 @@
1
1
  # Bundled Resources
2
2
 
3
- ## schemas/ (11 files)
3
+ ## schemas/ (12 files)
4
4
 
5
5
  - `schemas/bootstrap.schema.json`
6
6
  - `schemas/config.schema.json`
7
7
  - `schemas/context-registry.schema.json`
8
8
  - `schemas/layer.schema.json`
9
9
  - `schemas/migrations.schema.json`
10
+ - `schemas/pending-blocked.schema.json`
10
11
  - `schemas/priority.schema.json`
11
12
  - `schemas/relations.schema.json`
12
13
  - `schemas/severity.schema.json`