# Gitea 操作便捷化 - 产品需求文档

> **module**: gitea-cli
> **doc_type**: PRD
> **status**: Draft
> **owner**: chentao.jia
> **approver**: chentao.jia（提单人=负责人，本工单流程上自审）
> **issue**: [#331](http://43.130.59.228/FFAIWorkspace/workspace/issues/331)
> **upstream_docs**: 无（PRD 是依赖链起点）
> **last_verified**: 2026-05-14
>
> **事实源**: 本文档定义 v1 范围与验收边界。

---

## 目标与问题

### 要解决的问题

AI（Claude Code / Codex CLI）和团队成员在 Gitea 上执行日常操作（读 issue / 列 issue / 加 label / 改状态 / 评论），目前**没有任何 CLI 出口**，每次都要手写 `curl + python heredoc` 模板：

```bash
GITEA_TOKEN="${GITEA_API_TOKEN}"
curl -s -H "Authorization: token $GITEA_TOKEN" \
  "http://43.130.59.228/api/v1/repos/FFAIWorkspace/workspace/issues/331" \
  | python3 -c "import sys,json;d=json.load(sys.stdin);..."
```

一次稍复杂的操作 10-20 行，AI 容易在 JSON 转义、token 取值、label id 硬编码上出 bug（**本 PRD 撰写过程中 AI 自己就刚踩过 label id 硬编码错误，把 `Kind/Bug` 误加进 #331**）。

仓库已有 `scripts/ops/_gitea_api.py` 内部 library（chentao.jia 2026-05-11 commit `43c14dff` 抽出），但它只设计给批处理 Python 脚本 import，没有 CLI 出口，AI 临时查询时反而绕开它直接 curl。

同时，多个**项目约定**目前只写在 SOP 里靠人记，没法自动执行：

| 约定 | 现状 | 翻车证据 |
|---|---|---|
| 一个 issue 同时只能有一个 `Status/*` label | 手工维护 | 实际见过 issue 挂双 Status |
| `Owner/*` label 必须与 assignee 绑定 | 手工维护 | 经常出现 label 标了某人但 assignee 空 |
| PR 作者不能批/合自己的 PR | CLAUDE.md 铁律 | `.learnings/2026-05-11-no-self-merge-policy.md` |
| issue 标题不允许人名前缀（`[宏伟] xxx`） | SOP | 偶发 |
| issue 模板必填字段（业务问题/业务方/期望） | 软约束 | 偶发留空 |

### 解决方式（用户视角）

**AI 视角**：在 Bash 里一行命令完成 90% 常见 Gitea 查询/操作：

```bash
gitea issue 331                           # 读单个 issue（替代 4 行 curl + python）
gitea issue list --label Kind/Feature     # 列符合条件 issue
gitea issue comment 331 --body "..."      # 加评论
gitea label add 331 "Owner/Chentao"       # 加 label
gitea pr 318                              # 读单个 PR
```

**团队成员视角**：日常 ops 操作不再需要查 API 文档；常见操作有命令、错误提示对项目有上下文。

**项目约定执行视角**：把"工具该拦的事"从 SOP 文档落到工具/Gitea 原生能力/CI workflow——降低维护成本，AI-first 工作流下复利大。

### 成功指标

v1 验收（PR 全部 merge 时必须满足）：

- AI 在本仓库会话中**不再手写** `curl http://43.130.59.228/api/v1/...`，全部通过 `gitea` CLI 调用
- 团队成员一次新人入职演示，5 个常见 ops 操作（读 issue / 列 issue / 评论 / 加 label / 读 PR）能在 5 分钟内独立完成
- PR 作者无法在 Gitea Web UI 或 API 通过任何路径合并自己的 PR（被 workflow 拦截）
- issue 不可能同时挂两个 `Status/*` label（Gitea scoped labels 自动互斥）

长期信号（1 个月后回看）：

- 仓库内除 `scripts/ops/_gitea_api.py` 自身外，`curl .*43.130.59.228/api` 出现次数 ≤ 5（CI workflow yaml 例外）
- 自合事件 0 次
- Status 多挂事件 0 次

---

## 功能边界

### 范围内（v1 必做）

**3 件事，各自独立 PR**（`.gitea/workflows/**` 是高风险路径，按 CLAUDE.md 必须单独 PR）：

> ⚠️ **PR #371 的实际拆分（2026-05-14）**：第 1 件事（CLI）+ 第 2 件事的**迁移脚本部分**合并为单 PR，因为首个 CLI 是规范的落地范例，分开 review 会失去上下文；第 2 件事的 `.gitea/ISSUE_TEMPLATE` 改造**不做**（现 .md 模板内联引导更好）；第 3 件事（`pr-merge-guard.yml` workflow）按 CI 高风险路径走**后续独立 PR**。完整 rationale 见 PR #371 description「与 PRD 范围的差异」段。

#### 1. CLI 出口（`python -m _gitea_api` 或薄 wrapper 脚本 `scripts/ops/gitea`）

复用现有 `_gitea_api.py` library，**不重写 HTTP 层**。子命令清单（v1 收敛到 5 类）：

| 命令 | 说明 | 替代原 issue 哪条 |
|---|---|---|
| `gitea issue <num>` | 读单个 issue body + labels + assignees | — |
| `gitea issue list [--label X --state open --limit N]` | 列 issue | — |
| `gitea issue comment <num> --body "..."` | 加评论（body 可走 `-` stdin） | — |
| `gitea label add/remove <num> <label-name>` | 加/移 label（按名字，不暴露 id） | — |
| `gitea pr <num>` | 读单个 PR body + state + base/head | — |
| `gitea promote uat\|prod` | **v1.1（PR #384 落地）** 环境升级 FF-only 合并 — `uat` = develop→staging，`prod` = staging→production。`Do=fast-forward-only` 写死；CI 不全绿拒绝；自动 self-approve（CLAUDE.md 例外政策）；post-merge 校验 `base tip == head sha`。`--dry-run` 预演；`--force-anyway` 紧急绕 CI | — |

**显式不在 v1 范围**：

- ❌ `gitea issue transition` / `gitea issue claim` / `gitea issue close` — 用 Gitea 原生 + workflow 替代（见第 2、3 件事）
- ❌ `gitea pr create` / `gitea pr merge`（**通用** PR）— 人手在 web UI / 现有 ops 脚本处理；自合拦截走 workflow。**例外**：环境升级 PR 的创建+合并已纳入 `gitea promote` 子命令（v1.1）
- ❌ release / wiki / repo settings 操作

**鉴权**：复用 `_gitea_api.py` 的 `get_token()` 顺序（`GITEA_API_TOKEN` env → `GITEA_TOKEN` env），不引入新的 token 存储。

**错误提示**：对项目语境，例如 token 缺失时直接告诉用户"export GITEA_API_TOKEN（详见 CLAUDE.md Gitea 平台配置段）"，而不是 401 Unauthorized 原文。

#### 2. Gitea 平台配置（约定靠平台原生执行）

- **`Status/*` 改 scoped labels**（Gitea 1.21+ 支持 `key/value` 自动互斥）：现有 `Status/待领取` / `Status/PRD 中` / `Status/契约设计中` / `Status/开发中` / `Status/待验收` / `Status/Blocked` / `Status/Abandoned` / `Status/Need More Info` / `Status/需求待澄清` 全部改为 scoped。**没有兜底脚本——靠 Gitea 自身互斥。**
- **`Owner/*` 改 scoped labels**：同上，避免一个 issue 挂多个 Owner。
- **issue template required 字段**：`.gitea/issue_template/*.yml` 把"业务问题 / 业务方 / 期望效果"三段标 `required: true`。
- **issue template 标题校验**（如 Gitea 支持，则配；不支持则归到第 3 件事的 webhook）：正则 `^\[需求\] [^[]` 拦截 `[人名]` 前缀。

#### 3. PR 自合拦截 workflow（`.gitea/workflows/pr-merge-guard.yml`）

- 触发：`pull_request` event 的 `synchronize` / `opened` / `reopened` / `ready_for_review`
- 检查：`pr.user.login` 不在 PR 的 approver / merger 列表
- 失败行为：workflow status check fail，PR 合并按钮在 branch protection 配合下被禁
- 兜底文案：失败 status 的 description 链 CLAUDE.md `Git 规则摘要 → PR 合并规则` 段
- **不依赖**当前 CLI 工具，独立部署，独立 PR

### 范围外（v1 不做）

| 项 | 不做理由 | 何时考虑 |
|---|---|---|
| `gitea issue close` 时校验"待验收清单全勾" | 复杂度 vs 收益不匹配；SOP + L3 验收已经够 | 真发生过 ≥ 2 次跳验收事件再做 |
| `gitea issue transition` 切状态子命令 | 第 2 件事的 scoped labels 已替代核心需求 | 如果 scoped labels 仍漏（如 API 创建 issue 时绕过 scope）再加 |
| `gitea pr create`（通用） | 现有 `scripts/ops/*.py` 已能创建 PR；人手用 web UI 更直观 | AI-only 全自动开 PR 场景成熟后 |
| ~~`gitea promote uat\|prod`~~ | **已在 v1.1 落地**（PR #384，env-promote PR 的创建+合并合到一个机械化命令） | — |
| `gitea release` | 当前 release 流程在 staging/production 分支 merge 触发，无人手开 release | 有需求再加 |
| 安装到全局 `$PATH`（`gitea` 命令直接调） | 第一版只在仓库内 `scripts/ops/gitea` 暴露，避免污染全局环境 | 团队 ≥ 3 人稳定使用 v1 后做 |

---

## 用户与场景

### 用户角色

| 角色 | 主要场景 | 频率 |
|---|---|---|
| **AI agent**（Claude Code / Codex） | 会话中读 issue 取背景、列同类 issue、加评论汇报进度 | 每天 N 次 |
| **团队成员**（hongwei / lijian / chentao） | claim 工单、改 Status label、评论同步进度 | 每天 5-10 次 |
| **CI workflow**（.gitea/workflows/*.yml） | ai-review 提交评论、weekly-stale-sweep 关分支 issue | 自动触发 |

### 典型场景

**S1**：AI 接到任务 → 读 issue body → 列同模块历史 issue → 完工后回评论。
当前：4-5 段 curl heredoc。
v1 后：`gitea issue 331 && gitea issue list --label Module/xxx && gitea issue comment 331 --body "..."`。

**S2**：宏伟 claim 工单。
当前：手工在 web UI 加 assignee + 加 `Owner/Hongwei` label + 移除 `Status/待领取` + 加 `Status/PRD 中`（4 个动作，可能漏）。
v1 后：scoped labels 自动互斥处理 Status / Owner；assignee + label 仍需两步，但**不再可能挂双 Status**。

**S3**：AI 准备 merge 自己刚开的 PR。
当前：CLAUDE.md 写了铁律但**没工具拦**，靠人检查。
v1 后：workflow 自动 fail status check → branch protection 禁合并。

---

## 验收清单（v1）

按 `Status/待验收` 切换前必须全勾：

- [ ] `scripts/ops/gitea` CLI 5 类子命令 L1 集成测试通过（mock token + dry-run + 真调一次本仓库 API）
- [ ] CLI 在没有 token 时给出对项目有上下文的提示（指向 CLAUDE.md），不是 401 原文
- [ ] AI 在本会话演示用 CLI 完成 S1 全流程（读 + 列 + 评论），手写 curl 出现次数 = 0
- [ ] Gitea 平台所有 `Status/*` label 全部 scoped；测试创建一个 issue 加两个 Status/*，第二个自动替换第一个
- [ ] Gitea 平台所有 `Owner/*` label 全部 scoped
- [ ] `.gitea/issue_template/*.yml` 业务问题/业务方/期望三段 `required: true`，留空时 web UI 拒绝提交
- [ ] `.gitea/workflows/pr-merge-guard.yml` 部署；当前用户创建 PR 后尝试合并，被 workflow fail status 拦截
- [ ] 三个 PR 全部 merge 到 develop
- [ ] 在 #331 写"已交付项 vs 原 6 子命令"差异说明 + 收敛理由
- [ ] CLAUDE.md "Gitea 平台配置" 段加 CLI 使用说明 + 与 `_gitea_api.py` 关系说明

---

## 取舍与开放问题

### 取舍说明（写进 PR description）

| 原 issue 提议 | v1 决策 | 理由 |
|---|---|---|
| `gitea issue transition` 子命令 | ❌ 不做 | scoped labels 原生互斥已够 |
| `gitea issue claim` 子命令 | ❌ 不做 | scoped labels + 仍需手工 assignee；2 步 vs 1 步 ROI 低 |
| `gitea issue create` 校验模板字段 | ❌ 不做 | issue_template required 字段原生支持 |
| `gitea issue create` 拦人名前缀标题 | ⚠️ 看 Gitea template 是否支持 title regex，否则归 workflow | 平台能力优先 |
| `gitea pr merge` 拦自合 | ❌ 改成 workflow | CLI 拦不住 web UI 点合并；workflow 是唯一拦截点 |
| 关 issue 时校验验收清单 | ❌ 不做 | 成本 > 收益；SOP + L3 已经够 |

### 开放问题（PRD review 时确认）

1. **CLI 入口形态**：`scripts/ops/gitea`（shell wrapper） vs `python -m _gitea_api`（无 wrapper） vs 直接命名 `scripts/ops/gitea-cli.py`？
2. **Gitea 版本是否支持 scoped labels**？需要先验证 `43.130.59.228` 实例版本（Gitea 1.21+）。**这是 v1 第 2 件事的硬前提**——若不支持，第 2 件事降级为 webhook + workflow 兜底。
3. **`.gitea/issue_template/*.yml` 当前已有的模板**：要不要顺手把当前 `.gitea/issue_template/feature.yml`（如存在）改造，还是这个改造算 PR 范围外的"维护性补"？

---

## 关联

- 源工单：[#331](http://43.130.59.228/FFAIWorkspace/workspace/issues/331)
- 上游 library：`scripts/ops/_gitea_api.py`（chentao.jia 2026-05-11，commit `43c14dff`）
- 工作流约束：`docs/standards/14-feature-request-workflow.md`
- 翻车证据：`.learnings/2026-05-11-no-self-merge-policy.md`
- 工程化保险方法论：`docs/standards/12-five-meta-rules.md`（元根因 5：监控告警→工具执行）
