@mastra/factory 0.16.0-alpha.10 → 0.16.0-alpha.12

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.
Files changed (216) hide show
  1. package/README.md +37 -0
  2. package/dist/auth.js +1 -1
  3. package/dist/auth.js.map +1 -1
  4. package/dist/boards/review.d.ts.map +1 -1
  5. package/dist/boards/review.js +19 -8
  6. package/dist/boards/review.js.map +1 -1
  7. package/dist/boards/work.d.ts.map +1 -1
  8. package/dist/boards/work.js +31 -3
  9. package/dist/boards/work.js.map +1 -1
  10. package/dist/capabilities/intake.d.ts +4 -0
  11. package/dist/capabilities/intake.d.ts.map +1 -1
  12. package/dist/capabilities/version-control.d.ts +15 -0
  13. package/dist/capabilities/version-control.d.ts.map +1 -1
  14. package/dist/factory.d.ts +7 -7
  15. package/dist/factory.d.ts.map +1 -1
  16. package/dist/factory.js +100 -40
  17. package/dist/factory.js.map +1 -1
  18. package/dist/integrations/base.d.ts +3 -0
  19. package/dist/integrations/base.d.ts.map +1 -1
  20. package/dist/integrations/github/integration.d.ts.map +1 -1
  21. package/dist/integrations/github/integration.js +21 -0
  22. package/dist/integrations/github/integration.js.map +1 -1
  23. package/dist/integrations/github/routes.js +2 -1
  24. package/dist/integrations/github/routes.js.map +1 -1
  25. package/dist/integrations/github/sandbox.d.ts +41 -42
  26. package/dist/integrations/github/sandbox.d.ts.map +1 -1
  27. package/dist/integrations/github/sandbox.js +416 -227
  28. package/dist/integrations/github/sandbox.js.map +1 -1
  29. package/dist/integrations/github/webhook.d.ts +2 -5
  30. package/dist/integrations/github/webhook.d.ts.map +1 -1
  31. package/dist/integrations/github/webhook.js +8 -48
  32. package/dist/integrations/github/webhook.js.map +1 -1
  33. package/dist/integrations/gitlab/agent-tools.d.ts +14 -0
  34. package/dist/integrations/gitlab/agent-tools.d.ts.map +1 -0
  35. package/dist/integrations/gitlab/agent-tools.js +42 -0
  36. package/dist/integrations/gitlab/agent-tools.js.map +1 -0
  37. package/dist/integrations/gitlab/api.d.ts +222 -0
  38. package/dist/integrations/gitlab/api.d.ts.map +1 -0
  39. package/dist/integrations/gitlab/api.js +274 -0
  40. package/dist/integrations/gitlab/api.js.map +1 -0
  41. package/dist/integrations/gitlab/default-rules.d.ts +125 -0
  42. package/dist/integrations/gitlab/default-rules.d.ts.map +1 -0
  43. package/dist/integrations/gitlab/default-rules.js +172 -0
  44. package/dist/integrations/gitlab/default-rules.js.map +1 -0
  45. package/dist/integrations/gitlab/integration.d.ts +138 -0
  46. package/dist/integrations/gitlab/integration.d.ts.map +1 -0
  47. package/dist/integrations/gitlab/integration.js +705 -0
  48. package/dist/integrations/gitlab/integration.js.map +1 -0
  49. package/dist/integrations/gitlab/issue-reconciler.d.ts +6 -0
  50. package/dist/integrations/gitlab/issue-reconciler.d.ts.map +1 -0
  51. package/dist/integrations/gitlab/issue-reconciler.js +123 -0
  52. package/dist/integrations/gitlab/issue-reconciler.js.map +1 -0
  53. package/dist/integrations/gitlab/merge-request-reconciler.d.ts +6 -0
  54. package/dist/integrations/gitlab/merge-request-reconciler.d.ts.map +1 -0
  55. package/dist/integrations/gitlab/merge-request-reconciler.js +267 -0
  56. package/dist/integrations/gitlab/merge-request-reconciler.js.map +1 -0
  57. package/dist/integrations/gitlab/reconciler.d.ts +5 -0
  58. package/dist/integrations/gitlab/reconciler.d.ts.map +1 -0
  59. package/dist/integrations/gitlab/reconciler.js +31 -0
  60. package/dist/integrations/gitlab/reconciler.js.map +1 -0
  61. package/dist/integrations/gitlab/reconciliation-config.d.ts +3 -0
  62. package/dist/integrations/gitlab/reconciliation-config.d.ts.map +1 -0
  63. package/dist/integrations/gitlab/reconciliation-config.js +17 -0
  64. package/dist/integrations/gitlab/reconciliation-config.js.map +1 -0
  65. package/dist/integrations/gitlab/routes.d.ts +20 -0
  66. package/dist/integrations/gitlab/routes.d.ts.map +1 -0
  67. package/dist/integrations/gitlab/routes.js +473 -0
  68. package/dist/integrations/gitlab/routes.js.map +1 -0
  69. package/dist/integrations/gitlab/rules.d.ts +35 -0
  70. package/dist/integrations/gitlab/rules.d.ts.map +1 -0
  71. package/dist/integrations/gitlab/rules.js +391 -0
  72. package/dist/integrations/gitlab/rules.js.map +1 -0
  73. package/dist/integrations/gitlab/session-subscriptions.d.ts +32 -0
  74. package/dist/integrations/gitlab/session-subscriptions.d.ts.map +1 -0
  75. package/dist/integrations/gitlab/session-subscriptions.js +192 -0
  76. package/dist/integrations/gitlab/session-subscriptions.js.map +1 -0
  77. package/dist/integrations/gitlab/subscriptions.d.ts +70 -0
  78. package/dist/integrations/gitlab/subscriptions.d.ts.map +1 -0
  79. package/dist/integrations/gitlab/subscriptions.js +76 -0
  80. package/dist/integrations/gitlab/subscriptions.js.map +1 -0
  81. package/dist/integrations/gitlab/version-control.d.ts +16 -0
  82. package/dist/integrations/gitlab/version-control.d.ts.map +1 -0
  83. package/dist/integrations/gitlab/version-control.js +622 -0
  84. package/dist/integrations/gitlab/version-control.js.map +1 -0
  85. package/dist/integrations/gitlab/webhook-dispatch.d.ts +75 -0
  86. package/dist/integrations/gitlab/webhook-dispatch.d.ts.map +1 -0
  87. package/dist/integrations/gitlab/webhook-dispatch.js +235 -0
  88. package/dist/integrations/gitlab/webhook-dispatch.js.map +1 -0
  89. package/dist/integrations/gitlab/webhook.d.ts +72 -0
  90. package/dist/integrations/gitlab/webhook.d.ts.map +1 -0
  91. package/dist/integrations/gitlab/webhook.js +201 -0
  92. package/dist/integrations/gitlab/webhook.js.map +1 -0
  93. package/dist/integrations/incidentio/integration.js +1 -1
  94. package/dist/integrations/issue-reconciler.d.ts +3 -3
  95. package/dist/integrations/issue-reconciler.d.ts.map +1 -1
  96. package/dist/integrations/issue-reconciler.js +22 -2
  97. package/dist/integrations/issue-reconciler.js.map +1 -1
  98. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  99. package/dist/integrations/platform/github/integration.js +21 -0
  100. package/dist/integrations/platform/github/integration.js.map +1 -1
  101. package/dist/integrations/platform/gitlab/event-worker.d.ts +64 -0
  102. package/dist/integrations/platform/gitlab/event-worker.d.ts.map +1 -0
  103. package/dist/integrations/platform/gitlab/event-worker.js +306 -0
  104. package/dist/integrations/platform/gitlab/event-worker.js.map +1 -0
  105. package/dist/integrations/platform/gitlab/integration.d.ts +57 -0
  106. package/dist/integrations/platform/gitlab/integration.d.ts.map +1 -0
  107. package/dist/integrations/platform/gitlab/integration.js +125 -0
  108. package/dist/integrations/platform/gitlab/integration.js.map +1 -0
  109. package/dist/integrations/platform/incidentio/integration.js +1 -1
  110. package/dist/integrations/slack/integration.d.ts.map +1 -1
  111. package/dist/integrations/slack/integration.js +3 -3
  112. package/dist/integrations/slack/integration.js.map +1 -1
  113. package/dist/integrations/slack/slack.d.ts +2 -0
  114. package/dist/integrations/slack/slack.d.ts.map +1 -1
  115. package/dist/integrations/slack/slack.js +21 -7
  116. package/dist/integrations/slack/slack.js.map +1 -1
  117. package/dist/integrations/subscription-session.d.ts +37 -0
  118. package/dist/integrations/subscription-session.d.ts.map +1 -0
  119. package/dist/integrations/subscription-session.js +83 -0
  120. package/dist/integrations/subscription-session.js.map +1 -0
  121. package/dist/packages/_internals/workspace/dist/index.js +1 -11
  122. package/dist/packages/_internals/workspace/dist/index.js.map +1 -1
  123. package/dist/routes/intake.d.ts.map +1 -1
  124. package/dist/routes/intake.js +4 -2
  125. package/dist/routes/intake.js.map +1 -1
  126. package/dist/routes/oauth.js +1 -1
  127. package/dist/routes/projects.d.ts +1 -0
  128. package/dist/routes/projects.d.ts.map +1 -1
  129. package/dist/routes/projects.js +1 -0
  130. package/dist/routes/projects.js.map +1 -1
  131. package/dist/routes/source-control-sessions.d.ts +20 -0
  132. package/dist/routes/source-control-sessions.d.ts.map +1 -0
  133. package/dist/routes/source-control-sessions.js +319 -0
  134. package/dist/routes/source-control-sessions.js.map +1 -0
  135. package/dist/routes/source-control-settings.d.ts +10 -0
  136. package/dist/routes/source-control-settings.d.ts.map +1 -0
  137. package/dist/routes/source-control-settings.js +87 -0
  138. package/dist/routes/source-control-settings.js.map +1 -0
  139. package/dist/routes/surface.d.ts +3 -3
  140. package/dist/routes/surface.d.ts.map +1 -1
  141. package/dist/routes/surface.js +55 -13
  142. package/dist/routes/surface.js.map +1 -1
  143. package/dist/routes/tenant-credentials.d.ts +8 -0
  144. package/dist/routes/tenant-credentials.d.ts.map +1 -1
  145. package/dist/routes/tenant-credentials.js +20 -3
  146. package/dist/routes/tenant-credentials.js.map +1 -1
  147. package/dist/routes/work-items.d.ts.map +1 -1
  148. package/dist/routes/work-items.js +1 -1
  149. package/dist/routes/work-items.js.map +1 -1
  150. package/dist/rules/dispatcher.d.ts.map +1 -1
  151. package/dist/rules/dispatcher.js +7 -1
  152. package/dist/rules/dispatcher.js.map +1 -1
  153. package/dist/rules/start-coordinator.d.ts +1 -1
  154. package/dist/rules/start-coordinator.d.ts.map +1 -1
  155. package/dist/rules/start-coordinator.js +3 -2
  156. package/dist/rules/start-coordinator.js.map +1 -1
  157. package/dist/rules/transition-service.d.ts.map +1 -1
  158. package/dist/rules/transition-service.js +6 -3
  159. package/dist/rules/transition-service.js.map +1 -1
  160. package/dist/rules/types.d.ts +67 -3
  161. package/dist/rules/types.d.ts.map +1 -1
  162. package/dist/rules/types.js +23 -2
  163. package/dist/rules/types.js.map +1 -1
  164. package/dist/rules/validation.d.ts.map +1 -1
  165. package/dist/rules/validation.js +2 -0
  166. package/dist/rules/validation.js.map +1 -1
  167. package/dist/sandbox/git-ref.d.ts +7 -0
  168. package/dist/sandbox/git-ref.d.ts.map +1 -0
  169. package/dist/sandbox/git-ref.js +15 -0
  170. package/dist/sandbox/git-ref.js.map +1 -0
  171. package/dist/sandbox/session-retirement.js +1 -1
  172. package/dist/sandbox/workdir.d.ts.map +1 -1
  173. package/dist/sandbox/workdir.js +6 -6
  174. package/dist/sandbox/workdir.js.map +1 -1
  175. package/dist/session/factory-session.d.ts +31 -1
  176. package/dist/session/factory-session.d.ts.map +1 -1
  177. package/dist/session/factory-session.js +60 -1
  178. package/dist/session/factory-session.js.map +1 -1
  179. package/dist/session/source-control-tools.d.ts +16 -0
  180. package/dist/session/source-control-tools.d.ts.map +1 -0
  181. package/dist/session/source-control-tools.js +464 -0
  182. package/dist/session/source-control-tools.js.map +1 -0
  183. package/dist/storage/domains/intake/base.d.ts +18 -0
  184. package/dist/storage/domains/intake/base.d.ts.map +1 -1
  185. package/dist/storage/domains/intake/base.js +77 -0
  186. package/dist/storage/domains/intake/base.js.map +1 -1
  187. package/dist/work-item-branch.d.ts +3 -1
  188. package/dist/work-item-branch.d.ts.map +1 -1
  189. package/dist/work-item-branch.js +22 -4
  190. package/dist/work-item-branch.js.map +1 -1
  191. package/dist/workspace.d.ts +15 -1
  192. package/dist/workspace.d.ts.map +1 -1
  193. package/dist/workspace.js +57 -23
  194. package/dist/workspace.js.map +1 -1
  195. package/factory-skills/factory-gitlab-rereview/SKILL.md +19 -0
  196. package/factory-skills/factory-gitlab-review/SKILL.md +30 -0
  197. package/factory-skills/factory-rereview/SKILL.md +13 -6
  198. package/factory-skills/factory-review/SKILL.md +13 -6
  199. package/factory-skills/factory-review/references/archaeology.md +225 -0
  200. package/factory-skills/factory-review/references/categories/README.md +34 -0
  201. package/factory-skills/factory-review/references/categories/behavior-change.md +36 -0
  202. package/factory-skills/factory-review/references/categories/bug-fix.md +37 -0
  203. package/factory-skills/factory-review/references/categories/docs.md +32 -0
  204. package/factory-skills/factory-review/references/categories/experimental-flagged.md +34 -0
  205. package/factory-skills/factory-review/references/categories/infra-tooling.md +37 -0
  206. package/factory-skills/factory-review/references/categories/internal-capability.md +35 -0
  207. package/factory-skills/factory-review/references/categories/large-cross-cutting.md +35 -0
  208. package/factory-skills/factory-review/references/categories/mechanical.md +36 -0
  209. package/factory-skills/factory-review/references/categories/performance.md +33 -0
  210. package/factory-skills/factory-review/references/categories/public-api.md +38 -0
  211. package/factory-skills/factory-review/references/categories/refactor.md +35 -0
  212. package/factory-skills/factory-review/references/categories/revert.md +31 -0
  213. package/factory-skills/factory-review/references/categories/schema-storage.md +39 -0
  214. package/factory-skills/factory-review/references/categories/security.md +35 -0
  215. package/factory-skills/factory-review/references/categories/tests-only.md +38 -0
  216. package/package.json +9 -9
