@yemi33/minions 0.1.2292 → 0.1.2294
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/dashboard/js/settings.js +0 -45
- package/dashboard.js +3 -32
- package/docs/deprecated.json +6 -0
- package/docs/live-checkout-mode.md +0 -54
- package/docs/named-agents.md +1 -1
- package/docs/workspace-manifests.md +3 -4
- package/engine/cli.js +13 -0
- package/engine/features.js +14 -11
- package/engine/github.js +3 -0
- package/engine/lifecycle.js +86 -2
- package/engine/playbook.js +9 -4
- package/engine/prd-store.js +113 -7
- package/engine/queries.js +93 -72
- package/engine/shared.js +152 -30
- package/engine/work-items-store.js +21 -0
- package/engine.js +116 -151
- package/package.json +1 -1
- package/playbooks/fix.md +9 -0
|
@@ -273,6 +273,18 @@ function _fileContentHash(filePath) {
|
|
|
273
273
|
|
|
274
274
|
function _hydrateScopeFromJson(db, scope) {
|
|
275
275
|
const jsonItems = _readJsonArrayFallback(scope);
|
|
276
|
+
// Preserve the SQL-only prd_item_id FK across the delete+reinsert. It is owned
|
|
277
|
+
// by the PRD dual-write (prd-store.js#_upsertPrd), NOT represented in the JSON
|
|
278
|
+
// mirror, so a naive rebuild-from-JSON silently zeroes it for the whole scope —
|
|
279
|
+
// regressing the WI↔PRD link every time the JSON diverges (the count would
|
|
280
|
+
// oscillate as the PRD mirror re-stamps and the next hydrate wipes it again).
|
|
281
|
+
// Snapshot before the DELETE, restore onto the rows that survive the rebuild.
|
|
282
|
+
const fkById = new Map();
|
|
283
|
+
try {
|
|
284
|
+
for (const r of db.prepare('SELECT id, prd_item_id FROM work_items WHERE scope = ? AND prd_item_id IS NOT NULL').all(scope)) {
|
|
285
|
+
fkById.set(r.id, r.prd_item_id);
|
|
286
|
+
}
|
|
287
|
+
} catch { /* column absent on a pre-v15 schema — nothing to preserve */ }
|
|
276
288
|
// DELETE before re-insert: callers that wrote a smaller JSON file
|
|
277
289
|
// (test cleanup() removing items) must end up with a smaller SQL state.
|
|
278
290
|
db.prepare('DELETE FROM work_items WHERE scope = ?').run(scope);
|
|
@@ -298,6 +310,14 @@ function _hydrateScopeFromJson(db, scope) {
|
|
|
298
310
|
now,
|
|
299
311
|
);
|
|
300
312
|
}
|
|
313
|
+
// Restore the FK for rows that still exist after the rebuild (a WI dropped
|
|
314
|
+
// from the JSON is gone, and its link goes with it — correct).
|
|
315
|
+
if (fkById.size) {
|
|
316
|
+
const restore = db.prepare('UPDATE work_items SET prd_item_id = ? WHERE scope = ? AND id = ?');
|
|
317
|
+
for (const [id, fk] of fkById) {
|
|
318
|
+
try { restore.run(fk, scope, id); } catch { /* best effort */ }
|
|
319
|
+
}
|
|
320
|
+
}
|
|
301
321
|
}
|
|
302
322
|
|
|
303
323
|
function applyWorkItemsMutation(scope, mutator) {
|
|
@@ -375,4 +395,5 @@ module.exports = {
|
|
|
375
395
|
dropScope,
|
|
376
396
|
_filePathForScope,
|
|
377
397
|
_mirrorJsonFromSql,
|
|
398
|
+
_hydrateScopeFromJson, // exported for testing (prd_item_id-preservation)
|
|
378
399
|
};
|
package/engine.js
CHANGED
|
@@ -168,7 +168,7 @@ const { runPostCompletionHooks, updateWorkItemStatus, syncPrdItemStatus, reconci
|
|
|
168
168
|
syncPrsFromOutput, updatePrAfterReview, updatePrAfterFix, checkForLearnings, extractSkillsFromOutput,
|
|
169
169
|
updateAgentHistory, updateMetrics, createReviewFeedbackForAuthor, parseAgentOutput, syncPrdFromPrs, persistVerifyPrsToPrd,
|
|
170
170
|
isItemCompleted, classifyFailure: classifyFailureFallback, diagnoseEmptyOutput, processPendingRebases, resolveWorkItemPath,
|
|
171
|
-
mergeArtifactNotes, promoteCompletionArtifacts, pruneScopeMismatchDuplicatePrs } = require('./engine/lifecycle');
|
|
171
|
+
mergeArtifactNotes, promoteCompletionArtifacts, pruneScopeMismatchDuplicatePrs, collapseAllDuplicatePrRecords } = require('./engine/lifecycle');
|
|
172
172
|
|
|
173
173
|
// ─── Diagnostics: memory + event-loop + GC sampler (P-a1b2c3d4 / P-b2c3d4e5) ─
|
|
174
174
|
|
|
@@ -196,9 +196,15 @@ function cleanupTempAgent(agentId) {
|
|
|
196
196
|
try {
|
|
197
197
|
const agentDir = path.join(AGENTS_DIR, agentId);
|
|
198
198
|
// Keep output archive but remove temp agent directory (live-output.log etc.)
|
|
199
|
-
|
|
199
|
+
// Use _retryFsOp so Windows EBUSY/EPERM (AV / file indexer lock) is retried.
|
|
200
|
+
shared._retryFsOp(
|
|
201
|
+
() => fs.rmSync(agentDir, { recursive: true, force: true }),
|
|
202
|
+
`cleanupTempAgent(${agentId})`
|
|
203
|
+
);
|
|
200
204
|
log('info', `Temp agent ${agentId} cleaned up`);
|
|
201
|
-
} catch {
|
|
205
|
+
} catch (err) {
|
|
206
|
+
log('warn', `cleanupTempAgent: fs.rmSync failed for ${agentId}: ${err && err.message}`);
|
|
207
|
+
}
|
|
202
208
|
}
|
|
203
209
|
|
|
204
210
|
// Per-tick cache of refs that failed to fetch — avoids repeating 30s ETIMEDOUT for same missing ref
|
|
@@ -974,7 +980,17 @@ async function runWorktreeAdd(rootDir, worktreePath, addArgs, gitOpts, worktreeC
|
|
|
974
980
|
await shared.shellSafeGit(['worktree', 'remove', '--force', worktreePath], { ...gitOpts, cwd: rootDir, timeout: 30000 });
|
|
975
981
|
} catch (rmErr) {
|
|
976
982
|
log('warn', `runWorktreeAdd: worktree remove after partial checkout failed (${rmErr.message}) — falling back to fs-level cleanup`);
|
|
977
|
-
try {
|
|
983
|
+
try {
|
|
984
|
+
// W-mqw3g5be000s934f: use _retryFsOp so transient Windows EBUSY/EPERM from
|
|
985
|
+
// antivirus or GVFS virtual-filesystem handles do not silently abandon cleanup.
|
|
986
|
+
shared._retryFsOp(
|
|
987
|
+
() => fs.rmSync(worktreePath, { recursive: true, force: true }),
|
|
988
|
+
`runWorktreeAdd: fs-level cleanup ${worktreePath}`,
|
|
989
|
+
{ attempts: 3, baseMs: 100 },
|
|
990
|
+
);
|
|
991
|
+
} catch (fsErr) {
|
|
992
|
+
log('warn', `runWorktreeAdd: fs-level cleanup failed after retries (${fsErr.message}) — worktree directory may be leaked: ${worktreePath}`);
|
|
993
|
+
}
|
|
978
994
|
try { await shared.shellSafeGit(['worktree', 'prune'], { ...gitOpts, cwd: rootDir, timeout: 15000 }); } catch {}
|
|
979
995
|
}
|
|
980
996
|
const incompleteErr = new Error(`GVFS incomplete checkout at ${worktreePath}: worktree add exited cleanly but no source files were checked out (only .git was written). Retriable.`);
|
|
@@ -2933,10 +2949,10 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
2933
2949
|
try { await shared.shellSafeGit(['worktree', 'prune'], { ..._gitOpts, cwd: rootDir, timeout: 15000 }); } catch { /* optional */ }
|
|
2934
2950
|
removeStaleIndexLock(rootDir);
|
|
2935
2951
|
// Clean up partial worktree directory from the failed -b
|
|
2936
|
-
// attempt.
|
|
2937
|
-
//
|
|
2938
|
-
//
|
|
2939
|
-
//
|
|
2952
|
+
// attempt. This husk pre-dates `git worktree add`, so git
|
|
2953
|
+
// does not yet own it and shared.removeWorktree's
|
|
2954
|
+
// `git worktree remove --force` would no-op on it
|
|
2955
|
+
// (P-b3d9a162). Two guards before the delete:
|
|
2940
2956
|
// 1. isWorktreePathLive (fail-open): skip if another
|
|
2941
2957
|
// dispatch raced onto this path — it returns true on a
|
|
2942
2958
|
// SQL outage, so we leak the husk rather than nuke a
|
|
@@ -2945,6 +2961,7 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
2945
2961
|
// carries its own `.git` DIRECTORY (a mis-pointed
|
|
2946
2962
|
// worktreePath onto a real repo) — a linked worktree's
|
|
2947
2963
|
// `.git` is a FILE, so this never blocks a real husk.
|
|
2964
|
+
// W-mqw3i9vp000tcfaf: use shared._retryFsOp so Windows EBUSY/EPERM doesn't brick the branch.
|
|
2948
2965
|
try {
|
|
2949
2966
|
const _huskGit = path.join(worktreePath, '.git');
|
|
2950
2967
|
let _huskIsRealRepo = false;
|
|
@@ -2952,9 +2969,14 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
2952
2969
|
if (fs.existsSync(worktreePath)
|
|
2953
2970
|
&& !_huskIsRealRepo
|
|
2954
2971
|
&& !shared.isWorktreePathLive(worktreePath, { excludeDispatchId: id })) {
|
|
2955
|
-
|
|
2972
|
+
shared._retryFsOp(
|
|
2973
|
+
() => fs.rmSync(worktreePath, { recursive: true, force: true }),
|
|
2974
|
+
`husk cleanup before -b retry ${branchName}`
|
|
2975
|
+
);
|
|
2956
2976
|
}
|
|
2957
|
-
} catch {
|
|
2977
|
+
} catch (rmErr) {
|
|
2978
|
+
log('warn', `spawnAgent: husk cleanup failed before -b retry for ${branchName}: ${rmErr.message?.split('\n')[0]}`);
|
|
2979
|
+
}
|
|
2958
2980
|
try {
|
|
2959
2981
|
await runWorktreeAdd(rootDir, worktreePath, ['-b', branchName, _freshCreateBase], _worktreeGitOpts, 0);
|
|
2960
2982
|
} catch (e1b) {
|
|
@@ -5863,54 +5885,6 @@ function materializePlansAsWorkItems(config) {
|
|
|
5863
5885
|
return mutator(current) || current;
|
|
5864
5886
|
}, { defaultValue: fallback || {}, ...options });
|
|
5865
5887
|
};
|
|
5866
|
-
const declaredProjectPrdFilename = (fileName, projectName) => {
|
|
5867
|
-
const match = String(fileName || '').match(/-(\d{4}-\d{2}-\d{2}(?:-\d+)?)\.json$/);
|
|
5868
|
-
if (!match) return null;
|
|
5869
|
-
return safePrdFilenameForProject(projectName, match[1]);
|
|
5870
|
-
};
|
|
5871
|
-
const migratePrdFilenameReferences = (oldFileName, newFileName) => {
|
|
5872
|
-
if (!oldFileName || !newFileName || oldFileName === newFileName) return 0;
|
|
5873
|
-
let migrated = 0;
|
|
5874
|
-
const wiPaths = new Set([path.join(MINIONS_DIR, 'work-items.json')]);
|
|
5875
|
-
for (const project of getProjects(config)) wiPaths.add(projectWorkItemsPath(project));
|
|
5876
|
-
for (const wiPath of wiPaths) {
|
|
5877
|
-
if (!fs.existsSync(wiPath)) continue;
|
|
5878
|
-
mutateWorkItems(wiPath, (items) => {
|
|
5879
|
-
for (const wi of items) {
|
|
5880
|
-
if (!wi || typeof wi !== 'object') continue;
|
|
5881
|
-
if (wi.sourcePlan === oldFileName) {
|
|
5882
|
-
wi.sourcePlan = newFileName;
|
|
5883
|
-
migrated++;
|
|
5884
|
-
}
|
|
5885
|
-
if (wi._artifacts?.sourcePlan === oldFileName) {
|
|
5886
|
-
wi._artifacts.sourcePlan = newFileName;
|
|
5887
|
-
migrated++;
|
|
5888
|
-
}
|
|
5889
|
-
}
|
|
5890
|
-
return items;
|
|
5891
|
-
});
|
|
5892
|
-
}
|
|
5893
|
-
if (fs.existsSync(DISPATCH_PATH)) {
|
|
5894
|
-
mutateDispatch((dispatch) => {
|
|
5895
|
-
for (const queue of ['pending', 'active', 'completed']) {
|
|
5896
|
-
for (const entry of dispatch[queue] || []) {
|
|
5897
|
-
const metaItem = entry?.meta?.item;
|
|
5898
|
-
if (!metaItem || typeof metaItem !== 'object') continue;
|
|
5899
|
-
if (metaItem.sourcePlan === oldFileName) {
|
|
5900
|
-
metaItem.sourcePlan = newFileName;
|
|
5901
|
-
migrated++;
|
|
5902
|
-
}
|
|
5903
|
-
if (metaItem._prdFilename === oldFileName) {
|
|
5904
|
-
metaItem._prdFilename = newFileName;
|
|
5905
|
-
migrated++;
|
|
5906
|
-
}
|
|
5907
|
-
}
|
|
5908
|
-
}
|
|
5909
|
-
return dispatch;
|
|
5910
|
-
});
|
|
5911
|
-
}
|
|
5912
|
-
return migrated;
|
|
5913
|
-
};
|
|
5914
5888
|
const enforceDeclaredPlanProject = (fileName, currentPlan) => {
|
|
5915
5889
|
if (!currentPlan?.source_plan) return { fileName, plan: currentPlan };
|
|
5916
5890
|
let declaredProject = '';
|
|
@@ -5938,95 +5912,18 @@ function materializePlansAsWorkItems(config) {
|
|
|
5938
5912
|
return planData;
|
|
5939
5913
|
}, { skipWriteIfUnchanged: true });
|
|
5940
5914
|
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
// minions-opg-2026-06-10 incident).
|
|
5954
|
-
const archiveBasenames = new Set(
|
|
5955
|
-
safeReadDir(path.join(PRD_DIR, 'archive'))
|
|
5956
|
-
.filter(f => f.endsWith('.json'))
|
|
5957
|
-
.map(f => f.toLowerCase())
|
|
5958
|
-
);
|
|
5959
|
-
if (archiveBasenames.has(desiredFileName.toLowerCase())) {
|
|
5960
|
-
log('warn', `Plan project enforcement: skipping rename of ${fileName} to ${desiredFileName} — would collide with archived PRD`);
|
|
5961
|
-
} else {
|
|
5962
|
-
const toPath = shared.uniquePath(desiredPath);
|
|
5963
|
-
const toBasename = path.basename(toPath);
|
|
5964
|
-
if (archiveBasenames.has(toBasename.toLowerCase())) {
|
|
5965
|
-
// uniquePath bumped past a live conflict (e.g. <name>-2.json) but
|
|
5966
|
-
// the bumped name itself is owned by an archived PRD — same risk.
|
|
5967
|
-
log('warn', `Plan project enforcement: skipping rename of ${fileName} to ${toBasename} — would collide with archived PRD`);
|
|
5968
|
-
} else {
|
|
5969
|
-
// W-mqfevwrm — Serialize the rename behind file locks on BOTH the
|
|
5970
|
-
// source and destination paths so a concurrent `mutateJsonFileLocked`
|
|
5971
|
-
// on either name cannot interleave (creating a divergent ghost PRD
|
|
5972
|
-
// or losing data). Move the `.backup` sidecar along with the `.json`
|
|
5973
|
-
// so the canonical name retains its restore-from-backup safety net
|
|
5974
|
-
// — without this, deleting the renamed `.json` (concurrent sweep,
|
|
5975
|
-
// plan-completion purge, etc.) leaves no `.backup` under the new
|
|
5976
|
-
// name and the PRD is permanently lost (live incident 2026-06-15:
|
|
5977
|
-
// `minions-opg-2026-06-10.json` gone, only the OLD-name `.backup`
|
|
5978
|
-
// survived, all 9 WIs orphaned). The OLD-name `.backup` is removed
|
|
5979
|
-
// so it cannot resurrect a ghost PRD on a future read of the OLD
|
|
5980
|
-
// path (W-mouptdh1000h9f39-style landmine).
|
|
5981
|
-
try {
|
|
5982
|
-
withFileLock(`${fromPath}.lock`, () => {
|
|
5983
|
-
withFileLock(`${toPath}.lock`, () => {
|
|
5984
|
-
// Re-verify under both locks — another tick may have raced
|
|
5985
|
-
// ahead and either deleted the source or claimed the dest.
|
|
5986
|
-
if (!fs.existsSync(fromPath)) {
|
|
5987
|
-
log('warn', `Plan project enforcement: source ${fileName} disappeared before rename (raced)`);
|
|
5988
|
-
return;
|
|
5989
|
-
}
|
|
5990
|
-
if (fs.existsSync(toPath)) {
|
|
5991
|
-
log('warn', `Plan project enforcement: destination ${toBasename} appeared before rename (raced) — skipping`);
|
|
5992
|
-
return;
|
|
5993
|
-
}
|
|
5994
|
-
fs.renameSync(fromPath, toPath);
|
|
5995
|
-
// Move the `.backup` sidecar along with the primary `.json`
|
|
5996
|
-
// so safeJson's restore-from-backup path stays viable under
|
|
5997
|
-
// the new canonical name. Both ops are best-effort: missing
|
|
5998
|
-
// `.backup` is normal (nothing to move) and an unlink failure
|
|
5999
|
-
// on the OLD-name `.backup` is logged but non-fatal.
|
|
6000
|
-
const fromBackup = `${fromPath}.backup`;
|
|
6001
|
-
const toBackup = `${toPath}.backup`;
|
|
6002
|
-
if (fs.existsSync(fromBackup)) {
|
|
6003
|
-
try {
|
|
6004
|
-
fs.renameSync(fromBackup, toBackup);
|
|
6005
|
-
} catch (be) {
|
|
6006
|
-
// Fall back to copy + unlink if rename across the same dir
|
|
6007
|
-
// surfaces a Windows EPERM (AV/Search Indexer hold).
|
|
6008
|
-
try {
|
|
6009
|
-
fs.copyFileSync(fromBackup, toBackup);
|
|
6010
|
-
try { fs.unlinkSync(fromBackup); } catch { /* best-effort */ }
|
|
6011
|
-
} catch (ce) {
|
|
6012
|
-
log('warn', `Plan project enforcement: could not move .backup sidecar ${fileName}.backup → ${toBasename}.backup: ${be.message} / ${ce.message}`);
|
|
6013
|
-
}
|
|
6014
|
-
}
|
|
6015
|
-
}
|
|
6016
|
-
nextFileName = toBasename;
|
|
6017
|
-
const migrated = migratePrdFilenameReferences(fileName, nextFileName);
|
|
6018
|
-
if (migrated > 0) log('info', `Plan project enforcement: migrated ${migrated} PRD reference(s) from ${fileName} to ${nextFileName}`);
|
|
6019
|
-
changed = true;
|
|
6020
|
-
});
|
|
6021
|
-
});
|
|
6022
|
-
} catch (e) {
|
|
6023
|
-
log('warn', `Plan project enforcement: could not rename ${fileName} to ${toBasename}: ${e.message}`);
|
|
6024
|
-
}
|
|
6025
|
-
}
|
|
6026
|
-
}
|
|
6027
|
-
}
|
|
6028
|
-
if (changed) log('info', `Plan project enforcement: preserved declared project "${declaredProject}" for ${nextFileName}`);
|
|
6029
|
-
return { fileName: nextFileName, plan: normalizedPlan };
|
|
5915
|
+
// Phase 10 step 4.3 — the PRD filename is NO LONGER renamed to the canonical
|
|
5916
|
+
// "<project>-<date>.json". That rename existed only to keep the basename
|
|
5917
|
+
// identity (sourcePlan === <prd filename>) and the live↔archive collision
|
|
5918
|
+
// guard (footgun #7) working. With the surrogate-key model — archive is an
|
|
5919
|
+
// in-place flag (no move) and the WI↔PRD-item join is the stable SQL FK
|
|
5920
|
+
// (work_items.prd_item_id), not the basename — the rename is dead weight and
|
|
5921
|
+
// its own machinery (the `.backup` sidecar move, migratePrdFilenameReferences,
|
|
5922
|
+
// the rename-race window that prd-rename-race.test.js guarded) is retired.
|
|
5923
|
+
// The SEMANTIC enforcement above (declared project on the PRD + its features)
|
|
5924
|
+
// still runs; only the file-move is gone.
|
|
5925
|
+
if (changed) log('info', `Plan project enforcement: preserved declared project "${declaredProject}" for ${fileName}`);
|
|
5926
|
+
return { fileName, plan: normalizedPlan };
|
|
6030
5927
|
};
|
|
6031
5928
|
|
|
6032
5929
|
// Enforce: PRDs must be .json — auto-rename .md files that contain valid PRD JSON
|
|
@@ -6420,6 +6317,19 @@ function materializePlansAsWorkItems(config) {
|
|
|
6420
6317
|
log('info', `Re-opened work item ${itemId} in ${rProjName} (cross-project, PRD item set to ${rItem.status})`);
|
|
6421
6318
|
}
|
|
6422
6319
|
|
|
6320
|
+
// Stamp workItemId onto each newly created PRD item immediately after WI creation (P-b3c2d4e5).
|
|
6321
|
+
// Uses mutatePrdLocked so the write is protected by a file lock — not a bare safeWrite.
|
|
6322
|
+
if (newlyCreatedIds.size > 0) {
|
|
6323
|
+
try {
|
|
6324
|
+
mutatePrdLocked(file, plan, (current) => {
|
|
6325
|
+
for (const feature of (current.missing_features || [])) {
|
|
6326
|
+
if (newlyCreatedIds.has(feature.id)) feature.workItemId = feature.id;
|
|
6327
|
+
}
|
|
6328
|
+
return current;
|
|
6329
|
+
}, { skipWriteIfUnchanged: true });
|
|
6330
|
+
} catch (e) { log('warn', `workItemId stamp failed for ${file}: ${e.message}`); }
|
|
6331
|
+
}
|
|
6332
|
+
|
|
6423
6333
|
totalCreated += created;
|
|
6424
6334
|
}
|
|
6425
6335
|
|
|
@@ -8314,6 +8224,45 @@ function normalizeAc(ac) {
|
|
|
8314
8224
|
* @param {string} [options.workType] - Work type (used for ASK-specific vars)
|
|
8315
8225
|
* @returns {{ needsReview: boolean, checkpointCount: number|null }} checkpoint side-effect info
|
|
8316
8226
|
*/
|
|
8227
|
+
/**
|
|
8228
|
+
* SHERLOC (P-mqyp0008v022w3x4): detect prior completed explore WI that
|
|
8229
|
+
* references this fix WI via its `references` array. Returns the explore
|
|
8230
|
+
* WI's `resultSummary` string, or '' when none found.
|
|
8231
|
+
*
|
|
8232
|
+
* Lookup: scan all work items for type=explore, status=done, non-empty
|
|
8233
|
+
* resultSummary, and a reference whose URL includes the fix WI's ID.
|
|
8234
|
+
* The explore WI is expected to have been dispatched as a pre-localization
|
|
8235
|
+
* step and to carry a back-reference to the fix WI it was researching.
|
|
8236
|
+
*
|
|
8237
|
+
* @param {object} item - the fix work item being dispatched
|
|
8238
|
+
* @param {object} config - engine config (passed to getWorkItems)
|
|
8239
|
+
* @returns {string}
|
|
8240
|
+
*/
|
|
8241
|
+
function resolvePriorExploreContext(item, config) {
|
|
8242
|
+
if (!item || !item.id) return '';
|
|
8243
|
+
try {
|
|
8244
|
+
const allItems = queries.getWorkItems(config, { enrich: false });
|
|
8245
|
+
const candidates = allItems.filter(wi =>
|
|
8246
|
+
wi.type === WORK_TYPE.EXPLORE &&
|
|
8247
|
+
DONE_STATUSES.has(wi.status) &&
|
|
8248
|
+
wi.resultSummary &&
|
|
8249
|
+
Array.isArray(wi.references) &&
|
|
8250
|
+
wi.references.some(r => r && typeof r.url === 'string' && r.url.includes(item.id))
|
|
8251
|
+
);
|
|
8252
|
+
if (candidates.length === 0) return '';
|
|
8253
|
+
// Prefer most recently completed; fall back to most recently created.
|
|
8254
|
+
candidates.sort((a, b) => {
|
|
8255
|
+
const ta = a.completedAt || a.created || '';
|
|
8256
|
+
const tb = b.completedAt || b.created || '';
|
|
8257
|
+
return tb.localeCompare(ta);
|
|
8258
|
+
});
|
|
8259
|
+
return candidates[0].resultSummary || '';
|
|
8260
|
+
} catch (e) {
|
|
8261
|
+
log('warn', `resolvePriorExploreContext for ${item.id}: ${e.message}`);
|
|
8262
|
+
return '';
|
|
8263
|
+
}
|
|
8264
|
+
}
|
|
8265
|
+
|
|
8317
8266
|
function buildWorkItemDispatchVars(item, vars, config, options = {}) {
|
|
8318
8267
|
const { worktreePath, includeNotes = true, workType } = options;
|
|
8319
8268
|
|
|
@@ -8370,6 +8319,13 @@ function buildWorkItemDispatchVars(item, vars, config, options = {}) {
|
|
|
8370
8319
|
vars.notes_content = 'See the **Team Notes** section above for the latest shared team context.';
|
|
8371
8320
|
}
|
|
8372
8321
|
|
|
8322
|
+
// SHERLOC (P-mqyp0008v022w3x4): inject prior explore context for fix dispatches.
|
|
8323
|
+
// When a completed explore WI has a reference to this fix WI, surface its
|
|
8324
|
+
// resultSummary so the fix agent starts with pre-localized fault context.
|
|
8325
|
+
if (workType === WORK_TYPE.FIX) {
|
|
8326
|
+
vars.prior_explore_context = resolvePriorExploreContext(item, config);
|
|
8327
|
+
}
|
|
8328
|
+
|
|
8373
8329
|
// Resolve implicit context references (e.g., "ripley's plan", "the latest plan")
|
|
8374
8330
|
const resolvedCtx = resolveTaskContext(item, config);
|
|
8375
8331
|
if (resolvedCtx.additionalContext) {
|
|
@@ -9947,6 +9903,15 @@ async function tickInner() {
|
|
|
9947
9903
|
log('warn', `[pull-requests] scope-mismatch sweep error: ${err?.message || err}`);
|
|
9948
9904
|
}
|
|
9949
9905
|
|
|
9906
|
+
// P-e9f0a2b4 — Collapse prNumber duplicates (different canonical IDs, same
|
|
9907
|
+
// prNumber) that accumulated before upsert-time dedup was added. One mutation
|
|
9908
|
+
// per affected scope per reconcile tick; normally a no-op after first run.
|
|
9909
|
+
try {
|
|
9910
|
+
collapseAllDuplicatePrRecords(config);
|
|
9911
|
+
} catch (err) {
|
|
9912
|
+
log('warn', `[pull-requests] collapse-duplicates sweep error: ${err?.message || err}`);
|
|
9913
|
+
}
|
|
9914
|
+
|
|
9950
9915
|
// P-6b3a9f78 — Shared-branch PR reconciler (recovery sweep). Backfills the
|
|
9951
9916
|
// engine PR store for shared-branch plans whose aggregate remote PR exists
|
|
9952
9917
|
// but is untracked / under-linked, so the dashboard renders the single
|
|
@@ -10403,15 +10368,12 @@ async function tickInner() {
|
|
|
10403
10368
|
} else {
|
|
10404
10369
|
// P-a3f9b205: surface the per-project live-mode gate. Order matters:
|
|
10405
10370
|
// max_concurrency / agent_busy / branch_locked are all more specific
|
|
10406
|
-
// and win when both apply.
|
|
10407
|
-
// checkout mode so worktree-mode items on hybrid projects are not
|
|
10408
|
-
// falsely annotated live_checkout_busy (mirrors seeding guard above).
|
|
10371
|
+
// and win when both apply.
|
|
10409
10372
|
const itemProjName = item.project || item.meta?.project?.name || null;
|
|
10410
10373
|
if (
|
|
10411
10374
|
itemProjName
|
|
10412
10375
|
&& !READ_ONLY_ROOT_TASK_TYPES.has(item.type)
|
|
10413
10376
|
&& postLiveProjectsInUse.has(itemProjName)
|
|
10414
|
-
&& shared.resolveCheckoutMode(shared.findProjectByName(shared.getProjects(config), itemProjName), item.type) === 'live'
|
|
10415
10377
|
) {
|
|
10416
10378
|
reason = 'live_checkout_busy';
|
|
10417
10379
|
}
|
|
@@ -10607,6 +10569,8 @@ module.exports = {
|
|
|
10607
10569
|
|
|
10608
10570
|
// Shared helpers (used by lifecycle.js and tests)
|
|
10609
10571
|
reconcileItemsWithPrs, detectDependencyCycles,
|
|
10572
|
+
safePrdProjectSlug, safePrdFilenameForProject, // exported for testing (W-mqyn7joo0004079f)
|
|
10573
|
+
isSoftFixDispatch, // exported for testing (W-mqyn7joo0004079f)
|
|
10610
10574
|
areDependenciesMet, // exported for testing (P-bf04-decompose-zero-children)
|
|
10611
10575
|
parseConflictFiles, pruneAncestorDeps, preflightMergeSimulation, // exported for testing
|
|
10612
10576
|
resolveDependencyBranches, buildCrossRepoDepsSection, // exported for testing (P-faea3206)
|
|
@@ -10627,6 +10591,7 @@ module.exports = {
|
|
|
10627
10591
|
|
|
10628
10592
|
// Playbooks
|
|
10629
10593
|
renderPlaybook, validatePlaybookVars, PLAYBOOK_REQUIRED_VARS, buildWorkItemDispatchVars,
|
|
10594
|
+
resolvePriorExploreContext, // exported for testing (P-mqyp0008v022w3x4 SHERLOC)
|
|
10630
10595
|
renderProjectWorkItemPromptForAgent, // exported for testing
|
|
10631
10596
|
|
|
10632
10597
|
// Timeout / Steering / Idle (re-exported from engine/timeout.js)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2294",
|
|
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"
|
package/playbooks/fix.md
CHANGED
|
@@ -19,6 +19,15 @@ When `{{pr_id}}`/`{{pr_branch}}` identify an existing pull request, this is a fi
|
|
|
19
19
|
|
|
20
20
|
{{checkpoint_context}}
|
|
21
21
|
|
|
22
|
+
{{#prior_explore_context}}
|
|
23
|
+
## Exploration Context
|
|
24
|
+
|
|
25
|
+
A prior explore work item investigated this issue. Use this context as your starting point for fault localization:
|
|
26
|
+
|
|
27
|
+
{{prior_explore_context}}
|
|
28
|
+
|
|
29
|
+
{{/prior_explore_context}}
|
|
30
|
+
|
|
22
31
|
## Review Findings to Address
|
|
23
32
|
|
|
24
33
|
{{review_note}}
|