@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.
@@ -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 = "design_committed" | "plan_ready" | "execution_complete";
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 <= curIdx) {
100
- // Backward or same-phase navigation = new task. Reset everything.
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
- if (this.state.phases[current] === "active") {
106
- this.state.phases[current] = "complete";
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
- // Activating and immediately completing: the design doc is the
207
- // deliverable that signals brainstorm is done. Do NOT mark prompted
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
- // Activating and immediately completing: the implementation plan
223
- // is the deliverable that signals plan phase is done. Do NOT mark
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 === "skip") {
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 === "skip") {
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 === "skip") {
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 === "skip") {
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
- const advanced = prompt.nextPhase === "finalize" ? handler.advanceWorkflowTo("finalize") : false;
633
- if (advanced) {
634
- persistState();
635
- updateWidget(ctx);
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
- ctx.ui.setEditorText(prompt.nextPhase === "finalize" ? finishReminder + nextInSession : nextInSession);
638
- } else if (selected === "fresh") {
639
- const advanced = prompt.nextPhase === "finalize" ? handler.advanceWorkflowTo("finalize") : false;
640
- if (advanced) {
641
- persistState();
642
- updateWidget(ctx);
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
- // Explicit user-confirmed skip: mark the next phase as skipped, then move on.
647
- handler.skipWorkflowPhases([prompt.nextPhase]);
648
-
649
- const nextIdx = WORKFLOW_PHASES.indexOf(prompt.nextPhase);
650
- const phaseAfterSkip = WORKFLOW_PHASES[nextIdx + 1] ?? null;
651
-
652
- if (phaseAfterSkip) {
653
- const currentState = handler.getWorkflowState();
654
- const currentIdx = currentState?.currentPhase ? WORKFLOW_PHASES.indexOf(currentState.currentPhase) : -1;
655
- const afterSkipIdx = WORKFLOW_PHASES.indexOf(phaseAfterSkip);
656
- if (afterSkipIdx > currentIdx) {
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
- // Don't advance phase. Set editor text to prompt discussion.
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@tianhai/pi-workflow-kit",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "Workflow skills and enforcement extensions for pi",
5
5
  "keywords": [
6
6
  "pi-package"
@@ -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:**