superpowers-mcp 6.3.6 → 6.3.8

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.
Files changed (40) hide show
  1. package/README.ja.md +56 -62
  2. package/README.ko.md +56 -64
  3. package/README.md +58 -65
  4. package/README.zh-TW.md +56 -64
  5. package/docs/maintainers/upstream-sync.md +42 -0
  6. package/docs/skill-compositions.ja.md +194 -0
  7. package/docs/skill-compositions.ko.md +194 -0
  8. package/docs/skill-compositions.md +216 -0
  9. package/docs/skill-compositions.zh-TW.md +194 -0
  10. package/out/server.js +105 -146
  11. package/out/setup-runner.js +16 -16
  12. package/out/setup.js +17 -17
  13. package/package.json +12 -6
  14. package/scripts/upstream-drift.js +346 -0
  15. package/skills/brainstorming/SKILL.md +125 -25
  16. package/skills/brainstorming/scripts/helper.js +1 -1
  17. package/skills/brainstorming/scripts/server.cjs +61 -6
  18. package/skills/brainstorming/scripts/start-server.ps1 +20 -1
  19. package/skills/brainstorming/scripts/start-server.sh +2 -2
  20. package/skills/executing-plans/SKILL.md +7 -1
  21. package/skills/finishing-a-development-branch/SKILL.md +15 -0
  22. package/skills/subagent-driven-development/SKILL.md +121 -36
  23. package/skills/subagent-driven-development/implementer-prompt.md +19 -0
  24. package/skills/subagent-driven-development/re-review-prompt.md +10 -4
  25. package/skills/subagent-driven-development/scripts/review-package +6 -0
  26. package/skills/subagent-driven-development/scripts/review-package.ps1 +7 -0
  27. package/skills/subagent-driven-development/scripts/sdd-workspace +11 -4
  28. package/skills/subagent-driven-development/scripts/sdd-workspace.ps1 +28 -3
  29. package/skills/subagent-driven-development/task-reviewer-prompt.md +28 -10
  30. package/skills/systematic-debugging/SKILL.md +1 -1
  31. package/skills/systematic-debugging/find-polluter.ps1 +7 -5
  32. package/skills/systematic-debugging/find-polluter.sh +11 -9
  33. package/skills/systematic-debugging/root-cause-tracing.md +2 -2
  34. package/skills/test-driven-development/SKILL.md +27 -3
  35. package/skills/test-driven-development/writing-good-tests.md +7 -0
  36. package/skills/using-git-worktrees/SKILL.md +12 -0
  37. package/skills/using-superpowers/SKILL.md +1 -1
  38. package/skills/verification-before-completion/SKILL.md +54 -1
  39. package/skills/writing-plans/SKILL.md +20 -5
  40. package/skills/writing-skills/SKILL.md +30 -0
