# Gitea 集成

对接已有的自建 Gitea 实例，作为所有被管理项目的统一 git 后端。接入包「增配档」的核心。

## 风筝线模型（炼境与 Gitea 的关系）

🪁 **风筝 = Gitea 本体**（git 存储 + CI 执行）：独立部署、独立运行，炼境**不托管其进程、不背运维**。
🧵 **线 = 炼境的掌控**（控制面）：炼境是**唯一持线人**——Gitea 连接/token 统一存炼境，建仓/关联/迁移/配置 lefthook&CI 的操控全经炼境，子项目对 Gitea 的动作都从它手里过。
👀 **关注风筝的动态 = 观测面**（飞轮燃料）：炼境持续感知每个子项目 ↔ Gitea 的连接动态（push 的 commit type/scope、PR、CI 三阶段、review、停滞项目）。

> 「线」≠ 松散 API 对接：炼境是**主动掌控者**而非被动客户端。风筝越多 → 线越多 → 动态越丰富 → 飞轮越强（契合核心愿景）。

## 其他边界

- 替代炼境现有 GitHub 能力：`github-auth` / `github-publish` 已退役（2026-07-02，见 github-decommission），`cicd.rs` 的 GitHub Actions 生成改造为 Gitea Actions
- 炼境自身仓库也迁 Gitea（发布产物仍走 OSS updater 不变）
- **不使用 Gitea Issues / Projects / Milestones / Wiki**：任务跟踪的意图层（acceptance/complexity/depends/复盘）由蓝图任务卡承载，与 Claude 工作流深度耦合；Gitea 只做三件事——存代码、跑 CI、发事件。蓝图与 Gitea 的唯一耦合关节 = conventional commit scope ↔ 蓝图 module id 映射（单点、单向、可降级）

## 参考实现

`enterprise-system` 已趟通完整链路，属「照搬」而非「从零探索」：
- Gitea origin（`git.yljt.info`）+ Gitee 镜像（备份）
- lefthook 强制 conventional commit + typecheck（pre-commit）
- Gitea Actions 三阶段 CI：静态检查 → 测试（DeepSeek-V3 自修）→ PR code review（DeepSeek）
- Gitea Actions 语法兼容 GitHub Actions，迁移成本低

> 详见 `E:/code-project/enterprise-system/docs/ai-context/git-workflow.md` 与 `AGENTS.md`。

## 架构设计（2026-06-30 /architect）

### 实现策略

**webhook 接收端**：扩展已有 MCP Server（Axum，端口 27190），添加 `POST /webhook/gitea/:project_id` 路由。零新依赖，复用端口和进程。

**HTTP 客户端**：`ureq`（Cargo.toml 已有，github.rs 已有使用范例），调 Gitea API v1。

**数据表**：新增 `gitea_instances`（实例管理）+ `gitea_repos`（项目-仓库绑定）。

```sql
CREATE TABLE IF NOT EXISTS gitea_instances (
    id           TEXT PRIMARY KEY,
    name         TEXT NOT NULL,
    url          TEXT NOT NULL,           -- https://gitea.myserver.com
    access_token TEXT NOT NULL,
    is_default   INTEGER NOT NULL DEFAULT 0,
    created_at   TEXT DEFAULT (datetime('now'))
);

CREATE TABLE IF NOT EXISTS gitea_repos (
    id              TEXT PRIMARY KEY,
    instance_id     TEXT NOT NULL REFERENCES gitea_instances(id) ON DELETE CASCADE,
    project_id      TEXT NOT NULL,        -- blueprint_buffs.project_id（UUID）
    gitea_owner     TEXT NOT NULL,
    gitea_repo_name TEXT NOT NULL,
    clone_url       TEXT,
    webhook_secret  TEXT,                 -- HMAC-SHA256 secret
    webhook_id      INTEGER,              -- Gitea 返回的 hook ID
    linked_at       TEXT DEFAULT (datetime('now'))
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_gitea_repos_project ON gitea_repos(project_id);
```

**webhook 流程**：
```
Gitea push → POST /webhook/gitea/:project_id
  1. 从 gitea_repos 查 webhook_secret
  2. HMAC-SHA256 验签 X-Gitea-Signature（兼容 X-Hub-Signature-256）
  3. 解析 payload.commits[]（sha / message / timestamp）
  4. 批量 record_commit(project_id, sha, message, date)
  5. 返回 200 OK；验签失败记 log_event
```

