@mastra/factory 0.16.0-alpha.9 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -0
- package/dist/auth.js +1 -1
- package/dist/auth.js.map +1 -1
- package/dist/boards/review.d.ts.map +1 -1
- package/dist/boards/review.js +19 -8
- package/dist/boards/review.js.map +1 -1
- package/dist/boards/work.d.ts.map +1 -1
- package/dist/boards/work.js +31 -3
- package/dist/boards/work.js.map +1 -1
- package/dist/capabilities/intake.d.ts +4 -0
- package/dist/capabilities/intake.d.ts.map +1 -1
- package/dist/capabilities/version-control.d.ts +15 -0
- package/dist/capabilities/version-control.d.ts.map +1 -1
- package/dist/factory.d.ts +7 -7
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +100 -40
- package/dist/factory.js.map +1 -1
- package/dist/integrations/base.d.ts +3 -0
- package/dist/integrations/base.d.ts.map +1 -1
- package/dist/integrations/github/integration.d.ts.map +1 -1
- package/dist/integrations/github/integration.js +21 -0
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/routes.js +2 -1
- package/dist/integrations/github/routes.js.map +1 -1
- package/dist/integrations/github/sandbox.d.ts +41 -42
- package/dist/integrations/github/sandbox.d.ts.map +1 -1
- package/dist/integrations/github/sandbox.js +416 -227
- package/dist/integrations/github/sandbox.js.map +1 -1
- package/dist/integrations/github/webhook.d.ts +2 -5
- package/dist/integrations/github/webhook.d.ts.map +1 -1
- package/dist/integrations/github/webhook.js +8 -48
- package/dist/integrations/github/webhook.js.map +1 -1
- package/dist/integrations/gitlab/agent-tools.d.ts +14 -0
- package/dist/integrations/gitlab/agent-tools.d.ts.map +1 -0
- package/dist/integrations/gitlab/agent-tools.js +42 -0
- package/dist/integrations/gitlab/agent-tools.js.map +1 -0
- package/dist/integrations/gitlab/api.d.ts +222 -0
- package/dist/integrations/gitlab/api.d.ts.map +1 -0
- package/dist/integrations/gitlab/api.js +274 -0
- package/dist/integrations/gitlab/api.js.map +1 -0
- package/dist/integrations/gitlab/default-rules.d.ts +125 -0
- package/dist/integrations/gitlab/default-rules.d.ts.map +1 -0
- package/dist/integrations/gitlab/default-rules.js +172 -0
- package/dist/integrations/gitlab/default-rules.js.map +1 -0
- package/dist/integrations/gitlab/integration.d.ts +138 -0
- package/dist/integrations/gitlab/integration.d.ts.map +1 -0
- package/dist/integrations/gitlab/integration.js +705 -0
- package/dist/integrations/gitlab/integration.js.map +1 -0
- package/dist/integrations/gitlab/issue-reconciler.d.ts +6 -0
- package/dist/integrations/gitlab/issue-reconciler.d.ts.map +1 -0
- package/dist/integrations/gitlab/issue-reconciler.js +123 -0
- package/dist/integrations/gitlab/issue-reconciler.js.map +1 -0
- package/dist/integrations/gitlab/merge-request-reconciler.d.ts +6 -0
- package/dist/integrations/gitlab/merge-request-reconciler.d.ts.map +1 -0
- package/dist/integrations/gitlab/merge-request-reconciler.js +267 -0
- package/dist/integrations/gitlab/merge-request-reconciler.js.map +1 -0
- package/dist/integrations/gitlab/reconciler.d.ts +5 -0
- package/dist/integrations/gitlab/reconciler.d.ts.map +1 -0
- package/dist/integrations/gitlab/reconciler.js +31 -0
- package/dist/integrations/gitlab/reconciler.js.map +1 -0
- package/dist/integrations/gitlab/reconciliation-config.d.ts +3 -0
- package/dist/integrations/gitlab/reconciliation-config.d.ts.map +1 -0
- package/dist/integrations/gitlab/reconciliation-config.js +17 -0
- package/dist/integrations/gitlab/reconciliation-config.js.map +1 -0
- package/dist/integrations/gitlab/routes.d.ts +20 -0
- package/dist/integrations/gitlab/routes.d.ts.map +1 -0
- package/dist/integrations/gitlab/routes.js +473 -0
- package/dist/integrations/gitlab/routes.js.map +1 -0
- package/dist/integrations/gitlab/rules.d.ts +35 -0
- package/dist/integrations/gitlab/rules.d.ts.map +1 -0
- package/dist/integrations/gitlab/rules.js +391 -0
- package/dist/integrations/gitlab/rules.js.map +1 -0
- package/dist/integrations/gitlab/session-subscriptions.d.ts +32 -0
- package/dist/integrations/gitlab/session-subscriptions.d.ts.map +1 -0
- package/dist/integrations/gitlab/session-subscriptions.js +192 -0
- package/dist/integrations/gitlab/session-subscriptions.js.map +1 -0
- package/dist/integrations/gitlab/subscriptions.d.ts +70 -0
- package/dist/integrations/gitlab/subscriptions.d.ts.map +1 -0
- package/dist/integrations/gitlab/subscriptions.js +76 -0
- package/dist/integrations/gitlab/subscriptions.js.map +1 -0
- package/dist/integrations/gitlab/version-control.d.ts +16 -0
- package/dist/integrations/gitlab/version-control.d.ts.map +1 -0
- package/dist/integrations/gitlab/version-control.js +622 -0
- package/dist/integrations/gitlab/version-control.js.map +1 -0
- package/dist/integrations/gitlab/webhook-dispatch.d.ts +75 -0
- package/dist/integrations/gitlab/webhook-dispatch.d.ts.map +1 -0
- package/dist/integrations/gitlab/webhook-dispatch.js +235 -0
- package/dist/integrations/gitlab/webhook-dispatch.js.map +1 -0
- package/dist/integrations/gitlab/webhook.d.ts +72 -0
- package/dist/integrations/gitlab/webhook.d.ts.map +1 -0
- package/dist/integrations/gitlab/webhook.js +201 -0
- package/dist/integrations/gitlab/webhook.js.map +1 -0
- package/dist/integrations/incidentio/integration.js +1 -1
- package/dist/integrations/issue-reconciler.d.ts +3 -3
- package/dist/integrations/issue-reconciler.d.ts.map +1 -1
- package/dist/integrations/issue-reconciler.js +22 -2
- package/dist/integrations/issue-reconciler.js.map +1 -1
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +21 -0
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/integrations/platform/gitlab/event-worker.d.ts +64 -0
- package/dist/integrations/platform/gitlab/event-worker.d.ts.map +1 -0
- package/dist/integrations/platform/gitlab/event-worker.js +306 -0
- package/dist/integrations/platform/gitlab/event-worker.js.map +1 -0
- package/dist/integrations/platform/gitlab/integration.d.ts +57 -0
- package/dist/integrations/platform/gitlab/integration.d.ts.map +1 -0
- package/dist/integrations/platform/gitlab/integration.js +125 -0
- package/dist/integrations/platform/gitlab/integration.js.map +1 -0
- package/dist/integrations/platform/incidentio/integration.js +1 -1
- package/dist/integrations/slack/integration.d.ts.map +1 -1
- package/dist/integrations/slack/integration.js +3 -3
- package/dist/integrations/slack/integration.js.map +1 -1
- package/dist/integrations/slack/slack.d.ts +2 -0
- package/dist/integrations/slack/slack.d.ts.map +1 -1
- package/dist/integrations/slack/slack.js +21 -7
- package/dist/integrations/slack/slack.js.map +1 -1
- package/dist/integrations/subscription-session.d.ts +37 -0
- package/dist/integrations/subscription-session.d.ts.map +1 -0
- package/dist/integrations/subscription-session.js +83 -0
- package/dist/integrations/subscription-session.js.map +1 -0
- package/dist/packages/_internals/workspace/dist/index.js +1 -11
- package/dist/packages/_internals/workspace/dist/index.js.map +1 -1
- package/dist/routes/intake.d.ts.map +1 -1
- package/dist/routes/intake.js +4 -2
- package/dist/routes/intake.js.map +1 -1
- package/dist/routes/oauth.js +1 -1
- package/dist/routes/projects.d.ts +1 -0
- package/dist/routes/projects.d.ts.map +1 -1
- package/dist/routes/projects.js +1 -0
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/source-control-sessions.d.ts +20 -0
- package/dist/routes/source-control-sessions.d.ts.map +1 -0
- package/dist/routes/source-control-sessions.js +319 -0
- package/dist/routes/source-control-sessions.js.map +1 -0
- package/dist/routes/source-control-settings.d.ts +10 -0
- package/dist/routes/source-control-settings.d.ts.map +1 -0
- package/dist/routes/source-control-settings.js +87 -0
- package/dist/routes/source-control-settings.js.map +1 -0
- package/dist/routes/surface.d.ts +3 -3
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +55 -13
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/tenant-credentials.d.ts +8 -0
- package/dist/routes/tenant-credentials.d.ts.map +1 -1
- package/dist/routes/tenant-credentials.js +20 -3
- package/dist/routes/tenant-credentials.js.map +1 -1
- package/dist/routes/work-items.d.ts.map +1 -1
- package/dist/routes/work-items.js +1 -1
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +7 -1
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/start-coordinator.d.ts +1 -1
- package/dist/rules/start-coordinator.d.ts.map +1 -1
- package/dist/rules/start-coordinator.js +3 -2
- package/dist/rules/start-coordinator.js.map +1 -1
- package/dist/rules/transition-service.d.ts.map +1 -1
- package/dist/rules/transition-service.js +6 -3
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/rules/types.d.ts +67 -3
- package/dist/rules/types.d.ts.map +1 -1
- package/dist/rules/types.js +23 -2
- package/dist/rules/types.js.map +1 -1
- package/dist/rules/validation.d.ts.map +1 -1
- package/dist/rules/validation.js +2 -0
- package/dist/rules/validation.js.map +1 -1
- package/dist/sandbox/git-ref.d.ts +7 -0
- package/dist/sandbox/git-ref.d.ts.map +1 -0
- package/dist/sandbox/git-ref.js +15 -0
- package/dist/sandbox/git-ref.js.map +1 -0
- package/dist/sandbox/session-retirement.js +1 -1
- package/dist/sandbox/workdir.d.ts.map +1 -1
- package/dist/sandbox/workdir.js +6 -6
- package/dist/sandbox/workdir.js.map +1 -1
- package/dist/session/factory-session.d.ts +31 -1
- package/dist/session/factory-session.d.ts.map +1 -1
- package/dist/session/factory-session.js +60 -1
- package/dist/session/factory-session.js.map +1 -1
- package/dist/session/source-control-tools.d.ts +16 -0
- package/dist/session/source-control-tools.d.ts.map +1 -0
- package/dist/session/source-control-tools.js +464 -0
- package/dist/session/source-control-tools.js.map +1 -0
- package/dist/storage/domains/intake/base.d.ts +18 -0
- package/dist/storage/domains/intake/base.d.ts.map +1 -1
- package/dist/storage/domains/intake/base.js +77 -0
- package/dist/storage/domains/intake/base.js.map +1 -1
- package/dist/work-item-branch.d.ts +3 -1
- package/dist/work-item-branch.d.ts.map +1 -1
- package/dist/work-item-branch.js +22 -4
- package/dist/work-item-branch.js.map +1 -1
- package/dist/workspace.d.ts +15 -1
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +57 -23
- package/dist/workspace.js.map +1 -1
- package/factory-skills/factory-gitlab-rereview/SKILL.md +19 -0
- package/factory-skills/factory-gitlab-review/SKILL.md +30 -0
- package/factory-skills/factory-rereview/SKILL.md +13 -6
- package/factory-skills/factory-review/SKILL.md +13 -6
- package/factory-skills/factory-review/references/archaeology.md +225 -0
- package/factory-skills/factory-review/references/categories/README.md +34 -0
- package/factory-skills/factory-review/references/categories/behavior-change.md +36 -0
- package/factory-skills/factory-review/references/categories/bug-fix.md +37 -0
- package/factory-skills/factory-review/references/categories/docs.md +32 -0
- package/factory-skills/factory-review/references/categories/experimental-flagged.md +34 -0
- package/factory-skills/factory-review/references/categories/infra-tooling.md +37 -0
- package/factory-skills/factory-review/references/categories/internal-capability.md +35 -0
- package/factory-skills/factory-review/references/categories/large-cross-cutting.md +35 -0
- package/factory-skills/factory-review/references/categories/mechanical.md +36 -0
- package/factory-skills/factory-review/references/categories/performance.md +33 -0
- package/factory-skills/factory-review/references/categories/public-api.md +38 -0
- package/factory-skills/factory-review/references/categories/refactor.md +35 -0
- package/factory-skills/factory-review/references/categories/revert.md +31 -0
- package/factory-skills/factory-review/references/categories/schema-storage.md +39 -0
- package/factory-skills/factory-review/references/categories/security.md +35 -0
- package/factory-skills/factory-review/references/categories/tests-only.md +38 -0
- package/package.json +9 -9
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integration.js","names":["#orgIdByResourceId","#resolveIntakeDispatch","#listSources","#listItems","#listIssues","#getIssue","#createComment","#updateIssue","#versionControlContext","#versionControlContextForInstallation","#projects","#auth","#sourceControl","#integrationStorage","#resolveSourceReference","#contextForReference","#toIntakeSource","#listIssuePage","#toIntakeIssue","#resolveRequest","#listAllNotes","#accessToken","#accessTokenType","#baseUrl","#webhookSecret","#context"],"sources":["../../../src/integrations/gitlab/integration.ts"],"sourcesContent":["import type { RequestContext } from '@mastra/core/request-context';\nimport type { ApiRoute } from '@mastra/core/server';\nimport type { MastraWorker } from '@mastra/core/worker';\n\nimport type { IntegrationConnection } from '../../capabilities/connection.js';\nimport type {\n CreateIntakeCommentInput,\n CreatedIntakeComment,\n GetIntakeIssueInput,\n Intake,\n IntakeIssue,\n IntakeIssueDetail,\n IntakeItemPage,\n IntakeSource,\n ListIntakeIssuesInput,\n ListIntakeItemsInput,\n ResolveIntakeDispatchInput,\n ResolvedIntakeDispatch,\n UpdateIntakeIssueInput,\n} from '../../capabilities/intake.js';\nimport type { RouteAuth } from '../../routes/route.js';\nimport type { IntegrationStorageHandle } from '../../storage/domains/integrations/base.js';\nimport type { FactoryProjectsStorage } from '../../storage/domains/projects/base.js';\nimport type { SourceControlStorageHandle } from '../../storage/domains/source-control/base.js';\nimport type { FactoryIntegration, IntegrationContext, IntegrationTools } from '../base.js';\nimport { IssueReconcileWorker } from '../issue-reconcile-worker.js';\nimport { buildGitLabAgentTools } from './agent-tools.js';\nimport {\n GITLAB_ISSUES_PAGE_SIZE,\n GITLAB_NOTES_PAGE_SIZE,\n GITLAB_PROJECTS_PAGE_SIZE,\n GitLabApiClient,\n GitLabApiError,\n} from './api.js';\nimport type { GitLabIssue, GitLabNote, GitLabProject } from './api.js';\nimport { resolveGitLabRules } from './default-rules.js';\nimport type { GitLabEventRules, GitLabRuleOverrides } from './default-rules.js';\nimport { attachGitLabReconciler } from './reconciler.js';\nimport { gitlabReconciliationEnabled, gitlabReconciliationInterval } from './reconciliation-config.js';\nimport { buildGitLabRoutes } from './routes.js';\nimport { attachGitLabRules } from './rules.js';\nimport {\n createGitLabSubscriptionTools,\n parseCreatedMergeRequest,\n subscribeCurrentSessionToMergeRequest,\n} from './session-subscriptions.js';\nimport type { GitLabSubscriptionStorage } from './subscriptions.js';\nimport { buildGitLabVersionControl } from './version-control.js';\n\ninterface GitLabConnectionContext {\n id: string;\n label: string | null;\n api: GitLabApiClient;\n connection: IntegrationConnection;\n host: string;\n webBaseUrl?: string;\n /** Resolves a fresh provider token for a single brokered git operation. */\n repositoryAccessToken?: () => Promise<string>;\n}\n\ninterface GitLabSourceReference {\n /** Present only on legacy v1 ids; authentication is not resource identity. */\n connectionId?: string;\n /** Canonical v2 ids use the GitLab instance host plus immutable project id. */\n host?: string;\n projectId: string;\n /** Legacy ids carry display metadata; v2 resolves it from GitLab. */\n projectPath?: string;\n}\n\ninterface ResolvedGitLabSourceReference extends GitLabSourceReference {\n connectionId: string;\n host: string;\n projectPath: string;\n}\n\ninterface GitLabIssueReference extends GitLabSourceReference {\n issueIid: number;\n}\n\nexport interface GitLabMergeRequestReference {\n version: 1;\n host: string;\n projectId: number;\n mergeRequestIid: number;\n}\n\ninterface GitLabPageCursor {\n source: number;\n page: number;\n}\n\nexport interface GitLabStatusConnection {\n id: string;\n integrationId: string;\n status: 'active' | 'needs_reauth';\n accountLabel: string | null;\n}\n\nconst DIRECT_CONNECTION_ID = 'direct';\nconst GITLAB_CONNECTION_TOKEN_PREFIX = 'gitlab-connection:';\nconst GITLAB_SOURCE_PREFIX = 'gitlab-project:';\nconst GITLAB_ISSUE_PREFIX = 'gitlab-issue:';\nconst GITLAB_MERGE_REQUEST_PREFIX = 'gitlab-pr:';\nexport const GITLAB_TRUSTED_ACCESS_LEVEL = 30;\n// Bound issue detail reads to 2,000 notes. GitLab does not expose truncation through the Intake contract.\nconst MAX_NOTES_PAGES = 20;\nconst UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\nexport abstract class GitLabIntegrationBase implements FactoryIntegration {\n readonly id = 'gitlab';\n #projects: FactoryProjectsStorage | undefined;\n #sourceControl: SourceControlStorageHandle | undefined;\n #integrationStorage: GitLabSubscriptionStorage | undefined;\n #auth: RouteAuth | undefined;\n readonly #orgIdByResourceId = new Map<string, string | null>();\n readonly rules: GitLabEventRules;\n\n constructor(rules?: GitLabRuleOverrides) {\n this.rules = resolveGitLabRules(rules);\n }\n\n readonly intake: Intake = {\n resolveIntakeDispatch: input => this.#resolveIntakeDispatch(input),\n listSources: () => this.#listSources(),\n listItems: input => this.#listItems(input),\n listIssues: input => this.#listIssues(input),\n getIssue: input => this.#getIssue(input),\n createComment: input => this.#createComment(input),\n updateIssue: input => this.#updateIssue(input),\n };\n readonly versionControl = buildGitLabVersionControl({\n contextForConnection: connection => this.#versionControlContext(connection),\n contextForStoredInstallation: (connection, host) => this.#versionControlContextForInstallation(connection, host),\n });\n initialize({\n storage,\n projects,\n auth,\n sourceControl,\n }: {\n storage?: IntegrationStorageHandle;\n projects: FactoryProjectsStorage;\n auth: RouteAuth;\n sourceControl?: SourceControlStorageHandle;\n }): void {\n this.#projects = projects;\n this.#auth = auth;\n this.#sourceControl = sourceControl;\n this.#integrationStorage = storage as GitLabSubscriptionStorage | undefined;\n }\n\n /** Generic integration persistence (merge-request subscriptions). */\n get integrationStorage(): GitLabSubscriptionStorage {\n if (!this.#integrationStorage) {\n throw new Error(`${this.constructor.name} is not initialized — the factory binds storage during prepare().`);\n }\n return this.#integrationStorage;\n }\n\n /** Source-control rows for this integration; absent when the host runs without them. */\n get sourceControlStorage(): SourceControlStorageHandle | undefined {\n return this.#sourceControl;\n }\n\n get authEnabled(): boolean {\n return this.#auth?.enabled() ?? false;\n }\n\n get projects(): FactoryProjectsStorage {\n if (!this.#projects) {\n throw new Error(`${this.constructor.name} is not initialized — the factory binds storage during prepare().`);\n }\n return this.#projects;\n }\n\n async resolveOrgId(resourceId: string): Promise<string | null> {\n const cached = this.#orgIdByResourceId.get(resourceId);\n if (cached !== undefined) return cached;\n if (!UUID_PATTERN.test(resourceId)) {\n this.#orgIdByResourceId.set(resourceId, null);\n return null;\n }\n try {\n await this.projects.ensureReady();\n const project = await this.projects.getById({ id: resourceId });\n const orgId = project?.orgId ?? null;\n this.#orgIdByResourceId.set(resourceId, orgId);\n return orgId;\n } catch {\n return null;\n }\n }\n\n clearCaches(): void {\n this.#orgIdByResourceId.clear();\n }\n\n abstract hasActiveConnections(): Promise<boolean>;\n abstract authFailureMessage(): string;\n protected abstract activeContexts(): Promise<GitLabConnectionContext[]>;\n protected abstract contextById(connectionId: string): Promise<GitLabConnectionContext>;\n\n /** Resolve only a GitLab repository linked to this Factory in this organization. */\n async getLinkedRepository(input: { orgId: string; factoryProjectId: string; projectRepositoryId: string }) {\n if (!this.#sourceControl) return null;\n const connections = await this.#sourceControl.connections.list({\n orgId: input.orgId,\n factoryProjectId: input.factoryProjectId,\n });\n for (const connection of connections) {\n if (connection.integrationId !== 'gitlab') continue;\n const links = await this.#sourceControl.projectRepositories.list({\n orgId: input.orgId,\n connectionId: connection.id,\n });\n const link = links.find(candidate => candidate.id === input.projectRepositoryId);\n if (!link) continue;\n const repository = await this.#sourceControl.repositories.get({ orgId: input.orgId, id: link.repositoryId });\n const installation = await this.#sourceControl.installations.get({\n orgId: input.orgId,\n id: connection.installationId,\n });\n if (!repository || !installation) return null;\n const host = installation.providerMetadata.host;\n if (typeof host !== 'string' || !host) return null;\n return { repository, host: normalizeGitLabHost(host) };\n }\n return null;\n }\n\n async getProjectMemberAccessLevel(\n connectionId: string,\n projectId: string,\n username: string,\n ): Promise<number | undefined> {\n const normalized = username.trim().toLowerCase();\n if (!normalized) return undefined;\n const members = await (await this.contextById(connectionId)).api.listProjectMembers(projectId, { query: username });\n const member = members.find(\n candidate => candidate.username.toLowerCase() === normalized && candidate.state !== 'blocked',\n );\n return member?.access_level;\n }\n\n async getWorkItemAuthorUsername(\n connectionId: string,\n projectId: string,\n kind: 'issue' | 'merge_request',\n iid: number,\n ): Promise<string | undefined> {\n const api = (await this.contextById(connectionId)).api;\n const item = kind === 'issue' ? await api.getIssue(projectId, iid) : await api.getMergeRequest(projectId, iid);\n return item.author?.username;\n }\n\n async isProjectMemberTrustedForSource(sourceId: string, username: string): Promise<boolean> {\n const reference = decodeSourceId(sourceId);\n if (!reference) throw new GitLabApiError('GitLab source identity is invalid.', 400);\n const { context, source } = await this.#resolveSourceReference(reference);\n const accessLevel = await this.getProjectMemberAccessLevel(context.id, source.projectId, username);\n return (accessLevel ?? 0) >= GITLAB_TRUSTED_ACCESS_LEVEL;\n }\n\n async getIssueForFactoryProject(input: {\n orgId: string;\n factoryProjectId: string;\n issueId: string;\n }): Promise<IntakeIssueDetail | null> {\n if (!this.#sourceControl) throw new GitLabApiError('GitLab source control is unavailable.', 503);\n const reference = decodeIssueReference(input.issueId);\n const locator = parseIssueLocator(input.issueId);\n if (!reference && !locator) {\n throw new GitLabApiError('GitLab issue must include a project and issue IID.', 400);\n }\n\n const normalizePath = (value: string) => value.replace(/^\\/+|\\/+$/g, '').toLowerCase();\n const matches: Array<{\n connectionId: string;\n projectId: string;\n projectPath: string;\n }> = [];\n const connections = await this.#sourceControl.connections.list({\n orgId: input.orgId,\n factoryProjectId: input.factoryProjectId,\n });\n for (const connection of connections) {\n const installation = await this.#sourceControl.installations.get({\n orgId: input.orgId,\n id: connection.installationId,\n });\n if (!installation) continue;\n for (const projectRepository of await this.#sourceControl.projectRepositories.list({\n orgId: input.orgId,\n connectionId: connection.id,\n })) {\n const repository = await this.#sourceControl.repositories.get({\n orgId: input.orgId,\n id: projectRepository.repositoryId,\n });\n if (!repository) continue;\n if (reference?.projectId && repository.externalId !== reference.projectId) continue;\n if (reference?.projectPath && normalizePath(repository.slug) !== normalizePath(reference.projectPath)) continue;\n if (locator?.projectPath && normalizePath(repository.slug) !== normalizePath(locator.projectPath)) continue;\n if (reference?.connectionId && installation.externalId !== reference.connectionId) continue;\n const installationHost =\n typeof installation.providerMetadata.host === 'string'\n ? normalizeGitLabHost(installation.providerMetadata.host)\n : null;\n if (reference?.host && installationHost !== normalizeGitLabHost(reference.host)) continue;\n if (locator?.host && installationHost !== normalizeGitLabHost(locator.host)) continue;\n matches.push({\n connectionId: installation.externalId,\n projectId: repository.externalId,\n projectPath: repository.slug,\n });\n }\n }\n const match = matches.sort((left, right) => left.connectionId.localeCompare(right.connectionId))[0];\n if (!match) throw new GitLabApiError('GitLab issue is outside the active Factory project.', 404);\n return this.intake.getIssue({\n connection: gitlabConnection(match.connectionId),\n sourceId: encodeSourceId(match),\n issueId: input.issueId,\n });\n }\n\n /** Platform mode exposes org connections; direct mode returns undefined. */\n async statusConnections(): Promise<GitLabStatusConnection[] | undefined> {\n return undefined;\n }\n\n /** Direct mode verifies its configured credential; Platform mode uses connection status. */\n async verifyStatus(): Promise<void> {\n // Platform connection state is authoritative unless a provider overrides this probe.\n }\n\n protected get webhookSecret(): string | undefined {\n return undefined;\n }\n\n workers(ctx: IntegrationContext): MastraWorker[] {\n if (!gitlabReconciliationEnabled()) return [];\n const reconcile = attachGitLabReconciler(this, ctx);\n if (!reconcile) return [];\n const intervalMs = gitlabReconciliationInterval();\n return [\n new IssueReconcileWorker({\n integrationId: this.id,\n reconcile,\n ...(intervalMs ? { intervalMs } : {}),\n }),\n ];\n }\n routes(ctx: IntegrationContext): ApiRoute[] {\n const ingestFactoryEvent = attachGitLabRules(this, ctx);\n return buildGitLabRoutes({\n gitlab: this,\n auth: ctx.auth,\n intake: ctx.storage?.intake,\n emitAudit: ctx.hooks?.emitAudit,\n sandbox: ctx.sandbox,\n webhookSecret: this.webhookSecret,\n ingestFactoryEvent,\n ...(ctx.controller ? { controller: ctx.controller } : {}),\n });\n }\n\n async agentTools(args: { requestContext: RequestContext }): Promise<IntegrationTools> {\n return buildGitLabAgentTools({ requestContext: args.requestContext, gitlab: this });\n }\n\n sessionTools({ requestContext }: { requestContext: RequestContext }): IntegrationTools {\n return createGitLabSubscriptionTools(requestContext, this);\n }\n\n async postToolObserver({\n toolContext,\n requestContext,\n }: Parameters<NonNullable<FactoryIntegration['postToolObserver']>>[0]): Promise<void> {\n const mergeRequestUrl = parseCreatedMergeRequest(toolContext);\n if (!mergeRequestUrl || !requestContext) return;\n await subscribeCurrentSessionToMergeRequest(requestContext, mergeRequestUrl, 'auto-create-change-request', this);\n }\n\n abstract diagnostics(): Record<string, unknown>;\n\n /** Prefer the configured direct token over an older Platform installation on the same host. */\n async resolveActiveConnectionForHost(storedConnectionId: string, host: string): Promise<string> {\n if (this.diagnostics().mode !== 'direct') return storedConnectionId;\n const [direct] = await this.activeContexts();\n return direct && normalizeGitLabHost(direct.host) === normalizeGitLabHost(host) ? direct.id : storedConnectionId;\n }\n\n async #versionControlContext(connection: IntegrationConnection): Promise<GitLabConnectionContext> {\n const connectionId = connectionIdFromConnection(connection);\n if (connectionId) return this.contextById(connectionId);\n const contexts = await this.activeContexts();\n if (contexts.length !== 1) {\n throw new GitLabApiError(\n 'GitLab version-control requests must identify a connection when multiple GitLab accounts are connected.',\n 400,\n );\n }\n return contexts[0]!;\n }\n async #versionControlContextForInstallation(\n connection: IntegrationConnection,\n host: string | undefined,\n ): Promise<GitLabConnectionContext> {\n // A direct PAT intentionally overrides Platform credentials. Old links can\n // retain their Platform connection id; use the PAT only for the same host.\n if (this.diagnostics().mode === 'direct' && host) {\n const [direct] = await this.activeContexts();\n if (direct && normalizeGitLabHost(host) === normalizeGitLabHost(direct.host)) return direct;\n }\n return this.#versionControlContext(connection);\n }\n async #contextForReference(reference: GitLabSourceReference): Promise<GitLabConnectionContext> {\n if (reference.connectionId) return this.contextById(reference.connectionId);\n const contexts = (await this.activeContexts()).filter(\n context => !reference.host || normalizeGitLabHost(context.host) === normalizeGitLabHost(reference.host),\n );\n if (contexts.length === 0) {\n throw new GitLabApiError('No active GitLab connection can access this source.', 401);\n }\n if (contexts.length === 1) return contexts[0]!;\n\n if (this.#sourceControl) {\n const linkedConnectionIds = new Set(\n (await this.#sourceControl.projectRepositories.listConfiguredExternalKeys())\n .filter(key => key.repositoryExternalId === reference.projectId)\n .map(key => key.installationExternalId),\n );\n const linked = contexts.filter(context => linkedConnectionIds.has(context.id));\n if (linked.length > 0) return linked.sort((left, right) => left.id.localeCompare(right.id))[0]!;\n }\n\n for (const context of contexts.sort((left, right) => left.id.localeCompare(right.id))) {\n try {\n await context.api.getProject(reference.projectId);\n return context;\n } catch (error) {\n if (!(error instanceof GitLabApiError) || ![401, 403, 404].includes(error.status ?? 0)) throw error;\n // This credential cannot access the project; try another active account for the same canonical source.\n }\n }\n throw new GitLabApiError('No active GitLab connection can access this source.', 404);\n }\n\n async #resolveSourceReference(\n reference: GitLabSourceReference,\n ): Promise<{ context: GitLabConnectionContext; source: ResolvedGitLabSourceReference }> {\n const context = await this.#contextForReference(reference);\n const projectPath =\n reference.projectPath ?? (await context.api.getProject(reference.projectId)).path_with_namespace;\n return {\n context,\n source: {\n connectionId: context.id,\n host: normalizeGitLabHost(reference.host ?? context.host),\n projectId: reference.projectId,\n projectPath,\n },\n };\n }\n\n async #resolveIntakeDispatch({ externalSource }: ResolveIntakeDispatchInput): Promise<ResolvedIntakeDispatch | null> {\n if (externalSource.type !== 'issue') return null;\n const reference = decodeIssueReference(externalSource.externalId);\n if (!reference) return null;\n const { context, source } = await this.#resolveSourceReference(reference);\n return {\n connection: context.connection,\n sourceId: encodeSourceId(source),\n issueId: String(reference.issueIid),\n };\n }\n\n async #listSources(): Promise<IntakeSource[]> {\n const sources = new Map<string, IntakeSource>();\n for (const context of await this.activeContexts()) {\n for (let page = 1; ; page++) {\n const projects = await context.api.listProjects({ page });\n for (const project of projects) {\n const source = this.#toIntakeSource(context, project);\n if (!sources.has(source.id)) sources.set(source.id, source);\n }\n if (projects.length < GITLAB_PROJECTS_PAGE_SIZE) break;\n }\n }\n return [...sources.values()];\n }\n\n async #listItems(input: ListIntakeItemsInput): Promise<IntakeItemPage> {\n const result = await this.#listIssuePage(input.sourceIds, input.cursor);\n return {\n items: result.issues.map(({ issue, source, context }) => ({\n source: {\n type: 'issue',\n externalId: encodeIssueReference({ ...source, issueIid: issue.iid }),\n url: issue.web_url,\n },\n sourceId: encodeSourceId(source),\n title: `${source.projectPath}#${issue.iid}: ${issue.title}`,\n status: issue.state,\n labels: issue.labels ?? [],\n assignee: displayName(issue.assignee),\n createdAt: issue.created_at,\n updatedAt: issue.updated_at,\n metadata: {\n identifier: `${source.projectPath}#${issue.iid}`,\n projectId: source.projectId,\n projectPath: source.projectPath,\n connectionId: context.id,\n accountLabel: context.label,\n labelColors: Object.fromEntries((issue.labelDetails ?? []).map(label => [label.name, label.color])),\n },\n })),\n nextCursor: result.nextCursor,\n };\n }\n\n async #listIssues(input: ListIntakeIssuesInput) {\n const result = await this.#listIssuePage(input.sourceIds, input.cursor, input.labels);\n return {\n issues: result.issues.map(({ issue, source }) => ({\n ...this.#toIntakeIssue(issue, source.projectPath),\n sourceId: encodeSourceId(source),\n })),\n nextCursor: result.nextCursor,\n };\n }\n\n async #listIssuePage(sourceIds: string[], cursor?: string, labels?: string[]) {\n const sources = sourceIds.map(decodeSourceId).filter((source): source is GitLabSourceReference => source !== null);\n if (sources.length === 0) return { issues: [], nextCursor: null };\n const current = decodePageCursor(cursor);\n if (!current || current.source >= sources.length) return { issues: [], nextCursor: null };\n\n for (let sourceIndex = current.source; sourceIndex < sources.length; sourceIndex++) {\n const { source, context } = await this.#resolveSourceReference(sources[sourceIndex]!);\n const page = sourceIndex === current.source ? current.page : 1;\n const issues = await context.api.listIssues(source.projectId, { page, labels });\n const nextCursor =\n issues.length === GITLAB_ISSUES_PAGE_SIZE\n ? encodePageCursor({ source: sourceIndex, page: page + 1 })\n : sourceIndex + 1 < sources.length\n ? encodePageCursor({ source: sourceIndex + 1, page: 1 })\n : null;\n return { issues: issues.map(issue => ({ issue, source, context })), nextCursor };\n }\n return { issues: [], nextCursor: null };\n }\n\n async #getIssue(input: GetIntakeIssueInput): Promise<IntakeIssueDetail | null> {\n const resolved = await this.#resolveRequest(input);\n let issue: GitLabIssue;\n try {\n issue = await resolved.context.api.getIssue(resolved.projectId, resolved.issueIid);\n } catch (error) {\n if (error instanceof GitLabApiError && error.status === 404) return null;\n throw error;\n }\n const notes = await this.#listAllNotes(resolved.context.api, resolved.projectId, resolved.issueIid);\n return {\n ...this.#toIntakeIssue(issue, resolved.projectPath),\n description: issue.description?.trim() || null,\n comments: notes\n .filter(note => !note.system)\n .map(note => ({ author: displayName(note.author), body: note.body, createdAt: note.created_at })),\n };\n }\n\n async #listAllNotes(api: GitLabApiClient, projectId: string, issueIid: number): Promise<GitLabNote[]> {\n const notes: GitLabNote[] = [];\n for (let page = 1; page <= MAX_NOTES_PAGES; page++) {\n const result = await api.listNotes(projectId, issueIid, { page });\n notes.push(...result);\n if (result.length < GITLAB_NOTES_PAGE_SIZE) break;\n }\n return notes;\n }\n\n async #createComment(input: CreateIntakeCommentInput): Promise<CreatedIntakeComment | null> {\n const resolved = await this.#resolveRequest(input);\n try {\n const issue = await resolved.context.api.getIssue(resolved.projectId, resolved.issueIid);\n const note = await resolved.context.api.createNote(resolved.projectId, resolved.issueIid, input.body);\n return { id: String(note.id), url: `${issue.web_url}#note_${note.id}` };\n } catch (error) {\n if (error instanceof GitLabApiError && error.status === 404) return null;\n throw error;\n }\n }\n\n async #updateIssue(input: UpdateIntakeIssueInput): Promise<IntakeIssue | null> {\n const resolved = await this.#resolveRequest(input);\n let issue: GitLabIssue;\n try {\n issue = await resolved.context.api.getIssue(resolved.projectId, resolved.issueIid);\n } catch (error) {\n if (error instanceof GitLabApiError && error.status === 404) return null;\n throw error;\n }\n const stateEvent = targetStateEvent(input);\n if (!stateEvent) return null;\n if ((stateEvent === 'close' && issue.state === 'closed') || (stateEvent === 'reopen' && issue.state === 'opened')) {\n return this.#toIntakeIssue(issue, resolved.projectPath);\n }\n const updated = await resolved.context.api.updateIssueState(resolved.projectId, resolved.issueIid, stateEvent);\n return this.#toIntakeIssue(updated, resolved.projectPath);\n }\n\n async #resolveRequest(input: GetIntakeIssueInput): Promise<{\n context: GitLabConnectionContext;\n projectId: string;\n projectPath: string;\n issueIid: number;\n }> {\n const issueReference = decodeIssueReference(input.issueId);\n const source = input.sourceId ? decodeSourceId(input.sourceId) : null;\n const locator = parseIssueLocator(input.issueId);\n const reference = issueReference ?? source;\n let context: GitLabConnectionContext;\n if (reference) {\n context = await this.#contextForReference(reference);\n } else {\n const connectionId = connectionIdFromConnection(input.connection);\n const contexts = connectionId ? [await this.contextById(connectionId)] : await this.activeContexts();\n if (contexts.length !== 1) {\n throw new GitLabApiError('GitLab issue reference must resolve to exactly one active connection.', 400);\n }\n context = contexts[0]!;\n }\n const projectId = issueReference?.projectId ?? source?.projectId ?? locator?.projectPath;\n const issueIid = issueReference?.issueIid ?? locator?.issueIid ?? parsePositiveInteger(input.issueId);\n if (!projectId || !issueIid) {\n throw new GitLabApiError(\n 'GitLab issue must include a project and issue IID (for example, group/project#42).',\n 400,\n );\n }\n const projectPath =\n issueReference?.projectPath ??\n source?.projectPath ??\n locator?.projectPath ??\n (await context.api.getProject(projectId)).path_with_namespace;\n return { context, projectId, projectPath, issueIid };\n }\n\n #toIntakeSource(context: GitLabConnectionContext, project: GitLabProject): IntakeSource {\n const reference: GitLabSourceReference = {\n host: normalizeGitLabHost(context.host),\n projectId: String(project.id),\n projectPath: project.path_with_namespace,\n };\n return {\n id: encodeSourceId(reference),\n name: project.path_with_namespace,\n type: 'project',\n metadata: {\n projectId: String(project.id),\n projectPath: project.path_with_namespace,\n defaultBranch: project.default_branch ?? null,\n connectionId: context.id,\n accountLabel: context.label,\n url: project.web_url,\n },\n };\n }\n\n #toIntakeIssue(issue: GitLabIssue, projectPath: string): IntakeIssue {\n const assignees = issue.assignees?.map(displayName).filter((name): name is string => name !== null) ?? [];\n return {\n id: String(issue.iid),\n identifier: `${projectPath}#${issue.iid}`,\n title: issue.title,\n url: issue.web_url,\n author: displayName(issue.author),\n authorUsername: issue.author?.username ?? null,\n state: issue.state,\n stateType: issue.state === 'closed' ? 'completed' : 'unstarted',\n priority: typeof issue.weight === 'number' ? String(issue.weight) : null,\n assignee: displayName(issue.assignee) ?? assignees[0] ?? null,\n assignees,\n source: projectPath,\n labels: issue.labels ?? [],\n labelColors: Object.fromEntries((issue.labelDetails ?? []).map(label => [label.name, label.color])),\n commentCount: issue.user_notes_count ?? null,\n createdAt: issue.created_at,\n updatedAt: issue.updated_at,\n };\n }\n}\n\nexport type GitLabAccessTokenType = 'personal' | 'group';\n\nexport interface GitLabIntegrationConfig {\n rules?: GitLabRuleOverrides;\n accessToken?: string;\n /** Defaults to `personal`; both token types authenticate identically but have different resource reach. */\n accessTokenType?: GitLabAccessTokenType;\n baseUrl?: string;\n webhookSecret?: string;\n fetchImpl?: typeof fetch;\n}\n\nconst DIRECT_CONNECTION_TOKEN = 'gitlab-direct-access-token';\n\nexport class GitLabIntegration extends GitLabIntegrationBase {\n readonly #accessToken: string;\n readonly #accessTokenType: GitLabAccessTokenType;\n readonly #baseUrl: string;\n readonly #webhookSecret: string | undefined;\n readonly #context: GitLabConnectionContext;\n\n constructor(config: GitLabIntegrationConfig = {}) {\n super(config.rules);\n const accessToken = config.accessToken?.trim() || process.env.GITLAB_ACCESS_TOKEN?.trim();\n if (!accessToken) {\n throw new Error('GitLabIntegration: missing required GITLAB_ACCESS_TOKEN.');\n }\n this.#accessToken = accessToken;\n this.#accessTokenType = parseAccessTokenType(\n config.accessTokenType ?? process.env.GITLAB_ACCESS_TOKEN_TYPE?.trim(),\n );\n this.#baseUrl = (config.baseUrl ?? process.env.GITLAB_BASE_URL?.trim() ?? 'https://gitlab.com').replace(/\\/+$/, '');\n this.#webhookSecret = config.webhookSecret?.trim() || process.env.GITLAB_WEBHOOK_SECRET?.trim() || undefined;\n const api = new GitLabApiClient({\n baseUrl: this.#baseUrl,\n accessToken: this.#accessToken,\n ...(config.fetchImpl ? { fetchImpl: config.fetchImpl } : {}),\n });\n this.#context = {\n id: DIRECT_CONNECTION_ID,\n label: new URL(this.#baseUrl).host,\n api,\n connection: { type: 'oauth', accessToken: DIRECT_CONNECTION_TOKEN },\n host: new URL(this.#baseUrl).host,\n webBaseUrl: this.#baseUrl,\n repositoryAccessToken: async () => this.#accessToken,\n };\n }\n\n protected override get webhookSecret(): string | undefined {\n return this.#webhookSecret;\n }\n\n async hasActiveConnections(): Promise<boolean> {\n return true;\n }\n\n override async verifyStatus(): Promise<void> {\n await this.#context.api.getCurrentUser();\n }\n\n authFailureMessage(): string {\n return `GitLab rejected the configured ${this.#accessTokenType} access token. Check the token and its scopes.`;\n }\n\n protected async activeContexts(): Promise<GitLabConnectionContext[]> {\n return [this.#context];\n }\n\n protected async contextById(connectionId: string): Promise<GitLabConnectionContext> {\n if (connectionId !== DIRECT_CONNECTION_ID) {\n throw new GitLabApiError('GitLab connection is unavailable.', 401);\n }\n return this.#context;\n }\n\n diagnostics(): Record<string, unknown> {\n return {\n configured: true,\n mode: 'direct',\n accessTokenType: this.#accessTokenType,\n endpointHost: new URL(this.#baseUrl).host,\n webhookConfigured: Boolean(this.#webhookSecret),\n };\n }\n}\n\nfunction parseAccessTokenType(value: string | undefined): GitLabAccessTokenType {\n const normalized = value?.trim().toLowerCase() || 'personal';\n if (normalized === 'personal' || normalized === 'group') return normalized;\n throw new Error(\"GitLabIntegration: GITLAB_ACCESS_TOKEN_TYPE must be 'personal' or 'group'.\");\n}\n\nexport function encodeSourceId(reference: GitLabSourceReference): string {\n if (reference.host) {\n return `${GITLAB_SOURCE_PREFIX}${encodeOpaque({\n version: 2,\n host: normalizeGitLabHost(reference.host),\n projectId: reference.projectId,\n })}`;\n }\n if (!reference.connectionId || !reference.projectPath) {\n throw new Error('GitLab source reference is missing its instance host.');\n }\n return `${GITLAB_SOURCE_PREFIX}${encodeOpaque(reference)}`;\n}\n\nexport function decodeSourceId(value: string): GitLabSourceReference | null {\n return decodeOpaque(value, GITLAB_SOURCE_PREFIX, isSourceReference);\n}\n\nexport function encodeIssueReference(reference: GitLabIssueReference): string {\n if (reference.host) {\n return `${GITLAB_ISSUE_PREFIX}${encodeOpaque({\n version: 2,\n host: normalizeGitLabHost(reference.host),\n projectId: reference.projectId,\n issueIid: reference.issueIid,\n })}`;\n }\n if (!reference.connectionId || !reference.projectPath) {\n throw new Error('GitLab issue reference is missing its instance host.');\n }\n return `${GITLAB_ISSUE_PREFIX}${encodeOpaque(reference)}`;\n}\n\nexport function decodeIssueReference(value: string): GitLabIssueReference | null {\n return decodeOpaque(value, GITLAB_ISSUE_PREFIX, isIssueReference);\n}\n\nexport function decodeMergeRequestReference(value: string): GitLabMergeRequestReference | null {\n return decodeOpaque(value, GITLAB_MERGE_REQUEST_PREFIX, isMergeRequestReference);\n}\n\nexport function gitlabConnection(connectionId: string): IntegrationConnection {\n return { type: 'oauth', accessToken: `${GITLAB_CONNECTION_TOKEN_PREFIX}${connectionId}` };\n}\n\nfunction connectionIdFromConnection(connection: IntegrationConnection): string | null {\n if (connection.type !== 'oauth' || !connection.accessToken.startsWith(GITLAB_CONNECTION_TOKEN_PREFIX)) return null;\n return connection.accessToken.slice(GITLAB_CONNECTION_TOKEN_PREFIX.length) || null;\n}\n\nfunction encodeOpaque(value: unknown): string {\n return Buffer.from(JSON.stringify(value)).toString('base64url');\n}\n\nfunction decodeOpaque<T>(value: string, prefix: string, guard: (value: unknown) => value is T): T | null {\n if (!value.startsWith(prefix)) return null;\n try {\n const decoded = JSON.parse(Buffer.from(value.slice(prefix.length), 'base64url').toString('utf8')) as unknown;\n return guard(decoded) ? decoded : null;\n } catch {\n return null;\n }\n}\n\nexport function normalizeGitLabHost(host: string): string {\n return host.trim().toLowerCase().replace(/\\.$/, '');\n}\n\nfunction isSourceReference(value: unknown): value is GitLabSourceReference {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return false;\n const ref = value as Record<string, unknown>;\n if (typeof ref.projectId !== 'string' || ref.projectId.length === 0) return false;\n const canonical = ref.version === 2 && typeof ref.host === 'string' && normalizeGitLabHost(ref.host).length > 0;\n const legacy =\n typeof ref.connectionId === 'string' &&\n ref.connectionId.length > 0 &&\n typeof ref.projectPath === 'string' &&\n ref.projectPath.length > 0;\n return canonical || legacy;\n}\n\nfunction isIssueReference(value: unknown): value is GitLabIssueReference {\n return (\n isSourceReference(value) &&\n Number.isSafeInteger((value as GitLabIssueReference).issueIid) &&\n (value as GitLabIssueReference).issueIid > 0\n );\n}\n\nfunction isMergeRequestReference(value: unknown): value is GitLabMergeRequestReference {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return false;\n const ref = value as Record<string, unknown>;\n return (\n ref.version === 1 &&\n typeof ref.host === 'string' &&\n normalizeGitLabHost(ref.host).length > 0 &&\n Number.isSafeInteger(ref.projectId) &&\n (ref.projectId as number) > 0 &&\n Number.isSafeInteger(ref.mergeRequestIid) &&\n (ref.mergeRequestIid as number) > 0\n );\n}\n\nfunction encodePageCursor(cursor: GitLabPageCursor): string {\n return encodeOpaque(cursor);\n}\n\nfunction decodePageCursor(value: string | undefined): GitLabPageCursor | null {\n if (!value) return { source: 0, page: 1 };\n try {\n const decoded = JSON.parse(Buffer.from(value, 'base64url').toString('utf8')) as Record<string, unknown>;\n if (!Number.isSafeInteger(decoded.source) || Number(decoded.source) < 0) return null;\n if (!Number.isSafeInteger(decoded.page) || Number(decoded.page) < 1) return null;\n return { source: Number(decoded.source), page: Number(decoded.page) };\n } catch {\n return null;\n }\n}\n\nfunction displayName(user: { name?: string | null; username: string } | null | undefined): string | null {\n return user?.name?.trim() || user?.username || null;\n}\n\nfunction parsePositiveInteger(value: string): number | null {\n if (!/^\\d+$/.test(value)) return null;\n const parsed = Number(value);\n return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;\n}\n\nfunction parseIssueLocator(value: string): {\n host?: string;\n projectPath: string;\n issueIid: number;\n} | null {\n const shorthand = value.match(/^(.+?)#(\\d+)$/);\n if (shorthand) {\n const issueIid = parsePositiveInteger(shorthand[2]!);\n const projectPath = shorthand[1]!.replace(/^\\/+|\\/+$/g, '');\n return issueIid && projectPath ? { projectPath, issueIid } : null;\n }\n try {\n const url = new URL(value);\n const match = url.pathname.match(/^\\/(.+)\\/-\\/issues\\/(\\d+)\\/?$/);\n if (!match) return null;\n const issueIid = parsePositiveInteger(match[2]!);\n if (!issueIid) return null;\n const projectPath = decodeURIComponent(match[1]!);\n return { host: url.host, projectPath, issueIid };\n } catch {\n return null;\n }\n}\n\nfunction targetStateEvent(input: UpdateIntakeIssueInput): 'close' | 'reopen' | null {\n if (input.state.kind === 'byType') {\n // GitLab issues have only opened/closed states, so canceled is intentionally mapped to close.\n return input.state.stateType === 'completed' || input.state.stateType === 'canceled' ? 'close' : 'reopen';\n }\n const name = input.state.name.trim().toLowerCase();\n if (name === 'closed' || name === 'close') return 'close';\n if (name === 'opened' || name === 'open' || name === 'reopen') return 'reopen';\n return null;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { IntegrationContext } from '../base.js';
|
|
2
|
+
import type { IssueReconciler } from '../issue-reconciler.js';
|
|
3
|
+
import type { GitLabIntegrationBase } from './integration.js';
|
|
4
|
+
export type GitLabIssueReconciler = IssueReconciler;
|
|
5
|
+
export declare function attachGitLabIssueReconciler(gitlab: Pick<GitLabIntegrationBase, 'intake' | 'rules' | 'getProjectMemberAccessLevel' | 'getWorkItemAuthorUsername' | 'isProjectMemberTrustedForSource'>, context: IntegrationContext): GitLabIssueReconciler | undefined;
|
|
6
|
+
//# sourceMappingURL=issue-reconciler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issue-reconciler.d.ts","sourceRoot":"","sources":["../../../src/integrations/gitlab/issue-reconciler.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAI9D,MAAM,MAAM,qBAAqB,GAAG,eAAe,CAAC;AA+EpD,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,IAAI,CACV,qBAAqB,EACrB,QAAQ,GAAG,OAAO,GAAG,6BAA6B,GAAG,2BAA2B,GAAG,iCAAiC,CACrH,EACD,OAAO,EAAE,kBAAkB,GAC1B,qBAAqB,GAAG,SAAS,CAkDnC"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { workItemPhaseSemantics } from "../../boards/semantics.js";
|
|
2
|
+
import "../../boards/index.js";
|
|
3
|
+
import { createIssueReconciler } from "../issue-reconciler.js";
|
|
4
|
+
import { attachGitLabRules } from "./rules.js";
|
|
5
|
+
import { decodeIssueReference } from "./integration.js";
|
|
6
|
+
//#region src/integrations/gitlab/issue-reconciler.ts
|
|
7
|
+
function stringMetadata(item, key) {
|
|
8
|
+
const value = item.metadata?.[key];
|
|
9
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
10
|
+
}
|
|
11
|
+
function numberMetadata(item, key) {
|
|
12
|
+
const value = item.metadata?.[key];
|
|
13
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : void 0;
|
|
14
|
+
}
|
|
15
|
+
function normalizeHost(host) {
|
|
16
|
+
return host.trim().toLowerCase().replace(/\.$/, "");
|
|
17
|
+
}
|
|
18
|
+
function identityForItem(item) {
|
|
19
|
+
const reference = decodeIssueReference(item.externalSource?.externalId ?? "");
|
|
20
|
+
const projectId = Number(reference?.projectId);
|
|
21
|
+
if (!reference || !Number.isSafeInteger(projectId) || projectId <= 0) return null;
|
|
22
|
+
let urlHost;
|
|
23
|
+
try {
|
|
24
|
+
urlHost = new URL(item.externalSource?.url ?? "").host;
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const host = normalizeHost(reference.host ?? urlHost);
|
|
29
|
+
if (!host || host !== normalizeHost(urlHost)) return null;
|
|
30
|
+
const metadataProjectId = numberMetadata(item, "gitlabProjectId");
|
|
31
|
+
const metadataIssueIid = numberMetadata(item, "gitlabIssueIid");
|
|
32
|
+
const metadataHost = stringMetadata(item, "gitlabHost");
|
|
33
|
+
if (metadataProjectId && metadataProjectId !== projectId || metadataIssueIid && metadataIssueIid !== reference.issueIid || metadataHost && normalizeHost(metadataHost) !== host) return null;
|
|
34
|
+
return {
|
|
35
|
+
projectId,
|
|
36
|
+
issueIid: reference.issueIid,
|
|
37
|
+
host
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function closedIssueEvent(issue, identity) {
|
|
41
|
+
const { projectId, issueIid, host } = identity;
|
|
42
|
+
const projectPath = issue.source?.trim();
|
|
43
|
+
if (!projectPath) throw new Error("GitLab issue work item is missing canonical reconciliation metadata.");
|
|
44
|
+
const username = issue.authorUsername?.trim() || "factory-reconciler";
|
|
45
|
+
return {
|
|
46
|
+
event: "Issue Hook",
|
|
47
|
+
deliveryId: `reconcile:issue:${normalizeHost(host)}:${projectId}:${issueIid}:${issue.updatedAt}:closed`,
|
|
48
|
+
instanceHost: host,
|
|
49
|
+
payload: {
|
|
50
|
+
user_username: username,
|
|
51
|
+
user: { username },
|
|
52
|
+
project: {
|
|
53
|
+
id: projectId,
|
|
54
|
+
path_with_namespace: projectPath,
|
|
55
|
+
web_url: `https://${host}/${projectPath}`
|
|
56
|
+
},
|
|
57
|
+
object_attributes: {
|
|
58
|
+
iid: issueIid,
|
|
59
|
+
title: issue.title,
|
|
60
|
+
url: issue.url,
|
|
61
|
+
state: "closed",
|
|
62
|
+
action: "close",
|
|
63
|
+
created_at: issue.createdAt,
|
|
64
|
+
updated_at: issue.updatedAt,
|
|
65
|
+
author: { username },
|
|
66
|
+
assignees: (issue.assignees ?? []).map((username) => ({ username })),
|
|
67
|
+
labels: issue.labels
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function attachGitLabIssueReconciler(gitlab, context) {
|
|
73
|
+
if (!context.runtime || !gitlab.intake.resolveIntakeDispatch) return void 0;
|
|
74
|
+
const ingest = attachGitLabRules(gitlab, context);
|
|
75
|
+
if (!ingest) return void 0;
|
|
76
|
+
const boards = context.runtime.boards;
|
|
77
|
+
const reconciledMetadata = async (item, issue, sourceId) => {
|
|
78
|
+
const identity = identityForItem(item);
|
|
79
|
+
return {
|
|
80
|
+
...identity && {
|
|
81
|
+
gitlabHost: identity.host,
|
|
82
|
+
gitlabProjectId: identity.projectId,
|
|
83
|
+
gitlabIssueIid: identity.issueIid
|
|
84
|
+
},
|
|
85
|
+
identifier: issue.identifier,
|
|
86
|
+
state: issue.state,
|
|
87
|
+
stateType: issue.stateType,
|
|
88
|
+
author: issue.author,
|
|
89
|
+
authorTrusted: issue.authorUsername ? await gitlab.isProjectMemberTrustedForSource(sourceId, issue.authorUsername) : false,
|
|
90
|
+
assignee: issue.assignee,
|
|
91
|
+
assignees: issue.assignees ?? [],
|
|
92
|
+
labels: issue.labels,
|
|
93
|
+
labelColors: issue.labelColors ?? {},
|
|
94
|
+
updatedAt: issue.updatedAt
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
return createIssueReconciler({
|
|
98
|
+
integrationId: "gitlab",
|
|
99
|
+
intake: gitlab.intake,
|
|
100
|
+
projects: context.storage.projects,
|
|
101
|
+
storage: context.runtime.workItems,
|
|
102
|
+
isTerminal: (item) => workItemPhaseSemantics(boards, item)?.kind === "terminal",
|
|
103
|
+
issueId: (item) => {
|
|
104
|
+
const issueIid = identityForItem(item)?.issueIid;
|
|
105
|
+
return issueIid ? String(issueIid) : void 0;
|
|
106
|
+
},
|
|
107
|
+
metadata: (item, issue, dispatch) => {
|
|
108
|
+
if (!dispatch.sourceId) throw new Error("GitLab reconciliation did not resolve a source identity.");
|
|
109
|
+
return reconciledMetadata(item, issue, dispatch.sourceId);
|
|
110
|
+
},
|
|
111
|
+
onClosed: async (item, issue, _project, dispatch) => {
|
|
112
|
+
if (!dispatch.sourceId) throw new Error("GitLab reconciliation did not resolve a source identity.");
|
|
113
|
+
const identity = identityForItem(item);
|
|
114
|
+
if (!identity) throw new Error("GitLab issue work item is missing canonical reconciliation metadata.");
|
|
115
|
+
await ingest(closedIssueEvent(issue, identity));
|
|
116
|
+
return reconciledMetadata(item, issue, dispatch.sourceId);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
//#endregion
|
|
121
|
+
export { attachGitLabIssueReconciler };
|
|
122
|
+
|
|
123
|
+
//# sourceMappingURL=issue-reconciler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issue-reconciler.js","names":[],"sources":["../../../src/integrations/gitlab/issue-reconciler.ts"],"sourcesContent":["import { workItemPhaseSemantics } from '../../boards/index.js';\nimport type { IntakeIssue } from '../../capabilities/intake.js';\nimport type { WorkItemRow } from '../../storage/domains/work-items/base.js';\nimport type { IntegrationContext } from '../base.js';\nimport { createIssueReconciler } from '../issue-reconciler.js';\nimport type { IssueReconciler } from '../issue-reconciler.js';\nimport { decodeIssueReference } from './integration.js';\nimport type { GitLabIntegrationBase } from './integration.js';\nimport { attachGitLabRules } from './rules.js';\nimport type { ParsedGitLabWebhook } from './webhook.js';\n\nexport type GitLabIssueReconciler = IssueReconciler;\n\nfunction stringMetadata(item: WorkItemRow, key: string): string | undefined {\n const value = item.metadata?.[key];\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nfunction numberMetadata(item: WorkItemRow, key: string): number | undefined {\n const value = item.metadata?.[key];\n return typeof value === 'number' && Number.isSafeInteger(value) && value > 0 ? value : undefined;\n}\n\nfunction normalizeHost(host: string): string {\n return host.trim().toLowerCase().replace(/\\.$/, '');\n}\n\nfunction identityForItem(item: WorkItemRow): { projectId: number; issueIid: number; host: string } | null {\n const reference = decodeIssueReference(item.externalSource?.externalId ?? '');\n const projectId = Number(reference?.projectId);\n if (!reference || !Number.isSafeInteger(projectId) || projectId <= 0) return null;\n let urlHost: string;\n try {\n urlHost = new URL(item.externalSource?.url ?? '').host;\n } catch {\n return null;\n }\n const host = normalizeHost(reference.host ?? urlHost);\n if (!host || host !== normalizeHost(urlHost)) return null;\n const metadataProjectId = numberMetadata(item, 'gitlabProjectId');\n const metadataIssueIid = numberMetadata(item, 'gitlabIssueIid');\n const metadataHost = stringMetadata(item, 'gitlabHost');\n if (\n (metadataProjectId && metadataProjectId !== projectId) ||\n (metadataIssueIid && metadataIssueIid !== reference.issueIid) ||\n (metadataHost && normalizeHost(metadataHost) !== host)\n ) {\n return null;\n }\n return { projectId, issueIid: reference.issueIid, host };\n}\n\nfunction closedIssueEvent(\n issue: IntakeIssue,\n identity: { projectId: number; issueIid: number; host: string },\n): ParsedGitLabWebhook {\n const { projectId, issueIid, host } = identity;\n const projectPath = issue.source?.trim();\n if (!projectPath) {\n throw new Error('GitLab issue work item is missing canonical reconciliation metadata.');\n }\n const username = issue.authorUsername?.trim() || 'factory-reconciler';\n return {\n event: 'Issue Hook',\n deliveryId: `reconcile:issue:${normalizeHost(host)}:${projectId}:${issueIid}:${issue.updatedAt}:closed`,\n instanceHost: host,\n payload: {\n user_username: username,\n user: { username },\n project: {\n id: projectId,\n path_with_namespace: projectPath,\n web_url: `https://${host}/${projectPath}`,\n },\n object_attributes: {\n iid: issueIid,\n title: issue.title,\n url: issue.url,\n state: 'closed',\n action: 'close',\n created_at: issue.createdAt,\n updated_at: issue.updatedAt,\n author: { username },\n assignees: (issue.assignees ?? []).map(username => ({ username })),\n labels: issue.labels,\n },\n },\n };\n}\n\nexport function attachGitLabIssueReconciler(\n gitlab: Pick<\n GitLabIntegrationBase,\n 'intake' | 'rules' | 'getProjectMemberAccessLevel' | 'getWorkItemAuthorUsername' | 'isProjectMemberTrustedForSource'\n >,\n context: IntegrationContext,\n): GitLabIssueReconciler | undefined {\n if (!context.runtime || !gitlab.intake.resolveIntakeDispatch) return undefined;\n const ingest = attachGitLabRules(gitlab, context);\n if (!ingest) return undefined;\n const boards = context.runtime.boards;\n const reconciledMetadata = async (item: WorkItemRow, issue: IntakeIssue, sourceId: string) => {\n const identity = identityForItem(item);\n return {\n ...(identity && {\n gitlabHost: identity.host,\n gitlabProjectId: identity.projectId,\n gitlabIssueIid: identity.issueIid,\n }),\n identifier: issue.identifier,\n state: issue.state,\n stateType: issue.stateType,\n author: issue.author,\n authorTrusted: issue.authorUsername\n ? await gitlab.isProjectMemberTrustedForSource(sourceId, issue.authorUsername)\n : false,\n assignee: issue.assignee,\n assignees: issue.assignees ?? [],\n labels: issue.labels,\n labelColors: issue.labelColors ?? {},\n updatedAt: issue.updatedAt,\n };\n };\n\n return createIssueReconciler({\n integrationId: 'gitlab',\n intake: gitlab.intake,\n projects: context.storage.projects,\n storage: context.runtime.workItems,\n isTerminal: item => workItemPhaseSemantics(boards, item)?.kind === 'terminal',\n issueId: item => {\n const issueIid = identityForItem(item)?.issueIid;\n return issueIid ? String(issueIid) : undefined;\n },\n metadata: (item, issue, dispatch) => {\n if (!dispatch.sourceId) throw new Error('GitLab reconciliation did not resolve a source identity.');\n return reconciledMetadata(item, issue, dispatch.sourceId);\n },\n onClosed: async (item, issue, _project, dispatch) => {\n if (!dispatch.sourceId) throw new Error('GitLab reconciliation did not resolve a source identity.');\n const identity = identityForItem(item);\n if (!identity) throw new Error('GitLab issue work item is missing canonical reconciliation metadata.');\n await ingest(closedIssueEvent(issue, identity));\n return reconciledMetadata(item, issue, dispatch.sourceId);\n },\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { IntegrationContext } from '../base.js';
|
|
2
|
+
import type { IssueReconcileSummary } from '../issue-reconciler.js';
|
|
3
|
+
import type { GitLabIntegrationBase } from './integration.js';
|
|
4
|
+
export type GitLabMergeRequestReconciler = () => Promise<IssueReconcileSummary>;
|
|
5
|
+
export declare function attachGitLabMergeRequestReconciler(gitlab: Pick<GitLabIntegrationBase, 'versionControl' | 'rules' | 'getProjectMemberAccessLevel' | 'getWorkItemAuthorUsername' | 'resolveActiveConnectionForHost'> & Partial<Pick<GitLabIntegrationBase, 'integrationStorage'>>, context: IntegrationContext): GitLabMergeRequestReconciler | undefined;
|
|
6
|
+
//# sourceMappingURL=merge-request-reconciler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge-request-reconciler.d.ts","sourceRoot":"","sources":["../../../src/integrations/gitlab/merge-request-reconciler.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAEpE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAuI9D,MAAM,MAAM,4BAA4B,GAAG,MAAM,OAAO,CAAC,qBAAqB,CAAC,CAAC;AAahF,wBAAgB,kCAAkC,CAChD,MAAM,EAAE,IAAI,CACV,qBAAqB,EACnB,gBAAgB,GAChB,OAAO,GACP,6BAA6B,GAC7B,2BAA2B,GAC3B,gCAAgC,CACnC,GACC,OAAO,CAAC,IAAI,CAAC,qBAAqB,EAAE,oBAAoB,CAAC,CAAC,EAC5D,OAAO,EAAE,kBAAkB,GAC1B,4BAA4B,GAAG,SAAS,CAqL1C"}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { workItemPhaseSemantics } from "../../boards/semantics.js";
|
|
2
|
+
import "../../boards/index.js";
|
|
3
|
+
import { FACTORY_PULL_REQUEST_RECONCILIATION_KEY } from "../../storage/domains/work-items/base.js";
|
|
4
|
+
import { attachGitLabRules } from "./rules.js";
|
|
5
|
+
import { retireMergeRequestSubscriptions } from "./subscriptions.js";
|
|
6
|
+
import { decodeMergeRequestReference, gitlabConnection } from "./integration.js";
|
|
7
|
+
//#region src/integrations/gitlab/merge-request-reconciler.ts
|
|
8
|
+
function stringMetadata(item, key) {
|
|
9
|
+
const value = item.metadata?.[key];
|
|
10
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
11
|
+
}
|
|
12
|
+
function numberMetadata(item, key) {
|
|
13
|
+
const value = item.metadata?.[key];
|
|
14
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : void 0;
|
|
15
|
+
}
|
|
16
|
+
function normalizeHost(host) {
|
|
17
|
+
return host.trim().toLowerCase().replace(/\.$/, "");
|
|
18
|
+
}
|
|
19
|
+
/** The provider outcome a card's metadata already records, if it is closed. */
|
|
20
|
+
function reconciledOutcome(metadata) {
|
|
21
|
+
if (metadata.state !== "closed" || typeof metadata.merged !== "boolean") return void 0;
|
|
22
|
+
return metadata.merged ? "merged" : "closed";
|
|
23
|
+
}
|
|
24
|
+
function identityForItem(item) {
|
|
25
|
+
const reference = decodeMergeRequestReference(item.externalSource?.externalId ?? "");
|
|
26
|
+
if (!reference) return null;
|
|
27
|
+
const metadataProjectId = numberMetadata(item, "gitlabProjectId");
|
|
28
|
+
const metadataMergeRequestIid = numberMetadata(item, "gitlabMergeRequestIid");
|
|
29
|
+
const metadataHost = stringMetadata(item, "gitlabHost");
|
|
30
|
+
if (metadataProjectId && metadataProjectId !== reference.projectId || metadataMergeRequestIid && metadataMergeRequestIid !== reference.mergeRequestIid || metadataHost && normalizeHost(metadataHost) !== normalizeHost(reference.host)) return null;
|
|
31
|
+
return {
|
|
32
|
+
projectId: reference.projectId,
|
|
33
|
+
mergeRequestIid: reference.mergeRequestIid,
|
|
34
|
+
host: normalizeHost(reference.host)
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function projectPathFromUrl(url, host, mergeRequestIid) {
|
|
38
|
+
try {
|
|
39
|
+
const parsed = new URL(url);
|
|
40
|
+
if (parsed.host.toLowerCase() !== host.toLowerCase()) return void 0;
|
|
41
|
+
const suffix = `/-/merge_requests/${mergeRequestIid}`;
|
|
42
|
+
if (!parsed.pathname.endsWith(suffix)) return void 0;
|
|
43
|
+
return decodeURIComponent(parsed.pathname.slice(1, -suffix.length));
|
|
44
|
+
} catch {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function terminalEvent(item, pullRequest, identity) {
|
|
49
|
+
const { projectId, mergeRequestIid, host } = identity;
|
|
50
|
+
const projectPath = item.externalSource?.url ? projectPathFromUrl(item.externalSource.url, host, mergeRequestIid) : void 0;
|
|
51
|
+
if (!projectPath) throw new Error("GitLab merge-request work item is missing canonical reconciliation metadata.");
|
|
52
|
+
const action = pullRequest.merged ? "merge" : "close";
|
|
53
|
+
const username = pullRequest.author?.trim() || "factory-reconciler";
|
|
54
|
+
return {
|
|
55
|
+
event: "Merge Request Hook",
|
|
56
|
+
deliveryId: `reconcile:merge-request:${normalizeHost(host)}:${projectId}:${mergeRequestIid}:${pullRequest.updatedAt}:${action}`,
|
|
57
|
+
instanceHost: host,
|
|
58
|
+
payload: {
|
|
59
|
+
user_username: username,
|
|
60
|
+
user: { username },
|
|
61
|
+
project: {
|
|
62
|
+
id: projectId,
|
|
63
|
+
path_with_namespace: projectPath,
|
|
64
|
+
web_url: `https://${host}/${projectPath}`
|
|
65
|
+
},
|
|
66
|
+
object_attributes: {
|
|
67
|
+
iid: mergeRequestIid,
|
|
68
|
+
action,
|
|
69
|
+
state: pullRequest.merged ? "merged" : "closed",
|
|
70
|
+
title: pullRequest.title,
|
|
71
|
+
url: pullRequest.url,
|
|
72
|
+
created_at: pullRequest.createdAt,
|
|
73
|
+
updated_at: pullRequest.updatedAt,
|
|
74
|
+
source_branch: pullRequest.headBranch,
|
|
75
|
+
target_branch: pullRequest.baseBranch,
|
|
76
|
+
draft: pullRequest.draft,
|
|
77
|
+
author: { username },
|
|
78
|
+
assignees: (pullRequest.assignees ?? []).map((username) => ({ username })),
|
|
79
|
+
reviewers: (pullRequest.requestedReviewers ?? []).map((username) => ({ username })),
|
|
80
|
+
labels: pullRequest.labels ?? []
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
async function connectionForItem(gitlab, context, project, projectId, host) {
|
|
86
|
+
const keys = (await context.storage.sourceControl.projectRepositories.listConfiguredExternalKeys()).filter((key) => key.repositoryExternalId === projectId);
|
|
87
|
+
for (const key of keys.sort((left, right) => {
|
|
88
|
+
return Number(right.installationExternalId === "direct") - Number(left.installationExternalId === "direct") || left.installationExternalId.localeCompare(right.installationExternalId);
|
|
89
|
+
})) {
|
|
90
|
+
if (!(await context.storage.sourceControl.projectRepositories.listByExternalRepository(key)).some((target) => target.orgId === project.orgId && target.factoryProjectId === project.id)) continue;
|
|
91
|
+
const installationHost = (await context.storage.sourceControl.installations.findByExternalId({
|
|
92
|
+
orgId: project.orgId,
|
|
93
|
+
externalId: key.installationExternalId
|
|
94
|
+
}))?.providerMetadata.host;
|
|
95
|
+
if (typeof installationHost === "string" && installationHost.trim().toLowerCase().replace(/\.$/, "") === host.trim().toLowerCase().replace(/\.$/, "")) return gitlab.resolveActiveConnectionForHost(key.installationExternalId, host);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** The integration storage handle when the host bound one; tests may omit it. */
|
|
99
|
+
function subscriptionStorage(gitlab) {
|
|
100
|
+
try {
|
|
101
|
+
return gitlab.integrationStorage;
|
|
102
|
+
} catch {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function attachGitLabMergeRequestReconciler(gitlab, context) {
|
|
107
|
+
if (!context.runtime) return void 0;
|
|
108
|
+
const ingest = attachGitLabRules(gitlab, context);
|
|
109
|
+
if (!ingest) return void 0;
|
|
110
|
+
const boards = context.runtime.boards;
|
|
111
|
+
return async () => {
|
|
112
|
+
const summary = {
|
|
113
|
+
projects: 0,
|
|
114
|
+
checked: 0,
|
|
115
|
+
updated: 0,
|
|
116
|
+
closed: 0,
|
|
117
|
+
missing: 0,
|
|
118
|
+
failed: 0,
|
|
119
|
+
errors: []
|
|
120
|
+
};
|
|
121
|
+
for (const project of await context.storage.projects.listAll()) {
|
|
122
|
+
const items = (await context.runtime.workItems.list({
|
|
123
|
+
orgId: project.orgId,
|
|
124
|
+
factoryProjectId: project.id
|
|
125
|
+
})).filter((item) => {
|
|
126
|
+
if (item.externalSource?.integrationId !== "gitlab" || item.externalSource.type !== "pull-request") return false;
|
|
127
|
+
if (workItemPhaseSemantics(boards, item)?.kind !== "terminal") return true;
|
|
128
|
+
const metadata = item.metadata ?? {};
|
|
129
|
+
const outcome = reconciledOutcome(metadata);
|
|
130
|
+
return outcome === void 0 || metadata["factoryPullRequestReconciliation"] !== outcome;
|
|
131
|
+
});
|
|
132
|
+
if (items.length === 0) continue;
|
|
133
|
+
summary.projects += 1;
|
|
134
|
+
for (const item of items) {
|
|
135
|
+
summary.checked += 1;
|
|
136
|
+
try {
|
|
137
|
+
const identity = identityForItem(item);
|
|
138
|
+
if (!identity) {
|
|
139
|
+
summary.missing += 1;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const { projectId, mergeRequestIid, host } = identity;
|
|
143
|
+
const connectionId = await connectionForItem(gitlab, context, project, String(projectId), host);
|
|
144
|
+
if (!connectionId) {
|
|
145
|
+
summary.missing += 1;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const pullRequest = await gitlab.versionControl.getPullRequest({
|
|
149
|
+
connection: gitlabConnection(connectionId),
|
|
150
|
+
sourceId: String(projectId),
|
|
151
|
+
pullRequestId: String(mergeRequestIid)
|
|
152
|
+
});
|
|
153
|
+
if (!pullRequest) {
|
|
154
|
+
summary.missing += 1;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const current = item.metadata ?? {};
|
|
158
|
+
let authorTrusted;
|
|
159
|
+
if (pullRequest.author) try {
|
|
160
|
+
authorTrusted = (await gitlab.getProjectMemberAccessLevel(connectionId, String(projectId), pullRequest.author) ?? 0) >= 30;
|
|
161
|
+
} catch (error) {
|
|
162
|
+
summary.failed += 1;
|
|
163
|
+
summary.errors.push({
|
|
164
|
+
projectId: project.id,
|
|
165
|
+
workItemId: item.id,
|
|
166
|
+
error: `Unable to refresh GitLab author trust: ${error instanceof Error ? error.message : String(error)}`
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
const desired = {
|
|
170
|
+
gitlabHost: host,
|
|
171
|
+
gitlabProjectId: projectId,
|
|
172
|
+
gitlabMergeRequestIid: mergeRequestIid,
|
|
173
|
+
state: pullRequest.state,
|
|
174
|
+
draft: pullRequest.draft,
|
|
175
|
+
merged: pullRequest.merged,
|
|
176
|
+
assignees: pullRequest.assignees ?? [],
|
|
177
|
+
requestedReviewers: pullRequest.requestedReviewers ?? [],
|
|
178
|
+
labels: pullRequest.labels ?? [],
|
|
179
|
+
headBranch: pullRequest.headBranch,
|
|
180
|
+
baseBranch: pullRequest.baseBranch,
|
|
181
|
+
...pullRequest.author ? { author: pullRequest.author } : {},
|
|
182
|
+
...authorTrusted !== void 0 ? { authorTrusted } : {},
|
|
183
|
+
updatedAt: pullRequest.updatedAt
|
|
184
|
+
};
|
|
185
|
+
const metadataChanged = (next) => !Object.entries(next).every(([key, value]) => JSON.stringify(current[key]) === JSON.stringify(value));
|
|
186
|
+
if (pullRequest.state === "closed") {
|
|
187
|
+
const terminal = workItemPhaseSemantics(boards, item)?.kind === "terminal";
|
|
188
|
+
let cleanupFailed = false;
|
|
189
|
+
if (terminal) try {
|
|
190
|
+
await context.runtime.workItems.supersedeDecisionsForWorkItem({
|
|
191
|
+
orgId: project.orgId,
|
|
192
|
+
factoryProjectId: project.id,
|
|
193
|
+
workItemId: item.id,
|
|
194
|
+
supersededAt: /* @__PURE__ */ new Date()
|
|
195
|
+
});
|
|
196
|
+
} catch (error) {
|
|
197
|
+
cleanupFailed = true;
|
|
198
|
+
summary.failed += 1;
|
|
199
|
+
summary.errors.push({
|
|
200
|
+
projectId: project.id,
|
|
201
|
+
workItemId: item.id,
|
|
202
|
+
error: error instanceof Error ? error.message : String(error)
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
if (current.state !== "closed" || current.merged !== pullRequest.merged || !item.stages.includes(pullRequest.merged ? "done" : "canceled")) {
|
|
206
|
+
await ingest(terminalEvent(item, pullRequest, identity));
|
|
207
|
+
const subscriptions = subscriptionStorage(gitlab);
|
|
208
|
+
if (subscriptions) await retireMergeRequestSubscriptions({
|
|
209
|
+
host,
|
|
210
|
+
projectId: String(projectId),
|
|
211
|
+
changeRequestId: String(mergeRequestIid),
|
|
212
|
+
merged: pullRequest.merged
|
|
213
|
+
}, subscriptions);
|
|
214
|
+
}
|
|
215
|
+
const settled = cleanupFailed ? {} : { [FACTORY_PULL_REQUEST_RECONCILIATION_KEY]: pullRequest.merged ? "merged" : "closed" };
|
|
216
|
+
if (metadataChanged({
|
|
217
|
+
...desired,
|
|
218
|
+
...settled
|
|
219
|
+
})) {
|
|
220
|
+
await context.runtime.workItems.update({
|
|
221
|
+
orgId: project.orgId,
|
|
222
|
+
id: item.id,
|
|
223
|
+
userId: "factory-rule-dispatcher",
|
|
224
|
+
patch: { metadata: {
|
|
225
|
+
...current,
|
|
226
|
+
...desired,
|
|
227
|
+
...settled
|
|
228
|
+
} }
|
|
229
|
+
});
|
|
230
|
+
summary.updated += 1;
|
|
231
|
+
}
|
|
232
|
+
summary.closed += 1;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
const reopened = current["factoryPullRequestReconciliation"] === void 0 ? {} : { [FACTORY_PULL_REQUEST_RECONCILIATION_KEY]: null };
|
|
236
|
+
if (!metadataChanged({
|
|
237
|
+
...desired,
|
|
238
|
+
...reopened
|
|
239
|
+
})) continue;
|
|
240
|
+
await context.runtime.workItems.update({
|
|
241
|
+
orgId: project.orgId,
|
|
242
|
+
id: item.id,
|
|
243
|
+
userId: "factory-rule-dispatcher",
|
|
244
|
+
patch: { metadata: {
|
|
245
|
+
...current,
|
|
246
|
+
...desired,
|
|
247
|
+
...reopened
|
|
248
|
+
} }
|
|
249
|
+
});
|
|
250
|
+
summary.updated += 1;
|
|
251
|
+
} catch (error) {
|
|
252
|
+
summary.failed += 1;
|
|
253
|
+
summary.errors.push({
|
|
254
|
+
projectId: project.id,
|
|
255
|
+
workItemId: item.id,
|
|
256
|
+
error: error instanceof Error ? error.message : String(error)
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return summary;
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
//#endregion
|
|
265
|
+
export { attachGitLabMergeRequestReconciler };
|
|
266
|
+
|
|
267
|
+
//# sourceMappingURL=merge-request-reconciler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge-request-reconciler.js","names":[],"sources":["../../../src/integrations/gitlab/merge-request-reconciler.ts"],"sourcesContent":["import { workItemPhaseSemantics } from '../../boards/index.js';\nimport type { PullRequest } from '../../capabilities/version-control.js';\nimport type { FactoryProject } from '../../storage/domains/projects/base.js';\nimport { FACTORY_PULL_REQUEST_RECONCILIATION_KEY } from '../../storage/domains/work-items/base.js';\nimport type { WorkItemRow } from '../../storage/domains/work-items/base.js';\nimport type { IntegrationContext } from '../base.js';\nimport type { IssueReconcileSummary } from '../issue-reconciler.js';\nimport { decodeMergeRequestReference, gitlabConnection, GITLAB_TRUSTED_ACCESS_LEVEL } from './integration.js';\nimport type { GitLabIntegrationBase } from './integration.js';\nimport { attachGitLabRules } from './rules.js';\nimport { retireMergeRequestSubscriptions } from './subscriptions.js';\nimport type { ParsedGitLabWebhook } from './webhook.js';\n\nfunction stringMetadata(item: WorkItemRow, key: string): string | undefined {\n const value = item.metadata?.[key];\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nfunction numberMetadata(item: WorkItemRow, key: string): number | undefined {\n const value = item.metadata?.[key];\n return typeof value === 'number' && Number.isSafeInteger(value) && value > 0 ? value : undefined;\n}\n\nfunction normalizeHost(host: string): string {\n return host.trim().toLowerCase().replace(/\\.$/, '');\n}\n\n/** The provider outcome a card's metadata already records, if it is closed. */\nfunction reconciledOutcome(metadata: Record<string, unknown>): 'merged' | 'closed' | undefined {\n if (metadata.state !== 'closed' || typeof metadata.merged !== 'boolean') return undefined;\n return metadata.merged ? 'merged' : 'closed';\n}\n\nfunction identityForItem(item: WorkItemRow): { projectId: number; mergeRequestIid: number; host: string } | null {\n const reference = decodeMergeRequestReference(item.externalSource?.externalId ?? '');\n if (!reference) return null;\n const metadataProjectId = numberMetadata(item, 'gitlabProjectId');\n const metadataMergeRequestIid = numberMetadata(item, 'gitlabMergeRequestIid');\n const metadataHost = stringMetadata(item, 'gitlabHost');\n if (\n (metadataProjectId && metadataProjectId !== reference.projectId) ||\n (metadataMergeRequestIid && metadataMergeRequestIid !== reference.mergeRequestIid) ||\n (metadataHost && normalizeHost(metadataHost) !== normalizeHost(reference.host))\n ) {\n return null;\n }\n return {\n projectId: reference.projectId,\n mergeRequestIid: reference.mergeRequestIid,\n host: normalizeHost(reference.host),\n };\n}\n\nfunction projectPathFromUrl(url: string, host: string, mergeRequestIid: number): string | undefined {\n try {\n const parsed = new URL(url);\n if (parsed.host.toLowerCase() !== host.toLowerCase()) return undefined;\n const suffix = `/-/merge_requests/${mergeRequestIid}`;\n if (!parsed.pathname.endsWith(suffix)) return undefined;\n return decodeURIComponent(parsed.pathname.slice(1, -suffix.length));\n } catch {\n return undefined;\n }\n}\n\nfunction terminalEvent(\n item: WorkItemRow,\n pullRequest: PullRequest,\n identity: { projectId: number; mergeRequestIid: number; host: string },\n): ParsedGitLabWebhook {\n const { projectId, mergeRequestIid, host } = identity;\n const projectPath = item.externalSource?.url\n ? projectPathFromUrl(item.externalSource.url, host, mergeRequestIid)\n : undefined;\n if (!projectPath) {\n throw new Error('GitLab merge-request work item is missing canonical reconciliation metadata.');\n }\n const action = pullRequest.merged ? 'merge' : 'close';\n const username = pullRequest.author?.trim() || 'factory-reconciler';\n return {\n event: 'Merge Request Hook',\n deliveryId: `reconcile:merge-request:${normalizeHost(host)}:${projectId}:${mergeRequestIid}:${pullRequest.updatedAt}:${action}`,\n instanceHost: host,\n payload: {\n user_username: username,\n user: { username },\n project: {\n id: projectId,\n path_with_namespace: projectPath,\n web_url: `https://${host}/${projectPath}`,\n },\n object_attributes: {\n iid: mergeRequestIid,\n action,\n state: pullRequest.merged ? 'merged' : 'closed',\n title: pullRequest.title,\n url: pullRequest.url,\n created_at: pullRequest.createdAt,\n updated_at: pullRequest.updatedAt,\n source_branch: pullRequest.headBranch,\n target_branch: pullRequest.baseBranch,\n draft: pullRequest.draft,\n author: { username },\n assignees: (pullRequest.assignees ?? []).map(username => ({ username })),\n reviewers: (pullRequest.requestedReviewers ?? []).map(username => ({ username })),\n labels: pullRequest.labels ?? [],\n },\n },\n };\n}\n\nasync function connectionForItem(\n gitlab: Pick<GitLabIntegrationBase, 'resolveActiveConnectionForHost'>,\n context: IntegrationContext,\n project: FactoryProject,\n projectId: string,\n host: string,\n): Promise<string | undefined> {\n const keys = (await context.storage.sourceControl.projectRepositories.listConfiguredExternalKeys()).filter(\n key => key.repositoryExternalId === projectId,\n );\n for (const key of keys.sort((left, right) => {\n const directOrder =\n Number(right.installationExternalId === 'direct') - Number(left.installationExternalId === 'direct');\n return directOrder || left.installationExternalId.localeCompare(right.installationExternalId);\n })) {\n const targets = await context.storage.sourceControl.projectRepositories.listByExternalRepository(key);\n if (!targets.some(target => target.orgId === project.orgId && target.factoryProjectId === project.id)) continue;\n const installation = await context.storage.sourceControl.installations.findByExternalId({\n orgId: project.orgId,\n externalId: key.installationExternalId,\n });\n const installationHost = installation?.providerMetadata.host;\n if (\n typeof installationHost === 'string' &&\n installationHost.trim().toLowerCase().replace(/\\.$/, '') === host.trim().toLowerCase().replace(/\\.$/, '')\n ) {\n return gitlab.resolveActiveConnectionForHost(key.installationExternalId, host);\n }\n }\n return undefined;\n}\n\nexport type GitLabMergeRequestReconciler = () => Promise<IssueReconcileSummary>;\n\n/** The integration storage handle when the host bound one; tests may omit it. */\nfunction subscriptionStorage(\n gitlab: Partial<Pick<GitLabIntegrationBase, 'integrationStorage'>>,\n): GitLabIntegrationBase['integrationStorage'] | undefined {\n try {\n return gitlab.integrationStorage;\n } catch {\n return undefined;\n }\n}\n\nexport function attachGitLabMergeRequestReconciler(\n gitlab: Pick<\n GitLabIntegrationBase,\n | 'versionControl'\n | 'rules'\n | 'getProjectMemberAccessLevel'\n | 'getWorkItemAuthorUsername'\n | 'resolveActiveConnectionForHost'\n > &\n Partial<Pick<GitLabIntegrationBase, 'integrationStorage'>>,\n context: IntegrationContext,\n): GitLabMergeRequestReconciler | undefined {\n if (!context.runtime) return undefined;\n const ingest = attachGitLabRules(gitlab, context);\n if (!ingest) return undefined;\n const boards = context.runtime.boards;\n\n return async () => {\n const summary: IssueReconcileSummary = {\n projects: 0,\n checked: 0,\n updated: 0,\n closed: 0,\n missing: 0,\n failed: 0,\n errors: [],\n };\n for (const project of await context.storage.projects.listAll()) {\n const items = (\n await context.runtime!.workItems.list({ orgId: project.orgId, factoryProjectId: project.id })\n ).filter(item => {\n if (item.externalSource?.integrationId !== 'gitlab' || item.externalSource.type !== 'pull-request') {\n return false;\n }\n if (workItemPhaseSemantics(boards, item)?.kind !== 'terminal') return true;\n // Same contract as the GitHub sweep: a terminal card is settled once\n // the provider's terminal outcome has been replayed and stamped. Done\n // means the review finished, not that the MR merged, so a later close\n // still gets exactly one replay before the card drops out of the sweep.\n const metadata = item.metadata ?? {};\n const outcome = reconciledOutcome(metadata);\n return outcome === undefined || metadata[FACTORY_PULL_REQUEST_RECONCILIATION_KEY] !== outcome;\n });\n if (items.length === 0) continue;\n summary.projects += 1;\n for (const item of items) {\n summary.checked += 1;\n try {\n const identity = identityForItem(item);\n if (!identity) {\n summary.missing += 1;\n continue;\n }\n const { projectId, mergeRequestIid, host } = identity;\n const connectionId = await connectionForItem(gitlab, context, project, String(projectId), host);\n if (!connectionId) {\n summary.missing += 1;\n continue;\n }\n const pullRequest = await gitlab.versionControl.getPullRequest({\n connection: gitlabConnection(connectionId),\n sourceId: String(projectId),\n pullRequestId: String(mergeRequestIid),\n });\n if (!pullRequest) {\n summary.missing += 1;\n continue;\n }\n const current = item.metadata ?? {};\n let authorTrusted: boolean | undefined;\n if (pullRequest.author) {\n try {\n const accessLevel = await gitlab.getProjectMemberAccessLevel(\n connectionId,\n String(projectId),\n pullRequest.author,\n );\n authorTrusted = (accessLevel ?? 0) >= GITLAB_TRUSTED_ACCESS_LEVEL;\n } catch (error) {\n summary.failed += 1;\n summary.errors.push({\n projectId: project.id,\n workItemId: item.id,\n error: `Unable to refresh GitLab author trust: ${error instanceof Error ? error.message : String(error)}`,\n });\n }\n }\n const desired = {\n gitlabHost: host,\n gitlabProjectId: projectId,\n gitlabMergeRequestIid: mergeRequestIid,\n state: pullRequest.state,\n draft: pullRequest.draft,\n merged: pullRequest.merged,\n assignees: pullRequest.assignees ?? [],\n requestedReviewers: pullRequest.requestedReviewers ?? [],\n labels: pullRequest.labels ?? [],\n headBranch: pullRequest.headBranch,\n baseBranch: pullRequest.baseBranch,\n ...(pullRequest.author ? { author: pullRequest.author } : {}),\n ...(authorTrusted !== undefined ? { authorTrusted } : {}),\n updatedAt: pullRequest.updatedAt,\n };\n const metadataChanged = (next: Record<string, unknown>) =>\n !Object.entries(next).every(([key, value]) => JSON.stringify(current[key]) === JSON.stringify(value));\n if (pullRequest.state === 'closed') {\n const terminal = workItemPhaseSemantics(boards, item)?.kind === 'terminal';\n let cleanupFailed = false;\n if (terminal) {\n // Retire whatever the card still had pending, as the GitHub sweep\n // does, so a stale proposal cannot resurface on a settled card.\n try {\n await context.runtime!.workItems.supersedeDecisionsForWorkItem({\n orgId: project.orgId,\n factoryProjectId: project.id,\n workItemId: item.id,\n supersededAt: new Date(),\n });\n } catch (error) {\n cleanupFailed = true;\n summary.failed += 1;\n summary.errors.push({\n projectId: project.id,\n workItemId: item.id,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n const terminalTransition =\n current.state !== 'closed' ||\n current.merged !== pullRequest.merged ||\n !item.stages.includes(pullRequest.merged ? 'done' : 'canceled');\n if (terminalTransition) {\n await ingest(terminalEvent(item, pullRequest, identity));\n // The webhook path retires thread subscriptions itself; a missed\n // terminal event must not leave the session's MR chip open.\n const subscriptions = subscriptionStorage(gitlab);\n if (subscriptions) {\n await retireMergeRequestSubscriptions(\n {\n host,\n projectId: String(projectId),\n changeRequestId: String(mergeRequestIid),\n merged: pullRequest.merged,\n },\n subscriptions,\n );\n }\n }\n // The settled stamp is what takes a terminal card out of later\n // sweeps. Withhold it while cleanup failed so the next sweep retries.\n const settled = cleanupFailed\n ? {}\n : { [FACTORY_PULL_REQUEST_RECONCILIATION_KEY]: pullRequest.merged ? 'merged' : 'closed' };\n if (metadataChanged({ ...desired, ...settled })) {\n await context.runtime!.workItems.update({\n orgId: project.orgId,\n id: item.id,\n userId: 'factory-rule-dispatcher',\n patch: { metadata: { ...current, ...desired, ...settled } },\n });\n summary.updated += 1;\n }\n summary.closed += 1;\n continue;\n }\n // A reopened MR is live again: clear a stale stamp so a later close\n // gets its replay, but never write the key onto a card that lacks it.\n const reopened =\n current[FACTORY_PULL_REQUEST_RECONCILIATION_KEY] === undefined\n ? {}\n : { [FACTORY_PULL_REQUEST_RECONCILIATION_KEY]: null };\n if (!metadataChanged({ ...desired, ...reopened })) continue;\n await context.runtime!.workItems.update({\n orgId: project.orgId,\n id: item.id,\n userId: 'factory-rule-dispatcher',\n patch: { metadata: { ...current, ...desired, ...reopened } },\n });\n summary.updated += 1;\n } catch (error) {\n summary.failed += 1;\n summary.errors.push({\n projectId: project.id,\n workItemId: item.id,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n }\n return summary;\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { IntegrationContext } from '../base.js';
|
|
2
|
+
import type { IssueReconciler } from '../issue-reconciler.js';
|
|
3
|
+
import type { GitLabIntegrationBase } from './integration.js';
|
|
4
|
+
export declare function attachGitLabReconciler(gitlab: GitLabIntegrationBase, context: IntegrationContext): IssueReconciler | undefined;
|
|
5
|
+
//# sourceMappingURL=reconciler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconciler.d.ts","sourceRoot":"","sources":["../../../src/integrations/gitlab/reconciler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,KAAK,EAAE,eAAe,EAAyB,MAAM,wBAAwB,CAAC;AACrF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAI9D,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,qBAAqB,EAC7B,OAAO,EAAE,kBAAkB,GAC1B,eAAe,GAAG,SAAS,CAmB7B"}
|