@yemi33/minions 0.1.2218 → 0.1.2219

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/ado.js CHANGED
@@ -357,13 +357,19 @@ function applyAdoPrMetadata(pr, prData) {
357
357
  if (sourceBranch && (pr.branch !== sourceBranch || pr._branchResolutionError || pr._pendingReason === shared.PR_PENDING_REASON.MISSING_BRANCH)) {
358
358
  // P-a7c4d2e8 (F3): validate ADO-derived branch ref before persistence.
359
359
  // On invalid ref, log + skip so the poller keeps running (defensive).
360
+ // Narrow try to only the throwing validateGitRef call (F20); the
361
+ // non-throwing assignment + field deletes run after on the success path.
362
+ let validatedBranch;
360
363
  try {
361
- pr.branch = shared.validateGitRef(sourceBranch);
364
+ validatedBranch = shared.validateGitRef(sourceBranch);
365
+ } catch (refErr) {
366
+ log('warn', `ADO: invalid sourceRefName "${sourceBranch.slice(0, 64)}" for PR ${pr.id || '?'}: ${refErr.message}`);
367
+ }
368
+ if (validatedBranch !== undefined) {
369
+ pr.branch = validatedBranch;
362
370
  if (pr._branchResolutionError) delete pr._branchResolutionError;
363
371
  if (pr._pendingReason === shared.PR_PENDING_REASON.MISSING_BRANCH) delete pr._pendingReason;
364
372
  updated = true;
365
- } catch (refErr) {
366
- log('warn', `ADO: invalid sourceRefName "${sourceBranch.slice(0, 64)}" for PR ${pr.id || '?'}: ${refErr.message}`);
367
373
  }
368
374
  }
369
375
 
package/engine/cli.js CHANGED
@@ -555,7 +555,11 @@ const commands = {
555
555
  // before tick #1 would flip its auto-managed verdict. Idempotent.
556
556
  try {
557
557
  const projectsRoot = path.join(shared.MINIONS_DIR, 'projects');
558
- shared.migratePrGateFlags(projectsRoot);
558
+ // W-mqil3jtw — pass the configured agent roster so the migration
559
+ // cross-checks `agent` against real agent ids instead of a shape regex;
560
+ // a GitHub author login (e.g. `calebt_microsoft`) must not be mistaken
561
+ // for a managed persona and flip a context-only PR's observe toggle.
562
+ shared.migratePrGateFlags(projectsRoot, { config });
559
563
  } catch (err) { e.log('warn', `pr-gate-migration failed: ${err.message}`); }
560
564
 
561
565
  // One-time force-on of the CC worker pool. The pool has been the resolved
package/engine/github.js CHANGED
@@ -1884,6 +1884,7 @@ module.exports = {
1884
1884
  dismissPriorViewerChangesRequestedReviews, // W-mp7b1g8q000fea45 — clear stale CHANGES_REQUESTED on verdict flip
1885
1885
  isGhThrottled,
1886
1886
  getGhThrottleState,
1887
+ ghApi, // P-8c1b6e45 — used by engine/shared-branch-pr-reconcile.js to list open PRs on a feature branch
1887
1888
  // Exported for testing
1888
1889
  isGitHub,
1889
1890
  getRepoSlug,
@@ -315,6 +315,12 @@ const PLAYBOOK_OPTIONAL_VARS = new Set([
315
315
  // creation. Optional because every other playbook ignores it and the
316
316
  // default state is the empty string (renders the non-shared branch).
317
317
  'shared_branch',
318
+ // P-7e1a9c34 — the canonical shared-branch name, exposed so any playbook can
319
+ // reference {{feature_branch}} without a second source of truth. Set by
320
+ // renderProjectWorkItemPromptForAgent to item.featureBranch for shared-branch
321
+ // work-item dispatches; '' for parallel/PR-targeted dispatches. Optional
322
+ // because most playbooks never reference it.
323
+ 'feature_branch',
318
324
  // P-4d6e2af3 — comma-separated list of target projects for cross-repo
319
325
  // plans. Only set in engine.js's WORK_TYPE.PLAN branch when the WI carries
320
326
  // item._targetProjects (≥2 entries from dashboard.js#buildPlanWorkItem).
package/engine/queries.js CHANGED
@@ -602,18 +602,6 @@ function getAgentStatus(agentId) {
602
602
  if (active._blockingToolCall) {
603
603
  result._blockingToolCall = active._blockingToolCall;
604
604
  }
605
- // Detect in-flight tools: read only head+tail of live-output.log (max 2KB total)
606
- try {
607
- const liveLogPath = path.join(AGENTS_DIR, agentId, 'live-output.log');
608
- const { head, tail } = readHeadTail(liveLogPath, 1024);
609
- if (head) {
610
- // Detect in-flight tool calls (task_started with no task_notification)
611
- const inFlight = detectInFlightTool(tail);
612
- if (inFlight && inFlight.description) {
613
- result._runningToolDescription = inFlight.description;
614
- }
615
- }
616
- } catch { /* optional — don't block status */ }
617
605
  return result;
618
606
  }
619
607
 
@@ -713,7 +701,12 @@ function getAgents(config) {
713
701
  const s = getAgentStatus(a.id); // derives from dispatch.json
714
702
 
715
703
  let lastAction = 'Waiting for assignment';
716
- if (s.status === 'working') lastAction = s._runningToolDescription ? `Running: ${s._runningToolDescription}` : `Working: ${s.task}`;
704
+ // Show the dispatch task as a stable label for the whole run — don't swap
705
+ // in the transient per-tool-call `_runningToolDescription`, which reads like
706
+ // a task change to operators (e.g. an agent's own "Wait until temp dirs
707
+ // mostly cleared" bash step looked like a new task). Long-silent/blocking
708
+ // tool calls are still surfaced separately via `_blockingToolCall`.
709
+ if (s.status === 'working') lastAction = `Working: ${s.task}`;
717
710
  else if (DONE_STATUSES.has(s.status)) lastAction = `Done: ${s.task}`;
718
711
  else if (s.status === 'error') lastAction = `Error: ${s.task}`;
719
712
  else if (steeringInboxFiles.length > 0) {
@@ -0,0 +1,183 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Shared-branch PR reconciler (P-8c1b6e45).
5
+ *
6
+ * Recovery sweep that backfills the engine PR store for shared-branch plans
7
+ * whose aggregate PR already exists on the remote but was never tracked (or
8
+ * was tracked without the full `prdItems` linkage). This is the "belt" to the
9
+ * no-self-PR guards in the playbooks: even if a per-item leak opened a PR, or
10
+ * the engine missed the aggregate "Create PR for plan" item, this sweep heals
11
+ * the linkage so the dashboard renders the single aggregate PR
12
+ * (`countDistinctPrdItems > 1`) instead of N phantom per-item rows.
13
+ *
14
+ * Canonical incident: PR #228 (plan minions-opg-2026-06-17.json).
15
+ *
16
+ * Invariants:
17
+ * - NEVER creates a remote PR (no `gh pr create` / `az repos pr create`).
18
+ * - Per-PRD try/catch — one bad plan never aborts the sweep.
19
+ * - Idempotent — a fully-linked PRD is a no-op (no mutate, no heal log).
20
+ * - Multi-project shared-branch plans heal per-project.
21
+ * - Zero new runtime deps (Node built-ins + existing engine modules only).
22
+ */
23
+
24
+ const fs = require('fs');
25
+ const path = require('path');
26
+ const shared = require('./shared');
27
+ const queries = require('./queries');
28
+ const github = require('./github');
29
+ const ado = require('./ado');
30
+
31
+ const { log } = shared;
32
+ const PRD_DIR = queries.PRD_DIR;
33
+
34
+ // Test seam (consumed by P-4e7d2c56): when set, replaces the live remote PR
35
+ // lookup so unit tests can exercise the heal/no-op branches without a network.
36
+ let _remotePrFinderOverride = null;
37
+ function _setRemotePrFinderForTest(fn) {
38
+ _remotePrFinderOverride = (typeof fn === 'function') ? fn : null;
39
+ }
40
+
41
+ /** True for an active/completed shared-branch PRD that carries a feature branch. */
42
+ function _isSharedBranchPrd(plan) {
43
+ return !!plan
44
+ && plan.branch_strategy === 'shared-branch'
45
+ && typeof plan.feature_branch === 'string'
46
+ && plan.feature_branch.trim() !== ''
47
+ && Array.isArray(plan.missing_features)
48
+ && plan.status !== 'cancelled';
49
+ }
50
+
51
+ /** All PRD item ids for a plan — the aggregate linkage set. */
52
+ function _collectPlanItemIds(plan) {
53
+ const ids = [];
54
+ for (const item of (plan?.missing_features || [])) {
55
+ if (item && typeof item.id === 'string' && item.id) ids.push(item.id);
56
+ }
57
+ return ids;
58
+ }
59
+
60
+ /** Distinct project names referenced by a plan (top-level + per-item). */
61
+ function _collectPlanProjectNames(plan) {
62
+ const names = new Set();
63
+ if (plan?.project) names.add(plan.project);
64
+ for (const item of (plan?.missing_features || [])) {
65
+ if (item?.project) names.add(item.project);
66
+ }
67
+ return [...names];
68
+ }
69
+
70
+ /**
71
+ * Query the remote for an OPEN PR on feature_branch -> mainRef. Never creates one.
72
+ * GitHub: ghApi GET /pulls?head=<owner>:<branch>&base=<main>&state=open.
73
+ * ADO: ado.findOpenPrOnBranch (active PRs by sourceRefName), reusing the
74
+ * ado throttle / circuit-breaker plumbing — never raw curl.
75
+ * Returns { prNumber, url } or null.
76
+ */
77
+ async function _queryRemoteOpenPr(project, branch, mainRef) {
78
+ if (_remotePrFinderOverride) return _remotePrFinderOverride(project, branch, mainRef);
79
+ if (!project || !branch) return null;
80
+
81
+ if (github.isGitHub(project)) {
82
+ const slug = github.getRepoSlug(project);
83
+ if (!slug) return null;
84
+ const owner = slug.split('/')[0];
85
+ // validateGhEndpoint disallows ':' — encode the head separator as %3A and
86
+ // keep the branch slashes intact (GitHub expects head=<owner>:<branch>).
87
+ const head = `${owner}%3A${branch}`;
88
+ const endpoint = `/pulls?head=${head}&base=${encodeURIComponent(mainRef || '')}&state=open`;
89
+ const data = await github.ghApi(endpoint, slug);
90
+ const pr = Array.isArray(data) ? data[0] : null;
91
+ if (!pr || pr.number == null) return null;
92
+ return { prNumber: pr.number, url: pr.html_url || '' };
93
+ }
94
+
95
+ // ADO (and any non-GitHub host) — findOpenPrOnBranch matches active PRs by
96
+ // sourceRefName and returns { prNumber, url } or null.
97
+ return (await ado.findOpenPrOnBranch(project, branch)) || null;
98
+ }
99
+
100
+ /**
101
+ * Decide whether the PR store needs healing for this remote PR against a plan's
102
+ * full item set. Returns { needsHeal, missingIds }.
103
+ */
104
+ function _evaluateHeal(prs, project, remote, planIds) {
105
+ const canonicalId = shared.getCanonicalPrId(project, remote.prNumber, remote.url);
106
+ const record = shared.findPrRecord(prs, { id: canonicalId, prNumber: remote.prNumber, url: remote.url }, project);
107
+ const linked = new Set(Array.isArray(record?.prdItems) ? record.prdItems : []);
108
+ const missingIds = planIds.filter(id => !linked.has(id));
109
+ return { needsHeal: !record || missingIds.length > 0, missingIds };
110
+ }
111
+
112
+ /** Heal one project's PR store for one shared-branch PRD. */
113
+ async function _reconcilePrdForProject(plan, file, project, planIds) {
114
+ const mainRef = shared.resolveMainBranch(project.localPath, project.mainBranch);
115
+ const branch = shared.sanitizeBranch(plan.feature_branch);
116
+ const remote = await _queryRemoteOpenPr(project, branch, mainRef);
117
+ if (!remote) return; // no remote PR -> nothing to heal
118
+
119
+ const prPath = shared.projectPrPath(project);
120
+ const { needsHeal } = _evaluateHeal(shared.safeJsonArr(prPath), project, remote, planIds);
121
+ if (!needsHeal) return; // fully-linked -> idempotent no-op
122
+
123
+ // upsertPullRequestRecord MERGES prdItems + calls addPrLink per item, yielding
124
+ // the aggregate shape (countDistinctPrdItems > 1) the dashboard renders.
125
+ const result = shared.upsertPullRequestRecord(prPath, {
126
+ url: remote.url,
127
+ prNumber: remote.prNumber,
128
+ branch,
129
+ _project: project.name,
130
+ sourcePlan: file,
131
+ contextOnly: false,
132
+ }, { project, itemIds: planIds });
133
+
134
+ if (result && (result.created || result.linked)) {
135
+ const n = Array.isArray(result.record?.prdItems) ? result.record.prdItems.length : planIds.length;
136
+ log('info', `[shared-branch-reconcile] healed PRD ${file} -> registered aggregate PR ${result.id} (prdItems=${n})`);
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Enumerate active/completed shared-branch PRDs and heal the engine PR store
142
+ * for any whose aggregate remote PR exists but is untracked / under-linked.
143
+ * Per-PRD and per-project failures are isolated so one bad plan never aborts
144
+ * the sweep.
145
+ */
146
+ async function reconcileSharedBranchPrs(config) {
147
+ let planFiles;
148
+ try { planFiles = fs.readdirSync(PRD_DIR).filter(f => f.endsWith('.json')); }
149
+ catch { return; } // PRD dir absent -> nothing to do
150
+
151
+ for (const file of planFiles) {
152
+ try {
153
+ const plan = shared.safeJsonNoRestore(path.join(PRD_DIR, file));
154
+ if (!_isSharedBranchPrd(plan)) continue;
155
+ const planIds = _collectPlanItemIds(plan);
156
+ if (planIds.length === 0) continue;
157
+
158
+ for (const projName of _collectPlanProjectNames(plan)) {
159
+ try {
160
+ const project = shared.resolveProjectSource(projName, config, { allowCentral: false }).project;
161
+ if (!project) continue;
162
+ await _reconcilePrdForProject(plan, file, project, planIds);
163
+ } catch (projErr) {
164
+ log('warn', `[shared-branch-reconcile] project ${projName} for PRD ${file}: ${projErr?.message || projErr}`);
165
+ }
166
+ }
167
+ } catch (err) {
168
+ log('warn', `[shared-branch-reconcile] PRD ${file}: ${err?.message || err}`);
169
+ }
170
+ }
171
+ }
172
+
173
+ module.exports = {
174
+ reconcileSharedBranchPrs,
175
+ // exported for testing
176
+ _isSharedBranchPrd,
177
+ _collectPlanItemIds,
178
+ _collectPlanProjectNames,
179
+ _queryRemoteOpenPr,
180
+ _evaluateHeal,
181
+ _reconcilePrdForProject,
182
+ _setRemotePrFinderForTest,
183
+ };
package/engine/shared.js CHANGED
@@ -6882,12 +6882,22 @@ function upsertPullRequestRecord(prPath, entry, { project = null, itemId = null,
6882
6882
  //
6883
6883
  // Per-record decision:
6884
6884
  // 1. contextOnly := (_contextOnly === true)
6885
- // 2. legacyManaged := prdItems.length > 0 || _autoObserve === true ||
6886
- // sourcePlan || itemType || (agent is a Minions persona)
6887
- // "Minions persona" = non-empty kebab-case identifier other than 'human'.
6888
- // Anything with whitespace (human display names like "yemi shin", which
6889
- // github poller writes into `agent` from `prData.user.login`) is treated
6890
- // as a human author, not a managed agent.
6885
+ // 2. legacyManaged := _autoObserve === true || sourcePlan || itemType ||
6886
+ // (agent is a *configured* Minions persona)
6887
+ // "Configured Minions persona" = `agent` matches an id in
6888
+ // `config.agents` (case-insensitive). The github poller writes the PR
6889
+ // author's login into `agent` (from `prData.user.login`); human logins
6890
+ // like `calebt_microsoft` are kebab/snake-shaped and previously passed a
6891
+ // naive shape regex, mis-classifying a human PR as managed and flipping
6892
+ // its observe toggle ON on the next boot (W-mqil3jtw). Cross-checking the
6893
+ // real roster instead of a shape heuristic fixes that. A login that is
6894
+ // not a configured agent id is treated as a human author.
6895
+ // NOTE: `prdItems.length > 0` is intentionally NOT a managed signal in
6896
+ // this migration. A watch can link a PR context-only AND spin off a
6897
+ // courtesy-review WI whose id is stamped into `prdItems`; that stamp must
6898
+ // not override the explicit context-only intent. Genuine managed PRs
6899
+ // always carry a real signal above (configured agent / sourcePlan /
6900
+ // itemType / _autoObserve), so dropping prdItems-alone is safe here.
6891
6901
  // 3. If !contextOnly && !legacyManaged, set contextOnly = true and stamp
6892
6902
  // `_migrationNote: 'auto-set-contextOnly-by-pr-gate-simplification'`.
6893
6903
  // 4. Persist `contextOnly`; delete `_autoObserve`, `_manual`, `_contextOnly`.
@@ -6903,22 +6913,49 @@ function _prRecordHasLegacyGateKey(record) {
6903
6913
  || Object.prototype.hasOwnProperty.call(record, '_manual');
6904
6914
  }
6905
6915
 
6906
- function _prRecordIsLegacyManaged(record) {
6907
- const prdItemsCount = Array.isArray(record.prdItems) ? record.prdItems.length : 0;
6908
- if (prdItemsCount > 0) return true;
6916
+ // Resolve the configured agent roster (lower-cased Set) once per migration.
6917
+ // Accepts either an explicit `agentIds` (Set / array) or a `config` object
6918
+ // whose `agents` keys are the roster. Returns an empty Set when nothing is
6919
+ // supplied (no record can match a managed persona).
6920
+ // (Deliberately NOT queries.getAgents(config): that lives in queries.js — which
6921
+ // requires shared.js, so importing it here is circular — and it also reads
6922
+ // dispatch/inbox state. We only need the bare id set.)
6923
+ function _resolveConfiguredAgentIds(opts = {}) {
6924
+ let ids = opts.agentIds;
6925
+ if (ids == null && opts.config && opts.config.agents && typeof opts.config.agents === 'object') {
6926
+ ids = Object.keys(opts.config.agents);
6927
+ }
6928
+ const source = ids instanceof Set ? [...ids] : (Array.isArray(ids) ? ids : []);
6929
+ return new Set(source.map(id => String(id || '').trim().toLowerCase()).filter(Boolean));
6930
+ }
6931
+
6932
+ // `agentIds` (a Set, supplied by the boot migration) switches this helper into
6933
+ // "roster mode": the `agent` field only counts as a managed signal when it is
6934
+ // an actually-configured agent id, and prdItems-alone is dropped (see the
6935
+ // migration header). Callers that can't cheaply resolve the roster (the
6936
+ // runtime upsert/link demotion-guard at the `_prRecordIsLegacyManaged(target)`
6937
+ // call site) pass no agentIds and keep the legacy shape-heuristic behavior.
6938
+ function _prRecordIsLegacyManaged(record, agentIds) {
6939
+ const rosterMode = agentIds instanceof Set;
6940
+ // prdItems-alone is a managed signal only in the legacy runtime path, not the
6941
+ // boot migration (see header: courtesy-review watch contamination).
6942
+ if (!rosterMode && Array.isArray(record.prdItems) && record.prdItems.length > 0) return true;
6909
6943
  if (record._autoObserve === true) return true;
6910
6944
  if (record.sourcePlan) return true;
6911
6945
  if (record.itemType) return true;
6912
6946
  if (typeof record.agent === 'string') {
6913
6947
  const agent = record.agent.trim().toLowerCase();
6914
- if (agent && agent !== 'human' && _AGENT_PERSONA_RE.test(agent)) return true;
6948
+ if (agent && agent !== 'human') {
6949
+ if (rosterMode ? agentIds.has(agent) : _AGENT_PERSONA_RE.test(agent)) return true;
6950
+ }
6915
6951
  }
6916
6952
  return false;
6917
6953
  }
6918
6954
 
6919
- function migratePrGateFlags(projectsRoot) {
6955
+ function migratePrGateFlags(projectsRoot, opts = {}) {
6920
6956
  const summary = { projectsScanned: 0, projectsMigrated: 0, totalRecords: 0, totalMigrated: 0 };
6921
6957
  if (!projectsRoot || typeof projectsRoot !== 'string') return summary;
6958
+ const agentIds = _resolveConfiguredAgentIds(opts);
6922
6959
  let entries;
6923
6960
  try {
6924
6961
  entries = fs.readdirSync(projectsRoot, { withFileTypes: true });
@@ -6945,7 +6982,7 @@ function migratePrGateFlags(projectsRoot) {
6945
6982
  if (hasCanonical && !hasLegacy) { alreadyMigrated++; continue; }
6946
6983
 
6947
6984
  let contextOnly = (record._contextOnly === true);
6948
- if (!contextOnly && !_prRecordIsLegacyManaged(record)) {
6985
+ if (!contextOnly && !_prRecordIsLegacyManaged(record, agentIds)) {
6949
6986
  contextOnly = true;
6950
6987
  record._migrationNote = _PR_GATE_MIGRATION_NOTE;
6951
6988
  stamped++;
package/engine.js CHANGED
@@ -5026,6 +5026,7 @@ function reconcileItemsWithPrs(items, allPrs, { onlyIds } = {}) {
5026
5026
  const { consolidateInbox } = require('./engine/consolidation');
5027
5027
  const { pollPrStatus, pollPrHumanComments, reconcilePrs, checkLiveReviewStatus: adoCheckLiveReview, checkLiveBuildAndConflict: adoCheckLiveBuildAndConflict, needsAdoPollRetry, getAdoToken, isAdoThrottled, getAdoThrottleStateAll } = require('./engine/ado');
5028
5028
  const { pollPrStatus: ghPollPrStatus, pollPrHumanComments: ghPollPrHumanComments, reconcilePrs: ghReconcilePrs, checkLiveReviewStatus: ghCheckLiveReview, checkLiveBuildAndConflict: ghCheckLiveBuildAndConflict, isGhThrottled } = require('./engine/github');
5029
+ const { reconcileSharedBranchPrs } = require('./engine/shared-branch-pr-reconcile');
5029
5030
 
5030
5031
  // ─── State Snapshot ─────────────────────────────────────────────────────────
5031
5032
 
@@ -6881,6 +6882,10 @@ function renderProjectWorkItemPromptForAgent(item, workType, agentId, config, pr
6881
6882
  // implement→implement-shared gate at engine/playbook.js). Default '' for
6882
6883
  // parallel/PR-targeted dispatches keeps existing per-type PR behavior.
6883
6884
  shared_branch: (item.branchStrategy === 'shared-branch' && item.featureBranch) ? '1' : '',
6885
+ // Optional: the canonical shared-branch name so any playbook can reference
6886
+ // {{feature_branch}} without a second source of truth. Resolves to
6887
+ // item.featureBranch for shared-branch work-item dispatches; '' otherwise.
6888
+ feature_branch: item.featureBranch || '',
6884
6889
  notes_content: '',
6885
6890
  pr_id: item.pr_id || item._pr || item.targetPr || item.sourcePr || item.pr || '',
6886
6891
  pr_number: item.prNumber || item.pr_number || '',
@@ -9098,6 +9103,20 @@ async function tickInner() {
9098
9103
  } catch (err) {
9099
9104
  log('warn', `[pull-requests] scope-mismatch sweep error: ${err?.message || err}`);
9100
9105
  }
9106
+
9107
+ // P-6b3a9f78 — Shared-branch PR reconciler (recovery sweep). Backfills the
9108
+ // engine PR store for shared-branch plans whose aggregate remote PR exists
9109
+ // but is untracked / under-linked, so the dashboard renders the single
9110
+ // aggregate PR instead of N phantom per-item rows. Host-agnostic and fully
9111
+ // self-guarding (per-PRD/per-project try/catch, never creates a remote PR),
9112
+ // so it runs unconditionally in the reconcile phase like the other recovery
9113
+ // sweeps — independent of the ADO/GitHub poll + throttle gates.
9114
+ try {
9115
+ await reconcileSharedBranchPrs(config);
9116
+ } catch (err) {
9117
+ log('warn', `[shared-branch-reconcile] sweep error: ${err?.message || err}`);
9118
+ }
9119
+ if (_isTickStale(myGeneration)) return;
9101
9120
  }
9102
9121
 
9103
9122
  // 2.9. Stalled dispatch detection — auto-retry failed items blocking the graph (~20 min — cadence in ENGINE_DEFAULTS.stalledDispatchSweepEvery)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2218",
3
+ "version": "0.1.2219",
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"
package/playbooks/fix.md CHANGED
@@ -84,6 +84,23 @@ Long builds, dependency installs, and tests may be quiet for several minutes. Le
84
84
 
85
85
  ## Publish & Comment on PR
86
86
 
87
+ {{#shared_branch}}
88
+ This is part of a **shared-branch plan** — other plan items share `{{branch_name}}`.
89
+ After the fix is validated or any unavoidable limitation is clearly documented, commit only relevant files and push to the shared branch:
90
+
91
+ ```bash
92
+ git add <specific files>
93
+ git commit -m "fix: address review feedback on {{pr_id}}"
94
+ git push origin {{branch_name}}
95
+ ```
96
+
97
+ Push your commit to the shared branch and STOP. Do **NOT** create a PR — the engine
98
+ opens one aggregate PR automatically when all plan items complete. Opening a PR now
99
+ would be premature (other items are still in flight) and the early branch would collect
100
+ merge conflicts from concurrent sibling pushes. Posting review/fix **comments** on an
101
+ existing PR is still fine. Do NOT remove the worktree — the engine handles cleanup automatically.
102
+ {{/shared_branch}}
103
+ {{^shared_branch}}
87
104
  After the fix is validated or any unavoidable limitation is clearly documented, commit only relevant files and push:
88
105
 
89
106
  ```bash
@@ -98,6 +115,7 @@ Do NOT remove the worktree — the engine handles cleanup automatically.
98
115
  - pullRequestId: `{{pr_number}}`
99
116
  - content: Explain what was fixed, reference each review finding, include build/test status
100
117
  - Sign: `Fixed by Minions ({{agent_name}} — {{agent_role}} · {{agent_model}})`
118
+ {{/shared_branch}}
101
119
 
102
120
  ## PR description audit (mandatory unless `meta.skipDescriptionAudit`)
103
121
 
@@ -87,6 +87,22 @@ Long builds, dependency installs, and tests may be quiet for several minutes. Le
87
87
 
88
88
  ## Publish
89
89
 
90
+ {{#shared_branch}}
91
+ This is part of a **shared-branch plan** — other plan items share `{{branch_name}}`.
92
+ After the change is validated or any unavoidable limitation is clearly documented, commit only the relevant files and push to the shared branch:
93
+
94
+ ```bash
95
+ git add <specific files>
96
+ git commit -m "{{commit_message}}"
97
+ git push origin {{branch_name}}
98
+ ```
99
+
100
+ Push your commit to the shared branch and STOP. Do **NOT** create a PR — the engine
101
+ opens one aggregate PR automatically when all plan items complete. Opening a PR now
102
+ would be premature (other items are still in flight) and the early branch would collect
103
+ merge conflicts from concurrent sibling pushes.
104
+ {{/shared_branch}}
105
+ {{^shared_branch}}
90
106
  After the change is validated or any unavoidable limitation is clearly documented, commit only the relevant files and push this branch:
91
107
 
92
108
  ```bash
@@ -100,6 +116,7 @@ git push -u origin {{branch_name}}
100
116
  Create the PR for implement tasks — the engine tracks review and completion from the PR.
101
117
 
102
118
  Include build/test status and run instructions in the PR description. If the project has a runnable app, include the localhost URL.
119
+ {{/shared_branch}}
103
120
 
104
121
  ## PR description audit (mandatory unless `meta.skipDescriptionAudit`)
105
122
 
@@ -174,6 +191,11 @@ Omit `meta.descriptionAudit` entirely when the audit did not run (no commits pus
174
191
 
175
192
  ## When to Stop
176
193
 
194
+ {{#shared_branch}}
195
+ Your task is complete when the requested implementation is delivered, the validation story is truthful and sufficient for review, and your commit is pushed to the shared branch `{{branch_name}}`. Do NOT create a PR — the engine creates one when all plan items are done.
196
+ {{/shared_branch}}
197
+ {{^shared_branch}}
177
198
  Your task is complete when the requested implementation is delivered, the validation story is truthful and sufficient for review, the branch is pushed, and the PR exists. Include the PR URL in your final message so the engine can track it.
199
+ {{/shared_branch}}
178
200
 
179
201
  Do NOT run `gh pr merge` or any other merge command on your own PR. The engine reviews and merges PRs through a separate review cycle. Self-merging is prohibited.
@@ -75,7 +75,11 @@ For a `type: "fix"` work item that targets an existing PR, the engine reuses the
75
75
 
76
76
  If you are running a fix task and `{{pr_branch}}` is populated, your worktree is already on that branch — push the fix commit there and do **not** create a new PR. If `{{pr_branch}}` is empty but the task description / references clearly point at an existing PR, **stop** and emit a non-retryable completion (`failure_class: 'branch-mismatch'`) instead of branching off master and opening a duplicate PR. See issue #2999 for the canonical bad incident (ADO PR `!5284819` → duplicate `!5288540`).
77
77
 
78
- ## Engine Rules (apply to all tasks)
78
+ {{#shared_branch}}## Shared-Branch Plan No Self-PR (READ FIRST)
79
+
80
+ This task is part of a **shared-branch plan** — every plan item shares the branch `{{branch_name}}`. Commit your focused changes and push them to that branch (`git push origin {{branch_name}}`), then STOP. Do **NOT** create a PR — do **not** run `gh pr create` or `az repos pr create`. The engine opens ONE aggregate PR automatically when all plan items complete; creating a PR now would be premature and the early branch would collect merge conflicts from concurrent sibling pushes. Posting review/fix **comments** on existing PRs is still fine.
81
+
82
+ {{/shared_branch}}## Engine Rules (apply to all tasks)
79
83
 
80
84
  **Context compaction:** Your context window may be compacted mid-task by Claude's infrastructure. If you notice your earlier conversation history appears truncated or summarized, this is normal and expected. Do not interpret compaction as a signal to stop early or wrap up. Continue working toward your task objective — all relevant instructions and state remain available.
81
85
 
@@ -294,7 +298,7 @@ So write `Fixed by [Minions](https://icy-water-0224cc51e.2.azurestaticapps.net/m
294
298
 
295
299
  ## GitHub Tooling and Auth
296
300
 
297
- For GitHub repo operations, use GitHub MCP tools or the `gh` CLI. Prefer commands such as `gh pr create`, `gh pr view`, `gh pr comment`, `gh pr review --comment`, `gh issue view`, and `gh run view`. **For comment / review posts, always go through `minions pr comment` or include the marker template above.**
301
+ For GitHub repo operations, use GitHub MCP tools or the `gh` CLI. Prefer commands such as {{^shared_branch}}`gh pr create`, {{/shared_branch}}`gh pr view`, `gh pr comment`, `gh pr review --comment`, `gh issue view`, and `gh run view`. **For comment / review posts, always go through `minions pr comment` or include the marker template above.**
298
302
 
299
303
  If GitHub or Copilot auth fails, check GitHub/Copilot credentials only:
300
304
  - `gh auth status`
@@ -332,7 +336,7 @@ Output is JSON with the same fields. Exit 0 on success, 1 if not found.
332
336
 
333
337
  ## Azure DevOps Tooling
334
338
 
335
- For Azure DevOps repo operations, use the `az` CLI first. Prefer commands such as `az repos pr create`, `az repos pr show`, `az repos pr list`, `az repos pr comment`, `az repos pr reviewer`, `az boards work-item`, and `az pipelines` after setting defaults with `az devops configure`.
339
+ For Azure DevOps repo operations, use the `az` CLI first. Prefer commands such as {{^shared_branch}}`az repos pr create`, {{/shared_branch}}`az repos pr show`, `az repos pr list`, `az repos pr comment`, `az repos pr reviewer`, `az boards work-item`, and `az pipelines` after setting defaults with `az devops configure`.
336
340
 
337
341
  If `az` is unavailable or insufficient for a specific operation (e.g. PR comment threads on an older az-devops extension), fall back to the ADO REST API directly: acquire a token with `az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 --query accessToken -o tsv` and call the `_apis/git/...` endpoints with Bearer auth. Do not use `gh` for Azure DevOps repositories.
338
342
  {{/ado_shared_rules}}
@@ -49,6 +49,8 @@ Use the template at `{{team_root}}/playbooks/templates/verify-guide.md` and clea
49
49
 
50
50
  ## E2E PRs
51
51
 
52
+ <!-- Intentionally NOT guarded by {{^shared_branch}}: verify.md is the legitimate aggregate/E2E PR creator that opens the single plan-completion PR. Do not add a shared-branch no-self-PR guard here (P-5a6e8b21). -->
53
+
52
54
  For each project with aggregate verification changes, create or update one draft/review PR that combines the plan branches into a single reviewable diff. Reuse an existing `e2e/{{plan_slug}}` branch/PR when present. Include the plan summary, merged PRs, testing guide, and build/test status. Do not auto-complete or merge these PRs.
53
55
 
54
56
  Track each E2E PR in the project's `.minions/pull-requests.json` if it is not already tracked.
@@ -37,6 +37,22 @@ Treat this like the user typed the task directly into a CLI agent:
37
37
  - Do NOT publish code with a broken build or failing tests that you introduced.
38
38
  - Long builds and tests may be quiet for several minutes. Let normal CLI commands run without artificial heartbeat output.
39
39
 
40
+ {{#shared_branch}}
41
+ After the change is ready for review, commit only relevant files and push to the shared branch:
42
+
43
+ ```bash
44
+ git add <specific files>
45
+ git commit -m "feat({{item_id}}): <description>"
46
+ git push origin {{branch_name}}
47
+ ```
48
+
49
+ This is part of a **shared-branch plan** — other plan items share `{{branch_name}}`.
50
+ Push your commit to the shared branch and STOP. Do **NOT** create a PR — the engine
51
+ opens one aggregate PR automatically when all plan items complete. Opening a PR now
52
+ would be premature (other items are still in flight) and the early branch would collect
53
+ merge conflicts from concurrent sibling pushes.
54
+ {{/shared_branch}}
55
+ {{^shared_branch}}
40
56
  After the change is ready for review, commit only relevant files, push `{{branch_name}}`, create the PR, and post implementation notes with the validation result:
41
57
 
42
58
  ```bash
@@ -51,12 +67,18 @@ git push -u origin {{branch_name}}
51
67
  - title: `feat({{item_id}}): <description>`
52
68
 
53
69
  {{pr_comment_instructions}}
70
+ {{/shared_branch}}
54
71
 
55
72
  Do NOT remove the worktree — the engine handles cleanup automatically.
56
73
 
57
74
  ## After Successful Completion
58
75
 
76
+ {{#shared_branch}}
77
+ Write your findings to `{{team_root}}/notes/inbox/{{agent_id}}-{{item_id}}-{{date}}.md` only after the work item succeeds: build/tests pass and the branch is pushed to the shared branch. Do NOT create a PR — the engine opens one when all plan items complete.
78
+ {{/shared_branch}}
79
+ {{^shared_branch}}
59
80
  Write your findings to `{{team_root}}/notes/inbox/{{agent_id}}-{{item_id}}-{{date}}.md` only after the work item succeeds: build/tests pass, the branch is pushed, and the PR is created.
81
+ {{/shared_branch}}
60
82
 
61
83
  If you stop because the task failed, is blocked, or is only partially complete, do **not** write an inbox note. Put the failure details in your final response and in any required PR/work-item comment instead.
62
84
 
@@ -68,6 +90,11 @@ If you encounter merge conflicts during push or PR creation:
68
90
 
69
91
  ## When to Stop
70
92
 
93
+ {{#shared_branch}}
94
+ Your task is complete when the requested work item is delivered, the validation story is truthful and sufficient for review, and your commit is pushed to the shared branch `{{branch_name}}`. Do NOT create a PR — the engine creates one when all plan items are done. Do NOT continue into unrelated improvements.
95
+ {{/shared_branch}}
96
+ {{^shared_branch}}
71
97
  Your task is complete when the requested work item is delivered, the validation story is truthful and sufficient for review, the branch is pushed, and the PR exists. Do NOT continue into unrelated improvements.
98
+ {{/shared_branch}}
72
99
 
73
100
  Do NOT run `gh pr merge` or any other merge command on your own PR. The engine reviews and merges PRs through a separate review cycle. Self-merging is prohibited.