@@ -0,0 +1,346 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Upstream drift report for the skills this fork ships.
4
+ *
5
+ * The fork imports upstream superpowers content in reviewed batches. Between
6
+ * batches nothing records which upstream files moved, so every sync starts with
7
+ * a manual hunt. This tool stores the upstream blob SHAs captured at the last
8
+ * sync (tests/upstream-sync-baseline.json) and reports:
9
+ *
10
+ * - adopted skill files that changed, appeared or disappeared upstream
11
+ * - tracked files that are missing from this fork
12
+ * - upstream skills this fork deliberately does not adopt
13
+ * - files this fork adds on top of upstream
14
+ *
15
+ * Usage:
16
+ * node scripts/upstream-drift.js # offline: baseline integrity + coverage
17
+ * node scripts/upstream-drift.js --fetch # compare the baseline against upstream
18
+ * node scripts/upstream-drift.js --record # refresh the baseline from upstream
19
+ *
20
+ * Options:
21
+ * --ref <ref> upstream ref to read (default: the baseline ref, else "dev")
22
+ * --repo <owner/name> upstream repository (default: obra/superpowers)
23
+ * --ignore <skill> record an upstream skill as deliberately not adopted
24
+ * (repeatable; --record only)
25
+ * --json machine-readable report
26
+ * --fail-on-drift exit 1 when an adopted file drifted (skipped on a
27
+ * truncated upstream listing)
28
+ *
29
+ * `--record` refuses truncated upstream listings so an incomplete API response
30
+ * can never replace the last complete baseline.
31
+ *
32
+ * The upstream git tree is read through `gh api` when the GitHub CLI is
33
+ * available and falls back to the public GitHub API (Node 18+). Zero
34
+ * dependencies. Only --record writes, and it writes the baseline file only.
35
+ */
36
+
37
+ const fs = require("fs");
38
+ const path = require("path");
39
+ const { execFileSync } = require("child_process");
40
+
41
+ const REPO_ROOT = path.join(__dirname, "..");
42
+ const SKILLS_DIR = path.join(REPO_ROOT, "skills");
43
+ const BASELINE_PATH = path.join(REPO_ROOT, "tests", "upstream-sync-baseline.json");
44
+ const DEFAULT_REPO = "obra/superpowers";
45
+ const DEFAULT_REF = "dev";
46
+ const MAX_LISTED = 40;
47
+ const NETWORK_TIMEOUT_MS = 20000;
48
+
49
+ function parseArgs(argv) {
50
+ const opts = { fetch: false, record: false, json: false, failOnDrift: false, help: false, ref: "", repo: "", ignore: [] };
51
+ for (let i = 0; i < argv.length; i++) {
52
+ const arg = argv[i];
53
+ if (arg === "--fetch") opts.fetch = true;
54
+ else if (arg === "--record") opts.record = true;
55
+ else if (arg === "--json") opts.json = true;
56
+ else if (arg === "--fail-on-drift") opts.failOnDrift = true;
57
+ else if (arg === "--help" || arg === "-h") opts.help = true;
58
+ else if (arg === "--ref" || arg === "--repo" || arg === "--ignore") {
59
+ const value = argv[++i];
60
+ if (!value) throw new Error(`Missing value for ${arg}`);
61
+ if (arg === "--ref") opts.ref = value;
62
+ else if (arg === "--repo") opts.repo = value;
63
+ else opts.ignore.push(value);
64
+ } else {
65
+ throw new Error(`Unknown argument: ${arg}`);
66
+ }
67
+ }
68
+ return opts;
69
+ }
70
+
71
+ function printHelp() {
72
+ console.log(`Upstream drift report for the skills this fork ships.
73
+
74
+ node scripts/upstream-drift.js offline baseline integrity + local coverage
75
+ node scripts/upstream-drift.js --fetch compare the baseline against upstream
76
+ node scripts/upstream-drift.js --record refresh the baseline from upstream
77
+
78
+ Options: --ref <ref> --repo <owner/name> --ignore <skill> --json --fail-on-drift`);
79
+ }
80
+
81
+ /** The skill directory an upstream path belongs to ("skills/<name>/..." -> "<name>"). */
82
+ function skillNameOf(filePath) {
83
+ const parts = String(filePath).split("/");
84
+ return parts.length > 2 && parts[0] === "skills" ? parts[1] : "";
85
+ }
86
+
87
+ function readBaseline(baselinePath = BASELINE_PATH) {
88
+ const parsed = JSON.parse(fs.readFileSync(baselinePath, "utf-8"));
89
+ if (!parsed || typeof parsed !== "object" || typeof parsed.files !== "object" || parsed.files === null) {
90
+ throw new Error(`${path.relative(REPO_ROOT, baselinePath)} is missing its "files" map`);
91
+ }
92
+ return parsed;
93
+ }
94
+
95
+ /** Walk the fork's skills tree and return the upstream-style paths it covers. */
96
+ function localSkillPaths(skillsDir = SKILLS_DIR) {
97
+ const found = new Set();
98
+ const walk = (dir, relative) => {
99
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
100
+ const nextRelative = relative ? `${relative}/${entry.name}` : entry.name;
101
+ if (entry.isDirectory()) {
102
+ walk(path.join(dir, entry.name), nextRelative);
103
+ } else if (entry.isFile()) {
104
+ found.add(`skills/${nextRelative}`);
105
+ }
106
+ }
107
+ };
108
+ if (fs.existsSync(skillsDir)) {
109
+ walk(skillsDir, "");
110
+ }
111
+ return found;
112
+ }
113
+
114
+ /**
115
+ * Classify the current upstream tree against the recorded baseline.
116
+ *
117
+ * `added` and `changed`/`removed` cover the skills this fork adopts, so they are
118
+ * actionable drift. Files that belong to a skill the fork does not adopt (and
119
+ * has not listed in ignoredUpstreamSkills) land in `unadoptedFiles`, which is a
120
+ * decision to make rather than drift to review.
121
+ */
122
+ function classifyDrift(baselineFiles, upstreamFiles, ignoredSkills = []) {
123
+ const adoptedSkills = new Set(Object.keys(baselineFiles).map(skillNameOf).filter(Boolean));
124
+ const ignored = new Set(ignoredSkills);
125
+ const changed = [];
126
+ const added = [];
127
+ const removed = [];
128
+ const unadoptedFiles = [];
129
+ for (const [filePath, sha] of Object.entries(upstreamFiles)) {
130
+ if (Object.prototype.hasOwnProperty.call(baselineFiles, filePath)) {
131
+ if (baselineFiles[filePath] !== sha) changed.push(filePath);
132
+ continue;
133
+ }
134
+ const skill = skillNameOf(filePath);
135
+ if (adoptedSkills.has(skill)) added.push(filePath);
136
+ else if (!ignored.has(skill)) unadoptedFiles.push(filePath);
137
+ }
138
+ for (const filePath of Object.keys(baselineFiles)) {
139
+ if (!Object.prototype.hasOwnProperty.call(upstreamFiles, filePath)) removed.push(filePath);
140
+ }
141
+ const byPath = (a, b) => a.localeCompare(b);
142
+ return {
143
+ changed: changed.sort(byPath),
144
+ added: added.sort(byPath),
145
+ removed: removed.sort(byPath),
146
+ unadoptedFiles: unadoptedFiles.sort(byPath),
147
+ };
148
+ }
149
+
150
+ /** Compare the recorded baseline with what the fork actually ships. */
151
+ function classifyCoverage(baseline, localPaths) {
152
+ const files = baseline.files || {};
153
+ const ignored = new Set(Array.isArray(baseline.ignoredUpstreamSkills) ? baseline.ignoredUpstreamSkills : []);
154
+ const localSkillDirs = new Set([...localPaths].map(skillNameOf).filter(Boolean));
155
+ const upstreamSkillDirs = new Set((baseline.upstreamSkills || Object.keys(files).map(skillNameOf)).filter(Boolean));
156
+ const byPath = (a, b) => a.localeCompare(b);
157
+ return {
158
+ missingLocal: Object.keys(files).filter((filePath) => !localPaths.has(filePath)).sort(byPath),
159
+ localExtra: [...localPaths].filter((filePath) => !Object.prototype.hasOwnProperty.call(files, filePath)).sort(byPath),
160
+ unadoptedSkills: [...upstreamSkillDirs].filter((name) => !localSkillDirs.has(name) && !ignored.has(name)).sort(byPath),
161
+ forkOnlySkills: [...localSkillDirs].filter((name) => !upstreamSkillDirs.has(name)).sort(byPath),
162
+ ignoredSkills: [...ignored].sort(byPath),
163
+ };
164
+ }
165
+
166
+ const encodePath = (value) => String(value).split("/").map(encodeURIComponent).join("/");
167
+
168
+ async function fetchUpstreamTree(repo, ref) {
169
+ const endpoint = `repos/${encodePath(repo)}/git/trees/${encodeURIComponent(ref)}?recursive=1`;
170
+ let raw = null;
171
+ let source = "gh api";
172
+ let ghError = "";
173
+ try {
174
+ raw = execFileSync("gh", ["api", endpoint], {
175
+ encoding: "utf-8",
176
+ stdio: ["ignore", "pipe", "pipe"],
177
+ timeout: NETWORK_TIMEOUT_MS,
178
+ });
179
+ } catch (err) {
180
+ raw = null;
181
+ ghError = err && err.stderr ? String(err.stderr).trim().split("\n")[0] : String((err && err.message) || "");
182
+ }
183
+ if (!raw) {
184
+ source = "api.github.com";
185
+ const response = await fetch(`https://api.github.com/${endpoint}`, {
186
+ headers: { accept: "application/vnd.github+json", "user-agent": "superpowers-mcp-drift" },
187
+ signal: AbortSignal.timeout(NETWORK_TIMEOUT_MS),
188
+ });
189
+ if (!response.ok) {
190
+ throw new Error(`Could not read ${repo}@${ref} (gh failed${ghError ? `: ${ghError}` : ""}; GitHub API HTTP ${response.status})`);
191
+ }
192
+ raw = await response.text();
193
+ }
194
+ let parsed;
195
+ try {
196
+ parsed = JSON.parse(raw);
197
+ } catch (err) {
198
+ throw new Error(`Could not parse the upstream tree response: ${err.message}`);
199
+ }
200
+ const files = {};
201
+ for (const entry of parsed.tree || []) {
202
+ if (entry && entry.type === "blob" && typeof entry.path === "string" && entry.path.startsWith("skills/")) {
203
+ files[entry.path] = entry.sha;
204
+ }
205
+ }
206
+ return { commit: parsed.sha || "", files, source, truncated: Boolean(parsed.truncated) };
207
+ }
208
+
209
+ function list(title, entries) {
210
+ if (entries.length === 0) return;
211
+ console.log(`${title} (${entries.length}):`);
212
+ for (const entry of entries.slice(0, MAX_LISTED)) console.log(` - ${entry}`);
213
+ if (entries.length > MAX_LISTED) console.log(` ... and ${entries.length - MAX_LISTED} more`);
214
+ }
215
+
216
+ function reportCoverage(coverage) {
217
+ list("Tracked upstream files missing from the fork", coverage.missingLocal);
218
+ list("Upstream skills this fork does not adopt", coverage.unadoptedSkills);
219
+ list("Fork-only skills (not tracked upstream)", coverage.forkOnlySkills);
220
+ list("Fork-only files (not tracked upstream)", coverage.localExtra);
221
+ list("Upstream skills recorded as deliberately not adopted", coverage.ignoredSkills);
222
+ }
223
+
224
+ function reportDrift(drift) {
225
+ list("Adopted skill files changed upstream since the baseline", drift.changed);
226
+ list("New upstream files inside adopted skills", drift.added);
227
+ list("Adopted skill files removed upstream", drift.removed);
228
+ list("New upstream files in skills this fork does not adopt", drift.unadoptedFiles);
229
+ }
230
+
231
+ function requireCompleteTreeForRecord(tree) {
232
+ if (tree.truncated) {
233
+ throw new Error("Refusing to record a truncated upstream tree; the existing baseline was left unchanged");
234
+ }
235
+ }
236
+
237
+ async function main() {
238
+ const opts = parseArgs(process.argv.slice(2));
239
+ if (opts.help) {
240
+ printHelp();
241
+ return;
242
+ }
243
+
244
+ let baseline = null;
245
+ try {
246
+ baseline = readBaseline();
247
+ } catch (err) {
248
+ if (!opts.record) throw err;
249
+ console.log(`No usable baseline yet (${err.message}); --record will create one.`);
250
+ baseline = { repo: DEFAULT_REPO, ref: DEFAULT_REF, commit: "", capturedAt: "", files: {}, upstreamSkills: [], ignoredUpstreamSkills: [] };
251
+ }
252
+ const localPaths = localSkillPaths();
253
+
254
+ if (opts.record) {
255
+ const repo = opts.repo || baseline.repo || DEFAULT_REPO;
256
+ const ref = opts.ref || baseline.ref || DEFAULT_REF;
257
+ const ignored = [...new Set([...(baseline.ignoredUpstreamSkills || []), ...opts.ignore])].sort();
258
+ const tree = await fetchUpstreamTree(repo, ref);
259
+ requireCompleteTreeForRecord(tree);
260
+ const previousFiles = baseline.files || {};
261
+ const previousDrift = classifyDrift(previousFiles, tree.files, ignored);
262
+
263
+ // Track only the upstream files this fork actually ships: the baseline
264
+ // states which upstream blobs were imported, so a later deletion of an
265
+ // adopted file shows up as a missing local path.
266
+ const files = {};
267
+ for (const filePath of Object.keys(tree.files).sort()) {
268
+ if (localPaths.has(filePath)) files[filePath] = tree.files[filePath];
269
+ }
270
+ const upstreamSkills = [...new Set(Object.keys(tree.files).map(skillNameOf).filter(Boolean))].sort();
271
+ const recorded = { repo, ref, commit: tree.commit, capturedAt: new Date().toISOString().slice(0, 10), files, upstreamSkills, ignoredUpstreamSkills: ignored };
272
+ fs.writeFileSync(BASELINE_PATH, JSON.stringify(recorded, null, 2) + "\n", "utf-8");
273
+
274
+ console.log(`Baseline recorded from ${tree.source}: ${repo}@${ref} ${tree.commit.slice(0, 12)} — ${Object.keys(files).length} tracked files of ${Object.keys(tree.files).length} upstream skill files`);
275
+ if (Object.keys(previousFiles).length > 0) {
276
+ const moved = previousDrift.changed.length + previousDrift.added.length + previousDrift.removed.length;
277
+ console.log(moved === 0
278
+ ? "No adopted skill file moved upstream since the previous baseline."
279
+ : `warning: ${moved} adopted skill file(s) moved upstream since the previous baseline — record only what you actually reviewed.`);
280
+ }
281
+ if (opts.ignore.length > 0) {
282
+ console.log(`Recorded as deliberately not adopted: ${opts.ignore.join(", ")}`);
283
+ }
284
+ reportCoverage(classifyCoverage(recorded, localPaths));
285
+ return;
286
+ }
287
+
288
+ const repo = opts.repo || baseline.repo || DEFAULT_REPO;
289
+ const ref = opts.ref || baseline.ref || DEFAULT_REF;
290
+ const coverage = classifyCoverage(baseline, localPaths);
291
+ const tracked = Object.keys(baseline.files).length;
292
+ const ignored = Array.isArray(baseline.ignoredUpstreamSkills) ? baseline.ignoredUpstreamSkills : [];
293
+
294
+ if (!opts.fetch) {
295
+ if (opts.json) {
296
+ console.log(JSON.stringify({ baseline: { repo, ref, commit: baseline.commit, capturedAt: baseline.capturedAt, tracked }, coverage }, null, 2));
297
+ return;
298
+ }
299
+ console.log(`Baseline: ${repo}@${ref} ${String(baseline.commit).slice(0, 12)} (captured ${baseline.capturedAt})`);
300
+ console.log(`Tracked upstream skill files: ${tracked}`);
301
+ reportCoverage(coverage);
302
+ console.log("\nRun `node scripts/upstream-drift.js --fetch` to compare the baseline against upstream.");
303
+ return;
304
+ }
305
+
306
+ const tree = await fetchUpstreamTree(repo, ref);
307
+ const drift = classifyDrift(baseline.files, tree.files, ignored);
308
+ const driftCount = drift.changed.length + drift.added.length + drift.removed.length;
309
+ if (opts.json) {
310
+ console.log(JSON.stringify({
311
+ baseline: { repo, ref, commit: baseline.commit, capturedAt: baseline.capturedAt, tracked },
312
+ upstream: { ref, commit: tree.commit, tracked: Object.keys(tree.files).length, source: tree.source, truncated: tree.truncated },
313
+ drift,
314
+ driftCount,
315
+ coverage,
316
+ }, null, 2));
317
+ } else {
318
+ console.log(`Baseline: ${repo}@${baseline.ref} ${String(baseline.commit).slice(0, 12)} (captured ${baseline.capturedAt})`);
319
+ console.log(`Upstream: ${repo}@${ref} ${String(tree.commit).slice(0, 12)} via ${tree.source}`);
320
+ reportDrift(drift);
321
+ reportCoverage(coverage);
322
+ if (driftCount === 0) {
323
+ console.log("\nNo drift in adopted skill files.");
324
+ } else {
325
+ console.log(`\n${driftCount} adopted skill file(s) changed upstream. Review them before the next sync.`);
326
+ }
327
+ if (drift.unadoptedFiles.length > 0) {
328
+ console.log(`${drift.unadoptedFiles.length} upstream file(s) belong to skills this fork does not adopt — adopt or record them with --ignore.`);
329
+ }
330
+ if (tree.truncated) {
331
+ console.log("warning: the upstream listing was truncated, so this comparison may be partial.");
332
+ }
333
+ }
334
+ if (opts.failOnDrift && driftCount > 0 && !tree.truncated) {
335
+ process.exitCode = 1;
336
+ }
337
+ }
338
+
339
+ if (require.main === module) {
340
+ main().catch((err) => {
341
+ console.error(`❌ upstream-drift: ${err.message}`);
342
+ process.exitCode = 2;
343
+ });
344
+ }
345
+
346
+ module.exports = { parseArgs, skillNameOf, readBaseline, localSkillPaths, classifyDrift, classifyCoverage, requireCompleteTreeForRecord };
@@ -11,12 +11,48 @@ Start by classifying how much process the request needs, then work
11
11
  through your path: understand the context, refine the idea, present a
