@yemi33/minions 0.1.2424 → 0.1.2426
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/bin/install-internal-minions.js +1209 -0
- package/bin/minions.js +75 -9
- package/dashboard/docs/typography.md +27 -12
- package/dashboard/js/command-center.js +13 -11
- package/dashboard/js/live-stream.js +1 -1
- package/dashboard/js/memory-search.js +388 -43
- package/dashboard/js/modal-qa.js +5 -5
- package/dashboard/js/qa.js +39 -26
- package/dashboard/js/refresh.js +37 -18
- package/dashboard/js/render-agents.js +14 -18
- package/dashboard/js/render-dispatch.js +14 -74
- package/dashboard/js/render-inbox.js +3 -3
- package/dashboard/js/render-meetings.js +8 -8
- package/dashboard/js/render-other.js +45 -20
- package/dashboard/js/render-pipelines.js +6 -6
- package/dashboard/js/render-plans.js +14 -14
- package/dashboard/js/render-prd.js +46 -46
- package/dashboard/js/render-prs.js +9 -65
- package/dashboard/js/render-schedules.js +5 -5
- package/dashboard/js/render-skills.js +6 -6
- package/dashboard/js/render-utils.js +4 -4
- package/dashboard/js/render-watches.js +4 -16
- package/dashboard/js/render-work-items.js +95 -107
- package/dashboard/js/settings.js +175 -74
- package/dashboard/layout.html +3 -2
- package/dashboard/pages/inbox.html +1 -1
- package/dashboard/pages/work.html +1 -1
- package/dashboard/shared/model-display.js +9 -0
- package/dashboard/shared/watches-source.js +43 -0
- package/dashboard/shared/welcome-popup.js +259 -0
- package/dashboard/slim/body.html +6 -6
- package/dashboard/slim/js/chat.js +4 -2
- package/dashboard/slim/js/history.js +10 -3
- package/dashboard/slim/js/modals-tiles.js +85 -16
- package/dashboard/slim/js/status.js +12 -16
- package/dashboard/slim/styles.css +24 -4
- package/dashboard/styles.css +62 -27
- package/dashboard-build.js +8 -2
- package/dashboard.js +3854 -2514
- package/docs/README.md +7 -5
- package/docs/api-errors.md +144 -0
- package/docs/auto-discovery.md +83 -61
- package/docs/capture-demos.js +173 -30
- package/docs/command-center.md +5 -1
- package/docs/completion-reports.md +127 -11
- package/docs/constants.md +15 -3
- package/docs/constellation-style-telemetry.md +2 -2
- package/docs/copilot-cli-schema.md +32 -22
- package/docs/cross-repo-plans.md +30 -30
- package/docs/demo/memory-system.html +1 -1
- package/docs/deprecated.json +132 -18
- package/docs/diagnostics-memory.md +8 -4
- package/docs/human-vs-automated.md +2 -2
- package/docs/index.html +9 -2
- package/docs/internal-install.md +212 -0
- package/docs/kb-pr3223-cascade-archiving.md +16 -0
- package/docs/kb-pr696-merge-conflict-docs.md +23 -0
- package/docs/kb-sweep.md +2 -2
- package/docs/keep-processes.md +8 -1
- package/docs/live-checkout-mode.md +1 -1
- package/docs/managed-spawn.md +21 -3
- package/docs/named-agents.md +3 -2
- package/docs/onboarding.md +26 -0
- package/docs/plan-lifecycle.md +32 -32
- package/docs/pr-screenshots/pr-899/worker-pool-worktrees-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-899/worker-pool-worktrees-BEFORE.png +0 -0
- package/docs/pr-screenshots/pr-979/auto-fix-pane-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-979/auto-fix-pane-BEFORE.png +0 -0
- package/docs/pr-screenshots/pr-985/pr-column-em-dash-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-985/pr-column-em-dash-BEFORE.png +0 -0
- package/docs/qa-runbook-lifecycle.md +67 -51
- package/docs/qa-runbooks.md +2 -2
- package/docs/runtime-adapters.md +101 -50
- package/docs/security.md +28 -0
- package/docs/self-improvement.md +47 -13
- package/docs/skills.md +29 -0
- package/docs/slim-ux/concepts.md +2 -2
- package/docs/specs/agent-configurability.md +43 -41
- package/docs/specs/agent-rename.md +13 -13
- package/docs/team-memory.md +283 -14
- package/docs/tutorials/01-install-and-connect.md +23 -0
- package/docs/tutorials/03-plan-driven-feature.md +3 -3
- package/docs/tutorials/04-runtimes-and-harness.md +18 -21
- package/docs/tutorials/08-operations-and-recovery.md +3 -2
- package/docs/tutorials/README.md +4 -3
- package/docs/visual-evidence-ci.md +103 -0
- package/docs/watches.md +35 -30
- package/docs/worktree-lifecycle.md +151 -13
- package/engine/acp-transport.js +1 -0
- package/engine/ado.js +80 -33
- package/engine/agent-api-validation.js +571 -0
- package/engine/agent-worker-pool.js +7 -6
- package/engine/api-contracts/agent-content.js +761 -0
- package/engine/api-contracts/cc-ops.js +219 -0
- package/engine/api-contracts/config-runtime.js +419 -0
- package/engine/api-contracts/core.js +138 -0
- package/engine/api-contracts/index.js +506 -0
- package/engine/api-contracts/orchestration.js +602 -0
- package/engine/api-contracts/pull-requests.js +323 -0
- package/engine/api-contracts/qa-process.js +764 -0
- package/engine/api-contracts/work-plan-prd.js +494 -0
- package/engine/api-validation.js +543 -0
- package/engine/cc-api-validation.js +586 -0
- package/engine/cc-worker-pool.js +9 -1
- package/engine/cleanup.js +393 -271
- package/engine/cli.js +65 -7
- package/engine/comment-classifier.js +70 -5
- package/engine/consolidation.js +123 -5
- package/engine/content-api-validation.js +606 -0
- package/engine/create-pr-worktree.js +22 -1
- package/engine/db/migrations/027-review-learning-lifecycle.js +162 -0
- package/engine/dispatch.js +33 -0
- package/engine/distribution.js +189 -0
- package/engine/execution-model.js +59 -0
- package/engine/features.js +12 -0
- package/engine/github.js +67 -25
- package/engine/harness-context.js +313 -0
- package/engine/inbox-store.js +1 -1
- package/engine/kb-sweep.js +161 -38
- package/engine/keep-process-sweep.js +43 -3
- package/engine/lifecycle.js +1066 -165
- package/engine/llm.js +4 -0
- package/engine/managed-spawn.js +463 -48
- package/engine/meeting.js +197 -34
- package/engine/memory-retrieval.js +280 -0
- package/engine/memory-store.js +821 -38
- package/engine/model-discovery.js +17 -3
- package/engine/pipeline.js +802 -14
- package/engine/plan-prd-validation.js +745 -0
- package/engine/playbook.js +206 -17
- package/engine/pooled-agent-process.js +6 -1
- package/engine/pr-action.js +1 -1
- package/engine/pr-issue-validation.js +653 -0
- package/engine/pr-resolve.js +75 -13
- package/engine/prd-store.js +39 -6
- package/engine/preflight.js +7 -2
- package/engine/process-utils.js +79 -31
- package/engine/projects.js +198 -12
- package/engine/promotion.js +371 -0
- package/engine/qa-from-prd.js +3 -0
- package/engine/qa-process-validation.js +572 -0
- package/engine/qa-runbooks.js +41 -37
- package/engine/qa-runners/maestro.js +11 -7
- package/engine/qa-runners/playwright.js +37 -14
- package/engine/qa-runners.js +9 -10
- package/engine/qa-runs.js +124 -11
- package/engine/qa-sessions.js +139 -40
- package/engine/quarantine-refs.js +81 -11
- package/engine/queries.js +163 -9
- package/engine/review-learning-backfill.js +482 -0
- package/engine/review-learning.js +1236 -0
- package/engine/runtimes/claude.js +60 -2
- package/engine/runtimes/codex.js +57 -16
- package/engine/runtimes/contract.js +21 -0
- package/engine/runtimes/copilot.js +51 -3
- package/engine/runtimes/index.js +1 -0
- package/engine/scheduler.js +201 -32
- package/engine/settings-validation.js +909 -0
- package/engine/shared.js +705 -208
- package/engine/spawn-agent.js +48 -21
- package/engine/steering-constraints.js +31 -0
- package/engine/steering-store.js +10 -2
- package/engine/steering.js +14 -2
- package/engine/timeout.js +404 -111
- package/engine/untrusted-fence.js +24 -10
- package/engine/watch-actions.js +7 -2
- package/engine/watches.js +353 -51
- package/engine/work-item-validation.js +561 -0
- package/engine/work-items-store.js +61 -1
- package/engine/worktree-gc.js +656 -37
- package/engine/worktree-preflight.js +154 -0
- package/engine.js +2068 -342
- package/package.json +1 -1
- package/playbooks/_pr-description-audit.md +88 -41
- package/playbooks/build-fix-complex.md +3 -3
- package/playbooks/docs.md +2 -2
- package/playbooks/fix.md +18 -18
- package/playbooks/implement.md +18 -18
- package/playbooks/qa-session-draft.md +10 -12
- package/playbooks/qa-session-execute.md +16 -12
- package/playbooks/qa-validate.md +8 -7
- package/playbooks/shared-rules.md +45 -0
- package/playbooks/test.md +4 -0
- package/prompts/cc-system.md +1 -1
- package/skills/check-self-authored-review-comment/SKILL.md +2 -2
package/engine/github.js
CHANGED
|
@@ -7,8 +7,14 @@
|
|
|
7
7
|
const shared = require('./shared');
|
|
8
8
|
const { exec, execAsync, getProjects, readWorkItems, readPullRequests, safeJson, safeJsonArr, safeWrite, mutateMetrics, mutatePullRequests, MINIONS_DIR, getPrLinks, backfillPrPrdItems, log, ts, dateStamp, PR_STATUS, PR_POLLABLE_STATUSES, BUILD_STATUS, REVIEW_STATUS, FETCH_TIMEOUT_MS, ENGINE_DEFAULTS, createThrottleTracker, getProjectOrg } = shared;
|
|
9
9
|
const { getPrs } = require('./queries');
|
|
10
|
-
const {
|
|
10
|
+
const {
|
|
11
|
+
isPreviewStatusBody,
|
|
12
|
+
hasMinionsMarker,
|
|
13
|
+
hasVerdictPrefix,
|
|
14
|
+
classifyNonActionableAutomationComment,
|
|
15
|
+
} = require('./comment-classifier');
|
|
11
16
|
const { wrapUntrusted, buildSource } = require('./untrusted-fence');
|
|
17
|
+
const reviewLearning = require('./review-learning');
|
|
12
18
|
const ghToken = require('./gh-token');
|
|
13
19
|
const path = require('path');
|
|
14
20
|
|
|
@@ -176,19 +182,16 @@ function _isNonActionableComment(c, config = {}) {
|
|
|
176
182
|
const ignoredAuthors = new Set((config.engine?.ignoredCommentAuthors || []).map(a => String(a).toLowerCase()));
|
|
177
183
|
const login = String(c?.user?.login || '').toLowerCase();
|
|
178
184
|
if (ignoredAuthors.has(login)) return true;
|
|
179
|
-
// W-mqr41te300026723:
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
// user.type==='bot' equivalent and relies solely on body-content + marker gates.
|
|
185
|
+
// W-mqr41te300026723: no blanket bot-author drop. Known automation noise
|
|
186
|
+
// requires a content signature; identity is consulted only for exact
|
|
187
|
+
// producer envelopes such as the visual-evidence GitHub Actions status.
|
|
183
188
|
if (_isAgentComment(c)) return true;
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
// bot-only signal exported for testing.
|
|
191
|
-
if (isPreviewStatusBody(c?.body)) return true;
|
|
189
|
+
const automationReason = classifyNonActionableAutomationComment({
|
|
190
|
+
host: 'github',
|
|
191
|
+
body: c?.body,
|
|
192
|
+
author: c?.user,
|
|
193
|
+
});
|
|
194
|
+
if (automationReason) return true;
|
|
192
195
|
if (_isMinionsAuthoredComment(c)) return true;
|
|
193
196
|
return false;
|
|
194
197
|
}
|
|
@@ -1259,7 +1262,18 @@ async function pollPrHumanComments(config) {
|
|
|
1259
1262
|
const allComments = [
|
|
1260
1263
|
...(comments || []).map(c => ({ ...c, _type: 'issue' })),
|
|
1261
1264
|
...(Array.isArray(reviewComments) ? reviewComments : []).map(c => ({ ...c, _type: 'review' }))
|
|
1262
|
-
];
|
|
1265
|
+
].filter(c => c?.id != null);
|
|
1266
|
+
for (const c of allComments) {
|
|
1267
|
+
c._commentSource = reviewLearning.buildReviewCommentSource({
|
|
1268
|
+
host: 'github',
|
|
1269
|
+
commentType: c._type,
|
|
1270
|
+
commentId: c.id,
|
|
1271
|
+
author: c.user?.login || 'Human',
|
|
1272
|
+
createdAt: c.created_at,
|
|
1273
|
+
updatedAt: c.updated_at || c.created_at,
|
|
1274
|
+
content: c.body || '',
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1263
1277
|
|
|
1264
1278
|
// Backfill viewerDidAuthor on each comment so _isMinionsAuthoredComment
|
|
1265
1279
|
// can actually fire on production traffic. No-op when GraphQL or a test
|
|
@@ -1274,17 +1288,23 @@ async function pollPrHumanComments(config) {
|
|
|
1274
1288
|
// timestamp differs from what we already saw are queued as new feedback so
|
|
1275
1289
|
// a follow-up fix re-dispatches. First-seen comments and re-polls of
|
|
1276
1290
|
// already-seen edits do NOT route through this path.
|
|
1277
|
-
const prevEditsSeen = (pr.humanFeedback && pr.humanFeedback.editsSeen) || {};
|
|
1278
1291
|
const editsInput = allComments.map(c => ({
|
|
1279
|
-
id: c.
|
|
1292
|
+
id: c._commentSource.key,
|
|
1280
1293
|
createdRaw: c.created_at,
|
|
1281
1294
|
updatedRaw: c.updated_at,
|
|
1282
1295
|
}));
|
|
1296
|
+
const persistedEditsSeen = pr.humanFeedback?.editsSeen || {};
|
|
1297
|
+
const prevEditsSeen = reviewLearning.migrateLegacyReviewCommentEdits(
|
|
1298
|
+
persistedEditsSeen,
|
|
1299
|
+
editsInput.map(entry => entry.id),
|
|
1300
|
+
);
|
|
1301
|
+
const editsMigrationChanged = JSON.stringify(prevEditsSeen) !== JSON.stringify(persistedEditsSeen);
|
|
1283
1302
|
// Sort chronologically by updated timestamp so FIFO cap eviction keeps the
|
|
1284
1303
|
// most-recently-edited entries (the ones most likely to be re-edited).
|
|
1285
1304
|
editsInput.sort((a, b) => String(a.updatedRaw || '').localeCompare(String(b.updatedRaw || '')));
|
|
1286
|
-
const { editsSeen, newlyEditedIds, changed
|
|
1305
|
+
const { editsSeen, newlyEditedIds, changed } =
|
|
1287
1306
|
shared.buildEditsSeen(editsInput, prevEditsSeen);
|
|
1307
|
+
const editsSeenChanged = changed || editsMigrationChanged;
|
|
1288
1308
|
|
|
1289
1309
|
// Collect comments that should advance the cutoff separately from comments
|
|
1290
1310
|
// that should dispatch a fix. Informational bot/status comments and
|
|
@@ -1292,7 +1312,6 @@ async function pollPrHumanComments(config) {
|
|
|
1292
1312
|
const allCommentDates = [];
|
|
1293
1313
|
const allCommentEntries = [];
|
|
1294
1314
|
const newComments = [];
|
|
1295
|
-
const seenInNewComments = new Set();
|
|
1296
1315
|
const nonActionableCommentKeys = new Set();
|
|
1297
1316
|
const nonActionableCommentIds = new Set();
|
|
1298
1317
|
|
|
@@ -1317,6 +1336,8 @@ async function pollPrHumanComments(config) {
|
|
|
1317
1336
|
author: c.user?.login || 'Human',
|
|
1318
1337
|
content: c.body || '',
|
|
1319
1338
|
date,
|
|
1339
|
+
createdAt: c.created_at || date,
|
|
1340
|
+
updatedAt: c.updated_at || c.created_at || date,
|
|
1320
1341
|
_isAgent: false,
|
|
1321
1342
|
// Issue #2994: namespace GH comment ids by API source ("issue" from
|
|
1322
1343
|
// `/issues/N/comments` vs "review" from `/pulls/N/comments`). Each
|
|
@@ -1327,17 +1348,20 @@ async function pollPrHumanComments(config) {
|
|
|
1327
1348
|
_type: c._type,
|
|
1328
1349
|
};
|
|
1329
1350
|
entry.commentKey = _entryCommentKey(entry);
|
|
1351
|
+
entry.commentSource = c._commentSource;
|
|
1330
1352
|
allCommentEntries.push(entry);
|
|
1331
1353
|
|
|
1332
1354
|
if (dateMs && dateMs > cutoffMs) {
|
|
1333
1355
|
newComments.push(entry);
|
|
1334
|
-
|
|
1335
|
-
} else if (c.id != null && newlyEditedIds.has(String(c.id))) {
|
|
1356
|
+
} else if (newlyEditedIds.has(entry.commentSource.key)) {
|
|
1336
1357
|
// F6: pre-cutoff comment that was newly edited — route into newComments
|
|
1337
1358
|
// so a fresh fix dispatches, but tag with the edit timestamp so the
|
|
1338
1359
|
// fix-agent prompt context flags it as new.
|
|
1339
|
-
|
|
1340
|
-
|
|
1360
|
+
entry._isEditedRedispatch = true;
|
|
1361
|
+
newComments.push({
|
|
1362
|
+
...entry,
|
|
1363
|
+
date: editsSeen[entry.commentSource.key] || entry.commentSource.updatedAt || date,
|
|
1364
|
+
});
|
|
1341
1365
|
}
|
|
1342
1366
|
}
|
|
1343
1367
|
|
|
@@ -1348,6 +1372,15 @@ async function pollPrHumanComments(config) {
|
|
|
1348
1372
|
newComments.sort((a, b) => a.date.localeCompare(b.date));
|
|
1349
1373
|
const latestActionableComment = allCommentEntries[allCommentEntries.length - 1] || null;
|
|
1350
1374
|
const currentFeedback = pr.humanFeedback || {};
|
|
1375
|
+
const observedCommentSources = reviewLearning.normalizeReviewCommentSources(
|
|
1376
|
+
allCommentEntries.map(entry => entry.commentSource),
|
|
1377
|
+
);
|
|
1378
|
+
const commentSources = reviewLearning.selectReviewCommentSourceSnapshot(
|
|
1379
|
+
currentFeedback,
|
|
1380
|
+
observedCommentSources,
|
|
1381
|
+
);
|
|
1382
|
+
const commentSourcesChanged = JSON.stringify(commentSources)
|
|
1383
|
+
!== JSON.stringify(reviewLearning.normalizeReviewCommentSources(currentFeedback.commentSources));
|
|
1351
1384
|
const currentCommentKey = String(currentFeedback.lastProcessedCommentKey || '');
|
|
1352
1385
|
const currentCommentId = String(currentFeedback.lastProcessedCommentId || '');
|
|
1353
1386
|
const currentCauseIsNonActionable = !!(
|
|
@@ -1366,6 +1399,7 @@ async function pollPrHumanComments(config) {
|
|
|
1366
1399
|
delete nextFeedback.lastProcessedCommentId;
|
|
1367
1400
|
delete nextFeedback.lastProcessedCommentKey;
|
|
1368
1401
|
delete nextFeedback.feedbackContent;
|
|
1402
|
+
delete nextFeedback.commentSources;
|
|
1369
1403
|
}
|
|
1370
1404
|
return true;
|
|
1371
1405
|
};
|
|
@@ -1376,12 +1410,15 @@ async function pollPrHumanComments(config) {
|
|
|
1376
1410
|
pr.humanFeedback = { ...currentFeedback, lastProcessedCommentDate: allNewDates.sort().pop() };
|
|
1377
1411
|
repairNonActionableCause(pr.humanFeedback);
|
|
1378
1412
|
if (editsSeenChanged) pr.humanFeedback.editsSeen = editsSeen;
|
|
1413
|
+
if (commentSources.length > 0) pr.humanFeedback.commentSources = commentSources;
|
|
1379
1414
|
return true; // non-actionable comments only — persist cutoff without triggering fix
|
|
1380
1415
|
}
|
|
1381
1416
|
if (newComments.length === 0) {
|
|
1382
|
-
if (currentCauseIsNonActionable) {
|
|
1417
|
+
if (currentCauseIsNonActionable || commentSourcesChanged) {
|
|
1383
1418
|
pr.humanFeedback = { ...currentFeedback };
|
|
1384
1419
|
repairNonActionableCause(pr.humanFeedback);
|
|
1420
|
+
if (commentSources.length > 0) pr.humanFeedback.commentSources = commentSources;
|
|
1421
|
+
else delete pr.humanFeedback.commentSources;
|
|
1385
1422
|
if (editsSeenChanged) pr.humanFeedback.editsSeen = editsSeen;
|
|
1386
1423
|
return true;
|
|
1387
1424
|
}
|
|
@@ -1403,14 +1440,18 @@ async function pollPrHumanComments(config) {
|
|
|
1403
1440
|
// attacker-controlled part) lands inside.
|
|
1404
1441
|
const feedbackContent = allCommentEntries
|
|
1405
1442
|
.map(c => {
|
|
1406
|
-
const isNew = (new Date(c.date).getTime() || 0) > cutoffMs;
|
|
1443
|
+
const isNew = c._isEditedRedispatch || (new Date(c.date).getTime() || 0) > cutoffMs;
|
|
1444
|
+
const displayDate = c._isEditedRedispatch
|
|
1445
|
+
? (c.commentSource.updatedAt || c.updatedAt || c.date)
|
|
1446
|
+
: c.date;
|
|
1407
1447
|
const cleanedBody = String(c.content || '').replace(/@minions\s*/gi, '').trim();
|
|
1408
1448
|
const source = buildSource('pr-comment', {
|
|
1409
1449
|
host: 'gh', slug, number: prNum, author: c.author || 'unknown',
|
|
1450
|
+
key: c.commentSource.key,
|
|
1410
1451
|
});
|
|
1411
1452
|
const fenced = wrapUntrusted(cleanedBody, source);
|
|
1412
1453
|
const bodyForPrompt = fenced || cleanedBody;
|
|
1413
|
-
return `${isNew ? '**[NEW]** ' : ''}**${c.author}** (${
|
|
1454
|
+
return `${isNew ? '**[NEW]** ' : ''}**${c.author}** (${displayDate}):\n${bodyForPrompt}`;
|
|
1414
1455
|
})
|
|
1415
1456
|
.join('\n\n---\n\n');
|
|
1416
1457
|
|
|
@@ -1426,6 +1467,7 @@ async function pollPrHumanComments(config) {
|
|
|
1426
1467
|
lastProcessedCommentKey: newComments[newComments.length - 1].commentKey || _entryCommentKey(newComments[newComments.length - 1]),
|
|
1427
1468
|
pendingFix: true,
|
|
1428
1469
|
feedbackContent,
|
|
1470
|
+
commentSources: observedCommentSources,
|
|
1429
1471
|
editsSeen,
|
|
1430
1472
|
};
|
|
1431
1473
|
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* engine/harness-context.js — "Repo harnesses used" PR-body section.
|
|
3
|
+
*
|
|
4
|
+
* W-mrtdmtwq000kf391. When Minions opens/updates a PR for a dispatch, we surface
|
|
5
|
+
* the repo HARNESS assets that were actually USED by THAT dispatch's runtime +
|
|
6
|
+
* project, so the PR reviewer can see which CLAUDE.md / AGENTS.md / MCP / skill
|
|
7
|
+
* context the agent actually ran with. The report is the intersection of
|
|
8
|
+
* "configured" and "actually applied by the resolved runtime" — categories that
|
|
9
|
+
* produced nothing are omitted entirely (no "none" filler), and the rendered
|
|
10
|
+
* block is kept compact (a short bullet list, no preamble).
|
|
11
|
+
*
|
|
12
|
+
* This module is pure REPORTING. It reuses the same read-only harness-diagnostics
|
|
13
|
+
* source that backs `GET /api/harness/diagnostics` and `minions doctor --harness`
|
|
14
|
+
* (engine/preflight.js `_runtimeHarnessRows`) plus the CLAUDE.md propagation
|
|
15
|
+
* discovery (engine/claude-md-context.js). It never propagates, injects,
|
|
16
|
+
* suppresses, or attests any repository harness asset — the runtime-adapter
|
|
17
|
+
* harness methods are diagnostic-only (see docs/harness-propagation.md).
|
|
18
|
+
*
|
|
19
|
+
* engine/playbook.js renders the section into a dispatch prompt appendix that
|
|
20
|
+
* instructs the agent to append the fenced block verbatim to its PR body. The
|
|
21
|
+
* block is delimited by stable HTML-comment markers so a PR-description refresh
|
|
22
|
+
* (the description audit) can replace it in place instead of duplicating it.
|
|
23
|
+
*
|
|
24
|
+
* No engine state, no locking, no writes.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const fs = require('fs');
|
|
28
|
+
const os = require('os');
|
|
29
|
+
const path = require('path');
|
|
30
|
+
|
|
31
|
+
const HARNESS_SECTION_HEADING = '## Repo harnesses used';
|
|
32
|
+
const HARNESS_BLOCK_START = '<!-- minions:harnesses:start -->';
|
|
33
|
+
const HARNESS_BLOCK_END = '<!-- minions:harnesses:end -->';
|
|
34
|
+
|
|
35
|
+
// Defensive caps so a pathological skills dir or MCP config can never balloon
|
|
36
|
+
// the PR body / prompt appendix.
|
|
37
|
+
const MAX_NAMES = 25;
|
|
38
|
+
|
|
39
|
+
function _existing(rows) {
|
|
40
|
+
return (Array.isArray(rows) ? rows : []).filter(r => r && r.exists && r.path);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Enumerate skill NAMES: each immediate subdirectory of an existing skill root
|
|
44
|
+
// is one skill (matches how Claude/Copilot/Codex discover skills from a root).
|
|
45
|
+
function _listSkillNames(rows, existsFn) {
|
|
46
|
+
const names = new Set();
|
|
47
|
+
for (const r of _existing(rows)) {
|
|
48
|
+
if (typeof existsFn === 'function' && !existsFn(r.path)) continue;
|
|
49
|
+
let entries;
|
|
50
|
+
try { entries = fs.readdirSync(r.path, { withFileTypes: true }); }
|
|
51
|
+
catch { continue; }
|
|
52
|
+
for (const ent of entries) {
|
|
53
|
+
try { if (ent.isDirectory()) names.add(ent.name); } catch { /* ignore */ }
|
|
54
|
+
if (names.size >= MAX_NAMES) break;
|
|
55
|
+
}
|
|
56
|
+
if (names.size >= MAX_NAMES) break;
|
|
57
|
+
}
|
|
58
|
+
return [...names].sort().slice(0, MAX_NAMES);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Does a native-discovery runtime actually SEE an AGENTS.md this dispatch?
|
|
62
|
+
// Copilot/Codex walk up from their cwd (the dispatch worktree) to find the
|
|
63
|
+
// nearest AGENTS.md. We report the category only when one actually applies —
|
|
64
|
+
// checking the worktree root plus any dispatch path hints — so the block never
|
|
65
|
+
// claims discovery that produced nothing.
|
|
66
|
+
function _agentsMdApplied({ scanRoot, pathHints = [], existsFn = fs.existsSync }) {
|
|
67
|
+
if (!scanRoot) return false;
|
|
68
|
+
const dirs = new Set([scanRoot]);
|
|
69
|
+
for (const h of (Array.isArray(pathHints) ? pathHints : [])) {
|
|
70
|
+
if (h && typeof h === 'string') {
|
|
71
|
+
try { dirs.add(path.resolve(scanRoot, h)); } catch { /* ignore bad hint */ }
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
for (const d of dirs) {
|
|
75
|
+
try { if (existsFn(path.join(d, 'AGENTS.md'))) return true; } catch { /* ignore */ }
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Enumerate MCP server NAMES by reading the keys of each existing MCP config
|
|
81
|
+
// file (the cross-runtime `mcpServers` / `servers` / `mcp` object).
|
|
82
|
+
function _listMcpServerNames(rows) {
|
|
83
|
+
const names = new Set();
|
|
84
|
+
for (const r of _existing(rows)) {
|
|
85
|
+
let json;
|
|
86
|
+
try { json = JSON.parse(fs.readFileSync(r.path, 'utf8')); }
|
|
87
|
+
catch { continue; }
|
|
88
|
+
const servers = (json && (json.mcpServers || json.servers || json.mcp)) || {};
|
|
89
|
+
if (servers && typeof servers === 'object' && !Array.isArray(servers)) {
|
|
90
|
+
for (const k of Object.keys(servers)) {
|
|
91
|
+
names.add(k);
|
|
92
|
+
if (names.size >= MAX_NAMES) break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (names.size >= MAX_NAMES) break;
|
|
96
|
+
}
|
|
97
|
+
return [...names].sort().slice(0, MAX_NAMES);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Compute the harness inventory for a dispatch's runtime + project.
|
|
102
|
+
*
|
|
103
|
+
* The inventory reflects the RESOLVED dispatch — the runtime's actual
|
|
104
|
+
* invocation options and the actual dispatched checkout — not static
|
|
105
|
+
* discovery. Callers thread the values the engine already resolved so the
|
|
106
|
+
* report never over-claims context the runtime never saw:
|
|
107
|
+
* - `bareMode` — a `--bare` invocation strips the runtime's native
|
|
108
|
+
* repo-context discovery (CLAUDE.md/AGENTS.md).
|
|
109
|
+
* - `propagateClaudeMd` — the engine only injects CLAUDE.md into non-native
|
|
110
|
+
* runtimes when this resolves true.
|
|
111
|
+
* - `claudeMdInjectedFiles`— the authoritative list of files the engine
|
|
112
|
+
* actually injected this dispatch (preferred over
|
|
113
|
+
* re-discovery, which can diverge).
|
|
114
|
+
* - `worktreePath` — the actual dispatch checkout; project-scoped MCP /
|
|
115
|
+
* skill discovery scans THIS, not the operator
|
|
116
|
+
* checkout (`project.localPath`) the runtime never ran in.
|
|
117
|
+
*
|
|
118
|
+
* @param {object} opts
|
|
119
|
+
* @param {string} opts.runtimeCli Resolved runtime name (copilot|claude|codex).
|
|
120
|
+
* @param {object} [opts.project] Project object with `localPath` / `name`.
|
|
121
|
+
* @param {string} [opts.worktreePath] Dispatch checkout root for project-scoped scan.
|
|
122
|
+
* @param {boolean} [opts.bareMode] Resolved `--bare` invocation option.
|
|
123
|
+
* @param {boolean} [opts.nativeClaudeMd] Runtime's CLAUDE.md native-discovery
|
|
124
|
+
* capability (derived from the adapter when omitted).
|
|
125
|
+
* @param {string[]} [opts.claudeMdInjectedFiles] Authoritative injected-file result.
|
|
126
|
+
* @param {boolean} [opts.propagateClaudeMd] Resolved propagation flag (default true).
|
|
127
|
+
* @param {string[]} [opts.pathHints] CLAUDE.md discovery hints (fallback only).
|
|
128
|
+
* @param {string} [opts.homeDir] Override for tests.
|
|
129
|
+
* @param {function} [opts.existsFn] Override for tests.
|
|
130
|
+
* The returned inventory reports only harnesses ACTUALLY applied by the resolved
|
|
131
|
+
* runtime for this dispatch — the intersection of "configured" and "in effect".
|
|
132
|
+
* Categories that produced nothing are left empty and the renderer omits them.
|
|
133
|
+
*
|
|
134
|
+
* Skills are the exception: the engine cannot know at prompt-render time WHICH
|
|
135
|
+
* of the available project skills the agent will actually invoke, and listing
|
|
136
|
+
* every discovered/available skill is exactly the "discovered not used" bug the
|
|
137
|
+
* PR-body block must avoid. So `availableSkills` is a REFERENCE list (project
|
|
138
|
+
* scope only, BLOCKING-3 no user-scope leak) that the playbook hands to the
|
|
139
|
+
* agent; the agent reports the subset it actually used. It is intentionally NOT
|
|
140
|
+
* rendered into the block by `buildHarnessSection`.
|
|
141
|
+
*
|
|
142
|
+
* @returns {{runtime:string, claudeMd:{native:boolean, files:string[]},
|
|
143
|
+
* agentsMd:boolean, mcp:string[], availableSkills:string[]}}
|
|
144
|
+
*/
|
|
145
|
+
function computeDispatchHarnessInventory({
|
|
146
|
+
runtimeCli,
|
|
147
|
+
project = null,
|
|
148
|
+
worktreePath = '',
|
|
149
|
+
bareMode = false,
|
|
150
|
+
nativeClaudeMd = undefined,
|
|
151
|
+
claudeMdInjectedFiles = undefined,
|
|
152
|
+
propagateClaudeMd = true,
|
|
153
|
+
pathHints = [],
|
|
154
|
+
homeDir = os.homedir(),
|
|
155
|
+
existsFn = fs.existsSync,
|
|
156
|
+
} = {}) {
|
|
157
|
+
const runtime = String(runtimeCli || '').trim() || 'copilot';
|
|
158
|
+
const inv = {
|
|
159
|
+
runtime,
|
|
160
|
+
claudeMd: { native: false, files: [] },
|
|
161
|
+
agentsMd: false,
|
|
162
|
+
mcp: [],
|
|
163
|
+
availableSkills: [],
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
let claudeMdCtx = null;
|
|
167
|
+
let preflight = null;
|
|
168
|
+
let registry = null;
|
|
169
|
+
try { claudeMdCtx = require('./claude-md-context'); } catch { /* optional */ }
|
|
170
|
+
try { preflight = require('./preflight'); } catch { /* optional */ }
|
|
171
|
+
try { registry = require('./runtimes'); } catch { /* optional */ }
|
|
172
|
+
|
|
173
|
+
// CLAUDE.md discovery is a runtime CAPABILITY (Claude reads CLAUDE.md itself;
|
|
174
|
+
// Copilot/Codex do not). Prefer the caller-resolved value; fall back to the
|
|
175
|
+
// adapter capability for callers that don't thread it.
|
|
176
|
+
const nativeCapable = (typeof nativeClaudeMd === 'boolean')
|
|
177
|
+
? nativeClaudeMd
|
|
178
|
+
: (claudeMdCtx ? !!claudeMdCtx.runtimeAutoDiscoversClaudeMd(runtime) : false);
|
|
179
|
+
|
|
180
|
+
// A `--bare` invocation strips the runtime's native repo-context discovery, so
|
|
181
|
+
// native CLAUDE.md discovery is only in effect when the runtime is capable AND
|
|
182
|
+
// not running bare.
|
|
183
|
+
inv.claudeMd.native = nativeCapable && !bareMode;
|
|
184
|
+
|
|
185
|
+
if (!nativeCapable) {
|
|
186
|
+
// Copilot/Codex: the engine's bounded propagation injects CLAUDE.md only
|
|
187
|
+
// when the propagation flag is on. Prefer the authoritative injected-file
|
|
188
|
+
// result the engine actually produced this dispatch; fall back to discovery
|
|
189
|
+
// (under the same propagate gate) for callers that don't thread it. Scan the
|
|
190
|
+
// dispatch worktree, not the operator checkout the runtime never ran in.
|
|
191
|
+
if (propagateClaudeMd) {
|
|
192
|
+
if (Array.isArray(claudeMdInjectedFiles)) {
|
|
193
|
+
inv.claudeMd.files = claudeMdInjectedFiles.slice(0, MAX_NAMES);
|
|
194
|
+
} else if (claudeMdCtx) {
|
|
195
|
+
const scanRoot = worktreePath || (project && project.localPath) || '';
|
|
196
|
+
if (scanRoot) {
|
|
197
|
+
try {
|
|
198
|
+
const files = claudeMdCtx.discoverClaudeMdFiles({
|
|
199
|
+
projectRoot: scanRoot,
|
|
200
|
+
pathHints: Array.isArray(pathHints) ? pathHints : [],
|
|
201
|
+
});
|
|
202
|
+
inv.claudeMd.files = files.map(f => f.relPath);
|
|
203
|
+
} catch { /* degrade to none */ }
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// AGENTS.md: the runtimes that do NOT read CLAUDE.md natively (Copilot/Codex)
|
|
210
|
+
// DO discover AGENTS.md natively; Claude reads CLAUDE.md instead. Report the
|
|
211
|
+
// category only when the resolved runtime performs the discovery AND an
|
|
212
|
+
// AGENTS.md actually applies in the dispatch checkout the runtime ran in —
|
|
213
|
+
// never as a bare capability claim that produced nothing.
|
|
214
|
+
inv.agentsMd = nativeCapable
|
|
215
|
+
? false
|
|
216
|
+
: _agentsMdApplied({
|
|
217
|
+
scanRoot: worktreePath || (project && project.localPath) || '',
|
|
218
|
+
pathHints: Array.isArray(pathHints) ? pathHints : [],
|
|
219
|
+
existsFn,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// MCP + skills: reuse the diagnostic harness-rows source, scoped to this
|
|
223
|
+
// single project's ACTUAL checkout (the worktree), and enumerate the concrete
|
|
224
|
+
// names present on disk. Only PROJECT-scoped rows are reported — user-scoped
|
|
225
|
+
// (personal) MCP/skill directories are NOT disclosed in a public PR body, as
|
|
226
|
+
// they can leak private integration/client identifiers (e.g. a personal skill
|
|
227
|
+
// dir named `private-client-alpha`).
|
|
228
|
+
//
|
|
229
|
+
// MCP servers configured in the project are loaded/available to the runtime,
|
|
230
|
+
// so they are reported directly. Skills, by contrast, are only AVAILABLE — the
|
|
231
|
+
// engine can't know which the agent invokes — so they populate `availableSkills`
|
|
232
|
+
// (a reference the playbook hands to the agent) and are NOT rendered into the
|
|
233
|
+
// block; the agent reports the subset it actually used.
|
|
234
|
+
let adapter = null;
|
|
235
|
+
if (registry) { try { adapter = registry.resolveRuntime(runtime); } catch { adapter = null; } }
|
|
236
|
+
if (preflight && adapter && typeof preflight._runtimeHarnessRows === 'function') {
|
|
237
|
+
const scanProject = project
|
|
238
|
+
? (worktreePath ? { ...project, localPath: worktreePath } : project)
|
|
239
|
+
: null;
|
|
240
|
+
try {
|
|
241
|
+
const rows = preflight._runtimeHarnessRows(adapter, {
|
|
242
|
+
homeDir,
|
|
243
|
+
projects: scanProject ? [scanProject] : [],
|
|
244
|
+
existsFn,
|
|
245
|
+
});
|
|
246
|
+
inv.mcp = _listMcpServerNames(rows.mcpConfigProject || []);
|
|
247
|
+
inv.availableSkills = _listSkillNames(rows.skillRootsProject || [], existsFn);
|
|
248
|
+
} catch { /* degrade to empty */ }
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return inv;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Render the fenced "Repo harnesses used" markdown block from an inventory.
|
|
256
|
+
* Compact and used-only: the Runtime line always anchors the block, and every
|
|
257
|
+
* other engine-determinable category is emitted ONLY when it actually applied —
|
|
258
|
+
* no "none"/"n/a" filler rows and no explanatory preamble.
|
|
259
|
+
*
|
|
260
|
+
* Skills are deliberately NOT rendered here. The engine cannot know which of the
|
|
261
|
+
* available project skills the agent actually invoked, and enumerating all of
|
|
262
|
+
* them is the "discovered not used" bug this block must avoid. The playbook
|
|
263
|
+
* instructs the agent to add a `- **Skills used:** …` line for the skills it
|
|
264
|
+
* actually used (from `inventory.availableSkills`), or omit it entirely.
|
|
265
|
+
*
|
|
266
|
+
* The HTML-comment markers make the block idempotently replaceable when a PR
|
|
267
|
+
* body is refreshed.
|
|
268
|
+
*/
|
|
269
|
+
function buildHarnessSection(inventory) {
|
|
270
|
+
const inv = inventory || {};
|
|
271
|
+
const claudeMd = inv.claudeMd || { native: false, files: [] };
|
|
272
|
+
|
|
273
|
+
const lines = [
|
|
274
|
+
HARNESS_SECTION_HEADING,
|
|
275
|
+
'',
|
|
276
|
+
HARNESS_BLOCK_START,
|
|
277
|
+
`- **Runtime:** ${inv.runtime || 'unknown'}`,
|
|
278
|
+
];
|
|
279
|
+
|
|
280
|
+
if (claudeMd.native) {
|
|
281
|
+
lines.push('- **CLAUDE.md:** native (claude)');
|
|
282
|
+
} else if (Array.isArray(claudeMd.files) && claudeMd.files.length) {
|
|
283
|
+
lines.push(`- **CLAUDE.md:** ${claudeMd.files.join(', ')}`);
|
|
284
|
+
}
|
|
285
|
+
if (inv.agentsMd) {
|
|
286
|
+
lines.push('- **AGENTS.md:** discovered');
|
|
287
|
+
}
|
|
288
|
+
if (Array.isArray(inv.mcp) && inv.mcp.length) {
|
|
289
|
+
lines.push(`- **MCP:** ${inv.mcp.join(', ')}`);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
lines.push(HARNESS_BLOCK_END);
|
|
293
|
+
return lines.join('\n');
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Convenience: compute the inventory for a dispatch and render the section.
|
|
298
|
+
* Returns `{ inventory, section }`. `section` is always a non-empty string.
|
|
299
|
+
*/
|
|
300
|
+
function renderHarnessSectionForDispatch(opts = {}) {
|
|
301
|
+
const inventory = computeDispatchHarnessInventory(opts);
|
|
302
|
+
return { inventory, section: buildHarnessSection(inventory) };
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
module.exports = {
|
|
306
|
+
HARNESS_SECTION_HEADING,
|
|
307
|
+
HARNESS_BLOCK_START,
|
|
308
|
+
HARNESS_BLOCK_END,
|
|
309
|
+
MAX_NAMES,
|
|
310
|
+
computeDispatchHarnessInventory,
|
|
311
|
+
buildHarnessSection,
|
|
312
|
+
renderHarnessSectionForDispatch,
|
|
313
|
+
};
|
package/engine/inbox-store.js
CHANGED
|
@@ -239,7 +239,7 @@ function deleteInboxEntriesOlderThan(timestampMs, { unreadOnly = false } = {}) {
|
|
|
239
239
|
query += ' AND unread = 1';
|
|
240
240
|
}
|
|
241
241
|
const result = db.prepare(query).run(...params);
|
|
242
|
-
if (result.changes > 0) _emitInboxEvent({ timestampMs, unreadOnly, action: 'delete_older_than' });
|
|
242
|
+
if (result.changes > 0) _emitInboxEvent({ timestampMs, unreadOnly, action: 'delete_older_than', deletedCount: result.changes });
|
|
243
243
|
return { wrote: result.changes > 0, deletedCount: result.changes };
|
|
244
244
|
});
|
|
245
245
|
}
|