> 修正（2026-07-02，R10）：原设计写的签名头 `X-Gitea-Signature-256` 有误，Gitea 实际发送
> `X-Gitea-Signature`（payload 的 HMAC-SHA256 hex）；实现以实际行为为准并兼容 GitHub 风格头。
> project_id 采用与 commit_metrics/ingest_full_git_history 相同的 Project.id（getProjects 体系），
> 避免重蹈 project_id 双路径冲突（b13dc83 教训）。

**网络说明**：Gitea 服务器需能访问宿主机 `{ip}:27190`（MCP 端口）。UI 显示可复制的 webhook 回调地址，用户自行确认网络可达。

---

## 任务卡（concept，待 /architect）

### 💭 Gitea API 对接（握线基础）
- status: concept
- complexity: L
- acceptance: TBD —— 建仓/关联、推送、读 CI 状态、读 PR review 结果

> ⚠️ 建仓/关联部分已被 G1/G2 实现取代；剩余范围收窄为「读 CI 状态、读 PR review 结果」（依赖 Gitea Actions 落地后再议）。

### 💭 观测面：Gitea webhook 接入（持线人感知动态）
- status: concept
- complexity: L
- files: src-tauri/src/mcp_server.rs（内嵌 HTTP server 加 webhook 端点）, src-tauri/src/commands/blueprint.rs
- acceptance: TBD —— 复用炼境内嵌 HTTP server 新增 Gitea webhook 接收端点；实时接住各子项目 push/PR/CI/review 事件 → 解析 conventional commit（对接 flywheel-intelligence 的 L0 解析层）→ 写飞轮快照 + 刷新连接动态；webhook 不可达时降级为定时轮询 Gitea API

> ⚠️ push 事件部分已被 G3/G4 实现取代；剩余范围收窄为「PR/CI/review 事件 + 轮询降级」（见「PR 事件 ↔ 蓝图任务卡联动」concept 卡）。

### ✅ Gitea Actions workflow 生成
- status: done
- complexity: M
- depends: G1
- files: src-tauri/src/commands/cicd.rs, src/components/dashboard/CicdModal.tsx
- acceptance: generate_workflow 产出 Gitea Actions 模板（.gitea/workflows/，语法兼容 GitHub Actions，参考 enterprise-system 三阶段 CI）；web_ssh 与 tauri_oss 两类配置均支持；CicdModal 的 Secrets 提示文案改为 Gitea 路径（Settings → Actions → Secrets）；生成后模板中不残留 github.com 引用

> 一石三鸟：炼境自身仓库迁 Gitea 有了工具；CicdModal 残留 GitHub 模板的问题解决（见 github-decommission 复盘遗留②）；CI 事实流有了源头。

### 💭 DeepSeek CI review 集成（可选插件）
- status: concept
- complexity: L
- acceptance: TBD —— 静态检查 + 测试自修 + PR review；作为复杂项目可选增配，不进基线档

### ✅ R0. 炼境自身发版链路迁移（本机 act_runner + Gitea Actions）
- status: done
- complexity: M
- depends: Gitea Actions workflow 生成
- files: .gitea/workflows/release.yml（生成）, .github/workflows/release.yml（删除）
- acceptance: 本机安装 act_runner 并注册到 git.yljt.info（windows 标签，出站长连接、无入站需求）；用 CicdModal 为炼境生成 Gitea 版 release workflow 并按需微调；删除 .github/workflows/ 死配置；push 一个测试 tag 走通「构建 → 签名 → OSS 上传 → latest.json」全链路，客户端自动更新恢复供给

> 背景：GitHub 退役后 tag 不再触发旧 CI，自动更新断供——这是当前唯一的结构性不自洽。act_runner 主动出站拉任务，完美适配公网 Gitea + 内网本机拓扑；Tauri Windows 构建本就需要 Windows 环境，本机即最合适的 runner。

### 💭 旧项目 GitHub → Gitea 迁移
- status: concept
- complexity: M
- acceptance: TBD —— remote 切换 + 推送，安全增量，不丢历史

