plugin-git-manager 1.1.12 → 1.2.1
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/dist/client/187.d5545b7cc8b90bfc.js +0 -9
- package/dist/client/228.7588a0707cb3694a.js +0 -9
- package/dist/client/index.js +0 -9
- package/dist/externalVersion.js +5 -14
- package/dist/index.js +0 -9
- package/dist/server/actions/git-actions.js +0 -9
- package/dist/server/actions/gitlab-api.js +0 -9
- package/dist/server/actions/poller.js +0 -9
- package/dist/server/actions/review.js +302 -75
- package/dist/server/ai-tools.js +0 -9
- package/dist/server/collections/gitCodeReviews.js +0 -9
- package/dist/server/collections/gitRepositories.js +0 -9
- package/dist/server/collections/gitReviewFlows.js +0 -9
- package/dist/server/index.js +0 -9
- package/dist/server/migrations/20260508000000-add-auto-review-flow-id.js +0 -9
- package/dist/server/plugin.js +0 -9
- package/dist/server/poller.js +0 -9
- package/dist/server/utils/gitlab-url.js +0 -9
- package/dist/server/utils/redact.js +0 -9
- package/package.json +2 -2
- package/src/server/actions/git-actions.ts +90 -42
- package/src/server/actions/review.ts +332 -72
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This file is part of the NocoBase (R) project.
|
|
3
|
-
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
-
* Authors: NocoBase Team.
|
|
5
|
-
*
|
|
6
|
-
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
-
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
1
|
var __defProp = Object.defineProperty;
|
|
11
2
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -50,6 +41,17 @@ const REVIEW_QUEUE_TIMEOUT_MS = Math.max(
|
|
|
50
41
|
6e4,
|
|
51
42
|
Number.parseInt(process.env.GIT_REVIEW_QUEUE_TIMEOUT_MS || "", 10) || 10 * 60 * 1e3
|
|
52
43
|
);
|
|
44
|
+
const REVIEW_QUEUE_POLL_INTERVAL_MS = Math.max(
|
|
45
|
+
1e3,
|
|
46
|
+
Number.parseInt(process.env.GIT_REVIEW_QUEUE_POLL_INTERVAL_MS || "", 10) || 5e3
|
|
47
|
+
);
|
|
48
|
+
const REVIEW_PROCESS_LOCK_TTL_MS = Math.max(REVIEW_QUEUE_TIMEOUT_MS + 6e4, 11 * 60 * 1e3);
|
|
49
|
+
const REVIEW_QUEUE_WAKE_CHANNEL = "plugin-git-manager.review.wake";
|
|
50
|
+
const REVIEW_QUEUE_REDIS_CONNECTION = "plugin-git-manager.review.queue";
|
|
51
|
+
let reviewQueueTimer = null;
|
|
52
|
+
let reviewQueueKickTimer = null;
|
|
53
|
+
let reviewQueueProcessing = false;
|
|
54
|
+
let reviewWakeHandler = null;
|
|
53
55
|
function getActionParams(ctx) {
|
|
54
56
|
var _a, _b;
|
|
55
57
|
return { ...ctx.action.params, ...(_a = ctx.action.params) == null ? void 0 : _a.values, ...((_b = ctx.request) == null ? void 0 : _b.body) || {} };
|
|
@@ -67,15 +69,7 @@ async function withTriggerLock(app, key, fn) {
|
|
|
67
69
|
async function triggerReview(ctx, next) {
|
|
68
70
|
var _a, _b;
|
|
69
71
|
const params = getActionParams(ctx);
|
|
70
|
-
const {
|
|
71
|
-
flowId,
|
|
72
|
-
repositoryId,
|
|
73
|
-
targetType,
|
|
74
|
-
mrIid,
|
|
75
|
-
commitSha,
|
|
76
|
-
branch,
|
|
77
|
-
extraInstructions
|
|
78
|
-
} = params;
|
|
72
|
+
const { flowId, repositoryId, targetType, mrIid, commitSha, branch, extraInstructions } = params;
|
|
79
73
|
if (!repositoryId) ctx.throw(400, "repositoryId is required");
|
|
80
74
|
if (!targetType) ctx.throw(400, "targetType is required");
|
|
81
75
|
if (!["mr", "commit", "branch"].includes(targetType)) ctx.throw(400, "invalid targetType");
|
|
@@ -156,12 +150,19 @@ async function triggerReviewInternalLocked(app, args) {
|
|
|
156
150
|
triggeredBy: args.triggeredBy || "manual",
|
|
157
151
|
status: "pending",
|
|
158
152
|
// `startedAt` is stamped by the queue worker when execution actually
|
|
159
|
-
// starts. Pending rows
|
|
153
|
+
// starts. Pending rows are durable queue items for worker polling.
|
|
160
154
|
startedAt: null,
|
|
161
155
|
finishedAt: null,
|
|
162
156
|
durationMs: null,
|
|
163
157
|
postStatus: getInitialPostStatus(flow, args.targetType),
|
|
164
|
-
error: null
|
|
158
|
+
error: null,
|
|
159
|
+
metadata: {
|
|
160
|
+
queuedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
161
|
+
aiEmployeeUsername,
|
|
162
|
+
extraInstructions: args.extraInstructions || null,
|
|
163
|
+
userId: args.userId ?? null,
|
|
164
|
+
flowSnapshot: createFlowSnapshot(flow)
|
|
165
|
+
}
|
|
165
166
|
};
|
|
166
167
|
let reviewId;
|
|
167
168
|
if (existing) {
|
|
@@ -201,9 +202,14 @@ function registerReviewQueue(app) {
|
|
|
201
202
|
await processQueuedReview(app, message);
|
|
202
203
|
}
|
|
203
204
|
});
|
|
205
|
+
if (!isGitReviewWorker(app)) {
|
|
206
|
+
app.on("afterStart", () => clearLocalReviewMemoryQueue(app));
|
|
207
|
+
}
|
|
208
|
+
startReviewQueueProcessor(app);
|
|
204
209
|
}
|
|
205
210
|
function unregisterReviewQueue(app) {
|
|
206
211
|
app.eventQueue.unsubscribe(REVIEW_QUEUE_CHANNEL);
|
|
212
|
+
stopReviewQueueProcessor(app);
|
|
207
213
|
}
|
|
208
214
|
function createFlowSnapshot(flow) {
|
|
209
215
|
return {
|
|
@@ -230,12 +236,215 @@ function isGitReviewWorker(app) {
|
|
|
230
236
|
const workerMode = process.env.WORKER_MODE || "";
|
|
231
237
|
return app.serving(WORKER_JOB_GIT_REVIEW_PROCESS) || workerMode === "worker" || workerMode === "task" || process.env.APP_ROLE === "worker";
|
|
232
238
|
}
|
|
233
|
-
|
|
239
|
+
function clearLocalReviewMemoryQueue(app) {
|
|
240
|
+
var _a, _b, _c, _d, _e;
|
|
241
|
+
const eventQueue = app.eventQueue;
|
|
242
|
+
const adapter = eventQueue == null ? void 0 : eventQueue.adapter;
|
|
243
|
+
const fullChannel = (_a = eventQueue == null ? void 0 : eventQueue.getFullChannel) == null ? void 0 : _a.call(eventQueue, REVIEW_QUEUE_CHANNEL);
|
|
244
|
+
const queue = fullChannel ? (_c = (_b = adapter == null ? void 0 : adapter.queues) == null ? void 0 : _b.get) == null ? void 0 : _c.call(_b, fullChannel) : null;
|
|
245
|
+
if (!(queue == null ? void 0 : queue.length)) return;
|
|
246
|
+
adapter.queues.set(fullChannel, []);
|
|
247
|
+
(_e = (_d = app.log) == null ? void 0 : _d.warn) == null ? void 0 : _e.call(
|
|
248
|
+
_d,
|
|
249
|
+
`git review queue: cleared ${queue.length} stale local memory message(s) on non-worker node; pending DB rows will be picked up by workers`
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
function getReviewQueueRedisKey(app) {
|
|
253
|
+
const appName = app.name || process.env.APP_NAME || "main";
|
|
254
|
+
return `${appName}:plugin-git-manager:review:queue`;
|
|
255
|
+
}
|
|
256
|
+
async function getReviewQueueRedis(app) {
|
|
257
|
+
var _a, _b;
|
|
258
|
+
const manager = app.redisConnectionManager;
|
|
259
|
+
if (!(manager == null ? void 0 : manager.getConnectionSync)) {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
234
262
|
try {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
263
|
+
const connectionString = process.env.QUEUE_ADAPTER_REDIS_URL || process.env.REDIS_URL;
|
|
264
|
+
return await manager.getConnectionSync(
|
|
265
|
+
REVIEW_QUEUE_REDIS_CONNECTION,
|
|
266
|
+
connectionString ? { connectionString } : void 0
|
|
267
|
+
);
|
|
268
|
+
} catch (err) {
|
|
269
|
+
(_b = (_a = app.log) == null ? void 0 : _a.debug) == null ? void 0 : _b.call(_a, `git review queue: Redis queue unavailable, falling back to DB polling: ${(err == null ? void 0 : err.message) || err}`);
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
async function enqueueReviewToRedis(app, message) {
|
|
274
|
+
var _a, _b;
|
|
275
|
+
const redis = await getReviewQueueRedis(app);
|
|
276
|
+
if (!redis) return false;
|
|
277
|
+
await redis.sendCommand(["RPUSH", getReviewQueueRedisKey(app), JSON.stringify(message)]);
|
|
278
|
+
(_b = (_a = app.log) == null ? void 0 : _a.debug) == null ? void 0 : _b.call(_a, `git review queue: enqueued review ${message.reviewId} to Redis`);
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
async function publishReviewQueueWake(app, reviewId) {
|
|
282
|
+
var _a, _b, _c, _d;
|
|
283
|
+
try {
|
|
284
|
+
await ((_b = (_a = app.pubSubManager) == null ? void 0 : _a.publish) == null ? void 0 : _b.call(
|
|
285
|
+
_a,
|
|
286
|
+
REVIEW_QUEUE_WAKE_CHANNEL,
|
|
287
|
+
{ reviewId },
|
|
288
|
+
{ skipSelf: !isGitReviewWorker(app) }
|
|
289
|
+
));
|
|
290
|
+
} catch (err) {
|
|
291
|
+
(_d = (_c = app.log) == null ? void 0 : _c.debug) == null ? void 0 : _d.call(_c, `git review queue: wake publish skipped: ${(err == null ? void 0 : err.message) || err}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function startReviewQueueProcessor(app) {
|
|
295
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
296
|
+
if (!isGitReviewWorker(app)) {
|
|
297
|
+
(_b = (_a = app.log) == null ? void 0 : _a.debug) == null ? void 0 : _b.call(_a, "plugin-git-manager: review queue processor disabled on non-worker node");
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (reviewQueueTimer) return;
|
|
301
|
+
reviewWakeHandler = async () => {
|
|
302
|
+
scheduleReviewQueueTick(app, 0);
|
|
303
|
+
};
|
|
304
|
+
const subscribe = (_d = (_c = app.pubSubManager) == null ? void 0 : _c.subscribe) == null ? void 0 : _d.call(_c, REVIEW_QUEUE_WAKE_CHANNEL, reviewWakeHandler);
|
|
305
|
+
if (subscribe == null ? void 0 : subscribe.catch) {
|
|
306
|
+
subscribe.catch((err) => {
|
|
307
|
+
var _a2, _b2;
|
|
308
|
+
return (_b2 = (_a2 = app.log) == null ? void 0 : _a2.debug) == null ? void 0 : _b2.call(_a2, `git review queue: wake subscribe skipped: ${(err == null ? void 0 : err.message) || err}`);
|
|
238
309
|
});
|
|
310
|
+
}
|
|
311
|
+
reviewQueueTimer = setInterval(() => scheduleReviewQueueTick(app, 0), REVIEW_QUEUE_POLL_INTERVAL_MS);
|
|
312
|
+
(_e = reviewQueueTimer.unref) == null ? void 0 : _e.call(reviewQueueTimer);
|
|
313
|
+
scheduleReviewQueueTick(app, 1e3);
|
|
314
|
+
(_g = (_f = app.log) == null ? void 0 : _f.info) == null ? void 0 : _g.call(_f, `plugin-git-manager: review queue processor started (interval ${REVIEW_QUEUE_POLL_INTERVAL_MS}ms)`);
|
|
315
|
+
}
|
|
316
|
+
function stopReviewQueueProcessor(app) {
|
|
317
|
+
var _a, _b;
|
|
318
|
+
if (reviewQueueTimer) {
|
|
319
|
+
clearInterval(reviewQueueTimer);
|
|
320
|
+
reviewQueueTimer = null;
|
|
321
|
+
}
|
|
322
|
+
if (reviewQueueKickTimer) {
|
|
323
|
+
clearTimeout(reviewQueueKickTimer);
|
|
324
|
+
reviewQueueKickTimer = null;
|
|
325
|
+
}
|
|
326
|
+
if (reviewWakeHandler) {
|
|
327
|
+
const unsubscribe = (_b = (_a = app.pubSubManager) == null ? void 0 : _a.unsubscribe) == null ? void 0 : _b.call(_a, REVIEW_QUEUE_WAKE_CHANNEL, reviewWakeHandler);
|
|
328
|
+
if (unsubscribe == null ? void 0 : unsubscribe.catch) {
|
|
329
|
+
unsubscribe.catch(() => void 0);
|
|
330
|
+
}
|
|
331
|
+
reviewWakeHandler = null;
|
|
332
|
+
}
|
|
333
|
+
reviewQueueProcessing = false;
|
|
334
|
+
}
|
|
335
|
+
function scheduleReviewQueueTick(app, delayMs) {
|
|
336
|
+
var _a;
|
|
337
|
+
if (reviewQueueKickTimer) return;
|
|
338
|
+
reviewQueueKickTimer = setTimeout(() => {
|
|
339
|
+
reviewQueueKickTimer = null;
|
|
340
|
+
runReviewQueueTick(app).catch((err) => {
|
|
341
|
+
var _a2, _b;
|
|
342
|
+
return (_b = (_a2 = app.log) == null ? void 0 : _a2.error) == null ? void 0 : _b.call(_a2, "git review queue: processor tick failed", err);
|
|
343
|
+
});
|
|
344
|
+
}, delayMs);
|
|
345
|
+
(_a = reviewQueueKickTimer.unref) == null ? void 0 : _a.call(reviewQueueKickTimer);
|
|
346
|
+
}
|
|
347
|
+
async function runReviewQueueTick(app) {
|
|
348
|
+
if (reviewQueueProcessing || !isGitReviewWorker(app)) return;
|
|
349
|
+
reviewQueueProcessing = true;
|
|
350
|
+
try {
|
|
351
|
+
const redisMessages = await drainRedisReviewQueue(app, REVIEW_QUEUE_CONCURRENCY);
|
|
352
|
+
await processReviewQueueMessages(app, redisMessages);
|
|
353
|
+
const remaining = Math.max(1, REVIEW_QUEUE_CONCURRENCY - redisMessages.length);
|
|
354
|
+
await processPendingReviews(app, remaining);
|
|
355
|
+
} finally {
|
|
356
|
+
reviewQueueProcessing = false;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
async function drainRedisReviewQueue(app, count) {
|
|
360
|
+
var _a, _b;
|
|
361
|
+
const redis = await getReviewQueueRedis(app);
|
|
362
|
+
if (!redis) return [];
|
|
363
|
+
const key = getReviewQueueRedisKey(app);
|
|
364
|
+
const messages = [];
|
|
365
|
+
for (let i = 0; i < count; i += 1) {
|
|
366
|
+
const raw = await redis.sendCommand(["LPOP", key]);
|
|
367
|
+
if (!raw) break;
|
|
368
|
+
try {
|
|
369
|
+
messages.push(JSON.parse(String(raw)));
|
|
370
|
+
} catch (err) {
|
|
371
|
+
(_b = (_a = app.log) == null ? void 0 : _a.warn) == null ? void 0 : _b.call(_a, `git review queue: dropped invalid Redis message: ${(err == null ? void 0 : err.message) || err}`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return messages;
|
|
375
|
+
}
|
|
376
|
+
function getQueuedReviewMetadata(review) {
|
|
377
|
+
var _a;
|
|
378
|
+
const raw = (_a = review == null ? void 0 : review.get) == null ? void 0 : _a.call(review, "metadata");
|
|
379
|
+
if (!raw) return {};
|
|
380
|
+
if (typeof raw === "string") {
|
|
381
|
+
try {
|
|
382
|
+
return JSON.parse(raw) || {};
|
|
383
|
+
} catch {
|
|
384
|
+
return {};
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return typeof raw === "object" ? raw : {};
|
|
388
|
+
}
|
|
389
|
+
function toNullableNumber(value) {
|
|
390
|
+
if (value === null || value === void 0 || value === "") return null;
|
|
391
|
+
const parsed = Number(value);
|
|
392
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
393
|
+
}
|
|
394
|
+
function createReviewQueueMessageFromReview(review) {
|
|
395
|
+
const metadata = getQueuedReviewMetadata(review);
|
|
396
|
+
const targetType = review.get("targetType");
|
|
397
|
+
return {
|
|
398
|
+
reviewId: Number(review.get("id")),
|
|
399
|
+
repositoryId: Number(review.get("repositoryId")),
|
|
400
|
+
targetType,
|
|
401
|
+
mrIid: targetType === "mr" ? toNullableNumber(review.get("mrIid")) : null,
|
|
402
|
+
commitSha: targetType === "commit" ? review.get("commitSha") : null,
|
|
403
|
+
branch: targetType === "branch" ? review.get("branch") : null,
|
|
404
|
+
headSha: review.get("headSha"),
|
|
405
|
+
aiEmployeeUsername: metadata.aiEmployeeUsername || "",
|
|
406
|
+
extraInstructions: metadata.extraInstructions || void 0,
|
|
407
|
+
userId: metadata.userId ?? null,
|
|
408
|
+
flowSnapshot: metadata.flowSnapshot
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
async function processPendingReviews(app, count) {
|
|
412
|
+
const pending = await app.db.getRepository("gitCodeReviews").find({
|
|
413
|
+
filter: { status: "pending" },
|
|
414
|
+
sort: ["createdAt"],
|
|
415
|
+
limit: count
|
|
416
|
+
});
|
|
417
|
+
if (!(pending == null ? void 0 : pending.length)) return;
|
|
418
|
+
await processReviewQueueMessages(app, pending.map(createReviewQueueMessageFromReview));
|
|
419
|
+
}
|
|
420
|
+
async function processReviewQueueMessages(app, messages) {
|
|
421
|
+
if (!messages.length) return;
|
|
422
|
+
await Promise.all(messages.map((message) => processQueuedReview(app, message)));
|
|
423
|
+
}
|
|
424
|
+
async function withReviewProcessLock(app, reviewId, fn) {
|
|
425
|
+
const lockKey = `git-review:process:${reviewId}`;
|
|
426
|
+
return app.lockManager.runExclusive(lockKey, fn, REVIEW_PROCESS_LOCK_TTL_MS);
|
|
427
|
+
}
|
|
428
|
+
async function enqueueReview(app, message) {
|
|
429
|
+
var _a, _b;
|
|
430
|
+
try {
|
|
431
|
+
const queuedInRedis = await enqueueReviewToRedis(app, message);
|
|
432
|
+
if (queuedInRedis) {
|
|
433
|
+
await publishReviewQueueWake(app, message.reviewId);
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
await publishReviewQueueWake(app, message.reviewId);
|
|
437
|
+
if (isGitReviewWorker(app)) {
|
|
438
|
+
await app.eventQueue.publish(REVIEW_QUEUE_CHANNEL, message, {
|
|
439
|
+
timeout: REVIEW_QUEUE_TIMEOUT_MS,
|
|
440
|
+
maxRetries: 1
|
|
441
|
+
});
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
(_b = (_a = app.log) == null ? void 0 : _a.warn) == null ? void 0 : _b.call(
|
|
445
|
+
_a,
|
|
446
|
+
`git review queue: Redis queue is unavailable; review ${message.reviewId} will remain pending until a worker DB poller picks it up`
|
|
447
|
+
);
|
|
239
448
|
} catch (err) {
|
|
240
449
|
const safeMessage = (0, import_redact.redactPat)((err == null ? void 0 : err.message) || String(err));
|
|
241
450
|
await app.db.getRepository("gitCodeReviews").update({
|
|
@@ -261,46 +470,51 @@ async function failQueuedReview(app, reviewId, err) {
|
|
|
261
470
|
});
|
|
262
471
|
}
|
|
263
472
|
async function processQueuedReview(app, message) {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
flow
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
473
|
+
await withReviewProcessLock(app, message.reviewId, async () => {
|
|
474
|
+
var _a, _b, _c, _d, _e, _f;
|
|
475
|
+
const db = app.db;
|
|
476
|
+
const reviewsRepo = db.getRepository("gitCodeReviews");
|
|
477
|
+
const review = await reviewsRepo.findOne({ filterByTk: message.reviewId });
|
|
478
|
+
if (!review) {
|
|
479
|
+
(_b = (_a = app.log) == null ? void 0 : _a.warn) == null ? void 0 : _b.call(_a, `git review queue: review ${message.reviewId} not found, skipping`);
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
if (review.get("status") !== "pending") {
|
|
483
|
+
(_d = (_c = app.log) == null ? void 0 : _c.info) == null ? void 0 : _d.call(_c, `git review queue: review ${message.reviewId} is ${review.get("status")}, skipping`);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
const metadata = getQueuedReviewMetadata(review);
|
|
487
|
+
const targetType = message.targetType || review.get("targetType");
|
|
488
|
+
try {
|
|
489
|
+
const repo = await db.getRepository("gitRepositories").findOne({
|
|
490
|
+
filterByTk: message.repositoryId || review.get("repositoryId")
|
|
491
|
+
});
|
|
492
|
+
if (!repo) throw new Error("Repository not found");
|
|
493
|
+
const flowSnapshot = message.flowSnapshot || metadata.flowSnapshot;
|
|
494
|
+
const storedFlow = await db.getRepository("gitReviewFlows").findOne({
|
|
495
|
+
filterByTk: (flowSnapshot == null ? void 0 : flowSnapshot.id) || review.get("flowId")
|
|
496
|
+
});
|
|
497
|
+
const flow = createFlowFromSnapshot(flowSnapshot, storedFlow);
|
|
498
|
+
const aiEmployeeUsername = message.aiEmployeeUsername || metadata.aiEmployeeUsername || flow.get("aiEmployeeUsername");
|
|
499
|
+
if (!aiEmployeeUsername) throw new Error("Flow has no AI employee configured");
|
|
500
|
+
await runReview(app, {
|
|
501
|
+
reviewId: message.reviewId,
|
|
502
|
+
flow,
|
|
503
|
+
repo,
|
|
504
|
+
targetType,
|
|
505
|
+
mrIid: targetType === "mr" ? message.mrIid ?? toNullableNumber(review.get("mrIid")) : null,
|
|
506
|
+
commitSha: targetType === "commit" ? message.commitSha || review.get("commitSha") : null,
|
|
507
|
+
branch: message.branch || review.get("branch"),
|
|
508
|
+
headSha: message.headSha || review.get("headSha"),
|
|
509
|
+
aiEmployeeUsername,
|
|
510
|
+
extraInstructions: message.extraInstructions ?? metadata.extraInstructions ?? void 0,
|
|
511
|
+
userId: message.userId ?? metadata.userId ?? null
|
|
512
|
+
});
|
|
513
|
+
} catch (err) {
|
|
514
|
+
(_f = (_e = app.log) == null ? void 0 : _e.error) == null ? void 0 : _f.call(_e, "git review queue: failed before review execution", err);
|
|
515
|
+
await failQueuedReview(app, message.reviewId, err);
|
|
516
|
+
}
|
|
517
|
+
});
|
|
304
518
|
}
|
|
305
519
|
async function reviewApprovePost(ctx, next) {
|
|
306
520
|
var _a, _b;
|
|
@@ -440,7 +654,8 @@ async function runReview(app, args) {
|
|
|
440
654
|
} catch {
|
|
441
655
|
}
|
|
442
656
|
}
|
|
443
|
-
if (!AIEmployee)
|
|
657
|
+
if (!AIEmployee)
|
|
658
|
+
throw new Error("AIEmployee class not found \u2014 plugin-ai may not be installed or its exports changed");
|
|
444
659
|
const llmService = args.flow.get("llmService");
|
|
445
660
|
const model = args.flow.get("model");
|
|
446
661
|
const modelRef = llmService && model ? { llmService, model } : void 0;
|
|
@@ -465,7 +680,7 @@ async function runReview(app, args) {
|
|
|
465
680
|
]
|
|
466
681
|
}),
|
|
467
682
|
new Promise(
|
|
468
|
-
(
|
|
683
|
+
(_resolve, reject) => setTimeout(() => reject(new Error("AI review timed out after 5 minutes")), REVIEW_TIMEOUT_MS)
|
|
469
684
|
)
|
|
470
685
|
]);
|
|
471
686
|
const content = extractLastAiMessageContent(result);
|
|
@@ -541,19 +756,29 @@ function buildReviewPrompt(args) {
|
|
|
541
756
|
lines.push("");
|
|
542
757
|
if (args.targetType === "mr") {
|
|
543
758
|
lines.push(`Target: Merge Request !${args.mrIid}.`);
|
|
544
|
-
lines.push(
|
|
759
|
+
lines.push(
|
|
760
|
+
`Use the \`git_get_merge_request\` tool with repositoryId=${args.repo.get("id")} and mrIid=${args.mrIid} to fetch the diff and metadata.`
|
|
761
|
+
);
|
|
545
762
|
lines.push("Optionally call `git_get_merge_request_notes` to avoid duplicating prior comments.");
|
|
546
763
|
} else if (args.targetType === "commit") {
|
|
547
764
|
lines.push(`Target: Commit ${args.commitSha}.`);
|
|
548
|
-
lines.push(
|
|
765
|
+
lines.push(
|
|
766
|
+
`Use the \`git_get_commit\` tool with repositoryId=${args.repo.get("id")} and commitHash=${args.commitSha} to fetch the diff.`
|
|
767
|
+
);
|
|
549
768
|
} else {
|
|
550
769
|
lines.push(`Target: Branch ${args.branch}.`);
|
|
551
|
-
lines.push(
|
|
770
|
+
lines.push(
|
|
771
|
+
`Use \`git_list_commits\`, \`git_get_diff\`, and \`git_get_file_content\` (with repositoryId=${args.repo.get(
|
|
772
|
+
"id"
|
|
773
|
+
)}) to inspect recent changes on this branch.`
|
|
774
|
+
);
|
|
552
775
|
}
|
|
553
776
|
lines.push("");
|
|
554
777
|
lines.push("Produce a thorough but concise code review report in Markdown. Required sections:");
|
|
555
778
|
lines.push("1. **Summary** \u2014 overall assessment.");
|
|
556
|
-
lines.push(
|
|
779
|
+
lines.push(
|
|
780
|
+
"2. **Findings** \u2014 issues grouped by severity (`Critical`, `High`, `Medium`, `Low`, `Info`). For each finding include the file path, line/range when possible, the problem, and a suggested fix."
|
|
781
|
+
);
|
|
557
782
|
lines.push("3. **Suggestions** \u2014 non-blocking improvements.");
|
|
558
783
|
lines.push("4. **Verdict** \u2014 one of: `LGTM`, `Approve with comments`, `Request changes`, `Block`.");
|
|
559
784
|
lines.push("");
|
|
@@ -620,7 +845,7 @@ async function fetchMrHeadSha(repo, mrIid) {
|
|
|
620
845
|
if (isGitHub) {
|
|
621
846
|
const { projectPath } = (0, import_gitlab_url.parseGitLabProject)(repoUrl);
|
|
622
847
|
const response = await fetch(`https://api.github.com/repos/${projectPath}/pulls/${mrIid}`, {
|
|
623
|
-
headers: {
|
|
848
|
+
headers: { Authorization: `Bearer ${pat}`, Accept: "application/vnd.github.v3+json" }
|
|
624
849
|
});
|
|
625
850
|
if (!response.ok) return null;
|
|
626
851
|
const data = await response.json();
|
|
@@ -648,9 +873,9 @@ async function postNoteToGitLab(repo, mrIid, body) {
|
|
|
648
873
|
const response = await fetch(`https://api.github.com/repos/${projectPath}/issues/${mrIid}/comments`, {
|
|
649
874
|
method: "POST",
|
|
650
875
|
headers: {
|
|
651
|
-
|
|
876
|
+
Authorization: `Bearer ${pat}`,
|
|
652
877
|
"Content-Type": "application/json",
|
|
653
|
-
|
|
878
|
+
Accept: "application/vnd.github.v3+json"
|
|
654
879
|
},
|
|
655
880
|
body: JSON.stringify({ body })
|
|
656
881
|
});
|
|
@@ -668,7 +893,7 @@ async function postNoteToGitLab(repo, mrIid, body) {
|
|
|
668
893
|
headers: {
|
|
669
894
|
"PRIVATE-TOKEN": pat,
|
|
670
895
|
"Content-Type": "application/json",
|
|
671
|
-
|
|
896
|
+
Accept: "application/json"
|
|
672
897
|
},
|
|
673
898
|
body: JSON.stringify({ body })
|
|
674
899
|
});
|
|
@@ -703,7 +928,9 @@ function warnInvalidBranchFilter(filter, reason) {
|
|
|
703
928
|
if (loggedBadFilters.has(filter)) return;
|
|
704
929
|
loggedBadFilters.add(filter);
|
|
705
930
|
console.warn(
|
|
706
|
-
`[plugin-git-manager] branchFilter rejected (${reason}): ${JSON.stringify(
|
|
931
|
+
`[plugin-git-manager] branchFilter rejected (${reason}): ${JSON.stringify(
|
|
932
|
+
filter
|
|
933
|
+
)}. Flow will not match any branch.`
|
|
707
934
|
);
|
|
708
935
|
}
|
|
709
936
|
function branchMatches(flow, branch) {
|
package/dist/server/ai-tools.js
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This file is part of the NocoBase (R) project.
|
|
3
|
-
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
-
* Authors: NocoBase Team.
|
|
5
|
-
*
|
|
6
|
-
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
-
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
1
|
var __create = Object.create;
|
|
11
2
|
var __defProp = Object.defineProperty;
|
|
12
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This file is part of the NocoBase (R) project.
|
|
3
|
-
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
-
* Authors: NocoBase Team.
|
|
5
|
-
*
|
|
6
|
-
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
-
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
1
|
var __defProp = Object.defineProperty;
|
|
11
2
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This file is part of the NocoBase (R) project.
|
|
3
|
-
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
-
* Authors: NocoBase Team.
|
|
5
|
-
*
|
|
6
|
-
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
-
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
1
|
var __defProp = Object.defineProperty;
|
|
11
2
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This file is part of the NocoBase (R) project.
|
|
3
|
-
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
-
* Authors: NocoBase Team.
|
|
5
|
-
*
|
|
6
|
-
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
-
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
1
|
var __defProp = Object.defineProperty;
|
|
11
2
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
package/dist/server/index.js
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This file is part of the NocoBase (R) project.
|
|
3
|
-
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
-
* Authors: NocoBase Team.
|
|
5
|
-
*
|
|
6
|
-
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
-
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
1
|
var __create = Object.create;
|
|
11
2
|
var __defProp = Object.defineProperty;
|
|
12
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This file is part of the NocoBase (R) project.
|
|
3
|
-
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
-
* Authors: NocoBase Team.
|
|
5
|
-
*
|
|
6
|
-
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
-
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
1
|
var __defProp = Object.defineProperty;
|
|
11
2
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
package/dist/server/plugin.js
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This file is part of the NocoBase (R) project.
|
|
3
|
-
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
-
* Authors: NocoBase Team.
|
|
5
|
-
*
|
|
6
|
-
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
-
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
1
|
var __create = Object.create;
|
|
11
2
|
var __defProp = Object.defineProperty;
|
|
12
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
package/dist/server/poller.js
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This file is part of the NocoBase (R) project.
|
|
3
|
-
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
-
* Authors: NocoBase Team.
|
|
5
|
-
*
|
|
6
|
-
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
-
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
1
|
var __defProp = Object.defineProperty;
|
|
11
2
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This file is part of the NocoBase (R) project.
|
|
3
|
-
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
-
* Authors: NocoBase Team.
|
|
5
|
-
*
|
|
6
|
-
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
-
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
1
|
var __defProp = Object.defineProperty;
|
|
11
2
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This file is part of the NocoBase (R) project.
|
|
3
|
-
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
-
* Authors: NocoBase Team.
|
|
5
|
-
*
|
|
6
|
-
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
-
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
1
|
var __defProp = Object.defineProperty;
|
|
11
2
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"displayName": "Git Manager",
|
|
4
4
|
"displayName.zh-CN": "Git 管理器",
|
|
5
5
|
"description": "Manage Git repositories with PAT authentication - pull, push, fetch, diff, file browsing",
|
|
6
|
-
"version": "1.1
|
|
6
|
+
"version": "1.2.1",
|
|
7
7
|
"license": "Apache-2.0",
|
|
8
8
|
"main": "dist/server/index.js",
|
|
9
9
|
"files": [
|
|
@@ -34,4 +34,4 @@
|
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"simple-git": "^3.27.0"
|
|
36
36
|
}
|
|
37
|
-
}
|
|
37
|
+
}
|