@davidorex/pi-context 0.30.0 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/README.md +21 -6
  3. package/dist/block-api.d.ts +13 -0
  4. package/dist/block-api.d.ts.map +1 -1
  5. package/dist/block-api.js +1 -1
  6. package/dist/block-api.js.map +1 -1
  7. package/dist/content-hash.d.ts +13 -0
  8. package/dist/content-hash.d.ts.map +1 -1
  9. package/dist/content-hash.js +16 -0
  10. package/dist/content-hash.js.map +1 -1
  11. package/dist/context-dir.d.ts +12 -0
  12. package/dist/context-dir.d.ts.map +1 -1
  13. package/dist/context-dir.js +14 -0
  14. package/dist/context-dir.js.map +1 -1
  15. package/dist/context.d.ts +60 -0
  16. package/dist/context.d.ts.map +1 -1
  17. package/dist/context.js +44 -0
  18. package/dist/context.js.map +1 -1
  19. package/dist/index.d.ts +534 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +1879 -20
  22. package/dist/index.js.map +1 -1
  23. package/dist/migration-registry-loader.d.ts +16 -0
  24. package/dist/migration-registry-loader.d.ts.map +1 -1
  25. package/dist/migration-registry-loader.js +33 -0
  26. package/dist/migration-registry-loader.js.map +1 -1
  27. package/dist/ops-registry.d.ts +16 -0
  28. package/dist/ops-registry.d.ts.map +1 -1
  29. package/dist/ops-registry.js +270 -8
  30. package/dist/ops-registry.js.map +1 -1
  31. package/dist/pending-blocked-store.d.ts +83 -0
  32. package/dist/pending-blocked-store.d.ts.map +1 -0
  33. package/dist/pending-blocked-store.js +93 -0
  34. package/dist/pending-blocked-store.js.map +1 -0
  35. package/dist/schema-merge.d.ts +26 -0
  36. package/dist/schema-merge.d.ts.map +1 -0
  37. package/dist/schema-merge.js +176 -0
  38. package/dist/schema-merge.js.map +1 -0
  39. package/package.json +2 -1
  40. package/samples/conception.json +50 -0
  41. package/samples/schemas/framework-gaps.schema.json +1 -1
  42. package/samples/schemas/issues.schema.json +2 -2
  43. package/samples/schemas/layer-plans.schema.json +2 -2
  44. package/samples/schemas/research.schema.json +1 -1
  45. package/samples/schemas/work-orders.schema.json +2 -2
  46. package/schemas/config.schema.json +25 -1
  47. package/schemas/pending-blocked.schema.json +190 -0
  48. package/skill-narrative.md +7 -5
  49. package/skills/pi-context/SKILL.md +90 -7
  50. package/skills/pi-context/references/bundled-resources.md +2 -1
@@ -29,10 +29,10 @@ import { gatherExecutionContext } from "./execution-context.js";
29
29
  // registerAll runs at extension-load time, after both modules' top-level
30
30
  // function bindings exist, and the helpers are only referenced inside op `run`
31
31
  // closures (lazy), never at this module's top level.
32
- import { archiveSubstrate, initProject, listSubstrates, switchAndCreate, switchToExisting, switchToPrevious, } from "./index.js";
33
- import { edgesForLensByName, findReferencesInRepo, validateContextRelations, walkAncestorsByLens, walkLensDescendants, } from "./lens-view.js";
32
+ import { archiveSubstrate, checkStatus, initProject, installContext, listSubstrates, readCatalogSchemaText, resolveBlocked, resolveConflict, switchAndCreate, switchToExisting, switchToPrevious, updateContext, validateBlockItemsAgainstCatalog, } from "./index.js";
33
+ import { edgesForLensByName, findReferencesInRepo, loadLensView, validateContextRelations, walkAncestorsByLens, walkLensDescendants, } from "./lens-view.js";
34
34
  import { promoteItem } from "./promote-item.js";
35
- import { addressInto, renderReadText, structureForRead } from "./read-element.js";
35
+ import { addressInto, pageArray, renderReadText, structureForRead } from "./read-element.js";
36
36
  import { renameCanonicalId } from "./rename-canonical-id.js";
37
37
  import { listRoadmaps, loadRoadmap, renderRoadmap, validateRoadmaps } from "./roadmap-plan.js";
38
38
  import { samplesCatalog } from "./samples-catalog.js";
