@davidorex/pi-context 0.31.0 → 0.33.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 (77) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/README.md +16 -14
  3. package/dist/block-api.d.ts +20 -33
  4. package/dist/block-api.d.ts.map +1 -1
  5. package/dist/block-api.js +154 -3
  6. package/dist/block-api.js.map +1 -1
  7. package/dist/content-hash.d.ts +9 -0
  8. package/dist/content-hash.d.ts.map +1 -1
  9. package/dist/content-hash.js +11 -0
  10. package/dist/content-hash.js.map +1 -1
  11. package/dist/context-sdk.d.ts +157 -39
  12. package/dist/context-sdk.d.ts.map +1 -1
  13. package/dist/context-sdk.js +896 -251
  14. package/dist/context-sdk.js.map +1 -1
  15. package/dist/context.d.ts +158 -5
  16. package/dist/context.d.ts.map +1 -1
  17. package/dist/context.js +75 -5
  18. package/dist/context.js.map +1 -1
  19. package/dist/index.d.ts +151 -19
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +590 -97
  22. package/dist/index.js.map +1 -1
  23. package/dist/lens-view.d.ts +0 -5
  24. package/dist/lens-view.d.ts.map +1 -1
  25. package/dist/lens-view.js +43 -1
  26. package/dist/lens-view.js.map +1 -1
  27. package/dist/migration-registry-loader.d.ts +20 -12
  28. package/dist/migration-registry-loader.d.ts.map +1 -1
  29. package/dist/migration-registry-loader.js +46 -17
  30. package/dist/migration-registry-loader.js.map +1 -1
  31. package/dist/migrations-store.d.ts +45 -18
  32. package/dist/migrations-store.d.ts.map +1 -1
  33. package/dist/migrations-store.js +56 -22
  34. package/dist/migrations-store.js.map +1 -1
  35. package/dist/ops-registry.d.ts +18 -0
  36. package/dist/ops-registry.d.ts.map +1 -1
  37. package/dist/ops-registry.js +474 -130
  38. package/dist/ops-registry.js.map +1 -1
  39. package/dist/promote-item.d.ts.map +1 -1
  40. package/dist/promote-item.js +41 -12
  41. package/dist/promote-item.js.map +1 -1
  42. package/dist/roadmap-plan.d.ts +121 -99
  43. package/dist/roadmap-plan.d.ts.map +1 -1
  44. package/dist/roadmap-plan.js +281 -345
  45. package/dist/roadmap-plan.js.map +1 -1
  46. package/dist/status-vocab.d.ts +12 -2
  47. package/dist/status-vocab.d.ts.map +1 -1
  48. package/dist/status-vocab.js +14 -1
  49. package/dist/status-vocab.js.map +1 -1
  50. package/package.json +2 -2
  51. package/samples/blocks/milestone.json +3 -0
  52. package/samples/blocks/session-notes.json +1 -0
  53. package/samples/conception.json +315 -15
  54. package/samples/migrations.json +8 -0
  55. package/samples/schemas/context-contracts.schema.json +4 -0
  56. package/samples/schemas/conventions.schema.json +4 -0
  57. package/samples/schemas/decisions.schema.json +4 -0
  58. package/samples/schemas/features.schema.json +4 -0
  59. package/samples/schemas/framework-gaps.schema.json +9 -0
  60. package/samples/schemas/issues.schema.json +28 -0
  61. package/samples/schemas/layer-plans.schema.json +4 -0
  62. package/samples/schemas/milestone.schema.json +79 -0
  63. package/samples/schemas/phase.schema.json +4 -0
  64. package/samples/schemas/rationale.schema.json +4 -0
  65. package/samples/schemas/requirements.schema.json +4 -0
  66. package/samples/schemas/research.schema.json +45 -2
  67. package/samples/schemas/session-notes.schema.json +89 -0
  68. package/samples/schemas/spec-reviews.schema.json +4 -0
  69. package/samples/schemas/story.schema.json +8 -0
  70. package/samples/schemas/tasks.schema.json +4 -0
  71. package/samples/schemas/verification.schema.json +4 -0
  72. package/samples/schemas/work-orders.schema.json +4 -0
  73. package/schemas/config.schema.json +90 -4
  74. package/schemas/migrations.schema.json +25 -0
  75. package/skill-narrative.md +8 -6
  76. package/skills/pi-context/SKILL.md +70 -63
  77. package/skills/pi-context/references/bundled-resources.md +5 -1
@@ -10,9 +10,9 @@ description: >
10
10
 
11
11
  <tools_reference>
12
12
  <tool name="append-block-item">
13
- Append an item to an array in a project block file. Schema validation is automatic. Set autoId:true to allocate the next id from the block's id pattern when the item has no id.
13
+ Append an item to an array in a project block file. Schema validation is automatic. Set autoId:true to allocate the next id from the block's id pattern when the item has no id. Optional relations file the item's BIRTH edges in the same op run, after id allocation — each entry names the relation_type, the other endpoint's selector, and EXACTLY ONE orientation: direction (as_parent | as_child — the raw endpoint the new item occupies) or role (primary | counter — the semantic role the new item holds, mapped via the relation's declared role_direction; required for role-bearing orientation-ambiguous relation_types such as the gated-by / derived-from / supersedes / depends families, where the raw form is rejected). Filing item + edges as one atom lets a new item satisfy error-severity birth-edge invariants (e.g. a decision must cite a forcing artifact) that would refuse the bare item under the write-time gate. Write pipeline: after this op's write, rollup-kind stored statuses converge with their derivation, and the config invariants are re-evaluated delta-scoped — a violation newly introduced by this write refuses it at error severity (substrate byte-restored) or is surfaced on the result at warning severity (write-warning lines / writeWarnings); pre-existing violations never block.
14
14
 
15
- *Append items to project blocks (issues, decisions, or any user-defined block)*
15
+ *Append items to project blocks (issues, decisions, or any user-defined block), with optional atomic birth edges*
16
16
 
