@yemi33/minions 0.1.2195 → 0.1.2197
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/modal-qa.js +21 -3
- package/dashboard/js/modal.js +35 -1
- package/dashboard/js/refresh.js +1 -1
- package/dashboard/js/render-agents.js +18 -0
- package/dashboard/js/render-kb.js +27 -2
- package/dashboard/js/render-meetings.js +36 -3
- package/dashboard/js/render-pipelines.js +15 -10
- package/dashboard/js/render-plans.js +73 -3
- package/dashboard/js/render-prd.js +29 -5
- package/dashboard/js/render-prs.js +174 -3
- package/dashboard/js/render-schedules.js +12 -0
- package/dashboard/js/render-utils.js +151 -1
- package/dashboard/js/render-watches.js +61 -7
- package/dashboard/js/render-work-items.js +161 -20
- package/dashboard/js/state.js +110 -1
- package/dashboard/js/utils.js +246 -14
- package/dashboard/layout.html +2 -0
- package/dashboard/slim/body.html +23 -13
- package/dashboard/slim/js/knowledge.js +576 -0
- package/dashboard/slim/js/members.js +43 -0
- package/dashboard/slim/js/modals-tiles.js +6 -4
- package/dashboard/slim/js/pinned.js +7 -19
- package/dashboard/slim/js/status.js +17 -21
- package/dashboard/slim/styles.css +94 -36
- package/dashboard/styles.css +34 -0
- package/dashboard-build.js +1 -1
- package/dashboard.js +50 -2
- package/engine/dispatch.js +39 -4
- package/engine/lifecycle.js +27 -7
- package/engine/pipeline.js +10 -0
- package/engine/queries.js +81 -0
- package/engine/scheduler.js +19 -1
- package/engine/shared.js +35 -13
- package/engine.js +4 -6
- package/package.json +1 -1
package/engine/lifecycle.js
CHANGED
|
@@ -790,6 +790,19 @@ function reconcilePrdStatuses(config) {
|
|
|
790
790
|
|
|
791
791
|
// ─── PR Sync from Output ─────────────────────────────────────────────────────
|
|
792
792
|
|
|
793
|
+
const ONE_SHOT_CONTEXT_PR_TYPES = new Set([
|
|
794
|
+
WORK_TYPE.REVIEW,
|
|
795
|
+
WORK_TYPE.ASK,
|
|
796
|
+
WORK_TYPE.EXPLORE,
|
|
797
|
+
]);
|
|
798
|
+
|
|
799
|
+
function shouldSyncPrAsContextOnly(meta) {
|
|
800
|
+
const item = meta?.item;
|
|
801
|
+
if (!item?.oneShot || item.skipPr) return false;
|
|
802
|
+
const type = String(item.type || item.itemType || '').trim();
|
|
803
|
+
return ONE_SHOT_CONTEXT_PR_TYPES.has(type);
|
|
804
|
+
}
|
|
805
|
+
|
|
793
806
|
function syncPrsFromOutput(output, agentId, meta, config, opts = {}) {
|
|
794
807
|
const { structuredCompletion = null } = opts;
|
|
795
808
|
const outputText = String(output || '');
|
|
@@ -996,13 +1009,14 @@ function syncPrsFromOutput(output, agentId, meta, config, opts = {}) {
|
|
|
996
1009
|
url: prUrl,
|
|
997
1010
|
prdItems: meta?.item?.id ? [meta.item.id] : [],
|
|
998
1011
|
sourcePlan: meta?.item?.sourcePlan || '',
|
|
999
|
-
itemType: meta?.item?.itemType || ''
|
|
1012
|
+
itemType: meta?.item?.itemType || '',
|
|
1013
|
+
contextOnly: shouldSyncPrAsContextOnly(meta),
|
|
1000
1014
|
};
|
|
1001
1015
|
applyScheduleContextToPrEntry(entry, meta?.item, config);
|
|
1002
|
-
// Issue #1772: one-off dispatches (
|
|
1003
|
-
//
|
|
1004
|
-
//
|
|
1005
|
-
|
|
1016
|
+
// Issue #1772: explicit one-off review/ask/explore dispatches (for example,
|
|
1017
|
+
// "review this PR" via CC) must stay reference-only. QA-session setup and
|
|
1018
|
+
// other oneShot+skipPr PR-targeted work are operational context, not an
|
|
1019
|
+
// observe toggle, so they must not demote a real tracked PR.
|
|
1006
1020
|
newPrsByPath.get(prPath).entries.push({ prId, fullId, entry });
|
|
1007
1021
|
}
|
|
1008
1022
|
|
|
@@ -1145,7 +1159,7 @@ function _existingPrRecordForCanonicalId(canonicalPrId) {
|
|
|
1145
1159
|
* can render the real merged/abandoned status instead of a perpetual blue ○.
|
|
1146
1160
|
*
|
|
1147
1161
|
* Idempotent: returns `{ enrolled: false, reason: 'already_tracked' }` if a row
|
|
1148
|
-
* already exists. Marks new records with `
|
|
1162
|
+
* already exists. Marks new records with `contextOnly: true` so the engine
|
|
1149
1163
|
* doesn't try to manage them (no re-dispatch of fix/review loops). Live state
|
|
1150
1164
|
* is fetched via `gh pr view` for GitHub; ADO enrollment is best-effort with
|
|
1151
1165
|
* a conservative ACTIVE default.
|
|
@@ -1259,7 +1273,7 @@ async function enrollPrFromCanonicalId(canonicalPrId, project, opts = {}) {
|
|
|
1259
1273
|
url,
|
|
1260
1274
|
prdItems: opts.itemId ? [opts.itemId] : [],
|
|
1261
1275
|
_attachedAt: ts(),
|
|
1262
|
-
|
|
1276
|
+
contextOnly: true,
|
|
1263
1277
|
_enrolledBy: 'enrollPrFromCanonicalId',
|
|
1264
1278
|
};
|
|
1265
1279
|
if (liveState && liveState.mergedAt) entry.mergedAt = liveState.mergedAt;
|
|
@@ -5144,7 +5158,13 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
|
|
|
5144
5158
|
const itemId = meta.item.id;
|
|
5145
5159
|
const schedRunsPath = path.join(ENGINE_DIR, 'schedule-runs.json');
|
|
5146
5160
|
mutateJsonFileLocked(schedRunsPath, (runs) => {
|
|
5161
|
+
// P-c549d07e — preserve existing fields (esp. recentWorkItemIds ring
|
|
5162
|
+
// populated at dispatch time by engine/scheduler.writeScheduleRunEntry).
|
|
5163
|
+
// The literal-object replacement that lived here previously silently
|
|
5164
|
+
// clobbered the ring on every successful completion.
|
|
5165
|
+
const existing = typeof runs[scheduleId] === 'object' && runs[scheduleId] ? runs[scheduleId] : {};
|
|
5147
5166
|
runs[scheduleId] = {
|
|
5167
|
+
...existing,
|
|
5148
5168
|
lastRun: typeof runs[scheduleId] === 'string' ? runs[scheduleId] : (runs[scheduleId]?.lastRun || ts()),
|
|
5149
5169
|
lastWorkItemId: itemId,
|
|
5150
5170
|
lastResult: effectiveSuccess ? DISPATCH_RESULT.SUCCESS : DISPATCH_RESULT.ERROR,
|
package/engine/pipeline.js
CHANGED
|
@@ -427,6 +427,12 @@ function executeTaskStage(stage, stageState, run, config, pipeline = {}) {
|
|
|
427
427
|
...(wiBranch ? { branch: wiBranch } : {}),
|
|
428
428
|
_pipelineRun: run.runId,
|
|
429
429
|
_pipelineStage: stage.id,
|
|
430
|
+
// P-c549d07e — back-link to the dispatcher (pipeline:<id>) so the
|
|
431
|
+
// WI detail modal can render a clickable chip via openArtifact()
|
|
432
|
+
// → openPipelineDetail. Kept alongside the existing createdBy /
|
|
433
|
+
// _pipelineRun / _pipelineStage fields so we don't disturb any
|
|
434
|
+
// existing readers — meta.spawnedBy is purely additive.
|
|
435
|
+
meta: { spawnedBy: 'pipeline:' + run.pipelineId },
|
|
430
436
|
});
|
|
431
437
|
return workItems;
|
|
432
438
|
});
|
|
@@ -561,6 +567,8 @@ async function executePlanStage(stage, stageState, run, config, pipeline = {}) {
|
|
|
561
567
|
planFile: existingPlanFile, created: ts(), createdBy: 'pipeline:' + run.pipelineId,
|
|
562
568
|
// W-mp8ho6w500034a58: PLAN_TO_PRD is read-only — no branch needed.
|
|
563
569
|
_pipelineRun: run.runId, _pipelineStage: stage.id,
|
|
570
|
+
// P-c549d07e — back-link chip (see executeTaskStage above).
|
|
571
|
+
meta: { spawnedBy: 'pipeline:' + run.pipelineId },
|
|
564
572
|
...(project ? { project: project.name } : {}),
|
|
565
573
|
});
|
|
566
574
|
}
|
|
@@ -657,6 +665,8 @@ async function executePlanStage(stage, stageState, run, config, pipeline = {}) {
|
|
|
657
665
|
// W-mp8ho6w500034a58: PLAN_TO_PRD is read-only — no branch needed.
|
|
658
666
|
_pipelineRun: run.runId,
|
|
659
667
|
_pipelineStage: stage.id,
|
|
668
|
+
// P-c549d07e — back-link chip (see executeTaskStage above).
|
|
669
|
+
meta: { spawnedBy: 'pipeline:' + run.pipelineId },
|
|
660
670
|
...(project ? { project: project.name } : {}),
|
|
661
671
|
});
|
|
662
672
|
}
|
package/engine/queries.js
CHANGED
|
@@ -496,6 +496,72 @@ function getInboxFiles() {
|
|
|
496
496
|
try { return fs.readdirSync(INBOX_DIR).filter(f => f.endsWith('.md')); } catch { return []; }
|
|
497
497
|
}
|
|
498
498
|
|
|
499
|
+
// P-34fa5d79 — Note ↔ Work Item ↔ KB Entry linkage.
|
|
500
|
+
//
|
|
501
|
+
// Parse the YAML frontmatter from a note body and return the parsed fields.
|
|
502
|
+
// Same minimal contract as engine/kb-sweep.js#_parseFrontmatter — a single
|
|
503
|
+
// shared regex would have been nicer but importing kb-sweep from queries
|
|
504
|
+
// would form a circular dep with lifecycle (queries → kb-sweep → … →
|
|
505
|
+
// queries), so we keep a local copy of the four-line parser.
|
|
506
|
+
function _parseNoteFrontmatter(content) {
|
|
507
|
+
const m = String(content || '').match(/^---\n([\s\S]*?)\n---\n?/);
|
|
508
|
+
if (!m) return null;
|
|
509
|
+
const fm = {};
|
|
510
|
+
for (const line of m[1].split('\n')) {
|
|
511
|
+
const lm = line.match(/^([\w-]+):\s*(.*)$/);
|
|
512
|
+
if (lm) fm[lm[1]] = lm[2].trim();
|
|
513
|
+
}
|
|
514
|
+
return fm;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Extract the WI id that a note's frontmatter cites, or null. Accepts the
|
|
518
|
+
// three field-name conventions used across the repo today:
|
|
519
|
+
// - `work_item:` (dallas fix-summary template, the common case)
|
|
520
|
+
// - `wi:` (terser variant, used in a handful of newer notes)
|
|
521
|
+
// - `sourceItem:` (engine-emitted notes such as pr-auto-link-unverified)
|
|
522
|
+
// `id:` itself is the note's own NOTE-… id and is intentionally NOT matched —
|
|
523
|
+
// it would never carry a WI value in any existing convention.
|
|
524
|
+
function _wiIdFromNoteFrontmatter(fm) {
|
|
525
|
+
if (!fm) return null;
|
|
526
|
+
return fm.work_item || fm.wi || fm.sourceItem || null;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Build a wiId → [filename, ...] map by scanning the inbox + archive dirs.
|
|
530
|
+
// Archive entries are prefixed with `archive:` so the dashboard renderer can
|
|
531
|
+
// tell them apart (matches the convention already used by _artifacts.notes).
|
|
532
|
+
// Pure-function helper exposed for testing.
|
|
533
|
+
function _buildNotesByWiMap() {
|
|
534
|
+
const out = Object.create(null);
|
|
535
|
+
const addNote = (wiId, token) => {
|
|
536
|
+
if (!wiId || !token) return;
|
|
537
|
+
if (!out[wiId]) out[wiId] = [];
|
|
538
|
+
if (!out[wiId].includes(token)) out[wiId].push(token);
|
|
539
|
+
};
|
|
540
|
+
for (const f of safeReadDir(INBOX_DIR)) {
|
|
541
|
+
if (!f.endsWith('.md')) continue;
|
|
542
|
+
const fm = _parseNoteFrontmatter(safeRead(path.join(INBOX_DIR, f)));
|
|
543
|
+
const wiId = _wiIdFromNoteFrontmatter(fm);
|
|
544
|
+
if (wiId) addNote(wiId, f);
|
|
545
|
+
}
|
|
546
|
+
for (const f of safeReadDir(ARCHIVE_DIR)) {
|
|
547
|
+
if (!f.endsWith('.md')) continue;
|
|
548
|
+
const fm = _parseNoteFrontmatter(safeRead(path.join(ARCHIVE_DIR, f)));
|
|
549
|
+
const wiId = _wiIdFromNoteFrontmatter(fm);
|
|
550
|
+
if (wiId) addNote(wiId, 'archive:' + f);
|
|
551
|
+
}
|
|
552
|
+
return out;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Public lookup — return the array of note tokens (filenames or
|
|
556
|
+
// `archive:<filename>`) whose frontmatter cites this WI id. Returns [] when
|
|
557
|
+
// nothing matches. Scans inbox + archive each call; for the bulk WI list
|
|
558
|
+
// payload getWorkItems() pre-builds the map once per cache cycle.
|
|
559
|
+
function notesForWorkItem(wiId) {
|
|
560
|
+
if (!wiId) return [];
|
|
561
|
+
const map = _buildNotesByWiMap();
|
|
562
|
+
return map[wiId] || [];
|
|
563
|
+
}
|
|
564
|
+
|
|
499
565
|
function getInbox() {
|
|
500
566
|
return safeReadDir(INBOX_DIR)
|
|
501
567
|
.filter(f => f.endsWith('.md'))
|
|
@@ -1745,6 +1811,9 @@ function getWorkItems(config) {
|
|
|
1745
1811
|
// Use snapshot — sync access; cold start before any async warm returns [].
|
|
1746
1812
|
// Best-effort enrichment for work item _artifacts.notes, not correctness-critical.
|
|
1747
1813
|
const _kbEntries = getKnowledgeBaseEntriesSnapshot();
|
|
1814
|
+
// P-34fa5d79 — pre-build the wiId → notes map so each item's _notes lookup
|
|
1815
|
+
// is O(1) instead of re-scanning inbox+archive per item.
|
|
1816
|
+
const _notesByWi = _buildNotesByWiMap();
|
|
1748
1817
|
for (const item of allItems) {
|
|
1749
1818
|
const arts = shared.isPlainObject(item._artifacts) ? { ...item._artifacts } : {};
|
|
1750
1819
|
const agentId = item.dispatched_to || item.agent;
|
|
@@ -1779,6 +1848,13 @@ function getWorkItems(config) {
|
|
|
1779
1848
|
else if (item.planFile) arts.plan = item.planFile;
|
|
1780
1849
|
if (item._pr) arts.pr = item._pr;
|
|
1781
1850
|
if (Object.keys(arts).length > 0) item._artifacts = arts;
|
|
1851
|
+
// P-34fa5d79 — Note ↔ WI ↔ KB linkage. Slim string[] field of note
|
|
1852
|
+
// filenames whose YAML frontmatter cites this WI. Archive notes are
|
|
1853
|
+
// prefixed with `archive:` so the dashboard chip renderer can pick the
|
|
1854
|
+
// right open path. Field is left undefined (not []) when empty so the
|
|
1855
|
+
// /api/work-items JSON stays compact for the common no-notes case.
|
|
1856
|
+
const mentions = _notesByWi[item.id];
|
|
1857
|
+
if (mentions && mentions.length > 0) item._notes = mentions;
|
|
1782
1858
|
}
|
|
1783
1859
|
|
|
1784
1860
|
const statusOrder = {
|
|
@@ -2897,6 +2973,11 @@ module.exports = {
|
|
|
2897
2973
|
|
|
2898
2974
|
// Inbox
|
|
2899
2975
|
getInboxFiles, getInbox,
|
|
2976
|
+
// P-34fa5d79 — Note ↔ Work Item ↔ KB Entry linkage.
|
|
2977
|
+
notesForWorkItem,
|
|
2978
|
+
_parseNoteFrontmatter, // exported for testing
|
|
2979
|
+
_wiIdFromNoteFrontmatter, // exported for testing
|
|
2980
|
+
_buildNotesByWiMap, // exported for testing
|
|
2900
2981
|
|
|
2901
2982
|
// Agents
|
|
2902
2983
|
getAgentStatus, getAgentCharter, getAgents, getAgentDetail,
|
package/engine/scheduler.js
CHANGED
|
@@ -328,6 +328,12 @@ function createScheduledWorkItem(sched) {
|
|
|
328
328
|
...(sched.agentLock === true || sched.hardAgent === true ? { agentLock: true } : {}),
|
|
329
329
|
project: sched.project || null,
|
|
330
330
|
_scheduleId: sched.id,
|
|
331
|
+
// P-c549d07e — back-link to the schedule that spawned this WI. The WI
|
|
332
|
+
// detail modal renders this as a clickable chip via renderArtifactLink
|
|
333
|
+
// so the operator can navigate back to the dispatcher. Kept alongside
|
|
334
|
+
// _scheduleId (used by queries / lifecycle / consolidation) so we don't
|
|
335
|
+
// disturb the existing readers — meta.spawnedBy is purely additive.
|
|
336
|
+
meta: { spawnedBy: 'schedule:' + sched.id },
|
|
331
337
|
};
|
|
332
338
|
// Walk every string-valued field on the work item so vars embedded in
|
|
333
339
|
// nested fields (e.g. _harness.rubric added by callers, references[].url,
|
|
@@ -339,7 +345,19 @@ function createScheduledWorkItem(sched) {
|
|
|
339
345
|
|
|
340
346
|
function writeScheduleRunEntry(runs, scheduleId, workItemId, extra) {
|
|
341
347
|
const existing = typeof runs[scheduleId] === 'object' && runs[scheduleId] ? runs[scheduleId] : {};
|
|
342
|
-
|
|
348
|
+
// P-c549d07e — maintain a recentWorkItemIds ring (newest first, hard cap 5,
|
|
349
|
+
// deduplicated). Surfaces "Recent dispatches" chips in the schedule modal
|
|
350
|
+
// without forcing the dashboard to scan every WI for _scheduleId matches.
|
|
351
|
+
// Empty / falsy workItemId leaves the ring untouched (defensive — callers
|
|
352
|
+
// always pass a real id today, but the engine should never pollute the
|
|
353
|
+
// history with blanks if a future caller slips up).
|
|
354
|
+
let recent = Array.isArray(existing.recentWorkItemIds) ? existing.recentWorkItemIds.slice() : [];
|
|
355
|
+
if (workItemId) {
|
|
356
|
+
recent = recent.filter((id) => id !== workItemId);
|
|
357
|
+
recent.unshift(workItemId);
|
|
358
|
+
if (recent.length > 5) recent = recent.slice(0, 5);
|
|
359
|
+
}
|
|
360
|
+
runs[scheduleId] = { ...existing, lastRun: ts(), lastWorkItemId: workItemId, recentWorkItemIds: recent, ...(extra || {}) };
|
|
343
361
|
return runs[scheduleId];
|
|
344
362
|
}
|
|
345
363
|
|
package/engine/shared.js
CHANGED
|
@@ -6348,6 +6348,15 @@ function applyPrFieldDelta(target, before, after) {
|
|
|
6348
6348
|
function normalizePrRecord(pr, project = null) {
|
|
6349
6349
|
if (!pr || typeof pr !== 'object') return false;
|
|
6350
6350
|
let changed = false;
|
|
6351
|
+
const hasCanonicalContextOnly = Object.prototype.hasOwnProperty.call(pr, 'contextOnly');
|
|
6352
|
+
if (hasCanonicalContextOnly) {
|
|
6353
|
+
for (const legacyKey of ['_contextOnly', '_autoObserve', '_manual']) {
|
|
6354
|
+
if (Object.prototype.hasOwnProperty.call(pr, legacyKey)) {
|
|
6355
|
+
delete pr[legacyKey];
|
|
6356
|
+
changed = true;
|
|
6357
|
+
}
|
|
6358
|
+
}
|
|
6359
|
+
}
|
|
6351
6360
|
const prNumber = getPrNumber(pr.prNumber ?? pr.id ?? pr.url);
|
|
6352
6361
|
if (prNumber != null && pr.prNumber !== prNumber) {
|
|
6353
6362
|
pr.prNumber = prNumber;
|
|
@@ -6386,17 +6395,19 @@ function normalizePrLinkItems(value) {
|
|
|
6386
6395
|
return [...new Set(items.filter(item => typeof item === 'string' && item))];
|
|
6387
6396
|
}
|
|
6388
6397
|
|
|
6389
|
-
// W-mq5s5ttx000j7ab8-a — canonical `contextOnly` gate
|
|
6390
|
-
//
|
|
6391
|
-
//
|
|
6392
|
-
//
|
|
6393
|
-
//
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
+
// W-mq5s5ttx000j7ab8-a / W-mqerisvz000n3901 — canonical `contextOnly` gate
|
|
6399
|
+
// with a legacy bridge. Canonical `contextOnly` wins whenever it exists; legacy
|
|
6400
|
+
// `_contextOnly` is only a fallback for pre-migration/raw records. This keeps
|
|
6401
|
+
// observe toggles (`contextOnly:false`) from being overridden by stale legacy
|
|
6402
|
+
// contamination while still treating old context-only rows as reference-only.
|
|
6403
|
+
function isContextOnlyPrRecord(pr) {
|
|
6404
|
+
if (!pr || typeof pr !== 'object') return false;
|
|
6405
|
+
if (typeof pr.contextOnly === 'boolean') return pr.contextOnly;
|
|
6406
|
+
return pr._contextOnly === true;
|
|
6407
|
+
}
|
|
6408
|
+
|
|
6398
6409
|
function isAutoManagedPrRecord(pr) {
|
|
6399
|
-
return !!pr && typeof pr === 'object' && pr
|
|
6410
|
+
return !!pr && typeof pr === 'object' && !isContextOnlyPrRecord(pr);
|
|
6400
6411
|
}
|
|
6401
6412
|
|
|
6402
6413
|
function mergePrLinkItems(links, prId, itemIds) {
|
|
@@ -6548,6 +6559,13 @@ function upsertPullRequestRecord(prPath, entry, { project = null, itemId = null,
|
|
|
6548
6559
|
prNumber: prNumber ?? entry.prNumber ?? null,
|
|
6549
6560
|
prdItems: linkedItemIds,
|
|
6550
6561
|
};
|
|
6562
|
+
const normalizedEntryContextOnly = normalizedEntry.contextOnly != null
|
|
6563
|
+
? normalizedEntry.contextOnly
|
|
6564
|
+
: normalizedEntry._contextOnly;
|
|
6565
|
+
if (normalizedEntryContextOnly != null) {
|
|
6566
|
+
normalizedEntry.contextOnly = normalizedEntryContextOnly === true;
|
|
6567
|
+
delete normalizedEntry._contextOnly;
|
|
6568
|
+
}
|
|
6551
6569
|
|
|
6552
6570
|
let created = false;
|
|
6553
6571
|
let linked = false;
|
|
@@ -6568,7 +6586,8 @@ function upsertPullRequestRecord(prPath, entry, { project = null, itemId = null,
|
|
|
6568
6586
|
} else {
|
|
6569
6587
|
target.id = canonicalId;
|
|
6570
6588
|
if (prNumber != null) target.prNumber = prNumber;
|
|
6571
|
-
const targetWasAutoManaged = isAutoManagedPrRecord(target)
|
|
6589
|
+
const targetWasAutoManaged = isAutoManagedPrRecord(target)
|
|
6590
|
+
|| (target.contextOnly == null && target._contextOnly === true && _prRecordIsLegacyManaged(target));
|
|
6572
6591
|
for (const key of ['url', 'title', 'description', 'agent', 'branch', 'reviewStatus', 'status', 'created', 'sourcePlan', 'itemType']) {
|
|
6573
6592
|
if (normalizedEntry[key] != null && normalizedEntry[key] !== '' && (target[key] == null || target[key] === '')) {
|
|
6574
6593
|
target[key] = normalizedEntry[key];
|
|
@@ -6591,7 +6610,10 @@ function upsertPullRequestRecord(prPath, entry, { project = null, itemId = null,
|
|
|
6591
6610
|
: normalizedEntry._contextOnly;
|
|
6592
6611
|
if (incomingContextOnly != null) {
|
|
6593
6612
|
const wouldDemoteManagedPr = incomingContextOnly === true && targetWasAutoManaged;
|
|
6594
|
-
|
|
6613
|
+
target.contextOnly = wouldDemoteManagedPr ? false : incomingContextOnly === true;
|
|
6614
|
+
delete target._contextOnly;
|
|
6615
|
+
delete target._autoObserve;
|
|
6616
|
+
delete target._manual;
|
|
6595
6617
|
}
|
|
6596
6618
|
}
|
|
6597
6619
|
target.prdItems = normalizePrLinkItems(target.prdItems || []);
|
|
@@ -8298,7 +8320,7 @@ module.exports = {
|
|
|
8298
8320
|
normalizePrRecords,
|
|
8299
8321
|
normalizePrLinkItems, // exported for testing
|
|
8300
8322
|
mergePrLinkItems, // exported for testing
|
|
8301
|
-
|
|
8323
|
+
isContextOnlyPrRecord,
|
|
8302
8324
|
upsertPullRequestRecord,
|
|
8303
8325
|
isAutoManagedPrRecord, // W-mq5s5ttx000j7ab8-a — exported for engine + watch-plugin gate consolidation
|
|
8304
8326
|
migratePrGateFlags, // W-mq5s5ttx000j7ab8-a — boot migration wired from engine/cli.js
|
package/engine.js
CHANGED
|
@@ -6043,7 +6043,7 @@ async function discoverFromPrs(config, project) {
|
|
|
6043
6043
|
|
|
6044
6044
|
for (const pr of prs) {
|
|
6045
6045
|
if (pr.status !== PR_STATUS.ACTIVE) continue;
|
|
6046
|
-
if (pr
|
|
6046
|
+
if (shared.isContextOnlyPrRecord(pr)) {
|
|
6047
6047
|
_logPrDispatchSkipOnce(pr, 'context-only');
|
|
6048
6048
|
continue;
|
|
6049
6049
|
}
|
|
@@ -6057,11 +6057,9 @@ async function discoverFromPrs(config, project) {
|
|
|
6057
6057
|
log('info', `Branch mutex: skipping PR ${pr.id} dispatch — branch ${prBranchForMutex} locked by another agent`);
|
|
6058
6058
|
continue;
|
|
6059
6059
|
}
|
|
6060
|
-
// Auto-managed gate: single source of truth in engine/shared.js.
|
|
6061
|
-
//
|
|
6062
|
-
//
|
|
6063
|
-
// the prior inline `knownAgents.has(...) || prdItems || (_manual && !_contextOnly)`
|
|
6064
|
-
// silently dropped PRs with `_autoObserve: true` + human author.
|
|
6060
|
+
// Auto-managed gate: single source of truth in engine/shared.js. Canonical
|
|
6061
|
+
// `contextOnly` wins over stale legacy flags; legacy `_contextOnly` is only
|
|
6062
|
+
// a fallback for raw pre-migration records.
|
|
6065
6063
|
if (!shared.isAutoManagedPrRecord(pr)) {
|
|
6066
6064
|
_logPrDispatchSkipOnce(pr, 'not-auto-managed');
|
|
6067
6065
|
continue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2197",
|
|
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"
|