gitlab-auto-reviewers 2.0.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.
Files changed (119) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1878 -0
  3. package/dist/api/gitlab-api.d.ts +136 -0
  4. package/dist/api/gitlab-api.d.ts.map +1 -0
  5. package/dist/api/gitlab-api.js +334 -0
  6. package/dist/api/gitlab-api.js.map +1 -0
  7. package/dist/bin/cli.d.ts +10 -0
  8. package/dist/bin/cli.d.ts.map +1 -0
  9. package/dist/bin/cli.js +186 -0
  10. package/dist/bin/cli.js.map +1 -0
  11. package/dist/bin/deprecated-mcp.d.ts +12 -0
  12. package/dist/bin/deprecated-mcp.d.ts.map +1 -0
  13. package/dist/bin/deprecated-mcp.js +73 -0
  14. package/dist/bin/deprecated-mcp.js.map +1 -0
  15. package/dist/bin/index.d.ts +18 -0
  16. package/dist/bin/index.d.ts.map +1 -0
  17. package/dist/bin/index.js +78 -0
  18. package/dist/bin/index.js.map +1 -0
  19. package/dist/bin/mcp.d.ts +11 -0
  20. package/dist/bin/mcp.d.ts.map +1 -0
  21. package/dist/bin/mcp.js +43 -0
  22. package/dist/bin/mcp.js.map +1 -0
  23. package/dist/cache/cache.service.d.ts +113 -0
  24. package/dist/cache/cache.service.d.ts.map +1 -0
  25. package/dist/cache/cache.service.js +213 -0
  26. package/dist/cache/cache.service.js.map +1 -0
  27. package/dist/cli/commands.d.ts +40 -0
  28. package/dist/cli/commands.d.ts.map +1 -0
  29. package/dist/cli/commands.js +142 -0
  30. package/dist/cli/commands.js.map +1 -0
  31. package/dist/cli/output.d.ts +24 -0
  32. package/dist/cli/output.d.ts.map +1 -0
  33. package/dist/cli/output.js +143 -0
  34. package/dist/cli/output.js.map +1 -0
  35. package/dist/config/config.service.d.ts +89 -0
  36. package/dist/config/config.service.d.ts.map +1 -0
  37. package/dist/config/config.service.js +169 -0
  38. package/dist/config/config.service.js.map +1 -0
  39. package/dist/datasources/git-data-source.interface.d.ts +140 -0
  40. package/dist/datasources/git-data-source.interface.d.ts.map +1 -0
  41. package/dist/datasources/git-data-source.interface.js +2 -0
  42. package/dist/datasources/git-data-source.interface.js.map +1 -0
  43. package/dist/datasources/gitlab-api-data-source.d.ts +127 -0
  44. package/dist/datasources/gitlab-api-data-source.d.ts.map +1 -0
  45. package/dist/datasources/gitlab-api-data-source.js +248 -0
  46. package/dist/datasources/gitlab-api-data-source.js.map +1 -0
  47. package/dist/datasources/local-git-data-source.d.ts +124 -0
  48. package/dist/datasources/local-git-data-source.d.ts.map +1 -0
  49. package/dist/datasources/local-git-data-source.js +580 -0
  50. package/dist/datasources/local-git-data-source.js.map +1 -0
  51. package/dist/errors/error-handler.d.ts +113 -0
  52. package/dist/errors/error-handler.d.ts.map +1 -0
  53. package/dist/errors/error-handler.js +230 -0
  54. package/dist/errors/error-handler.js.map +1 -0
  55. package/dist/index.d.ts +139 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +139 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/logging/example.d.ts +15 -0
  60. package/dist/logging/example.d.ts.map +1 -0
  61. package/dist/logging/example.js +79 -0
  62. package/dist/logging/example.js.map +1 -0
  63. package/dist/logging/index.d.ts +7 -0
  64. package/dist/logging/index.d.ts.map +1 -0
  65. package/dist/logging/index.js +7 -0
  66. package/dist/logging/index.js.map +1 -0
  67. package/dist/logging/logger.service.d.ts +98 -0
  68. package/dist/logging/logger.service.d.ts.map +1 -0
  69. package/dist/logging/logger.service.js +160 -0
  70. package/dist/logging/logger.service.js.map +1 -0
  71. package/dist/mcp/server.d.ts +67 -0
  72. package/dist/mcp/server.d.ts.map +1 -0
  73. package/dist/mcp/server.js +213 -0
  74. package/dist/mcp/server.js.map +1 -0
  75. package/dist/mcp/tools.d.ts +22 -0
  76. package/dist/mcp/tools.d.ts.map +1 -0
  77. package/dist/mcp/tools.js +176 -0
  78. package/dist/mcp/tools.js.map +1 -0
  79. package/dist/services/blacklist.service.d.ts +32 -0
  80. package/dist/services/blacklist.service.d.ts.map +1 -0
  81. package/dist/services/blacklist.service.js +59 -0
  82. package/dist/services/blacklist.service.js.map +1 -0
  83. package/dist/services/codeowners.service.d.ts +45 -0
  84. package/dist/services/codeowners.service.d.ts.map +1 -0
  85. package/dist/services/codeowners.service.js +200 -0
  86. package/dist/services/codeowners.service.js.map +1 -0
  87. package/dist/services/comment-builder.service.d.ts +48 -0
  88. package/dist/services/comment-builder.service.d.ts.map +1 -0
  89. package/dist/services/comment-builder.service.js +61 -0
  90. package/dist/services/comment-builder.service.js.map +1 -0
  91. package/dist/services/contributors.service.d.ts +52 -0
  92. package/dist/services/contributors.service.d.ts.map +1 -0
  93. package/dist/services/contributors.service.js +144 -0
  94. package/dist/services/contributors.service.js.map +1 -0
  95. package/dist/services/reviewer-service.d.ts +125 -0
  96. package/dist/services/reviewer-service.d.ts.map +1 -0
  97. package/dist/services/reviewer-service.js +554 -0
  98. package/dist/services/reviewer-service.js.map +1 -0
  99. package/dist/services/team-members.service.d.ts +29 -0
  100. package/dist/services/team-members.service.d.ts.map +1 -0
  101. package/dist/services/team-members.service.js +45 -0
  102. package/dist/services/team-members.service.js.map +1 -0
  103. package/dist/services/whitelist.service.d.ts +31 -0
  104. package/dist/services/whitelist.service.d.ts.map +1 -0
  105. package/dist/services/whitelist.service.js +51 -0
  106. package/dist/services/whitelist.service.js.map +1 -0
  107. package/dist/tools.d.ts +22 -0
  108. package/dist/tools.d.ts.map +1 -0
  109. package/dist/tools.js +176 -0
  110. package/dist/tools.js.map +1 -0
  111. package/dist/types/index.d.ts +502 -0
  112. package/dist/types/index.d.ts.map +1 -0
  113. package/dist/types/index.js +91 -0
  114. package/dist/types/index.js.map +1 -0
  115. package/dist/types.d.ts +219 -0
  116. package/dist/types.d.ts.map +1 -0
  117. package/dist/types.js +7 -0
  118. package/dist/types.js.map +1 -0
  119. package/package.json +71 -0