17
17
  | Parameter | Type | Required | Description |
18
18
  |-----------|------|----------|-------------|
@@ -20,10 +20,11 @@ Append an item to an array in a project block file. Schema validation is automat
20
20
  | `arrayKey` | string | yes | Array key in the block (e.g., 'issues', 'decisions') |
21
21
  | `item` | unknown | yes | Item object to append — must conform to block schema |
22
22
  | `autoId` | boolean | no | When true and the item has no id, allocate the next id from the block's id pattern |
23
+ | `relations` | array | no | Birth edges filed atomically with the item, after id allocation, via the same validated append-relation porcelain (each entry oriented by direction OR role) |
23
24
  </tool>
24
25
 
25
26
  <tool name="update-block-item">
26
- Update fields on an item in a project block array. Finds by predicate field match.
27
+ Update fields on an item in a project block array. Finds by predicate field match. Write pipeline: after this op's write, rollup-kind stored statuses converge with their derivation, and the config invariants are re-evaluated delta-scoped — a violation newly introduced by this write refuses it at error severity (substrate byte-restored) or is surfaced on the result at warning severity (write-warning lines / writeWarnings); pre-existing violations never block.
27
28
 
28
29
  *Update items in project blocks — change status, add details, mark resolved*
29
30
 
@@ -36,21 +37,23 @@ Update fields on an item in a project block array. Finds by predicate field matc
36
37
  </tool>
37
38
 
38
39
  <tool name="append-relation">
39
- Append a closure-table relation (edge: parent, child, relation_type, optional ordinal) to relations.json. Shape is AJV-validated; an exact-duplicate edge (same parent+child+relation_type) is a no-op. Reference integrity (endpoints resolve, relation_type registered, no cycle) is NOT checked here — run context-validate after. Creates relations.json if absent.
40
+ Append a closure-table relation (edge: relation_type, optional ordinal) to relations.json. Orient the edge with EITHER raw --parent/--child OR the role-typed --primary/--counter (which maps to parent/child via the relation's declared role_direction); the two pairs are mutually exclusive. A bare --parent/--child append of a relation that is BOTH role-bearing and orientation-ambiguous (its source/target kinds overlap) is rejected — re-issue with --primary/--counter. Shape is AJV-validated; an exact-duplicate edge (same parent+child+relation_type) is a no-op. Reference integrity (endpoints resolve, relation_type registered, no cycle) is NOT checked here — run context-validate after. Creates relations.json if absent. Write pipeline: after this op's write, rollup-kind stored statuses converge with their derivation, and the config invariants are re-evaluated delta-scoped — a violation newly introduced by this write refuses it at error severity (substrate byte-restored) or is surfaced on the result at warning severity (write-warning lines / writeWarnings); pre-existing violations never block.
40
41
 
41
- *Create a relation/edge between two items (parentchild under a relation_type)*
42
+ *Create a relation/edge between two items (raw --parent/--child, or role-typed --primary/--counter mapped via role_direction)*
42
43
 
43
44
  | Parameter | Type | Required | Description |
44
45
  |-----------|------|----------|-------------|
45
- | `parent` | string | yes | Canonical id (or lens bin name) of the parent endpoint |
46
- | `child` | string | yes | Canonical id of the child endpoint |
46
+ | `parent` | string | no | Parent-endpoint selector (canonical id / <alias>:<refname> / lens bin) RAW orientation. Mutually exclusive with --primary/--counter. |
47
+ | `child` | string | no | Child-endpoint selector RAW orientation. Mutually exclusive with --primary/--counter. |
48
+ | `primary` | string | no | Selector of the endpoint holding the relation's PRIMARY semantic role (ROLE-TYPED orientation; mapped to parent/child via the relation's declared role_direction). Requires --counter; the relation_type must declare role_direction. |
49
+ | `counter` | string | no | Selector of the endpoint holding the relation's COUNTER role (ROLE-TYPED orientation). Requires --primary. |
47
50
  | `relation_type` | string | yes | Registered relation_type canonical_id / hierarchy edge type / lens id |
48
51
  | `ordinal` | integer | no | Optional sibling-ordering within (parent, relation_type) |
49
52
  | `dryRun` | boolean | no | Preview without writing relations.json |
50
53
  </tool>
51
54
 
52
55
  <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.
56
+ 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. Write pipeline: after this op's write, rollup-kind stored statuses converge with their derivation, and the config invariants are re-evaluated delta-scoped — a violation newly introduced by this write refuses it at error severity (substrate byte-restored) or is surfaced on the result at warning severity (write-warning lines / writeWarnings); pre-existing violations never block.
54
57
 
55
58
  *Remove a relation/edge between two items (the inverse of append-relation)*
56
59
 
@@ -63,7 +66,7 @@ Remove the single closure-table relation (edge) matching parent+child+relation_t
63
66
  </tool>
64
67
 
65
68
  <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.
69
+ 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. This op takes RAW parent/child (old + new) and BYPASSES the write-time orientation gate that append-relation applies — it writes the endpoints verbatim, so it is the affordance for re-orienting an existing edge; reference integrity is NOT checked here — run context-validate after. Write pipeline: after this op's write, rollup-kind stored statuses converge with their derivation, and the config invariants are re-evaluated delta-scoped — a violation newly introduced by this write refuses it at error severity (substrate byte-restored) or is surfaced on the result at warning severity (write-warning lines / writeWarnings); pre-existing violations never block.
67
70
 
68
71
  *Atomically swap one relation/edge for another in a single write*
69
72
 
