Vibe Coding 多人游戏(二十六)—— OpenClaw 工具链全景
23 个 Skill 全家桶
开发类:
| Skill | 作用 |
|---|---|
| gts-dev-feat | 新功能开发——调度 OpenCode 生成代码 |
| gts-dev-fix | Bug 修复——分析日志 + 锁定测试 + 修复 |
| gts-dev-refactor | 代码重构——不改变行为只改结构 |
三个开发 Skill 的核心区别是入口和出口:
gts-dev-feat:从需求出发,先写 Delta Specs → 确认 → 写代码 → BDD 测试 → 提交。不需要事先分析日志。gts-dev-fix:从 bug 出发,先分析日志 → 定位根因 → 写测试(RED)→ 修代码(GREEN)→ 提交。有一步必须做:分析日志之前不写代码。 有个经典反例——AI 看到 bug 不查日志直接改代码,拍脑袋修了三次都没修好,最后查日志发现是animationName被覆盖了。gts-dev-refactor:从现有代码出发,行为不变,结构变。完成后 BDD 测试全通过才能提交。
测试类:
| Skill | 作用 |
|---|---|
| gts-e2e-test | 手动 E2E 测试(双窗口) |
| gts-e2e-auto | 自动化 E2E scenarios |
| gts-e2e-perf | 性能测试(FPS/CPU 热点) |
| gts-test | BDD 集成测试 |
测试 Skill 的设计理念是:将测试流程固化,AI 不需要思考"怎么测",只要执行。 比如 gts-e2e-test 的流程固定为:重启服务端 → 打开窗口 1 → 打开窗口 2 → 匹配 → 进入游戏 → 验证。每一次步骤都一样,AI 执行久了就像肌肉记忆。
gts-e2e-perf 是后来加的——发现前端性能优化后需要验证 FPS 是否提升,所以专门写了一个 Skill 来做双窗口 + CDP Profiler 录制 + 自动分析。
部署运维类:
| Skill | 作用 |
|---|---|
| gts-deploy | 部署到腾讯云 SCF |
| gts-service | 启动/重启/停止本地服务 |
| gts-logs | 自动抓取 SCF 日志 |
gts-service 是一个特别容易出错的 Skill。因为重启顺序必须正确(先 room 再 match),而且 room 重启后必须等它 ready 才能继续。早期 Skill 的实现是粗暴的 kill -9 && sleep 3 && yarn dev——但 room 不一定 3 秒就启动完。后来改成了健康检查轮询。
代码管理类:
| Skill | 作用 |
|---|---|
| gts-save-flow | 审核→BDD→编译→规格→笔记→记忆→提交 |
| gts-save-memory | 保存 daily log + commit |
| gts-git-commit | git add → commit(或 push) |
| gts-git-pull | 从 GitHub 拉取更新 |
gts-save-flow 是最长的 Skill——全流程跑完大约需要 5-10 分钟。早期它什么都能干(审核+BDD+编译+规格+笔记+记忆+提交),但太长了,AI 在中间容易出错(比如规格更新时忘了更新 workflow-rules.md)。所以拆成了更细的粒度的多个 Skill。
辅助类:
| Skill | 作用 |
|---|---|
| gts-analysis | 分析需求/方案,输出报告 |
| gts-code-review | 代码审查(调度 OpenCode 审) |
| gts-recall | 查记忆+近对话,分析工作进展 |
| gts-stop | 紧急停止一切操作,清理残留进程 |
| gts-conversation-end | 清理服务/定时器,结束对话 |
gts-recall 是最"软"的 Skill。它不生成代码、不跑测试——只查记忆、查近期的对话记录,然后给一个"工作简报":过去几天做了什么、有哪些进展、下一步建议。对于长期项目的回顾特别有用,尤其是兄弟隔了一周没开发,回来不知道从哪继续。
内容生产类:
| Skill | 作用 |
|---|---|
| yuque-jianshu-fetch | 抓取语雀知识库/简书文章转 Markdown 保存到笔记目录 |
| zhihu-auto-publish | CDP 控制 Chrome 粘贴 HTML 到知乎编辑器 |
| cnblogs-publish | MetaWeblog API 自动发布 Markdown 到博客园 |
yuque-jianshu-fetch 解决的是"知识同步"问题——语雀上的技术文章不能直接当本地 Markdown 用。这个 Skill 抓取语雀文档内容 + 简书文章,转为标准 Markdown 格式保存到笔记目录。语雀的 API 需要 OAuth 鉴权,简书不需要登录但需要处理 HTML 转 Markdown 的格式问题。
zhihu-auto-publish 是发布工具——通过 Playwright CDP 连接 Chrome 浏览器实例,打开知乎专栏编辑器粘贴 HTML 内容。知乎编辑器不接受 Markdown 粘贴,必须先转 HTML。这个 Skill 用到纯前端 JS 的 TurndownService(HTML→Markdown 反向工具)来处理格式转换。发布前必须人工检查,因为 CDP 控制浏览器存在死链接和误操作的风险。
cnblogs-publish 走的是博客园的 MetaWeblog API(XML-RPC 协议),纯后端操作,不需要浏览器。它处理 Markdown 渲染——博客园的 Markdown 引擎和标准的 GFM 有差异,需要在发布前用 marked + 自定义 renderer 做一次渲染适配。
搜索类:
| Skill | 作用 |
|---|---|
| web-search | 多源 Web 搜索(firecrawl/searXNG),带去重 |
web-search 是一个看起来简单但其实有坑的 Skill。搜索工具(web_search)只返回摘要,不返回完整内容——摘要可能截断了关键信息。所以标准流程是:搜索 → 读摘要筛选 → 用 web_fetch 拉完整页面 → 去重合并。中文搜索还有个额外的问题:同一 query 在不同时间返回不同结果,所以搜索后要尽量把可用的页面都抓下来,减少重复搜索。
通知通道
| 通道 | 用途 | 优先级 |
|---|---|---|
| 桌面通知(msg *) | 需要兄弟确认时 | 最高 |
| 飞书通知 | 日常通知 | 正常 |
| 聊天窗口 | 普通消息 | 正常 |
规则:需要兄弟决策时,必须发桌面通知。不能假设对方在聊天工具前。
有一次 AI 完成了一项修复,在飞书上给我留言"已修复,请验收"。但我在忙别的事没有看飞书——结果 AI 等了两个小时才得到回复。后来在 gts-dev-fix 的结尾加了一步:如果 fix 需要兄弟确认,先发桌面通知(msg * "Bug XXX 已修复,请验收"),再发飞书通知。桌面通知的到达率接近 100%。
入口检查协议
每次收到消息后,第一件事:检查后台任务是否完成。有已完成任务先汇报,再接新活。
这是最高频的违规项——但也是最容易记住的:先查后做,先汇报再接。
入口检查不只是检查子 session——还包括检查 process list 中是否有后台服务在跑。比如 room-service 被 gts-service skill 重启了,但匹配服务还没起来——这时候收到一个"帮我查一下日志"的命令,AI 应该先汇报"服务正在重启中",而不是去查日志(查到的也是旧数据)。
这条入口检查协议是用几个惨痛教训换来的。有一次 AI 在 room-service 重启过程中开始查日志——查到的日志是重启前的旧日志,AI 基于旧日志做了错误判断,又一个下午白费了。
Skill 的演化过程
Skill 不是一次写成的。每个 Skill 都经历了"粗→精"的过程:
以 gts-dev-fix 为例:
- v1:直接让 AI 修 bug,没有规范 → AI 修了 3 次才修好
- v2:加了"先分析日志再改代码" → 修复成功率提升到 70%
- v3:加了"先写测试(RED)再修代码(GREEN)" → 修复后回归,BDD 能拦住
- v4:加了"代码审核"步骤 → 修复后代码质量过关
每个 v 都需要 1-2 天的实际使用,才能发现痛点、迭代优化。16 个 Skill 在 30 天内迭代完毕——平均 2 天一个 Skill,每个迭代 1-2 次。
附录:核心 Skill 完整定义
以下为 6 个核心 Skill 的完整 SKILL.md 文件内容:
gts-acceptance
--- name: "gts-acceptance" description: "循环改→测→验直到通过或超时,支持多问题顺序修复,TDD集成+E2E验收,通过后自动部署" ---验收流程(硬性操作规程)
> 触发词:兄弟说「验收:<标准>」或「验收」。
> 自动循环模式:Specs 发现 → 逐个问题走 TDD 修复(注入 Specs)→ 全量集成测试 → Specs 覆盖验证 → E2E 自动验收 → 自动部署 → 双通道通知(含覆盖统计)。
> 全程自动化:执行gts-dev-fix时不需要兄弟确认。
> ⚠️ 验收结束时必须执行双通道通知,禁止只跑完不说话。
两级测试流程规则(本 Skill 所有步骤共用)
区分
层级 工具 成本 验证内容 集成测试 BDD / jest 低(秒级) 代码层逻辑,纯函数/逻辑层/API 流程 自动测试 E2E Playwright 高(分钟级) 浏览器层完整渲染 + 用户交互 流程纪律
集成测试 > E2E 自动测试 — 因为集成测试成本更低,应优先使用。
Bug → 加日志 → 写/改集成测试复现 bug → 收集数据 → 调度 OpenCode Pro 分析根因 → 修改测试使其按根因场景真实失败(RED) → 调度 OpenCode 修复代码 → 集成测试全绿(GREEN) → 调度 OpenCode 写 E2E 测试脚本 → 跑 E2E → ✅ E2E 通过 → 自动部署 → 双通道通知 → ❌ E2E 失败 → 调度 OpenCode Pro 分析原因 → 修 → 重测
### 线上 bug 的特殊处理
E2E 只跑线上环境,本地不跑。
类型
处理
线上 bug
线上 E2E 复现 → 截图+日志 → 调度 OpenCode Pro 分析
本地 bug
本地 E2E 复现
线上部署/环境 bug
线上 E2E
通知协议(本 Skill 所有步骤共用)
验收结束时执行双通道通知:
1️⃣ 桌面通知
msg * "<30字摘要>"
</code></pre>
<h3>2️⃣ 飞书通知(可选,仅在线 bug 或部署时)</h3>
<pre><code class="language-json">{
"kind": "agentTurn",
"message": "<通知内容(≤10字)>",
"delivery": { "mode": "announce", "channel": "feishu", "to": "user:ou_..." }
}
</code></pre>
<h3>通知纪律</h3>
<ul>
<li>飞书通知内容 <strong>≤10 字</strong></li>
<li>桌面通知必须调用 <code>msg *</code></li>
<li>结果通知在验收结束或部署完成时发送</li>
</ul>
<hr>
<h2>E2E 验收协议</h2>
<h3>适用的测试脚本</h3>
<ul>
<li>E2E headless(CI 用):<code>test/e2e/auto/*.cjs</code></li>
<li>E2E headful(眼睛看):用 Playwright <code>headless: false</code></li>
<li>验收流程 <strong>优先用 headless</strong>,不打断不确认</li>
</ul>
<h3>脚本结构</h3>
<pre><code>登录 → 创建房间 → 加入 → 准备 → 开始游戏 → 操作 → DebugEndGame → 验证 UI 元素
→ 关闭弹窗 → 返回房间 → 再次进入 → 截图
</code></pre>
<hr>
<h2> TDD 纪律 — 先让测试失败(2026-07-06 新增)</h2>
<p>验收流程中必须先让集成测试因 bug 真实失败,再修复代码使其通过。</p>
<ul>
<li><strong>禁止用模拟函数代替实际代码做集成测试</strong></li>
<li><strong>正确做法</strong>:测试必须直接调用被测试的实际代码/组件,在不修复时因 bug 真实 ❌ 失败</li>
<li>跟「先汇报再继续」「等确认发通知」「入口检查」同级最高优先级</li>
</ul>
<hr>
<h2>流程(流水线 — 自动执行,不需要兄弟确认)</h2>
<h3>Step A: 识别问题</h3>
<ul>
<li>区分场景:本地验收 / 线上 bug</li>
<li>依据 bug 类型决定测试策略</li>
</ul>
<h3>Step B: 集成测试(RED)</h3>
<ul>
<li>根因分析 → 调度 OpenCode Pro(根因分析交给 OpenCode Pro)</li>
<li>创建/修改 BDD 集成测试(.feature + .steps.ts),调用实际代码</li>
<li>确认测试因 bug 真实失败(RED ❌)</li>
</ul>
<h3>Step C: 修复(GREEN)</h3>
<ul>
<li>调度 OpenCode 修复代码</li>
<li>集成测试全绿 ✅</li>
</ul>
<h3>Step D: E2E 自动验收</h3>
<ul>
<li>运行 Playwright headless E2E 脚本</li>
<li>验证用户可见的 UI 行为</li>
<li>E2E 失败 → 分析原因 → 修 → 重测</li>
</ul>
<h3>Step E: 自动部署(2026-07-06 新增)</h3>
<ul>
<li>E2E 本地通过后 → <strong>自动部署</strong>,不询问兄弟</li>
<li>部署失败 → 报错,问兄弟要不要修</li>
<li>规则来源:兄弟明确说「以后本地e2e测试通过后,自动去部署,不要问我」</li>
</ul>
<h3>Step F: 输出结果 + 双通道通知</h3>
<p><strong>通知时机:</strong> 验收结束时立即执行。</p>
<table>
<thead>
<tr>
<th>结果</th>
<th>桌面消息</th>
<th>飞书通知(≤10字)</th>
</tr>
</thead>
<tbody><tr>
<td>E2E 全绿 + 部署成功</td>
<td>✅ ✅</td>
<td><code>验收通过+已部署</code></td>
</tr>
<tr>
<td>E2E 失败</td>
<td>❌</td>
<td><code>验收未通过</code></td>
</tr>
<tr>
<td>部署失败</td>
<td>⚠️ 部署失败</td>
<td><code>部署失败</code></td>
</tr>
</tbody></table>
<p>通知内容必须包含:</p>
<ul>
<li>E2E 结果(全绿/失败)</li>
<li>测试文件/功能</li>
<li>部署结果</li>
</ul>
<hr>
<h2>验收优化记录</h2>
<h3>历史教训(重要!)</h3>
<ol>
<li><strong>不要模拟</strong>:集成测试不要 mock 实际代码(replayState / MockWsClient),必须调用 <code>readState</code> / <code>writeState</code> 真实操作</li>
<li><strong>验收流程全程自动化</strong>:E2E 期间不打断不确认,不要问「我要跑了哦?」</li>
<li><strong>验收结束必须通知</strong>:禁止只跑完不说话(2026-06-26 规则收紧)</li>
<li><strong>TDD 纪律</strong>:必须先 RED 失败再 GREEN,禁止跳过(2026-07-06 新增)</li>
<li><strong>根因分析交给 OpenCode Pro</strong>:不自己分析根因(2026-07-06 新增)</li>
<li><strong>E2E 通过后自动部署</strong>:不询问,直接部署(2026-07-06 兄弟要求)</li>
</ol>
<hr>
<h2>附录:多 bug 顺序修复</h2>
<p>如果兄弟报告了多个 bug,用一个验收流程按顺序逐个修复:</p>
<ol>
<li>所有 bug 列清单</li>
<li>按兄弟指定的顺序逐个走 Step B→C→D→E</li>
<li>修复完最后一个再通知</li>
</ol>
<h2>附录:参考文件</h2>
<ul>
<li>E2E helper: <code>test/e2e/e2e-helpers.cjs</code></li>
<li>现有 E2E: <code>test/e2e/auto/*.cjs</code></li>
</ul>
<pre><code>
### gts-e2e-test
```markdown
---
name: "gts-e2e-test"
description: "兄弟说「e2e测试」时触发。手动双窗口,列出可选 scenarios 供选择。线上测试跳过本地服务。"
---
# E2E 手动测试(硬性操作规程)
> 触发词:兄弟说「e2e测试」/「e2e」/「e2e test」,可附带操作描述和验证目标。
> 双窗口手动操作,可选注入专用日志,停止后抓数据验证。
---
## 步骤
### Step 0:判断环境 + 重启服务 + 部署检查
**先判断测试目标:**
#### SCF 线上环境(脚本含 `scf`、URL 以 `.tcloudbaseapp.com` 结尾等)
1. **跳过本地服务重启**
2. **检查部署状态**:运行 `git diff HEAD -- packages/frontend/src/ packages/room-service/src/ packages/match-service/src/ packages/logic/src/` 检查是否有未部署的生产代码改动
- 如有改动 → 询问兄弟:「有 N 个生产文件待部署,要先部署再测吗?」
- 如兄弟说「部署」→ 走 gts-deploy skill 部署对应服务
- 如兄弟说「不用」→ 跳过,直接测线上现有版本
3. **继续 Step 1**
#### 本地环境
先确认「是 webpack dev server 还是本地 prod」→ 按以下步骤重启:
**前置检查 — 前端服务:**
1. 检查 webpack-dev-server 是否在运行:`Get-Process -Name node | Where-Object { $_.CommandLine -match 'webpack' }`
2. 如未运行 → `cd <project>\packages\frontend && yarn webpack:dev-server` 启动,等启动完成
3. 如已运行 → 跳过,保留现有 webpack 进程
**重启核心服务:**
4. Kill 现有 room (4003) + match (3000) 进程
5. 启动 room-service(`yarn dev`),等启动完成
6. 启动 match-service(`yarn dev`),等连接成功
不重启会导致 match-service WS 失连,游戏卡在"查找房间中"。
### Step 1:解析输入 + 明确验证目标
兄弟可选择指定 scenarios 或附带验收标准。
**每次测试前必须先明确验证目标**:
- 兄弟说清楚要验证什么、预期行为是什么、怎么算通过
- 如果本次测试是在某个功能变更(`feat:/fix:/refactor:`)流程中触发的,验证目标以 `笔记/项目文档/changes/<日期>-<功能名>/spec.md` 中的验证策略为准
- 如果兄弟没有说验证目标,主动问:「这次 E2E 要验证什么?预期行为是什么?」
**无指定时:** 默认跑 `scenarios/perf-scene3.json`。
**指定场景时:** 列出可选 scenarios 供兄弟选择(**动态读取 `test/e2e/scenarios/*.json`**):
</code></pre>
<p>=== 可用 Scenarios ===</p>
<p>[1] 两轮胜利弹窗 — scenarios/gameover-twocycle.json (46块, auto)
[2] SCF 双窗口 — scenarios/scf-twowin.json (22块, manual)
[3] 性能录制 — scenarios/perf-scene3.json (24块, perf)</p>
<p>运行方式:node e2e-runner.cjs scenarios/<name>.json</p>
<pre><code>
所有 scenarios 路径相对于 `packages/frontend/test/e2e/`。
> **每次列出时动态读取** `packages/frontend/test/e2e/scenarios/*.json`。上面的列表是示例,以实际文件为准。
**有验收标准时:** 在选定 scenario 基础上修改或新增 scenario JSON。
**无验收标准时:** 直接跑选定 scenario。
### Step 2:生成并启动测试
**有验收标准:** 以选定的 scenario 为模板修改或新增 JSON,保存到 `test/e2e/scenarios/`。
**无验收标准:** 直接跑选定的 scenario:
</code></pre>
<p>node e2e-runner.cjs scenarios/<name>.json</p>
<pre><code>
以 background 模式运行,**记录 session ID**。`process(action=list)` 确认进程已在运行,然后告知兄弟「开搞」。
> ⚠️ poll 只做状态检查,不拉日志数据。跑完再一次性拉日志分析。
### Step 3:不阻塞等待
- **启动后告知兄弟,不持续等待**
- **兄弟发新消息时**:
- 判断消息是否与当前测试相关(如看日志、分析结果等)→ **保留测试进程**,直接处理
- 与当前测试无关(让我干别的事)→ 先 `process(action=kill, sessionId=<记录id>)` 精准 kill 测试进程,再处理新请求
- **如果测试自然退出(收到 completion event)** → 输出结果分析 + 发通知
### Step 4:结果分析
场景退出后:
1. **日志已自动保存** — saveLogs block 或 saveE2EData 已在停止时自动保存日志
2. **简要分析** — 在会话中输出关键结论
3. **写入变更文档(如当前有变更流程)** — 如果本次测试是在某个功能变更流程中触发的,将测试结论写入对应 `log.md`:
</code></pre>
<h2>E2E 手动验证</h2>
<ul>
<li>时间:<日期></li>
<li>验证目标:<什么行为></li>
<li>结论:通过/失败</li>
<li>失败原因:<如有></li>
</ul>
<pre><code>
### Step 5:双通道通知
分析后发飞书通知(≤10字)+ 桌面消息告知兄弟。
---
## 索引维护规则
> Scenarios 新增/移动/重命名后,**必须同步更新本 SKILL.md 的场景索引表**。
> 运行方式统一为:`node e2e-runner.cjs scenarios/<name>.json`
## 执行纪律
1. **判断测试环境** — SCF 线上跳过 Step 0 服务重启,但需做部署检查
2. Scenarios 在 `test/e2e/scenarios/` 目录下查找(JSON 文件)
3. 默认无指定时跑 perf-scene3
4. 有验收标准时基于 template 修改或新增 scenario JSON
5. 测试过程不阻塞等兄弟
6. 跑完后双通道通知
</code></pre>
<h3>gts-deploy</h3>
<pre><code class="language-markdown">---
name: "gts-deploy"
description: "部署GTS-Play服务(room1/room2/match1/frontend/all)到SCF或静态托管。E2E通过后自动部署不询问"
---
# gts-deploy — 部署 GTS-Play 服务到线上
## 触发词
- `部署`
- `发布`
- `deploy`
- 本地 E2E 测试通过后自动触发(不需要确认)
## 前置条件
- 工作目录:`<project>\packages\meta3d-platform-publish`
- 部署到腾讯云 SCF(服务端)或 CloudBase 静态托管(前端)
- 生产 URL:
- room1: `wss://<appid>-<room1>.ap-shanghai.tencentscf.com?room-id=1`
- room2: `wss://<appid>-<room2>.ap-shanghai.tencentscf.com?room-id=2`
- match: `https://<appid>-<match>.ap-shanghai.tencentscf.com`
## 规则
### 规则1:不要问部署什么服务
- **从改动文件判断受影响的服务**,不询问兄弟
判断逻辑:
| 改动的目录 | 必须部署的服务 |
|-----------|---------------|
| `packages/room-service/` | room1 + room2(两个 SCF 函数都跑同一份代码) |
| `packages/match-service/` | match1 |
| `packages/frontend/` | frontend(CloudBase 静态托管) |
| `packages/logic/` | room1 + room2 + match1(所有服务端) |
| 只改 `packages/room-service/` | room1 + room2 |
| 同时改 `room-service` + `frontend` | room1 + room2 + frontend |
| 同时改 `match-service` + `frontend` | match1 + frontend |
| 同时改所有 | 全部 |
**只部署受影响的**,不多部署不必要的。
### 规则2:E2E 通过后自动部署,不问兄弟
本地 E2E 测试通过后,**立即自动调用 deploy 流程**,不再问兄弟「要不要部署」。
- 流程:E2E 全绿 ✅ → 自动进入 Step 3(执行部署)
- 部署完成后发桌面通知告知结果
- 若部署失败 → 报错并问兄弟是否要修
- 此规则优先于 Step 2 的「等兄弟确认」(自动触发时跳过 Step 2)
## 流程
### Step 1: 判断部署目标
> 不询问,自动判断。
### Step 2: 确认部署(仅手动触发时执行)
- 当兄弟明确说「部署」「发布」「deploy」时 → 直接部署不询问
- 当自动触发时(E2E 通过后)→ **跳过此步**
- **双通道通知**:桌面消息 + 飞书通知(≤10字)
### Step 3: 按目标执行
#### 服务端部署(room1 / room2 / match1)
```bash
# room1
yarn deploy_room1 # build_logic → build_room1 → zip_room1 → deploy-scf.js room1
# room2
yarn deploy_room2 # build_logic → build_room2 → zip_room2 → deploy-scf.js room2
# match1
yarn deploy_match1 # build match-service → zip_match → deploy-scf.js match1
</code></pre>
<p><code>deploy-scf.js</code> 自动做:</p>
<ol>
<li>读取桌面上的对应 zip 文件</li>
<li>base64 编码后调用 SCF <code>UpdateFunctionCode</code> API</li>
<li>等待函数状态变为 Active</li>
<li>调用 <code>UpdateFunctionConfiguration</code> 设置并发/超时/内存等</li>
</ol>
<h4>前端部署(frontend)</h4>
<pre><code class="language-bash">yarn publish_multiplayer_demo_static_test
</code></pre>
<p>gulp task 自动执行:</p>
<ol>
<li><code>mark_test</code> — 标记测试环境</li>
<li><code>update_config</code> — 写入生产配置到 <code>ConfigUtils.ts</code></li>
<li><code>update_multiplayer_config</code> — 设置 <code>Config.ts</code> 为 isProduction:true</li>
<li><code>build_multiplayer_test</code> — webpack_origin 构建前端</li>
<li><code>delete_platform_code_static</code> — 删除 CloudBase 旧文件</li>
<li><code>update_platform_code_static</code> — 上传新文件</li>
</ol>
<h3>Step 4: 验证部署</h3>
<h4>服务端验证</h4>
<ul>
<li><strong>检查 exit code</strong>:gulp task exit code = 0 才算成功</li>
<li><strong>检查 SCF 状态</strong>:可通知兄弟要不要跑 BDD 测试(<code>yarn test</code>)验证</li>
<li><strong>检查错误输出</strong>:如果有 stderr,分析错误原因,给处理方案</li>
</ul>
<h4>前端验证</h4>
<ul>
<li><strong>检查 webpack 构建</strong>:exit code = 0,无编译错误</li>
<li><strong>检查上传</strong>:<code>update_platform_code_static</code> 成功</li>
<li><strong>前端配置还原</strong>:部署后 Config.ts 变为 isProduction:true,如需本地开发则改回 false</li>
<li><strong>快速验证</strong>:问兄弟是否需要在浏览器打开确认</li>
</ul>
<h3>Step 5: 通知结果</h3>
<ul>
<li><strong>桌面通知</strong>(必须):<code>msg * "<30字摘要>"</code></li>
<li><strong>飞书通知</strong>(可选):channel=feishu,≤10字</li>
<li>告知部署成功/失败、验证结论</li>
<li>同时告知 Config.ts 是否被设为 isProduction:true</li>
</ul>
<h2>注意事项</h2>
<ul>
<li>部署前无需重启任何服务(SCF 部署新版本后自动切换)</li>
<li>部署 room 后如需验证,建议跑 BDD 测试但先问兄弟</li>
<li>前端部署后 Config.ts 变成 isProduction:true,若后续本地开发需改回 false</li>
<li>部署失败 → 汇报错误信息,问兄弟是否调度 OpenCode 修复</li>
<li> 不询问兄弟部署什么服务,从改动文件自动判断</li>
<li> E2E 通过后自动部署,不询问确认(2026-07-06 新增)</li>
</ul>
<h2>参考</h2>
<ul>
<li>gulpfile:<code>packages/meta3d-platform-publish/gulpfile.js</code></li>
<li>部署脚本:<code>packages/meta3d-platform-publish/scripts/deploy-scf.js</code></li>
<li>配置参考:<code>frontend/src/logic_layer/MultiplayerUrlConfig.ts</code></li>
</ul>
<pre><code>
### gts-save-flow
```markdown
---
name: "gts-save-flow"
description: "兄弟说「保存」时触发。审核→BDD→编译→规格→笔记→记忆→项目提交推送→GitHub两段同步"
---
# gts-save-flow — 保存协议(硬性操作规程)
> 触发词:兄弟说「保存」(仅二字)。
> ⚠️ git 命令在 `.openclaw/` 根目录执行(不是 workspace 子目录),明确标注目录的除外。
---
## 流程(8+1 步)
兄弟说「保存」后,严格按以下顺序执行每一步:
### Step 0:改动总结
1. 获取上次保存到现在的改动清单:
- 读取 `笔记/决策记录/.last-save` 获取上次保存的 commit SHA
- `git log --oneline <last-save-sha>..HEAD` 列出所有中间 commit
- `git diff <last-save-sha>..HEAD --stat` 列出文件改动统计
- 生成简洁的 **改动摘要**
2. **问题分析**:
- 是否改了服务端代码 → 是否重启了服务?
- 是否改动了 `node_modules` → 是否经兄弟确认?
3. 在会话中输出改动摘要 + 问题预警,不需要发飞书通知
> ⏸️ 等兄弟确认「继续」才走下一步
### Step 1:快速审核改动
- `git status` 看改动文件
- 快速过一遍改动的合理性(防止意外改动混入)
- 有问题 → 通知兄弟确认
### Step 2:跑 BDD 测试
- 如果 `packages/frontend/test/` 或 `packages/frontend/src/` 有改动:
- `cd packages/frontend && npx jest --config jest.multiplayer.json --silent`
- 如果 `packages/room-service/test/` 或 `packages/room-service/src/` 有改动:
- `cd packages/room-service && npx jest --config jest.json --silent`
- 如果 `packages/match-service/test/` 或 `packages/match-service/src/` 有改动:
- `cd packages/match-service && npx jest --config jest.json --silent`
- 测试失败 → 修到全绿再继续
### Step 2.5:编译检查
- 如果 TypeScript 文件有改动,BDD 全绿后追加 `npx tsc --noEmit`
- ⚠️ jest(ts-jest)只转译不做类型检查,必须靠 `tsc` 暴露
- 有错误 → 修到零错误,回头再跑 BDD 确认
### Step 3:规格同步(Specs 文件)
检查本次改动的业务逻辑与 `.feature` 规格文件是否一致:
1. 查看 `笔记/项目文档/changes/` 下活跃变更目录的 specs 文件
2. 对比 git diff 中改动的业务行为与 specs 描述是否匹配
3. 如果新增了业务场景/状态机转换/消息逻辑但无 specs 覆盖 → 新增场景
4. 如果 specs 与实际实现不一致 → 更新 specs
5. 同步更新对应的 `.steps.ts` 测试步骤文件(如需新步骤定义)
6. 如果需要新的状态类型 → 检查 `state/StateType.ts` 是否有对应类型
> 只在改动涉及业务逻辑时执行此步骤,纯文档/配置改动跳过。
> 更新 specs 后重新跑一遍 BDD(Step 2)确认规格与代码一致。
### Step 4:更新笔记
- 看情况更新 `笔记/` 中对应目录:
- 架构级/决策 → `项目文档/` + `决策记录/`
- 重构/功能实现 → `方案/` + `代码笔记/`
- 日常调试 → `讨论记录/`
### Step 5:更新持久记忆
- 更新 `workspace/memory/` 对应日期的文件
- 如果 `MEMORY.md` 中的配置/规则/教训需要更新 → 同时更新
### Step 6:GitHub 同步(三段提交)
> push 步骤可能需FQ,如果失败通知兄弟手动推。
#### Part 0:提交并推送 GTS-Play 项目代码(在项目根目录执行)
```powershell
Set-Location <project>
git add -A
git commit -m "feat|fix|refactor: <改动摘要>"
git push origin dev
</code></pre>
<ul>
<li>推送前检查 <code>git status --short</code> 确保没有意外文件混入</li>
<li><code>webpack.log</code>、截图 PNG、<code>node_modules</code> 等已 gitignore 的不应被包含</li>
<li>如有未跟踪文件混入 → 告知兄弟确认是否加 gitignore</li>
</ul>
<h4>Part 1:last-save(.openclaw 仓库本地提交)</h4>
<p><strong>⚠️ 顺序不可错:先写 <code>.last-save</code>,再 <code>git add</code>,确保包含在 commit 中。</strong></p>
<pre><code class="language-powershell">Set-Location <openclaw-home>
# 1. 获取当前 HEAD SHA
$SHA = git rev-parse HEAD
# 2. 先写入 .last-save(路径:workspace/笔记/决策记录/.last-save)
Set-Content -Path workspace/笔记/决策记录/.last-save -Value $SHA -NoNewline
# 3. 再 stage(此时 .last-save 已在工作区)
git add -A
# 4. 提交
git commit -m "save: <日期> <改动摘要>"
</code></pre>
<h4>Part 2:push 到 GitHub</h4>
<pre><code class="language-powershell">Set-Location <openclaw-home>
git push origin main
</code></pre>
<p>如果 push 失败(网络/FQ问题)→ 通知兄弟手动推。</p>
<hr>
<h2>提交纪律</h2>
<ol>
<li>所有 step 串行执行,不能跳步</li>
<li>Step 2 测试不过必须修,不能跳过</li>
<li>Step 6 三段提交不合并,确保:<ul>
<li>GTS-Play 项目先 commit + push(Part 0)</li>
<li><code>.last-save</code> 留在本地(Part 1)</li>
<li><code>.openclaw</code> push 到 GitHub(Part 2)</li>
</ul>
</li>
<li><strong>必须先写 <code>.last-save</code> 再 <code>git add -A</code></strong>,否则 <code>.last-save</code> 不会被包含在保存提交中</li>
</ol>
<h3>last-save 更新机制</h3>
<ul>
<li>保存成功后,将<strong>当前 HEAD 的完整 SHA</strong> 写入 <code>workspace/笔记/决策记录/.last-save</code> 文件</li>
<li>⚠️ 必须在 <code>git add -A</code> <strong>之前</strong>写入</li>
<li>下次「保存」时读取此文件确定上次保存点</li>
<li>⚠️ 不要用 <code>git tag</code> 或 <code>git stash</code> 记录 last-save,只用 <code>.last-save</code> 文本文件</li>
</ul>
<pre><code>
### opencode-schedule
```markdown
---
name: "opencode-schedule"
description: "OpenCode 调度方式:写 .opencode-brief.md → type pipe → opencode run --attach --no-replay"
---
# opencode-schedule — OpenCode 调度协议
> 所有调度 OpenCode 的 skill 统一引用本协议。
> 不可单独使用,只能被 gts-dev-workflow / gts-dev-fix / gts-dev-feat / gts-dev-refactor / gts-code-review / gts-analysis 等 skill 引用。
## 调度方式(标准模板)
### 1️⃣ 写 brief 文件
```markdown
**文件路径:** `<projectDir>/.opencode-brief.md`
**内容要求:**
- 自动在开头注入 `笔记/项目文档/project-context.md` 的内容(bot 拼接,OpenCode 不自己读)
- 包含方案内容(如有)
- 包含 Delta Specs(如有)
- 包含 TDD 模板(先写测试、再实现)
- 包含集成测试纪律( 先让测试因 bug 真实失败,禁止 mock 函数替代)
- 末尾写「不需要代码审核,代码审核是单独步骤」
</code></pre>
<h3>2️⃣ 调度命令</h3>
<pre><code class="language-powershell"># 简单修复 / 常规功能 / 重构 / Verify — Flash
exec(
command="cd <projectDir> && type .opencode-brief.md | opencode run -m opencode-go/deepseek-v4-flash --dir . --attach http://localhost:4096 --no-replay",
background=true,
timeout=0
)
# 方案 / 架构 / 复杂bug根因分析 — Pro + max
exec(
command="cd <projectDir> && type .opencode-brief.md | opencode run -m opencode-go/deepseek-v4-pro --variant max --dir . --attach http://localhost:4096 --no-replay",
background=true,
timeout=0
)
# 纯方案(plan agent,禁止改代码)
exec(
command="cd <projectDir> && type .opencode-brief.md | opencode run --agent plan -m opencode-go/deepseek-v4-pro --variant max --dir . --attach http://localhost:4096 --no-replay",
background=true,
timeout=0
)
</code></pre>
<h3>3️⃣ 参数说明</h3>
<table>
<thead>
<tr>
<th>参数</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td><code>type .opencode-brief.md |</code></td>
<td>stdin pipe 传入 brief(PS 不支持 <code><</code> 重定向)</td>
</tr>
<tr>
<td><code>-m opencode-go/deepseek-v4-*</code></td>
<td>模型名必须带 provider 前缀 <code>opencode-go/</code></td>
</tr>
<tr>
<td><code>--variant max</code></td>
<td>Pro 专用,最大 reasoning</td>
</tr>
<tr>
<td><code>--agent plan</code></td>
<td>Pro 专用,plan agent 模式(物理禁止改代码)</td>
</tr>
<tr>
<td><code>--dir .</code></td>
<td>工作目录</td>
</tr>
<tr>
<td><code>--attach http://localhost:4096</code></td>
<td>连接到已有 web UI 查看进度</td>
</tr>
<tr>
<td><code>--no-replay</code></td>
<td>不需要回放</td>
</tr>
</tbody></table>
<h3> 4️⃣ poll 步骤(同一回合内连续 poll,不 yield,不等兄弟消息)</h3>
<p><strong>调度 OpenCode 后,第一件事是立即开始 poll 循环</strong>,不是等兄弟发消息再看。</p>
<p><strong>核心原则:不 yield 等兄弟消息。在同一回合内连续 poll,直到完成或达到工具调用上限。</strong></p>
<pre><code class="language-text">Step 1: dispatch OpenCode (background=true)
Step 2: process(action=list) 确认启动,拿到 sessionId
→ 找到 "gentle-haven",状态为 "running"
Step 3: 同一回合内连续 poll 循环:
pollCount = 0
WHILE pollCount < 40:
process(action=poll, sessionId=gentle-haven, timeout=30000)
→ 等 30 秒
→ 如果 completed:break,进 Step 4
→ 如果仍 running:
pollCount += 1
每 10 次输出一次进度
(如「OpenCode 还在跑,已等 X 分钟」)
→ 继续下一轮 poll(不 yield,不等用户消息)
IF pollCount >= 40 仍未完成(超过 20 分钟):
输出「任务超过 20 分钟,设置 cron 继续监控」
→ 设置 cron job 每 30s 自动醒来检查
→ YIELD 结束当前回合
后续由 cron 自动唤醒,不再等兄弟消息
Step 4: 任务完成 → 拉完整日志
process(action=log, sessionId=<sessionId>)
Step 5: 分析结果 → 汇报给兄弟
(按 MEMORY.md → 先汇报再继续 规则)
</code></pre>
<p><strong>关键规则:</strong></p>
<ul>
<li><strong>不 yield,不依赖兄弟消息作为触发条件</strong> — 同一回合内连续 poll</li>
<li>单次 poll 等 30 秒,OpenCode 有输出时可能提前返回(不用等满 30s)</li>
<li>每 10 轮输出一次进度文本,避免 silent tool loop</li>
<li>40 轮 = 20 分钟上限(保守值)。超过后退化为 cron+yield 兜底</li>
<li>大部分 OpenCode 任务(修复/审核)在 3-15 分钟内完成</li>
<li>每次 poll 约消耗 ~200 tokens,40 轮 = ~8k tokens,在 1M 上下文中可控</li>
<li><strong>禁止先告诉兄弟「去看 web UI」再 poll</strong> — dispatch 后就静默 poll,不出声</li>
<li>完成后再主动汇报结果,不需要用户来问进度</li>
</ul>
<blockquote>
<p>只有耗时超过 20 分钟的任务才用 cron+yield 兜底。绝大部分任务在这个时间前完成。</p>
</blockquote>
<p><strong>对照 MEMORY.md 的 token 优化规则:</strong></p>
<ul>
<li><code>tool-loop-yield</code>:单回合 >30 轮 tool call 时主动输出进度。这里每 10 轮输出一次,符合规则</li>
<li><code>spawn-subagent</code>:超过 20 分钟的复杂任务可以考虑 spawn 子 session 独立监控</li>
</ul>
<h3>5️⃣ 硬性规则</h3>
<ul>
<li><strong> 调度 OpenCode 时禁止自己改代码/停 OpenCode</strong><ul>
<li><strong>不能自己改代码</strong> — 调度了 OpenCode 就让它改,我不碰</li>
<li><strong>不能擅自停 OpenCode</strong> — 我根本停不了 OpenCode(杀进程/重启服务都没用)</li>
<li><strong>后果:</strong> 旧 OpenCode 还在跑 → 下次调度时两个 OpenCode 抢文件 → 冲突爆炸 → 兄弟发现</li>
<li><strong>正确做法:</strong> dispatch → poll 等完成 → 出结果汇报。旧进程的 uncommitted 改动不影响新调度</li>
</ul>
</li>
<li><strong> 统一用 <code>exec(background=true) + stdin pipe</code></strong>,禁止 sessions_spawn</li>
<li><strong> 先写 <code>.opencode-brief.md</code></strong>,再用 <code>type</code> 管道传</li>
<li><strong> 遇代码修改必须调度 OpenCode</strong> — 不改代码自己手写</li>
<li><strong> <code>timeout=0</code> 不限时</strong> — OpenCode 做复杂改动跑 15-30 分钟很正常</li>
<li><strong> 优先选 Flash</strong> — 只有复杂逻辑/架构/审核才用 Pro</li>
<li><strong> brief 末尾必须写「不需要代码审核,代码审核是单独步骤」</strong></li>
<li><strong> 禁止让 OpenCode 做 E2E 相关工作</strong>(重启服务、启动脚本、截图、抓日志)</li>
<li><strong> 不擅自判断 OpenCode 卡住</strong> — poll 没输出 ≠ 卡住,至少等 1 小时</li>
<li><strong> dispatch后必须立即开始 poll</strong> — 不是先告诉兄弟去看 web UI。先 poll 拿到 sessionId 再说话。违反后果:兄弟需要连催 3 次</li>
<li><strong> [兄弟指令完整传达] OpenCode brief 不能挑重点、不能合并概括、不能省略任何条目</strong></li>
<li><strong> [根因分析交给 OpenCode Pro] — 不自己分析根因</strong><ul>
<li>遇到 bug 需要分析根因时,只做数据收集,不 trace 代码路径分析根因</li>
<li>数据收集包括:服务端日志、E2E 运行日志、关键代码路径文件列表、问题现象描述</li>
<li>数据收集完 → 写 brief → 调度 OpenCode Pro(plan agent)分析根因</li>
<li>违反后果:自己分析了一堆却分析错方向,兄弟还要纠正</li>
</ul>
</li>
<li><strong> brief 必须包含 specs + TDD 模板</strong> — 参照 gts-dev-workflow 的 brief 模板</li>
<li><strong> 模型选择:兄弟指定模型时按兄弟说的执行,不判断</strong></li>
</ul>
<h3>6️⃣ 模型选择速查</h3>
<table>
<thead>
<tr>
<th>任务类型</th>
<th>模型</th>
<th>额外参数</th>
</tr>
</thead>
<tbody><tr>
<td>简单修复 / 常规实现 / 重构</td>
<td>Flash</td>
<td>无</td>
</tr>
<tr>
<td>Verify(场景覆盖检查)</td>
<td>Flash</td>
<td>无</td>
</tr>
<tr>
<td>代码审核</td>
<td>Pro + max</td>
<td><code>--variant max</code></td>
</tr>
<tr>
<td>方案 / 架构设计</td>
<td>Pro + max, plan agent</td>
<td><code>--agent plan --variant max</code></td>
</tr>
<tr>
<td>复杂bug根因分析</td>
<td>Pro + max</td>
<td><code>--variant max</code></td>
</tr>
<tr>
<td>兄弟指定模型</td>
<td>按兄弟说的</td>
<td>不判断</td>
</tr>
</tbody></table>
<pre><code>
---
下期讲 **P27:前端性能优化(含 AI 素材管线)**——SoA、帧管理、MMD+FBX、AI 生成 3D。
**下一篇:[Vibe Coding 多人游戏(二十七)—— 前端性能优化](https://www.cnblogs.com/chaogex/p/21195307)**
</code></pre>
浙公网安备 33010602011771号