@yemi33/minions 0.1.2444 → 0.1.2445

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.
@@ -36,8 +36,10 @@ const MINIONS_DIR = shared.MINIONS_DIR;
36
36
  // against context-only PRs because casting a review vote, posting a
37
37
  // comment, asking a question, or running a read-only explore never mutates the
38
38
  // PR's source branch. Auto-discovery (engine.js#discoverFromPrs) is still
39
- // gated on `contextOnly` separately only explicit WIs (dashboard, watches,
40
- // CC) can target a context-only PR, and only via these types.
39
+ // gated on `contextOnly` separately. Mutating types are not universally barred
40
+ // from a context-only PR either they survive when the work item carries
41
+ // explicit human/API provenance (see getStalePrDispatchReason's context-only
42
+ // gate and shared.isExplicitlyRequestedPrDispatch).
41
43
  const NON_MUTATING_DISPATCH_TYPES = new Set([
42
44
  WORK_TYPE.REVIEW,
43
45
  WORK_TYPE.ASK,
@@ -594,18 +596,29 @@ function getStalePrDispatchReason(entry, config) {
594
596
  const prLabel = entry.meta.pr?.id || entry.meta.pr?.url || entry.id;
595
597
  if (!tracked) return `PR ${prLabel} is no longer tracked`;
596
598
  if (tracked.status !== PR_STATUS.ACTIVE) return `PR ${tracked.id || prLabel} is ${tracked.status || 'missing status'}`;
597
- // Combined gate (W-mq5v612m001g1545 + #3126):
598
- // 1. `entry.meta?.source !== 'work-item'` blocks any non-work-item dispatch
599
- // (e.g. legacy `pr`/`pr-human-feedback` discovery paths) from running
600
- // against a context-only PR. Auto-discovery in engine.js#discoverFromPrs
601
- // already skips context-only PRs at the source, so this is defense-in-depth.
602
- // 2. `!NON_MUTATING_DISPATCH_TYPES.has(entry.type)` even an explicit
603
- // work-item dispatch is dropped if it's a mutating type (fix, implement,
604
- // test, verify, decompose, docs). Only review/ask/explore which never
605
- // push to the PR's source branch are allowed through.
606
- if (shared.isContextOnlyPrRecord(tracked)
607
- && (entry.meta?.source !== 'work-item' || !NON_MUTATING_DISPATCH_TYPES.has(entry.type))) {
608
- return `PR ${tracked.id || prLabel} is context-only`;
599
+ // Context-only gate (W-mq5v612m001g1545 + #3126, corrected by
600
+ // W-ms5sr82s00ze0af8-c). `contextOnly` suppresses AUTOMATIC PR discovery and
601
+ // auto-management only — it must not cancel PR-targeted work a human or API
602
+ // caller explicitly created. A dispatch survives a context-only PR when
603
+ // either leg holds:
604
+ // 1. It is a work-item dispatch of a non-mutating type (review/ask/explore)
605
+ // casting a vote, commenting, or reading never pushes to the PR's
606
+ // source branch, so intent doesn't need to be established.
607
+ // 2. `shared.isExplicitlyRequestedPrDispatch(entry, wi)`the dispatch
608
+ // came from an explicit work item (`meta.source === 'work-item'`) whose
609
+ // structured provenance (`_originAgent` / `_originWi` from the
610
+ // `X-Minions-Agent` / `X-Minions-Origin-Wi` headers, or an explicit
611
+ // `createdBy` root such as `dashboard`) proves a human or API caller
612
+ // asked for it. This is what un-breaks operator-authored `fix` work
613
+ // items, which fail leg 1 purely because `fix` is a mutating type.
614
+ // Auto-discovered dispatches (`source: 'pr'`, `'pr-human-feedback'`) satisfy
615
+ // neither leg and are still pruned exactly as before. The work item is only
616
+ // resolved when both cheap checks have already failed.
617
+ if (shared.isContextOnlyPrRecord(tracked)) {
618
+ const allowed = entry.meta?.source === 'work-item'
619
+ && (NON_MUTATING_DISPATCH_TYPES.has(entry.type)
620
+ || shared.isExplicitlyRequestedPrDispatch(entry, _resolveWorkItemForPrunedEntry(entry)));
621
+ if (!allowed) return `PR ${tracked.id || prLabel} is context-only`;
609
622
  }
610
623
 
611
624
  const queuedBranch = entry.meta.branch || entry.meta.pr?.branch || '';
@@ -655,7 +668,9 @@ function cancelSourceWorkItemForPrunedDispatch(entry, reason) {
655
668
 
656
669
  /**
657
670
  * W-mr9jo2n2000667ad (bug B) — best-effort resolution of the work item behind
658
- * a pending dispatch entry, used only by the scope-overlap gate below.
671
+ * a pending dispatch entry, used by the scope-overlap gate below and by the
672
+ * context-only provenance gate in getStalePrDispatchReason (which needs the
673
+ * persisted `_originAgent` / `createdBy` fields, not just the queued snapshot).
659
674
  */
660
675
  function _resolveWorkItemForPrunedEntry(entry) {
661
676
  const itemId = entry?.meta?.item?.id;
package/engine/shared.js CHANGED
@@ -7697,6 +7697,85 @@ function isAutoManagedPrRecord(pr) {
7697
7697
  return !!pr && typeof pr === 'object' && !isContextOnlyPrRecord(pr);
7698
7698
  }
7699
7699
 
7700
+ /**
7701
+ * Work-item `createdBy` roots that represent an EXPLICIT request: a human
7702
+ * operator acting through the dashboard, an external caller on
7703
+ * `POST /api/work-items`, or an operator-authored automation definition
7704
+ * (watch rule, schedule, pipeline, QA session). All of these are work a
7705
+ * person deliberately declared.
7706
+ *
7707
+ * Deliberately NOT listed: engine-derived origins (`engine:*`,
7708
+ * `decomposition`, `lifecycle:*`, `harness:iterate`). Those are the engine
7709
+ * auto-managing a PR on its own initiative, which is exactly what a
7710
+ * `contextOnly` PR is flagged to suppress.
7711
+ *
7712
+ * Fails CLOSED — an unlisted `createdBy` is treated as non-explicit, so a new
7713
+ * engine-automatic creator can never accidentally inherit explicit status. A
7714
+ * new human/API-facing creator must register its root here.
7715
+ */
7716
+ const EXPLICIT_WORK_ITEM_ORIGIN_ROOTS = new Set([
7717
+ 'dashboard', // dashboard UI + POST /api/work-items, incl. CC/agent API callers
7718
+ // (dashboard.js hard-codes createdBy:'dashboard'; also
7719
+ // dashboard:execute, dashboard:revision, dashboard:promotion:…)
7720
+ 'human', // engine/meeting.js
7721
+ 'operator',
7722
+ 'watch', // operator-authored watch rule (watch:<watchId>)
7723
+ 'scheduler', // operator-authored schedule (scheduler, scheduler:harness)
7724
+ 'pipeline', // operator-authored pipeline definition (pipeline:<pipelineId>)
7725
+ 'qa-dispatch', // operator-started QA session
7726
+ 'qa-session',
7727
+ ]);
7728
+
7729
+ /**
7730
+ * Normalize a work item's `createdBy` to its namespace root so the allowlist
7731
+ * above stays short. Creators namespace themselves as `<root>:<detail>`
7732
+ * (`dashboard:execute`, `pipeline:<id>`, `watch:<id>`, `scheduler:harness`);
7733
+ * `qa-session-<phase>` (engine/qa-sessions.js) uses a hyphen instead.
7734
+ */
7735
+ function workItemOriginRoot(createdBy) {
7736
+ const raw = typeof createdBy === 'string' ? createdBy.trim().toLowerCase() : '';
7737
+ if (!raw) return '';
7738
+ const root = raw.split(':')[0];
7739
+ return root.startsWith('qa-session-') ? 'qa-session' : root;
7740
+ }
7741
+
7742
+ /**
7743
+ * W-ms5sr82s00ze0af8-c — provenance gate for PR-targeted dispatches against a
7744
+ * `contextOnly` PR.
7745
+ *
7746
+ * `contextOnly` suppresses AUTOMATIC PR discovery and auto-management; it must
7747
+ * never cancel PR-targeted work a human or API caller explicitly created (the
7748
+ * defect that cancelled the operator-authored `fix` work items for opg PRs
7749
+ * #1066 / #1067 with `_lastDispatchResult: 'pruned-stale-pr-dispatch'`).
7750
+ *
7751
+ * Explicit provenance is read from structured fields only — never from title
7752
+ * or description text:
7753
+ * 1. `entry.meta.source === 'work-item'` is mandatory. Auto-discovery
7754
+ * (`source: 'pr'`, `'pr-human-feedback'`) never qualifies, whatever work
7755
+ * item it happens to resolve to.
7756
+ * 2. The backing work item must carry either the `_originAgent` /
7757
+ * `_originWi` audit fields persisted by `POST /api/work-items` from the
7758
+ * `X-Minions-Agent` / `X-Minions-Origin-Wi` headers, or a `createdBy`
7759
+ * whose root is in EXPLICIT_WORK_ITEM_ORIGIN_ROOTS.
7760
+ *
7761
+ * @param {object} entry - pending dispatch entry
7762
+ * @param {object} [workItem] - the resolved source work item; falls back to
7763
+ * the `entry.meta.item` snapshot the dispatch was queued with.
7764
+ * @returns {boolean}
7765
+ */
7766
+ function isExplicitlyRequestedPrDispatch(entry, workItem = null) {
7767
+ if (!entry || typeof entry !== 'object') return false;
7768
+ if (entry.meta?.source !== 'work-item') return false;
7769
+ const item = (workItem && typeof workItem === 'object')
7770
+ ? workItem
7771
+ : (entry.meta?.item && typeof entry.meta.item === 'object' ? entry.meta.item : null);
7772
+ if (!item) return false;
7773
+ const originAgent = typeof item._originAgent === 'string' ? item._originAgent.trim() : '';
7774
+ const originWi = typeof item._originWi === 'string' ? item._originWi.trim() : '';
7775
+ if (originAgent || originWi) return true;
7776
+ return EXPLICIT_WORK_ITEM_ORIGIN_ROOTS.has(workItemOriginRoot(item.createdBy));
7777
+ }
7778
+
7700
7779
  function mergePrLinkItems(links, prId, itemIds) {
7701
7780
  if (!prId) return;
7702
7781
  const merged = new Set([...(links[prId] || []), ...normalizePrLinkItems(itemIds)]);
@@ -9568,6 +9647,9 @@ module.exports = {
9568
9647
  upsertPullRequestRecord,
9569
9648
  collapseDuplicatePrRecords, // P-e9f0a2b4 — one-time repair helper
9570
9649
  isAutoManagedPrRecord, // W-mq5s5ttx000j7ab8-a — exported for engine + watch-plugin gate consolidation
9650
+ isExplicitlyRequestedPrDispatch, // W-ms5sr82s00ze0af8-c — provenance gate shared by both stale-PR-dispatch pruners
9651
+ workItemOriginRoot, // exported for testing
9652
+ EXPLICIT_WORK_ITEM_ORIGIN_ROOTS,
9571
9653
  autoEnrollPrFromWorkItem,
9572
9654
  autoEnrollPrFromFixWorkItem,
9573
9655
  deriveUrlForPrRef, // exported for testing
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2444",
3
+ "version": "0.1.2445",
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"