@@ -0,0 +1,554 @@
1
+ import { GitLabAPI } from "../api/gitlab-api.js";
2
+ import { LocalGitDataSource } from "../datasources/local-git-data-source.js";
3
+ import { GitLabAPIDataSource } from "../datasources/gitlab-api-data-source.js";
4
+ import { ContributorsService } from "./contributors.service.js";
5
+ import { WhitelistService } from "./whitelist.service.js";
6
+ import { BlacklistService } from "./blacklist.service.js";
7
+ import { CodeOwnersService } from "./codeowners.service.js";
8
+ import { TeamMembersService } from "./team-members.service.js";
9
+ import { CommentBuilderService } from "./comment-builder.service.js";
10
+ import { Logger } from "../logging/logger.service.js";
11
+ /**
12
+ * Main service for suggesting and managing merge request reviewers.
13
+ *
14
+ * Orchestrates multiple analysis strategies to suggest appropriate reviewers:
15
+ * - Previous contributors based on git blame analysis
16
+ * - Team members with FTE-aware load balancing
17
+ * - Code owners with approval requirements and load balancing
18
+ *
19
+ * Provides transparency into reviewer selection with detailed scoring and reasoning.
20
+ */
21
+ export class ReviewerService {
22
+ dataSource;
23
+ contributorsService;
24
+ whitelistService;
25
+ blacklistService;
26
+ codeOwnersService;
27
+ teamMembersService;
28
+ commentBuilderService;
29
+ logger;
30
+ cache;
31
+ /**
32
+ * Creates a new ReviewerService instance.
33
+ *
34
+ * @param gitlabUrl - The base URL of the GitLab instance (e.g., 'https://gitlab.com')
35
+ * @param token - GitLab personal access token or API token
36
+ * @param repoPath - Optional local repository path for git operations (enables local mode)
37
+ * @param logger - Optional logger instance for structured logging
38
+ * @param cache - Optional cache service for performance optimization
39
+ */
40
+ constructor(gitlabUrl, token, repoPath, logger, cache) {
41
+ this.logger = logger || new Logger('ReviewerService');
42
+ this.cache = cache;
43
+ const gitlabApi = new GitLabAPI({ baseUrl: gitlabUrl, token }, this.logger.child('GitLabAPI'));
44
+ this.dataSource = repoPath
45
+ ? new LocalGitDataSource(gitlabApi, repoPath, this.logger.child('LocalGitDataSource'))
46
+ : new GitLabAPIDataSource(gitlabApi, this.logger.child('GitLabAPIDataSource'));
47
+ this.contributorsService = new ContributorsService(this.dataSource);
48
+ this.whitelistService = new WhitelistService(this.dataSource);
49
+ this.blacklistService = new BlacklistService(this.dataSource);
50
+ this.codeOwnersService = new CodeOwnersService(this.dataSource);
51
+ this.teamMembersService = new TeamMembersService();
52
+ this.commentBuilderService = new CommentBuilderService(gitlabUrl);
53
+ }
54
+ /**
55
+ * Suggests reviewers for a merge request using multiple analysis strategies.
56
+ *
57
+ * Analyzes the merge request to suggest appropriate reviewers based on:
58
+ * - Previous contributors to modified code (git blame analysis)
59
+ * - Team membership with FTE-aware load balancing
60
+ * - Code ownership rules with approval requirements
61
+ *
62
+ * Returns a formatted comment for posting to GitLab and detailed reviewer
63
+ * suggestions with transparency information (scores, reasoning, workload).
64
+ *
65
+ * @param params - Parameters including project, MR IID, or branch information
66
+ * @returns Reviewer suggestions with formatted comment and detailed analysis
67
+ * @throws Error if project/MR cannot be resolved or required data is unavailable
68
+ */
69
+ async suggestReviewers(params) {
70
+ const endTimer = this.logger.startTimer('suggestReviewers');
71
+ try {
72
+ // Resolve project and MR identifiers
73
+ let project;
74
+ if (params.project) {
75
+ project = String(params.project);
76
+ }
77
+ else if (this.dataSource instanceof LocalGitDataSource) {
78
+ project = await this.dataSource.getProjectFromRemote();
79
+ }
80
+ else {
81
+ throw new Error("Either 'project' parameter or 'repoPath' parameter is required. Provide 'repoPath' to use local git mode.");
82
+ }
83
+ let mr;
84
+ if (params.mergeRequestIid) {
85
+ mr = params.mergeRequestIid;
86
+ }
87
+ else {
88
+ const branch = params.branch ||
89
+ (this.dataSource.getCurrentBranch
90
+ ? await this.dataSource.getCurrentBranch()
91
+ : undefined);
92
+ if (!branch)
93
+ throw new Error("Either mergeRequestIid or branch must be provided");
94
+ const mergeRequest = await this.dataSource.getMergeRequestByBranch(project, branch);
95
+ if (!mergeRequest) {
96
+ throw new Error(`No merge request found for branch ${branch} in project ${project}`);
97
+ }
98
+ mr = mergeRequest.iid;
99
+ }
100
+ // Fetch merge request details
101
+ const mergeRequest = await this.dataSource.getMergeRequest(project, mr);
102
+ if (!mergeRequest) {
103
+ throw new Error(`Merge request ${mr} not found in project ${project}`);
104
+ }
105
+ if (!mergeRequest.diff_refs) {
106
+ throw new Error(`Merge request ${mr} has no diff refs`);
107
+ }
108
+ const author = mergeRequest.author.username.toLowerCase();
109
+ const base = mergeRequest.diff_refs.base_sha;
110
+ const head = mergeRequest.diff_refs.head_sha;
111
+ // Parallelize independent data fetches with partial failure handling
112
+ this.logger.info('Fetching data in parallel', { project, mr, headSha: head });
113
+ const parallelTimer = this.logger.startTimer('parallel data fetch');
114
+ const [diffsResult, mergeRequestsResult, codeOwnerRulesResult, whitelistResult] = await Promise.allSettled([
115
+ this.fetchWithCache(`diffs:${project}:${mr}`, () => this.dataSource.getDiffs(project, mr)),
116
+ this.fetchWithCache(`merge-requests:${project}`, () => this.dataSource.getMergeRequests(project)),
117
+ this.fetchWithCache(`codeowners:${project}:${head}`, () => this.codeOwnersService.getCodeOwners(project, head)),
118
+ this.fetchWithCache(`whitelist:${project}:${head}`, () => this.whitelistService.getWhitelist(project, head)),
119
+ ]);
120
+ parallelTimer();
121
+ // Extract results and handle failures
122
+ const diffs = this.extractResult(diffsResult, 'diffs', []);
123
+ const mergeRequests = this.extractResult(mergeRequestsResult, 'merge requests', []);
124
+ const codeOwnerRules = this.extractResult(codeOwnerRulesResult, 'code owners', []);
125
+ const whitelist = this.extractResult(whitelistResult, 'whitelist', []);
126
+ // Parallelize contributor and blacklist fetches with partial failure handling
127
+ const [contributorsResult, blacklistResult] = await Promise.allSettled([
128
+ this.fetchWithCache(`contributors:${project}:${mr}:${base}`, () => this.contributorsService.getContributors(project, mr, base, whitelist)),
129
+ this.fetchWithCache(`blacklist:${project}:${mr}:${author}`, () => this.blacklistService.getBlackList(project, mr, author)),
130
+ ]);
131
+ const contributors = this.extractResult(contributorsResult, 'contributors', []);
132
+ const blacklist = this.extractResult(blacklistResult, 'blacklist', []);
133
+ // Process data (synchronous operations)
134
+ const changedFiles = diffs.map(({ new_path, old_path }) => new_path || old_path);
135
+ const reviewerWorkload = mergeRequests.reduce((result, { reviewers }) => {
136
+ reviewers?.forEach(({ username }) => {
137
+ const reviewer = username.toLowerCase();
138
+ result[reviewer] = (result[reviewer] || 0) + 1;
139
+ });
140
+ return result;
141
+ }, {});
142
+ // Filter and process reviewers
143
+ const contributorReviewers = contributors.filter(({ username }) => username && !blacklist.includes(username));
144
+ const authorContributor = whitelist.find((c) => c.username === author);
145
+ const authorTeams = authorContributor?.teams?.map((t) => t.name) || [];
146
+ const teamMembers = authorTeams.map((team) => ({
147
+ team,
148
+ members: whitelist
149
+ .filter(({ teams }) => teams?.some((t) => t.name === team))
150
+ .map(({ username }) => username),
151
+ }));
152
+ const filteredTeamMembers = teamMembers
153
+ .map(({ team, members }) => ({
154
+ team,
155
+ members: members.filter((member) => member !== null && !blacklist.includes(member)),
156
+ }))
157
+ .filter(({ members }) => members.length > 0);
158
+ const teamMemberReviewers = this.teamMembersService.applyTeamLoadBalancing(filteredTeamMembers, whitelist, reviewerWorkload);
159
+ const codeOwnerMatches = this.codeOwnersService.matchCodeOwners(changedFiles, codeOwnerRules);
160
+ const filteredCodeOwners = codeOwnerMatches
161
+ .map((group) => ({
162
+ ...group,
163
+ owners: group.owners.filter((owner) => !blacklist.includes(owner)),
164
+ }))
165
+ .filter(({ owners }) => owners.length > 0);
166
+ const codeOwnerReviewers = this.codeOwnersService.applyLoadBalancing(filteredCodeOwners, whitelist, reviewerWorkload);
167
+ const comment = this.commentBuilderService.buildReviewerAssignmentComment({
168
+ projectId: project,
169
+ base,
170
+ contributors: contributorReviewers,
171
+ teamMembers: teamMemberReviewers,
172
+ codeOwners: codeOwnerReviewers,
173
+ });
174
+ this.logger.info('Reviewer suggestion completed', {
175
+ project,
176
+ mr,
177
+ contributorCount: contributorReviewers.length,
178
+ teamMemberCount: teamMemberReviewers.length,
179
+ codeOwnerCount: codeOwnerReviewers.length,
180
+ });
181
+ return {
182
+ comment,
183
+ contributors: contributorReviewers.map((c) => this.buildContributorSuggestion(c, reviewerWorkload, whitelist)),
184
+ teamMembers: teamMemberReviewers.map((t) => this.buildTeamMemberSuggestion(t, reviewerWorkload, whitelist)),
185
+ codeOwners: codeOwnerReviewers.flatMap((co) => co.owners.map((owner) => this.buildCodeOwnerSuggestion(owner, co, reviewerWorkload, whitelist))),
186
+ };
187
+ }
188
+ finally {
189
+ endTimer();
190
+ }
191
+ }
192
+ /**
193
+ * Analyzes contributors for a merge request without suggesting reviewers.
194
+ *
195
+ * Provides detailed contributor analysis including which files were changed
196
+ * and which previous contributors modified those files. Useful for
197
+ * understanding contribution patterns without generating reviewer suggestions.
198
+ *
199
+ * @param params - Parameters including project and merge request IID
200
+ * @returns Changed files and contributors with their modification details
201
+ * @throws Error if merge request is not found or has no diffs
202
+ */
203
+ async getContributorAnalysis(params) {
204
+ const endTimer = this.logger.startTimer('getContributorAnalysis');
205
+ try {
206
+ const project = String(params.project);
207
+ const mr = params.mergeRequestIid;
208
+ // Fetch merge request and diffs in parallel
209
+ const [mergeRequest, diffs] = await Promise.all([
210
+ this.dataSource.getMergeRequest(project, mr),
211
+ this.fetchWithCache(`diffs:${project}:${mr}`, () => this.dataSource.getDiffs(project, mr)),
212
+ ]);
213
+ if (!mergeRequest) {
214
+ throw new Error(`Merge request ${mr} not found in project ${project}`);
215
+ }
216
+ if (!mergeRequest.diff_refs) {
217
+ throw new Error(`Merge request ${mr} has no diff refs`);
218
+ }
219
+ const base = mergeRequest.diff_refs.base_sha;
220
+ const head = mergeRequest.diff_refs.head_sha;
221
+ // Fetch whitelist and contributors in parallel
222
+ const [whitelist, changedFiles] = await Promise.all([
223
+ this.fetchWithCache(`whitelist:${project}:${head}`, () => this.whitelistService.getWhitelist(project, head)),
224
+ Promise.resolve(diffs.map(({ new_path, old_path }) => new_path || old_path)),
225
+ ]);
226
+ const contributors = await this.fetchWithCache(`contributors:${project}:${mr}:${base}`, () => this.contributorsService.getContributors(project, mr, base, whitelist));
227
+ this.logger.info('Contributor analysis completed', {
228
+ project,
229
+ mr,
230
+ contributorCount: contributors.length,
231
+ fileCount: changedFiles.length,
232
+ });
233
+ return {
234
+ changedFiles,
235
+ contributors,
236
+ };
237
+ }
238
+ finally {
239
+ endTimer();
240
+ }
241
+ }
242
+ /**
243
+ * Analyzes current reviewer workload across all open merge requests.
244
+ *
245
+ * Calculates how many open merge requests each reviewer is currently
246
+ * assigned to, useful for understanding team capacity and load distribution.
247
+ *
248
+ * @param params - Parameters including project identifier
249
+ * @returns Array of reviewers with their current open MR counts
250
+ */
251
+ async getReviewerWorkload(params) {
252
+ const endTimer = this.logger.startTimer('getReviewerWorkload');
253
+ try {
254
+ const project = String(params.project);
255
+ const mergeRequests = await this.fetchWithCache(`merge-requests:${project}`, () => this.dataSource.getMergeRequests(project));
256
+ const workload = mergeRequests.reduce((result, { reviewers }) => {
257
+ reviewers?.forEach(({ username }) => {
258
+ const reviewer = username.toLowerCase();
259
+ result[reviewer] = (result[reviewer] || 0) + 1;
260
+ });
261
+ return result;
262
+ }, {});
263
+ this.logger.info('Workload analysis completed', {
264
+ project,
265
+ reviewerCount: Object.keys(workload).length,
266
+ });
267
+ return {
268
+ reviewers: Object.entries(workload).map(([username, count]) => ({
269
+ username,
270
+ openMRs: count,
271
+ })),
272
+ };
273
+ }
274
+ finally {
275
+ endTimer();
276
+ }
277
+ }
278
+ /**
279
+ * Posts a comment to a GitLab merge request.
280
+ *
281
+ * @param params - Parameters including project, MR IID or branch, and comment text
282
+ * @returns The created note with ID and body
283
+ * @throws Error if project/MR cannot be resolved
284
+ */
285
+ async postComment(params) {
286
+ const endTimer = this.logger.startTimer('postComment');
287
+ try {
288
+ let project;
289
+ if (params.project) {
290
+ project = String(params.project);
291
+ }
292
+ else if (this.dataSource instanceof LocalGitDataSource) {
293
+ project = await this.dataSource.getProjectFromRemote();
294
+ }
295
+ else {
296
+ throw new Error("Either 'project' parameter or 'repoPath' parameter is required. Provide 'repoPath' to use local git mode.");
297
+ }
298
+ let mr;
299
+ if (params.mergeRequestIid) {
300
+ mr = params.mergeRequestIid;
301
+ }
302
+ else {
303
+ const branch = params.branch ||
304
+ (this.dataSource.getCurrentBranch
305
+ ? await this.dataSource.getCurrentBranch()
306
+ : undefined);
307
+ if (!branch)
308
+ throw new Error("Either mergeRequestIid or branch must be provided");
309
+ const mergeRequest = await this.dataSource.getMergeRequestByBranch(project, branch);
310
+ if (!mergeRequest) {
311
+ throw new Error(`No merge request found for branch ${branch} in project ${project}`);
312
+ }
313
+ mr = mergeRequest.iid;
314
+ }
315
+ const result = await this.dataSource.createNote(project, mr, params.comment);
316
+ this.logger.info('Comment posted successfully', {
317
+ project,
318
+ mr,
319
+ noteId: result.id,
320
+ });
321
+ return result;
322
+ }
323
+ finally {
324
+ endTimer();
325
+ }
326
+ }
327
+ /**
328
+ * Suggests reviewers and posts the suggestion comment to the merge request.
329
+ *
330
+ * Combines suggestReviewers() and postComment() into a single operation.
331
+ *
332
+ * @param params - Parameters including project, MR IID or branch
333
+ * @returns Reviewer suggestions with formatted comment (same as suggestReviewers)
334
+ * @throws Error if project/MR cannot be resolved or comment posting fails
335
+ */
336
+ async inviteReviewers(params) {
337
+ const endTimer = this.logger.startTimer('inviteReviewers');
338
+ try {
339
+ const result = await this.suggestReviewers(params);
340
+ await this.postComment({ ...params, comment: result.comment });
341
+ this.logger.info('Reviewers invited successfully', {
342
+ project: params.project ? String(params.project) : undefined,
343
+ mr: params.mergeRequestIid,
344
+ });
345
+ return result;
346
+ }
347
+ finally {
348
+ endTimer();
349
+ }
350
+ }
351
+ /**
352
+ * Helper method to fetch data with optional caching
353
+ * If cache is not available, directly executes the operation
354
+ */
355
+ async fetchWithCache(key, operation) {
356
+ if (!this.cache) {
357
+ return operation();
358
+ }
359
+ return this.cache.wrap(key, operation);
360
+ }
361
+ /**
362
+ * Extract result from Promise.allSettled with partial failure handling
363
+ * If the promise failed and a fallback is provided, returns the fallback
364
+ * If the promise failed and no fallback is provided, throws the error
365
+ */
366
+ extractResult(result, operationName, fallback) {
367
+ if (result.status === 'fulfilled') {
368
+ return result.value;
369
+ }
370
+ // Promise was rejected
371
+ this.logger.warn(`Failed to fetch ${operationName}`, {
372
+ operation: operationName,
373
+ error: result.reason?.message || String(result.reason),
374
+ });
375
+ if (fallback !== undefined) {
376
+ this.logger.info(`Using fallback for ${operationName}`, {
377
+ operation: operationName,
378
+ });
379
+ return fallback;
380
+ }
381
+ // No fallback provided, throw the error
382
+ throw result.reason;
383
+ }
384
+ /**
385
+ * Build a detailed contributor suggestion with transparency information
386
+ */
387
+ buildContributorSuggestion(contributor, workload, whitelist) {
388
+ const username = contributor.username;
389
+ const currentWorkload = workload[username] || 0;
390
+ const whitelistEntry = whitelist.find((c) => c.username === username);
391
+ const fte = whitelistEntry?.fte || 1;
392
+ const capacity = fte * 10; // Assume 10 MRs per FTE as capacity
393
+ const utilizationPercent = capacity > 0 ? Math.round((currentWorkload / capacity) * 100) : 0;
394
+ const filesModified = contributor.changes?.length || 0;
395
+ // Calculate contribution score (0-100 based on files modified)
396
+ const contributionScore = Math.min(100, filesModified * 10);
397
+ // Calculate availability score (0-100, inverse of utilization)
398
+ const availabilityScore = Math.max(0, 100 - utilizationPercent);
399
+ const totalScore = (contributionScore * 0.7) + (availabilityScore * 0.3);
400
+ const factors = [
401
+ {
402
+ type: 'contribution',
403
+ description: `Modified ${filesModified} file(s) in the changed areas`,
404
+ weight: 0.7,
405
+ },
406
+ {
407
+ type: 'availability',
408
+ description: `Current workload: ${currentWorkload} MR(s), ${utilizationPercent}% utilized`,
409
+ weight: 0.3,
410
+ },
411
+ ];
412
+ return {
413
+ username,
414
+ reason: `Modified ${filesModified} file(s)`,
415
+ workload: currentWorkload,
416
+ fte,
417
+ score: {
418
+ total: Math.round(totalScore),
419
+ breakdown: {
420
+ contribution: Math.round(contributionScore),
421
+ codeOwnership: 0,
422
+ teamMembership: 0,
423
+ availability: Math.round(availabilityScore),
424
+ },
425
+ },
426
+ reasoning: {
427
+ primary: `${username} has previously contributed to ${filesModified} of the modified files`,
428
+ factors,
429
+ },
430
+ workloadDetails: {
431
+ current: currentWorkload,
432
+ capacity,
433
+ utilizationPercent,
434
+ },
435
+ };
436
+ }
437
+ /**
438
+ * Build a detailed team member suggestion with transparency information
439
+ */
440
+ buildTeamMemberSuggestion(team, workload, _whitelist) {
441
+ const username = team.members[0];
442
+ const currentWorkload = workload[username] || 0;
443
+ const memberScore = team.memberScores?.[0];
444
+ const fte = memberScore?.fte || 1;
445
+ const capacity = fte * 10; // Assume 10 MRs per FTE as capacity
446
+ const utilizationPercent = capacity > 0 ? Math.round((currentWorkload / capacity) * 100) : 0;
447
+ // Calculate team membership score (100 for team member)
448
+ const teamMembershipScore = 100;
449
+ // Calculate availability score (0-100, inverse of utilization)
450
+ const availabilityScore = Math.max(0, 100 - utilizationPercent);
451
+ const totalScore = (teamMembershipScore * 0.5) + (availabilityScore * 0.5);
452
+ const loadBalancingExplanation = team.loadBalanced
453
+ ? `Selected via load balancing: ${username} has the lowest workload ratio (${memberScore?.ratio?.toFixed(2) || 'N/A'}) among team members`
454
+ : `Selected as team member`;
455
+ const fteImpact = fte !== 1
456
+ ? `FTE of ${fte} considered in load balancing calculation`
457
+ : 'Full-time availability (FTE: 1.0)';
458
+ const factors = [
459
+ {
460
+ type: 'team',
461
+ description: `Member of Team::${team.team}`,
462
+ weight: 0.5,
463
+ },
464
+ {
465
+ type: 'availability',
466
+ description: `${loadBalancingExplanation}. ${fteImpact}`,
467
+ weight: 0.5,
468
+ },
469
+ ];
470
+ return {
471
+ username,
472
+ reason: `Team ${team.team} member with lowest workload`,
473
+ workload: currentWorkload,
474
+ fte,
475
+ score: {
476
+ total: Math.round(totalScore),
477
+ breakdown: {
478
+ contribution: 0,
479
+ codeOwnership: 0,
480
+ teamMembership: Math.round(teamMembershipScore),
481
+ availability: Math.round(availabilityScore),
482
+ },
483
+ },
484
+ reasoning: {
485
+ primary: `${username} is a member of Team::${team.team} with the lowest current workload`,
486
+ factors,
487
+ },
488
+ workloadDetails: {
489
+ current: currentWorkload,
490
+ capacity,
491
+ utilizationPercent,
492
+ },
493
+ };
494
+ }
495
+ /**
496
+ * Build a detailed code owner suggestion with transparency information
497
+ */
498
+ buildCodeOwnerSuggestion(owner, codeOwnerGroup, workload, whitelist) {
499
+ const currentWorkload = workload[owner] || 0;
500
+ const whitelistEntry = whitelist.find((c) => c.username === owner);
501
+ const fte = whitelistEntry?.fte || 1;
502
+ const capacity = fte * 10; // Assume 10 MRs per FTE as capacity
503
+ const utilizationPercent = capacity > 0 ? Math.round((currentWorkload / capacity) * 100) : 0;
504
+ // Calculate code ownership score (100 for code owner)
505
+ const codeOwnershipScore = 100;
506
+ // Calculate availability score (0-100, inverse of utilization)
507
+ const availabilityScore = Math.max(0, 100 - utilizationPercent);
508
+ const totalScore = (codeOwnershipScore * 0.6) + (availabilityScore * 0.4);
509
+ const ownerScore = codeOwnerGroup.ownerScores?.find((s) => s.username === owner);
510
+ const loadBalancingExplanation = codeOwnerGroup.loadBalanced && ownerScore
511
+ ? `Selected via load balancing: ratio ${ownerScore.ratio.toFixed(2)} (${ownerScore.assignedMRs} MRs / ${ownerScore.fte} FTE)`
512
+ : 'Designated code owner';
513
+ const fteImpact = fte !== 1
514
+ ? `FTE of ${fte} reduces effective capacity and was considered in selection`
515
+ : 'Full-time availability (FTE: 1.0)';
516
+ const factors = [
517
+ {
518
+ type: 'ownership',
519
+ description: `Code owner for ${codeOwnerGroup.sectionName} section (${codeOwnerGroup.files.length} file(s))`,
520
+ weight: 0.6,
521
+ },
522
+ {
523
+ type: 'availability',
524
+ description: `${loadBalancingExplanation}. ${fteImpact}`,
525
+ weight: 0.4,
526
+ },
527
+ ];
528
+ return {
529
+ username: owner,
530
+ reason: `Code owner for ${codeOwnerGroup.sectionName}`,
531
+ workload: currentWorkload,
532
+ fte,
533
+ score: {
534
+ total: Math.round(totalScore),
535
+ breakdown: {
536
+ contribution: 0,
537
+ codeOwnership: Math.round(codeOwnershipScore),
538
+ teamMembership: 0,
539
+ availability: Math.round(availabilityScore),
540
+ },
541
+ },
542
+ reasoning: {
543
+ primary: `${owner} is a designated code owner for the ${codeOwnerGroup.sectionName} section`,
544
+ factors,
545
+ },
546
+ workloadDetails: {
547
+ current: currentWorkload,
548
+ capacity,
549
+ utilizationPercent,
550
+ },
551
+ };
552
+ }
553
+ }
554
+ //# sourceMappingURL=reviewer-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reviewer-service.js","sourceRoot":"","sources":["../../src/services/reviewer-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import { Contributor } from "../types.js";
2
+ import { LoadBalancedTeam } from "../types/index.js";
3
+ interface TeamInput {
4
+ team: string;
5
+ members: string[];
6
+ }
7
+ /**
8
+ * Service for applying load balancing to team member reviewer assignments.
9
+ *
10
+ * Distributes review assignments among team members based on their FTE
11
+ * (Full-Time Equivalent) and current workload to ensure fair distribution.
12
+ */
13
+ export declare class TeamMembersService {
14
+ /**
15
+ * Applies load balancing to team member assignments.
16
+ *
17
+ * Calculates workload ratios for each team member based on their assigned
18
+ * merge requests and FTE, then sorts members by ratio to prioritize those
19
+ * with lower workload.
20
+ *
21
+ * @param teamMembers - Array of teams with their member lists
22
+ * @param contributors - Array of all contributors with FTE information
23
+ * @param workload - Map of usernames to their current open MR counts
24
+ * @returns Array of teams with load-balanced member ordering and scores
25
+ */
26
+ applyTeamLoadBalancing(teamMembers: TeamInput[], contributors: Contributor[], workload: Record<string, number>): LoadBalancedTeam[];
27
+ }
28
+ export {};
29
+ //# sourceMappingURL=team-members.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"team-members.service.d.ts","sourceRoot":"","sources":["../../src/services/team-members.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAuB,MAAM,mBAAmB,CAAC;AAE1E,UAAU,SAAS;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;;GAKG;AACH,qBAAa,kBAAkB;IAC7B;;;;;;;;;;;OAWG;IACH,sBAAsB,CACpB,WAAW,EAAE,SAAS,EAAE,EACxB,YAAY,EAAE,WAAW,EAAE,EAC3B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,gBAAgB,EAAE;CA4BtB"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Service for applying load balancing to team member reviewer assignments.
3
+ *
4
+ * Distributes review assignments among team members based on their FTE
5
+ * (Full-Time Equivalent) and current workload to ensure fair distribution.
6
+ */
7
+ export class TeamMembersService {
8
+ /**
9
+ * Applies load balancing to team member assignments.
10
+ *
11
+ * Calculates workload ratios for each team member based on their assigned
12
+ * merge requests and FTE, then sorts members by ratio to prioritize those
13
+ * with lower workload.
14
+ *
15
+ * @param teamMembers - Array of teams with their member lists
16
+ * @param contributors - Array of all contributors with FTE information
17
+ * @param workload - Map of usernames to their current open MR counts
18
+ * @returns Array of teams with load-balanced member ordering and scores
19
+ */
20
+ applyTeamLoadBalancing(teamMembers, contributors, workload) {
21
+ return teamMembers.map(({ team, members }) => {
22
+ const memberScores = members.map((username) => {
23
+ const contributor = contributors.find((c) => c.username === username);
24
+ if (!contributor) {
25
+ return { username, ratio: 999, fte: 0, score: 0 };
26
+ }
27
+ const assignedMRs = workload[username] || 0;
28
+ const teamData = contributor.teams?.find((t) => t.name === team);
29
+ const relevantFte = teamData?.fte || 0;
30
+ const ratio = relevantFte > 0 ? assignedMRs / relevantFte : 999;
31
+ return { username, ratio, fte: relevantFte, score: assignedMRs };
32
+ });
33
+ const sortedMembers = memberScores
34
+ .sort((a, b) => a.ratio - b.ratio)
35
+ .map((m) => m.username);
36
+ return {
37
+ team,
38
+ members: sortedMembers,
39
+ loadBalanced: true,
40
+ memberScores: memberScores.sort((a, b) => a.ratio - b.ratio),
41
+ };
42
+ });
43
+ }
44
+ }
45
+ //# sourceMappingURL=team-members.service.js.map