@tianhai/pi-workflow-kit 0.5.1 → 0.5.3
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/docs/plans/2026-04-10-brainstorming-boundary-enforcement-design.md +60 -0
- package/extensions/plan-tracker.ts +6 -0
- package/extensions/workflow-monitor/workflow-handler.ts +5 -0
- package/extensions/workflow-monitor/workflow-tracker.ts +53 -20
- package/extensions/workflow-monitor/workflow-transitions.ts +34 -1
- package/extensions/workflow-monitor.ts +74 -37
- package/package.json +1 -1
- package/skills/brainstorming/SKILL.md +13 -5
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Brainstorming Skill Boundary Enforcement
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-04-10
|
|
4
|
+
**Status:** approved
|
|
5
|
+
|
|
6
|
+
## Problem
|
|
7
|
+
|
|
8
|
+
The brainstorming skill's boundaries are advisory only — a quiet `## Boundaries` bullet list mid-file that the model reads then ignores once a task "feels" straightforward. There is no structural enforcement (pi doesn't implement `allowed-tools`, no tool-blocking extension API).
|
|
9
|
+
|
|
10
|
+
In practice, `/skill:brainstorming` was invoked, the skill was loaded, and the agent immediately jumped to reading code, diagnosing the bug, and editing source files — violating every boundary.
|
|
11
|
+
|
|
12
|
+
## Design
|
|
13
|
+
|
|
14
|
+
Two changes to `skills/brainstorming/SKILL.md`:
|
|
15
|
+
|
|
16
|
+
### 1. Add `allowed-tools` frontmatter
|
|
17
|
+
|
|
18
|
+
```yaml
|
|
19
|
+
allowed-tools: read bash
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Pi doesn't parse this field yet, but:
|
|
23
|
+
- It's part of the Agent Skills spec (experimental)
|
|
24
|
+
- Future pi versions may inject it into the system prompt
|
|
25
|
+
- Serves as machine-readable declaration of intent
|
|
26
|
+
- Zero cost today
|
|
27
|
+
|
|
28
|
+
### 2. Replace quiet Boundaries section with prominent top-of-file block
|
|
29
|
+
|
|
30
|
+
Move from a mid-file `## Boundaries` section to a visually distinct blockquote immediately after the `# Heading`, before the Overview:
|
|
31
|
+
|
|
32
|
+
```markdown
|
|
33
|
+
> ⚠️ **BOUNDARY — DO NOT VIOLATE**
|
|
34
|
+
>
|
|
35
|
+
> This skill is **read-only exploration**. You MUST NOT use `edit` or `write` tools.
|
|
36
|
+
> The only tools allowed are `read` and `bash` (for investigation only).
|
|
37
|
+
>
|
|
38
|
+
> - ✅ Read code and docs: yes
|
|
39
|
+
> - ✅ Write to `docs/plans/`: yes (design documents only)
|
|
40
|
+
> - ❌ Edit or create any other files: **absolutely no**
|
|
41
|
+
>
|
|
42
|
+
> If you find yourself reaching for `edit` or `write`, **stop**. Present what
|
|
43
|
+
> you found as a design section and ask the user to approve it first.
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Key improvements over current form:
|
|
47
|
+
- **Blockquote + warning emoji** — visually distinct from normal content
|
|
48
|
+
- **"DO NOT VIOLATE"** — strong language models respond to
|
|
49
|
+
- **Names forbidden tools explicitly** (`edit`, `write`) — no ambiguity
|
|
50
|
+
- **Recovery instruction** — "stop, present what you found" — constructive next step
|
|
51
|
+
- **Positioned at the top** — seen before the Overview, not buried mid-file
|
|
52
|
+
|
|
53
|
+
## Out of scope
|
|
54
|
+
|
|
55
|
+
- Extension-level enforcement (requires pi core changes to support tool call interception)
|
|
56
|
+
- Changing other skills' boundaries (this is a pattern, but brainstorming is the most frequently violated)
|
|
57
|
+
|
|
58
|
+
## Testing
|
|
59
|
+
|
|
60
|
+
Manual verification: invoke `/skill:brainstorming`, confirm the model does not reach for `edit`/`write` during exploration.
|
|
@@ -236,6 +236,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
236
236
|
}
|
|
237
237
|
};
|
|
238
238
|
|
|
239
|
+
// Listen for clear signal from workflow-monitor's /workflow-reset command.
|
|
240
|
+
// This allows cross-extension communication without a callTool API.
|
|
241
|
+
pi.events.on("plan_tracker:clear", () => {
|
|
242
|
+
tasks = [];
|
|
243
|
+
});
|
|
244
|
+
|
|
239
245
|
// Reconstruct state + widget on session events
|
|
240
246
|
// session_start covers startup, reload, new, resume, fork (pi v0.65.0+)
|
|
241
247
|
pi.on("session_start", async (_event, ctx) => {
|
|
@@ -88,6 +88,7 @@ export interface WorkflowHandler {
|
|
|
88
88
|
restoreWorkflowStateFromBranch(branch: SessionEntry[]): void;
|
|
89
89
|
markWorkflowPrompted(phase: Phase): boolean;
|
|
90
90
|
completeCurrentWorkflowPhase(): boolean;
|
|
91
|
+
completeWorkflowPhase(phase: Phase): boolean;
|
|
91
92
|
advanceWorkflowTo(phase: Phase): boolean;
|
|
92
93
|
skipWorkflowPhases(phases: Phase[]): boolean;
|
|
93
94
|
handleSkillFileRead(path: string): boolean;
|
|
@@ -323,6 +324,10 @@ export function createWorkflowHandler(): WorkflowHandler {
|
|
|
323
324
|
return tracker.completeCurrent();
|
|
324
325
|
},
|
|
325
326
|
|
|
327
|
+
completeWorkflowPhase(phase: Phase) {
|
|
328
|
+
return tracker.completePhase(phase);
|
|
329
|
+
},
|
|
330
|
+
|
|
326
331
|
advanceWorkflowTo(phase) {
|
|
327
332
|
return tracker.advanceTo(phase);
|
|
328
333
|
},
|
|
@@ -12,9 +12,31 @@ export interface WorkflowTrackerState {
|
|
|
12
12
|
prompted: Record<Phase, boolean>;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export type TransitionBoundary =
|
|
15
|
+
export type TransitionBoundary =
|
|
16
|
+
| "design_reviewable"
|
|
17
|
+
| "plan_reviewable"
|
|
18
|
+
| "design_committed"
|
|
19
|
+
| "plan_ready"
|
|
20
|
+
| "execution_complete";
|
|
16
21
|
|
|
17
22
|
export function computeBoundaryToPrompt(state: WorkflowTrackerState): TransitionBoundary | null {
|
|
23
|
+
// Reviewable: current phase has its deliverable artifact but hasn't been
|
|
24
|
+
// user-confirmed as complete. Prompt the user to review before moving on.
|
|
25
|
+
if (
|
|
26
|
+
state.currentPhase === "brainstorm" &&
|
|
27
|
+
state.artifacts.brainstorm &&
|
|
28
|
+
state.phases.brainstorm === "active" &&
|
|
29
|
+
!state.prompted.brainstorm
|
|
30
|
+
) {
|
|
31
|
+
return "design_reviewable";
|
|
32
|
+
}
|
|
33
|
+
if (state.currentPhase === "plan" && state.artifacts.plan && state.phases.plan === "active" && !state.prompted.plan) {
|
|
34
|
+
return "plan_reviewable";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Committed: phase is complete but user hasn't been prompted for
|
|
38
|
+
// transition options yet (e.g. phases completed via skip-confirmation
|
|
39
|
+
// "mark complete" or execute phase auto-completing on all tasks terminal).
|
|
18
40
|
if (state.phases.brainstorm === "complete" && !state.prompted.brainstorm) {
|
|
19
41
|
return "design_committed";
|
|
20
42
|
}
|
|
@@ -96,24 +118,32 @@ export class WorkflowTracker {
|
|
|
96
118
|
|
|
97
119
|
if (current) {
|
|
98
120
|
const curIdx = WORKFLOW_PHASES.indexOf(current);
|
|
99
|
-
if (nextIdx
|
|
100
|
-
//
|
|
121
|
+
if (nextIdx === curIdx) {
|
|
122
|
+
// Same-phase navigation is a no-op. This prevents accidental resets
|
|
123
|
+
// when plan_tracker init is called while already in execute, or when
|
|
124
|
+
// a skill is re-invoked during its own phase.
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
if (nextIdx < curIdx) {
|
|
128
|
+
// Backward navigation = intentional new task. Reset everything.
|
|
101
129
|
this.reset();
|
|
102
130
|
// Fall through to activate the target phase below.
|
|
103
131
|
} else {
|
|
104
|
-
// Forward advance: auto-complete the current phase.
|
|
105
|
-
|
|
106
|
-
|
|
132
|
+
// Forward advance: do NOT auto-complete the current phase.
|
|
133
|
+
// Phase completion requires explicit user confirmation via
|
|
134
|
+
// boundary prompts or skip-confirmation "mark complete".
|
|
135
|
+
// However, refuse to jump over unresolved intermediate phases.
|
|
136
|
+
for (let i = curIdx + 1; i < nextIdx; i++) {
|
|
137
|
+
const intermediate = WORKFLOW_PHASES[i]!;
|
|
138
|
+
const status = this.state.phases[intermediate];
|
|
139
|
+
if (status !== "complete" && status !== "skipped") {
|
|
140
|
+
// Can't advance past an unresolved intermediate phase.
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
107
143
|
}
|
|
108
144
|
}
|
|
109
145
|
}
|
|
110
146
|
|
|
111
|
-
for (const p of WORKFLOW_PHASES) {
|
|
112
|
-
if (p !== phase && this.state.phases[p] === "active") {
|
|
113
|
-
this.state.phases[p] = "complete";
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
147
|
this.state.currentPhase = phase;
|
|
118
148
|
if (this.state.phases[phase] === "pending") {
|
|
119
149
|
this.state.phases[phase] = "active";
|
|
@@ -138,6 +168,10 @@ export class WorkflowTracker {
|
|
|
138
168
|
completeCurrent(): boolean {
|
|
139
169
|
const phase = this.state.currentPhase;
|
|
140
170
|
if (!phase) return false;
|
|
171
|
+
return this.completePhase(phase);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
completePhase(phase: Phase): boolean {
|
|
141
175
|
if (this.state.phases[phase] === "complete") return false;
|
|
142
176
|
this.state.phases[phase] = "complete";
|
|
143
177
|
return true;
|
|
@@ -203,11 +237,9 @@ export class WorkflowTracker {
|
|
|
203
237
|
}
|
|
204
238
|
let changed = false;
|
|
205
239
|
changed = this.recordArtifact("brainstorm", path) || changed;
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
// so the agent_end boundary prompt still fires to offer session handoff.
|
|
240
|
+
// Activate brainstorm phase but do NOT auto-complete.
|
|
241
|
+
// User confirms completion via the reviewable boundary prompt at agent_end.
|
|
209
242
|
changed = this.advanceTo("brainstorm") || changed;
|
|
210
|
-
changed = this.completeCurrent() || changed;
|
|
211
243
|
return changed;
|
|
212
244
|
}
|
|
213
245
|
|
|
@@ -219,11 +251,9 @@ export class WorkflowTracker {
|
|
|
219
251
|
}
|
|
220
252
|
let changed = false;
|
|
221
253
|
changed = this.recordArtifact("plan", path) || changed;
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
// prompted so the agent_end boundary prompt still fires.
|
|
254
|
+
// Activate plan phase but do NOT auto-complete.
|
|
255
|
+
// User confirms completion via the reviewable boundary prompt at agent_end.
|
|
225
256
|
changed = this.advanceTo("plan") || changed;
|
|
226
|
-
changed = this.completeCurrent() || changed;
|
|
227
257
|
return changed;
|
|
228
258
|
}
|
|
229
259
|
|
|
@@ -231,6 +261,9 @@ export class WorkflowTracker {
|
|
|
231
261
|
}
|
|
232
262
|
|
|
233
263
|
onPlanTrackerInit(): boolean {
|
|
264
|
+
// Guard: don't advance if already in execute (prevents accidental resets).
|
|
265
|
+
// Also refuse to jump over unresolved phases (e.g., plan still active).
|
|
266
|
+
if (this.state.currentPhase === "execute") return false;
|
|
234
267
|
return this.advanceTo("execute");
|
|
235
268
|
}
|
|
236
269
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Phase, TransitionBoundary } from "./workflow-tracker";
|
|
2
2
|
|
|
3
|
-
export type TransitionChoice = "next" | "fresh" | "skip" | "discuss";
|
|
3
|
+
export type TransitionChoice = "next" | "fresh" | "skip" | "revise" | "discuss";
|
|
4
4
|
|
|
5
5
|
export interface TransitionPrompt {
|
|
6
6
|
boundary: TransitionBoundary;
|
|
@@ -17,8 +17,36 @@ const BASE_OPTIONS: TransitionPrompt["options"] = [
|
|
|
17
17
|
{ choice: "discuss", label: "Discuss" },
|
|
18
18
|
];
|
|
19
19
|
|
|
20
|
+
// Reviewable options: shown when a phase has its artifact but hasn't
|
|
21
|
+
// been user-confirmed as complete. Includes explicit approval + revision.
|
|
22
|
+
const REVIEWABLE_OPTIONS: TransitionPrompt["options"] = [
|
|
23
|
+
{ choice: "next", label: "✓ Looks good, next step (this session)" },
|
|
24
|
+
{ choice: "fresh", label: "✓ Looks good, fresh session → next step" },
|
|
25
|
+
{ choice: "skip", label: "Skip phase" },
|
|
26
|
+
{ choice: "revise", label: "✗ Needs more work" },
|
|
27
|
+
{ choice: "discuss", label: "Discuss" },
|
|
28
|
+
];
|
|
29
|
+
|
|
20
30
|
export function getTransitionPrompt(boundary: TransitionBoundary, artifactPath: string | null): TransitionPrompt {
|
|
21
31
|
switch (boundary) {
|
|
32
|
+
// Reviewable: phase has artifact but user hasn't confirmed completion
|
|
33
|
+
case "design_reviewable":
|
|
34
|
+
return {
|
|
35
|
+
boundary,
|
|
36
|
+
title: `Design written${artifactPath ? `: ${artifactPath}` : ""}. Ready to proceed?`,
|
|
37
|
+
nextPhase: "plan",
|
|
38
|
+
artifactPath,
|
|
39
|
+
options: REVIEWABLE_OPTIONS,
|
|
40
|
+
};
|
|
41
|
+
case "plan_reviewable":
|
|
42
|
+
return {
|
|
43
|
+
boundary,
|
|
44
|
+
title: `Plan written${artifactPath ? `: ${artifactPath}` : ""}. Ready to proceed?`,
|
|
45
|
+
nextPhase: "execute",
|
|
46
|
+
artifactPath,
|
|
47
|
+
options: REVIEWABLE_OPTIONS,
|
|
48
|
+
};
|
|
49
|
+
// Committed: phase already complete, user chooses how to proceed
|
|
22
50
|
case "design_committed":
|
|
23
51
|
return {
|
|
24
52
|
boundary,
|
|
@@ -53,3 +81,8 @@ export function getTransitionPrompt(boundary: TransitionBoundary, artifactPath:
|
|
|
53
81
|
};
|
|
54
82
|
}
|
|
55
83
|
}
|
|
84
|
+
|
|
85
|
+
/** Whether a boundary represents a phase that still needs user confirmation. */
|
|
86
|
+
export function isReviewableBoundary(boundary: TransitionBoundary): boolean {
|
|
87
|
+
return boundary === "design_reviewable" || boundary === "plan_reviewable";
|
|
88
|
+
}
|
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
WORKFLOW_TRACKER_ENTRY_TYPE,
|
|
46
46
|
type WorkflowTrackerState,
|
|
47
47
|
} from "./workflow-monitor/workflow-tracker";
|
|
48
|
-
import { getTransitionPrompt } from "./workflow-monitor/workflow-transitions";
|
|
48
|
+
import { getTransitionPrompt, isReviewableBoundary } from "./workflow-monitor/workflow-transitions";
|
|
49
49
|
|
|
50
50
|
type SelectOption<T extends string> = { label: string; value: T };
|
|
51
51
|
|
|
@@ -165,6 +165,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
const boundaryToPhase: Record<TransitionBoundary, keyof typeof phaseToSkill> = {
|
|
168
|
+
design_reviewable: "brainstorm",
|
|
169
|
+
plan_reviewable: "plan",
|
|
168
170
|
design_committed: "brainstorm",
|
|
169
171
|
plan_ready: "plan",
|
|
170
172
|
execution_complete: "execute",
|
|
@@ -233,12 +235,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
233
235
|
const missingSkill = phaseToSkill[missing] ?? missing;
|
|
234
236
|
const options = [
|
|
235
237
|
{ label: `Do ${missing} now`, value: "do_now" as const },
|
|
238
|
+
{ label: `Mark ${missing} as complete`, value: "mark_complete" as const },
|
|
236
239
|
{ label: `Skip ${missing}`, value: "skip" as const },
|
|
237
240
|
{ label: "Cancel", value: "cancel" as const },
|
|
238
241
|
];
|
|
239
242
|
const choice = await selectValue(ctx, `Phase "${missing}" is unresolved. What would you like to do?`, options);
|
|
240
243
|
|
|
241
|
-
if (choice === "
|
|
244
|
+
if (choice === "mark_complete") {
|
|
245
|
+
handler.completeWorkflowPhase(missing as Phase);
|
|
246
|
+
handler.handleInputText(text);
|
|
247
|
+
persistState();
|
|
248
|
+
updateWidget(ctx);
|
|
249
|
+
return;
|
|
250
|
+
} else if (choice === "skip") {
|
|
242
251
|
handler.skipWorkflowPhases([missing]);
|
|
243
252
|
handler.handleInputText(text);
|
|
244
253
|
persistState();
|
|
@@ -280,12 +289,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
280
289
|
const skill = phaseToSkill[phase] ?? phase;
|
|
281
290
|
const options = [
|
|
282
291
|
{ label: `Do ${phase} now`, value: "do_now" as const },
|
|
292
|
+
{ label: `Mark ${phase} as complete`, value: "mark_complete" as const },
|
|
283
293
|
{ label: `Skip ${phase}`, value: "skip" as const },
|
|
284
294
|
{ label: "Cancel", value: "cancel" as const },
|
|
285
295
|
];
|
|
286
296
|
const choice = await selectValue(ctx, `Phase "${phase}" is unresolved. What would you like to do?`, options);
|
|
287
297
|
|
|
288
|
-
if (choice === "
|
|
298
|
+
if (choice === "mark_complete") {
|
|
299
|
+
handler.completeWorkflowPhase(phase as Phase);
|
|
300
|
+
persistState();
|
|
301
|
+
updateWidget(ctx);
|
|
302
|
+
} else if (choice === "skip") {
|
|
289
303
|
handler.skipWorkflowPhases([phase]);
|
|
290
304
|
persistState();
|
|
291
305
|
updateWidget(ctx);
|
|
@@ -312,12 +326,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
312
326
|
const missingSkill = phaseToSkill[missing] ?? missing;
|
|
313
327
|
const options = [
|
|
314
328
|
{ label: `Do ${missing} now`, value: "do_now" as const },
|
|
329
|
+
{ label: `Mark ${missing} as complete`, value: "mark_complete" as const },
|
|
315
330
|
{ label: `Skip ${missing}`, value: "skip" as const },
|
|
316
331
|
{ label: "Cancel", value: "cancel" as const },
|
|
317
332
|
];
|
|
318
333
|
const choice = await selectValue(ctx, `Phase "${missing}" is unresolved. What would you like to do?`, options);
|
|
319
334
|
|
|
320
|
-
if (choice === "
|
|
335
|
+
if (choice === "mark_complete") {
|
|
336
|
+
handler.completeWorkflowPhase(missing as Phase);
|
|
337
|
+
persistState();
|
|
338
|
+
updateWidget(ctx);
|
|
339
|
+
return "allowed";
|
|
340
|
+
} else if (choice === "skip") {
|
|
321
341
|
handler.skipWorkflowPhases([missing]);
|
|
322
342
|
persistState();
|
|
323
343
|
updateWidget(ctx);
|
|
@@ -356,12 +376,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
356
376
|
const skill = phaseToSkill[phase] ?? phase;
|
|
357
377
|
const options = [
|
|
358
378
|
{ label: `Do ${phase} now`, value: "do_now" as const },
|
|
379
|
+
{ label: `Mark ${phase} as complete`, value: "mark_complete" as const },
|
|
359
380
|
{ label: `Skip ${phase}`, value: "skip" as const },
|
|
360
381
|
{ label: "Cancel", value: "cancel" as const },
|
|
361
382
|
];
|
|
362
383
|
const choice = await selectValue(ctx, `Phase "${phase}" is unresolved. What would you like to do?`, options);
|
|
363
384
|
|
|
364
|
-
if (choice === "
|
|
385
|
+
if (choice === "mark_complete") {
|
|
386
|
+
handler.completeWorkflowPhase(phase as Phase);
|
|
387
|
+
persistState();
|
|
388
|
+
updateWidget(ctx);
|
|
389
|
+
} else if (choice === "skip") {
|
|
365
390
|
handler.skipWorkflowPhases([phase]);
|
|
366
391
|
persistState();
|
|
367
392
|
updateWidget(ctx);
|
|
@@ -608,18 +633,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
608
633
|
|
|
609
634
|
const boundaryPhase = boundaryToPhase[boundary];
|
|
610
635
|
const prompt = getTransitionPrompt(boundary, latestState.artifacts[boundaryPhase]);
|
|
636
|
+
const reviewable = isReviewableBoundary(boundary);
|
|
611
637
|
|
|
612
638
|
const options = prompt.options.map((o) => o.label);
|
|
613
639
|
const pickedLabel = await ctx.ui.select(prompt.title, options);
|
|
614
640
|
|
|
615
641
|
const selected = prompt.options.find((o) => o.label === pickedLabel)?.choice ?? null;
|
|
616
642
|
|
|
617
|
-
const marked = handler.markWorkflowPrompted(boundaryPhase);
|
|
618
|
-
if (marked) {
|
|
619
|
-
persistState();
|
|
620
|
-
updateWidget(ctx);
|
|
621
|
-
}
|
|
622
|
-
|
|
623
643
|
const nextSkill = phaseToSkill[prompt.nextPhase] ?? "writing-plans";
|
|
624
644
|
const nextInSession = `/skill:${nextSkill}`;
|
|
625
645
|
const fresh = `/workflow-next ${prompt.nextPhase}${prompt.artifactPath ? ` ${prompt.artifactPath}` : ""}`;
|
|
@@ -628,42 +648,56 @@ export default function (pi: ExtensionAPI) {
|
|
|
628
648
|
"- Does this work require documentation updates? (README, CHANGELOG, API docs, inline docs)\n" +
|
|
629
649
|
"- What was learned during this implementation? (surprises, codebase knowledge, things to do differently)\n\n";
|
|
630
650
|
|
|
631
|
-
if (selected === "next") {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
651
|
+
if (selected === "next" || selected === "fresh") {
|
|
652
|
+
// For reviewable boundaries: mark current phase complete first.
|
|
653
|
+
// For committed boundaries: phase is already complete.
|
|
654
|
+
if (reviewable) {
|
|
655
|
+
handler.completeCurrentWorkflowPhase();
|
|
636
656
|
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
657
|
+
|
|
658
|
+
// Advance to the next phase
|
|
659
|
+
handler.advanceWorkflowTo(prompt.nextPhase);
|
|
660
|
+
handler.markWorkflowPrompted(boundaryPhase);
|
|
661
|
+
persistState();
|
|
662
|
+
updateWidget(ctx);
|
|
663
|
+
|
|
664
|
+
if (selected === "next") {
|
|
665
|
+
ctx.ui.setEditorText(prompt.nextPhase === "finalize" ? finishReminder + nextInSession : nextInSession);
|
|
666
|
+
} else {
|
|
667
|
+
ctx.ui.setEditorText(prompt.nextPhase === "finalize" ? finishReminder + fresh : fresh);
|
|
643
668
|
}
|
|
644
|
-
ctx.ui.setEditorText(prompt.nextPhase === "finalize" ? finishReminder + fresh : fresh);
|
|
645
669
|
} else if (selected === "skip") {
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
const
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
if (
|
|
657
|
-
handler.advanceWorkflowTo(phaseAfterSkip);
|
|
670
|
+
if (reviewable) {
|
|
671
|
+
// Skip the current phase (the one with the artifact) and advance to next.
|
|
672
|
+
handler.skipWorkflowPhases([boundaryPhase]);
|
|
673
|
+
handler.advanceWorkflowTo(prompt.nextPhase);
|
|
674
|
+
} else {
|
|
675
|
+
// Committed boundary: skip the NEXT phase and advance past it.
|
|
676
|
+
handler.skipWorkflowPhases([prompt.nextPhase]);
|
|
677
|
+
const nextIdx = WORKFLOW_PHASES.indexOf(prompt.nextPhase);
|
|
678
|
+
const phaseAfterSkip = WORKFLOW_PHASES[nextIdx + 1] ?? null;
|
|
679
|
+
|
|
680
|
+
if (phaseAfterSkip && handler.advanceWorkflowTo(phaseAfterSkip)) {
|
|
658
681
|
const skipSkill = phaseToSkill[phaseAfterSkip] ?? "writing-plans";
|
|
659
682
|
ctx.ui.setEditorText(`/skill:${skipSkill}`);
|
|
660
683
|
}
|
|
661
684
|
}
|
|
662
685
|
|
|
686
|
+
handler.markWorkflowPrompted(boundaryPhase);
|
|
663
687
|
persistState();
|
|
664
688
|
updateWidget(ctx);
|
|
689
|
+
} else if (selected === "revise") {
|
|
690
|
+
// Reviewable only: user wants to keep working. Don't set prompted
|
|
691
|
+
// so the review prompt fires again at the next agent_end.
|
|
692
|
+
// Don't advance, don't modify phase state.
|
|
665
693
|
} else if (selected === "discuss") {
|
|
666
|
-
//
|
|
694
|
+
// For reviewable: don't set prompted (prompt fires again after discussion).
|
|
695
|
+
// For committed: set prompted (phase is already done, user just wants to chat).
|
|
696
|
+
if (!reviewable) {
|
|
697
|
+
handler.markWorkflowPrompted(boundaryPhase);
|
|
698
|
+
persistState();
|
|
699
|
+
updateWidget(ctx);
|
|
700
|
+
}
|
|
667
701
|
ctx.ui.setEditorText(
|
|
668
702
|
`Let's discuss before moving to the next step.\n` +
|
|
669
703
|
`We're at: ${prompt.title}\n` +
|
|
@@ -756,10 +790,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
756
790
|
description: "Reset workflow tracker to fresh state for a new task",
|
|
757
791
|
async handler(_args, ctx) {
|
|
758
792
|
handler.resetState();
|
|
759
|
-
// Emit a clear signal so plan-tracker also reconstructs to empty
|
|
793
|
+
// Emit a clear signal so plan-tracker also reconstructs to empty on next
|
|
794
|
+
// session reload. Also notify plan-tracker in real time via the shared
|
|
795
|
+
// event bus so its in-memory state and widget update immediately.
|
|
760
796
|
pi.appendEntry(PLAN_TRACKER_CLEARED_TYPE, { clearedAt: Date.now() });
|
|
761
797
|
persistState();
|
|
762
798
|
updateWidget(ctx);
|
|
799
|
+
pi.events.emit("plan_tracker:clear");
|
|
763
800
|
if (ctx.hasUI) {
|
|
764
801
|
ctx.ui.notify("Workflow reset. Ready for a new task.", "info");
|
|
765
802
|
}
|
package/package.json
CHANGED
|
@@ -1,23 +1,31 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: brainstorming
|
|
3
3
|
description: "You MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores user intent, requirements and design before implementation."
|
|
4
|
+
allowed-tools: read bash
|
|
4
5
|
---
|
|
5
6
|
|
|
6
7
|
> **Related skills:** Consider `/skill:using-git-worktrees` to set up an isolated workspace, then `/skill:writing-plans` for implementation planning.
|
|
7
8
|
|
|
8
9
|
# Brainstorming Ideas Into Designs
|
|
9
10
|
|
|
11
|
+
> ⚠️ **BOUNDARY — DO NOT VIOLATE**
|
|
12
|
+
>
|
|
13
|
+
> This skill is **read-only exploration**. You MUST NOT use `edit` or `write` tools.
|
|
14
|
+
> The only tools allowed are `read` and `bash` (for investigation only).
|
|
15
|
+
>
|
|
16
|
+
> - ✅ Read code and docs: yes
|
|
17
|
+
> - ✅ Write to `docs/plans/`: yes (design documents only)
|
|
18
|
+
> - ❌ Edit or create any other files: **absolutely no**
|
|
19
|
+
>
|
|
20
|
+
> If you find yourself reaching for `edit` or `write`, **stop**. Present what
|
|
21
|
+
> you found as a design section and ask the user to approve it first.
|
|
22
|
+
|
|
10
23
|
## Overview
|
|
11
24
|
|
|
12
25
|
Help turn ideas into fully formed designs and specs through natural collaborative dialogue.
|
|
13
26
|
|
|
14
27
|
Start by understanding the current project context, then ask questions one at a time to refine the idea. Once you understand what you're building, present the design in small sections (200-300 words), checking after each section whether it looks right so far.
|
|
15
28
|
|
|
16
|
-
## Boundaries
|
|
17
|
-
- Read code and docs: yes
|
|
18
|
-
- Write to docs/plans/: yes
|
|
19
|
-
- Edit or create any other files: no
|
|
20
|
-
|
|
21
29
|
## The Process
|
|
22
30
|
|
|
23
31
|
**Before anything else — check git state:**
|