@yemi33/minions 0.1.2218 → 0.1.2220
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/dashboard/slim/styles.css +9 -1
- package/engine/ado.js +9 -3
- package/engine/cli.js +5 -1
- package/engine/github.js +1 -0
- package/engine/playbook.js +6 -0
- package/engine/queries.js +6 -13
- package/engine/shared-branch-pr-reconcile.js +183 -0
- package/engine/shared.js +49 -12
- package/engine.js +19 -0
- package/package.json +1 -1
- package/playbooks/fix.md +18 -0
- package/playbooks/implement.md +22 -0
- package/playbooks/shared-rules.md +7 -3
- package/playbooks/verify.md +2 -0
- package/playbooks/work-item.md +27 -0
|
@@ -186,7 +186,11 @@
|
|
|
186
186
|
.layout {
|
|
187
187
|
display: grid;
|
|
188
188
|
grid-template-columns: minmax(0, 820px) minmax(0, 575px);
|
|
189
|
-
|
|
189
|
+
/* Status row is content-sized (`auto`) so the right-side control panel
|
|
190
|
+
(Watches + Knowledge) grows to fit and never scrolls internally — no
|
|
191
|
+
fixed/max height. History takes the remaining space and scrolls within
|
|
192
|
+
its own region. (W-mqgx8qkv0007218d) */
|
|
193
|
+
grid-template-rows: auto minmax(0, 1fr);
|
|
190
194
|
grid-template-areas:
|
|
191
195
|
"actions status"
|
|
192
196
|
"actions history";
|
|
@@ -1100,6 +1104,10 @@
|
|
|
1100
1104
|
|
|
1101
1105
|
/* ── Status panel sub-divisions: Team cards over System tiles,
|
|
1102
1106
|
separated by a divider line (no text headers). ──── */
|
|
1107
|
+
/* The Status panel is the right-side control panel. Its body never scrolls
|
|
1108
|
+
internally — the panel grows to fit Team cards + Watches/Knowledge tiles
|
|
1109
|
+
(the status grid row is `auto`-sized). (W-mqgx8qkv0007218d) */
|
|
1110
|
+
.panel-status .panel-body { overflow: visible; }
|
|
1103
1111
|
.team-section { margin-bottom: 14px; }
|
|
1104
1112
|
.cockpit-section { padding-top: 14px; border-top: 1px solid var(--border); }
|
|
1105
1113
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
package/engine/playbook.js
CHANGED
|
@@ -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
|
-
|
|
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 :=
|
|
6886
|
-
//
|
|
6887
|
-
// "Minions persona" =
|
|
6888
|
-
//
|
|
6889
|
-
//
|
|
6890
|
-
//
|
|
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
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
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'
|
|
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.
|
|
3
|
+
"version": "0.1.2220",
|
|
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
|
|
package/playbooks/implement.md
CHANGED
|
@@ -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
|
-
##
|
|
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}}
|
package/playbooks/verify.md
CHANGED
|
@@ -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.
|
package/playbooks/work-item.md
CHANGED
|
@@ -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.
|