### ✅ G7. 新建仓库后一键接线（本地 init + 首次 push）
- status: done
- complexity: M
- depends: G2
- files: src/components/dashboard/GiteaPanel.tsx, src/lib/commands.ts
- acceptance: gitea_create_repo/gitea_link_repo 成功后，检测项目本地状态（复用 scan_local_git）：无 .git → 提供「一键接线」按钮执行 init + 首次 commit（复用 git_init_and_commit）+ 设 origin（git_remote_set，clone_url 已知）+ push（git_push）；已有 git 但无 origin → 只做后两步；已有 origin → 不显示按钮；每步失败给出可读错误；接线完成后提示「日常 commit/push 归项目会话（接线/放风筝边界）」

### 💭 /ship 泛化 + 工作流档位分发
- status: concept
- notes: /ship 去 enterprise-system 硬编码（仓库路径从 git remote get-url origin 推导、分支模型可配置），进入增配档接入包成为可一键分发的能力；配套「基线档 push CI」workflow 模板（master push → typecheck + cargo test/vitest，远端强制测试补"测试烂在本地"的洞，产出 CI 状态数据即观测面首批 CI 燃料）。项目导师按画像提示升降档。

### 💭 PR 事件 ↔ 蓝图任务卡联动
- status: concept
- depends: G3
- notes: webhook 收到 PR 事件 → 解析 title 的 conventional scope → 匹配蓝图模块 → 对应任务卡置 🔵 in_progress（加锁提示）。铁律 R09：绝不自动置 ✅，完成语义只能由 Claude/人工验收产生；merge 事件仅在导师报告中生成"待验收提醒"。不使用 Gitea Issues/Projects 做任务系统（见决策记录）。

---

## 任务卡（可执行，拆自 2026-06-30 /architect）

### ✅ G1. DB schema + Gitea 实例 CRUD
- status: done
- complexity: M
- files: src-tauri/src/db.rs, src-tauri/src/commands/gitea.rs, src-tauri/src/lib.rs, src/lib/commands.ts
- acceptance: gitea_instances + gitea_repos 表建好（migrate）；save/list/delete/test_connection 命令可调；test_connection 调 GET /api/v1/user 返回 Gitea 用户名验证 token 有效性

### ✅ G2. 仓库创建 / 迁移 / 绑定
- status: done
- complexity: M
- depends: G1
- files: src-tauri/src/commands/gitea.rs, src/lib/commands.ts
- acceptance: gitea_create_repo 调 Gitea API 建仓后写 gitea_repos；gitea_migrate_repo 能从 GitHub/GitLab 迁移仓库到 Gitea；gitea_link_repo 绑定已有仓库；命令失败返回可读错误

### ✅ G3. Webhook 接收端（MCP Server 扩展）
- status: done
- complexity: M
- depends: G1
- files: src-tauri/src/mcp_server.rs
- acceptance: POST /webhook/gitea/:project_id 路由上线；HMAC-SHA256 验签通过后解析 commits 数组批量写 commit_metrics；验签失败记 log_event；单元测试覆盖 parse_push_payload + hmac_verify

### ✅ G4. Webhook 注册命令
- status: done
- complexity: M
- depends: G2, G3
- files: src-tauri/src/commands/gitea.rs, src/lib/commands.ts
- acceptance: gitea_setup_webhook 生成随机 secret，调 Gitea API POST /repos/:owner/:repo/hooks 注册（events=push），写 webhook_secret + webhook_id 到 gitea_repos；gitea_unlink_repo 清理绑定

### ✅ G6. 远端轮询采集（webhook 不可达时的降级路径）
- status: done
- complexity: M
- depends: G2
- files: src-tauri/src/commands/gitea.rs, src-tauri/src/lib.rs, src/lib/commands.ts, src/components/dashboard/GiteaPanel.tsx
- acceptance: 后台常驻线程每 10 分钟对所有 gitea_repos 绑定仓库调 GET /api/v1/repos/:owner/:repo/commits 拉取最新 commit，record_commit 幂等写入；gitea_poll_now 命令供 UI 手动触发并返回摘要；解析函数有单元测试；轮询结果记 log_event；适配「公网 Gitea + 内网炼境」拓扑（纯出站，无需 webhook 可达）

> 背景：用户实际拓扑为公网云 Gitea + 内网本机炼境，webhook 入站不可达；且不宜 frp 全端口映射 27190（MCP 路由无鉴权）。轮询是该拓扑下唯一安全的远端采集方式。

