@davidorex/pi-context 0.31.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +12 -11
  3. package/dist/block-api.d.ts.map +1 -1
  4. package/dist/block-api.js +27 -2
  5. package/dist/block-api.js.map +1 -1
  6. package/dist/context-sdk.d.ts +71 -33
  7. package/dist/context-sdk.d.ts.map +1 -1
  8. package/dist/context-sdk.js +547 -149
  9. package/dist/context-sdk.js.map +1 -1
  10. package/dist/context.d.ts +153 -2
  11. package/dist/context.d.ts.map +1 -1
  12. package/dist/context.js +75 -5
  13. package/dist/context.js.map +1 -1
  14. package/dist/index.d.ts +71 -19
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +405 -94
  17. package/dist/index.js.map +1 -1
  18. package/dist/lens-view.d.ts +0 -5
  19. package/dist/lens-view.d.ts.map +1 -1
  20. package/dist/lens-view.js +43 -1
  21. package/dist/lens-view.js.map +1 -1
  22. package/dist/migration-registry-loader.d.ts +20 -12
  23. package/dist/migration-registry-loader.d.ts.map +1 -1
  24. package/dist/migration-registry-loader.js +46 -17
  25. package/dist/migration-registry-loader.js.map +1 -1
  26. package/dist/migrations-store.d.ts +45 -18
  27. package/dist/migrations-store.d.ts.map +1 -1
  28. package/dist/migrations-store.js +56 -22
  29. package/dist/migrations-store.js.map +1 -1
  30. package/dist/ops-registry.d.ts.map +1 -1
  31. package/dist/ops-registry.js +91 -118
  32. package/dist/ops-registry.js.map +1 -1
  33. package/dist/promote-item.d.ts.map +1 -1
  34. package/dist/promote-item.js +41 -12
  35. package/dist/promote-item.js.map +1 -1
  36. package/dist/roadmap-plan.d.ts +121 -99
  37. package/dist/roadmap-plan.d.ts.map +1 -1
  38. package/dist/roadmap-plan.js +281 -345
  39. package/dist/roadmap-plan.js.map +1 -1
  40. package/dist/status-vocab.d.ts +12 -2
  41. package/dist/status-vocab.d.ts.map +1 -1
  42. package/dist/status-vocab.js +14 -1
  43. package/dist/status-vocab.js.map +1 -1
  44. package/package.json +1 -1
  45. package/samples/blocks/milestone.json +3 -0
  46. package/samples/blocks/session-notes.json +1 -0
  47. package/samples/conception.json +308 -15
  48. package/samples/migrations.json +8 -0
  49. package/samples/schemas/context-contracts.schema.json +4 -0
  50. package/samples/schemas/conventions.schema.json +4 -0
  51. package/samples/schemas/decisions.schema.json +4 -0
  52. package/samples/schemas/features.schema.json +4 -0
  53. package/samples/schemas/framework-gaps.schema.json +4 -0
  54. package/samples/schemas/issues.schema.json +28 -0
  55. package/samples/schemas/layer-plans.schema.json +4 -0
  56. package/samples/schemas/milestone.schema.json +79 -0
  57. package/samples/schemas/phase.schema.json +4 -0
  58. package/samples/schemas/rationale.schema.json +4 -0
  59. package/samples/schemas/requirements.schema.json +4 -0
  60. package/samples/schemas/research.schema.json +4 -0
  61. package/samples/schemas/session-notes.schema.json +89 -0
  62. package/samples/schemas/spec-reviews.schema.json +4 -0
  63. package/samples/schemas/story.schema.json +8 -0
  64. package/samples/schemas/tasks.schema.json +4 -0
  65. package/samples/schemas/verification.schema.json +4 -0
  66. package/samples/schemas/work-orders.schema.json +4 -0
  67. package/schemas/config.schema.json +77 -3
  68. package/schemas/migrations.schema.json +25 -0
  69. package/skill-narrative.md +7 -5
  70. package/skills/pi-context/SKILL.md +44 -49
  71. package/skills/pi-context/references/bundled-resources.md +5 -1
