Superpowers 源码解读(六):辅助工具与脚本的精妙设计
深入解析 Visual Companion WebSocket 服务器、Git Worktrees 集成以及 Graphviz 渲染工具的实现细节
前言
在前五阶段的学*中,我们已经掌握了 Superpowers 的核心架构、技能系统设计、工作流技能、子代理系统和测试系统。第六阶段将目光投向「辅助工具与脚本」——这些看似不起眼,却蕴含精妙设计的小工具。
本阶段揭示一个重要理念:好的工具设计能让复杂系统变得简单可靠。
本文学*目录
skills/brainstorming/scripts/ # Visual Companion 工具├── server.cjs # WebSocket 服务器├── start-server.sh # 启动脚本├── stop-server.sh # 停止脚本├── frame-template.html # 页面框架模板└── helper.js # 客户端辅助脚本skills/using-git-worktrees/ # Git Worktrees 集成└── SKILL.mdskills/writing-skills/└── render-graphs.js # Graphviz 渲染工具
一、Visual Companion:零依赖 WebSocket 服务器
1.1 什么是 Visual Companion?
在 brainstorming(头脑风暴)技能中,Agent 需要与用户进行视觉交互——展示 UI 原型图、流程图,让用户点击选择偏好。Visual Companion 是一个实时通信工具:
Agent 写 HTML 文件 → 浏览器自动刷新显示用户在浏览器点击 → Agent 实时收到选择结果核心是一个 零依赖的 WebSocket 服务器,只用 Node.js 内置模块实现完整的 RFC 6455 协议。
1.2 WebSocket 协议实现(RFC 6455)
握手阶段
客户端发起 WebSocket 连接时,发送 HTTP Upgrade 请求,包含 Sec-WebSocket-Key 头。服务端计算 Sec-WebSocket-Accept 响应:
const WS_MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';function computeAcceptKey(clientKey) { return crypto .createHash('sha1') .update(clientKey + WS_MAGIC) .digest('base64');}
这个魔法字符串是 RFC 6455 规定的固定值,用于防止跨协议攻击。
帧编码(服务端 → 客户端)
function encodeFrame(opcode, payload) { const fin = 0x80; // 最终帧标志 const len = payload.length; let header; if (len < 126) { // 短格式:2 字节头部 header = Buffer.alloc(2); header[0] = fin | opcode; header[1] = len; } else if (len < 65536) { // 中等格式:4 字节头部,16 位扩展长度 header = Buffer.alloc(4); header[0] = fin | opcode; header[1] = 126; header.writeUInt16BE(len, 2); } else { // 长格式:10 字节头部,64 位扩展长度 header = Buffer.alloc(10); header[0] = fin | opcode; header[1] = 127; header.writeBigUInt64BE(BigInt(len), 2); } return Buffer.concat([header, payload]);}
帧解码(客户端 → 服务端)
客户端发送的帧必须进行掩码处理(防止缓存投毒攻击):
function decodeFrame(buffer) { const opcode = buffer[0] & 0x0F; const masked = (buffer[1] & 0x80) !== 0; let payloadLen = buffer[1] & 0x7F; if (!masked) throw new Error('Client frames must be masked'); // 用 XOR 解码数据 const mask = buffer.slice(maskOffset, dataOffset); const data = Buffer.alloc(payloadLen); for (let i = 0; i < payloadLen; i++) { data[i] = buffer[dataOffset + i] ^ mask[i % 4]; } return { opcode, payload: data, bytesConsumed: totalLen };}
操作码定义
操作码 | 常量 | 用途 |
0x01 | TEXT | 文本消息 |
0x08 | CLOSE | 关闭连接 |
0x09 | PING | 心跳检测 |
0x0A | PONG | 心跳响应 |
1.3 服务器架构

1.4 消息协议
服务端 → 客户端
消息类型 | 用途 |
{ "type": "reload" } | 通知浏览器刷新页面 |
{ "type": "screen-added" } | 新屏幕文件创建(日志用) |
{ "type": "screen-updated" } | 屏幕文件更新(日志用) |
客户端 → 服务端
{ "choice": "option-a", "question": "选择配色方案" }
用户的选择会被追加到 .events 文件供 Agent 读取:
function handleMessage(text) { const event = JSON.parse(text); touchActivity(); // 更新活动时间 console.log(JSON.stringify({ source: 'user-event', ...event })); if (event.choice) { const eventsFile = path.join(SCREEN_DIR, '.events'); fs.appendFileSync(eventsFile, JSON.stringify(event) + '\n'); }}
1.5 生命周期管理
自动退出机制
const IDLE_TIMEOUT_MS = 30 * 60 * 1000; // 30 分钟无活动function ownerAlive() { if (!OWNER_PID) return true; try { process.kill(OWNER_PID, 0); // 检查进程是否存活 return true; } catch (e) { return false; }}// 每 60 秒检查一次setInterval(() => { if (!ownerAlive()) shutdown('owner process exited'); else if (Date.now() - lastActivity > IDLE_TIMEOUT_MS) shutdown('idle timeout');}, 60 * 1000);
这种设计确保:
所有者进程死亡 → 服务器自动退出(不会变成孤儿)30 分钟无活动 → 服务器自动退出(释放资源)进程关系

为什么是祖父进程?因为 $PPID 是执行脚本的 shell,脚本结束后就消失了。真正的所有者是 Harness。
二、启动与停止脚本:跨平台兼容的艺术
2.1 启动脚本设计
启动脚本 start-server.sh 需要处理多种复杂场景:后台运行、前台运行、跨平台兼容、进程存活验证等。
命令行参数
./start-server.sh \ --project-dir <path> # 持久化目录(而非 /tmp) --host <bind-host> # 绑定地址(默认 127.0.0.1) --url-host <host> # URL 显示主机名 --foreground # 前台运行 --background # 强制后台运行
典型使用场景:
场景 | 参数选择 |
本地开发(默认) | 无参数,后台运行 |
Codex CI 环境 | 自动前台(会被检测) |
Windows/MSYS2 | 自动前台(后台不稳定) |
远程服务器 | --host 0.0.0.0 |
2.2 环境自适应
不同环境对后台进程的处理方式不同,脚本需要自动适应:
# Codex CI 环境会杀死后台进程if [[ -n "${CODEX_CI:-}" && "$FOREGROUND" != "true" && "$FORCE_BACKGROUND" != "true" ]]; then FOREGROUND="true"fi# Windows/MSYS2 的后台进程会被回收case "${OSTYPE:-}" in msys*|cygwin*|mingw*) FOREGROUND="true" ;;esacif [[ -n "${MSYSTEM:-}" ]]; then FOREGROUND="true"fi
这种「渐进式降级」策略确保在各种环境中都能正常工作。
2.3 会话目录设计
每次启动都会创建独立的会话目录,避免冲突:
SESSION_ID="$-$(date +%s)"if [[ -n "$PROJECT_DIR" ]]; then # 持久化目录 SCREEN_DIR="${PROJECT_DIR}/.superpowers/brainstorm/${SESSION_ID}"else # 临时目录 SCREEN_DIR="/tmp/brainstorm-${SESSION_ID}"fi
目录结构:
/tmp/brainstorm-12345-1711440000/├── .server-info # 服务器信息 JSON├── .server.pid # 进程 ID├── .server.log # 日志输出├── .events # 用户事件记录├── screen-1.html # 屏幕文件(Agent 写入)└── screen-2.html
2.4 启动流程详解

2.5 启动验证机制
脚本不会简单地启动进程就退出,而是等待服务器真正就绪:
# 等待 server-started 消息(最多 5 秒)for i in {1..50}; do if grep -q "server-started" "$LOG_FILE" 2>/dev/null; then # 额外验证:进程存活检查(防止被进程收割器杀死) 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 with --foreground"}' exit 1 fi grep "server-started" "$LOG_FILE" | head -1 exit 0 fi sleep 0.1done
这种双重验证(消息 + 存活)确保返回的连接信息是真正可用的。
2.6 停止脚本设计
停止脚本 stop-server.sh 实现了优雅关闭流程:
# 1. 尝试优雅关闭(SIGTERM)kill "$pid" 2>/dev/null || true# 2. 等待最多 2 秒for i in {1..20}; do if ! kill -0 "$pid" 2>/dev/null; then break fi sleep 0.1done# 3. 如果仍存活,强制终止(SIGKILL)if kill -0 "$pid" 2>/dev/null; then kill -9 "$pid" 2>/dev/null || true sleep 0.1fi# 4. 清理文件rm -f "$PID_FILE" "${SCREEN_DIR}/.server.log"# 5. 只删除临时目录,保留持久化目录if [[ "$SCREEN_DIR" == /tmp/* ]]; then rm -rf "$SCREEN_DIR"fi
关键设计决策:
目录类型 | 停止后处理 |
/tmp/brainstorm-* | 完全删除 |
.superpowers/brainstorm/ | 保留(供后续查看 mockup) |
这体现了「用户数据优先」的设计理念。
三、Git Worktrees:安全的并行开发隔离
3.1 为什么需要 Worktrees?
在 Superpowers 的工作流中,经常需要同时处理多个任务:

传统的 git checkout 会切换整个工作区,但 git worktree 可以让多个分支同时存在:
# 创建独立工作区git worktree add .worktrees/feature-auth -b feature/auth# 结果project/├── (主工作区,在 main 分支)└── .worktrees/ └── feature-auth/ (独立工作区,在 feature/auth 分支)
3.2 目录选择优先级
Superpowers 设计了清晰的优先级规则:

实现代码:
# 优先级 1:检查现有目录ls -d .worktrees 2>/dev/null # 首选(隐藏目录)ls -d worktrees 2>/dev/null # 备选# 优先级 2:检查配置文件grep -i "worktree.*director" CLAUDE.md 2>/dev/null# 优先级 3:询问用户echo "No worktree directory found. Where should I create worktrees?"echo "1. .worktrees/ (project-local, hidden)"echo "2. ~/.config/superpowers/worktrees/<project-name>/ (global)"
3.3 安全验证:.gitignore 检查
这是最关键的安全措施——必须确保 worktree 目录被忽略:
# 检查目录是否被忽略git check-ignore -q .worktrees 2>/dev/null || git check-ignore -q worktrees 2>/dev/null
如果未被忽略:
# 按照规则"立即修复损坏的东西"echo ".worktrees/" >> .gitignoregit add .gitignoregit commit -m "Add .worktrees to gitignore"# 然后继续创建 worktree
为什么这很重要?
如果 .worktrees/ 未被忽略,会发生:
git status 显示 worktree 内的所有文件可能意外提交 worktree 内容污染仓库,造成团队协作混乱3.4 完整创建流程

自动项目检测:
# Node.jsif [ -f package.json ]; then npm install; fi# Rustif [ -f Cargo.toml ]; then cargo build; fi# Pythonif [ -f requirements.txt ]; then pip install -r requirements.txt; fiif [ -f pyproject.toml ]; then poetry install; fi# Goif [ -f go.mod ]; then go mod download; fi
3.5 与其他技能的集成
Git Worktrees 是「基础设施」技能,被多个核心技能调用:

调用者 | 时机 |
brainstorming | Phase 4:设计批准后,实施前 |
subagent-driven-development | 执行任务前 |
executing-plans | 执行计划前 |
四、Graphviz 渲染工具:从代码到可视化
4.1 为什么需要渲染工具?
Superpowers 的技能文档中大量使用 Graphviz DOT 语言描述流程图:
digraph tdd_flow { rankdir=TB; start [shape=circle]; start -> "Write Test"; "Write Test" -> "Run Test" -> "Failing?" -> "Write Code";}
但这些 DOT 代码块在 Markdown 中只是文本,用户无法直观看到流程图。render-graphs.js 工具解决了这个问题。
4.2 工具实现原理
提取 DOT 代码块
function extractDotBlocks(markdown) { const blocks = []; const regex = /```dot\n([\s\S]*?)```/g; let match; while ((match = regex.exec(markdown)) !== null) { const content = match[1].trim(); // 提取 digraph 名称 const nameMatch = content.match(/digraph\s+(\w+)/); const name = nameMatch ? nameMatch[1] : `graph_${blocks.length + 1}`; blocks.push({ name, content }); } return blocks;}
调用系统 Graphviz
function renderToSvg(dotContent) { try { return execSync('dot -Tsvg', { input: dotContent, encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 // 10MB 缓冲区 }); } catch (err) { console.error('Error running dot:', err.message); return null; }}
4.3 合并模式
一个技能可能有多个流程图,--combine 参数可以将它们合并为一个 SVG:
function combineGraphs(blocks, skillName) { const bodies = blocks.map((block, i) => { const body = extractGraphBody(block.content); // 包装为 cluster 子图 return ` subgraph cluster_${i} { label="${block.name}"; ${body.split('\n').map(line => ' ' + line).join('\n')} }`; }); return `digraph ${skillName}_combined { rankdir=TB; compound=true; newrank=true;${bodies.join('\n\n')}}`;}
4.4 使用方式
# 单独渲染每个流程图./render-graphs.js ../subagent-driven-development# 输出# Found 3 diagram(s) in subagent-driven-development/SKILL.md# Rendered: main_flow.svg# Rendered: review_cycle.svg# Rendered: error_handling.svg# 合并渲染./render-graphs.js ../subagent-driven-development --combine# 输出到 diagrams/ 目录
五、设计洞察与最佳实践
5.1 零依赖哲学
Superpowers 的 WebSocket 服务器完全使用 Node.js 内置模块实现:
const crypto = require('crypto'); // SHA1 哈希const http = require('http'); // HTTP 服务器const fs = require('fs'); // 文件系统const path = require('path'); // 路径处理
为什么不用 ws、socket.io 等成熟库?
原因 | 说明 |
减少依赖 | 用户不需要安装额外 npm 包 |
完全控制 | 可以精确处理边缘情况 |
学*价值 | 理解协议本质 |
安全性 | 减少供应链攻击面 |
5.2 跨平台兼容策略

平台 | 问题 | 解决方案 |
macOS/Linux | 默认行为 | 后台运行(nohup + disown) |
Windows/MSYS2 | 后台进程会被回收 | 自动前台运行 |
Codex CI | 会杀死后台进程 | 检测环境变量,自动前台 |
远程服务器 | 需要外部访问 | --host 0.0.0.0 --url-host <公网IP> |
渐进式降级原则:
最优方案(后台运行)→ 不支持 → 次优方案(前台运行)→ 不支持 → 报错提示
5.3 安全优先设计
Git Worktrees 技能展示了一个重要原则:安全验证不可跳过

这种设计防止了:
意外提交 worktree 内容污染仓库团队协作混乱5.4 自动化优先
所有脚本都尽可能减少用户手动操作:
# 自动检测项目类型并安装依赖[ -f package.json ] && npm install[ -f Cargo.toml ] && cargo build[ -f go.mod ] && go mod download# 自动运行基线测试npm test || cargo test || pytest || go test ./...
设计理念:让工具自己「弄清楚」该做什么,而不是让用户告诉它。
5.5 生命周期管理
机制 | 目的 | 实现 |
30 分钟无活动退出 | 防止孤儿进程占用资源 | setInterval 检查 lastActivity |
所有者进程检测 | Agent 退出时服务器也退出 | process.kill(OWNER_PID, 0) |
优雅关闭 + 强制终止 | 确保服务器能停止 | SIGTERM → 等待 → SIGKILL |
持久化目录保留 | 用户数据不丢失 | 只删除 /tmp/* 目录 |
六、总结
第六阶段的学*揭示了 Superpowers 项目中「小工具,大智慧」的设计哲学。
核心收获
工具 | 关键设计点 |
WebSocket 服务器 | 零依赖实现完整 RFC 6455,生命周期自动管理 |
启动/停止脚本 | 跨平台自适应,双重启动验证,优雅关闭流程 |
Git Worktrees | 目录选择优先级,强制 .gitignore 检查,自动项目检测 |
Graphviz 渲染 | 从 Markdown 提取 DOT,单独/合并渲染 |
设计原则总结
原则 | 实践 |
零依赖 | 减少安装负担,提高可靠性 |
跨平台兼容 | 渐进式降级,自动适应环境 |
安全优先 | 强制验证,防止用户犯错 |
自动化 | 工具自己「弄清楚」,减少用户负担 |
生命周期管理 | 防止孤儿进程,资源自动清理 |
参考资料
- [RFC 6455: The WebSocket Protocol](https://datatracker.ietf.org/doc/html/rfc6455)
- [Git Worktrees 官方文档](
https://git-scm.com/docs/git-worktree)
- [Graphviz DOT 语言](
https://graphviz.org/doc/info/lang.html)
- [Superpowers 项目](
https://github.com/jessevogt/superpowers)
- [作者博客](
https://blog.fsck.com/2025/10/09/superpowers/)
本文是 Superpowers 源码学*系列第六阶段笔记。下一阶段将深入跨平台支持,了解 superpowers 如何适配 Codex、OpenCode、Gemini 等多个 AI 编码平台。
版权声明:本文转载于今日头条,版权归作者所有,如果侵权,请联系本站编辑删除