### ✅ G5. Gitea 治理面板 UI
- status: done
- complexity: M
- depends: G1, G2, G3, G4
- files: src/components/dashboard/GiteaPanel.tsx, src/components/dashboard/BlueprintModal.tsx
- acceptance: 治理面板新增"Gitea"区块；可添加/测试/删除实例；显示已绑定仓库列表（仓库名/webhook 状态）；操作：新建仓库、迁移、绑定已有、设置 webhook；显示可复制的 webhook 回调地址

## 决策记录

- **公网 Gitea + 内网炼境为标准拓扑（2026-07-02 裁决）**：观测面以 G6 出站轮询为主（每 10 分钟），webhook 仅作同网/有穿透场景的补充。理由：① 炼境 27190 端口（MCP 路由无鉴权）保持零公网暴露；② 飞轮是统计系统非监控系统，10 分钟延迟在天/周分析粒度下为零影响；③ /ship 在 enterprise-system 已实证全出站 API 可支撑完整 PR 流水线；④ 主动拉取更贴合风筝线"持线人"定位。若未来需秒级实时：云服务器 nginx 仅反代 /webhook/gitea/ 路径 + 内网穿透（备案，不建议）
- 不用 frp 直接映射 27190：MCP Server 工具路由无鉴权，全端口暴露 = append_project_note 等工具裸奔公网
- **建仓边界·接线/放风筝（2026-07-03 裁决）**：炼境管"接线"=一次性 git 基建（建远端仓库/迁移/绑定/webhook + 本地 init/首次 commit/设 origin/首次 push——机械、零意图判断）；子项目 AI 会话管"放风筝"=日常内容生产（commit/push/分支/PR/蓝图内容——每步带意图）。判据：不需要理解项目内容的 git 操作归炼境，需要理解的归项目会话。原 GitHub PublishModal 本就覆盖全链路，Gitea 版只重建了远端一半，本地接线断档由 G7 补
- **工作流两档制（2026-07-02 裁决）**：/ship 完整 PR 流水线是"稳定剂"元素，有明确 scope（事故成本高：对外交付/生产系统）与 boundary（探索期高频迭代项目，PR 税会腰斩节奏——炼境自身单日 20+ commit 实证）。炼境不统一推广任何工作流，按项目画像分档：基线档=直推+lefthook+本地测试+push CI；增配档=/ship 全流水线（enterprise-system 为活样板）。升降档判据交给飞轮数据（返工率/CI 拦截率/review 有效率），工作流选择本身进入置信度体系。炼境自身升档时机：稳定使用期 或 飞轮阶段三需要 PR/review 数据

## 复盘笔记

【复盘】G7 一键接线 + autoforphone 首个 UI 全流程接入（2026-07-03）
任务类型: 前端组件 + 实战接入
复杂度: M
实际轮数: 3轮（预估 1 轮）
是否返工: 是
返工原因: ① 建仓 403——token 缺 write:user scope（POST /user/repos 归 user 类，权限指导遗漏）；② 改 Token 静默失效——giteaUpdateInstance 前端传 snake_case 参数被 Tauri camelCase 匹配丢弃（潜伏自 G1 的未使用封装）；③ 首推 980MB 断连——lib/ 下 6 个 AutoJS6 APK + 源码归档被收进首次 commit，超反代请求体限制
有效策略: "没有最近活动"（Gitea token 页）证明请求未到达 → 直指本地存储问题；git ls-tree -l 按体积排序秒定大文件元凶；远端为空时 amend 首次 commit 即可，无历史包袱
遗留改进（G7 检测盲区）: ① origin 已配置但远端为空/首推未成功的中间态不触发接线引导（本次因手动 remote add 提前存在而按钮未现）；② 接线前无大文件预检（>50MB 警告），撞上 APK 类产物会断连——两项待下轮迭代
auto_applied_rules: R08, R10
rule_effectiveness: R08=有效（两处"UI 报成功实际没发生"均按此定性）, R10=有效（scope 归类以 403 实测为准）

