- workflow 新增 StalledSource 介面(Source+CurrentUser+GetIssue)與 高階入口 RunStalled/ResolveIssueXrefs(xrefs 輸出附 source 出處) - APIClient 補上 StalledSource 面向(GetIssue/TryGetIssue 等) - CLI 新增 teai stalled [--hours N](JSON 與 gitea.py 位元組相容: 停用 HTML 轉義、縮排 2)與 teai xrefs <owner>/<repo> <number> (404 → exit 3);table 模式 stalled_hours 定點一位小數 - 接線層與 workflow 層單元測試(注入假資料源、固定時刻) - README 狀態行更新 驗證:go build/vet/test 全綠;對真實 API 並行掃描,stalled 輸出 與 gitea.py 位元組一致(2169 bytes);xrefs 邊界案例(PR#50/ issue#49 略過、CJK 緊鄰、跨倉庫 owner/repo#N)與 Python 版一致。
322 lines
15 KiB
Markdown
322 lines
15 KiB
Markdown
# teai
|
||
|
||
teai 是 Gitea 的命令列(CLI)工具,以更完整、更可靠的方式操作 Gitea。
|
||
名稱取「**te**a + **AI**」:這套工具主要為了讓 AI agent 能以更完整、更可靠的
|
||
方式操作 Gitea(查詢、回覆、審核、自動化……)。
|
||
|
||
- **語言:** Go(與 Gitea 本體一致),僅用標準庫,離線可建置
|
||
- **目標站點:** `https://gitea.alterminal.com`
|
||
- **狀態:** 基礎建設(#5:internal/gitea)、工作流命令(#6:whoami/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「設定 → 應用程式」產生)
|
||
|
||
### 從原始碼建置
|
||
|
||
```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 0.1.0-dev
|
||
```
|
||
|
||
也可以直接裝進 `GOBIN`(預設 `~/go/bin`;該目錄已在 PATH 時最省事):
|
||
|
||
```sh
|
||
go install ./cmd/teai
|
||
```
|
||
|
||
> **注意:** `go install gitea.alterminal.com/alterminal/teai/cmd/teai@latest` 目前**不可用**——
|
||
> module 尚未發佈任何 tag,`@latest` 會解析到任意中繼 commit(且 #21 修復前 main 建置失敗)。
|
||
> 請在 clone 目錄內以版本控制的方式建置;發佈 tag 後再提供 `teai@<version>` 安裝路徑。
|
||
|
||
### 登入設定(首次使用)
|
||
|
||
teai 與 `tea` 共用同一份組態檔;若 `tea whoami` 已可正常輸出,可跳過本節。
|
||
|
||
```sh
|
||
teai login add --url https://gitea.alterminal.com
|
||
# 未給 --token 時自 stdin 讀一行:貼上 token 後按 Enter(不會進 shell 歷史)
|
||
teai whoami # 驗證:應輸出 {"username":"<你的帳號>"}
|
||
```
|
||
|
||
- token 驗證失敗(HTTP 401)不會寫入組態,結束碼 3。
|
||
- 組態檔:`--config`/`TEA_CONFIG` 指定,預設 `~/.config/tea/config.yml`;teai 採行級編輯,
|
||
不會改壞 tea 也在使用的欄位,檔案權限 `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. **認證沿用 tea**:讀 `tea` 的登入組態(`TEA_CONFIG` 或 `~/.config/tea/config.yml`),不另建認證體系。
|
||
6. **安全預設**:查詢類唯讀;寫入類命令(#8 起)一律要求明確 `--yes`,未確認時不發出任何請求、不產生任何遠端變更。
|
||
|
||
### 日常操作(tea 對等指令;#3 不避開 tea 已有功能;#8 已實作)
|
||
|
||
issues/pulls/labels/milestones/releases/repos/api 的日常操作,自行實作、輸出一致 JSON、共用同一套認證與全域選項。介面語義以 `tea`/Gitea API 為相容參考。已實作:
|
||
|
||
```sh
|
||
# issues
|
||
teai issues list --repo <owner>/<repo> [--state open|closed|all]
|
||
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>
|
||
# 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 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 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>]
|
||
# 任意 API 呼叫(逃生口)
|
||
teai api <path> [--method GET|POST|PATCH|DELETE] [--data '<json>'] [--yes]
|
||
```
|
||
|
||
> `--yes` 閘門:所有寫入類命令(create/close/comment/merge/labels create/milestones create/api 非 GET 動詞)未帶 `--yes` 時回 exit 2(用法錯誤),且不發出任何 HTTP 請求。
|
||
> `teai api` 的 path 內含查詢字串(如 `/repos/a/b/issues?state=closed`)時會正確傳遞。
|
||
|
||
### 全域介面
|
||
|
||
```sh
|
||
teai [全域選項] <命令> [參數]
|
||
```
|
||
|
||
| 選項 | 說明 |
|
||
| --- | --- |
|
||
| `--url <URL>` | Gitea 站點,預設 `https://gitea.alterminal.com` |
|
||
| `--token <TOKEN>` | API token;未給則依序嘗試 `TEAI_TOKEN` 環境變數、tea 登入組態 |
|
||
| `--config <path>` | tea 組態檔路徑,等同 `TEA_CONFIG` |
|
||
| `--output json\|table` | 輸出格式,預設 `json` |
|
||
| `--timeout <dur>` | HTTP 請求逾時,預設 `30s` |
|
||
|
||
### 指令總覽
|
||
|
||
| 命令 | 對應 gitea.py | 說明 |
|
||
| --- | --- | --- |
|
||
| `teai login list\|add\|default\|remove` | — | 管理 tea 相容登入組態(見下) |
|
||
| `teai whoami` | `get_current_username` | 目前帳號 |
|
||
| `teai orgs` | `for_all_organizations` | 我所屬的組織 |
|
||
| `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 與 tea 共用同一份組態檔(`--config`/`TEA_CONFIG`,預設 `~/.config/tea/config.yml`)。
|
||
寫入採行級編輯:只動目標項目的行,未知欄位(`ssh_*` 等)與 `preferences` 區段逐字保留,
|
||
不改壞 tea 也在使用的檔案;檔案權限 `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`
|
||
|
||
### `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`
|
||
|
||
```sh
|
||
teai orgs # 我所屬的組織
|
||
teai whoami # 目前帳號(讀 tea 組態)
|
||
```
|
||
|
||
### `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 | 成功(含「沒有工作」→ 輸出 `null`/`[]`) |
|
||
| 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 父子追蹤與冷卻 |
|
||
| `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`)空清單輸出 `[]`。對 JSON 消費者來說 `[]` 比「無輸出」更明確,
|
||
交叉驗證時以此差異為準。`next` 兩者語義相同(無工作 → `null`/「沒有未完成的工作」)。
|
||
- **全域選項位置**: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` 或 tea 組態,避免出現在命令列。
|
||
- 清單類 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)。
|