@davidorex/pi-context 0.30.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +127 -0
- package/README.md +27 -11
- package/dist/block-api.d.ts +13 -0
- package/dist/block-api.d.ts.map +1 -1
- package/dist/block-api.js +28 -3
- 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-sdk.d.ts +71 -33
- package/dist/context-sdk.d.ts.map +1 -1
- package/dist/context-sdk.js +547 -149
- package/dist/context-sdk.js.map +1 -1
- package/dist/context.d.ts +213 -2
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +119 -5
- package/dist/context.js.map +1 -1
- package/dist/index.d.ts +595 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2225 -55
- package/dist/index.js.map +1 -1
- package/dist/lens-view.d.ts +0 -5
- package/dist/lens-view.d.ts.map +1 -1
- package/dist/lens-view.js +43 -1
- package/dist/lens-view.js.map +1 -1
- package/dist/migration-registry-loader.d.ts +36 -12
- package/dist/migration-registry-loader.d.ts.map +1 -1
- package/dist/migration-registry-loader.js +79 -17
- package/dist/migration-registry-loader.js.map +1 -1
- package/dist/migrations-store.d.ts +45 -18
- package/dist/migrations-store.d.ts.map +1 -1
- package/dist/migrations-store.js +56 -22
- package/dist/migrations-store.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 +352 -117
- 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/promote-item.d.ts.map +1 -1
- package/dist/promote-item.js +41 -12
- package/dist/promote-item.js.map +1 -1
- package/dist/roadmap-plan.d.ts +121 -99
- package/dist/roadmap-plan.d.ts.map +1 -1
- package/dist/roadmap-plan.js +281 -345
- package/dist/roadmap-plan.js.map +1 -1
- 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/dist/status-vocab.d.ts +12 -2
- package/dist/status-vocab.d.ts.map +1 -1
- package/dist/status-vocab.js +14 -1
- package/dist/status-vocab.js.map +1 -1
- package/package.json +2 -1
- package/samples/blocks/milestone.json +3 -0
- package/samples/blocks/session-notes.json +1 -0
- package/samples/conception.json +358 -15
- package/samples/migrations.json +8 -0
- package/samples/schemas/context-contracts.schema.json +4 -0
- package/samples/schemas/conventions.schema.json +4 -0
- package/samples/schemas/decisions.schema.json +4 -0
- package/samples/schemas/features.schema.json +4 -0
- package/samples/schemas/framework-gaps.schema.json +5 -1
- package/samples/schemas/issues.schema.json +30 -2
- package/samples/schemas/layer-plans.schema.json +6 -2
- package/samples/schemas/milestone.schema.json +79 -0
- package/samples/schemas/phase.schema.json +4 -0
- package/samples/schemas/rationale.schema.json +4 -0
- package/samples/schemas/requirements.schema.json +4 -0
- package/samples/schemas/research.schema.json +5 -1
- package/samples/schemas/session-notes.schema.json +89 -0
- package/samples/schemas/spec-reviews.schema.json +4 -0
- package/samples/schemas/story.schema.json +8 -0
- package/samples/schemas/tasks.schema.json +4 -0
- package/samples/schemas/verification.schema.json +4 -0
- package/samples/schemas/work-orders.schema.json +6 -2
- package/schemas/config.schema.json +101 -3
- package/schemas/migrations.schema.json +25 -0
- package/schemas/pending-blocked.schema.json +190 -0
- package/skill-narrative.md +14 -10
- package/skills/pi-context/SKILL.md +127 -49
- package/skills/pi-context/references/bundled-resources.md +7 -2
package/dist/ops-registry.js
CHANGED
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
import path from "node:path";
|
|
20
20
|
import { Type } from "typebox";
|
|
21
21
|
import { appendToBlock, appendToNestedArray, nextId, readBlock, readBlockDir, removeFromBlock, removeFromNestedArray, updateItemInBlock, updateNestedArrayItem, upsertItemInBlock, writeBlock, } from "./block-api.js";
|
|
22
|
-
import { adoptConception, amendConfigEntry, loadConfig
|
|
22
|
+
import { adoptConception, amendConfigEntry, loadConfig } from "./context.js";
|
|
23
23
|
import { BootstrapNotFoundError, schemaPath, tryResolveContextDir } from "./context-dir.js";
|
|
24
|
-
import { appendRelationByRef, appendRelationsByRef, completeTask, contextState, currentState, deriveBootstrapState, filterBlockItems, joinBlocks, readBlockItem, readBlockPage, removeRelationByRef, replaceRelationByRef, resolveItemById, resolveItemsByIds, validateContext, } from "./context-sdk.js";
|
|
24
|
+
import { appendRelationByRef, appendRelationsByRef, completeTask, contextState, currentState, deriveBootstrapState, endpointKey, filterBlockItems, joinBlocks, readBlockItem, readBlockPage, removeRelationByRef, replaceRelationByRef, resolveItemById, resolveItemsByIds, validateContext, } from "./context-sdk.js";
|
|
25
25
|
import { gatherExecutionContext } from "./execution-context.js";
|
|
26
26
|
// initProject + the switch/list/archive helpers are defined in index.ts (shared
|
|
27
27
|
// with the /context command handlers + the context-* tools). This is a cyclic
|
|
@@ -29,12 +29,12 @@ 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
|
-
import {
|
|
37
|
+
import { loadRoadmap, renderRoadmap, validateRoadmap } from "./roadmap-plan.js";
|
|
38
38
|
import { samplesCatalog } from "./samples-catalog.js";
|
|
39
39
|
import { readSchema, writeSchemaChecked } from "./schema-write.js";
|
|
40
40
|
import { truncateHead } from "./truncate.js";
|
|
@@ -115,37 +115,15 @@ export function boundedJsonOutput(r) {
|
|
|
115
115
|
const { over, totalBytes } = overReadCap(s);
|
|
116
116
|
return over ? { data: null, truncated: true, totalBytes, complete: false } : r.json;
|
|
117
117
|
}
|
|
118
|
-
// ── serializeRoadmapView ────────────────────────────────────────────────────
|
|
119
|
-
// Strip non-serializable fields (suggestionTemplate fn, grouped Map) from the
|
|
120
|
-
// embedded LoadedLensView records before tool serialization. Relocated verbatim
|
|
121
|
-
// from the extension factory; consumed by the context-roadmap-load op.
|
|
122
|
-
const serializeRoadmapView = (view) => ({
|
|
123
|
-
roadmap: view.roadmap,
|
|
124
|
-
phases: view.phases.map((pv) => ({
|
|
125
|
-
phase: pv.phase,
|
|
126
|
-
lensView: "error" in pv.lensView
|
|
127
|
-
? pv.lensView
|
|
128
|
-
: {
|
|
129
|
-
lens: pv.lensView.lens,
|
|
130
|
-
items: pv.lensView.items,
|
|
131
|
-
edges: pv.lensView.edges,
|
|
132
|
-
grouped: Object.fromEntries(pv.lensView.grouped),
|
|
133
|
-
uncategorized: pv.lensView.uncategorized,
|
|
134
|
-
},
|
|
135
|
-
status: pv.status,
|
|
136
|
-
...(pv.milestone ? { milestone: pv.milestone } : {}),
|
|
137
|
-
...(pv.milestoneSatisfied !== undefined ? { milestoneSatisfied: pv.milestoneSatisfied } : {}),
|
|
138
|
-
})),
|
|
139
|
-
phaseOrder: view.phaseOrder,
|
|
140
|
-
cycles: view.cycles,
|
|
141
|
-
edges: view.edges,
|
|
142
|
-
});
|
|
143
118
|
export const ops = [
|
|
144
119
|
{
|
|
145
120
|
name: "append-block-item",
|
|
146
121
|
label: "Append Block Item",
|
|
147
122
|
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
123
|
promptSnippet: "Append items to project blocks (issues, decisions, or any user-defined block)",
|
|
124
|
+
examples: [
|
|
125
|
+
`pi-context append-block-item --block framework-gaps --arrayKey gaps --autoId true --item @/tmp/fgap.json --writer '{"kind":"human","user":"you@example.com"}' --json`,
|
|
126
|
+
],
|
|
149
127
|
parameters: Type.Object({
|
|
150
128
|
block: Type.String({ description: "Block name (e.g., 'issues', 'decisions')" }),
|
|
151
129
|
arrayKey: Type.String({ description: "Array key in the block (e.g., 'issues', 'decisions')" }),
|
|
@@ -183,6 +161,9 @@ export const ops = [
|
|
|
183
161
|
label: "Update Block Item",
|
|
184
162
|
description: "Update fields on an item in a project block array. Finds by predicate field match.",
|
|
185
163
|
promptSnippet: "Update items in project blocks — change status, add details, mark resolved",
|
|
164
|
+
examples: [
|
|
165
|
+
`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`,
|
|
166
|
+
],
|
|
186
167
|
parameters: Type.Object({
|
|
187
168
|
block: Type.String({ description: "Block name (e.g., 'issues', 'decisions')" }),
|
|
188
169
|
arrayKey: Type.String({ description: "Array key in the block" }),
|
|
@@ -205,14 +186,30 @@ export const ops = [
|
|
|
205
186
|
{
|
|
206
187
|
name: "append-relation",
|
|
207
188
|
label: "Append Relation",
|
|
208
|
-
description: "Append a closure-table relation (edge:
|
|
209
|
-
"
|
|
210
|
-
"
|
|
211
|
-
"
|
|
212
|
-
|
|
189
|
+
description: "Append a closure-table relation (edge: relation_type, optional ordinal) to relations.json. Orient the edge with " +
|
|
190
|
+
"EITHER raw --parent/--child OR the role-typed --primary/--counter (which maps to parent/child via the relation's " +
|
|
191
|
+
"declared role_direction); the two pairs are mutually exclusive. A bare --parent/--child append of a relation that " +
|
|
192
|
+
"is BOTH role-bearing and orientation-ambiguous (its source/target kinds overlap) is rejected — re-issue with " +
|
|
193
|
+
"--primary/--counter. Shape is AJV-validated; an exact-duplicate edge (same parent+child+relation_type) is a no-op. " +
|
|
194
|
+
"Reference integrity (endpoints resolve, relation_type registered, no cycle) is NOT checked here — run " +
|
|
195
|
+
"context-validate after. Creates relations.json if absent.",
|
|
196
|
+
promptSnippet: "Create a relation/edge between two items (raw --parent/--child, or role-typed --primary/--counter mapped via role_direction)",
|
|
197
|
+
examples: [
|
|
198
|
+
`pi-context append-relation --parent VER-001 --child TASK-001 --relation_type verification_verifies_item --writer '{"kind":"human","user":"you@example.com"}' --json`,
|
|
199
|
+
],
|
|
213
200
|
parameters: Type.Object({
|
|
214
|
-
parent: Type.String({
|
|
215
|
-
|
|
201
|
+
parent: Type.Optional(Type.String({
|
|
202
|
+
description: "Parent-endpoint selector (canonical id / <alias>:<refname> / lens bin) — RAW orientation. Mutually exclusive with --primary/--counter.",
|
|
203
|
+
})),
|
|
204
|
+
child: Type.Optional(Type.String({
|
|
205
|
+
description: "Child-endpoint selector — RAW orientation. Mutually exclusive with --primary/--counter.",
|
|
206
|
+
})),
|
|
207
|
+
primary: Type.Optional(Type.String({
|
|
208
|
+
description: "Selector of the endpoint holding the relation's PRIMARY semantic role (ROLE-TYPED orientation; mapped to parent/child via the relation's declared role_direction). Requires --counter; the relation_type must declare role_direction.",
|
|
209
|
+
})),
|
|
210
|
+
counter: Type.Optional(Type.String({
|
|
211
|
+
description: "Selector of the endpoint holding the relation's COUNTER role (ROLE-TYPED orientation). Requires --primary.",
|
|
212
|
+
})),
|
|
216
213
|
relation_type: Type.String({
|
|
217
214
|
description: "Registered relation_type canonical_id / hierarchy edge type / lens id",
|
|
218
215
|
}),
|
|
@@ -222,26 +219,32 @@ export const ops = [
|
|
|
222
219
|
surface: "use",
|
|
223
220
|
run(cwd, params, ctx) {
|
|
224
221
|
// Cycle-5 porcelain: STRING selectors (bare refname / <alias>:<refname> /
|
|
225
|
-
// lens-bin) are resolved to structured EdgeEndpoints and written via the
|
|
226
|
-
//
|
|
227
|
-
//
|
|
228
|
-
//
|
|
229
|
-
//
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
222
|
+
// lens-bin) are resolved to structured EdgeEndpoints and written via the raw
|
|
223
|
+
// plumbing. The append accepts EITHER raw --parent/--child OR the role-typed
|
|
224
|
+
// --primary/--counter form (FGAP-113); messaging renders the RESOLVED stored
|
|
225
|
+
// orientation (endpointKey of the returned edge), so a role-typed call reports
|
|
226
|
+
// the parent/child it actually filed. Under dryRun the byRef fn validates the
|
|
227
|
+
// prospective relations + dedup-checks without writing (TASK-010 shared
|
|
228
|
+
// preview path).
|
|
229
|
+
const { appended, edge } = appendRelationByRef(cwd, {
|
|
230
|
+
...(params.parent !== undefined ? { parent: params.parent } : {}),
|
|
231
|
+
...(params.child !== undefined ? { child: params.child } : {}),
|
|
232
|
+
...(params.primary !== undefined ? { primary: params.primary } : {}),
|
|
233
|
+
...(params.counter !== undefined ? { counter: params.counter } : {}),
|
|
233
234
|
relation_type: params.relation_type,
|
|
234
235
|
...(params.ordinal !== undefined ? { ordinal: params.ordinal } : {}),
|
|
235
236
|
}, ctx, { dryRun: params.dryRun });
|
|
237
|
+
const from = endpointKey(edge.parent);
|
|
238
|
+
const to = endpointKey(edge.child);
|
|
236
239
|
const ordinalNote = params.ordinal !== undefined ? ` (ordinal ${params.ordinal})` : "";
|
|
237
240
|
if (params.dryRun) {
|
|
238
241
|
return appended
|
|
239
|
-
? `would append relation ${
|
|
240
|
-
: `would no-op (duplicate): relation ${
|
|
242
|
+
? `would append relation ${from} -[${params.relation_type}]-> ${to}${ordinalNote}`
|
|
243
|
+
: `would no-op (duplicate): relation ${from} -[${params.relation_type}]-> ${to}`;
|
|
241
244
|
}
|
|
242
245
|
return appended
|
|
243
|
-
? `Appended relation ${
|
|
244
|
-
: `Relation ${
|
|
246
|
+
? `Appended relation ${from} -[${params.relation_type}]-> ${to}${ordinalNote}`
|
|
247
|
+
: `Relation ${from} -[${params.relation_type}]-> ${to} already exists — no-op`;
|
|
245
248
|
},
|
|
246
249
|
},
|
|
247
250
|
{
|
|
@@ -252,6 +255,9 @@ export const ops = [
|
|
|
252
255
|
"inverse of append-relation (ordinal is NOT part of identity). An absent edge is an idempotent no-op. " +
|
|
253
256
|
"Reference integrity is NOT checked here — run context-validate after if the removal changes resolvability.",
|
|
254
257
|
promptSnippet: "Remove a relation/edge between two items (the inverse of append-relation)",
|
|
258
|
+
examples: [
|
|
259
|
+
`pi-context remove-relation --parent VER-001 --child TASK-001 --relation_type verification_verifies_item --writer '{"kind":"human","user":"you@example.com"}' --json`,
|
|
260
|
+
],
|
|
255
261
|
parameters: Type.Object({
|
|
256
262
|
parent: Type.String({ description: "Canonical id (or lens bin name) of the parent endpoint" }),
|
|
257
263
|
child: Type.String({ description: "Canonical id of the child endpoint" }),
|
|
@@ -284,8 +290,13 @@ export const ops = [
|
|
|
284
290
|
description: "Atomically replace one closure-table relation with another in a SINGLE write (no half-state: the old edge and " +
|
|
285
291
|
"the new edge never coexist on disk). The old edge is matched on the (parent, child, relation_type) dedup identity; " +
|
|
286
292
|
"the new edge is written with its optional ordinal. If the old edge is absent the call is effectively an append of " +
|
|
287
|
-
"the new edge.
|
|
293
|
+
"the new edge. This op takes RAW parent/child (old + new) and BYPASSES the write-time orientation gate that " +
|
|
294
|
+
"append-relation applies — it writes the endpoints verbatim, so it is the affordance for re-orienting an existing " +
|
|
295
|
+
"edge; reference integrity is NOT checked here — run context-validate after.",
|
|
288
296
|
promptSnippet: "Atomically swap one relation/edge for another in a single write",
|
|
297
|
+
examples: [
|
|
298
|
+
`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`,
|
|
299
|
+
],
|
|
289
300
|
parameters: Type.Object({
|
|
290
301
|
old_parent: Type.String({ description: "Parent endpoint selector of the edge to remove" }),
|
|
291
302
|
old_child: Type.String({ description: "Child endpoint selector of the edge to remove" }),
|
|
@@ -340,14 +351,20 @@ export const ops = [
|
|
|
340
351
|
{
|
|
341
352
|
name: "append-relations",
|
|
342
353
|
label: "Append Relations (bulk)",
|
|
343
|
-
description: "Append MANY closure-table relations to relations.json in a single write. Each edge is an object " +
|
|
344
|
-
"{
|
|
345
|
-
"
|
|
346
|
-
"
|
|
347
|
-
|
|
354
|
+
description: "Append MANY closure-table relations to relations.json in a single write. Each edge is an object with " +
|
|
355
|
+
"{ relation_type, ordinal? } plus EITHER a raw { parent, child } pair OR the role-typed { primary, counter } pair " +
|
|
356
|
+
"(mapped to parent/child via the relation's declared role_direction); the two pairs are mutually exclusive per edge, " +
|
|
357
|
+
"and a bare { parent, child } for an orientation-ambiguous role-bearing relation rejects the whole batch before any " +
|
|
358
|
+
"write. Per-(parent, child, relation_type) duplicates are skipped (against on-disk edges AND earlier edges in the " +
|
|
359
|
+
"same batch). Returns appended/skipped counts. Reference integrity is NOT checked here — run context-validate " +
|
|
360
|
+
"after. Creates relations.json if absent.",
|
|
361
|
+
promptSnippet: "Create many relations/edges between items in one write (raw or role-typed per edge)",
|
|
362
|
+
examples: [
|
|
363
|
+
`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`,
|
|
364
|
+
],
|
|
348
365
|
parameters: Type.Object({
|
|
349
366
|
edges: Type.Unknown({
|
|
350
|
-
description: "JSON array of { parent, child
|
|
367
|
+
description: "JSON array of edge objects. Each edge is { relation_type, ordinal? } plus EITHER a raw { parent, child } pair OR the role-typed { primary, counter } pair (mapped to parent/child via the relation's declared role_direction); the two orientation pairs are mutually exclusive per edge. Selectors are id / <alias>:<refname> / lens-bin.",
|
|
351
368
|
}),
|
|
352
369
|
dryRun: Type.Optional(Type.Boolean({ description: "Preview without writing relations.json" })),
|
|
353
370
|
}),
|
|
@@ -364,12 +381,16 @@ export const ops = [
|
|
|
364
381
|
}
|
|
365
382
|
}
|
|
366
383
|
if (!Array.isArray(edges)) {
|
|
367
|
-
throw new Error(`edges parameter must be a JSON array of
|
|
384
|
+
throw new Error(`edges parameter must be a JSON array of relation edge objects`);
|
|
368
385
|
}
|
|
369
386
|
// Under dryRun the byRef fn replays the on-disk + in-batch dedup and
|
|
370
387
|
// validates the prospective relations without writing (TASK-010 shared
|
|
371
|
-
// preview path).
|
|
372
|
-
|
|
388
|
+
// preview path). Each edge accepts raw {parent,child} or role-typed
|
|
389
|
+
// {primary,counter} (FGAP-113); orientation + the ambiguous-bare-append
|
|
390
|
+
// reject are applied inside appendRelationsByRef before any write.
|
|
391
|
+
const { appended, skipped } = appendRelationsByRef(cwd, edges, ctx, {
|
|
392
|
+
dryRun: params.dryRun,
|
|
393
|
+
});
|
|
373
394
|
return params.dryRun
|
|
374
395
|
? `would append ${appended}, skip ${skipped} (duplicates)`
|
|
375
396
|
: `appended ${appended}, skipped ${skipped} (duplicates)`;
|
|
@@ -382,6 +403,9 @@ export const ops = [
|
|
|
382
403
|
"REPLACED (full-shape replacement, not shallow-merge — use update-block-item for merge); otherwise the item is " +
|
|
383
404
|
"appended. Schema validation is automatic. idField defaults to 'id'.",
|
|
384
405
|
promptSnippet: "Append-or-replace a full block item by id (replacement, not merge)",
|
|
406
|
+
examples: [
|
|
407
|
+
`pi-context upsert-block-item --block tasks --arrayKey tasks --item @/tmp/task.json --writer '{"kind":"human","user":"you@example.com"}' --json`,
|
|
408
|
+
],
|
|
385
409
|
parameters: Type.Object({
|
|
386
410
|
block: Type.String({ description: "Block name (e.g., 'issues', 'decisions')" }),
|
|
387
411
|
arrayKey: Type.String({ description: "Array key in the block (e.g., 'issues', 'decisions')" }),
|
|
@@ -423,6 +447,9 @@ export const ops = [
|
|
|
423
447
|
"(unresolvable/non-item source, unregistered destination alias, unregistered destination relation_type, refname " +
|
|
424
448
|
"collision) throw. Pass dryRun to compute the destination without writing.",
|
|
425
449
|
promptSnippet: "Promote an item into another substrate as a derived copy with a lineage edge",
|
|
450
|
+
examples: [
|
|
451
|
+
`pi-context promote-item --source DEC-0001 --destinationSubstrate .context --writer '{"kind":"human","user":"you@example.com"}' --json`,
|
|
452
|
+
],
|
|
426
453
|
parameters: Type.Object({
|
|
427
454
|
source: Type.String({ description: "Source item selector (bare refname / <alias>:<refname>)" }),
|
|
428
455
|
destinationSubstrate: Type.String({ description: "Registered destination substrate alias" }),
|
|
@@ -461,6 +488,9 @@ export const ops = [
|
|
|
461
488
|
label: "Append Block Nested Item",
|
|
462
489
|
description: "Append an item to a nested array on a parent-array item in a project block. Schema validation is automatic.",
|
|
463
490
|
promptSnippet: "Append items to nested arrays inside parent items (e.g., findings inside a review)",
|
|
491
|
+
examples: [
|
|
492
|
+
`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`,
|
|
493
|
+
],
|
|
464
494
|
parameters: Type.Object({
|
|
465
495
|
block: Type.String({ description: "Block name (e.g., 'spec-reviews')" }),
|
|
466
496
|
arrayKey: Type.String({ description: "Parent array key (e.g., 'reviews')" }),
|
|
@@ -493,6 +523,9 @@ export const ops = [
|
|
|
493
523
|
label: "Update Block Nested Item",
|
|
494
524
|
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
525
|
promptSnippet: "Update items inside nested arrays — change finding state, mark resolved",
|
|
526
|
+
examples: [
|
|
527
|
+
`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`,
|
|
528
|
+
],
|
|
496
529
|
parameters: Type.Object({
|
|
497
530
|
block: Type.String({ description: "Block name (e.g., 'spec-reviews')" }),
|
|
498
531
|
arrayKey: Type.String({ description: "Parent array key (e.g., 'reviews')" }),
|
|
@@ -527,6 +560,9 @@ export const ops = [
|
|
|
527
560
|
label: "Remove Block Item",
|
|
528
561
|
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
562
|
promptSnippet: "Remove items from project blocks — prune retracted issues, dedupe entries",
|
|
563
|
+
examples: [
|
|
564
|
+
`pi-context remove-block-item --block issues --arrayKey issues --match '{"id":"ISSUE-001"}' --writer '{"kind":"human","user":"you@example.com"}' --json`,
|
|
565
|
+
],
|
|
530
566
|
parameters: Type.Object({
|
|
531
567
|
block: Type.String({ description: "Block name (e.g., 'issues')" }),
|
|
532
568
|
arrayKey: Type.String({ description: "Top-level array key (e.g., 'issues')" }),
|
|
@@ -546,6 +582,9 @@ export const ops = [
|
|
|
546
582
|
label: "Remove Block Nested Item",
|
|
547
583
|
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
584
|
promptSnippet: "Remove nested items — drop rejected findings, retract nested references",
|
|
585
|
+
examples: [
|
|
586
|
+
`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`,
|
|
587
|
+
],
|
|
549
588
|
parameters: Type.Object({
|
|
550
589
|
block: Type.String({ description: "Block name (e.g., 'spec-reviews')" }),
|
|
551
590
|
arrayKey: Type.String({ description: "Parent array key (e.g., 'reviews')" }),
|
|
@@ -574,6 +613,7 @@ export const ops = [
|
|
|
574
613
|
label: "Read Block Dir",
|
|
575
614
|
description: "Enumerate and parse all .json files in a <substrate-dir>/<subdir>/ directory, returned as a sorted array. Missing directories return [].",
|
|
576
615
|
promptSnippet: "Enumerate project block subdirectories (phases, schemas, etc.) as parsed JSON",
|
|
616
|
+
examples: [`pi-context read-block-dir --subdir phases --json`],
|
|
577
617
|
parameters: Type.Object({
|
|
578
618
|
subdir: Type.String({ description: "Subdirectory under the substrate dir (e.g., 'phases', 'schemas')" }),
|
|
579
619
|
}),
|
|
@@ -589,6 +629,7 @@ export const ops = [
|
|
|
589
629
|
label: "Read Block",
|
|
590
630
|
description: "Read a project block file as structured JSON.",
|
|
591
631
|
promptSnippet: "Read a project block as structured JSON",
|
|
632
|
+
examples: [`pi-context read-block --block tasks --json`],
|
|
592
633
|
parameters: Type.Object({
|
|
593
634
|
block: Type.String({ description: "Block name (e.g., 'issues', 'tasks', 'requirements')" }),
|
|
594
635
|
}),
|
|
@@ -611,6 +652,9 @@ export const ops = [
|
|
|
611
652
|
label: "Write Block",
|
|
612
653
|
description: "Write or replace an entire project block with schema validation.",
|
|
613
654
|
promptSnippet: "Write or replace a project block with schema validation",
|
|
655
|
+
examples: [
|
|
656
|
+
`pi-context write-block --block architecture --data @/tmp/architecture.json --writer '{"kind":"human","user":"you@example.com"}' --json`,
|
|
657
|
+
],
|
|
614
658
|
parameters: Type.Object({
|
|
615
659
|
block: Type.String({ description: "Block name (e.g., 'project', 'architecture')" }),
|
|
616
660
|
data: Type.Unknown({ description: "Complete block data — must conform to block schema" }),
|
|
@@ -628,6 +672,7 @@ export const ops = [
|
|
|
628
672
|
label: "Context Status",
|
|
629
673
|
description: "Get derived context state — source metrics, block summaries, planning lifecycle status.",
|
|
630
674
|
promptSnippet: "Get context state — source metrics, block summaries, planning lifecycle status",
|
|
675
|
+
examples: [`pi-context context-status --json`],
|
|
631
676
|
parameters: Type.Object({}),
|
|
632
677
|
surface: "use",
|
|
633
678
|
run(cwd, _params) {
|
|
@@ -635,11 +680,24 @@ export const ops = [
|
|
|
635
680
|
return { json: result };
|
|
636
681
|
},
|
|
637
682
|
},
|
|
683
|
+
{
|
|
684
|
+
name: "context-check-status",
|
|
685
|
+
label: "Context Check Status",
|
|
686
|
+
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. Like every substrate-lifecycle ceremony it seeds the catalog's config migration declarations into migrations.json (idempotent) before its first config read, so a version-lagging legacy substrate is diagnosable; beyond that seed it writes nothing.",
|
|
687
|
+
promptSnippet: "Report installed-vs-catalog schema drift + the version gap for behind schemas (read-only)",
|
|
688
|
+
examples: [`pi-context context-check-status --json`],
|
|
689
|
+
parameters: Type.Object({}),
|
|
690
|
+
surface: "use",
|
|
691
|
+
run(cwd, _params) {
|
|
692
|
+
return { json: checkStatus(cwd) };
|
|
693
|
+
},
|
|
694
|
+
},
|
|
638
695
|
{
|
|
639
696
|
name: "context-validate",
|
|
640
697
|
label: "Context Validate",
|
|
641
698
|
description: "Validate cross-block referential integrity — check that IDs referenced across blocks exist.",
|
|
642
699
|
promptSnippet: "Validate cross-block referential integrity",
|
|
700
|
+
examples: [`pi-context context-validate --json`],
|
|
643
701
|
parameters: Type.Object({}),
|
|
644
702
|
surface: "use",
|
|
645
703
|
run(cwd, _params) {
|
|
@@ -652,6 +710,7 @@ export const ops = [
|
|
|
652
710
|
label: "Read Config",
|
|
653
711
|
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
712
|
promptSnippet: "Read project config — vocabulary, lenses, relation_types, status_buckets",
|
|
713
|
+
examples: [`pi-context read-config --registry block_kinds --id tasks --json`],
|
|
655
714
|
parameters: Type.Object({
|
|
656
715
|
registry: Type.Optional(Type.String({
|
|
657
716
|
description: "Address ONE config registry/map by key (e.g. 'relation_types', 'lenses', 'block_kinds', 'status_buckets')",
|
|
@@ -673,10 +732,14 @@ export const ops = [
|
|
|
673
732
|
if (!entry.found) {
|
|
674
733
|
return `read-config: entry not found in ${params.registry} — ${entry.resolved}`;
|
|
675
734
|
}
|
|
676
|
-
const read = structureForRead(entry.value, {
|
|
735
|
+
const read = structureForRead(entry.value, {
|
|
736
|
+
whole: true,
|
|
737
|
+
label: `config.${params.registry}.${params.id}`,
|
|
738
|
+
});
|
|
677
739
|
return { read };
|
|
678
740
|
}
|
|
679
741
|
const read = structureForRead(reg.value, {
|
|
742
|
+
whole: true,
|
|
680
743
|
label: `config.${params.registry}`,
|
|
681
744
|
overCapDirective: {
|
|
682
745
|
tool: "read-config",
|
|
@@ -730,7 +793,7 @@ export const ops = [
|
|
|
730
793
|
if (tool === undefined) {
|
|
731
794
|
return `list-tools: tool not found — name=${params.name}`;
|
|
732
795
|
}
|
|
733
|
-
const read = structureForRead(tool, { label: `tool ${params.name}` });
|
|
796
|
+
const read = structureForRead(tool, { whole: true, label: `tool ${params.name}` });
|
|
734
797
|
return { read };
|
|
735
798
|
}
|
|
736
799
|
// Default: compact index (FGAP-101) — name + param count + one-line description.
|
|
@@ -757,6 +820,7 @@ export const ops = [
|
|
|
757
820
|
label: "Read Samples Catalog",
|
|
758
821
|
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
822
|
promptSnippet: "Discover installable sample block kinds — title, shape, relation_types, invariants, lenses",
|
|
823
|
+
examples: [`pi-context read-samples-catalog --kind tasks --json`],
|
|
760
824
|
parameters: Type.Object({
|
|
761
825
|
kind: Type.Optional(Type.String({ description: "Filter to one block_kind canonical_id (e.g. 'tasks')" })),
|
|
762
826
|
}),
|
|
@@ -768,17 +832,48 @@ export const ops = [
|
|
|
768
832
|
const read = structureForRead(catalog, {
|
|
769
833
|
label: params.kind ? `samples kind=${params.kind}` : "samples catalog",
|
|
770
834
|
// Whole catalog → narrow by kind; a single kind has no finer
|
|
771
|
-
// addressing (edge → head-leading marker, no directive).
|
|
772
|
-
|
|
835
|
+
// addressing (edge → head-leading marker, no directive). The
|
|
836
|
+
// single-kind read is an addressed node — return it whole (capped),
|
|
837
|
+
// never a 50-item page of an incidental array child.
|
|
838
|
+
...(params.kind
|
|
839
|
+
? { whole: true }
|
|
840
|
+
: { overCapDirective: { tool: "read-samples-catalog", hint: "kind=<canonical_id>" } }),
|
|
773
841
|
});
|
|
774
842
|
return { read };
|
|
775
843
|
},
|
|
776
844
|
},
|
|
845
|
+
{
|
|
846
|
+
name: "read-catalog-schema",
|
|
847
|
+
label: "Read Catalog Schema",
|
|
848
|
+
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.",
|
|
849
|
+
promptSnippet: "Fetch and print the verbatim catalog schema body for a named block kind (raw JSON Schema, diffable locally)",
|
|
850
|
+
examples: ["pi-context read-catalog-schema --kind tasks", "pi-context read-catalog-schema --kind tasks --json"],
|
|
851
|
+
parameters: Type.Object({
|
|
852
|
+
kind: Type.String({ description: "Catalog block_kind canonical_id (e.g. 'tasks')" }),
|
|
853
|
+
}),
|
|
854
|
+
surface: "use",
|
|
855
|
+
// The catalog schema file carries its own trailing newline (`}\n`); emit the file
|
|
856
|
+
// bytes exactly — preserving that single newline, appending none — so
|
|
857
|
+
// `read-catalog-schema --kind <k> | diff <installed> -` shows no phantom line when
|
|
858
|
+
// content matches (the pre-flag defect was the print path appending a second
|
|
859
|
+
// newline, doubling it to `}\n\n`).
|
|
860
|
+
verbatimText: true,
|
|
861
|
+
run(_cwd, params) {
|
|
862
|
+
// Package-intrinsic: reads the extension's bundled catalog schema file,
|
|
863
|
+
// not the project substrate — cwd is unused (like read-samples-catalog).
|
|
864
|
+
// Returns the RAW TEXT bytes as a prose-string OpResult so the verbatim
|
|
865
|
+
// catalog body prints as-is (renderOpResultText) and rides the --json
|
|
866
|
+
// envelope as a string; no {json}/{read} wrap that would re-serialize and
|
|
867
|
+
// alter the bytes the operator diffs.
|
|
868
|
+
return readCatalogSchemaText(params.kind).text;
|
|
869
|
+
},
|
|
870
|
+
},
|
|
777
871
|
{
|
|
778
872
|
name: "context-current-state",
|
|
779
873
|
label: "Context Current State",
|
|
780
|
-
description: "Derive 'where are we + what's next' purely from the substrate — focus, in-flight
|
|
781
|
-
promptSnippet: "Derive current project state — focus, in-flight, next actions, blocked",
|
|
874
|
+
description: "Derive 'where are we + what's next' purely from the substrate — focus, in-flight items, ranked atomic-next actions, blocked items, and milestone rollups. Every facet derives from the config-declared `state_derivation` registry: which block kinds + status bucket count as in-flight, the focus fallback kind + bucket, the ordered cross-kind next-actions push order with per-entry ranking (a named field + ordered value list, e.g. gap priority P0..P3) or topo ordering over the blocking-relation graph, the relation_types whose edges contribute blockers (the stock set being `task_depends_on_task` dependencies + `task_gated_by_item` gates), the membership rollups (e.g. milestones over `phase_positioned_in_milestone`) with their complete/incomplete status strings, and the next-actions head-size cap. A blocked item's dependency/gate target that has not reached the complete bucket is reported in blockedBy and held out of nextActions; a target reaching its complete status releases it. A substrate whose config declares no `state_derivation` reports focus 'state-derivation not configured' with empty arrays. No writes; nothing hand-stored.",
|
|
875
|
+
promptSnippet: "Derive current project state from the config-declared state_derivation registry — focus, in-flight, ranked next actions, blocked, milestone rollups",
|
|
876
|
+
examples: [`pi-context context-current-state --json`],
|
|
782
877
|
parameters: Type.Object({}),
|
|
783
878
|
surface: "use",
|
|
784
879
|
run(cwd, _params) {
|
|
@@ -791,6 +886,7 @@ export const ops = [
|
|
|
791
886
|
label: "Context Bootstrap State",
|
|
792
887
|
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
888
|
promptSnippet: "Derive substrate bootstrap state — no-pointer | no-config | skeleton | not-installed | ready (never throws pre-bootstrap)",
|
|
889
|
+
examples: [`pi-context context-bootstrap-state --json`],
|
|
794
890
|
parameters: Type.Object({}),
|
|
795
891
|
surface: "use",
|
|
796
892
|
run(cwd, _params) {
|
|
@@ -803,6 +899,7 @@ export const ops = [
|
|
|
803
899
|
label: "Rename Canonical Id",
|
|
804
900
|
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
901
|
promptSnippet: "Rename a canonical_id (item/relation_type/lens/layer) across substrate; dryRun to preview",
|
|
902
|
+
examples: [`pi-context rename-canonical-id --kind item --oldId TASK-001 --newId TASK-100 --dryRun true --json`],
|
|
806
903
|
parameters: Type.Object({
|
|
807
904
|
kind: Type.String({ description: "One of: item | relation_type | lens | layer" }),
|
|
808
905
|
oldId: Type.String({ description: "Current canonical_id to rename from" }),
|
|
@@ -826,6 +923,9 @@ export const ops = [
|
|
|
826
923
|
"relation_type / lens / layer / block_kind) is NOT checked here — run context-validate after. dryRun previews " +
|
|
827
924
|
"without writing.",
|
|
828
925
|
promptSnippet: "Add/replace/remove one entry in a config.json registry (vocabulary, lenses, invariants, status_buckets)",
|
|
926
|
+
examples: [
|
|
927
|
+
`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`,
|
|
928
|
+
],
|
|
829
929
|
parameters: Type.Object({
|
|
830
930
|
registry: Type.String({
|
|
831
931
|
description: "One of: block_kinds | relation_types | lenses | layers | invariants | status_buckets | display_strings | naming | installed_schemas | installed_blocks | hierarchy",
|
|
@@ -869,6 +969,7 @@ export const ops = [
|
|
|
869
969
|
label: "Read Schema",
|
|
870
970
|
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
971
|
promptSnippet: "Read a block schema as structured JSON — optionally address one property via `path`",
|
|
972
|
+
examples: [`pi-context read-schema --schemaName framework-gaps --path properties.gaps.items.required --json`],
|
|
872
973
|
parameters: Type.Object({
|
|
873
974
|
schemaName: Type.String({
|
|
874
975
|
description: "Schema name without extension (e.g., 'tasks', 'decisions', 'issues')",
|
|
@@ -886,7 +987,7 @@ export const ops = [
|
|
|
886
987
|
if (!addr.found) {
|
|
887
988
|
return `read-schema: property not found — ${addr.resolved}`;
|
|
888
989
|
}
|
|
889
|
-
const read = structureForRead(addr.value, { label: `${params.schemaName} ${addr.resolved}` });
|
|
990
|
+
const read = structureForRead(addr.value, { whole: true, label: `${params.schemaName} ${addr.resolved}` });
|
|
890
991
|
return { read };
|
|
891
992
|
}
|
|
892
993
|
const result = { schema, schemaPath: schemaPathStr };
|
|
@@ -910,6 +1011,9 @@ export const ops = [
|
|
|
910
1011
|
"read/write of items declaring an older schema_version throws version-mismatch. Registering the block_kind " +
|
|
911
1012
|
"that points at this schema is a separate step (amend-config block_kinds).",
|
|
912
1013
|
promptSnippet: "Create or replace a block-kind JSON Schema (meta-validated, atomic)",
|
|
1014
|
+
examples: [
|
|
1015
|
+
`pi-context write-schema --operation create --schemaName tasks --schema @/tmp/tasks.schema.json --writer '{"kind":"human","user":"you@example.com"}' --json`,
|
|
1016
|
+
],
|
|
913
1017
|
parameters: Type.Object({
|
|
914
1018
|
operation: Type.String({ description: "create | replace" }),
|
|
915
1019
|
schemaName: Type.String({ description: "Schema name without extension (e.g., 'tasks')" }),
|
|
@@ -937,11 +1041,52 @@ export const ops = [
|
|
|
937
1041
|
return `write-schema: ${verb} schema '${params.schemaName}' at ${result.schemaPath}`;
|
|
938
1042
|
},
|
|
939
1043
|
},
|
|
1044
|
+
{
|
|
1045
|
+
name: "resolve-conflict",
|
|
1046
|
+
label: "Resolve Schema Conflict",
|
|
1047
|
+
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.",
|
|
1048
|
+
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)",
|
|
1049
|
+
examples: [
|
|
1050
|
+
`pi-context resolve-conflict --schemaName tasks --schema @/tmp/tasks.reconciled.json --writer '{"kind":"human","user":"you@example.com"}' --json`,
|
|
1051
|
+
],
|
|
1052
|
+
parameters: Type.Object({
|
|
1053
|
+
schemaName: Type.String({ description: "Schema name without extension (e.g., 'tasks')" }),
|
|
1054
|
+
schema: Type.Optional(Type.Unknown({
|
|
1055
|
+
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.",
|
|
1056
|
+
})),
|
|
1057
|
+
}),
|
|
1058
|
+
surface: "use",
|
|
1059
|
+
authGated: true,
|
|
1060
|
+
run(cwd, params, ctx) {
|
|
1061
|
+
const result = resolveConflict(cwd, params.schemaName, params.schema, ctx);
|
|
1062
|
+
return { json: result };
|
|
1063
|
+
},
|
|
1064
|
+
},
|
|
1065
|
+
{
|
|
1066
|
+
name: "resolve-blocked",
|
|
1067
|
+
label: "Resolve Blocked",
|
|
1068
|
+
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. The commit is all-or-nothing: a throw partway through it restores every touched file byte-exact — migrations.json, the installed schema, the block file, config.json, and the pending record — and reports the failure, never a partial commit. On a substrate whose config carries no substrate_id, resolve-blocked establishes the identity at entry (mints, persists, registers) before the commit's stamping write and reports it under substrateIdEstablished.",
|
|
1069
|
+
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)",
|
|
1070
|
+
examples: [`pi-context resolve-blocked --schemaName tasks --yes --json`],
|
|
1071
|
+
parameters: Type.Object({
|
|
1072
|
+
schemaName: Type.String({
|
|
1073
|
+
description: "Schema name with a pending-blocked entry (from update's blocked report)",
|
|
1074
|
+
}),
|
|
1075
|
+
}),
|
|
1076
|
+
surface: "use",
|
|
1077
|
+
authGated: true,
|
|
1078
|
+
run(cwd, params, ctx) {
|
|
1079
|
+
return { json: resolveBlocked(cwd, params.schemaName, ctx) };
|
|
1080
|
+
},
|
|
1081
|
+
},
|
|
940
1082
|
{
|
|
941
1083
|
name: "write-schema-migration",
|
|
942
1084
|
label: "Write Schema Migration",
|
|
943
|
-
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.",
|
|
1085
|
+
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/map_each operations on dotted JSON paths; map_each addresses an array — table mode maps each string element through a lookup (unmatched elements become {relation_type, item_endpoint} with parent/child fallback), set-on-each mode sets a field on every object element. The loaded MigrationRegistry resolves the recorded edge at next read/write so block items declaring an older schema_version walk forward without process restart. Requires user authorization via interactive confirmation at the pi-dispatch auth-gate; on confirm, the verified terminal-operator identity is stamped as writer.",
|
|
944
1086
|
promptSnippet: "Declare a schema version-bump migration (identity or declarative-transform) into migrations.json",
|
|
1087
|
+
examples: [
|
|
1088
|
+
`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`,
|
|
1089
|
+
],
|
|
945
1090
|
parameters: Type.Object({
|
|
946
1091
|
operation: Type.String({ description: "create | replace | remove" }),
|
|
947
1092
|
schemaName: Type.String({ description: "Schema name without extension (e.g., 'tasks')." }),
|
|
@@ -975,6 +1120,7 @@ export const ops = [
|
|
|
975
1120
|
label: "Context Init",
|
|
976
1121
|
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
1122
|
promptSnippet: "Initialize the substrate dir (bootstrap pointer + dirs + skeleton config; onward via accept-all OR amend-config/edit)",
|
|
1123
|
+
examples: [`pi-context context-init --contextDir .context --json`],
|
|
978
1124
|
parameters: Type.Object({
|
|
979
1125
|
contextDir: Type.String({
|
|
980
1126
|
description: "Substrate dir name (e.g. .context). Required — no default.",
|
|
@@ -992,6 +1138,7 @@ export const ops = [
|
|
|
992
1138
|
label: "Accept-All Conception",
|
|
993
1139
|
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
1140
|
promptSnippet: "Adopt the canonical conception as config (accept-all)",
|
|
1141
|
+
examples: [`pi-context context-accept-all --json`],
|
|
995
1142
|
parameters: Type.Object({}),
|
|
996
1143
|
surface: "use",
|
|
997
1144
|
authGated: true,
|
|
@@ -1009,11 +1156,65 @@ export const ops = [
|
|
|
1009
1156
|
return { json: result };
|
|
1010
1157
|
},
|
|
1011
1158
|
},
|
|
1159
|
+
{
|
|
1160
|
+
name: "context-install",
|
|
1161
|
+
label: "Context Install",
|
|
1162
|
+
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. On a substrate whose config carries no substrate_id, install establishes the identity at entry (mints, persists to config.json, registers in the project registry) and reports it under substrateIdEstablished; an established identity is never re-minted.",
|
|
1163
|
+
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)",
|
|
1164
|
+
examples: ["pi-context context-install --json", "pi-context context-install --update true --json"],
|
|
1165
|
+
parameters: Type.Object({
|
|
1166
|
+
update: Type.Optional(Type.Boolean({
|
|
1167
|
+
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.",
|
|
1168
|
+
})),
|
|
1169
|
+
}),
|
|
1170
|
+
surface: "use",
|
|
1171
|
+
authGated: true,
|
|
1172
|
+
run(cwd, params) {
|
|
1173
|
+
const result = installContext(cwd, { overwrite: params.update === true });
|
|
1174
|
+
if (result.error)
|
|
1175
|
+
return result.error;
|
|
1176
|
+
return { json: result };
|
|
1177
|
+
},
|
|
1178
|
+
},
|
|
1179
|
+
{
|
|
1180
|
+
name: "update",
|
|
1181
|
+
label: "Update Installed Model",
|
|
1182
|
+
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) vs write-failed (a non-validation throw at the write boundary, e.g. the block writer's duplicate-item-id guard; the failures entry carries the thrown message, the items were NOT flagged invalid, and no markers or pending-blocked record are produced) — the installed -> catalog version pair, and for a validation failure the per-item failures naming the failing item id, field, and constraint. A live blocked resync also persists a pending-blocked record (pinning the target catalog schema + the chain reaching it) consumable by resolve-blocked, which commits the resolution once the block's items are fixed. Pass dryRun to preview the per-schema action plan; dryRun predicts the precise per-schema catalog-ahead outcome (resync / migrate / block / merge / conflict) by running the forward-migration + re-validation in memory, the per-blocked-schema diagnostic detail, the config-registry entries that would be added, AND the migration declarations that would be registered, writing nothing beyond the idempotent ceremony seed of the catalog's config migration declarations into migrations.json (every substrate-lifecycle ceremony seeds at entry, before its first config read, so a version-lagging legacy substrate heals instead of throwing). When a catalog-ahead resync is blocked because the block's items fail the catalog schema (validation-failed), update inscribes git-style failure markers INTO the block file at the offending items (full-line `<<<<<<< BLOCKED …` / `>>>>>>> target: …` sentinels), pinning the pre-marker bytes so resolve-blocked can strip the markers and re-validate; the schema and migrations.json stay byte-unchanged. A dryRun preview writes no markers. Because update applies per-component (a blocked schema rolls back only itself; the additive registry propagation writes regardless), a run that refuses any schema while applying registry additions or other-schema resyncs/migrations/merges reports the partiality under partialApplication — applied and notApplied channel mirrors plus a one-line summary naming what was applied alongside what was refused and why — so a blocked run never reads as a no-op; dryRun reports the predicted partiality in the same shape. On a substrate whose config carries no substrate_id, a LIVE update establishes the identity at entry (mints, persists to config.json, registers in the project registry) before its first identity-stamping write — so a pre-identity substrate heals on the ceremony instead of refusing — and reports it under substrateIdEstablished; an established identity is never re-minted, and dryRun (no stamping writes) establishes nothing.",
|
|
1183
|
+
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 (no-migration-chain / validation-failed / write-failed for a non-validation write-boundary throw), version pair, per-item failures — and a validation-failed block persists a pending-blocked record (target catalog schema + the chain reaching it) resolved via resolve-blocked once the block's items are fixed; a validation-failed block is marked in place with git-style failure markers (recoverable; stripped + re-validated by resolve-blocked); --dry-run predicts the precise per-schema outcome — resync / migrate / block / merge / conflict — via in-memory forward-migration + re-validation, writing nothing; a run that refuses any schema while applying registry additions or other-schema updates surfaces the partiality under partialApplication with a one-line summary, so a blocked run never reads as a no-op)",
|
|
1184
|
+
examples: [`pi-context update --dryRun true --json`],
|
|
1185
|
+
parameters: Type.Object({
|
|
1186
|
+
dryRun: Type.Optional(Type.Boolean({ description: "Preview the per-schema action plan without writing anything." })),
|
|
1187
|
+
}),
|
|
1188
|
+
surface: "use",
|
|
1189
|
+
run(cwd, params) {
|
|
1190
|
+
const result = updateContext(cwd, { dryRun: params.dryRun === true });
|
|
1191
|
+
if (result.error)
|
|
1192
|
+
return result.error;
|
|
1193
|
+
return { json: result };
|
|
1194
|
+
},
|
|
1195
|
+
},
|
|
1196
|
+
{
|
|
1197
|
+
name: "validate-block-items",
|
|
1198
|
+
label: "Validate Block Items",
|
|
1199
|
+
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.",
|
|
1200
|
+
promptSnippet: "Validate a block's items against the catalog schema version — returns the per-item failures (item id, field, constraint) without writing",
|
|
1201
|
+
examples: [`pi-context validate-block-items --block tasks --json`],
|
|
1202
|
+
parameters: Type.Object({
|
|
1203
|
+
block: Type.String({ description: "Block name (e.g. 'tasks')" }),
|
|
1204
|
+
}),
|
|
1205
|
+
surface: "use",
|
|
1206
|
+
run(cwd, params) {
|
|
1207
|
+
return { json: validateBlockItemsAgainstCatalog(cwd, params.block) };
|
|
1208
|
+
},
|
|
1209
|
+
},
|
|
1012
1210
|
{
|
|
1013
1211
|
name: "context-switch",
|
|
1014
1212
|
label: "Context Switch",
|
|
1015
1213
|
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
1214
|
promptSnippet: "Switch the bootstrap pointer to a different substrate dir",
|
|
1215
|
+
examples: [
|
|
1216
|
+
`pi-context context-switch --target_dir .context --writer '{"kind":"human","user":"you@example.com"}' --json`,
|
|
1217
|
+
],
|
|
1017
1218
|
parameters: Type.Object({
|
|
1018
1219
|
target_dir: Type.String({
|
|
1019
1220
|
description: "Substrate dir name to switch to (e.g. '.context'). Required for default + create_new modes; ignored for to_previous mode.",
|
|
@@ -1068,6 +1269,7 @@ export const ops = [
|
|
|
1068
1269
|
label: "Context List",
|
|
1069
1270
|
description: "Enumerate top-level dirs under cwd containing a config.json (switchable substrates). Marks the active one with isActive=true. Read-only.",
|
|
1070
1271
|
promptSnippet: "List switchable substrate dirs under cwd",
|
|
1272
|
+
examples: [`pi-context context-list --json`],
|
|
1071
1273
|
parameters: Type.Object({}),
|
|
1072
1274
|
surface: "use",
|
|
1073
1275
|
run(cwd, _params) {
|
|
@@ -1080,6 +1282,9 @@ export const ops = [
|
|
|
1080
1282
|
label: "Context Archive",
|
|
1081
1283
|
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
1284
|
promptSnippet: "Archive a non-active substrate dir to archive/<dir>/",
|
|
1285
|
+
examples: [
|
|
1286
|
+
`pi-context context-archive --target_dir .context-old --writer '{"kind":"human","user":"you@example.com"}' --json`,
|
|
1287
|
+
],
|
|
1083
1288
|
parameters: Type.Object({
|
|
1084
1289
|
target_dir: Type.String({
|
|
1085
1290
|
description: "Substrate dir name to archive (e.g. '.project'). Refused if it is the active substrate.",
|
|
@@ -1103,6 +1308,7 @@ export const ops = [
|
|
|
1103
1308
|
label: "Filter Block Items",
|
|
1104
1309
|
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
1310
|
promptSnippet: "Filter a block's items by a predicate — eq / neq / in / matches against a single field",
|
|
1311
|
+
examples: [`pi-context filter-block-items --block framework-gaps --field status --op eq --value '"open"' --json`],
|
|
1106
1312
|
parameters: Type.Object({
|
|
1107
1313
|
block: Type.String({
|
|
1108
1314
|
description: "Block name (e.g., 'tasks', 'decisions', 'framework-gaps', 'context-contracts')",
|
|
@@ -1134,6 +1340,7 @@ export const ops = [
|
|
|
1134
1340
|
label: "Resolve Item By Id",
|
|
1135
1341
|
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
1342
|
promptSnippet: "Resolve a kind-prefixed ID (DEC-/FEAT-/FGAP-/issue-/REQ-/TASK-/etc.) to its owning block and item",
|
|
1343
|
+
examples: [`pi-context resolve-item-by-id --id TASK-001 --json`],
|
|
1137
1344
|
parameters: Type.Object({
|
|
1138
1345
|
id: Type.String({ description: "Kind-prefixed ID, e.g., DEC-NNNN / FEAT-NNN / FGAP-NNN / ISSUE-NNN" }),
|
|
1139
1346
|
}),
|
|
@@ -1158,6 +1365,7 @@ export const ops = [
|
|
|
1158
1365
|
label: "Read Block Item",
|
|
1159
1366
|
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
1367
|
promptSnippet: "Read one item from a block by id (block-scoped; null if absent)",
|
|
1368
|
+
examples: [`pi-context read-block-item --block tasks --id TASK-001 --json`],
|
|
1161
1369
|
parameters: Type.Object({
|
|
1162
1370
|
block: Type.String({ description: "Block name (e.g., 'tasks', 'decisions', 'framework-gaps')" }),
|
|
1163
1371
|
id: Type.String({ description: "Item id within the block (e.g., 'TASK-NNN')" }),
|
|
@@ -1176,6 +1384,7 @@ export const ops = [
|
|
|
1176
1384
|
label: "Read Block Page",
|
|
1177
1385
|
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
1386
|
promptSnippet: "Paginate a block's items — offset + limit; returns {items,total,hasMore}",
|
|
1387
|
+
examples: [`pi-context read-block-page --block tasks --limit 20 --json`],
|
|
1179
1388
|
parameters: Type.Object({
|
|
1180
1389
|
block: Type.String({ description: "Block name (e.g., 'framework-gaps', 'decisions', 'issues')" }),
|
|
1181
1390
|
offset: Type.Optional(Type.Integer({ minimum: 0, description: "Start index (default 0)" })),
|
|
@@ -1195,6 +1404,9 @@ export const ops = [
|
|
|
1195
1404
|
label: "Join Blocks",
|
|
1196
1405
|
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
1406
|
promptSnippet: "Join two blocks in one call — by relation edge or shared field; returns {left,right[]} pairs",
|
|
1407
|
+
examples: [
|
|
1408
|
+
`pi-context join-blocks --leftBlock tasks --rightBlock verification --relationType verification_verifies_item --leftEndpoint child --json`,
|
|
1409
|
+
],
|
|
1198
1410
|
parameters: Type.Object({
|
|
1199
1411
|
leftBlock: Type.String({ description: "Left block name (e.g., 'tasks')" }),
|
|
1200
1412
|
rightBlock: Type.String({ description: "Right block name (e.g., 'verification')" }),
|
|
@@ -1237,6 +1449,7 @@ export const ops = [
|
|
|
1237
1449
|
label: "Resolve Items By Id (Bulk)",
|
|
1238
1450
|
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
1451
|
promptSnippet: "Resolve a batch of kind-prefixed ids (DEC-/FGAP-/TASK-/issue-/REQ-/...) in one call",
|
|
1452
|
+
examples: [`pi-context resolve-items-by-id --ids '["TASK-001","DEC-0001","FGAP-042"]' --json`],
|
|
1240
1453
|
parameters: Type.Object({
|
|
1241
1454
|
ids: Type.Array(Type.String(), {
|
|
1242
1455
|
description: "Array of kind-prefixed ids (DEC-/FGAP-/TASK-/issue-/REQ-/...) to resolve in one call",
|
|
@@ -1259,6 +1472,9 @@ export const ops = [
|
|
|
1259
1472
|
label: "Complete Task",
|
|
1260
1473
|
description: "Complete a task with verification gate — requires a passing verification entry targeting the task.",
|
|
1261
1474
|
promptSnippet: "Complete a task — gates on passing verification before updating status",
|
|
1475
|
+
examples: [
|
|
1476
|
+
`pi-context complete-task --taskId TASK-001 --verificationId VER-001 --writer '{"kind":"human","user":"you@example.com"}' --json`,
|
|
1477
|
+
],
|
|
1262
1478
|
parameters: Type.Object({
|
|
1263
1479
|
taskId: Type.String({ description: "Task ID to complete" }),
|
|
1264
1480
|
verificationId: Type.String({
|
|
@@ -1276,6 +1492,7 @@ export const ops = [
|
|
|
1276
1492
|
label: "Context Validate Relations",
|
|
1277
1493
|
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
1494
|
promptSnippet: "Validate substrate relations against config + items",
|
|
1495
|
+
examples: [`pi-context context-validate-relations --json`],
|
|
1279
1496
|
parameters: Type.Object({}),
|
|
1280
1497
|
surface: "use",
|
|
1281
1498
|
run(cwd, _params) {
|
|
@@ -1288,6 +1505,7 @@ export const ops = [
|
|
|
1288
1505
|
label: "Context Edges For Lens",
|
|
1289
1506
|
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
1507
|
promptSnippet: "Materialize edges for a named lens (auto-derived or hand-curated)",
|
|
1508
|
+
examples: [`pi-context context-edges-for-lens --lensId feature-decomposition --json`],
|
|
1291
1509
|
parameters: Type.Object({
|
|
1292
1510
|
lensId: Type.String({ description: "Lens id from config.lenses[].id" }),
|
|
1293
1511
|
}),
|
|
@@ -1298,11 +1516,51 @@ export const ops = [
|
|
|
1298
1516
|
return { read };
|
|
1299
1517
|
},
|
|
1300
1518
|
},
|
|
1519
|
+
{
|
|
1520
|
+
name: "context-lens-view",
|
|
1521
|
+
label: "Context Lens View",
|
|
1522
|
+
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.",
|
|
1523
|
+
promptSnippet: "Project a config-declared lens as a binned item-view — bin->count summary, or one bin's items paged",
|
|
1524
|
+
examples: [
|
|
1525
|
+
`pi-context context-lens-view --lensId gaps-by-status --json`,
|
|
1526
|
+
`pi-context context-lens-view --lensId gaps-by-status --bin identified --limit 20 --json`,
|
|
1527
|
+
],
|
|
1528
|
+
parameters: Type.Object({
|
|
1529
|
+
lensId: Type.String({ description: "Lens id from config.lenses[].id" }),
|
|
1530
|
+
bin: Type.Optional(Type.String({ description: "Return this bin's items paged; omit for a bin->count summary" })),
|
|
1531
|
+
offset: Type.Optional(Type.Integer({ minimum: 0, description: "Per-bin page start index (default 0)" })),
|
|
1532
|
+
limit: Type.Optional(Type.Integer({ minimum: 1, description: "Per-bin page size (default 50)" })),
|
|
1533
|
+
}),
|
|
1534
|
+
surface: "use",
|
|
1535
|
+
run(cwd, params) {
|
|
1536
|
+
const view = loadLensView(cwd, params.lensId);
|
|
1537
|
+
if ("error" in view)
|
|
1538
|
+
throw new Error(view.error); // unknown lens / no-config -> non-zero exit
|
|
1539
|
+
if (params.bin !== undefined) {
|
|
1540
|
+
const items = view.grouped.get(params.bin);
|
|
1541
|
+
if (items === undefined)
|
|
1542
|
+
throw new Error(`Bin '${params.bin}' not declared on lens '${params.lensId}'. Bins: ${view.lens.bins.join(", ")}`);
|
|
1543
|
+
const page = pageArray(items, { offset: params.offset, limit: params.limit });
|
|
1544
|
+
return { read: structureForRead(page, { whole: true, label: `lens ${params.lensId} bin ${params.bin}` }) };
|
|
1545
|
+
}
|
|
1546
|
+
const summary = {
|
|
1547
|
+
lens: view.lens.id,
|
|
1548
|
+
kind: view.lens.kind ?? "target",
|
|
1549
|
+
bins: Object.fromEntries(view.lens.bins.map((b) => [b, (view.grouped.get(b) ?? []).length])),
|
|
1550
|
+
uncategorized: view.uncategorized.length,
|
|
1551
|
+
total: view.items.length,
|
|
1552
|
+
};
|
|
1553
|
+
return { read: structureForRead(summary, { whole: true, label: `lens ${params.lensId} bins` }) };
|
|
1554
|
+
},
|
|
1555
|
+
},
|
|
1301
1556
|
{
|
|
1302
1557
|
name: "context-walk-descendants",
|
|
1303
1558
|
label: "Context Walk Descendants",
|
|
1304
|
-
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).",
|
|
1559
|
+
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). For a DISJOINT-kind relation, querying from the wrong (target-kind) end THROWS naming walk-ancestors instead of silently returning []; same-kind / wildcard relations return [] honestly.",
|
|
1305
1560
|
promptSnippet: "Walk closure-table descendants under a relation_type",
|
|
1561
|
+
examples: [
|
|
1562
|
+
`pi-context context-walk-descendants --parentId FEAT-008 --relationType feature_decomposed_into_task --json`,
|
|
1563
|
+
],
|
|
1306
1564
|
parameters: Type.Object({
|
|
1307
1565
|
parentId: Type.String({ description: "Parent id (canonical id or lens bin name)" }),
|
|
1308
1566
|
relationType: Type.String({ description: "Relation type from config.relation_types[].canonical_id" }),
|
|
@@ -1316,8 +1574,9 @@ export const ops = [
|
|
|
1316
1574
|
{
|
|
1317
1575
|
name: "walk-ancestors",
|
|
1318
1576
|
label: "Walk Ancestors",
|
|
1319
|
-
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).",
|
|
1577
|
+
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). For a DISJOINT-kind relation, querying from the wrong (source-kind) end THROWS naming context-walk-descendants instead of silently returning []; same-kind / wildcard relations return [] honestly.",
|
|
1320
1578
|
promptSnippet: "Walk closure-table ancestors under a relation_type",
|
|
1579
|
+
examples: [`pi-context walk-ancestors --itemId TASK-042 --relationType feature_decomposed_into_task --json`],
|
|
1321
1580
|
parameters: Type.Object({
|
|
1322
1581
|
itemId: Type.String({ description: "Child item id whose ancestors are sought" }),
|
|
1323
1582
|
relationType: Type.String({ description: "Relation type from config.relation_types[].canonical_id" }),
|
|
@@ -1334,6 +1593,7 @@ export const ops = [
|
|
|
1334
1593
|
label: "Find References",
|
|
1335
1594
|
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
1595
|
promptSnippet: "Find closure-table edges incident on an item id",
|
|
1596
|
+
examples: [`pi-context find-references --itemId TASK-001 --direction both --json`],
|
|
1337
1597
|
parameters: Type.Object({
|
|
1338
1598
|
itemId: Type.String({ description: "Item id whose incident edges are sought" }),
|
|
1339
1599
|
direction: Type.Optional(Type.Union([Type.Literal("inbound"), Type.Literal("outbound"), Type.Literal("both")], {
|
|
@@ -1352,6 +1612,7 @@ export const ops = [
|
|
|
1352
1612
|
label: "Gather Execution Context",
|
|
1353
1613
|
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
1614
|
promptSnippet: "Compose ContextBundle for unit + context-contract-declared bundle_relation_types",
|
|
1615
|
+
examples: [`pi-context gather-execution-context --unitId TASK-001 --kind task --json`],
|
|
1355
1616
|
parameters: Type.Object({
|
|
1356
1617
|
unitId: Type.String({ description: "Work-unit id (e.g. TASK-NNN / DEC-NNNN / FGAP-NNN)" }),
|
|
1357
1618
|
kind: Type.String({
|
|
@@ -1374,71 +1635,45 @@ export const ops = [
|
|
|
1374
1635
|
{
|
|
1375
1636
|
name: "context-roadmap-load",
|
|
1376
1637
|
label: "Context: load roadmap",
|
|
1377
|
-
description: "Load
|
|
1378
|
-
promptSnippet: "Load
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
}),
|
|
1638
|
+
description: "Load the derived roadmap view over the milestone_precedes_milestone DAG: milestone-block items topo-ordered by the authored precedes edges (order + cycles), each milestone carrying its derived status/phaseCount (currentState's milestone rollup), its member phases (parents of phase_positioned_in_milestone edges), each phase's tasks (parents of task_positioned_in_phase edges), and per-phase + per-milestone status rollups. Adjacency comes strictly from the authored edges — never inferred from order. Zero milestones is a valid empty view.",
|
|
1639
|
+
promptSnippet: "Load the derived milestone roadmap view",
|
|
1640
|
+
examples: [`pi-context context-roadmap-load --json`],
|
|
1641
|
+
parameters: Type.Object({}),
|
|
1382
1642
|
surface: "use",
|
|
1383
|
-
run(cwd,
|
|
1384
|
-
const view = loadRoadmap(cwd
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
}
|
|
1389
|
-
// whole: a structured RoadmapView (phases + lens-views + rollups) — keep
|
|
1390
|
-
// the view shape intact rather than paging an inner array.
|
|
1391
|
-
const read = structureForRead(serializeRoadmapView(view), {
|
|
1392
|
-
whole: true,
|
|
1393
|
-
label: `roadmap ${params.roadmapId}`,
|
|
1394
|
-
});
|
|
1643
|
+
run(cwd, _params) {
|
|
1644
|
+
const view = loadRoadmap(cwd);
|
|
1645
|
+
// whole: a structured MilestoneRoadmapView (milestones + rollups + edges)
|
|
1646
|
+
// — keep the view shape intact rather than paging an inner array.
|
|
1647
|
+
const read = structureForRead(view, { whole: true, label: "roadmap (derived)" });
|
|
1395
1648
|
return { read };
|
|
1396
1649
|
},
|
|
1397
1650
|
},
|
|
1398
1651
|
{
|
|
1399
1652
|
name: "context-roadmap-render",
|
|
1400
1653
|
label: "Context: render roadmap",
|
|
1401
|
-
description: "Render
|
|
1402
|
-
promptSnippet: "Render
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
}),
|
|
1654
|
+
description: "Render the derived roadmap as pure-textual markdown — milestone order list (topo over the authored milestone_precedes_milestone edges), per-milestone sections with **Preceded by:** adjacency lines sourced strictly from those edges (alphabetically sorted; '—' when none), per-milestone rollup counts, and per-phase task tables. Cycle participants surface under a separate heading with a Cycles-detected line. NO mermaid / graph syntax; adjacency is never inferred from order consecutive pairs.",
|
|
1655
|
+
promptSnippet: "Render the derived milestone roadmap as markdown",
|
|
1656
|
+
examples: [`pi-context context-roadmap-render`],
|
|
1657
|
+
parameters: Type.Object({}),
|
|
1406
1658
|
surface: "use",
|
|
1407
|
-
run(cwd,
|
|
1408
|
-
const view = loadRoadmap(cwd
|
|
1659
|
+
run(cwd, _params) {
|
|
1660
|
+
const view = loadRoadmap(cwd);
|
|
1409
1661
|
if ("error" in view) {
|
|
1410
1662
|
return { json: view };
|
|
1411
1663
|
}
|
|
1412
|
-
|
|
1413
|
-
return renderRoadmap(view, naming);
|
|
1664
|
+
return renderRoadmap(view);
|
|
1414
1665
|
},
|
|
1415
1666
|
},
|
|
1416
1667
|
{
|
|
1417
1668
|
name: "context-roadmap-validate",
|
|
1418
|
-
label: "Context: validate roadmap
|
|
1419
|
-
description: "Validate
|
|
1420
|
-
promptSnippet: "Validate
|
|
1421
|
-
|
|
1422
|
-
roadmapId: Type.Optional(Type.String({ description: "Filter to issues matching this roadmap_id (omit for full-project validation)" })),
|
|
1423
|
-
}),
|
|
1424
|
-
surface: "use",
|
|
1425
|
-
run(cwd, params) {
|
|
1426
|
-
const result = validateRoadmaps(cwd);
|
|
1427
|
-
const filtered = params.roadmapId
|
|
1428
|
-
? result.issues.filter((i) => !i.roadmap_id || i.roadmap_id === params.roadmapId)
|
|
1429
|
-
: result.issues;
|
|
1430
|
-
return { json: { status: result.status, issues: filtered } };
|
|
1431
|
-
},
|
|
1432
|
-
},
|
|
1433
|
-
{
|
|
1434
|
-
name: "context-roadmap-list",
|
|
1435
|
-
label: "Context: list roadmaps",
|
|
1436
|
-
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
|
-
promptSnippet: "List roadmaps",
|
|
1669
|
+
label: "Context: validate roadmap",
|
|
1670
|
+
description: "Validate the derived roadmap over the milestone_precedes_milestone edges. Error codes: roadmap_precedes_endpoint_missing (a precedes-edge endpoint that is not a milestone-block item), roadmap_milestone_cycle (a cycle in the precedes graph), roadmap_milestone_missing (a phase_positioned_in_milestone edge whose child is not a known milestone). Warning: roadmap_status_unknown_value (a member phase whose task rollup buckets unknown with items present). Info: roadmap_milestone_isolated (a milestone with zero precedes edges while others are ordered) — info never affects status: invalid iff any error-code issue, warnings iff any warning-code issue, else clean. Display strings flow through config.display_strings (pi-context divergence).",
|
|
1671
|
+
promptSnippet: "Validate the derived milestone roadmap",
|
|
1672
|
+
examples: [`pi-context context-roadmap-validate --json`],
|
|
1438
1673
|
parameters: Type.Object({}),
|
|
1439
1674
|
surface: "use",
|
|
1440
1675
|
run(cwd, _params) {
|
|
1441
|
-
return { json:
|
|
1676
|
+
return { json: validateRoadmap(cwd) };
|
|
1442
1677
|
},
|
|
1443
1678
|
},
|
|
1444
1679
|
];
|