From 80bc1006251764ef8fb9153f8a7d5c63a7e8baa3 Mon Sep 17 00:00:00 2001 From: jiangnan <1394485448@qq.com> Date: Fri, 20 Mar 2026 11:27:04 +0800 Subject: [PATCH] =?UTF-8?q?superpowers=20=E4=B8=AD=E6=96=87=E7=A4=BE?= =?UTF-8?q?=E5=8C=BA=E7=89=88=EF=BC=9A14=20=E4=B8=AA=E7=BF=BB=E8=AF=91=20+?= =?UTF-8?q?=205=20=E4=B8=AA=E4=B8=AD=E5=9B=BD=E7=89=B9=E8=89=B2=E5=8E=9F?= =?UTF-8?q?=E5=88=9B=20skills?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 基于 obra/superpowers (MIT) 完整汉化,新增中国开发者专属 skills: - 中文代码审查:适配国内团队沟通文化 - 中文 Git 工作流:支持 Gitee/Coding/极狐 GitLab - 中文技术文档:中英混排规范、排版标准 - 中文提交规范:Conventional Commits 中文适配 + commitlint 配置 - MCP 服务器构建:生产级 MCP 工具开发方法论 支持工具:CC / Cursor / Codex CLI / Gemini 安装:npx superpowers-zh --- .claude-plugin/marketplace.json | 20 + .claude-plugin/plugin.json | 13 + .codex/INSTALL.md | 67 + .cursor-plugin/plugin.json | 18 + .gitattributes | 18 + .github/ISSUE_TEMPLATE/bug_report.md | 48 + .github/ISSUE_TEMPLATE/config.yml | 5 + .github/ISSUE_TEMPLATE/feature_request.md | 33 + .github/ISSUE_TEMPLATE/platform_support.md | 22 + .github/PULL_REQUEST_TEMPLATE.md | 78 ++ .gitignore | 7 + .opencode/INSTALL.md | 83 ++ .opencode/plugins/superpowers.js | 107 ++ GEMINI.md | 2 + LICENSE | 21 + README.md | 151 +++ agents/code-reviewer.md | 48 + bin/superpowers-zh.js | 85 ++ commands/brainstorm.md | 5 + commands/execute-plan.md | 5 + commands/write-plan.md | 5 + docs/README.codex.md | 91 ++ docs/README.opencode.md | 108 ++ gemini-extension.json | 6 + hooks/hooks-cursor.json | 10 + hooks/hooks.json | 16 + hooks/run-hook.cmd | 46 + hooks/session-start | 57 + package.json | 46 + skills/brainstorming/SKILL.md | 164 +++ .../brainstorming/scripts/frame-template.html | 214 +++ skills/brainstorming/scripts/helper.js | 88 ++ skills/brainstorming/scripts/server.cjs | 338 +++++ skills/brainstorming/scripts/start-server.sh | 153 +++ skills/brainstorming/scripts/stop-server.sh | 55 + .../spec-document-reviewer-prompt.md | 48 + skills/brainstorming/visual-companion.md | 286 ++++ skills/chinese-code-review/SKILL.md | 277 ++++ skills/chinese-commit-conventions/SKILL.md | 364 ++++++ skills/chinese-documentation/SKILL.md | 448 +++++++ skills/chinese-git-workflow/SKILL.md | 510 ++++++++ skills/dispatching-parallel-agents/SKILL.md | 182 +++ skills/executing-plans/SKILL.md | 175 +++ .../finishing-a-development-branch/SKILL.md | 200 +++ skills/mcp-builder/SKILL.md | 255 ++++ skills/receiving-code-review/SKILL.md | 213 +++ skills/requesting-code-review/SKILL.md | 105 ++ .../requesting-code-review/code-reviewer.md | 146 +++ skills/subagent-driven-development/SKILL.md | 277 ++++ .../code-quality-reviewer-prompt.md | 26 + .../implementer-prompt.md | 113 ++ .../spec-reviewer-prompt.md | 61 + skills/systematic-debugging/CREATION-LOG.md | 119 ++ skills/systematic-debugging/SKILL.md | 296 +++++ .../condition-based-waiting-example.ts | 158 +++ .../condition-based-waiting.md | 115 ++ .../systematic-debugging/defense-in-depth.md | 122 ++ skills/systematic-debugging/find-polluter.sh | 63 + .../root-cause-tracing.md | 169 +++ skills/systematic-debugging/test-academic.md | 14 + .../systematic-debugging/test-pressure-1.md | 58 + .../systematic-debugging/test-pressure-2.md | 68 + .../systematic-debugging/test-pressure-3.md | 69 + skills/test-driven-development/SKILL.md | 371 ++++++ .../testing-anti-patterns.md | 299 +++++ skills/using-git-worktrees/SKILL.md | 218 ++++ skills/using-superpowers/SKILL.md | 134 ++ .../references/codex-tools.md | 25 + .../references/gemini-tools.md | 33 + .../verification-before-completion/SKILL.md | 139 ++ skills/writing-plans/SKILL.md | 145 +++ .../plan-document-reviewer-prompt.md | 49 + skills/writing-skills/SKILL.md | 654 ++++++++++ .../anthropic-best-practices.md | 1149 +++++++++++++++++ .../examples/CLAUDE_MD_TESTING.md | 189 +++ .../writing-skills/graphviz-conventions.dot | 172 +++ .../writing-skills/persuasion-principles.md | 187 +++ skills/writing-skills/render-graphs.js | 168 +++ .../testing-skills-with-subagents.md | 384 ++++++ tests/brainstorm-server/package-lock.json | 36 + tests/brainstorm-server/package.json | 10 + tests/brainstorm-server/server.test.js | 424 ++++++ .../windows-lifecycle.test.sh | 351 +++++ tests/brainstorm-server/ws-protocol.test.js | 392 ++++++ tests/claude-code/README.md | 158 +++ tests/claude-code/analyze-token-usage.py | 168 +++ tests/claude-code/run-skill-tests.sh | 187 +++ .../test-document-review-system.sh | 177 +++ tests/claude-code/test-helpers.sh | 202 +++ ...subagent-driven-development-integration.sh | 314 +++++ .../test-subagent-driven-development.sh | 165 +++ .../prompts/action-oriented.txt | 3 + .../prompts/after-planning-flow.txt | 17 + .../prompts/claude-suggested-it.txt | 11 + .../prompts/i-know-what-sdd-means.txt | 8 + .../prompts/mid-conversation-execute-plan.txt | 3 + .../prompts/please-use-brainstorming.txt | 1 + .../prompts/skip-formalities.txt | 3 + .../subagent-driven-development-please.txt | 1 + .../prompts/use-systematic-debugging.txt | 1 + tests/explicit-skill-requests/run-all.sh | 70 + .../run-claude-describes-sdd.sh | 100 ++ .../run-extended-multiturn-test.sh | 113 ++ .../explicit-skill-requests/run-haiku-test.sh | 144 +++ .../run-multiturn-test.sh | 143 ++ tests/explicit-skill-requests/run-test.sh | 136 ++ tests/opencode/run-tests.sh | 163 +++ tests/opencode/setup.sh | 73 ++ tests/opencode/test-plugin-loading.sh | 72 ++ tests/opencode/test-priority.sh | 198 +++ tests/opencode/test-tools.sh | 104 ++ .../prompts/dispatching-parallel-agents.txt | 8 + .../prompts/executing-plans.txt | 1 + .../prompts/requesting-code-review.txt | 3 + .../prompts/systematic-debugging.txt | 11 + .../prompts/test-driven-development.txt | 7 + .../prompts/writing-plans.txt | 10 + tests/skill-triggering/run-all.sh | 60 + tests/skill-triggering/run-test.sh | 88 ++ .../subagent-driven-dev/go-fractals/design.md | 81 ++ tests/subagent-driven-dev/go-fractals/plan.md | 172 +++ .../go-fractals/scaffold.sh | 45 + tests/subagent-driven-dev/run-test.sh | 106 ++ .../subagent-driven-dev/svelte-todo/design.md | 70 + tests/subagent-driven-dev/svelte-todo/plan.md | 222 ++++ .../svelte-todo/scaffold.sh | 46 + 126 files changed, 16364 insertions(+) create mode 100644 .claude-plugin/marketplace.json create mode 100644 .claude-plugin/plugin.json create mode 100644 .codex/INSTALL.md create mode 100644 .cursor-plugin/plugin.json create mode 100644 .gitattributes create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/platform_support.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .gitignore create mode 100644 .opencode/INSTALL.md create mode 100644 .opencode/plugins/superpowers.js create mode 100644 GEMINI.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 agents/code-reviewer.md create mode 100755 bin/superpowers-zh.js create mode 100644 commands/brainstorm.md create mode 100644 commands/execute-plan.md create mode 100644 commands/write-plan.md create mode 100644 docs/README.codex.md create mode 100644 docs/README.opencode.md create mode 100644 gemini-extension.json create mode 100644 hooks/hooks-cursor.json create mode 100644 hooks/hooks.json create mode 100755 hooks/run-hook.cmd create mode 100755 hooks/session-start create mode 100644 package.json create mode 100644 skills/brainstorming/SKILL.md create mode 100644 skills/brainstorming/scripts/frame-template.html create mode 100644 skills/brainstorming/scripts/helper.js create mode 100644 skills/brainstorming/scripts/server.cjs create mode 100755 skills/brainstorming/scripts/start-server.sh create mode 100755 skills/brainstorming/scripts/stop-server.sh create mode 100644 skills/brainstorming/spec-document-reviewer-prompt.md create mode 100644 skills/brainstorming/visual-companion.md create mode 100644 skills/chinese-code-review/SKILL.md create mode 100644 skills/chinese-commit-conventions/SKILL.md create mode 100644 skills/chinese-documentation/SKILL.md create mode 100644 skills/chinese-git-workflow/SKILL.md create mode 100644 skills/dispatching-parallel-agents/SKILL.md create mode 100644 skills/executing-plans/SKILL.md create mode 100644 skills/finishing-a-development-branch/SKILL.md create mode 100644 skills/mcp-builder/SKILL.md create mode 100644 skills/receiving-code-review/SKILL.md create mode 100644 skills/requesting-code-review/SKILL.md create mode 100644 skills/requesting-code-review/code-reviewer.md create mode 100644 skills/subagent-driven-development/SKILL.md create mode 100644 skills/subagent-driven-development/code-quality-reviewer-prompt.md create mode 100644 skills/subagent-driven-development/implementer-prompt.md create mode 100644 skills/subagent-driven-development/spec-reviewer-prompt.md create mode 100644 skills/systematic-debugging/CREATION-LOG.md create mode 100644 skills/systematic-debugging/SKILL.md create mode 100644 skills/systematic-debugging/condition-based-waiting-example.ts create mode 100644 skills/systematic-debugging/condition-based-waiting.md create mode 100644 skills/systematic-debugging/defense-in-depth.md create mode 100755 skills/systematic-debugging/find-polluter.sh create mode 100644 skills/systematic-debugging/root-cause-tracing.md create mode 100644 skills/systematic-debugging/test-academic.md create mode 100644 skills/systematic-debugging/test-pressure-1.md create mode 100644 skills/systematic-debugging/test-pressure-2.md create mode 100644 skills/systematic-debugging/test-pressure-3.md create mode 100644 skills/test-driven-development/SKILL.md create mode 100644 skills/test-driven-development/testing-anti-patterns.md create mode 100644 skills/using-git-worktrees/SKILL.md create mode 100644 skills/using-superpowers/SKILL.md create mode 100644 skills/using-superpowers/references/codex-tools.md create mode 100644 skills/using-superpowers/references/gemini-tools.md create mode 100644 skills/verification-before-completion/SKILL.md create mode 100644 skills/writing-plans/SKILL.md create mode 100644 skills/writing-plans/plan-document-reviewer-prompt.md create mode 100644 skills/writing-skills/SKILL.md create mode 100644 skills/writing-skills/anthropic-best-practices.md create mode 100644 skills/writing-skills/examples/CLAUDE_MD_TESTING.md create mode 100644 skills/writing-skills/graphviz-conventions.dot create mode 100644 skills/writing-skills/persuasion-principles.md create mode 100755 skills/writing-skills/render-graphs.js create mode 100644 skills/writing-skills/testing-skills-with-subagents.md create mode 100644 tests/brainstorm-server/package-lock.json create mode 100644 tests/brainstorm-server/package.json create mode 100644 tests/brainstorm-server/server.test.js create mode 100755 tests/brainstorm-server/windows-lifecycle.test.sh create mode 100644 tests/brainstorm-server/ws-protocol.test.js create mode 100644 tests/claude-code/README.md create mode 100755 tests/claude-code/analyze-token-usage.py create mode 100755 tests/claude-code/run-skill-tests.sh create mode 100755 tests/claude-code/test-document-review-system.sh create mode 100755 tests/claude-code/test-helpers.sh create mode 100755 tests/claude-code/test-subagent-driven-development-integration.sh create mode 100755 tests/claude-code/test-subagent-driven-development.sh create mode 100644 tests/explicit-skill-requests/prompts/action-oriented.txt create mode 100644 tests/explicit-skill-requests/prompts/after-planning-flow.txt create mode 100644 tests/explicit-skill-requests/prompts/claude-suggested-it.txt create mode 100644 tests/explicit-skill-requests/prompts/i-know-what-sdd-means.txt create mode 100644 tests/explicit-skill-requests/prompts/mid-conversation-execute-plan.txt create mode 100644 tests/explicit-skill-requests/prompts/please-use-brainstorming.txt create mode 100644 tests/explicit-skill-requests/prompts/skip-formalities.txt create mode 100644 tests/explicit-skill-requests/prompts/subagent-driven-development-please.txt create mode 100644 tests/explicit-skill-requests/prompts/use-systematic-debugging.txt create mode 100755 tests/explicit-skill-requests/run-all.sh create mode 100755 tests/explicit-skill-requests/run-claude-describes-sdd.sh create mode 100755 tests/explicit-skill-requests/run-extended-multiturn-test.sh create mode 100755 tests/explicit-skill-requests/run-haiku-test.sh create mode 100755 tests/explicit-skill-requests/run-multiturn-test.sh create mode 100755 tests/explicit-skill-requests/run-test.sh create mode 100755 tests/opencode/run-tests.sh create mode 100755 tests/opencode/setup.sh create mode 100755 tests/opencode/test-plugin-loading.sh create mode 100755 tests/opencode/test-priority.sh create mode 100755 tests/opencode/test-tools.sh create mode 100644 tests/skill-triggering/prompts/dispatching-parallel-agents.txt create mode 100644 tests/skill-triggering/prompts/executing-plans.txt create mode 100644 tests/skill-triggering/prompts/requesting-code-review.txt create mode 100644 tests/skill-triggering/prompts/systematic-debugging.txt create mode 100644 tests/skill-triggering/prompts/test-driven-development.txt create mode 100644 tests/skill-triggering/prompts/writing-plans.txt create mode 100755 tests/skill-triggering/run-all.sh create mode 100755 tests/skill-triggering/run-test.sh create mode 100644 tests/subagent-driven-dev/go-fractals/design.md create mode 100644 tests/subagent-driven-dev/go-fractals/plan.md create mode 100755 tests/subagent-driven-dev/go-fractals/scaffold.sh create mode 100755 tests/subagent-driven-dev/run-test.sh create mode 100644 tests/subagent-driven-dev/svelte-todo/design.md create mode 100644 tests/subagent-driven-dev/svelte-todo/plan.md create mode 100755 tests/subagent-driven-dev/svelte-todo/scaffold.sh diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json new file mode 100644 index 0000000..d974368 --- /dev/null +++ b/.claude-plugin/marketplace.json @@ -0,0 +1,20 @@ +{ + "name": "superpowers-zh", + "description": "AI 编程超能力中文版:19 个 skills 库", + "owner": { + "name": "jnMetaCode", + "url": "https://github.com/jnMetaCode" + }, + "plugins": [ + { + "name": "superpowers-zh", + "description": "AI 编程超能力中文版:19 个 skills(14 翻译 + 5 中国特色原创)", + "version": "1.0.0", + "source": "./", + "author": { + "name": "jnMetaCode", + "url": "https://github.com/jnMetaCode" + } + } + ] +} diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..ecaaeda --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,13 @@ +{ + "name": "superpowers-zh", + "description": "AI 编程超能力中文版:19 个 skills(14 翻译 + 5 中国特色原创)", + "version": "1.0.0", + "author": { + "name": "jnMetaCode", + "url": "https://github.com/jnMetaCode" + }, + "homepage": "https://github.com/jnMetaCode/superpowers-zh", + "repository": "https://github.com/jnMetaCode/superpowers-zh", + "license": "MIT", + "keywords": ["skills", "tdd", "debugging", "chinese", "中文", "best-practices", "workflows"] +} diff --git a/.codex/INSTALL.md b/.codex/INSTALL.md new file mode 100644 index 0000000..1fcef08 --- /dev/null +++ b/.codex/INSTALL.md @@ -0,0 +1,67 @@ +# 为 Codex 安装 Superpowers 中文版 + +通过原生 skill 发现机制在 Codex 中启用 superpowers skills。只需克隆并创建符号链接。 + +## 前置条件 + +- Git + +## 安装步骤 + +1. **克隆 superpowers-zh 仓库:** + ```bash + git clone https://github.com/jnMetaCode/superpowers-zh.git ~/.codex/superpowers + ``` + +2. **创建 skills 符号链接:** + ```bash + mkdir -p ~/.agents/skills + ln -s ~/.codex/superpowers/skills ~/.agents/skills/superpowers + ``` + + **Windows (PowerShell):** + ```powershell + New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.agents\skills" + cmd /c mklink /J "$env:USERPROFILE\.agents\skills\superpowers" "$env:USERPROFILE\.codex\superpowers\skills" + ``` + +3. **重启 Codex**(退出并重新启动 CLI)以发现 skills。 + +## 从旧版引导方式迁移 + +如果你在原生 skill 发现机制之前安装过 superpowers,需要: + +1. **更新仓库:** + ```bash + cd ~/.codex/superpowers && git pull + ``` + +2. **创建 skills 符号链接**(上面的步骤 2)——这是新的发现机制。 + +3. **移除旧的引导代码块**——删除 `~/.codex/AGENTS.md` 中引用 `superpowers-codex bootstrap` 的代码块,已不再需要。 + +4. **重启 Codex。** + +## 验证 + +```bash +ls -la ~/.agents/skills/superpowers +``` + +你应该看到一个符号链接(Windows 上为目录连接),指向你的 superpowers skills 目录。 + +## 更新 + +```bash +cd ~/.codex/superpowers && git pull +``` + +Skills 通过符号链接即时更新。 + +## 卸载 + +```bash +rm ~/.agents/skills/superpowers +``` + +可选:删除克隆的仓库:`rm -rf ~/.codex/superpowers`。 diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json new file mode 100644 index 0000000..2b6d737 --- /dev/null +++ b/.cursor-plugin/plugin.json @@ -0,0 +1,18 @@ +{ + "name": "superpowers-zh", + "displayName": "Superpowers 中文版", + "description": "AI 编程超能力中文版:19 个 skills(14 翻译 + 5 中国特色原创)", + "version": "1.0.0", + "author": { + "name": "jnMetaCode", + "url": "https://github.com/jnMetaCode" + }, + "homepage": "https://github.com/jnMetaCode/superpowers-zh", + "repository": "https://github.com/jnMetaCode/superpowers-zh", + "license": "MIT", + "keywords": ["skills", "tdd", "debugging", "chinese", "中文", "best-practices", "workflows"], + "skills": "./skills/", + "agents": "./agents/", + "commands": "./commands/", + "hooks": "./hooks/hooks-cursor.json" +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..def8027 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,18 @@ +# Ensure shell scripts always have LF line endings +*.sh text eol=lf +hooks/session-start text eol=lf + +# Ensure the polyglot wrapper keeps LF (it's parsed by both cmd and bash) +*.cmd text eol=lf + +# Common text files +*.md text eol=lf +*.json text eol=lf +*.js text eol=lf +*.mjs text eol=lf +*.ts text eol=lf + +# Explicitly mark binary files +*.png binary +*.jpg binary +*.gif binary diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..dc812a9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,48 @@ +--- +name: Bug 报告 +about: 某些功能未按预期工作 +labels: bug +--- + + + +- [ ] 我已搜索现有 issue,确认这不是重复的 + +## 环境信息 + +| 字段 | 值 | +|------|---| +| Superpowers 版本 | | +| 工具(Claude Code、Cursor 等) | | +| 工具版本 | | +| 模型 | | +| 操作系统 + Shell | | + +## 这是 Superpowers 的问题还是平台的问题? + + +- [ ] 我已确认此问题在未安装 Superpowers 的情况下不会发生 + +## 发生了什么? + + +## 复现步骤 +1. +2. +3. + +## 预期行为 + + +## 实际行为 + + +## 调试日志或对话记录 + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..e842530 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: 问题与帮助 + url: https://github.com/jnMetaCode/superpowers-zh/discussions + about: 使用问题、故障排除和一般讨论,请访问 Discussions 而不是开 issue。 diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..5591b99 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,33 @@ +--- +name: 功能请求 +about: 提议对 Superpowers 进行改变或添加 +labels: enhancement +--- + + + +- [ ] 我已搜索现有 issue,确认此功能之前未被提议过 + +## 这解决了什么问题? + + +## 建议的方案 + + +## 你考虑了哪些替代方案? + + +## 这适合放在核心 Superpowers 中吗? + + +## 上下文 + diff --git a/.github/ISSUE_TEMPLATE/platform_support.md b/.github/ISSUE_TEMPLATE/platform_support.md new file mode 100644 index 0000000..d89cb77 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/platform_support.md @@ -0,0 +1,22 @@ +--- +name: IDE / 平台支持请求 +about: 请求支持新的 IDE、编辑器或 AI 编程工具 +labels: platform-support +--- + + + +- [ ] 我已搜索现有 issue 中关于此 IDE/平台的内容 + +## 哪个 IDE 或平台? + + +## 该工具是否有插件或扩展系统? + + +## 你是否尝试过手动安装? + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..8dbb88d --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,78 @@ + + +## 你要解决什么问题? + + +## 这个 PR 做了什么改变? + + +## 这个改变适合放在核心库中吗? + + +## 你考虑了哪些替代方案? + + +## 这个 PR 是否包含多个不相关的改变? + + +## 已有的 PR +- [ ] 我已查看所有已开放和已关闭的 PR,确认没有重复或先前的类似工作 +- 相关 PR: + + + +## 测试环境 + +| 工具(如 Claude Code、Cursor) | 工具版本 | 模型 | 模型版本/ID | +|-------------------------------|---------|------|------------| +| | | | | + +## 评估 +- 你(或你的人类搭档)用什么初始提示词开始了导致这个改变的会话? +- 做出改变后你运行了多少次评估会话? +- 与改变之前相比,结果有何变化? + + + +## 严格性 + +- [ ] 如果这是 skills 改变:我使用了 `superpowers:writing-skills` 并完成了对抗性压力测试(在下方粘贴结果) +- [ ] 这个改变经过了对抗性测试,而不仅仅是正常路径 +- [ ] 我没有在未经大量评估证明改变是改进的情况下修改精心调整的内容(红旗表格、合理化描述、"人类搭档"用语) + + + +## 人工审核 +- [ ] 提交前已有人工审核过完整的 diff + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1c25a50 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.worktrees/ +.private-journal/ +.claude/ +.DS_Store +node_modules/ +inspo +triage/ diff --git a/.opencode/INSTALL.md b/.opencode/INSTALL.md new file mode 100644 index 0000000..b855dc4 --- /dev/null +++ b/.opencode/INSTALL.md @@ -0,0 +1,83 @@ +# 为 OpenCode 安装 Superpowers 中文版 + +## 前置条件 + +- 已安装 [OpenCode.ai](https://opencode.ai) + +## 安装步骤 + +在你的 `opencode.json`(全局或项目级别)中将 superpowers-zh 添加到 `plugin` 数组: + +```json +{ + "plugin": ["superpowers@git+https://github.com/jnMetaCode/superpowers-zh.git"] +} +``` + +重启 OpenCode。完成——插件会自动安装并注册所有 skills。 + +通过询问来验证:"告诉我你的超能力" + +## 从旧版符号链接安装方式迁移 + +如果你之前使用 `git clone` 和符号链接安装过 superpowers,请移除旧的配置: + +```bash +# 移除旧的符号链接 +rm -f ~/.config/opencode/plugins/superpowers.js +rm -rf ~/.config/opencode/skills/superpowers + +# 可选:移除克隆的仓库 +rm -rf ~/.config/opencode/superpowers + +# 如果你在 opencode.json 中为 superpowers 添加过 skills.paths,请将其移除 +``` + +然后按照上面的安装步骤操作。 + +## 使用方法 + +使用 OpenCode 的原生 `skill` 工具: + +``` +use skill tool to list skills +use skill tool to load superpowers/brainstorming +``` + +## 更新 + +Superpowers 会在你重启 OpenCode 时自动更新。 + +要固定到特定版本: + +```json +{ + "plugin": ["superpowers@git+https://github.com/jnMetaCode/superpowers-zh.git#v1.0.0"] +} +``` + +## 故障排除 + +### 插件未加载 + +1. 检查日志:`opencode run --print-logs "hello" 2>&1 | grep -i superpowers` +2. 验证 `opencode.json` 中的插件配置 +3. 确保你运行的是最新版本的 OpenCode + +### Skills 未找到 + +1. 使用 `skill` 工具列出已发现的内容 +2. 检查插件是否已加载(见上文) + +### 工具映射 + +当 skills 引用 Claude Code 工具时: +- `TodoWrite` → `todowrite` +- `Task` 子代理 → `@mention` 语法 +- `Skill` 工具 → OpenCode 的原生 `skill` 工具 +- 文件操作 → 你的原生工具 + +## 获取帮助 + +- 报告问题:https://github.com/jnMetaCode/superpowers-zh/issues +- 完整文档:https://github.com/jnMetaCode/superpowers-zh diff --git a/.opencode/plugins/superpowers.js b/.opencode/plugins/superpowers.js new file mode 100644 index 0000000..5e7833b --- /dev/null +++ b/.opencode/plugins/superpowers.js @@ -0,0 +1,107 @@ +/** + * Superpowers plugin for OpenCode.ai + * + * Injects superpowers bootstrap context via system prompt transform. + * Auto-registers skills directory via config hook (no symlinks needed). + */ + +import path from 'path'; +import fs from 'fs'; +import os from 'os'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +// Simple frontmatter extraction (avoid dependency on skills-core for bootstrap) +const extractAndStripFrontmatter = (content) => { + const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/); + if (!match) return { frontmatter: {}, content }; + + const frontmatterStr = match[1]; + const body = match[2]; + const frontmatter = {}; + + for (const line of frontmatterStr.split('\n')) { + const colonIdx = line.indexOf(':'); + if (colonIdx > 0) { + const key = line.slice(0, colonIdx).trim(); + const value = line.slice(colonIdx + 1).trim().replace(/^["']|["']$/g, ''); + frontmatter[key] = value; + } + } + + return { frontmatter, content: body }; +}; + +// Normalize a path: trim whitespace, expand ~, resolve to absolute +const normalizePath = (p, homeDir) => { + if (!p || typeof p !== 'string') return null; + let normalized = p.trim(); + if (!normalized) return null; + if (normalized.startsWith('~/')) { + normalized = path.join(homeDir, normalized.slice(2)); + } else if (normalized === '~') { + normalized = homeDir; + } + return path.resolve(normalized); +}; + +export const SuperpowersPlugin = async ({ client, directory }) => { + const homeDir = os.homedir(); + const superpowersSkillsDir = path.resolve(__dirname, '../../skills'); + const envConfigDir = normalizePath(process.env.OPENCODE_CONFIG_DIR, homeDir); + const configDir = envConfigDir || path.join(homeDir, '.config/opencode'); + + // Helper to generate bootstrap content + const getBootstrapContent = () => { + // Try to load using-superpowers skill + const skillPath = path.join(superpowersSkillsDir, 'using-superpowers', 'SKILL.md'); + if (!fs.existsSync(skillPath)) return null; + + const fullContent = fs.readFileSync(skillPath, 'utf8'); + const { content } = extractAndStripFrontmatter(fullContent); + + const toolMapping = `**Tool Mapping for OpenCode:** +When skills reference tools you don't have, substitute OpenCode equivalents: +- \`TodoWrite\` → \`todowrite\` +- \`Task\` tool with subagents → Use OpenCode's subagent system (@mention) +- \`Skill\` tool → OpenCode's native \`skill\` tool +- \`Read\`, \`Write\`, \`Edit\`, \`Bash\` → Your native tools + +**Skills location:** +Superpowers skills are in \`${configDir}/skills/superpowers/\` +Use OpenCode's native \`skill\` tool to list and load skills.`; + + return ` +You have superpowers. + +**IMPORTANT: The using-superpowers skill content is included below. It is ALREADY LOADED - you are currently following it. Do NOT use the skill tool to load "using-superpowers" again - that would be redundant.** + +${content} + +${toolMapping} +`; + }; + + return { + // Inject skills path into live config so OpenCode discovers superpowers skills + // without requiring manual symlinks or config file edits. + // This works because Config.get() returns a cached singleton — modifications + // here are visible when skills are lazily discovered later. + config: async (config) => { + config.skills = config.skills || {}; + config.skills.paths = config.skills.paths || []; + if (!config.skills.paths.includes(superpowersSkillsDir)) { + config.skills.paths.push(superpowersSkillsDir); + } + }, + + // Use system prompt transform to inject bootstrap (fixes #226 agent reset bug) + 'experimental.chat.system.transform': async (_input, output) => { + const bootstrap = getBootstrapContent(); + if (bootstrap) { + (output.system ||= []).push(bootstrap); + } + } + }; +}; diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..0dd2f58 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,2 @@ +@./skills/using-superpowers/SKILL.md +@./skills/using-superpowers/references/gemini-tools.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..da684a5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 jnMetaCode + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..53adb30 --- /dev/null +++ b/README.md @@ -0,0 +1,151 @@ +# superpowers-zh(AI 编程超能力 · 中文版) + +🌐 **简体中文** | [English (upstream)](https://github.com/obra/superpowers) + +> **让你的 AI 编程助手真正会干活** — 从头脑风暴到代码审查,从 TDD 到调试,每个 skill 都是经过实战验证的工作方法论。 + +Chinese community edition of [superpowers](https://github.com/obra/superpowers), including full translations and China-specific development skills. + +[![GitHub stars](https://img.shields.io/github/stars/jnMetaCode/superpowers-zh?style=social)](https://github.com/jnMetaCode/superpowers-zh) +[![npm version](https://img.shields.io/npm/v/superpowers-zh)](https://www.npmjs.com/package/superpowers-zh) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://makeapullrequest.com) +[![QQ群](https://img.shields.io/badge/QQ群-833585047-blue?logo=tencentqq)](https://qm.qq.com/q/x8kyqzlfDc) + +### 📊 项目规模 + +| 📦 翻译 Skills | 🇨🇳 中国特色 Skills | 🤖 支持工具 | +|:---:|:---:|:---:| +| **14** | **5** | **Claude Code / Cursor / Codex / Gemini** | + +--- + +## 这是什么? + +[superpowers](https://github.com/obra/superpowers) 是目前最火的 AI 编程 skills 框架(99k+ stars),为 Claude Code、Cursor 等 AI 编程工具提供**系统化的工作方法论**。 + +**superpowers-zh** 在完整翻译的基础上,新增了面向中国开发者的特色 skills: + +### 翻译的 Skills(14 个) + +| Skill | 用途 | +|-------|------| +| **头脑风暴** (brainstorming) | 需求分析 → 设计规格,不写代码先想清楚 | +| **编写计划** (writing-plans) | 把规格拆成可执行的实施步骤 | +| **执行计划** (executing-plans) | 按计划逐步实施,每步验证 | +| **测试驱动开发** (test-driven-development) | 严格 TDD:先写测试,再写代码 | +| **系统化调试** (systematic-debugging) | 四阶段调试法:定位→分析→假设→修复 | +| **请求代码审查** (requesting-code-review) | 派遣审查 agent 检查代码质量 | +| **接收代码审查** (receiving-code-review) | 技术严谨地处理审查反馈,拒绝敷衍 | +| **完成前验证** (verification-before-completion) | 证据先行——声称完成前必须跑验证 | +| **派遣并行 Agent** (dispatching-parallel-agents) | 多任务并发执行 | +| **子 Agent 驱动开发** (subagent-driven-development) | 每个任务一个 agent,两轮审查 | +| **Git Worktree 使用** (using-git-worktrees) | 隔离式特性开发 | +| **完成开发分支** (finishing-a-development-branch) | 合并/PR/保留/丢弃四选一 | +| **编写 Skills** (writing-skills) | 创建新 skill 的方法论 | +| **使用 Superpowers** (using-superpowers) | 元技能:如何调用和优先使用 skills | + +### 🇨🇳 中国特色 Skills(5 个) + +| Skill | 用途 | 上游有吗? | +|-------|------|:---:| +| **中文代码审查** (chinese-code-review) | 符合国内团队文化的代码审查规范 | 无 | +| **中文 Git 工作流** (chinese-git-workflow) | 适配 Gitee/Coding/极狐 GitLab | 无 | +| **中文技术文档** (chinese-documentation) | 中文排版规范、中英混排、告别机翻味 | 无 | +| **中文提交规范** (chinese-commit-conventions) | 适配国内团队的 commit message 规范 | 无 | +| **MCP 服务器构建** (mcp-builder) | 构建生产级 MCP 工具,扩展 AI 能力边界 | 无 | + +--- + +## 快速开始 + +### 方式一:npm 安装(推荐) + +```bash +cd /your/project +npx superpowers-zh +``` + +### 方式二:手动安装 + +```bash +# 克隆仓库 +git clone https://github.com/jnMetaCode/superpowers-zh.git + +# 复制 skills 到你的项目 +cp -r superpowers-zh/skills /your/project/.claude/skills +# 或 +cp -r superpowers-zh/skills /your/project/.cursor/skills +``` + +### 方式三:在 CLAUDE.md 中引用 + +```markdown +# CLAUDE.md + +请使用 superpowers-zh 的 skills 来指导你的工作流程。 +Skills 位于 ./skills/ 目录下。 +``` + +--- + +## 对比上游 + +| 特性 | superpowers (英文) | superpowers-zh (中文) | +|------|-------------------|----------------------| +| Skills 数量 | 14 | **19**(14 翻译 + 5 原创) | +| 语言 | 英文 | 中文(技术术语保留英文) | +| 代码审查规范 | 西方直接风格 | 适配国内团队沟通文化 | +| Git 平台 | GitHub 为主 | GitHub + Gitee + Coding + 极狐 | +| Git 提交规范 | 无 | Conventional Commits 中文适配 | +| 文档规范 | 英文 | 中文排版规范 + 中英混排 | +| MCP 构建 | 无 | MCP 服务器构建方法论 | +| 社区 | Discord | QQ 群 | + +--- + +## 贡献 + +欢迎参与!翻译改进、新增 skills、Bug 修复都可以。 + +### 贡献方向 + +我们只接收符合 superpowers 定位的 skill——**AI 编程工作流方法论**。好的 skill 应该: + +- 教 AI 助手**怎么干活**,而不是某个框架/语言的教程 +- 解决上游英文版不覆盖的**中国开发者痛点** +- 有明确的步骤、检查清单、示例,AI 加载后能直接执行 + +欢迎提 Issue 讨论你的想法! + +--- + +## 社区交流 + +| 群名 | 群号 | 加入方式 | +|------|------|---------| +| AI Agent 中文实践群 | **833585047** | [点击加入](https://qm.qq.com/q/x8kyqzlfDc) | + +--- + +## 致谢 + +- 原始英文版:[obra/superpowers](https://github.com/obra/superpowers)(MIT 协议) +- 感谢 [@obra](https://github.com/obra) 创建了这个优秀的项目 +- 姊妹项目:[agency-agents-zh](https://github.com/jnMetaCode/agency-agents-zh)(180 个 AI 智能体专家团队) + +--- + +## 许可证 + +MIT License — 自由使用,商业或个人均可。 + +--- + +
+ +**AI 编程超能力:让你的 AI 助手真正会干活** + +[Star 本项目](https://github.com/jnMetaCode/superpowers-zh) · [提交 Issue](https://github.com/jnMetaCode/superpowers-zh/issues) · [贡献代码](https://github.com/jnMetaCode/superpowers-zh/pulls) + +
diff --git a/agents/code-reviewer.md b/agents/code-reviewer.md new file mode 100644 index 0000000..b3d550a --- /dev/null +++ b/agents/code-reviewer.md @@ -0,0 +1,48 @@ +--- +name: code-reviewer +description: | + 当一个主要项目步骤完成并需要根据原始计划和编码标准进行审查时使用此智能体。示例:Context: 用户正在创建一个代码审查智能体,应在逻辑代码块编写完成后调用。user: "我已经按照计划第 3 步完成了用户认证系统的实现" assistant: "干得好!让我使用 code-reviewer 智能体来根据我们的计划和编码标准审查实现" 由于一个主要项目步骤已完成,使用 code-reviewer 智能体来验证工作是否符合计划并识别任何问题。 Context: 用户完成了一个重要功能的实现。user: "任务管理系统的 API 端点现在完成了——这涵盖了我们架构文档中的第 2 步" assistant: "很好!让我用 code-reviewer 智能体检查这个实现,确保它符合我们的计划并遵循最佳实践" 规划文档中的一个编号步骤已完成,所以应该用 code-reviewer 智能体审查工作。 +model: inherit +--- + +你是一位高级代码审查员,精通软件架构、设计模式和最佳实践。你的角色是根据原始计划审查已完成的项目步骤,确保代码质量标准得到满足。 + +审查已完成的工作时,你将: + +1. **计划对齐分析**: + - 将实现与原始规划文档或步骤描述进行对比 + - 识别与计划的方案、架构或需求的任何偏差 + - 评估偏差是合理的改进还是有问题的偏离 + - 验证所有计划的功能是否都已实现 + +2. **代码质量评估**: + - 审查代码是否遵循已建立的模式和约定 + - 检查错误处理、类型安全和防御性编程是否恰当 + - 评估代码组织、命名约定和可维护性 + - 评估测试覆盖率和测试实现的质量 + - 寻找潜在的安全漏洞或性能问题 + +3. **架构和设计审查**: + - 确保实现遵循 SOLID 原则和已建立的架构模式 + - 检查关注点分离和松耦合是否恰当 + - 验证代码与现有系统的集成是否良好 + - 评估可扩展性和可延展性的考虑 + +4. **文档和标准**: + - 验证代码包含适当的注释和文档 + - 检查文件头、函数文档和内联注释是否存在且准确 + - 确保遵循项目特定的编码标准和约定 + +5. **问题识别和建议**: + - 将问题明确分类为:关键(必须修复)、重要(应该修复)或建议(锦上添花) + - 对每个问题提供具体示例和可操作的建议 + - 识别计划偏差时,解释它们是有问题的还是有益的 + - 在有帮助时提供带有代码示例的具体改进建议 + +6. **沟通协议**: + - 如果发现与计划的重大偏差,请编码智能体审查并确认更改 + - 如果识别出原始计划本身的问题,建议更新计划 + - 对于实现问题,提供明确的修复指导 + - 在指出问题之前始终先肯定做得好的地方 + +你的输出应该是结构化的、可操作的,聚焦于帮助保持高代码质量同时确保项目目标的达成。要全面但简洁,始终提供建设性的反馈,帮助改进当前实现和未来的开发实践。 diff --git a/bin/superpowers-zh.js b/bin/superpowers-zh.js new file mode 100755 index 0000000..c453bea --- /dev/null +++ b/bin/superpowers-zh.js @@ -0,0 +1,85 @@ +#!/usr/bin/env node + +import { existsSync, mkdirSync, cpSync, readdirSync, readFileSync } from 'fs'; +import { resolve, dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const PKG = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json'), 'utf8')); +const SKILLS_SRC = resolve(__dirname, '..', 'skills'); +const AGENTS_SRC = resolve(__dirname, '..', 'agents'); +const PROJECT_DIR = process.cwd(); + +const TARGETS = [ + { name: 'Claude Code', dir: '.claude/skills' }, + { name: 'Cursor', dir: '.cursor/skills' }, + { name: 'Codex CLI', dir: '.codex/skills' }, +]; + +function countDirs(dir) { + if (!existsSync(dir)) return 0; + return readdirSync(dir, { withFileTypes: true }).filter(e => e.isDirectory()).length; +} + +function showHelp() { + console.log(` + superpowers-zh v${PKG.version} — AI 编程超能力中文版 + + 用法: + npx superpowers-zh 安装 skills 到当前项目 + npx superpowers-zh --help 显示帮助 + npx superpowers-zh --version 显示版本 + + 说明: + 自动检测当前项目使用的 AI 编程工具(.claude/ .cursor/ .codex/), + 将 ${countDirs(SKILLS_SRC)} 个 skills 安装到对应目录。 + 如果未检测到任何工具,默认安装到 .claude/skills/。 + + 项目:https://github.com/jnMetaCode/superpowers-zh +`); +} + +function install() { + console.log(`\n superpowers-zh v${PKG.version} — AI 编程超能力中文版\n`); + console.log(` 源: ${countDirs(SKILLS_SRC)} 个 skills`); + console.log(` 目标项目: ${PROJECT_DIR}\n`); + + let installed = 0; + + for (const target of TARGETS) { + const configDir = resolve(PROJECT_DIR, target.dir.split('/')[0]); + const dest = resolve(PROJECT_DIR, target.dir); + + if (existsSync(configDir) || installed === 0) { + mkdirSync(dest, { recursive: true }); + cpSync(SKILLS_SRC, dest, { recursive: true }); + const count = countDirs(dest); + console.log(` ✅ ${target.name}: ${count} 个 skills -> ${dest}`); + installed++; + + if (target.name === 'Claude Code' && existsSync(AGENTS_SRC)) { + const agentsDest = resolve(PROJECT_DIR, '.claude', 'agents'); + mkdirSync(agentsDest, { recursive: true }); + cpSync(AGENTS_SRC, agentsDest, { recursive: true }); + } + } + } + + if (installed === 0) { + const dest = resolve(PROJECT_DIR, '.claude', 'skills'); + mkdirSync(dest, { recursive: true }); + cpSync(SKILLS_SRC, dest, { recursive: true }); + console.log(` ✅ 默认安装: ${countDirs(dest)} 个 skills -> ${dest}`); + } + + console.log('\n 安装完成!重启你的 AI 编程工具即可生效。\n'); +} + +const arg = process.argv[2]; +if (arg === '--help' || arg === '-h') { + showHelp(); +} else if (arg === '--version' || arg === '-v') { + console.log(PKG.version); +} else { + install(); +} diff --git a/commands/brainstorm.md b/commands/brainstorm.md new file mode 100644 index 0000000..f7edc17 --- /dev/null +++ b/commands/brainstorm.md @@ -0,0 +1,5 @@ +--- +description: "已弃用 - 请改用 superpowers:brainstorming 技能" +--- + +告诉你的搭档这个命令已弃用,将在下个主要版本中移除。他们应该让你使用"superpowers brainstorming"技能。 diff --git a/commands/execute-plan.md b/commands/execute-plan.md new file mode 100644 index 0000000..c13cf20 --- /dev/null +++ b/commands/execute-plan.md @@ -0,0 +1,5 @@ +--- +description: "已弃用 - 请改用 superpowers:executing-plans 技能" +--- + +告诉你的搭档这个命令已弃用,将在下个主要版本中移除。他们应该让你使用"superpowers executing-plans"技能。 diff --git a/commands/write-plan.md b/commands/write-plan.md new file mode 100644 index 0000000..07d10f2 --- /dev/null +++ b/commands/write-plan.md @@ -0,0 +1,5 @@ +--- +description: "已弃用 - 请改用 superpowers:writing-plans 技能" +--- + +告诉你的搭档这个命令已弃用,将在下个主要版本中移除。他们应该让你使用"superpowers writing-plans"技能。 diff --git a/docs/README.codex.md b/docs/README.codex.md new file mode 100644 index 0000000..38a49b9 --- /dev/null +++ b/docs/README.codex.md @@ -0,0 +1,91 @@ +# Superpowers 中文版 — Codex CLI 安装指南 + +在 Codex 中使用 superpowers-zh 的完整指南。 + +## 快速安装 + +告诉 Codex: + +``` +Fetch and follow instructions from https://raw.githubusercontent.com/jnMetaCode/superpowers-zh/refs/heads/main/.codex/INSTALL.md +``` + +## 手动安装 + +### 前置条件 + +- OpenAI Codex CLI +- Git + +### 步骤 + +1. 克隆仓库: + ```bash + git clone https://github.com/jnMetaCode/superpowers-zh.git ~/.codex/superpowers-zh + ``` + +2. 创建 skills 符号链接: + ```bash + mkdir -p ~/.agents/skills + ln -s ~/.codex/superpowers-zh/skills ~/.agents/skills/superpowers + ``` + +3. 重启 Codex。 + +4. **子代理 skills(可选):** `dispatching-parallel-agents` 和 `subagent-driven-development` 需要 Codex 的多代理功能。在 Codex 配置中添加: + ```toml + [features] + multi_agent = true + ``` + +### Windows + +使用 junction 代替符号链接(无需开发者模式): + +```powershell +New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.agents\skills" +cmd /c mklink /J "$env:USERPROFILE\.agents\skills\superpowers" "$env:USERPROFILE\.codex\superpowers-zh\skills" +``` + +## 工作原理 + +Codex 原生支持 skill 发现——启动时扫描 `~/.agents/skills/` 目录,解析 SKILL.md 的 frontmatter,按需加载 skills。通过一个符号链接即可注册所有 skills: + +``` +~/.agents/skills/superpowers/ → ~/.codex/superpowers-zh/skills/ +``` + +`using-superpowers` skill 会自动被发现并强制执行 skill 使用纪律——无需额外配置。 + +## 使用 + +Skills 自动发现。Codex 在以下情况激活 skills: +- 你提到 skill 名称(如 "use brainstorming") +- 任务匹配 skill 的描述 +- `using-superpowers` skill 指示 Codex 使用某个 skill + +## 更新 + +```bash +cd ~/.codex/superpowers-zh && git pull +``` + +Skills 通过符号链接即时更新。 + +## 卸载 + +```bash +rm ~/.agents/skills/superpowers +``` + +**Windows (PowerShell):** +```powershell +Remove-Item "$env:USERPROFILE\.agents\skills\superpowers" +``` + +可选:删除克隆的仓库 `rm -rf ~/.codex/superpowers-zh` + +## 获取帮助 + +- 提交 Issue:https://github.com/jnMetaCode/superpowers-zh/issues +- 项目主页:https://github.com/jnMetaCode/superpowers-zh diff --git a/docs/README.opencode.md b/docs/README.opencode.md new file mode 100644 index 0000000..d9a52b6 --- /dev/null +++ b/docs/README.opencode.md @@ -0,0 +1,108 @@ +# Superpowers 中文版 — OpenCode 安装指南 + +在 [OpenCode.ai](https://opencode.ai) 中使用 superpowers-zh 的完整指南。 + +## 安装 + +在 `opencode.json`(全局或项目级)的 `plugin` 数组中添加: + +```json +{ + "plugin": ["superpowers@git+https://github.com/jnMetaCode/superpowers-zh.git"] +} +``` + +重启 OpenCode。插件通过 Bun 自动安装并注册所有 skills。 + +验证方式:问 "告诉我你有哪些 superpowers" + +## 使用 + +### 查找 Skills + +使用 OpenCode 原生的 `skill` 工具列出所有可用 skills: + +``` +use skill tool to list skills +``` + +### 加载 Skill + +``` +use skill tool to load superpowers/brainstorming +``` + +### 个人 Skills + +在 `~/.config/opencode/skills/` 中创建你自己的 skills: + +```bash +mkdir -p ~/.config/opencode/skills/my-skill +``` + +创建 `~/.config/opencode/skills/my-skill/SKILL.md`: + +```markdown +--- +name: my-skill +description: 当 [条件] 时使用 - [功能描述] +--- + +# 我的 Skill + +[你的 skill 内容] +``` + +### 项目 Skills + +在项目的 `.opencode/skills/` 目录中创建项目级 skills。 + +**Skill 优先级:** 项目 skills > 个人 skills > Superpowers skills + +## 更新 + +重启 OpenCode 时自动更新。插件每次启动都从 git 仓库重新安装。 + +锁定特定版本: + +```json +{ + "plugin": ["superpowers@git+https://github.com/jnMetaCode/superpowers-zh.git#v1.0.0"] +} +``` + +## 工作原理 + +插件做两件事: + +1. **注入引导上下文** — 通过 `experimental.chat.system.transform` hook,为每次对话添加 superpowers 意识 +2. **注册 skills 目录** — 通过 `config` hook,让 OpenCode 发现所有 skills,无需符号链接或手动配置 + +### 工具映射 + +为 Claude Code 编写的 skills 自动适配 OpenCode: + +- `TodoWrite` → `todowrite` +- `Task`(子代理)→ OpenCode 的 `@mention` 系统 +- `Skill` 工具 → OpenCode 原生 `skill` 工具 +- 文件操作 → OpenCode 原生工具 + +## 故障排查 + +### 插件未加载 + +1. 检查 OpenCode 日志:`opencode run --print-logs "hello" 2>&1 | grep -i superpowers` +2. 确认 `opencode.json` 中的插件配置正确 +3. 确保运行的是最新版本的 OpenCode + +### Skills 未找到 + +1. 使用 `skill` 工具列出可用 skills +2. 检查插件是否正确加载(见上) +3. 每个 skill 需要包含有效 YAML frontmatter 的 `SKILL.md` 文件 + +## 获取帮助 + +- 提交 Issue:https://github.com/jnMetaCode/superpowers-zh/issues +- 项目主页:https://github.com/jnMetaCode/superpowers-zh +- OpenCode 文档:https://opencode.ai/docs/ diff --git a/gemini-extension.json b/gemini-extension.json new file mode 100644 index 0000000..d1020a0 --- /dev/null +++ b/gemini-extension.json @@ -0,0 +1,6 @@ +{ + "name": "superpowers", + "description": "Core skills library: TDD, debugging, collaboration patterns, and proven techniques", + "version": "5.0.0", + "contextFileName": "GEMINI.md" +} diff --git a/hooks/hooks-cursor.json b/hooks/hooks-cursor.json new file mode 100644 index 0000000..6df4461 --- /dev/null +++ b/hooks/hooks-cursor.json @@ -0,0 +1,10 @@ +{ + "version": 1, + "hooks": { + "sessionStart": [ + { + "command": "./hooks/session-start" + } + ] + } +} diff --git a/hooks/hooks.json b/hooks/hooks.json new file mode 100644 index 0000000..79d8cee --- /dev/null +++ b/hooks/hooks.json @@ -0,0 +1,16 @@ +{ + "hooks": { + "SessionStart": [ + { + "matcher": "startup|clear|compact", + "hooks": [ + { + "type": "command", + "command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" session-start", + "async": false + } + ] + } + ] + } +} diff --git a/hooks/run-hook.cmd b/hooks/run-hook.cmd new file mode 100755 index 0000000..ceec3a7 --- /dev/null +++ b/hooks/run-hook.cmd @@ -0,0 +1,46 @@ +: << 'CMDBLOCK' +@echo off +REM Cross-platform polyglot wrapper for hook scripts. +REM On Windows: cmd.exe runs the batch portion, which finds and calls bash. +REM On Unix: the shell interprets this as a script (: is a no-op in bash). +REM +REM Hook scripts use extensionless filenames (e.g. "session-start" not +REM "session-start.sh") so Claude Code's Windows auto-detection -- which +REM prepends "bash" to any command containing .sh -- doesn't interfere. +REM +REM Usage: run-hook.cmd [args...] + +if "%~1"=="" ( + echo run-hook.cmd: missing script name >&2 + exit /b 1 +) + +set "HOOK_DIR=%~dp0" + +REM Try Git for Windows bash in standard locations +if exist "C:\Program Files\Git\bin\bash.exe" ( + "C:\Program Files\Git\bin\bash.exe" "%HOOK_DIR%%~1" %2 %3 %4 %5 %6 %7 %8 %9 + exit /b %ERRORLEVEL% +) +if exist "C:\Program Files (x86)\Git\bin\bash.exe" ( + "C:\Program Files (x86)\Git\bin\bash.exe" "%HOOK_DIR%%~1" %2 %3 %4 %5 %6 %7 %8 %9 + exit /b %ERRORLEVEL% +) + +REM Try bash on PATH (e.g. user-installed Git Bash, MSYS2, Cygwin) +where bash >nul 2>nul +if %ERRORLEVEL% equ 0 ( + bash "%HOOK_DIR%%~1" %2 %3 %4 %5 %6 %7 %8 %9 + exit /b %ERRORLEVEL% +) + +REM No bash found - exit silently rather than error +REM (plugin still works, just without SessionStart context injection) +exit /b 0 +CMDBLOCK + +# Unix: run the named script directly +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +SCRIPT_NAME="$1" +shift +exec bash "${SCRIPT_DIR}/${SCRIPT_NAME}" "$@" diff --git a/hooks/session-start b/hooks/session-start new file mode 100755 index 0000000..2371935 --- /dev/null +++ b/hooks/session-start @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# SessionStart hook for superpowers plugin + +set -euo pipefail + +# Determine plugin root directory +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +# Check if legacy skills directory exists and build warning +warning_message="" +legacy_skills_dir="${HOME}/.config/superpowers/skills" +if [ -d "$legacy_skills_dir" ]; then + warning_message="\n\nIN YOUR FIRST REPLY AFTER SEEING THIS MESSAGE YOU MUST TELL THE USER:⚠️ **WARNING:** Superpowers now uses Claude Code's skills system. Custom skills in ~/.config/superpowers/skills will not be read. Move custom skills to ~/.claude/skills instead. To make this message go away, remove ~/.config/superpowers/skills" +fi + +# Read using-superpowers content +using_superpowers_content=$(cat "${PLUGIN_ROOT}/skills/using-superpowers/SKILL.md" 2>&1 || echo "Error reading using-superpowers skill") + +# Escape string for JSON embedding using bash parameter substitution. +# Each ${s//old/new} is a single C-level pass - orders of magnitude +# faster than the character-by-character loop this replaces. +escape_for_json() { + local s="$1" + s="${s//\\/\\\\}" + s="${s//\"/\\\"}" + s="${s//$'\n'/\\n}" + s="${s//$'\r'/\\r}" + s="${s//$'\t'/\\t}" + printf '%s' "$s" +} + +using_superpowers_escaped=$(escape_for_json "$using_superpowers_content") +warning_escaped=$(escape_for_json "$warning_message") +session_context="\nYou have superpowers.\n\n**Below is the full content of your 'superpowers:using-superpowers' skill - your introduction to using skills. For all other skills, use the 'Skill' tool:**\n\n${using_superpowers_escaped}\n\n${warning_escaped}\n" + +# Output context injection as JSON. +# Cursor hooks expect additional_context. +# Claude Code hooks expect hookSpecificOutput.additionalContext. +# Claude Code reads BOTH fields without deduplication, so we must only +# emit the field consumed by the current platform to avoid double injection. +# +# Uses printf instead of heredoc (cat < +在你展示设计方案并获得用户批准之前,不要调用任何实现技能、编写任何代码、搭建任何项目或采取任何实现行动。这适用于所有项目,无论看起来多简单。 + + +## 反模式:"这个太简单了,不需要设计" + +每个项目都要经过这个流程。一个待办事项列表、一个单函数工具、一个配置变更——全都需要。"简单"的项目恰恰是未经检验的假设造成最多浪费的地方。设计可以很简短(对于真正简单的项目几句话就够了),但你必须展示出来并获得批准。 + +## 检查清单 + +你必须为以下每个条目创建任务,并按顺序完成: + +1. **探索项目上下文** — 检查文件、文档、最近的 commit +2. **提供视觉伴侣**(如果主题涉及视觉问题)— 这是一条独立的消息,不要与澄清问题合并。参见下方的"视觉伴侣"部分。 +3. **提出澄清问题** — 每次一个,了解目的/约束/成功标准 +4. **提出 2-3 种方案** — 附带权衡分析和你的推荐 +5. **展示设计** — 按复杂度分节展示,每节展示后获得用户批准 +6. **编写设计文档** — 保存到 `docs/superpowers/specs/YYYY-MM-DD--design.md` 并 commit +7. **规格审查循环** — 调度 spec-document-reviewer 子代理,提供精心组织的审查上下文(绝不是你的会话历史);修复问题后重新调度直到通过(最多 3 次迭代,之后交给人工处理) +8. **用户审查书面规格** — 在继续之前请用户审查规格文件 +9. **过渡到实现** — 调用 writing-plans 技能创建实现计划 + +## 流程图 + +```dot +digraph brainstorming { + "探索项目上下文" [shape=box]; + "有视觉相关问题?" [shape=diamond]; + "提供视觉伴侣\n(独立消息,不含其他内容)" [shape=box]; + "提出澄清问题" [shape=box]; + "提出 2-3 种方案" [shape=box]; + "分节展示设计" [shape=box]; + "用户批准设计?" [shape=diamond]; + "编写设计文档" [shape=box]; + "规格审查循环" [shape=box]; + "规格审查通过?" [shape=diamond]; + "用户审查规格?" [shape=diamond]; + "调用 writing-plans 技能" [shape=doublecircle]; + + "探索项目上下文" -> "有视觉相关问题?"; + "有视觉相关问题?" -> "提供视觉伴侣\n(独立消息,不含其他内容)" [label="是"]; + "有视觉相关问题?" -> "提出澄清问题" [label="否"]; + "提供视觉伴侣\n(独立消息,不含其他内容)" -> "提出澄清问题"; + "提出澄清问题" -> "提出 2-3 种方案"; + "提出 2-3 种方案" -> "分节展示设计"; + "分节展示设计" -> "用户批准设计?"; + "用户批准设计?" -> "分节展示设计" [label="否,修改"]; + "用户批准设计?" -> "编写设计文档" [label="是"]; + "编写设计文档" -> "规格审查循环"; + "规格审查循环" -> "规格审查通过?"; + "规格审查通过?" -> "规格审查循环" [label="发现问题,\n修复后重新调度"]; + "规格审查通过?" -> "用户审查规格?" [label="通过"]; + "用户审查规格?" -> "编写设计文档" [label="要求修改"]; + "用户审查规格?" -> "调用 writing-plans 技能" [label="批准"]; +} +``` + +**终止状态是调用 writing-plans。** 不要调用 frontend-design、mcp-builder 或任何其他实现技能。头脑风暴之后你唯一要调用的技能是 writing-plans。 + +## 流程详述 + +**理解想法:** + +- 首先查看当前项目状态(文件、文档、最近的 commit) +- 在提出详细问题之前,先评估范围:如果需求描述了多个独立子系统(例如"构建一个包含聊天、文件存储、计费和分析的平台"),立即指出这一点。不要花时间用问题去细化一个需要先拆分的项目。 +- 如果项目规模过大,单个规格说明无法覆盖,帮助用户分解为子项目:有哪些独立的部分,它们之间有什么关系,应该按什么顺序构建?然后通过正常的设计流程进行第一个子项目的头脑风暴。每个子项目都有自己的规格 → 计划 → 实现周期。 +- 对于范围适当的项目,每次提一个问题来完善想法 +- 尽量使用选择题,开放式问题也可以 +- 每条消息只提一个问题——如果一个主题需要更多探索,拆分成多个问题 +- 重点理解:目的、约束、成功标准 + +**探索方案:** + +- 提出 2-3 种不同的方案及其权衡 +- 以对话的方式展示选项,附上你的推荐和理由 +- 先展示你推荐的方案并解释原因 + +**展示设计:** + +- 一旦你认为理解了要构建的内容,就展示设计 +- 每个部分的篇幅与其复杂度匹配:简单的几句话,复杂的最多 200-300 字 +- 每个部分展示后询问是否正确 +- 涵盖:架构、组件、数据流、错误处理、测试 +- 随时准备回头澄清不明确的地方 + +**面向隔离和清晰的设计:** + +- 将系统拆分为更小的单元,每个单元有一个明确的职责,通过定义良好的接口通信,可以独立理解和测试 +- 对于每个单元,你应该能回答:它做什么,如何使用,它依赖什么? +- 别人能否不看内部实现就理解一个单元的功能?你能否在不影响调用者的情况下修改内部实现?如果不能,边界需要调整。 +- 更小、边界清晰的单元也更便于你工作——你对能一次放入上下文的代码推理得更好,文件越专注你的编辑越可靠。当文件变大时,这通常意味着它承担了过多职责。 + +**在现有代码库中工作:** + +- 在提出更改之前先探索现有结构。遵循现有模式。 +- 如果现有代码存在影响当前工作的问题(例如文件过大、边界不清、职责纠缠),在设计中包含有针对性的改进——就像一个优秀的开发者在工作中改进经手的代码一样。 +- 不要提议无关的重构。专注于服务当前目标的事情。 + +## 设计之后 + +**文档:** + +- 将验证通过的设计(规格说明)写入 `docs/superpowers/specs/YYYY-MM-DD--design.md` + - (用户对规格位置的偏好优先于此默认值) +- 如果可用,使用 elements-of-style:writing-clearly-and-concisely 技能 +- 将设计文档 commit 到 git + +**规格审查循环:** +编写规格文档后: + +1. 调度 spec-document-reviewer 子代理(参见 spec-document-reviewer-prompt.md) +2. 如果发现问题:修复,重新调度,重复直到通过 +3. 如果循环超过 3 次迭代,交给人工指导 + +**用户审查关卡:** +规格审查循环通过后,请用户在继续之前审查书面规格: + +> "规格已编写并 commit 到 ``。请审查一下,如果在我们开始编写实现计划之前你想做任何修改,请告诉我。" + +等待用户回复。如果他们要求修改,做出修改并重新运行规格审查循环。只有在用户批准后才继续。 + +**实现:** + +- 调用 writing-plans 技能创建详细的实现计划 +- 不要调用任何其他技能。writing-plans 是下一步。 + +## 核心原则 + +- **每次一个问题** — 不要同时抛出多个问题 +- **优先选择题** — 在可能的情况下比开放式问题更容易回答 +- **严格遵循 YAGNI** — 从所有设计中移除不必要的功能 +- **探索替代方案** — 在做决定之前始终提出 2-3 种方案 +- **增量验证** — 展示设计,获得批准后再继续 +- **保持灵活** — 有不明确的地方就回头澄清 + +## 视觉伴侣 + +一个基于浏览器的伴侣工具,用于在头脑风暴过程中展示原型、图表和视觉选项。它是一个工具——不是一种模式。接受伴侣意味着它可用于适合视觉呈现的问题;并不意味着每个问题都要通过浏览器。 + +**提供伴侣:** 当你预计后续问题会涉及视觉内容(原型、布局、图表)时,提供一次以获得同意: +> "我们接下来讨论的一些内容,如果能在浏览器中展示给你看可能会更直观。我可以在讨论过程中为你制作原型、图表、对比图和其他视觉材料。这个功能还比较新,可能会消耗较多 token。要试试吗?(需要打开一个本地 URL)" + +**此提议必须是一条独立的消息。** 不要将它与澄清问题、上下文摘要或任何其他内容合并。消息中应该只包含上述提议,没有其他内容。等待用户回复后再继续。如果他们拒绝,继续纯文本的头脑风暴。 + +**逐问题决策:** 即使用户接受了,也要对每个问题单独决定是使用浏览器还是终端。判断标准:**用户看到它是否比读到它更容易理解?** + +- **使用浏览器** 展示本身就是视觉的内容——原型、线框图、布局对比、架构图、并排视觉设计 +- **使用终端** 展示文本内容——需求问题、概念选择、权衡列表、A/B/C/D 文字选项、范围决策 + +关于 UI 主题的问题不一定是视觉问题。"在这个上下文中个性化是什么意思?"是一个概念问题——使用终端。"哪种向导布局更好?"是一个视觉问题——使用浏览器。 + +如果他们同意使用伴侣,在继续之前阅读详细指南: +`skills/brainstorming/visual-companion.md` diff --git a/skills/brainstorming/scripts/frame-template.html b/skills/brainstorming/scripts/frame-template.html new file mode 100644 index 0000000..dcfe018 --- /dev/null +++ b/skills/brainstorming/scripts/frame-template.html @@ -0,0 +1,214 @@ + + + + + Superpowers Brainstorming + + + +
+

Superpowers Brainstorming

+
Connected
+
+ +
+
+ +
+
+ +
+ Click an option above, then return to the terminal +
+ + + diff --git a/skills/brainstorming/scripts/helper.js b/skills/brainstorming/scripts/helper.js new file mode 100644 index 0000000..111f97f --- /dev/null +++ b/skills/brainstorming/scripts/helper.js @@ -0,0 +1,88 @@ +(function() { + const WS_URL = 'ws://' + window.location.host; + let ws = null; + let eventQueue = []; + + function connect() { + ws = new WebSocket(WS_URL); + + ws.onopen = () => { + eventQueue.forEach(e => ws.send(JSON.stringify(e))); + eventQueue = []; + }; + + ws.onmessage = (msg) => { + const data = JSON.parse(msg.data); + if (data.type === 'reload') { + window.location.reload(); + } + }; + + ws.onclose = () => { + setTimeout(connect, 1000); + }; + } + + 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 + }); + + // Update indicator bar (defer so toggleSelect runs first) + setTimeout(() => { + const indicator = document.getElementById('indicator-text'); + if (!indicator) return; + const container = target.closest('.options') || target.closest('.cards'); + const selected = container ? container.querySelectorAll('.selected') : []; + if (selected.length === 0) { + indicator.textContent = 'Click an option above, then return to the terminal'; + } else if (selected.length === 1) { + const label = selected[0].querySelector('h3, .content h3, .card-body h3')?.textContent?.trim() || selected[0].dataset.choice; + indicator.innerHTML = '' + label + ' selected — return to terminal to continue'; + } else { + indicator.innerHTML = '' + selected.length + ' selected — return to terminal to continue'; + } + }, 0); + }); + + // 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(); +})(); diff --git a/skills/brainstorming/scripts/server.cjs b/skills/brainstorming/scripts/server.cjs new file mode 100644 index 0000000..86c3080 --- /dev/null +++ b/skills/brainstorming/scripts/server.cjs @@ -0,0 +1,338 @@ +const crypto = require('crypto'); +const http = require('http'); +const fs = require('fs'); +const path = require('path'); + +// ========== WebSocket Protocol (RFC 6455) ========== + +const OPCODES = { TEXT: 0x01, CLOSE: 0x08, PING: 0x09, PONG: 0x0A }; +const WS_MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; + +function computeAcceptKey(clientKey) { + return crypto.createHash('sha1').update(clientKey + WS_MAGIC).digest('base64'); +} + +function encodeFrame(opcode, payload) { + const fin = 0x80; + const len = payload.length; + let header; + + if (len < 126) { + header = Buffer.alloc(2); + header[0] = fin | opcode; + header[1] = len; + } else if (len < 65536) { + header = Buffer.alloc(4); + header[0] = fin | opcode; + header[1] = 126; + header.writeUInt16BE(len, 2); + } else { + header = Buffer.alloc(10); + header[0] = fin | opcode; + header[1] = 127; + header.writeBigUInt64BE(BigInt(len), 2); + } + + return Buffer.concat([header, payload]); +} + +function decodeFrame(buffer) { + if (buffer.length < 2) return null; + + const secondByte = buffer[1]; + const opcode = buffer[0] & 0x0F; + const masked = (secondByte & 0x80) !== 0; + let payloadLen = secondByte & 0x7F; + let offset = 2; + + if (!masked) throw new Error('Client frames must be masked'); + + if (payloadLen === 126) { + if (buffer.length < 4) return null; + payloadLen = buffer.readUInt16BE(2); + offset = 4; + } else if (payloadLen === 127) { + if (buffer.length < 10) return null; + payloadLen = Number(buffer.readBigUInt64BE(2)); + offset = 10; + } + + const maskOffset = offset; + const dataOffset = offset + 4; + const totalLen = dataOffset + payloadLen; + if (buffer.length < totalLen) return null; + + 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 }; +} + +// ========== Configuration ========== + +const PORT = process.env.BRAINSTORM_PORT || (49152 + Math.floor(Math.random() * 16383)); +const HOST = process.env.BRAINSTORM_HOST || '127.0.0.1'; +const URL_HOST = process.env.BRAINSTORM_URL_HOST || (HOST === '127.0.0.1' ? 'localhost' : HOST); +const SCREEN_DIR = process.env.BRAINSTORM_DIR || '/tmp/brainstorm'; +const OWNER_PID = process.env.BRAINSTORM_OWNER_PID ? Number(process.env.BRAINSTORM_OWNER_PID) : null; + +const MIME_TYPES = { + '.html': 'text/html', '.css': 'text/css', '.js': 'application/javascript', + '.json': 'application/json', '.png': 'image/png', '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.svg': 'image/svg+xml' +}; + +// ========== Templates and Constants ========== + +const WAITING_PAGE = ` + +Brainstorm Companion + + +

Brainstorm Companion

+

Waiting for the agent to push a screen...

`; + +const frameTemplate = fs.readFileSync(path.join(__dirname, 'frame-template.html'), 'utf-8'); +const helperScript = fs.readFileSync(path.join(__dirname, 'helper.js'), 'utf-8'); +const helperInjection = ''; + +// ========== Helper Functions ========== + +function isFullDocument(html) { + const trimmed = html.trimStart().toLowerCase(); + return trimmed.startsWith('', content); +} + +function getNewestScreen() { + const files = fs.readdirSync(SCREEN_DIR) + .filter(f => f.endsWith('.html')) + .map(f => { + const fp = path.join(SCREEN_DIR, f); + return { path: fp, mtime: fs.statSync(fp).mtime.getTime() }; + }) + .sort((a, b) => b.mtime - a.mtime); + return files.length > 0 ? files[0].path : null; +} + +// ========== HTTP Request Handler ========== + +function handleRequest(req, res) { + touchActivity(); + if (req.method === 'GET' && req.url === '/') { + const screenFile = getNewestScreen(); + let html = screenFile + ? (raw => isFullDocument(raw) ? raw : wrapInFrame(raw))(fs.readFileSync(screenFile, 'utf-8')) + : WAITING_PAGE; + + if (html.includes('')) { + html = html.replace('', helperInjection + '\n'); + } else { + html += helperInjection; + } + + res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); + res.end(html); + } else if (req.method === 'GET' && req.url.startsWith('/files/')) { + const fileName = req.url.slice(7); + const filePath = path.join(SCREEN_DIR, path.basename(fileName)); + if (!fs.existsSync(filePath)) { + res.writeHead(404); + res.end('Not found'); + return; + } + const ext = path.extname(filePath).toLowerCase(); + const contentType = MIME_TYPES[ext] || 'application/octet-stream'; + res.writeHead(200, { 'Content-Type': contentType }); + res.end(fs.readFileSync(filePath)); + } else { + res.writeHead(404); + res.end('Not found'); + } +} + +// ========== WebSocket Connection Handling ========== + +const clients = new Set(); + +function handleUpgrade(req, socket) { + const key = req.headers['sec-websocket-key']; + if (!key) { socket.destroy(); return; } + + const accept = computeAcceptKey(key); + socket.write( + 'HTTP/1.1 101 Switching Protocols\r\n' + + 'Upgrade: websocket\r\n' + + 'Connection: Upgrade\r\n' + + 'Sec-WebSocket-Accept: ' + accept + '\r\n\r\n' + ); + + let buffer = Buffer.alloc(0); + clients.add(socket); + + socket.on('data', (chunk) => { + buffer = Buffer.concat([buffer, chunk]); + while (buffer.length > 0) { + let result; + try { + result = decodeFrame(buffer); + } catch (e) { + socket.end(encodeFrame(OPCODES.CLOSE, Buffer.alloc(0))); + clients.delete(socket); + return; + } + if (!result) break; + buffer = buffer.slice(result.bytesConsumed); + + switch (result.opcode) { + case OPCODES.TEXT: + handleMessage(result.payload.toString()); + break; + case OPCODES.CLOSE: + socket.end(encodeFrame(OPCODES.CLOSE, Buffer.alloc(0))); + clients.delete(socket); + return; + case OPCODES.PING: + socket.write(encodeFrame(OPCODES.PONG, result.payload)); + break; + case OPCODES.PONG: + break; + default: { + const closeBuf = Buffer.alloc(2); + closeBuf.writeUInt16BE(1003); + socket.end(encodeFrame(OPCODES.CLOSE, closeBuf)); + clients.delete(socket); + return; + } + } + } + }); + + socket.on('close', () => clients.delete(socket)); + socket.on('error', () => clients.delete(socket)); +} + +function handleMessage(text) { + let event; + try { + event = JSON.parse(text); + } catch (e) { + console.error('Failed to parse WebSocket message:', e.message); + return; + } + 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'); + } +} + +function broadcast(msg) { + const frame = encodeFrame(OPCODES.TEXT, Buffer.from(JSON.stringify(msg))); + for (const socket of clients) { + try { socket.write(frame); } catch (e) { clients.delete(socket); } + } +} + +// ========== Activity Tracking ========== + +const IDLE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes +let lastActivity = Date.now(); + +function touchActivity() { + lastActivity = Date.now(); +} + +// ========== File Watching ========== + +const debounceTimers = new Map(); + +// ========== Server Startup ========== + +function startServer() { + if (!fs.existsSync(SCREEN_DIR)) fs.mkdirSync(SCREEN_DIR, { recursive: true }); + + // Track known files to distinguish new screens from updates. + // macOS fs.watch reports 'rename' for both new files and overwrites, + // so we can't rely on eventType alone. + const knownFiles = new Set( + fs.readdirSync(SCREEN_DIR).filter(f => f.endsWith('.html')) + ); + + const server = http.createServer(handleRequest); + server.on('upgrade', handleUpgrade); + + const watcher = fs.watch(SCREEN_DIR, (eventType, filename) => { + if (!filename || !filename.endsWith('.html')) return; + + if (debounceTimers.has(filename)) clearTimeout(debounceTimers.get(filename)); + debounceTimers.set(filename, setTimeout(() => { + debounceTimers.delete(filename); + const filePath = path.join(SCREEN_DIR, filename); + + if (!fs.existsSync(filePath)) return; // file was deleted + touchActivity(); + + if (!knownFiles.has(filename)) { + knownFiles.add(filename); + const eventsFile = path.join(SCREEN_DIR, '.events'); + if (fs.existsSync(eventsFile)) fs.unlinkSync(eventsFile); + console.log(JSON.stringify({ type: 'screen-added', file: filePath })); + } else { + console.log(JSON.stringify({ type: 'screen-updated', file: filePath })); + } + + broadcast({ type: 'reload' }); + }, 100)); + }); + watcher.on('error', (err) => console.error('fs.watch error:', err.message)); + + function shutdown(reason) { + console.log(JSON.stringify({ type: 'server-stopped', reason })); + const infoFile = path.join(SCREEN_DIR, '.server-info'); + if (fs.existsSync(infoFile)) fs.unlinkSync(infoFile); + fs.writeFileSync( + path.join(SCREEN_DIR, '.server-stopped'), + JSON.stringify({ reason, timestamp: Date.now() }) + '\n' + ); + watcher.close(); + clearInterval(lifecycleCheck); + server.close(() => process.exit(0)); + } + + function ownerAlive() { + if (!OWNER_PID) return true; + try { process.kill(OWNER_PID, 0); return true; } catch (e) { return false; } + } + + // Check every 60s: exit if owner process died or idle for 30 minutes + const lifecycleCheck = setInterval(() => { + if (!ownerAlive()) shutdown('owner process exited'); + else if (Date.now() - lastActivity > IDLE_TIMEOUT_MS) shutdown('idle timeout'); + }, 60 * 1000); + lifecycleCheck.unref(); + + server.listen(PORT, HOST, () => { + const info = JSON.stringify({ + type: 'server-started', port: Number(PORT), host: HOST, + url_host: URL_HOST, url: 'http://' + URL_HOST + ':' + PORT, + screen_dir: SCREEN_DIR + }); + console.log(info); + fs.writeFileSync(path.join(SCREEN_DIR, '.server-info'), info + '\n'); + }); +} + +if (require.main === module) { + startServer(); +} + +module.exports = { computeAcceptKey, encodeFrame, decodeFrame, OPCODES }; diff --git a/skills/brainstorming/scripts/start-server.sh b/skills/brainstorming/scripts/start-server.sh new file mode 100755 index 0000000..a0ef299 --- /dev/null +++ b/skills/brainstorming/scripts/start-server.sh @@ -0,0 +1,153 @@ +#!/usr/bin/env bash +# Start the brainstorm server and output connection info +# Usage: start-server.sh [--project-dir ] [--host ] [--url-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 Store session files under /.superpowers/brainstorm/ +# instead of /tmp. Files persist after server stops. +# --host Host/interface to bind (default: 127.0.0.1). +# Use 0.0.0.0 in remote/containerized environments. +# --url-host Hostname shown in returned URL JSON. +# --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="" +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 + ;; + --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 + +# 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 + case "${OSTYPE:-}" in + msys*|cygwin*|mingw*) FOREGROUND="true" ;; + esac + if [[ -n "${MSYSTEM:-}" ]]; then + FOREGROUND="true" + fi +fi + +# Generate unique session directory +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 + +PID_FILE="${SCREEN_DIR}/.server.pid" +LOG_FILE="${SCREEN_DIR}/.server.log" + +# Create fresh session directory +mkdir -p "$SCREEN_DIR" + +# 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" + +# 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 + +# On Windows/MSYS2, the MSYS2 PID namespace is invisible to Node.js. +# Skip owner-PID monitoring — the 30-minute idle timeout prevents orphans. +case "${OSTYPE:-}" in + msys*|cygwin*|mingw*) OWNER_PID="" ;; +esac + +# Foreground mode for environments that reap detached/background processes. +if [[ "$FOREGROUND" == "true" ]]; then + echo "$$" > "$PID_FILE" + env BRAINSTORM_DIR="$SCREEN_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.cjs + 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="$SCREEN_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.cjs > "$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 i 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 diff --git a/skills/brainstorming/scripts/stop-server.sh b/skills/brainstorming/scripts/stop-server.sh new file mode 100755 index 0000000..2e5973d --- /dev/null +++ b/skills/brainstorming/scripts/stop-server.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# Stop the brainstorm server and clean up +# Usage: stop-server.sh +# +# 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. + +SCREEN_DIR="$1" + +if [[ -z "$SCREEN_DIR" ]]; then + echo '{"error": "Usage: stop-server.sh "}' + exit 1 +fi + +PID_FILE="${SCREEN_DIR}/.server.pid" + +if [[ -f "$PID_FILE" ]]; then + pid=$(cat "$PID_FILE") + + # Try to stop gracefully, fallback to force if still alive + kill "$pid" 2>/dev/null || true + + # Wait for graceful shutdown (up to ~2s) + for i 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" "${SCREEN_DIR}/.server.log" + + # Only delete ephemeral /tmp directories + if [[ "$SCREEN_DIR" == /tmp/* ]]; then + rm -rf "$SCREEN_DIR" + fi + + echo '{"status": "stopped"}' +else + echo '{"status": "not_running"}' +fi diff --git a/skills/brainstorming/spec-document-reviewer-prompt.md b/skills/brainstorming/spec-document-reviewer-prompt.md new file mode 100644 index 0000000..7e61864 --- /dev/null +++ b/skills/brainstorming/spec-document-reviewer-prompt.md @@ -0,0 +1,48 @@ +# 规格文档审查员提示模板 + +调度规格文档审查员子代理时使用此模板。 + +**用途:** 验证规格是否完整、一致,并为实现计划做好准备。 + +**调度时机:** 规格文档写入 docs/superpowers/specs/ 之后 + +``` +Task tool(通用): + description: "审查规格文档" + prompt: | + 你是一名规格文档审查员。验证此规格是否完整并准备好进行计划编写。 + + **待审查规格:** [SPEC_FILE_PATH] + + ## 检查内容 + + | 类别 | 检查要点 | + |------|----------| + | 完整性 | TODO、占位符、"TBD"、不完整的章节 | + | 一致性 | 内部矛盾、相互冲突的需求 | + | 清晰度 | 需求模糊到可能导致构建出错误的东西 | + | 范围 | 是否足够聚焦以用于单个计划——而非涵盖多个独立子系统 | + | YAGNI | 未请求的功能、过度设计 | + + ## 校准标准 + + **只标记会在实现计划阶段造成实际问题的事项。** + 缺失的章节、矛盾之处、或者模糊到可能被两种不同方式理解的需求—— + 这些才是问题。措辞上的小改进、风格偏好、以及"某些章节不如其他章节详细"则不是。 + + 除非存在会导致计划出错的严重缺陷,否则应予以通过。 + + ## 输出格式 + + ## 规格审查 + + **状态:** 通过 | 发现问题 + + **问题(如有):** + - [章节 X]:[具体问题] - [为什么这对计划编写很重要] + + **建议(仅供参考,不阻止通过):** + - [改进建议] +``` + +**审查员返回:** 状态、问题(如有)、建议 diff --git a/skills/brainstorming/visual-companion.md b/skills/brainstorming/visual-companion.md new file mode 100644 index 0000000..287c87c --- /dev/null +++ b/skills/brainstorming/visual-companion.md @@ -0,0 +1,286 @@ +# 视觉伴侣指南 + +基于浏览器的视觉头脑风暴伴侣,用于展示原型、图表和选项。 + +## 何时使用 + +逐问题决定,而非按会话决定。判断标准:**用户看到它是否比读到它更容易理解?** + +**使用浏览器** 当内容本身是视觉的: + +- **UI 原型** — 线框图、布局、导航结构、组件设计 +- **架构图** — 系统组件、数据流、关系图 +- **并排视觉对比** — 对比两种布局、两种配色方案、两种设计方向 +- **设计细节打磨** — 当问题涉及外观感受、间距、视觉层次 +- **空间关系** — 状态机、流程图、实体关系图 + +**使用终端** 当内容是文字或表格的: + +- **需求和范围问题** — "X 是什么意思?"、"哪些功能在范围内?" +- **概念性 A/B/C 选择** — 在用文字描述的方案之间做选择 +- **权衡列表** — 优缺点、对比表 +- **技术决策** — API 设计、数据建模、架构方案选择 +- **澄清问题** — 任何回答是文字而非视觉偏好的问题 + +关于 UI 主题的问题不一定是视觉问题。"你想要什么样的向导?"是概念性的——使用终端。"这些向导布局中哪个感觉对?"是视觉性的——使用浏览器。 + +## 工作原理 + +服务器监视一个目录中的 HTML 文件,将最新的文件提供给浏览器。你写入 HTML 内容,用户在浏览器中看到它,并可以点击选择选项。选择结果被记录到一个 `.events` 文件中,你在下一轮会话中读取它。 + +**内容片段 vs 完整文档:** 如果你的 HTML 文件以 `/.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 +``` + +**其他环境:** 服务器必须在会话轮次之间持续在后台运行。如果你的环境会回收分离的进程,使用 `--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 + +
+

在终端中继续...

+
+ ``` + + 这样可以防止用户盯着一个已经解决的选择,而对话已经继续了。当下一个视觉问题出现时,照常推送新的内容文件。 + +6. 重复直到完成。 + +## 编写内容片段 + +只写放在页面内部的内容。服务器会自动用框架模板包裹它(头部、主题 CSS、选择指示器和所有交互基础设施)。 + +**最简示例:** + +```html +

哪种布局更好?

+

考虑可读性和视觉层次

+ +
+
+
A
+
+

单栏

+

简洁、专注的阅读体验

+
+
+
+
B
+
+

双栏

+

侧边栏导航加主内容区

+
+
+
+``` + +就这些。不需要 ``,不需要 CSS,不需要 `