@yemi33/minions 0.1.1707 → 0.1.1709
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 +11 -5
- package/engine/cli.js +22 -0
- package/engine/copilot-models.json +1 -1
- package/engine/preflight.js +18 -0
- package/engine/shared.js +105 -0
- package/engine.js +28 -2
- package/minions.js +7 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.1709 (2026-05-04)
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
- auto-heal projects with missing workSources at engine boot
|
|
7
|
+
|
|
8
|
+
## 0.1.1708 (2026-05-04)
|
|
9
|
+
|
|
10
|
+
### Fixes
|
|
11
|
+
- surface silent-discovery footgun when projects miss workSources
|
|
12
|
+
|
|
13
|
+
## 0.1.1706 (2026-05-04)
|
|
4
14
|
|
|
5
15
|
### Features
|
|
6
16
|
- preserve CC fan-out dedup scope (#2037)
|
|
7
17
|
|
|
8
|
-
### Other
|
|
9
|
-
- test(llm): add unit tests for resolution helper edge cases and bin cache reset (#2039)
|
|
10
|
-
- test(ado): add unit tests for pure helpers (project label, GUID, metadata apply, stale flag) (#2038)
|
|
11
|
-
|
|
12
18
|
## 0.1.1705 (2026-05-04)
|
|
13
19
|
|
|
14
20
|
### Features
|
package/engine/cli.js
CHANGED
|
@@ -379,6 +379,28 @@ const commands = {
|
|
|
379
379
|
// refactor. No disk write — the on-disk config still carries `ccModel`.
|
|
380
380
|
try { shared.applyLegacyCcModelMigration(config, { logger: e.log }); }
|
|
381
381
|
catch (err) { e.log('warn', `legacy ccModel migration failed: ${err.message}`); }
|
|
382
|
+
|
|
383
|
+
// Auto-heal projects missing workSources (cloned-repo / hand-rolled-config
|
|
384
|
+
// footgun): without this block, discoverFromWorkItems / discoverFromPrs
|
|
385
|
+
// bail silently and the engine looks healthy but never dispatches. The
|
|
386
|
+
// disk-side mutation re-derives heal state from the on-disk copy so we
|
|
387
|
+
// don't clobber a concurrent dashboard write between the in-memory check
|
|
388
|
+
// and the lock acquire. skipWriteIfUnchanged makes the write a no-op when
|
|
389
|
+
// nothing needed healing (e.g. dashboard already fixed it).
|
|
390
|
+
try {
|
|
391
|
+
const heal = shared.backfillProjectWorkSourceDefaults(config);
|
|
392
|
+
if (heal.changed) {
|
|
393
|
+
const configPath = path.join(shared.MINIONS_DIR, 'config.json');
|
|
394
|
+
shared.mutateJsonFileLocked(configPath, (onDisk) => {
|
|
395
|
+
shared.backfillProjectWorkSourceDefaults(onDisk);
|
|
396
|
+
return onDisk;
|
|
397
|
+
}, { defaultValue: {}, skipWriteIfUnchanged: true });
|
|
398
|
+
for (const h of heal.healed) {
|
|
399
|
+
e.log('info', `Auto-healed project "${h.project}" — backfilled missing workSources: ${h.sources.join(', ')}`);
|
|
400
|
+
}
|
|
401
|
+
console.log(` Auto-healed ${heal.healed.length} project(s) with missing workSources defaults — engine will now dispatch their work.`);
|
|
402
|
+
}
|
|
403
|
+
} catch (err) { e.log('warn', `workSources auto-heal failed: ${err.message}`); }
|
|
382
404
|
const interval = config.engine?.tickInterval || shared.ENGINE_DEFAULTS.tickInterval;
|
|
383
405
|
|
|
384
406
|
const { getProjects } = require('./shared');
|
package/engine/preflight.js
CHANGED
|
@@ -226,6 +226,24 @@ function runPreflight(opts = {}) {
|
|
|
226
226
|
results.push({ name: `Runtime config (${w.id})`, ok: 'warn', message: w.message });
|
|
227
227
|
}
|
|
228
228
|
} catch { /* defensive — preflight must never throw */ }
|
|
229
|
+
|
|
230
|
+
// Project workSources warnings — catches the silent-discovery footgun
|
|
231
|
+
// where a project (often added by a hand-rolled config or by cloning the
|
|
232
|
+
// repo without `minions init`) has no workSources block, so engine.js
|
|
233
|
+
// discoverFromWorkItems / discoverFromPrs return [] without logging.
|
|
234
|
+
try {
|
|
235
|
+
const projectWarns = shared.projectWorkSourceWarnings(opts.config, (project) => {
|
|
236
|
+
const wiPath = shared.projectWorkItemsPath(project);
|
|
237
|
+
const prPath = shared.projectPrPath(project);
|
|
238
|
+
const safeJson = shared.safeJson;
|
|
239
|
+
const wi = (() => { try { const a = safeJson(wiPath); return Array.isArray(a) ? a.length : 0; } catch { return 0; } })();
|
|
240
|
+
const pr = (() => { try { const a = safeJson(prPath); return Array.isArray(a) ? a.length : 0; } catch { return 0; } })();
|
|
241
|
+
return { workItems: wi, pullRequests: pr };
|
|
242
|
+
});
|
|
243
|
+
for (const w of projectWarns) {
|
|
244
|
+
results.push({ name: `Project config (${w.id})`, ok: 'warn', message: w.message });
|
|
245
|
+
}
|
|
246
|
+
} catch { /* defensive */ }
|
|
229
247
|
}
|
|
230
248
|
|
|
231
249
|
return { passed: allOk, results };
|
package/engine/shared.js
CHANGED
|
@@ -1120,6 +1120,109 @@ function runtimeConfigWarnings(config, registeredRuntimes) {
|
|
|
1120
1120
|
return warnings;
|
|
1121
1121
|
}
|
|
1122
1122
|
|
|
1123
|
+
/**
|
|
1124
|
+
* Detect projects whose discovery would silently no-op because the
|
|
1125
|
+
* `workSources` block is missing or its sub-flags are disabled. Catches the
|
|
1126
|
+
* common "I cloned the repo and ran it without `minions init`" footgun where
|
|
1127
|
+
* `engine.js` `discoverFromWorkItems` / `discoverFromPrs` bail on
|
|
1128
|
+
* `if (!src?.enabled) return [];` with no log output.
|
|
1129
|
+
*
|
|
1130
|
+
* Pure helper — pass `getDataCounts(project) → { workItems: N, pullRequests: N }`
|
|
1131
|
+
* so the caller controls disk reads (preflight reads files; tests inject counts).
|
|
1132
|
+
* If `getDataCounts` is omitted, every project with a missing/disabled source
|
|
1133
|
+
* is reported (caller decides whether to surface).
|
|
1134
|
+
*
|
|
1135
|
+
* Returns: `{ id, message, project }[]` — `id` is one of:
|
|
1136
|
+
* - `project-worksources-missing` — no workSources block at all
|
|
1137
|
+
* - `project-worksources-disabled` — block exists but a sub-source is disabled
|
|
1138
|
+
*/
|
|
1139
|
+
function projectWorkSourceWarnings(config, getDataCounts) {
|
|
1140
|
+
const warnings = [];
|
|
1141
|
+
if (!config || typeof config !== 'object') return warnings;
|
|
1142
|
+
const projects = Array.isArray(config.projects) ? config.projects : [];
|
|
1143
|
+
const topSources = config.workSources || null;
|
|
1144
|
+
for (const project of projects) {
|
|
1145
|
+
if (!project || typeof project !== 'object') continue;
|
|
1146
|
+
const projSources = project.workSources || null;
|
|
1147
|
+
const counts = (typeof getDataCounts === 'function')
|
|
1148
|
+
? (getDataCounts(project) || {})
|
|
1149
|
+
: { workItems: Infinity, pullRequests: Infinity };
|
|
1150
|
+
const wiCount = Number(counts.workItems) || 0;
|
|
1151
|
+
const prCount = Number(counts.pullRequests) || 0;
|
|
1152
|
+
|
|
1153
|
+
// Case 1: project has no workSources AND no top-level fallback. Discovery
|
|
1154
|
+
// will return [] for everything. This is the cloned-repo footgun.
|
|
1155
|
+
if (!projSources && !topSources) {
|
|
1156
|
+
if (wiCount > 0 || prCount > 0) {
|
|
1157
|
+
warnings.push({
|
|
1158
|
+
id: 'project-worksources-missing',
|
|
1159
|
+
project: project.name,
|
|
1160
|
+
message: `Project "${project.name}" has no workSources block — work-item and PR discovery are silently disabled (${wiCount} work item(s), ${prCount} PR(s) waiting). Run \`minions init\` if you cloned the repo directly, or re-link the project: \`minions add ${project.localPath || project.name}\`.`,
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
continue;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
// Case 2: workSources exists but a sub-source is disabled while data sits
|
|
1167
|
+
// unprocessed. Could be intentional, but worth surfacing.
|
|
1168
|
+
const wiSrc = projSources?.workItems || topSources?.workItems;
|
|
1169
|
+
if ((!wiSrc || wiSrc.enabled === false) && wiCount > 0) {
|
|
1170
|
+
warnings.push({
|
|
1171
|
+
id: 'project-worksources-disabled',
|
|
1172
|
+
project: project.name,
|
|
1173
|
+
message: `Project "${project.name}" has ${wiCount} unprocessed work item(s) but workSources.workItems.enabled is not true — engine will not dispatch them. Toggle in Dashboard → Settings → Project, or set \`workSources.workItems.enabled: true\` in config.json.`,
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
const prSrc = projSources?.pullRequests || topSources?.pullRequests;
|
|
1177
|
+
if ((!prSrc || prSrc.enabled === false) && prCount > 0) {
|
|
1178
|
+
warnings.push({
|
|
1179
|
+
id: 'project-worksources-disabled',
|
|
1180
|
+
project: project.name,
|
|
1181
|
+
message: `Project "${project.name}" has ${prCount} pull-request record(s) but workSources.pullRequests.enabled is not true — engine will not poll or review them. Toggle in Dashboard → Settings → Project, or set \`workSources.pullRequests.enabled: true\` in config.json.`,
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
return warnings;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
/**
|
|
1189
|
+
* Boot-time auto-heal for the cloned-repo / hand-rolled-config footgun: any
|
|
1190
|
+
* project missing a `workSources.workItems` or `workSources.pullRequests`
|
|
1191
|
+
* sub-block gets the dashboard's default backfilled. We only touch *missing*
|
|
1192
|
+
* sub-blocks — an explicit `enabled: false` is treated as user intent and
|
|
1193
|
+
* left alone.
|
|
1194
|
+
*
|
|
1195
|
+
* Pure helper — mutates `config` in place and returns
|
|
1196
|
+
* `{ changed: boolean, healed: Array<{ project, sources }> }` so the caller
|
|
1197
|
+
* can decide whether to persist + log. Returns `{ changed: false, healed: [] }`
|
|
1198
|
+
* for null/empty/malformed input.
|
|
1199
|
+
*/
|
|
1200
|
+
function backfillProjectWorkSourceDefaults(config) {
|
|
1201
|
+
const result = { changed: false, healed: [] };
|
|
1202
|
+
if (!config || typeof config !== 'object') return result;
|
|
1203
|
+
const projects = Array.isArray(config.projects) ? config.projects : [];
|
|
1204
|
+
for (const project of projects) {
|
|
1205
|
+
if (!project || typeof project !== 'object') continue;
|
|
1206
|
+
const filled = [];
|
|
1207
|
+
if (!project.workSources || typeof project.workSources !== 'object') {
|
|
1208
|
+
project.workSources = {};
|
|
1209
|
+
}
|
|
1210
|
+
if (!project.workSources.pullRequests) {
|
|
1211
|
+
project.workSources.pullRequests = { enabled: true, cooldownMinutes: 30 };
|
|
1212
|
+
filled.push('pullRequests');
|
|
1213
|
+
}
|
|
1214
|
+
if (!project.workSources.workItems) {
|
|
1215
|
+
project.workSources.workItems = { enabled: true, cooldownMinutes: 0 };
|
|
1216
|
+
filled.push('workItems');
|
|
1217
|
+
}
|
|
1218
|
+
if (filled.length > 0) {
|
|
1219
|
+
result.changed = true;
|
|
1220
|
+
result.healed.push({ project: project.name, sources: filled });
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
return result;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1123
1226
|
// ─── Status & Type Constants ─────────────────────────────────────────────────
|
|
1124
1227
|
|
|
1125
1228
|
const WI_STATUS = {
|
|
@@ -2515,6 +2618,8 @@ module.exports = {
|
|
|
2515
2618
|
resolveAgentMaxBudget, resolveAgentBareMode,
|
|
2516
2619
|
applyLegacyCcModelMigration, _resetLegacyCcModelMigrationFlag,
|
|
2517
2620
|
runtimeConfigWarnings,
|
|
2621
|
+
projectWorkSourceWarnings,
|
|
2622
|
+
backfillProjectWorkSourceDefaults,
|
|
2518
2623
|
WI_STATUS, DONE_STATUSES, PLAN_TERMINAL_STATUSES, WORK_TYPE, PLAN_STATUS, PRD_ITEM_STATUS, PRD_MATERIALIZABLE, PR_STATUS, PR_POLLABLE_STATUSES, PR_PENDING_REASON, DISPATCH_RESULT, trackReviewMetric, queuePlanToPrd,
|
|
2519
2624
|
WATCH_STATUS, WATCH_TARGET_TYPE, WATCH_CONDITION, WATCH_ABSOLUTE_CONDITIONS,
|
|
2520
2625
|
PIPELINE_STATUS, STAGE_TYPE, MEETING_STATUS, AGENT_STATUS,
|
package/engine.js
CHANGED
|
@@ -2252,12 +2252,35 @@ function ensurePrBranchForDispatch(project, pr, automationType) {
|
|
|
2252
2252
|
}
|
|
2253
2253
|
|
|
2254
2254
|
|
|
2255
|
+
// Tracks per-process which silent-discovery warnings have already been logged
|
|
2256
|
+
// so we don't spam the log every tick. Cleared on process exit (no need to
|
|
2257
|
+
// persist — the warning is for the operator at engine startup/run time).
|
|
2258
|
+
const _warnedSilentDiscovery = new Set();
|
|
2259
|
+
|
|
2260
|
+
function _warnSilentDiscoveryOnce(kind, project, dataPath, config) {
|
|
2261
|
+
const key = `${kind}:${project?.name || project?.localPath || 'default'}`;
|
|
2262
|
+
if (_warnedSilentDiscovery.has(key)) return;
|
|
2263
|
+
let count = 0;
|
|
2264
|
+
try { const arr = safeJson(dataPath); if (Array.isArray(arr)) count = arr.length; } catch {}
|
|
2265
|
+
if (count <= 0) return; // empty file is fine — no warning worth surfacing
|
|
2266
|
+
_warnedSilentDiscovery.add(key);
|
|
2267
|
+
const projName = project?.name || '(unnamed)';
|
|
2268
|
+
const hasBlock = !!(project?.workSources?.[kind] || config?.workSources?.[kind]);
|
|
2269
|
+
const hint = hasBlock
|
|
2270
|
+
? `Toggle in Dashboard → Settings → Project, or set \`workSources.${kind}.enabled: true\` in config.json.`
|
|
2271
|
+
: `Run \`minions doctor\` to inspect, \`minions init\` if you cloned the repo without setup, or re-link the project: \`minions add ${project?.localPath || projName}\`.`;
|
|
2272
|
+
log('warn', `Silent-discovery footgun: project "${projName}" has ${count} record(s) in ${path.basename(dataPath)} but workSources.${kind}.enabled is not true — engine will not pick them up. ${hint}`);
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2255
2275
|
/**
|
|
2256
2276
|
* Scan pull-requests.json for PRs needing review or fixes
|
|
2257
2277
|
*/
|
|
2258
2278
|
async function discoverFromPrs(config, project) {
|
|
2259
2279
|
const src = project?.workSources?.pullRequests || config.workSources?.pullRequests;
|
|
2260
|
-
if (!src?.enabled)
|
|
2280
|
+
if (!src?.enabled) {
|
|
2281
|
+
_warnSilentDiscoveryOnce('pullRequests', project, projectPrPath(project), config);
|
|
2282
|
+
return [];
|
|
2283
|
+
}
|
|
2261
2284
|
|
|
2262
2285
|
const prs = queries.getPrs(project);
|
|
2263
2286
|
const cooldownMs = (src.cooldownMinutes || 30) * 60 * 1000;
|
|
@@ -2757,7 +2780,10 @@ function refreshDeferredWorkItemPrompt(item, config) {
|
|
|
2757
2780
|
|
|
2758
2781
|
function discoverFromWorkItems(config, project) {
|
|
2759
2782
|
const src = project?.workSources?.workItems || config.workSources?.workItems;
|
|
2760
|
-
if (!src?.enabled)
|
|
2783
|
+
if (!src?.enabled) {
|
|
2784
|
+
_warnSilentDiscoveryOnce('workItems', project, projectWorkItemsPath(project), config);
|
|
2785
|
+
return [];
|
|
2786
|
+
}
|
|
2761
2787
|
|
|
2762
2788
|
const root = project?.localPath ? path.resolve(project.localPath) : path.resolve(MINIONS_DIR, '..');
|
|
2763
2789
|
const items = safeJson(projectWorkItemsPath(project)) || [];
|
package/minions.js
CHANGED
|
@@ -173,6 +173,13 @@ function buildProjectEntry({ name, description, localPath, repoHost, repositoryI
|
|
|
173
173
|
repoName: repoName || name,
|
|
174
174
|
mainBranch: mainBranch || 'main',
|
|
175
175
|
prUrlBase: buildPrUrlBase({ repoHost, org, project, repoName }),
|
|
176
|
+
// Discovery defaults must mirror dashboard.js POST /api/projects — without
|
|
177
|
+
// these, discoverFromWorkItems / discoverFromPrs silently no-op (the engine
|
|
178
|
+
// looks healthy but never dispatches anything).
|
|
179
|
+
workSources: {
|
|
180
|
+
pullRequests: { enabled: true, cooldownMinutes: 30 },
|
|
181
|
+
workItems: { enabled: true, cooldownMinutes: 0 },
|
|
182
|
+
},
|
|
176
183
|
};
|
|
177
184
|
}
|
|
178
185
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1709",
|
|
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"
|