@yemi33/minions 0.1.2101 → 0.1.2102

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.
@@ -75,11 +75,11 @@ Each fix cause (build-failure, review-feedback, human-feedback, merge-conflict,
75
75
 
76
76
  A paused cause **suppresses further auto-dispatch for that cause** until cleared. Recovery paths (any one):
77
77
 
78
- 1. **Push a new SHA to the PR branch** — the next poll refreshes `pr.headSha`, the evidence fingerprint shifts, and `clearPrNoOpFixAttempt` runs on the next non-noop completion.
78
+ 1. **Push a new SHA to the PR branch** — the next poll refreshes `pr.headSha`, the evidence fingerprint shifts, and `clearPrNoOpFixAttempt` runs on the next non-noop completion. Since #2979 (head-SHA in BUILD_FAILURE + REVIEW_FEEDBACK fingerprints) and W-mpx44p05000ze8d8 (proactive GC sweep in `pollPrStatus`), a force-push also drops the stale `_noOpFixes[cause]` record on the next poll — the dashboard `_pausedCauses` chip clears without waiting for a fresh fix dispatch.
79
79
  2. **Click the red chip on the dashboard** — confirms, then `POST /api/pull-requests/clear-paused-cause` (`dashboard.js`, issue #2969) validates the cause against `shared.PR_FIX_CAUSE`, locates the PR via `mutatePullRequests` across project/central files, and calls `clearPrNoOpFixAttempt` to wipe the cause record.
80
80
  3. **Direct API call** — `POST /api/pull-requests/clear-paused-cause` with `{ prId, cause }`.
81
81
 
82
- The exported `recordPrNoOpFixAttempt` / `clearPrNoOpFixAttempt` helpers in `engine/lifecycle.js` are the only sanctioned entry points; the dispatch evaluator in `engine/lifecycle.js` (search `_noOpFixes[cause]`) gates re-dispatch on `record.paused === true`.
82
+ The exported `recordPrNoOpFixAttempt` / `clearPrNoOpFixAttempt` / `sweepStalePrNoOpFixes` helpers in `engine/lifecycle.js` are the only sanctioned entry points. The dispatch evaluator gates re-dispatch on `shared.isPrNoOpFixCausePaused(pr, cause)` (which re-validates the fingerprint per call), the dashboard chip surface uses `shared.getPrPausedCauses(pr)` (same gate), and the `pollPrStatus` callback in `engine/ado.js` + `engine/github.js` calls `sweepStalePrNoOpFixes(pr)` each tick to GC records whose fingerprint no longer matches the live PR.
83
83
 
84
84
  ## 5. Fix completes
85
85
 
package/engine/ado.js CHANGED
@@ -24,6 +24,12 @@ function engine() {
24
24
  let _dispatch = null;
25
25
  function dispatchModule() { if (!_dispatch) _dispatch = require('./dispatch'); return _dispatch; }
26
26
 
27
+ // Lazy require for lifecycle module — only needed for the stale-noopfix sweep
28
+ // in pollPrStatus (W-mpx44p05000ze8d8). lifecycle.js lazy-requires ado.js back,
29
+ // so this MUST stay lazy to avoid a circular import at module-load time.
30
+ let _lifecycle = null;
31
+ function lifecycleModule() { if (!_lifecycle) _lifecycle = require('./lifecycle'); return _lifecycle; }
32
+
27
33
  const stripRefsHeads = s => (s || '').replace('refs/heads/', '');
28
34
  const getAdoPrUrl = (project, prNumber) => {
29
35
  if (project.prUrlBase) return `${project.prUrlBase}${prNumber}`;
@@ -1476,6 +1482,21 @@ async function pollPrStatus(config) {
1476
1482
  updated = true;
1477
1483
  }
1478
1484
 
1485
+ // W-mpx44p05000ze8d8 — Proactive GC for stale `_noOpFixes` records. Any
1486
+ // cause whose stored evidence fingerprint no longer matches the live PR
1487
+ // (head SHA shifted, build failure reason changed, new human comment,
1488
+ // review status flipped, etc.) is dropped via clearPrNoOpFixAttempt. The
1489
+ // dispatch gate already ignores these stale records (isPrNoOpFixCausePaused
1490
+ // re-validates the fingerprint), but without this sweep they linger in
1491
+ // pull-requests.json forever — bloating the file and confusing the
1492
+ // dashboard `pr-paused-chip`. Runs AFTER all live-field updates so the
1493
+ // fingerprint comparison uses post-poll PR state.
1494
+ try {
1495
+ if (lifecycleModule().sweepStalePrNoOpFixes(pr)) updated = true;
1496
+ } catch (e) {
1497
+ log('warn', `sweepStalePrNoOpFixes for ${pr.id}: ${e.message}`);
1498
+ }
1499
+
1479
1500
  return updated;
1480
1501
  } catch (err) {
1481
1502
  // Auth errors → mark build status stale so dashboard shows uncertainty
package/engine/github.js CHANGED
@@ -23,6 +23,12 @@ function engine() {
23
23
  let _dispatch = null;
24
24
  function dispatchModule() { if (!_dispatch) _dispatch = require('./dispatch'); return _dispatch; }
25
25
 
26
+ // Lazy require for lifecycle module — only needed for the stale-noopfix sweep
27
+ // in pollPrStatus (W-mpx44p05000ze8d8). lifecycle.js lazy-requires github.js
28
+ // back, so this MUST stay lazy to avoid a circular import at module-load time.
29
+ let _lifecycle = null;
30
+ function lifecycleModule() { if (!_lifecycle) _lifecycle = require('./lifecycle'); return _lifecycle; }
31
+
26
32
  // W-mp5trwh60008386d: test seam so unit tests can mock `gh api` shell-outs
27
33
  // without spawning the real CLI. Production code goes through the real
28
34
  // `execAsync` from shared; tests call `_setExecAsyncForTest(fn)` to swap in
@@ -1021,6 +1027,21 @@ async function pollPrStatus(config) {
1021
1027
  }
1022
1028
  }
1023
1029
 
1030
+ // W-mpx44p05000ze8d8 — Proactive GC for stale `_noOpFixes` records. Any
1031
+ // cause whose stored evidence fingerprint no longer matches the live PR
1032
+ // (head SHA shifted, build failure reason changed, new human comment,
1033
+ // review status flipped, etc.) is dropped via clearPrNoOpFixAttempt. The
1034
+ // dispatch gate already ignores these stale records (isPrNoOpFixCausePaused
1035
+ // re-validates the fingerprint), but without this sweep they linger in
1036
+ // pull-requests.json forever — bloating the file and confusing the
1037
+ // dashboard `pr-paused-chip`. Runs AFTER all live-field updates so the
1038
+ // fingerprint comparison uses post-poll PR state.
1039
+ try {
1040
+ if (lifecycleModule().sweepStalePrNoOpFixes(pr)) updated = true;
1041
+ } catch (e) {
1042
+ log('warn', `sweepStalePrNoOpFixes for ${pr.id}: ${e.message}`);
1043
+ }
1044
+
1024
1045
  return updated;
1025
1046
  });
1026
1047
 
@@ -2090,6 +2090,37 @@ function clearPrNoOpFixAttempt(target, cause) {
2090
2090
  }
2091
2091
  }
2092
2092
 
2093
+ // W-mpx44p05000ze8d8 — Proactive GC for stale `_noOpFixes` records. Called
2094
+ // from `engine/ado.js` + `engine/github.js` `pollPrStatus` after the live PR
2095
+ // fields (headSha, buildStatus, humanFeedback, mergeStatus, reviewStatus,
2096
+ // lastPushedAt, etc.) have been refreshed for the current tick. Any
2097
+ // `_noOpFixes[cause]` whose stored `evidenceFingerprint` no longer matches
2098
+ // the live one is dropped via `clearPrNoOpFixAttempt` — same cleanup the
2099
+ // resume endpoint and successful-fix path already use. Records without a
2100
+ // stored `evidenceFingerprint` (impossible from `recordPrNoOpFixAttempt`
2101
+ // today, but safe against future / legacy data) also drop, because the
2102
+ // dispatch gate already ignores them. Returns true when any record was
2103
+ // removed so the poll caller can flip its `updated` flag to persist the
2104
+ // purge.
2105
+ function sweepStalePrNoOpFixes(target) {
2106
+ if (!target || !target._noOpFixes || typeof target._noOpFixes !== 'object') return false;
2107
+ let modified = false;
2108
+ // Snapshot cause keys before mutation — clearPrNoOpFixAttempt deletes the
2109
+ // whole `_noOpFixes` object once the last record is removed, which would
2110
+ // invalidate a live `Object.keys()` iterator.
2111
+ const causes = Object.keys(target._noOpFixes);
2112
+ for (const cause of causes) {
2113
+ const record = target._noOpFixes[cause];
2114
+ if (!record || typeof record !== 'object') continue;
2115
+ const liveFp = shared.prFixEvidenceFingerprint(target, cause);
2116
+ if (record.evidenceFingerprint !== liveFp) {
2117
+ clearPrNoOpFixAttempt(target, cause);
2118
+ modified = true;
2119
+ }
2120
+ }
2121
+ return modified;
2122
+ }
2123
+
2093
2124
  function updatePrAfterFix(pr, project, source, options = {}, legacyDispatchId = '') {
2094
2125
  if (!pr?.id) return;
2095
2126
  if (!options || typeof options !== 'object' || Array.isArray(options)) {
@@ -5137,6 +5168,10 @@ module.exports = {
5137
5168
  // Issue #2969 — exported so dashboard.js can clear a paused cause from the
5138
5169
  // resume endpoint.
5139
5170
  clearPrNoOpFixAttempt,
5171
+ // W-mpx44p05000ze8d8 — exported so engine/ado.js + engine/github.js can
5172
+ // drop stale `_noOpFixes` records during their PR status poll, and so
5173
+ // unit tests can exercise the sweep helper directly.
5174
+ sweepStalePrNoOpFixes,
5140
5175
  // Issue #2969 — exported for direct unit testing of the pause-flip alert
5141
5176
  // path without going through updatePrAfterFix's many guards.
5142
5177
  recordPrNoOpFixAttempt,
package/engine/shared.js CHANGED
@@ -5261,20 +5261,18 @@ function isPrNoOpFixCausePaused(pr, cause) {
5261
5261
  return record.evidenceFingerprint === prFixEvidenceFingerprint(pr, cause);
5262
5262
  }
5263
5263
 
5264
- // Issue #2969 — Derive the list of currently-paused causes for surfacing on
5265
- // `/api/pull-requests` + the dashboard PR card. Returns deduped cause strings
5266
- // from `_noOpFixes[c].paused === true`. Does NOT re-validate evidence
5267
- // fingerprints (the dashboard surface shows the persisted pause flag — when
5268
- // evidence drifts and the discovery filter ignores the stale record, the
5269
- // next fix dispatch overwrites `_noOpFixes[cause]` and naturally re-derives
5270
- // this field on the following enrichment pass).
5264
+ // Issue #2969 / W-mpx44p05000ze8d8 — Derive the list of currently-paused
5265
+ // causes for surfacing on `/api/pull-requests` + the dashboard PR card.
5266
+ // Mirrors `isPrNoOpFixCausePaused` exactly: a cause is "paused" only when
5267
+ // `record.paused === true` AND the stored `evidenceFingerprint` still
5268
+ // matches the live PR's fingerprint. Without the fingerprint check the chip
5269
+ // surfaced stale pauses for PRs the dispatch gate had already cleared (the
5270
+ // engine would re-dispatch but the dashboard kept showing ⏸).
5271
5271
  function getPrPausedCauses(pr) {
5272
5272
  if (!pr || !pr._noOpFixes || typeof pr._noOpFixes !== 'object') return [];
5273
5273
  const causes = [];
5274
- for (const [cause, record] of Object.entries(pr._noOpFixes)) {
5275
- if (record && typeof record === 'object' && record.paused === true) {
5276
- causes.push(cause);
5277
- }
5274
+ for (const cause of Object.keys(pr._noOpFixes)) {
5275
+ if (isPrNoOpFixCausePaused(pr, cause)) causes.push(cause);
5278
5276
  }
5279
5277
  return causes;
5280
5278
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2101",
3
+ "version": "0.1.2102",
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"