@@ -146,6 +146,9 @@ export const ops = [
146
146
  label: "Append Block Item",
147
147
  description: "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.",
148
148
  promptSnippet: "Append items to project blocks (issues, decisions, or any user-defined block)",
149
+ examples: [
150
+ `pi-context append-block-item --block framework-gaps --arrayKey gaps --autoId true --item @/tmp/fgap.json --writer '{"kind":"human","user":"you@example.com"}' --json`,
151
+ ],
149
152
  parameters: Type.Object({
150
153
  block: Type.String({ description: "Block name (e.g., 'issues', 'decisions')" }),
151
154
  arrayKey: Type.String({ description: "Array key in the block (e.g., 'issues', 'decisions')" }),
@@ -183,6 +186,9 @@ export const ops = [
183
186
  label: "Update Block Item",
184
187
  description: "Update fields on an item in a project block array. Finds by predicate field match.",
185
188
  promptSnippet: "Update items in project blocks — change status, add details, mark resolved",
189
+ examples: [
190
+ `pi-context update-block-item --block tasks --arrayKey tasks --match '{"id":"TASK-001"}' --updates '{"status":"in-progress"}' --writer '{"kind":"human","user":"you@example.com"}' --json`,
191
+ ],
186
192
  parameters: Type.Object({
187
193
  block: Type.String({ description: "Block name (e.g., 'issues', 'decisions')" }),
188
194
  arrayKey: Type.String({ description: "Array key in the block" }),
@@ -210,6 +216,9 @@ export const ops = [
210
216
  "integrity (endpoints resolve, relation_type registered, no cycle) is NOT checked here — run context-validate " +
211
217
  "after. Creates relations.json if absent.",
212
218
  promptSnippet: "Create a relation/edge between two items (parent→child under a relation_type)",
219
+ examples: [
220
+ `pi-context append-relation --parent VER-001 --child TASK-001 --relation_type verification_verifies_item --writer '{"kind":"human","user":"you@example.com"}' --json`,
221
+ ],
213
222
  parameters: Type.Object({
214
223
  parent: Type.String({ description: "Canonical id (or lens bin name) of the parent endpoint" }),
215
224
  child: Type.String({ description: "Canonical id of the child endpoint" }),
@@ -252,6 +261,9 @@ export const ops = [
252
261
  "inverse of append-relation (ordinal is NOT part of identity). An absent edge is an idempotent no-op. " +
253
262
  "Reference integrity is NOT checked here — run context-validate after if the removal changes resolvability.",
254
263
  promptSnippet: "Remove a relation/edge between two items (the inverse of append-relation)",
264
+ examples: [
265
+ `pi-context remove-relation --parent VER-001 --child TASK-001 --relation_type verification_verifies_item --writer '{"kind":"human","user":"you@example.com"}' --json`,
266
+ ],
255
267
  parameters: Type.Object({
256
268
  parent: Type.String({ description: "Canonical id (or lens bin name) of the parent endpoint" }),
257
269
  child: Type.String({ description: "Canonical id of the child endpoint" }),
@@ -286,6 +298,9 @@ export const ops = [
286
298
  "the new edge is written with its optional ordinal. If the old edge is absent the call is effectively an append of " +
287
299
  "the new edge. Reference integrity is NOT checked here — run context-validate after.",
288
300
  promptSnippet: "Atomically swap one relation/edge for another in a single write",
301
+ examples: [
302
+ `pi-context replace-relation --old_parent TASK-001 --old_child DEC-0001 --old_relation_type task_informed_by_decision --parent TASK-001 --child DEC-0002 --relation_type task_informed_by_decision --writer '{"kind":"human","user":"you@example.com"}' --json`,
303
+ ],
289
304
  parameters: Type.Object({
290
305
  old_parent: Type.String({ description: "Parent endpoint selector of the edge to remove" }),
291
306
  old_child: Type.String({ description: "Child endpoint selector of the edge to remove" }),
@@ -345,6 +360,9 @@ export const ops = [
345
360
  "on-disk edges AND earlier edges in the same batch). Returns appended/skipped counts. Reference integrity is NOT " +
346
361
  "checked here — run context-validate after. Creates relations.json if absent.",
347
362
  promptSnippet: "Create many relations/edges between items in one write",
363
+ examples: [
364
+ `pi-context append-relations --edges '[{"parent":"FEAT-008","child":"TASK-042","relation_type":"feature_decomposed_into_task"}]' --writer '{"kind":"human","user":"you@example.com"}' --json`,
365
+ ],
348
366
  parameters: Type.Object({
349
367
  edges: Type.Unknown({
350
368
  description: "JSON array of { parent, child, relation_type, ordinal? } selector objects (parent/child are id/lens-bin selectors)",
@@ -382,6 +400,9 @@ export const ops = [
382
400
  "REPLACED (full-shape replacement, not shallow-merge — use update-block-item for merge); otherwise the item is " +
383
401
  "appended. Schema validation is automatic. idField defaults to 'id'.",
384
402
  promptSnippet: "Append-or-replace a full block item by id (replacement, not merge)",
403
+ examples: [
404
+ `pi-context upsert-block-item --block tasks --arrayKey tasks --item @/tmp/task.json --writer '{"kind":"human","user":"you@example.com"}' --json`,
405
+ ],
385
406
  parameters: Type.Object({
386
407
  block: Type.String({ description: "Block name (e.g., 'issues', 'decisions')" }),
387
408
  arrayKey: Type.String({ description: "Array key in the block (e.g., 'issues', 'decisions')" }),
@@ -423,6 +444,9 @@ export const ops = [
423
444
  "(unresolvable/non-item source, unregistered destination alias, unregistered destination relation_type, refname " +
424
445
  "collision) throw. Pass dryRun to compute the destination without writing.",
425
446
  promptSnippet: "Promote an item into another substrate as a derived copy with a lineage edge",
447
+ examples: [
448
+ `pi-context promote-item --source DEC-0001 --destinationSubstrate .context --writer '{"kind":"human","user":"you@example.com"}' --json`,
449
+ ],
426
450
  parameters: Type.Object({
427
451
  source: Type.String({ description: "Source item selector (bare refname / <alias>:<refname>)" }),
428
452
  destinationSubstrate: Type.String({ description: "Registered destination substrate alias" }),
@@ -461,6 +485,9 @@ export const ops = [
461
485
  label: "Append Block Nested Item",
462
486
  description: "Append an item to a nested array on a parent-array item in a project block. Schema validation is automatic.",
463
487
  promptSnippet: "Append items to nested arrays inside parent items (e.g., findings inside a review)",
488
+ examples: [
489
+ `pi-context append-block-nested-item --block spec-reviews --arrayKey reviews --match '{"id":"REVIEW-001"}' --nestedKey findings --item @/tmp/finding.json --writer '{"kind":"human","user":"you@example.com"}' --json`,
490
+ ],
464
491
  parameters: Type.Object({
465
492
  block: Type.String({ description: "Block name (e.g., 'spec-reviews')" }),
466
493
  arrayKey: Type.String({ description: "Parent array key (e.g., 'reviews')" }),
@@ -493,6 +520,9 @@ export const ops = [
493
520
  label: "Update Block Nested Item",
494
521
  description: "Update fields on a nested-array item inside a parent-array item in a project block. Finds parent and nested by predicate field match. Throws on parent or nested miss (mirrors update-block-item semantics).",
495
522
  promptSnippet: "Update items inside nested arrays — change finding state, mark resolved",
523
+ examples: [
524
+ `pi-context update-block-nested-item --block spec-reviews --arrayKey reviews --match '{"id":"REVIEW-001"}' --nestedKey findings --nestedMatch '{"id":"F-001"}' --updates '{"state":"resolved"}' --writer '{"kind":"human","user":"you@example.com"}' --json`,
525
+ ],
496
526
  parameters: Type.Object({
497
527
  block: Type.String({ description: "Block name (e.g., 'spec-reviews')" }),
498
528
  arrayKey: Type.String({ description: "Parent array key (e.g., 'reviews')" }),
@@ -527,6 +557,9 @@ export const ops = [
527
557
  label: "Remove Block Item",
528
558
  description: "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.",
529
559
  promptSnippet: "Remove items from project blocks — prune retracted issues, dedupe entries",
560
+ examples: [
561
+ `pi-context remove-block-item --block issues --arrayKey issues --match '{"id":"ISSUE-001"}' --writer '{"kind":"human","user":"you@example.com"}' --json`,
562
+ ],
530
563
  parameters: Type.Object({
531
564
  block: Type.String({ description: "Block name (e.g., 'issues')" }),
532
565
  arrayKey: Type.String({ description: "Top-level array key (e.g., 'issues')" }),
@@ -546,6 +579,9 @@ export const ops = [
546
579
  label: "Remove Block Nested Item",
547
580
  description: "Remove items matching a predicate from a nested array on a parent-array item in a project block. Throws on parent miss; returns { removed: 0 } on nested miss without throwing.",
548
581
  promptSnippet: "Remove nested items — drop rejected findings, retract nested references",
582
+ examples: [
583
+ `pi-context remove-block-nested-item --block spec-reviews --arrayKey reviews --match '{"id":"REVIEW-001"}' --nestedKey findings --nestedMatch '{"id":"F-001"}' --writer '{"kind":"human","user":"you@example.com"}' --json`,
584
+ ],
549
585
  parameters: Type.Object({
550
586
  block: Type.String({ description: "Block name (e.g., 'spec-reviews')" }),
551
587
  arrayKey: Type.String({ description: "Parent array key (e.g., 'reviews')" }),
@@ -574,6 +610,7 @@ export const ops = [
574
610
  label: "Read Block Dir",
575
611
  description: "Enumerate and parse all .json files in a <substrate-dir>/<subdir>/ directory, returned as a sorted array. Missing directories return [].",
576
612
  promptSnippet: "Enumerate project block subdirectories (phases, schemas, etc.) as parsed JSON",
613
+ examples: [`pi-context read-block-dir --subdir phases --json`],
577
614
  parameters: Type.Object({
578
615
  subdir: Type.String({ description: "Subdirectory under the substrate dir (e.g., 'phases', 'schemas')" }),
579
616
  }),
@@ -589,6 +626,7 @@ export const ops = [
589
626
  label: "Read Block",
590
627
  description: "Read a project block file as structured JSON.",
591
628
  promptSnippet: "Read a project block as structured JSON",
629
+ examples: [`pi-context read-block --block tasks --json`],
592
630
  parameters: Type.Object({
593
631
  block: Type.String({ description: "Block name (e.g., 'issues', 'tasks', 'requirements')" }),
594
632
  }),
@@ -611,6 +649,9 @@ export const ops = [
611
649
  label: "Write Block",
612
650
  description: "Write or replace an entire project block with schema validation.",
613
651
  promptSnippet: "Write or replace a project block with schema validation",
652
+ examples: [
653
+ `pi-context write-block --block architecture --data @/tmp/architecture.json --writer '{"kind":"human","user":"you@example.com"}' --json`,
654
+ ],
614
655
  parameters: Type.Object({
615
656
  block: Type.String({ description: "Block name (e.g., 'project', 'architecture')" }),
616
657
  data: Type.Unknown({ description: "Complete block data — must conform to block schema" }),
@@ -628,6 +669,7 @@ export const ops = [
628
669
  label: "Context Status",
629
670
  description: "Get derived context state — source metrics, block summaries, planning lifecycle status.",
630
671
  promptSnippet: "Get context state — source metrics, block summaries, planning lifecycle status",
672
+ examples: [`pi-context context-status --json`],
631
673
  parameters: Type.Object({}),
632
674
  surface: "use",
633
675
  run(cwd, _params) {
@@ -635,11 +677,24 @@ export const ops = [
635
677
  return { json: result };
636
678
  },
637
679
  },
680
+ {
681
+ name: "context-check-status",
682
+ label: "Context Check Status",
683
+ description: "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.",
684
+ promptSnippet: "Report installed-vs-catalog schema drift + the version gap for behind schemas (read-only)",
685
+ examples: [`pi-context context-check-status --json`],
686
+ parameters: Type.Object({}),
687
+ surface: "use",
688
+ run(cwd, _params) {
689
+ return { json: checkStatus(cwd) };
690
+ },
691
+ },
638
692
  {
639
693
  name: "context-validate",
640
694
  label: "Context Validate",
641
695
  description: "Validate cross-block referential integrity — check that IDs referenced across blocks exist.",
642
696
  promptSnippet: "Validate cross-block referential integrity",
697
+ examples: [`pi-context context-validate --json`],
643
698
  parameters: Type.Object({}),
644
699
  surface: "use",
645
700
  run(cwd, _params) {
@@ -652,6 +707,7 @@ export const ops = [
652
707
  label: "Read Config",
653
708
  description: "Read the substrate config.json as structured JSON — vocabulary, lenses, relation_types, status_buckets, display_strings, layers, block_kinds, installed_schemas, installed_blocks. Address ONE registry/map via `registry` (e.g. relation_types) and ONE entry within it via `id` (canonical_id) instead of reading the whole config.",
654
709
  promptSnippet: "Read project config — vocabulary, lenses, relation_types, status_buckets",
710
+ examples: [`pi-context read-config --registry block_kinds --id tasks --json`],
655
711
  parameters: Type.Object({
656
712
  registry: Type.Optional(Type.String({
657
713
  description: "Address ONE config registry/map by key (e.g. 'relation_types', 'lenses', 'block_kinds', 'status_buckets')",
@@ -673,10 +729,14 @@ export const ops = [
673
729
  if (!entry.found) {
674
730
  return `read-config: entry not found in ${params.registry} — ${entry.resolved}`;
675
731
  }
676
- const read = structureForRead(entry.value, { label: `config.${params.registry}.${params.id}` });
732
+ const read = structureForRead(entry.value, {
733
+ whole: true,
734
+ label: `config.${params.registry}.${params.id}`,
735
+ });
677
736
  return { read };
678
737
  }
679
738
  const read = structureForRead(reg.value, {
739
+ whole: true,
680
740
  label: `config.${params.registry}`,
681
741
  overCapDirective: {
682
742
  tool: "read-config",
@@ -730,7 +790,7 @@ export const ops = [
730
790
  if (tool === undefined) {
731
791
  return `list-tools: tool not found — name=${params.name}`;
732
792
  }
733
- const read = structureForRead(tool, { label: `tool ${params.name}` });
793
+ const read = structureForRead(tool, { whole: true, label: `tool ${params.name}` });
734
794
  return { read };
735
795
  }
736
796
  // Default: compact index (FGAP-101) — name + param count + one-line description.
@@ -757,6 +817,7 @@ export const ops = [
757
817
  label: "Read Samples Catalog",
758
818
  description: "Enumerate installable sample block kinds (packaged view): per kind — title, description, item shape, applicable relation_types (as source/target), invariants, lenses — plus top-level relation_type/lens/invariant/layer/status_bucket registries. Package-intrinsic: reads the extension's bundled samples catalog, independent of any project. Optional `kind` returns one packaged kind.",
759
819
  promptSnippet: "Discover installable sample block kinds — title, shape, relation_types, invariants, lenses",
820
+ examples: [`pi-context read-samples-catalog --kind tasks --json`],
760
821
  parameters: Type.Object({
761
822
  kind: Type.Optional(Type.String({ description: "Filter to one block_kind canonical_id (e.g. 'tasks')" })),
762
823
  }),
@@ -768,17 +829,48 @@ export const ops = [
768
829
  const read = structureForRead(catalog, {
769
830
  label: params.kind ? `samples kind=${params.kind}` : "samples catalog",
770
831
  // Whole catalog → narrow by kind; a single kind has no finer
771
- // addressing (edge → head-leading marker, no directive).
772
- ...(params.kind ? {} : { overCapDirective: { tool: "read-samples-catalog", hint: "kind=<canonical_id>" } }),
832
+ // addressing (edge → head-leading marker, no directive). The
833
+ // single-kind read is an addressed node return it whole (capped),
834
+ // never a 50-item page of an incidental array child.
835
+ ...(params.kind
836
+ ? { whole: true }
837
+ : { overCapDirective: { tool: "read-samples-catalog", hint: "kind=<canonical_id>" } }),
773
838
  });
774
839
  return { read };
775
840
  },
776
841
  },
842
+ {
843
+ name: "read-catalog-schema",
844
+ label: "Read Catalog Schema",
845
+ description: "Fetch and print the verbatim catalog schema body (raw JSON Schema: properties/definitions/$id) for a named block kind — diffable locally against the installed `<substrate>/schemas/<name>.schema.json` without touching node_modules. Read-only; the projection-returning sibling is read-samples-catalog.",
846
+ promptSnippet: "Fetch and print the verbatim catalog schema body for a named block kind (raw JSON Schema, diffable locally)",
847
+ examples: ["pi-context read-catalog-schema --kind tasks", "pi-context read-catalog-schema --kind tasks --json"],
848
+ parameters: Type.Object({
849
+ kind: Type.String({ description: "Catalog block_kind canonical_id (e.g. 'tasks')" }),
850
+ }),
851
+ surface: "use",
852
+ // The catalog schema file carries its own trailing newline (`}\n`); emit the file
853
+ // bytes exactly — preserving that single newline, appending none — so
854
+ // `read-catalog-schema --kind <k> | diff <installed> -` shows no phantom line when
855
+ // content matches (the pre-flag defect was the print path appending a second
856
+ // newline, doubling it to `}\n\n`).
857
+ verbatimText: true,
858
+ run(_cwd, params) {
859
+ // Package-intrinsic: reads the extension's bundled catalog schema file,
860
+ // not the project substrate — cwd is unused (like read-samples-catalog).
861
+ // Returns the RAW TEXT bytes as a prose-string OpResult so the verbatim
862
+ // catalog body prints as-is (renderOpResultText) and rides the --json
863
+ // envelope as a string; no {json}/{read} wrap that would re-serialize and
864
+ // alter the bytes the operator diffs.
865
+ return readCatalogSchemaText(params.kind).text;
866
+ },
867
+ },
777
868
  {
778
869
  name: "context-current-state",
779
870
  label: "Context Current State",
780
871
  description: "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.",
781
872
  promptSnippet: "Derive current project state — focus, in-flight, next actions, blocked",
873
+ examples: [`pi-context context-current-state --json`],
782
874
  parameters: Type.Object({}),
783
875
  surface: "use",
784
876
  run(cwd, _params) {
@@ -791,6 +883,7 @@ export const ops = [
791
883
  label: "Context Bootstrap State",
792
884
  description: "Derive the substrate bootstrap state for the cwd, purely from the filesystem: 'no-pointer' | 'no-config' | 'skeleton' | 'not-installed' | 'ready', plus the resolved contextDir and any declared-but-unmaterialized installed assets. Bootstrap (/context init or /context switch -c <new-dir>) now writes a minimal schema-valid config empty of vocabulary, so a freshly-bootstrapped substrate lands at 'skeleton' — onward via /context accept-all (adopt the packaged catalog, then /context install) OR amend-config / edit (build a custom vocabulary). Unlike every other tool, this NEVER throws on an un-bootstrapped substrate — it returns 'no-pointer' so you can detect a fresh substrate and tell the user to run /context init <substrate-dir> → /context accept-all → /context install (bootstrap requires user authorization via interactive confirmation). No writes.",
793
885
  promptSnippet: "Derive substrate bootstrap state — no-pointer | no-config | skeleton | not-installed | ready (never throws pre-bootstrap)",
886
+ examples: [`pi-context context-bootstrap-state --json`],
794
887
  parameters: Type.Object({}),
795
888
  surface: "use",
796
889
  run(cwd, _params) {
@@ -803,6 +896,7 @@ export const ops = [
803
896
  label: "Rename Canonical Id",
804
897
  description: "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.",
805
898
  promptSnippet: "Rename a canonical_id (item/relation_type/lens/layer) across substrate; dryRun to preview",
899
+ examples: [`pi-context rename-canonical-id --kind item --oldId TASK-001 --newId TASK-100 --dryRun true --json`],
806
900
  parameters: Type.Object({
807
901
  kind: Type.String({ description: "One of: item | relation_type | lens | layer" }),
808
902
  oldId: Type.String({ description: "Current canonical_id to rename from" }),
@@ -826,6 +920,9 @@ export const ops = [
826
920
  "relation_type / lens / layer / block_kind) is NOT checked here — run context-validate after. dryRun previews " +
827
921
  "without writing.",
828
922
  promptSnippet: "Add/replace/remove one entry in a config.json registry (vocabulary, lenses, invariants, status_buckets)",
923
+ examples: [
924
+ `pi-context amend-config --registry relation_types --operation add --key task_blocks_task --entry @/tmp/relation-type.json --writer '{"kind":"human","user":"you@example.com"}' --json`,
925
+ ],
829
926
  parameters: Type.Object({
830
927
  registry: Type.String({
831
928
  description: "One of: block_kinds | relation_types | lenses | layers | invariants | status_buckets | display_strings | naming | installed_schemas | installed_blocks | hierarchy",
@@ -869,6 +966,7 @@ export const ops = [
869
966
  label: "Read Schema",
870
967
  description: "Read a substrate schema by name as parsed JSON. Returns null when the schema file is absent. Address ONE property via `path` (dotted/bracket, e.g. properties.tasks.items.properties.status) instead of reading the whole schema.",
871
968
  promptSnippet: "Read a block schema as structured JSON — optionally address one property via `path`",
969
+ examples: [`pi-context read-schema --schemaName framework-gaps --path properties.gaps.items.required --json`],
872
970
  parameters: Type.Object({
873
971
  schemaName: Type.String({
874
972
  description: "Schema name without extension (e.g., 'tasks', 'decisions', 'issues')",
@@ -886,7 +984,7 @@ export const ops = [
886
984
  if (!addr.found) {
887
985
  return `read-schema: property not found — ${addr.resolved}`;
888
986
  }
889
- const read = structureForRead(addr.value, { label: `${params.schemaName} ${addr.resolved}` });
987
+ const read = structureForRead(addr.value, { whole: true, label: `${params.schemaName} ${addr.resolved}` });
890
988
  return { read };
891
989
  }
892
990
  const result = { schema, schemaPath: schemaPathStr };
@@ -910,6 +1008,9 @@ export const ops = [
910
1008
  "read/write of items declaring an older schema_version throws version-mismatch. Registering the block_kind " +
911
1009
  "that points at this schema is a separate step (amend-config block_kinds).",
912
1010
  promptSnippet: "Create or replace a block-kind JSON Schema (meta-validated, atomic)",
1011
+ examples: [
1012
+ `pi-context write-schema --operation create --schemaName tasks --schema @/tmp/tasks.schema.json --writer '{"kind":"human","user":"you@example.com"}' --json`,
1013
+ ],
913
1014
  parameters: Type.Object({
914
1015
  operation: Type.String({ description: "create | replace" }),
915
1016
  schemaName: Type.String({ description: "Schema name without extension (e.g., 'tasks')" }),
@@ -937,11 +1038,52 @@ export const ops = [
937
1038
  return `write-schema: ${verb} schema '${params.schemaName}' at ${result.schemaPath}`;
938
1039
  },
939
1040
  },
1041
+ {
1042
+ name: "resolve-conflict",
1043
+ label: "Resolve Schema Conflict",
1044
+ description: "Commit the reconciliation of a schema merge conflict surfaced by update. Run this AFTER reconciling a both-diverged conflict update reported: it writes the reconciled schema body (meta-validated, atomic, operation 'replace') AND advances the merge base for that schema to the packaged catalog body. Advancing the base is the step a bare write-schema lacks — without it, update's 3-way merge re-derives the SAME conflict on every subsequent run because the base never moves off the original pre-conflict body. With the base advanced to the catalog, the next update sees the schema as locally-modified (base === catalog ≠ your body) and the deterministic merge takes your reconciled body (base === theirs → ours) — auto-merging with zero conflicts and preserving your resolution. If schema is omitted, the current on-disk schema is treated as already reconciled and only the base is advanced. The calling agent runs this; no subordinate resolver is spawned.",
1045
+ promptSnippet: "Commit a reconciled schema conflict: write the resolved body + advance the merge base to the catalog so update stops re-reporting it (run after reconciling an update conflict)",
1046
+ examples: [
1047
+ `pi-context resolve-conflict --schemaName tasks --schema @/tmp/tasks.reconciled.json --writer '{"kind":"human","user":"you@example.com"}' --json`,
1048
+ ],
1049
+ parameters: Type.Object({
1050
+ schemaName: Type.String({ description: "Schema name without extension (e.g., 'tasks')" }),
1051
+ schema: Type.Optional(Type.Unknown({
1052
+ description: "The reconciled schema body R (whole JSON Schema object, draft-07; accepts a JSON string). If omitted, the current on-disk schema is treated as already reconciled and only the merge base is advanced.",
1053
+ })),
1054
+ }),
1055
+ surface: "use",
1056
+ authGated: true,
1057
+ run(cwd, params, ctx) {
1058
+ const result = resolveConflict(cwd, params.schemaName, params.schema, ctx);
1059
+ return { json: result };
1060
+ },
1061
+ },
1062
+ {
1063
+ name: "resolve-blocked",
1064
+ label: "Resolve Blocked",
1065
+ description: "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.",
1066
+ promptSnippet: "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)",
1067
+ examples: [`pi-context resolve-blocked --schemaName tasks --yes --json`],
1068
+ parameters: Type.Object({
1069
+ schemaName: Type.String({
1070
+ description: "Schema name with a pending-blocked entry (from update's blocked report)",
1071
+ }),
1072
+ }),
1073
+ surface: "use",
1074
+ authGated: true,
1075
+ run(cwd, params, ctx) {
1076
+ return { json: resolveBlocked(cwd, params.schemaName, ctx) };
1077
+ },
1078
+ },
940
1079
  {
941
1080
  name: "write-schema-migration",
942
1081
  label: "Write Schema Migration",
943
1082
  description: "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.",
944
1083
  promptSnippet: "Declare a schema version-bump migration (identity or declarative-transform) into migrations.json",
1084
+ examples: [
1085
+ `pi-context write-schema-migration --operation create --schemaName tasks --fromVersion 1.0.0 --toVersion 1.1.0 --kind identity --writer '{"kind":"human","user":"you@example.com"}' --json`,
1086
+ ],
945
1087
  parameters: Type.Object({
946
1088
  operation: Type.String({ description: "create | replace | remove" }),
947
1089
  schemaName: Type.String({ description: "Schema name without extension (e.g., 'tasks')." }),
@@ -975,6 +1117,7 @@ export const ops = [
975
1117
  label: "Context Init",
976
1118
  description: "Initialize the substrate dir: bootstrap pointer + dirs + a minimal schema-valid SKELETON config empty of vocabulary. Lands at the 'skeleton' bootstrap state — onward via accept-all (adopt the packaged catalog, then install) OR amend-config / edit (build a custom vocabulary).",
977
1119
  promptSnippet: "Initialize the substrate dir (bootstrap pointer + dirs + skeleton config; onward via accept-all OR amend-config/edit)",
1120
+ examples: [`pi-context context-init --contextDir .context --json`],
978
1121
  parameters: Type.Object({
979
1122
  contextDir: Type.String({
980
1123
  description: "Substrate dir name (e.g. .context). Required — no default.",
@@ -992,6 +1135,7 @@ export const ops = [
992
1135
  label: "Accept-All Conception",
993
1136
  description: "Adopt the canonical packaged conception (samples/conception.json) as this substrate's config.json (accept-all). Writes config only — run install after. Skeleton-aware: overwrites a SKELETON config (the empty-of-vocabulary config init / switch -c writes) but never a POPULATED one.",
994
1137
  promptSnippet: "Adopt the canonical conception as config (accept-all)",
1138
+ examples: [`pi-context context-accept-all --json`],
995
1139
  parameters: Type.Object({}),
996
1140
  surface: "use",
997
1141
  authGated: true,
@@ -1009,11 +1153,65 @@ export const ops = [
1009
1153
  return { json: result };
1010
1154
  },
1011
1155
  },
1156
+ {
1157
+ name: "context-install",
1158
+ label: "Context Install",
1159
+ description: "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.",
1160
+ promptSnippet: "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)",
1161
+ examples: ["pi-context context-install --json", "pi-context context-install --update true --json"],
1162
+ parameters: Type.Object({
1163
+ update: Type.Optional(Type.Boolean({
1164
+ description: "When true, re-sync existing installed schemas (migration-aware) and replace empty blocks with the catalog starter; populated block data is never overwritten. When false (default), skip existing files.",
1165
+ })),
1166
+ }),
1167
+ surface: "use",
1168
+ authGated: true,
1169
+ run(cwd, params) {
1170
+ const result = installContext(cwd, { overwrite: params.update === true });
1171
+ if (result.error)
1172
+ return result.error;
1173
+ return { json: result };
1174
+ },
1175
+ },
1176
+ {
1177
+ name: "update",
1178
+ label: "Update Installed Model",
1179
+ description: "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.",
1180
+ promptSnippet: "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)",
1181
+ examples: [`pi-context update --dryRun true --json`],
1182
+ parameters: Type.Object({
1183
+ dryRun: Type.Optional(Type.Boolean({ description: "Preview the per-schema action plan without writing anything." })),
1184
+ }),
1185
+ surface: "use",
1186
+ run(cwd, params) {
1187
+ const result = updateContext(cwd, { dryRun: params.dryRun === true });
1188
+ if (result.error)
1189
+ return result.error;
1190
+ return { json: result };
1191
+ },
1192
+ },
1193
+ {
1194
+ name: "validate-block-items",
1195
+ label: "Validate Block Items",
1196
+ description: "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.",
1197
+ promptSnippet: "Validate a block's items against the catalog schema version — returns the per-item failures (item id, field, constraint) without writing",
1198
+ examples: [`pi-context validate-block-items --block tasks --json`],
1199
+ parameters: Type.Object({
1200
+ block: Type.String({ description: "Block name (e.g. 'tasks')" }),
1201
+ }),
1202
+ surface: "use",
1203
+ run(cwd, params) {
1204
+ return { json: validateBlockItemsAgainstCatalog(cwd, params.block) };
1205
+ },
1206
+ },
1012
1207
  {
1013
1208
  name: "context-switch",
1014
1209
  label: "Context Switch",
1015
1210
  description: "Flip the bootstrap pointer to a different substrate dir (parallel to git switch). Default: flip to an existing substrate at target_dir (requires config.json present). create_new=true: bootstrap a fresh substrate at target_dir AND flip in one operation. to_previous=true: flip back to the pointer's previous_contextDir (target_dir ignored).",
1016
1211
  promptSnippet: "Switch the bootstrap pointer to a different substrate dir",
1212
+ examples: [
1213
+ `pi-context context-switch --target_dir .context --writer '{"kind":"human","user":"you@example.com"}' --json`,
1214
+ ],
1017
1215
  parameters: Type.Object({
1018
1216
  target_dir: Type.String({
1019
1217
  description: "Substrate dir name to switch to (e.g. '.context'). Required for default + create_new modes; ignored for to_previous mode.",
@@ -1068,6 +1266,7 @@ export const ops = [
1068
1266
  label: "Context List",
1069
1267
  description: "Enumerate top-level dirs under cwd containing a config.json (switchable substrates). Marks the active one with isActive=true. Read-only.",
1070
1268
  promptSnippet: "List switchable substrate dirs under cwd",
1269
+ examples: [`pi-context context-list --json`],
1071
1270
  parameters: Type.Object({}),
1072
1271
  surface: "use",
1073
1272
  run(cwd, _params) {
@@ -1080,6 +1279,9 @@ export const ops = [
1080
1279
  label: "Context Archive",
1081
1280
  description: "Move a non-active substrate dir to archive/<dir>/. Refuses to archive the active substrate (the dir the bootstrap pointer currently names) or to clobber an existing archive/<dir>/.",
1082
1281
  promptSnippet: "Archive a non-active substrate dir to archive/<dir>/",
1282
+ examples: [
1283
+ `pi-context context-archive --target_dir .context-old --writer '{"kind":"human","user":"you@example.com"}' --json`,
1284
+ ],
1083
1285
  parameters: Type.Object({
1084
1286
  target_dir: Type.String({
1085
1287
  description: "Substrate dir name to archive (e.g. '.project'). Refused if it is the active substrate.",
@@ -1103,6 +1305,7 @@ export const ops = [
1103
1305
  label: "Filter Block Items",
1104
1306
  description: "Filter the array items of a block by a single-field predicate (eq / neq / in / matches). Discovers the single top-level array property in the block; items missing the predicate field are never matched. Wraps the canonical readBlock + caller-side filter into one queryable surface; never mutates the block.",
1105
1307
  promptSnippet: "Filter a block's items by a predicate — eq / neq / in / matches against a single field",
1308
+ examples: [`pi-context filter-block-items --block framework-gaps --field status --op eq --value '"open"' --json`],
1106
1309
  parameters: Type.Object({
1107
1310
  block: Type.String({
1108
1311
  description: "Block name (e.g., 'tasks', 'decisions', 'framework-gaps', 'context-contracts')",
@@ -1134,6 +1337,7 @@ export const ops = [
1134
1337
  label: "Resolve Item By Id",
1135
1338
  description: "Look up the block, array key, and item payload for a given ID across all blocks in the substrate dir. Returns null when no item matches. Mirrors the resolveItemById SDK function and shares its prefix-vs-block invariant — IDs whose prefix maps to a known block but live elsewhere throw at index-build time.",
1136
1339
  promptSnippet: "Resolve a kind-prefixed ID (DEC-/FEAT-/FGAP-/issue-/REQ-/TASK-/etc.) to its owning block and item",
1340
+ examples: [`pi-context resolve-item-by-id --id TASK-001 --json`],
1137
1341
  parameters: Type.Object({
1138
1342
  id: Type.String({ description: "Kind-prefixed ID, e.g., DEC-NNNN / FEAT-NNN / FGAP-NNN / ISSUE-NNN" }),
1139
1343
  }),
@@ -1158,6 +1362,7 @@ export const ops = [
1158
1362
  label: "Read Block Item",
1159
1363
  description: "Read a single item from a named block by its id — returns the item or null. Block-scoped (unlike resolve-item-by-id, which searches all blocks by kind-prefixed id). Avoids fetching a whole large block to get one item.",
1160
1364
  promptSnippet: "Read one item from a block by id (block-scoped; null if absent)",
1365
+ examples: [`pi-context read-block-item --block tasks --id TASK-001 --json`],
1161
1366
  parameters: Type.Object({
1162
1367
  block: Type.String({ description: "Block name (e.g., 'tasks', 'decisions', 'framework-gaps')" }),
1163
1368
  id: Type.String({ description: "Item id within the block (e.g., 'TASK-NNN')" }),
@@ -1176,6 +1381,7 @@ export const ops = [
1176
1381
  label: "Read Block Page",
1177
1382
  description: "Paginate a block's items: returns { items, total, hasMore }. offset default 0, limit default 50. Use for blocks too large to fetch whole (past the 50KB read-block cap). total is the full item count; hasMore signals another page.",
1178
1383
  promptSnippet: "Paginate a block's items — offset + limit; returns {items,total,hasMore}",
1384
+ examples: [`pi-context read-block-page --block tasks --limit 20 --json`],
1179
1385
  parameters: Type.Object({
1180
1386
  block: Type.String({ description: "Block name (e.g., 'framework-gaps', 'decisions', 'issues')" }),
1181
1387
  offset: Type.Optional(Type.Integer({ minimum: 0, description: "Start index (default 0)" })),
@@ -1195,6 +1401,9 @@ export const ops = [
1195
1401
  label: "Join Blocks",
1196
1402
  description: "Join two blocks in one call. EDGE mode: pass `relationType` — pairs left items with right-block items connected by that relations.json edge (`leftEndpoint` parent|child, default parent). FIELD mode: pass `leftField`+`rightField` — pairs where left[leftField] === right[rightField]. Optional left pre-filter via where{Field,Op,Value}. Returns [{left, right:[]}] (right always an array; one-to-many). Use instead of N+1 read-block + resolve calls.",
1197
1403
  promptSnippet: "Join two blocks in one call — by relation edge or shared field; returns {left,right[]} pairs",
1404
+ examples: [
1405
+ `pi-context join-blocks --leftBlock tasks --rightBlock verification --relationType verification_verifies_item --leftEndpoint child --json`,
1406
+ ],
1198
1407
  parameters: Type.Object({
1199
1408
  leftBlock: Type.String({ description: "Left block name (e.g., 'tasks')" }),
1200
1409
  rightBlock: Type.String({ description: "Right block name (e.g., 'verification')" }),
@@ -1237,6 +1446,7 @@ export const ops = [
1237
1446
  label: "Resolve Items By Id (Bulk)",
1238
1447
  description: "Bulk variant of resolve-item-by-id — resolve N kind-prefixed ids against a single buildIdIndex traversal. Returns an object mapping each input id to its ItemLocation (block / arrayKey / item) or null when not found. Coexists with the singular resolve-item-by-id tool; bulk collapses the N×singular-call pattern for callers resolving multiple ids in one render pass.",
1239
1448
  promptSnippet: "Resolve a batch of kind-prefixed ids (DEC-/FGAP-/TASK-/issue-/REQ-/...) in one call",
1449
+ examples: [`pi-context resolve-items-by-id --ids '["TASK-001","DEC-0001","FGAP-042"]' --json`],
1240
1450
  parameters: Type.Object({
1241
1451
  ids: Type.Array(Type.String(), {
1242
1452
  description: "Array of kind-prefixed ids (DEC-/FGAP-/TASK-/issue-/REQ-/...) to resolve in one call",
@@ -1259,6 +1469,9 @@ export const ops = [
1259
1469
  label: "Complete Task",
1260
1470
  description: "Complete a task with verification gate — requires a passing verification entry targeting the task.",
1261
1471
  promptSnippet: "Complete a task — gates on passing verification before updating status",
1472
+ examples: [
1473
+ `pi-context complete-task --taskId TASK-001 --verificationId VER-001 --writer '{"kind":"human","user":"you@example.com"}' --json`,
1474
+ ],
1262
1475
  parameters: Type.Object({
1263
1476
  taskId: Type.String({ description: "Task ID to complete" }),
1264
1477
  verificationId: Type.String({
@@ -1276,6 +1489,7 @@ export const ops = [
1276
1489
  label: "Context Validate Relations",
1277
1490
  description: "Validate substrate relations.json edges against config-declared lenses + hierarchy + relation_types and the cross-block id index. Returns SubstrateValidationResult with status (clean/warnings/invalid) and per-issue diagnostics.",
1278
1491
  promptSnippet: "Validate substrate relations against config + items",
1492
+ examples: [`pi-context context-validate-relations --json`],
1279
1493
  parameters: Type.Object({}),
1280
1494
  surface: "use",
1281
1495
  run(cwd, _params) {
@@ -1288,6 +1502,7 @@ export const ops = [
1288
1502
  label: "Context Edges For Lens",
1289
1503
  description: "Materialize the Edge[] for a named lens — synthetic edges from derived_from_field for auto-derived lenses; authored edges filtered by relation_type for hand-curated lenses; unioned items from composition members for kind=composition lenses.",
1290
1504
  promptSnippet: "Materialize edges for a named lens (auto-derived or hand-curated)",
1505
+ examples: [`pi-context context-edges-for-lens --lensId feature-decomposition --json`],
1291
1506
  parameters: Type.Object({
1292
1507
  lensId: Type.String({ description: "Lens id from config.lenses[].id" }),
1293
1508
  }),
@@ -1298,11 +1513,51 @@ export const ops = [
1298
1513
  return { read };
1299
1514
  },
1300
1515
  },
1516
+ {
1517
+ name: "context-lens-view",
1518
+ label: "Context Lens View",
1519
+ description: "Project a config-declared lens (config.lenses[]) as a binned item-view. Without --bin, a bin->count summary (always under the read cap). With --bin, that bin's items paged by --offset/--limit. Serves target, composition, and hand-curated lenses.",
1520
+ promptSnippet: "Project a config-declared lens as a binned item-view — bin->count summary, or one bin's items paged",
1521
+ examples: [
1522
+ `pi-context context-lens-view --lensId gaps-by-status --json`,
1523
+ `pi-context context-lens-view --lensId gaps-by-status --bin identified --limit 20 --json`,
1524
+ ],
1525
+ parameters: Type.Object({
1526
+ lensId: Type.String({ description: "Lens id from config.lenses[].id" }),
1527
+ bin: Type.Optional(Type.String({ description: "Return this bin's items paged; omit for a bin->count summary" })),
1528
+ offset: Type.Optional(Type.Integer({ minimum: 0, description: "Per-bin page start index (default 0)" })),
1529
+ limit: Type.Optional(Type.Integer({ minimum: 1, description: "Per-bin page size (default 50)" })),
1530
+ }),
1531
+ surface: "use",
1532
+ run(cwd, params) {
1533
+ const view = loadLensView(cwd, params.lensId);
1534
+ if ("error" in view)
1535
+ throw new Error(view.error); // unknown lens / no-config -> non-zero exit
1536
+ if (params.bin !== undefined) {
1537
+ const items = view.grouped.get(params.bin);
1538
+ if (items === undefined)
1539
+ throw new Error(`Bin '${params.bin}' not declared on lens '${params.lensId}'. Bins: ${view.lens.bins.join(", ")}`);
1540
+ const page = pageArray(items, { offset: params.offset, limit: params.limit });
1541
+ return { read: structureForRead(page, { whole: true, label: `lens ${params.lensId} bin ${params.bin}` }) };
1542
+ }
1543
+ const summary = {
1544
+ lens: view.lens.id,
1545
+ kind: view.lens.kind ?? "target",
1546
+ bins: Object.fromEntries(view.lens.bins.map((b) => [b, (view.grouped.get(b) ?? []).length])),
1547
+ uncategorized: view.uncategorized.length,
1548
+ total: view.items.length,
1549
+ };
1550
+ return { read: structureForRead(summary, { whole: true, label: `lens ${params.lensId} bins` }) };
1551
+ },
1552
+ },
1301
1553
  {
1302
1554
  name: "context-walk-descendants",
1303
1555
  label: "Context Walk Descendants",
1304
1556
  description: "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).",
1305
1557
  promptSnippet: "Walk closure-table descendants under a relation_type",
1558
+ examples: [
1559
+ `pi-context context-walk-descendants --parentId FEAT-008 --relationType feature_decomposed_into_task --json`,
1560
+ ],
1306
1561
  parameters: Type.Object({
1307
1562
  parentId: Type.String({ description: "Parent id (canonical id or lens bin name)" }),
1308
1563
  relationType: Type.String({ description: "Relation type from config.relation_types[].canonical_id" }),
@@ -1318,6 +1573,7 @@ export const ops = [
1318
1573
  label: "Walk Ancestors",
1319
1574
  description: "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).",
1320
1575
  promptSnippet: "Walk closure-table ancestors under a relation_type",
1576
+ examples: [`pi-context walk-ancestors --itemId TASK-042 --relationType feature_decomposed_into_task --json`],
1321
1577
  parameters: Type.Object({
1322
1578
  itemId: Type.String({ description: "Child item id whose ancestors are sought" }),
1323
1579
  relationType: Type.String({ description: "Relation type from config.relation_types[].canonical_id" }),
@@ -1334,6 +1590,7 @@ export const ops = [
1334
1590
  label: "Find References",
1335
1591
  description: "Find all closure-table edges incident on an item id (inbound, outbound, or both). Returns Edge[] preserving relation_type + ordinal per record — edge-level view, not the id-chain projection that walk-ancestors / context-walk-descendants emit.",
1336
1592
  promptSnippet: "Find closure-table edges incident on an item id",
1593
+ examples: [`pi-context find-references --itemId TASK-001 --direction both --json`],
1337
1594
  parameters: Type.Object({
1338
1595
  itemId: Type.String({ description: "Item id whose incident edges are sought" }),
1339
1596
  direction: Type.Optional(Type.Union([Type.Literal("inbound"), Type.Literal("outbound"), Type.Literal("both")], {
@@ -1352,6 +1609,7 @@ export const ops = [
1352
1609
  label: "Gather Execution Context",
1353
1610
  description: "Compose a ContextBundle for a work-unit by reading its context-contract (by unit_kind) and walking declared relation_types bidirectionally per direction semantic. Returns unit + perRelationType buckets of resolved items + traversal_depth + scoped_at. Substrate primitive serving harness-confined dispatch.",
1354
1611
  promptSnippet: "Compose ContextBundle for unit + context-contract-declared bundle_relation_types",
1612
+ examples: [`pi-context gather-execution-context --unitId TASK-001 --kind task --json`],
1355
1613
  parameters: Type.Object({
1356
1614
  unitId: Type.String({ description: "Work-unit id (e.g. TASK-NNN / DEC-NNNN / FGAP-NNN)" }),
1357
1615
  kind: Type.String({
@@ -1376,6 +1634,7 @@ export const ops = [
1376
1634
  label: "Context: load roadmap",
1377
1635
  description: "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'.",
1378
1636
  promptSnippet: "Load a roadmap by id",
1637
+ examples: [`pi-context context-roadmap-load --roadmapId ROADMAP-001 --json`],
1379
1638
  parameters: Type.Object({
1380
1639
  roadmapId: Type.String({ description: "ROADMAP-NNN id from <config.root>/roadmap.json" }),
1381
1640
  }),
@@ -1400,6 +1659,7 @@ export const ops = [
1400
1659
  label: "Context: render roadmap",
1401
1660
  description: "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.",
1402
1661
  promptSnippet: "Render a roadmap as markdown",
1662
+ examples: [`pi-context context-roadmap-render --roadmapId ROADMAP-001`],
1403
1663
  parameters: Type.Object({
1404
1664
  roadmapId: Type.String({ description: "ROADMAP-NNN id from <config.root>/roadmap.json" }),
1405
1665
  }),
@@ -1418,6 +1678,7 @@ export const ops = [
1418
1678
  label: "Context: validate roadmap(s)",
1419
1679
  description: "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.",
1420
1680
  promptSnippet: "Validate roadmaps",
1681
+ examples: [`pi-context context-roadmap-validate --roadmapId ROADMAP-001 --json`],
1421
1682
  parameters: Type.Object({
1422
1683
  roadmapId: Type.Optional(Type.String({ description: "Filter to issues matching this roadmap_id (omit for full-project validation)" })),
1423
1684
  }),
@@ -1435,6 +1696,7 @@ export const ops = [
1435
1696
  label: "Context: list roadmaps",
1436
1697
  description: "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).",
1437
1698
  promptSnippet: "List roadmaps",
1699
+ examples: [`pi-context context-roadmap-list --json`],
1438
1700
  parameters: Type.Object({}),
1439
1701
  surface: "use",
1440
1702
  run(cwd, _params) {