@@ -0,0 +1,622 @@
1
+ import { GitLabApiError } from "./api.js";
2
+ import { createHash } from "crypto";
3
+ //#region src/integrations/gitlab/version-control.ts
4
+ function buildGitLabVersionControl(deps) {
5
+ let storage;
6
+ const sourceControlStorage = () => {
7
+ if (!storage) throw new Error("GitLab VersionControl is not initialized.");
8
+ return storage;
9
+ };
10
+ const listPullRequests = async (input) => {
11
+ const context = await deps.contextForConnection(input.connection);
12
+ const page = parsePositiveCursor(input.cursor);
13
+ const state = input.state === "open" ? "opened" : input.state === "closed" ? "all" : input.state ?? "opened";
14
+ const mergeRequests = await context.api.listMergeRequests(input.sourceId, {
15
+ page,
16
+ state
17
+ });
18
+ return {
19
+ pullRequests: mergeRequests.filter((mergeRequest) => input.state !== "closed" || mergeRequest.state === "closed" || mergeRequest.state === "merged").filter((mergeRequest) => input.includeDrafts !== false || !isDraft(mergeRequest)).map(toPullRequest),
20
+ nextCursor: mergeRequests.length === 30 ? String(page + 1) : null
21
+ };
22
+ };
23
+ const getPullRequest = async (input) => {
24
+ const context = await deps.contextForConnection(input.connection);
25
+ try {
26
+ return toPullRequest(await context.api.getMergeRequest(input.sourceId, requirePositiveId(input.pullRequestId, "merge request")));
27
+ } catch (error) {
28
+ if (error instanceof GitLabApiError && error.status === 404) return null;
29
+ throw error;
30
+ }
31
+ };
32
+ const createPullRequest = async (input) => {
33
+ const context = await deps.contextForConnection(input.connection);
34
+ const title = input.draft && !/^(?:draft:|\[draft\])/i.test(input.title) ? `Draft: ${input.title}` : input.title;
35
+ return toPullRequest(await context.api.createMergeRequest(input.sourceId, {
36
+ sourceBranch: input.headBranch,
37
+ targetBranch: input.baseBranch,
38
+ title,
39
+ description: input.body
40
+ }));
41
+ };
42
+ const updatePullRequest = async (input) => {
43
+ return toPullRequest(await (await deps.contextForConnection(input.connection)).api.updateMergeRequest(input.sourceId, requirePositiveId(input.pullRequestId, "merge request"), {
44
+ title: input.title,
45
+ description: input.body === null ? "" : input.body,
46
+ targetBranch: input.baseBranch,
47
+ stateEvent: input.state === "closed" ? "close" : input.state === "open" ? "reopen" : void 0
48
+ }));
49
+ };
50
+ const closePullRequest = (input) => updatePullRequest({
51
+ ...input,
52
+ state: "closed"
53
+ });
54
+ const mergePullRequest = async (input) => {
55
+ if (input.method === "rebase") throw notSupported("GitLab rebases merge requests asynchronously; rebase-and-merge is not supported.");
56
+ const context = await deps.contextForConnection(input.connection);
57
+ const commitMessage = combinedCommitMessage(input.commitTitle, input.commitMessage);
58
+ const result = await context.api.mergeMergeRequest(input.sourceId, requirePositiveId(input.pullRequestId, "merge request"), {
59
+ squash: input.method === "squash",
60
+ mergeCommitMessage: input.method === "squash" ? void 0 : commitMessage,
61
+ squashCommitMessage: input.method === "squash" ? commitMessage : void 0
62
+ });
63
+ const merged = result.state === "merged" || Boolean(result.merged_at);
64
+ return {
65
+ merged,
66
+ message: result.message ?? (merged ? "Merge request merged." : "Merge request was not merged."),
67
+ sha: result.squash_commit_sha ?? result.merge_commit_sha ?? result.sha ?? null
68
+ };
69
+ };
70
+ const listComments = async (input) => {
71
+ const context = await deps.contextForConnection(input.connection);
72
+ const mergeRequestIid = requirePositiveId(input.pullRequestId, "merge request");
73
+ const page = parsePositiveCursor(input.cursor);
74
+ const notes = await context.api.listMergeRequestNotes(input.sourceId, mergeRequestIid, { page });
75
+ return {
76
+ comments: notes.filter((note) => !note.system && note.type !== "DiffNote").map((note) => toPullRequestComment(webBaseUrl(context), input.sourceId, mergeRequestIid, note)),
77
+ nextCursor: notes.length === 100 ? String(page + 1) : null
78
+ };
79
+ };
80
+ const createComment = async (input) => {
81
+ const context = await deps.contextForConnection(input.connection);
82
+ const mergeRequestIid = requirePositiveId(input.pullRequestId, "merge request");
83
+ const note = await context.api.createMergeRequestNote(input.sourceId, mergeRequestIid, input.body);
84
+ return toPullRequestComment(webBaseUrl(context), input.sourceId, mergeRequestIid, note);
85
+ };
86
+ const updateComment = async (input) => {
87
+ const context = await deps.contextForConnection(input.connection);
88
+ const { mergeRequestIid, noteId } = parseNoteId(input.commentId);
89
+ const note = await context.api.updateMergeRequestNote(input.sourceId, mergeRequestIid, noteId, input.body);
90
+ return toPullRequestComment(webBaseUrl(context), input.sourceId, mergeRequestIid, note);
91
+ };
92
+ const deleteComment = async (input) => {
93
+ const context = await deps.contextForConnection(input.connection);
94
+ const { mergeRequestIid, noteId } = parseNoteId(input.commentId);
95
+ await context.api.deleteMergeRequestNote(input.sourceId, mergeRequestIid, noteId);
96
+ };
97
+ const listReviews = async (input) => {
98
+ const context = await deps.contextForConnection(input.connection);
99
+ const mergeRequestIid = requirePositiveId(input.pullRequestId, "merge request");
100
+ return {
101
+ reviews: ((await context.api.getMergeRequestApprovals(input.sourceId, mergeRequestIid)).approved_by ?? []).map(({ user }) => toApprovalReview(webBaseUrl(context), input.sourceId, mergeRequestIid, user)),
102
+ nextCursor: null
103
+ };
104
+ };
105
+ const getReview = async (input) => {
106
+ const mergeRequestIid = requirePositiveId(input.pullRequestId, "merge request");
107
+ const approvalPrefix = `${mergeRequestIid}:approval`;
108
+ const commentPrefix = `${mergeRequestIid}:comment:`;
109
+ if (input.reviewId !== approvalPrefix && !input.reviewId.startsWith(`${approvalPrefix}:`) && !input.reviewId.startsWith(commentPrefix)) return null;
110
+ const context = await deps.contextForConnection(input.connection);
111
+ if (input.reviewId === approvalPrefix || input.reviewId.startsWith(`${approvalPrefix}:`)) {
112
+ const reviewerKey = input.reviewId === approvalPrefix ? null : input.reviewId.slice(approvalPrefix.length + 1);
113
+ if (reviewerKey === "") return null;
114
+ const currentUser = reviewerKey === null ? await context.api.getCurrentUser() : null;
115
+ const user = ((await context.api.getMergeRequestApprovals(input.sourceId, mergeRequestIid)).approved_by ?? []).map((entry) => entry.user).find((candidate) => reviewerKey === null ? currentUser?.id !== void 0 && candidate.id === currentUser.id || candidate.username === currentUser?.username : String(candidate.id ?? candidate.username) === reviewerKey);
116
+ return user ? toApprovalReview(webBaseUrl(context), input.sourceId, mergeRequestIid, user) : null;
117
+ }
118
+ const noteId = parsePositiveInteger(input.reviewId.slice(commentPrefix.length));
119
+ if (noteId === null) return null;
120
+ try {
121
+ const note = await context.api.getMergeRequestNote(input.sourceId, mergeRequestIid, noteId);
122
+ if (note.system || note.type === "DiffNote") return null;
123
+ return toCommentReview(webBaseUrl(context), input.sourceId, mergeRequestIid, note);
124
+ } catch (error) {
125
+ if (error instanceof GitLabApiError && error.status === 404) return null;
126
+ throw error;
127
+ }
128
+ };
129
+ const createReview = async (input) => submitReviewAction(deps, {
130
+ connection: input.connection,
131
+ sourceId: input.sourceId,
132
+ pullRequestId: input.pullRequestId,
133
+ event: input.event,
134
+ body: input.body,
135
+ commitId: input.commitId
136
+ });
137
+ const updateReview = async () => {
138
+ throw notSupported("GitLab does not expose mutable pending review objects.");
139
+ };
140
+ const submitReview = async (input) => submitReviewAction(deps, {
141
+ connection: input.connection,
142
+ sourceId: input.sourceId,
143
+ pullRequestId: input.pullRequestId,
144
+ event: input.event,
145
+ body: input.body
146
+ });
147
+ const dismissReview = async () => {
148
+ throw notSupported("GitLab approval dismissal has no GitHub review equivalent.");
149
+ };
150
+ const deletePendingReview = async () => {
151
+ throw notSupported("GitLab does not expose pending review objects.");
152
+ };
153
+ const listReviewComments = async (input) => {
154
+ const context = await deps.contextForConnection(input.connection);
155
+ const mergeRequestIid = requirePositiveId(input.pullRequestId, "merge request");
156
+ const page = parsePositiveCursor(input.cursor);
157
+ const discussions = await context.api.listMergeRequestDiscussions(input.sourceId, mergeRequestIid, { page });
158
+ return {
159
+ comments: discussions.flatMap((discussion) => {
160
+ const position = discussionPosition(discussion.notes);
161
+ if (!position) return [];
162
+ const root = discussion.notes.find((note) => note.position) ?? discussion.notes[0];
163
+ const replyToId = root ? packDiscussionNoteId(mergeRequestIid, discussion.id, root.id) : null;
164
+ return discussion.notes.filter((note) => !note.system).map((note) => toReviewComment(webBaseUrl(context), input.sourceId, mergeRequestIid, discussion.id, note, note === root ? null : replyToId, position));
165
+ }),
166
+ nextCursor: discussions.length === 100 ? String(page + 1) : null
167
+ };
168
+ };
169
+ const createReviewComment = async (input) => {
170
+ const context = await deps.contextForConnection(input.connection);
171
+ const mergeRequestIid = requirePositiveId(input.pullRequestId, "merge request");
172
+ if (input.replyToId) {
173
+ const thread = parseReviewReplyId(input.replyToId);
174
+ if (thread.mergeRequestIid !== mergeRequestIid) throw new GitLabApiError("GitLab discussion does not belong to the requested merge request.", 400);
175
+ const discussion = await context.api.getMergeRequestDiscussion(input.sourceId, mergeRequestIid, thread.discussionId);
176
+ const position = discussionPosition(discussion.notes);
177
+ if (!position) throw new GitLabApiError("GitLab discussion is not anchored to a diff.", 400);
178
+ const root = discussion.notes.find((note) => note.position) ?? discussion.notes[0];
179
+ if (!root) throw new GitLabApiError("GitLab discussion response did not include a note.", 502);
180
+ const note = await context.api.addMergeRequestDiscussionNote(input.sourceId, mergeRequestIid, thread.discussionId, input.body);
181
+ return toReviewComment(webBaseUrl(context), input.sourceId, mergeRequestIid, thread.discussionId, note, packDiscussionNoteId(mergeRequestIid, thread.discussionId, root.id), position);
182
+ }
183
+ const mergeRequest = await context.api.getMergeRequest(input.sourceId, mergeRequestIid);
184
+ if (!mergeRequest.diff_refs) throw new GitLabApiError("GitLab merge request diff refs are not ready.", 409);
185
+ if (mergeRequest.diff_refs.head_sha !== input.commitId) throw new GitLabApiError("GitLab merge request changed since this review comment was prepared.", 409);
186
+ const { path, line, side } = input;
187
+ if (typeof path !== "string" || typeof line !== "number" || side !== "left" && side !== "right") throw new GitLabApiError("GitLab diff review comment position is invalid.", 400);
188
+ if (input.startLine === void 0 !== (input.startSide === void 0)) throw new GitLabApiError("A multi-line GitLab review comment requires both startLine and startSide.", 400);
189
+ const position = {
190
+ position_type: "text",
191
+ ...mergeRequest.diff_refs,
192
+ old_path: path,
193
+ new_path: path,
194
+ old_line: side === "left" ? line : void 0,
195
+ new_line: side === "right" ? line : void 0,
196
+ ...input.startLine !== void 0 && input.startSide ? { line_range: {
197
+ start: discussionLine(path, input.startLine, input.startSide),
198
+ end: discussionLine(path, line, side)
199
+ } } : {}
200
+ };
201
+ const discussion = await context.api.createMergeRequestDiscussion(input.sourceId, mergeRequestIid, {
202
+ body: input.body,
203
+ commitId: input.commitId,
204
+ position
205
+ });
206
+ const note = discussion.notes.find((candidate) => candidate.position) ?? discussion.notes[0];
207
+ if (!note) throw new GitLabApiError("GitLab discussion response did not include a note.", 502);
208
+ return toReviewComment(webBaseUrl(context), input.sourceId, mergeRequestIid, discussion.id, note, null, position);
209
+ };
210
+ const updateReviewComment = async (input) => {
211
+ const context = await deps.contextForConnection(input.connection);
212
+ const reference = parseDiscussionNoteId(input.commentId);
213
+ const discussion = await context.api.getMergeRequestDiscussion(input.sourceId, reference.mergeRequestIid, reference.discussionId);
214
+ const position = discussionPosition(discussion.notes);
215
+ if (!position) throw new GitLabApiError("GitLab discussion is not anchored to a diff.", 400);
216
+ const root = discussion.notes.find((candidate) => candidate.position) ?? discussion.notes[0];
217
+ if (!root) throw new GitLabApiError("GitLab discussion response did not include a note.", 502);
218
+ const note = await context.api.updateMergeRequestDiscussionNote(input.sourceId, reference.mergeRequestIid, reference.discussionId, reference.noteId, input.body);
219
+ return toReviewComment(webBaseUrl(context), input.sourceId, reference.mergeRequestIid, reference.discussionId, note, note.id === root.id ? null : packDiscussionNoteId(reference.mergeRequestIid, reference.discussionId, root.id), position);
220
+ };
221
+ const deleteReviewComment = async (input) => {
222
+ const context = await deps.contextForConnection(input.connection);
223
+ const reference = parseDiscussionNoteId(input.commentId);
224
+ await context.api.deleteMergeRequestDiscussionNote(input.sourceId, reference.mergeRequestIid, reference.discussionId, reference.noteId);
225
+ };
226
+ const resolveReviewThread = async (input) => {
227
+ const context = await deps.contextForConnection(input.connection);
228
+ const reference = parseDiscussionNoteId(input.commentId);
229
+ await context.api.resolveMergeRequestDiscussion(input.sourceId, reference.mergeRequestIid, reference.discussionId, input.resolved);
230
+ };
231
+ const listRequestedReviewers = async (input) => {
232
+ return toRequestedReviewers(await (await deps.contextForConnection(input.connection)).api.getMergeRequest(input.sourceId, requirePositiveId(input.pullRequestId, "merge request")));
233
+ };
234
+ const requestReviewers = async (input) => {
235
+ rejectTeamReviewers(input.teams);
236
+ const context = await deps.contextForConnection(input.connection);
237
+ const mergeRequestIid = requirePositiveId(input.pullRequestId, "merge request");
238
+ const mergeRequest = await context.api.getMergeRequest(input.sourceId, mergeRequestIid);
239
+ const requestedMembers = await resolveMembers(context.api, input.sourceId, input.users ?? []);
240
+ const currentIds = await reviewerIds(context.api, input.sourceId, mergeRequest.reviewers ?? []);
241
+ const reviewerIdsToSet = [.../* @__PURE__ */ new Set([...currentIds, ...requestedMembers.map((member) => member.id)])];
242
+ return toRequestedReviewers(await context.api.setMergeRequestReviewers(input.sourceId, mergeRequestIid, reviewerIdsToSet));
243
+ };
244
+ const removeRequestedReviewers = async (input) => {
245
+ rejectTeamReviewers(input.teams);
246
+ const context = await deps.contextForConnection(input.connection);
247
+ const mergeRequestIid = requirePositiveId(input.pullRequestId, "merge request");
248
+ const mergeRequest = await context.api.getMergeRequest(input.sourceId, mergeRequestIid);
249
+ const removals = new Set((input.users ?? []).map((username) => username.toLowerCase()));
250
+ const retained = (mergeRequest.reviewers ?? []).filter((user) => !removals.has(user.username.toLowerCase()));
251
+ const retainedIds = await reviewerIds(context.api, input.sourceId, retained);
252
+ return toRequestedReviewers(await context.api.setMergeRequestReviewers(input.sourceId, mergeRequestIid, retainedIds));
253
+ };
254
+ return {
255
+ initialize: (input) => {
256
+ storage = input.storage;
257
+ },
258
+ registerInstallation: async ({ orgId, userId, installation }) => {
259
+ const requestedConnection = parseConnection(installation.metadata?.connection);
260
+ if (!requestedConnection) throw new GitLabApiError("GitLab installation metadata must include a valid connection.", 400);
261
+ const context = await deps.contextForConnection(requestedConnection);
262
+ return sourceControlStorage().installations.upsert({
263
+ orgId,
264
+ connectedByUserId: userId,
265
+ externalId: installation.externalId,
266
+ accountName: installation.accountName,
267
+ accountType: installation.accountType,
268
+ providerMetadata: {
269
+ ...installation.metadata,
270
+ connection: context.connection,
271
+ host: normalizeHost(context.host)
272
+ }
273
+ });
274
+ },
275
+ registerRepositories: async ({ orgId, installationId, repositories }) => {
276
+ const normalizedRepositories = repositories.map((repository) => ({
277
+ ...repository,
278
+ slug: normalizeSlug(repository.slug)
279
+ }));
280
+ return await Promise.all(normalizedRepositories.map((repository) => sourceControlStorage().repositories.upsert({
281
+ orgId,
282
+ input: {
283
+ installationId,
284
+ externalId: repository.externalId,
285
+ slug: repository.slug,
286
+ defaultBranch: repository.defaultBranch,
287
+ providerMetadata: repository.metadata
288
+ }
289
+ })));
290
+ },
291
+ getRepositoryTarget: async ({ orgId, repositoryId }) => {
292
+ const repository = await sourceControlStorage().repositories.get({
293
+ orgId,
294
+ id: repositoryId
295
+ });
296
+ if (!repository) throw new Error("Version-control repository not found.");
297
+ const installation = await sourceControlStorage().installations.get({
298
+ orgId,
299
+ id: repository.installationId
300
+ });
301
+ if (!installation) throw new Error("Version-control installation not found.");
302
+ const connection = parseConnection(installation.providerMetadata.connection);
303
+ if (!connection) throw new GitLabApiError("GitLab installation connection metadata is invalid.", 500);
304
+ return {
305
+ connection: (await (deps.contextForStoredInstallation ?? deps.contextForConnection)(connection, typeof installation.providerMetadata.host === "string" ? installation.providerMetadata.host : void 0)).connection,
306
+ sourceId: `${repository.externalId}:${repository.slug}`
307
+ };
308
+ },
309
+ getRepositoryAccess: async ({ orgId, repositoryId }) => {
310
+ const repository = await sourceControlStorage().repositories.get({
311
+ orgId,
312
+ id: repositoryId
313
+ });
314
+ if (!repository) throw new Error("Version-control repository not found.");
315
+ const installation = await sourceControlStorage().installations.get({
316
+ orgId,
317
+ id: repository.installationId
318
+ });
319
+ if (!installation) throw new Error("Version-control installation not found.");
320
+ const connection = parseConnection(installation.providerMetadata.connection);
321
+ if (!connection) throw new GitLabApiError("GitLab installation connection metadata is invalid.", 500);
322
+ const context = await (deps.contextForStoredInstallation ?? deps.contextForConnection)(connection, typeof installation.providerMetadata.host === "string" ? installation.providerMetadata.host : void 0);
323
+ if (!context.repositoryAccessToken) throw notSupported("This GitLab connection does not expose credentials for repository cloning.");
324
+ const token = await context.repositoryAccessToken();
325
+ return {
326
+ cloneUrl: `${webBaseUrl(context)}/${normalizeSlug(repository.slug)}.git`,
327
+ authorization: {
328
+ scheme: "bearer",
329
+ token,
330
+ username: "oauth2"
331
+ }
332
+ };
333
+ },
334
+ listPullRequests,
335
+ getPullRequest,
336
+ createPullRequest,
337
+ updatePullRequest,
338
+ closePullRequest,
339
+ mergePullRequest,
340
+ listComments,
341
+ resolveReviewThread,
342
+ createComment,
343
+ updateComment,
344
+ deleteComment,
345
+ listReviews,
346
+ getReview,
347
+ createReview,
348
+ updateReview,
349
+ submitReview,
350
+ dismissReview,
351
+ deletePendingReview,
352
+ listReviewComments,
353
+ createReviewComment,
354
+ updateReviewComment,
355
+ deleteReviewComment,
356
+ listRequestedReviewers,
357
+ requestReviewers,
358
+ removeRequestedReviewers
359
+ };
360
+ }
361
+ async function submitReviewAction(deps, input) {
362
+ const context = await deps.contextForConnection(input.connection);
363
+ const mergeRequestIid = requirePositiveId(input.pullRequestId, "merge request");
364
+ if (input.event === "request-changes") throw notSupported("GitLab has no first-class request-changes review event.");
365
+ if (input.event === void 0) throw notSupported("GitLab has no first-class pending review object.");
366
+ if (input.event === "approve") {
367
+ const body = input.body?.trim();
368
+ await context.api.approveMergeRequest(input.sourceId, mergeRequestIid, input.commitId);
369
+ const comment = body ? toPullRequestComment(webBaseUrl(context), input.sourceId, mergeRequestIid, await context.api.createMergeRequestNote(input.sourceId, mergeRequestIid, body)) : null;
370
+ return {
371
+ id: String(mergeRequestIid) + ":approval",
372
+ url: comment?.url ?? mergeRequestUrl(webBaseUrl(context), input.sourceId, mergeRequestIid),
373
+ author: comment?.author ?? null,
374
+ body: comment?.body ?? null,
375
+ state: "approved",
376
+ commitId: null,
377
+ submittedAt: comment?.createdAt ?? null
378
+ };
379
+ }
380
+ const body = input.body?.trim();
381
+ if (!body) throw new GitLabApiError("GitLab comment reviews require a body.", 400);
382
+ const note = await context.api.createMergeRequestNote(input.sourceId, mergeRequestIid, body);
383
+ return toCommentReview(webBaseUrl(context), input.sourceId, mergeRequestIid, note);
384
+ }
385
+ function toCommentReview(host, sourceId, mergeRequestIid, note) {
386
+ const comment = toPullRequestComment(host, sourceId, mergeRequestIid, note);
387
+ return {
388
+ id: `${mergeRequestIid}:comment:${note.id}`,
389
+ url: comment.url,
390
+ author: comment.author,
391
+ body: comment.body,
392
+ state: "commented",
393
+ commitId: null,
394
+ submittedAt: note.created_at
395
+ };
396
+ }
397
+ function toApprovalReview(host, sourceId, mergeRequestIid, user) {
398
+ return {
399
+ id: `${mergeRequestIid}:approval:${user.id ?? user.username}`,
400
+ url: mergeRequestUrl(host, sourceId, mergeRequestIid),
401
+ author: displayName(user),
402
+ body: null,
403
+ state: "approved",
404
+ commitId: null,
405
+ submittedAt: null
406
+ };
407
+ }
408
+ function toReviewComment(host, sourceId, mergeRequestIid, discussionId, note, replyToId, fallbackPosition) {
409
+ const position = note.position ?? fallbackPosition;
410
+ if (!position) throw new GitLabApiError("GitLab review comment is missing its diff position.", 502);
411
+ const base = toPullRequestComment(host, sourceId, mergeRequestIid, note);
412
+ const side = position.new_line !== null && position.new_line !== void 0 ? "right" : "left";
413
+ return {
414
+ ...base,
415
+ id: packDiscussionNoteId(mergeRequestIid, discussionId, note.id),
416
+ path: side === "right" ? position.new_path : position.old_path,
417
+ line: side === "right" ? position.new_line ?? null : position.old_line ?? null,
418
+ side,
419
+ commitId: position.head_sha,
420
+ replyToId
421
+ };
422
+ }
423
+ function mergeRequestUrl(host, sourceId, mergeRequestIid) {
424
+ return `${host}/${sourcePath(sourceId)}/-/merge_requests/${mergeRequestIid}`;
425
+ }
426
+ function webBaseUrl(context) {
427
+ const host = normalizeHost(context.host);
428
+ if (!context.webBaseUrl) return `https://${host}`;
429
+ let url;
430
+ try {
431
+ url = new URL(context.webBaseUrl);
432
+ } catch {
433
+ throw new GitLabApiError("GitLab web base URL is invalid.", 400);
434
+ }
435
+ const loopback = url.hostname === "localhost" || url.hostname === "[::1]" || /^127(?:\.\d{1,3}){3}$/.test(url.hostname);
436
+ if (url.host !== host || url.protocol !== "https:" && !(url.protocol === "http:" && loopback) || url.username || url.password || url.search || url.hash) throw new GitLabApiError("GitLab web base URL is invalid.", 400);
437
+ return `${url.origin}${url.pathname.replace(/\/+$/, "")}`;
438
+ }
439
+ function sourcePath(sourceId) {
440
+ return normalizeSlug(/^\d+:(.+)$/.exec(sourceId)?.[1] ?? sourceId);
441
+ }
442
+ function packDiscussionId(mergeRequestIid, discussionId) {
443
+ if (!discussionId || discussionId.includes(":")) throw new GitLabApiError("GitLab discussion id is invalid.", 400);
444
+ return `${mergeRequestIid}:${discussionId}`;
445
+ }
446
+ function packDiscussionNoteId(mergeRequestIid, discussionId, noteId) {
447
+ return `${packDiscussionId(mergeRequestIid, discussionId)}:${noteId}`;
448
+ }
449
+ function parseReviewReplyId(value) {
450
+ const parts = value.split(":");
451
+ if (parts.length === 2) return parseDiscussionId(value);
452
+ if (parts.length === 3 && parts[1]) {
453
+ requirePositiveId(parts[2], "discussion note");
454
+ return {
455
+ mergeRequestIid: requirePositiveId(parts[0], "merge request"),
456
+ discussionId: parts[1]
457
+ };
458
+ }
459
+ throw new GitLabApiError("GitLab review reply id is invalid.", 400);
460
+ }
461
+ function parseDiscussionId(value) {
462
+ const parts = value.split(":");
463
+ if (parts.length !== 2 || !parts[1]) throw new GitLabApiError("GitLab discussion id is invalid.", 400);
464
+ return {
465
+ mergeRequestIid: requirePositiveId(parts[0], "merge request"),
466
+ discussionId: parts[1]
467
+ };
468
+ }
469
+ function parseDiscussionNoteId(value) {
470
+ const parts = value.split(":");
471
+ if (parts.length !== 3 || !parts[1]) throw new GitLabApiError("GitLab discussion note id is invalid.", 400);
472
+ return {
473
+ mergeRequestIid: requirePositiveId(parts[0], "merge request"),
474
+ discussionId: parts[1],
475
+ noteId: requirePositiveId(parts[2], "discussion note")
476
+ };
477
+ }
478
+ function discussionPosition(notes) {
479
+ return notes.find((note) => note.position)?.position ?? void 0;
480
+ }
481
+ function discussionLine(path, line, side) {
482
+ const pathHash = createHash("sha1").update(path).digest("hex");
483
+ const oldLine = side === "left" ? line : void 0;
484
+ const newLine = side === "right" ? line : void 0;
485
+ return {
486
+ line_code: `${pathHash}_${oldLine ?? 0}_${newLine ?? 0}`,
487
+ type: side === "left" ? "old" : "new",
488
+ old_line: oldLine,
489
+ new_line: newLine
490
+ };
491
+ }
492
+ function toRequestedReviewers(mergeRequest) {
493
+ return {
494
+ users: (mergeRequest.reviewers ?? []).map((user) => user.username),
495
+ teams: []
496
+ };
497
+ }
498
+ async function resolveMembers(api, sourceId, usernames) {
499
+ return Promise.all(usernames.map((username) => resolveMember(api, sourceId, username)));
500
+ }
501
+ async function resolveMember(api, sourceId, username) {
502
+ const normalized = username.trim();
503
+ if (!normalized) throw new GitLabApiError("GitLab reviewer username is invalid.", 400);
504
+ const member = (await api.listProjectMembers(sourceId, { query: normalized })).find((candidate) => candidate.username.toLowerCase() === normalized.toLowerCase());
505
+ if (!member) throw new GitLabApiError(`GitLab reviewer ${normalized} is not a project member.`, 404);
506
+ return member;
507
+ }
508
+ async function reviewerIds(api, sourceId, users) {
509
+ return Promise.all(users.map(async (user) => user.id ?? (await resolveMember(api, sourceId, user.username)).id));
510
+ }
511
+ function rejectTeamReviewers(teams) {
512
+ if (!teams?.length) return;
513
+ throw notSupported("GitLab merge requests do not have a direct team-reviewer equivalent.");
514
+ }
515
+ function notSupported(message) {
516
+ return new GitLabApiError(message, 501);
517
+ }
518
+ function toPullRequest(mergeRequest) {
519
+ return {
520
+ id: String(mergeRequest.iid),
521
+ title: mergeRequest.title,
522
+ url: mergeRequest.web_url,
523
+ author: displayName(mergeRequest.author),
524
+ assignees: mergeRequest.assignees?.map((user) => user.username),
525
+ requestedReviewers: mergeRequest.reviewers?.map((user) => user.username),
526
+ labels: mergeRequest.labels,
527
+ body: mergeRequest.description?.trim() ? mergeRequest.description : null,
528
+ state: mergeRequest.state === "closed" || mergeRequest.state === "merged" ? "closed" : "open",
529
+ draft: isDraft(mergeRequest),
530
+ merged: mergeRequest.state === "merged" || Boolean(mergeRequest.merged_at),
531
+ mergeable: mergeableState(mergeRequest.merge_status),
532
+ baseBranch: mergeRequest.target_branch,
533
+ headBranch: mergeRequest.source_branch,
534
+ headSha: mergeRequest.sha,
535
+ createdAt: mergeRequest.created_at,
536
+ updatedAt: mergeRequest.updated_at
537
+ };
538
+ }
539
+ function toPullRequestComment(host, sourceId, mergeRequestIid, note) {
540
+ return {
541
+ id: `${mergeRequestIid}:${note.id}`,
542
+ url: `${mergeRequestUrl(host, sourceId, mergeRequestIid)}#note_${note.id}`,
543
+ author: displayName(note.author),
544
+ body: note.body,
545
+ createdAt: note.created_at,
546
+ updatedAt: note.updated_at ?? note.created_at
547
+ };
548
+ }
549
+ function displayName(user) {
550
+ return user?.username || user?.name?.trim() || null;
551
+ }
552
+ function isDraft(mergeRequest) {
553
+ return mergeRequest.draft ?? mergeRequest.work_in_progress ?? /^(?:draft:|\[draft\])/i.test(mergeRequest.title);
554
+ }
555
+ function mergeableState(status) {
556
+ if (status === "can_be_merged") return true;
557
+ if (status === "cannot_be_merged") return false;
558
+ return null;
559
+ }
560
+ function combinedCommitMessage(title, body) {
561
+ const parts = [title?.trim(), body?.trim()].filter((part) => Boolean(part));
562
+ return parts.length > 0 ? parts.join("\n\n") : void 0;
563
+ }
564
+ function parseNoteId(value) {
565
+ const parts = value.split(":");
566
+ if (parts.length !== 2) throw new GitLabApiError("GitLab merge request note id is invalid.", 400);
567
+ return {
568
+ mergeRequestIid: requirePositiveId(parts[0], "merge request"),
569
+ noteId: requirePositiveId(parts[1], "note")
570
+ };
571
+ }
572
+ function parsePositiveCursor(cursor) {
573
+ if (cursor === void 0) return 1;
574
+ const page = parsePositiveInteger(cursor);
575
+ if (page === null) throw new GitLabApiError("GitLab cursor must be a positive page number.", 400);
576
+ return page;
577
+ }
578
+ function requirePositiveId(value, resource) {
579
+ const parsed = parsePositiveInteger(value);
580
+ if (parsed === null) throw new GitLabApiError(`GitLab ${resource} id must be a positive integer.`, 400);
581
+ return parsed;
582
+ }
583
+ function parsePositiveInteger(value) {
584
+ if (!/^\d+$/.test(value)) return null;
585
+ const parsed = Number(value);
586
+ return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;
587
+ }
588
+ function parseConnection(value) {
589
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
590
+ const connection = value;
591
+ if (connection.type === "oauth" && typeof connection.accessToken === "string" && connection.accessToken.length > 0) return {
592
+ type: "oauth",
593
+ accessToken: connection.accessToken
594
+ };
595
+ if (connection.type === "app-installation" && Number.isSafeInteger(connection.installationId) && Number(connection.installationId) > 0) return {
596
+ type: "app-installation",
597
+ installationId: Number(connection.installationId)
598
+ };
599
+ return null;
600
+ }
601
+ function normalizeHost(value) {
602
+ const host = value.trim();
603
+ if (!host || host.includes("/") || host.includes("@")) throw new GitLabApiError("GitLab host is invalid.", 400);
604
+ let parsed;
605
+ try {
606
+ parsed = new URL(`https://${host}`);
607
+ } catch {
608
+ throw new GitLabApiError("GitLab host is invalid.", 400);
609
+ }
610
+ if (parsed.host !== host) throw new GitLabApiError("GitLab host is invalid.", 400);
611
+ return host;
612
+ }
613
+ function normalizeSlug(value) {
614
+ const slug = value.replace(/^\/+|\/+$/g, "");
615
+ const segments = slug.split("/");
616
+ if (!slug || segments.some((segment) => !/^[A-Za-z0-9_.-]+$/.test(segment) || segment === "." || segment === "..")) throw new GitLabApiError("GitLab repository slug is invalid.", 400);
617
+ return slug;
618
+ }
619
+ //#endregion
620
+ export { buildGitLabVersionControl };
621
+
622
+ //# sourceMappingURL=version-control.js.map