【复盘】R0 发版链路迁移 + 换钥（2026-07-02）
任务类型: 基础设施 + 架构调整
复杂度: M（实际含排障接近 L）
实际轮数: 4轮（预估 1 轮）——3 次构建失败排障 + 1 次换钥
是否返工: 是
返工原因: ① 签名密钥密码丢失（唯一存储在不可读的 GitHub Secrets + 用户记忆），构建反复死于 Wrong password，本机全部留痕渠道（shell 历史/AI 会话/文档 git 历史/密钥库）搜索无果，最终按单人场景换钥（手动装一次新版的代价≈0）；② act_runner 排障三连：生成的 config.yaml 默认 docker labels 导致 daemon 拒启（须 docker_host: "-" + labels windows:host）、迁移仓库默认关闭 Actions 单元（API PATCH has_actions 开启）、Gitea 1.22 无 /actions/runs API（改用 commit statuses API 监控）
有效策略: runner 在本机 = 失败日志本地可查（trace 级日志直接定位 Wrong password 真凶）；换钥前先验证「存量客户端规模」推翻了"换钥死锁"的默认假设
遗留风险: 无。全链路已验证：tag push → 本机 runner 构建签名（4m43s）→ OSS latest.json 0.1.16 上线
auto_applied_rules: R10
rule_effectiveness: R10=有效（Gitea 1.22 API 行为以实测为准，两处文档假设被纠正）

【教训·已固化】密钥类 secret 必须双写存储：新签名密码同时存于 Gitea Secrets + 炼境密钥凭据库。单一存储在"不可读的 secret 管理器 + 人脑"等于定时炸弹。

【复盘】G6 远端轮询采集（2026-07-02）
任务类型: 后端命令 + 前端组件
复杂度: M
实际轮数: 1轮（预估 1 轮）
是否返工: 否
有效策略: 需求源自真实拓扑约束（用户确认公网 Gitea + 内网炼境），先做安全裁决（否决 frp 全端口映射）再写代码；解析函数纯函数化可测；复用 record_commit 幂等性使轮询与本地 watcher/全量导入三路径共存不重复计数
遗留风险: ① 轮询间隔 600s 硬编码，仓库数大时（50+）单轮串行请求可能拉长，届时可加并发或间隔配置；② limit=50 意味着 10 分钟内单仓库超 50 commit 会漏采（实际不可能，全量导入可兜底）
auto_applied_rules: R01, R07
rule_effectiveness: R01=有效（gitea_poll_now 命令与 commands.ts 封装同步）, R07=有效（解析纯函数 2 测试）

【复盘】Gitea Actions workflow 生成（2026-07-02）
任务类型: 后端命令 + 前端组件
复杂度: M
实际轮数: 1轮（预估 1 轮）
是否返工: 否
有效策略: 语法兼容性使改造收敛为三处——输出目录 .gitea/workflows、去掉 GitHub Release 步骤（发布走 OSS 不需要 Release 页）、runs-on 改自建 runner 标签；用单元测试断言"无 github.com/GITHUB_TOKEN 残留"把验收标准变成回归测试
遗留风险: ① tauri_oss 模板需要带 windows 标签的自建 act_runner，尚未实际部署；② actions/cache（setup-node 的 pnpm 缓存）依赖 act_runner 的 cache server，旧版本 runner 可能不生效
auto_applied_rules: R01, R07
rule_effectiveness: R01=有效（命令未新增，仅改产出）, R07=有效（模板残留断言测试）

【复盘】G2-G5 控制面 + 观测面全链路（2026-07-02）
任务类型: 后端命令 + MCP工具 + 前端组件
复杂度: M×4
实际轮数: 1轮（预估 2 轮）
是否返工: 否
有效策略: 续接 🔵 锁前先读代码判断真实断点（G1 已含查询命令，G2 只缺三个写命令）；webhook 验签/解析拆成纯函数使单元测试不依赖 HTTP 层；绑定 UI 的 project_id 显式对齐 ingest_full_git_history 的 Project.id 体系
遗留风险: ① ~~全链路未在真实 Gitea 实例上端到端验证~~（已消除，2026-07-02 晚：git.yljt.info 实测通过——实例连接 ✓ 迁移绑定 ✓ push ✓ 轮询采集 ✓；webhook 因公网拓扑不适用，由 G6 轮询替代）；② gitea_migrate_repo 未处理同名仓库已存在的场景（Gitea 返回 409，错误信息可读但需手动改名）；③ webhook 回调地址依赖用户确认网络可达，无自动探测
auto_applied_rules: R01, R05, R06, R07, R10
rule_effectiveness: R01=有效, R05=有效（复用已有表无新 migration）, R06=有效（upsert 依赖注入可测）, R07=有效（5 个新测试）, R10=有效（纠正了设计文档中错误的签名头 X-Gitea-Signature-256 → 实际 X-Gitea-Signature）
