@yemi33/minions 0.1.1722 → 0.1.1724
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/CHANGELOG.md +10 -0
- package/engine/ado.js +27 -0
- package/engine/copilot-models.json +1 -1
- package/engine/github.js +28 -0
- package/engine/lifecycle.js +36 -4
- package/engine/shared.js +38 -1
- package/engine.js +104 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/engine/ado.js
CHANGED
|
@@ -708,6 +708,7 @@ async function pollPrStatus(config) {
|
|
|
708
708
|
delete pr.buildStatus;
|
|
709
709
|
delete pr.buildFailReason;
|
|
710
710
|
delete pr.buildErrorLog;
|
|
711
|
+
delete pr.buildFailureSignature;
|
|
711
712
|
delete pr._buildFailNotified;
|
|
712
713
|
delete pr._buildStatusStale;
|
|
713
714
|
delete pr._buildStatusDetail;
|
|
@@ -738,6 +739,11 @@ async function pollPrStatus(config) {
|
|
|
738
739
|
pr._adoSourceCommit = sourceCommit;
|
|
739
740
|
updated = true;
|
|
740
741
|
}
|
|
742
|
+
const targetCommit = prData.lastMergeTargetCommit?.commitId || '';
|
|
743
|
+
if (targetCommit && pr._adoTargetCommit !== targetCommit) {
|
|
744
|
+
pr._adoTargetCommit = targetCommit;
|
|
745
|
+
updated = true;
|
|
746
|
+
}
|
|
741
747
|
|
|
742
748
|
const reviewers = prData.reviewers || [];
|
|
743
749
|
const votes = reviewers.map(r => r.vote).filter(v => v !== undefined);
|
|
@@ -811,6 +817,7 @@ async function pollPrStatus(config) {
|
|
|
811
817
|
const mergeCommitId = prData.lastMergeCommit?.commitId;
|
|
812
818
|
let buildStatus = pr.buildStatus || 'none';
|
|
813
819
|
let buildFailReason = pr.buildFailReason || '';
|
|
820
|
+
let buildFailureSignature = pr.buildFailureSignature || '';
|
|
814
821
|
let buildStatusResolved = true;
|
|
815
822
|
let buildStatusStaleDetail = '';
|
|
816
823
|
|
|
@@ -834,6 +841,11 @@ async function pollPrStatus(config) {
|
|
|
834
841
|
if (buildStatus === 'failing') {
|
|
835
842
|
const failed = prBuilds.find(b => b.result === 'failed');
|
|
836
843
|
buildFailReason = failed?.definition?.name || 'Build failed';
|
|
844
|
+
buildFailureSignature = shared.safeSlugComponent([
|
|
845
|
+
failed?.definition?.name,
|
|
846
|
+
failed?.result,
|
|
847
|
+
failed?.status,
|
|
848
|
+
].filter(Boolean).join('\n') || buildFailReason, 80);
|
|
837
849
|
}
|
|
838
850
|
} else if (allBuilds.length > 0 && pr.buildStatus) {
|
|
839
851
|
// Stale merge-commit fallback — ADO returned builds for this PR's merge ref
|
|
@@ -879,6 +891,8 @@ async function pollPrStatus(config) {
|
|
|
879
891
|
pr.buildStatus = buildStatus;
|
|
880
892
|
if (buildFailReason) pr.buildFailReason = buildFailReason;
|
|
881
893
|
else delete pr.buildFailReason;
|
|
894
|
+
if (buildFailureSignature) pr.buildFailureSignature = buildFailureSignature;
|
|
895
|
+
else delete pr.buildFailureSignature;
|
|
882
896
|
// Build transitioned — clear grace period and auto-complete flag
|
|
883
897
|
delete pr._buildFixPushedAt;
|
|
884
898
|
if (buildStatus === 'failing') delete pr._autoCompleted;
|
|
@@ -894,6 +908,7 @@ async function pollPrStatus(config) {
|
|
|
894
908
|
// fix agents to be dispatched blind.
|
|
895
909
|
if (buildStatus === 'passing') {
|
|
896
910
|
delete pr.buildErrorLog;
|
|
911
|
+
delete pr.buildFailureSignature;
|
|
897
912
|
// Reset build fix retry counter on recovery — allows fresh auto-fix cycles if build breaks again
|
|
898
913
|
if (pr.buildFixAttempts) { delete pr.buildFixAttempts; delete pr.buildFixEscalated; }
|
|
899
914
|
}
|
|
@@ -909,6 +924,16 @@ async function pollPrStatus(config) {
|
|
|
909
924
|
} catch {}
|
|
910
925
|
}
|
|
911
926
|
}
|
|
927
|
+
if (buildStatus === 'failing') {
|
|
928
|
+
if (buildFailReason && pr.buildFailReason !== buildFailReason) {
|
|
929
|
+
pr.buildFailReason = buildFailReason;
|
|
930
|
+
updated = true;
|
|
931
|
+
}
|
|
932
|
+
if (buildFailureSignature && pr.buildFailureSignature !== buildFailureSignature) {
|
|
933
|
+
pr.buildFailureSignature = buildFailureSignature;
|
|
934
|
+
updated = true;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
912
937
|
}
|
|
913
938
|
|
|
914
939
|
// Auto-complete: set auto-complete on PR when builds green + review approved
|
|
@@ -1041,6 +1066,8 @@ async function pollPrHumanComments(config) {
|
|
|
1041
1066
|
|
|
1042
1067
|
pr.humanFeedback = {
|
|
1043
1068
|
lastProcessedCommentDate: latestDate,
|
|
1069
|
+
lastProcessedCommentId: String(newHumanComments[newHumanComments.length - 1].commentId),
|
|
1070
|
+
lastProcessedCommentKey: `${newHumanComments[newHumanComments.length - 1].threadId}:${newHumanComments[newHumanComments.length - 1].commentId}`,
|
|
1044
1071
|
pendingFix: true,
|
|
1045
1072
|
feedbackContent
|
|
1046
1073
|
};
|
package/engine/github.js
CHANGED
|
@@ -372,6 +372,10 @@ async function pollPrStatus(config) {
|
|
|
372
372
|
pr.lastPushedAt = ts();
|
|
373
373
|
updated = true;
|
|
374
374
|
}
|
|
375
|
+
if (prData.base?.sha && pr.baseSha !== prData.base.sha) {
|
|
376
|
+
pr.baseSha = prData.base.sha;
|
|
377
|
+
updated = true;
|
|
378
|
+
}
|
|
375
379
|
|
|
376
380
|
if (pr.status !== newStatus) {
|
|
377
381
|
log('info', `PR ${pr.id} status: ${pr.status} → ${newStatus}`);
|
|
@@ -389,6 +393,7 @@ async function pollPrStatus(config) {
|
|
|
389
393
|
delete pr.buildStatus;
|
|
390
394
|
delete pr.buildFailReason;
|
|
391
395
|
delete pr.buildErrorLog;
|
|
396
|
+
delete pr.buildFailureSignature;
|
|
392
397
|
delete pr._buildFailNotified;
|
|
393
398
|
delete pr.buildFixAttempts;
|
|
394
399
|
delete pr.buildFixEscalated;
|
|
@@ -471,6 +476,7 @@ async function pollPrStatus(config) {
|
|
|
471
476
|
const runs = checksData.check_runs;
|
|
472
477
|
let buildStatus = 'none';
|
|
473
478
|
let buildFailReason = '';
|
|
479
|
+
let buildFailureSignature = '';
|
|
474
480
|
|
|
475
481
|
if (runs.length > 0) {
|
|
476
482
|
const hasFailed = runs.some(r => r.conclusion === 'failure' || r.conclusion === 'timed_out');
|
|
@@ -481,6 +487,13 @@ async function pollPrStatus(config) {
|
|
|
481
487
|
buildStatus = 'failing';
|
|
482
488
|
const failed = runs.find(r => r.conclusion === 'failure' || r.conclusion === 'timed_out');
|
|
483
489
|
buildFailReason = failed?.name || 'Check failed';
|
|
490
|
+
buildFailureSignature = shared.safeSlugComponent([
|
|
491
|
+
failed?.name,
|
|
492
|
+
failed?.conclusion,
|
|
493
|
+
failed?.output?.title,
|
|
494
|
+
failed?.output?.summary,
|
|
495
|
+
failed?.output?.text,
|
|
496
|
+
].filter(Boolean).join('\n') || buildFailReason, 80);
|
|
484
497
|
} else if (allDone && allPassed) {
|
|
485
498
|
buildStatus = 'passing';
|
|
486
499
|
} else {
|
|
@@ -493,6 +506,8 @@ async function pollPrStatus(config) {
|
|
|
493
506
|
pr.buildStatus = buildStatus;
|
|
494
507
|
if (buildFailReason) pr.buildFailReason = buildFailReason;
|
|
495
508
|
else delete pr.buildFailReason;
|
|
509
|
+
if (buildFailureSignature) pr.buildFailureSignature = buildFailureSignature;
|
|
510
|
+
else delete pr.buildFailureSignature;
|
|
496
511
|
// Build transitioned — clear grace period and auto-complete flag
|
|
497
512
|
delete pr._buildFixPushedAt;
|
|
498
513
|
if (buildStatus === 'failing') delete pr._autoCompleted; // allow re-merge after fix
|
|
@@ -504,6 +519,7 @@ async function pollPrStatus(config) {
|
|
|
504
519
|
// while a queued build was still running.
|
|
505
520
|
if (buildStatus === 'passing') {
|
|
506
521
|
delete pr.buildErrorLog;
|
|
522
|
+
delete pr.buildFailureSignature;
|
|
507
523
|
// Reset build fix retry counter on recovery — allows fresh auto-fix cycles if build breaks again
|
|
508
524
|
if (pr.buildFixAttempts) { delete pr.buildFixAttempts; delete pr.buildFixEscalated; }
|
|
509
525
|
}
|
|
@@ -519,6 +535,16 @@ async function pollPrStatus(config) {
|
|
|
519
535
|
} catch {}
|
|
520
536
|
}
|
|
521
537
|
}
|
|
538
|
+
if (buildStatus === 'failing') {
|
|
539
|
+
if (buildFailReason && pr.buildFailReason !== buildFailReason) {
|
|
540
|
+
pr.buildFailReason = buildFailReason;
|
|
541
|
+
updated = true;
|
|
542
|
+
}
|
|
543
|
+
if (buildFailureSignature && pr.buildFailureSignature !== buildFailureSignature) {
|
|
544
|
+
pr.buildFailureSignature = buildFailureSignature;
|
|
545
|
+
updated = true;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
522
548
|
}
|
|
523
549
|
}
|
|
524
550
|
|
|
@@ -636,6 +662,8 @@ async function pollPrHumanComments(config) {
|
|
|
636
662
|
|
|
637
663
|
pr.humanFeedback = {
|
|
638
664
|
lastProcessedCommentDate: latestDate,
|
|
665
|
+
lastProcessedCommentId: String(newComments[newComments.length - 1].commentId),
|
|
666
|
+
lastProcessedCommentKey: String(newComments[newComments.length - 1].commentId),
|
|
639
667
|
pendingFix: true,
|
|
640
668
|
feedbackContent
|
|
641
669
|
};
|
package/engine/lifecycle.js
CHANGED
|
@@ -1431,7 +1431,26 @@ async function updatePrAfterReview(agentId, pr, project, config, resultSummary,
|
|
|
1431
1431
|
}
|
|
1432
1432
|
}
|
|
1433
1433
|
|
|
1434
|
-
function
|
|
1434
|
+
function getHumanFeedbackAutomationCauseKey(pr) {
|
|
1435
|
+
const feedback = pr?.humanFeedback;
|
|
1436
|
+
if (!feedback || typeof feedback !== 'object') return '';
|
|
1437
|
+
const commentRef = feedback.lastProcessedCommentKey
|
|
1438
|
+
|| feedback.lastProcessedCommentId
|
|
1439
|
+
|| feedback.commentId
|
|
1440
|
+
|| feedback.lastProcessedCommentDate
|
|
1441
|
+
|| feedback.feedbackContent
|
|
1442
|
+
|| '';
|
|
1443
|
+
return commentRef ? `human-comment:${shared.safeSlugComponent(commentRef, 80)}` : '';
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
function shouldClearHumanFeedbackPendingFix(target, completedPr, automationCauseKey) {
|
|
1447
|
+
if (!target?.humanFeedback?.pendingFix) return true;
|
|
1448
|
+
const currentCauseKey = getHumanFeedbackAutomationCauseKey(target);
|
|
1449
|
+
const completedCauseKey = automationCauseKey || getHumanFeedbackAutomationCauseKey(completedPr);
|
|
1450
|
+
return !currentCauseKey || !completedCauseKey || currentCauseKey === completedCauseKey;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
function updatePrAfterFix(pr, project, source, automationCauseKey = '', dispatchId = '') {
|
|
1435
1454
|
|
|
1436
1455
|
if (!pr?.id) return;
|
|
1437
1456
|
const prPath = project ? shared.projectPrPath(project) : path.join(path.resolve(MINIONS_DIR, '..'), '.minions', 'pull-requests.json');
|
|
@@ -1442,13 +1461,26 @@ function updatePrAfterFix(pr, project, source) {
|
|
|
1442
1461
|
// Never downgrade from approved — fix was dispatched but PR is already approved
|
|
1443
1462
|
if (target.reviewStatus !== 'approved') target.reviewStatus = 'waiting';
|
|
1444
1463
|
if (source === 'pr-human-feedback') {
|
|
1445
|
-
|
|
1464
|
+
const clearPendingFix = shouldClearHumanFeedbackPendingFix(target, pr, automationCauseKey);
|
|
1465
|
+
if (target.humanFeedback && clearPendingFix) target.humanFeedback.pendingFix = false;
|
|
1446
1466
|
target.minionsReview = { ...target.minionsReview, note: 'Fixed human feedback, awaiting re-review', fixedAt: ts() };
|
|
1447
|
-
|
|
1467
|
+
if (clearPendingFix) {
|
|
1468
|
+
log('info', `Updated ${pr.id} → cleared humanFeedback.pendingFix, reset to waiting for re-review`);
|
|
1469
|
+
} else {
|
|
1470
|
+
log('info', `Updated ${pr.id} → preserved newer humanFeedback.pendingFix, reset to waiting for re-review`);
|
|
1471
|
+
}
|
|
1448
1472
|
} else {
|
|
1449
1473
|
target.minionsReview = { ...target.minionsReview, note: 'Fixed, awaiting re-review', fixedAt: ts() };
|
|
1450
1474
|
log('info', `Updated ${pr.id} → reviewStatus: waiting (fix pushed)`);
|
|
1451
1475
|
}
|
|
1476
|
+
if (automationCauseKey) {
|
|
1477
|
+
shared.markPrAutomationCause(target, automationCauseKey, {
|
|
1478
|
+
source,
|
|
1479
|
+
dispatchId: dispatchId || null,
|
|
1480
|
+
status: 'handled',
|
|
1481
|
+
handledAt: ts(),
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1452
1484
|
return prs;
|
|
1453
1485
|
}, { defaultValue: [] });
|
|
1454
1486
|
}
|
|
@@ -2762,7 +2794,7 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
|
|
|
2762
2794
|
log('warn', `Skipping PR review metadata update for ${meta?.pr?.id || meta?.pr?.url || '(unknown PR)'} because review dispatch ${dispatchItem.id} did not complete cleanly`);
|
|
2763
2795
|
}
|
|
2764
2796
|
if (type === WORK_TYPE.FIX && effectiveSuccess) {
|
|
2765
|
-
updatePrAfterFix(meta?.pr, meta?.project, meta?.source);
|
|
2797
|
+
updatePrAfterFix(meta?.pr, meta?.project, meta?.source, meta?.automationCauseKey, dispatchItem?.id);
|
|
2766
2798
|
// (#984) Sync PRD status for PR-linked features: fix work items have a different ID
|
|
2767
2799
|
// than the original PRD feature, so syncPrdItemStatus(fixWiId, ...) finds nothing.
|
|
2768
2800
|
// Use the PR's prdItems to propagate done status when the original work item is done.
|
package/engine/shared.js
CHANGED
|
@@ -836,7 +836,7 @@ const ENGINE_DEFAULTS = {
|
|
|
836
836
|
maxDispatchPromptBytes: 1024 * 1024, // 1 MB — dispatch items with prompts larger than this sidecar to engine/contexts/ to prevent dispatch.json OOM (#1167)
|
|
837
837
|
maxStateFileBytes: 100 * 1024 * 1024, // 100 MB — fail startup with a clear error when dispatch.json / cooldowns.json exceed this, rather than silently OOMing on JSON.parse (#1167)
|
|
838
838
|
ccMaxTurns: 50, // max tool-use turns for CC/doc-chat before CLI stops
|
|
839
|
-
ccSessionTtlMs:
|
|
839
|
+
ccSessionTtlMs: 7 * 24 * 60 * 60 * 1000, // 7d — keep chats resumable after breaks, still bounded by turn cap
|
|
840
840
|
docSessionTtlMs: 7 * 24 * 60 * 60 * 1000, // 7d — longer-lived doc sessions, still bounded
|
|
841
841
|
maxLlmRawBytes: 256 * 1024, // keep only a bounded stdout tail from direct Claude calls
|
|
842
842
|
maxLlmStderrBytes: 64 * 1024, // keep only a bounded stderr tail from direct Claude calls
|
|
@@ -2532,6 +2532,40 @@ function safeSlugComponent(text, maxLen = 80) {
|
|
|
2532
2532
|
return `${base}-${hash}`.slice(0, maxLen);
|
|
2533
2533
|
}
|
|
2534
2534
|
|
|
2535
|
+
const PR_AUTOMATION_CAUSE_LIMIT = 50;
|
|
2536
|
+
|
|
2537
|
+
function getPrAutomationCauses(pr) {
|
|
2538
|
+
const causes = pr?._automationFixCauses;
|
|
2539
|
+
return causes && typeof causes === 'object' && !Array.isArray(causes) ? causes : {};
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2542
|
+
function hasPrAutomationCause(pr, causeKey) {
|
|
2543
|
+
return !!(causeKey && getPrAutomationCauses(pr)[causeKey]);
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
function markPrAutomationCause(pr, causeKey, details = {}) {
|
|
2547
|
+
if (!pr || !causeKey) return false;
|
|
2548
|
+
const now = ts();
|
|
2549
|
+
const causes = { ...getPrAutomationCauses(pr) };
|
|
2550
|
+
causes[causeKey] = {
|
|
2551
|
+
...(causes[causeKey] || {}),
|
|
2552
|
+
...details,
|
|
2553
|
+
status: details.status || causes[causeKey]?.status || 'handled',
|
|
2554
|
+
updatedAt: now,
|
|
2555
|
+
};
|
|
2556
|
+
if (!causes[causeKey].firstSeenAt) causes[causeKey].firstSeenAt = now;
|
|
2557
|
+
|
|
2558
|
+
const entries = Object.entries(causes);
|
|
2559
|
+
if (entries.length > PR_AUTOMATION_CAUSE_LIMIT) {
|
|
2560
|
+
entries
|
|
2561
|
+
.sort((a, b) => String(b[1]?.updatedAt || b[1]?.firstSeenAt || '').localeCompare(String(a[1]?.updatedAt || a[1]?.firstSeenAt || '')))
|
|
2562
|
+
.slice(PR_AUTOMATION_CAUSE_LIMIT)
|
|
2563
|
+
.forEach(([key]) => delete causes[key]);
|
|
2564
|
+
}
|
|
2565
|
+
pr._automationFixCauses = causes;
|
|
2566
|
+
return true;
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2535
2569
|
function formatTranscriptEntry(t) {
|
|
2536
2570
|
return '### ' + (t.agent || 'agent') + ' (' + (t.type || '') + ', Round ' + (t.round || '?') + ')\n\n' + (t.content || '');
|
|
2537
2571
|
}
|
|
@@ -2716,6 +2750,9 @@ module.exports = {
|
|
|
2716
2750
|
redactSecrets,
|
|
2717
2751
|
slugify,
|
|
2718
2752
|
safeSlugComponent,
|
|
2753
|
+
getPrAutomationCauses,
|
|
2754
|
+
hasPrAutomationCause,
|
|
2755
|
+
markPrAutomationCause,
|
|
2719
2756
|
formatTranscriptEntry,
|
|
2720
2757
|
getPinnedItems,
|
|
2721
2758
|
_logBuffer, // exported for testing
|
package/engine.js
CHANGED
|
@@ -2308,6 +2308,93 @@ function ensurePrBranchForDispatch(project, pr, automationType) {
|
|
|
2308
2308
|
return '';
|
|
2309
2309
|
}
|
|
2310
2310
|
|
|
2311
|
+
function prCausePart(value, fallback = 'unknown') {
|
|
2312
|
+
const raw = String(value || '').trim();
|
|
2313
|
+
return shared.safeSlugComponent(raw || fallback, 80);
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
function getPrCauseHead(pr) {
|
|
2317
|
+
return pr?.headSha
|
|
2318
|
+
|| pr?.headSHA
|
|
2319
|
+
|| pr?.head?.sha
|
|
2320
|
+
|| pr?._adoSourceCommit
|
|
2321
|
+
|| pr?._adoHeadCommit
|
|
2322
|
+
|| pr?.lastMergeSourceCommit?.commitId
|
|
2323
|
+
|| pr?.lastMergeCommit?.commitId
|
|
2324
|
+
|| '';
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
function getPrCauseBase(pr) {
|
|
2328
|
+
return pr?.baseSha
|
|
2329
|
+
|| pr?.base?.sha
|
|
2330
|
+
|| pr?._adoTargetCommit
|
|
2331
|
+
|| pr?.lastMergeTargetCommit?.commitId
|
|
2332
|
+
|| pr?.targetSha
|
|
2333
|
+
|| pr?.targetRefSha
|
|
2334
|
+
|| pr?.baseRefName
|
|
2335
|
+
|| pr?.targetRefName
|
|
2336
|
+
|| '';
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
function getPrAutomationCauseKey(kind, pr) {
|
|
2340
|
+
if (kind === 'review-feedback') {
|
|
2341
|
+
const reviewRef = pr?.minionsReview?.dispatchId
|
|
2342
|
+
|| pr?.minionsReview?.reviewedAt
|
|
2343
|
+
|| pr?.lastReviewedAt
|
|
2344
|
+
|| getPrCauseHead(pr)
|
|
2345
|
+
|| pr?.reviewNote
|
|
2346
|
+
|| pr?.minionsReview?.note
|
|
2347
|
+
|| 'review';
|
|
2348
|
+
return `review-feedback:${prCausePart(reviewRef)}`;
|
|
2349
|
+
}
|
|
2350
|
+
if (kind === 'human-comment') {
|
|
2351
|
+
const commentRef = pr?.humanFeedback?.lastProcessedCommentKey
|
|
2352
|
+
|| pr?.humanFeedback?.lastProcessedCommentId
|
|
2353
|
+
|| pr?.humanFeedback?.commentId
|
|
2354
|
+
|| pr?.humanFeedback?.lastProcessedCommentDate
|
|
2355
|
+
|| pr?.humanFeedback?.feedbackContent
|
|
2356
|
+
|| 'comment';
|
|
2357
|
+
return `human-comment:${prCausePart(commentRef)}`;
|
|
2358
|
+
}
|
|
2359
|
+
if (kind === 'build') {
|
|
2360
|
+
const checkName = pr?.buildFailReason || pr?._buildStatusDetail || 'check';
|
|
2361
|
+
const signature = pr?.buildFailureSignature
|
|
2362
|
+
|| pr?.buildErrorLog
|
|
2363
|
+
|| pr?._buildStatusDetail
|
|
2364
|
+
|| pr?.buildStatusDetail
|
|
2365
|
+
|| pr?.buildFailReason
|
|
2366
|
+
|| 'failure';
|
|
2367
|
+
return `build:${prCausePart(checkName)}:${prCausePart(getPrCauseHead(pr), 'unknown-head')}:${prCausePart(signature)}`;
|
|
2368
|
+
}
|
|
2369
|
+
if (kind === 'merge-conflict') {
|
|
2370
|
+
return `merge-conflict:${prCausePart(getPrCauseBase(pr), 'unknown-base')}:${prCausePart(getPrCauseHead(pr), 'unknown-head')}`;
|
|
2371
|
+
}
|
|
2372
|
+
return `${kind}:${prCausePart(getPrCauseHead(pr) || pr?.id || 'pr')}`;
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
function getPrAutomationDispatchKey(baseKey, causeKey) {
|
|
2376
|
+
return `${baseKey}-${shared.safeSlugComponent(causeKey, 96)}`;
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
function isPrAutomationCausePending(project, pr, causeKey) {
|
|
2380
|
+
if (!causeKey) return false;
|
|
2381
|
+
const prCanonicalId = shared.getCanonicalPrId(project, pr, pr?.url || '');
|
|
2382
|
+
const dispatch = getDispatch();
|
|
2383
|
+
return [...(dispatch.pending || []), ...(dispatch.active || [])].some(d => {
|
|
2384
|
+
if (d.meta?.automationCauseKey !== causeKey) return false;
|
|
2385
|
+
if (!prCanonicalId) return true;
|
|
2386
|
+
const dispatchProject = d.meta?.project?.name
|
|
2387
|
+
? (getProjects(getConfig()).find(p => p.name === d.meta.project.name) || d.meta.project)
|
|
2388
|
+
: (d.meta?.project || null);
|
|
2389
|
+
const dispatchPrId = shared.getCanonicalPrId(dispatchProject, d.meta?.pr, d.meta?.pr?.url || '');
|
|
2390
|
+
return !dispatchPrId || dispatchPrId === prCanonicalId;
|
|
2391
|
+
});
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
function isPrAutomationCauseHandledOrPending(project, pr, causeKey) {
|
|
2395
|
+
return shared.hasPrAutomationCause(pr, causeKey) || isPrAutomationCausePending(project, pr, causeKey);
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2311
2398
|
|
|
2312
2399
|
// Tracks per-process which silent-discovery warnings have already been logged
|
|
2313
2400
|
// so we don't spam the log every tick. Cleared on process exit (no need to
|
|
@@ -2448,10 +2535,13 @@ async function discoverFromPrs(config, project) {
|
|
|
2448
2535
|
|
|
2449
2536
|
// Fresh reviewer comments are actionable fixes, even while the PR is otherwise
|
|
2450
2537
|
// awaiting a stale-vote re-review or has build-fix retries escalated.
|
|
2451
|
-
const
|
|
2538
|
+
const humanFixBaseKey = `human-fix-${project?.name || 'default'}-${prDisplayId}`;
|
|
2539
|
+
const humanCauseKey = getPrAutomationCauseKey('human-comment', pr);
|
|
2540
|
+
const humanFixKey = getPrAutomationDispatchKey(humanFixBaseKey, humanCauseKey);
|
|
2452
2541
|
const hasCoalescedFeedback = (dispatchCooldowns.get(humanFixKey)?.pendingContexts || []).length > 0;
|
|
2453
2542
|
if (pollEnabled && autoFixHumanComments && (pr.humanFeedback?.pendingFix || hasCoalescedFeedback) && !fixDispatched) {
|
|
2454
2543
|
const key = humanFixKey;
|
|
2544
|
+
if (isPrAutomationCauseHandledOrPending(project, pr, humanCauseKey)) continue;
|
|
2455
2545
|
let staleCoalesced = [];
|
|
2456
2546
|
const alreadyDispatched = isAlreadyDispatched(key);
|
|
2457
2547
|
const blockedByCooldown = isOnCooldown(key, cooldownMs);
|
|
@@ -2492,7 +2582,7 @@ async function discoverFromPrs(config, project) {
|
|
|
2492
2582
|
pr_id: pr.id, pr_number: prNumber, pr_title: pr.title || '', pr_branch: prBranch,
|
|
2493
2583
|
reviewer: 'Human Reviewer',
|
|
2494
2584
|
review_note: reviewNote,
|
|
2495
|
-
}, `Fix ${pr.id}: ${pr.title || ''} — human feedback`, { dispatchKey: key, source: 'pr-human-feedback', pr, branch: prBranch, project: projMeta });
|
|
2585
|
+
}, `Fix ${pr.id}: ${pr.title || ''} — human feedback`, { dispatchKey: key, automationCauseKey: humanCauseKey, source: 'pr-human-feedback', pr, branch: prBranch, project: projMeta });
|
|
2496
2586
|
if (item) { newWork.push(item); fixDispatched = true; }
|
|
2497
2587
|
}
|
|
2498
2588
|
|
|
@@ -2547,7 +2637,9 @@ async function discoverFromPrs(config, project) {
|
|
|
2547
2637
|
// PRs with changes requested → route back to author for fix.
|
|
2548
2638
|
// Gate on evalLoopEnabled and provider polling — the review→fix cycle depends on fresh vote state.
|
|
2549
2639
|
if (evalLoopEnabled && pollEnabled && autoFixReviewFeedback && reviewStatus === 'changes-requested' && !awaitingReReview && !fixDispatched) {
|
|
2550
|
-
const
|
|
2640
|
+
const reviewCauseKey = getPrAutomationCauseKey('review-feedback', pr);
|
|
2641
|
+
const key = getPrAutomationDispatchKey(`fix-${project?.name || 'default'}-${prDisplayId}`, reviewCauseKey);
|
|
2642
|
+
if (isPrAutomationCauseHandledOrPending(project, pr, reviewCauseKey)) continue;
|
|
2551
2643
|
if (fixThrottled || isAlreadyDispatched(key) || isOnCooldown(key, cooldownMs)) continue;
|
|
2552
2644
|
const agentId = resolveAgent('fix', config, { authorAgent: pr.agent });
|
|
2553
2645
|
if (!agentId) continue;
|
|
@@ -2557,7 +2649,7 @@ async function discoverFromPrs(config, project) {
|
|
|
2557
2649
|
const item = buildPrDispatch(agentId, config, project, pr, 'fix', {
|
|
2558
2650
|
pr_id: pr.id, pr_branch: prBranch,
|
|
2559
2651
|
review_note: pr.minionsReview?.note || pr.reviewNote || 'See PR thread comments',
|
|
2560
|
-
}, `Fix ${pr.id}: ${pr.title || ''} — review feedback`, { dispatchKey: key, source: 'pr', pr, branch: prBranch, project: projMeta });
|
|
2652
|
+
}, `Fix ${pr.id}: ${pr.title || ''} — review feedback`, { dispatchKey: key, automationCauseKey: reviewCauseKey, source: 'pr', pr, branch: prBranch, project: projMeta });
|
|
2561
2653
|
if (item) {
|
|
2562
2654
|
newWork.push(item); setCooldown(key); fixDispatched = true;
|
|
2563
2655
|
}
|
|
@@ -2572,7 +2664,9 @@ async function discoverFromPrs(config, project) {
|
|
|
2572
2664
|
}
|
|
2573
2665
|
const autoFixBuilds = config.engine?.autoFixBuilds ?? ENGINE_DEFAULTS.autoFixBuilds;
|
|
2574
2666
|
if (pollEnabled && autoFixBuilds && pr.status === PR_STATUS.ACTIVE && pr.buildStatus === 'failing') {
|
|
2575
|
-
const
|
|
2667
|
+
const buildCauseKey = getPrAutomationCauseKey('build', pr);
|
|
2668
|
+
const key = getPrAutomationDispatchKey(`build-fix-${project?.name || 'default'}-${prDisplayId}`, buildCauseKey);
|
|
2669
|
+
if (isPrAutomationCauseHandledOrPending(project, pr, buildCauseKey)) continue;
|
|
2576
2670
|
if (fixThrottled || isAlreadyDispatched(key) || isOnCooldown(key, cooldownMs)) continue;
|
|
2577
2671
|
|
|
2578
2672
|
// Pre-dispatch live build check — cached buildStatus may be stale: ADO can
|
|
@@ -2633,7 +2727,7 @@ async function discoverFromPrs(config, project) {
|
|
|
2633
2727
|
const item = buildPrDispatch(agentId, config, project, pr, 'fix', {
|
|
2634
2728
|
pr_id: pr.id, pr_branch: prBranch,
|
|
2635
2729
|
review_note: reviewNote,
|
|
2636
|
-
}, `Fix build failure on ${pr.id}: ${pr.title || ''}`, { dispatchKey: key, source: 'pr', pr, branch: prBranch, project: projMeta });
|
|
2730
|
+
}, `Fix build failure on ${pr.id}: ${pr.title || ''}`, { dispatchKey: key, automationCauseKey: buildCauseKey, source: 'pr', pr, branch: prBranch, project: projMeta });
|
|
2637
2731
|
if (item) {
|
|
2638
2732
|
newWork.push(item); setCooldown(key); fixDispatched = true;
|
|
2639
2733
|
try {
|
|
@@ -2664,13 +2758,14 @@ async function discoverFromPrs(config, project) {
|
|
|
2664
2758
|
// PRs with merge conflicts — dispatch fix to resolve (gated by provider polling + autoFixConflicts)
|
|
2665
2759
|
const autoFixConflicts = config.engine?.autoFixConflicts ?? ENGINE_DEFAULTS.autoFixConflicts;
|
|
2666
2760
|
if (pollEnabled && autoFixConflicts && pr.status === PR_STATUS.ACTIVE && pr._mergeConflict && !fixDispatched) {
|
|
2667
|
-
const
|
|
2761
|
+
const conflictCauseKey = getPrAutomationCauseKey('merge-conflict', pr);
|
|
2762
|
+
const key = getPrAutomationDispatchKey(`conflict-fix-${project?.name || 'default'}-${prDisplayId}`, conflictCauseKey);
|
|
2668
2763
|
// Suppress re-dispatch for 10 min after last attempt — ADO/GitHub recomputes
|
|
2669
2764
|
// mergeStatus asynchronously (1–5 min lag), so the flag may stay set even after
|
|
2670
2765
|
// a successful push. _conflictFixedAt is cleared when the poller confirms clean status.
|
|
2671
2766
|
const conflictFixedAt = pr._conflictFixedAt;
|
|
2672
2767
|
const withinLag = conflictFixedAt && Date.now() - new Date(conflictFixedAt).getTime() < 10 * 60 * 1000;
|
|
2673
|
-
if (!withinLag && !fixThrottled && !isAlreadyDispatched(key) && !isOnCooldown(key, cooldownMs)) {
|
|
2768
|
+
if (!withinLag && !fixThrottled && !isPrAutomationCauseHandledOrPending(project, pr, conflictCauseKey) && !isAlreadyDispatched(key) && !isOnCooldown(key, cooldownMs)) {
|
|
2674
2769
|
// Pre-dispatch live conflict check — cached `_mergeConflict` may be
|
|
2675
2770
|
// stale: ADO/GitHub recompute mergeStatus asynchronously (1–5 min lag),
|
|
2676
2771
|
// so a successful upstream merge can leave the flag set even after the
|
|
@@ -2702,7 +2797,7 @@ async function discoverFromPrs(config, project) {
|
|
|
2702
2797
|
const item = buildPrDispatch(agentId, config, project, pr, 'fix', {
|
|
2703
2798
|
pr_id: pr.id, pr_branch: prBranch,
|
|
2704
2799
|
review_note: `This PR has merge conflicts with the target branch. Inspect the live PR and repository history, choose the safest merge/rebase/update strategy, resolve all conflicts, validate the result, and push the branch.`,
|
|
2705
|
-
}, `Fix merge conflicts on ${pr.id}: ${pr.title || ''}`, { dispatchKey: key, source: 'pr', pr, branch: prBranch, project: projMeta });
|
|
2800
|
+
}, `Fix merge conflicts on ${pr.id}: ${pr.title || ''}`, { dispatchKey: key, automationCauseKey: conflictCauseKey, source: 'pr', pr, branch: prBranch, project: projMeta });
|
|
2706
2801
|
if (item) {
|
|
2707
2802
|
newWork.push(item);
|
|
2708
2803
|
setCooldown(key);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1724",
|
|
4
4
|
"description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
|
|
5
5
|
"bin": {
|
|
6
6
|
"minions": "bin/minions.js"
|