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.
- package/LICENSE +21 -0
- package/README.md +1878 -0
- package/dist/api/gitlab-api.d.ts +136 -0
- package/dist/api/gitlab-api.d.ts.map +1 -0
- package/dist/api/gitlab-api.js +334 -0
- package/dist/api/gitlab-api.js.map +1 -0
- package/dist/bin/cli.d.ts +10 -0
- package/dist/bin/cli.d.ts.map +1 -0
- package/dist/bin/cli.js +186 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/bin/deprecated-mcp.d.ts +12 -0
- package/dist/bin/deprecated-mcp.d.ts.map +1 -0
- package/dist/bin/deprecated-mcp.js +73 -0
- package/dist/bin/deprecated-mcp.js.map +1 -0
- package/dist/bin/index.d.ts +18 -0
- package/dist/bin/index.d.ts.map +1 -0
- package/dist/bin/index.js +78 -0
- package/dist/bin/index.js.map +1 -0
- package/dist/bin/mcp.d.ts +11 -0
- package/dist/bin/mcp.d.ts.map +1 -0
- package/dist/bin/mcp.js +43 -0
- package/dist/bin/mcp.js.map +1 -0
- package/dist/cache/cache.service.d.ts +113 -0
- package/dist/cache/cache.service.d.ts.map +1 -0
- package/dist/cache/cache.service.js +213 -0
- package/dist/cache/cache.service.js.map +1 -0
- package/dist/cli/commands.d.ts +40 -0
- package/dist/cli/commands.d.ts.map +1 -0
- package/dist/cli/commands.js +142 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/output.d.ts +24 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +143 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/config/config.service.d.ts +89 -0
- package/dist/config/config.service.d.ts.map +1 -0
- package/dist/config/config.service.js +169 -0
- package/dist/config/config.service.js.map +1 -0
- package/dist/datasources/git-data-source.interface.d.ts +140 -0
- package/dist/datasources/git-data-source.interface.d.ts.map +1 -0
- package/dist/datasources/git-data-source.interface.js +2 -0
- package/dist/datasources/git-data-source.interface.js.map +1 -0
- package/dist/datasources/gitlab-api-data-source.d.ts +127 -0
- package/dist/datasources/gitlab-api-data-source.d.ts.map +1 -0
- package/dist/datasources/gitlab-api-data-source.js +248 -0
- package/dist/datasources/gitlab-api-data-source.js.map +1 -0
- package/dist/datasources/local-git-data-source.d.ts +124 -0
- package/dist/datasources/local-git-data-source.d.ts.map +1 -0
- package/dist/datasources/local-git-data-source.js +580 -0
- package/dist/datasources/local-git-data-source.js.map +1 -0
- package/dist/errors/error-handler.d.ts +113 -0
- package/dist/errors/error-handler.d.ts.map +1 -0
- package/dist/errors/error-handler.js +230 -0
- package/dist/errors/error-handler.js.map +1 -0
- package/dist/index.d.ts +139 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +139 -0
- package/dist/index.js.map +1 -0
- package/dist/logging/example.d.ts +15 -0
- package/dist/logging/example.d.ts.map +1 -0
- package/dist/logging/example.js +79 -0
- package/dist/logging/example.js.map +1 -0
- package/dist/logging/index.d.ts +7 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +7 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/logger.service.d.ts +98 -0
- package/dist/logging/logger.service.d.ts.map +1 -0
- package/dist/logging/logger.service.js +160 -0
- package/dist/logging/logger.service.js.map +1 -0
- package/dist/mcp/server.d.ts +67 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +213 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +22 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +176 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/services/blacklist.service.d.ts +32 -0
- package/dist/services/blacklist.service.d.ts.map +1 -0
- package/dist/services/blacklist.service.js +59 -0
- package/dist/services/blacklist.service.js.map +1 -0
- package/dist/services/codeowners.service.d.ts +45 -0
- package/dist/services/codeowners.service.d.ts.map +1 -0
- package/dist/services/codeowners.service.js +200 -0
- package/dist/services/codeowners.service.js.map +1 -0
- package/dist/services/comment-builder.service.d.ts +48 -0
- package/dist/services/comment-builder.service.d.ts.map +1 -0
- package/dist/services/comment-builder.service.js +61 -0
- package/dist/services/comment-builder.service.js.map +1 -0
- package/dist/services/contributors.service.d.ts +52 -0
- package/dist/services/contributors.service.d.ts.map +1 -0
- package/dist/services/contributors.service.js +144 -0
- package/dist/services/contributors.service.js.map +1 -0
- package/dist/services/reviewer-service.d.ts +125 -0
- package/dist/services/reviewer-service.d.ts.map +1 -0
- package/dist/services/reviewer-service.js +554 -0
- package/dist/services/reviewer-service.js.map +1 -0
- package/dist/services/team-members.service.d.ts +29 -0
- package/dist/services/team-members.service.d.ts.map +1 -0
- package/dist/services/team-members.service.js +45 -0
- package/dist/services/team-members.service.js.map +1 -0
- package/dist/services/whitelist.service.d.ts +31 -0
- package/dist/services/whitelist.service.d.ts.map +1 -0
- package/dist/services/whitelist.service.js +51 -0
- package/dist/services/whitelist.service.js.map +1 -0
- package/dist/tools.d.ts +22 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +176 -0
- package/dist/tools.js.map +1 -0
- package/dist/types/index.d.ts +502 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +91 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types.d.ts +219 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- 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
|