# Gitea ops CLI（gitea-cli）

> **负责人**: chentao.jia
> **状态**: ✓ v1 已交付（PR #371）；v1.1 `gitea promote` PR 审查中（PR #384）
> **最后更新**: 2026-05-15
> **关联工单**: [Issue #331](http://43.130.59.228/FFAIWorkspace/workspace/issues/331)

---

## 模块概述

把 AI / 团队成员日常 Gitea 操作（读 issue / 列 issue / 加评论 / 加移 label / 读 PR）从"手写 curl + python heredoc"收敛到 `scripts/ops/gitea` CLI；同时落地通用 CLI 设计规范，让后续项目的所有 CLI 工具长得像同一个工具。

承担"AI-first 工作流的命令行操作收敛"职责。

## 文档导航

| 文档 | 用途 | 何时读取 | 状态 |
|------|------|---------|------|
| [01-prd](./01-prd.md) | v1 功能边界、3 件事拆分、范围内/外清单、验收清单、取舍说明 | 改动 CLI 范围 / 调整规范前必读 | 🚧 Draft |
| **设计规范**：[`docs/standards/15-cli-design-spec.md`](../../standards/15-cli-design-spec.md) | 元规则 5 条 + 退出码 4 档 + JSON 错误结构 + 反模式 + CLI/library 分层 | 写新 CLI / 改本工具前必读 | ✓ Active |

> 当前状态：14-feature-request-workflow `Status/PRD 中` → v1 PR (`#371`) 审查中；merge 后切 `Status/待验收` → 关闭。
>
> 本模块**不规划**独立的 `03-architecture / 06-data-model / 07-api / 09-test-scenarios`——CLI 工具的"架构"就是 `docs/standards/15-cli-design-spec.md` 里的元规则，"API"就是 `scripts/ops/gitea --help`，"data model"就是 Gitea 上游 API 响应字段。所有这些都通过代码 + spec 自表达，写独立文档反而成漂移源头。

## 代码与测试位置

- **CLI 入口**：`scripts/ops/gitea`（可执行 Python 脚本，无扩展名）
- **共享 library**（HTTP / token / 业务原语）：[`scripts/ops/_gitea_api.py`](../../../scripts/ops/_gitea_api.py)
- **一次性迁移脚本**：`scripts/ops/setup-gitea-labels.py`（已跑，把 Status/* + Owner/* 改 scoped）
- **L1 集成测试**：`testing/ops/gitea.test.py`（17 组测试 / ~40 个 assert，覆盖 6 子命令 + promote uat|prod 入口 + 退出码 + scoped 替换透明化 + token 错误结构化；末尾 subprocess 自动连跑 promote 单测）
- **promote 单测**（mock Api）：`testing/ops/gitea_promote_unit.test.py`（5 组测试 / 12 assert，覆盖 cmd_promote 的 E_NOT_FF_ABLE / E_CI_NOT_GREEN / --force-anyway / E_POST_MERGE_DRIFT / happy path）
- **同目录使用说明**：[`scripts/ops/README.md`](../../../scripts/ops/README.md)

## v1 范围（已交付）

| 项 | 文件 |
|---|---|
| 5 类 CLI 子命令（issue get/list/comment / pr get / label add/remove） | `scripts/ops/gitea` |
| 设计规范 v1 | `docs/standards/15-cli-design-spec.md` |
| 平台 scoped labels 迁移（Status/* 6 个 + Owner/* 3 个） | `scripts/ops/setup-gitea-labels.py` + 已跑 |
| L1 集成测试 12 组 / ~29 assert | `testing/ops/gitea.test.py` v1 段 |
| 2 条踩坑沉淀 | `.learnings/2026-05-14-*.md` |
| CLAUDE.md + AGENTS.md 加 #11 #12 不变量 | 入口文档同步 |

## v1.1 范围（PR #384 审查中）

| 项 | 文件 |
|---|---|
| `gitea promote uat\|prod` 子命令（环境升级 FF-only 合并，替代 Gitea web UI 手点） | `scripts/ops/gitea` |
| L1 + 单测覆盖（promote dry-run + 错误路径 mock） | `testing/ops/gitea.test.py` T13–T17 + `testing/ops/gitea_promote_unit.test.py` |
| 政策同步：CLAUDE.md / AGENTS.md / docs/standards/05-development-workflow.md 加「必须用 gitea promote」+「env-promote PR 允许 self-approve」 | 入口文档 + 工作流标准 |
| 起因：PR #383 误点 squash 事故复盘 | `.learnings/ERRORS/ERR-20260515-002-prod-squash-instead-of-ff.md` + `.learnings/2026-05-15-gitea-promote-cli-rollout.md` |

## v1.x 外（后续 PR / 不做）

- 🚧 **下一 PR**：`.gitea/workflows/pr-merge-guard.yml` — PR 自合拦截（CLAUDE.md 铁律工具化，CI workflow 高风险路径单独走）
- ⏸ **v1.2 可选**：`_resolve_label_id` 用 `paginate` 支持 >100 labels；`cmd_issue_list` 加 `--page`；`gitea promote` 时 server-side `head=/base=` filter（**已实测 Gitea 1.21 base 参数被忽略，当前客户端 filter 是唯一可行；等 Gitea 升级后再切**）
- ⏸ **v2 可选**：`gitea pr create`（通用）/ `gitea issue close` / 关 issue 时校验验收清单（PRD 显式 defer 项）
- ❌ **不做**：`.gitea/ISSUE_TEMPLATE/*.yml` 改 yml form required 字段（现 `.md` 模板内联引导更好，required 由 plan-feature skill 流程兜底）

## 踩坑沉淀

- [argparse 全局 flag 不渗到 subparser，要前置 hoist](../../../.learnings/2026-05-14-argparse-global-flags-need-hoisting.md)
- [Gitea label id 硬编码必翻车，永远按名字解析](../../../.learnings/2026-05-14-gitea-label-id-hardcoding-trap.md)
