@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.
@@ -0,0 +1,5 @@
1
+ {
2
+ "runtime": "copilot",
3
+ "models": null,
4
+ "cachedAt": "2026-05-12T20:22:16.745Z"
5
+ }
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
  };
@@ -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
- target.lastDispatchedAt = now;
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
- delete target.lastDispatchedAt;
1881
- delete target.lastDispatchOutcome;
1882
- delete target.lastDispatchHeadSha;
1883
- delete target.lastDispatchReason;
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 noop'd against
2975
- // the same head SHA — chronic across PRs #2315–#2323 where every fix
2976
- // agent rebutted "this is a pre-existing master baseline" but the
2977
- // cached buildStatus:failing kept re-triggering the loop. The check
2978
- // clears automatically once a new commit lands (lastDispatchHeadSha
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
- if (pr.lastDispatchOutcome === 'noop'
2982
- && pr.lastDispatchHeadSha
2987
+ const lastBuildDispatch = pr._lastDispatchByCause?.[shared.PR_FIX_CAUSE.BUILD_FAILURE];
2988
+ if (lastBuildDispatch?.outcome === 'noop'
2989
+ && lastBuildDispatch.headSha
2983
2990
  && currentHeadSha
2984
- && pr.lastDispatchHeadSha === currentHeadSha) {
2985
- log('info', `Skipping build-fix for ${pr.id}: last dispatch was noop on the same head ${currentHeadSha.slice(0, 8)} (${(pr.lastDispatchReason || '').slice(0, 120)})`);
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.1904",
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"