Ctrl K
ComunidadGuías…

docs/plan/2026-08-09-git-operations-technical-design.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Git 提交、拉取与推送技术方案

## 1. 文档信息

- 文档状态:已确认方案
- 适用项目:Mao
- 覆盖端:桌面/Web/安卓共用前端、桌面 Electron、本地模式、云端模式、Java 后端
- 目标位置:任务页右侧检查器的 Git Tab,“变更”工具栏右侧
- 对应组件:`desktop/src/components/task/GitChangeList.vue`
- 方案范围:提交代码、拉取代码、推送代码三个图标及完整执行链路

## 2. 需求背景

当前任务页右侧 Git Tab 已支持以下只读能力:

- 发现工作区根仓库或一级子目录中的多个 Git 仓库;
- 查看当前分支、文件变更数量和增删行数;
- 查看变更文件树和文件 diff;
- 手动刷新 Git 状态。

现有工具栏只有刷新按钮。用户在移动端、Web 或桌面端查看代码变更后,仍需通过终端执行提交、拉取和推送,操作链路不完整。尤其在移动端和 CLOUD 模式下,用户缺少便捷的终端入口。

本需求在“变更”工具栏增加三个图标,为当前选中仓库提供提交、拉取和推送能力。其中提交信息不由用户输入,而是由当前工作区所属主会话模型根据仓库变更自动生成,生成成功后直接完成提交。

## 3. 需求描述

### 3.1 工具栏

Git 工具栏按以下固定顺序展示:

1. 提交:`Check` 图标;
2. 拉取:`Download` 图标;
3. 推送:`Upload` 图标;
4. 刷新:保留现有 `Refresh` 图标。

按钮统一使用 28×28 像素点击区域,提供 `title`、tooltip 和 `aria-label`。操作执行期间,仅当前按钮显示旋转加载状态,不展示阶段文案,不使用全屏遮罩。

三个操作同时支持:

- CLOUD:Web、安卓、Electron 中的云端任务;
- LOCAL:Electron 中的本地任务。

用户主动点击 Git 图标属于显式 UI 操作,不受会话 `READ_ONLY`、`READ_WRITE`、`SMART` 等 Agent 工具权限等级限制,也不进入 Agent 工具审批流。服务端仍必须校验用户会话归属、工作区路径和仓库路径。

### 3.2 自动提交

点击提交图标后,系统执行以下固定流程:

1. 获取当前选中仓库全部变更,包括已暂存、未暂存、已跟踪和未跟踪文件;
2. 生成用于模型分析的受限 diff;
3. 使用工作区所属主会话模型生成提交信息;
4. 校验提交信息格式;
5. 执行 `git add -A`;
6. 执行 `git commit --no-verify`;
7. 返回短提交哈希和标题并刷新 Git 状态。

生成后不展示确认框,不允许用户编辑,直接提交。

提交信息采用 Conventional Commits 中文格式:

```text
<type>(<scope>): <中文描述>

- <中文改动要点>
- <中文改动要点>
```

约束如下:

- `type` 和 `scope` 使用英文;
- 描述和正文使用简体中文;
- 正文必须为 `- ` 开头的无序列表;
- 标题不设置字符数上限;
- 不提供近期 Git 历史或聊天上下文给模型;
- 模型首次输出不合规时,将校验错误反馈给同一模型并重试一次;
- 第二次仍不合规、模型调用失败或超时,终止提交;
- 生成失败时不执行 `git add`,不使用固定文案或规则模板降级提交。

提交使用工作区所属主会话的模型。即使当前右侧面板正在查看边路任务或子代理,也不使用子会话模型。

提交信息生成是独立后台 LLM 调用:

- 不创建用户消息或助手消息;
- 不写入聊天记录;
- 不占用后续 Agent 对话上下文;
- 记录实际模型调用用量。

若仓库未配置 `user.name` 或 `user.email`,仅为本次提交注入:

```text
Mao Agent <[email protected]>
```

不得修改仓库级或全局 Git 配置。

提交成功提示格式:

```text
提交成功 <shortHash>:<commit title>
```

提交失败时保留 `git add -A` 已产生的暂存状态,不执行 `git reset`,也不尝试恢复提交前的暂存/未暂存边界。

### 3.3 Diff 构建与敏感信息过滤

模型输入包含:

- 全量变更文件路径;
- 每个文件的变更类型;
- 每个文件的增删行数;
- 普通文本文件的受限 diff;
- 是否为二进制、敏感文件或内容被截断的标记。

文本 diff 总上限固定为 200 KB。超限时:

