brainstorming
在任何创造性工作之前必须使用此技能——创建功能、构建组件、添加功能或修改行为。在实现之前先探索用户意图、需求和设计。
#planning #design
Install
npx skills add https://github.com/jnMetaCode/superpowers-zh/tree/main/skills/brainstorming
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install jnmetacode-superpowers-zh@llmmart
git clone https://github.com/jnMetaCode/superpowers-zh.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole jnmetacode/superpowers-zh collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
头脑风暴:将想法转化为设计
通过自然的协作对话,帮助将想法转化为完整的设计和规格说明。
先判断这个需求需要多少流程,然后沿着对应的路径推进:理解上下文、完善想法、展示设计、获得你的人类伙伴批准。
三条路径
在提出第一个问题之前,先给需求分类,并把分类说出来——"这个看起来是有界的,所以我会在这里直接给一份简短设计,而不是写规格文档"——好让你的人类伙伴能纠正你:
- 探路(Spike) — 一个可行性问题("我们能不能……"、"有没有可能……"、"糙一点没关系"),它的产出是一个答案,不是要留下的代码。用 2-3 句话说明问题和你打算怎么试,得到一个点头,然后用不牺牲正确性的最低成本去弄清楚。不写设计文档,不写规格文件。以建议的形式汇报发现;过程中搭的任何东西都明确标注为一次性的。
- 有界(Bounded) — 对本仓库里已经存在的代码做范围明确的改动:加一个开关、一个小接口、改一个文件的 bug。"知道这是个什么类型的应用"不算数——有界意味着你要改的那条流程此刻就在仓库里、可以读。如果没有现成的流程可改,这个任务就不是有界的。问那些真正重要的澄清问题,在对话里给出一份简短设计(几句话到几个短段落),然后停下。只有在你的人类伙伴对这份设计说"可以"之后,实现才开始——有界任务的批准和架构级任务的批准是同样硬的关卡。不写规格文件,不写实现计划文档。
- 架构级(Architectural) — 新项目、新子系统,以及会重构组件之间关系、或改动他人依赖的接口的改动。走完整流程:提问、方案对比、分节设计、书面规格,然后交给 writing-plans 技能。
在两条路径之间拿不准时,选更重的那条。这个棘轮只朝一个方向转:任务进行中发现隐藏的复杂度,就升级路径——停下来、说明情况、升上去。任何情况下都不在任务中途降级。
反模式:"这个太简单了,不需要批准"
每条路径的终点都是你的人类伙伴在实现之前批准你的意图。一个待办事项列表、一个单函数工具、一个配置变更——设计可以只是对话里的两句话,但你必须把它展示出来并获得批准。"简单"的任务恰恰是未经检验的假设造成最多浪费的地方。随简单程度缩放的是产出物,永远不是批准。
危险信号
| 心里的想法 | 实际情况 |
|---|---|
| "这个太简单了,不需要设计" | 简单意味着简短的设计,不是没有设计。对话里两句话,然后获得批准。 |
| "我就说它是有界的,跳过规格文档" | 为了少干活而去够一个标签,这本身就是"拿不准"——选更重的那条路径。 |
| "它是有界的,设计也很显然——我一边让他们读一边开工" | 关卡是批准,不是设计的长度。展示完就停,直到听见"可以"。 |
| "这类应用我很熟,所以它是有界的" | 有界衡量的是仓库,不是你的熟悉程度。新项目没有现成的流程可改——那是架构级。 |
| "探路跑通了,那这些代码就留着吧" | 探路的产出是一个答案。要留下代码是一个新的需求——给它重新分类。 |
| "范围是变大了,但我快做完了,不用重新分类" | 隐藏的复杂度会在任务中途升级路径。停下来,说明情况。 |
| "他们批准了探路,那后续改动也算批准了" | 每个任务有自己的分类,也有自己的批准。 |
检查清单
先分类,宣布路径,然后为你所在路径上的每个条目创建任务,并按顺序完成。
探路(Spike):
- 探索项目上下文 — 够用来框定这次试探即可
- 展示问题 + 试探计划 — 2-3 句话
- 获得批准 — 一个点头就够
- 动手调查 — 用不牺牲正确性的最低成本
- 汇报发现 — 以建议的形式;搭出来的任何东西都标注为一次性的
有界(Bounded):
- 探索项目上下文 — 检查文件、文档、最近的 commit
- 提出澄清问题 — 每次一个,只问那些真正重要的
- 在对话里展示简短设计 — 思路、会动哪些文件、怎么测
- 获得批准 — 停下并等待一个明确的"可以";展示完设计顺口就开工,等于跳过了关卡
- 实现 — 走正常的开发工作流(TDD 同样适用);不写计划文档
架构级(Architectural):
- 探索项目上下文 — 检查文件、文档、最近的 commit
- 在需要时才提供视觉伴侣 — 不要一上来就提。第一次遇到"这个问题画出来比说出来更清楚"时,才在那一刻提供(作为独立的一条消息);对方同意后,浏览器标签页会为你打开。如果自始至终没出现视觉问题,就永远不要提。参见下方"视觉伴侣"部分。
- 提出澄清问题 — 每次一个,了解目的/约束/成功标准
- 提出 2-3 种方案 — 附带权衡分析和你的推荐
- 展示设计 — 按复杂度分节展示,每节展示后获得用户批准
- 编写设计文档 — 保存到
docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md并 commit - 规格自检 — 快速内联检查占位符、矛盾、模糊性、范围(详见下方)
- 用户审查书面规格 — 在继续之前请用户审查规格文件
- 过渡到实现 — 调用 writing-plans 技能创建实现计划
流程图
digraph brainstorming {
"分类:探路 / 有界 / 架构级" [shape=diamond];
"展示问题 + 试探计划(2-3 句)" [shape=box];
"提出澄清问题(有界)" [shape=box];
"在对话里展示简短设计" [shape=box];
"人类伙伴批准?" [shape=diamond];
"动手调查;汇报建议" [shape=doublecircle];
"走正常工作流实现(无计划文档)" [shape=doublecircle];
"探索项目上下文" [shape=box];
"提出澄清问题" [shape=box];
"提出 2-3 种方案" [shape=box];
"分节展示设计" [shape=box];
"用户批准设计?" [shape=diamond];
"编写设计文档" [shape=box];
"规格自检\n(内联修复)" [shape=box];
"用户审查规格?" [shape=diamond];
"调用 writing-plans 技能" [shape=doublecircle];
"发现隐藏复杂度? 升级路径" [shape=box];
"分类:探路 / 有界 / 架构级" -> "展示问题 + 试探计划(2-3 句)" [label="探路"];
"分类:探路 / 有界 / 架构级" -> "提出澄清问题(有界)" [label="有界"];
"分类:探路 / 有界 / 架构级" -> "探索项目上下文" [label="架构级"];
"展示问题 + 试探计划(2-3 句)" -> "人类伙伴批准?";
"提出澄清问题(有界)" -> "在对话里展示简短设计";
"在对话里展示简短设计" -> "人类伙伴批准?";
"人类伙伴批准?" -> "动手调查;汇报建议" [label="探路:是"];
"人类伙伴批准?" -> "走正常工作流实现(无计划文档)" [label="有界:是"];
"发现隐藏复杂度? 升级路径" -> "分类:探路 / 有界 / 架构级";
"探索项目上下文" -> "提出澄清问题";
"提出澄清问题" -> "提出 2-3 种方案";
"提出 2-3 种方案" -> "分节展示设计";
"分节展示设计" -> "用户批准设计?";
"用户批准设计?" -> "分节展示设计" [label="否,修改"];
"用户批准设计?" -> "编写设计文档" [label="是"];
"编写设计文档" -> "规格自检\n(内联修复)";
"规格自检\n(内联修复)" -> "用户审查规格?";
"用户审查规格?" -> "编写设计文档" [label="要求修改"];
"用户审查规格?" -> "调用 writing-plans 技能" [label="批准"];
}
终止状态跟着路径走。 架构级:头脑风暴之后你唯一要调用的技能是 writing-plans——绝不调用 frontend-design、mcp-builder 或任何其他实现技能。有界:获得批准之后,直接走正常的开发工作流去实现,不写计划文档。探路:终止状态是一份汇报出去的建议。
流程详述
下面这些小节服务于有界和架构级两条路径(探路在"展示试探计划、拿到点头"就停了)。从探索方案往后都是架构级路径的深度——对有界的工作来说,上下文加几个问题再加一份对话里的简短设计,就是全部流程。
理解想法:
- 首先查看当前项目状态(文件、文档、最近的 commit)
- 在提出详细问题之前,先评估范围:如果需求描述了多个独立子系统(例如"构建一个包含聊天、文件存储、计费和分析的平台"),立即指出这一点。不要花时间用问题去细化一个需要先拆分的项目。
- 如果项目规模过大,单个规格说明无法覆盖,帮助用户分解为子项目:有哪些独立的部分,它们之间有什么关系,应该按什么顺序构建?然后通过正常的设计流程进行第一个子项目的头脑风暴。每个子项目都有自己的规格 → 计划 → 实现周期。
- 对于范围适当的项目,每次提一个问题来完善想法
- 尽量使用选择题,开放式问题也可以
- 每条消息只提一个问题——如果一个主题需要更多探索,拆分成多个问题
- 重点理解:目的、约束、成功标准
探索方案:
- 提出 2-3 种不同的方案及其权衡
- 以对话的方式展示选项,附上你的推荐和理由
- 先展示你推荐的方案并解释原因
- 严格遵循 YAGNI —— 从每个方案和设计里移除不必要的功能
展示设计:
- 一旦你认为理解了要构建的内容,就展示设计
- 每个部分的篇幅与其复杂度匹配:简单的几句话,复杂的最多 200-300 字
- 每个部分展示后询问是否正确
- 涵盖:架构、组件、数据流、错误处理、测试
- 随时准备回头澄清不明确的地方
面向隔离和清晰的设计:
- 将系统拆分为更小的单元,每个单元有一个明确的职责,通过定义良好的接口通信,可以独立理解和测试
- 对于每个单元,你应该能回答:它做什么,如何使用,它依赖什么?
- 别人能否不看内部实现就理解一个单元的功能?你能否在不影响调用者的情况下修改内部实现?如果不能,边界需要调整。
- 更小、边界清晰的单元也更便于你工作——你对能一次放入上下文的代码推理得更好,文件越专注你的编辑越可靠。当文件变大时,这通常意味着它承担了过多职责。
在现有代码库中工作:
- 在提出更改之前先探索现有结构。遵循现有模式。
- 如果现有代码存在影响当前工作的问题(例如文件过大、边界不清、职责纠缠),在设计中包含有针对性的改进——就像一个优秀的开发者在工作中改进经手的代码一样。
- 不要提议无关的重构。专注于服务当前目标的事情。
设计之后(架构级路径)
文档:
- 将验证通过的设计(规格说明)写入
docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md- (用户对规格位置的偏好优先于此默认值)
- 如果可用,使用 elements-of-style:writing-clearly-and-concisely 技能
- 将设计文档 commit 到 git
规格自检: 编写规格文档后,以全新的视角审视它:
- 占位符扫描: 有没有"待定"、"TODO"、未完成的章节或模糊的需求?修复它们。
- 内部一致性: 各章节之间有矛盾吗?架构和功能描述匹配吗?
- 范围检查: 这是否聚焦到可以用一个实现计划覆盖,还是需要进一步拆分?
- 模糊性检查: 有没有需求可以被两种方式理解?如果有,选择一种并明确写出来。
发现问题就直接内联修复。无需重新审查——修好继续推进。
用户审查关卡: 规格自检完成后,请用户在继续之前审查书面规格:
"规格已编写并 commit 到
<path>。请审查一下,如果在我们开始编写实现计划之前你想做任何修改,请告诉我。"
等待用户回复。如果他们要求修改,做出修改并重新运行规格自检。只有在用户批准后才继续。
实现:
- 调用 writing-plans 技能创建详细的实现计划
- 不要调用任何其他技能。writing-plans 是下一步。
视觉伴侣
一个基于浏览器的伴侣工具,用于在头脑风暴过程中展示原型、图表和视觉选项。它是一个工具——不是一种模式。接受伴侣意味着它可用于适合视觉呈现的问题;并不意味着每个问题都要通过浏览器。
提供伴侣(在需要时才提): 不要一上来就提。 等到某个问题确实"画出来比说出来更清楚"时再提——要是真正的原型 / 布局 / 图表问题,而不仅仅是话题跟 UI 沾边。第一次出现这种情况时,就在那一刻提供,作为独立的一条消息:
"接下来这部分,我展示给你看可能更容易理解——我可以在讨论过程中,在一个浏览器标签页里做原型、图表和对比。这个功能还比较新,可能会消耗较多 token。要我打开吗?我来帮你打开。"
此提议必须是一条独立的消息。 只有这条提议——不含澄清问题、内容摘要或任何其他内容。等待用户回复。如果他们接受,用 --open 启动服务,浏览器会自动打开到第一屏。如果他们拒绝,继续纯文本进行,并且不要再提,除非他们自己提起。
逐问题决策: 即使用户接受了,也要对每个问题单独决定是使用浏览器还是终端。判断标准:用户看到它是否比读到它更容易理解?
- 使用浏览器 展示本身就是视觉的内容——原型、线框图、布局对比、架构图、并排视觉设计
- 使用终端 展示文本内容——需求问题、概念选择、权衡列表、A/B/C/D 文字选项、范围决策
关于 UI 主题的问题不一定是视觉问题。"在这个上下文中个性化是什么意思?"是一个概念问题——使用终端。"哪种向导布局更好?"是一个视觉问题——使用浏览器。
如果他们同意使用伴侣,在继续之前阅读详细指南:
skills/brainstorming/visual-companion.md
Files (superpowers-zh)
-
scripts
-
frame-template.html 7.9 KB · in bundle
-
helper.js 5.5 KB
(function() { const MIN_RECONNECT_MS = 500; const MAX_RECONNECT_MS = 30000; const TOMBSTONE_AFTER_MS = 15000; // show the "paused" overlay after this long disconnected // Pure: next backoff delay (doubles, capped). Exported for unit tests. function nextReconnectDelay(current, max) { return Math.min(current * 2, max); } if (typeof module !== 'undefined' && module.exports) { module.exports = { nextReconnectDelay, MIN_RECONNECT_MS, MAX_RECONNECT_MS, TOMBSTONE_AFTER_MS }; } // Everything below is browser-only; bail out when loaded in Node (tests). if (typeof window === 'undefined') return; let ws = null; let eventQueue = []; let reconnectDelay = MIN_RECONNECT_MS; let reconnectTimer = null; let disconnectedSince = null; let everConnected = false; let tombstoneShown = false; function sessionKey() { try { return window.sessionStorage && window.sessionStorage.getItem('brainstorm-session-key'); } catch (e) {} return null; } function websocketUrl() { const key = sessionKey(); return 'ws://' + window.location.host + (key ? '/?key=' + encodeURIComponent(key) : ''); } function reloadAfterRecovery() { const key = sessionKey(); if (key) { window.location.replace('/?key=' + encodeURIComponent(key)); } else { window.location.reload(); } } // Reflect connection state in the frame's status pill (absent on full-doc screens). function setStatus(state) { const el = document.querySelector('.status'); if (!el) return; const map = { connecting: ['Connecting…', 'var(--text-tertiary)'], connected: ['Connected', 'var(--success)'], reconnecting: ['Reconnecting…', 'var(--warning)'], disconnected: ['Disconnected', 'var(--error)'] }; const [text, color] = map[state] || map.disconnected; el.textContent = text; el.style.setProperty('--status-color', color); } // Self-styled so it works on framed and full-document screens alike. function showTombstone() { if (tombstoneShown) return; tombstoneShown = true; const el = document.createElement('div'); el.id = 'bs-tombstone'; el.style.cssText = 'position:fixed;inset:0;z-index:99999;display:flex;' + 'align-items:center;justify-content:center;padding:2rem;text-align:center;' + 'background:rgba(20,20,22,0.92);color:#f5f5f7;font-family:system-ui,sans-serif'; el.innerHTML = '<div style="max-width:480px">' + '<h2 style="margin:0 0 .5rem;font-weight:600">Companion paused</h2>' + '<p style="margin:0;opacity:.85">This brainstorm companion has stopped. ' + 'Ask your coding agent to bring it back — this page reconnects automatically.</p></div>'; if (document.body) document.body.appendChild(el); } function connect() { if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; } setStatus(everConnected ? 'reconnecting' : 'connecting'); ws = new WebSocket(websocketUrl()); ws.onopen = () => { const recovered = tombstoneShown; everConnected = true; disconnectedSince = null; reconnectDelay = MIN_RECONNECT_MS; tombstoneShown = false; setStatus('connected'); eventQueue.forEach(e => ws.send(JSON.stringify(e))); eventQueue = []; // Recovered from a tombstoned outage (e.g. the server restarted on the same // port) — reload through the keyed bootstrap when possible so the cookie is // refreshed before the visible URL returns to bare /. if (recovered) reloadAfterRecovery(); }; ws.onmessage = (msg) => { let data; try { data = JSON.parse(msg.data); } catch (e) { return; } if (data.type === 'reload') window.location.reload(); }; ws.onclose = () => { ws = null; if (disconnectedSince === null) disconnectedSince = Date.now(); if (Date.now() - disconnectedSince >= TOMBSTONE_AFTER_MS) { setStatus('disconnected'); showTombstone(); } else { setStatus('reconnecting'); } reconnectTimer = setTimeout(connect, reconnectDelay); reconnectDelay = nextReconnectDelay(reconnectDelay, MAX_RECONNECT_MS); }; // Let onclose own reconnection so we don't schedule it twice. ws.onerror = () => { try { ws.close(); } catch (e) {} }; } function sendEvent(event) { event.timestamp = Date.now(); if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify(event)); } else { eventQueue.push(event); } } // Capture clicks on choice elements document.addEventListener('click', (e) => { const target = e.target.closest('[data-choice]'); if (!target) return; sendEvent({ type: 'click', text: target.textContent.trim(), choice: target.dataset.choice, id: target.id || null }); }); // Frame UI: selection tracking window.selectedChoice = null; window.toggleSelect = function(el) { const container = el.closest('.options') || el.closest('.cards'); const multi = container && container.dataset.multiselect !== undefined; if (container && !multi) { container.querySelectorAll('.option, .card').forEach(o => o.classList.remove('selected')); } if (multi) { el.classList.toggle('selected'); } else { el.classList.add('selected'); } window.selectedChoice = el.dataset.choice; }; // Expose API for explicit use window.brainstorm = { send: sendEvent, choice: (value, metadata = {}) => sendEvent({ type: 'choice', value, ...metadata }) }; connect(); })(); -
server.cjs 25.1 KB · in bundle
-
start-server.sh 6.7 KB
#!/usr/bin/env bash # Start the brainstorm server and output connection info # Usage: start-server.sh [--project-dir <path>] [--host <bind-host>] [--url-host <display-host>] [--foreground] [--background] # # Starts server on a random high port, outputs JSON with URL. # Each session gets its own directory to avoid conflicts. # # Options: # --project-dir <path> Store session files under <path>/.superpowers/brainstorm/ # instead of /tmp. Files persist after server stops. # --host <bind-host> Host/interface to bind (default: 127.0.0.1). # Use 0.0.0.0 in remote/containerized environments. # --url-host <host> Hostname shown in returned URL JSON. # --idle-timeout-minutes <n> Shut down after n minutes idle (default 240 = 4h). # --open Auto-open the browser on the first screen (use only # after the user approves the visual companion). # --foreground Run server in the current terminal (no backgrounding). # --background Force background mode (overrides Codex auto-foreground). SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" # Parse arguments PROJECT_DIR="" FOREGROUND="false" FORCE_BACKGROUND="false" BIND_HOST="127.0.0.1" URL_HOST="" IDLE_TIMEOUT_MINUTES="" while [[ $# -gt 0 ]]; do case "$1" in --project-dir) PROJECT_DIR="$2" shift 2 ;; --host) BIND_HOST="$2" shift 2 ;; --url-host) URL_HOST="$2" shift 2 ;; --idle-timeout-minutes) IDLE_TIMEOUT_MINUTES="$2" shift 2 ;; --open) export BRAINSTORM_OPEN=1 shift ;; --foreground|--no-daemon) FOREGROUND="true" shift ;; --background|--daemon) FORCE_BACKGROUND="true" shift ;; *) echo "{\"error\": \"Unknown argument: $1\"}" exit 1 ;; esac done if [[ -z "$URL_HOST" ]]; then if [[ "$BIND_HOST" == "127.0.0.1" || "$BIND_HOST" == "localhost" ]]; then URL_HOST="localhost" else URL_HOST="$BIND_HOST" fi fi if [[ -n "$IDLE_TIMEOUT_MINUTES" ]]; then if ! [[ "$IDLE_TIMEOUT_MINUTES" =~ ^[0-9]+$ ]] || [[ "$IDLE_TIMEOUT_MINUTES" -lt 1 ]]; then echo "{\"error\": \"--idle-timeout-minutes must be a positive integer\"}" exit 1 fi export BRAINSTORM_IDLE_TIMEOUT_MS=$(( IDLE_TIMEOUT_MINUTES * 60 * 1000 )) fi is_windows_like_shell() { case "${OSTYPE:-}" in msys*|cygwin*|mingw*) return 0 ;; esac if [[ -n "${MSYSTEM:-}" ]]; then return 0 fi local uname_s uname_s="$(uname -s 2>/dev/null || true)" case "$uname_s" in MSYS*|MINGW*|CYGWIN*) return 0 ;; esac return 1 } # Some environments reap detached/background processes. Auto-foreground when detected. if [[ -n "${CODEX_CI:-}" && "$FOREGROUND" != "true" && "$FORCE_BACKGROUND" != "true" ]]; then FOREGROUND="true" fi # Windows/Git Bash reaps nohup background processes. Auto-foreground when detected. if [[ "$FOREGROUND" != "true" && "$FORCE_BACKGROUND" != "true" ]]; then if is_windows_like_shell; then FOREGROUND="true" fi fi # Session files (server.log, server-info, .last-token) embed the session key — # keep everything this script and the server create owner-only. umask 077 # Generate unique session directory SESSION_ID="$$-$(date +%s)" if [[ -n "$PROJECT_DIR" ]]; then SESSION_DIR="${PROJECT_DIR}/.superpowers/brainstorm/${SESSION_ID}" # Persist the bound port and key per project so a restart reuses them and an # already-open browser tab reconnects to the same URL with a valid cookie. export BRAINSTORM_PORT_FILE="${PROJECT_DIR}/.superpowers/brainstorm/.last-port" export BRAINSTORM_TOKEN_FILE="${PROJECT_DIR}/.superpowers/brainstorm/.last-token" else SESSION_DIR="/tmp/brainstorm-${SESSION_ID}" fi STATE_DIR="${SESSION_DIR}/state" PID_FILE="${STATE_DIR}/server.pid" LOG_FILE="${STATE_DIR}/server.log" SERVER_ID_FILE="${STATE_DIR}/server-instance-id" # Create fresh session directory with content and state peers mkdir -p "${SESSION_DIR}/content" "$STATE_DIR" SERVER_ID="" if [[ -r /dev/urandom ]]; then SERVER_ID="$(od -An -N24 -tx1 /dev/urandom 2>/dev/null | tr -d ' \n' || true)" fi if ! [[ "$SERVER_ID" =~ ^[A-Za-z0-9_-]{32,64}$ ]]; then SERVER_ID="$(printf '%08x%08x%08x%08x' "$$" "$(date +%s)" "${RANDOM:-0}" "${RANDOM:-0}")" fi printf '%s\n' "$SERVER_ID" > "$SERVER_ID_FILE" chmod 600 "$SERVER_ID_FILE" 2>/dev/null || true # Kill any existing server if [[ -f "$PID_FILE" ]]; then old_pid=$(cat "$PID_FILE") kill "$old_pid" 2>/dev/null rm -f "$PID_FILE" fi cd "$SCRIPT_DIR" || exit 1 # Resolve the harness PID (grandparent of this script). # $PPID is the ephemeral shell the harness spawned to run us — it dies # when this script exits. The harness itself is $PPID's parent. OWNER_PID="$(ps -o ppid= -p "$PPID" 2>/dev/null | tr -d ' ')" if [[ -z "$OWNER_PID" || "$OWNER_PID" == "1" ]]; then OWNER_PID="$PPID" fi # Windows/MSYS2: Node.js cannot see POSIX PIDs from the MSYS2 namespace. # Passing a PID node cannot verify causes server to log owner-pid-invalid # and self-terminate at the 60-second lifecycle check. Clear it so the # watchdog is disabled and the idle timeout becomes the only shutdown trigger. if is_windows_like_shell; then OWNER_PID="" fi # Foreground mode for environments that reap detached/background processes. if [[ "$FOREGROUND" == "true" ]]; then env BRAINSTORM_DIR="$SESSION_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.cjs "--brainstorm-server-id=$SERVER_ID" & SERVER_PID=$! echo "$SERVER_PID" > "$PID_FILE" wait "$SERVER_PID" exit $? fi # Start server, capturing output to log file # Use nohup to survive shell exit; disown to remove from job table nohup env BRAINSTORM_DIR="$SESSION_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.cjs "--brainstorm-server-id=$SERVER_ID" > "$LOG_FILE" 2>&1 & SERVER_PID=$! disown "$SERVER_PID" 2>/dev/null echo "$SERVER_PID" > "$PID_FILE" # Wait for server-started message (check log file) for _ in {1..50}; do if grep -q "server-started" "$LOG_FILE" 2>/dev/null; then # Verify server is still alive after a short window (catches process reapers) alive="true" for _ in {1..20}; do if ! kill -0 "$SERVER_PID" 2>/dev/null; then alive="false" break fi sleep 0.1 done if [[ "$alive" != "true" ]]; then echo "{\"error\": \"Server started but was killed. Retry in a persistent terminal with: $SCRIPT_DIR/start-server.sh${PROJECT_DIR:+ --project-dir $PROJECT_DIR} --host $BIND_HOST --url-host $URL_HOST --foreground\"}" exit 1 fi grep "server-started" "$LOG_FILE" | head -1 exit 0 fi sleep 0.1 done # Timeout - server didn't start echo '{"error": "Server failed to start within 5 seconds"}' exit 1 -
stop-server.sh 3.2 KB
#!/usr/bin/env bash # Stop the brainstorm server and clean up # Usage: stop-server.sh <session_dir> # # Kills the server process. Only deletes session directory if it's # under /tmp (ephemeral). Persistent directories (.superpowers/) are # kept so mockups can be reviewed later. SESSION_DIR="$1" if [[ -z "$SESSION_DIR" ]]; then echo '{"error": "Usage: stop-server.sh <session_dir>"}' exit 1 fi STATE_DIR="${SESSION_DIR}/state" PID_FILE="${STATE_DIR}/server.pid" SERVER_ID_FILE="${STATE_DIR}/server-instance-id" mark_stopped() { local reason="$1" rm -f "${STATE_DIR}/server-info" printf '{"reason":"%s","timestamp":%s}\n' "$reason" "$(date +%s)" > "${STATE_DIR}/server-stopped" } read_expected_server_id() { [[ -f "$SERVER_ID_FILE" ]] || return 1 local id id="$(tr -d '\r\n' < "$SERVER_ID_FILE" 2>/dev/null || true)" [[ "$id" =~ ^[A-Za-z0-9_-]{32,64}$ ]] || return 1 printf '%s\n' "$id" } command_line_for_pid() { local pid="$1" if [[ -r "/proc/$pid/cmdline" ]]; then tr '\0' '\n' < "/proc/$pid/cmdline" 2>/dev/null || true return 0 fi ps -ww -p "$pid" -o command= 2>/dev/null || ps -f -p "$pid" 2>/dev/null | sed '1d' || true } command_has_server_id() { local pid="$1" local expected="$2" local expected_arg="--brainstorm-server-id=$expected" if [[ -r "/proc/$pid/cmdline" ]]; then local arg while IFS= read -r -d '' arg || [[ -n "$arg" ]]; do [[ "$arg" == "$expected_arg" ]] && return 0 done < "/proc/$pid/cmdline" return 1 fi local command_line command_line="$(command_line_for_pid "$pid")" [[ -n "$command_line" ]] || return 1 case " $command_line " in *" $expected_arg "*) return 0 ;; *) return 1 ;; esac } # Confirm a PID has this session's per-start instance id, not just a familiar # process name. Ambiguous or legacy metadata fails closed as stale_pid. is_brainstorm_server() { kill -0 "$1" 2>/dev/null || return 1 local expected_id expected_id="$(read_expected_server_id)" || return 1 command_has_server_id "$1" "$expected_id" || return 1 return 0 } if [[ -f "$PID_FILE" ]]; then pid=$(cat "$PID_FILE") # Refuse to signal a PID we can't prove is our server. A stale pid file may # point at an unrelated process after a reboot/PID wraparound. if ! is_brainstorm_server "$pid"; then rm -f "$PID_FILE" "$SERVER_ID_FILE" mark_stopped "stale_pid" echo '{"status": "stale_pid"}' exit 0 fi # Try to stop gracefully, fallback to force if still alive kill "$pid" 2>/dev/null || true # Wait for graceful shutdown (up to ~2s) for _ in {1..20}; do if ! kill -0 "$pid" 2>/dev/null; then break fi sleep 0.1 done # If still running, escalate to SIGKILL if kill -0 "$pid" 2>/dev/null; then kill -9 "$pid" 2>/dev/null || true # Give SIGKILL a moment to take effect sleep 0.1 fi if kill -0 "$pid" 2>/dev/null; then echo '{"status": "failed", "error": "process still running"}' exit 1 fi rm -f "$PID_FILE" "$SERVER_ID_FILE" "${STATE_DIR}/server.log" mark_stopped "stop-server.sh" # Only delete ephemeral /tmp directories if [[ "$SESSION_DIR" == /tmp/* ]]; then rm -rf "$SESSION_DIR" fi echo '{"status": "stopped"}' else echo '{"status": "not_running"}' fi
-
-
SKILL.md 15 KB
--- name: brainstorming description: "在任何创造性工作之前必须使用此技能——创建功能、构建组件、添加功能或修改行为。在实现之前先探索用户意图、需求和设计。" version: "1.0.0" license: MIT metadata: hermes: tags: [design, planning] --- # 头脑风暴:将想法转化为设计 通过自然的协作对话,帮助将想法转化为完整的设计和规格说明。 先判断这个需求需要多少流程,然后沿着对应的路径推进:理解上下文、完善想法、展示设计、获得你的人类伙伴批准。 <HARD-GATE> 在你告诉你的人类伙伴你打算做什么、并得到他们批准之前,不要调用任何实现技能、编写任何代码、搭建任何项目或采取任何实现行动。这适用于下面**每一条路径上的每一个任务**——仪式感随任务大小缩放,批准这道关卡永远不缩放。 </HARD-GATE> ## 三条路径 在提出第一个问题之前,先给需求分类,并把分类**说出来**——"这个看起来是有界的,所以我会在这里直接给一份简短设计,而不是写规格文档"——好让你的人类伙伴能纠正你: - **探路(Spike)** — 一个可行性问题("我们能不能……"、"有没有可能……"、"糙一点没关系"),它的产出是一个**答案**,不是要留下的代码。用 2-3 句话说明问题和你打算怎么试,得到一个点头,然后用不牺牲正确性的最低成本去弄清楚。不写设计文档,不写规格文件。以建议的形式汇报发现;过程中搭的任何东西都明确标注为一次性的。 - **有界(Bounded)** — 对**本仓库里已经存在的代码**做范围明确的改动:加一个开关、一个小接口、改一个文件的 bug。"知道这是个什么类型的应用"不算数——有界意味着**你要改的那条流程此刻就在仓库里、可以读**。如果没有现成的流程可改,这个任务就不是有界的。问那些真正重要的澄清问题,**在对话里**给出一份简短设计(几句话到几个短段落),然后**停下**。只有在你的人类伙伴对这份设计说"可以"之后,实现才开始——有界任务的批准和架构级任务的批准是同样硬的关卡。不写规格文件,不写实现计划文档。 - **架构级(Architectural)** — 新项目、新子系统,以及会重构组件之间关系、或改动他人依赖的接口的改动。走完整流程:提问、方案对比、分节设计、书面规格,然后交给 writing-plans 技能。 在两条路径之间拿不准时,选更重的那条。这个棘轮只朝一个方向转:任务进行中发现隐藏的复杂度,就**升级**路径——停下来、说明情况、升上去。任何情况下都不在任务中途降级。 ## 反模式:"这个太简单了,不需要批准" 每条路径的终点都是你的人类伙伴在实现之前批准你的意图。一个待办事项列表、一个单函数工具、一个配置变更——设计可以只是对话里的两句话,但你**必须**把它展示出来并获得批准。"简单"的任务恰恰是未经检验的假设造成最多浪费的地方。随简单程度缩放的是**产出物**,永远不是批准。 ## 危险信号 | 心里的想法 | 实际情况 | |---------|---------| | "这个太简单了,不需要设计" | 简单意味着简短的设计,不是没有设计。对话里两句话,然后获得批准。 | | "我就说它是有界的,跳过规格文档" | 为了少干活而去够一个标签,这本身就是"拿不准"——选更重的那条路径。 | | "它是有界的,设计也很显然——我一边让他们读一边开工" | 关卡是**批准**,不是设计的长度。展示完就停,直到听见"可以"。 | | "这类应用我很熟,所以它是有界的" | 有界衡量的是**仓库**,不是你的熟悉程度。新项目没有现成的流程可改——那是架构级。 | | "探路跑通了,那这些代码就留着吧" | 探路的产出是一个答案。要留下代码是一个**新的需求**——给它重新分类。 | | "范围是变大了,但我快做完了,不用重新分类" | 隐藏的复杂度会在任务中途升级路径。停下来,说明情况。 | | "他们批准了探路,那后续改动也算批准了" | 每个任务有自己的分类,也有自己的批准。 | ## 检查清单 先分类,宣布路径,然后为你所在路径上的每个条目创建任务,并按顺序完成。 **探路(Spike):** 1. **探索项目上下文** — 够用来框定这次试探即可 2. **展示问题 + 试探计划** — 2-3 句话 3. **获得批准** — 一个点头就够 4. **动手调查** — 用不牺牲正确性的最低成本 5. **汇报发现** — 以建议的形式;搭出来的任何东西都标注为一次性的 **有界(Bounded):** 1. **探索项目上下文** — 检查文件、文档、最近的 commit 2. **提出澄清问题** — 每次一个,只问那些真正重要的 3. **在对话里展示简短设计** — 思路、会动哪些文件、怎么测 4. **获得批准** — **停下**并等待一个明确的"可以";展示完设计顺口就开工,等于跳过了关卡 5. **实现** — 走正常的开发工作流(TDD 同样适用);不写计划文档 **架构级(Architectural):** 1. **探索项目上下文** — 检查文件、文档、最近的 commit 2. **在需要时才提供视觉伴侣** — **不要一上来就提**。第一次遇到"这个问题画出来比说出来更清楚"时,才在那一刻提供(作为独立的一条消息);对方同意后,浏览器标签页会为你打开。如果自始至终没出现视觉问题,就永远不要提。参见下方"视觉伴侣"部分。 3. **提出澄清问题** — 每次一个,了解目的/约束/成功标准 4. **提出 2-3 种方案** — 附带权衡分析和你的推荐 5. **展示设计** — 按复杂度分节展示,每节展示后获得用户批准 6. **编写设计文档** — 保存到 `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md` 并 commit 7. **规格自检** — 快速内联检查占位符、矛盾、模糊性、范围(详见下方) 8. **用户审查书面规格** — 在继续之前请用户审查规格文件 9. **过渡到实现** — 调用 writing-plans 技能创建实现计划 ## 流程图 ```dot digraph brainstorming { "分类:探路 / 有界 / 架构级" [shape=diamond]; "展示问题 + 试探计划(2-3 句)" [shape=box]; "提出澄清问题(有界)" [shape=box]; "在对话里展示简短设计" [shape=box]; "人类伙伴批准?" [shape=diamond]; "动手调查;汇报建议" [shape=doublecircle]; "走正常工作流实现(无计划文档)" [shape=doublecircle]; "探索项目上下文" [shape=box]; "提出澄清问题" [shape=box]; "提出 2-3 种方案" [shape=box]; "分节展示设计" [shape=box]; "用户批准设计?" [shape=diamond]; "编写设计文档" [shape=box]; "规格自检\n(内联修复)" [shape=box]; "用户审查规格?" [shape=diamond]; "调用 writing-plans 技能" [shape=doublecircle]; "发现隐藏复杂度? 升级路径" [shape=box]; "分类:探路 / 有界 / 架构级" -> "展示问题 + 试探计划(2-3 句)" [label="探路"]; "分类:探路 / 有界 / 架构级" -> "提出澄清问题(有界)" [label="有界"]; "分类:探路 / 有界 / 架构级" -> "探索项目上下文" [label="架构级"]; "展示问题 + 试探计划(2-3 句)" -> "人类伙伴批准?"; "提出澄清问题(有界)" -> "在对话里展示简短设计"; "在对话里展示简短设计" -> "人类伙伴批准?"; "人类伙伴批准?" -> "动手调查;汇报建议" [label="探路:是"]; "人类伙伴批准?" -> "走正常工作流实现(无计划文档)" [label="有界:是"]; "发现隐藏复杂度? 升级路径" -> "分类:探路 / 有界 / 架构级"; "探索项目上下文" -> "提出澄清问题"; "提出澄清问题" -> "提出 2-3 种方案"; "提出 2-3 种方案" -> "分节展示设计"; "分节展示设计" -> "用户批准设计?"; "用户批准设计?" -> "分节展示设计" [label="否,修改"]; "用户批准设计?" -> "编写设计文档" [label="是"]; "编写设计文档" -> "规格自检\n(内联修复)"; "规格自检\n(内联修复)" -> "用户审查规格?"; "用户审查规格?" -> "编写设计文档" [label="要求修改"]; "用户审查规格?" -> "调用 writing-plans 技能" [label="批准"]; } ``` **终止状态跟着路径走。** 架构级:头脑风暴之后你唯一要调用的技能是 writing-plans——绝不调用 frontend-design、mcp-builder 或任何其他实现技能。有界:获得批准之后,直接走正常的开发工作流去实现,不写计划文档。探路:终止状态是一份汇报出去的建议。 ## 流程详述 下面这些小节服务于**有界**和**架构级**两条路径(探路在"展示试探计划、拿到点头"就停了)。从**探索方案**往后都是架构级路径的深度——对有界的工作来说,上下文加几个问题再加一份对话里的简短设计,就是全部流程。 **理解想法:** - 首先查看当前项目状态(文件、文档、最近的 commit) - 在提出详细问题之前,先评估范围:如果需求描述了多个独立子系统(例如"构建一个包含聊天、文件存储、计费和分析的平台"),立即指出这一点。不要花时间用问题去细化一个需要先拆分的项目。 - 如果项目规模过大,单个规格说明无法覆盖,帮助用户分解为子项目:有哪些独立的部分,它们之间有什么关系,应该按什么顺序构建?然后通过正常的设计流程进行第一个子项目的头脑风暴。每个子项目都有自己的规格 → 计划 → 实现周期。 - 对于范围适当的项目,每次提一个问题来完善想法 - 尽量使用选择题,开放式问题也可以 - 每条消息只提一个问题——如果一个主题需要更多探索,拆分成多个问题 - 重点理解:目的、约束、成功标准 **探索方案:** - 提出 2-3 种不同的方案及其权衡 - 以对话的方式展示选项,附上你的推荐和理由 - 先展示你推荐的方案并解释原因 - 严格遵循 YAGNI —— 从每个方案和设计里移除不必要的功能 **展示设计:** - 一旦你认为理解了要构建的内容,就展示设计 - 每个部分的篇幅与其复杂度匹配:简单的几句话,复杂的最多 200-300 字 - 每个部分展示后询问是否正确 - 涵盖:架构、组件、数据流、错误处理、测试 - 随时准备回头澄清不明确的地方 **面向隔离和清晰的设计:** - 将系统拆分为更小的单元,每个单元有一个明确的职责,通过定义良好的接口通信,可以独立理解和测试 - 对于每个单元,你应该能回答:它做什么,如何使用,它依赖什么? - 别人能否不看内部实现就理解一个单元的功能?你能否在不影响调用者的情况下修改内部实现?如果不能,边界需要调整。 - 更小、边界清晰的单元也更便于你工作——你对能一次放入上下文的代码推理得更好,文件越专注你的编辑越可靠。当文件变大时,这通常意味着它承担了过多职责。 **在现有代码库中工作:** - 在提出更改之前先探索现有结构。遵循现有模式。 - 如果现有代码存在影响当前工作的问题(例如文件过大、边界不清、职责纠缠),在设计中包含有针对性的改进——就像一个优秀的开发者在工作中改进经手的代码一样。 - 不要提议无关的重构。专注于服务当前目标的事情。 ## 设计之后(架构级路径) **文档:** - 将验证通过的设计(规格说明)写入 `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md` - (用户对规格位置的偏好优先于此默认值) - 如果可用,使用 elements-of-style:writing-clearly-and-concisely 技能 - 将设计文档 commit 到 git **规格自检:** 编写规格文档后,以全新的视角审视它: 1. **占位符扫描:** 有没有"待定"、"TODO"、未完成的章节或模糊的需求?修复它们。 2. **内部一致性:** 各章节之间有矛盾吗?架构和功能描述匹配吗? 3. **范围检查:** 这是否聚焦到可以用一个实现计划覆盖,还是需要进一步拆分? 4. **模糊性检查:** 有没有需求可以被两种方式理解?如果有,选择一种并明确写出来。 发现问题就直接内联修复。无需重新审查——修好继续推进。 **用户审查关卡:** 规格自检完成后,请用户在继续之前审查书面规格: > "规格已编写并 commit 到 `<path>`。请审查一下,如果在我们开始编写实现计划之前你想做任何修改,请告诉我。" 等待用户回复。如果他们要求修改,做出修改并重新运行规格自检。只有在用户批准后才继续。 **实现:** - 调用 writing-plans 技能创建详细的实现计划 - 不要调用任何其他技能。writing-plans 是下一步。 ## 视觉伴侣 一个基于浏览器的伴侣工具,用于在头脑风暴过程中展示原型、图表和视觉选项。它是一个工具——不是一种模式。接受伴侣意味着它可用于适合视觉呈现的问题;并不意味着每个问题都要通过浏览器。 **提供伴侣(在需要时才提):** **不要一上来就提。** 等到某个问题确实"画出来比说出来更清楚"时再提——要是真正的原型 / 布局 / 图表问题,而不仅仅是话题跟 UI 沾边。第一次出现这种情况时,就在那一刻提供,作为独立的一条消息: > "接下来这部分,我展示给你看可能更容易理解——我可以在讨论过程中,在一个浏览器标签页里做原型、图表和对比。这个功能还比较新,可能会消耗较多 token。要我打开吗?我来帮你打开。" **此提议必须是一条独立的消息。** 只有这条提议——不含澄清问题、内容摘要或任何其他内容。等待用户回复。如果他们接受,用 `--open` 启动服务,浏览器会自动打开到第一屏。如果他们拒绝,继续纯文本进行,并且不要再提,除非他们自己提起。 **逐问题决策:** 即使用户接受了,也要对每个问题单独决定是使用浏览器还是终端。判断标准:**用户看到它是否比读到它更容易理解?** - **使用浏览器** 展示本身就是视觉的内容——原型、线框图、布局对比、架构图、并排视觉设计 - **使用终端** 展示文本内容——需求问题、概念选择、权衡列表、A/B/C/D 文字选项、范围决策 关于 UI 主题的问题不一定是视觉问题。"在这个上下文中个性化是什么意思?"是一个概念问题——使用终端。"哪种向导布局更好?"是一个视觉问题——使用浏览器。 如果他们同意使用伴侣,在继续之前阅读详细指南: `skills/brainstorming/visual-companion.md` -
spec-document-reviewer-prompt.md 1.6 KB
# 规格文档审查员提示模板 调度规格文档审查员子代理时使用此模板。 **用途:** 验证规格是否完整、一致,并为实现计划做好准备。 **调度时机:** 规格文档写入 docs/superpowers/specs/ 之后 ``` Task tool(通用): description: "审查规格文档" prompt: | 你是一名规格文档审查员。验证此规格是否完整并准备好进行计划编写。 **待审查规格:** [SPEC_FILE_PATH] ## 检查内容 | 类别 | 检查要点 | |------|----------| | 完整性 | TODO、占位符、"TBD"、不完整的章节 | | 一致性 | 内部矛盾、相互冲突的需求 | | 清晰度 | 需求模糊到可能导致构建出错误的东西 | | 范围 | 是否足够聚焦以用于单个计划——而非涵盖多个独立子系统 | | YAGNI | 未请求的功能、过度设计 | ## 校准标准 **只标记会在实现计划阶段造成实际问题的事项。** 缺失的章节、矛盾之处、或者模糊到可能被两种不同方式理解的需求—— 这些才是问题。措辞上的小改进、风格偏好、以及"某些章节不如其他章节详细"则不是。 除非存在会导致计划出错的严重缺陷,否则应予以通过。 ## 输出格式 ## 规格审查 **状态:** 通过 | 发现问题 **问题(如有):** - [章节 X]:[具体问题] - [为什么这对计划编写很重要] **建议(仅供参考,不阻止通过):** - [改进建议] ``` **审查员返回:** 状态、问题(如有)、建议 -
visual-companion.md 11.5 KB
# 视觉伴侣指南 基于浏览器的视觉头脑风暴伴侣,用于展示原型、图表和选项。 ## 何时使用 逐问题决定,而非按会话决定。判断标准:**用户看到它是否比读到它更容易理解?** **使用浏览器** 当内容本身是视觉的: - **UI 原型** — 线框图、布局、导航结构、组件设计 - **架构图** — 系统组件、数据流、关系图 - **并排视觉对比** — 对比两种布局、两种配色方案、两种设计方向 - **设计细节打磨** — 当问题涉及外观感受、间距、视觉层次 - **空间关系** — 状态机、流程图、实体关系图 **使用终端** 当内容是文字或表格的: - **需求和范围问题** — "X 是什么意思?"、"哪些功能在范围内?" - **概念性 A/B/C 选择** — 在用文字描述的方案之间做选择 - **权衡列表** — 优缺点、对比表 - **技术决策** — API 设计、数据建模、架构方案选择 - **澄清问题** — 任何回答是文字而非视觉偏好的问题 关于 UI 主题的问题不一定是视觉问题。"你想要什么样的向导?"是概念性的——使用终端。"这些向导布局中哪个感觉对?"是视觉性的——使用浏览器。 ## 工作原理 服务器监视一个目录中的 HTML 文件,将最新的文件提供给浏览器。你写入 HTML 内容,用户在浏览器中看到它,并可以点击选择选项。选择结果被记录到一个 `.events` 文件中,你在下一轮会话中读取它。 **内容片段 vs 完整文档:** 如果你的 HTML 文件以 `<!DOCTYPE` 或 `<html` 开头,服务器会原样提供(仅注入辅助脚本)。否则,服务器会自动将你的内容包裹在框架模板中——添加头部、CSS 主题、选择指示器和所有交互基础设施。**默认写内容片段即可。** 只有当你需要完全控制页面时才写完整文档。 ## 启动会话 ```bash # 启动服务器并持久化(原型保存到项目中) scripts/start-server.sh --project-dir /path/to/project # 返回:{"type":"server-started","port":52341,"url":"http://localhost:52341", # "screen_dir":"/path/to/project/.superpowers/brainstorm/12345-1706000000"} ``` 保存响应中的 `screen_dir`。告诉用户打开该 URL。 **查找连接信息:** 服务器将其启动 JSON 写入 `$SCREEN_DIR/.server-info`。如果你在后台启动了服务器且没有捕获 stdout,读取该文件以获取 URL 和端口。使用 `--project-dir` 时,检查 `<project>/.superpowers/brainstorm/` 获取会话目录。 **注意:** 传入项目根目录作为 `--project-dir`,这样原型会持久化在 `.superpowers/brainstorm/` 中,不会因服务器重启而丢失。不传的话,文件会保存到 `/tmp` 并在清理时被删除。提醒用户将 `.superpowers/` 添加到 `.gitignore`(如果尚未添加)。 **按平台启动服务器:** **Claude Code (macOS / Linux):** ```bash # 默认模式即可——脚本会自动将服务器放到后台 scripts/start-server.sh --project-dir /path/to/project ``` **Claude Code (Windows):** ```bash # Windows 会自动检测并使用前台模式,这会阻塞工具调用。 # 在 Bash 工具调用上设置 run_in_background: true, # 让服务器在会话轮次之间存活。 scripts/start-server.sh --project-dir /path/to/project ``` 通过 Bash 工具调用时,设置 `run_in_background: true`。然后在下一轮读取 `$SCREEN_DIR/.server-info` 获取 URL 和端口。 **Codex:** ```bash # Codex 会回收后台进程。脚本会自动检测 CODEX_CI 并 # 切换到前台模式。正常运行即可——不需要额外标志。 scripts/start-server.sh --project-dir /path/to/project ``` **Gemini CLI:** ```bash # 使用 --foreground 并在 shell 工具调用上设置 is_background: true, # 让进程在轮次之间存活 scripts/start-server.sh --project-dir /path/to/project --foreground ``` **Copilot CLI:** ```bash # 用 Copilot CLI 的非阻塞 / 后台 shell 机制启动,让服务器能跨会话轮次存活。 # 保留 --foreground —— 由 harness 而不是脚本来负责放到后台。 # 启动器是 .sh,所以要通过 bash 调用(Windows 上用 Git Bash 的 bash.exe, # 从 PowerShell 工具里调)。 bash scripts/start-server.sh --project-dir /path/to/project --open --foreground ``` **其他环境:** 服务器必须在会话轮次之间持续在后台运行。如果你的环境会回收分离的进程,使用 `--foreground` 并通过平台的后台执行机制启动命令。 如果浏览器无法访问该 URL(在远程/容器化环境中常见),绑定一个非回环主机: ```bash scripts/start-server.sh \ --project-dir /path/to/project \ --host 0.0.0.0 \ --url-host localhost ``` 使用 `--url-host` 控制返回的 URL JSON 中显示的主机名。 ## 工作循环 1. **检查服务器存活**,然后**将 HTML 写入** `screen_dir` 中的新文件: - 每次写入前,检查 `$SCREEN_DIR/.server-info` 是否存在。如果不存在(或 `.server-stopped` 存在),服务器已关闭——在继续之前用 `start-server.sh` 重启。服务器在 30 分钟无活动后会自动退出。 - 使用语义化文件名:`platform.html`、`visual-style.html`、`layout.html` - **绝不复用文件名** — 每个屏幕用一个新文件 - 使用 Write 工具 — **绝不使用 cat/heredoc**(会在终端产生噪音) - 服务器自动提供最新的文件 2. **告诉用户预期内容并结束你的回合:** - 每一步都提醒他们 URL(不仅仅是第一次) - 简要文字说明屏幕上的内容(例如"展示了 3 个首页布局选项") - 请他们在终端中回复:"看一下,告诉我你的想法。如果你愿意,可以点击选择一个选项。" 3. **在你的下一轮** — 用户在终端回复后: - 如果存在 `$SCREEN_DIR/.events`,读取它——其中包含用户的浏览器交互(点击、选择),格式为 JSON 行 - 将终端文字和事件合并以获得完整信息 - 终端消息是主要反馈;`.events` 提供结构化的交互数据 4. **迭代或推进** — 如果反馈要求修改当前屏幕,写入新文件(例如 `layout-v2.html`)。只有当前步骤验证通过后才进入下一个问题。 5. **回到终端时卸载** — 当下一步不需要浏览器时(例如澄清问题、权衡讨论),推送一个等待屏幕以清除过时内容: ```html <!-- 文件名:waiting.html(或 waiting-2.html 等)--> <div style="display:flex;align-items:center;justify-content:center;min-height:60vh"> <p class="subtitle">在终端中继续...</p> </div> ``` 这样可以防止用户盯着一个已经解决的选择,而对话已经继续了。当下一个视觉问题出现时,照常推送新的内容文件。 6. 重复直到完成。 ## 编写内容片段 只写放在页面内部的内容。服务器会自动用框架模板包裹它(头部、主题 CSS、选择指示器和所有交互基础设施)。 **最简示例:** ```html <h2>哪种布局更好?</h2> <p class="subtitle">考虑可读性和视觉层次</p> <div class="options"> <div class="option" data-choice="a" onclick="toggleSelect(this)"> <div class="letter">A</div> <div class="content"> <h3>单栏</h3> <p>简洁、专注的阅读体验</p> </div> </div> <div class="option" data-choice="b" onclick="toggleSelect(this)"> <div class="letter">B</div> <div class="content"> <h3>双栏</h3> <p>侧边栏导航加主内容区</p> </div> </div> </div> ``` 就这些。不需要 `<html>`,不需要 CSS,不需要 `<script>` 标签。服务器会提供这一切。 ## 可用的 CSS 类 框架模板为你的内容提供以下 CSS 类: ### 选项(A/B/C 选择) ```html <div class="options"> <div class="option" data-choice="a" onclick="toggleSelect(this)"> <div class="letter">A</div> <div class="content"> <h3>标题</h3> <p>描述</p> </div> </div> </div> ``` **多选:** 在容器上添加 `data-multiselect` 让用户选择多个选项。每次点击切换选中状态。指示栏显示数量。 ```html <div class="options" data-multiselect> <!-- 相同的选项标记——用户可以选择/取消选择多个 --> </div> ``` ### 卡片(视觉设计) ```html <div class="cards"> <div class="card" data-choice="design1" onclick="toggleSelect(this)"> <div class="card-image"><!-- 原型内容 --></div> <div class="card-body"> <h3>名称</h3> <p>描述</p> </div> </div> </div> ``` ### 原型容器 ```html <div class="mockup"> <div class="mockup-header">预览:仪表盘布局</div> <div class="mockup-body"><!-- 你的原型 HTML --></div> </div> ``` ### 分屏视图(并排) ```html <div class="split"> <div class="mockup"><!-- 左侧 --></div> <div class="mockup"><!-- 右侧 --></div> </div> ``` ### 优缺点 ```html <div class="pros-cons"> <div class="pros"><h4>优点</h4><ul><li>好处</li></ul></div> <div class="cons"><h4>缺点</h4><ul><li>不足</li></ul></div> </div> ``` ### 模拟元素(线框图构建块) ```html <div class="mock-nav">Logo | 首页 | 关于 | 联系我们</div> <div style="display: flex;"> <div class="mock-sidebar">导航</div> <div class="mock-content">主内容区域</div> </div> <button class="mock-button">操作按钮</button> <input class="mock-input" placeholder="输入框"> <div class="placeholder">占位区域</div> ``` ### 排版和区块 - `h2` — 页面标题 - `h3` — 章节标题 - `.subtitle` — 标题下方的辅助文字 - `.section` — 带底部边距的内容块 - `.label` — 小号大写标签文字 ## 浏览器事件格式 当用户在浏览器中点击选项时,交互记录会保存到 `$SCREEN_DIR/.events`(每行一个 JSON 对象)。推送新屏幕时文件会自动清空。 ```jsonl {"type":"click","choice":"a","text":"选项 A - 简单布局","timestamp":1706000101} {"type":"click","choice":"c","text":"选项 C - 复杂网格","timestamp":1706000108} {"type":"click","choice":"b","text":"选项 B - 混合方案","timestamp":1706000115} ``` 完整的事件流展示了用户的探索路径——他们可能在确定之前点击了多个选项。最后一个 `choice` 事件通常是最终选择,但点击模式可以揭示犹豫或值得询问的偏好。 如果 `.events` 不存在,说明用户没有与浏览器交互——仅使用他们的终端文字。 ## 设计技巧 - **保真度匹配问题** — 布局问题用线框图,细节打磨问题用精细设计 - **在每个页面上解释问题** — "哪种布局看起来更专业?"而不仅仅是"选一个" - **推进前先迭代** — 如果反馈修改了当前屏幕,写入新版本 - 每个屏幕最多 **2-4 个选项** - **必要时使用真实内容** — 对于摄影作品集,使用实际图片(Unsplash)。占位内容会掩盖设计问题。 - **保持原型简洁** — 专注于布局和结构,而非像素级精确的设计 ## 文件命名 - 使用语义化名称:`platform.html`、`visual-style.html`、`layout.html` - 绝不复用文件名——每个屏幕必须是新文件 - 迭代版本:添加版本后缀如 `layout-v2.html`、`layout-v3.html` - 服务器按修改时间提供最新文件 ## 清理 ```bash scripts/stop-server.sh $SCREEN_DIR ``` 如果会话使用了 `--project-dir`,原型文件会持久化在 `.superpowers/brainstorm/` 中以供日后参考。只有 `/tmp` 会话会在停止时被删除。 ## 参考 - 框架模板(CSS 参考):`scripts/frame-template.html` - 辅助脚本(客户端):`scripts/helper.js`
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.