Files
teai/README.md
chenyunda218 4a5d02fd70 fix: stalled PR 作者已交棒審核改判 reviewer-idle(agents #51)
considerPull 的 author-idle 分支缺少 #47 的交棒檢查,且既有
IsDeliveryReport 訊號為 issue 交付報告設計,PR 端「已就緒,可審閱+
@審核者」形態無對應訊號——fox#60 作者留言交棒後仍被誤判 author-idle。

新增 IsReviewHandoff(@目前審核者帳號 ∧ 就緒語,mention 邊界感知
避免 @chenyunda218 誤命中 chenyunda),considerPull 在判 author-idle
前檢查,命中改判 reviewer-idle,讓既有 48 小時改派規則自然適用。
gitea.py 側 _consider pull 分支以 _is_review_handoff 同步(另 PR)。
2026-09-16 13:17:55 +08:00

424 lines
21 KiB
Markdown
Raw Permalink 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.
# teai
teai 是 Gitea 的命令列(CLI)工具,以更完整、更可靠的方式操作 Gitea。
名稱取「**te**a + **AI**」:這套工具主要為了讓 AI agent 能以更完整、更可靠的
方式操作 Gitea(查詢、回覆、審核、自動化……)。
- **語言:** Go(與 Gitea 本體一致),僅用標準庫,離線可建置
- **目標站點:** `https://gitea.alterminal.com`
- **狀態:** 基礎建設(#5:internal/gitea)、工作流命令(#6:whoami/role/orgs/members/mine/pulls/next)、日常操作命令(#8:issues/pulls/labels/milestones/releases/repos/api)與管理者巡邏命令(#7:stalled/xrefs)已實作
- **範圍(#3):** 不避開 tea 已有的功能;單一工具涵蓋完整操作
## 安裝與建置
### 需求
- **Go 1.26 以上**(`go version` 確認;本倉庫僅用標準庫,離線可建置)
- **git**
- Gitea 帳號的 **API token**(設定階段使用;在 Gitea「設定 → 應用程式」產生)
### 以 go install 安裝(建議)
自 v0.1.0 起 module 已發佈 tag,可直接以版本安裝(二元檔裝進 `GOBIN`,預設 `~/go/bin`):
```sh
go install gitea.alterminal.com/alterminal/teai/cmd/teai@latest # 最新 tag(亦可 @v<版本> 指定)
teai version # 驗證:顯示安裝的 tag 版本(見下方說明)
```
本 module 為公開倉庫,預設 `GOPROXY`/`GOSUMDB` 即可解析(已於乾淨環境驗證)。
若所在網路環境無法經公用 proxy 抓取,可改設 `GOPRIVATE=gitea.alterminal.com`
(略過 proxy 與 checksum database,直接從 Gitea 抓取)。
`teai version` 顯示建入的 module 版本,即安裝指定的 tag(如 `v0.1.1`)。
注意:讀 build info 顯示安裝版本的支援,自包含該變更的 tag 起(v0.1.1)才生效——
v0.1.0 早於此變更,以 `@v0.1.0` 安裝(或此變更前的 `@latest`)仍顯示舊行為 `0.1.0-dev`。
### 從原始碼建置(開發用)
開發、離線或想鎖定特定 commit 時,clone 後自行建置:
```sh
git clone https://gitea.alterminal.com/alterminal/teai.git
cd teai
go build -o teai ./cmd/teai
install -Dm755 teai ~/.local/bin/teai # 或複製到任何 PATH 內目錄
teai version # 驗證:teai version v0.1.1-0.<時間戳>-<commit>(見下方說明)
```
也可以在 clone 目錄內直接裝進 `GOBIN`:
```sh
go install ./cmd/teai
```
clone 建置時 Go 會把 VCS 狀態寫進 build info:位於 tag 上顯示該 tag、
其後的 commit 顯示 pseudo-version(如 `v0.1.1-0.20260912160956-5a48fdd590b2`)、
工作樹有修改再附加 `+dirty`;無 VCS 資訊(如從 tarball 解壓建置)時
`Main.Version` 為 `(devel)`,此時退回開發版本號 `0.1.0-dev`。
### 登入設定(首次使用)
```sh
teai login add --url https://gitea.alterminal.com
# 未給 --token 時自 stdin 讀一行:貼上 token 後按 Enter(不會進 shell 歷史)
teai whoami # 驗證:應輸出 {"username":"<你的帳號>"}
```
- token 驗證失敗(HTTP 401)不會寫入組態,結束碼 3。
- 組態檔:`--config`/`TEAI_CONFIG` 指定,預設 `~/.config/teai/config.yml`;檔案權限 `0600`。
- 多站台以 `--name` 區分、`teai login default <名稱>` 切換預設;詳見下方 [`teai login`](#teai-login--管理登入) 一節。
## 指令設計
> 工作流規則的唯一來源是 [`alterminal/agents`](https://gitea.alterminal.com/alterminal/agents) 的 `AGENTS.md`;
> 現行原型為 `agents/scripts/lib/gitea.py`(Python)。teai 的指令集以它為行為基準,用 Go 重寫並納入本 CLI。
### 設計原則
1. **功能自主完整(#3)**:teai 自行實作完整的功能集,**不**以「tea 已有」為由略過(tea 本身存在諸多問題,見 #3)。除了 gitea.py 工作流(跨倉庫/跨組織聚合、工作流判定、機器可讀輸出),也涵蓋日常操作(issues/pulls/labels/milestones/release……),tea 只是相容參考與對照組。由單一二進位檔提供一致介面,避免多工具拼接。
2. **規則單一來源**:「最後一則留言不是自己」等工作流判定規則以 `AGENTS.md` 為準,teai 只實作、不改義。規則要變更,先改 AGENTS.md,teai 跟著改。
3. **機器可讀優先**:預設輸出 JSON;`--output table` 僅供人工抽查。
4. **可直接替換原型**:`gitea.py` 的每個子命令在 teai 都有語義相同的對應命令,可交叉驗證、平滑遷移。
5. **自主認證組態**:登入資料寫在 teai 自己的組態(`TEAI_CONFIG` 或 `~/.config/teai/config.yml`),不依賴 tea。
6. **安全預設**:查詢類唯讀;寫入類命令(#8 起)一律要求明確 `--yes`,未確認時不發出任何請求、不產生任何遠端變更。
### 日常操作(tea 對等指令;#3 不避開 tea 已有功能;#8 已實作)
issues/pulls/labels/milestones/releases/repos/organizations/api 的日常操作,自行實作、輸出一致 JSON、共用同一套認證與全域選項。介面語義以 `tea`/Gitea API 為相容參考。已實作:
```sh
# issues
teai issues list --repo <owner>/<repo> [--state open|closed|all] [--labels a,b] # 多標籤 AND
teai issues view --repo <owner>/<repo> <number> [--comments]
teai issues create --repo <owner>/<repo> --title … [--body …] [--assignees a,b] --yes
teai issues close --repo <owner>/<repo> --yes <number>
teai issues comment --repo <owner>/<repo> [--body …] --yes <number> [留言…] # 位置參數優先(#40)
# pulls(無子命令仍是工作流清單:--mine/--reviewer/--repo)
teai pulls list --repo <owner>/<repo> [--state open|closed|all]
teai pulls view --repo <owner>/<repo> <number> [--comments]
teai pulls create --repo <owner>/<repo> --head <branch> [--base main] --title … [--body …] --yes
teai pulls merge --repo <owner>/<repo> [--style merge|rebase|rebase-merge|squash] --yes <number>
teai pulls approve --repo <owner>/<repo> [--body …] --yes <number> [意見…] # POST reviews,event=APPROVED(#40)
teai pulls close --repo <owner>/<repo> --yes <number> # 關閉不合併(PATCH state=closed;#40)
teai pulls checkout --repo <owner>/<repo> [--dir <path>] <number> # 本機 git 取得 head(#40)
teai pulls comment --repo <owner>/<repo> [--body …] --yes <number> [留言…]
# 通用留言(tea comment 對等;issue 與 PR 共用端點,#40)
teai comment --repo <owner>/<repo> [--body …] --yes <number> [留言…]
# labels/milestones/releases/repos
teai labels list --repo <owner>/<repo>
teai labels create --repo <owner>/<repo> --name … [--color #RRGGBB] [--description …] --yes
teai milestones # 跨倉庫 open 里程碑總覽(gitea.py milestones 對應;期限近者在前)
teai milestones --repo <owner>/<repo> # 同 milestones list(單倉庫,維持既有語義)
teai milestones list --repo <owner>/<repo> [--state open|closed|all]
teai milestones create --repo <owner>/<repo> --title … [--description …] [--due RFC3339] --yes
teai releases list --repo <owner>/<repo>
teai repos list [--org <org>]
# 組織管理(tea organizations 對等;#45)
teai organizations list # 列出自己可存取的組織(GET /user/orgs)
teai organizations create --name <org> [--full-name …] [--description …] [--website …] [--location …] [--visibility public|limited|private] --yes
teai organizations delete <org> --yes # 刪除組織(不可逆;DELETE /orgs/{org})
# 任意 API 呼叫(逃生口)
teai api <path> [--method GET|POST|PATCH|DELETE] [--data '<json>'] [--yes]
```
> `--yes` 閘門:所有寫入類命令(create/close/comment/merge/approve/labels create/milestones create/organizations create/delete/api 非 GET 動詞)未帶 `--yes` 時回 exit 2(用法錯誤),且不發出任何 HTTP 請求。
> `teai api` 的 path 內含查詢字串(如 `/repos/a/b/issues?state=closed`)時會正確傳遞。
> `teai pulls checkout` 是本機唯讀輔助:同倉庫 head 在 `--dir`(預設當前目錄)的既有工作樹 `git fetch origin <ref> && git checkout -B <ref> FETCH_HEAD`;跨倉庫 head 需 `--dir`(新目錄)執行 `git clone --branch <ref> <clone_url>`。teai 不代管 git 認證,私有倉庫請自備 credential helper。
### 全域介面
```sh
teai [全域選項] <命令> [參數]
```
| 選項 | 說明 |
| --- | --- |
| `--url <URL>` | Gitea 站點,預設 `https://gitea.alterminal.com` |
| `--token <TOKEN>` | API token;未給則依序嘗試 `TEAI_TOKEN` 環境變數、teai 登入組態 |
| `--config <path>` | teai 組態檔路徑,等同 `TEAI_CONFIG` |
| `--output json\|table` | 輸出格式,預設 `json` |
| `--timeout <dur>` | HTTP 請求逾時,預設 `30s` |
### 指令總覽
| 命令 | 對應 gitea.py | 說明 |
| --- | --- | --- |
| `teai login list\|add\|default\|remove` | — | 管理登入組態(見下) |
| `teai whoami` | `get_current_username` | 目前帳號 |
| `teai role` | — | 目前角色(Supervisor 團隊→supervisor,Agents 團隊→worker) |
| `teai orgs` | `for_all_organizations` | 我所屬的組織 |
| `teai organizations list\|create\|delete` | — | 組織管理(tea 對等日常操作;`orgs` 是工作流精簡版) |
| `teai members [--has-work]` | `members [--has-work]` | Agents 團隊成員;`--has-work` 只列有未完成工作者 |
| `teai mine` | `mine` | 分派給我、待跟進的 issues |
| `teai pulls [--mine\|--reviewer] [--repo <owner>/<repo>]` | `pulls` 系列 | 跨倉庫 PR 跟進清單 |
| `teai next` | `next` | 下一件該做的事 |
| `teai stalled [--hours N]` | `stalled` | 全組織停滯項目掃描(管理者巡邏) |
| `teai xrefs <owner>/<repo> <number>` | (內部函式公開化) | 解析內文/留言中的 issue/PR 引用 |
| `teai version` | — | 版本資訊(骨架已實作) |
> `gitea.py` 的底線函式(`_pr_needs_author_followup`、`_resolve_xref_targets` 等)是內部實作,teai 將其放在 `internal/` 套件,不另設 CLI 命令。
### `teai next` — 下一件該做的事
```sh
teai next # JSON;沒有工作 → 輸出 null,exit 0
teai next --output table # 人類可讀
```
拾取規則(與 AGENTS.md/gitea.py 完全一致,最久未更新者優先):
1. 自己發起、最後一則留言不是自己的 open PR(role=author)
2. 自己被指定為審核者、最後一則留言不是自己的 open PR(role=reviewer,含尚無留言)
3. 分派給自己、最後一則留言不是自己的 open issue
輸出(與 `find_next_work()` 同欄位):
```json
{
"type": "pull",
"role": "author",
"repo": "alterminal/teai",
"number": 2,
"title": "初始化專案:Go 骨架與 CLI 進入點",
"url": "https://gitea.alterminal.com/alterminal/teai/pulls/2"
}
```
### `teai login` — 管理登入
登入組態由 teai 自行管理(`--config`/`TEAI_CONFIG`,預設 `~/.config/teai/config.yml`)。
寫入採行級編輯:只動目標項目的行,未知欄位與註解逐字保留;檔案權限 `0600`、暫存檔原子替換。
```sh
teai login list # 列出登入(一律不含 token)
teai login add [--url <URL>] [--name <名稱>] [--default=false] [--token <TOKEN>]
# 先以 token 呼叫 GET /user 驗證,成功才寫入(並記下帳號)
teai login default <名稱> # 設定預設登入
teai login remove <名稱> [--yes] # 移除登入;移除預設者時其餘第一筆自動遞補
```
- `login add` 未給 `--token` 時自 stdin 讀一行,避免 token 進 shell 歷史;token 驗證失敗(401)
不寫入,結束碼 3。
- 未給 `--name` 時:同站(URL 主機相同)已有登入 → 就地更新該項目;否則以 URL 主機為名稱。
- 輸出 JSON(`--output table` 供人工查看),欄位 `name`/`url`/`user`/`default`,**永不輸出 token**。
### `teai stalled [--hours N]` — 管理者巡邏
```sh
teai stalled # 預設 4 小時門檻
teai stalled --hours 6
teai stalled --hours 48 --output table
```
- **判定**:open issue/PR 的最後活動距今超過 N 小時(預設 4)。
- **父追蹤項**(內文/留言有 `#N` 子項引用):子項的留言、state 變更與 PR 合併時間一併計入 last_activity;仍有 open 子項時 `reason=parent-tracking` 並附 `child_activity`。
- **冷卻**:最後留言出自 `ceo` 的項目 24 小時內不再列入(已催促過),冷卻後仍停滯會再出現。
- **輸出**:JSON 陣列,停滯由長到短;無停滯項目 → 空陣列 `[]`。
每筆欄位:
```json
{
"type": "issue",
"repo": "alterminal/agents",
"number": 30,
"title": "…",
"assignees": ["alex"],
"last_comment_by": "alex",
"last_activity_at": "2026-09-09T22:00:00+08:00",
"stalled_hours": 10.5,
"reason": "assignee-idle"
}
```
PR 改帶 `author` 與 `reviewers` 欄位。`reason` 取值:
- issue:`no-assignee`、`assignee-idle`、`no-commenter`、`waiting-outside`、`parent-tracking`
- PR:`no-reviewer`、`author-idle`、`reviewer-idle`、`no-commenter`
`assignee-idle` 判定前會先檢查最後一則 assignee 留言是否為**交付報告**(agents #47):
逐項查核/驗收結果(結構訊號)∧ 回報他件/請他人確認關閉(交棒訊號)同時命中 →
改判 `waiting-outside`——已交付待發起人確認不是 assignee 停滯。認領/進度/預告式
留言(無交棒對象)維持 `assignee-idle`,不製造「認領後棄置永不被催」的新死角。
`author-idle` 判定前會先檢查 PR 作者的最後一則留言是否為**審核交棒**(agents #51):
@目前審核者帳號 ∧ 就緒語(已就緒/可審閱/請審閱)同時命中 → 改判 `reviewer-idle`
——作者已表明就緒並點名審核者,球在審核者,讓既有 reviewer 催促與 48 小時改派規則
自然適用。@的帳號必須在目前審核者清單且為完整匹配(@chenyunda218 不算提及
chenyunda);僅就緒語而無點名對象(進度回報、預告式)維持 `author-idle`。
### `teai milestones` — 跨倉庫里程碑總覽
```sh
teai milestones # 各倉庫 open 里程碑概況(跨所屬組織;期限近者在前)
teai milestones --output table
```
管理者巡邏用(`gitea.py milestones` 對應,agents AGENTS.md 10.1)。帶 `--repo` 時
維持現行單倉庫 `list` 語義(`teai milestones --repo a/b` = `teai milestones list --repo a/b`)。
每筆欄位(鍵集鍵序與 `gitea.py` 全等,實測位元組一致):
```json
{
"repo": "alterminal/test",
"id": 6,
"title": "xcheck31-ms-overdue",
"url": "https://gitea.alterminal.com/alterminal/test/milestone/6",
"open_issues": 2,
"closed_issues": 0,
"due_on": "2026-09-08T08:00:00+08:00",
"due_hours": 61.3,
"overdue": true,
"unassigned": [
{
"type": "issue",
"number": 14,
"title": "…",
"url": "https://gitea.alterminal.com/alterminal/test/issues/14"
}
],
"updated_at": "2026-09-10T21:17:37+08:00"
}
```
- **排序**:`due_on` 近者在前;無期限者最後(`gitea.py` 的 `(due_on is None, due_on)` 鍵)。
- **`overdue`/`due_hours`**:期限已過為 `true`(`due_hours` 正數);無期限 → `null`。
- **`unassigned`**:該里程碑 open 條目(issue 與 PR)中無 assignee 者的摘要;PR 條目另帶 `author`。
- **`updated_at`**:里程碑本身與其 open 條目 `updated_at` 的最大值(判斷活躍度用)。
- 空輸出 → `[]`(`gitea.py` 無輸出;依 #26 慣例差異)。
### `teai members [--has-work]`
```sh
teai members # Agents 團隊全體成員(去重、排序)
teai members --has-work # 只列有未完成工作的成員(判定同 next)
```
### `teai pulls [filters]`
```sh
teai pulls # 有留言且最後一則不是作者的 open PR(跨倉庫)
teai pulls --mine # 再限定:作者是我(我該跟進的 PR)
teai pulls --reviewer # 我是 requested reviewer、最後留言不是自己(含尚無留言)
teai pulls --repo alterminal/teai # 限定單一倉庫
```
`--mine` 與 `--reviewer` 不可併用。
### `teai mine`
```sh
teai mine # 分派給我、最後一則留言不是自己的 open issues(跨所屬組織)
```
### `teai orgs` / `teai whoami` / `teai role`
```sh
teai orgs # 我所屬的組織
teai whoami # 目前帳號
teai role # 目前角色
```
角色由所屬組織的團隊決定:
- 在 **Supervisor** 團隊 → `{"role":"supervisor"}`
- 在 **Agents** 團隊 → `{"role":"worker"}`
- 同時屬於兩者時以 supervisor 為準
- 兩者都不屬於 → 輸出 `null`,exit 0
### `teai xrefs <owner>/<repo> <number>` — 引用解析
解析該 issue/PR 內文與全部留言中的 `#N`、`repo#N`、`owner/repo#N` 引用,輸出解析後的目標清單(去重):
```json
[
{ "owner": "alterminal", "repo": "agents", "number": 32, "source": "body" }
]
```
`stalled` 的父追蹤項判定內部呼叫同一套邏輯,不重複實作。
### 輸出與結束碼
所有命令支援 `--output json|table`,預設 JSON。結束碼:
| 碼 | 意義 |
| --- | --- |
| 0 | 成功(含「沒有結果」→ 清單類命令輸出 `[]`、`next`/`role` 輸出 `null`;#37 判決) |
| 2 | 用法錯誤(未知命令/參數) |
| 3 | API 錯誤(連線失敗、401、403、5xx) |
### 與 gitea.py 的對應與遷移
| gitea.py 子命令 | teai 對應 | 語義 |
| --- | --- | --- |
| `next` | `teai next` | 相同:author-PR → reviewer-PR → issue,最久未更新優先 |
| `stalled [--hours N]` | `teai stalled [--hours N]` | 相同,含 xref 父子追蹤與冷卻 |
| `milestones` | `teai milestones` | 相同:跨倉庫 open 里程碑總覽,期限近者在前(#31) |
| `members [--has-work]` | `teai members [--has-work]` | 相同 |
| `pulls`/`pulls --mine`/`pulls --reviewer` | `teai pulls [--mine\|--reviewer]` | 相同 |
| `mine` | `teai mine` | 相同 |
| (底線函式) | `internal/` 套件 | 不設 CLI 命令;`xrefs` 為其邏輯的公開化 |
已知輸出差異(刻意保留,機器可讀優先):
- **空清單**:`gitea.py` 的 `_dump` 對空清單**不輸出任何內容**;teai 的清單類命令
(`mine`/`pulls`/`stalled`/`milestones` 總覽)空清單一律輸出 `[]`(exit 0)。對 JSON
消費者來說 `[]` 比「無輸出」更明確(可區分「沒有結果」與「查詢失敗無輸出」),交叉驗證時
以此差異為準。`next` 兩者語義相同:無工作時 teai 輸出 `null`(exit 0)、`gitea.py` 印
「沒有未完成的工作」。**判決 #37:維持 teai 現行 JSON 一致性(方向 2)**——消費端
(agents 倉庫巡邏提示與觸發腳本)應以「輸出 `[]`/`null` 且 exit 0」為「沒有」判據,
不得以「無輸出」判斷;`members [--has-work]` 維持逐行帳號、空時無輸出(兩者一致,不受影響)。
`teai role` 不在 Supervisor/Agents 時同樣輸出 `null`(exit 0)。
- **清單欄位集(`mine`/`pulls`)**:`gitea.py` 直接傾倒完整 Gitea API 物件
(`_dump` 的副作用,欄位集隨 Gitea 版本浮動);teai 輸出策展摘要欄位
(`mine`:repo/number/title/url;`pulls`:number/title/state/author/
head/base/updated_at…依命令定義)。交叉驗證比對**項目集合**(列出哪些
issue/PR)與判定語義,不逐欄位比對;`next`/`stalled`/`milestones` 總覽
兩者為策展欄位且鍵集鍵序全等,可逐欄位比對(#31 實測位元組一致)。判定 #26(維持摘要、文件化差異)。
- **全域選項位置**:teai 的全域選項(`--output` 等)可出現在命令之前或之後,
且可與命令旗標交錯(`teai members --has-work --output table`);
gitea.py 的旗標剖析較寬鬆,兩者命令列介面以此行為對齊。
遷移策略:
1. 以 gitea.py 為行為基準:同帳號同時跑兩者,輸出應一致(交叉驗證)。
2. gitea.py 保留不刪;待 AGENTS.md 改引用 teai 時才停用對應腳本。
3. 實作順序:`whoami`/`orgs`/`members`(讀取類)→ `pulls`/`mine`/`next`(判定類)→ `stalled`(最複雜,含 xref)。
### 安全設計
- 本期命令全部唯讀;寫入類命令未來加入時須 `--yes` 明確確認。
- token 不入輸出、不入日誌;建議用 `TEAI_TOKEN` 或 teai 組態,避免出現在命令列。
- 清單類 API 一律帶 `limit`(Gitea 預設上限 50),避免漏抓。
- 「有無實際產出」的判斷一律對照遠端實況(API 回傳),不信任回報文字。
## 開發
```sh
go build ./... # 建置全部套件
go test ./... # 跑全部測試
go vet ./... # 靜態檢查
```
套件結構:
| 路徑 | 內容 |
| --- | --- |
| `cmd/teai/` | 程式進入點(`main`) |
| `internal/cli/` | 命令列架構(flag 剖析、子命令分派、輸出) |
| `internal/` 其他子套件 | 依功能新增(如 `internal/gitea`、`internal/workflow`) |
### 慣例
- 文件與溝通一律使用**繁體中文**
- 對外行為(CLI 輸出、錯誤訊息)以英文為主,方便除錯與自動化剖析
- 變更一律走 **feature 分支 + Pull Request**,由審核者合併;不直推 `main`
- Commit 訊息與 PR 標題使用繁體中文或英文皆可,但需簡潔明確
## 授權
尚未決定(待維護者選定,例如 MIT/Apache-2.0)。