# ClickVibe 状态模型:事实分级与按钮决策表 > 2026-08-22 讨论沉淀。回答一个问题:**"这个 issue 现在处于什么状态,下一步该做什么、显示什么按钮"** —— 判断必须只依赖客观、保证存在的事实,任何"可能缺失"的东西都只能当增强器,不能当门槛。 ## 一、核心原则 1. **git + GitHub 原生事实 = 判断的地基**。它们客观存在,不依赖任何人"记得写"。 2. **workflow 文件 ≠ 门槛**。它是缓存(worktree 路径其实可推导、会话 id、事件历史),缺失时判断必须照常工作,最多结论更保守。 3. **comment meta = 增强器**。允许缺失;缺失时走降级链(GitHub 原生 review → 人工确认),**永不因缺 meta 而卡死,也永不因缺判据而瞎猜**。 4. **入口从 GitHub issue 出发**:枚举 repo 的 open issue,用约定(config 的 repo 路径 + worktreeRoot + issue 号)算出候选 worktree/分支,再用 git 查真相;workflow 文件存在时只叠加缓存信息。 ## 二、事实分级 | 级别 | 事实 | 来源 | 获取手段 | |---|---|---|---| | **硬** | issue OPEN/CLOSED | GitHub | `gh issue view` | | **硬** | worktree 有无、registered branch | 本地 git | `git worktree list --porcelain` 交叉约定路径 | | **硬** | 目标分支有无(本地/远端) | 本地 git | `git show-ref` / `for-each-ref` | | **硬** | 内容更新(不管是否 commit) | 本地 git | `git status --porcelain` + `git log ..HEAD` | | **硬** | fork 点(baseline 曾经是什么) | 本地 git | `git merge-base origin/main ` | | **硬** | 应同步基线(现在该是什么) | 本地 git | `origin/HEAD` / `origin/main` 当前 tip | | **硬** | PR 存在 / open / merged / closed | GitHub | `gh pr list --head ` + `gh pr view` | | **硬** | GitHub 原生 review(APPROVED/CHANGES_REQUESTED/COMMENTED) | GitHub | `gh pr view --json reviews`(受控词表,字段保证存在) | | **软** | review 结论(通过 + 问题列表) | 本地事件 / comment meta | 见降级链 | | **软** | 结论绑定的 HEAD | 本地事件 / comment meta | 同上 | | **软** | 会话 id(续会话用) | 本地(进程/文件) | 缺失 → resume 降级为重新开发 | | **软** | 任务是否在跑 | 进程本地 | 唯一非 git/GitHub 事实,天然临时;可推导出"中断"结论 | | **软** | comment meta(事件流水) | GitHub 评论 | 只影响时间线展示,不影响判断 | ## 三、按钮决策表(按优先级) ### P0 终端状态(一票否决) | 状态事实 | 按钮 | |---|---| | issue CLOSED(无论其他) | 无(展示"已关闭") | | PR merged | 无(展示"✅ 已交付") | ### P1 任务在跑(进程活着) | 状态事实 | 按钮 | |---|---| | dev/review 任务在跑 | 无动作(显示进度)+「停止」 | ### P2 结构坏(有痕迹,但形态不对) | 状态事实 | 按钮 | |---|---| | 有分支(有内容)+ 无 worktree | 「恢复 worktree 继续开发」 | | 有分支(空)+ 无 worktree | 「开始开发」(复用/重建) | | worktree 在,但 detached / 错分支 | 「修复 worktree」 | | worktree 落后 origin/main | 「同步 worktree」——优先于一切阶段动作 | ### P3 开发生命周期(结构正常) | # | 状态事实 | 按钮 | |---|---|---| | 1 | 无 worktree + 无分支 + 无 PR(未开发) | 「开始开发」(Codex/Claude + 安全演练) | | 2 | worktree+分支就位,无内容更新 | 「开始开发」(继续) | | 3 | 有未提交改动,无任务 | 「恢复开发」;无会话 id → 降级「重新开发」 | | 4 | 有提交,无 PR | 「创建 PR」(开发完成,推送建 PR 后 Review) | | 5 | PR open,无 review 结论 | 「Review」 | | 6 | review 未通过 | 「按意见返工」 | | 7 | review 通过 + HEAD == 结论哈希 | 「合并 PR」 | | 8 | review 通过 + HEAD ≠ 结论哈希 | 「重新 Review」(结论过期) | | 9 | PR closed 未合并 | 「查看原因 / 重新开发」(异常,需人) | ### 软事实降级链(贯穿) - **会话 id 缺失** → resume 降级为「重新开发」(新会话) - **review 结论缺失** → ①本地事件缓存 → ②comment meta → ③GitHub 原生 review(`reviews` 字段)→ ④「人工确认」(不自动合并、不自动返工) - **comment meta 缺失** → 只影响时间线展示,不影响判断 ## 四、与现状的差异(落地清单) 1. **去掉 workflow 门槛**:状态推导入口从"已持久化 workflow"改为"GitHub 枚举的每个 open issue"。对无 workflow 的 issue,用约定算候选 worktree/分支,直接查 git 填事实(worktree 无 → head=null;分支无 → 无内容;PR 用 `gh pr list --head ` 查)。`deriveNextAction` 纯函数已支持 idle 分支,缺的只是入口。**回归示例**:本次 "#5 后从未开发过的 issue 不显示开发按钮" 就是 workflow 门槛的症状。 2. **补 PR 状态查询**:当前 `/state` 里 `prMerged` 写死 `false`(注释:需要网络查询,/state 不做网络 IO)。合并状态应实时查 GitHub(或按需 + 短缓存),否则已合并的 PR 还显示"合并 PR"按钮。 3. **comment 流水带 meta**(关联 #4):开发完成 / review 完成 / 合并都要发评论,meta 至少含:事件类型、绑定的 HEAD、结论(passed + 问题列表)、issue 号。写入是尽力而为,失败时本地事件照记,状态不倒退。 ## 五、关联 - 本模型是 **#7(项目优先界面)** 的展示规范:每个 issue 一行 = 状态徽章(阶段)+ 唯一动作按钮(本表) - **#9(自动选取)** 的 ready 判定 = P3 #1/#2 状态 + blockedBy 依赖全完成 - **#4(PR 评论流水)** 提供 meta,让 GitHub 成为可重建账本 - **#11(跨机器)** 后,"本地 git"事实源路由到执行机,事实类型与判断不变 ## 六、P2 恢复动作明细(点「开始开发」后自动执行) `ensureWorktree` 对 worktree/分支 4 种组合自动处理,无需人工: | 组合 | 决策 | 动作 | |---|---|---| | 分支无 + worktree 无 | add-new-branch | `git worktree add <路径> -b <分支> origin/HEAD`(从远端默认分支建) | | 分支有 + worktree 有 | reuse | 直接复用 | | 分支有 + worktree 无 | add-existing-branch | `git worktree add <路径> <分支>` | | 分支无 + worktree 有(detached) | attach-detached | `git switch -c <分支>` | 半状态兜底: | 场景 | 决策 | 动作 | |---|---|---| | detached 但分支已存在 | attach-existing | `git switch <分支>` | | git 注册存在但路径缺失/为空(stale) | repair | 清理注册后重建 | | 分支被其他 worktree 占用 | conflict | 拒绝(不覆盖) | | 路径是非空未注册目录 | conflict | 拒绝(不覆盖) | 安全边界:冲突一律拒绝,绝不覆盖现有内容;新分支只从 fetch 后的 origin/HEAD 创建,不继承主仓库碰巧停留的 HEAD。 ## 七、状态视图展示规范 ### 原则 1. **基础事实常驻,派生信号按需**——客观存在的信息常显;对比算出来的信息"有情况才显示,没情况不显示"。 2. **对比对象只有一个:origin/main(远端)**。不显示本地 main(判断不用它,且本地未 pull 会误导)。 3. **数字必须带语义**,不能裸数字:"落后 2"要能读成"主干有 2 个新提交我还没有"。 ### 基础事实(常驻 3 项) ``` 📁 worktree ~/.clickvibe/worktrees/clickvibe/clickvibe-issue-7 (工位在哪) 🌿 分支 clickvibe-issue-7 @ 9f3a2c1 (在哪干活,HEAD 是什么) 📍 基线 origin/main @ 8715172 (从哪出发,定格不变) ``` - 基线 = 创建分支时的 `origin/HEAD`(兼容回退 `origin/main`)+ 当时 hash - 基线**永远不变**,主干怎么前进它都定格 ### 派生信号(按需出现) | 状态 | 显示 | 语义 | 按钮 | |---|---|---|---| | 落后 0 · 领先 0 | 无 | 干净,无需关注 | 无 | | 落后 N > 0 | ⚠ 落后 origin/main N | 主干自基线后新增 N 个提交,还没并入 | 「同步 worktree」 | | 领先 M > 0 | 领先 M | 比主干多 M 个提交(开发成果/待 review 量) | 无(状态徽章已表达"有内容") | | 领先 M · 落后 N(分叉) | 领先 M · 落后 N | 分支与主干分叉,同步将 merge 主干进来 | 「同步 worktree」 | 配套出现项: ``` 🔄 origin/clickvibe-issue-7 @ … ← 分支推到远端才显示(push 前后对比) 🔗 PR #N ← 建了 PR 才显示 ``` ### 任务进行中形态(developing / reviewing) ``` 🚀 开发流程 [开发中] 📁 worktree ~/.clickvibe/worktrees/clickvibe/clickvibe-issue-7 🌿 分支 clickvibe-issue-7 @ 9f3a2c1 📍 基线 origin/main @ 8715172 ■ 实时输出(agent 实时行,深色等宽,200px 滚动) [clickvibe] 开发基线: origin/main @ 8715172 $ git status ... ... [停止任务] ``` - **「实时输出」** = 当前任务的 agent 实时行 + `[clickvibe]` 系统提示行(启动失败/超时/截断等);类名 `cv-dev-log` - 数据恢复(断线重连、Host 重启、2000 行上限)由 #3 负责,本规范只管展示形态 - 任务结束 → 实时输出区收起,内容沉淀进 📜 历史(事件时间线) ### 完整详情视图形态 ``` 🚀 开发流程 [待 review] 📁 worktree ~/.clickvibe/worktrees/clickvibe/clickvibe-issue-7 🌿 分支 clickvibe-issue-7 @ 9f3a2c1 📍 基线 origin/main @ 8715172 ⚠ 落后 origin/main 2 [同步 worktree] ← 仅落后时 🔗 PR #18 ← 仅建了 PR 时 ``` ### 列表视图(#7 每行一个 issue) ``` clickvibe-issue-7 🚧 开发中 ⚠落后2 ``` - 每行 = 状态徽章 + 分支名 + 落后徽章(落后才显示) - 领先不显示(状态徽章"开发中/待 review"已表达"有内容") - 点开一行 → 展开上面的详情视图 ### 与按钮决策表的关系 - 落后检查在**阶段动作之前**(P2 优先):落后 → 同步按钮,同步完成落后归零、领先保留 - worktree 缺失时视图退化为:基础事实里能算的 + 结构坏提示(「恢复 worktree」) - 本规范只负责**展示**;判断仍由 `deriveNextAction`(纯函数)负责,展示层不重复推导