@yemi33/minions 0.1.2274 → 0.1.2276
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 +15 -0
- package/engine/queries.js +26 -3
- package/engine.js +28 -3
- package/package.json +1 -1
package/dashboard.js
CHANGED
|
@@ -8260,6 +8260,21 @@ const server = http.createServer(async (req, res) => {
|
|
|
8260
8260
|
cleanupPlanWorktrees(body.file, planObj || {}, PROJECTS, getConfig());
|
|
8261
8261
|
} catch (e) { console.error('plan worktree cleanup:', e.message); }
|
|
8262
8262
|
safeUnlink(planPath);
|
|
8263
|
+
// Neutralize the `.backup` sidecar so `safeJson` auto-restore can't
|
|
8264
|
+
// RESURRECT the PRD we just deleted (the live file is gone, but a stray
|
|
8265
|
+
// `prd/<plan>.json.backup` would be auto-restored on the next safeJson read
|
|
8266
|
+
// — the PRD comes back, with its prior `status: approved/active`, and the
|
|
8267
|
+
// materializer re-dispatches its work items). Mirrors the Archive handler's
|
|
8268
|
+
// backup cleanup (handlePlansArchive). A PRD delete must remove the restore
|
|
8269
|
+
// fuel, not just the live file.
|
|
8270
|
+
if (body.file.endsWith('.json')) {
|
|
8271
|
+
try {
|
|
8272
|
+
const backupCleanup = shared.neutralizeJsonBackupSidecar(planPath);
|
|
8273
|
+
if (!backupCleanup.ok) {
|
|
8274
|
+
console.warn(`Delete backup cleanup failed for ${body.file}: unlink (${backupCleanup.unlinkError}) / neutralize (${backupCleanup.writeError})`);
|
|
8275
|
+
}
|
|
8276
|
+
} catch (e) { console.warn(`Delete backup cleanup threw for ${body.file}: ${e.message}`); }
|
|
8277
|
+
}
|
|
8263
8278
|
|
|
8264
8279
|
// Clean up materialized work items from all projects + central
|
|
8265
8280
|
let cleaned = 0;
|
package/engine/queries.js
CHANGED
|
@@ -1977,6 +1977,18 @@ function getWorkItems(config, opts) {
|
|
|
1977
1977
|
// Use snapshot — sync access; cold start before any async warm returns [].
|
|
1978
1978
|
// Best-effort enrichment for work item _artifacts.notes, not correctness-critical.
|
|
1979
1979
|
const _kbEntries = getKnowledgeBaseEntriesSnapshot();
|
|
1980
|
+
// PERF (dashboard event-loop freeze fix): the notes-linkage match below is
|
|
1981
|
+
// `entry contains agentId AND entry contains itemId`. Filtering the FULL
|
|
1982
|
+
// _kbEntries (can be ~10k) and _archiveFiles (~2k) per work item is O(items ×
|
|
1983
|
+
// files) — on a busy instance that's millions of String.includes() per
|
|
1984
|
+
// /api/work-items call, run synchronously, which hard-freezes the single
|
|
1985
|
+
// dashboard event loop. Bucket the two large collections by agentId ONCE per
|
|
1986
|
+
// distinct agent (lazily, like _agentDirCache above), then each item only
|
|
1987
|
+
// scans its agent's much-smaller bucket for the itemId. Exact same match
|
|
1988
|
+
// semantics; O(agents × files) one-time + O(items × bucket) instead of
|
|
1989
|
+
// O(items × files). _inboxFiles stays a direct filter (tiny — a handful).
|
|
1990
|
+
const _kbByAgent = {}; // agentId → kb entries whose source includes agentId
|
|
1991
|
+
const _archiveByAgent = {}; // agentId → archive filenames that include agentId
|
|
1980
1992
|
// P-34fa5d79 — pre-build the wiId → notes map so each item's _notes lookup
|
|
1981
1993
|
// is O(1) instead of re-scanning inbox+archive per item.
|
|
1982
1994
|
const _notesByWi = _buildNotesByWiMap();
|
|
@@ -1996,14 +2008,25 @@ function getWorkItems(config, opts) {
|
|
|
1996
2008
|
// Notes: inbox → KB (via source field) → archive (fallback if KB was swept)
|
|
1997
2009
|
const itemId = item.id || '___';
|
|
1998
2010
|
const matchInbox = _inboxFiles.filter(f => f.includes(agentId) && f.includes(itemId));
|
|
1999
|
-
|
|
2011
|
+
// Lazily bucket KB by agentId (computed once per distinct agent), then
|
|
2012
|
+
// filter the small bucket by itemId — same result as filtering all of
|
|
2013
|
+
// _kbEntries by (agentId AND itemId), without the per-item full scan.
|
|
2014
|
+
if (_kbByAgent[agentId] === undefined) {
|
|
2015
|
+
_kbByAgent[agentId] = _kbEntries.filter(kb => kb.source && kb.source.includes(agentId));
|
|
2016
|
+
}
|
|
2017
|
+
const matchKb = _kbByAgent[agentId].filter(kb => kb.source.includes(itemId));
|
|
2000
2018
|
const allNotes = [
|
|
2001
2019
|
...matchInbox,
|
|
2002
2020
|
...matchKb.map(kb => 'kb:' + kb.cat + '/' + kb.file),
|
|
2003
2021
|
];
|
|
2004
|
-
// Archive fallback — only if nothing found in inbox or KB
|
|
2022
|
+
// Archive fallback — only if nothing found in inbox or KB. Same agentId
|
|
2023
|
+
// bucketing as KB so the (potentially large) archive list isn't fully
|
|
2024
|
+
// re-scanned per item.
|
|
2005
2025
|
if (allNotes.length === 0) {
|
|
2006
|
-
|
|
2026
|
+
if (_archiveByAgent[agentId] === undefined) {
|
|
2027
|
+
_archiveByAgent[agentId] = _archiveFiles.filter(f => f.includes(agentId));
|
|
2028
|
+
}
|
|
2029
|
+
const matchArchive = _archiveByAgent[agentId].filter(f => f.includes(itemId));
|
|
2007
2030
|
for (const f of matchArchive) allNotes.push('archive:' + f);
|
|
2008
2031
|
}
|
|
2009
2032
|
if (allNotes.length > 0) arts.notes = _mergeArtifactNotes(arts.notes, allNotes);
|
package/engine.js
CHANGED
|
@@ -8782,18 +8782,41 @@ function discoverCentralWorkItems(config) {
|
|
|
8782
8782
|
// the renderPlaybook pass logs an "unresolved template variables" warning
|
|
8783
8783
|
// every time a fresh plan-to-prd dispatches.
|
|
8784
8784
|
vars.existing_prd_json = '';
|
|
8785
|
-
// Check if a PRD already exists for this plan — reuse its filename to
|
|
8785
|
+
// Check if a PRD already exists for this plan — reuse its filename to
|
|
8786
|
+
// avoid duplicates (#884). Match by BASENAME (not exact string) so a
|
|
8787
|
+
// `plans/` prefix or separator difference between item.planFile and a
|
|
8788
|
+
// PRD's source_plan can't fork a duplicate PRD (#415 class).
|
|
8786
8789
|
let prdFilename = null;
|
|
8790
|
+
const planKey = path.basename(String(item.planFile || ''));
|
|
8787
8791
|
const prdFiles = safeReadDir(PRD_DIR).filter(f => f.endsWith('.json'));
|
|
8788
8792
|
for (const pf of prdFiles) {
|
|
8789
8793
|
const prd = safeJson(path.join(PRD_DIR, pf));
|
|
8790
|
-
if (prd
|
|
8794
|
+
if (planKey && prd && path.basename(String(prd.source_plan || '')) === planKey) {
|
|
8791
8795
|
prdFilename = pf;
|
|
8792
8796
|
try { vars.existing_prd_json = fs.readFileSync(path.join(PRD_DIR, pf), 'utf8'); } catch (_) { /* ignore */ }
|
|
8793
8797
|
log('info', `plan-to-prd: reusing existing PRD "${pf}" for plan "${item.planFile}" (#884)`);
|
|
8794
8798
|
break;
|
|
8795
8799
|
}
|
|
8796
8800
|
}
|
|
8801
|
+
// Same-plan in-flight reuse (dedup hardening): no on-disk PRD found, but
|
|
8802
|
+
// if ANOTHER plan-to-prd item for the SAME plan already pinned a
|
|
8803
|
+
// _prdFilename — a sibling WI from a prior tick, or another item being
|
|
8804
|
+
// prepared this same tick — REUSE it so concurrent/repeat plan-to-prd
|
|
8805
|
+
// runs for one plan converge to a single PRD instead of splitting into
|
|
8806
|
+
// <slug>-<date>.json / -2.json / -3.json. Match by basename.
|
|
8807
|
+
if (!prdFilename && planKey) {
|
|
8808
|
+
for (const otherItem of items) {
|
|
8809
|
+
if (!otherItem || otherItem.id === item.id || !otherItem._prdFilename) continue;
|
|
8810
|
+
if (path.basename(String(otherItem.planFile || '')) === planKey) { prdFilename = otherItem._prdFilename; break; }
|
|
8811
|
+
}
|
|
8812
|
+
if (!prdFilename) {
|
|
8813
|
+
for (const [otherId, m] of mutations) {
|
|
8814
|
+
if (otherId === item.id || !m || !m._prdFilename) continue;
|
|
8815
|
+
if (m._planKey && m._planKey === planKey) { prdFilename = m._prdFilename; break; }
|
|
8816
|
+
}
|
|
8817
|
+
}
|
|
8818
|
+
if (prdFilename) log('info', `plan-to-prd: reusing in-flight PRD "${prdFilename}" for plan "${item.planFile}" (same-plan dedup)`);
|
|
8819
|
+
}
|
|
8797
8820
|
if (!prdFilename) {
|
|
8798
8821
|
// Generate unique PRD filename — check prd/, prd/archive/, AND any
|
|
8799
8822
|
// _prdFilename already pinned by sibling work items (W-mozkn1bb001j66fd):
|
|
@@ -8832,7 +8855,9 @@ function discoverCentralWorkItems(config) {
|
|
|
8832
8855
|
while (prdExisting.has(prdFilename)) { prdFilename = prdBase + '-' + prdCounter + '.json'; prdCounter++; }
|
|
8833
8856
|
}
|
|
8834
8857
|
vars.prd_filename = prdFilename;
|
|
8835
|
-
|
|
8858
|
+
// Pin _planKey alongside _prdFilename so the same-plan in-flight reuse
|
|
8859
|
+
// above can recognize a sibling prepared earlier THIS tick by plan.
|
|
8860
|
+
mutations.set(item.id, Object.assign(mutations.get(item.id) || {}, { _prdFilename: prdFilename, _planKey: planKey }));
|
|
8836
8861
|
vars.branch_strategy_hint = item.branchStrategy
|
|
8837
8862
|
? `The user requested **${item.branchStrategy}** strategy. Use this unless the analysis strongly suggests otherwise.`
|
|
8838
8863
|
: 'Choose the best strategy based on your analysis of item dependencies.';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2276",
|
|
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"
|