@yemi33/minions 0.1.1007 → 0.1.1008
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 +3 -3
- package/dashboard.js +17 -8
- package/engine/cleanup.js +22 -1
- package/engine/lifecycle.js +10 -6
- package/engine/pipeline.js +47 -21
- package/engine/teams.js +31 -10
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.1008 (2026-04-16)
|
|
4
4
|
|
|
5
5
|
### Features
|
|
6
6
|
- fix duplicate PR creation + cancel stale review dispatches on PR close (#1137)
|
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
- implement dashboard loop/watch management panel
|
|
26
26
|
|
|
27
27
|
### Fixes
|
|
28
|
+
- harden audited state transitions
|
|
29
|
+
- scrub stale temp agent IDs from metrics.json (#1129)
|
|
28
30
|
- harden repo-scoped PR identity handling
|
|
29
31
|
- only gate reviews/fixes when no free agent slots remain
|
|
30
32
|
- extend auto-link fallback to ADO projects
|
|
@@ -43,8 +45,6 @@
|
|
|
43
45
|
- build fix sets fixDispatched to block same-tick conflict fix
|
|
44
46
|
- ENGINE_DEFAULTS undefined in tick + playbook empty-var false positives
|
|
45
47
|
- defer review setCooldown to post-gating in discoverWork
|
|
46
|
-
- move review verdict check before updateWorkItemStatus(DONE)
|
|
47
|
-
- address review feedback — move writeToInbox outside lock, add absolute condition auto-expire
|
|
48
48
|
|
|
49
49
|
### Other
|
|
50
50
|
- refactor: migrate PR links to array format and consolidate shared helpers
|
package/dashboard.js
CHANGED
|
@@ -720,6 +720,7 @@ async function executeCCActions(actions) {
|
|
|
720
720
|
const kbDir = path.join(MINIONS_DIR, 'knowledge', category);
|
|
721
721
|
if (!fs.existsSync(kbDir)) fs.mkdirSync(kbDir, { recursive: true });
|
|
722
722
|
shared.safeWrite(path.join(kbDir, slug + '.md'), `# ${action.title}\n\n${action.content || action.description || ''}`);
|
|
723
|
+
queries.invalidateKnowledgeBaseCache();
|
|
723
724
|
results.push({ type: 'knowledge', ok: true });
|
|
724
725
|
break;
|
|
725
726
|
}
|
|
@@ -1874,13 +1875,18 @@ const server = http.createServer(async (req, res) => {
|
|
|
1874
1875
|
if (!body.source || !body.itemId) return jsonReply(res, 400, { error: 'source and itemId required' });
|
|
1875
1876
|
const planPath = resolvePlanPath(body.source);
|
|
1876
1877
|
if (!fs.existsSync(planPath)) return jsonReply(res, 404, { error: 'plan file not found' });
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1878
|
+
let removed = false;
|
|
1879
|
+
mutateJsonFileLocked(planPath, (plan) => {
|
|
1880
|
+
if (!plan || Array.isArray(plan) || typeof plan !== 'object') plan = { missing_features: [] };
|
|
1881
|
+
const features = Array.isArray(plan.missing_features) ? plan.missing_features : [];
|
|
1882
|
+
const idx = features.findIndex(f => f.id === body.itemId);
|
|
1883
|
+
if (idx < 0) return plan;
|
|
1884
|
+
features.splice(idx, 1);
|
|
1885
|
+
plan.missing_features = features;
|
|
1886
|
+
removed = true;
|
|
1887
|
+
return plan;
|
|
1888
|
+
}, { defaultValue: { missing_features: [] } });
|
|
1889
|
+
if (!removed) return jsonReply(res, 404, { error: 'item not found in plan' });
|
|
1884
1890
|
|
|
1885
1891
|
// Also remove any materialized work item for this plan item
|
|
1886
1892
|
let cancelled = false;
|
|
@@ -2237,7 +2243,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
2237
2243
|
for (const cat of shared.KB_CATEGORIES) result[cat] = [];
|
|
2238
2244
|
for (const e of entries) {
|
|
2239
2245
|
if (!result[e.cat]) result[e.cat] = [];
|
|
2240
|
-
result[e.cat].push({ file: e.file, category: e.cat, title: e.title, agent: e.agent, date: e.date, size: e.size, preview: e.preview });
|
|
2246
|
+
result[e.cat].push({ file: e.file, category: e.cat, title: e.title, agent: e.agent, date: e.date, sortTs: e.sortTs, size: e.size, preview: e.preview });
|
|
2241
2247
|
}
|
|
2242
2248
|
const swept = safeJson(path.join(ENGINE_DIR, 'kb-swept.json'));
|
|
2243
2249
|
if (swept) result.lastSwept = swept.timestamp;
|
|
@@ -2431,6 +2437,7 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
2431
2437
|
|
|
2432
2438
|
const summary = `${merged} duplicates merged, ${removed} stale removed, ${reclassified} reclassified${pruned ? ', ' + pruned + ' old swept files pruned' : ''}`;
|
|
2433
2439
|
safeWrite(path.join(ENGINE_DIR, 'kb-swept.json'), JSON.stringify({ timestamp: new Date().toISOString(), summary }));
|
|
2440
|
+
queries.invalidateKnowledgeBaseCache();
|
|
2434
2441
|
global._kbSweepLastResult = { ok: true, summary, plan };
|
|
2435
2442
|
global._kbSweepLastCompletedAt = Date.now();
|
|
2436
2443
|
} catch (e) {
|
|
@@ -3276,6 +3283,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3276
3283
|
if (!fs.existsSync(kbDir)) fs.mkdirSync(kbDir, { recursive: true });
|
|
3277
3284
|
const kbFile = path.join(kbDir, name);
|
|
3278
3285
|
safeWrite(kbFile, kbContent);
|
|
3286
|
+
queries.invalidateKnowledgeBaseCache();
|
|
3279
3287
|
|
|
3280
3288
|
// Move inbox item to archive
|
|
3281
3289
|
const archiveDir = path.join(MINIONS_DIR, 'notes', 'archive');
|
|
@@ -4679,6 +4687,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
4679
4687
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
4680
4688
|
const header = '# ' + title + '\n\n*Created by human teammate on ' + new Date().toISOString().slice(0, 10) + '*\n\n';
|
|
4681
4689
|
safeWrite(filePath, header + content);
|
|
4690
|
+
queries.invalidateKnowledgeBaseCache();
|
|
4682
4691
|
invalidateStatusCache();
|
|
4683
4692
|
return jsonReply(res, 200, { ok: true, path: filePath });
|
|
4684
4693
|
}},
|
package/engine/cleanup.js
CHANGED
|
@@ -9,7 +9,7 @@ const shared = require('./shared');
|
|
|
9
9
|
const queries = require('./queries');
|
|
10
10
|
|
|
11
11
|
const { exec, execSilent, log, ts, ENGINE_DEFAULTS } = shared;
|
|
12
|
-
const { safeJson, safeWrite, safeReadDir, mutateWorkItems, getProjects, projectWorkItemsPath, projectPrPath,
|
|
12
|
+
const { safeJson, safeWrite, safeReadDir, mutateWorkItems, mutateJsonFileLocked, getProjects, projectWorkItemsPath, projectPrPath,
|
|
13
13
|
sanitizeBranch, KB_CATEGORIES } = shared;
|
|
14
14
|
const { getDispatch, getAgentStatus } = queries;
|
|
15
15
|
|
|
@@ -667,6 +667,9 @@ function runCleanup(config, verbose = false) {
|
|
|
667
667
|
}
|
|
668
668
|
} catch { /* optional — file may not exist */ }
|
|
669
669
|
|
|
670
|
+
// 14. Scrub stale temp agent keys from metrics.json
|
|
671
|
+
try { scrubStaleMetrics(); } catch { /* best-effort cleanup */ }
|
|
672
|
+
|
|
670
673
|
if (cleaned.ccSessions + cleaned.docSessions + cleaned.cooldowns + cleaned.pidFiles + cleaned.pendingContextsTrimmed > 0) {
|
|
671
674
|
log('info', `Cleanup (resources): ${cleaned.ccSessions} cc-sessions, ${cleaned.docSessions} doc-sessions, ${cleaned.cooldowns} cooldowns, ${cleaned.pendingContextsTrimmed} pendingCtx trimmed, ${cleaned.pidFiles} PID files`);
|
|
672
675
|
}
|
|
@@ -674,9 +677,27 @@ function runCleanup(config, verbose = false) {
|
|
|
674
677
|
return cleaned;
|
|
675
678
|
}
|
|
676
679
|
|
|
680
|
+
// ─── Metrics Scrub ──────────────────────────────────────────────────────────
|
|
681
|
+
|
|
682
|
+
/** Remove stale temp-*, agent1, and _test-* keys from metrics.json.
|
|
683
|
+
* Mirrors the guard in lifecycle.js updateMetrics() — cleans up keys
|
|
684
|
+
* that were written before that guard existed. */
|
|
685
|
+
function scrubStaleMetrics() {
|
|
686
|
+
const metricsPath = path.join(ENGINE_DIR, 'metrics.json');
|
|
687
|
+
if (!fs.existsSync(metricsPath)) return;
|
|
688
|
+
mutateJsonFileLocked(metricsPath, metrics => {
|
|
689
|
+
for (const key of Object.keys(metrics)) {
|
|
690
|
+
if (key.startsWith('temp-') || key === 'agent1' || key.startsWith('_test')) {
|
|
691
|
+
delete metrics[key];
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
|
|
677
697
|
// ─── Exports ─────────────────────────────────────────────────────────────────
|
|
678
698
|
|
|
679
699
|
module.exports = {
|
|
680
700
|
runCleanup,
|
|
701
|
+
scrubStaleMetrics,
|
|
681
702
|
worktreeDirMatchesBranch, // exported for testing
|
|
682
703
|
};
|
package/engine/lifecycle.js
CHANGED
|
@@ -13,6 +13,7 @@ const { safeRead, safeJson, safeWrite, mutateJsonFileLocked, mutateWorkItems, ex
|
|
|
13
13
|
const { trackEngineUsage } = require('./llm');
|
|
14
14
|
const queries = require('./queries');
|
|
15
15
|
const { isBranchActive } = require('./cooldown');
|
|
16
|
+
const { worktreeDirMatchesBranch } = require('./cleanup');
|
|
16
17
|
const { getConfig, getInboxFiles, getNotes, getPrs, getDispatch,
|
|
17
18
|
MINIONS_DIR, ENGINE_DIR, PLANS_DIR, PRD_DIR, INBOX_DIR, AGENTS_DIR } = queries;
|
|
18
19
|
|
|
@@ -1065,8 +1066,10 @@ const PENDING_REBASES_PATH = path.join(ENGINE_DIR, 'pending-rebases.json');
|
|
|
1065
1066
|
|
|
1066
1067
|
function queuePendingRebase(pr, project, mergedItemId) {
|
|
1067
1068
|
mutateJsonFileLocked(PENDING_REBASES_PATH, (pending) => {
|
|
1068
|
-
|
|
1069
|
+
const prDisplayId = shared.getPrDisplayId(pr);
|
|
1070
|
+
if (pending.some(e => e.projectName === project.name && shared.getPrDisplayId(e.prId) === prDisplayId)) return pending; // already queued
|
|
1069
1071
|
pending.push({ prId: pr.id, branch: pr.branch, projectName: project.name, mergedItemId, queuedAt: ts(), attempts: 0 });
|
|
1072
|
+
return pending;
|
|
1070
1073
|
}, { defaultValue: [] });
|
|
1071
1074
|
}
|
|
1072
1075
|
|
|
@@ -1087,9 +1090,11 @@ async function processPendingRebases(config) {
|
|
|
1087
1090
|
const project = shared.getProjects(config).find(p => p.name === entry.projectName);
|
|
1088
1091
|
if (!project) continue;
|
|
1089
1092
|
|
|
1090
|
-
const prs =
|
|
1091
|
-
const pr =
|
|
1093
|
+
const prs = getPrs(project);
|
|
1094
|
+
const pr = shared.findPrRecord(prs, entry.prId, project);
|
|
1095
|
+
if (pr && pr.id !== entry.prId) entry.prId = pr.id;
|
|
1092
1096
|
if (!pr) continue; // PR closed/merged since queuing
|
|
1097
|
+
if (pr.status !== PR_STATUS.ACTIVE) continue; // PR closed/merged since queuing
|
|
1093
1098
|
|
|
1094
1099
|
const result = await rebaseBranchOntoMain(pr, project, config);
|
|
1095
1100
|
if (!result.success) {
|
|
@@ -1120,12 +1125,11 @@ async function handlePostMerge(pr, project, config, newStatus) {
|
|
|
1120
1125
|
const root = path.resolve(project.localPath);
|
|
1121
1126
|
const wtRoot = path.resolve(root, config.engine?.worktreeRoot || '../worktrees');
|
|
1122
1127
|
// Find worktrees matching this branch — dir format is {slug}-{branch}-{suffix}
|
|
1123
|
-
const branchSlug = shared.sanitizeBranch(pr.branch).toLowerCase();
|
|
1124
1128
|
try {
|
|
1125
1129
|
const dirs = require('fs').readdirSync(wtRoot);
|
|
1126
1130
|
for (const dir of dirs) {
|
|
1127
1131
|
const dirLower = dir.toLowerCase();
|
|
1128
|
-
if (dirLower.
|
|
1132
|
+
if (worktreeDirMatchesBranch(dirLower, pr.branch) || dir === pr.branch || dir === `bt-${prNum}`) {
|
|
1129
1133
|
const wtPath = path.join(wtRoot, dir);
|
|
1130
1134
|
try {
|
|
1131
1135
|
if (!require('fs').statSync(wtPath).isDirectory()) continue;
|
|
@@ -1802,7 +1806,7 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
|
|
|
1802
1806
|
// Find the worktree directory for this dispatch's branch
|
|
1803
1807
|
const branchSlug = shared.sanitizeBranch ? shared.sanitizeBranch(meta.branch) : meta.branch.replace(/[^a-zA-Z0-9._\-\/]/g, '-');
|
|
1804
1808
|
const dirs = fs.readdirSync(worktreeRoot).filter(d => {
|
|
1805
|
-
return d.
|
|
1809
|
+
return worktreeDirMatchesBranch(d.toLowerCase(), meta.branch) && fs.statSync(path.join(worktreeRoot, d)).isDirectory();
|
|
1806
1810
|
});
|
|
1807
1811
|
// Only remove if no other active dispatch uses this branch
|
|
1808
1812
|
const dispatch = getDispatch();
|
package/engine/pipeline.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const path = require('path');
|
|
9
9
|
const shared = require('./shared');
|
|
10
|
+
const queries = require('./queries');
|
|
10
11
|
const { safeJson, safeWrite, safeRead, safeReadDir, uid, log, ts, dateStamp, mutateJsonFileLocked, mutateWorkItems, slugify, formatTranscriptEntry, WI_STATUS, WORK_TYPE, PLAN_STATUS, PR_STATUS, PIPELINE_STATUS, STAGE_TYPE, MEETING_STATUS, ENGINE_DEFAULTS } = shared;
|
|
11
12
|
const http = require('http');
|
|
12
13
|
const { parseCronExpr, shouldRunNow } = require('./scheduler');
|
|
@@ -144,6 +145,46 @@ function resolveStageConfig(stage, run) {
|
|
|
144
145
|
return resolved;
|
|
145
146
|
}
|
|
146
147
|
|
|
148
|
+
function collectPipelinePrRefs(pipeline, run) {
|
|
149
|
+
const refs = [];
|
|
150
|
+
const seen = new Set();
|
|
151
|
+
function addPrRef(resource) {
|
|
152
|
+
if (!resource) return;
|
|
153
|
+
let type = '';
|
|
154
|
+
let id = '';
|
|
155
|
+
let url = '';
|
|
156
|
+
if (typeof resource === 'string') {
|
|
157
|
+
id = resource.trim();
|
|
158
|
+
url = /^https?:\/\//i.test(id) ? id : '';
|
|
159
|
+
} else if (typeof resource === 'object') {
|
|
160
|
+
type = String(resource.type || '').trim().toLowerCase();
|
|
161
|
+
id = String(resource.label || resource.id || '').trim();
|
|
162
|
+
url = String(resource.url || '').trim();
|
|
163
|
+
} else {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (type && type !== 'pr') return;
|
|
167
|
+
const refId = id || url;
|
|
168
|
+
if (!refId && !url) return;
|
|
169
|
+
const prNumber = shared.getPrNumber({ id: refId, url });
|
|
170
|
+
if (!type && prNumber == null) return;
|
|
171
|
+
const key = url || refId;
|
|
172
|
+
if (!key || seen.has(key)) return;
|
|
173
|
+
seen.add(key);
|
|
174
|
+
refs.push({ id: refId, url });
|
|
175
|
+
}
|
|
176
|
+
for (const res of (pipeline?.monitoredResources || [])) addPrRef(res);
|
|
177
|
+
for (const stage of (pipeline?.stages || [])) {
|
|
178
|
+
for (const res of (stage?.monitoredResources || [])) addPrRef(res);
|
|
179
|
+
}
|
|
180
|
+
if (run) {
|
|
181
|
+
for (const [, stageState] of Object.entries(run.stages || {})) {
|
|
182
|
+
for (const prRef of (stageState.artifacts?.prs || [])) addPrRef(prRef);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return refs;
|
|
186
|
+
}
|
|
187
|
+
|
|
147
188
|
// ── Condition Evaluation ─────────────────────────────────────────────────────
|
|
148
189
|
|
|
149
190
|
/**
|
|
@@ -186,27 +227,12 @@ function evaluateCondition(condition, ctx) {
|
|
|
186
227
|
return pipelineRuns.length >= threshold;
|
|
187
228
|
}
|
|
188
229
|
case 'allBuildsGreen': {
|
|
189
|
-
// True when all PRs in monitoredResources (or run artifacts
|
|
190
|
-
const
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
// Collect PR IDs to check: from monitoredResources (type: 'pr') or run artifact PRs
|
|
196
|
-
const prIds = new Set();
|
|
197
|
-
for (const res of (pipeline?.monitoredResources || [])) {
|
|
198
|
-
const r = typeof res === 'string' ? { label: res } : res;
|
|
199
|
-
if (r.type === 'pr' && r.label) prIds.add(r.label);
|
|
200
|
-
}
|
|
201
|
-
// Also check PRs from run artifacts
|
|
202
|
-
if (run) {
|
|
203
|
-
for (const [, s] of Object.entries(run.stages || {})) {
|
|
204
|
-
for (const prId of (s.artifacts?.prs || [])) prIds.add(prId);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
if (prIds.size === 0) return false; // no PRs to check = can't confirm green
|
|
208
|
-
for (const prId of prIds) {
|
|
209
|
-
const pr = allPrs.find(p => p.id === prId);
|
|
230
|
+
// True when all PRs in monitoredResources (pipeline-level or stage-level) or run artifacts have buildStatus 'passing'
|
|
231
|
+
const allPrs = queries.getPullRequests(config);
|
|
232
|
+
const prRefs = collectPipelinePrRefs(pipeline, run);
|
|
233
|
+
if (prRefs.length === 0) return false; // no PRs to check = can't confirm green
|
|
234
|
+
for (const prRef of prRefs) {
|
|
235
|
+
const pr = shared.findPrRecord(allPrs, prRef);
|
|
210
236
|
if (!pr || pr.buildStatus !== 'passing') return false;
|
|
211
237
|
}
|
|
212
238
|
return true;
|
package/engine/teams.js
CHANGED
|
@@ -526,7 +526,7 @@ async function teamsNotifyPrEvent(pr, event, project, prFilePath) {
|
|
|
526
526
|
if (!cfg.notifyEvents || !cfg.notifyEvents.includes(event)) return;
|
|
527
527
|
|
|
528
528
|
// Dedup check — don't re-notify the same event
|
|
529
|
-
if (pr._teamsNotifiedEvents && pr._teamsNotifiedEvents.includes(event)) return;
|
|
529
|
+
if (!prFilePath && pr._teamsNotifiedEvents && pr._teamsNotifiedEvents.includes(event)) return;
|
|
530
530
|
|
|
531
531
|
const card = cards.buildPrCard(pr, event, project);
|
|
532
532
|
|
|
@@ -535,24 +535,45 @@ async function teamsNotifyPrEvent(pr, event, project, prFilePath) {
|
|
|
535
535
|
const convKeys = Object.keys(state.conversations || {});
|
|
536
536
|
if (convKeys.length === 0) return;
|
|
537
537
|
|
|
538
|
+
let claimedEvent = false;
|
|
539
|
+
let shouldPost = true;
|
|
538
540
|
try {
|
|
539
|
-
await teamsPost(convKeys[0], card);
|
|
540
|
-
log('info', `Teams PR notification sent: ${event} for ${pr.id}`);
|
|
541
|
-
|
|
542
|
-
// Record dedup — update _teamsNotifiedEvents on PR via lock
|
|
543
541
|
if (prFilePath) {
|
|
544
542
|
mutateJsonFileLocked(prFilePath, (prs) => {
|
|
545
543
|
if (!Array.isArray(prs)) return prs;
|
|
546
544
|
const target = shared.findPrRecord(prs, pr);
|
|
547
|
-
if (target)
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
545
|
+
if (!target) return prs;
|
|
546
|
+
if (!target._teamsNotifiedEvents) target._teamsNotifiedEvents = [];
|
|
547
|
+
if (target._teamsNotifiedEvents.includes(event)) {
|
|
548
|
+
shouldPost = false;
|
|
549
|
+
return prs;
|
|
552
550
|
}
|
|
551
|
+
target._teamsNotifiedEvents.push(event);
|
|
552
|
+
claimedEvent = true;
|
|
553
|
+
return prs;
|
|
553
554
|
}, { defaultValue: [] });
|
|
554
555
|
}
|
|
556
|
+
if (!shouldPost) return;
|
|
557
|
+
await teamsPost(convKeys[0], card);
|
|
558
|
+
if (pr && typeof pr === 'object') {
|
|
559
|
+
if (!Array.isArray(pr._teamsNotifiedEvents)) pr._teamsNotifiedEvents = [];
|
|
560
|
+
if (!pr._teamsNotifiedEvents.includes(event)) pr._teamsNotifiedEvents.push(event);
|
|
561
|
+
}
|
|
562
|
+
log('info', `Teams PR notification sent: ${event} for ${pr.id}`);
|
|
555
563
|
} catch (err) {
|
|
564
|
+
if (claimedEvent && prFilePath) {
|
|
565
|
+
try {
|
|
566
|
+
mutateJsonFileLocked(prFilePath, (prs) => {
|
|
567
|
+
if (!Array.isArray(prs)) return prs;
|
|
568
|
+
const target = shared.findPrRecord(prs, pr);
|
|
569
|
+
if (!target || !Array.isArray(target._teamsNotifiedEvents)) return prs;
|
|
570
|
+
target._teamsNotifiedEvents = target._teamsNotifiedEvents.filter(e => e !== event);
|
|
571
|
+
return prs;
|
|
572
|
+
}, { defaultValue: [] });
|
|
573
|
+
} catch (revertErr) {
|
|
574
|
+
log('warn', `Teams PR dedup revert failed for ${pr.id}: ${revertErr.message}`);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
556
577
|
log('warn', `Teams PR notification failed for ${pr.id}: ${err.message}`);
|
|
557
578
|
}
|
|
558
579
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1008",
|
|
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"
|