@taskless-app/swarm 0.6.49 → 0.6.51
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/cli.js +326 -116
- package/dist/overmind.js +630 -158
- package/package.json +2 -2
package/dist/overmind.js
CHANGED
|
@@ -133,6 +133,123 @@ var init_types = __esm({
|
|
|
133
133
|
}
|
|
134
134
|
});
|
|
135
135
|
|
|
136
|
+
// ../swarm-core/src/verifyVerdict.ts
|
|
137
|
+
function isVerifyVerdict(v) {
|
|
138
|
+
return typeof v === "string" && VERIFY_VERDICT_SET.has(v);
|
|
139
|
+
}
|
|
140
|
+
function toVerifyVerdict(v) {
|
|
141
|
+
return isVerifyVerdict(v) ? v : null;
|
|
142
|
+
}
|
|
143
|
+
function isNegativeVerifyVerdict(v) {
|
|
144
|
+
return v === "not_reproduced" || v === "found_issues";
|
|
145
|
+
}
|
|
146
|
+
function verifyVerdictLabel(v) {
|
|
147
|
+
switch (v) {
|
|
148
|
+
case "confirmed":
|
|
149
|
+
return "\u041F\u041E\u0414\u0422\u0412\u0415\u0420\u0416\u0414\u0410\u042E";
|
|
150
|
+
case "not_reproduced":
|
|
151
|
+
return "\u041D\u0415 \u0412\u041E\u0421\u041F\u0420\u041E\u0418\u0417\u0412\u041E\u0414\u0418\u0422\u0421\u042F";
|
|
152
|
+
case "found_issues":
|
|
153
|
+
return "\u041D\u0410\u0428\u0401\u041B";
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function parseVerifyVerdictComment(text) {
|
|
157
|
+
const line = (text ?? "").split("\n").find((l) => l.trim().length > 0);
|
|
158
|
+
if (!line) return null;
|
|
159
|
+
let best = null;
|
|
160
|
+
for (const [verdict, marks] of VERDICT_MARKS) {
|
|
161
|
+
for (const mark of marks) {
|
|
162
|
+
const at = line.indexOf(mark);
|
|
163
|
+
if (at < 0) continue;
|
|
164
|
+
if (!best || at < best.at) best = { verdict, at };
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return best?.verdict ?? null;
|
|
168
|
+
}
|
|
169
|
+
var VERIFY_VERDICTS, VERIFY_VERDICT_SET, VERDICT_MARKS;
|
|
170
|
+
var init_verifyVerdict = __esm({
|
|
171
|
+
"../swarm-core/src/verifyVerdict.ts"() {
|
|
172
|
+
"use strict";
|
|
173
|
+
VERIFY_VERDICTS = ["confirmed", "not_reproduced", "found_issues"];
|
|
174
|
+
VERIFY_VERDICT_SET = new Set(VERIFY_VERDICTS);
|
|
175
|
+
VERDICT_MARKS = [
|
|
176
|
+
["not_reproduced", ["\u041D\u0415 \u0412\u041E\u0421\u041F\u0420\u041E\u0418\u0417\u0412\u041E\u0414\u0418\u0422\u0421\u042F", "NOT REPRODUCED", "\u041D\u0415 \u041F\u041E\u0414\u0422\u0412\u0415\u0420\u0416\u0414\u0410\u042E", "NOT CONFIRMED"]],
|
|
177
|
+
["found_issues", ["\u041D\u0410\u0428\u0401\u041B", "\u041D\u0410\u0428\u0415\u041B", "FOUND"]],
|
|
178
|
+
["confirmed", ["\u041F\u041E\u0414\u0422\u0412\u0415\u0420\u0416\u0414\u0410\u042E", "CONFIRMED"]]
|
|
179
|
+
];
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// ../swarm-core/src/reviewPhase.ts
|
|
184
|
+
function reviewPhaseKindOf(label2) {
|
|
185
|
+
if (!label2) return null;
|
|
186
|
+
return PHASE_BY_LENGTH.find((k) => label2.startsWith(REVIEW_PHASE_TEXT[k])) ?? null;
|
|
187
|
+
}
|
|
188
|
+
function computeReviewPhase(input) {
|
|
189
|
+
const { reviewVerdict, ciRed } = input;
|
|
190
|
+
const verifyVerdict = input.verifyVerdict ?? null;
|
|
191
|
+
if (!ciRed && isNegativeVerifyVerdict(verifyVerdict)) {
|
|
192
|
+
const kind = verifyVerdict === "found_issues" ? "verify_found" : "verify_not_reproduced";
|
|
193
|
+
return { kind, label: REVIEW_PHASE_TEXT[kind], ready: false };
|
|
194
|
+
}
|
|
195
|
+
if (reviewVerdict === "approve" && !ciRed) {
|
|
196
|
+
if (verifyVerdict === "confirmed" || input.verifyExpected === false) {
|
|
197
|
+
if (input.ciGreen !== true) {
|
|
198
|
+
return { kind: "ready_awaiting_ci", label: REVIEW_PHASE_TEXT.ready_awaiting_ci, ready: false };
|
|
199
|
+
}
|
|
200
|
+
return { kind: "ready", label: REVIEW_PHASE_TEXT.ready, ready: true };
|
|
201
|
+
}
|
|
202
|
+
if (input.verifierDead) {
|
|
203
|
+
return { kind: "verify_dead", label: REVIEW_PHASE_TEXT.verify_dead, ready: true };
|
|
204
|
+
}
|
|
205
|
+
return { kind: "awaiting_verify", label: REVIEW_PHASE_TEXT.awaiting_verify, ready: false };
|
|
206
|
+
}
|
|
207
|
+
if (ciRed) {
|
|
208
|
+
const jobs = (input.ciFailing ?? []).filter((j) => j && j !== "?");
|
|
209
|
+
const label3 = jobs.length ? `${REVIEW_PHASE_TEXT.ci_fixing}: ${jobs.join(", ")}` : REVIEW_PHASE_TEXT.ci_fixing;
|
|
210
|
+
return { kind: "ci_fixing", label: label3, ready: false };
|
|
211
|
+
}
|
|
212
|
+
if (reviewVerdict === "changes") {
|
|
213
|
+
return { kind: "changes", label: REVIEW_PHASE_TEXT.changes, ready: false };
|
|
214
|
+
}
|
|
215
|
+
if (input.reviewerName) {
|
|
216
|
+
return { kind: "in_review", label: `\u041D\u0430 \u0440\u0435\u0432\u044C\u044E \u0443 ${input.reviewerName}`, ready: false };
|
|
217
|
+
}
|
|
218
|
+
if (input.reviewerDead) {
|
|
219
|
+
return { kind: "reviewer_dead", label: REVIEW_PHASE_TEXT.reviewer_dead, ready: true };
|
|
220
|
+
}
|
|
221
|
+
const p = input.ciProgress;
|
|
222
|
+
if (p && p.total > 0 && p.pass >= p.total) {
|
|
223
|
+
return { kind: "awaiting_reviewer", label: REVIEW_PHASE_TEXT.awaiting_reviewer, ready: false };
|
|
224
|
+
}
|
|
225
|
+
const label2 = p && p.total > 0 ? `${REVIEW_PHASE_TEXT.checks} \u2014 CI ${p.pass}/${p.total}` : REVIEW_PHASE_TEXT.checks;
|
|
226
|
+
return { kind: "checks", label: label2, ready: false };
|
|
227
|
+
}
|
|
228
|
+
var REVIEW_PHASE_TEXT, PHASE_BY_LENGTH;
|
|
229
|
+
var init_reviewPhase = __esm({
|
|
230
|
+
"../swarm-core/src/reviewPhase.ts"() {
|
|
231
|
+
"use strict";
|
|
232
|
+
init_verifyVerdict();
|
|
233
|
+
REVIEW_PHASE_TEXT = {
|
|
234
|
+
checks: "\u0418\u0434\u0443\u0442 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438",
|
|
235
|
+
awaiting_reviewer: "\u0417\u0435\u043B\u0451\u043D\u044B\u0439 \u2014 \u0436\u0434\u0451\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430",
|
|
236
|
+
ci_fixing: "CI \u043A\u0440\u0430\u0441\u043D\u044B\u0439 \u2014 \u0434\u0440\u043E\u043D \u0447\u0438\u043D\u0438\u0442",
|
|
237
|
+
in_review: "\u041D\u0430 \u0440\u0435\u0432\u044C\u044E",
|
|
238
|
+
changes: "\u041D\u0443\u0436\u043D\u044B \u043F\u0440\u0430\u0432\u043A\u0438 \u2014 \u0434\u0440\u043E\u043D \u043F\u0440\u0430\u0432\u0438\u0442",
|
|
239
|
+
verify_found: "\u0412\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u043D\u0430\u0448\u0451\u043B \u2014 \u043D\u0443\u0436\u043D\u044B \u043F\u0440\u0430\u0432\u043A\u0438",
|
|
240
|
+
verify_not_reproduced: "\u0412\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u043D\u0435 \u0432\u043E\u0441\u043F\u0440\u043E\u0438\u0437\u0432\u0451\u043B \u2014 \u043D\u0443\u0436\u043D\u044B \u043F\u0440\u0430\u0432\u043A\u0438",
|
|
241
|
+
awaiting_verify: "\u0417\u0435\u043B\u0451\u043D\u044B\u0439 \u2014 \u0436\u0434\u0451\u043C \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044E",
|
|
242
|
+
verify_dead: "\u0412\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u043D\u0435 \u0434\u0430\u043B \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A",
|
|
243
|
+
ready: "\u0417\u0435\u043B\u0451\u043D\u044B\u0439 \u2014 \u0433\u043E\u0442\u043E\u0432 \u043A \u043C\u0435\u0440\u0436\u0443",
|
|
244
|
+
ready_awaiting_ci: "\u0413\u043E\u0442\u043E\u0432 \u043A \u043C\u0435\u0440\u0436\u0443 \u2014 \u0436\u0434\u0451\u0442 CI",
|
|
245
|
+
reviewer_dead: "\u0420\u0435\u0432\u044C\u044E\u0435\u0440 \u0443\u043C\u0435\u0440 \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435\u0442, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A"
|
|
246
|
+
};
|
|
247
|
+
PHASE_BY_LENGTH = Object.keys(REVIEW_PHASE_TEXT).sort(
|
|
248
|
+
(a, b) => REVIEW_PHASE_TEXT[b].length - REVIEW_PHASE_TEXT[a].length
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
|
|
136
253
|
// ../swarm-core/src/timeline.ts
|
|
137
254
|
function resolveDepRef(ref, lk) {
|
|
138
255
|
const mapped = lk.slugByCuid.get(ref);
|
|
@@ -304,7 +421,8 @@ function computeCounters(lanes) {
|
|
|
304
421
|
if (l.dependsOn.some((d) => !d.met)) c.waitingDep++;
|
|
305
422
|
else c.waitingSlot++;
|
|
306
423
|
} else if (l.status === "review") {
|
|
307
|
-
if (l.
|
|
424
|
+
if (reviewPhaseKindOf(l.reviewPhase) === "ready_awaiting_ci") c.waitingCI++;
|
|
425
|
+
else if (l.blocker.tone === "merge" || l.reviewVerdict === "approve") c.waitingMerge++;
|
|
308
426
|
else c.waitingCI++;
|
|
309
427
|
}
|
|
310
428
|
}
|
|
@@ -314,119 +432,11 @@ var DEFAULT_RUN_MS;
|
|
|
314
432
|
var init_timeline = __esm({
|
|
315
433
|
"../swarm-core/src/timeline.ts"() {
|
|
316
434
|
"use strict";
|
|
435
|
+
init_reviewPhase();
|
|
317
436
|
DEFAULT_RUN_MS = 25 * 6e4;
|
|
318
437
|
}
|
|
319
438
|
});
|
|
320
439
|
|
|
321
|
-
// ../swarm-core/src/verifyVerdict.ts
|
|
322
|
-
function isVerifyVerdict(v) {
|
|
323
|
-
return typeof v === "string" && VERIFY_VERDICT_SET.has(v);
|
|
324
|
-
}
|
|
325
|
-
function toVerifyVerdict(v) {
|
|
326
|
-
return isVerifyVerdict(v) ? v : null;
|
|
327
|
-
}
|
|
328
|
-
function isNegativeVerifyVerdict(v) {
|
|
329
|
-
return v === "not_reproduced" || v === "found_issues";
|
|
330
|
-
}
|
|
331
|
-
function verifyVerdictLabel(v) {
|
|
332
|
-
switch (v) {
|
|
333
|
-
case "confirmed":
|
|
334
|
-
return "\u041F\u041E\u0414\u0422\u0412\u0415\u0420\u0416\u0414\u0410\u042E";
|
|
335
|
-
case "not_reproduced":
|
|
336
|
-
return "\u041D\u0415 \u0412\u041E\u0421\u041F\u0420\u041E\u0418\u0417\u0412\u041E\u0414\u0418\u0422\u0421\u042F";
|
|
337
|
-
case "found_issues":
|
|
338
|
-
return "\u041D\u0410\u0428\u0401\u041B";
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
function parseVerifyVerdictComment(text) {
|
|
342
|
-
const line = (text ?? "").split("\n").find((l) => l.trim().length > 0);
|
|
343
|
-
if (!line) return null;
|
|
344
|
-
let best = null;
|
|
345
|
-
for (const [verdict, marks] of VERDICT_MARKS) {
|
|
346
|
-
for (const mark of marks) {
|
|
347
|
-
const at = line.indexOf(mark);
|
|
348
|
-
if (at < 0) continue;
|
|
349
|
-
if (!best || at < best.at) best = { verdict, at };
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
return best?.verdict ?? null;
|
|
353
|
-
}
|
|
354
|
-
var VERIFY_VERDICTS, VERIFY_VERDICT_SET, VERDICT_MARKS;
|
|
355
|
-
var init_verifyVerdict = __esm({
|
|
356
|
-
"../swarm-core/src/verifyVerdict.ts"() {
|
|
357
|
-
"use strict";
|
|
358
|
-
VERIFY_VERDICTS = ["confirmed", "not_reproduced", "found_issues"];
|
|
359
|
-
VERIFY_VERDICT_SET = new Set(VERIFY_VERDICTS);
|
|
360
|
-
VERDICT_MARKS = [
|
|
361
|
-
["not_reproduced", ["\u041D\u0415 \u0412\u041E\u0421\u041F\u0420\u041E\u0418\u0417\u0412\u041E\u0414\u0418\u0422\u0421\u042F", "NOT REPRODUCED", "\u041D\u0415 \u041F\u041E\u0414\u0422\u0412\u0415\u0420\u0416\u0414\u0410\u042E", "NOT CONFIRMED"]],
|
|
362
|
-
["found_issues", ["\u041D\u0410\u0428\u0401\u041B", "\u041D\u0410\u0428\u0415\u041B", "FOUND"]],
|
|
363
|
-
["confirmed", ["\u041F\u041E\u0414\u0422\u0412\u0415\u0420\u0416\u0414\u0410\u042E", "CONFIRMED"]]
|
|
364
|
-
];
|
|
365
|
-
}
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
// ../swarm-core/src/reviewPhase.ts
|
|
369
|
-
function computeReviewPhase(input) {
|
|
370
|
-
const { reviewVerdict, ciRed } = input;
|
|
371
|
-
const verifyVerdict = input.verifyVerdict ?? null;
|
|
372
|
-
if (!ciRed && isNegativeVerifyVerdict(verifyVerdict)) {
|
|
373
|
-
const kind = verifyVerdict === "found_issues" ? "verify_found" : "verify_not_reproduced";
|
|
374
|
-
return { kind, label: REVIEW_PHASE_TEXT[kind], ready: false };
|
|
375
|
-
}
|
|
376
|
-
if (reviewVerdict === "approve" && !ciRed) {
|
|
377
|
-
if (verifyVerdict === "confirmed" || input.verifyExpected === false) {
|
|
378
|
-
return { kind: "ready", label: REVIEW_PHASE_TEXT.ready, ready: true };
|
|
379
|
-
}
|
|
380
|
-
if (input.verifierDead) {
|
|
381
|
-
return { kind: "verify_dead", label: REVIEW_PHASE_TEXT.verify_dead, ready: true };
|
|
382
|
-
}
|
|
383
|
-
return { kind: "awaiting_verify", label: REVIEW_PHASE_TEXT.awaiting_verify, ready: false };
|
|
384
|
-
}
|
|
385
|
-
if (ciRed) {
|
|
386
|
-
const jobs = (input.ciFailing ?? []).filter((j) => j && j !== "?");
|
|
387
|
-
const label3 = jobs.length ? `${REVIEW_PHASE_TEXT.ci_fixing}: ${jobs.join(", ")}` : REVIEW_PHASE_TEXT.ci_fixing;
|
|
388
|
-
return { kind: "ci_fixing", label: label3, ready: false };
|
|
389
|
-
}
|
|
390
|
-
if (reviewVerdict === "changes") {
|
|
391
|
-
return { kind: "changes", label: REVIEW_PHASE_TEXT.changes, ready: false };
|
|
392
|
-
}
|
|
393
|
-
if (input.reviewerName) {
|
|
394
|
-
return { kind: "in_review", label: `\u041D\u0430 \u0440\u0435\u0432\u044C\u044E \u0443 ${input.reviewerName}`, ready: false };
|
|
395
|
-
}
|
|
396
|
-
if (input.reviewerDead) {
|
|
397
|
-
return { kind: "reviewer_dead", label: REVIEW_PHASE_TEXT.reviewer_dead, ready: true };
|
|
398
|
-
}
|
|
399
|
-
const p = input.ciProgress;
|
|
400
|
-
if (p && p.total > 0 && p.pass >= p.total) {
|
|
401
|
-
return { kind: "awaiting_reviewer", label: REVIEW_PHASE_TEXT.awaiting_reviewer, ready: false };
|
|
402
|
-
}
|
|
403
|
-
const label2 = p && p.total > 0 ? `${REVIEW_PHASE_TEXT.checks} \u2014 CI ${p.pass}/${p.total}` : REVIEW_PHASE_TEXT.checks;
|
|
404
|
-
return { kind: "checks", label: label2, ready: false };
|
|
405
|
-
}
|
|
406
|
-
var REVIEW_PHASE_TEXT, PHASE_BY_LENGTH;
|
|
407
|
-
var init_reviewPhase = __esm({
|
|
408
|
-
"../swarm-core/src/reviewPhase.ts"() {
|
|
409
|
-
"use strict";
|
|
410
|
-
init_verifyVerdict();
|
|
411
|
-
REVIEW_PHASE_TEXT = {
|
|
412
|
-
checks: "\u0418\u0434\u0443\u0442 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438",
|
|
413
|
-
awaiting_reviewer: "\u0417\u0435\u043B\u0451\u043D\u044B\u0439 \u2014 \u0436\u0434\u0451\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430",
|
|
414
|
-
ci_fixing: "CI \u043A\u0440\u0430\u0441\u043D\u044B\u0439 \u2014 \u0434\u0440\u043E\u043D \u0447\u0438\u043D\u0438\u0442",
|
|
415
|
-
in_review: "\u041D\u0430 \u0440\u0435\u0432\u044C\u044E",
|
|
416
|
-
changes: "\u041D\u0443\u0436\u043D\u044B \u043F\u0440\u0430\u0432\u043A\u0438 \u2014 \u0434\u0440\u043E\u043D \u043F\u0440\u0430\u0432\u0438\u0442",
|
|
417
|
-
verify_found: "\u0412\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u043D\u0430\u0448\u0451\u043B \u2014 \u043D\u0443\u0436\u043D\u044B \u043F\u0440\u0430\u0432\u043A\u0438",
|
|
418
|
-
verify_not_reproduced: "\u0412\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u043D\u0435 \u0432\u043E\u0441\u043F\u0440\u043E\u0438\u0437\u0432\u0451\u043B \u2014 \u043D\u0443\u0436\u043D\u044B \u043F\u0440\u0430\u0432\u043A\u0438",
|
|
419
|
-
awaiting_verify: "\u0417\u0435\u043B\u0451\u043D\u044B\u0439 \u2014 \u0436\u0434\u0451\u043C \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044E",
|
|
420
|
-
verify_dead: "\u0412\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u043D\u0435 \u0434\u0430\u043B \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A",
|
|
421
|
-
ready: "\u0417\u0435\u043B\u0451\u043D\u044B\u0439 \u2014 \u0433\u043E\u0442\u043E\u0432 \u043A \u043C\u0435\u0440\u0436\u0443",
|
|
422
|
-
reviewer_dead: "\u0420\u0435\u0432\u044C\u044E\u0435\u0440 \u0443\u043C\u0435\u0440 \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435\u0442, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A"
|
|
423
|
-
};
|
|
424
|
-
PHASE_BY_LENGTH = Object.keys(REVIEW_PHASE_TEXT).sort(
|
|
425
|
-
(a, b) => REVIEW_PHASE_TEXT[b].length - REVIEW_PHASE_TEXT[a].length
|
|
426
|
-
);
|
|
427
|
-
}
|
|
428
|
-
});
|
|
429
|
-
|
|
430
440
|
// ../swarm-core/src/mergeGate.ts
|
|
431
441
|
function computeMergeGate(input) {
|
|
432
442
|
if (!input.hasRun) {
|
|
@@ -1723,13 +1733,61 @@ function similarTasks(model, text, limit = 5) {
|
|
|
1723
1733
|
}
|
|
1724
1734
|
return scored.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
1725
1735
|
}
|
|
1736
|
+
function historyFiles(model) {
|
|
1737
|
+
let idx = historyIndex.get(model);
|
|
1738
|
+
if (!idx) {
|
|
1739
|
+
const files = new Set(model.entries.flatMap((e) => e.files));
|
|
1740
|
+
const byBase = /* @__PURE__ */ new Map();
|
|
1741
|
+
for (const f of files) {
|
|
1742
|
+
const base3 = f.slice(f.lastIndexOf("/") + 1);
|
|
1743
|
+
byBase.set(base3, [...byBase.get(base3) ?? [], f]);
|
|
1744
|
+
}
|
|
1745
|
+
idx = { files, byBase };
|
|
1746
|
+
historyIndex.set(model, idx);
|
|
1747
|
+
}
|
|
1748
|
+
return idx;
|
|
1749
|
+
}
|
|
1750
|
+
function namedPaths(model, text, fileExists) {
|
|
1751
|
+
const { files, byBase } = historyFiles(model);
|
|
1752
|
+
const known = (p) => files.has(p) || (fileExists?.(p) ?? false);
|
|
1753
|
+
const raw = [...new Set((text.match(PATH_CANDIDATE) ?? []).map((c) => c.replace(/^\.?\/+/, "").replace(/\.+$/, "")))].filter((c) => c && !c.split("/").includes(".."));
|
|
1754
|
+
raw.sort((a, b) => b.split("/").length - a.split("/").length);
|
|
1755
|
+
const out = [];
|
|
1756
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
1757
|
+
const take = (p) => {
|
|
1758
|
+
if (out.includes(p)) return;
|
|
1759
|
+
out.push(p);
|
|
1760
|
+
dirs.add(dirOf(p));
|
|
1761
|
+
};
|
|
1762
|
+
for (const c of raw) {
|
|
1763
|
+
if (known(c)) {
|
|
1764
|
+
take(c);
|
|
1765
|
+
continue;
|
|
1766
|
+
}
|
|
1767
|
+
const base3 = c.slice(c.lastIndexOf("/") + 1);
|
|
1768
|
+
let matches = (byBase.get(base3) ?? []).filter((f) => f === c || f.endsWith(`/${c}`));
|
|
1769
|
+
if (matches.length > 1) matches = matches.filter((f) => dirs.has(dirOf(f)));
|
|
1770
|
+
if (matches.length === 1) {
|
|
1771
|
+
take(matches[0]);
|
|
1772
|
+
continue;
|
|
1773
|
+
}
|
|
1774
|
+
if (!c.includes("/") && fileExists) {
|
|
1775
|
+
const sibling = [...dirs].map((d) => d ? `${d}/${c}` : c).filter(fileExists);
|
|
1776
|
+
if (sibling.length === 1) take(sibling[0]);
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
return out;
|
|
1780
|
+
}
|
|
1726
1781
|
function predictFiles(model, opts) {
|
|
1727
1782
|
if (!hasZoneModel(model)) return [];
|
|
1728
1783
|
const limit = opts.limit ?? 40;
|
|
1729
1784
|
const best = /* @__PURE__ */ new Map();
|
|
1785
|
+
const named = opts.text ? namedPaths(model, opts.text, opts.fileExists) : [];
|
|
1786
|
+
for (const path of named) best.set(path, { path, weight: 1, why: "\u043D\u0430\u0437\u0432\u0430\u043D \u0432 \u0437\u0430\u0434\u0430\u0447\u0435", named: true });
|
|
1787
|
+
const factor = named.length ? NAMED_BACKGROUND_FACTOR : 1;
|
|
1730
1788
|
const put = (path, weight, why) => {
|
|
1731
1789
|
const cur = best.get(path);
|
|
1732
|
-
if (!cur || cur.weight < weight) best.set(path, { path, weight, why });
|
|
1790
|
+
if (!cur || cur.weight < weight * factor) best.set(path, { path, weight: weight * factor, why });
|
|
1733
1791
|
};
|
|
1734
1792
|
if (opts.text) {
|
|
1735
1793
|
const sim = similarTasks(model, opts.text, opts.similar ?? 5);
|
|
@@ -1746,12 +1804,18 @@ function predictFiles(model, opts) {
|
|
|
1746
1804
|
}
|
|
1747
1805
|
return [...best.values()].sort((a, b) => b.weight - a.weight || a.path.localeCompare(b.path)).slice(0, limit);
|
|
1748
1806
|
}
|
|
1807
|
+
function heldByPrediction(predicted) {
|
|
1808
|
+
const named = predicted.filter((p) => p.named);
|
|
1809
|
+
return (named.length ? named : predicted).map((p) => p.path);
|
|
1810
|
+
}
|
|
1749
1811
|
function overlapRisk(predicted, actual, opts = {}) {
|
|
1750
1812
|
const o = { ...OVERLAP_DEFAULTS, ...opts };
|
|
1751
1813
|
const actualSet = new Set(actual);
|
|
1752
1814
|
const actualDirs = new Set([...actualSet].map(dirOf).filter(Boolean));
|
|
1753
1815
|
const totalWeight = predicted.reduce((a, p) => a + p.weight, 0);
|
|
1754
1816
|
if (!totalWeight) return { overlap: false, score: 0, sharedFiles: [], sharedDirs: [] };
|
|
1817
|
+
const hasNamed = predicted.some((p) => p.named);
|
|
1818
|
+
let namedHit = false;
|
|
1755
1819
|
let hit = 0;
|
|
1756
1820
|
let strong = false;
|
|
1757
1821
|
const sharedFiles = [];
|
|
@@ -1760,24 +1824,26 @@ function overlapRisk(predicted, actual, opts = {}) {
|
|
|
1760
1824
|
if (actualSet.has(p.path)) {
|
|
1761
1825
|
hit += p.weight;
|
|
1762
1826
|
sharedFiles.push(p.path);
|
|
1763
|
-
if (p.
|
|
1827
|
+
if (p.named) namedHit = true;
|
|
1828
|
+
if (p.weight >= o.strongFile && (p.named || !hasNamed)) strong = true;
|
|
1764
1829
|
continue;
|
|
1765
1830
|
}
|
|
1766
1831
|
const d = dirOf(p.path);
|
|
1767
1832
|
if (d && actualDirs.has(d)) {
|
|
1768
1833
|
hit += p.weight * 0.25;
|
|
1769
1834
|
sharedDirs.add(d);
|
|
1835
|
+
if (p.named) namedHit = true;
|
|
1770
1836
|
}
|
|
1771
1837
|
}
|
|
1772
1838
|
const score = Math.min(1, hit / totalWeight);
|
|
1773
1839
|
return {
|
|
1774
|
-
overlap: strong || score >= o.threshold,
|
|
1840
|
+
overlap: strong || score >= o.threshold && (!hasNamed || namedHit),
|
|
1775
1841
|
score,
|
|
1776
1842
|
sharedFiles: sharedFiles.slice(0, 10),
|
|
1777
1843
|
sharedDirs: [...sharedDirs].slice(0, 5)
|
|
1778
1844
|
};
|
|
1779
1845
|
}
|
|
1780
|
-
var SPINE_ZONE, DEFAULTS, EMPTY_ZONE_MODEL, GENERIC_DIRS, WORKSPACE_DIRS, GENERIC_TEST_DIRS, STOP_TERMS, OVERLAP_DEFAULTS;
|
|
1846
|
+
var SPINE_ZONE, DEFAULTS, EMPTY_ZONE_MODEL, GENERIC_DIRS, WORKSPACE_DIRS, GENERIC_TEST_DIRS, STOP_TERMS, PATH_CANDIDATE, historyIndex, NAMED_BACKGROUND_FACTOR, OVERLAP_DEFAULTS;
|
|
1781
1847
|
var init_codeZones = __esm({
|
|
1782
1848
|
"../swarm-core/src/codeZones.ts"() {
|
|
1783
1849
|
"use strict";
|
|
@@ -1856,6 +1922,9 @@ var init_codeZones = __esm({
|
|
|
1856
1922
|
"\u043F\u0435\u0440\u0444",
|
|
1857
1923
|
"wip"
|
|
1858
1924
|
]);
|
|
1925
|
+
PATH_CANDIDATE = /[\w@.\-/]+\.[A-Za-z][A-Za-z0-9]{0,5}/g;
|
|
1926
|
+
historyIndex = /* @__PURE__ */ new WeakMap();
|
|
1927
|
+
NAMED_BACKGROUND_FACTOR = 0.1;
|
|
1859
1928
|
OVERLAP_DEFAULTS = { threshold: 0.2, strongFile: 0.35 };
|
|
1860
1929
|
}
|
|
1861
1930
|
});
|
|
@@ -1875,7 +1944,7 @@ var init_effort = __esm({
|
|
|
1875
1944
|
});
|
|
1876
1945
|
|
|
1877
1946
|
// ../swarm-core/src/queuePlan.ts
|
|
1878
|
-
var MIN_MS, QUEUE_NOMINAL_RUN_MS, QUEUE_MIN_TAIL_MS
|
|
1947
|
+
var MIN_MS, QUEUE_NOMINAL_RUN_MS, QUEUE_MIN_TAIL_MS;
|
|
1879
1948
|
var init_queuePlan = __esm({
|
|
1880
1949
|
"../swarm-core/src/queuePlan.ts"() {
|
|
1881
1950
|
"use strict";
|
|
@@ -1886,7 +1955,6 @@ var init_queuePlan = __esm({
|
|
|
1886
1955
|
MIN_MS = 6e4;
|
|
1887
1956
|
QUEUE_NOMINAL_RUN_MS = 25 * MIN_MS;
|
|
1888
1957
|
QUEUE_MIN_TAIL_MS = 3 * MIN_MS;
|
|
1889
|
-
QUEUE_REVIEW_TAIL_MS = 10 * MIN_MS;
|
|
1890
1958
|
}
|
|
1891
1959
|
});
|
|
1892
1960
|
|
|
@@ -6253,6 +6321,10 @@ function rowToRun(row) {
|
|
|
6253
6321
|
// вычисляемая фаза ревью (TASWM-230)
|
|
6254
6322
|
review_cycle: row.review_cycle ?? 0,
|
|
6255
6323
|
// старые прогоны без колонки → 0
|
|
6324
|
+
// TASWM-577: намерение смержить (человек отметил «пора влить»). В sqlite-бэкенде оно
|
|
6325
|
+
// живёт только здесь — ставить его некому, кроме самого демона, зато читает его
|
|
6326
|
+
// реконсайл, и колонка нужна, чтобы отметка пережила рестарт.
|
|
6327
|
+
merge_requested_at: row.merge_requested_at ?? null,
|
|
6256
6328
|
task_title: row.task_title ?? null,
|
|
6257
6329
|
task_desc: row.task_desc ?? null,
|
|
6258
6330
|
// TASWM-556: когда заведена сама задача — по ней проверка дубля отличает свой
|
|
@@ -6328,6 +6400,7 @@ CREATE TABLE IF NOT EXISTS runs (
|
|
|
6328
6400
|
verifies_run_id INTEGER,
|
|
6329
6401
|
verify_verdict TEXT,
|
|
6330
6402
|
review_cycle INTEGER NOT NULL DEFAULT 0,
|
|
6403
|
+
merge_requested_at TEXT,
|
|
6331
6404
|
task_title TEXT,
|
|
6332
6405
|
task_desc TEXT,
|
|
6333
6406
|
task_created_at TEXT,
|
|
@@ -6422,6 +6495,8 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
6422
6495
|
"ALTER TABLE runs ADD COLUMN verify_verdict TEXT",
|
|
6423
6496
|
// Авто-ревью v2 (TASWM-162): номер цикла ревью — пере-ревью после фикс-итерации.
|
|
6424
6497
|
"ALTER TABLE runs ADD COLUMN review_cycle INTEGER NOT NULL DEFAULT 0",
|
|
6498
|
+
// TASWM-577: намерение смержить — по нему рой вливает PR сам, по очереди мержа.
|
|
6499
|
+
"ALTER TABLE runs ADD COLUMN merge_requested_at TEXT",
|
|
6425
6500
|
// Вычисляемая фаза ревью (TASWM-230): человеко-фраза «чего ждёт PR», её считает демон.
|
|
6426
6501
|
"ALTER TABLE runs ADD COLUMN review_phase TEXT",
|
|
6427
6502
|
// Приоритетная очередь (TASWM-269): явный приоритет прогона, выше — раньше из queued.
|
|
@@ -7207,7 +7282,7 @@ var init_droneStore = __esm({
|
|
|
7207
7282
|
|
|
7208
7283
|
// src/overmind.ts
|
|
7209
7284
|
import { homedir as homedir7 } from "os";
|
|
7210
|
-
import { existsSync as existsSync20, readFileSync as readFileSync25, readdirSync as readdirSync14, renameSync as renameSync12 } from "fs";
|
|
7285
|
+
import { existsSync as existsSync20, readFileSync as readFileSync25, readdirSync as readdirSync14, renameSync as renameSync12, statSync as statSync13 } from "fs";
|
|
7211
7286
|
import { basename as basename6, dirname as dirname5, join as join34 } from "path";
|
|
7212
7287
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "url";
|
|
7213
7288
|
import { spawn as spawn4 } from "child_process";
|
|
@@ -7555,6 +7630,12 @@ function depsFailureRetriable(code) {
|
|
|
7555
7630
|
function runRef(runKey) {
|
|
7556
7631
|
return { __runRef: runKey };
|
|
7557
7632
|
}
|
|
7633
|
+
function isoOrNull(v) {
|
|
7634
|
+
if (v == null) return null;
|
|
7635
|
+
const raw = v instanceof Date ? v.toISOString() : typeof v === "string" ? v : "";
|
|
7636
|
+
const at = Date.parse(raw);
|
|
7637
|
+
return Number.isNaN(at) ? null : new Date(at).toISOString();
|
|
7638
|
+
}
|
|
7558
7639
|
function isRunRef(v) {
|
|
7559
7640
|
return typeof v === "object" && v !== null && "__runRef" in v;
|
|
7560
7641
|
}
|
|
@@ -8022,6 +8103,17 @@ var SwarmClient = class _SwarmClient {
|
|
|
8022
8103
|
runKeyOf(id) {
|
|
8023
8104
|
return this.runs.get(id)?.runKey ?? null;
|
|
8024
8105
|
}
|
|
8106
|
+
/**
|
|
8107
|
+
* Cuid прогона В ПРОДЕ — им адресуются прод-РУЧКИ, а не MCP-тулы (TASWM-577: демон
|
|
8108
|
+
* вливает PR тем же `POST /swarm/merge`, что и кнопка, и снимает намерение тем же
|
|
8109
|
+
* `DELETE /swarm/merge-request/:runId`). null — прогон до прода ещё не долетел
|
|
8110
|
+
* (создан этим демоном, флаш не прошёл): адресовать его там нечем, и вызывающий
|
|
8111
|
+
* обязан прочитать это как «сейчас нельзя», а не как ошибку.
|
|
8112
|
+
*/
|
|
8113
|
+
prodRunId(id) {
|
|
8114
|
+
const key = this.runs.get(id)?.runKey;
|
|
8115
|
+
return key ? this.prodIdFor(key) ?? null : null;
|
|
8116
|
+
}
|
|
8025
8117
|
runByKey(key) {
|
|
8026
8118
|
const direct = [...this.runs.values()].find((r) => r.runKey === key);
|
|
8027
8119
|
if (direct) return this.mat(direct);
|
|
@@ -9568,6 +9660,9 @@ var SwarmClient = class _SwarmClient {
|
|
|
9568
9660
|
verify_verdict: null,
|
|
9569
9661
|
review_phase: null,
|
|
9570
9662
|
// вычисляемая фаза ревью (TASWM-230) — прод хранит и раздаёт
|
|
9663
|
+
// TASWM-577: намерение смержить — прод-поле (его ставит человек ручкой
|
|
9664
|
+
// /swarm/merge-request), заполняется ниже из ответа.
|
|
9665
|
+
merge_requested_at: null,
|
|
9571
9666
|
// Локальная бухгалтерия (в прод-схему не уходит) — на гидрации из прода дефолт 0;
|
|
9572
9667
|
// существующая локальная запись НЕ перетирается (поле не в field-copy ниже),
|
|
9573
9668
|
// в отличие от respawns, — авто-ревью v2, TASWM-162.
|
|
@@ -9603,6 +9698,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
9603
9698
|
local.task_title = pr.taskTitle ?? local.task_title;
|
|
9604
9699
|
local.task_desc = pr.taskDesc ?? local.task_desc;
|
|
9605
9700
|
local.task_created_at = pr.taskCreatedAt ?? local.task_created_at;
|
|
9701
|
+
if ("mergeRequestedAt" in pr) local.merge_requested_at = isoOrNull(pr.mergeRequestedAt);
|
|
9606
9702
|
local.name = pr.droneName ?? local.name;
|
|
9607
9703
|
local.kind = pr.role ?? local.kind;
|
|
9608
9704
|
const heroProd = pr.heroId ?? null;
|
|
@@ -12393,7 +12489,7 @@ function parkReasonUnretriable(reason) {
|
|
|
12393
12489
|
// package.json
|
|
12394
12490
|
var package_default = {
|
|
12395
12491
|
name: "@taskless-app/swarm",
|
|
12396
|
-
version: "0.6.
|
|
12492
|
+
version: "0.6.51",
|
|
12397
12493
|
type: "module",
|
|
12398
12494
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
12399
12495
|
keywords: [
|
|
@@ -12444,7 +12540,7 @@ var package_default = {
|
|
|
12444
12540
|
execa: "^10.0.0",
|
|
12445
12541
|
zod: "3.25.76"
|
|
12446
12542
|
},
|
|
12447
|
-
gitHead: "
|
|
12543
|
+
gitHead: "24aad254ba4f3f71904310b73435e647a08f22f5"
|
|
12448
12544
|
};
|
|
12449
12545
|
|
|
12450
12546
|
// src/version.ts
|
|
@@ -12762,18 +12858,48 @@ async function prHeadBranch(prUrl, run = realRunner) {
|
|
|
12762
12858
|
return null;
|
|
12763
12859
|
}
|
|
12764
12860
|
}
|
|
12765
|
-
|
|
12861
|
+
function prRepoPath(prUrl) {
|
|
12766
12862
|
const m = prUrl.trim().match(/github\.com\/([^/\s]+)\/([^/\s]+)\/pull\/\d+/i);
|
|
12767
|
-
|
|
12768
|
-
|
|
12863
|
+
return isGithubPrUrl(prUrl) && m ? `${m[1]}/${m[2]}` : null;
|
|
12864
|
+
}
|
|
12865
|
+
async function prBaseSync(prUrl, run = realRunner) {
|
|
12866
|
+
const repoPath = prRepoPath(prUrl);
|
|
12867
|
+
if (!repoPath) return null;
|
|
12769
12868
|
try {
|
|
12770
12869
|
const j = JSON.parse(await run(["pr", "view", prUrl, "--json", "baseRefName,headRefOid"]));
|
|
12771
12870
|
const base3 = j.baseRefName;
|
|
12772
12871
|
const head = j.headRefOid;
|
|
12773
12872
|
if (!base3 || !head) return null;
|
|
12774
|
-
const out = await run(["api", `repos/${
|
|
12873
|
+
const out = await run(["api", `repos/${repoPath}/compare/${base3}...${head}?per_page=1`, "--jq", ".behind_by"]);
|
|
12775
12874
|
const behindBy = Number(out.trim());
|
|
12776
|
-
return Number.isFinite(behindBy) && behindBy >= 0 ? { behindBy, base: base3 } : null;
|
|
12875
|
+
return Number.isFinite(behindBy) && behindBy >= 0 ? { behindBy, base: base3, headSha: head } : null;
|
|
12876
|
+
} catch {
|
|
12877
|
+
return null;
|
|
12878
|
+
}
|
|
12879
|
+
}
|
|
12880
|
+
var ACTIONS_RUN_OK = /* @__PURE__ */ new Set(["success", "skipped", "neutral"]);
|
|
12881
|
+
async function prActionsRuns(prUrl, headSha2, run = realRunner) {
|
|
12882
|
+
const repoPath = prRepoPath(prUrl);
|
|
12883
|
+
if (!repoPath || !/^[0-9a-f]{7,40}$/i.test(headSha2)) return null;
|
|
12884
|
+
try {
|
|
12885
|
+
const j = JSON.parse(
|
|
12886
|
+
await run(["api", `repos/${repoPath}/actions/runs?head_sha=${headSha2}&per_page=100&exclude_pull_requests=true`])
|
|
12887
|
+
);
|
|
12888
|
+
const runs = j.workflow_runs;
|
|
12889
|
+
if (!Array.isArray(runs)) return null;
|
|
12890
|
+
if (typeof j.total_count === "number" && j.total_count > runs.length) return null;
|
|
12891
|
+
const latest = /* @__PURE__ */ new Map();
|
|
12892
|
+
for (const r of runs) {
|
|
12893
|
+
const key = `${r.workflow_id ?? r.name ?? "?"}:${r.event ?? "?"}`;
|
|
12894
|
+
const seen = latest.get(key);
|
|
12895
|
+
if (!seen || (r.id ?? 0) > (seen.id ?? 0)) latest.set(key, r);
|
|
12896
|
+
}
|
|
12897
|
+
const label2 = (r, what) => `${r.name || "workflow"} (${what || "?"})`;
|
|
12898
|
+
return {
|
|
12899
|
+
total: runs.length,
|
|
12900
|
+
active: runs.filter((r) => r.status !== "completed").map((r) => label2(r, r.status)),
|
|
12901
|
+
failed: [...latest.values()].filter((r) => r.status === "completed" && !ACTIONS_RUN_OK.has(r.conclusion ?? "")).map((r) => label2(r, r.conclusion))
|
|
12902
|
+
};
|
|
12777
12903
|
} catch {
|
|
12778
12904
|
return null;
|
|
12779
12905
|
}
|
|
@@ -14815,6 +14941,7 @@ function buildAnomalyCandidates(db, now, thresholds, opts) {
|
|
|
14815
14941
|
if (run.resolves_run_id != null || run.reviews_run_id != null) continue;
|
|
14816
14942
|
if (!run.pr_url) continue;
|
|
14817
14943
|
if (run.review_verdict === "approve") {
|
|
14944
|
+
if (reviewPhaseKindOf(run.review_phase) === "ready_awaiting_ci") continue;
|
|
14818
14945
|
candidates.push({
|
|
14819
14946
|
key: `pr_stuck_merge:${run.id}`,
|
|
14820
14947
|
minDurationMs: thresholds.prStuckMergeMs,
|
|
@@ -15337,22 +15464,10 @@ function mergeQueueCandidates(runs, repo, repoFallback) {
|
|
|
15337
15464
|
}
|
|
15338
15465
|
var BASE_TTL_MS = 2e4;
|
|
15339
15466
|
function makeMergeQueueResolver(deps) {
|
|
15340
|
-
const
|
|
15467
|
+
const read = deps.headState ?? makeHeadStateReader(deps);
|
|
15341
15468
|
const evaluate = async (e, prUrl, ready) => {
|
|
15342
|
-
const
|
|
15343
|
-
|
|
15344
|
-
const ttl = deps.ttlMs ?? BASE_TTL_MS;
|
|
15345
|
-
if (!cached || now - cached.at >= ttl) {
|
|
15346
|
-
const base3 = await deps.baseSync(prUrl).catch(() => null);
|
|
15347
|
-
let checks = "none";
|
|
15348
|
-
if (base3?.behindBy === 0 && deps.checks) {
|
|
15349
|
-
const ci = await deps.checks(prUrl).catch(() => null);
|
|
15350
|
-
checks = ci ? ci.bucket : null;
|
|
15351
|
-
}
|
|
15352
|
-
cached = { at: now, base: base3, checks };
|
|
15353
|
-
memo.set(prUrl, cached);
|
|
15354
|
-
}
|
|
15355
|
-
return computeMergeQueue({ entry: e, ready, behindBy: cached.base ? cached.base.behindBy : null, checks: cached.checks });
|
|
15469
|
+
const st = await read(prUrl);
|
|
15470
|
+
return computeMergeQueue({ entry: e, ready, behindBy: st.behindBy, checks: st.checks });
|
|
15356
15471
|
};
|
|
15357
15472
|
return async (run) => {
|
|
15358
15473
|
if (!deps.baseSync || !run.pr_url) return null;
|
|
@@ -15387,6 +15502,124 @@ function makeMergeQueueResolver(deps) {
|
|
|
15387
15502
|
function entryOf(r) {
|
|
15388
15503
|
return { runId: r.id, prNumber: prNumberOf(r.pr_url), slug: r.task_slug };
|
|
15389
15504
|
}
|
|
15505
|
+
function makeHeadStateReader(deps) {
|
|
15506
|
+
const memo = /* @__PURE__ */ new Map();
|
|
15507
|
+
return async (prUrl) => {
|
|
15508
|
+
const now = Date.now();
|
|
15509
|
+
const ttl = deps.ttlMs ?? BASE_TTL_MS;
|
|
15510
|
+
const cached = memo.get(prUrl);
|
|
15511
|
+
if (cached && now - cached.at < ttl) return { behindBy: cached.behindBy, checks: cached.checks, headSha: cached.headSha };
|
|
15512
|
+
const base3 = deps.baseSync ? await deps.baseSync(prUrl).catch(() => null) : null;
|
|
15513
|
+
let checks = "none";
|
|
15514
|
+
if (base3?.behindBy === 0 && deps.checks) {
|
|
15515
|
+
const ci = await deps.checks(prUrl).catch(() => null);
|
|
15516
|
+
checks = ci ? ci.bucket : null;
|
|
15517
|
+
}
|
|
15518
|
+
const fresh = { behindBy: base3 ? base3.behindBy : null, checks, headSha: base3?.headSha ?? null };
|
|
15519
|
+
memo.set(prUrl, { at: now, ...fresh });
|
|
15520
|
+
return fresh;
|
|
15521
|
+
};
|
|
15522
|
+
}
|
|
15523
|
+
function hasMergeIntent(r) {
|
|
15524
|
+
return !!(r.merge_requested_at ?? "").trim();
|
|
15525
|
+
}
|
|
15526
|
+
var NO_CI_GRACE_MS = 10 * 6e4;
|
|
15527
|
+
var shortSha = (sha) => sha.slice(0, 7);
|
|
15528
|
+
function makeMergeIntentResolver(deps) {
|
|
15529
|
+
const read = deps.headState ?? makeHeadStateReader(deps);
|
|
15530
|
+
const firstSeen = /* @__PURE__ */ new Map();
|
|
15531
|
+
const ciSettled = async (prUrl, st, now) => {
|
|
15532
|
+
const sha = st.headSha;
|
|
15533
|
+
if (!sha) {
|
|
15534
|
+
return { action: "wait", code: "checks_unknown", reason: "gh \u043D\u0435 \u0441\u043A\u0430\u0437\u0430\u043B HEAD PR \u2014 \u0431\u0435\u0437 \u043D\u0435\u0433\u043E \u043D\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C, \u0434\u043E\u0431\u0435\u0436\u0430\u043B \u043B\u0438 \u043D\u0430 \u043D\u0451\u043C CI. \u0421\u043F\u0440\u043E\u0448\u0443 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u043C \u0442\u0438\u043A\u043E\u043C" };
|
|
15535
|
+
}
|
|
15536
|
+
if (!deps.actionsRuns) {
|
|
15537
|
+
return { action: "wait", code: "checks_unknown", reason: "\u0440\u043E\u044E \u043D\u0435\u0447\u0435\u043C \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043F\u0440\u043E\u0433\u043E\u043D\u044B GitHub Actions \u043D\u0430 HEAD PR \u2014 \u043F\u043E \u043E\u0434\u043D\u043E\u043C\u0443 \u0430\u0433\u0440\u0435\u0433\u0430\u0442\u0443 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A \u0432\u0441\u043B\u0435\u043F\u0443\u044E \u043D\u0435 \u0432\u043B\u0438\u0432\u0430\u044E" };
|
|
15538
|
+
}
|
|
15539
|
+
const runs = await deps.actionsRuns(prUrl, sha).catch(() => null);
|
|
15540
|
+
if (!runs) {
|
|
15541
|
+
return { action: "wait", code: "checks_unknown", reason: `\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u043F\u0440\u043E\u0441\u0438\u0442\u044C \u043F\u0440\u043E\u0433\u043E\u043D\u044B GitHub Actions \u043D\u0430 ${shortSha(sha)} \u2014 \u0441\u043F\u0440\u043E\u0448\u0443 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u043C \u0442\u0438\u043A\u043E\u043C` };
|
|
15542
|
+
}
|
|
15543
|
+
if (runs.failed.length > 0) {
|
|
15544
|
+
return { action: "clear", code: "checks_red", reason: `\u043D\u0430 ${shortSha(sha)} \u0443\u043F\u0430\u043B \u043F\u0440\u043E\u0433\u043E\u043D GitHub Actions: ${runs.failed.join(", ")} \u2014 \u0432\u043B\u0438\u0432\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E, \u043D\u0443\u0436\u043D\u0430 \u0434\u043E\u0440\u0430\u0431\u043E\u0442\u043A\u0430` };
|
|
15545
|
+
}
|
|
15546
|
+
if (runs.active.length > 0) {
|
|
15547
|
+
return {
|
|
15548
|
+
action: "wait",
|
|
15549
|
+
code: "checks_pending",
|
|
15550
|
+
reason: `\u043D\u0430 ${shortSha(sha)} \u0435\u0449\u0451 \u0438\u0434\u0443\u0442 \u043F\u0440\u043E\u0433\u043E\u043D\u044B GitHub Actions: ${runs.active.join(", ")} \u2014 \u0432\u0438\u0434\u0438\u043C\u044B\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u043C\u043E\u0433\u0443\u0442 \u0431\u044B\u0442\u044C \u0437\u0435\u043B\u0451\u043D\u044B\u043C\u0438, \u043F\u043E\u043A\u0430 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0430\u044F \u0432\u043E\u043B\u043D\u0430 \u0434\u0436\u043E\u0431 \u043D\u0435 \u0441\u043E\u0437\u0434\u0430\u043D\u0430`
|
|
15551
|
+
};
|
|
15552
|
+
}
|
|
15553
|
+
if (st.checks === "none") {
|
|
15554
|
+
if (runs.total > 0) {
|
|
15555
|
+
return { action: "wait", code: "checks_pending", reason: `\u043F\u0440\u043E\u0433\u043E\u043D\u044B GitHub Actions \u043D\u0430 ${shortSha(sha)} \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0438\u0441\u044C, \u0430 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A \u0443 PR \u0435\u0449\u0451 \u043D\u0435 \u0432\u0438\u0434\u043D\u043E \u2014 \u0436\u0434\u0443, \u043F\u043E\u043A\u0430 GitHub \u0438\u0445 \u043F\u043E\u043A\u0430\u0436\u0435\u0442` };
|
|
15556
|
+
}
|
|
15557
|
+
const seen = firstSeen.get(prUrl);
|
|
15558
|
+
const waited = seen && seen.sha === sha ? now - seen.at : 0;
|
|
15559
|
+
const graceMin = NO_CI_GRACE_MS / 6e4;
|
|
15560
|
+
if (waited < NO_CI_GRACE_MS) {
|
|
15561
|
+
return {
|
|
15562
|
+
action: "wait",
|
|
15563
|
+
code: "checks_pending",
|
|
15564
|
+
reason: `\u043D\u0430 ${shortSha(sha)} \u043F\u043E\u043A\u0430 \u043D\u0438 \u043E\u0434\u043D\u043E\u0439 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u0438 \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u043F\u0440\u043E\u0433\u043E\u043D\u0430 GitHub Actions \u2014 CI \u043C\u043E\u0433 \u0435\u0449\u0451 \u043D\u0435 \u0437\u0430\u0432\u0435\u0441\u0442\u0438 \u0434\u0436\u043E\u0431\u044B \u043F\u043E\u0441\u043B\u0435 \u043F\u0443\u0448\u0430. \u0416\u0434\u0443 \u0434\u043E ${graceMin} \u043C\u0438\u043D (\u043F\u0440\u043E\u0448\u043B\u043E ${Math.floor(waited / 6e4)})`
|
|
15565
|
+
};
|
|
15566
|
+
}
|
|
15567
|
+
return { action: "merge", code: "queue_head", reason: `\u0435\u0433\u043E \u0447\u0435\u0440\u0451\u0434 \u0438 \u0431\u0430\u0437\u0430 \u0441\u0432\u0435\u0436\u0430\u044F, \u0430 ${shortSha(sha)} ${graceMin} \u043C\u0438\u043D \u043F\u0440\u043E\u0441\u0442\u043E\u044F\u043B \u0431\u0435\u0437 \u0435\u0434\u0438\u043D\u043E\u0439 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u0438 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 GitHub Actions \u2014 CI \u0443 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F \u043D\u0435\u0442` };
|
|
15568
|
+
}
|
|
15569
|
+
return { action: "merge", code: "queue_head", reason: `\u0435\u0433\u043E \u0447\u0435\u0440\u0451\u0434, \u0431\u0430\u0437\u0430 \u0441\u0432\u0435\u0436\u0430\u044F, \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u043D\u0430 ${shortSha(sha)} \u0437\u0435\u043B\u0451\u043D\u044B\u0435 \u0438 \u0432\u0441\u0435 \u043F\u0440\u043E\u0433\u043E\u043D\u044B GitHub Actions \u043D\u0430 \u043D\u0451\u043C \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u044B` };
|
|
15570
|
+
};
|
|
15571
|
+
const confirmLive = async (prUrl, sha, verdict) => {
|
|
15572
|
+
if (!sha) {
|
|
15573
|
+
return { action: "wait", code: "checks_unknown", reason: "gh \u043D\u0435 \u0441\u043A\u0430\u0437\u0430\u043B HEAD PR \u2014 \u043D\u0435 \u043F\u043E\u043D\u044F\u0442\u044C, \u043F\u0440\u043E \u043A\u0430\u043A\u043E\u0439 \u043A\u043E\u043C\u043C\u0438\u0442 \u044D\u0442\u043E\u0442 \u0432\u0435\u0440\u0434\u0438\u043A\u0442; \u0441\u043F\u0440\u043E\u0448\u0443 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u043C \u0442\u0438\u043A\u043E\u043C" };
|
|
15574
|
+
}
|
|
15575
|
+
const live = deps.baseSync ? await deps.baseSync(prUrl).catch(() => null) : null;
|
|
15576
|
+
if (!live || !live.headSha) {
|
|
15577
|
+
return { action: "wait", code: "base_unknown", reason: "\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0435\u0440\u0435\u0441\u043F\u0440\u043E\u0441\u0438\u0442\u044C \u0436\u0438\u0432\u043E\u0439 HEAD PR \u2014 \u0441\u043F\u0440\u043E\u0448\u0443 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u043C \u0442\u0438\u043A\u043E\u043C" };
|
|
15578
|
+
}
|
|
15579
|
+
if (verdict.action === "merge" && live.behindBy > 0) {
|
|
15580
|
+
return {
|
|
15581
|
+
action: "refresh_base",
|
|
15582
|
+
code: "base_stale",
|
|
15583
|
+
reason: `\u0431\u0430\u0437\u0430 \u0443\u0448\u043B\u0430 \u0432\u043F\u0435\u0440\u0451\u0434 \u043D\u0430 ${live.behindBy} \u043A\u043E\u043C\u043C\u0438\u0442(\u043E\u0432) \u043F\u043E\u0441\u043B\u0435 \u043F\u0440\u0435\u0434\u044B\u0434\u0443\u0449\u0435\u0433\u043E \u043C\u0435\u0440\u0436\u0430 \u2014 \u0441\u043D\u0430\u0447\u0430\u043B\u0430 \u0432\u043B\u0438\u0442\u044C main \u0438 \u0434\u043E\u0436\u0434\u0430\u0442\u044C\u0441\u044F CI \u043D\u0430 \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D\u043D\u043E\u0439 \u0431\u0430\u0437\u0435`
|
|
15584
|
+
};
|
|
15585
|
+
}
|
|
15586
|
+
if (live.headSha !== sha) {
|
|
15587
|
+
return { action: "wait", code: "checks_pending", reason: `\u043D\u0430 PR \u043D\u043E\u0432\u044B\u0439 \u043A\u043E\u043C\u043C\u0438\u0442 ${shortSha(live.headSha)} \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u044E \u0437\u0430\u043D\u043E\u0432\u043E` };
|
|
15588
|
+
}
|
|
15589
|
+
return verdict;
|
|
15590
|
+
};
|
|
15591
|
+
return async (run) => {
|
|
15592
|
+
if (!deps.baseSync || !run.pr_url || !hasMergeIntent(run)) return null;
|
|
15593
|
+
const num = prNumberOf(run.pr_url);
|
|
15594
|
+
if (num == null) return null;
|
|
15595
|
+
const marked = mergeQueueCandidates(deps.listRuns(), effectiveRepoOf(run.repo, deps.repoFallback), deps.repoFallback).filter(hasMergeIntent);
|
|
15596
|
+
const mine = marked.find((c) => prNumberOf(c.pr_url) === num);
|
|
15597
|
+
if (!mine) return null;
|
|
15598
|
+
const queue = orderMergeQueue(marked.map(entryOf));
|
|
15599
|
+
const entry = entryOf(mine);
|
|
15600
|
+
const position = queue.findIndex((e) => e.runId === entry.runId) + 1;
|
|
15601
|
+
const head = queue[0];
|
|
15602
|
+
if (head.runId !== entry.runId) {
|
|
15603
|
+
return {
|
|
15604
|
+
action: "wait",
|
|
15605
|
+
code: "queue_wait",
|
|
15606
|
+
reason: `\u0440\u043E\u0439 \u0432\u043B\u0438\u0432\u0430\u0435\u0442 \u043F\u043E \u043E\u0434\u043D\u043E\u043C\u0443: \u0441\u0435\u0439\u0447\u0430\u0441 \u043E\u0447\u0435\u0440\u0435\u0434\u044C PR #${head.prNumber}${head.slug ? ` (${head.slug})` : ""}, \u044D\u0442\u043E\u0442 \u2014 ${position}-\u0439 \u0438\u0437 ${queue.length}`,
|
|
15607
|
+
position,
|
|
15608
|
+
size: queue.length
|
|
15609
|
+
};
|
|
15610
|
+
}
|
|
15611
|
+
const st = await read(mine.pr_url);
|
|
15612
|
+
const now = deps.now ? deps.now() : Date.now();
|
|
15613
|
+
if (st.headSha) {
|
|
15614
|
+
const seen = firstSeen.get(mine.pr_url);
|
|
15615
|
+
if (!seen || seen.sha !== st.headSha) firstSeen.set(mine.pr_url, { sha: st.headSha, at: now });
|
|
15616
|
+
}
|
|
15617
|
+
const d = computeMergeQueue({ entry, ready: queue, behindBy: st.behindBy, checks: st.checks });
|
|
15618
|
+
const verdict = d.code === "queue_head" ? await ciSettled(mine.pr_url, st, now) : { action: d.code === "base_stale" ? "refresh_base" : d.code === "checks_red" ? "clear" : "wait", code: d.code, reason: d.reason };
|
|
15619
|
+
const final = verdict.action === "merge" || verdict.action === "clear" ? await confirmLive(mine.pr_url, st.headSha, verdict) : verdict;
|
|
15620
|
+
return { ...final, position, size: queue.length, ...st.headSha ? { headSha: st.headSha } : {} };
|
|
15621
|
+
};
|
|
15622
|
+
}
|
|
15390
15623
|
function needsBaseRefresh(d) {
|
|
15391
15624
|
return d?.code === "base_stale";
|
|
15392
15625
|
}
|
|
@@ -15418,9 +15651,15 @@ function makeMergeGateParts(deps) {
|
|
|
15418
15651
|
});
|
|
15419
15652
|
};
|
|
15420
15653
|
const repoOfRun = deps.repoFullNameOf ? (r) => deps.repoFullNameOf(effectiveRepoOf(r.repo, deps.repoFallback)) : void 0;
|
|
15654
|
+
const headState = makeHeadStateReader({
|
|
15655
|
+
...deps.baseSync ? { baseSync: deps.baseSync } : {},
|
|
15656
|
+
...deps.checks ? { checks: deps.checks } : {},
|
|
15657
|
+
...deps.ttlMs != null ? { ttlMs: deps.ttlMs } : {}
|
|
15658
|
+
});
|
|
15421
15659
|
const queueFor = makeMergeQueueResolver({
|
|
15422
15660
|
listRuns: deps.listRuns,
|
|
15423
15661
|
gateAllows: async (r) => (await gateFor(r)).allowed,
|
|
15662
|
+
headState,
|
|
15424
15663
|
...deps.repoFallback ? { repoFallback: deps.repoFallback } : {},
|
|
15425
15664
|
...deps.baseSync ? { baseSync: deps.baseSync } : {},
|
|
15426
15665
|
...deps.checks ? { checks: deps.checks } : {},
|
|
@@ -15446,7 +15685,68 @@ function makeMergeGateParts(deps) {
|
|
|
15446
15685
|
}
|
|
15447
15686
|
};
|
|
15448
15687
|
};
|
|
15449
|
-
|
|
15688
|
+
const intentFor = makeMergeIntentResolver({
|
|
15689
|
+
listRuns: deps.listRuns,
|
|
15690
|
+
headState,
|
|
15691
|
+
...deps.repoFallback ? { repoFallback: deps.repoFallback } : {},
|
|
15692
|
+
...deps.baseSync ? { baseSync: deps.baseSync } : {},
|
|
15693
|
+
...deps.checks ? { checks: deps.checks } : {},
|
|
15694
|
+
...deps.actionsRuns ? { actionsRuns: deps.actionsRuns } : {},
|
|
15695
|
+
...deps.ttlMs != null ? { ttlMs: deps.ttlMs } : {}
|
|
15696
|
+
});
|
|
15697
|
+
return { gate, queue: queueFor, intent: intentFor };
|
|
15698
|
+
}
|
|
15699
|
+
|
|
15700
|
+
// src/prodMerge.ts
|
|
15701
|
+
init_config();
|
|
15702
|
+
init_taskless();
|
|
15703
|
+
var TIMEOUT_MS = 3e4;
|
|
15704
|
+
async function tokenFor(api, deps) {
|
|
15705
|
+
return deps.token ? deps.token() : resolveApiToken(api);
|
|
15706
|
+
}
|
|
15707
|
+
async function prodMergePr(runId, sha, deps = {}) {
|
|
15708
|
+
const api = deps.api ?? TASKLESS_API;
|
|
15709
|
+
const doFetch = deps.fetchFn ?? fetch;
|
|
15710
|
+
try {
|
|
15711
|
+
const token = await tokenFor(api, deps);
|
|
15712
|
+
if (!token) return { ok: false, error: "\u043D\u0435\u0447\u0435\u043C \u0445\u043E\u0434\u0438\u0442\u044C \u0432 \u043F\u0440\u043E\u0434 (\u043D\u0435\u0442 \u0442\u043E\u043A\u0435\u043D\u0430)" };
|
|
15713
|
+
const res = await doFetch(restEndpoint(api, "/swarm/merge"), {
|
|
15714
|
+
method: "POST",
|
|
15715
|
+
headers: { Authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
15716
|
+
body: JSON.stringify(sha ? { runId, sha } : { runId }),
|
|
15717
|
+
signal: AbortSignal.timeout(TIMEOUT_MS)
|
|
15718
|
+
});
|
|
15719
|
+
if (!res.ok) {
|
|
15720
|
+
const text = await res.text().catch(() => "");
|
|
15721
|
+
let code;
|
|
15722
|
+
try {
|
|
15723
|
+
code = JSON.parse(text).error;
|
|
15724
|
+
} catch {
|
|
15725
|
+
code = void 0;
|
|
15726
|
+
}
|
|
15727
|
+
if (code === "head_moved") return { ok: false, error: "HEAD PR \u0441\u043C\u0435\u043D\u0438\u043B\u0441\u044F \u043F\u043E\u0441\u043B\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438", headMoved: true };
|
|
15728
|
+
return { ok: false, error: `\u043F\u0440\u043E\u0434 \u043E\u0442\u043A\u0430\u0437\u0430\u043B (${res.status}): ${text.slice(0, 300)}` };
|
|
15729
|
+
}
|
|
15730
|
+
return { ok: true };
|
|
15731
|
+
} catch (e) {
|
|
15732
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
15733
|
+
}
|
|
15734
|
+
}
|
|
15735
|
+
async function prodClearMergeIntent(runId, deps = {}) {
|
|
15736
|
+
const api = deps.api ?? TASKLESS_API;
|
|
15737
|
+
const doFetch = deps.fetchFn ?? fetch;
|
|
15738
|
+
try {
|
|
15739
|
+
const token = await tokenFor(api, deps);
|
|
15740
|
+
if (!token) return false;
|
|
15741
|
+
const res = await doFetch(restEndpoint(api, `/swarm/merge-request/${encodeURIComponent(runId)}`), {
|
|
15742
|
+
method: "DELETE",
|
|
15743
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
15744
|
+
signal: AbortSignal.timeout(TIMEOUT_MS)
|
|
15745
|
+
});
|
|
15746
|
+
return res.ok;
|
|
15747
|
+
} catch {
|
|
15748
|
+
return false;
|
|
15749
|
+
}
|
|
15450
15750
|
}
|
|
15451
15751
|
|
|
15452
15752
|
// src/envGate.ts
|
|
@@ -17803,6 +18103,14 @@ function compareQueuePriority(a, b) {
|
|
|
17803
18103
|
function orderQueue(runs) {
|
|
17804
18104
|
return [...runs].sort(compareQueuePriority);
|
|
17805
18105
|
}
|
|
18106
|
+
function placeReturners(returners, queued, unmet) {
|
|
18107
|
+
const awaited = new Set(queued.flatMap(unmet));
|
|
18108
|
+
const first = (r) => r.entry?.headPassed === true || awaited.has(r.run.task_slug);
|
|
18109
|
+
return {
|
|
18110
|
+
beforeHead: returners.filter(first),
|
|
18111
|
+
afterHead: returners.filter((r) => !first(r))
|
|
18112
|
+
};
|
|
18113
|
+
}
|
|
17806
18114
|
|
|
17807
18115
|
// src/watchdog.ts
|
|
17808
18116
|
init_src();
|
|
@@ -18034,8 +18342,8 @@ async function headSha(repo) {
|
|
|
18034
18342
|
function runText(run) {
|
|
18035
18343
|
return [run.task_title ?? "", run.task_desc ?? ""].join(" ").trim();
|
|
18036
18344
|
}
|
|
18037
|
-
function predictForRun(model, run) {
|
|
18038
|
-
return predictFiles(model, { zone: run.area, text: runText(run) });
|
|
18345
|
+
function predictForRun(model, run, fileExists) {
|
|
18346
|
+
return predictFiles(model, { zone: run.area, text: runText(run), ...fileExists ? { fileExists } : {} });
|
|
18039
18347
|
}
|
|
18040
18348
|
function short(files, n = 3) {
|
|
18041
18349
|
return files.slice(0, n).join(", ") + (files.length > n ? ` \u0438 \u0435\u0449\u0451 ${files.length - n}` : "");
|
|
@@ -18050,17 +18358,17 @@ var SPINE_PREDICTION_WEIGHT = 0.7;
|
|
|
18050
18358
|
function spineOfPrediction(spine, predicted) {
|
|
18051
18359
|
return predicted.filter((p) => spine.has(p.path) && p.weight >= SPINE_PREDICTION_WEIGHT).map((p) => p.path);
|
|
18052
18360
|
}
|
|
18053
|
-
function decideConflict({ model, candidate, active }) {
|
|
18361
|
+
function decideConflict({ model, candidate, active, fileExists }) {
|
|
18054
18362
|
if ((candidate.kind ?? "drone") !== "drone") return { kind: "clear" };
|
|
18055
18363
|
if (!hasZoneModel(model)) return { kind: "unknown" };
|
|
18056
|
-
const predicted = predictForRun(model, candidate);
|
|
18364
|
+
const predicted = predictForRun(model, candidate, fileExists);
|
|
18057
18365
|
if (!predicted.length) return { kind: "unknown" };
|
|
18058
18366
|
if (!active.length) return { kind: "clear" };
|
|
18059
18367
|
const spine = new Set(model.spine);
|
|
18060
18368
|
const candidateSpine = new Set(spineOfPrediction(spine, predicted));
|
|
18061
18369
|
if (candidateSpine.size) {
|
|
18062
18370
|
for (const w of active) {
|
|
18063
|
-
const theirSpine = w.actual ? w.files.filter((f) => spine.has(f)) : spineOfPrediction(spine, predictForRun(model, w.run));
|
|
18371
|
+
const theirSpine = w.actual ? w.files.filter((f) => spine.has(f)) : spineOfPrediction(spine, predictForRun(model, w.run, fileExists));
|
|
18064
18372
|
const shared = theirSpine.filter((f) => candidateSpine.has(f));
|
|
18065
18373
|
if (!shared.length) continue;
|
|
18066
18374
|
return {
|
|
@@ -18186,6 +18494,7 @@ var CONFLICT_RESOLVE_COOLDOWN_MS = Number(process.env.SWARM_CONFLICT_COOLDOWN_MS
|
|
|
18186
18494
|
var RESOLVE_LIMIT = Number(process.env.SWARM_RESOLVE_LIMIT) || 3;
|
|
18187
18495
|
var BASE_MERGED_MARK = "\u0431\u0430\u0437\u0430 \u0432\u043B\u0438\u0442\u0430 \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438";
|
|
18188
18496
|
var BASE_NOOP_MARK = "\u0432\u043B\u0438\u0432\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E";
|
|
18497
|
+
var RESOLVER_RESOLVED_MARK = "\u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442 \u0441 main \u0440\u0430\u0437\u0440\u0435\u0448\u0451\u043D \u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440\u043E\u043C";
|
|
18189
18498
|
var REVIEW_SPAWN_LIMIT = Number(process.env.SWARM_REVIEW_SPAWN_LIMIT) || 3;
|
|
18190
18499
|
var REVIEW_CYCLE_LIMIT = Number(process.env.SWARM_REVIEW_CYCLE_LIMIT) || 3;
|
|
18191
18500
|
var CI_WAKE_LIMIT = Number(process.env.SWARM_CI_WAKE_LIMIT) || 3;
|
|
@@ -18282,11 +18591,56 @@ async function needsBaseRefreshNow(deps, run, stormActive, now) {
|
|
|
18282
18591
|
if (last != null && now - last < CONFLICT_RESOLVE_COOLDOWN_MS) return false;
|
|
18283
18592
|
return needsBaseRefresh(await deps.mergeQueue(run).catch(() => null));
|
|
18284
18593
|
}
|
|
18594
|
+
async function dropMergeIntent(deps, run, reason) {
|
|
18595
|
+
if (!deps.clearMergeIntent) return false;
|
|
18596
|
+
if (!await deps.clearMergeIntent(run).catch(() => false)) return false;
|
|
18597
|
+
deps.db.updateRun(run.id, { merge_requested_at: null });
|
|
18598
|
+
deps.db.addEvent(run.id, "step", `\u043C\u0435\u0440\u0436 \u0441\u043D\u044F\u0442: ${reason}`);
|
|
18599
|
+
narrate(deps, `\u0421\u043D\u044F\u043B \u043D\u0430\u043C\u0435\u0440\u0435\u043D\u0438\u0435 \u0441\u043C\u0435\u0440\u0436\u0438\u0442\u044C PR ${droneLabel(run)} \u2014 ${reason}.`);
|
|
18600
|
+
return true;
|
|
18601
|
+
}
|
|
18602
|
+
function mergeIntentDropReason(deps, run, conflicting) {
|
|
18603
|
+
if (needsFixIteration(run)) return "\u043F\u043E PR \u043F\u0440\u0438\u0448\u0451\u043B \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \xAB\u043D\u0443\u0436\u043D\u044B \u043F\u0440\u0430\u0432\u043A\u0438\xBB \u2014 \u0441\u043D\u0430\u0447\u0430\u043B\u0430 \u0434\u043E\u0440\u0430\u0431\u043E\u0442\u043A\u0430";
|
|
18604
|
+
if (conflicting && resolveEscalated(deps.db, run.id)) return "\u0440\u043E\u0439 \u043D\u0435 \u0441\u043C\u043E\u0433 \u0441\u0432\u0435\u0441\u0442\u0438 \u0432\u0435\u0442\u043A\u0443 \u0441 \u0431\u0430\u0437\u043E\u0439 \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A";
|
|
18605
|
+
return null;
|
|
18606
|
+
}
|
|
18607
|
+
var MERGE_INTENT_FAIL_MARK = "\u043C\u0435\u0440\u0436 \u043F\u043E \u043D\u0430\u043C\u0435\u0440\u0435\u043D\u0438\u044E \u043D\u0435 \u043F\u0440\u043E\u0448\u0451\u043B";
|
|
18608
|
+
var MERGE_INTENT_FAIL_NOTE_MS = 60 * 6e4;
|
|
18609
|
+
function noteMergeIntentFailure(deps, run, error, now) {
|
|
18610
|
+
const text = `${MERGE_INTENT_FAIL_MARK}: ${error} \u2014 \u043F\u043E\u0432\u0442\u043E\u0440\u044E \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u043C \u0442\u0438\u043A\u043E\u043C`;
|
|
18611
|
+
const prev = deps.db.events(run.id).filter((e) => (e.text ?? "").startsWith(MERGE_INTENT_FAIL_MARK));
|
|
18612
|
+
const last = prev[prev.length - 1];
|
|
18613
|
+
if (last && last.text === text) {
|
|
18614
|
+
const at = Date.parse(last.at.replace(" ", "T") + "Z");
|
|
18615
|
+
if (!Number.isNaN(at) && now - at < MERGE_INTENT_FAIL_NOTE_MS) return;
|
|
18616
|
+
}
|
|
18617
|
+
deps.db.addEvent(run.id, "step", text);
|
|
18618
|
+
}
|
|
18285
18619
|
function lastCiCheckMs(db, runId) {
|
|
18286
18620
|
const evs = db.events(runId).filter((e) => e.kind === "ci_wake" || e.kind === "ci_checked");
|
|
18287
18621
|
if (!evs.length) return null;
|
|
18288
18622
|
return Date.parse(evs[evs.length - 1].at.replace(" ", "T") + "Z");
|
|
18289
18623
|
}
|
|
18624
|
+
function headMovedBy(e) {
|
|
18625
|
+
if (e.kind !== "conflict") return false;
|
|
18626
|
+
const text = e.text ?? "";
|
|
18627
|
+
return text.startsWith(BASE_MERGED_MARK) || text.includes(RESOLVER_RESOLVED_MARK);
|
|
18628
|
+
}
|
|
18629
|
+
function ciGreenForHead(db, run, events) {
|
|
18630
|
+
if (ciWatchCloseKind(db, run) != null) return true;
|
|
18631
|
+
for (let i = events.length - 1; i >= 0; i -= 1) {
|
|
18632
|
+
const e = events[i];
|
|
18633
|
+
if (headMovedBy(e)) return false;
|
|
18634
|
+
if (e.kind === "ci_wake") {
|
|
18635
|
+
if ((e.text ?? "").startsWith("\u043F\u043E\u0431\u0443\u0434\u043A\u0430 \u0441\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C")) continue;
|
|
18636
|
+
return false;
|
|
18637
|
+
}
|
|
18638
|
+
if (e.kind === "ci_checked") {
|
|
18639
|
+
return /^CI (pass|зелёный) /.test(e.text ?? "");
|
|
18640
|
+
}
|
|
18641
|
+
}
|
|
18642
|
+
return false;
|
|
18643
|
+
}
|
|
18290
18644
|
function reviewPhaseInputFor(deps, run, projectWantsVerify = true) {
|
|
18291
18645
|
const events = deps.db.events(run.id);
|
|
18292
18646
|
let ciRed = false;
|
|
@@ -18317,6 +18671,7 @@ function reviewPhaseInputFor(deps, run, projectWantsVerify = true) {
|
|
|
18317
18671
|
return {
|
|
18318
18672
|
reviewVerdict: run.review_verdict,
|
|
18319
18673
|
ciRed,
|
|
18674
|
+
ciGreen: ciGreenForHead(deps.db, run, events),
|
|
18320
18675
|
ciFailing,
|
|
18321
18676
|
ciProgress,
|
|
18322
18677
|
reviewerName: reviewer?.name ?? null,
|
|
@@ -19289,6 +19644,7 @@ async function handleCi(deps, run, now, worktreeExists, hasTranscript, droneCap)
|
|
|
19289
19644
|
return;
|
|
19290
19645
|
}
|
|
19291
19646
|
const ci = await deps.ci.checks(run.pr_url).catch(() => null);
|
|
19647
|
+
if (ci?.bucket !== "fail" && deps.returners?.get(run.id)?.kind === "ci") deps.returners.delete(run.id);
|
|
19292
19648
|
if (ci?.bucket === "denied") {
|
|
19293
19649
|
const seen = ciDeniedStreak(deps.db, run.id) + 1;
|
|
19294
19650
|
if (seen < CI_DENIED_LIMIT) {
|
|
@@ -19322,7 +19678,11 @@ async function handleCi(deps, run, now, worktreeExists, hasTranscript, droneCap)
|
|
|
19322
19678
|
return;
|
|
19323
19679
|
}
|
|
19324
19680
|
if (ci?.bucket === "fail") {
|
|
19325
|
-
|
|
19681
|
+
const capFull = countActiveDrones(deps.db) >= droneCap;
|
|
19682
|
+
if (await wakeAuthorOnRedCi(deps, run, now, ci, worktreeExists, hasTranscript, droneCap)) {
|
|
19683
|
+
deps.returners?.delete(run.id);
|
|
19684
|
+
} else {
|
|
19685
|
+
if (capFull || deps.returners?.get(run.id)?.kind === "ci") noteReturner(deps, run, now, { kind: "ci", ci, seenAt: now, since: now });
|
|
19326
19686
|
deps.db.addEvent(run.id, "ci_checked", `CI fail (${ci.pass}/${ci.total}) \u2014 \u0431\u0443\u0434\u0438\u0442\u044C \u0430\u0432\u0442\u043E\u0440\u0430 \u0441\u0435\u0439\u0447\u0430\u0441 \u043D\u0435\u0447\u0435\u043C/\u0440\u0430\u043D\u043E, \u0436\u0434\u0443 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u0433\u043E \u043E\u043A\u043D\u0430`);
|
|
19327
19687
|
}
|
|
19328
19688
|
return;
|
|
@@ -19712,6 +20072,13 @@ async function zoneConflictFor(db, models, activeFiles, run, repo, repoFallback)
|
|
|
19712
20072
|
if (!hasZoneModel(model)) return { kind: "unknown" };
|
|
19713
20073
|
const holders = db.listRuns().filter((r) => holdsFiles(r));
|
|
19714
20074
|
activeFiles.prune(holders.map((r) => r.id));
|
|
20075
|
+
const fileExists = (p) => {
|
|
20076
|
+
try {
|
|
20077
|
+
return statSync13(join34(repo, p), { throwIfNoEntry: false })?.isFile() ?? false;
|
|
20078
|
+
} catch {
|
|
20079
|
+
return false;
|
|
20080
|
+
}
|
|
20081
|
+
};
|
|
19715
20082
|
const active = [];
|
|
19716
20083
|
for (const r of holders) {
|
|
19717
20084
|
if (r.id === run.id) continue;
|
|
@@ -19720,10 +20087,10 @@ async function zoneConflictFor(db, models, activeFiles, run, repo, repoFallback)
|
|
|
19720
20087
|
const files = snapshot.length ? snapshot : await activeFiles.filesOf(r);
|
|
19721
20088
|
const prUrl = r.status === "review" ? r.pr_url : null;
|
|
19722
20089
|
active.push(
|
|
19723
|
-
files.length ? { run: r, files, actual: true, prUrl } : { run: r, files: predictForRun(model, r)
|
|
20090
|
+
files.length ? { run: r, files, actual: true, prUrl } : { run: r, files: heldByPrediction(predictForRun(model, r, fileExists)), actual: false, prUrl }
|
|
19724
20091
|
);
|
|
19725
20092
|
}
|
|
19726
|
-
return decideConflict({ model, candidate: run, active });
|
|
20093
|
+
return decideConflict({ model, candidate: run, active, fileExists });
|
|
19727
20094
|
}
|
|
19728
20095
|
function activeDronesOnRepo(deps, repo, exceptRunId) {
|
|
19729
20096
|
return deps.db.listRuns().filter(
|
|
@@ -20051,10 +20418,57 @@ async function failBeforeStart(deps, run, reason, opts = {}) {
|
|
|
20051
20418
|
}
|
|
20052
20419
|
return "failed";
|
|
20053
20420
|
}
|
|
20054
|
-
async function batchSpawnQueue(deps, droneCap) {
|
|
20055
|
-
|
|
20421
|
+
async function batchSpawnQueue(deps, droneCap, now) {
|
|
20422
|
+
const queue = orderQueue(deps.db.listRuns("queued"));
|
|
20423
|
+
const { beforeHead, afterHead } = placeReturners(pendingReturners(deps), queue, (q) => deps.db.unmetDeps(q.depends_on));
|
|
20424
|
+
await liftReturners(deps, beforeHead, now, droneCap);
|
|
20425
|
+
let afterHeadDue = afterHead.length > 0;
|
|
20426
|
+
for (const run of queue) {
|
|
20056
20427
|
if (countActiveDrones(deps.db) >= droneCap) break;
|
|
20057
|
-
await trySpawnQueuedRun(deps, run)
|
|
20428
|
+
if (await trySpawnQueuedRun(deps, run) === "spawned" && afterHeadDue) {
|
|
20429
|
+
afterHeadDue = false;
|
|
20430
|
+
for (const { entry } of afterHead) entry.headPassed = true;
|
|
20431
|
+
await liftReturners(deps, afterHead, now, droneCap);
|
|
20432
|
+
}
|
|
20433
|
+
}
|
|
20434
|
+
if (afterHeadDue) await liftReturners(deps, afterHead, now, droneCap);
|
|
20435
|
+
}
|
|
20436
|
+
var RETURNER_CI_TTL_MS = 3 * CI_CHECK_COOLDOWN_MS;
|
|
20437
|
+
function noteReturner(deps, run, now, entry) {
|
|
20438
|
+
const pool = deps.returners ??= /* @__PURE__ */ new Map();
|
|
20439
|
+
const prev = pool.get(run.id);
|
|
20440
|
+
pool.set(run.id, { ...entry, since: prev?.since ?? entry.since, ...prev?.headPassed ? { headPassed: true } : {} });
|
|
20441
|
+
}
|
|
20442
|
+
function pendingReturners(deps) {
|
|
20443
|
+
const pool = deps.returners;
|
|
20444
|
+
if (!pool?.size) return [];
|
|
20445
|
+
const out = [];
|
|
20446
|
+
for (const [id, entry] of pool) {
|
|
20447
|
+
const run = deps.db.getRun(id);
|
|
20448
|
+
if (!run || run.status !== "review" || !run.pr_url) pool.delete(id);
|
|
20449
|
+
else out.push({ run, entry });
|
|
20450
|
+
}
|
|
20451
|
+
return out.sort((a, b) => a.entry.since - b.entry.since || a.run.id - b.run.id);
|
|
20452
|
+
}
|
|
20453
|
+
async function liftReturners(deps, returners, now, droneCap) {
|
|
20454
|
+
for (const { run, entry } of returners) {
|
|
20455
|
+
if (countActiveDrones(deps.db) >= droneCap) return;
|
|
20456
|
+
if (!deps.respawn || deps.db.activeResolverFor(run.id)) continue;
|
|
20457
|
+
let lifted;
|
|
20458
|
+
if (entry.kind === "changes") {
|
|
20459
|
+
if (!needsFixIteration(run)) {
|
|
20460
|
+
deps.returners?.delete(run.id);
|
|
20461
|
+
continue;
|
|
20462
|
+
}
|
|
20463
|
+
lifted = await wakeAuthorOnChanges(deps, run, now, droneCap);
|
|
20464
|
+
} else {
|
|
20465
|
+
if (!deps.ci || now - entry.seenAt > RETURNER_CI_TTL_MS) {
|
|
20466
|
+
deps.returners?.delete(run.id);
|
|
20467
|
+
continue;
|
|
20468
|
+
}
|
|
20469
|
+
lifted = await wakeAuthorOnRedCi(deps, run, now, entry.ci, deps.worktreeExists ?? existsSync20, deps.hasTranscript ?? executorHasSession, droneCap);
|
|
20470
|
+
}
|
|
20471
|
+
if (lifted) deps.returners?.delete(run.id);
|
|
20058
20472
|
}
|
|
20059
20473
|
}
|
|
20060
20474
|
async function resumeWatchdogParked(deps, droneCap) {
|
|
@@ -20346,7 +20760,7 @@ async function manageRelief(deps, now, droneCap, stormActive) {
|
|
|
20346
20760
|
const pausedForRelief = deps.db.listRuns("paused").filter((r) => isReliefPause(r.stop_reason));
|
|
20347
20761
|
const recovering = pausedForRelief.length > 0 || state.overloadedAt != null && now - state.overloadedAt < cfg.rampWindowMs;
|
|
20348
20762
|
if (!recovering) {
|
|
20349
|
-
await batchSpawnQueue(deps, droneCap);
|
|
20763
|
+
await batchSpawnQueue(deps, droneCap, now);
|
|
20350
20764
|
return;
|
|
20351
20765
|
}
|
|
20352
20766
|
if (!rampDue(state.lastRampAt ?? null, now, cfg)) return;
|
|
@@ -20836,7 +21250,7 @@ async function tickOnce(deps) {
|
|
|
20836
21250
|
if (deps.resourcesOk) {
|
|
20837
21251
|
await manageRelief(deps, now, droneCap, stormActive);
|
|
20838
21252
|
} else if (!stormActive) {
|
|
20839
|
-
await batchSpawnQueue(deps, droneCap);
|
|
21253
|
+
await batchSpawnQueue(deps, droneCap, now);
|
|
20840
21254
|
}
|
|
20841
21255
|
if (!stormActive) {
|
|
20842
21256
|
await resumeWatchdogParked(deps, droneCap);
|
|
@@ -20853,7 +21267,7 @@ async function tickOnce(deps) {
|
|
|
20853
21267
|
});
|
|
20854
21268
|
deps.db.updateRun(run.id, { status: "done" });
|
|
20855
21269
|
deps.db.addEvent(run.id, "done", "\u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442 \u0440\u0430\u0437\u0440\u0435\u0448\u0451\u043D, main \u0432\u043B\u0438\u0442, PR \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D");
|
|
20856
|
-
deps.db.addEvent(run.resolves_run_id, "conflict",
|
|
21270
|
+
deps.db.addEvent(run.resolves_run_id, "conflict", `${RESOLVER_RESOLVED_MARK} ${run.name} \u2014 PR \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D`);
|
|
20857
21271
|
continue;
|
|
20858
21272
|
}
|
|
20859
21273
|
if (run.reviews_run_id != null) {
|
|
@@ -20906,11 +21320,13 @@ async function tickOnce(deps) {
|
|
|
20906
21320
|
const prUrl = run.pr_url;
|
|
20907
21321
|
const st = await deps.prStatus(prUrl);
|
|
20908
21322
|
run = syncBranchFromPr(deps.db, run, st.headBranch);
|
|
21323
|
+
if (st.conflicting) deps.returners?.delete(run.id);
|
|
20909
21324
|
if (st.merged) {
|
|
20910
21325
|
if (await finalizeMergedReview(deps, run)) narrate(deps, `PR ${droneLabel(run)} \u0441\u043C\u0435\u0440\u0436\u0435\u043D \u2014 ${run.task_slug} \u0437\u0430\u043A\u0440\u044B\u0442\u0430, \u0441\u043B\u043E\u0442 \u0441\u0432\u043E\u0431\u043E\u0434\u0435\u043D.`);
|
|
20911
21326
|
continue;
|
|
20912
21327
|
}
|
|
20913
21328
|
if (prLiveness(st) === "closed") {
|
|
21329
|
+
if (hasMergeIntent(run)) await dropMergeIntent(deps, run, "PR \u0437\u0430\u043A\u0440\u044B\u0442 \u0431\u0435\u0437 \u043C\u0435\u0440\u0436\u0430");
|
|
20914
21330
|
await finalizeClosedPrReview(deps, run, "PR \u0437\u0430\u043A\u0440\u044B\u0442 \u0431\u0435\u0437 \u043C\u0435\u0440\u0436\u0430 \u2014 \u0440\u0435\u0432\u044C\u044E \u043E\u0442\u043C\u0435\u043D\u0435\u043D\u043E, \u043F\u0440\u043E\u0433\u043E\u043D \u0437\u0430\u043A\u0440\u044B\u0442");
|
|
20915
21331
|
narrate(deps, `PR ${droneLabel(run)} \u0437\u0430\u043A\u0440\u044B\u0442 \u0431\u0435\u0437 \u043C\u0435\u0440\u0436\u0430 \u2014 ${run.task_slug} \u0441\u043D\u0438\u043C\u0430\u0435\u0442\u0441\u044F \u0441 \u0440\u0435\u0432\u044C\u044E, \u0441\u043B\u043E\u0442 \u0441\u0432\u043E\u0431\u043E\u0434\u0435\u043D.`);
|
|
20916
21332
|
continue;
|
|
@@ -20940,7 +21356,37 @@ async function tickOnce(deps) {
|
|
|
20940
21356
|
continue;
|
|
20941
21357
|
}
|
|
20942
21358
|
await retireOutdatedReviewers(deps, run);
|
|
20943
|
-
|
|
21359
|
+
let intentBaseStale = false;
|
|
21360
|
+
if (hasMergeIntent(run)) {
|
|
21361
|
+
const drop = mergeIntentDropReason(deps, run, st.conflicting === true);
|
|
21362
|
+
if (drop) {
|
|
21363
|
+
if (await dropMergeIntent(deps, run, drop)) run = deps.db.getRun(run.id) ?? run;
|
|
21364
|
+
} else if (deps.mergeIntent && !stormActive) {
|
|
21365
|
+
const decision = await deps.mergeIntent(run).catch(() => null);
|
|
21366
|
+
if (decision?.action === "clear") {
|
|
21367
|
+
if (await dropMergeIntent(deps, run, decision.reason)) run = deps.db.getRun(run.id) ?? run;
|
|
21368
|
+
} else if (decision?.action === "refresh_base") {
|
|
21369
|
+
if (resolveEscalated(deps.db, run.id)) {
|
|
21370
|
+
if (await dropMergeIntent(deps, run, "\u0440\u043E\u0439 \u043D\u0435 \u0441\u043C\u043E\u0433 \u043F\u0440\u0438\u0432\u0435\u0441\u0442\u0438 \u0432\u0435\u0442\u043A\u0443 \u043A \u0441\u0432\u0435\u0436\u0435\u0439 \u0431\u0430\u0437\u0435 \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A")) run = deps.db.getRun(run.id) ?? run;
|
|
21371
|
+
} else {
|
|
21372
|
+
intentBaseStale = true;
|
|
21373
|
+
}
|
|
21374
|
+
} else if (decision?.action === "merge" && deps.mergePr && !st.conflicting) {
|
|
21375
|
+
const res = await deps.mergePr(run, decision.headSha).catch((e) => ({ ok: false, error: describeError(e) }));
|
|
21376
|
+
if (res.ok) {
|
|
21377
|
+
deps.db.addEvent(run.id, "step", `\u0440\u043E\u0439 \u0432\u043B\u0438\u043B PR \u043F\u043E \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u043C\u0435\u0440\u0436\u0430: ${decision.reason} (TASWM-577)`);
|
|
21378
|
+
narrate(deps, `\u0412\u043B\u0438\u043B PR ${droneLabel(run)} \u043F\u043E \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u043C\u0435\u0440\u0436\u0430: ${decision.reason}.`);
|
|
21379
|
+
continue;
|
|
21380
|
+
}
|
|
21381
|
+
if ("headMoved" in res && res.headMoved) {
|
|
21382
|
+
deps.db.addEvent(run.id, "step", `\u043C\u0435\u0440\u0436 \u043F\u043E \u043D\u0430\u043C\u0435\u0440\u0435\u043D\u0438\u044E \u043E\u0442\u043B\u043E\u0436\u0435\u043D: HEAD PR \u0441\u043C\u0435\u043D\u0438\u043B\u0441\u044F \u043F\u043E\u0441\u043B\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 ${(decision.headSha ?? "").slice(0, 7)} \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u044E \u043D\u043E\u0432\u044B\u0439 \u043A\u043E\u043C\u043C\u0438\u0442 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u043C \u0442\u0438\u043A\u043E\u043C (TASWM-580)`);
|
|
21383
|
+
} else {
|
|
21384
|
+
noteMergeIntentFailure(deps, run, res.error, now);
|
|
21385
|
+
}
|
|
21386
|
+
}
|
|
21387
|
+
}
|
|
21388
|
+
}
|
|
21389
|
+
const baseStale = !st.conflicting && (intentBaseStale || await needsBaseRefreshNow(deps, run, stormActive, now));
|
|
20944
21390
|
if ((st.conflicting || baseStale) && deps.resolveConflict && !stormActive) {
|
|
20945
21391
|
if (deps.db.activeResolverFor(run.id)) continue;
|
|
20946
21392
|
if (deps.db.resolverCountFor(run.id) >= RESOLVE_LIMIT) {
|
|
@@ -20981,8 +21427,13 @@ async function tickOnce(deps) {
|
|
|
20981
21427
|
} else if (!st.conflicting && deps.ci && deps.respawn && !stormActive) {
|
|
20982
21428
|
await handleCi(deps, run, now, worktreeExists, hasTranscript, droneCap);
|
|
20983
21429
|
}
|
|
20984
|
-
if (needsFixIteration(run) && !stormActive && !st.conflicting && !deps.db.activeResolverFor(run.id)
|
|
20985
|
-
|
|
21430
|
+
if (needsFixIteration(run) && !stormActive && !st.conflicting && !deps.db.activeResolverFor(run.id)) {
|
|
21431
|
+
const capFull = countActiveDrones(deps.db) >= droneCap;
|
|
21432
|
+
if (await wakeAuthorOnChanges(deps, run, now, droneCap)) {
|
|
21433
|
+
deps.returners?.delete(run.id);
|
|
21434
|
+
continue;
|
|
21435
|
+
}
|
|
21436
|
+
if (capFull) noteReturner(deps, run, now, { kind: "changes", since: now });
|
|
20986
21437
|
}
|
|
20987
21438
|
const reviewerGatesOk = !st.conflicting && !st.isDraft && !!deps.spawnReviewer && !stormActive && (!deps.reviewCutoffAt || run.updated_at >= deps.reviewCutoffAt) && deps.db.reviewerCountFor(run.id) === 0 && // ГЕЙТ ПЕРВЫМ: живой/успешный ревьюер уже есть — ни капа, ни спавна не трогаем
|
|
20988
21439
|
// …и НИ ОДНОГО живого/ждущего ревьюера ЛЮБОГО цикла (TASWM-299). reviewerCountFor
|
|
@@ -22458,7 +22909,10 @@ async function runDaemon(opts) {
|
|
|
22458
22909
|
const ci = await prCiStatus(url);
|
|
22459
22910
|
if (ci.bucket === "denied" || ci.bucket === "unavailable") return null;
|
|
22460
22911
|
return { bucket: ci.bucket };
|
|
22461
|
-
}
|
|
22912
|
+
},
|
|
22913
|
+
// TASWM-577: прогоны GitHub Actions на HEAD — только вливанию по намерению: рой
|
|
22914
|
+
// вливает сам и обязан дождаться, пока CI на этом коммите добежит целиком.
|
|
22915
|
+
actionsRuns: (url, sha) => prActionsRuns(url, sha)
|
|
22462
22916
|
} : {}
|
|
22463
22917
|
});
|
|
22464
22918
|
const deps = {
|
|
@@ -22468,6 +22922,24 @@ async function runDaemon(opts) {
|
|
|
22468
22922
|
worktreesDir,
|
|
22469
22923
|
// TASWM-472: реконсайлу от очереди нужен ровно исход `base_stale` (см. needsBaseRefreshNow).
|
|
22470
22924
|
...SWARM_MERGE_QUEUE ? { mergeQueue: mergeParts.queue } : {},
|
|
22925
|
+
// TASWM-577: вливание ПО НАМЕРЕНИЮ — та же ось, тот же вход (mergeParts), но очередь из
|
|
22926
|
+
// одних отмеченных PR. Выключается тем же ключом, что и очередь: SWARM_MERGE_QUEUE=0 —
|
|
22927
|
+
// рой не вливает ничего, мержит человек кнопкой, как до этой задачи.
|
|
22928
|
+
...SWARM_MERGE_QUEUE ? { mergeIntent: mergeParts.intent } : {},
|
|
22929
|
+
// Сам мерж и снятие намерения идут в ПРОД-ручки (`POST /swarm/merge` — GitHub App
|
|
22930
|
+
// воркспейса, squash; `DELETE /swarm/merge-request/:runId`), а адресуются они cuid'ом
|
|
22931
|
+
// прогона в проде. Он есть только у клиентского бэкенда: в sqlite-режиме прогонов в
|
|
22932
|
+
// проде нет вовсе, и рой там по-прежнему не мержит — мержит человек.
|
|
22933
|
+
...db instanceof SwarmClient ? {
|
|
22934
|
+
mergePr: async (run, sha) => {
|
|
22935
|
+
const prodId = db.prodRunId(run.id);
|
|
22936
|
+
return prodId ? prodMergePr(prodId, sha) : { ok: false, error: "\u043F\u0440\u043E\u0433\u043E\u043D \u0435\u0449\u0451 \u043D\u0435 \u0434\u043E\u043B\u0435\u0442\u0435\u043B \u0434\u043E \u043F\u0440\u043E\u0434\u0430 \u2014 \u0432\u043B\u0438\u0432\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u043C" };
|
|
22937
|
+
},
|
|
22938
|
+
clearMergeIntent: async (run) => {
|
|
22939
|
+
const prodId = db.prodRunId(run.id);
|
|
22940
|
+
return prodId ? prodClearMergeIntent(prodId) : false;
|
|
22941
|
+
}
|
|
22942
|
+
} : {},
|
|
22471
22943
|
narrate: narrator.narrate,
|
|
22472
22944
|
control,
|
|
22473
22945
|
spawn: async (run) => {
|