1. 保留全部文件元数据;
2. 为普通文本文件公平分配内容额度,避免前几个大文件占满上限;
3. 对被截断文件显式标记 `truncated=true`;
4. 不因截断而阻止生成。

二进制文件只发送路径和变更类型,不发送内容。

敏感文件采用代码内固定规则,不增加设置页面或仓库配置文件。至少覆盖:

- `.env`、`.env.*`;
- `*.pem`、`*.key`、`*.p12`、`*.pfx`;
- `id_rsa`、`id_dsa`、`id_ecdsa`、`id_ed25519` 及对应私钥命名;
- 文件名包含 `credential`、`credentials`、`secret`、`secrets`、`token` 的常见凭据文件。

命中敏感规则后:

- 文件仍属于“全部当前变更”,会被提交;
- 文件路径和变更类型会发送给模型;
- 文件 diff 内容不得发送给后端生成服务、模型服务、日志或活动记录。

LOCAL 模式允许将经过上述过滤和 200 KB 限制后的 diff 上传到 Mao 后端,再由后端调用主会话模型。原始未过滤 diff 不上传。

### 3.4 拉取

点击拉取图标后直接执行,不弹确认框。拉取采用 merge 模式,使用等价命令:

```bash
git pull --no-edit
```

允许 Git 在分支分叉时生成 merge commit,合并提交信息使用 Git 默认内容,不调用 AI。

工作区存在已跟踪或未跟踪变更时,执行以下流程:

1. 创建带唯一 Mao 标识的 stash,包含已跟踪和未跟踪文件,不包含 ignored 文件;
2. 记录本次 stash 的对象 ID,不依赖可能变化的 `stash@{0}`;
3. 执行 `git pull --no-edit`;
4. pull 成功且未产生冲突时,恢复本次 stash;
5. pull 非冲突失败时,立即尝试恢复本次 stash;
6. pull 已进入合并冲突时,不恢复 stash,保留冲突现场和带 Mao 标识的 stash,并在错误信息中提示 stash 引用;
7. stash 恢复产生冲突时,保留 Git 冲突现场并明确提示,不自动 reset、abort 或覆盖文件。

自动 stash 等价于:

```bash
git stash push --include-untracked -m "mao-auto-pull-<operationId>"
```

不得使用 `--all`,不得 stash ignored 文件。

### 3.5 推送

点击推送图标后直接执行,不弹确认框。工作区存在未提交变更时仍允许推送已有提交。

已配置 upstream 时执行普通:

```bash
git push
```

未配置 upstream 时:

1. 获取当前分支和 remote 列表;
2. 存在 `origin` 时,执行 `git push --set-upstream origin <branch>`;
3. 不存在 `origin` 且仅有一个 remote 时,使用该 remote;
4. 不存在 `origin` 且有多个 remote 时终止并提示用户先配置 upstream;
5. 没有 remote 时按钮禁用。

推送永不使用 `--force` 或 `--force-with-lease`。出现 non-fast-forward 时失败并提示用户先拉取处理。

### 3.6 状态与禁用规则

- 没有待提交变更:提交按钮禁用,tooltip 显示“没有待提交的变更”;
- 没有 remote:拉取和推送按钮禁用,tooltip 显示“仓库未配置远端”;
- detached HEAD:允许提交,禁用拉取和推送,并提示先切换分支;
- 当前仓库 Git 状态不可用:三个写操作均禁用;
- 同一仓库已有 Git 写操作执行中:拒绝新的提交、拉取或推送请求,立即提示“Git 操作进行中”;
- Agent 主任务或边路任务运行期间:三个 Git 操作仍保持可用;
- 自动生成提交信息期间工作区继续变化:不做快照一致性校验,最终 `git add -A` 会提交当时的最新全部变更;
- 每次操作完成后刷新仓库发现结果、当前仓库状态和文件变更列表。

## 4. 明确不做

本需求明确不实现以下内容:

1. 不提供提交信息输入框、确认弹窗或编辑能力;
2. 不提供文件勾选、部分提交、逐文件暂存、取消暂存或暂存区管理;
3. 不在提交前自动运行测试、构建或由 AI 选择检查命令;
4. 提交固定使用 `--no-verify`,不执行 `pre-commit`、`prepare-commit-msg`、`commit-msg` 等 Git hooks;
5. 不在提交后自动 push;
6. push 前不弹确认框;
7. pull 前不弹确认框;
8. 不提供强制推送;
9. 不提供 rebase pull 或 fast-forward-only pull;
10. 不在 pull merge commit 时生成 AI 提交信息;
11. 不新增 Git 作者设置页面;
12. 不修改用户仓库级或全局 Git 身份配置;
13. 不新增 LOCAL Git 凭证管理,不把 CLOUD Token 下发到 Electron;
14. 不新增本地模型配置;
15. 不提供敏感文件规则的用户配置或仓库配置;
16. 不把提交信息生成写入聊天记录;
17. 不向模型发送聊天上下文或近期 Git 提交历史;
18. 不对生成期间的工作区变更做快照一致性校验;
19. 不因 Agent 正在运行而禁用 Git 操作;
20. 不自动解决 merge、stash pop 或 push 冲突;
21. 不自动删除冲突场景下保留的 Mao stash;
22. 不支持工作区一级子目录以外的任意 `repoPath`。

