feat(forge): pluggable forge abstraction + GitLab and Gitea/Forgejo/Codeberg (#1913)

* refactor(forge): forge-neutral foundation (GitHub-only)

Decouple git-hosting from GitHub behind a neutral abstraction (issue #1616), GitHub-only for now; existing GitHub behaviour is unchanged.

- Forge manifest, neutral ForgeService contract, forge registry + resolver, and a client forge-module registry.
- GitHub code renamed to the neutral shape; PR/Issue attachment wording preserved.
- forge.search.response enums parse tolerantly (unknown kind/auth state degrade instead of breaking the client).
- createPullRequest reports typed CLI/auth errors instead of a generic message.
- forge-resolver host/remote caches are LRU-bounded.
- Forge host trust is explicit: only a known cloud host or a CLI-authenticated host is ever talked to; an unauthenticated GitHub Enterprise host fails resolution instead of routing to github.com.
- Docs: forge-providers guide, glossary and i18n forge-copy conventions, architecture and rpc-namespacing terminology.
- Vitest React Native mocks (unistyles, svg, linking, lucide) consolidated into shared aliased test-stubs.

* feat(forge): GitLab adapter, forge-aware UI, pipelines and approvals

GitLab adapter over the glab CLI on the neutral contracts: MR status, forge-aware UI, pipeline tree, and N-of-M approvals.

- threadIsResolved is part of the neutral timeline item.
- Pipeline load failures show an error instead of an empty section.
- Manual pipeline jobs render as pending.
- Fork/detached MR head pipelines are fetched by MR iid (glab ci get --merge-request).

* feat(forge): Gitea family adapter (Gitea, Forgejo, Codeberg)

One adapter over the tea CLI serving Gitea, Forgejo, and Codeberg on the neutral contracts.

- CI status aggregates commit statuses and Actions runs together.
- Gitea's terminal "warning" state maps to failure on server and client.
- Gitea Actions check details are reachable from the PR pane by workflowRunId.

* refactor(forge): localize compatibility handling

* test(forge): expect normalized GitLab facts

---------

Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
This commit is contained in:
nllptrx
2026-07-17 15:03:26 +08:00
committed by GitHub
co-authored by Mohamed Boudra
parent 04d1ebdce0
commit a8ebd390fa
187 changed files with 20988 additions and 2907 deletions
+45 -9
View File
@@ -102,6 +102,7 @@ export const ja: TranslationResources = {
addImage: "画像を追加",
addFile: "ファイルをアップロード",
addIssueOrPr: "イシューまたはPRを追加",
addIssueOrPr_mr: "イシューまたはMRを追加",
dropImagesHere: "ここに画像をドロップ",
dropFilesHere: "ここにファイルをドロップ",
editQueuedMessage: "キューに入れたメッセージを編集",
@@ -109,8 +110,8 @@ export const ja: TranslationResources = {
openImage: "画像添付ファイルを開く",
removeImage: "画像添付ファイルを削除",
removeFile: "ファイル添付ファイルを削除",
openGithub: "{{kind}} #{{number}}を開く",
removeGithub: "{{kind}} #{{number}}を削除",
openGithub: "{{kind}} {{number}}を開く",
removeGithub: "{{kind}} {{number}}を削除",
element: "要素",
openBrowserElement: "ブラウザ要素の添付ファイルを開く",
removeBrowserElement: "ブラウザ要素の添付ファイルを削除",
@@ -134,7 +135,9 @@ export const ja: TranslationResources = {
searching: "検索中...",
noResults: "結果が見つかりません。",
searchPlaceholder: "イシューとPRを検索...",
searchPlaceholder_mr: "イシューとMRを検索...",
title: "イシューまたはPRを添付",
title_mr: "イシューまたはMRを添付",
},
},
agentControls: {
@@ -622,6 +625,9 @@ export const ja: TranslationResources = {
label: "PRを作成",
pending: "PRを作成中...",
success: "PRが作成されました",
label_mr: "MRを作成",
pending_mr: "MRを作成中...",
success_mr: "MRが作成されました",
},
mergeBranch: {
label: "ローカルでマージ",
@@ -644,7 +650,13 @@ export const ja: TranslationResources = {
rebase: "PRをマージ(リベース)",
pending: "PRをマージ中...",
success: "PRがマージされました",
squash_mr: "MRをマージ(スカッシュ)",
merge_mr: "MRをマージ(マージ)",
rebase_mr: "MRをマージ(リベース)",
pending_mr: "MRをマージ中...",
success_mr: "MRがマージされました",
},
viewPr_mr: "MRを表示",
autoMerge: {
enableSquash: "自動マージ(スカッシュ)",
enableMerge: "自動マージ(マージ)",
@@ -655,7 +667,7 @@ export const ja: TranslationResources = {
disabled: "自動マージが無効になりました",
},
unavailable: {
viewPrNoGithub: "GitHubが接続されていないため、PRの表示は現在利用できません",
viewPrNoForge: "{{brand}}が接続されていないため、{{noun}}の表示は現在利用できません",
pullNoRemote:
"このブランチはまだリモートに接続されていないため、プルはここでは利用できません",
pullDirty:
@@ -669,9 +681,12 @@ export const ja: TranslationResources = {
"このブランチはまだリモートに接続されていないため、プル&プッシュはここでは利用できません",
pullAndPushDirty:
"ローカルに変更があるためプル&プッシュは利用できません。先にコミットまたはスタッシュしてください",
pullAndPushNoIncoming: "先にプルする受信変更がないため、プル&プッシュは利用できません",
pullAndPushInSync:
"このブランチはすでに同期されているため、プル&プッシュは利用できません",
createPrNoGithub: "GitHubが接続されていないため、PRの作成は現在利用できません",
pullAndPushNothingToPush:
"プル後に送信する新しい変更がないため、プル&プッシュは利用できません",
createPrNoForge: "{{brand}}が接続されていないため、{{noun}}の作成は現在利用できません",
createPrNoCommits: "このブランチにまだ新しいコミットがないため、PRの作成は利用できません",
mergeNoBase: "ベースブランチを特定できなかったため、マージは利用できません",
mergeDirty:
@@ -682,6 +697,9 @@ export const ja: TranslationResources = {
"ローカルに変更があるため更新は利用できません。先にコミットまたはスタッシュしてください",
updateCurrent: "このブランチはすでに{{baseRef}}と最新の状態のため、更新は利用できません",
mergePrNoGithub: "GitHubが接続されていないため、PRのマージは現在利用できません",
archiveNotWorktree:
"このワークスペースはPaseoワークツリーとして作成されていないため、アーカイブはここでは利用できません",
mergePrNoForge: "{{brand}}が接続されていないため、{{noun}}のマージは現在利用できません",
mergePrMissing: "プルリクエストがまだないため、PRのマージは利用できません",
mergePrDraft: "プルリクエストがまだドラフトのため、PRのマージは利用できません",
mergePrMerged: "プルリクエストはすでにマージされているため、PRのマージは利用できません",
@@ -690,7 +708,7 @@ export const ja: TranslationResources = {
mergePrQueue:
"このリポジトリはマージキューを使用しているため、PRのマージはここでは利用できません",
mergePrNotReady:
"GitHub上でプルリクエストがマージ可能になるまで、PRのマージは利用できません",
"{{brand}}上で{{noun}}がマージ可能になるまで、{{noun}}のマージは利用できません",
autoMergeCannotDisable:
"自動マージは有効になっていますが、このアカウントでは無効にできません",
},
@@ -741,7 +759,7 @@ export const ja: TranslationResources = {
expandAllFolders: "すべてのフォルダを展開",
refreshing: "更新中",
refresh: "更新",
refreshState: "gitとGitHubの状態を更新",
refreshState: "gitと{{brand}}の状態を更新",
failedRefresh: "gitの状態の更新に失敗しました。",
emptyHiddenWhitespace: "空白を非表示にすると変更は表示されません",
emptyUncommitted: "未コミットの変更なし",
@@ -777,13 +795,23 @@ export const ja: TranslationResources = {
pr: {
actions: {
viewPullRequest: "表示",
openOn: "{{brand}}で開く",
},
sections: {
checks: "チェック",
pipeline: "パイプライン",
reviews: "レビュー",
},
empty: {
noJobs: "ジョブなし",
loadingPipeline: "パイプラインを読み込み中...",
pipelineJobsLoadFailed: "パイプラインのジョブを読み込めませんでした",
allowedToFail: "失敗を許可",
},
approvals: "{{given}} / {{required}} 承認",
accessibility: {
pullRequest: "プルリクエスト#{{number}}",
pullRequest_mr: "マージリクエスト !{{number}}",
},
states: {
draft: "ドラフト",
@@ -800,11 +828,19 @@ export const ja: TranslationResources = {
time: {
justNow: "たった今",
},
thread: {
discussion: "ディスカッションスレッド",
},
errors: {
statusLoadFailed: "プルリクエストのステータスを読み込めません",
activityLoadFailed: "プルリクエストのアクティビティを読み込めません",
},
},
forgeSetup: {
installCli: "{{brand}} の機能を使うには {{cli}} CLI をインストールしてください。",
signIn: "{{brand}} の機能を使うには {{command}} を実行してください。",
generic: "このホストで {{brand}} をセットアップすると、その機能を使えます。",
},
},
},
sidebar: {
@@ -936,9 +972,9 @@ export const ja: TranslationResources = {
refPicker: {
startingRef: "開始Ref",
chooseStart: "開始点を選択",
checkoutHint: "PR #{{number}}をチェックアウトしますか?",
checkoutPr: "PR #{{number}}をチェックアウト",
dismissCheckoutHint: "PR #{{number}}のチェックアウトヒントを閉じる",
checkoutHint: "{{noun}} {{numberPrefix}}{{number}}をチェックアウトしますか?",
checkoutPr: "{{noun}} {{numberPrefix}}{{number}}をチェックアウト",
dismissCheckoutHint: "{{noun}} {{numberPrefix}}{{number}}のチェックアウトヒントを閉じる",
intoBase: "{{baseRef}}に",
searching: "検索中...",
noMatchingRefs: "一致するRefがありません。",