@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.
- package/CHANGELOG.md +69 -0
- package/README.md +21 -6
- package/dist/block-api.d.ts +13 -0
- package/dist/block-api.d.ts.map +1 -1
- package/dist/block-api.js +1 -1
- package/dist/block-api.js.map +1 -1
- package/dist/content-hash.d.ts +13 -0
- package/dist/content-hash.d.ts.map +1 -1
- package/dist/content-hash.js +16 -0
- package/dist/content-hash.js.map +1 -1
- package/dist/context-dir.d.ts +12 -0
- package/dist/context-dir.d.ts.map +1 -1
- package/dist/context-dir.js +14 -0
- package/dist/context-dir.js.map +1 -1
- package/dist/context.d.ts +60 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +44 -0
- package/dist/context.js.map +1 -1
- package/dist/index.d.ts +534 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1879 -20
- package/dist/index.js.map +1 -1
- package/dist/migration-registry-loader.d.ts +16 -0
- package/dist/migration-registry-loader.d.ts.map +1 -1
- package/dist/migration-registry-loader.js +33 -0
- package/dist/migration-registry-loader.js.map +1 -1
- package/dist/ops-registry.d.ts +16 -0
- package/dist/ops-registry.d.ts.map +1 -1
- package/dist/ops-registry.js +270 -8
- package/dist/ops-registry.js.map +1 -1
- package/dist/pending-blocked-store.d.ts +83 -0
- package/dist/pending-blocked-store.d.ts.map +1 -0
- package/dist/pending-blocked-store.js +93 -0
- package/dist/pending-blocked-store.js.map +1 -0
- package/dist/schema-merge.d.ts +26 -0
- package/dist/schema-merge.d.ts.map +1 -0
- package/dist/schema-merge.js +176 -0
- package/dist/schema-merge.js.map +1 -0
- package/package.json +2 -1
- package/samples/conception.json +50 -0
- package/samples/schemas/framework-gaps.schema.json +1 -1
- package/samples/schemas/issues.schema.json +2 -2
- package/samples/schemas/layer-plans.schema.json +2 -2
- package/samples/schemas/research.schema.json +1 -1
- package/samples/schemas/work-orders.schema.json +2 -2
- package/schemas/config.schema.json +25 -1
- package/schemas/pending-blocked.schema.json +190 -0
- package/skill-narrative.md +7 -5
- package/skills/pi-context/SKILL.md +90 -7
- package/skills/pi-context/references/bundled-resources.md +2 -1
package/dist/ops-registry.js
CHANGED
|
@@ -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, {
|
|
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
|
-
|
|
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) {
|