12
12
  design, and get your human partner's approval.
13
13
 
14
+ ## Establish Shared Understanding
15
+
16
+ The outcome of brainstorming is an understanding your human partner can
17
+ recognize and correct, grounded in what they want to accomplish.
18
+
19
+ 1. **Discover intent.** Use the request and available context to identify
20
+ the intended outcome, who it is for, and what success looks like. When
21
+ that information is missing, ask one focused question about purpose or
22
+ intended use before proposing features or an approach. Knowing the app
23
+ genre does not tell you why your partner wants it. Gathering missing
24
+ requirements does not ask them to authorize the task again.
25
+ 2. **Write back your understanding.** Summarize the intended outcome,
26
+ relevant constraints, and success criteria in a short note your partner
27
+ can assess. Separate what they said from assumptions. Invite correction
28
+ and incorporate their answer before treating this as the design brief.
29
+ 3. **Carry intent into the design.** Preserve the agreed understanding in
30
+ the selected path's design artifact: the written spec for architectural
31
+ work, or the in-chat design/probe for bounded work and spikes. Check
32
+ proposed features and technical choices against that understanding.
33
+
34
+ When the request already supplies the purpose and constraints, reflect
35
+ that understanding instead of asking the same questions again. Keep the
36
+ note concise; its accuracy and the opportunity to correct it matter.
37
+
14
38
  <HARD-GATE>
