11 KiB
Gitea Adapter — 写模式(Write Patterns)
Gitea adapter 参考实现(Increment 3,自 dogfood 源
_shared/gitea-write-patterns.md拆分上提)。本目录承载后端绑定的 API 形态(REST 端点 + swagger 契约,curl 形态);页名规范与寻址语义 是后端中立契约,见core/rules/artifact-addressing.md(本文不重复)。 实例基址由实例配置提供(下文<gitea-base-url>),见core/adapters/TERMINOLOGY.md。认证一律-H "Authorization: token <token>"; 端点契约以<gitea-base-url>/swagger.v1.json为唯一正典(按需 jq 提取, 见core/skills/gitea-rest/)。
Owner/repo 固定为 Octopus/octopus。所有 wiki 页名遵循 Core 契约
{slug}/{type}-{seq:02d}-{title}(例外页全枚举见 artifact-addressing.md)。
Wiki URL 构造 — html_url 规则
黄金规则:绝不手工拼接 wiki URL。 POST .../wiki/new、
GET .../wiki/page/{mangled-name}、GET .../wiki/pages 响应中的
html_url 字段是唯一权威链接,发布时捕获并原样复用。页名 →
html_url 的变换不可推导(/→%2F、含斜杠页名带 .- 尾缀、CJK
百分号编码),必须读 API。
两种标识符勿混淆:
| 标识符 | 是什么 | 用途 |
|---|---|---|
逻辑页名(title) |
原始页标识;字面 /、无主机、无编码 |
写侧:POST .../wiki/new 的 title;PATCH 省略 title 保名 |
mangled pageName(sub_url) |
后端改写的存储名(含 %2F 编码与 .- 尾缀) |
读侧/改侧路径参数:先 GET .../wiki/pages 拿 sub_url,原样用于 wiki/page/{pageName},绝不手工拼 |
html_url |
后端生成的完整可点击 URL | markdown 链接、target_url、PR 正文 |
去向:
- 工件索引位置列 — 一个单元格同时存两者:
[`{page_name}`]({html_url})。链接文本供读侧回读该页,href 供人 点击(见 Pattern 10)。 - commit-status
target_url— 终报页的html_url(见 Pattern 8)。 - 页内交叉链接 — 用
html_url。
无 API 响应可用时(静态源串)用 <gitea-base-url>,且仅此一处来源。
Pattern 1: create-wiki-page
curl -fsS -X POST "<gitea-base-url>/api/v1/repos/Octopus/octopus/wiki/new" \
-H "Authorization: token <token>" -H "Content-Type: application/json" \
-d '{
"title": "{slug}/{type}-{seq:02d}-{title}",
"content_base64": "<base64(内容)>",
"message": "{可选 commit message}"
}'
base64 陷阱(强制注明):wiki 写接口只认 content_base64;传
content 会被静默忽略(无报错、返回 2xx,页面存成 0 字节)。正文必须
先 base64 编码,且发布后回读确认非空。
发布→验证(强制):发布后立刻回读确认存在且内容一致:
# 1. 列页拿 mangled sub_url(绝不手工拼 mangled name)
curl -fsS -H "Authorization: token <token>" \
"<gitea-base-url>/api/v1/repos/Octopus/octopus/wiki/pages"
# 2. 用返回的 sub_url 原样读页
curl -fsS -H "Authorization: token <token>" \
"<gitea-base-url>/api/v1/repos/Octopus/octopus/wiki/page/{sub_url}"
404 / 内容为空或不一致 → 修复后重发。页名冲突(409)→ 该页已存在,
改用 Pattern 2 update,绝不另发新页。响应的 html_url 立即捕获复用。
Pattern 2: update-wiki-page
curl -fsS -X PATCH "<gitea-base-url>/api/v1/repos/Octopus/octopus/wiki/page/{sub_url}" \
-H "Authorization: token <token>" -H "Content-Type: application/json" \
-d '{
"content_base64": "<base64(新内容)>",
"message": "{commit message}"
}'
路径参数用 mangled sub_url(先 GET .../wiki/pages 获取,原样使用)。
省略 title 保持页名不变——只发 content_base64+message。
同样只认 content_base64(content 会静默存 0 字节页)。更新后再回读
验证;409 冲突 → 拉最新内容手工合并后重试。
Pattern 3: create-issue
curl -fsS -X POST "<gitea-base-url>/api/v1/repos/Octopus/octopus/issues" \
-H "Authorization: token <token>" -H "Content-Type: application/json" \
-d '{"title":"{标题}","body":"{正文}","labels":[{label_id}]}'
工单交叉链接(强制)
父子工单组必须双向链接:父工单 task list 引用 #<number>;子工单正文
带 ## 父级 / Parent 节引用父 #<number>。
衍生工单创建
- 技术债(verify Phase 5.5):
TD-NNN经分配台账取号后升票,## Parent指回登记册源工单。 - 基线失败(Phase 5.55):label
baseline-failure+BF-NNN(族伞签, 按失败签名去重)。 - 不稳定测试(Phase 5.56):label
flaky-test+FT-NNN(同上)。
Pattern 4: update-issue
curl -fsS -X PATCH "<gitea-base-url>/api/v1/repos/Octopus/octopus/issues/{index}" \
-H "Authorization: token <token>" -H "Content-Type: application/json" \
-d '{"body":"{正文}","state":"{open|closed}"}'
原位更新正文(checklist 勾选、live 状态表维护);关闭工单即触发 归档动作(见 artifact-addressing.md §4.3 + Pattern 10)。
Pattern 5: add-issue-comment
curl -fsS -X POST "<gitea-base-url>/api/v1/repos/Octopus/octopus/issues/{index}/comments" \
-H "Authorization: token <token>" -H "Content-Type: application/json" \
-d '{"body":"{评论正文}"}'
首次评论后捕获返回的 comment_id——后续对同一逻辑评论的更新必须走
Pattern 6 原位 edit,绝不再 create。用于:评审综合(Synthesis)、
状态备注、TD 登记、claim 认领。
Pattern 6: edit-issue-comment
curl -fsS -X PATCH "<gitea-base-url>/api/v1/repos/Octopus/octopus/issues/comments/{comment_id}" \
-H "Authorization: token <token>" -H "Content-Type: application/json" \
-d '{"body":"{新正文}"}'
单评论聚合不变量(工件索引、当前状态表等)的执行手段。
Pattern 7: move-issue-to-column
看板列迁移(Todo → In Progress → Review → Done)。projects/columns 发现用 REST;「把工单移到列」的操作端点语义复杂,经 swagger 查询后 调用,不臆造路径:
# 列出 repo 级 projects(projects 仅 repo 级,无 org/user 级端点)
curl -fsS -H "Authorization: token <token>" \
"<gitea-base-url>/api/v1/repos/Octopus/octopus/projects"
# 列某 project 的 columns
curl -fsS -H "Authorization: token <token>" \
"<gitea-base-url>/api/v1/repos/Octopus/octopus/projects/{project_id}/columns"
# 移动工单到列:先经 swagger.v1.json 查询 projects 相关端点后调用
curl -s <gitea-base-url>/swagger.v1.json -o /tmp/gitea-sw.json
jq -r '.paths | keys[]' /tmp/gitea-sw.json | grep projects
Pattern 7.5: move-issue-to-pipeline-stage
管线阶段板列(Pipeline Stages board column)承载阶段迁移——阶段转移
落到板列,不落 ## 当前状态 行(该表只承载 PR / 评审 / CI 行与
非阶段阻塞项)。列序列按管线阶段定义;移动与 Pattern 7 同款
(projects/columns 经 REST + swagger 发现,同上)。
Pattern 8: post-commit-status
commit status 直发 REST(评审综合的 Tier-2 落点):
curl -X POST "<gitea-base-url>/api/v1/repos/Octopus/octopus/statuses/{sha}" \
-H "Authorization: token <token>" \
-H "Content-Type: application/json" \
-d '{
"context": "pipeline/review-{stage}",
"state": "{success|failure|pending|error}",
"target_url": "{html_url}",
"description": "{≤140 chars 摘要}"
}'
context 公式:pipeline/review-{stage}(code / review-dag /
audit-process)。token 从实例配置读取(此处 <token> 占位)。
merge 前读回验证:GET /commits/{PR_SHA}/status。
Pattern 9: create-iteration-board
# 建 project(board):先经 swagger 查询 create 端点契约
curl -s <gitea-base-url>/swagger.v1.json -o /tmp/gitea-sw.json
jq '.paths["/repos/{owner}/{repo}/projects"].post' /tmp/gitea-sw.json
# 随后 POST 建板(title="{slug} — Iteration {N}", description="…")
# 再对每列(Todo / In Progress / Review / Done):
jq '.paths["/repos/{owner}/{repo}/projects/{project_id}/columns"].post' /tmp/gitea-sw.json
# 随后 POST 建列
projects 仅 repo 级。DAG 聚合 agent 在单门 PASS 后建板;工单正文模板带
## Node Reference(指向 {epic-slug}/dag)、## Acceptance Criteria、
## Parent。
Pattern 10: artifact-index(工单 ↔ 工件索引)
技能发布工件后,在源工单维护 ## 工件索引 评论——单一原位编辑的
索引(反向链接 + compaction 恢复主路径;语义不变量见
core/rules/artifact-addressing.md §4):
# 1. 找源工单(PR body / commit 的 Closes #N,或 DAG 父映射);无则跳过
# 2. 评论已存在?
curl -fsS -H "Authorization: token <token>" \
"<gitea-base-url>/api/v1/repos/Octopus/octopus/issues/{index}/comments"
# → 扫 body 以 "## 工件索引" 开头的评论(遗留前缀 "## Pipeline 工件追踪表"
# 原位升级,不重复发)
# 3a. 不存在 → POST .../issues/{index}/comments 初始化
# 3b. 存在 → PATCH .../issues/comments/{comment_id} 原位编辑(复用 comment_id)
索引表模板(每工件一行;技能只增改自己的行,绝不删他技的行):
## 工件索引
slug: `{slug}` — source issue #{N}
| 工件 | 类型 | 版本 | 位置 | 重读 |
|------|------|------|------|------|
| DAG | 任务图 | v1 (frozen) | [`{epic-slug}/dag`]({html_url}) | CORE |
位置列填充规则:单元格 = markdown 链接 [`{page_name}`]({html_url});
链接文本(逻辑页名,字面 /)供读侧先 GET .../wiki/pages 拿 sub_url
后回读该页;href(html_url)供人点击,必须取自 API 响应,严禁拼接。
重读优先级:CORE = compaction 后必读(重读集 = 全部 CORE 行);
ON-DEMAND = 按需;ARCHIVE = 已归档不读。
归档动作(archive-at-close):工单关闭时由关闭方 agent 原位 edit
本评论——表格上方加归档横幅(> **状态**: ✅ 已归档 — issue #{N} 关闭于 {date})+ 全部行 重读 置 ARCHIVE;不删行、不改位置列、不发第二条
评论。主路径 verify Phase 5.6;跳过 verify 的路由由关闭 agent 补执行。
各技能行映射:
| 技能 | 工件 ID | 位置 |
|---|---|---|
analyze-dag |
DAG |
{epic-slug}/dag |
review-artifact |
REVIEW-{stage} |
{slug}/reviews/{stage}/final/report |
review-code |
REVIEW-code |
{slug}/reviews/code/final/report |
review-code(DAG task) |
REVIEW-code-task-{node-id} |
{epic-slug}/reviews/code/final/report-task-{node-id} |
verify |
VERIFY-{N} |
{slug}/05-verify-iteration-{N} |
verify(milestone) |
VERIFY-M-{M-id} |
{epic-slug}/05-verify-milestone-{M-id}(重读 ON-DEMAND) |
verify(DAG task) |
VERIFY-TASK-{node-id} |
{epic-slug}/05-verify-task-{node-id}(重读 ON-DEMAND) |