docs: project scaffolding — README + architecture + roadmap + porting plan + ADRs

Initial documentation for route A (port claude-code-action to Gitea Actions).
Research complete, no implementation code yet. See docs/02-roadmap.md for milestones.
This commit is contained in:
pangtiankai 2026-05-25 20:36:54 +08:00
parent cba50bc083
commit c29af91113
6 changed files with 551 additions and 1 deletions

View File

@ -0,0 +1,35 @@
# 占位 / 模板 —— 实际 workflow 会在 Milestone A2 阶段填充并改名为 claude.yml
# 这里只展示目标形态,让任何来读仓库的人都能立刻理解最终调用方式
name: Claude Code Agent
on:
issues:
types: [opened, edited]
issue_comment:
types: [created]
pull_request:
types: [opened, synchronize]
permissions:
contents: write
issues: write
pull-requests: write
jobs:
claude:
# 仅在评论/正文含 @claude 时跑
if: contains(github.event.comment.body, '@claude') || contains(github.event.issue.body, '@claude') || contains(github.event.pull_request.body, '@claude')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run claude-gitea-agent
uses: pangtiankai/claude-gitea-agent@main
with:
gitea_token: ${{ secrets.GITEA_TOKEN }}
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
# 可选:限制 Claude 能用的工具
# allowed_tools: 'Read,Edit,Write,Bash'

View File

