@yemi33/minions 0.1.2297 → 0.1.2298
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/docs/completion-reports.md +1 -0
- package/engine/dispatch.js +31 -0
- package/engine/lifecycle.js +42 -17
- package/engine.js +1 -10
- package/package.json +1 -1
|
@@ -81,6 +81,7 @@ Do **not** invent, regenerate, or share the nonce across dispatches — each spa
|
|
|
81
81
|
| `noop` | boolean | Canonical no-op signal. See [No-op semantics](#no-op-semantics). |
|
|
82
82
|
| `noopReason` | string | Human-readable rationale shown when `noop: true`. Falls back to `summary` if absent. |
|
|
83
83
|
| `files_changed` | string \| array | Comma-separated list (or array) of key files changed. |
|
|
84
|
+
| `affected_files` | string[] | Optional array of relative file paths this dispatch touched or plans to touch. Used by the dispatcher to emit a conflict warning (`WI <new-id> may conflict with in-progress <existing-id> on files: [list]`) when a new WI's `affected_files` overlaps with an in-progress WI's `affected_files`. Logging-only — dispatch is never blocked. Stored back onto the work item after completion so re-dispatches can also participate in overlap detection. |
|
|
84
85
|
| `tests` | string | `pass`, `fail`, `skipped`, `N/A`, or a free-form note like `skipped — relying on PR pipeline`. |
|
|
85
86
|
| `pending` | string | Any remaining work, or `none`. |
|
|
86
87
|
| `followups` | array | Optional. PR-comment follow-up work items the agent dispatched via `POST /api/work-items` with `meta.pr_followup` set. Each entry: `{wi_id, title, reason, parent_comment_id}`. See [PR-comment follow-ups](#pr-comment-follow-ups). |
|
package/engine/dispatch.js
CHANGED
|
@@ -166,6 +166,36 @@ function getBranchDispatchLockKey(entry) {
|
|
|
166
166
|
return `${projectKey}:${normalizedBranch}`;
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
// ─── File-Overlap Conflict Detection (P-mqyp000ab028c9d0) ───────────────────
|
|
170
|
+
//
|
|
171
|
+
// Logging-only warning surfaced when a new WI declares affected_files that
|
|
172
|
+
// overlap with files declared by a currently in-progress (active) WI. No
|
|
173
|
+
// blocking — dispatch always proceeds. Goal: surface silent parallel edit
|
|
174
|
+
// races before they become merge conflicts.
|
|
175
|
+
|
|
176
|
+
function _getItemAffectedFiles(dispatchItem) {
|
|
177
|
+
const files = dispatchItem?.meta?.item?.affected_files;
|
|
178
|
+
if (!Array.isArray(files) || files.length === 0) return null;
|
|
179
|
+
const set = new Set(files.filter(f => typeof f === 'string' && f.length > 0));
|
|
180
|
+
return set.size > 0 ? set : null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Called inside addToDispatch (before push) to emit one log line per active
|
|
184
|
+
// dispatch that shares at least one file with the incoming item.
|
|
185
|
+
function _warnFileOverlap(newItem, activeDispatches) {
|
|
186
|
+
const newFiles = _getItemAffectedFiles(newItem);
|
|
187
|
+
if (!newFiles) return;
|
|
188
|
+
const newWiId = newItem.meta?.item?.id || newItem.id;
|
|
189
|
+
for (const existing of activeDispatches) {
|
|
190
|
+
const existingFiles = _getItemAffectedFiles(existing);
|
|
191
|
+
if (!existingFiles) continue;
|
|
192
|
+
const overlap = [...newFiles].filter(f => existingFiles.has(f));
|
|
193
|
+
if (overlap.length === 0) continue;
|
|
194
|
+
const existingWiId = existing.meta?.item?.id || existing.id;
|
|
195
|
+
log('warn', `WI ${newWiId} may conflict with in-progress ${existingWiId} on files: ${overlap.join(', ')}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
169
199
|
function findActivePrOrBranchLock(dispatch, item) {
|
|
170
200
|
const active = dispatch.active || [];
|
|
171
201
|
|
|
@@ -238,6 +268,7 @@ function addToDispatch(item) {
|
|
|
238
268
|
log('info', `Dedup: skipping ${item.id} — ${activeLock.reason} already in ${activeLock.existing.id}`);
|
|
239
269
|
return dispatch;
|
|
240
270
|
}
|
|
271
|
+
_warnFileOverlap(item, dispatch.active || []);
|
|
241
272
|
dispatch.pending.push(item);
|
|
242
273
|
added = true;
|
|
243
274
|
return dispatch;
|
package/engine/lifecycle.js
CHANGED
|
@@ -495,25 +495,25 @@ function checkPlanCompletion(meta, config) {
|
|
|
495
495
|
function archivePlan(planFile, plan, projects, config) {
|
|
496
496
|
const planPath = path.join(PRD_DIR, planFile);
|
|
497
497
|
|
|
498
|
-
//
|
|
499
|
-
|
|
500
|
-
|
|
498
|
+
// Phase 10 — archive the PRD IN PLACE: flag it (archived/status/archivedAt)
|
|
499
|
+
// where it sits, rather than MOVING it to prd/archive/. This matches the
|
|
500
|
+
// canonical dashboard (_archivePrdPostProcess, #533) + watch-action archive
|
|
501
|
+
// paths. The legacy move was the LAST path still physically relocating a PRD,
|
|
502
|
+
// and it reintroduced the live↔archive basename-collision class (footgun #7)
|
|
503
|
+
// that the in-place flag model retired — plus it needed a .backup neutralize
|
|
504
|
+
// that is moot now that prd/*.json never gets a .backup sidecar at all (#570).
|
|
505
|
+
// mutateJsonFileLocked dual-writes the flag into SQL via the chokepoint; the
|
|
506
|
+
// archived-PRD scanner guards (#530) already treat status:'archived' correctly.
|
|
501
507
|
try {
|
|
502
508
|
if (fs.existsSync(planPath)) {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
// plan completion and spawning duplicate verify tasks for already-archived plans.
|
|
512
|
-
// On Windows, the unlink can fail due to file locking; overwrite with archived status
|
|
513
|
-
// as a fallback so a restored backup is inert even if deletion fails.
|
|
514
|
-
const backupCleanup = shared.neutralizeJsonBackupSidecar(planPath);
|
|
515
|
-
if (!backupCleanup.ok) {
|
|
516
|
-
log('warn', `Archive backup cleanup failed for ${planFile}: unlink failed (${backupCleanup.unlinkError}); fallback neutralize failed (${backupCleanup.writeError})`);
|
|
509
|
+
mutateJsonFileLocked(planPath, (data) => {
|
|
510
|
+
if (!data || Array.isArray(data) || typeof data !== 'object') data = {};
|
|
511
|
+
data.status = 'archived';
|
|
512
|
+
data.archived = true;
|
|
513
|
+
data.archivedAt = new Date().toISOString();
|
|
514
|
+
return data;
|
|
515
|
+
}, { defaultValue: {} });
|
|
516
|
+
log('info', `Archived PRD in place: ${planFile} (archived flag set)`);
|
|
517
517
|
}
|
|
518
518
|
} catch (err) {
|
|
519
519
|
log('warn', `Failed to archive PRD ${planFile}: ${err.message}`);
|
|
@@ -777,6 +777,27 @@ function updateWorkItemStatus(meta, status, reason) {
|
|
|
777
777
|
syncPrdItemStatus(itemId, status, meta.item?.sourcePlan);
|
|
778
778
|
}
|
|
779
779
|
|
|
780
|
+
// P-mqyp000ab028c9d0 — persist affected_files from a completion report onto the
|
|
781
|
+
// work item for future file-overlap checks. Runs only when the completion report
|
|
782
|
+
// declares a non-empty string[] for `affected_files`; no-ops otherwise.
|
|
783
|
+
function storeAffectedFilesFromCompletion(meta, structuredCompletion) {
|
|
784
|
+
const raw = structuredCompletion?.affected_files;
|
|
785
|
+
if (!Array.isArray(raw) || raw.length === 0) return;
|
|
786
|
+
const files = raw.filter(f => typeof f === 'string' && f.length > 0);
|
|
787
|
+
if (files.length === 0) return;
|
|
788
|
+
const itemId = meta?.item?.id;
|
|
789
|
+
if (!itemId) return;
|
|
790
|
+
const wiPath = resolveWorkItemPath(meta);
|
|
791
|
+
if (!wiPath) return;
|
|
792
|
+
mutateJsonFileLocked(wiPath, (items) => {
|
|
793
|
+
if (!Array.isArray(items)) return items;
|
|
794
|
+
const target = items.find(i => i.id === itemId);
|
|
795
|
+
if (!target) return items;
|
|
796
|
+
target.affected_files = files;
|
|
797
|
+
return items;
|
|
798
|
+
}, { skipWriteIfUnchanged: true });
|
|
799
|
+
}
|
|
800
|
+
|
|
780
801
|
const _VALID_PRD_STATUSES = new Set([...Object.values(WI_STATUS), 'missing']);
|
|
781
802
|
// (#984) PRD statuses that are stale when the work item is actually done
|
|
782
803
|
const _STALE_PRD_STATUSES = new Set([WI_STATUS.DISPATCHED, WI_STATUS.FAILED, WI_STATUS.PENDING]);
|
|
@@ -5426,6 +5447,10 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
|
|
|
5426
5447
|
meta._noopReason = noopRationale.slice(0, 500);
|
|
5427
5448
|
}
|
|
5428
5449
|
updateWorkItemStatus(meta, WI_STATUS.DONE, '');
|
|
5450
|
+
// P-mqyp000ab028c9d0 — persist affected_files from the completion report
|
|
5451
|
+
// onto the work item so future dispatches of the same WI can participate in
|
|
5452
|
+
// the file-overlap conflict detection in dispatch.js#_warnFileOverlap.
|
|
5453
|
+
try { storeAffectedFilesFromCompletion(meta, structuredCompletion); } catch (err) { log('warn', `storeAffectedFilesFromCompletion: ${err.message}`); }
|
|
5429
5454
|
// W-mqtplpk6001oe6d5 — back-stamp workItemId onto the PRD item now that WI is done.
|
|
5430
5455
|
if (meta.item.sourcePlan) {
|
|
5431
5456
|
try { stampPrdItemWorkItemId(meta.item.id, meta.item.sourcePlan); } catch (err) { log('warn', `stampPrdItemWorkItemId: ${err.message}`); }
|
package/engine.js
CHANGED
|
@@ -5762,15 +5762,6 @@ function safePrdProjectSlug(projectName) {
|
|
|
5762
5762
|
return slug || 'project';
|
|
5763
5763
|
}
|
|
5764
5764
|
|
|
5765
|
-
function safePrdFilenameForProject(projectName, suffix) {
|
|
5766
|
-
const fileName = `${safePrdProjectSlug(projectName)}-${suffix}.json`;
|
|
5767
|
-
const resolved = shared.sanitizePath(fileName, PRD_DIR);
|
|
5768
|
-
if (path.dirname(resolved) !== path.resolve(PRD_DIR)) {
|
|
5769
|
-
throw new Error('invalid PRD filename: nested paths are not allowed');
|
|
5770
|
-
}
|
|
5771
|
-
return path.basename(resolved);
|
|
5772
|
-
}
|
|
5773
|
-
|
|
5774
5765
|
/**
|
|
5775
5766
|
* Atomically reserve a unique PRD filename in `prdDir` (P-9b7e5d3c).
|
|
5776
5767
|
*
|
|
@@ -10570,7 +10561,7 @@ module.exports = {
|
|
|
10570
10561
|
|
|
10571
10562
|
// Shared helpers (used by lifecycle.js and tests)
|
|
10572
10563
|
reconcileItemsWithPrs, detectDependencyCycles,
|
|
10573
|
-
safePrdProjectSlug,
|
|
10564
|
+
safePrdProjectSlug, // exported for testing (W-mqyn7joo0004079f)
|
|
10574
10565
|
isSoftFixDispatch, // exported for testing (W-mqyn7joo0004079f)
|
|
10575
10566
|
areDependenciesMet, // exported for testing (P-bf04-decompose-zero-children)
|
|
10576
10567
|
parseConflictFiles, pruneAncestorDeps, preflightMergeSimulation, // exported for testing
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2298",
|
|
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"
|