## 5. 现有架构与技术选型

### 5.1 前端

技术栈:Vue 3 Composition API、TypeScript、Element Plus。

复用现有:

- `desktop/src/components/task/GitChangeList.vue`:Git 工具栏和变更树;
- `desktop/src/components/task/TaskInspector.vue`:仓库选择、Git 状态和刷新编排;
- `desktop/src/composables/workspace-git-provider.ts`:CLOUD/LOCAL provider 抽象;
- `desktop/src/composables/useGitStatus.ts`:当前仓库状态;
- `desktop/src/composables/useGitRepos.ts`:多仓库发现和当前仓库选择;
- `desktop/src/types/git.ts`:Git DTO 类型。

选择在 `WorkspaceGitProvider` 增加统一写操作,确保 UI 不感知 CLOUD/LOCAL 的实现差异。

### 5.2 LOCAL

技术栈:Electron IPC、Node.js `child_process`、参数数组执行 Git。

复用现有:

- `desktop/electron/gitStatus.cjs` 的 `resolveRepoDir()`、Git 状态和 diff 解析;
- `desktop/electron/main.cjs` 的 Git IPC 注册;
- `desktop/electron/preload.cjs` 的安全 API 暴露;
- `desktop/src/types/electron.d.ts` 的 Electron API 声明。

LOCAL 拉取和推送沿用本机 Git 的 credential helper、SSH Agent 或系统凭证。不得把后端保存的 Access Token下发到 Electron。

### 5.3 CLOUD

技术栈:Spring Boot、Java 17、REST API、`ProcessBuilder`、MyBatis-Plus、Flyway。

复用现有:

- `FileController.requireOwnedSession()` 的会话归属校验;
- `WorkspaceGitService` 的工作区解析、仓库解析、状态和 diff 能力;
- `PathSandbox` 的路径沙箱;
- `GitCredentialService` 和现有 askpass 机制的 HTTPS Token;
- `ActivityService` 的会话活动记录。

CLOUD 远程操作使用当前用户在 Mao 中配置的 HTTPS Access Token。Token 只通过进程环境变量和受限 askpass 脚本提供,不拼接到命令参数、远端 URL、接口响应或日志。

### 5.4 LLM

复用现有:

- `LlmAdapter.chat()` 同步非流式调用;
- `ChatRequest`、`ChatResponse`、`ChatUsage`;
- `HarnessService.resolveModel()` 的“指定模型,缺失时默认模型”规则;
- `LlmModel` 到 `LlmModelConfig` 的配置映射方式。

提交信息生成使用非流式请求,不提供工具定义,模型只返回文本。请求由后端统一执行,LOCAL 前端仅上传脱敏后的模型输入,不接触模型 API Key。

## 6. 总体架构

```text
GitChangeList
  └─ emit(commit / pull / push)
      └─ TaskInspector
          └─ WorkspaceGitProvider
              ├─ CLOUD provider
              │   └─ REST /v1/files/workspace-git-*
              │       ├─ WorkspaceGitService
              │       ├─ GitCommitMessageService → LlmAdapter
              │       ├─ GitCredentialService / askpass
              │       └─ ActivityService / LLM usage
              └─ LOCAL provider
                  ├─ Electron IPC 读取状态、构建脱敏 diff、执行 Git
                  └─ REST generate-commit-message
                      ├─ 会话归属和主模型解析
                      ├─ GitCommitMessageService → LlmAdapter
                      └─ LLM usage
```

CLOUD 自动提交由服务端完成 diff 构建、模型生成和 Git 提交。

LOCAL 自动提交分两段完成:

1. Electron 在本机仓库构建经过敏感过滤和大小限制的模型输入;
2. 前端将模型输入提交给后端生成接口;
3. 生成成功后前端把提交信息传回 Electron;
4. Electron 对本机仓库执行 `git add -A` 和 `git commit --no-verify`。

后端不得接受 LOCAL 客户端上传的任意工作区路径并在服务器执行 Git;LOCAL 生成接口只处理结构化 diff 数据和会话模型解析。

