Skip to content
2 changes: 1 addition & 1 deletion README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ npm install -g teamai-cli

### チーム管理者 / 個人利用

Git ホスト(GitHub、GitLab、GitCode、CNB、TGit、またはプライベート Git サービス)に共有リポジトリを作成します。メンバーにはブランチへの push 権限を付け、デフォルトブランチは保護したままにします。その後 `teamai init https://github.com/your-org/your-repo` を実行します。
Git ホスト(GitHub、GitLab、GitCode、Gitea、CNB、TGit、またはプライベート Git サービス)に共有リポジトリを作成します。メンバーにはブランチへの push 権限を付け、デフォルトブランチは保護したままにします。その後 `teamai init https://github.com/your-org/your-repo` を実行します。

> **まだチームリポジトリがない場合は?** 本番向けの skills、rules、review agents が入ったテンプレートから始められます。[teamai-hub](https://github.com/teamai-hub) org を開き、**Fork** してから、新しいリポジトリに対して `teamai init` を実行してください。

Expand Down
2 changes: 1 addition & 1 deletion README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ npm install -g teamai-cli

### 팀 관리자 / 개인 사용자

Git 호스트(GitHub, GitLab, GitCode, CNB, TGit 또는 비공개 Git 서비스)에 공유 저장소를 만듭니다. 팀원에게는 브랜치 push 권한을 주고, 기본 브랜치는 보호된 채로 둡니다. 그다음 `teamai init https://github.com/your-org/your-repo`를 실행하세요.
Git 호스트(GitHub, GitLab, GitCode, Gitea, CNB, TGit 또는 비공개 Git 서비스)에 공유 저장소를 만듭니다. 팀원에게는 브랜치 push 권한을 주고, 기본 브랜치는 보호된 채로 둡니다. 그다음 `teamai init https://github.com/your-org/your-repo`를 실행하세요.

> **아직 팀 저장소가 없나요?** 프로덕션에 바로 쓸 수 있는 Skills, Rules, review agents가 미리 들어 있는 템플릿으로 시작하세요. [teamai-hub](https://github.com/teamai-hub) org를 둘러본 뒤 **Fork**하고, 새로 만든 저장소에 `teamai init`을 실행하면 됩니다.

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ npm install -g teamai-cli

### Team admin / solo user

Create a shared repo on your git host (GitHub, GitLab, GitCode, CNB, TGit, or a private Git service). Give teammates permission to push branches, and leave the default branch protected. Then run `teamai init https://github.com/your-org/your-repo`.
Create a shared repo on your git host (GitHub, GitLab, GitCode, Gitea, CNB, TGit, or a private Git service). Give teammates permission to push branches, and leave the default branch protected. Then run `teamai init https://github.com/your-org/your-repo`.

> **No team repo yet?** Start from a template pre-loaded with production-ready skills, rules, and review agents. Browse the [teamai-hub](https://github.com/teamai-hub) org, click **Fork**, then `teamai init` against your new repo.

Expand Down
2 changes: 1 addition & 1 deletion README.th.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ npm install -g teamai-cli

### ผู้ดูแลทีม / ผู้ใช้คนเดียว

สร้างรีโปที่ใช้ร่วมกันบน Git host (GitHub, GitLab, GitCode, CNB, TGit หรือบริการ Git ส่วนตัว) ให้สมาชิกสิทธิ์ push สาขา และปล่อยให้สาขาหลักถูกป้องกันไว้ จากนั้นรัน `teamai init https://github.com/your-org/your-repo`
สร้างรีโปที่ใช้ร่วมกันบน Git host (GitHub, GitLab, GitCode, Gitea, CNB, TGit หรือบริการ Git ส่วนตัว) ให้สมาชิกสิทธิ์ push สาขา และปล่อยให้สาขาหลักถูกป้องกันไว้ จากนั้นรัน `teamai init https://github.com/your-org/your-repo`

> **ยังไม่มีรีโปของทีม?** เริ่มจากเทมเพลตที่มี Skills, Rules และ review agents พร้อมใช้จริงอยู่แล้ว เปิดดู org [teamai-hub](https://github.com/teamai-hub) กด **Fork** แล้วรัน `teamai init` กับรีโปใหม่ของคุณ

Expand Down
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ npm install -g teamai-cli

### 团队管理员 / 个人使用者

在 Git 托管平台(GitHub、GitLab、GitCode、CNB、TGit,或私有 Git 服务)创建共享仓库。给同事分支写权限,主干保持保护。然后运行 `teamai init https://github.com/your-org/your-repo`。
在 Git 托管平台(GitHub、GitLab、GitCode、Gitea、CNB、TGit,或私有 Git 服务)创建共享仓库。给同事分支写权限,主干保持保护。然后运行 `teamai init https://github.com/your-org/your-repo`。

> **还没有团队仓库?** 可以从内置了成套 skills、rules、review agents 的模板起步。浏览 [teamai-hub](https://github.com/teamai-hub) org,点 **Fork** 生成自己的仓库,再对它执行 `teamai init`。

Expand Down
1 change: 1 addition & 0 deletions docs/designs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ One document per feature design, in English. The status column says how far each
| --- | --- | --- |
| Implemented | [Data directory layout](data-directory-layout.md) — global home + per-project partitioning | #374 |
| Implemented | [Unified dashboard](dashboard-unified.md) — four-module local dashboard | — |
| In progress | [Gitea provider](gitea-provider.md) — self-hosted authentication, PRs and review discussions | — |
| Implemented | [GitCode provider](gitcode-provider.md) | #361 |
| Implemented | [Model profiles](model-profiles.md) — `teamai models` | — |
| Implemented | [Team env by directory](env-by-directory.md) — each directory's shell gets the env of the scope that governs it | #1018 |
Expand Down
145 changes: 145 additions & 0 deletions docs/designs/gitea-provider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# Gitea provider

> Status: In progress
> Baselines: v0.27.0 for the backport; main for review discussions.

## Background and scope

An unrecognized self-hosted Gitea remote previously selected the transport-only
Git provider. Branches could be pushed but PR creation remained manual. This
provider adds explicit instance configuration, PAT authentication, PR creation
and reviewers, repository/org operations, and PR import without changing the
core push algorithm or introducing another platform CLI.

## Data flow and ownership

```text
init URL → registry → GiteaProvider → verified login → private Git clone
push branch → createPullRequest → recover exact PR → request reviewers
import PR URL → configured-origin validation → metadata/commits/diff → MRData
shallow import → validated clone URL → one-off Basic header → Git
```

`repo-url.ts` owns instance validation and URL parsing. `gitea-api.ts` owns PAT
resolution, per-process login caching, bounded HTTP requests and Git credentials.
`org.ts` maps organization repositories; `mr-fetch.ts` maps evidence for import;
`probe.ts` performs anonymous detection; `index.ts` implements GitProvider.

The registry, provider exports/schema, init help, doctor, shallow clone/fetch,
MR dispatch, and init clone target are connected explicitly. Init must pass the
canonical remote to Gitea rather than reducing it to owner/repo, preserving SSH
transport and port. Both shallow-auth callers await login resolution.

## Shared state review

| State | Writers | Readers |
| --- | --- | --- |
| Instance/token environment | User shell or process launcher; the provider does not persist env changes | `repo-url.ts`, `getGiteaToken`, API/Git authentication, registry and doctor |
| Current/legacy netrc | Interactive `authenticate` calls `writeGiteaNetrcToken` for the configured host; existing GitCode login and external `gf auth login` share this file | Gitea `getGiteaToken`, existing GitCode/TGit credential readers, and Git's HTTP transport; the legacy location is read-only for Gitea |
| Process login map | `giteaWhoami` after a successful `/user` response | `giteaWhoami` and synchronous `giteaRepoClone`; key includes instance and token |
| Clone origin | Git clone writes the validated transport URL; an operator replaces persisted HTTP credentials during rotation | Existing pull/push Git commands; shallow fetch and cached-head lookup validate the origin before attaching Gitea authentication |
| Local/team provider selection | Existing init/config persistence and an explicit team-manifest migration | Provider schema/registry, init, push, clone/import and doctor; this change adds the enum and wiring, not another config store |
| Remote PR/reviewer state | `giteaPullCreate` creates PRs and requests reviewers separately | Duplicate recovery reads exact head/base and repository identities; MR import reads metadata, commits, diff and optional reviews/comments |
| Normalized discussions | Main `mr-fetch.ts` maps independent source comments and `pruneThreads` filters them | `formatMrThreads` and the learning prompt; the 0.27 backport does not produce discussions |

The netrc hostname namespace intentionally follows the existing providers and
cannot separate two services on the same host by port. Such services require
separate user directories or explicit env tokens; stored Git origins still need
an independent credential update.

## Instance and credential boundaries

`GITEA_URL` is required and preserves scheme, port and subpath. Optional
`TEAMAI_GITEA_HOST` is a same-host routing hint, not an alias map. HTTP inputs
are validated against origin and base-path segments before any credential is
used. SSH keeps the supplied port and requires the same hostname. Anonymous
version probing has a three-second total budget, omits credentials and rejects
redirects. A compatible response does not establish Forgejo identity.

PAT precedence is env, then a matching hostname in netrc. Interactive init uses
the existing hidden secret prompt and verifies before saving mode 0600. The
verified login is cached only per instance/token in the current process. REST
uses token auth; HTTP Git uses Basic with that login. Private team HTTP origins
persist encoded credentials for bare push; SSH origins never receive a PAT.
Shallow clones/fetches use one-off headers. Netrc is not port-scoped; token/TLS
migration must update existing origins separately. Errors redact raw and encoded
credentials and authorization headers.

The synchronous clone/org-existence contract is retained: rare synchronous API
lookups use a bounded Node child, passing the PAT over stdin instead of argv.
Init normally has already verified and cached the login before cloning.

## PR and evidence behavior

- Create uses head/base/title/body. Duplicate or ambiguous transport failure
recovery enumerates open PRs and matches both full repository names and refs.
Only one exact match is accepted; authorization/server errors are not retried.
- Gitea 1.27 accepts reviewers in the create body. Separate per-login requests
are intentional: an invalid reviewer cannot prevent PR creation or discard
other valid requests. Existing invitations are recognized; failures are visible
warnings and independent of the returned PR URL.
- Pagination honors next links and otherwise continues to an empty page; a
short page alone is not terminal. Cross-endpoint next links are rejected.
Reaching a resource budget fails or reports truncation instead of silently
claiming complete evidence.
- Required PR metadata/commits/diff failures stop import. The diff is limited
to 50 KiB on read, with a UTF-8-safe incompleteness marker. The 0.27 backport
does not read discussions. Main reads review summaries, inline comments and issue comments using MRFetchOptions; each retains its source ID and timestamp. Gitea exposes no reply-parent ID or authoritative source line in that contract, so comments remain independent and diff positions are not presented as line numbers. The extraction prompt labels missing resolution state explicitly and forbids inferring resolved status or agreements from missing metadata or a merged PR. A real-model draft exposed that risk during validation. Pending reviews and generated TeamAI comments are excluded; optional API failures warn and preserve other evidence.
- A private repository's 404 is ambiguous, so clone failure does not trigger
automatic repository creation. Organization/repository creation is explicit.

## Verification

Initial unit and integration checks cover URL forms, host/port/subpath rejection,
env/netrc/TTY auth, PR duplicate recovery, reviewer partial failure, pagination,
bounded diff, SSH-origin preservation, org mapping and redaction. Tests and
real-CLI records must name the exact build before the feature is declared ready.
Live tests require an explicit opt-in and dedicated test accounts.

Local verification on 2026-10-19 used Node 22 and isolated Git 2.54:

- Main `4a047e8` passed typecheck, lint, build and the full unit run:
11,077 tests passed, 25 skipped. The 0.27 backport at `30eb00f` passed the
same checks with 8,208 tests passed, 19 skipped. Each source tree stayed
unchanged during its checks. An earlier main run failed because an old
whole-module test mock omitted the new diagnostic helper; it now preserves
the real exports and only replaces network/AI extraction.
- The Gitea directory's focused coverage at `4d23906` is 97.62% statements
and lines, 84.41% branches and 89.36% functions (directory aggregates).
- Gitea 1.27.3 fixtures exercised packaged init and explicit-port SSH push
on both branches. The 0.27 backport also exercised HTTP PR creation,
repeated PR reuse and reviewer assignment. Main's complete HTTP
init/push/repeat record and final-head service-interaction reruns are still
pending; SSH and Forgejo records do not substitute for them. Separate API
fixtures checked 70 commits, 140 unique review/comment bodies, exact PR
recovery, token revocation/rotation, anonymous public fetch and private denial.
- Forgejo 15.0.10 fixtures exercised packaged init, push, PR reuse and
reviewer assignment. Both servers were also used for actual CLI import
with a real Claude backend; this is a bounded compatibility sample.
- The `4d23906` package (SHA256
`501192853b24e0fd6041074276b11d9567fab75897f1d109e999e3d8f8aa008e`)
imported a merged Gitea PR whose repository name contains `github.com`.
CLI and model exited 0, and all three discussion sources reached the prompt.
The preceding package selected GitHub for that same URL and failed before AI.

Rejected-URL diagnostics were also exercised with synthetic URL credentials,
query parameters and fragments. At `73ac81a`, packaged single and batch CLI
errors retained only the safe host/path. After the Docker daemon disconnected,
the main legacy-parser collision was verified with the native built CLI at
`5dc9ff1`, in a temporary project using dry-run/output without overriding HOME.
Its JavaScript matched the retained package byte for byte. The 0.27 package
passed both CLI rejection paths in Docker. Interrupted Docker container state
and service-interaction reruns still require the daemon to be restored.

Model output remains a draft requiring review: earlier runs inferred resolved
status without evidence or confused the CLI and server versions. The prompt
now guards unknown resolution state; a successful import is not proof of content
accuracy. Target-host deployment, upstream CI, release publication and actual
member acceptance remain outside these local results.

## Non-goals

Anonymous team init, SSH hostname aliases, gitea.com global mapping, Gitea Actions
templates, and CI extraction/comment commands are excluded. A credential helper
may replace embedded HTTP origins later; this provider does not redesign every
existing provider's credential storage.
Loading
Loading