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:
co-authored by
Mohamed Boudra
parent
04d1ebdce0
commit
a8ebd390fa
@@ -42,7 +42,7 @@ import { WorktreeRequestError } from "./worktree-errors.js";
|
||||
import type { WorkspaceGitRuntimeSnapshot } from "./workspace-git-service.js";
|
||||
import type { GeneratedWorkspaceName } from "./worktree-branch-name-generator.js";
|
||||
import { WorkspaceAutoName } from "./workspace-auto-name.js";
|
||||
import type { GitHubService } from "../services/github-service.js";
|
||||
import type { ForgeService } from "../services/forge-service.js";
|
||||
import { createNoopWorkspaceGitService } from "./test-utils/workspace-git-service-stub.js";
|
||||
import {
|
||||
asSessionLogger,
|
||||
@@ -370,7 +370,7 @@ function createWorkspaceRuntimeSnapshot(
|
||||
cwd: string,
|
||||
overrides?: {
|
||||
git?: Partial<WorkspaceGitRuntimeSnapshot["git"]>;
|
||||
github?: Partial<WorkspaceGitRuntimeSnapshot["github"]>;
|
||||
forge?: Partial<WorkspaceGitRuntimeSnapshot["forge"]>;
|
||||
},
|
||||
): WorkspaceGitRuntimeSnapshot {
|
||||
const base: WorkspaceGitRuntimeSnapshot = {
|
||||
@@ -390,7 +390,7 @@ function createWorkspaceRuntimeSnapshot(
|
||||
hasRemote: true,
|
||||
diffStat: { additions: 1, deletions: 0 },
|
||||
},
|
||||
github: {
|
||||
forge: {
|
||||
featuresEnabled: true,
|
||||
pullRequest: {
|
||||
url: "https://github.com/acme/repo/pull/123",
|
||||
@@ -410,17 +410,17 @@ function createWorkspaceRuntimeSnapshot(
|
||||
...base.git,
|
||||
...overrides?.git,
|
||||
},
|
||||
github: {
|
||||
...base.github,
|
||||
...overrides?.github,
|
||||
forge: {
|
||||
...base.forge,
|
||||
...overrides?.forge,
|
||||
pullRequest:
|
||||
overrides?.github && "pullRequest" in overrides.github
|
||||
? (overrides.github.pullRequest ?? null)
|
||||
: base.github.pullRequest,
|
||||
overrides?.forge && "pullRequest" in overrides.forge
|
||||
? (overrides.forge.pullRequest ?? null)
|
||||
: base.forge.pullRequest,
|
||||
error:
|
||||
overrides?.github && "error" in overrides.github
|
||||
? (overrides.github.error ?? null)
|
||||
: base.github.error,
|
||||
overrides?.forge && "error" in overrides.forge
|
||||
? (overrides.forge.error ?? null)
|
||||
: base.forge.error,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -532,7 +532,7 @@ function createSessionForWorkspaceTests(
|
||||
terminalManager?: TerminalManager | null;
|
||||
projectRegistry?: SessionOptions["projectRegistry"];
|
||||
workspaceRegistry?: SessionOptions["workspaceRegistry"];
|
||||
github?: GitHubService;
|
||||
github?: ForgeService;
|
||||
paseoHome?: string;
|
||||
worktreesRoot?: string;
|
||||
renameCurrentBranch?: (
|
||||
@@ -668,6 +668,7 @@ function createSessionForWorkspaceTests(
|
||||
}),
|
||||
scheduleRefreshForCwd: () => {},
|
||||
onWorkspaceStateMayHaveChanged: () => {},
|
||||
invalidateForge: () => {},
|
||||
getMetrics: () => ({
|
||||
checkoutDiffTargetCount: 0,
|
||||
checkoutDiffSubscriptionCount: 0,
|
||||
@@ -822,6 +823,7 @@ test("create_agent_request keeps requested child cwd when grouped under an exist
|
||||
}),
|
||||
scheduleRefreshForCwd: () => {},
|
||||
onWorkspaceStateMayHaveChanged: () => {},
|
||||
invalidateForge: () => {},
|
||||
getMetrics: () => ({
|
||||
checkoutDiffTargetCount: 0,
|
||||
checkoutDiffSubscriptionCount: 0,
|
||||
@@ -945,6 +947,7 @@ test("create_agent_request does not title an existing workspace from the agent p
|
||||
}),
|
||||
scheduleRefreshForCwd: () => {},
|
||||
onWorkspaceStateMayHaveChanged: () => {},
|
||||
invalidateForge: () => {},
|
||||
getMetrics: () => ({
|
||||
checkoutDiffTargetCount: 0,
|
||||
checkoutDiffSubscriptionCount: 0,
|
||||
@@ -1327,6 +1330,7 @@ test("archive emits an authoritative agent_update upsert for subscribed clients"
|
||||
}),
|
||||
scheduleRefreshForCwd: () => {},
|
||||
onWorkspaceStateMayHaveChanged: () => {},
|
||||
invalidateForge: () => {},
|
||||
getMetrics: () => ({
|
||||
checkoutDiffTargetCount: 0,
|
||||
checkoutDiffSubscriptionCount: 0,
|
||||
@@ -1687,6 +1691,7 @@ test("close_items_request archives agents and kills terminals in one batch", asy
|
||||
}),
|
||||
scheduleRefreshForCwd: () => {},
|
||||
onWorkspaceStateMayHaveChanged: () => {},
|
||||
invalidateForge: () => {},
|
||||
getMetrics: () => ({
|
||||
checkoutDiffTargetCount: 0,
|
||||
checkoutDiffSubscriptionCount: 0,
|
||||
@@ -1872,6 +1877,7 @@ test("close_items_request archives stored agents that are not currently loaded",
|
||||
}),
|
||||
scheduleRefreshForCwd: () => {},
|
||||
onWorkspaceStateMayHaveChanged: () => {},
|
||||
invalidateForge: () => {},
|
||||
getMetrics: () => ({
|
||||
checkoutDiffTargetCount: 0,
|
||||
checkoutDiffSubscriptionCount: 0,
|
||||
@@ -2019,6 +2025,7 @@ test("close_items_request continues after an archive failure", async () => {
|
||||
}),
|
||||
scheduleRefreshForCwd: () => {},
|
||||
onWorkspaceStateMayHaveChanged: () => {},
|
||||
invalidateForge: () => {},
|
||||
getMetrics: () => ({
|
||||
checkoutDiffTargetCount: 0,
|
||||
checkoutDiffSubscriptionCount: 0,
|
||||
@@ -3013,6 +3020,7 @@ test("workspace update stream keeps persisted workspace visible after agents sto
|
||||
}),
|
||||
scheduleRefreshForCwd: () => {},
|
||||
onWorkspaceStateMayHaveChanged: () => {},
|
||||
invalidateForge: () => {},
|
||||
getMetrics: () => ({
|
||||
checkoutDiffTargetCount: 0,
|
||||
checkoutDiffSubscriptionCount: 0,
|
||||
@@ -5338,7 +5346,7 @@ test("archive_workspace_request archives a worktree-kind workspace and removes t
|
||||
hasRemote: false,
|
||||
diffStat: null,
|
||||
},
|
||||
github: {
|
||||
forge: {
|
||||
featuresEnabled: false,
|
||||
pullRequest: null,
|
||||
error: null,
|
||||
@@ -6190,7 +6198,7 @@ test("fetch_workspaces_response reads runtime fields from passive workspace git
|
||||
aheadOfOrigin: 3,
|
||||
behindOfOrigin: 1,
|
||||
},
|
||||
github: {
|
||||
forge: {
|
||||
pullRequest: {
|
||||
url: "https://github.com/acme/repo/pull/456",
|
||||
title: "Ship runtime payloads",
|
||||
@@ -6357,7 +6365,7 @@ test("workspace_update includes updated runtime fields", async () => {
|
||||
currentBranch: "feature/runtime-payloads",
|
||||
isDirty: true,
|
||||
},
|
||||
github: {
|
||||
forge: {
|
||||
pullRequest: {
|
||||
url: "https://github.com/acme/repo/pull/789",
|
||||
title: "Updated runtime payloads",
|
||||
@@ -7350,7 +7358,7 @@ function createWorkspaceCreatePrRepo(): WorkspaceCreatePrRepoFixture {
|
||||
return { tempDir, repoDir, paseoHome, headRef, prFileName, prNumber };
|
||||
}
|
||||
|
||||
function createPrCheckoutGitHubService(params: { headRef: string }): GitHubService {
|
||||
function createPrCheckoutGitHubService(params: { headRef: string }): ForgeService {
|
||||
return {
|
||||
listPullRequests: async () => [],
|
||||
listIssues: async () => [],
|
||||
@@ -7375,6 +7383,10 @@ function createPrCheckoutGitHubService(params: { headRef: string }): GitHubServi
|
||||
headRepositoryUrl: null,
|
||||
isCrossRepository: false,
|
||||
}),
|
||||
defaultCheckoutRefs: ({ changeRequestNumber }) => [
|
||||
{ remoteName: "origin", remoteRef: `refs/pull/${changeRequestNumber}/head` },
|
||||
],
|
||||
buildPrLocalBranchName: ({ headRef }) => headRef,
|
||||
getCurrentPullRequestStatus: async () => null,
|
||||
getPullRequestTimeline: async ({ prNumber }) => ({
|
||||
prNumber,
|
||||
@@ -7384,7 +7396,7 @@ function createPrCheckoutGitHubService(params: { headRef: string }): GitHubServi
|
||||
truncated: false,
|
||||
error: null,
|
||||
}),
|
||||
getGitHubCheckDetails: async ({ checkRunId, workflowRunId }) => ({
|
||||
getCheckDetails: async ({ checkRunId, workflowRunId }) => ({
|
||||
checkRunId,
|
||||
workflowRunId: workflowRunId ?? null,
|
||||
name: "test",
|
||||
@@ -7397,7 +7409,11 @@ function createPrCheckoutGitHubService(params: { headRef: string }): GitHubServi
|
||||
failedJobs: [],
|
||||
truncated: false,
|
||||
}),
|
||||
searchIssuesAndPrs: async () => ({ items: [], githubFeaturesEnabled: true }),
|
||||
searchIssuesAndPrs: async () => ({
|
||||
items: [],
|
||||
featuresEnabled: true,
|
||||
githubFeaturesEnabled: true,
|
||||
}),
|
||||
createPullRequest: async () => ({ number: 1, url: "https://github.com/acme/repo/pull/1" }),
|
||||
mergePullRequest: async () => ({ success: true }),
|
||||
enablePullRequestAutoMerge: async () => ({ success: true }),
|
||||
|
||||
Reference in New Issue
Block a user