## 7. 数据结构设计

### 7.1 前端统一类型

在 `desktop/src/types/git.ts` 增加:

```ts
export type GitOperationType = 'commit' | 'pull' | 'push'

export interface GitRemoteState {
  remotes: string[]
  hasRemote: boolean
  detachedHead: boolean
  upstream?: string
}

export interface GitOperationResult {
  success: boolean
  operation: GitOperationType
  message?: string
  error?: string
  branch?: string
  commitHash?: string
  commitTitle?: string
  stashRef?: string
  conflict?: boolean
}

export interface GitCommitGenerationInput {
  files: Array<{
    path: string
    changeType: string
    insertions: number
    deletions: number
    binary: boolean
    sensitive: boolean
    truncated: boolean
    diff?: string
  }>
  truncated: boolean
  diffBytes: number
}
```

`GitStatusResult` 增加远端和 HEAD 状态,供按钮禁用判断使用。

### 7.2 Provider 接口

`WorkspaceGitProvider` 增加:

```ts
commit(): Promise<GitOperationResult>
pull(): Promise<GitOperationResult>
push(): Promise<GitOperationResult>
```

多仓库模式下由 `TaskInspector` 的包装 provider 固定当前 `selectedRepoPath`,与现有 `getStatus()` 和 `getFileDiff()` 行为一致。

### 7.3 LLM 用量表

新增 Flyway 迁移 `V073__llm_usage.sql`,建立统一后台 LLM 调用用量表,字段至少包括:

| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | BIGINT | 自增主键 |
| `user_id` | BIGINT | 发起用户 |
| `session_id` | BIGINT | 主会话 |
| `model_id` | BIGINT | 实际调用模型 |
| `scene` | VARCHAR(64) | 本场景固定 `git_commit_message` |
| `prompt_tokens` | INT | 输入 token |
| `completion_tokens` | INT | 输出 token |
| `total_tokens` | INT | 总 token |
| `success` | TINYINT | 调用是否成功 |
| `created_at` | DATETIME | 创建时间 |

增加实体、Mapper 和 Service。提交信息格式重试发生两次模型调用时,两次均分别记录实际用量。统计服务按模型汇总消息用量与该表后台调用用量,不创建隐藏消息。

### 7.4 活动记录

通过 `ActivityService` 写入以下活动类型:

- `GIT_COMMIT`;
- `GIT_PULL`;
- `GIT_PUSH`。

记录内容包括:

- 主会话 ID;
- 仓库相对路径;
- 操作类型;
- 成功或失败;
- 分支;
- 提交短哈希和标题;
- stash 引用或冲突标记;
- 耗时;
- 脱敏后的简短错误。

不得记录:

- Access Token、SSH 密钥或 credential helper 输出;
- 完整 diff;
- 敏感文件内容;
- 带凭证的远端 URL;
- 完整模型 prompt。

LOCAL 操作结果由前端回传后端活动接口,后端校验会话归属并只接受结构化摘要,不能接受任意活动类型或未限制长度的日志文本。

## 8. API 设计

所有接口前缀为 `/api/v1`,下列路径基于控制器 `/v1/files` 描述。

### 8.1 CLOUD 提交

```http
POST /v1/files/workspace-git-commit
Content-Type: application/json
```

请求:

```json
{
  "sessionId": 123,
  "repoPath": "project-a"
}
```

服务端从主会话读取 `modelId`,完成 diff 构建、生成和提交。客户端不得指定模型 ID或提交信息。

### 8.2 CLOUD 拉取

```http
POST /v1/files/workspace-git-pull
```

请求必须包含 `sessionId`;工作区根仓库不传 `repoPath`,多仓库模式必须传当前选中仓库的一级子目录 `repoPath`。

### 8.3 CLOUD 推送

```http
POST /v1/files/workspace-git-push
```

请求必须包含 `sessionId`;工作区根仓库不传 `repoPath`,多仓库模式必须传当前选中仓库的一级子目录 `repoPath`。

### 8.4 LOCAL 提交信息生成

```http
POST /v1/files/git-commit-message
```

请求:

```json
{
  "sessionId": 123,
  "changes": {
    "files": [],
    "truncated": false,
    "diffBytes": 0
  }
}
```

服务端必须:

- 校验会话属于当前用户;
- 使用主会话 `modelId`;
- 再次校验结构、文件数量、单字段长度和请求体 200 KB 级边界;
- 拒绝客户端提供模型 API Key、任意 system prompt 或模型 ID;
- 返回校验通过的提交标题和正文。

### 8.5 LOCAL 活动记录