@ -1,3 +1,48 @@
# claude-gitea-agent
Claude Code agent that responds to Gitea issues — interactive requirements discussion + auto PR (path A: ported claude-code-action)
> Claude Code 自动响应 Gitea issue 的代理 —— 在 issue 里和你交互式确认需求,对齐后自动开发并提 PR。
## 项目目标
复刻 Anthropic 官方 [`claude-code-action`](https://github.com/anthropics/claude-code-action) 的 GitHub 体验,但跑在 Gitea 上:
- **触发**:在 Gitea issue 或 PR 评论里 `@claude ...`
- **交互**Claude 在评论里和你来回讨论需求superpowers brainstorming 风格)
- **执行**:对齐后自动建分支、写代码、跑测试、提 PR
- **运行环境**:你的本地 act_runner已部署
## 当前状态
🟡 **筹划期** —— 仓库刚建好docs 在写,代码移植还没开始。
| 阶段 | 状态 |
|---|---|
| 调研开源方案 | ✅ 完成([docs/02-roadmap.md](docs/02-roadmap.md) |
| 仓库脚手架 | ✅ 完成 |
| Gitea API 客户端移植 | ⏳ 待开始 |
| 触发器 + 评论交互 | ⏳ 待开始 |
| PR 生成流程 | ⏳ 待开始 |
| 端到端跑通 | ⏳ 待开始 |
| 升级到 webhook 长驻服务(路线 B | 🔮 未来 |
## 架构选择
**路线 A**fork 上游 `claude-code-action`,替换 GitHub API 调用为 Gitea API跑在 Gitea Actions 上。
- 优点:站在巨人肩膀上,提示词/上下文构造/PR 流程沿用官方逻辑
- 缺点Gitea Actions 冷启动 ~30s每轮交互有延迟不算"真实时"
- 后续可升级到路线 B长驻 webhook 服务 + Claude Agent SDK细节见 [docs/02-roadmap.md](docs/02-roadmap.md)
详见 [docs/01-architecture.md](docs/01-architecture.md)。
## 快速链接
- [01 - 架构](docs/01-architecture.md)
- [02 - 路线图](docs/02-roadmap.md)
- [03 - 移植计划](docs/03-porting-plan.md)
- [04 - 设计决策](docs/04-decisions.md)
- 上游参考:[anthropics/claude-code-action](https://github.com/anthropics/claude-code-action)
## License
MIT

110
docs/01-architecture.md Normal file
View File

@ -0,0 +1,110 @@
# 01 - 架构
## 总体数据流
```
┌─────────────┐ ┌────────────┐
│ 开发者 │ ① @claude 在 issue/PR 评论提需求 │ Gitea │
│ (浏览器) │ ───────────────────────────────────────▶│ (3000) │
└─────────────┘ └─────┬──────┘
│ ② webhook → workflow_dispatch
┌───────────────┐
│ act_runner │
│ (本机容器) │
└─────┬─────────┘
│ ③ 启动 Action
┌────────────────────────────────────┐
│ claude-gitea-agent Action │
│ ┌──────────────────────────────┐ │
│ │ 1. parse trigger │ │
│ │ 2. fetch issue/PR context │ │
│ │ 3. build prompt + skills │ │
│ │ 4. exec `claude` CLI │ │
│ │ 5. post comment / open PR │ │
│ └──────────────────────────────┘ │
└──────┬─────────────────────────────┘
│ ④ Gitea API (评论/分支/PR)
┌────────────┐
│ Gitea │ ⑤ 用户在评论里看到 Claude 回复
└────────────┘
```
## 关键组件
### 1. 触发器trigger
| 入口 | 上游 | Gitea 对应 |
|---|---|---|
| `issue_comment` | `github.event.comment.body` | `gitea.event.comment.body` ✅ 一致 |
| `issues` (opened/edited) | 一致 | 一致 |
| `pull_request_review_comment` | 一致 | 一致 |
| `workflow_dispatch` | 手动触发 | 一致 |
触发短语:`@claude` 在评论/issue 正文里出现。
### 2. Gitea API 适配层src/gitea/
**对应上游 `src/github/` 的 1:1 重写**
| 上游模块 | Gitea 移植 | 改造点 |
|---|---|---|
| `github/api/` | `gitea/api/` | 用 Gitea Swagger SDK 或 fetchendpoint 90% 相似 |
| `github/data/` | `gitea/data/` | 数据 schema 重写(少量字段差异) |
| `github/operations/` | `gitea/operations/` | 评论 / branch / PR 创建逻辑 |
| `github/validation/` | `gitea/validation/` | webhook 签名校验、权限检查 |
| `github/utils/` | `gitea/utils/` | 工具函数 |
### 3. 提示词层src/create-prompt/
**几乎不动**。生成给 Claude 的初始 prompt包含 issue 标题/正文、相关代码、历史评论)。
### 4. 模式层src/modes/
- `tag` 模式:被 @claude 提到时响应(**主用**
- `agent` 模式:显式 prompt 启动(高级用法,后期支持)
**几乎不动**。
### 5. Claude 执行层
调用本机 `claude` CLI 或 `@anthropic-ai/claude-agent-sdk`。携带:
- `CLAUDE_AGENT_SDK_AUTH`:用户的 API keyGitea Secret
- `--add-dir`:仓库 checkout 后的工作目录
- `--allowed-tools`:默认 Read/Edit/Write/Bash可通过 inputs 限制
## Gitea Actions 兼容性预判
| 上游依赖 | Gitea Actions 表现 | 处理 |
|---|---|---|
| `@actions/core` | ✅ 完全兼容 | 原样用 |
| `@actions/github` | ❌ 调用 GitHub API | 替换为 Gitea API |
| `@octokit/rest` | ❌ GitHub-only | 替换为 Gitea SDK 或 fetch |
| `@octokit/graphql` | ❌ Gitea 暂无 GraphQL | 改写为 REST 拼接 |
| `@octokit/webhooks-types` | 类型定义 | 自己写 Gitea 的 types |
| `@modelcontextprotocol/sdk` | ✅ 与平台无关 | 原样用 |
| `@anthropic-ai/claude-agent-sdk` | ✅ 与平台无关 | 原样用 |
| `actions/checkout@v4` | ✅ Gitea Actions 兼容 | 原样用 |
## 关键路径估算(路线 A
| 阶段 | 工作量 | 关键风险 |
|---|---|---|
| 搭基础types/auth/api 骨架) | 2-3 天 | Gitea SDK 选型 |
| 端口 issue 评论 + PR 评论场景 | 3-4 天 | webhook payload 字段差异 |
| 端口 branch 创建 + PR 提交 | 2-3 天 | Gitea PR API 偶尔有差异 |
| 跑通 end-to-end demo | 1-2 天 | act_runner Docker socket 权限 |
| 文档 + 自部署指南 | 1 天 | - |
| **合计** | **~2 周** | |
## 升级到路线 B 的接口
设计时**保留这两个边界**便于以后切到长驻服务:
1. `EventDispatcher` 接口:当前实现读 `GITEA_EVENT_PATH` 文件;未来实现订阅 webhook
2. `SessionStore` 接口:当前每次 Action run 都重新 build prompt未来用同一个 Claude session 持续追加消息
具体见 [02-roadmap.md](02-roadmap.md)。

111
docs/02-roadmap.md Normal file
View File

@ -0,0 +1,111 @@
# 02 - 路线图
## 调研结论2026-05-25
调研了 4 个候选 OSS
| 项目 | Stars | 契合度 | 结论 |
|---|---|---|---|
| anthropics/claude-code-action | 7.7k | ⭐⭐⭐⭐⭐ | **GitHub Only**,移植到 Gitea 是这次项目的核心工作 |
| All-Hands-AI/OpenHands | 40k+ | ⭐⭐⭐ | 非 Claude 原生Gitea 支持弱,引入了大量不需要的东西 |
| SWE-agent/SWE-agent | 19.3k | ⭐⭐ | 学术为主,一次性自动修,不擅长多轮交互 |
| Turrain/claude-gitea-app | 1 | ⭐⭐⭐ | 名字对路但太小20KB不能依赖 |
**Gitea 生态周边可复用**
- [Sqcows/forgejo-mcp](https://github.com/Sqcows/forgejo-mcp) — Forgejo/Gitea MCP server103 工具
- [pkulik0/gitea-skill](https://github.com/pkulik0/gitea-skill) — `tea` CLI 的 Claude Code Skill
**结论**:复刻 `claude-code-action` 是最直接的路径OpenHands 等通用方案在交互性上不及。
---
## 路线 A移植 `claude-code-action` 到 Gitea Actions当前阶段
**目标**:端到端跑通最小可用版本,体感先达到 "issue 里说话 → Claude 在评论里回复 → 同意后 PR 自动开"。
### Milestone A1基础设施第 1 周)
- [ ] fork upstream确定 TS/Bun 工具链能在 act_runner 里跑
- [ ] 在 Gitea 控制面板创建 `@claude` bot 用户 + 对应 access token
- [ ] 验证 Gitea webhook → workflow_dispatch 链路
- [ ] 写 Gitea API 客户端(`src/gitea/api/`),覆盖:
- 评论 CRUD
- issue/PR 读取
- 分支创建 + push
- PR 创建
### Milestone A2tag 模式跑通(第 2 周)
- [ ] 移植 `src/modes/tag/`@claude 触发的最常用模式)
- [ ] 移植 `src/create-prompt/`(基本不动,确认 context 字段映射)
- [ ] 移植 `src/entrypoints/`(适配 Gitea 事件载荷格式)
- [ ] `.gitea/workflows/claude.yml` 示例工作流
- [ ] 在本仓库自己开 issue 验证"对话+提 PR"流程
### 验收标准
```
1. 我在某个 Gitea 仓库开 issue「加一个返回当前时间的 /now 接口」
2. issue 正文末尾 @claude
3. 1 分钟内出现 Claude 评论:「先确认几点... a/b/c」
4. 我回复评论「a 这样b 那样」
5. Claude 再回复对齐 + 开始执行
6. Claude push 分支 + 开 PRPR 描述里贴着完整对话和实现说明
```
---
## 路线 B长驻 webhook 服务(未来)
**触发条件**:路线 A 跑通后,发现每轮交互 30s+ 冷启动太难受,再启动 B。
### 架构差异
```
路线 A每次评论 → 启动 Action → 加载 context → 退出 (冷)
路线 B每次评论 → POST 到长驻服务 → 复用 Claude session → 立刻回复 (热)
```
### 主要工作
- 用 Python 或 Node 写一个常驻 webhook serverFastAPI / Hono
- 用 `@anthropic-ai/claude-agent-sdk` 维护 `{issue_id: ClaudeSession}` 字典
- 每条 Gitea 评论 webhook → 找到对应 session → `session.send_message()` → 回写评论
- 长会话超时回收策略24h 不活跃释放)
- 加载 superpowers skills[skills 目录注入](https://docs.claude.com/api/agent-sdk)
- 部署:和 Gitea 同机 docker-compose加一个 `claude-agent` 服务
### 升级路径
设计路线 A 代码时已保留两个抽象接口:
```typescript
interface EventDispatcher {
// A: read GITEA_EVENT_PATH file
// B: subscribe to webhook stream
next(): Promise<GiteaEvent>
}
interface SessionStore {
// A: fresh prompt each run
// B: persistent ClaudeSession per issue
get(key: string): Promise<Session>
save(key: string, session: Session): Promise<void>
}
```
切换路线时只需新实现这两个接口核心业务逻辑trigger 解析、Gitea API、PR 流程)不动。
---
## 不做的事(明确拒绝)
- ❌ **不**追求支持 GitHub + Gitea + GitLab 多平台。Gitea-only避免抽象税
- ❌ **不**在路线 A 阶段做 Web UI / dashboard。命令行 + Gitea 评论已够
- ❌ **不**做"全自动决策合并 PR"。Claude 提 PR人审核合并
- ❌ **不**移植上游所有 modereview/agent 等),只做 tag mode
## 决策记录
非显然的设计选择记在 [04-decisions.md](04-decisions.md)。

139
docs/03-porting-plan.md Normal file
View File

@ -0,0 +1,139 @@
# 03 - 移植计划
> 把 `anthropics/claude-code-action` 源代码逐文件迁移到本仓库的清单。
> 上游版本master @ 2026-05-23克隆在 `/home/pangtiankai/projects/claude-code-action-upstream/`
## 目录映射
| 上游 | 本仓库 | 移植量 |
|---|---|---|
| `action.yml` | `action.yml` | 🟡 改 inputs去掉 GitHub 特有字段(如 `github_token``gitea_token` |
| `package.json` | `package.json` | 🟡 替换 `@octokit/*` 依赖 |
| `bunfig.toml` | `bunfig.toml` | ✅ 原样 |
| `tsconfig.json` | `tsconfig.json` | ✅ 原样 |
| `src/entrypoints/` | `src/entrypoints/` | 🟡 适配 Gitea 事件载荷字段 |
| `src/modes/tag/` | `src/modes/tag/` | 🟢 90% 原样,少量字段重命名 |
| `src/modes/agent/` | — | ⛔ **暂不移植**(后期再说) |
| `src/create-prompt/` | `src/create-prompt/` | 🟢 90% 原样 |
| `src/github/api/` | `src/gitea/api/` | 🔴 **完全重写**Gitea API client |
| `src/github/data/` | `src/gitea/data/` | 🔴 重写 schema |
| `src/github/operations/` | `src/gitea/operations/` | 🔴 重写评论、branch、PR |
| `src/github/validation/` | `src/gitea/validation/` | 🔴 重写 |
| `src/github/utils/` | `src/gitea/utils/` | 🟡 部分通用,部分重写 |
| `src/auth/` | `src/auth/` | 🟢 90% 原样Anthropic API key 认证不变) |
| `src/mcp/` | `src/mcp/` | ✅ 原样 |
| `src/utils/` | `src/utils/` | ✅ 原样 |
| `base-action/` | `base-action/` | 🟢 90% 原样 |
**统计**:约 30% 文件要重写60% 只改字段或导入路径10% 完全不动。
## 重写优先级P0 = 第一周必交付)
### P0 — Gitea API client`src/gitea/api/`
最低要凑齐的 endpoint
| 操作 | Gitea API | 备注 |
|---|---|---|
| 获取 issue | `GET /repos/{owner}/{repo}/issues/{number}` | |
| 获取 issue 评论列表 | `GET /repos/{owner}/{repo}/issues/{number}/comments` | |
| 创建评论 | `POST /repos/{owner}/{repo}/issues/{number}/comments` | |
| 更新评论 | `PATCH /repos/{owner}/{repo}/issues/comments/{id}` | 用于流式更新("思考中..."→ 最终回复) |
| 获取 PR | `GET /repos/{owner}/{repo}/pulls/{number}` | |
| 获取 PR diff | `GET /repos/{owner}/{repo}/pulls/{number}.diff` | |
| 创建分支 | `POST /repos/{owner}/{repo}/branches` | |
| 创建 PR | `POST /repos/{owner}/{repo}/pulls` | |
| 获取仓库内容 | `GET /repos/{owner}/{repo}/contents/{filepath}` | |
**选择**:直接用 `fetch` + zod schema不引入完整 Gitea SDK依赖最小化
### P0 — Trigger 解析(`src/modes/tag/`
需要适配的事件类型:
| Gitea 事件 | trigger 条件 |
|---|---|
| `issues.opened` | issue 正文含 `@claude` |
| `issues.edited` | 同上 |
| `issue_comment.created` | 评论正文含 `@claude` |
| `pull_request.opened` | PR 正文含 `@claude` |
| `pull_request_comment.created` | PR 评论含 `@claude` |
### P0 — Entrypoint`src/entrypoints/`
读取 `GITHUB_EVENT_PATH` → 改成 `GITEA_EVENT_PATH`Gitea Actions 自动注入)。
事件载荷字段:
```
github.event.issue.user.login → gitea.event.issue.user.login ✅ 一致
github.event.comment.user.login → gitea.event.comment.user.login ✅ 一致
github.repository → gitea.repository ✅ 一致
github.event.pull_request.head.sha → 一致
```
### P1 — Status comment"我在干活" 进度评论)
上游用 GitHub Checks API 做进度展示Gitea 没有等价 API[Gitea Issue #28381](https://github.com/go-gitea/gitea/issues/28381) 是 commit status不一样
替代方案:**用一条评论的连续 edit 模拟**。
- 第一条评论:"🤔 Claude 正在分析需求..."
- 边干边 PATCH 更新这条评论的正文
- 完成后定格成最终内容
### P2 — `agent` mode、`review` mode
后期再说。
## 依赖替换
### package.json
去掉:
```json
"@actions/github": "^...",
"@octokit/rest": "^...",
"@octokit/graphql": "^...",
"@octokit/webhooks-types": "^..."
```
新增(如果需要):
```json
// 如果用生成的 SDK
"gitea-js": "latest"
// 否则只用 fetch + zod零新增
```
保留原样:
```json
"@actions/core": "...", // 跟 Gitea Actions 兼容
"@anthropic-ai/claude-agent-sdk": "...",
"@modelcontextprotocol/sdk": "...",
"shell-quote": "...",
"zod": "..."
```
## webhook payload 差异速查
Gitea 的 webhook payload 大部分字段和 GitHub 一致(这是有意为之的),主要差异:
| GitHub | Gitea | 说明 |
|---|---|---|
| `pull_request.head.repo` | `pull_request.head.repo` | Gitea 始终有此字段GH 偶尔 null |
| `installation` | — | Gitea 没有 GitHub App 概念 |
| `sender.type === "Bot"` | `sender.type === "user"` | Gitea 暂无 Bot 用户类型 |
完整差异参考:[Gitea webhook 文档](https://docs.gitea.com/usage/webhooks)。
## 测试策略
1. **本仓库自身做 dogfooding**:在 `claude-gitea-agent` 自己的 issue 里 @claude,让它给自己写功能
2. **mock 测试**`test/fixtures/` 放几个 Gitea webhook payload 样本
3. **CI**:暂不上 CI跑通 happy path 后再加
## 当前阻塞 / 风险
| 风险 | 缓解 |
|---|---|
| Gitea Actions 跑 bun 容器可能要装 bun | runner image 用 `catthehacker/ubuntu:act-latest`(已包含 nodebun 安装 1 行命令) |
| Gitea webhook 评论事件名是 `issue_comment` 还是 `issues_comment` | 写 entrypoint 时先 dump payload 验证 |
| Anthropic API rate limit 在循环交互场景下会不会爆 | 暂不优化,到了再说 |

110
docs/04-decisions.md Normal file
View File

@ -0,0 +1,110 @@
# 04 - 设计决策ADR
> 仅记录"非显然"的选择 —— 同一份代码读者扫一眼就懂的东西不在这里。
## ADR-001fork 上游而不是从零写
**日期**2026-05-25
**状态**:✅ 采纳
**背景**:要做"issue 触发 Claude → 交互 → PR"的工具,候选方案有从 0 写、用 OpenHands、复刻 SWE-agent、移植 claude-code-action。
**决策**:移植 anthropics/claude-code-action。
**原因**
- 上游的 prompt 构造、context 收集、PR 生成流程是 Anthropic 内部生产用的,质量靠谱
- TypeScript 实现,比起从 0 写省 2-3 周
- Gitea API 与 GitHub API 大量相似,移植量约 30% 文件
- 不引入 OpenHands 等大型框架的认知和维护成本
**代价**
- 引入 TS/Bun 工具链(如果团队更熟 Python是个学习成本
- 跟随上游升级有维护成本mitigated by上游 API 变化不会太频繁)
---
## ADR-002路线 AActions先行路线 Bwebhook 服务)后置
**日期**2026-05-25
**状态**:✅ 采纳
**背景**Gitea Actions 冷启动 ~30s每轮交互体感差。一开始就上长驻服务能解决但工作量翻倍。
**决策**:先做路线 A跑通验收标准后再决定是否升级 B。
**原因**
- 体感差但能用 ≫ 体感好但跑不起来
- 真实跑起来才知道哪些环节实际是瓶颈(可能 30s 启动里实际只有 5s 在等 Claude瓶颈是别的
- 留好抽象接口(`EventDispatcher` / `SessionStore`),升级不重写业务
**代价**
- 用户首次体验差,可能让人产生"这玩意没用"的错觉
- mitigated by文档明说"路线 A 是过渡B 才是终态"
---
## ADR-003不引入完整 Gitea SDK用 fetch + zod
**日期**2026-05-25
**状态**:✅ 采纳
**背景**Gitea 有 `gitea-js` SDK但维护活跃度一般。
**决策**:直接 `fetch` + zod schema 做 API 调用。
**原因**
- 需要的 endpoint 只有 ~10 个,自己写 client 不到 200 行
- zod schema 比 SDK 自带类型更可控(出问题好排查)
- 少一个依赖,少一份升级负担
- 上游 `@octokit/rest` 本来也只用了 1% 的功能
**代价**
- 失去了 SDK 的 retry / rate-limit 内置逻辑
- mitigated by路线 A 阶段流量很小,需要时再加 wrapper
---
## ADR-004Status comment 用"连续 edit 单条评论"模拟 GitHub Checks
**日期**2026-05-25
**状态**:✅ 采纳
**背景**:上游用 GitHub Checks API 做进度展示(带 ✅ / ⏳ 复选框。Gitea 没有等价 API。
**决策**:发一条评论作为状态板,全程 PATCH 这条评论的正文模拟进度更新。
**原因**
- 唯一可行的方案(除非等 Gitea 实现 Checks API
- 用户看到的视觉效果差不多(一条会变的评论 vs 一个会变的 Check
- 实现简单
**代价**
- 不像 Checks 那样能在 PR 顶部显眼地显示状态
- mitigated by评论会自动滚到底部user 自然能看到最新进度
---
## ADR-005暂不支持 GitHub App 等价的 bot 身份
**日期**2026-05-25
**状态**:✅ 采纳
**背景**:上游支持 GitHub App 安装bot 评论时会有 "Claude (bot)" 标识。Gitea 暂无原生 bot 用户类型。
**决策**:用普通用户 `@claude`(或任意命名)作为 bot 身份access token 模式调用。
**原因**
- Gitea 没有 App 概念,硬模拟没必要
- 普通用户 + token 模式简单直接,权限可控
**代价**
- 评论里没有 bot 标识,看起来像普通用户在说话
- mitigated by评论里加固定 footer "🤖 Powered by claude-gitea-agent"
---
## 待决策
- 长会话状态存哪?路线 B 时再选候选SQLite / Redis / 文件)
- 是否支持私有仓库默认支持token 权限够就行
- 多仓库共享 runner 还是每仓库独立运行时按需启动act_runner 已支持