# Goblin 当前 PR 第二轮代码审查报告

- 审查范围：`origin/main...HEAD`（`feat/issue-280`，HEAD `1e0ed70e`）
- 规模：118 个提交，813 个文件，约 55,921 行新增 / 32,371 行删除
- 审查方式：只读静态审查；并行检查服务端数据边界、前端查询/状态同步、终端与恢复流程
- 未执行：未修改仓库代码，未运行测试、typecheck 或 boundary check

## 结论

发现 2 个高可信 P1 问题。两者都位于权威状态或刷新边界，建议合并前修复。

## Findings

### [P1] 带 Git 诊断的远端能力降级被静默丢弃

涉及位置：

- `src/server/modules/workspace-runtimes.ts:568-570, 794-808`
- `src/server/modules/remote-workspace.ts:184-200`
- `src/server/modules/remote-workspace-lifecycle-write-paths.ts:50-70`
- `src/server/routes/repo.ts:92-102`

当一个已经具备 Git 能力的远端 workspace 在同一 runtime 内发生以下变化时：

- 远端 Git 被卸载；或
- Git probe 失败，但 workspace 目录仍然可读；

remote resolver 会合理地返回 `ready + gitAvailable: false`。如果存在 `gitDiagnostic`，write path 会构造一个 `status: 'ready'`、Git capability 为 unavailable、同时携带 diagnostics 的新 probe，并准备通过 `beforeCapabilityCommit` 原子清理 Git pane tabs、terminal authority 等持久状态。

但 `workspaceRefreshMayCommit()` 当前要求：

```ts
probe.status === 'ready' && probe.diagnostics.length === 0
```

`workspaceProbeTransitionForRemoteCommit()` 因而直接丢弃上述能力降级 transition。远端 lifecycle 仍会提交为 ready，但 workspace probe 保留旧的 Git available 状态。

后果：

- `beforeCapabilityCommit` 不执行，Git 相关持久状态没有被事务清理；
- remote lifecycle 与 workspace capability 两个投影互相矛盾；
- `workspaceRuntimeHasGitCapability()` 继续依据旧 probe 放行 Git API；
- 用户看到的 Git tabs/terminal 仍存在，但实际 Git 请求会在更下层失败。

建议将“是否允许提交 capability transition”和“是否存在可展示 diagnostics”拆开。只要 workspace 本身 ready，Git unavailable 应作为有效能力状态提交，并在同一原子边界执行清理；diagnostics 不应阻止权威状态更新。

### [P1] Worktree 状态查询可永久停留在旧快照

涉及位置：

- `src/web/repo-data-query.ts:588-605`
- `src/web/repo-data-query.test.ts:832-870`
- `src/server/routes/workspace.ts:217-224`

`useRepoWorktreeStatusReadModel()` 使用 `staleTime: Number.POSITIVE_INFINITY`。最新提交 `1e0ed70e` 删除了 `refetchOnMount: 'always'`；此前提交 `7c2246d7` 又删除了 status/changes 页签可见时主动刷新的逻辑。应用的 query 默认配置同时关闭了 focus refetch。

当前服务端 `repo-worktree-snapshot` invalidation 只覆盖应用知道的写操作，例如 workspace trash-file。它无法覆盖用户在外部终端或其他工具中进行的文件和 Git 操作。

触发示例：

1. 用户打开仓库，worktree 状态首次读取为 clean；
2. 用户在外部终端修改文件、切换分支，或创建/删除 worktree；
3. 用户回到应用、切换页面后重新进入，或重新激活 status/changes；
4. 查询仍被视为永久 fresh，不重新读取，界面持续显示旧的 dirty/changeCount/worktree 列表。

新增测试名称 `shares cached status across observers and only refetches after invalidation` 也将“没有显式 invalidation 就不刷新”固化为当前行为。

建议恢复一个明确的可见性刷新边界，例如在 status/changes 激活、workspace 重新进入或窗口恢复可见时刷新。至少应恢复 remount refresh；服务端 invalidation 可继续作为应用内写操作的快速刷新路径，但不能作为外部变化的唯一来源。

## 已复核但未列为 Finding

审查中曾怀疑 `workspace-pane-tabs-restore.ts` 对 `deferred` workspace 返回成功会永久跳过 tabs 恢复。继续检查调用链后发现，启动恢复在调用前会过滤 probe 未 ready 的 workspace，非活动 workspace 还存在按视图触发的 projection promotion。因此现有证据不足以证明用户可触发的永久丢失，本报告不将其列为问题。

## 合并建议

建议暂缓合并，优先修复以上两项，并分别补充以下回归场景：

1. 已有 Git capability 的远端 workspace 刷新为“目录可读、Git unavailable、带 diagnostic”时，能力降级和清理必须提交；
2. 外部修改 worktree 后，通过页面重进或可见性恢复能够重新读取状态，而不依赖应用内 invalidation。