```http
POST /v1/files/workspace-git-activity
```

仅用于记录 Electron 已执行的结构化 Git 结果。请求字段使用枚举和长度上限,服务端校验会话归属。

### 8.6 响应

统一使用项目 `Result<T>`。Git 操作数据结构与 `GitOperationResult` 对齐。业务失败返回可读错误,不把原始进程环境或完整 stderr 直接返回。

## 9. 后端实现设计

### 9.1 服务拆分

保留 `WorkspaceGitService` 负责仓库路径解析、状态、diff 和 Git 进程基础执行;新增:

- `GitCommitMessageService`:模型输入校验、prompt 构建、模型解析、格式校验、一次纠错重试、用量记录;
- `GitWriteOperationService`:CLOUD commit/pull/push、互斥锁、凭证环境、stash 生命周期和活动记录;
- `LlmUsageService`:统一后台模型用量落库。

不把写操作继续堆入只读 DTO 解析逻辑,避免 `WorkspaceGitService` 同时承担模型、凭证、活动和状态职责。

### 9.2 Git 执行器

写操作统一使用 `ProcessBuilder(List<String>)`,严禁拼接 shell 命令。每条操作统一 60 秒超时,超时后强制终止进程并释放仓库锁。

Git 环境至少设置:

```text
GIT_TERMINAL_PROMPT=0
```

CLOUD 远程操作同时配置当前用户的 `GIT_ASKPASS` 和域名 Token 环境变量。进程 stdout/stderr 设置总大小上限,错误输出先脱敏再返回。

### 9.3 仓库锁

按规范化仓库根路径建立进程内互斥锁:

- commit、pull、push 共用同一把仓库写锁;
- 获取失败立即返回“Git 操作进行中”,不排队;
- 所有成功、异常、超时路径均在 `finally` 释放;
- 锁 Map 在无持有者后移除,避免长期增长。

Electron 主进程使用同样的按仓库根路径互斥策略。

### 9.4 提交信息 Prompt

System prompt 固定声明:

- 只生成提交信息,不解释;
- 标题必须符合 Conventional Commits;
- type/scope 英文、描述和正文简体中文;
- 标题后空一行;
- 正文至少一条 `- ` 列表;
- 不臆测未在 diff 或元数据中体现的改动;
- 敏感/二进制/截断文件只能依据元数据概括;
- 输出不得包含 Markdown 代码围栏。

User prompt 只包含结构化变更摘要和受限 diff,不包含聊天历史。

格式校验至少验证:

```regex
^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-zA-Z0-9._/-]+\))?!?: .+
```

标题后必须存在空行,正文每个非空行必须以 `- ` 开头。标题不做长度限制。

首次校验失败时,用同一模型再次请求,并附上具体校验错误和首次输出;第二次失败抛出业务错误。

### 9.5 CLOUD Commit

固定步骤:

1. 校验会话归属;
2. 解析工作区和 `repoPath`;
3. 获取仓库写锁;
4. 检查存在变更;
5. 构建过滤后的 200 KB 模型输入;
6. 调用主会话模型生成提交信息;
7. 执行 `git add -A`;
8. 检测 Git 作者身份;缺失时为本次 commit 增加 `-c user.name=Mao Agent -c [email protected]`;
9. 使用临时消息文件或多个 `-m` 参数执行 `git commit --no-verify`,不得经 shell 展开;
10. 获取 `git rev-parse --short HEAD`;
11. 记录活动并返回;
12. 释放锁。

模型生成后不重新校验工作区快照。步骤 7 会纳入生成期间出现的最新全部变更。

### 9.6 CLOUD Pull

固定步骤:

1. 校验仓库、分支和 remote;
2. 获取写锁;
3. 检测工作区是否有变更;
4. 有变更则创建唯一 Mao stash 并记录对象 ID;
5. 使用凭证环境执行 `git pull --no-edit`;
6. 根据退出状态和 Git 冲突状态判断是否恢复 stash;
7. 无 pull 冲突时,按对象 ID恢复本次 stash;
8. pull 冲突时保留 stash 和冲突现场;
9. 记录活动、返回状态并释放锁。

恢复时不得简单执行无参数 `git stash pop`。应使用记录的对象 ID,并在成功应用后只删除对应 stash;若应用冲突,保留 stash 记录和冲突文件。

### 9.7 CLOUD Push

固定步骤:

1. 校验当前不是 detached HEAD;
2. 获取 remote 和 upstream;
3. 获取写锁;
4. 有 upstream 时执行普通 push;
5. 无 upstream 时按 `origin` 优先规则选择 remote 并执行 `--set-upstream`;
6. non-fast-forward 直接失败;
7. 记录活动并释放锁。