@@ -36,14 +36,16 @@ Update fields on an item in a project block array. Finds by predicate field matc
36
36
  </tool>
37
37
 
38
38
  <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.
39
+ 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.
40
40
 
41
- *Create a relation/edge between two items (parentchild under a relation_type)*
41
+ *Create a relation/edge between two items (raw --parent/--child, or role-typed --primary/--counter mapped via role_direction)*
42
42
 
43
43
  | Parameter | Type | Required | Description |
44
44
  |-----------|------|----------|-------------|
45
- | `parent` | string | yes | Canonical id (or lens bin name) of the parent endpoint |
46
- | `child` | string | yes | Canonical id of the child endpoint |
45
+ | `parent` | string | no | Parent-endpoint selector (canonical id / <alias>:<refname> / lens bin) RAW orientation. Mutually exclusive with --primary/--counter. |
46
+ | `child` | string | no | Child-endpoint selector RAW orientation. Mutually exclusive with --primary/--counter. |
47
+ | `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. |
48
+ | `counter` | string | no | Selector of the endpoint holding the relation's COUNTER role (ROLE-TYPED orientation). Requires --primary. |
47
49
  | `relation_type` | string | yes | Registered relation_type canonical_id / hierarchy edge type / lens id |
48
50
  | `ordinal` | integer | no | Optional sibling-ordering within (parent, relation_type) |
49
51
  | `dryRun` | boolean | no | Preview without writing relations.json |
@@ -63,7 +65,7 @@ Remove the single closure-table relation (edge) matching parent+child+relation_t
63
65
  </tool>
64
66
 
65
67
  <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.
68
+ 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.
67
69
 
68
70
  *Atomically swap one relation/edge for another in a single write*
69
71
 
@@ -80,13 +82,13 @@ Atomically replace one closure-table relation with another in a SINGLE write (no
80
82
  </tool>
81
83
 
82
84
  <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.
85
+ 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.
84
86
 
85
- *Create many relations/edges between items in one write*
87
+ *Create many relations/edges between items in one write (raw or role-typed per edge)*
86
88
 
87
89
  | Parameter | Type | Required | Description |
88
90
  |-----------|------|----------|-------------|
89
- | `edges` | unknown | yes | JSON array of { parent, child, relation_type, ordinal? } selector objects (parent/child are id/lens-bin selectors) |
91
+ | `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
92
  | `dryRun` | boolean | no | Preview without writing relations.json |
91
93
  </tool>
92
94
 
@@ -212,7 +214,7 @@ Get derived context state — source metrics, block summaries, planning lifecycl
212
214
  </tool>
213
215
 
214
216
  <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.
217
+ 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
218
 
217
219
  *Report installed-vs-catalog schema drift + the version gap for behind schemas (read-only)*
218
220
 
@@ -267,9 +269,9 @@ Fetch and print the verbatim catalog schema body (raw JSON Schema: properties/de
267
269
  </tool>
268
270
 
269
271
  <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.
272
+ 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 has not reached the complete bucket is reported in blockedBy and held out of nextActions; a target reaching its complete status releases it. A substrate whose config declares no `state_derivation` reports focus 'state-derivation not configured' with empty arrays. No writes; nothing hand-stored.
271
273
 
272
- *Derive current project state — focus, in-flight, next actions, blocked*
274
+ *Derive current project state from the config-declared state_derivation registry — focus, in-flight, ranked next actions, blocked, milestone rollups*
273
275
 
274
276
  </tool>
275
277
 
@@ -343,7 +345,7 @@ Commit the reconciliation of a schema merge conflict surfaced by update. Run thi
343
345
  </tool>
344
346
 
345
347
  <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.
348
+ 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
349
 
348
350
  *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
351
 
@@ -353,7 +355,7 @@ Commit the resolution of a blocked schema surfaced by update. Run AFTER fixing t
353
355
  </tool>
354
356
 
355
357
  <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.
358
+ 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
359
 
358
360
  *Declare a schema version-bump migration (identity or declarative-transform) into migrations.json*
359
361
 
@@ -386,7 +388,7 @@ Adopt the canonical packaged conception (samples/conception.json) as this substr
386
388
  </tool>
387
389
 
388
390
  <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.
391
+ 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
392
 
391
393
  *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
394
 
@@ -396,9 +398,9 @@ Install (materialize) the schemas and starter blocks declared in config.json's i
396
398
  </tool>
397
399
 
398
400
  <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.
401
+ 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
402
 
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)*
403
+ *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
404
 
