@yemi33/minions 0.1.2369 → 0.1.2371
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/minions.js +38 -11
- package/dashboard/js/memory-search.js +112 -0
- package/dashboard/js/render-kb.js +15 -0
- package/dashboard/js/render-other.js +2 -30
- package/dashboard/js/render-work-items.js +0 -34
- package/dashboard/js/settings.js +27 -27
- package/dashboard/pages/inbox.html +1 -1
- package/dashboard/pages/tools.html +1 -1
- package/dashboard.js +148 -258
- package/docs/README.md +2 -3
- package/docs/architecture.excalidraw +2 -2
- package/docs/auto-discovery.md +3 -3
- package/docs/completion-reports.md +0 -121
- package/docs/copilot-cli-schema.md +9 -17
- package/docs/deprecated.json +0 -51
- package/docs/design-state-storage.md +4 -4
- package/docs/harness-propagation.md +33 -263
- package/docs/human-vs-automated.md +1 -1
- package/docs/named-agents.md +0 -2
- package/docs/plan-lifecycle.md +5 -6
- package/docs/qa-runbook-lifecycle.md +10 -25
- package/docs/shared-lifecycle-module-map.md +2 -10
- package/docs/team-memory.md +18 -4
- package/engine/ado-comment.js +5 -11
- package/engine/ado.js +10 -5
- package/engine/cleanup.js +16 -36
- package/engine/cli.js +13 -175
- package/engine/comment-format.js +8 -182
- package/engine/consolidation.js +72 -1
- package/engine/db/index.js +60 -10
- package/engine/db/migrations/017-agent-memory.js +208 -0
- package/engine/db/migrations/018-sql-only-cutover.js +56 -0
- package/engine/dispatch-store.js +0 -114
- package/engine/dispatch.js +1 -6
- package/engine/features.js +6 -0
- package/engine/gh-comment.js +2 -10
- package/engine/github.js +8 -6
- package/engine/lifecycle.js +141 -173
- package/engine/llm.js +9 -11
- package/engine/managed-spawn.js +1 -6
- package/engine/memory-retrieval.js +165 -0
- package/engine/memory-store.js +367 -0
- package/engine/metrics-store.js +4 -128
- package/engine/pipeline.js +4 -7
- package/engine/playbook.js +64 -198
- package/engine/prd-store.js +115 -141
- package/engine/preflight.js +8 -55
- package/engine/projects.js +34 -41
- package/engine/pull-requests-store.js +9 -234
- package/engine/qa-runs.js +4 -15
- package/engine/qa-sessions.js +5 -17
- package/engine/queries.js +23 -103
- package/engine/routing.js +1 -1
- package/engine/runtimes/claude.js +1 -2
- package/engine/runtimes/codex.js +0 -2
- package/engine/runtimes/copilot.js +1 -4
- package/engine/shared-branch-pr-reconcile.js +2 -5
- package/engine/shared.js +179 -533
- package/engine/small-state-store.js +12 -647
- package/engine/spawn-agent.js +5 -96
- package/engine/state-operations.js +166 -0
- package/engine/supervisor.js +0 -1
- package/engine/watch-actions.js +2 -3
- package/engine/watches-store.js +2 -128
- package/engine/work-items-store.js +3 -254
- package/engine.js +102 -334
- package/package.json +2 -2
- package/playbooks/build-fix-complex.md +0 -4
- package/playbooks/fix.md +0 -4
- package/playbooks/implement-shared.md +0 -4
- package/playbooks/implement.md +0 -4
- package/playbooks/plan-to-prd.md +16 -11
- package/playbooks/plan.md +0 -4
- package/playbooks/review.md +0 -6
- package/playbooks/shared-rules.md +0 -65
- package/docs/harness-transparency.md +0 -199
- package/docs/project-skills.md +0 -193
- package/engine/discover-project-skills.js +0 -673
- package/engine/playbook-intents.js +0 -76
package/engine/metrics-store.js
CHANGED
|
@@ -10,17 +10,6 @@
|
|
|
10
10
|
// applyMetricsMutation(fn) -> mutator receives + returns the
|
|
11
11
|
// legacy-shape object; diff-then-apply
|
|
12
12
|
// into per-row UPSERT/DELETE
|
|
13
|
-
// _mirrorJsonFromSql(filePath) -> writes the SQL state back to
|
|
14
|
-
// metrics.json for legacy direct-readers
|
|
15
|
-
//
|
|
16
|
-
// Like work-items-store and pull-requests-store, an external-edit
|
|
17
|
-
// detection layer (mtime tracking + resync) catches direct fs.writeFile
|
|
18
|
-
// edits from tests or operators so the next read/mutation rebuilds SQL
|
|
19
|
-
// from the JSON canonical state.
|
|
20
|
-
|
|
21
|
-
const fs = require('fs');
|
|
22
|
-
const path = require('path');
|
|
23
|
-
|
|
24
13
|
// Reserved top-level keys → kind sentinel in the table.
|
|
25
14
|
const RESERVED_KEYS = new Map([
|
|
26
15
|
['_engine', 'engine_category'],
|
|
@@ -33,50 +22,12 @@ const RESERVED_KEY_BY_KIND = new Map([
|
|
|
33
22
|
['context_pressure', '_contextPressure'],
|
|
34
23
|
]);
|
|
35
24
|
|
|
36
|
-
function _metricsFilePath() {
|
|
37
|
-
const shared = require('./shared');
|
|
38
|
-
return path.join(shared.MINIONS_DIR, 'engine', 'metrics.json');
|
|
39
|
-
}
|
|
40
|
-
|
|
41
25
|
function _parseRow(row) {
|
|
42
26
|
if (!row || !row.data) return null;
|
|
43
27
|
try { return JSON.parse(row.data); }
|
|
44
28
|
catch { return null; }
|
|
45
29
|
}
|
|
46
30
|
|
|
47
|
-
function _readJsonObjectFallback() {
|
|
48
|
-
const fp = _metricsFilePath();
|
|
49
|
-
let raw;
|
|
50
|
-
try { raw = fs.readFileSync(fp, 'utf8'); }
|
|
51
|
-
catch { return {}; }
|
|
52
|
-
try {
|
|
53
|
-
const parsed = JSON.parse(raw);
|
|
54
|
-
return (parsed && typeof parsed === 'object') ? parsed : {};
|
|
55
|
-
} catch (e) {
|
|
56
|
-
try {
|
|
57
|
-
// eslint-disable-next-line no-console
|
|
58
|
-
console.warn(`[metrics-store] corrupt JSON in ${fp}: ${e.message}`);
|
|
59
|
-
} catch { /* console may be wrapped */ }
|
|
60
|
-
return {};
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Content-hash fingerprint of the JSON mirror so external edits are
|
|
65
|
-
// detected unambiguously. (mtime, size) is too coarse: ISO timestamps
|
|
66
|
-
// have fixed byte length, so a `last_*` swap inside the same ms tick
|
|
67
|
-
// preserves both axes. SHA-1 on a few-KB file is sub-ms.
|
|
68
|
-
let _lastMirrorHash = null;
|
|
69
|
-
|
|
70
|
-
const crypto = require('crypto');
|
|
71
|
-
|
|
72
|
-
function _fileContentHash(filePath) {
|
|
73
|
-
try {
|
|
74
|
-
const buf = fs.readFileSync(filePath);
|
|
75
|
-
return crypto.createHash('sha1').update(buf).digest('hex');
|
|
76
|
-
}
|
|
77
|
-
catch { return null; }
|
|
78
|
-
}
|
|
79
|
-
|
|
80
31
|
// Convert legacy-shape metrics object into (kind, key, data) rows.
|
|
81
32
|
function _flatten(metrics) {
|
|
82
33
|
const out = [];
|
|
@@ -120,38 +71,6 @@ function _inflate(rows) {
|
|
|
120
71
|
return out;
|
|
121
72
|
}
|
|
122
73
|
|
|
123
|
-
function _hydrateFromJson(db) {
|
|
124
|
-
const json = _readJsonObjectFallback();
|
|
125
|
-
db.prepare('DELETE FROM metrics').run();
|
|
126
|
-
const rows = _flatten(json);
|
|
127
|
-
if (rows.length === 0) return;
|
|
128
|
-
const now = Date.now();
|
|
129
|
-
const ins = db.prepare(`
|
|
130
|
-
INSERT INTO metrics (kind, key, data, updated_at)
|
|
131
|
-
VALUES (?, ?, ?, ?)
|
|
132
|
-
ON CONFLICT(kind, key) DO NOTHING
|
|
133
|
-
`);
|
|
134
|
-
for (const { kind, key, value } of rows) {
|
|
135
|
-
ins.run(kind, key, JSON.stringify(value), now);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function _resyncIfJsonDiverged(db) {
|
|
140
|
-
const jsonPath = _metricsFilePath();
|
|
141
|
-
const currentHash = _fileContentHash(jsonPath);
|
|
142
|
-
if (currentHash == null) return;
|
|
143
|
-
if (_lastMirrorHash != null && currentHash === _lastMirrorHash) return;
|
|
144
|
-
if (_lastMirrorHash == null) {
|
|
145
|
-
const sqlHas = db.prepare('SELECT 1 FROM metrics LIMIT 1').get();
|
|
146
|
-
if (sqlHas) {
|
|
147
|
-
_lastMirrorHash = currentHash;
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
_hydrateFromJson(db);
|
|
152
|
-
_lastMirrorHash = currentHash;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
74
|
function _readAllRows(db) {
|
|
156
75
|
const raw = db.prepare('SELECT kind, key, data FROM metrics ORDER BY rowid').all();
|
|
157
76
|
const rows = [];
|
|
@@ -163,26 +82,15 @@ function _readAllRows(db) {
|
|
|
163
82
|
return rows;
|
|
164
83
|
}
|
|
165
84
|
|
|
166
|
-
// Read the full legacy-shape metrics object
|
|
167
|
-
// is empty AND JSON has content (mirrors the work-items-store fallback
|
|
168
|
-
// for fresh installs + legacy test seeds).
|
|
85
|
+
// Read the full legacy-shape metrics object from SQL.
|
|
169
86
|
function readMetrics() {
|
|
170
87
|
const { getDb } = require('./db');
|
|
171
|
-
|
|
172
|
-
try { db = getDb(); }
|
|
173
|
-
catch { return _readJsonObjectFallback(); }
|
|
88
|
+
const db = getDb();
|
|
174
89
|
|
|
175
90
|
migrateWatchCcTriageKeysOnce();
|
|
176
91
|
|
|
177
|
-
_resyncIfJsonDiverged(db);
|
|
178
|
-
|
|
179
92
|
const rows = _readAllRows(db);
|
|
180
|
-
|
|
181
|
-
const fallback = _readJsonObjectFallback();
|
|
182
|
-
if (fallback && Object.keys(fallback).length > 0) return fallback;
|
|
183
|
-
return {};
|
|
184
|
-
}
|
|
185
|
-
return _inflate(rows);
|
|
93
|
+
return rows.length === 0 ? {} : _inflate(rows);
|
|
186
94
|
}
|
|
187
95
|
|
|
188
96
|
function _indexByCompositeKey(rows) {
|
|
@@ -229,7 +137,7 @@ function _applyMetricsDiff(db, diff) {
|
|
|
229
137
|
return true;
|
|
230
138
|
}
|
|
231
139
|
|
|
232
|
-
function applyMetricsMutation(mutator
|
|
140
|
+
function applyMetricsMutation(mutator) {
|
|
233
141
|
const { getDb, withTransaction } = require('./db');
|
|
234
142
|
let db;
|
|
235
143
|
try { db = getDb(); }
|
|
@@ -238,7 +146,6 @@ function applyMetricsMutation(mutator, options = {}) {
|
|
|
238
146
|
}
|
|
239
147
|
|
|
240
148
|
return withTransaction(db, () => {
|
|
241
|
-
_resyncIfJsonDiverged(db);
|
|
242
149
|
const beforeRows = _readAllRows(db);
|
|
243
150
|
const beforeObj = _inflate(beforeRows);
|
|
244
151
|
const beforeSnapshot = JSON.parse(JSON.stringify(beforeObj));
|
|
@@ -248,43 +155,13 @@ function applyMetricsMutation(mutator, options = {}) {
|
|
|
248
155
|
const beforeRowsSnap = _flatten(beforeSnapshot);
|
|
249
156
|
const diff = _computeMetricsDiff(beforeRowsSnap, afterRows);
|
|
250
157
|
const wrote = _applyMetricsDiff(db, diff);
|
|
251
|
-
// W-mradg74d000rfd45 — phantom-record race fix: mirror the JSON
|
|
252
|
-
// sidecar INSIDE this same SQL write transaction, BEFORE it commits,
|
|
253
|
-
// instead of after (as the caller used to do post-return). Engine and
|
|
254
|
-
// dashboard are separate Node processes, each with its own in-memory
|
|
255
|
-
// mirror-hash cache; mirroring post-commit left a window where a
|
|
256
|
-
// sibling process could observe the newly committed SQL rows before
|
|
257
|
-
// the JSON file caught up and read stale/missing metrics from the
|
|
258
|
-
// sidecar. Mirroring pre-commit means "SQL committed" and "JSON
|
|
259
|
-
// mirror has the data" can never observably disagree across
|
|
260
|
-
// processes. Mirrors the work-items-store.js fix from PR #720.
|
|
261
|
-
if (wrote) {
|
|
262
|
-
try { _mirrorJsonFromSql(options.mirrorPath); } catch { /* best-effort — mirror failure must not block the SQL write */ }
|
|
263
|
-
}
|
|
264
158
|
return { wrote, result: afterObj };
|
|
265
159
|
});
|
|
266
160
|
}
|
|
267
161
|
|
|
268
|
-
function _mirrorJsonFromSql(filePath) {
|
|
269
|
-
try {
|
|
270
|
-
const shared = require('./shared');
|
|
271
|
-
const { getDb } = require('./db');
|
|
272
|
-
// Read SQL directly — bypass JSON fallback so a mutation that
|
|
273
|
-
// empties all metrics rows doesn't resurrect stale JSON content.
|
|
274
|
-
const obj = _inflate(_readAllRows(getDb()));
|
|
275
|
-
const target = filePath || _metricsFilePath();
|
|
276
|
-
shared.safeWrite(target, obj);
|
|
277
|
-
const h = _fileContentHash(target);
|
|
278
|
-
if (h != null) _lastMirrorHash = h;
|
|
279
|
-
} catch {
|
|
280
|
-
// Mirror failures are non-fatal — SQL has already committed.
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
162
|
function _resetMetricsTableForTest() {
|
|
285
163
|
const { getDb } = require('./db');
|
|
286
164
|
try { getDb().exec('DELETE FROM metrics'); } catch { /* not initialized */ }
|
|
287
|
-
_lastMirrorHash = null;
|
|
288
165
|
}
|
|
289
166
|
|
|
290
167
|
// W-mrb1a3950003d9ea — one-time collapse of legacy `watch-cc-triage:<watchId>`
|
|
@@ -351,7 +228,6 @@ function _resetWatchCcTriageMigrationFlagForTest() {
|
|
|
351
228
|
module.exports = {
|
|
352
229
|
readMetrics,
|
|
353
230
|
applyMetricsMutation,
|
|
354
|
-
_mirrorJsonFromSql,
|
|
355
231
|
_resetMetricsTableForTest,
|
|
356
232
|
migrateWatchCcTriageKeys,
|
|
357
233
|
migrateWatchCcTriageKeysOnce,
|
package/engine/pipeline.js
CHANGED
|
@@ -575,13 +575,11 @@ const _VALID_EXISTING_PRD_STATUSES = new Set(Object.values(PLAN_STATUS));
|
|
|
575
575
|
// a usable PRD. Require both a recognized top-level status AND an array
|
|
576
576
|
// `missing_features` field before counting the PRD as an existing conversion.
|
|
577
577
|
function _findExistingPrdForPlan(planFile, prdDir) {
|
|
578
|
-
|
|
579
|
-
const prdFiles = safeReadDir(prdDir).filter(f => f.endsWith('.json'));
|
|
578
|
+
const prdFiles = require('./prd-store').listPrdRows().filter(row => !row.archived);
|
|
580
579
|
const planKey = _canonicalPlanName(planFile);
|
|
581
|
-
for (const pf of prdFiles) {
|
|
580
|
+
for (const { filename: pf, plan: prd } of prdFiles) {
|
|
582
581
|
// safeJsonNoRestore: PRDs are terminal artifacts — never restore archived
|
|
583
582
|
// PRDs from a stale .backup sidecar (W-mouptdh1000h9f39).
|
|
584
|
-
const prd = safeJsonNoRestore(path.join(prdDir, pf));
|
|
585
583
|
if (!prd?.source_plan || _canonicalPlanName(path.basename(String(prd.source_plan))) !== planKey) continue;
|
|
586
584
|
if (!(_VALID_EXISTING_PRD_STATUSES.has(prd.status) || isPrdArchived(prd)) || !Array.isArray(prd.missing_features)) {
|
|
587
585
|
log('warn', `Pipeline plan-to-prd: found PRD "${pf}" for plan "${planFile}" but it lacks a valid status/missing_features (garbage/incomplete conversion) — treating plan-to-prd as not yet done`);
|
|
@@ -992,12 +990,11 @@ function isStageComplete(stage, stageState, run, config) {
|
|
|
992
990
|
const plans = artifacts.plans || [];
|
|
993
991
|
const discoveredPrds = [];
|
|
994
992
|
const discoveredWiIds = [];
|
|
995
|
-
const
|
|
993
|
+
const prdRows = require('./prd-store').listPrdRows().filter(row => !row.archived);
|
|
996
994
|
for (const planFile of plans) {
|
|
997
995
|
const planKey = _canonicalPlanName(planFile);
|
|
998
|
-
for (const pf of
|
|
996
|
+
for (const { filename: pf, plan: prd } of prdRows) {
|
|
999
997
|
// safeJsonNoRestore — see _findExistingPrdForPlan above (W-mouptdh1000h9f39).
|
|
1000
|
-
const prd = safeJsonNoRestore(path.join(prdDir, pf));
|
|
1001
998
|
// Canonical match: a collision-bumped run plan (<name>-<date>-2.md)
|
|
1002
999
|
// still links to the PRD written for <name>-<date>.md, so the stage's
|
|
1003
1000
|
// autoApprove reaches the PRD instead of stranding it awaiting-approval.
|
package/engine/playbook.js
CHANGED
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const fs = require('fs');
|
|
8
|
-
const os = require('os');
|
|
9
8
|
const path = require('path');
|
|
10
9
|
const shared = require('./shared');
|
|
11
10
|
const queries = require('./queries');
|
|
12
11
|
const { wrapUntrusted, buildSource } = require('./untrusted-fence');
|
|
13
12
|
const { MINIONS_BRAND_URL } = require('./comment-format');
|
|
13
|
+
const features = require('./features');
|
|
14
|
+
const memoryRetrieval = require('./memory-retrieval');
|
|
15
|
+
const memoryStore = require('./memory-store');
|
|
14
16
|
|
|
15
17
|
const { safeJsonArr, safeRead, getProjects, log, ts, dateStamp, truncateTextBytes, ENGINE_DEFAULTS, WI_STATUS, WORK_TYPE, PR_STATUS, DISPATCH_RESULT, getProjectOrg } = shared;
|
|
16
18
|
const { getConfig, getDispatch, getNotes, getPrs, getKnowledgeBaseIndex, AGENTS_DIR } = queries;
|
|
@@ -78,7 +80,7 @@ function getPrCommentInstructions(project) {
|
|
|
78
80
|
const repoName = project?.repoName || '';
|
|
79
81
|
const adoOrg = getProjectOrg(project);
|
|
80
82
|
const adoProject = project?.adoProject || '<ado-project>';
|
|
81
|
-
return `Post the comment via \`minions pr comment --host ado\` (preferred) — it prepends the hidden \`<!-- minions:agent=… kind=… -->\` marker
|
|
83
|
+
return `Post the comment via \`minions pr comment --host ado\` (preferred) — it prepends the hidden \`<!-- minions:agent=… kind=… -->\` marker and hyperlinks the Minions brand mention for you, exactly like the GitHub path:\n` +
|
|
82
84
|
`- \`minions pr comment <number> --host ado --ado-org ${adoOrg} --ado-project ${adoProject} --repo-id ${repoId || '<repo-id>'} --agent <your-agent-id> --kind <kind> [--wi <work-item-id>] --body-file <body-file.md>\`\n` +
|
|
83
85
|
`- Pick \`--kind\` from: \`review-decision\` | \`verify-report\` | \`rebase-report\` | \`positive-signal\` | \`fix-summary\` | \`other\`\n` +
|
|
84
86
|
`- Use --body-file so Markdown, quotes, and newlines pass safely\n\n` +
|
|
@@ -128,7 +130,6 @@ function getPrVoteInstructions(project, config) {
|
|
|
128
130
|
"<your review body here>\n" +
|
|
129
131
|
"```\n" +
|
|
130
132
|
`Then run: \`gh pr review <number> --comment --body-file <verdict.md> --repo ${org}/${repo}\`. Do NOT use \`--approve\` or \`--request-changes\` flags — they will fail.\n\n` +
|
|
131
|
-
`When you author the summary body, append the collapsible **"Harnesses used"** \`<details>\` section described in the shared rules (mirror of your \`harnessUsed\` completion field) so reviewers see which skills / MCPs / docs informed the change; omit it when you used none.\n\n` +
|
|
132
133
|
`**Brand link:** whenever the body names "Minions" (e.g. the \`Reviewed by Minions (…)\` sign-off), render it as \`[Minions](${MINIONS_BRAND_URL})\` per the shared rules. On the GitHub path the engine also auto-links the first bare \`… by Minions\` trailer as a backstop (\`engine/comment-format.js#linkifyBrandTrailer\`), but write the link yourself so the first occurrence is correct.`;
|
|
133
134
|
}
|
|
134
135
|
// Azure DevOps — prefer `az` CLI first, ADO REST API as fallback.
|
|
@@ -140,16 +141,14 @@ function getPrVoteInstructions(project, config) {
|
|
|
140
141
|
const autoApplyReviewVote = config?.engine?.autoApplyReviewVote ?? ENGINE_DEFAULTS.autoApplyReviewVote;
|
|
141
142
|
if (!autoApplyReviewVote) {
|
|
142
143
|
return `**Do NOT cast a platform reviewer vote.** The operator has "Auto-apply review vote to PR" disabled, so Minions record verdicts as comments only — the human casts the final vote. Do NOT run \`az repos pr set-vote\` or PUT a reviewer vote via the REST API.\n\n` +
|
|
143
|
-
`Post your verdict as a thread comment. **Preferred:** \`minions pr comment <number> --host ado --ado-org ${getProjectOrg(project)} --ado-project ${project?.adoProject || '<ado-project>'} --repo-id ${repoId || '<repo-id>'} --agent <your-agent-id> --kind review-decision [--wi <work-item-id>] --body-file <verdict.md>\` (applies the marker +
|
|
144
|
+
`Post your verdict as a thread comment. **Preferred:** \`minions pr comment <number> --host ado --ado-org ${getProjectOrg(project)} --ado-project ${project?.adoProject || '<ado-project>'} --repo-id ${repoId || '<repo-id>'} --agent <your-agent-id> --kind review-decision [--wi <work-item-id>] --body-file <verdict.md>\` (applies the marker + brand link for you). Fallback: \`az repos pr comment create --pull-request-id <number> --content @<verdict.md>\`\n\n` +
|
|
144
145
|
`The verdict body file's first non-marker line MUST be \`VERDICT: APPROVE\` or \`VERDICT: REQUEST_CHANGES\` — the engine parses this to record your verdict in local state.\n\n` +
|
|
145
|
-
`When you author the comment body, append the collapsible **"Harnesses used"** \`<details>\` section described in the shared rules (mirror of your \`harnessUsed\` completion field); omit it when you used none.\n\n` +
|
|
146
146
|
`**Brand link:** whenever the body names "Minions" (e.g. the \`Reviewed by Minions (…)\` sign-off), render it as \`Reviewed by [Minions](${MINIONS_BRAND_URL}) (…)\` per the shared rules. \`minions pr comment --host ado\` auto-links the first bare \`… by Minions\` trailer for you; the raw \`az repos pr comment\` fallback does NOT, so if you use it you MUST link it yourself.`;
|
|
147
147
|
}
|
|
148
148
|
return `For Azure DevOps, use the \`az\` CLI first to set your reviewer vote:\n` +
|
|
149
149
|
`- \`az repos pr set-vote --id <number> --vote {approve | approve-with-suggestions | reject | reset | wait-for-author}\`\n` +
|
|
150
|
-
`- Pair the vote with a thread comment. **Preferred:** \`minions pr comment <number> --host ado --ado-org ${getProjectOrg(project)} --ado-project ${project?.adoProject || '<ado-project>'} --repo-id ${repoId || '<repo-id>'} --agent <your-agent-id> --kind review-decision [--wi <work-item-id>] --body-file <verdict.md>\` (applies the marker +
|
|
150
|
+
`- Pair the vote with a thread comment. **Preferred:** \`minions pr comment <number> --host ado --ado-org ${getProjectOrg(project)} --ado-project ${project?.adoProject || '<ado-project>'} --repo-id ${repoId || '<repo-id>'} --agent <your-agent-id> --kind review-decision [--wi <work-item-id>] --body-file <verdict.md>\` (applies the marker + brand link for you). Fallback: \`az repos pr comment create --pull-request-id <number> --content @<verdict.md>\`\n\n` +
|
|
151
151
|
`If \`az\` is unavailable, fall back to the ADO REST API: get a token with \`az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 --query accessToken -o tsv\` and \`PUT .../_apis/git/repositories/${repoId}/pullrequests/<number>/reviewers/<reviewerId>?api-version=7.1\` with body \`{"vote":10}\` (10=approve, 5=approve-with-suggestions, 0=no-vote, -5=wait-for-author, -10=reject; Bearer auth). Do not use \`gh\` for Azure DevOps repositories.\n\n` +
|
|
152
|
-
`When you author the comment body, append the collapsible **"Harnesses used"** \`<details>\` section described in the shared rules (mirror of your \`harnessUsed\` completion field) so reviewers see which skills / MCPs / docs informed the change; render it identically to the GitHub path and omit it when you used none.\n\n` +
|
|
153
152
|
`**Brand link:** whenever the body names "Minions" (e.g. the \`Reviewed by Minions (…)\` sign-off), render it as \`Reviewed by [Minions](${MINIONS_BRAND_URL}) (…)\` per the shared rules. \`minions pr comment --host ado\` auto-links the first bare \`… by Minions\` trailer for you (same backstop as GitHub); the raw \`az repos pr comment\` / REST fallbacks do NOT, so if you use those you MUST link it yourself.`;
|
|
154
153
|
}
|
|
155
154
|
|
|
@@ -343,12 +342,6 @@ const PLAYBOOK_OPTIONAL_VARS = new Set([
|
|
|
343
342
|
// PR-context vars on non-PR tasks (implement/explore/etc.)
|
|
344
343
|
'pr_id', 'pr_number', 'pr_title', 'pr_branch', 'pr_author', 'pr_url',
|
|
345
344
|
'reviewer',
|
|
346
|
-
// W-mq16xtdx001a347e — review dispatches inject this block (or empty string
|
|
347
|
-
// when discovery finds no project-local review skills). Optional because
|
|
348
|
-
// non-review playbooks never reference it and review-against-skill-less
|
|
349
|
-
// projects legitimately resolve it to ''.
|
|
350
|
-
'project_review_skills_block',
|
|
351
|
-
'skip_project_review_skills',
|
|
352
345
|
// W-mq5l3f2u000i9045 — opt-out flag for the PR description audit + screenshot
|
|
353
346
|
// refresh fragment inlined into fix.md / implement.md. Truthy (set when
|
|
354
347
|
// item.meta.skipDescriptionAudit is true) renders the "audit suppressed"
|
|
@@ -389,26 +382,6 @@ const PLAYBOOK_OPTIONAL_VARS = new Set([
|
|
|
389
382
|
'role', // 'primary' | 'co-service'
|
|
390
383
|
'primary_project', // canonical primary project name; empty on single-project sessions
|
|
391
384
|
'co_services_json', // JSON-encoded co-service project names (e.g. ["api","worker"]); empty when none
|
|
392
|
-
// W-mq16xtdx001a347e (PR-82) — project-local review-skill discovery vars.
|
|
393
|
-
// Always rendered (empty string when no discoveries); listed here so the
|
|
394
|
-
// unresolved-var check doesn't complain when discovery returns nothing or
|
|
395
|
-
// projects legitimately resolve it to ''.
|
|
396
|
-
'project_review_skills_block',
|
|
397
|
-
'skip_project_review_skills',
|
|
398
|
-
// W-mq1cczi90006b21f — generic, intent-filtered project skills block.
|
|
399
|
-
// Spliced by review/fix/implement/plan/etc. when discovery + intent map
|
|
400
|
-
// produce matches. Optional for the same reason as the review-only alias.
|
|
401
|
-
'project_skills_block',
|
|
402
|
-
'skip_project_skills',
|
|
403
|
-
// P-f52d81ba — monorepo-aware harness scoping. The engine derives a dominant
|
|
404
|
-
// sub-project for the dispatch (explicit meta.workdir first segment, or
|
|
405
|
-
// detectDominantSubproject over the current dispatch's changed paths) and threads
|
|
406
|
-
// it here so playbook.js can pass it as `scopeSubproject` into project-skill
|
|
407
|
-
// discovery. Optional with an empty-string default: single-root projects and
|
|
408
|
-
// mixed/root diffs legitimately resolve it to '' (flat discovery), and no
|
|
409
|
-
// playbook references {{dominant_subproject}} directly, so the unresolved-var check
|
|
410
|
-
// must stay quiet when it is unset.
|
|
411
|
-
'dominant_subproject',
|
|
412
385
|
// M006 — typed handoff envelope fields. Set on a follow-up dispatch meta
|
|
413
386
|
// when lifecycle.js queues the item after a completing agent so the
|
|
414
387
|
// receiving agent has explicit context about who handed off and why.
|
|
@@ -716,10 +689,61 @@ function renderPlaybook(type, vars) {
|
|
|
716
689
|
inertAppendices.push('\n\n---\n\n## Pinned Context (CRITICAL — READ FIRST)\n\n' + (fenced || pinnedContent));
|
|
717
690
|
}
|
|
718
691
|
|
|
692
|
+
// Structured memory rollout is deliberately two-stage. The feature flag
|
|
693
|
+
// turns retrieval on; shadow mode (default) records selection telemetry but
|
|
694
|
+
// leaves the legacy notes/personal-memory prompt unchanged. Once shadow mode
|
|
695
|
+
// is disabled, a non-empty bounded retrieval pack replaces those broad
|
|
696
|
+
// appendices. Pinned operator context always remains fully visible.
|
|
697
|
+
let relevantMemoryInjected = false;
|
|
698
|
+
if (features.isFeatureOn('memoryRetrieval', configEarly)) {
|
|
699
|
+
const engineConfig = configEarly.engine || {};
|
|
700
|
+
const shadow = engineConfig.memoryRetrievalShadowMode ?? ENGINE_DEFAULTS.memoryRetrievalShadowMode;
|
|
701
|
+
try {
|
|
702
|
+
const retrieved = memoryRetrieval.retrieveRelevantMemory({
|
|
703
|
+
workItemId: vars.item_id || vars.task_id || '',
|
|
704
|
+
title: vars.item_name || vars.pr_title || '',
|
|
705
|
+
description: vars.item_description || vars.task_description || vars.pr_description || '',
|
|
706
|
+
type: vars.work_type || type,
|
|
707
|
+
project: projectName || '',
|
|
708
|
+
agent: vars.agent_id || '',
|
|
709
|
+
failureClass: vars.failure_class || '',
|
|
710
|
+
sourcePlan: vars.source_plan || '',
|
|
711
|
+
references: vars.references || '',
|
|
712
|
+
prTitle: vars.pr_title || '',
|
|
713
|
+
prBranch: vars.pr_branch || '',
|
|
714
|
+
}, {
|
|
715
|
+
topK: engineConfig.memoryRetrievalTopK ?? ENGINE_DEFAULTS.memoryRetrievalTopK,
|
|
716
|
+
maxBytes: engineConfig.memoryRetrievalMaxBytes ?? ENGINE_DEFAULTS.memoryRetrievalMaxBytes,
|
|
717
|
+
candidateLimit: engineConfig.memoryRetrievalCandidateLimit ?? ENGINE_DEFAULTS.memoryRetrievalCandidateLimit,
|
|
718
|
+
});
|
|
719
|
+
memoryStore.recordRetrievalRun({
|
|
720
|
+
workItemId: vars.item_id || vars.task_id || null,
|
|
721
|
+
project: projectName,
|
|
722
|
+
agent: vars.agent_id || null,
|
|
723
|
+
query: retrieved.query,
|
|
724
|
+
candidateCount: retrieved.candidates.length,
|
|
725
|
+
selectedCount: retrieved.selected.length,
|
|
726
|
+
selectedBytes: retrieved.bytes,
|
|
727
|
+
durationMs: retrieved.durationMs,
|
|
728
|
+
shadow,
|
|
729
|
+
});
|
|
730
|
+
if (!shadow && retrieved.text) {
|
|
731
|
+
const fenced = wrapUntrusted(
|
|
732
|
+
retrieved.text,
|
|
733
|
+
buildSource('memory-retrieval', { item: vars.item_id || vars.task_id || 'unknown' })
|
|
734
|
+
);
|
|
735
|
+
inertAppendices.push('\n\n---\n\n## Relevant Memory (task-selected; verify against live code)\n\n' + (fenced || retrieved.text));
|
|
736
|
+
relevantMemoryInjected = true;
|
|
737
|
+
}
|
|
738
|
+
} catch (e) {
|
|
739
|
+
log('warn', `Memory retrieval failed for ${vars.item_id || vars.task_id || type}; using legacy memory: ${e.message}`);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
719
743
|
// Inject team notes (single injection point — not in buildAgentContext) — capped via ENGINE_DEFAULTS.
|
|
720
744
|
// F5: wrap in <UNTRUSTED-INPUT> fence — notes.md is an LLM-consolidated mix
|
|
721
745
|
// of agent inbox notes (semi-trusted) and human edits.
|
|
722
|
-
let notes = getNotes();
|
|
746
|
+
let notes = relevantMemoryInjected ? '' : getNotes();
|
|
723
747
|
if (notes) {
|
|
724
748
|
if (Buffer.byteLength(notes, 'utf8') > ENGINE_DEFAULTS.maxNotesPromptBytes) {
|
|
725
749
|
const sections = notes.split(/(?=^### )/m);
|
|
@@ -738,7 +762,7 @@ function renderPlaybook(type, vars) {
|
|
|
738
762
|
// notes budget; missing file degrades gracefully (silent skip).
|
|
739
763
|
// F5: fence — agent-authored inbox notes routed into this file; any agent
|
|
740
764
|
// could include attacker-controlled quoted material.
|
|
741
|
-
const agentIdForMemory = vars.agent_id;
|
|
765
|
+
const agentIdForMemory = relevantMemoryInjected ? '' : vars.agent_id;
|
|
742
766
|
if (agentIdForMemory && /^[a-z][a-z0-9-]{0,40}$/i.test(agentIdForMemory) && !String(agentIdForMemory).toLowerCase().startsWith('temp-')) {
|
|
743
767
|
const agentMemRel = `knowledge/agents/${String(agentIdForMemory).toLowerCase()}.md`;
|
|
744
768
|
const agentMemPath = path.join(MINIONS_DIR, agentMemRel);
|
|
@@ -807,78 +831,6 @@ function renderPlaybook(type, vars) {
|
|
|
807
831
|
} catch (e) { log('warn', `managed-spawn live-processes inject failed: ${e.message}`); }
|
|
808
832
|
}
|
|
809
833
|
|
|
810
|
-
// W-mq16xtdx001a347e (PR #82) + W-mq1cczi90006b21f (generalization) —
|
|
811
|
-
// surface project-local skills relevant to the playbook being dispatched.
|
|
812
|
-
//
|
|
813
|
-
// PR-82 hard-scoped this to type === 'review'. This wiring generalizes it:
|
|
814
|
-
// 1. Discovery walks every project skill / command / documented slash-
|
|
815
|
-
// command (engine/discover-project-skills.js).
|
|
816
|
-
// 2. engine/playbook-intents.js maps playbook → intent set.
|
|
817
|
-
// 3. Discovered entries are filtered to the playbook's intent set and
|
|
818
|
-
// rendered into `project_skills_block`. Empty intent set or zero
|
|
819
|
-
// matches → empty string (no stray header).
|
|
820
|
-
//
|
|
821
|
-
// Header copy: review dispatches keep PR-82's "## Project review skills"
|
|
822
|
-
// header (existing agents/tests grep for it). All other playbooks use the
|
|
823
|
-
// generalized "## Project skills" header.
|
|
824
|
-
//
|
|
825
|
-
// Backward-compat: `project_review_skills_block` and
|
|
826
|
-
// `vars.skip_project_review_skills` remain wired so external consumers of
|
|
827
|
-
// the PR-82 names keep working. They alias to the generic var/flag.
|
|
828
|
-
const skipProjectSkills = !!(vars.skip_project_skills || vars.skip_project_review_skills);
|
|
829
|
-
let projectSkillsBlock = '';
|
|
830
|
-
if (!skipProjectSkills) {
|
|
831
|
-
try {
|
|
832
|
-
const discover = require('./discover-project-skills');
|
|
833
|
-
const { intentsForPlaybook } = require('./playbook-intents');
|
|
834
|
-
const projectPath = matchedProject?.localPath || '';
|
|
835
|
-
if (projectPath) {
|
|
836
|
-
// Inherit intent set from parent dispatch type when the engine
|
|
837
|
-
// passes vars.parent_dispatch_type (followup-dispatch flow).
|
|
838
|
-
const effectiveType = vars.parent_dispatch_type || type;
|
|
839
|
-
const intents = intentsForPlaybook(effectiveType);
|
|
840
|
-
if (intents.length > 0) {
|
|
841
|
-
// P-f52d81ba — monorepo-aware scoping. When the engine derived a
|
|
842
|
-
// dominant sub-project for this dispatch (git diff / PR files /
|
|
843
|
-
// references → detectDominantSubproject, threaded in via vars.dominant_subproject),
|
|
844
|
-
// surface that sub-project's skills FIRST. Empty/unset ⇒ today's flat
|
|
845
|
-
// global name-sort. Use the diagnostics variant so per-surface
|
|
846
|
-
// overflow (entries dropped past the maxFilesPerSurface cap) is
|
|
847
|
-
// observable instead of silent.
|
|
848
|
-
const scopeSubproject = typeof vars.dominant_subproject === 'string' ? vars.dominant_subproject : '';
|
|
849
|
-
const { entries, truncations } = discover.discoverProjectSkillsWithDiagnostics({
|
|
850
|
-
projectPath,
|
|
851
|
-
scopeSubproject,
|
|
852
|
-
opts: { maxFilesPerSurface: ENGINE_DEFAULTS.projectSkillMaxFilesPerSurface },
|
|
853
|
-
});
|
|
854
|
-
if (Array.isArray(truncations) && truncations.length > 0) {
|
|
855
|
-
log('warn', `discover-project-skills: ${truncations.length} surface(s) exceeded maxFilesPerSurface (entries dropped) for project ${matchedProject?.name || projectPath}${scopeSubproject ? ', subproject=' + scopeSubproject : ''}: ${truncations.map(t => `${t.dir} (${t.scanned}/${t.total})`).join('; ')}`);
|
|
856
|
-
}
|
|
857
|
-
// Stash discovery diagnostics on the vars object so the engine
|
|
858
|
-
// caller (spawnAgent) can record detected sub-project + truncations onto
|
|
859
|
-
// the dispatch record for later grounding/observability. Best-effort:
|
|
860
|
-
// callers that don't read it simply ignore the field.
|
|
861
|
-
vars._discoveryDiagnostics = { subproject: scopeSubproject, truncations: Array.isArray(truncations) ? truncations : [] };
|
|
862
|
-
const filtered = discover.filterByIntents(entries, intents);
|
|
863
|
-
// type === 'review' keeps PR-82 header copy + meta.review.* outcome
|
|
864
|
-
// guidance; everything else uses the generic header that points at
|
|
865
|
-
// meta.skill.* in the completion report.
|
|
866
|
-
projectSkillsBlock = (type === WORK_TYPE.REVIEW)
|
|
867
|
-
? discover.renderReviewSkillsBlock(filtered)
|
|
868
|
-
: discover.renderProjectSkillsBlock(filtered);
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
} catch (e) {
|
|
872
|
-
log('warn', `discover-project-skills failed: ${e.message}`);
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
vars.project_skills_block = projectSkillsBlock;
|
|
876
|
-
// Backward-compat alias for any consumer that still references
|
|
877
|
-
// {{project_review_skills_block}} directly. Same content; cheaper than
|
|
878
|
-
// re-rendering. Non-review playbooks that don't reference the alias
|
|
879
|
-
// ignore it (it's in PLAYBOOK_OPTIONAL_VARS).
|
|
880
|
-
vars.project_review_skills_block = projectSkillsBlock;
|
|
881
|
-
|
|
882
834
|
// W-mpeiwz6k0005bf34-c — opt-in qa-validate context block. Injected only
|
|
883
835
|
// when the dispatcher set vars.qa_run_id (truthy) from the work item's
|
|
884
836
|
// `meta.qaRunId`. Mirrors the managed_spawn hint pattern: the playbook is
|
|
@@ -1134,72 +1086,13 @@ function buildSystemPrompt(agentId, config, project) {
|
|
|
1134
1086
|
return { prompt, warnings: [] };
|
|
1135
1087
|
}
|
|
1136
1088
|
|
|
1137
|
-
//
|
|
1138
|
-
//
|
|
1139
|
-
// personal-scope/installed-plugin skill discovered by queries.getSkills().
|
|
1140
|
-
// Caps at ~2KB by truncating alphabetically with a "_...and N more_" tail.
|
|
1141
|
-
// Returns '' when there are no personal-scope skills to advertise.
|
|
1142
|
-
const EXISTING_SKILLS_MAX_BYTES = 2048;
|
|
1143
|
-
const PERSONAL_SKILL_SCOPES = new Set(['claude-code', 'copilot', 'agent-skill', 'plugin', 'copilot-plugin']);
|
|
1144
|
-
|
|
1145
|
-
function _buildExistingSkillsSection(config) {
|
|
1146
|
-
let skills;
|
|
1147
|
-
try {
|
|
1148
|
-
skills = queries.getSkills(config) || [];
|
|
1149
|
-
} catch { return ''; }
|
|
1150
|
-
const personal = skills
|
|
1151
|
-
.filter(s => PERSONAL_SKILL_SCOPES.has(s.scope))
|
|
1152
|
-
.map(s => ({
|
|
1153
|
-
name: String(s.name || '').trim(),
|
|
1154
|
-
description: String(s.description || '').split('\n')[0].trim(),
|
|
1155
|
-
}))
|
|
1156
|
-
.filter(s => s.name)
|
|
1157
|
-
.sort((a, b) => a.name.localeCompare(b.name));
|
|
1158
|
-
if (personal.length === 0) return '';
|
|
1159
|
-
|
|
1160
|
-
const header = `## Existing Skills (do not duplicate)\n\nThese personal-scope skills already exist on disk. Do NOT emit a \`\`\`skill block whose name duplicates or near-duplicates one of these — the engine will flag it for human review and skip the write.\n\n`;
|
|
1161
|
-
const lines = personal.map(s => s.description ? `- ${s.name} — ${s.description}` : `- ${s.name}`);
|
|
1162
|
-
|
|
1163
|
-
let body = lines.join('\n') + '\n\n';
|
|
1164
|
-
if (Buffer.byteLength(header + body, 'utf8') <= EXISTING_SKILLS_MAX_BYTES) {
|
|
1165
|
-
return header + body;
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
// Truncate alphabetically until we fit, leaving room for the tail line.
|
|
1169
|
-
let kept = lines.length;
|
|
1170
|
-
while (kept > 0) {
|
|
1171
|
-
const dropped = lines.length - kept;
|
|
1172
|
-
const tail = `_...and ${dropped} more (alphabetical truncation; full list on disk)_\n\n`;
|
|
1173
|
-
body = lines.slice(0, kept).join('\n') + '\n' + tail;
|
|
1174
|
-
if (Buffer.byteLength(header + body, 'utf8') <= EXISTING_SKILLS_MAX_BYTES) {
|
|
1175
|
-
return header + body;
|
|
1176
|
-
}
|
|
1177
|
-
kept--;
|
|
1178
|
-
}
|
|
1179
|
-
// Pathological: header alone exceeds the cap. Return at least the header
|
|
1180
|
-
// with a tail showing every skill was dropped, so the agent still sees the
|
|
1181
|
-
// section heading.
|
|
1182
|
-
return header + `_...and ${lines.length} more (alphabetical truncation; full list on disk)_\n\n`;
|
|
1183
|
-
}
|
|
1184
|
-
|
|
1185
|
-
// Bulk context: history, notes, conventions, skills — prepended to user/task prompt.
|
|
1089
|
+
// Bulk Minions context: history, notes, and orchestration state prepended to
|
|
1090
|
+
// the user/task prompt. Repository harness discovery remains runtime-native.
|
|
1186
1091
|
// This is the content that grows over time and would bloat the system prompt.
|
|
1187
1092
|
function buildAgentContext(agentId, config, project) {
|
|
1188
1093
|
project = project || getProjects(config)[0] || {};
|
|
1189
1094
|
let context = '';
|
|
1190
1095
|
|
|
1191
|
-
function appendContextFile(heading, filePath, maxBytes, extra = '') {
|
|
1192
|
-
if (!filePath) return;
|
|
1193
|
-
const content = safeRead(filePath);
|
|
1194
|
-
if (!content || !content.trim()) return;
|
|
1195
|
-
const truncated = Buffer.byteLength(content, 'utf8') > maxBytes
|
|
1196
|
-
? truncateTextBytes(content, maxBytes, '\n\n_...truncated; read the full file if needed_')
|
|
1197
|
-
: content;
|
|
1198
|
-
context += `## ${heading}\n\n`;
|
|
1199
|
-
if (extra) context += `${extra}\n\n`;
|
|
1200
|
-
context += `${truncated}\n\n`;
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
1096
|
function appendIndex(heading, body, maxBytes) {
|
|
1204
1097
|
if (!body || !String(body).trim()) return;
|
|
1205
1098
|
const truncated = Buffer.byteLength(body, 'utf8') > maxBytes
|
|
@@ -1218,28 +1111,9 @@ function buildAgentContext(agentId, config, project) {
|
|
|
1218
1111
|
const trimmed = (header ? header + '\n' : '') + recent.join('');
|
|
1219
1112
|
context += `## Your Recent History (last 5 tasks)\n\n${trimmed}\n\n`;
|
|
1220
1113
|
}
|
|
1221
|
-
|
|
1222
|
-
// Project conventions and instruction files — explicit, inert context for
|
|
1223
|
-
// every runtime. Runtime-native skills/commands stay in their native indexes.
|
|
1224
|
-
if (project.localPath) {
|
|
1225
|
-
appendContextFile('Project Conventions (from CLAUDE.md)', path.join(project.localPath, 'CLAUDE.md'), 8192);
|
|
1226
|
-
appendContextFile('Project Agent Instructions (from AGENTS.md)', path.join(project.localPath, 'AGENTS.md'), 8192,
|
|
1227
|
-
'These instructions are explicitly injected because some runtimes suppress automatic AGENTS.md loading. Follow them unless they conflict with the Minions task contract or playbook.');
|
|
1228
|
-
appendContextFile('Project Copilot Instructions (from .github/copilot-instructions.md)', path.join(project.localPath, '.github', 'copilot-instructions.md'), 8192,
|
|
1229
|
-
'Follow these repository instructions unless they conflict with the Minions task contract or playbook.');
|
|
1230
|
-
}
|
|
1231
|
-
|
|
1232
|
-
appendContextFile('User Claude Instructions (from ~/.claude/CLAUDE.md)', path.join(os.homedir(), '.claude', 'CLAUDE.md'), 8192,
|
|
1233
|
-
'These are the user-level Claude Code instructions available in regular Claude usage. Follow them unless they conflict with the Minions task contract or playbook.');
|
|
1234
|
-
|
|
1235
1114
|
appendIndex('Knowledge Base Reference', getKnowledgeBaseIndex(), 8192);
|
|
1236
1115
|
|
|
1237
|
-
context += `## Reference Files\n\nKnowledge base entries are in \`knowledge/{category}/*.md\`, and project-local playbooks live in \`projects/<project>/playbooks
|
|
1238
|
-
|
|
1239
|
-
// E2.b (W-mp7goxe4000p75f7): inject existing personal-scope skill catalog
|
|
1240
|
-
// so agents can see what already exists before emitting a near-duplicate
|
|
1241
|
-
// skill block. Caps at ~2KB; truncates alphabetically with a tail count.
|
|
1242
|
-
context += _buildExistingSkillsSection(config);
|
|
1116
|
+
context += `## Reference Files\n\nKnowledge base entries are in \`knowledge/{category}/*.md\`, and project-local playbooks live in \`projects/<project>/playbooks/\`.\n\n`;
|
|
1243
1117
|
|
|
1244
1118
|
// Minions awareness: what's in flight, who's doing what
|
|
1245
1119
|
const dispatch = getDispatch();
|
|
@@ -1265,9 +1139,9 @@ function buildAgentContext(agentId, config, project) {
|
|
|
1265
1139
|
const prs = getPrs(p).filter(pr => pr.status === PR_STATUS.ACTIVE);
|
|
1266
1140
|
for (const pr of prs) allPrs.push({ ...pr, _project: p.name });
|
|
1267
1141
|
}
|
|
1268
|
-
// Also check central pull-
|
|
1142
|
+
// Also check the central SQL pull-request scope.
|
|
1269
1143
|
try {
|
|
1270
|
-
const centralPrs =
|
|
1144
|
+
const centralPrs = require('./pull-requests-store').readPullRequestsForScope('central');
|
|
1271
1145
|
for (const pr of centralPrs.filter(pr => pr.status === PR_STATUS.ACTIVE)) {
|
|
1272
1146
|
if (!allPrs.some(p => p.id === pr.id)) allPrs.push({ ...pr, _project: 'central' });
|
|
1273
1147
|
}
|
|
@@ -1378,14 +1252,6 @@ function buildPrDispatch(agentId, config, project, pr, type, extraVars, taskLabe
|
|
|
1378
1252
|
const vars = {
|
|
1379
1253
|
...buildBaseVars(agentId, config, project),
|
|
1380
1254
|
branch_name: extraVars?.pr_branch || '',
|
|
1381
|
-
// W-mq16xtdx001a347e + W-mq1cczi90006b21f — honor both meta flag names
|
|
1382
|
-
// (skipProjectReviewSkills is the PR-82 alias). Default OFF: review/fix
|
|
1383
|
-
// dispatches surface the project-local skills block.
|
|
1384
|
-
skip_project_review_skills: !!(meta && meta.skipProjectReviewSkills),
|
|
1385
|
-
// W-mq1cczi90006b21f — generalized project-skills suppression flag.
|
|
1386
|
-
// Honors either the new `meta.skipProjectSkills` or the PR-82
|
|
1387
|
-
// `meta.skipProjectReviewSkills` alias so older work items keep working.
|
|
1388
|
-
skip_project_skills: !!(meta && (meta.skipProjectSkills || meta.skipProjectReviewSkills)),
|
|
1389
1255
|
// W-mq5l3f2u000i9045 — opt-out for the inlined PR description audit
|
|
1390
1256
|
// section in fix.md. Truthy renders the "audit suppressed" notice; falsy
|
|
1391
1257
|
// (the default) leaves the standard audit instructions in place.
|