## 10. Electron 实现设计

### 10.1 IPC

新增 IPC:

- `git-commit-input`:本机生成经过过滤的模型输入;
- `git-commit`:接收后端已校验的提交信息并执行 add/commit;
- `git-pull`;
- `git-push`。

在 `preload.cjs` 暴露对应方法,并在 `electron.d.ts` 补齐类型。

提交信息由后端返回后,Electron 仍执行最小结构校验和长度限制,再作为参数传给 Git;不得使用 `exec` 或 shell 字符串。

### 10.2 LOCAL 自动提交编排

LOCAL provider 内部执行:

1. IPC 获取本机受限 diff;
2. 调用后端 `/files/git-commit-message`;
3. IPC 执行本机提交;
4. 调用后端活动记录接口;
5. 返回统一结果。

若步骤 2 失败,不调用步骤 3。若步骤 3 在 add 后失败,保留暂存状态。

### 10.3 LOCAL 凭证

pull/push 直接继承 Electron 主进程可用的本机 Git 环境,支持用户已配置的:

- Git credential helper;
- SSH Agent;
- 系统密钥链;
- Git 配置中的其他本机认证方式。

设置 `GIT_TERMINAL_PROMPT=0`,避免无终端 IPC 请求挂起。认证缺失时在 60 秒内失败并提示用户在本机配置 Git 凭证。

## 11. 前端实现设计

### 11.1 GitChangeList

`GitChangeList.vue` 增加 props:

- `hasRemote`;
- `detachedHead`;
- `operation`;
- `operationLoading`。

增加 emits:

- `commit`;
- `pull`;
- `push`。

按钮只负责展示、禁用和触发,不直接调用 API。

### 11.2 TaskInspector

`TaskInspector.vue` 负责:

- 调用当前 `WorkspaceGitProvider`;
- 维护当前 UI 操作状态;
- 使用 `ElMessage` 展示成功和错误;
- 操作完成后调用现有 `refreshAll()`;
- 在多仓库模式下确保操作目标为当前 `selectedRepoPath`;
- 将主会话 ID而不是当前子会话 ID用于模型生成和活动记录。

前端按钮禁用只改善体验,不能替代服务端/Electron 的仓库锁和状态校验。

### 11.3 用户反馈

- 提交成功:展示短哈希和标题;
- 拉取成功:展示当前分支已更新;
- 推送成功:展示当前分支已推送;
- 远端无更新也视为成功并提示“已是最新状态”;
- 失败:展示经过分类的中文错误;
- stash 被保留:错误中必须展示 Mao stash 标识或可定位引用;
- 操作结束后停止按钮旋转,无论成功或失败都刷新 Git 状态。

## 12. 错误分类

至少识别并转换以下错误:

- 当前工作区不是 Git 仓库;
- 没有待提交变更;
- 提交信息生成超时或失败;
- 提交信息格式连续两次不合规;
- Git 操作进行中;
- Git index lock 被外部进程占用;
- 仓库没有 remote;
- detached HEAD 无法拉取或推送;
- 多 remote 且无 `origin`,无法自动选择 upstream;
- 认证失败或凭证缺失;
- 远端不可达或网络超时;
- push non-fast-forward;
- pull merge conflict;
- stash 创建失败;
- stash 恢复冲突或失败;
- Git 子进程 60 秒超时;
- 非法 `repoPath` 或越权会话。

错误转换不得把带凭证 URL 或进程环境输出给客户端。

## 13. 安全设计

1. 所有 Git 命令通过参数数组执行,禁止 shell 拼接;
2. CLOUD 接口先校验 `sessionId` 属于当前用户;
3. `repoPath` 继续只允许工作区一级子目录,并通过 `PathSandbox`;
4. LOCAL Electron 继续通过 `resolveRepoDir()` 限定目标目录;
5. 提交信息作为独立参数或临时文件传入,不参与命令解析;
6. 临时提交信息文件使用仅当前用户可读权限并在 `finally` 删除;
7. Token 仅进入受控进程环境和 askpass,不进入命令参数;
8. 模型输入在 LOCAL 端先过滤,后端再做结构和大小校验;
9. 敏感文件 diff 不进入模型、日志、活动或错误;
10. Git 输出设置字节上限,防止大输出占满内存;
11. 同一仓库写操作互斥;
12. 不支持 force push、reset hard 或自动冲突覆盖;
13. 活动记录接口字段使用枚举、长度限制和服务端生成时间;
14. LLM 用量表不保存 prompt 或 diff。

## 14. 性能与超时