@@ -80,20 +83,20 @@ Atomically replace one closure-table relation with another in a SINGLE write (no
80
83
  </tool>
81
84
 
82
85
  <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.
86
+ Append MANY closure-table relations to relations.json in a single write. Each edge is an object with { relation_type, ordinal? } plus EITHER a raw { parent, child } pair OR the role-typed { primary, counter } pair (mapped to parent/child via the relation's declared role_direction); the two pairs are mutually exclusive per edge, and a bare { parent, child } for an orientation-ambiguous role-bearing relation rejects the whole batch before any write. 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. Write pipeline: after this op's write, rollup-kind stored statuses converge with their derivation, and the config invariants are re-evaluated delta-scoped — a violation newly introduced by this write refuses it at error severity (substrate byte-restored) or is surfaced on the result at warning severity (write-warning lines / writeWarnings); pre-existing violations never block.
84
87
 
85
- *Create many relations/edges between items in one write*
88
+ *Create many relations/edges between items in one write (raw or role-typed per edge)*
86
89
 
87
90
  | Parameter | Type | Required | Description |
88
91
  |-----------|------|----------|-------------|
89
- | `edges` | unknown | yes | JSON array of { parent, child, relation_type, ordinal? } selector objects (parent/child are id/lens-bin selectors) |
92
+ | `edges` | unknown | yes | JSON array of edge objects. Each edge is { relation_type, ordinal? } plus EITHER a raw { parent, child } pair OR the role-typed { primary, counter } pair (mapped to parent/child via the relation's declared role_direction); the two orientation pairs are mutually exclusive per edge. Selectors are id / <alias>:<refname> / lens-bin. |
90
93
  | `dryRun` | boolean | no | Preview without writing relations.json |
91
94
  </tool>
92
95
 
93
96
  <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'.
97
+ 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'. Optional relations file BIRTH edges in the same op run when the upsert resolves to an APPEND — each entry names the relation_type, the other endpoint's selector, and EXACTLY ONE orientation: direction (as_parent | as_child, raw) or role (primary | counter, mapped via the relation's declared role_direction; required for role-bearing orientation-ambiguous relation_types) — one atom under the write-time gate, so a new filing can satisfy error-severity birth-edge invariants. dryRun previews the upsert AND runs the same orientation guard over the entries (a preview refuses what the live run would orientation-refuse; endpoint resolution stays out — the item is unwritten). When the upsert resolves to a REPLACE, supplying relations refuses the write (birth edges are for new items; file edges on an existing item via append-relation). Write pipeline: after this op's write, rollup-kind stored statuses converge with their derivation, and the config invariants are re-evaluated delta-scoped — a violation newly introduced by this write refuses it at error severity (substrate byte-restored) or is surfaced on the result at warning severity (write-warning lines / writeWarnings); pre-existing violations never block.
95
98
 
96
- *Append-or-replace a full block item by id (replacement, not merge)*
99
+ *Append-or-replace a full block item by id (replacement, not merge), with optional atomic birth edges*
97
100
 
98
101
  | Parameter | Type | Required | Description |
99
102
  |-----------|------|----------|-------------|
@@ -102,10 +105,11 @@ Append-or-replace an item in a project block array by id: if an item with the sa
102
105
  | `item` | unknown | yes | Full item object to upsert — must conform to block schema |
103
106
  | `idField` | string | no | Field used as the upsert key (default 'id') |
104
107
  | `dryRun` | boolean | no | Preview the upsert without writing |
108
+ | `relations` | array | no | Birth edges filed atomically with an APPEND-mode upsert, each entry oriented by direction OR role (refused on replace mode — use append-relation for existing items) |
105
109
  </tool>
106
110
 
107
111
  <tool name="promote-item">
108
- Promote a substrate item into another (registered) substrate as a NEW content-addressed item, recording the 'item_derived_from_item' lineage edge in the destination relations.json (parent = the new derived item, child = the source, carrying the source content_hash). The destination write-path mints a fresh oid + content_hash + content object. When the source block's status enum supports it, the source is marked superseded. Preconditions (unresolvable/non-item source, unregistered destination alias, unregistered destination relation_type, refname collision) throw. Pass dryRun to compute the destination without writing.
112
+ Promote a substrate item into another (registered) substrate as a NEW content-addressed item, recording the 'item_derived_from_item' lineage edge in the destination relations.json (parent = the new derived item, child = the source, carrying the source content_hash). The destination write-path mints a fresh oid + content_hash + content object. When the source block's status enum supports it, the source is marked superseded. Preconditions (unresolvable/non-item source, unregistered destination alias, unregistered destination relation_type, refname collision) throw. Pass dryRun to compute the destination without writing. Write pipeline: after this op's write, rollup-kind stored statuses converge with their derivation, and the config invariants are re-evaluated delta-scoped — a violation newly introduced by this write refuses it at error severity (substrate byte-restored) or is surfaced on the result at warning severity (write-warning lines / writeWarnings); pre-existing violations never block.
109
113
 
110
114
  *Promote an item into another substrate as a derived copy with a lineage edge*
111
115
 
@@ -148,7 +152,7 @@ Update fields on a nested-array item inside a parent-array item in a project blo
148
152
  </tool>
149
153
 
150
154
  <tool name="remove-block-item">
151
- Remove items matching a predicate from a top-level array in a project block. Idempotent — returns { removed: 0 } on no match without throwing. Schema validation runs after removal.
155
+ Remove items matching a predicate from a top-level array in a project block. Idempotent — returns { removed: 0 } on no match without throwing. Schema validation runs after removal. Write pipeline: after this op's write, rollup-kind stored statuses converge with their derivation, and the config invariants are re-evaluated delta-scoped — a violation newly introduced by this write refuses it at error severity (substrate byte-restored) or is surfaced on the result at warning severity (write-warning lines / writeWarnings); pre-existing violations never block.
152
156
 
153
157
  *Remove items from project blocks — prune retracted issues, dedupe entries*
154
158
 
@@ -194,7 +198,7 @@ Read a project block file as structured JSON.
194
198
  </tool>
195
199
 
196
200
  <tool name="write-block">
197
- Write or replace an entire project block with schema validation.
201
+ Write or replace an entire project block with schema validation. Write pipeline: after this op's write, rollup-kind stored statuses converge with their derivation, and the config invariants are re-evaluated delta-scoped — a violation newly introduced by this write refuses it at error severity (substrate byte-restored) or is surfaced on the result at warning severity (write-warning lines / writeWarnings); pre-existing violations never block.
198
202
 
199
203
  *Write or replace a project block with schema validation*
200
204
 
@@ -212,7 +216,7 @@ Get derived context state — source metrics, block summaries, planning lifecycl
212
216
  </tool>
213
217
 
214
218
  <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.
219
+ 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. Like every substrate-lifecycle ceremony it seeds the catalog's config migration declarations into migrations.json (idempotent) before its first config read, so a version-lagging legacy substrate is diagnosable; beyond that seed it writes nothing.
216
220
 
217
221
  *Report installed-vs-catalog schema drift + the version gap for behind schemas (read-only)*
218
222
 
@@ -267,9 +271,9 @@ Fetch and print the verbatim catalog schema body (raw JSON Schema: properties/de
267
271
  </tool>
268
272
 
269
273
  <tool name="context-current-state">
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.
274
+ Derive 'where are we + what's next' purely from the substrate — focus, in-flight items, ranked atomic-next actions, blocked items, and milestone rollups. Every facet derives from the config-declared `state_derivation` registry: which block kinds + status bucket count as in-flight, the focus fallback kind + bucket, the ordered cross-kind next-actions push order with per-entry ranking (a named field + ordered value list, e.g. gap priority P0..P3) or topo ordering over the blocking-relation graph, the relation_types whose edges contribute blockers (the stock set being `task_depends_on_task` dependencies + `task_gated_by_item` gates), the membership rollups (e.g. milestones over `phase_positioned_in_milestone`) with their complete/incomplete status strings, and the next-actions head-size cap. A blocked item's dependency/gate target that is not complete is reported in blockedBy and held out of nextActions; completeness follows the target kind's truth model — a rollup-declared kind (state_derivation.rollups, e.g. milestone) completes by its DERIVED membership rollup, the same verdict the milestones facet reports, so one read never self-contradicts and a derived-status kind's stored status field is never consulted; every other kind completes by its status bucketing to complete. A substrate whose config declares no `state_derivation` reports focus 'state-derivation not configured' with empty arrays. No writes; nothing hand-stored.
271
275
 
272
- *Derive current project state — focus, in-flight, next actions, blocked*
276
+ *Derive current project state from the config-declared state_derivation registry — focus, in-flight, ranked next actions, blocked, milestone rollups*
273
277
 
274
278
  </tool>
275
279
 
@@ -281,7 +285,7 @@ Derive the substrate bootstrap state for the cwd, purely from the filesystem: 'n
281
285
  </tool>
282
286
 
283
287
  <tool name="rename-canonical-id">
284
- Rename a canonical_id (kind: item | relation_type | lens | layer) from oldId to newId across all substrate surfaces that carry it as DATA — item home block + relations.json edges, or the relevant config registries. Out-of-substrate occurrences (analysis MDs, git history) are REPORTED, never rewritten. block_kind renames are unsupported (filesystem cascade). Use dryRun to preview the would-change counts without writing.
288
+ Rename a canonical_id (kind: item | relation_type | lens | layer) from oldId to newId across all substrate surfaces that carry it as DATA — item home block + relations.json edges, or the relevant config registries. Out-of-substrate occurrences (analysis MDs, git history) are REPORTED, never rewritten. block_kind renames are unsupported (filesystem cascade). Use dryRun to preview the would-change counts without writing. Write pipeline: after this op's write, rollup-kind stored statuses converge with their derivation, and the config invariants are re-evaluated delta-scoped — a violation newly introduced by this write refuses it at error severity (substrate byte-restored) or is surfaced on the result at warning severity (write-warning lines / writeWarnings); pre-existing violations never block.
285
289
 
286
290
  *Rename a canonical_id (item/relation_type/lens/layer) across substrate; dryRun to preview*
287
291
 
@@ -343,7 +347,7 @@ Commit the reconciliation of a schema merge conflict surfaced by update. Run thi
343
347
  </tool>
344
348
 
345
349
  <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.
350
+ 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. The commit is all-or-nothing: a throw partway through it restores every touched file byte-exact — migrations.json, the installed schema, the block file, config.json, and the pending record — and reports the failure, never a partial commit. On a substrate whose config carries no substrate_id, resolve-blocked establishes the identity at entry (mints, persists, registers) before the commit's stamping write and reports it under substrateIdEstablished.
347
351
 
348
352
  *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
353
 
@@ -353,7 +357,7 @@ Commit the resolution of a blocked schema surfaced by update. Run AFTER fixing t
353
357
  </tool>
354
358
 
355
359
  <tool name="write-schema-migration">
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.
360
+ 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/map_each operations on dotted JSON paths; map_each addresses an array — table mode maps each string element through a lookup (unmatched elements become {relation_type, item_endpoint} with parent/child fallback), set-on-each mode sets a field on every object element. 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.
357
361
 
358
362
  *Declare a schema version-bump migration (identity or declarative-transform) into migrations.json*
359
363
 
@@ -386,7 +390,7 @@ Adopt the canonical packaged conception (samples/conception.json) as this substr
386
390
  </tool>
387
391
 
388
392
  <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.
393
+ 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. On a substrate whose config carries no substrate_id, install establishes the identity at entry (mints, persists to config.json, registers in the project registry) and reports it under substrateIdEstablished; an established identity is never re-minted.
390
394
 
391
395
  *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
396
 
@@ -396,15 +400,25 @@ Install (materialize) the schemas and starter blocks declared in config.json's i
396
400
  </tool>
397
401
 
398
402
  <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.
403
+ 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) vs write-failed (a non-validation throw at the write boundary, e.g. the block writer's duplicate-item-id guard; the failures entry carries the thrown message, the items were NOT flagged invalid, and no markers or pending-blocked record are produced) — 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 beyond the idempotent ceremony seed of the catalog's config migration declarations into migrations.json (every substrate-lifecycle ceremony seeds at entry, before its first config read, so a version-lagging legacy substrate heals instead of throwing). 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. Because update applies per-component (a blocked schema rolls back only itself; the additive registry propagation writes regardless), a run that refuses any schema while applying registry additions or other-schema resyncs/migrations/merges reports the partiality under partialApplication — applied and notApplied channel mirrors plus a one-line summary naming what was applied alongside what was refused and why — so a blocked run never reads as a no-op; dryRun reports the predicted partiality in the same shape. On a substrate whose config carries no substrate_id, a LIVE update establishes the identity at entry (mints, persists to config.json, registers in the project registry) before its first identity-stamping write — so a pre-identity substrate heals on the ceremony instead of refusing — and reports it under substrateIdEstablished; an established identity is never re-minted, and dryRun (no stamping writes) establishes nothing.
400
404
 
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)*
405
+ *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 (no-migration-chain / validation-failed / write-failed for a non-validation write-boundary throw), version pair, per-item failures — and a validation-failed block 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; a run that refuses any schema while applying registry additions or other-schema updates surfaces the partiality under partialApplication with a one-line summary, so a blocked run never reads as a no-op)*
402
406
 
403
407
  | Parameter | Type | Required | Description |
404
408
  |-----------|------|----------|-------------|
405
409
  | `dryRun` | boolean | no | Preview the per-schema action plan without writing anything. |
406
410
  </tool>
407
411
 
412
+ <tool name="context-reconcile">
413
+ Converge stored substrate state with its derivation (the repair half of the derived-status invariant class). For every block kind a derived-status invariant declares (paired with its state_derivation.rollups entry), computes each item's stored-vs-derived status delta using the SAME completeness helper the state derivation's gate satisfaction and context-validate use — the preview, the detector, and the repair cannot disagree. The sweep also includes declared-baseline STALENESS: every stale_conditions-bearing item whose status buckets complete and whose typed condition fired (item-status / file-changed / revision-moved) or whose citation content pin drifted transitions to stale — the same evaluateStalenessCandidates verdict context-validate flags with; flag-only anchor-drift items never transition. --dryRun returns the exact delta + transition sets a live run would apply (deltas: id, block, from stored value, to derived value, declaring invariant; stalenessTransitions: id, block, from, to stale, firing reasons), writing nothing. A live run applies exactly those sets through the standard validated write path — identity-stamped, envelope-stamped, attested to the invoking writer — and reports the applied counts; a converge-write is not authoring, the written value IS the derivation, and the stale transition applies a condition the item itself declared. Scope: derived-status deltas + declared-staleness transitions ONLY — the op never writes an authored-status kind (feature/gap/issue/task buckets are human judgment) and never touches prose; those classes are flagged for review by context-validate, not auto-repaired. Ceremony discipline: seeds the catalog config migration declarations at entry, and a live run on a substrate with no substrate_id establishes the identity first (reported under substrateIdEstablished). A converged substrate is a clean no-op both ways.
414
+
415
+ *Converge stored rollup-kind statuses with their derivation and apply declared complete-to-stale transitions (--dryRun previews the exact sets; live applies through the validated write path; never touches authored statuses or prose)*
416
+
417
+ | Parameter | Type | Required | Description |
418
+ |-----------|------|----------|-------------|
419
+ | `dryRun` | boolean | no | Preview the exact delta set without writing anything. |
420
+ </tool>
421
+
408
422
  <tool name="validate-block-items">
409
423
  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
424
 
@@ -520,14 +534,14 @@ Bulk variant of resolve-item-by-id — resolve N kind-prefixed ids against a sin
520
534
  </tool>
521
535
 
522
536
  <tool name="complete-task">
523
- Complete a task with verification gate — requires a passing verification entry targeting the task.
537
+ Complete a task with verification gate — the closure ATOM. Requires a passing verification entry, then FILES the verification_verifies_item edge itself (idempotent — a pre-existing exact edge is a no-op) and flips the task status to completed in one op run, so the write-time invariant gate judges the joint end-state. No prior append-relation step is needed (a standalone edge or status write would be refused by error-severity closure invariants; this op IS the legal transition). Write pipeline: after this op's write, rollup-kind stored statuses converge with their derivation, and the config invariants are re-evaluated delta-scoped — a violation newly introduced by this write refuses it at error severity (substrate byte-restored) or is surfaced on the result at warning severity (write-warning lines / writeWarnings); pre-existing violations never block.
524
538
 
525
- *Complete a task — gates on passing verification before updating status*
539
+ *Complete a task — gates on passing verification, files the verification edge itself, then flips status (one atom)*
526
540
 
527
541
  | Parameter | Type | Required | Description |
528
542
  |-----------|------|----------|-------------|
529
543
  | `taskId` | string | yes | Task ID to complete |
530
- | `verificationId` | string | yes | Verification entry ID (must target this task with status 'passed') |
544
+ | `verificationId` | string | yes | Verification entry ID (must have status 'passed'; the op files the linking edge itself) |
531
545
  </tool>
532
546
 
533
547
  <tool name="context-validate-relations">
@@ -561,7 +575,7 @@ Project a config-declared lens (config.lenses[]) as a binned item-view. Without
561
575
  </tool>
562
576
 
563
577
  <tool name="context-walk-descendants">
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).
578
+ 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). For a DISJOINT-kind relation, querying from the wrong (target-kind) end THROWS naming walk-ancestors instead of silently returning []; same-kind / wildcard relations return [] honestly.
565
579
 
566
580
  *Walk closure-table descendants under a relation_type*
567
581
 
@@ -572,7 +586,7 @@ Walk closure-table descendants of a parent id under a given relation_type. Retur
572
586
  </tool>
573
587
 
574
588
  <tool name="walk-ancestors">
575
- Walk closure-table ancestors of an item id under a given relation_type — reverse-direction counterpart to context-walk-descendants. Returns string[] of ancestor ids (may be empty if no parents or relations.json absent).
589
+ Walk closure-table ancestors of an item id under a given relation_type — reverse-direction counterpart to context-walk-descendants. Returns string[] of ancestor ids (may be empty if no parents or relations.json absent). For a DISJOINT-kind relation, querying from the wrong (source-kind) end THROWS naming context-walk-descendants instead of silently returning []; same-kind / wildcard relations return [] honestly.
576
590
 
577
591
  *Walk closure-table ancestors under a relation_type*
578
592
 
@@ -606,39 +620,23 @@ Compose a ContextBundle for a work-unit by reading its context-contract (by unit
606
620
  </tool>
607
621
 
608
622
  <tool name="context-roadmap-load">
609
- Load a roadmap by id and return the materialized RoadmapView (phases, lens-views, status rollup, milestone resolution, scoped phase_depends_on edges, topo-ordered phaseOrder + cycles). Phase ordering lives in relations.json with relation_type='phase_depends_on'.
623
+ Load the derived roadmap view over the milestone_precedes_milestone DAG: milestone-block items topo-ordered by the authored precedes edges (order + cycles), each milestone carrying its derived status/phaseCount (currentState's milestone rollup), its member phases (parents of phase_positioned_in_milestone edges), each phase's tasks (parents of task_positioned_in_phase edges), and per-phase + per-milestone status rollups. Adjacency comes strictly from the authored edges — never inferred from order. Zero milestones is a valid empty view.
610
624
 
611
- *Load a roadmap by id*
625
+ *Load the derived milestone roadmap view*
612
626
 
613
- | Parameter | Type | Required | Description |
614
- |-----------|------|----------|-------------|
615
- | `roadmapId` | string | yes | ROADMAP-NNN id from <config.root>/roadmap.json |
616
627
  </tool>
617
628
 
618
629
  <tool name="context-roadmap-render">
619
- Render a roadmap by id as pure-textual markdown — phase order list, per-phase adjacency lines (sourced from view.edges, alphabetically sorted), status rollup counts, milestone resolution, exit criteria. NO mermaid / graph syntax: per-phase **Depends on:** lines come strictly from authored phase_depends_on edges scoped to in-roadmap phases.
630
+ Render the derived roadmap as pure-textual markdown — milestone order list (topo over the authored milestone_precedes_milestone edges), per-milestone sections with **Preceded by:** adjacency lines sourced strictly from those edges (alphabetically sorted; '—' when none), per-milestone rollup counts, and per-phase task tables. Cycle participants surface under a separate heading with a Cycles-detected line. NO mermaid / graph syntax; adjacency is never inferred from order consecutive pairs.
620
631
 
621
- *Render a roadmap as markdown*
632
+ *Render the derived milestone roadmap as markdown*
622
633
 
623
- | Parameter | Type | Required | Description |
624
- |-----------|------|----------|-------------|
625
- | `roadmapId` | string | yes | ROADMAP-NNN id from <config.root>/roadmap.json |
626
634
  </tool>
627
635
 
628
636
  <tool name="context-roadmap-validate">
629
- Validate every roadmap × phase × milestone in <config.root>/roadmap.json. Codes: roadmap_lens_missing, roadmap_phase_dep_missing, roadmap_phase_cycle, roadmap_composition_cycle, roadmap_milestone_evidence_block_missing, roadmap_milestone_query_invalid, roadmap_status_unknown_value. Display strings flow through config.display_strings (pi-context divergence). Optional roadmapId filter restricts issue list to a single roadmap.
637
+ Validate the derived roadmap over the milestone_precedes_milestone edges. Error codes: roadmap_precedes_endpoint_missing (a precedes-edge endpoint that is not a milestone-block item), roadmap_milestone_cycle (a cycle in the precedes graph), roadmap_milestone_missing (a phase_positioned_in_milestone edge whose child is not a known milestone). Warning: roadmap_status_unknown_value (a member phase whose task rollup buckets unknown with items present). Info: roadmap_milestone_isolated (a milestone with zero precedes edges while others are ordered) — info never affects status: invalid iff any error-code issue, warnings iff any warning-code issue, else clean. Display strings flow through config.display_strings (pi-context divergence).
630
638
 
631
- *Validate roadmaps*
632
-
633
- | Parameter | Type | Required | Description |
634
- |-----------|------|----------|-------------|
635
- | `roadmapId` | string | no | Filter to issues matching this roadmap_id (omit for full-project validation) |
636
- </tool>
637
-
638
- <tool name="context-roadmap-list">
639
- List every roadmap in <config.root>/roadmap.json with id, title, optional status, and phase count. Returns [] when roadmap.json absent (opt-in block; absence is the truthful answer).
640
-
641
- *List roadmaps*
639
+ *Validate the derived milestone roadmap*
642
640
 
643
641
  </tool>
644
642
 
@@ -648,7 +646,7 @@ List every roadmap in <config.root>/roadmap.json with id, title, optional status
648
646
  <command name="/context">
649
647
  Context state management
650
648
 
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`
649
+ Subcommands: `init`, `switch`, `list`, `archive`, `install`, `check-status`, `accept-all`, `view`, `lens-curate`, `roadmap-view`, `roadmap-validate`, `status`, `add-work`, `validate`, `help`
652
650
  </command>
653
651
 
654
652
  </commands_reference>
@@ -658,7 +656,7 @@ Subcommands: `init`, `switch`, `list`, `archive`, `install`, `check-status`, `ac
658
656
  </events>
659
657
 
660
658
  <bundled_resources>
661
- 12 schemas, 34 samples bundled.
659
+ 12 schemas, 38 samples bundled.
662
660
  See references/bundled-resources.md for full inventory.
663
661
  </bundled_resources>
664
662
 
@@ -683,7 +681,9 @@ Names valid for the `installed_blocks` array in `<substrate-dir>/config.json`. I
683
681
  | `context-contracts` | `samples/blocks/context-contracts.json` |
684
682
  | `phase` | `samples/blocks/phase.json` |
685
683
  | `story` | `samples/blocks/story.json` |
684
+ | `milestone` | `samples/blocks/milestone.json` |
686
685
  | `work-orders` | `samples/blocks/work-orders.json` |
686
+ | `session-notes` | `samples/blocks/session-notes.json` |
687
687
 
688
688
  </installable_blocks>
689
689
 
@@ -708,7 +708,9 @@ Names valid for the `installed_schemas` array in `<substrate-dir>/config.json`.
708
708
  | `context-contracts` | `samples/schemas/context-contracts.schema.json` |
709
709
  | `phase` | `samples/schemas/phase.schema.json` |
710
710
  | `story` | `samples/schemas/story.schema.json` |
711
+ | `milestone` | `samples/schemas/milestone.schema.json` |
711
712
  | `work-orders` | `samples/schemas/work-orders.schema.json` |
713
+ | `session-notes` | `samples/schemas/session-notes.schema.json` |
712
714
 
713
715
  </installable_schemas>
714
716
 
@@ -722,7 +724,7 @@ Names valid for the `installed_schemas` array in `<substrate-dir>/config.json`.
722
724
  | `framework-gaps` | Framework Gaps | `gaps` | id, title, status (string (identified|accepted|in-progress|closed|wontfix|superseded_by)), priority? (string (P0|P1|P2|P3)), package, layer? (string (L1|L2|L3|L4|L5)), description, evidence (array), impact, canonical_vocabulary?, proposed_resolution, related_features? (array), related_decisions? (array), related_issues? (array), created_by, created_at, closed_by?, closed_at?, oid?, content_hash?, content_parent? |
723
725
  | `tasks` | Tasks | `tasks` | id, description, status (string (planned|in-progress|completed|blocked|cancelled)), files? (array), acceptance_criteria? (array), assigned_agent?, notes?, oid?, content_hash?, content_parent? |
724
726
  | `verification` | Verification | `verifications` | id, status (string (passed|failed|partial|skipped)), method (string (command|inspect|test)), evidence?, timestamp?, criteria_results? (array), oid?, content_hash?, content_parent? |
725
- | `issues` | Issues | `issues` | id, title, body, location, status (string (open|resolved|deferred)), category (string (primitive|issue|cleanup|capability|composition)), priority (string (low|medium|high|critical)), package, source? (string (human|agent|monitor|workflow)), resolved_by?, oid?, content_hash?, content_parent? |
727
+ | `issues` | Issues | `issues` | id, title, body, location, status (string (open|resolved|deferred)), category (string (primitive|issue|cleanup|capability|composition)), priority (string (low|medium|high|critical)), package, source? (string (human|agent|monitor|workflow)), resolved_by?, resolved_at?, oid?, content_hash?, content_parent? |
726
728
  | `features` | Features | `features` | id, title, status (string (proposed|approved|in-progress|in-review|complete|blocked|cancelled)), layer (string (L1|L2|L3|L4|L5)), description, motivation?, acceptance_criteria (array), created_by, created_at, modified_by?, modified_at?, approved_by?, approved_at?, oid?, content_hash?, content_parent? |
727
729
  | `research` | Research | `research` | id, title, status (string (planned|in-progress|complete|stale|superseded|revised)), layer (string (L1|L2|L3|L4|L5)), type (string (investigative|comparative|empirical|historical|audit|landscape|feasibility|curation)), question, method, scope? (array), findings_summary, findings_document?, grounding? (object), grounded_at?, stale_conditions? (array), citations? (array), conducted_by?, conducted_at?, created_by, created_at, modified_by?, modified_at?, oid?, content_hash?, content_parent? |
728
730
  | `rationale` | Design Rationale | `rationales` | id, title, narrative, phase? (integer), oid?, content_hash?, content_parent? |
@@ -732,8 +734,10 @@ Names valid for the `installed_schemas` array in `<substrate-dir>/config.json`.
732
734
  | `conventions` | Conventions | `rules` | id, description, enforcement (string (lint|test|review|manual)), severity (string (error|warning|info)), oid?, content_hash?, content_parent? |
733
735
  | `context-contracts` | Context contracts | `contracts` | id, unit_kind, bundle_relation_types (array), description?, notes?, created_by, created_at, modified_by?, modified_at?, oid?, content_hash?, content_parent? |
734
736
  | `phase` | Phases | `phases` | id, name, intent, goal?, status (string (planned|in-progress|completed)), success_criteria? (array), specs? (array), artifacts_produced? (array), oid?, content_hash?, content_parent? |
735
- | `story` | Stories | `stories` | id, title, status (string (proposed|ready|in-progress|in-review|complete|blocked)), description?, acceptance_criteria? (array), created_by?, created_at?, modified_by?, modified_at?, oid?, content_hash?, content_parent? |
737
+ | `story` | Stories | `stories` | id, title, user_kind?, status (string (proposed|ready|in-progress|in-review|complete|blocked)), description?, acceptance_criteria? (array), created_by?, created_at?, modified_by?, modified_at?, oid?, content_hash?, content_parent? |
738
+ | `milestone` | Milestones | `milestones` | id, name, status (string (planned|reached)), release?, created_by?, created_at?, modified_by?, modified_at?, oid?, content_hash?, content_parent? |
736
739
  | `work-orders` | Work Orders | `work_orders` | id, title, status (string (proposed|in-progress|real-check-passed|real-check-failed|completed|cancelled)), target_agent, input_contract (object), context_blocks (array), output_contract (object), scope (object), real_check_criteria (object), description?, created_by?, created_at?, modified_by?, modified_at?, oid?, content_hash?, content_parent? |
740
+ | `session-notes` | Session Notes | `sessions` | id, timestamp, focus, discoveries? (array), questions? (array), decisions_made? (array), current_status, next_steps (array), oid?, content_hash?, content_parent? |
737
741
 
738
742
  **Status Enums:**
739
743
 
@@ -765,6 +769,7 @@ Names valid for the `installed_schemas` array in `<substrate-dir>/config.json`.
765
769
  | `conventions` | `severity` | error, warning, info |
766
770
  | `phase` | `status` | planned, in-progress, completed |
767
771
  | `story` | `status` | proposed, ready, in-progress, in-review, complete, blocked |
772
+ | `milestone` | `status` | planned, reached |
768
773
  | `work-orders` | `status` | proposed, in-progress, real-check-passed, real-check-failed, completed, cancelled |
769
774
 
770
775
  </planning_vocabulary>
@@ -774,7 +779,7 @@ pi-context manages structured project state in the substrate directory — a dir
774
779
  </objective>
775
780
 
776
781
  <block_files>
777
- 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.
782
+ 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.
778
783
 
779
784
  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.
780
785
  </block_files>
@@ -788,11 +793,11 @@ Every block write validates against `<root>/schemas/<blockname>.schema.json`. If
788
793
  </schema_validation>
789
794
 
790
795
  <context_init>
791
- `/context init <substrate-dir>` creates the substrate skeleton: the `.pi-context.json` bootstrap pointer (declaring the chosen substrate-dir name) plus the substrate root and its `schemas/` directory. Nothing is imposed — no `config.json`, no schemas, and no starter blocks are written (ship-no-defaults). Idempotent: re-running preserves existing dirs. Populate the substrate next with `/context accept-all` (adopt the canonical conception) followed by `/context install`.
796
+ `/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`.
792
797
  </context_init>
793
798
 
794
799
  <context_accept_all>
795
- `/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 an existing `config.json` (offer, don't impose). This is the accept-all path; per-entry step-through curation is a separate surface.
800
+ `/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.
796
801
  </context_accept_all>
797
802
 
798
803
  <context_install>
@@ -804,7 +809,7 @@ The installable catalog IS the packaged conception (`samples/conception.json`):
804
809
  <substrate_config>
805
810
  `<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
811
 
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.
812
+ 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 filing one of those two edges as a birth relation on the filing itself (`append-block-item --relations`; each entry oriented by `direction: as_parent|as_child` for raw appends, or `role: primary|counter` for role-bearing orientation-ambiguous relation_types such as the gated-by / derived-from / supersedes / depends families, mapped via the relation's declared `role_direction`) or by `append-relation` when amending an existing artifact. On a substrate that raises a birth-edge invariant to `error`, the atomic filing form is the only path the write-time gate accepts — a bare filing is refused, and the edge cannot be added afterward because the intermediate state is itself the violation. Task closure has the same shape: `complete-task` files the `verification_verifies_item` edge itself and flips the task status in one gate-judged run, so no standalone `append-relation` precedes it.
808
813
 
809
814
  `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.
810
815
 
@@ -820,7 +825,7 @@ Source-of-truth-drift invariant: `validateContext` requires the active `config.s
820
825
  </cross_substrate>
821
826
 
822
827
  <schema_versioning>
823
- 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`.
828
+ 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`.
824
829
  </schema_versioning>
825
830
 
826
831
  <lens_views>
@@ -828,6 +833,8 @@ Lenses are named projections over a target block. A lens declares `id`, `target`
828
833
 
829
834
  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.
830
835
 
836
+ 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.
837
+
831
838
  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.)
832
839
 
833
840
  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.
@@ -901,7 +908,7 @@ On `session_start`, checks npm registry for newer versions of `@davidorex/pi-pro
901
908
  </update_check>
902
909
 
903
910
  <success_criteria>
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.
911
+ - `<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.
905
912
  - `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
906
913
  - Block writes validate against schemas — invalid data rejected with specific error
907
914
  - `/context status` returns current derived state without errors
@@ -15,7 +15,7 @@
15
15
  - `schemas/status.schema.json`
16
16
  - `schemas/verification-method.schema.json`
17
17
 
18
- ## samples/ (34 files)
18
+ ## samples/ (38 files)
19
19
 
20
20
  - `samples/blocks/context-contracts.json`
21
21
  - `samples/blocks/conventions.json`
@@ -24,10 +24,12 @@
24
24
  - `samples/blocks/framework-gaps.json`
25
25
  - `samples/blocks/issues.json`
26
26
  - `samples/blocks/layer-plans.json`
27
+ - `samples/blocks/milestone.json`
27
28
  - `samples/blocks/phase.json`
28
29
  - `samples/blocks/rationale.json`
29
30
  - `samples/blocks/requirements.json`
30
31
  - `samples/blocks/research.json`
32
+ - `samples/blocks/session-notes.json`
31
33
  - `samples/blocks/spec-reviews.json`
32
34
  - `samples/blocks/story.json`
33
35
  - `samples/blocks/tasks.json`
@@ -42,10 +44,12 @@
42
44
  - `samples/schemas/framework-gaps.schema.json`
43
45
  - `samples/schemas/issues.schema.json`
44
46
  - `samples/schemas/layer-plans.schema.json`
47
+ - `samples/schemas/milestone.schema.json`
45
48
  - `samples/schemas/phase.schema.json`
46
49
  - `samples/schemas/rationale.schema.json`
47
50
  - `samples/schemas/requirements.schema.json`
48
51
  - `samples/schemas/research.schema.json`
52
+ - `samples/schemas/session-notes.schema.json`
49
53
  - `samples/schemas/spec-reviews.schema.json`
50
54
  - `samples/schemas/story.schema.json`
51
55
  - `samples/schemas/tasks.schema.json`