pi-matt-pocock 0.2.1 → 0.2.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-matt-pocock",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "type": "module",
5
5
  "description": "BDD-first engineering and productivity skills adapted from Matt Pocock's skills",
6
6
  "keywords": [
package/src/index.ts CHANGED
@@ -29,7 +29,7 @@ import {
29
29
  workflowDefinitions,
30
30
  workflowPlacement,
31
31
  } from "./catalog.ts";
32
- import { resolveAccessibleReference, resolveProcedureBundle, resolveWorkflowContext } from "./resolver.ts";
32
+ import { resolveAccessibleReference, resolveProcedureBundle, resolveProcedureDelta, resolveWorkflowContext } from "./resolver.ts";
33
33
  /** Geometry bound once: every Matt Pocock row shares hint and wrapping. */
34
34
  const mattPocockRows = bindLifecycleRenderers({
35
35
  fit: truncateToWidth,
@@ -49,6 +49,7 @@ import {
49
49
  transitionState,
50
50
  workflowGuidance,
51
51
  workflowRoutes,
52
+ withDeliveryDefaults,
52
53
  WORKFLOW_STATE_ENTRY,
53
54
  WORKFLOW_STATE_VERSION,
54
55
  type TerminalWorkflowState,
@@ -164,9 +165,29 @@ function userRequestSection(request: string): string {
164
165
  return request ? `\n\nUser target/request:\n${request}` : "";
165
166
  }
166
167
 
167
- function workflowPrompt(state: WorkflowState, action: string, request = ""): string {
168
- const bundle = resolveWorkflowContext(state.procedure, state.loadedReferences);
169
- return `# Matt Pocock workflow procedure\n\nRoute: ${state.route}\nPhase: ${state.phase}\nWork item: ${state.workItemId}\n\n${bundle.content}${userRequestSection(request)}${stateContract(state, action)}`;
168
+ /** Delivered when a procedure's body is already in the session, so the prompt is never empty. */
169
+ const ALREADY_DELIVERED = "Its instructions were delivered earlier in this session and are unchanged. Continue against them; do not re-request them.";
170
+
171
+ /**
172
+ * The prompt for a workflow step, plus the state that records what it delivered.
173
+ *
174
+ * Only procedures this session has not been shown are embedded. A transition re-delivers
175
+ * the active context every time, and a bundle carries its whole dependency closure, so
176
+ * re-sending that closure would spend the context the work needs and end a long task early
177
+ * for want of room rather than for want of work.
178
+ */
179
+ function deliveryStep(state: WorkflowState, action: string, request = "", full = false): { text: string; state: WorkflowState } {
180
+ const delta = resolveProcedureDelta(
181
+ state.procedure,
182
+ state.loadedReferences,
183
+ full ? [] : state.deliveredProcedures,
184
+ );
185
+ const next: WorkflowState = { ...state, deliveredProcedures: delta.delivered };
186
+ const body = delta.content ? delta.content : ALREADY_DELIVERED;
187
+ return {
188
+ text: `# Matt Pocock workflow procedure\n\nRoute: ${next.route}\nPhase: ${next.phase}\nWork item: ${next.workItemId}\n\n${body}${userRequestSection(request)}${stateContract(next, action)}`,
189
+ state: next,
190
+ };
170
191
  }
171
192
 
172
193
  function standalonePrompt(capability: string, request = ""): string {
@@ -185,24 +206,33 @@ function standaloneReferencePrompt(capability: string, reference: string): strin
185
206
  return `# Matt Pocock standalone reference\n\nCapability: ${definition.id}\nReference: ${bundle.root}\nPersistent workflow state: none\nAvailable references: ${bundle.availableReferences.join(", ") || "none"}\n\n${bundle.content}`;
186
207
  }
187
208
 
188
- function startWorkflow(route: string): WorkflowState {
209
+ /**
210
+ * Begin a workflow and persist its state exactly once, carrying the delivery set
211
+ * this first step shows. A second entry would read as two workflows begun, and a
212
+ * persisted state without the set would re-deliver the whole closure on restore.
213
+ */
214
+ function startWorkflow(route: string, request = ""): { state: WorkflowState; text: string } {
189
215
  if (!findWorkflowRoute(route)) throw new Error(`Unknown Matt Pocock route: ${route}`);
190
- const state = routeEntryState(route, randomUUID());
191
- if (!state) throw new Error(`Workflow ${route} has no catalog entry procedure.`);
192
- resolveProcedureBundle(state.procedure);
193
- persistWorkflow(state);
194
- return state;
216
+ const created = routeEntryState(route, randomUUID());
217
+ if (!created) throw new Error(`Workflow ${route} has no catalog entry procedure.`);
218
+ resolveProcedureBundle(created.procedure);
219
+ const step = deliveryStep(created, "started workflow", request);
220
+ persistWorkflow(step.state);
221
+ return step;
195
222
  }
196
223
 
197
224
  function loadActiveReference(reference: string): { state: WorkflowState; content: string } {
198
225
  if (!activeWorkflow) throw new Error("No active Matt Pocock workflow.");
199
226
  const bundle = resolveAccessibleReference(activeWorkflow.procedure, activeWorkflow.loadedReferences, reference);
200
227
  const loadedReferences = [...new Set([...activeWorkflow.loadedReferences, ...bundle.loaded])];
201
- const state = { ...activeWorkflow, loadedReferences };
228
+ // The reference's own closure is deduplicated too: a reference whose body is already in
229
+ // the session does not need to arrive twice.
230
+ const delta = resolveProcedureDelta(reference, [], activeWorkflow.deliveredProcedures);
231
+ const state: WorkflowState = { ...activeWorkflow, loadedReferences, deliveredProcedures: delta.delivered };
202
232
  persistWorkflow(state);
203
233
  return {
204
234
  state,
205
- content: `# Matt Pocock workflow reference\n\nWork item: ${state.workItemId}\nReference: ${bundle.root}\n\n${bundle.content}${stateContract(state, `loaded reference ${bundle.root}`)}`,
235
+ content: `# Matt Pocock workflow reference\n\nWork item: ${state.workItemId}\nReference: ${bundle.root}\n\n${delta.content || ALREADY_DELIVERED}${stateContract(state, `loaded reference ${bundle.root}`)}`,
206
236
  };
207
237
  }
208
238
 
@@ -227,9 +257,9 @@ async function chooseRoute(ctx: ExtensionCommandContext): Promise<void> {
227
257
  if (!choice) return;
228
258
  const route = routeFromChoice(choice);
229
259
  if (!route) return;
230
- const state = startWorkflow(route);
260
+ const step = startWorkflow(route);
231
261
  clearPiStatus(ctx.ui, "matt-pocock");
232
- deliverProcedure(workflowPrompt(state, "started workflow"), { ...state, request: "" });
262
+ deliverProcedure(step.text, { ...step.state, request: "" });
233
263
  }
234
264
 
235
265
  async function chooseCapability(ctx: ExtensionCommandContext): Promise<void> {
@@ -247,8 +277,9 @@ function applyTransition(ctx: ExtensionCommandContext, target: string): void {
247
277
  }
248
278
  const state = transitionState(activeWorkflow, target);
249
279
  resolveProcedureBundle(state.procedure);
250
- persistWorkflow(state);
251
- deliverProcedure(workflowPrompt(state, `transitioned to ${state.procedure}`), { ...state, request: "" });
280
+ const step = deliveryStep(state, `transitioned to ${state.procedure}`);
281
+ persistWorkflow(step.state);
282
+ deliverProcedure(step.text, { ...step.state, request: "" });
252
283
  }
253
284
 
254
285
  async function chooseTransition(ctx: ExtensionCommandContext): Promise<void> {
@@ -342,15 +373,21 @@ export default function mattPocock(extensionApi: ExtensionAPI): void {
342
373
  if (!workflowPlacement(record.route, record.procedure)) {
343
374
  throw new Error(`Procedure ${record.procedure} is not part of workflow ${record.route}.`);
344
375
  }
345
- const content = workflowPrompt(record, "restored workflow");
346
- activeWorkflow = record;
376
+ // A restore is a context boundary: this session may have been compacted or restarted,
377
+ // so the whole active context is re-delivered rather than deduplicated. Delivery
378
+ // tracking only suppresses repeats inside one continuous session.
379
+ const restored = withDeliveryDefaults(record);
380
+ const step = deliveryStep(restored, "restored workflow", "", true);
381
+ // Not persisted here: session_start must not write to the branch. The next transition
382
+ // records the delivery set it has actually shown.
383
+ activeWorkflow = step.state;
347
384
  refreshActiveTools(true);
348
385
  clearPiStatus(ctx.ui, "matt-pocock");
349
386
  pi.sendMessage({
350
387
  customType: PROCEDURE_ENTRY,
351
- content,
388
+ content: step.text,
352
389
  display: false,
353
- details: record,
390
+ details: step.state,
354
391
  }, { deliverAs: "nextTurn" });
355
392
  } catch (error) {
356
393
  activeWorkflow = record;
@@ -416,10 +453,10 @@ export default function mattPocock(extensionApi: ExtensionAPI): void {
416
453
  if (activeWorkflow) {
417
454
  throw new Error(`Workflow ${activeWorkflow.workItemId} is already active. Complete or cancel it before starting another.`);
418
455
  }
419
- const state = startWorkflow(params.route);
456
+ const step = startWorkflow(params.route);
420
457
  return {
421
- content: [{ type: "text", text: workflowPrompt(state, "started workflow") }],
422
- details: { mode: "workflow", ...state },
458
+ content: [{ type: "text", text: step.text }],
459
+ details: { mode: "workflow", ...step.state },
423
460
  };
424
461
  }
425
462
  if (params.mode === "reference") {
@@ -471,11 +508,12 @@ export default function mattPocock(extensionApi: ExtensionAPI): void {
471
508
  if (params.action === "transition") {
472
509
  const state = transitionState(activeWorkflow, params.target);
473
510
  resolveProcedureBundle(state.procedure);
474
- persistWorkflow(state);
511
+ const step = deliveryStep(state, `transitioned to ${state.procedure}`);
512
+ persistWorkflow(step.state);
475
513
  clearPiStatus(ctx.ui, "matt-pocock");
476
514
  return {
477
- content: [{ type: "text", text: workflowPrompt(state, `transitioned to ${state.procedure}`) }],
478
- details: { action: "transition", subject: workflowEventSubject("transition", state.phase), state },
515
+ content: [{ type: "text", text: step.text }],
516
+ details: { action: "transition", subject: workflowEventSubject("transition", state.phase), state: step.state },
479
517
  };
480
518
  }
481
519
  if (params.action === "load") {
@@ -715,9 +753,9 @@ export default function mattPocock(extensionApi: ExtensionAPI): void {
715
753
 
716
754
  if (findWorkflowRoute(token)) {
717
755
  if (activeWorkflow) throw new Error(`Workflow ${activeWorkflow.workItemId} is already active.`);
718
- const state = startWorkflow(token);
756
+ const step = startWorkflow(token, task);
719
757
  clearPiStatus(ctx.ui, "matt-pocock");
720
- deliverProcedure(workflowPrompt(state, "started workflow", task), { ...state, request: task });
758
+ deliverProcedure(step.text, { ...step.state, request: task });
721
759
  return;
722
760
  }
723
761
  const capability = findProcedure(token);
package/src/resolver.ts CHANGED
@@ -125,3 +125,33 @@ export function resolveAccessibleReference(
125
125
  }
126
126
  return resolveProcedureBundle(target.id);
127
127
  }
128
+
129
+ export interface ProcedureDelta {
130
+ content: string;
131
+ delivered: string[];
132
+ availableReferences: string[];
133
+ }
134
+
135
+ /**
136
+ * The part of the active context this session has not been shown yet.
137
+ *
138
+ * A workflow re-delivers its prompt on every transition, and a procedure's bundle carries
139
+ * its whole dependency closure -- so re-sending the closure each time spends the context
140
+ * the work itself needs, and a long task stops partway for lack of room rather than for
141
+ * lack of work. A procedure already delivered in this session is therefore not sent again:
142
+ * the instructions the model has already read stay read, and only what is new arrives.
143
+ */
144
+ export function resolveProcedureDelta(
145
+ activeProcedure: string,
146
+ loadedReferences: string[],
147
+ delivered: string[],
148
+ ): ProcedureDelta {
149
+ const context = resolveWorkflowContext(activeProcedure, loadedReferences);
150
+ const already = new Set(delivered);
151
+ const pending = context.loaded.filter((id) => !already.has(id));
152
+ return {
153
+ content: pending.map((id) => renderSource(requireDefinition(id))).join("\n\n"),
154
+ delivered: [...new Set([...delivered, ...context.loaded])],
155
+ availableReferences: context.availableReferences,
156
+ };
157
+ }
package/src/workflow.ts CHANGED
@@ -18,6 +18,8 @@ export interface WorkflowState {
18
18
  phase: string;
19
19
  status: "active";
20
20
  loadedReferences: string[];
21
+ /** Procedures whose body has already been delivered in this session. */
22
+ deliveredProcedures: string[];
21
23
  }
22
24
 
23
25
  export interface TerminalWorkflowState {
@@ -54,6 +56,7 @@ export function routeEntryState(route: string, workItemId: string): WorkflowStat
54
56
  phase: placement.phase,
55
57
  status: "active",
56
58
  loadedReferences: [],
59
+ deliveredProcedures: [],
57
60
  };
58
61
  }
59
62
 
@@ -113,7 +116,17 @@ export function isWorkflowState(value: unknown): value is WorkflowState {
113
116
  && typeof candidate.phase === "string"
114
117
  && candidate.status === "active"
115
118
  && Array.isArray(candidate.loadedReferences)
116
- && candidate.loadedReferences.every((reference) => typeof reference === "string");
119
+ && candidate.loadedReferences.every((reference) => typeof reference === "string")
120
+ // Absent is tolerated so a workflow persisted before delivery tracking existed still
121
+ // restores; it simply re-delivers once, which is harmless.
122
+ && (candidate.deliveredProcedures === undefined
123
+ || (Array.isArray(candidate.deliveredProcedures)
124
+ && candidate.deliveredProcedures.every((id) => typeof id === "string")));
125
+ }
126
+
127
+ /** Backfill delivery tracking on a state persisted before it existed. */
128
+ export function withDeliveryDefaults(state: WorkflowState): WorkflowState {
129
+ return { ...state, deliveredProcedures: state.deliveredProcedures ?? [] };
117
130
  }
118
131
 
119
132
  export function isTerminalWorkflowState(value: unknown): value is TerminalWorkflowState {
@@ -138,13 +151,19 @@ export function latestWorkflowRecord(entries: unknown[]): WorkflowSessionData |
138
151
 
139
152
  export function latestWorkflowState(entries: unknown[]): WorkflowState | undefined {
140
153
  const record = latestWorkflowRecord(entries);
141
- return record?.status === "active" ? record : undefined;
154
+ return record?.status === "active" ? withDeliveryDefaults(record) : undefined;
142
155
  }
143
156
 
144
157
  export function workflowGuidance(state: WorkflowState, availableReferences: string[]): string {
145
158
  const next = allowedTransitions(state.route, state.procedure);
146
159
  return `Matt Pocock workflow active: ${state.route} · ${state.phase}.
147
- Work item: ${state.workItemId}. Follow the loaded ${state.procedure} procedure. Use matt_pocock_active to transition only to: ${next.join(", ") || "none"}; load an available reference; complete the work; or cancel it with a reason. Available references: ${availableReferences.join(", ") || "none"}. Exact shapes: {"action": "transition", "target": "<allowedNext id>"}; {"action": "load", "reference": "<availableReferences id>"}; {"action": "complete"}; {"action": "cancel", "reason": "<why>"}. Proceed autonomously through non-user-owned work: once the current procedure's deliverables or decisions are ready, transition to the next applicable procedure via matt_pocock_active immediately without stopping to ask permission. Ask only for a genuinely user-owned decision, unavailable fact, or required external action. Use matt_pocock_ask for structured workflow decisions. Complete only after the integrated candidate has applicable verification and all blocking reviews have returned with findings resolved; a completed review assignment is not itself a PASS verdict. Evidence must match that candidate. If only required results remain outstanding, yield with the workflow still active rather than declaring completion. Once these conditions hold, call matt_pocock_active with action complete instead of leaving stale active state.`;
160
+ Work item: ${state.workItemId}. Follow the loaded ${state.procedure} procedure.
161
+
162
+ Drive the current procedure to its end in this turn. A multi-step deliverable is not finished because one step passed: keep implementing, testing and fixing until every step the procedure covers is done, rather than stopping at the first green slice or handing back a plan for the remainder. Do not end the turn to ask whether to continue.
163
+
164
+ Use matt_pocock_active to transition only to: ${next.join(", ") || "none"}; load an available reference; complete the work; or cancel it with a reason. Available references: ${availableReferences.join(", ") || "none"}. Exact shapes: {"action": "transition", "target": "<allowedNext id>"}; {"action": "load", "reference": "<availableReferences id>"}; {"action": "complete"}; {"action": "cancel", "reason": "<why>"}. Proceed autonomously through non-user-owned work: transition to the next applicable procedure as soon as this one's deliverables are complete, without stopping to ask permission. Ask only for a genuinely user-owned decision, an unavailable fact, or a required external action, and use matt_pocock_ask for those.
165
+
166
+ Yield only when the remaining work genuinely depends on an external actor, on a teammate result you cannot produce yourself, or on context you no longer have — and when you yield, state plainly what is unfinished rather than implying completion. Never announce completion early: call matt_pocock_active with action complete only once the integrated candidate has applicable verification and all blocking reviews have returned with their findings resolved; a completed review assignment is not itself a PASS verdict, and evidence must match that candidate.`;
148
167
  }
149
168
 
150
169
  export function availableWorkflowsGuidance(): string {