15
- Do NOT invoke any implementation skill, write any code, scaffold any
16
- project, or take any implementation action until you have told your
17
- human partner what you intend and they have approved it. This applies
18
- to EVERY task on EVERY path below — the ceremony scales with the task;
19
- the approval gate never does.
39
+ Before taking any implementation action, including invoking an
40
+ implementation skill, writing product code, scaffolding, installing
41
+ product dependencies, or creating an external project, complete the
42
+ selected path's prerequisites:
43
+
44
+ - Spike: the human partner approves the question and probe.
45
+ - Bounded: the human partner approves the short in-chat design.
46
+ - Architectural: the human partner reviews and approves the written spec,
47
+ then reviews the written implementation plan and selects its execution
48
+ method. Conversational design approval only permits writing the spec;
49
+ written-spec approval only permits invoking writing-plans.
50
+
51
+ A reply approves the stage actually presented. Approval of an idea or
52
+ feature scope does not approve artifacts that do not exist yet. Resume
53
+ at the earliest incomplete stage; do not turn one approval into permission
54
+ to skip the rest of the selected path. Read-only project exploration is
55
+ allowed while those prerequisites remain incomplete.
20
56
  </HARD-GATE>
21
57
 
22
58
  ## Three Paths
@@ -53,18 +89,17 @@ stop, say so, and step up. Nothing downgrades mid-task.
53
89
 
