Superpowers 源码解读(六):辅助工具与脚本的精妙设计

Superpowers 源码解读(六):辅助工具与脚本的精妙设计

深入解析 Visual Companion WebSocket 服务器、Git Worktrees 集成以及 Graphviz 渲染工具的实现细节

前言

在前五阶段的学习中,我们已经掌握了 Superpowers 的核心架构、技能系统设计、工作流技能、子代理系统和测试系统。第六阶段将目光投向「辅助工具与脚本」------这些看似不起眼,却蕴含精妙设计的小工具。

本阶段揭示一个重要理念:好的工具设计能让复杂系统变得简单可靠


📁 本文学习目录

bash 复制代码
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.md

skills/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 响应:

javascript 复制代码
const WS_MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';

function computeAcceptKey(clientKey) {
  return crypto
    .createHash('sha1')
    .update(clientKey + WS_MAGIC)
    .digest('base64');
}

这个魔法字符串是 RFC 6455 规定的固定值,用于防止跨协议攻击。

帧编码(服务端 → 客户端)
javascript 复制代码
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]);
}
帧解码(客户端 → 服务端)

客户端发送的帧必须进行掩码处理(防止缓存投毒攻击):

javascript 复制代码
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 服务器架构

flowchart TB subgraph HTTP层["HTTP Server"] A[GET / → 返回最新 HTML] B[GET /files/* → 静态文件服务] C[Upgrade → WebSocket 握手] end subgraph 监听层["fs.watch()"] D[监听 .html 文件变化] E[防抖处理 100ms] F[广播 reload 消息] end subgraph 通信层["WebSocket Pool"] G[管理客户端连接] H[接收用户事件] I[写入 .events 文件] end subgraph 生命周期["Lifecycle Manager"] J[30 分钟无活动退出] K[所有者进程检测] L[优雅关闭处理] end A --> D B --> D C --> G D --> E --> F --> G G --> H --> I J --> L K --> L

1.4 消息协议

服务端 → 客户端
消息类型 用途
{ "type": "reload" } 通知浏览器刷新页面
{ "type": "screen-added" } 新屏幕文件创建(日志用)
{ "type": "screen-updated" } 屏幕文件更新(日志用)
客户端 → 服务端
json 复制代码
{ "choice": "option-a", "question": "选择配色方案" }

用户的选择会被追加到 .events 文件供 Agent 读取:

javascript 复制代码
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 生命周期管理

自动退出机制
javascript 复制代码
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 分钟无活动 → 服务器自动退出(释放资源)
进程关系
flowchart TD A[Agent Harness 进程] --> B[start-server.sh 脚本] B --> C[Node.js server.cjs 进程] C -->|跟踪祖父 PID| A style A fill:#e1f5fe style C fill:#fff3e0

为什么是祖父进程?因为 $PPID 是执行脚本的 shell,脚本结束后就消失了。真正的所有者是 Harness。

二、启动与停止脚本:跨平台兼容的艺术

2.1 启动脚本设计

启动脚本 start-server.sh 需要处理多种复杂场景:后台运行、前台运行、跨平台兼容、进程存活验证等。

命令行参数
bash 复制代码
./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 环境自适应

不同环境对后台进程的处理方式不同,脚本需要自动适应:

bash 复制代码
# 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" ;;
esac
if [[ -n "${MSYSTEM:-}" ]]; then
  FOREGROUND="true"
fi

这种「渐进式降级」策略确保在各种环境中都能正常工作。

2.3 会话目录设计

每次启动都会创建独立的会话目录,避免冲突:

bash 复制代码
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

目录结构:

bash 复制代码
/tmp/brainstorm-12345-1711440000/
├── .server-info     # 服务器信息 JSON
├── .server.pid      # 进程 ID
├── .server.log      # 日志输出
├── .events          # 用户事件记录
├── screen-1.html    # 屏幕文件(Agent 写入)
└── screen-2.html

2.4 启动流程详解

css 复制代码
mermaid
flowchart TD
    A[解析命令行参数] --> B[生成唯一会话 ID]
    B --> C[创建会话目录]
    C --> D[杀死旧服务器]
    D --> E{检测环境}
    E -->|Windows/Codex CI| F[前台模式]
    E -->|其他| G[后台模式]
    
    F --> H[直接运行 node server.cjs]
    G --> I[nohup + disown 后台运行]
    
    H --> J[等待 server-started 消息]
    I --> J
    
    J --> K{5 秒内收到消息?}
    K -->|否| L[报错退出]
    K -->|是| M[验证进程存活]
    M --> N{存活?}
    N -->|否| O[报错:进程被杀死]
    N -->|是| P[输出连接信息]   

2.5 启动验证机制

脚本不会简单地启动进程就退出,而是等待服务器真正就绪:

bash 复制代码
# 等待 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.1
done

这种双重验证(消息 + 存活)确保返回的连接信息是真正可用的。

2.6 停止脚本设计

停止脚本 stop-server.sh 实现了优雅关闭流程:

bash 复制代码
# 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.1
done

# 3. 如果仍存活,强制终止(SIGKILL)
if kill -0 "$pid" 2>/dev/null; then
  kill -9 "$pid" 2>/dev/null || true
  sleep 0.1
fi

# 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 的工作流中,经常需要同时处理多个任务:

flowchart LR A[主分支: 运行测试] --> B[功能分支 A: 开发新功能] A --> C[功能分支 B: 修复 bug] style A fill:#e8f5e9 style B fill:#fff3e0 style C fill:#e3f2fd

传统的 git checkout 会切换整个工作区,但 git worktree 可以让多个分支同时存在:

bash 复制代码
# 创建独立工作区
git worktree add .worktrees/feature-auth -b feature/auth

# 结果
project/
├── (主工作区,在 main 分支)
└── .worktrees/
    └── feature-auth/  (独立工作区,在 feature/auth 分支)

3.2 目录选择优先级

Superpowers 设计了清晰的优先级规则:

flowchart TD A[检查现有目录] --> B{.worktrees/ 存在?} B -->|是| C[使用 .worktrees/] B -->|否| D{worktrees/ 存在?} D -->|是| E[使用 worktrees/] D -->|否| F[检查 CLAUDE.md 配置] F --> G{有配置?} G -->|是| H[使用配置路径] G -->|否| I[询问用户]

实现代码:

bash 复制代码
# 优先级 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 目录被忽略

bash 复制代码
# 检查目录是否被忽略
git check-ignore -q .worktrees 2>/dev/null || git check-ignore -q worktrees 2>/dev/null

如果未被忽略:

bash 复制代码
# 按照规则"立即修复损坏的东西"
echo ".worktrees/" >> .gitignore
git add .gitignore
git commit -m "Add .worktrees to gitignore"
# 然后继续创建 worktree

为什么这很重要?

如果 .worktrees/ 未被忽略,会发生:

  • git status 显示 worktree 内的所有文件
  • 可能意外提交 worktree 内容
  • 污染仓库,造成团队协作混乱

3.4 完整创建流程

flowchart TD A[检测项目名] --> B[检查目录是否被忽略] B --> C{已忽略?} C -->|否| D[添加到 .gitignore 并提交] C -->|是| E[创建 worktree] D --> E E --> F[进入工作区] F --> G[自动检测项目类型] G --> H[运行依赖安装] H --> I[运行基线测试] I --> J{测试通过?} J -->|是| K[报告就绪] J -->|否| L[报告失败,询问用户]

自动项目检测:

bash 复制代码
# Node.js
if [ -f package.json ]; then npm install; fi

# Rust
if [ -f Cargo.toml ]; then cargo build; fi

# Python
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
if [ -f pyproject.toml ]; then poetry install; fi

# Go
if [ -f go.mod ]; then go mod download; fi

3.5 与其他技能的集成

Git Worktrees 是「基础设施」技能,被多个核心技能调用:

flowchart LR A[brainstorming] -->|设计确认后| B[using-git-worktrees] B --> C[subagent-driven-development] C --> D[finishing-a-development-branch] D -->|清理| B
调用者 时机
brainstorming Phase 4:设计批准后,实施前
subagent-driven-development 执行任务前
executing-plans 执行计划前

四、Graphviz 渲染工具:从代码到可视化

4.1 为什么需要渲染工具?

Superpowers 的技能文档中大量使用 Graphviz DOT 语言描述流程图:

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 代码块
javascript 复制代码
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
javascript 复制代码
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:

javascript 复制代码
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 使用方式

bash 复制代码
# 单独渲染每个流程图
./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 内置模块实现:

javascript 复制代码
const crypto = require('crypto');  // SHA1 哈希
const http = require('http');      // HTTP 服务器
const fs = require('fs');          // 文件系统
const path = require('path');      // 路径处理

为什么不用 ws、socket.io 等成熟库?

原因 说明
减少依赖 用户不需要安装额外 npm 包
完全控制 可以精确处理边缘情况
学习价值 理解协议本质
安全性 减少供应链攻击面

5.2 跨平台兼容策略

flowchart TD A[检测运行环境] --> B{Codex CI?} B -->|是| C[自动前台运行] B -->|否| D{Windows/MSYS2?} D -->|是| C D -->|否| E{后台运行} E --> F{进程存活?} F -->|是| G[正常工作] F -->|否| H[提示用户使用 --foreground] style C fill:#fff3e0 style G fill:#e8f5e9 style H fill:#ffebee
平台 问题 解决方案
macOS/Linux 默认行为 后台运行(nohup + disown)
Windows/MSYS2 后台进程会被回收 自动前台运行
Codex CI 会杀死后台进程 检测环境变量,自动前台
远程服务器 需要外部访问 --host 0.0.0.0 --url-host <公网IP>

渐进式降级原则:

复制代码
最优方案(后台运行)→ 不支持 → 次优方案(前台运行)→ 不支持 → 报错提示

5.3 安全优先设计

Git Worktrees 技能展示了一个重要原则:安全验证不可跳过

flowchart TD A[准备创建 worktree] --> B[检查 .gitignore] B --> C{目录已被忽略?} C -->|是| D[创建 worktree] C -->|否| E[强制添加到 .gitignore] E --> F[提交更改] F --> D style C fill:#fff3e0 style E fill:#ffebee style D fill:#e8f5e9

这种设计防止了:

  • 意外提交 worktree 内容
  • 污染仓库
  • 团队协作混乱

5.4 自动化优先

所有脚本都尽可能减少用户手动操作:

bash 复制代码
# 自动检测项目类型并安装依赖
[ -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,单独/合并渲染

设计原则总结

原则 实践
零依赖 减少安装负担,提高可靠性
跨平台兼容 渐进式降级,自动适应环境
安全优先 强制验证,防止用户犯错
自动化 工具自己「弄清楚」,减少用户负担
生命周期管理 防止孤儿进程,资源自动清理

参考资料


本文是 Superpowers 源码学习系列第六阶段笔记。下一阶段将深入跨平台支持,了解 superpowers 如何适配 Codex、OpenCode、Gemini 等多个 AI 编码平台。

相关推荐
超梦dasgg12 小时前
Spring AI 智能航空助手项目实战
java·人工智能·后端·spring·ai编程
码点滴12 小时前
从“失忆症“到“数智分身“:Hermes Agent 如何重塑你的 AI 交互体验?
人工智能·架构·prompt·ai编程·hermes
常威正在打来福12 小时前
【实战】Claude Code + Superpowers:从零开发一个完整项目
ai编程·claude
大田稻谷13 小时前
从认知科学到代码实现——为 AI Agent 构建记忆系统
ai编程
库洛西鲁13 小时前
Claude Code 怎么配置和使用?从安装到实战踩坑全记录(2026)
ai编程·claude
小虎AI生活13 小时前
一人公司获客神器,WorkBuddy + 即梦 Seedance 2.0,这个组合真的绝了
ai编程
程序员老刘14 小时前
当全网都在喊“程序员要被AI取代了”,Flutter给了另一种答案
flutter·ai编程·客户端
最强小杰14 小时前
OpenClaw 安装踩坑全记录:npm 和 Bun 两种方式的原理差异与实战配置(2026)
ai编程·mcp
tanis_314 小时前
MinerU MCP Server 部署与工作流实战:Claude Desktop / Cursor / Cline 接入指南
ai编程·mcp
时空系15 小时前
第6篇:多维数据盒——管理大量数据 python中文编程
开发语言·python·ai编程