All checks were successful
Push & PR Check / check (pull_request) Successful in 1m8s
- 抽出连接与凭证存储注入的删除登记本体 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>
281 lines
26 KiB
Markdown
281 lines
26 KiB
Markdown
# 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)
|