54
90
  ## Anti-Pattern: "Too Simple To Need Approval"
55
91
 
56
- Every path ends with your human partner approving your intent before
57
- implementation. A todo list, a single-function utility, a config
58
- change — the design may be two sentences in chat, but you MUST present
59
- it and get approval. "Simple" tasks are where unexamined assumptions
60
- cause the most wasted work. What scales with simplicity is the
61
- artifact, never the approval.
92
+ Every path ends with your human partner approving the required design
93
+ before implementation. A bounded change may need only two sentences in
94
+ chat. A new todo-list project is architectural and requires the written
95
+ spec and planning handoffs. Scale the artifact to the selected path;
96
+ complete that path's reviews before implementation.
62
97
 
63
98
  ## Red Flags
64
99
 
65
100
  | Thought | Reality |
66
101
  |---------|---------|
67
- | "This is too simple to need a design" | Simple means a short design, not no design. Two sentences in chat, then approval. |
102
+ | "This is too simple to need a design" | Follow the selected path: a bounded change gets a short chat design; an architectural change gets the written spec and planning handoffs. |
68
103
  | "I'll call it bounded and skip the spec" | Reaching for a label to skip work IS the doubt — take the heavier path. |
69
104
  | "It's bounded and the design is obvious — I'll start while they read it" | The gate is the approval, not the design's length. Present, then stop until you hear yes. |
