@yemi33/minions 0.1.320 → 0.1.322
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/CHANGELOG.md +9 -1
- package/engine/ado.js +9 -9
- package/engine/cleanup.js +2 -2
- package/engine/cli.js +14 -14
- package/engine/github.js +8 -8
- package/engine/lifecycle.js +8 -8
- package/engine/meeting.js +4 -4
- package/engine/pipeline.js +11 -11
- package/engine/playbook.js +7 -7
- package/engine/scheduler.js +2 -2
- package/engine.js +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.322 (2026-04-03)
|
|
4
|
+
|
|
5
|
+
### Other
|
|
6
|
+
- refactor: final magic string replacements in engine, lifecycle, cleanup
|
|
7
|
+
|
|
8
|
+
## 0.1.321 (2026-04-03)
|
|
4
9
|
|
|
5
10
|
### Fixes
|
|
6
11
|
- project scan finds git repos — .git was in skipDirs
|
|
7
12
|
|
|
13
|
+
### Other
|
|
14
|
+
- refactor: replace magic strings in remaining engine files with constants
|
|
15
|
+
|
|
8
16
|
## 0.1.319 (2026-04-03)
|
|
9
17
|
|
|
10
18
|
### Other
|
package/engine/ado.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const shared = require('./shared');
|
|
8
|
-
const { exec, getAdoOrgBase, addPrLink, log, dateStamp } = shared;
|
|
8
|
+
const { exec, getAdoOrgBase, addPrLink, log, dateStamp, PR_STATUS } = shared;
|
|
9
9
|
const { getPrs } = require('./queries');
|
|
10
10
|
|
|
11
11
|
// Lazy require to avoid circular dependency — only needed for engine().handlePostMerge
|
|
@@ -80,7 +80,7 @@ async function forEachActivePr(config, token, callback) {
|
|
|
80
80
|
if (!project.adoOrg || !project.adoProject || !project.repositoryId) continue;
|
|
81
81
|
|
|
82
82
|
const prs = getPrs(project);
|
|
83
|
-
const activePrs = prs.filter(pr => pr.status ===
|
|
83
|
+
const activePrs = prs.filter(pr => pr.status === PR_STATUS.ACTIVE);
|
|
84
84
|
if (activePrs.length === 0) continue;
|
|
85
85
|
|
|
86
86
|
let projectUpdated = 0;
|
|
@@ -123,18 +123,18 @@ async function pollPrStatus(config) {
|
|
|
123
123
|
const prData = await adoFetch(`${repoBase}?api-version=7.1`, token);
|
|
124
124
|
|
|
125
125
|
let newStatus = pr.status;
|
|
126
|
-
if (prData.status === 'completed') newStatus =
|
|
127
|
-
else if (prData.status === 'abandoned') newStatus =
|
|
128
|
-
else if (prData.status === 'active') newStatus =
|
|
126
|
+
if (prData.status === 'completed') newStatus = PR_STATUS.MERGED;
|
|
127
|
+
else if (prData.status === 'abandoned') newStatus = PR_STATUS.ABANDONED;
|
|
128
|
+
else if (prData.status === 'active') newStatus = PR_STATUS.ACTIVE;
|
|
129
129
|
|
|
130
130
|
if (pr.status !== newStatus) {
|
|
131
131
|
log('info', `PR ${pr.id} status: ${pr.status} → ${newStatus}`);
|
|
132
132
|
pr.status = newStatus;
|
|
133
133
|
updated = true;
|
|
134
134
|
|
|
135
|
-
if (newStatus ===
|
|
135
|
+
if (newStatus === PR_STATUS.MERGED || newStatus === PR_STATUS.ABANDONED) {
|
|
136
136
|
if (pr.reviewStatus === 'waiting') {
|
|
137
|
-
pr.reviewStatus = newStatus ===
|
|
137
|
+
pr.reviewStatus = newStatus === PR_STATUS.MERGED ? 'approved' : 'pending';
|
|
138
138
|
log('info', `PR ${pr.id} reviewStatus: waiting → ${pr.reviewStatus} (${newStatus})`);
|
|
139
139
|
}
|
|
140
140
|
await engine().handlePostMerge(pr, project, config, newStatus);
|
|
@@ -157,7 +157,7 @@ async function pollPrStatus(config) {
|
|
|
157
157
|
.map(r => r.displayName)
|
|
158
158
|
.filter(Boolean);
|
|
159
159
|
// Fallback: if PR was merged and no decisive votes, use completedBy
|
|
160
|
-
if (!reviewedBy.length && newStatus ===
|
|
160
|
+
if (!reviewedBy.length && newStatus === PR_STATUS.MERGED && prData.closedBy?.displayName) {
|
|
161
161
|
reviewedBy.push(prData.closedBy.displayName);
|
|
162
162
|
}
|
|
163
163
|
if (JSON.stringify(pr.reviewedBy || []) !== JSON.stringify(reviewedBy)) {
|
|
@@ -184,7 +184,7 @@ async function pollPrStatus(config) {
|
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
if (newStatus !==
|
|
187
|
+
if (newStatus !== PR_STATUS.ACTIVE) return updated;
|
|
188
188
|
|
|
189
189
|
const statusData = await adoFetch(`${repoBase}/statuses?api-version=7.1`, token);
|
|
190
190
|
|
package/engine/cleanup.js
CHANGED
|
@@ -111,7 +111,7 @@ function runCleanup(config, verbose = false) {
|
|
|
111
111
|
const prs = safeJson(projectPrPath(project)) || [];
|
|
112
112
|
const mergedBranches = new Set();
|
|
113
113
|
for (const pr of prs) {
|
|
114
|
-
if (pr.status ===
|
|
114
|
+
if (pr.status === shared.PR_STATUS.MERGED || pr.status === shared.PR_STATUS.ABANDONED || pr.status === shared.PLAN_STATUS.COMPLETED) {
|
|
115
115
|
if (pr.branch) mergedBranches.add(pr.branch);
|
|
116
116
|
}
|
|
117
117
|
}
|
|
@@ -223,7 +223,7 @@ function runCleanup(config, verbose = false) {
|
|
|
223
223
|
const freshPrs = safeJson(projectPrPath(project)) || [];
|
|
224
224
|
const freshMergedBranches = new Set();
|
|
225
225
|
for (const pr of freshPrs) {
|
|
226
|
-
if (pr.status ===
|
|
226
|
+
if (pr.status === shared.PR_STATUS.MERGED || pr.status === shared.PR_STATUS.ABANDONED || pr.status === shared.PLAN_STATUS.COMPLETED) {
|
|
227
227
|
if (pr.branch) freshMergedBranches.add(pr.branch);
|
|
228
228
|
}
|
|
229
229
|
}
|
package/engine/cli.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const shared = require('./shared');
|
|
9
|
-
const { safeRead, safeJson, safeWrite } = shared;
|
|
9
|
+
const { safeRead, safeJson, safeWrite, WI_STATUS, WORK_TYPE, PLAN_STATUS, PR_STATUS, DISPATCH_RESULT } = shared;
|
|
10
10
|
const queries = require('./queries');
|
|
11
11
|
const { getConfig, getControl, getDispatch, getAgentStatus,
|
|
12
12
|
MINIONS_DIR, ENGINE_DIR, AGENTS_DIR, PLANS_DIR, PRD_DIR, CONTROL_PATH, DISPATCH_PATH } = queries;
|
|
@@ -159,8 +159,8 @@ const commands = {
|
|
|
159
159
|
const wiPath = path.join(MINIONS_DIR, "projects", item.meta.project.name, "work-items.json");
|
|
160
160
|
const wiItems = safeJson(wiPath) || [];
|
|
161
161
|
const wi = wiItems.find(w => w.id === item.meta.item.id);
|
|
162
|
-
if (wi && wi.status !==
|
|
163
|
-
wi.status =
|
|
162
|
+
if (wi && wi.status !== WI_STATUS.DISPATCHED) {
|
|
163
|
+
wi.status = WI_STATUS.DISPATCHED;
|
|
164
164
|
wi.dispatched_to = wi.dispatched_to || agentId;
|
|
165
165
|
wi.dispatched_at = wi.dispatched_at || new Date().toISOString();
|
|
166
166
|
safeWrite(wiPath, wiItems);
|
|
@@ -210,7 +210,7 @@ const commands = {
|
|
|
210
210
|
if (!hasResult && !hasError) continue;
|
|
211
211
|
|
|
212
212
|
const isSuccess = hasResult && !hasError;
|
|
213
|
-
const result = isSuccess ?
|
|
213
|
+
const result = isSuccess ? DISPATCH_RESULT.SUCCESS : DISPATCH_RESULT.ERROR;
|
|
214
214
|
|
|
215
215
|
e.log('info', `Orphan recovery: ${agentId} (${item.id}) completed while engine was down — result: ${result}`);
|
|
216
216
|
|
|
@@ -222,7 +222,7 @@ const commands = {
|
|
|
222
222
|
|
|
223
223
|
// Update work item status
|
|
224
224
|
if (item.meta?.item?.id) {
|
|
225
|
-
const status = isSuccess ?
|
|
225
|
+
const status = isSuccess ? WI_STATUS.DONE : WI_STATUS.FAILED;
|
|
226
226
|
try {
|
|
227
227
|
lifecycle.updateWorkItemStatus(item.meta, status, isSuccess ? '' : 'Completed while engine was down');
|
|
228
228
|
} catch {
|
|
@@ -286,8 +286,8 @@ const commands = {
|
|
|
286
286
|
const items = safeJson(wiPath) || [];
|
|
287
287
|
let changed = false;
|
|
288
288
|
for (const item of items) {
|
|
289
|
-
if (item.status ===
|
|
290
|
-
item.status =
|
|
289
|
+
if (item.status === WI_STATUS.DISPATCHED && !activeIds.has(item.id)) {
|
|
290
|
+
item.status = WI_STATUS.PENDING;
|
|
291
291
|
delete item.dispatched_at;
|
|
292
292
|
delete item.dispatched_to;
|
|
293
293
|
changed = true;
|
|
@@ -656,7 +656,7 @@ const commands = {
|
|
|
656
656
|
id: `W${String(items.length + 1).padStart(3, '0')}`,
|
|
657
657
|
title: title,
|
|
658
658
|
type: opts.type || 'implement',
|
|
659
|
-
status:
|
|
659
|
+
status: WI_STATUS.QUEUED,
|
|
660
660
|
priority: opts.priority || 'medium',
|
|
661
661
|
complexity: opts.complexity || 'medium',
|
|
662
662
|
description: opts.description || title,
|
|
@@ -750,7 +750,7 @@ const commands = {
|
|
|
750
750
|
}
|
|
751
751
|
|
|
752
752
|
const id = e.addToDispatch({
|
|
753
|
-
type:
|
|
753
|
+
type: WORK_TYPE.PLAN_TO_PRD,
|
|
754
754
|
agent: agentId,
|
|
755
755
|
agentName: config.agents[agentId]?.name,
|
|
756
756
|
agentRole: config.agents[agentId]?.role,
|
|
@@ -815,13 +815,13 @@ const commands = {
|
|
|
815
815
|
}
|
|
816
816
|
if (exists && name === 'pullRequests') {
|
|
817
817
|
const prs = safeJson(filePath) || [];
|
|
818
|
-
const pending = prs.filter(p => p.status ===
|
|
819
|
-
const needsFix = prs.filter(p => p.status ===
|
|
818
|
+
const pending = prs.filter(p => p.status === PR_STATUS.ACTIVE && (p.reviewStatus === 'pending' || p.reviewStatus === 'waiting'));
|
|
819
|
+
const needsFix = prs.filter(p => p.status === PR_STATUS.ACTIVE && p.reviewStatus === 'changes-requested');
|
|
820
820
|
console.log(` PRs: ${pending.length} pending review, ${needsFix.length} need fixes`);
|
|
821
821
|
}
|
|
822
822
|
if (exists && name === 'workItems') {
|
|
823
823
|
const items = safeJson(filePath) || [];
|
|
824
|
-
const queued = items.filter(i => i.status ===
|
|
824
|
+
const queued = items.filter(i => i.status === WI_STATUS.QUEUED);
|
|
825
825
|
console.log(` Items: ${queued.length} queued`);
|
|
826
826
|
}
|
|
827
827
|
if (name === 'specs' || name === 'mergedDesignDocs') {
|
|
@@ -853,7 +853,7 @@ const commands = {
|
|
|
853
853
|
const killed = dispatch.active || [];
|
|
854
854
|
for (const item of killed) {
|
|
855
855
|
if (item.meta) {
|
|
856
|
-
e.updateWorkItemStatus(item.meta,
|
|
856
|
+
e.updateWorkItemStatus(item.meta, WI_STATUS.PENDING, '');
|
|
857
857
|
const itemId = item.meta.item?.id;
|
|
858
858
|
if (itemId) {
|
|
859
859
|
const wiPath = (item.meta.source === 'central-work-item' || item.meta.source === 'central-work-item-fanout')
|
|
@@ -865,7 +865,7 @@ const commands = {
|
|
|
865
865
|
const items = safeJson(wiPath) || [];
|
|
866
866
|
const target = items.find(i => i.id === itemId);
|
|
867
867
|
if (target) {
|
|
868
|
-
target.status =
|
|
868
|
+
target.status = WI_STATUS.PENDING;
|
|
869
869
|
delete target.dispatched_at;
|
|
870
870
|
delete target.dispatched_to;
|
|
871
871
|
delete target.failReason;
|
package/engine/github.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const shared = require('./shared');
|
|
8
|
-
const { exec, getProjects, projectPrPath, projectWorkItemsPath, safeJson, safeWrite, MINIONS_DIR, addPrLink, getPrLinks, log, dateStamp } = shared;
|
|
8
|
+
const { exec, getProjects, projectPrPath, projectWorkItemsPath, safeJson, safeWrite, MINIONS_DIR, addPrLink, getPrLinks, log, dateStamp, PR_STATUS } = shared;
|
|
9
9
|
const { getPrs } = require('./queries');
|
|
10
10
|
const path = require('path');
|
|
11
11
|
|
|
@@ -53,7 +53,7 @@ async function forEachActiveGhPr(config, callback) {
|
|
|
53
53
|
if (!slug) continue;
|
|
54
54
|
|
|
55
55
|
const prs = getPrs(project);
|
|
56
|
-
const activePrs = prs.filter(pr => pr.status ===
|
|
56
|
+
const activePrs = prs.filter(pr => pr.status === PR_STATUS.ACTIVE);
|
|
57
57
|
if (activePrs.length === 0) continue;
|
|
58
58
|
|
|
59
59
|
let projectUpdated = 0;
|
|
@@ -79,7 +79,7 @@ async function forEachActiveGhPr(config, callback) {
|
|
|
79
79
|
// Also poll manually-linked PRs from central pull-requests.json (extract slug from URL)
|
|
80
80
|
const centralPath = path.join(MINIONS_DIR, 'pull-requests.json');
|
|
81
81
|
const centralPrs = safeJson(centralPath) || [];
|
|
82
|
-
const activeCentral = centralPrs.filter(pr => pr.status ===
|
|
82
|
+
const activeCentral = centralPrs.filter(pr => pr.status === PR_STATUS.ACTIVE && pr.url);
|
|
83
83
|
let centralUpdated = 0;
|
|
84
84
|
for (const pr of activeCentral) {
|
|
85
85
|
const ghMatch = pr.url.match(/github\.com\/([^/]+\/[^/]+)\/pull\/(\d+)/);
|
|
@@ -123,19 +123,19 @@ async function pollPrStatus(config) {
|
|
|
123
123
|
|
|
124
124
|
// Map GitHub PR state to minions status
|
|
125
125
|
let newStatus = pr.status;
|
|
126
|
-
if (prData.merged) newStatus =
|
|
127
|
-
else if (prData.state === 'closed') newStatus =
|
|
128
|
-
else if (prData.state === 'open') newStatus =
|
|
126
|
+
if (prData.merged) newStatus = PR_STATUS.MERGED;
|
|
127
|
+
else if (prData.state === 'closed') newStatus = PR_STATUS.ABANDONED;
|
|
128
|
+
else if (prData.state === 'open') newStatus = PR_STATUS.ACTIVE;
|
|
129
129
|
|
|
130
130
|
if (pr.status !== newStatus) {
|
|
131
131
|
log('info', `PR ${pr.id} status: ${pr.status} → ${newStatus}`);
|
|
132
132
|
pr.status = newStatus;
|
|
133
133
|
updated = true;
|
|
134
134
|
|
|
135
|
-
if (newStatus ===
|
|
135
|
+
if (newStatus === PR_STATUS.MERGED || newStatus === PR_STATUS.ABANDONED) {
|
|
136
136
|
// Resolve stale 'waiting' review status — won't be polled again after this
|
|
137
137
|
if (pr.reviewStatus === 'waiting') {
|
|
138
|
-
pr.reviewStatus = newStatus ===
|
|
138
|
+
pr.reviewStatus = newStatus === PR_STATUS.MERGED ? 'approved' : 'pending';
|
|
139
139
|
log('info', `PR ${pr.id} reviewStatus: waiting → ${pr.reviewStatus} (${newStatus})`);
|
|
140
140
|
}
|
|
141
141
|
await engine().handlePostMerge(pr, project, config, newStatus);
|
package/engine/lifecycle.js
CHANGED
|
@@ -197,7 +197,7 @@ function checkPlanCompletion(meta, config) {
|
|
|
197
197
|
const prs = (safeJson(shared.projectPrPath(p)) || [])
|
|
198
198
|
.filter(pr => {
|
|
199
199
|
const linkedId = prLinks[pr.id];
|
|
200
|
-
return pr.status ===
|
|
200
|
+
return pr.status === PR_STATUS.ACTIVE && linkedId && doneItems.find(w => w.id === linkedId);
|
|
201
201
|
});
|
|
202
202
|
if (prs.length > 0) {
|
|
203
203
|
projectPrs[p.name] = { project: p, prs, mainBranch: p.mainBranch || 'main' };
|
|
@@ -693,7 +693,7 @@ function updatePrAfterReview(agentId, pr, project) {
|
|
|
693
693
|
// Record the reviewer — actual verdict comes from ADO/GitHub votes via pollPrStatus.
|
|
694
694
|
// Set to 'waiting' so pollPrStatus updates it with the real vote on next cycle.
|
|
695
695
|
const dispatch = getDispatch();
|
|
696
|
-
const completedEntry = (dispatch.completed || []).find(d => d.agent === agentId && d.type ===
|
|
696
|
+
const completedEntry = (dispatch.completed || []).find(d => d.agent === agentId && d.type === WORK_TYPE.REVIEW);
|
|
697
697
|
|
|
698
698
|
// Set reviewStatus to 'waiting' (single source of truth — synced from ADO/GitHub votes on next poll)
|
|
699
699
|
target.reviewStatus = 'waiting';
|
|
@@ -988,7 +988,7 @@ function updateMetrics(agentId, dispatchItem, result, taskUsage, prsCreatedCount
|
|
|
988
988
|
if (result === DISPATCH_RESULT.SUCCESS) {
|
|
989
989
|
m.tasksCompleted++;
|
|
990
990
|
if (prsCreatedCount > 0) m.prsCreated = (m.prsCreated || 0) + prsCreatedCount;
|
|
991
|
-
if (dispatchItem.type ===
|
|
991
|
+
if (dispatchItem.type === WORK_TYPE.REVIEW) m.reviewsDone++;
|
|
992
992
|
} else if (result === 'retry') {
|
|
993
993
|
// Auto-retry: count cost but not as a final outcome
|
|
994
994
|
m.tasksRetried = (m.tasksRetried || 0) + 1;
|
|
@@ -1243,7 +1243,7 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
|
|
|
1243
1243
|
}
|
|
1244
1244
|
|
|
1245
1245
|
// Detect implement tasks that completed without creating a PR
|
|
1246
|
-
if (isSuccess && (type ===
|
|
1246
|
+
if (isSuccess && (type === WORK_TYPE.IMPLEMENT || type === WORK_TYPE.IMPLEMENT_LARGE || type === WORK_TYPE.FIX) && prsCreatedCount === 0 && meta?.item?.id) {
|
|
1247
1247
|
// Check if a PR already exists linked to this work item (from a previous attempt)
|
|
1248
1248
|
const projects = shared.getProjects(config);
|
|
1249
1249
|
const existingPrFound = Object.values(getPrLinks()).includes(meta.item.id);
|
|
@@ -1280,8 +1280,8 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
|
|
|
1280
1280
|
}
|
|
1281
1281
|
}
|
|
1282
1282
|
|
|
1283
|
-
if (type ===
|
|
1284
|
-
if (type ===
|
|
1283
|
+
if (type === WORK_TYPE.REVIEW) updatePrAfterReview(agentId, meta?.pr, meta?.project);
|
|
1284
|
+
if (type === WORK_TYPE.FIX) updatePrAfterFix(meta?.pr, meta?.project, meta?.source);
|
|
1285
1285
|
checkForLearnings(agentId, config.agents[agentId], dispatchItem.task);
|
|
1286
1286
|
if (isSuccess) extractSkillsFromOutput(stdout, agentId, dispatchItem, config);
|
|
1287
1287
|
updateAgentHistory(agentId, dispatchItem, result);
|
|
@@ -1311,14 +1311,14 @@ function syncPrdFromPrs(config) {
|
|
|
1311
1311
|
for (const project of allProjects) {
|
|
1312
1312
|
const wiPath = projectWorkItemsPath(project);
|
|
1313
1313
|
const items = safeJson(wiPath) || [];
|
|
1314
|
-
const hasPending = items.some(wi => wi.status ===
|
|
1314
|
+
const hasPending = items.some(wi => wi.status === WI_STATUS.PENDING && !wi._pr);
|
|
1315
1315
|
if (!hasPending) continue;
|
|
1316
1316
|
const reconciled = reconcileItemsWithPrs(items, allPrs);
|
|
1317
1317
|
if (reconciled > 0) {
|
|
1318
1318
|
safeWrite(wiPath, items);
|
|
1319
1319
|
// Sync done status to PRD JSON for each newly reconciled item
|
|
1320
1320
|
for (const wi of items) {
|
|
1321
|
-
if (wi.status ===
|
|
1321
|
+
if (wi.status === WI_STATUS.DONE) syncPrdItemStatus(wi.id, 'done', wi.sourcePlan);
|
|
1322
1322
|
}
|
|
1323
1323
|
totalReconciled += reconciled;
|
|
1324
1324
|
}
|
package/engine/meeting.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const shared = require('./shared');
|
|
9
|
-
const { safeJson, safeWrite, safeRead, uid, log, ENGINE_DEFAULTS } = shared;
|
|
9
|
+
const { safeJson, safeWrite, safeRead, uid, log, ENGINE_DEFAULTS, WORK_TYPE, DISPATCH_RESULT } = shared;
|
|
10
10
|
const queries = require('./queries');
|
|
11
11
|
const { getDispatch, getConfig } = queries;
|
|
12
12
|
const { renderPlaybook } = require('./playbook');
|
|
@@ -118,7 +118,7 @@ function discoverMeetingWork(config) {
|
|
|
118
118
|
if (!prompt) continue;
|
|
119
119
|
|
|
120
120
|
work.push({
|
|
121
|
-
type:
|
|
121
|
+
type: WORK_TYPE.MEETING,
|
|
122
122
|
agent: concluder,
|
|
123
123
|
agentName: agents[concluder]?.name || concluder,
|
|
124
124
|
agentRole: agents[concluder]?.role || 'Agent',
|
|
@@ -165,7 +165,7 @@ function discoverMeetingWork(config) {
|
|
|
165
165
|
if (!prompt) continue;
|
|
166
166
|
|
|
167
167
|
work.push({
|
|
168
|
-
type:
|
|
168
|
+
type: WORK_TYPE.MEETING,
|
|
169
169
|
agent: agentId,
|
|
170
170
|
agentName: agents[agentId]?.name || agentId,
|
|
171
171
|
agentRole: agents[agentId]?.role || 'Agent',
|
|
@@ -278,7 +278,7 @@ function _killMeetingDispatches(meetingId) {
|
|
|
278
278
|
dp.active = (dp.active || []).filter(d => d.meta?.meetingId !== meetingId);
|
|
279
279
|
dp.completed = dp.completed || [];
|
|
280
280
|
for (const d of toKill) {
|
|
281
|
-
dp.completed.push({ ...d, result:
|
|
281
|
+
dp.completed.push({ ...d, result: DISPATCH_RESULT.ERROR, reason: 'Meeting ended/advanced by human', completed_at: new Date().toISOString() });
|
|
282
282
|
}
|
|
283
283
|
if (dp.completed.length > 100) dp.completed = dp.completed.slice(-100);
|
|
284
284
|
return dp;
|
package/engine/pipeline.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const path = require('path');
|
|
9
9
|
const shared = require('./shared');
|
|
10
|
-
const { safeJson, safeWrite, safeRead, safeReadDir, uid, log, ts, dateStamp, mutateJsonFileLocked } = shared;
|
|
10
|
+
const { safeJson, safeWrite, safeRead, safeReadDir, uid, log, ts, dateStamp, mutateJsonFileLocked, WI_STATUS, WORK_TYPE, PLAN_STATUS, PR_STATUS } = shared;
|
|
11
11
|
const { parseCronExpr, shouldRunNow } = require('./scheduler');
|
|
12
12
|
|
|
13
13
|
const PIPELINES_DIR = path.join(__dirname, '..', 'pipelines');
|
|
@@ -172,7 +172,7 @@ function executeTaskStage(stage, stageState, run, config) {
|
|
|
172
172
|
type: item.type || stage.taskType || 'explore',
|
|
173
173
|
priority: item.priority || stage.priority || 'medium',
|
|
174
174
|
agent: item.agent || stage.agent || '',
|
|
175
|
-
status:
|
|
175
|
+
status: WI_STATUS.PENDING,
|
|
176
176
|
created: ts(),
|
|
177
177
|
createdBy: 'pipeline:' + run.pipelineId,
|
|
178
178
|
branch: `pipeline/${run.pipelineId}/${stage.id}`,
|
|
@@ -243,9 +243,9 @@ function executePlanStage(stage, stageState, run, config) {
|
|
|
243
243
|
workItems.push({
|
|
244
244
|
id: wiId,
|
|
245
245
|
title: `Convert plan to PRD: ${path.basename(filePath)}`,
|
|
246
|
-
type:
|
|
246
|
+
type: WORK_TYPE.PLAN_TO_PRD,
|
|
247
247
|
priority: 'high',
|
|
248
|
-
status:
|
|
248
|
+
status: WI_STATUS.PENDING,
|
|
249
249
|
planFile: path.basename(filePath),
|
|
250
250
|
created: ts(),
|
|
251
251
|
createdBy: 'pipeline:' + run.pipelineId,
|
|
@@ -347,7 +347,7 @@ function isStageComplete(stage, stageState, run, config) {
|
|
|
347
347
|
if (ids.length === 0) return false;
|
|
348
348
|
return ids.every(id => {
|
|
349
349
|
const wi = workItems.find(w => w.id === id);
|
|
350
|
-
return !wi || wi.status ===
|
|
350
|
+
return !wi || wi.status === WI_STATUS.DONE || wi.status === WI_STATUS.FAILED; // missing = treat as done
|
|
351
351
|
});
|
|
352
352
|
}
|
|
353
353
|
case 'meeting': {
|
|
@@ -372,7 +372,7 @@ function isStageComplete(stage, stageState, run, config) {
|
|
|
372
372
|
const prdWiIds = artifacts.workItems || [];
|
|
373
373
|
const prdDone = prdWiIds.every(id => {
|
|
374
374
|
const wi = all.find(w => w.id === id);
|
|
375
|
-
return !wi || wi.status ===
|
|
375
|
+
return !wi || wi.status === WI_STATUS.DONE || wi.status === WI_STATUS.FAILED; // missing = treat as done
|
|
376
376
|
});
|
|
377
377
|
if (!prdDone) return false;
|
|
378
378
|
|
|
@@ -390,7 +390,7 @@ function isStageComplete(stage, stageState, run, config) {
|
|
|
390
390
|
}
|
|
391
391
|
// Find materialized work items for discovered PRDs
|
|
392
392
|
for (const prdFile of (artifacts.prds || [])) {
|
|
393
|
-
const prdItems = all.filter(w => w.sourcePlan === prdFile && w.type !==
|
|
393
|
+
const prdItems = all.filter(w => w.sourcePlan === prdFile && w.type !== WORK_TYPE.PLAN_TO_PRD);
|
|
394
394
|
for (const wi of prdItems) {
|
|
395
395
|
if (!(artifacts.workItems || []).includes(wi.id)) {
|
|
396
396
|
artifacts.workItems = artifacts.workItems || [];
|
|
@@ -405,8 +405,8 @@ function isStageComplete(stage, stageState, run, config) {
|
|
|
405
405
|
for (const prdFile of artifacts.prds) {
|
|
406
406
|
const prdPath = path.join(prdDir, prdFile);
|
|
407
407
|
const prd = safeJson(prdPath);
|
|
408
|
-
if (prd && prd.status ===
|
|
409
|
-
prd.status =
|
|
408
|
+
if (prd && prd.status === PLAN_STATUS.AWAITING_APPROVAL) {
|
|
409
|
+
prd.status = PLAN_STATUS.APPROVED;
|
|
410
410
|
prd.approvedAt = ts();
|
|
411
411
|
prd.approvedBy = 'pipeline:' + run.pipelineId;
|
|
412
412
|
safeWrite(prdPath, prd);
|
|
@@ -420,7 +420,7 @@ function isStageComplete(stage, stageState, run, config) {
|
|
|
420
420
|
if (implementIds.length === 0 && artifacts.prds?.length > 0) return false; // items not materialized yet
|
|
421
421
|
return implementIds.every(id => {
|
|
422
422
|
const wi = all.find(w => w.id === id);
|
|
423
|
-
return !wi || wi.status ===
|
|
423
|
+
return !wi || wi.status === WI_STATUS.DONE || wi.status === WI_STATUS.FAILED; // missing = treat as done
|
|
424
424
|
});
|
|
425
425
|
}
|
|
426
426
|
case 'merge-prs': {
|
|
@@ -431,7 +431,7 @@ function isStageComplete(stage, stageState, run, config) {
|
|
|
431
431
|
const prs = safeJson(shared.projectPrPath(project)) || [];
|
|
432
432
|
for (const prId of prIds) {
|
|
433
433
|
const pr = prs.find(p => p.id === prId);
|
|
434
|
-
if (pr && pr.status !==
|
|
434
|
+
if (pr && pr.status !== PR_STATUS.MERGED && pr.status !== PR_STATUS.ABANDONED) return false;
|
|
435
435
|
}
|
|
436
436
|
}
|
|
437
437
|
return true;
|
package/engine/playbook.js
CHANGED
|
@@ -9,7 +9,7 @@ const path = require('path');
|
|
|
9
9
|
const shared = require('./shared');
|
|
10
10
|
const queries = require('./queries');
|
|
11
11
|
|
|
12
|
-
const { safeJson, safeRead, getProjects, log, dateStamp } = shared;
|
|
12
|
+
const { safeJson, safeRead, getProjects, log, dateStamp, WI_STATUS, WORK_TYPE, PR_STATUS, DISPATCH_RESULT } = shared;
|
|
13
13
|
const { getConfig, getDispatch, getNotes, getAgentCharter, getPrs, AGENTS_DIR } = queries;
|
|
14
14
|
|
|
15
15
|
const MINIONS_DIR = path.resolve(__dirname, '..');
|
|
@@ -135,7 +135,7 @@ function resolveTaskContext(item, config) {
|
|
|
135
135
|
// Check work-items to find which plan file this agent created
|
|
136
136
|
const workItems = safeJson(path.join(MINIONS_DIR, 'work-items.json')) || [];
|
|
137
137
|
const agentPlanItems = workItems.filter(w =>
|
|
138
|
-
w.type ===
|
|
138
|
+
w.type === WORK_TYPE.PLAN && w.dispatched_to === agent.id && w.status === WI_STATUS.DONE && w._planFileName
|
|
139
139
|
).sort((a, b) => (b.completedAt || '').localeCompare(a.completedAt || ''));
|
|
140
140
|
|
|
141
141
|
if (agentPlanItems.length > 0) {
|
|
@@ -390,7 +390,7 @@ function buildAgentContext(agentId, config, project) {
|
|
|
390
390
|
|
|
391
391
|
// Recent completions (last 5, not 10)
|
|
392
392
|
const recentCompleted = (dispatch.completed || []).slice(-5).reverse().map(d =>
|
|
393
|
-
`- **${d.agent}** ${d.result ===
|
|
393
|
+
`- **${d.agent}** ${d.result === DISPATCH_RESULT.SUCCESS ? 'completed' : 'failed'}: ${(d.task || '').slice(0, 80)}${d.resultSummary ? ' — ' + d.resultSummary.slice(0, 100) : ''}`
|
|
394
394
|
);
|
|
395
395
|
if (recentCompleted.length > 0) {
|
|
396
396
|
context += `## Recently Completed\n\n${recentCompleted.join('\n')}\n\n`;
|
|
@@ -400,13 +400,13 @@ function buildAgentContext(agentId, config, project) {
|
|
|
400
400
|
const projects = getProjects(config);
|
|
401
401
|
const allPrs = [];
|
|
402
402
|
for (const p of projects) {
|
|
403
|
-
const prs = getPrs(p).filter(pr => pr.status ===
|
|
403
|
+
const prs = getPrs(p).filter(pr => pr.status === PR_STATUS.ACTIVE || pr.status === 'linked');
|
|
404
404
|
for (const pr of prs) allPrs.push({ ...pr, _project: p.name });
|
|
405
405
|
}
|
|
406
406
|
// Also check central pull-requests.json
|
|
407
407
|
try {
|
|
408
408
|
const centralPrs = safeJson(path.join(MINIONS_DIR, 'pull-requests.json')) || [];
|
|
409
|
-
for (const pr of centralPrs.filter(pr => pr.status ===
|
|
409
|
+
for (const pr of centralPrs.filter(pr => pr.status === PR_STATUS.ACTIVE || pr.status === 'linked')) {
|
|
410
410
|
if (!allPrs.some(p => p.id === pr.id)) allPrs.push({ ...pr, _project: 'central' });
|
|
411
411
|
}
|
|
412
412
|
} catch (e) { log('warn', 'read central pull-requests: ' + e.message); }
|
|
@@ -450,10 +450,10 @@ function buildBaseVars(agentId, config, project) {
|
|
|
450
450
|
}
|
|
451
451
|
|
|
452
452
|
function selectPlaybook(workType, item) {
|
|
453
|
-
if (item?.branchStrategy === 'shared-branch' && (workType ===
|
|
453
|
+
if (item?.branchStrategy === 'shared-branch' && (workType === WORK_TYPE.IMPLEMENT || workType === WORK_TYPE.IMPLEMENT_LARGE)) {
|
|
454
454
|
return 'implement-shared';
|
|
455
455
|
}
|
|
456
|
-
if (workType ===
|
|
456
|
+
if (workType === WORK_TYPE.REVIEW && !item?._pr && !item?.pr_id) {
|
|
457
457
|
return 'work-item';
|
|
458
458
|
}
|
|
459
459
|
const typeSpecificPlaybooks = ['explore', 'review', 'test', 'plan-to-prd', 'plan', 'ask', 'verify', 'decompose', 'meeting-investigate', 'meeting-debate', 'meeting-conclude'];
|
package/engine/scheduler.js
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
const fs = require('fs');
|
|
25
25
|
const path = require('path');
|
|
26
26
|
const shared = require('./shared');
|
|
27
|
-
const { safeJson, safeWrite, mutateJsonFileLocked } = shared;
|
|
27
|
+
const { safeJson, safeWrite, mutateJsonFileLocked, WI_STATUS } = shared;
|
|
28
28
|
|
|
29
29
|
const SCHEDULE_RUNS_PATH = path.join(__dirname, 'schedule-runs.json');
|
|
30
30
|
|
|
@@ -125,7 +125,7 @@ function discoverScheduledWork(config) {
|
|
|
125
125
|
type: sched.type || 'implement',
|
|
126
126
|
priority: sched.priority || 'medium',
|
|
127
127
|
description: sched.description || sched.title,
|
|
128
|
-
status:
|
|
128
|
+
status: WI_STATUS.PENDING,
|
|
129
129
|
created: new Date().toISOString(),
|
|
130
130
|
createdBy: 'scheduler',
|
|
131
131
|
agent: sched.agent || null,
|
package/engine.js
CHANGED
|
@@ -1319,7 +1319,7 @@ function discoverFromPrs(config, project) {
|
|
|
1319
1319
|
}
|
|
1320
1320
|
|
|
1321
1321
|
// PRs with build failures — route to author (has session context from implementing)
|
|
1322
|
-
if (pr.status ===
|
|
1322
|
+
if (pr.status === PR_STATUS.ACTIVE && pr.buildStatus === 'failing') {
|
|
1323
1323
|
const key = `build-fix-${project?.name || 'default'}-${pr.id}`;
|
|
1324
1324
|
if (isAlreadyDispatched(key) || isOnCooldown(key, cooldownMs)) continue;
|
|
1325
1325
|
const agentId = resolveAgent('fix', config, pr.agent);
|
|
@@ -1638,7 +1638,7 @@ function materializeSpecsAsWorkItems(config, project) {
|
|
|
1638
1638
|
|
|
1639
1639
|
const prs = getPrs(project);
|
|
1640
1640
|
const mergedPrs = prs.filter(pr =>
|
|
1641
|
-
(pr.status ===
|
|
1641
|
+
(pr.status === PR_STATUS.MERGED || pr.status === PLAN_STATUS.COMPLETED) &&
|
|
1642
1642
|
!tracker.processedPrs[pr.id]
|
|
1643
1643
|
);
|
|
1644
1644
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.322",
|
|
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"
|