@yemi33/minions 0.1.526 → 0.1.527
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 +5 -1
- package/dashboard/js/render-work-items.js +4 -4
- package/dashboard.js +3 -3
- package/engine/cleanup.js +3 -9
- package/engine/dispatch.js +4 -15
- package/engine/lifecycle.js +2 -27
- package/engine/playbook.js +1 -1
- package/engine/shared.js +30 -0
- package/engine.js +7 -28
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.527 (2026-04-07)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
- Replace direct safeJson reads of work-items.json with getWorkItems() (#424)
|
|
7
|
+
|
|
3
8
|
## 0.1.526 (2026-04-07)
|
|
4
9
|
|
|
5
10
|
### Features
|
|
6
|
-
- Replace new Date().toISOString() with ts() from shared.js (#411)
|
|
7
11
|
- Add Concurrency & Lock Ordering section to CLAUDE.md (#409)
|
|
8
12
|
- 9 test isolation verification tests — run last to detect pollution
|
|
9
13
|
|
|
@@ -175,8 +175,8 @@ async function submitWorkItemEdit(id, source, e) {
|
|
|
175
175
|
const refsRaw = document.getElementById('wi-edit-refs')?.value || '';
|
|
176
176
|
const references = refsRaw.split('\n').filter(function(l) { return l.trim(); }).map(function(l) {
|
|
177
177
|
var parts = l.split('|').map(function(s) { return s.trim(); });
|
|
178
|
-
return { url: parts[0], title: parts[1] || parts[0], type: parts[2] || 'link' };
|
|
179
|
-
});
|
|
178
|
+
return { url: parts[0] || '', title: parts[1] || parts[0] || '', type: parts[2] || 'link' };
|
|
179
|
+
}).filter(function(r) { return r.url; });
|
|
180
180
|
const acRaw = document.getElementById('wi-edit-ac')?.value || '';
|
|
181
181
|
const acceptanceCriteria = acRaw.split('\n').filter(function(l) { return l.trim(); });
|
|
182
182
|
if (!title) { if (btn) { btn.disabled = false; btn.textContent = 'Save'; } alert('Title is required'); return; }
|
|
@@ -387,8 +387,8 @@ async function _submitCreateWorkItem(e) {
|
|
|
387
387
|
const refsRaw = document.getElementById('wi-new-refs')?.value || '';
|
|
388
388
|
const references = refsRaw.split('\n').filter(l => l.trim()).map(l => {
|
|
389
389
|
const parts = l.split('|').map(s => s.trim());
|
|
390
|
-
return { url: parts[0], title: parts[1] || parts[0], type: parts[2] || 'link' };
|
|
391
|
-
});
|
|
390
|
+
return { url: parts[0] || '', title: parts[1] || parts[0] || '', type: parts[2] || 'link' };
|
|
391
|
+
}).filter(r => r.url);
|
|
392
392
|
|
|
393
393
|
try {
|
|
394
394
|
const body = { title, description: desc, type, priority };
|
package/dashboard.js
CHANGED
|
@@ -192,8 +192,8 @@ async function checkNpmVersion() {
|
|
|
192
192
|
_npmVersionCache = { latest: version || null, checkedAt: new Date().toISOString() };
|
|
193
193
|
_npmVersionCacheTs = now;
|
|
194
194
|
} catch (e) {
|
|
195
|
-
console.error('[version-check] npm view failed:', e.message?.split('\n')[0]);
|
|
196
|
-
_npmVersionCache = _npmVersionCache || { latest: null, checkedAt: null, error: e.message?.split('\n')[0] || 'check failed' };
|
|
195
|
+
console.error('[version-check] npm view failed:', e.message?.split('\n')?.[0]);
|
|
196
|
+
_npmVersionCache = _npmVersionCache || { latest: null, checkedAt: null, error: e.message?.split('\n')?.[0] || 'check failed' };
|
|
197
197
|
}
|
|
198
198
|
return _npmVersionCache;
|
|
199
199
|
}
|
|
@@ -3816,7 +3816,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3816
3816
|
// Determine project
|
|
3817
3817
|
reloadConfig();
|
|
3818
3818
|
const projects = shared.getProjects(CONFIG);
|
|
3819
|
-
const targetProject = projectName ? projects.find(p => p.name?.toLowerCase() === projectName.toLowerCase()) : projects[0];
|
|
3819
|
+
const targetProject = projectName ? projects.find(p => p.name?.toLowerCase() === projectName.toLowerCase()) : (projects[0] || null);
|
|
3820
3820
|
const prPath = targetProject ? shared.projectPrPath(targetProject) : path.join(MINIONS_DIR, 'pull-requests.json');
|
|
3821
3821
|
|
|
3822
3822
|
// Extract PR number from URL
|
package/engine/cleanup.js
CHANGED
|
@@ -308,15 +308,9 @@ function runCleanup(config, verbose = false) {
|
|
|
308
308
|
// Collect all work item IDs across all sources
|
|
309
309
|
const allWiIds = new Set();
|
|
310
310
|
try {
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
} catch (e) { log('warn', 'read
|
|
314
|
-
for (const project of projects) {
|
|
315
|
-
try {
|
|
316
|
-
const projItems = safeJson(projectWorkItemsPath(project)) || [];
|
|
317
|
-
projItems.forEach(w => allWiIds.add(w.id));
|
|
318
|
-
} catch (e) { log('warn', 'read project work items for orphan check: ' + e.message); }
|
|
319
|
-
}
|
|
311
|
+
const allItems = queries.getWorkItems();
|
|
312
|
+
allItems.forEach(w => allWiIds.add(w.id));
|
|
313
|
+
} catch (e) { log('warn', 'read work items for orphan check: ' + e.message); }
|
|
320
314
|
|
|
321
315
|
let changed = false;
|
|
322
316
|
for (const queue of ['pending', 'active']) {
|
package/engine/dispatch.js
CHANGED
|
@@ -126,14 +126,8 @@ function completeDispatch(id, result = DISPATCH_RESULT.SUCCESS, reason = '', res
|
|
|
126
126
|
if (processWorkItemFailure && result === DISPATCH_RESULT.ERROR && item.meta?.item?.id) {
|
|
127
127
|
let retries = (item.meta.item._retryCount || 0);
|
|
128
128
|
try {
|
|
129
|
-
const
|
|
130
|
-
if (
|
|
131
|
-
const items = safeJson(wiPath);
|
|
132
|
-
if (items && Array.isArray(items)) {
|
|
133
|
-
const wi = items.find(i => i.id === item.meta.item.id);
|
|
134
|
-
if (wi) retries = wi._retryCount || 0;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
129
|
+
const wi = queries.getWorkItems().find(i => i.id === item.meta.item.id);
|
|
130
|
+
if (wi) retries = wi._retryCount || 0;
|
|
137
131
|
} catch (e) { log('warn', 'read retry count: ' + e.message); }
|
|
138
132
|
const maxRetries = ENGINE_DEFAULTS.maxRetries;
|
|
139
133
|
if (retryableFailure && retries < maxRetries) {
|
|
@@ -178,13 +172,8 @@ function completeDispatch(id, result = DISPATCH_RESULT.SUCCESS, reason = '', res
|
|
|
178
172
|
const config = getConfig();
|
|
179
173
|
const failedId = item.meta.item.id;
|
|
180
174
|
const blockedItems = [];
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
items.filter(w => w.status === WI_STATUS.PENDING && (w.depends_on || []).includes(failedId))
|
|
184
|
-
.forEach(w => blockedItems.push(`- \`${w.id}\` — ${w.title}`));
|
|
185
|
-
}
|
|
186
|
-
const centralItems = safeJson(path.join(MINIONS_DIR, 'work-items.json')) || [];
|
|
187
|
-
centralItems.filter(w => w.status === WI_STATUS.PENDING && (w.depends_on || []).includes(failedId))
|
|
175
|
+
const allItems = queries.getWorkItems(config);
|
|
176
|
+
allItems.filter(w => w.status === WI_STATUS.PENDING && (w.depends_on || []).includes(failedId))
|
|
188
177
|
.forEach(w => blockedItems.push(`- \`${w.id}\` — ${w.title}`));
|
|
189
178
|
|
|
190
179
|
writeInboxAlert(`failed-${failedId}`,
|
package/engine/lifecycle.js
CHANGED
|
@@ -30,20 +30,7 @@ function checkPlanCompletion(meta, config) {
|
|
|
30
30
|
const projects = shared.getProjects(config);
|
|
31
31
|
|
|
32
32
|
// Collect work items from ALL projects + central (PRD items can be in either)
|
|
33
|
-
|
|
34
|
-
for (const p of projects) {
|
|
35
|
-
try {
|
|
36
|
-
const wi = safeJson(shared.projectWorkItemsPath(p)) || [];
|
|
37
|
-
allWorkItems = allWorkItems.concat(wi);
|
|
38
|
-
} catch { /* optional */ }
|
|
39
|
-
}
|
|
40
|
-
// Also check central work-items.json (for no-project setups)
|
|
41
|
-
try {
|
|
42
|
-
const central = safeJson(path.join(MINIONS_DIR, 'work-items.json')) || [];
|
|
43
|
-
for (const w of central) {
|
|
44
|
-
if (!allWorkItems.some(existing => existing.id === w.id)) allWorkItems.push(w);
|
|
45
|
-
}
|
|
46
|
-
} catch { /* optional */ }
|
|
33
|
+
const allWorkItems = queries.getWorkItems(config);
|
|
47
34
|
const planItems = allWorkItems.filter(w => w.sourcePlan === planFile && w.itemType !== 'pr' && w.itemType !== 'verify');
|
|
48
35
|
if (planItems.length === 0) return;
|
|
49
36
|
|
|
@@ -330,19 +317,7 @@ function archivePlan(planFile, plan, projects, config) {
|
|
|
330
317
|
if (plan.feature_branch) branchSlugs.add(shared.sanitizeBranch(plan.feature_branch).toLowerCase());
|
|
331
318
|
|
|
332
319
|
// Collect work items for this plan
|
|
333
|
-
|
|
334
|
-
for (const p of projects) {
|
|
335
|
-
try {
|
|
336
|
-
const wi = safeJson(shared.projectWorkItemsPath(p)) || [];
|
|
337
|
-
allWorkItems = allWorkItems.concat(wi);
|
|
338
|
-
} catch { /* optional */ }
|
|
339
|
-
}
|
|
340
|
-
try {
|
|
341
|
-
const central = safeJson(path.join(MINIONS_DIR, 'work-items.json')) || [];
|
|
342
|
-
for (const w of central) {
|
|
343
|
-
if (!allWorkItems.some(existing => existing.id === w.id)) allWorkItems.push(w);
|
|
344
|
-
}
|
|
345
|
-
} catch { /* optional */ }
|
|
320
|
+
const allWorkItems = queries.getWorkItems(config);
|
|
346
321
|
const planItems = allWorkItems.filter(w => w.sourcePlan === planFile);
|
|
347
322
|
const doneItems = planItems.filter(w => DONE_STATUSES.has(w.status));
|
|
348
323
|
|
package/engine/playbook.js
CHANGED
|
@@ -133,7 +133,7 @@ function resolveTaskContext(item, config) {
|
|
|
133
133
|
try {
|
|
134
134
|
const plans = fs.readdirSync(path.join(MINIONS_DIR, 'plans')).filter(f => f.endsWith('.md') || f.endsWith('.json'));
|
|
135
135
|
// Check work-items to find which plan file this agent created
|
|
136
|
-
const workItems =
|
|
136
|
+
const workItems = queries.getWorkItems();
|
|
137
137
|
const agentPlanItems = workItems.filter(w =>
|
|
138
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 || ''));
|
package/engine/shared.js
CHANGED
|
@@ -738,6 +738,34 @@ function killImmediate(proc) {
|
|
|
738
738
|
}
|
|
739
739
|
}
|
|
740
740
|
|
|
741
|
+
// ─── Work Items & Pull Requests Mutation Helpers ────────────────────────────
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Atomic read-modify-write for work-items JSON files.
|
|
745
|
+
* Wraps mutateJsonFileLocked with defaultValue of [].
|
|
746
|
+
* @param {string} filePath - Path to the work-items JSON file
|
|
747
|
+
* @param {Function} mutator - Receives the array, mutates in place or returns new value
|
|
748
|
+
*/
|
|
749
|
+
function mutateWorkItems(filePath, mutator) {
|
|
750
|
+
return mutateJsonFileLocked(filePath, (data) => {
|
|
751
|
+
if (!Array.isArray(data)) data = [];
|
|
752
|
+
return mutator(data) || data;
|
|
753
|
+
}, { defaultValue: [] });
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* Atomic read-modify-write for pull-requests JSON files.
|
|
758
|
+
* Wraps mutateJsonFileLocked with defaultValue of [].
|
|
759
|
+
* @param {string} filePath - Path to the pull-requests JSON file
|
|
760
|
+
* @param {Function} mutator - Receives the array, mutates in place or returns new value
|
|
761
|
+
*/
|
|
762
|
+
function mutatePullRequests(filePath, mutator) {
|
|
763
|
+
return mutateJsonFileLocked(filePath, (data) => {
|
|
764
|
+
if (!Array.isArray(data)) data = [];
|
|
765
|
+
return mutator(data) || data;
|
|
766
|
+
}, { defaultValue: [] });
|
|
767
|
+
}
|
|
768
|
+
|
|
741
769
|
module.exports = {
|
|
742
770
|
MINIONS_DIR,
|
|
743
771
|
PR_LINKS_PATH,
|
|
@@ -753,6 +781,8 @@ module.exports = {
|
|
|
753
781
|
safeUnlink,
|
|
754
782
|
withFileLock,
|
|
755
783
|
mutateJsonFileLocked,
|
|
784
|
+
mutateWorkItems,
|
|
785
|
+
mutatePullRequests,
|
|
756
786
|
uid,
|
|
757
787
|
uniquePath,
|
|
758
788
|
writeToInbox,
|
package/engine.js
CHANGED
|
@@ -149,16 +149,8 @@ function resolveDependencyBranches(depIds, sourcePlan, project, config) {
|
|
|
149
149
|
const projects = shared.getProjects(config);
|
|
150
150
|
|
|
151
151
|
// Find work items for each dependency plan item
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
const wiPath = shared.projectWorkItemsPath(p);
|
|
155
|
-
const items = safeJson(wiPath) || [];
|
|
156
|
-
for (const wi of items) {
|
|
157
|
-
if (depIds.includes(wi.id)) {
|
|
158
|
-
depWorkItems.push(wi);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
152
|
+
const allItems = queries.getWorkItems(config);
|
|
153
|
+
const depWorkItems = allItems.filter(wi => depIds.includes(wi.id));
|
|
162
154
|
|
|
163
155
|
// Find PR branches for each dependency work item
|
|
164
156
|
for (const p of projects) {
|
|
@@ -837,13 +829,7 @@ function areDependenciesMet(item, config) {
|
|
|
837
829
|
const projects = getProjects(config);
|
|
838
830
|
|
|
839
831
|
// Collect work items from ALL projects (dependencies can be cross-project)
|
|
840
|
-
|
|
841
|
-
for (const p of projects) {
|
|
842
|
-
try {
|
|
843
|
-
const wi = safeJson(projectWorkItemsPath(p)) || [];
|
|
844
|
-
allWorkItems = allWorkItems.concat(wi);
|
|
845
|
-
} catch (e) { log('warn', 'read project work items for deps: ' + e.message); }
|
|
846
|
-
}
|
|
832
|
+
const allWorkItems = queries.getWorkItems(config);
|
|
847
833
|
// PRD item statuses that count as "done" for dep resolution
|
|
848
834
|
const PRD_MET_STATUSES = DONE_STATUSES;
|
|
849
835
|
|
|
@@ -1195,13 +1181,7 @@ function materializePlansAsWorkItems(config) {
|
|
|
1195
1181
|
const statusFilter = ['missing', 'planned'];
|
|
1196
1182
|
// Also materialize in-pr/done items that never got a work item (race with PR status sync)
|
|
1197
1183
|
const allExistingWiIds = new Set();
|
|
1198
|
-
for (const
|
|
1199
|
-
for (const w of (safeJson(projectWorkItemsPath(p)) || [])) {
|
|
1200
|
-
if (w.id) allExistingWiIds.add(w.id);
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
// Also check central work-items.json
|
|
1204
|
-
for (const w of (safeJson(path.join(MINIONS_DIR, 'work-items.json')) || [])) {
|
|
1184
|
+
for (const w of queries.getWorkItems()) {
|
|
1205
1185
|
if (w.id) allExistingWiIds.add(w.id);
|
|
1206
1186
|
}
|
|
1207
1187
|
const items = plan.missing_features.filter(f =>
|
|
@@ -2307,10 +2287,9 @@ function discoverWork(config) {
|
|
|
2307
2287
|
}
|
|
2308
2288
|
|
|
2309
2289
|
// Gate reviews and fixes: do not dispatch until all implement items are complete
|
|
2310
|
-
const hasIncompleteImplements =
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
});
|
|
2290
|
+
const hasIncompleteImplements = queries.getWorkItems(config).some(i =>
|
|
2291
|
+
['queued', 'pending', 'dispatched'].includes(i.status) && (i.type || '').startsWith('implement')
|
|
2292
|
+
);
|
|
2314
2293
|
if (hasIncompleteImplements) {
|
|
2315
2294
|
if (allReviews.length > 0) {
|
|
2316
2295
|
log('info', `Gating ${allReviews.length} reviews — implement items still in progress`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.527",
|
|
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"
|