--- name: forge-bugfix description: | 一次修一个 bug + 独立 worktree + 独立 TDD + Bug 修复验收报告 + **多会话并行协调** + dev server 端口治理 + Pass 边界接力(v7.2)。 每个 bug 从登记开始就创建结构化 Bug 修复验收报告。支持 3-4 个窗口同时修不同功能域的 bug, 靠 `.forge/active.md` 心跳文件 + 功能域判重 + 合并前 merge 预演避免撞车; 启动应用时优先使用项目统一 dev entrypoint,避免 worktree 抢端口或测到旧服务。 在 Codex 环境中,前端复现和修复后自验优先使用 browser-use:browser 的 in-app browser; Computer Use 只作为 browser-use 不可用或非浏览器桌面应用的兜底。 铁律:不做根因分析就不写修复代码 + 每个 bug 独立 worktree/TDD/commit/QA + 没有 Bug 修复验收报告不算完 + 用户问题闭环断言、配置/开关/数据就绪检查、QA 截图证据完整性门禁缺一不可 + 新发现禁止当场顺手修(原 bug Pass 后询问,用户确认才分流到 backlog)+ 多 bug 默认不串成长会话 + 原 bug 已 Pass 后遇到任何新问题,必须先告知“本次 bug 已完成”,询问是否写入文档并给接力 prompt + **并行会话必须在 active.md 登记,同域归并会话、异域鼓励并行**。 会话级(一次性):P0 环境探测(+ 读 active.md 看其他会话)→ P1 问题理解 + 强制读 PRD / ENG / Bugfix 历史 / Memory。 每次修复(可循环): P2 范围推荐(用户指定、QA 发现、backlog 候选、按功能域判重)→ P2.5 创建/更新 Bug 修复验收报告 → P3 创建 worktree + 写入 active.md + 复现 → P4 根因追踪 + 5 Whys + 方案确认 → P5 独立 TDD 驱动修复 + 原子提交 → P5.3 更新报告为待 QA 状态 → P6 调用 forge-qa 做自动验收 → P6.5 用户人工验收或批次最终验收等待 → P7 按三选一分流(Pass 合并 + merge 预演 / Fail 回 P5 / Pass + 新发现) → P7.4 Pass 边界确认(询问是否把新问题写入文档 + 接力 prompt) → P7.5 新发现分流到 backlog.md → P8 沉淀(active.md 由 forge-fupan 清理)。 出口:强烈建议新会话或 /clear、/compact 后继续下一个 bug; 全部完成 → 建议 /forge-review、/forge-ship、/forge-fupan 或 /forge-status。 触发方式:用户说"bugfix"、"反馈个问题"、"修这个 bug"、"这里有问题"、"为什么不对"、"排查一下"、"investigate"、"forge-bugfix", 或用户报告错误、异常行为、功能失效时主动建议使用。 allowed-tools: - Bash - Read - Write - Edit - Glob - Grep - TodoWrite - AskUserQuestion - WebSearch - Skill # P6 必须用 Skill 工具调用 forge-qa(mode=B) --- > **文档落地路径**:遵循 forge-doc-policy 规范。完整白名单 + frontmatter schema 见 > `~/claudecode_workspace/工具/forge-cookbook/skills/forge-doc-policy/doc-paths.md`。 # /forge-bugfix:一次一 bug + Bug 修复验收报告 ## 设计哲学 **短会话、单 bug 隔离、结构化证据、可追溯、可丢弃。** 核心机制: - **Bug 修复验收报告**:每个 bug 从登记/领取开始就创建 `docs/bugfix/reviews/BF-XX.md` - 发现时:记录来源、现象、初始截图/日志、关联 Feature Spec - 修复时:记录 worktree、TDD 红绿证据、根因、commit、涉及文件 - QA 时:forge-qa 回填前后端地址、环境身份校验、逐步截图、深度断言 - 人工验收时:用户只看"人工验收指南"和同一组验收地址,填最终结论 - **两种验收节奏**: - **单 bug 模式**:QA 全过后立即进入 P6.5,等用户验收该 bug - **QA 自动闭环 / 批量模式**:单 bug QA 通过后标记 `qa-pass-pending-final-review`,最后由批次汇总统一交给用户验收 - **新发现必须分流**(禁止顺手修): - 原 bug 未 Pass 前:只记录为“待确认新发现”,不进入当前修复 - 原 bug 已 Pass 后:先明确告知用户“本次 bug 修复已完成”,再询问是否写入文档 - 用户确认后,独立 bug → `docs/bugfix/backlog.md` 的待修区,分配 BF-XX 编号 - 用户确认后,新需求 → `docs/bugfix/backlog.md` 的新需求区,建议 /forge-prd 立项 - 用户确认后,模糊反馈 → `docs/bugfix/backlog.md` 的待澄清区 - 同根(并入当前修复)→ **AI 必须举证**(同文件/同函数/同数据流的具体证据),举证不通过默认独立 bug - **Pass 边界接力**: - 只要原 bug 已 Pass,不论新问题来自用户验收、QA 发现,还是 AI 在修复中发现的衍生问题,都不得继续修 - AI 必须先说清楚:原 bug 已收敛、证据是什么、当前修复到此关闭 - 再问用户:是否把新问题更新到 `docs/bugfix/backlog.md` / 报告中 - 用户确认后,AI 完成 P7.5 分流,并给一段简短接力 prompt,让用户开另一个会话继续 - **下一个 bug 默认建议新会话或 /clear、/compact**: - 上下文干净 + 边界清晰 + 避免长会话的 scope 蔓延 - 除非用户明确要共因地一起修 > 演进历史和设计决策背景见 `forge-cookbook/docs/forge-bugfix-changelog.md`。 ## 铁律 1. **不做根因分析,就不写修复代码。** 直觉再强也要先验证。 2. **每次只修 1 bug,或 1-2 个**经 P4.5 确认共因**的 bug**。其余进 `docs/bugfix/backlog.md`。 3. **每个 bug 独立 worktree + 独立 TDD + 独立 commit + 独立 QA 回归**。批量只做编排,不合并工程单元。 4. **修完不自动合并**,必须等用户填完单 bug 或批次最终验收结论。 5. **没有 Bug 修复验收报告不算完**。每个 BF 编号必须有 `docs/bugfix/reviews/BF-XX.md`,经 forge-qa 和用户/批次两层验收后才进 P7。 6. **新发现的 bug / 新需求 / 模糊反馈 → 原 bug Pass 后询问是否写入 `docs/bugfix/backlog.md`**,绝不在当前修复内夹带。 7. **同根判定必须举证**。AI 声称"这条新发现是当前 bug 同根"时,必须列出具体证据(同文件、同函数、同数据流),证据不足默认为独立 bug。 8. **并行协调必须登记**(v6.0)。P2 确认范围 + P3 创建 worktree 之后,必须在项目根 `.forge/active.md` 追加一行会话登记;P2 推荐前必须读 `.forge/active.md` 做功能域判重;P7 合并前必须跑 `git merge --no-commit --no-ff` 预演。清理 active 的责任在 forge-fupan 或 /forge-status,不在 forge-bugfix 自己。 9. **自动闭环有上限**。同一 bug 连续回修失败 3 次,或遇到需求/设计/环境身份不确定,必须标记 `blocked-human` 并让用户判断,禁止无限循环。 --- ## 流程总览 ``` ═══════════ 会话级(每会话一次,多次修复复用)═══════════ P0 环境探测(前置脚本,自动执行) P1 问题理解 + 强制读 PRD/ENGINEERING/Bugfix 历史/Memory ═══════════ 每次修复(一次一 bug,可循环)═══════════ P2 范围推荐 ├─ AI 从 docs/bugfix/backlog.md 捞候选 + 列出本会话新报告的 bug ├─ AI 接收 forge-qa 发现的结构化 bug(若来自 QA 自动闭环) ├─ 推荐"本次修 X"(1 个 / 或 1-2 共因),其余 → backlog └─ 用户确认范围 P2.5 创建/更新 Bug 修复验收报告 docs/bugfix/reviews/BF-XX.md ├─ 写入来源、现象、初始证据、Feature Spec 场景、功能域、当前状态 └─ 同步 backlog.md 的报告链接 P3 创建 worktree(端口预检)+ 复现 P4 根因追踪 + 假设验证 + 5 Whys + 修复方案确认 P5 worktree 内独立 TDD 驱动修复 + 原子提交 P5.3 ⭐ 更新 Bug 修复验收报告为待 QA 状态 ├─ 写入 TDD 红绿证据、根因、修复摘要、commit、涉及文件 └─ 写入人工验收指南草案,QA 区域留给 forge-qa P6 ⭐ 调用 forge-qa 自动验收 ├─ forge-qa 针对每个验证项跑自动化测试 ├─ 强校验 Frontend/Backend 地址、PID、cwd、commit 一致性 ├─ 把每个前端交互关键步骤截图嵌入报告 ├─ QA 全过 → 单 bug 模式进 P6.5;批量模式标记 qa-pass-pending-final-review └─ QA 有挂 → 有界回 P5;达到上限或有疑问则 blocked-human P6.5 ⭐ 用户人工验收 / 批次最终验收 ├─ AI 输出"请人工验收 @ docs/bugfix/reviews/BF-XX.md" ├─ 用户打开文档填"你的验收"列 + 最终结论(Pass / Fail / Pass + 新发现) └─ 用户说"验收了" → AI 读报告 → 判定 P7 ⭐ 按最终结论分流 ├─ Pass → worktree 合并决策(合并 / 暂存 / 推迟) ├─ Fail → 回 P5(只修原 bug,不接新问题) └─ Pass + 新发现 → 先完成当前修复的合并/暂存决策 → 进 P7.4 P7.4 ⭐ Pass 边界确认 + 文档更新询问 ├─ 告知用户:本次 bug 已完成,当前修复到此关闭 ├─ 列出新问题来源:用户反馈 / QA 发现 / AI 发现的衍生问题 ├─ 询问是否写入 docs/bugfix/backlog.md / 报告索引 ├─ 用户确认 → 进 P7.5 分流,并输出简短接力 prompt └─ 用户不确认 → 不写 backlog,不继续修新问题,结束本次 bugfix P7.5 ⭐ 新发现分流到 backlog ├─ 逐条分析,AI 做分类判断(同根 / 独立 bug / 新需求 / 模糊反馈) ├─ 同根(声称时必须举证)→ 新 bug 建议在新会话修 ├─ 独立 bug → backlog.md 的 🐛 待修区,分配 BF-XX ├─ 新需求 → backlog.md 的 💡 新需求区,建议 /forge-prd 立项 └─ 模糊反馈 → backlog.md 的 🌀 待澄清区 P8 沉淀:归档 backlog 对应条目 + bugfix 文档补记 ═══════════ 出口 ═══════════ - 用户想继续修下一个 bug → 默认建议新会话或 /clear、/compact (除非明确共因才本会话继续,由用户主动选择) - 全部完成 → 建议 /forge-review、/forge-ship 或 /forge-fupan ``` **红线**: 1. 写任何修复代码前必须完成 P2-P4(含范围确认和 5 Whys 根因确认) 2. 每个 BF 编号必须先有 Bug 修复验收报告(P2.5),修复后必须更新报告(P5.3)才能进入 QA 3. 报告没有用户最终结论或批次最终结论前,**不进 P7**——即使 QA 全过 4. 同根判定声称必须举证,证据不足默认独立 bug → 原 bug Pass 后问用户是否写入 backlog,再走新会话 --- ## P0 环境探测(会话级·前置脚本,自动执行) ```bash # === 基础信息 === _BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown") _ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd) echo "分支: $_BRANCH" echo "根目录: $_ROOT" echo "---" # === Git 状态 === echo "=== Git 状态 ===" git status --porcelain 2>/dev/null | head -20 echo "=== 最近 10 条提交 ===" git log --oneline -10 2>/dev/null echo "---" # === 现有 worktree 清单 === echo "=== 现有 worktree ===" git worktree list 2>/dev/null echo "---" # === 复现引擎 1: gstack/browse → $BROWSE === BROWSE="" for p in "$_ROOT/.Codex/skills/gstack/browse/dist/browse" \ "$HOME/.Codex/skills/gstack/browse/dist/browse" \ "$_ROOT/.Codex/skills/browse/dist/browse" \ "$HOME/.Codex/skills/browse/dist/browse"; do [ -x "$p" ] && BROWSE="$p" && break done [ -n "$BROWSE" ] && echo "BROWSE=$BROWSE" || echo "BROWSE=(不可用)" # === 复现引擎 2: Playwright → $PW_AVAILABLE === PW_AVAILABLE="false" if command -v npx &>/dev/null && npx playwright --version &>/dev/null 2>&1; then PW_AVAILABLE="true" elif python3 -c "from playwright.sync_api import sync_playwright" 2>/dev/null; then PW_AVAILABLE="true" fi echo "PW_AVAILABLE=$PW_AVAILABLE" # === 框架 / 测试框架 / Dev Server 状态 === # ... 框架探测 / 测试框架探测 ... # === 统一 dev server 入口 → $APP_URL === APP_URL="" DEV_STATUS="" if [ -f "$_ROOT/package.json" ] && (cd "$_ROOT" && npm run 2>/dev/null | grep -q "dev:status"); then DEV_STATUS="$(cd "$_ROOT" && npm run dev:status 2>/dev/null || true)" echo "$DEV_STATUS" APP_URL="$(printf "%s\n" "$DEV_STATUS" | awk '/Frontend:/{print $2; exit}')" elif [ -x "$_ROOT/scripts/dev-stack.sh" ]; then DEV_STATUS="$(cd "$_ROOT" && bash scripts/dev-stack.sh status 2>/dev/null || true)" echo "$DEV_STATUS" APP_URL="$(printf "%s\n" "$DEV_STATUS" | awk '/Frontend:/{print $2; exit}')" else # 兼容旧项目:只读探测,不把探测结果当作 worktree 启动许可 for port in 3456 3000 4000 5173 8080 8000; do if lsof -i :"$port" -sTCP:LISTEN &>/dev/null 2>&1; then APP_URL="http://localhost:$port" _PID=$(lsof -ti :"$port" -sTCP:LISTEN 2>/dev/null | head -1) _CWD=$(lsof -p "$_PID" 2>/dev/null | awk '$4=="cwd"{print $9}') echo "APP_URL=$APP_URL (PID=$_PID cwd=$_CWD)" break fi done fi [ -z "$APP_URL" ] && echo "APP_URL=(未检测到运行中的应用)" # === 项目文档清单 === DOC_PRD=""; DOC_ENG=""; DOC_QA=""; DOC_BACKLOG=""; DOC_BUGFIX=""; DOC_REVIEWS="" for p in "$_ROOT/docs/PRD.md" "$_ROOT/PRD.md"; do [ -f "$p" ] && DOC_PRD="$p" && echo "PRD: $p" && break; done for p in "$_ROOT/docs/ENGINEERING.md" "$_ROOT/ENGINEERING.md"; do [ -f "$p" ] && DOC_ENG="$p" && echo "ENGINEERING: $p" && break; done for p in "$_ROOT/docs/QA.md" "$_ROOT/QA.md"; do [ -f "$p" ] && DOC_QA="$p" && echo "QA: $p" && break; done # backlog(bug 任务池,单一入口) for p in "$_ROOT/docs/bugfix/backlog.md" "$_ROOT/backlog.md"; do [ -f "$p" ] && DOC_BACKLOG="$p" && echo "BACKLOG: $p" && break done if [ -z "$DOC_BACKLOG" ]; then DOC_BACKLOG="$_ROOT/docs/bugfix/backlog.md" echo "BACKLOG(首次使用,将从模板初始化): $DOC_BACKLOG" fi # reviews 目录(每个 bug 一个 Bug 修复验收报告) DOC_REVIEWS="$_ROOT/docs/bugfix/reviews" mkdir -p "$DOC_REVIEWS" 2>/dev/null echo "REVIEWS 目录: $DOC_REVIEWS" [ -d "$_ROOT/docs/bugfix" ] && DOC_BUGFIX="$_ROOT/docs/bugfix" && echo "BUGFIX 历史: $DOC_BUGFIX" # === 报告目录 === REPORT_DIR="$_ROOT/.gstack/bugfix-reports" mkdir -p "$REPORT_DIR/screenshots" 2>/dev/null echo "报告目录: $REPORT_DIR" # === 并行会话环境(v6.0 新增)=== # active.md: 跨 worktree 的心跳文件,项目根 .forge/active.md mkdir -p "$_ROOT/.forge" 2>/dev/null ACTIVE="$_ROOT/.forge/active.md" if [ ! -f "$ACTIVE" ]; then # 首次使用,从模板初始化(模板路径按 skill 安装位置回退) for tpl in "$HOME/.claude/skills/forge-bugfix/templates/active.md" \ "$HOME/.claude/skills/forge/skills/forge-bugfix/templates/active.md"; do [ -f "$tpl" ] && cp "$tpl" "$ACTIVE" && echo "✅ 初始化 .forge/active.md(请编辑功能域声明区)" && break done fi echo "ACTIVE=$ACTIVE" # 当前 Claude Code session id(通过 PID 回溯 ~/.claude/sessions/.json) SID_SCRIPT="" for s in "$HOME/.claude/skills/forge-bugfix/scripts/get-session-id.sh" \ "$HOME/.claude/skills/forge/skills/forge-bugfix/scripts/get-session-id.sh"; do [ -x "$s" ] || [ -f "$s" ] && SID_SCRIPT="$s" && break done CURRENT_SID="" if [ -n "$SID_SCRIPT" ]; then CURRENT_SID=$(bash "$SID_SCRIPT" 2>/dev/null || echo "") fi [ -n "$CURRENT_SID" ] && echo "SESSION_ID=$CURRENT_SID" || echo "SESSION_ID=(无法自动获取,后续 P3 会提示)" # 扫一眼 active.md 里"进行中会话"节,报告当前有哪些并行会话 if [ -f "$ACTIVE" ]; then echo "--- 当前并行会话 ---" awk '/^## 进行中会话/{flag=1;next} /^## /{flag=0} flag && /^- /{print}' "$ACTIVE" | grep -v '