@yemi33/minions 0.1.2174 → 0.1.2175
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/watches.js +72 -1
- package/package.json +1 -1
package/engine/watches.js
CHANGED
|
@@ -150,7 +150,14 @@ function registerTargetType(type, spec) {
|
|
|
150
150
|
throw new Error(`registerTargetType(${type}): absoluteConditions entry '${c}' is not in conditions[]`);
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
|
-
|
|
153
|
+
// W-mqa63opd000ha836 — optional hook: returns true when the target has
|
|
154
|
+
// reached a state from which `condition` can never fire again (e.g. a
|
|
155
|
+
// PR's status is `merged` and we're watching `build-fail`). Default
|
|
156
|
+
// returns false (back-compat — current target types unchanged).
|
|
157
|
+
const terminalFn = typeof spec.isTerminalForCondition === 'function'
|
|
158
|
+
? spec.isTerminalForCondition
|
|
159
|
+
: () => false;
|
|
160
|
+
TARGET_TYPES[type] = { ...spec, absoluteConditions: absoluteSet, isTerminalForCondition: terminalFn };
|
|
154
161
|
}
|
|
155
162
|
|
|
156
163
|
/** Returns the registered spec for a target type, or null. */
|
|
@@ -517,6 +524,40 @@ function checkWatches(config, state) {
|
|
|
517
524
|
});
|
|
518
525
|
}
|
|
519
526
|
|
|
527
|
+
// W-mqa63opd000ha836 — auto-expire watches whose target has reached a
|
|
528
|
+
// terminal state from which the watched condition can never fire again.
|
|
529
|
+
// Independent of the absolute-condition fire-once path above: covers the
|
|
530
|
+
// long-tail case where a `build-fail` (or similar) watch was armed on an
|
|
531
|
+
// active PR that subsequently merged without ever tripping its condition.
|
|
532
|
+
// Without this, the watch sits `active` forever, polling every interval.
|
|
533
|
+
//
|
|
534
|
+
// Guardrail: do NOT auto-expire until the watch has had a chance to fire
|
|
535
|
+
// on its first real check — `triggerCount > 0` covers post-fire watches;
|
|
536
|
+
// `prevState.status === entity.status` covers second-and-later checks
|
|
537
|
+
// (initial _captureState on tick-1 makes these equal, so a watch armed
|
|
538
|
+
// on an already-terminal target with `condition: merged` still fires
|
|
539
|
+
// once via the absolute-condition path before this branch expires it).
|
|
540
|
+
if (watch.status === WATCH_STATUS.ACTIVE) {
|
|
541
|
+
const _ttForTerm = TARGET_TYPES[watch.targetType];
|
|
542
|
+
if (_ttForTerm && typeof _ttForTerm.isTerminalForCondition === 'function') {
|
|
543
|
+
try {
|
|
544
|
+
const _entityForTerm = _ttForTerm.fetchEntity(watch.target, state || {});
|
|
545
|
+
if (_entityForTerm) {
|
|
546
|
+
const _hadShot = (watch.triggerCount || 0) > 0
|
|
547
|
+
|| (previousState && previousState.status !== undefined
|
|
548
|
+
&& previousState.status === _entityForTerm.status);
|
|
549
|
+
if (_hadShot && _ttForTerm.isTerminalForCondition(watch.condition, _entityForTerm, previousState)) {
|
|
550
|
+
watch.status = WATCH_STATUS.EXPIRED;
|
|
551
|
+
const _termStatus = _entityForTerm.status;
|
|
552
|
+
log('info', `Watch auto-expired (terminal target state): ${watch.id} — ${watch.targetType} ${watch.target} is ${_termStatus}, condition ${watch.condition} cannot fire again`);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
} catch (termErr) {
|
|
556
|
+
log('warn', `Watch terminal-state check error (${watch.id}): ${termErr.message}`);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
520
561
|
// Capture state for change detection on next check
|
|
521
562
|
watch._lastState = _captureState(watch, state);
|
|
522
563
|
|
|
@@ -704,6 +745,36 @@ registerTargetType(WATCH_TARGET_TYPE.PR, {
|
|
|
704
745
|
WATCH_CONDITION.MERGED, WATCH_CONDITION.BUILD_FAIL, WATCH_CONDITION.BUILD_PASS,
|
|
705
746
|
WATCH_CONDITION.READY_FOR_MERGE,
|
|
706
747
|
],
|
|
748
|
+
// W-mqa63opd000ha836 — zombie-watch janitor: once a PR reaches a terminal
|
|
749
|
+
// status (merged / closed / abandoned), most PR conditions can never fire
|
|
750
|
+
// again so the watch should auto-expire instead of polling forever.
|
|
751
|
+
//
|
|
752
|
+
// Excluded from terminal-expire:
|
|
753
|
+
// - merged — already handled by the absoluteConditions fire-once
|
|
754
|
+
// path; including it here is redundant and risks
|
|
755
|
+
// expiring before the watch's one shot to fire.
|
|
756
|
+
// - status-change — could theoretically still fire on a status flip
|
|
757
|
+
// (e.g. closed→reopened on GitHub), even though rare.
|
|
758
|
+
// - any — same rationale as status-change.
|
|
759
|
+
//
|
|
760
|
+
// Build-fail / build-pass / vote-change / new-comments / head-commit-change /
|
|
761
|
+
// mergeable-flipped / behind-master / ready-for-merge / draft-flipped all
|
|
762
|
+
// mutate fields that are frozen the moment the PR is merged/closed/abandoned,
|
|
763
|
+
// so a fresh fire is impossible.
|
|
764
|
+
isTerminalForCondition: (condition, pr) => {
|
|
765
|
+
if (!pr || !pr.status) return false;
|
|
766
|
+
const status = pr.status;
|
|
767
|
+
const isTerminal = status === shared.PR_STATUS.MERGED
|
|
768
|
+
|| status === shared.PR_STATUS.CLOSED
|
|
769
|
+
|| status === shared.PR_STATUS.ABANDONED;
|
|
770
|
+
if (!isTerminal) return false;
|
|
771
|
+
if (condition === WATCH_CONDITION.MERGED
|
|
772
|
+
|| condition === WATCH_CONDITION.STATUS_CHANGE
|
|
773
|
+
|| condition === WATCH_CONDITION.ANY) {
|
|
774
|
+
return false;
|
|
775
|
+
}
|
|
776
|
+
return true;
|
|
777
|
+
},
|
|
707
778
|
fetchEntity: (target, state) => findPrByTarget(state.pullRequests, target),
|
|
708
779
|
captureState: (pr) => ({
|
|
709
780
|
status: pr.status, buildStatus: pr.buildStatus, reviewStatus: pr.reviewStatus,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2175",
|
|
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"
|