Claude Cursor opencode Skill

brainstorming

在任何创造性工作之前必须使用此技能——创建功能、构建组件、添加功能或修改行为。在实现之前先探索用户意图、需求和设计。

LLM Mart · 0 points · 12 views 48 listing impressions 0 install-command copies

#planning #design

Virus-scanned Reviewed automatically before listing.

Full trust report

Download jnmetacode-superpowers-zh-skills_brainstorming-9b30f15.zip · 30 KB
Part of jnmetacode/superpowers-zh — 20 skills

Install

skills CLI npx skills add https://github.com/jnMetaCode/superpowers-zh/tree/main/skills/brainstorming
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install jnmetacode-superpowers-zh@llmmart
Git 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):

  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 技能创建实现计划

流程图

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

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.

No comments yet.

Reviews (0)

No reviews yet.

Related