@yemi33/minions 0.1.1904 → 0.1.1906
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/engine/copilot-models.json +5 -0
- package/engine/github.js +68 -0
- package/engine/lifecycle.js +34 -8
- package/engine/shared.js +1 -0
- package/engine.js +17 -10
- package/package.json +1 -1
package/engine/github.js
CHANGED
|
@@ -85,9 +85,54 @@ function _isNonActionableComment(c, config = {}) {
|
|
|
85
85
|
if (_isAgentComment(c)) return true;
|
|
86
86
|
if (_isCiReportCommentBody(c?.body)) return true;
|
|
87
87
|
if (_isPreviewStatusComment(c)) return true;
|
|
88
|
+
if (_isAgentPositiveSignalComment(c, config)) return true;
|
|
88
89
|
return false;
|
|
89
90
|
}
|
|
90
91
|
|
|
92
|
+
// P-a3f9b2c1 — Detect agent self-positive-signal comments (verification SUCCESS reports,
|
|
93
|
+
// VERDICT:APPROVE recaps, noop:true confessions) posted via the shared `gh` PAT identity.
|
|
94
|
+
//
|
|
95
|
+
// Sibling to PR #2431's `_isAgentSelfReviewDeclinedComment` but keys off body-shape markers
|
|
96
|
+
// + an opt-in shared-minions login allowlist (REST `/issues/:n/comments` doesn't expose
|
|
97
|
+
// GraphQL's `viewerDidAuthor` flag, so we approximate via configured logins).
|
|
98
|
+
//
|
|
99
|
+
// Configuration:
|
|
100
|
+
// engine.botCommentLogins: string[] — primary opt-in list (e.g. ['yemi33'])
|
|
101
|
+
// engine.ignoredCommentAuthors: string[] — fallback (back-compat with existing knob)
|
|
102
|
+
//
|
|
103
|
+
// Reference: PR #2433 (3 consecutive noop fix-dispatches when verify agents posted
|
|
104
|
+
// "Result: SUCCESS" comments via the shared yemi33 PAT identity).
|
|
105
|
+
function _sharedMinionsLogins(config = {}) {
|
|
106
|
+
const primary = Array.isArray(config?.engine?.botCommentLogins) ? config.engine.botCommentLogins : [];
|
|
107
|
+
const fallback = Array.isArray(config?.engine?.ignoredCommentAuthors) ? config.engine.ignoredCommentAuthors : [];
|
|
108
|
+
return new Set([...primary, ...fallback].map(s => String(s || '').toLowerCase()).filter(Boolean));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Pure body-shape matcher for agent positive-signal markers in PR comments.
|
|
112
|
+
// Triggers on:
|
|
113
|
+
// - "Verification SUCCESS" / "Verification SUCCEEDED" / "Verification ran" (verify-dispatch reports)
|
|
114
|
+
// - "Result: SUCCESS" (verify-dispatch report header per playbooks/verify.md)
|
|
115
|
+
// - "VERDICT: APPROVE" (review-loop positive verdict — defense-in-depth alongside _hasMinionsReviewVerdict)
|
|
116
|
+
// - `noop: true` / `"noop": true` (completion-report-style noop confession copied to comment)
|
|
117
|
+
function _hasAgentPositiveSignalBody(body) {
|
|
118
|
+
const text = String(body || '');
|
|
119
|
+
if (!text) return false;
|
|
120
|
+
if (/(?:^|\n)\s*\*{0,2}VERDICT[:\s]+\*{0,2}APPROVE\b/i.test(text)) return true;
|
|
121
|
+
if (/"?\bnoop\b"?\s*:\s*true\b/i.test(text)) return true;
|
|
122
|
+
if (/\bVerification\s+(?:SUCCESS(?:FUL|FULLY)?|SUCCEEDED|ran)\b/i.test(text)) return true;
|
|
123
|
+
if (/(?:^|\n)\s*#{0,3}\s*Result\s*:\s*SUCCESS\b/i.test(text)) return true;
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function _isAgentPositiveSignalComment(c, config = {}) {
|
|
128
|
+
if (!c) return false;
|
|
129
|
+
const sharedLogins = _sharedMinionsLogins(config);
|
|
130
|
+
if (sharedLogins.size === 0) return false;
|
|
131
|
+
const login = String(c?.user?.login || '').toLowerCase();
|
|
132
|
+
if (!login || !sharedLogins.has(login)) return false;
|
|
133
|
+
return _hasAgentPositiveSignalBody(c.body);
|
|
134
|
+
}
|
|
135
|
+
|
|
91
136
|
// W-mp2h696g000a7bc0 — Detect agent self-review-declined no-op comments.
|
|
92
137
|
//
|
|
93
138
|
// Per the documented contract (memory `subject: self-review`, docs/completion-reports.md:83-105):
|
|
@@ -838,6 +883,26 @@ async function pollPrHumanComments(config) {
|
|
|
838
883
|
}
|
|
839
884
|
if (newComments.length === 0) return false;
|
|
840
885
|
|
|
886
|
+
// P-a3f9b2c1 — Defense-in-depth: when the PR is already approved AND every new comment
|
|
887
|
+
// is from a configured shared-minions PAT identity AND every new comment matches the
|
|
888
|
+
// positive-signal body shape, suppress pendingFix and advance cutoff. Belt-and-suspenders
|
|
889
|
+
// against future regressions in `_isAgentPositiveSignalComment`. This is intentionally
|
|
890
|
+
// narrow — a single human-shaped comment in newComments (e.g. "rename _foo to bar") will
|
|
891
|
+
// disqualify the entire batch and let the normal human-feedback flow run.
|
|
892
|
+
if (String(pr.reviewStatus || '').toLowerCase() === 'approved') {
|
|
893
|
+
const sharedLogins = _sharedMinionsLogins(config);
|
|
894
|
+
if (sharedLogins.size > 0) {
|
|
895
|
+
const allFromSharedPat = newComments.every(nc => sharedLogins.has(String(nc.author || '').toLowerCase()));
|
|
896
|
+
const allPositiveShape = newComments.every(nc => _hasAgentPositiveSignalBody(nc.content));
|
|
897
|
+
if (allFromSharedPat && allPositiveShape) {
|
|
898
|
+
const cutoff = allNewDates.sort().pop() || newComments[newComments.length - 1].date;
|
|
899
|
+
pr.humanFeedback = { ...(pr.humanFeedback || {}), lastProcessedCommentDate: cutoff };
|
|
900
|
+
log('info', `PR ${pr.id}: ${newComments.length} new shared-PAT positive-signal comment(s) on approved PR — defense-in-depth suppression`);
|
|
901
|
+
return true;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
841
906
|
// Sort all comments chronologically and build full context for the fix agent
|
|
842
907
|
allCommentEntries.sort((a, b) => a.date.localeCompare(b.date));
|
|
843
908
|
newComments.sort((a, b) => a.date.localeCompare(b.date));
|
|
@@ -1136,5 +1201,8 @@ module.exports = {
|
|
|
1136
1201
|
_isAgentComment, // exported for testing
|
|
1137
1202
|
_isNonActionableComment, // exported for testing
|
|
1138
1203
|
_isAgentSelfReviewDeclinedComment, // exported for testing
|
|
1204
|
+
_isAgentPositiveSignalComment, // exported for testing (P-a3f9b2c1)
|
|
1205
|
+
_hasAgentPositiveSignalBody, // exported for testing (P-a3f9b2c1)
|
|
1206
|
+
_sharedMinionsLogins, // exported for testing (P-a3f9b2c1)
|
|
1139
1207
|
_isPreviewStatusComment, // exported for testing
|
|
1140
1208
|
};
|
package/engine/lifecycle.js
CHANGED
|
@@ -1847,15 +1847,32 @@ function recordPrNoOpFixAttempt(target, cause, source, dispatchItem, branchChang
|
|
|
1847
1847
|
// filter can short-circuit duplicate build-fix dispatches against an
|
|
1848
1848
|
// unchanged commit. Reset happens implicitly when headSha advances and the
|
|
1849
1849
|
// discovery filter compares lastDispatchHeadSha to the current head.
|
|
1850
|
+
//
|
|
1851
|
+
// Tracking is partitioned by cause (`_lastDispatchByCause[cause]`) so a
|
|
1852
|
+
// human-feedback noop on head X never suppresses an unrelated build-failure
|
|
1853
|
+
// dispatch on head X (the PR-wide top-level fields would, see
|
|
1854
|
+
// W-mp2vohea00112739). The legacy top-level fields are kept in sync for
|
|
1855
|
+
// any external consumer (dashboards, debugging) but the build-fix
|
|
1856
|
+
// eligibility filter reads ONLY the per-cause map.
|
|
1850
1857
|
const headSha = getPrFixBaselineHead(target);
|
|
1851
|
-
|
|
1852
|
-
target.lastDispatchOutcome = 'noop';
|
|
1853
|
-
target.lastDispatchHeadSha = headSha;
|
|
1854
|
-
target.lastDispatchReason = String(
|
|
1858
|
+
const reasonText = String(
|
|
1855
1859
|
noopReason
|
|
1856
1860
|
|| branchChange?.reason
|
|
1857
1861
|
|| 'fix completed without changing the PR branch'
|
|
1858
1862
|
).slice(0, 500);
|
|
1863
|
+
target._lastDispatchByCause = target._lastDispatchByCause
|
|
1864
|
+
&& typeof target._lastDispatchByCause === 'object' ? target._lastDispatchByCause : {};
|
|
1865
|
+
target._lastDispatchByCause[cause] = {
|
|
1866
|
+
outcome: 'noop',
|
|
1867
|
+
headSha,
|
|
1868
|
+
reason: reasonText,
|
|
1869
|
+
dispatchedAt: now,
|
|
1870
|
+
dispatchId: dispatchItem?.id || null,
|
|
1871
|
+
};
|
|
1872
|
+
target.lastDispatchedAt = now;
|
|
1873
|
+
target.lastDispatchOutcome = 'noop';
|
|
1874
|
+
target.lastDispatchHeadSha = headSha;
|
|
1875
|
+
target.lastDispatchReason = reasonText;
|
|
1859
1876
|
|
|
1860
1877
|
if (cause === shared.PR_FIX_CAUSE.HUMAN_FEEDBACK && target.humanFeedback) {
|
|
1861
1878
|
target.humanFeedback.pendingFix = !paused;
|
|
@@ -1877,10 +1894,19 @@ function clearPrNoOpFixAttempt(target, cause) {
|
|
|
1877
1894
|
// the same head; once the agent actually pushed a fix we no longer want them
|
|
1878
1895
|
// to suppress a fresh dispatch (the SHA may have moved or the next failure
|
|
1879
1896
|
// is genuinely new).
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1897
|
+
if (target._lastDispatchByCause && typeof target._lastDispatchByCause === 'object') {
|
|
1898
|
+
delete target._lastDispatchByCause[cause];
|
|
1899
|
+
if (Object.keys(target._lastDispatchByCause).length === 0) delete target._lastDispatchByCause;
|
|
1900
|
+
}
|
|
1901
|
+
// Only clear the legacy top-level fields when no other cause still has a
|
|
1902
|
+
// tracked noop — otherwise a successful fix for cause A would silently wipe
|
|
1903
|
+
// out the same-head suppression record for unrelated cause B.
|
|
1904
|
+
if (!target._lastDispatchByCause) {
|
|
1905
|
+
delete target.lastDispatchedAt;
|
|
1906
|
+
delete target.lastDispatchOutcome;
|
|
1907
|
+
delete target.lastDispatchHeadSha;
|
|
1908
|
+
delete target.lastDispatchReason;
|
|
1909
|
+
}
|
|
1884
1910
|
}
|
|
1885
1911
|
|
|
1886
1912
|
function updatePrAfterFix(pr, project, source, options = {}, legacyDispatchId = '') {
|
package/engine/shared.js
CHANGED
|
@@ -1099,6 +1099,7 @@ const ENGINE_DEFAULTS = {
|
|
|
1099
1099
|
autoCompletePrs: false, // auto-merge PRs when builds green + review approved (opt-in)
|
|
1100
1100
|
prMergeMethod: 'squash', // merge method: squash, merge, rebase
|
|
1101
1101
|
ignoredCommentAuthors: [], // comments from these authors are auto-closed and never trigger fixes
|
|
1102
|
+
botCommentLogins: [], // P-a3f9b2c1: opt-in shared-minions GH login list — comments from these logins are suppressed ONLY when body matches positive-signal markers (Verification SUCCESS / VERDICT:APPROVE / noop:true). Narrower than ignoredCommentAuthors which suppresses all comments by login.
|
|
1102
1103
|
agentBusyReassignMs: 600000, // 10min — reassign work item to another agent if preferred agent is busy beyond this threshold
|
|
1103
1104
|
ccEffort: null, // effort level for CC/doc-chat (null, 'low', 'medium', 'high')
|
|
1104
1105
|
enablePreDispatchEval: false, // opt-in: cheap LLM gate before queueing — see engine/pre-dispatch-eval.js (Ripley §3 recommendation, 2026-05-11 architecture review)
|
package/engine.js
CHANGED
|
@@ -2971,18 +2971,25 @@ async function discoverFromPrs(config, project) {
|
|
|
2971
2971
|
const autoFixBuilds = config.engine?.autoFixBuilds ?? ENGINE_DEFAULTS.autoFixBuilds;
|
|
2972
2972
|
if (pollEnabled && autoFixBuilds && pr.status === PR_STATUS.ACTIVE && pr.buildStatus === 'failing'
|
|
2973
2973
|
&& !isPrNoOpFixCauseSuppressed(pr, shared.PR_FIX_CAUSE.BUILD_FAILURE)) {
|
|
2974
|
-
// P-b7e1c4d2: skip when the most recent dispatch already
|
|
2975
|
-
// the same head SHA — chronic across PRs #2315–#2323
|
|
2976
|
-
// agent rebutted "this is a pre-existing master baseline"
|
|
2977
|
-
// cached buildStatus:failing kept re-triggering the loop. The
|
|
2978
|
-
// clears automatically once a new commit lands (
|
|
2979
|
-
// stops matching the current head).
|
|
2974
|
+
// P-b7e1c4d2: skip when the most recent BUILD-FAILURE dispatch already
|
|
2975
|
+
// noop'd against the same head SHA — chronic across PRs #2315–#2323
|
|
2976
|
+
// where every fix agent rebutted "this is a pre-existing master baseline"
|
|
2977
|
+
// but the cached buildStatus:failing kept re-triggering the loop. The
|
|
2978
|
+
// check clears automatically once a new commit lands (the per-cause
|
|
2979
|
+
// headSha stops matching the current head).
|
|
2980
|
+
//
|
|
2981
|
+
// W-mp2vohea00112739: this guard reads `_lastDispatchByCause['build-failure']`
|
|
2982
|
+
// instead of the PR-wide `lastDispatch*` fields. The PR-wide fields are
|
|
2983
|
+
// shared across all causes, so a `human-feedback` noop on head X used to
|
|
2984
|
+
// suppress an unrelated `build-failure` dispatch on the same head until
|
|
2985
|
+
// a new commit landed (live repro on PR #2433).
|
|
2980
2986
|
const currentHeadSha = String(pr.headSha || pr._adoSourceCommit || pr._adoHeadCommit || '').trim();
|
|
2981
|
-
|
|
2982
|
-
|
|
2987
|
+
const lastBuildDispatch = pr._lastDispatchByCause?.[shared.PR_FIX_CAUSE.BUILD_FAILURE];
|
|
2988
|
+
if (lastBuildDispatch?.outcome === 'noop'
|
|
2989
|
+
&& lastBuildDispatch.headSha
|
|
2983
2990
|
&& currentHeadSha
|
|
2984
|
-
&&
|
|
2985
|
-
log('info', `Skipping build-fix for ${pr.id}: last dispatch was noop on the same head ${currentHeadSha.slice(0, 8)} (${(
|
|
2991
|
+
&& lastBuildDispatch.headSha === currentHeadSha) {
|
|
2992
|
+
log('info', `Skipping build-fix for ${pr.id}: last build-failure dispatch was noop on the same head ${currentHeadSha.slice(0, 8)} (${(lastBuildDispatch.reason || '').slice(0, 120)})`);
|
|
2986
2993
|
continue;
|
|
2987
2994
|
}
|
|
2988
2995
|
const buildCauseKey = getPrAutomationCauseKey('build', pr);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1906",
|
|
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"
|