- 模型输入文本 diff:最多 200 KB;
- 模型生成:60 秒;
- commit:60 秒;
- pull:60 秒;
- push:60 秒;
- Git stdout/stderr:沿用现有 2 MB 级总输出上限,错误响应进一步裁剪;
- 同一仓库不排队;
- 不同仓库可并行操作;
- 操作完成后只刷新仓库发现和当前仓库状态,不轮询操作进度。

## 15. 实现步骤

### 阶段一:公共类型和只读状态扩展

1. 扩展 Git 状态 DTO,返回 remote、upstream 和 detached HEAD;
2. 扩展 `types/git.ts`;
3. 扩展 `WorkspaceGitProvider`;
4. 确保单仓库和多仓库模式都能获得同一状态字段。

### 阶段二:提交信息生成服务

1. 新增固定敏感文件规则;
2. 实现公平截断的 200 KB diff 构建器,Java 和 Electron 使用同一组规则及测试样例;
3. 新增 `GitCommitMessageService`;
4. 实现主会话模型解析、非流式 LLM 调用、格式校验和一次纠错;
5. 新增统一 LLM 用量表、实体、Mapper、Service 和统计汇总;
6. 新增 LOCAL 生成接口。

### 阶段三:CLOUD 写操作

1. 新增仓库写锁;
2. 实现 commit;
3. 接入仅本次生效的 Mao 默认作者;
4. 实现自动 stash + merge pull + 精确恢复;
5. 实现普通 push 和 upstream 自动设置;
6. 接入 HTTPS Token askpass;
7. 写入脱敏活动记录;
8. 增加三个 REST 接口。

### 阶段四:LOCAL 写操作

1. 扩展 `gitStatus.cjs` 或拆出 Git 写操作模块;
2. 实现 Electron 仓库写锁;
3. 实现本地受限 diff 构建;
4. 实现 commit、pull、push;
5. 注册 IPC 并更新 preload 和类型;
6. 接入后端模型生成和活动记录。

### 阶段五:前端交互

1. 在 `GitChangeList.vue` 增加三个图标;
2. 实现 tooltip、ARIA、加载和禁用规则;
3. 在 `TaskInspector.vue` 接入统一 provider;
4. 实现成功/失败 toast;
5. 操作后刷新 Git 状态;
6. 验证窄屏和安卓布局不溢出。

### 阶段六:测试与发版说明

1. 增加后端单元测试;
2. 增加 Electron Git 模块测试;
3. 增加前端单元测试;
4. 执行后端测试和前端 build;
5. 更新根 `CHANGELOG.md` 当前版本的“后端”“前端(桌面 / Web / 安卓)”“桌面 Electron”;
6. 不自动重启 Mao 后端,由用户自行重启。

## 16. 测试方案

### 16.1 提交信息生成

- 普通多文件变更生成合法标题和列表正文;
- type/scope 英文,描述和正文中文;
- 标题无长度限制;
- 首次格式错误,第二次纠正成功;
- 两次格式错误后终止;
- 模型失败或 60 秒超时后不执行 add/commit;
- 敏感文件只进入元数据;
- 二进制文件只进入元数据;
- 超过 200 KB 时公平截断且保留全部文件元数据;
- 不包含聊天历史和 Git 历史;
- 两次模型调用均正确记录用量。

### 16.2 Commit

- 提交全部 tracked/untracked/staged/unstaged 变更;
- 无变更时拒绝且不调用模型;
- 已配置身份时沿用用户身份;
- 缺失身份时仅本次使用 `Mao Agent <[email protected]>`;
- 仓库 Git config 和 global config 不被修改;
- 验证 hooks 确实被 `--no-verify` 跳过;
- commit 失败后保留暂存状态;
- 生成期间新增文件会被最终 `add -A` 纳入;
- 返回短哈希和标题;
- detached HEAD 可以提交。

### 16.3 Pull

- 干净工作区普通 pull;
- 分叉时以 Git 默认信息创建 merge commit;
- 脏工作区自动 stash tracked 和 untracked;
- ignored 文件不进入 stash;
- pull 成功后精确恢复本次 stash;
- 非冲突失败后恢复 stash;
- pull 冲突时保留冲突现场和 Mao stash;
- stash pop 冲突时保留冲突状态和 stash;
- 60 秒超时后释放锁;
- detached HEAD 和无 remote 时拒绝。

### 16.4 Push

- upstream 已配置时普通 push;
- 无 upstream 且有 origin 时自动设置;
- 无 origin 且只有一个 remote 时自动设置;
- 无 origin 且多个 remote 时拒绝;
- 工作区有未提交变更时仍可推送已有提交;
- non-fast-forward 失败且不强推;
- detached HEAD 和无 remote 时拒绝;
- HTTPS Token 不出现在命令、日志和响应。