70
105
  | "I understand this kind of app, so it's bounded" | Bounded measures the repo, not your familiarity. A new project has no existing flow — it is architectural. |
@@ -98,7 +133,7 @@ your path and complete them in order.
98
133
  4. **Propose 2-3 approaches** — with trade-offs and your recommendation
99
134
  5. **Present design** — in sections scaled to their complexity, get user approval after each section
100
135
  6. **Write design doc** — save to `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md` and commit
101
- 7. **Spec self-review** — quick inline check for placeholders, contradictions, ambiguity, scope (see below)
136
+ 7. **Planning-handoff review** — simulate the next planning stage, rate and inventory its burdens, and take the required bounded branch (see below)
102
137
  8. **User reviews written spec** — ask user to review the spec file before proceeding
103
138
  9. **Transition to implementation** — invoke writing-plans skill to create implementation plan
104
139
 
@@ -119,7 +154,7 @@ digraph brainstorming {
119
154
  "Present design sections" [shape=box];
120
155
  "User approves design?" [shape=diamond];
121
156
  "Write design doc" [shape=box];
122
- "Spec self-review\n(fix inline)" [shape=box];
157
+ "Planning-handoff review\n(rate; burden ledger; bounded branch)" [shape=box];
123
158
  "User reviews spec?" [shape=diamond];
124
159
  "Invoke writing-plans skill" [shape=doublecircle];
125
160
  "Hidden complexity? Upgrade path" [shape=box];
@@ -139,8 +174,8 @@ digraph brainstorming {
139
174
  "Present design sections" -> "User approves design?";
140
175
  "User approves design?" -> "Present design sections" [label="no, revise"];
141
176
  "User approves design?" -> "Write design doc" [label="yes"];
142
- "Write design doc" -> "Spec self-review\n(fix inline)";
143
- "Spec self-review\n(fix inline)" -> "User reviews spec?";
177
+ "Write design doc" -> "Planning-handoff review\n(rate; burden ledger; bounded branch)";
178
+ "Planning-handoff review\n(rate; burden ledger; bounded branch)" -> "User reviews spec?";
144
179
  "User reviews spec?" -> "Write design doc" [label="changes requested"];
145
180
  "User reviews spec?" -> "Invoke writing-plans skill" [label="approved"];
146
181
  }
@@ -169,6 +204,7 @@ is the whole process.
169
204
  - For appropriately-scoped projects, ask questions one at a time to refine the idea
170
205
  - Prefer multiple choice questions when possible, but open-ended is fine too
171
206
  - Only one question per message - if a topic needs more exploration, break it into multiple questions
207
+ - A request for context is not an answer - if the user asks for trade-offs, implications, or examples instead of choosing, give them that and then re-ask the same question. It stays the only open question until they decide; don't pair it with a new one
172
208
  - Focus on understanding: purpose, constraints, success criteria
173
209
 
174
210
  **Exploring approaches:**
@@ -209,15 +245,79 @@ is the whole process.
209
245
  - Use elements-of-style:writing-clearly-and-concisely skill if available
210
246
  - Commit the design document to git
211
247
 
212
- **Spec Self-Review:**
213
- After writing the spec document, look at it with fresh eyes:
214
-
215
- 1. **Placeholder scan:** Any "TBD", "TODO", incomplete sections, or vague requirements? Fix them.
216
- 2. **Internal consistency:** Do any sections contradict each other? Does the architecture match the feature descriptions?
217
- 3. **Scope check:** Is this focused enough for a single implementation plan, or does it need decomposition?
218
- 4. **Ambiguity check:** Could any requirement be interpreted two different ways? If so, pick one and make it explicit.
219
-
220
- Fix any issues inline. No need to re-review — just fix and move on.
248
+ **Planning-Handoff Review:**
249
+ This is the spec self-review. Before asking for review, set the conversation
250
+ aside and temporarily act as a
251
+ fresh planning agent whose only inputs are the finished spec and the repository.
252
+ Begin mapping how you would turn the spec into an implementation plan, but do not
253
+ write that plan. Trace each affected entry point through the relevant components
254
+ and state transitions. Note each point where planning would require you to
255
+ rediscover design intent or invent a binding decision rather than choose an
256
+ implementation detail. Include placeholders, internal contradictions, scope
257
+ problems, and ambiguous requirements in this assessment; they are handoff seams,
258
+ not a separate review stage.
259
+
260
+ Rate the spec's planning-handoff readiness from 0.0–9.9 and explain what
261
+ prevents the next higher rating using repository and artifact evidence:
262
+ 0 = reject; 2 = major redesign; 4 = substantial design rescue; 6 = usable but
263
+ planning must reconstruct a binding decision; 8 = handoff-ready with only
264
+ planning-owned choices; 9.9 = rare exemplary ceiling, never perfection. The
265
+ rating prompts the assessment; it does not decide whether the artifact improves.
266
+
267
+ Translate every design-owned reason preventing the next higher rating into a
268
+ burden ledger before editing. One burden is one independent binding decision or
269
+ piece of design reconstruction the planning agent must resolve before it can
270
+ specify implementation work. Record planning-owned choices separately rather
271
+ than counting them as burdens. For each burden, state the repository or artifact
272
+ evidence, what the planner would have to invent, and its weight:
273
+
274
+ - **minor (1):** a localized clarification or reconstruction;
275
+ - **major (2):** an unresolved binding decision or cross-component design
276
+ uncertainty. Record contradictions and departures from the approved design
277
+ separately; they are not ordinary tradeable burdens.
278
+
279
+ After the initial rating and ledger, take exactly one branch:
280
+
281
+ - If the rating is below 9.0 **or** the ledger contains any burden, preserve the
282
+ initial draft, return to the spec-writer role, and make one bounded improvement
283
+ pass targeting the evidence-based reasons preventing 9.0 and the named
284
+ burdens. Do not resolve a purely planning-owned choice merely to raise the
285
+ rating.
286
+ - Only if the rating is at least 9.0 **and** the burden ledger is empty, make no
287
+ edit.
288
+
289
+ The bounded pass is the only review-driven editing phase after the first
290
+ complete draft. It may clarify, reconcile, and complete the spec using the
291
+ approved design, stated requirements, and repository evidence; preserve the
292
+ binding decisions already approved in the conversation. When an improvement
293
+ would require a new or changed binding design decision, leave it as a burden
294
+ instead of choosing it.
295
+
296
+ After the pass, close editing and reassess both versions read-only. Trace every
297
+ concept changed by the pass through the whole spec, including the state model,
298
+ entry points, failure/recovery rules, and acceptance criteria. Give a fresh
299
+ rating, build the final burden ledger from scratch, and compare it with the
300
+ initial ledger. Include burdens that moved or appeared elsewhere. Check
301
+ separately for a newly introduced major burden, contradiction, departure from
302
+ approved design, or scope change. More specific wording is not automatically an
303
+ improvement.
304
+
305
+ Select the revised draft only when its total weighted burden is lower and it
306
+ introduces no new major burden, contradiction, or design departure. New minor
307
+ burdens are permitted only when the total burden still falls. Otherwise restore
308
+ the preserved initial draft. Restoring the original is the only spec mutation
309
+ permitted after reassessment: do not fix the revised draft or begin another
310
+ pass. Present the selected spec through the normal user review handoff, and
311
+ commit the selected draft — with any changes the user requests afterwards — so
312
+ that handoff always points at a committed revision.
313
+ Keep the ratings, burden ledgers, and comparison in your private review; do not
314
+ add them to the spec or require them as a separate handoff artifact.
315
+ If the original was restored, mention briefly that the tentative revision was
316
+ rejected and the original retained; do not add a separate review artifact.
317
+
318
+ Accept, restore, and report based on burden—not whether the rating rose. The
319
+ review never grants permission to begin planning. Do not produce task sequencing,
320
+ invoke `writing-plans`, or repeat the pass.
221
321
 
222
322
  **User Review Gate:**
223
323
  After the spec review loop passes, ask the user to review the written spec before proceeding:
@@ -160,7 +160,7 @@
160
160
  // Expose API for explicit use
161
161
  window.brainstorm = {
162
162
  send: sendEvent,
163
- choice: (value, metadata = {}) => sendEvent({ type: 'choice', value, ...metadata })
163
+ choice: (value, metadata = {}) => sendEvent({ type: 'choice', ...metadata, choice: value })
164
164
  };
165
165
 
166
166
  connect();