403
405
  | Parameter | Type | Required | Description |
404
406
  |-----------|------|----------|-------------|
@@ -561,7 +563,7 @@ Project a config-declared lens (config.lenses[]) as a binned item-view. Without
561
563
  </tool>
562
564
 
563
565
  <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).
566
+ 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
567
 
566
568
  *Walk closure-table descendants under a relation_type*
567
569
 
@@ -572,7 +574,7 @@ Walk closure-table descendants of a parent id under a given relation_type. Retur
572
574
  </tool>
573
575
 
574
576
  <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).
577
+ 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
578
 
577
579
  *Walk closure-table ancestors under a relation_type*
578
580
 
@@ -606,39 +608,23 @@ Compose a ContextBundle for a work-unit by reading its context-contract (by unit
606
608
  </tool>
607
609
 
608
610
  <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'.
611
+ 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
612
 
611
- *Load a roadmap by id*
613
+ *Load the derived milestone roadmap view*
612
614
 
613
- | Parameter | Type | Required | Description |
614
- |-----------|------|----------|-------------|
615
- | `roadmapId` | string | yes | ROADMAP-NNN id from <config.root>/roadmap.json |
616
615
  </tool>
617
616
 
618
617
  <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.
618
+ 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
619
 
621
- *Render a roadmap as markdown*
620
+ *Render the derived milestone roadmap as markdown*
622
621
 
623
- | Parameter | Type | Required | Description |
624
- |-----------|------|----------|-------------|
625
- | `roadmapId` | string | yes | ROADMAP-NNN id from <config.root>/roadmap.json |
626
622
  </tool>
627
623
 
628
624
  <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.
625
+ 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
626
 
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*
627
+ *Validate the derived milestone roadmap*
642
628
 
643
629
  </tool>
644
630
 
@@ -648,7 +634,7 @@ List every roadmap in <config.root>/roadmap.json with id, title, optional status
648
634
  <command name="/context">
649
635
  Context state management
650
636
 
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`
637
+ Subcommands: `init`, `switch`, `list`, `archive`, `install`, `check-status`, `accept-all`, `view`, `lens-curate`, `roadmap-view`, `roadmap-validate`, `status`, `add-work`, `validate`, `help`
652
638
  </command>
653
639
 
654
640
  </commands_reference>
@@ -658,7 +644,7 @@ Subcommands: `init`, `switch`, `list`, `archive`, `install`, `check-status`, `ac
658
644
  </events>
659
645
 
660
646
  <bundled_resources>
661
- 12 schemas, 34 samples bundled.
647
+ 12 schemas, 38 samples bundled.
662
648
  See references/bundled-resources.md for full inventory.
663
649
  </bundled_resources>
664
650
 
@@ -683,7 +669,9 @@ Names valid for the `installed_blocks` array in `<substrate-dir>/config.json`. I
683
669
  | `context-contracts` | `samples/blocks/context-contracts.json` |
684
670
  | `phase` | `samples/blocks/phase.json` |
685
671
  | `story` | `samples/blocks/story.json` |
672
+ | `milestone` | `samples/blocks/milestone.json` |
686
673
  | `work-orders` | `samples/blocks/work-orders.json` |
674
+ | `session-notes` | `samples/blocks/session-notes.json` |
687
675
 
688
676
  </installable_blocks>
689
677
 
@@ -708,7 +696,9 @@ Names valid for the `installed_schemas` array in `<substrate-dir>/config.json`.
708
696
  | `context-contracts` | `samples/schemas/context-contracts.schema.json` |
709
697
  | `phase` | `samples/schemas/phase.schema.json` |
710
698
  | `story` | `samples/schemas/story.schema.json` |
699
+ | `milestone` | `samples/schemas/milestone.schema.json` |
711
700
  | `work-orders` | `samples/schemas/work-orders.schema.json` |
701
+ | `session-notes` | `samples/schemas/session-notes.schema.json` |
712
702
 
713
703
  </installable_schemas>
714
704
 
@@ -722,7 +712,7 @@ Names valid for the `installed_schemas` array in `<substrate-dir>/config.json`.
722
712
  | `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
713
  | `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
714
  | `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? |
715
+ | `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
716
  | `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
717
  | `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
718
  | `rationale` | Design Rationale | `rationales` | id, title, narrative, phase? (integer), oid?, content_hash?, content_parent? |
@@ -732,8 +722,10 @@ Names valid for the `installed_schemas` array in `<substrate-dir>/config.json`.
732
722
  | `conventions` | Conventions | `rules` | id, description, enforcement (string (lint|test|review|manual)), severity (string (error|warning|info)), oid?, content_hash?, content_parent? |
733
723
  | `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
724
  | `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? |
725
+ | `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? |
726
+ | `milestone` | Milestones | `milestones` | id, name, status (string (planned|reached)), release?, created_by?, created_at?, modified_by?, modified_at?, oid?, content_hash?, content_parent? |
736
727
  | `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? |
728
+ | `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
729
 
738
730
  **Status Enums:**
739
731
 
@@ -765,6 +757,7 @@ Names valid for the `installed_schemas` array in `<substrate-dir>/config.json`.
765
757
  | `conventions` | `severity` | error, warning, info |
766
758
  | `phase` | `status` | planned, in-progress, completed |
767
759
  | `story` | `status` | proposed, ready, in-progress, in-review, complete, blocked |
760
+ | `milestone` | `status` | planned, reached |
768
761
  | `work-orders` | `status` | proposed, in-progress, real-check-passed, real-check-failed, completed, cancelled |
769
762
 
770
763
  </planning_vocabulary>
@@ -774,7 +767,7 @@ pi-context manages structured project state in the substrate directory — a dir
774
767
  </objective>
775
768
 
776
769
  <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.
770
+ 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
771
 
779
772
  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
773
  </block_files>
@@ -788,11 +781,11 @@ Every block write validates against `<root>/schemas/<blockname>.schema.json`. If
788
781
  </schema_validation>
789
782
 
790
783
  <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`.
784
+ `/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
785
  </context_init>
793
786
 
794
787
  <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.
788
+ `/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
789
  </context_accept_all>
797
790
 
798
791
  <context_install>
@@ -820,7 +813,7 @@ Source-of-truth-drift invariant: `validateContext` requires the active `config.s
820
813
  </cross_substrate>
821
814
 
822
815
  <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`.
816
+ 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
817
  </schema_versioning>
825
818
 
826
819
  <lens_views>
@@ -828,6 +821,8 @@ Lenses are named projections over a target block. A lens declares `id`, `target`
828
821
 
829
822
  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
823
 
824
+ 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.
825
+
831
826
  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
827
 
833
828
  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 +896,7 @@ On `session_start`, checks npm registry for newer versions of `@davidorex/pi-pro
901
896
  </update_check>
902
897
 
903
898
  <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.
899
+ - `<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
900
  - `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
901
  - Block writes validate against schemas — invalid data rejected with specific error
907
902
  - `/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`