dev-manager-tauri/.blueprint/modules/gitea-integration.md
lanrtop fccb76783d
All checks were successful
Push & PR Check / check (pull_request) Successful in 1m8s
fix(gitea-integration): 处置 G8 独立审查——删除登记本体补外部清理测试,轮询写入前复核绑定,解绑读删同事务(G8)
- 抽出连接与凭证存储注入的删除登记本体 projects::delete_project_registration
  (delete_project_records + 提交后 Gitea 外部清理),delete_project 只做薄包装;
  补测试校验被删项目的 webhook 签名凭证被清理、他项目凭证保留,以及孤儿绑定可经删除登记清理
- 轮询 commit 与 PR 写入抽成 ingest_polled_commits / ingest_polled_prs,写入前用
  poll_target_still_bound 复核绑定仍在且项目仍登记,避免按本轮开头的旧快照写入已删除或已解绑的项目
- gitea_unlink_repo 的读与删放进同一事务,与删除登记路径同写法
- 孤儿绑定 warn 写明清理路径(对该 ID 调 unregister_project)
- 审查第 5 条(连接池外键只在迁移连接开启)经探针证伪:bundled SQLite 默认 foreign_keys=1,不改

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 10:49:26 +09:00

281 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 回调地址
### 🔵 G8. 删除项目登记时一并解除 Gitea 绑定(轮询不再写入已删除项目)
- status: in_progress
- complexity: M
- depends: G2, G6
- files: src-tauri/src/commands/projects.rs, src-tauri/src/commands/gitea.rs, src-tauri/src/mcp_tools.rs, docs/ai-context/project-context.yaml
- acceptance:
- agent_verify: [cargo test] 删除登记本体(UI 删除按钮与 MCP unregister_project 共用)在同一事务里删除该项目的 gitea_repos 绑定,且只删本项目的绑定 → 回归测试通过;事务内后续删除失败时绑定随之回滚 → 测试通过
- agent_verify: [cargo test] Gitea 轮询目标加载跳过「绑定所属项目已不存在」的遗留绑定并单独返回,供记日志 → 测试通过
- agent_verify: [cargo test] 去掉修复后回归测试失败(变异验证);全量 cargo test 无 FAILED
- 来源:重复登记排查会话发现——删除登记不清 Gitea 绑定,后台轮询(每 10 分钟)继续把 commit 与 PR 事件写到已删除的项目 ID 下
- 实现(2026-10-03,分支 `fix/gitea-unbind-on-unregister`):
- 解绑本体拆成两段、边界只写一处:`gitea::remove_repo_link_records`(只动数据库,可进调用方事务)+ `gitea::cleanup_removed_repo_link`(提交后尽力删除 Gitea 侧 webhook 与本机签名凭证,webhook 删除加 10 秒超时);UI「解绑」`gitea_unlink_repo` 与删除登记共用这两段
- 删除登记本体 `projects::delete_project_registration`(连接与凭证存储注入)= `delete_project_records`(关联表、Gitea 绑定、workspace、孤儿 profile 同一事务)+ 提交后 Gitea 外部清理;UI `delete_project`(薄包装)与 MCP `unregister_project` 都经它
- 轮询兜底 `gitea::load_poll_targets`:所属项目已不存在的遗留绑定不进入 commit / PR 轮询,每个绑定每次运行只记一次 warn(runtime_logs 只留 500 条,逐轮记会挤掉其他日志),日志写明清理路径(对该 ID 调 unregister_project)
- 轮询写入前复核(审查后补):commit 与 PR 的写入抽成 `ingest_polled_commits` / `ingest_polled_prs`,各自先用 `poll_target_still_bound` 复核绑定仍在(owner/repo 一致)且项目仍登记,不在就不写——每轮开头的快照到写入之间可能隔着数分钟的 HTTP 请求
- UI「解绑」`gitea_unlink_repo` 的读与删放进同一事务(审查后补,与删除登记同写法)
- 契约同步:`unregister_project` 描述补「Gitea 仓库绑定」;`docs/ai-context/project-context.yaml` MCP 工具接口段补一句。Tauri command 签名未变,无前端改动,无 schema 变更
- verification(2026-10-03):
- 回归测试 `commands::projects::tests::deleting_project_registration_unbinds_its_gitea_repo` 修复前失败(残留绑定 1 条),修复后通过;`gitea_unbinding_rolls_back_with_failed_project_deletion`(触发器让删 workspace 失败 → 绑定仍在)、`commands::gitea::tests::poll_targets_skip_bindings_whose_project_no_longer_exists`、`unlink_removes_binding_and_webhook_secret_reference` 通过
- 变异验证:去掉删除登记里的解绑调用、去掉轮询的项目存在过滤 → 对应两条测试失败;还原后通过
- 全量 `cargo test`:307 passed,0 failed,5 ignored
- 只读查询运行中炼境 DB:现有 7 条 Gitea 绑定都属于已登记项目,兜底过滤不会误跳过在用绑定,目前也没有遗留孤儿绑定
- 审查处置后新增 3 条测试:`commands::projects::tests::deleting_project_registration_removes_its_webhook_secret`(MemoryCredentialStore,webhook_id 为 NULL 不发网络;被删项目的签名凭证已删、另一项目的保留)、`unregistering_orphaned_binding_id_cleans_the_binding`(workspace 已删、绑定仍在 → 对该 ID 删除登记后绑定与凭证都清掉)、`commands::gitea::tests::polled_results_are_dropped_when_binding_is_gone_mid_round`(快照含三个项目,之后一个删除登记、一个 UI 解绑 → 只有仍绑定的项目写入 commit 与 PR 事件)。既有断言未改
- 审查处置后变异验证:`delete_project_registration` 去掉外部清理 → 两条 projects 新测试失败;`ingest_polled_commits` 去掉复核 → 「deleted 写了 commit」失败;`ingest_polled_prs` 去掉复核 → 「deleted 写了 PR 事件」失败;复核只查项目、不查绑定 → 「unlinked 写了 commit」失败。均从暂存区还原后通过
- 审查处置后全量 `cargo test`:310 passed,0 failed,5 ignored
- 待统筹会话合并后实测:在运行中的炼境里删除一个已绑定的测试项目,确认 Gitea 绑定与 webhook 一并清理、下一轮轮询不再写入该 ID
- review: "2026-10-03 独立审查(只读审查 c1632a0,结论可合并)5 条发现处置:① 低:删除登记提交后的 Gitea 外部清理没有测试 → 采纳。抽出连接与存储注入的本体 `projects::delete_project_registration`,`delete_project` 只做薄包装,补 `deleting_project_registration_removes_its_webhook_secret`;② 低:轮询目标只在每轮开头取一次快照,删除提交后本轮剩余时间仍会写入 → 采纳。commit 与每页 PR 写入前都复核;比审查建议多核对绑定行与 owner/repo,所以 UI 解绑和换绑也同样生效;补 `polled_results_are_dropped_when_binding_is_gone_mid_round`,窗口缩到复核到写入之间的毫秒级;③ 提示:孤儿绑定日志只写了「需清理」→ 采纳。日志写明对该 ID 调 MCP unregister_project,并补 `unregistering_orphaned_binding_id_cleans_the_binding`,用测试校验这条清理路径的数据库与凭证部分;④ 提示:`gitea_unlink_repo` 的读与删不在同一事务(原有问题)→ 采纳(改动 3 行)。用 unchecked_transaction 包住 remove_repo_link_records 再提交,并发设置 webhook 时由 WAL 读快照过期报 busy、整体回滚,不再出现删新行、清旧凭证的错配;没有并发测试;⑤ 提示:生产连接池只有迁移连接开了外键 → 不成立。rusqlite 的 bundled 特性用 `-DSQLITE_DEFAULT_FOREIGN_KEYS=1` 编译 SQLite(libsqlite3-sys 0.30.1 build.rs 第 123 行),每条新连接默认 foreign_keys=1。临时探针测试从 r2d2 内存池取 3 条连接、另开 1 条裸连接,PRAGMA foreign_keys 均为 1,跑完已还原,未提交。测试与生产一致,ON DELETE CASCADE 在生产照常生效。本卡没有改任何既有断言。"
- known_limits: "轮询复核与写入之间还剩毫秒级的非原子窗口:复核之后、写入之前恰好提交的删除,仍可能让一页数据写进去。要彻底消除,得用 BEGIN IMMEDIATE 把复核和写入包在一起,代价是轮询与 UI 写入抢写锁,收益抵不上成本,所以没做。gitea_unlink_repo 的事务化只是按 SQLite WAL 语义推理的结果,没做并发实测(铁律禁止对真实 DB 做并发实验)。薄包装 delete_project 本身没有测试:取连接、purge_project、remove_project_mcp 和本体的先后顺序都没测,只测了本体 delete_project_registration。webhook_id 非空时删除 Gitea 侧 hook 的分支没对真实 Gitea 验证过,测试特意把它设成 NULL,避免发网络请求。外键默认开启依赖 rusqlite 的 bundled 特性;改用系统 SQLite 时要在连接管理器 with_init 里显式开启。"
## 决策记录
- **公网 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)