### 16.5 多仓库和并发

- 操作只作用于当前 `selectedRepoPath`;
- 非法、绝对、多级和越界 `repoPath` 被拒绝;
- 同一仓库并发操作立即拒绝;
- 不同仓库可并行;
- 主任务或边路任务运行时按钮仍可用;
- 子代理视图仍使用主会话模型。

### 16.6 前端

- 图标顺序为 Check、Download、Upload、Refresh;
- 每个按钮有 tooltip 和 aria-label;
- 无变更禁用提交;
- 无 remote 禁用拉取和推送;
- detached HEAD 仅提交可用;
- 仅当前操作按钮旋转;
- 不出现确认框、输入框和阶段文案;
- 提交成功提示包含短哈希和标题;
- 操作完成后刷新;
- 移动端宽度下工具栏不换行、不遮挡计数。

### 16.7 验证命令

```bash
cd backend && mvn test
cd desktop && npm run test:unit
cd desktop && npm run build
```

Electron Git 模块若采用 Node 内置测试框架,应增加对应 npm script 并纳入 `npm run test:unit` 或 CI 独立步骤。

## 17. 落地清单

### 后端

- [ ] 扩展 CLOUD Git 状态的 remote/upstream/detached 字段
- [ ] 新增 `GitCommitMessageService`
- [ ] 新增固定敏感文件识别与 200 KB diff 构建器
- [ ] 新增提交信息格式校验和一次纠错
- [ ] 新增 `V073__llm_usage.sql`
- [ ] 新增 LLM 用量实体、Mapper、Service 和统计汇总
- [ ] 新增仓库级写操作互斥锁
- [ ] 新增 CLOUD commit/pull/push 服务
- [ ] 为远程操作接入用户 HTTPS Token askpass
- [ ] 新增 Git 操作活动类型和脱敏记录
- [ ] 新增四个 Git REST 接口及 LOCAL 活动记录接口
- [ ] 增加服务与控制器测试

### 桌面 Electron

- [ ] 扩展本地 Git 状态字段
- [ ] 实现本地敏感过滤和受限 diff
- [ ] 实现本地仓库写锁
- [ ] 实现本地 commit/pull/push
- [ ] 实现精确 stash 恢复和冲突保留
- [ ] 新增 IPC handlers
- [ ] 更新 `preload.cjs`
- [ ] 更新 `electron.d.ts`
- [ ] 增加 Git 模块测试

### 前端(桌面/Web/安卓)

- [ ] 扩展 `types/git.ts`
- [ ] 扩展 `WorkspaceGitProvider`
- [ ] 实现 CLOUD provider
- [ ] 实现 LOCAL provider 编排
- [ ] 在 Git 工具栏增加 Check/Download/Upload
- [ ] 实现按钮禁用、加载、tooltip 和 ARIA
- [ ] 实现 toast 和操作后刷新
- [ ] 覆盖单仓库、多仓库和移动端布局测试

### 文档与发版

- [ ] 更新根 `CHANGELOG.md` 的“后端”
- [ ] 更新根 `CHANGELOG.md` 的“前端(桌面 / Web / 安卓)”
- [ ] 更新根 `CHANGELOG.md` 的“桌面 Electron”
- [ ] 执行后端测试、前端单测和构建
- [ ] 由用户自行重启后端服务

## 18. 验收标准

1. Git Tab 工具栏固定展示提交、拉取、推送、刷新四个图标,移动端不溢出;
2. CLOUD 与 LOCAL 均能完成三个操作;
3. 点击提交后无需输入或确认,AI 成功生成信息后直接提交全部当前变更;
4. 提交信息符合 Conventional Commits 中文标题加列表正文;
5. 敏感文件和二进制内容不会发送给模型;
6. LOCAL 上传模型输入不超过 200 KB 文本 diff,且不包含敏感内容;
7. 生成失败或格式两次不合规时不执行 add/commit;
8. 提交执行 `--no-verify`,缺失作者时仅本次注入 Mao 默认身份;
9. pull 对脏工作区自动 stash tracked/untracked,按既定冲突策略恢复或保留;
10. push 无 upstream 时按 origin 优先规则自动建立,永不强推;
11. 同一仓库写操作互斥,所有操作 60 秒超时;
12. 操作结果写入脱敏活动记录,模型用量写入统一用量表;
13. 不写入聊天消息,不改变 Agent 会话上下文;
14. 所有路径、会话归属和命令参数均通过安全校验;
15. 后端测试、前端单测和前端构建通过。