@yemi33/minions 0.1.2147 → 0.1.2148
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 +8 -0
- package/dashboard/slim/body.html +2 -1
- package/dashboard/slim/js/command-send.js +3 -2
- package/dashboard/slim/js/history.js +9 -2
- package/dashboard/slim/styles.css +88 -10
- package/docs/README.md +1 -0
- package/docs/completion-reports.md +44 -12
- package/docs/project-skills.md +133 -0
- package/engine/discover-project-skills.js +490 -0
- package/engine/discover-review-skills.js +15 -269
- package/engine/playbook-intents.js +76 -0
- package/engine/playbook.js +59 -20
- package/engine.js +12 -0
- package/package.json +1 -1
- package/playbooks/fix.md +4 -0
- package/playbooks/implement-shared.md +4 -0
- package/playbooks/implement.md +4 -0
- package/playbooks/plan-to-prd.md +4 -0
- package/playbooks/plan.md +4 -0
- package/playbooks/review.md +3 -3
- package/bin/minions.js.rej +0 -16
- package/dashboard/slim/body.html.rej +0 -11
- package/dashboard/slim/js/command-send.js.rej +0 -12
- package/dashboard/slim/js/history.js.rej +0 -26
- package/dashboard/slim/styles.css.rej +0 -124
- package/docs/README.md.rej +0 -9
- package/docs/onboarding.md.rej +0 -10
|
@@ -1,279 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* engine/discover-review-skills.js —
|
|
2
|
+
* engine/discover-review-skills.js — backward-compat shim for PR #82
|
|
3
|
+
* (W-mq16xtdx001a347e).
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* PR #82 introduced `discover-review-skills.js` as a review-only helper. The
|
|
6
|
+
* follow-on W-mq1cczi90006b21f generalized it into
|
|
7
|
+
* `engine/discover-project-skills.js`, which discovers every project skill
|
|
8
|
+
* and classifies them into an intent vocabulary.
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* Returns a deduplicated array of:
|
|
16
|
-
* { kind: 'skill'|'command'|'slash-command', name, path, oneLineDescription }
|
|
17
|
-
*
|
|
18
|
-
* Contract:
|
|
19
|
-
* - Worktree missing / unreadable → returns [] (never throws)
|
|
20
|
-
* - Single bounded readdir per surface (no recursive grep)
|
|
21
|
-
* - File-count + walltime budget caps the walk in pathological repos
|
|
22
|
-
* - Pure: no engine state mutations, no logging beyond debug
|
|
10
|
+
* This file re-exports the two public PR-82 names so existing imports keep
|
|
11
|
+
* resolving without forcing every consumer to migrate in lockstep. The shim
|
|
12
|
+
* filters to the `review` intent and renders the original "## Project review
|
|
13
|
+
* skills" header copy. New code should import directly from
|
|
14
|
+
* engine/discover-project-skills.js and call `filterByIntents` itself.
|
|
23
15
|
*/
|
|
24
16
|
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
// Word-bounded so we don't match arbitrary substrings like "previewer" or
|
|
30
|
-
// "swarmm" inside unrelated names.
|
|
31
|
-
const KEYWORD_RE = /\b(review|review-swarm|swarm|code-review|pr-review|constellation-review)\b/i;
|
|
32
|
-
|
|
33
|
-
// Slash-command extractor — pulls `/foo-review`, `/review-swarm`, etc. out of
|
|
34
|
-
// markdown prose. Anchored so `/path/to/file` doesn't match (must look like a
|
|
35
|
-
// slash-command token: leading `/` immediately followed by an id with no `/`).
|
|
36
|
-
const SLASH_COMMAND_RE = /(?<![A-Za-z0-9/_-])\/([a-z][a-z0-9-]*)(?![A-Za-z0-9/])/g;
|
|
37
|
-
|
|
38
|
-
const DEFAULTS = {
|
|
39
|
-
maxFilesPerSurface: 50, // hard cap on skill packs / command files we'll read
|
|
40
|
-
maxBytesPerFile: 8 * 1024, // only need frontmatter + first heading
|
|
41
|
-
docsScanMaxBytes: 32 * 1024, // CLAUDE.md / copilot-instructions.md top window
|
|
42
|
-
walltimeMs: 250, // bail rather than block dispatch on pathological FS
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
function _now() { return Date.now(); }
|
|
46
|
-
|
|
47
|
-
function _safeReadHead(filePath, maxBytes) {
|
|
48
|
-
let fd;
|
|
49
|
-
try {
|
|
50
|
-
fd = fs.openSync(filePath, 'r');
|
|
51
|
-
const buf = Buffer.alloc(maxBytes);
|
|
52
|
-
const n = fs.readSync(fd, buf, 0, maxBytes, 0);
|
|
53
|
-
return buf.slice(0, n).toString('utf8');
|
|
54
|
-
} catch { return ''; }
|
|
55
|
-
finally { if (fd !== undefined) { try { fs.closeSync(fd); } catch { /* ignore */ } } }
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function _parseFrontmatter(content) {
|
|
59
|
-
const m = String(content || '').match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
60
|
-
if (!m) return {};
|
|
61
|
-
const out = {};
|
|
62
|
-
for (const line of m[1].split(/\r?\n/)) {
|
|
63
|
-
const lm = line.match(/^([\w-]+):\s*(.*)$/);
|
|
64
|
-
if (!lm) continue;
|
|
65
|
-
out[lm[1].toLowerCase()] = lm[2].trim().replace(/^["']|["']$/g, '');
|
|
66
|
-
}
|
|
67
|
-
return out;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function _firstHeading(content) {
|
|
71
|
-
const lines = String(content || '').split(/\r?\n/);
|
|
72
|
-
for (const line of lines) {
|
|
73
|
-
const m = line.match(/^#+\s+(.+?)\s*$/);
|
|
74
|
-
if (m) return m[1].trim();
|
|
75
|
-
}
|
|
76
|
-
return '';
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function _firstNonEmptyLine(content) {
|
|
80
|
-
const lines = String(content || '').split(/\r?\n/);
|
|
81
|
-
for (const line of lines) {
|
|
82
|
-
const t = line.trim();
|
|
83
|
-
if (t && !t.startsWith('---') && !t.startsWith('#')) return t;
|
|
84
|
-
}
|
|
85
|
-
return '';
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function _truncate(s, max) {
|
|
89
|
-
const text = String(s || '').trim();
|
|
90
|
-
if (text.length <= max) return text;
|
|
91
|
-
return text.slice(0, max - 1).trim() + '…';
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function _discoverSkills(projectPath, opts, deadline) {
|
|
95
|
-
const out = [];
|
|
96
|
-
const skillsDir = path.join(projectPath, '.claude', 'skills');
|
|
97
|
-
let entries;
|
|
98
|
-
try { entries = fs.readdirSync(skillsDir, { withFileTypes: true }); } catch { return out; }
|
|
99
|
-
let scanned = 0;
|
|
100
|
-
for (const ent of entries) {
|
|
101
|
-
if (scanned >= opts.maxFilesPerSurface) break;
|
|
102
|
-
if (_now() > deadline) break;
|
|
103
|
-
if (!ent.isDirectory()) continue;
|
|
104
|
-
const skillPath = path.join(skillsDir, ent.name, 'SKILL.md');
|
|
105
|
-
let stat;
|
|
106
|
-
try { stat = fs.statSync(skillPath); } catch { continue; }
|
|
107
|
-
if (!stat.isFile()) continue;
|
|
108
|
-
scanned += 1;
|
|
109
|
-
const head = _safeReadHead(skillPath, opts.maxBytesPerFile);
|
|
110
|
-
if (!head) continue;
|
|
111
|
-
const fm = _parseFrontmatter(head);
|
|
112
|
-
const name = fm.name || ent.name;
|
|
113
|
-
const desc = fm.description || '';
|
|
114
|
-
const hay = `${name} ${desc}`;
|
|
115
|
-
if (!KEYWORD_RE.test(hay)) continue;
|
|
116
|
-
out.push({
|
|
117
|
-
kind: 'skill',
|
|
118
|
-
name: String(name),
|
|
119
|
-
path: path.relative(projectPath, skillPath).split(path.sep).join('/'),
|
|
120
|
-
oneLineDescription: _truncate(desc || _firstHeading(head) || name, 200),
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
return out;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function _discoverCommands(projectPath, opts, deadline) {
|
|
127
|
-
const out = [];
|
|
128
|
-
const cmdDir = path.join(projectPath, '.claude', 'commands');
|
|
129
|
-
let entries;
|
|
130
|
-
try { entries = fs.readdirSync(cmdDir, { withFileTypes: true }); } catch { return out; }
|
|
131
|
-
let scanned = 0;
|
|
132
|
-
for (const ent of entries) {
|
|
133
|
-
if (scanned >= opts.maxFilesPerSurface) break;
|
|
134
|
-
if (_now() > deadline) break;
|
|
135
|
-
if (!ent.isFile()) continue;
|
|
136
|
-
if (!/\.md$/i.test(ent.name)) continue;
|
|
137
|
-
scanned += 1;
|
|
138
|
-
const base = ent.name.replace(/\.md$/i, '');
|
|
139
|
-
const cmdPath = path.join(cmdDir, ent.name);
|
|
140
|
-
const head = _safeReadHead(cmdPath, opts.maxBytesPerFile);
|
|
141
|
-
const heading = _firstHeading(head);
|
|
142
|
-
const hay = `${base} ${heading}`;
|
|
143
|
-
if (!KEYWORD_RE.test(hay)) continue;
|
|
144
|
-
out.push({
|
|
145
|
-
kind: 'command',
|
|
146
|
-
name: `/${base}`,
|
|
147
|
-
path: path.relative(projectPath, cmdPath).split(path.sep).join('/'),
|
|
148
|
-
oneLineDescription: _truncate(heading || _firstNonEmptyLine(head) || base, 200),
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
return out;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function _extractSlashCommandsFromDoc(content) {
|
|
155
|
-
const found = new Map();
|
|
156
|
-
const text = String(content || '');
|
|
157
|
-
let m;
|
|
158
|
-
SLASH_COMMAND_RE.lastIndex = 0;
|
|
159
|
-
while ((m = SLASH_COMMAND_RE.exec(text)) !== null) {
|
|
160
|
-
const id = m[1];
|
|
161
|
-
if (!KEYWORD_RE.test(id)) continue;
|
|
162
|
-
if (!found.has(id)) found.set(id, m.index);
|
|
163
|
-
}
|
|
164
|
-
return [...found.keys()];
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function _discoverDocSlashCommands(projectPath, opts, deadline, alreadySeen) {
|
|
168
|
-
const out = [];
|
|
169
|
-
const docPaths = [
|
|
170
|
-
path.join(projectPath, '.github', 'copilot-instructions.md'),
|
|
171
|
-
path.join(projectPath, 'CLAUDE.md'),
|
|
172
|
-
];
|
|
173
|
-
for (const docPath of docPaths) {
|
|
174
|
-
if (_now() > deadline) break;
|
|
175
|
-
let stat;
|
|
176
|
-
try { stat = fs.statSync(docPath); } catch { continue; }
|
|
177
|
-
if (!stat.isFile()) continue;
|
|
178
|
-
const head = _safeReadHead(docPath, opts.docsScanMaxBytes);
|
|
179
|
-
if (!head) continue;
|
|
180
|
-
const ids = _extractSlashCommandsFromDoc(head);
|
|
181
|
-
const relDoc = path.relative(projectPath, docPath).split(path.sep).join('/');
|
|
182
|
-
for (const id of ids) {
|
|
183
|
-
const fullName = `/${id}`;
|
|
184
|
-
if (alreadySeen.has(fullName)) continue;
|
|
185
|
-
alreadySeen.add(fullName);
|
|
186
|
-
out.push({
|
|
187
|
-
kind: 'slash-command',
|
|
188
|
-
name: fullName,
|
|
189
|
-
path: relDoc,
|
|
190
|
-
oneLineDescription: `Documented review entrypoint in ${relDoc}`,
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return out;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* @param {object} args
|
|
199
|
-
* @param {string} args.projectPath — absolute path to the project worktree / checkout
|
|
200
|
-
* @param {object} [args.opts] — override defaults (mostly for tests)
|
|
201
|
-
* @returns {Array<{kind:string,name:string,path:string,oneLineDescription:string}>}
|
|
202
|
-
*/
|
|
203
|
-
function discoverReviewSkills(args) {
|
|
204
|
-
const projectPath = args && args.projectPath;
|
|
205
|
-
if (!projectPath || typeof projectPath !== 'string') return [];
|
|
206
|
-
try { if (!fs.statSync(projectPath).isDirectory()) return []; } catch { return []; }
|
|
207
|
-
|
|
208
|
-
const opts = Object.assign({}, DEFAULTS, args.opts || {});
|
|
209
|
-
const deadline = _now() + Math.max(1, opts.walltimeMs);
|
|
210
|
-
|
|
211
|
-
const seenKey = new Set(); // dedupe across surfaces by `${kind}:${name}`
|
|
212
|
-
const seenSlash = new Set();
|
|
213
|
-
const all = [];
|
|
214
|
-
|
|
215
|
-
for (const entry of _discoverSkills(projectPath, opts, deadline)) {
|
|
216
|
-
const key = `skill:${entry.name}`;
|
|
217
|
-
if (seenKey.has(key)) continue;
|
|
218
|
-
seenKey.add(key);
|
|
219
|
-
all.push(entry);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
for (const entry of _discoverCommands(projectPath, opts, deadline)) {
|
|
223
|
-
const key = `command:${entry.name}`;
|
|
224
|
-
if (seenKey.has(key)) continue;
|
|
225
|
-
seenKey.add(key);
|
|
226
|
-
seenSlash.add(entry.name); // /foo from .claude/commands subsumes doc-mention
|
|
227
|
-
all.push(entry);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
for (const entry of _discoverDocSlashCommands(projectPath, opts, deadline, seenSlash)) {
|
|
231
|
-
const key = `slash-command:${entry.name}`;
|
|
232
|
-
if (seenKey.has(key)) continue;
|
|
233
|
-
seenKey.add(key);
|
|
234
|
-
all.push(entry);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
return all;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Format a discovery result list as a Markdown block ready to splice into the
|
|
242
|
-
* review playbook. Returns empty string when the list is empty so the caller
|
|
243
|
-
* can no-op cleanly (no stray header, no blank padding lines).
|
|
244
|
-
*
|
|
245
|
-
* @param {Array} entries — output of discoverReviewSkills()
|
|
246
|
-
* @returns {string}
|
|
247
|
-
*/
|
|
248
|
-
function renderReviewSkillsBlock(entries) {
|
|
249
|
-
if (!Array.isArray(entries) || entries.length === 0) return '';
|
|
250
|
-
const lines = [];
|
|
251
|
-
lines.push('## Project review skills (prefer these when applicable)');
|
|
252
|
-
lines.push('');
|
|
253
|
-
lines.push("This project ships purpose-built review tooling. When the diff under review is within scope of one of these skills, INVOKE IT FIRST and use its findings as the primary signal — your verdict can then be anchored to what the skill returned plus any gaps you spot on top.");
|
|
254
|
-
lines.push('');
|
|
255
|
-
for (const e of entries) {
|
|
256
|
-
const kindHint = e.kind === 'skill' ? `skill: \`${e.name}\``
|
|
257
|
-
: e.kind === 'command' ? `\`${e.name}\``
|
|
258
|
-
: `\`${e.name}\``;
|
|
259
|
-
const pathHint = e.path ? ` (\`${e.path}\`)` : '';
|
|
260
|
-
const desc = e.oneLineDescription ? ` — ${e.oneLineDescription}` : '';
|
|
261
|
-
lines.push(`- ${kindHint}${pathHint}${desc}`);
|
|
262
|
-
}
|
|
263
|
-
lines.push('');
|
|
264
|
-
lines.push("Record the skill outcome in your completion report's `meta.review` block (`skillInvoked` or `skillSkipped` — see `docs/completion-reports.md`) so the engine can later measure skill-vs-first-principles signal.");
|
|
265
|
-
return lines.join('\n');
|
|
266
|
-
}
|
|
17
|
+
const {
|
|
18
|
+
discoverReviewSkills,
|
|
19
|
+
renderReviewSkillsBlock,
|
|
20
|
+
} = require('./discover-project-skills');
|
|
267
21
|
|
|
268
22
|
module.exports = {
|
|
269
23
|
discoverReviewSkills,
|
|
270
24
|
renderReviewSkillsBlock,
|
|
271
|
-
// exported for tests
|
|
272
|
-
_internal: {
|
|
273
|
-
KEYWORD_RE,
|
|
274
|
-
SLASH_COMMAND_RE,
|
|
275
|
-
DEFAULTS,
|
|
276
|
-
_parseFrontmatter,
|
|
277
|
-
_extractSlashCommandsFromDoc,
|
|
278
|
-
},
|
|
279
25
|
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* engine/playbook-intents.js — W-mq1cczi90006b21f
|
|
3
|
+
*
|
|
4
|
+
* Maps Minions playbook names (work-item types) to the intent buckets each
|
|
5
|
+
* playbook should surface from project-local skill discovery. The mapping is
|
|
6
|
+
* the single source of truth for "which playbook gets which slice of the
|
|
7
|
+
* skills block" — engine/playbook.js consults this at render time.
|
|
8
|
+
*
|
|
9
|
+
* Intent vocabulary (defined in engine/discover-project-skills.js):
|
|
10
|
+
* review | build | fix | test | plan | research | deploy | observability | meta
|
|
11
|
+
*
|
|
12
|
+
* Adding a new playbook? Add an entry here. Playbooks not in this map fall
|
|
13
|
+
* through to NO skills block (the engine treats undefined as the empty set,
|
|
14
|
+
* which is the safe default — agents see no header rather than a noisy one).
|
|
15
|
+
*
|
|
16
|
+
* Adding a new intent? Add the keyword regex to INTENT_KEYWORDS in
|
|
17
|
+
* engine/discover-project-skills.js AND a row to this map (or update an
|
|
18
|
+
* existing playbook to include it). Then document the verb in
|
|
19
|
+
* docs/project-skills.md.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
// Canonical mapping. Lower-cased playbook names; intent values must be
|
|
23
|
+
// members of INTENT_VOCABULARY in engine/discover-project-skills.js.
|
|
24
|
+
const PLAYBOOK_INTENTS = Object.freeze({
|
|
25
|
+
// Review-flavored playbooks — preserves PR-82 behavior verbatim.
|
|
26
|
+
review: ['review'],
|
|
27
|
+
|
|
28
|
+
// Fix dispatches often need to re-run review tooling after the fix lands,
|
|
29
|
+
// and may also touch tests; surface review+test alongside debug/triage.
|
|
30
|
+
fix: ['fix', 'test', 'review'],
|
|
31
|
+
|
|
32
|
+
// Implementation surfaces build/scaffold/codemod skills plus the matching
|
|
33
|
+
// test skills, and research for codebase investigation.
|
|
34
|
+
implement: ['build', 'test', 'research'],
|
|
35
|
+
'implement-shared': ['build', 'test', 'research'],
|
|
36
|
+
|
|
37
|
+
// Planning surfaces planning + research (codebase exploration is part of
|
|
38
|
+
// good planning).
|
|
39
|
+
plan: ['plan', 'research'],
|
|
40
|
+
'plan-to-prd': ['plan', 'research'],
|
|
41
|
+
decompose: ['plan', 'research'],
|
|
42
|
+
|
|
43
|
+
// Doc / verify / build-and-test playbooks — keep tight intent sets so
|
|
44
|
+
// we don't dump build-bumping skills into a docs prompt.
|
|
45
|
+
docs: ['research'],
|
|
46
|
+
verify: ['test', 'review'],
|
|
47
|
+
'build-and-test': ['build', 'test'],
|
|
48
|
+
test: ['test'],
|
|
49
|
+
|
|
50
|
+
// Exploration / asks: research-heavy.
|
|
51
|
+
explore: ['research'],
|
|
52
|
+
|
|
53
|
+
// qa-validate: targeted at test/qa skills.
|
|
54
|
+
'qa-validate': ['test'],
|
|
55
|
+
|
|
56
|
+
// Followup-dispatch is a template (not a top-level playbook); the engine
|
|
57
|
+
// inherits the parent dispatch's intent set when rendering follow-ups.
|
|
58
|
+
// See engine/playbook.js → renderPlaybook for the inheritance plumb.
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Look up the intent set for a playbook name. Returns [] for unknown
|
|
63
|
+
* playbooks (caller treats empty as "render no block").
|
|
64
|
+
*
|
|
65
|
+
* @param {string} playbookName
|
|
66
|
+
* @returns {string[]}
|
|
67
|
+
*/
|
|
68
|
+
function intentsForPlaybook(playbookName) {
|
|
69
|
+
if (!playbookName || typeof playbookName !== 'string') return [];
|
|
70
|
+
return PLAYBOOK_INTENTS[playbookName.toLowerCase()] || [];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = {
|
|
74
|
+
PLAYBOOK_INTENTS,
|
|
75
|
+
intentsForPlaybook,
|
|
76
|
+
};
|
package/engine/playbook.js
CHANGED
|
@@ -339,6 +339,17 @@ const PLAYBOOK_OPTIONAL_VARS = new Set([
|
|
|
339
339
|
'role', // 'primary' | 'co-service'
|
|
340
340
|
'primary_project', // canonical primary project name; empty on single-project sessions
|
|
341
341
|
'co_services_json', // JSON-encoded co-service project names (e.g. ["api","worker"]); empty when none
|
|
342
|
+
// W-mq16xtdx001a347e (PR-82) — project-local review-skill discovery vars.
|
|
343
|
+
// Always rendered (empty string when no discoveries); listed here so the
|
|
344
|
+
// unresolved-var check doesn't complain when discovery returns nothing or
|
|
345
|
+
// projects legitimately resolve it to ''.
|
|
346
|
+
'project_review_skills_block',
|
|
347
|
+
'skip_project_review_skills',
|
|
348
|
+
// W-mq1cczi90006b21f — generic, intent-filtered project skills block.
|
|
349
|
+
// Spliced by review/fix/implement/plan/etc. when discovery + intent map
|
|
350
|
+
// produce matches. Optional for the same reason as the review-only alias.
|
|
351
|
+
'project_skills_block',
|
|
352
|
+
'skip_project_skills',
|
|
342
353
|
]);
|
|
343
354
|
|
|
344
355
|
const PLAYBOOK_REQUIRED_VARS = {
|
|
@@ -695,30 +706,57 @@ function renderPlaybook(type, vars) {
|
|
|
695
706
|
} catch (e) { log('warn', `managed-spawn live-processes inject failed: ${e.message}`); }
|
|
696
707
|
}
|
|
697
708
|
|
|
698
|
-
// W-mq16xtdx001a347e
|
|
699
|
-
//
|
|
700
|
-
//
|
|
701
|
-
//
|
|
702
|
-
//
|
|
703
|
-
//
|
|
704
|
-
//
|
|
705
|
-
|
|
709
|
+
// W-mq16xtdx001a347e (PR #82) + W-mq1cczi90006b21f (generalization) —
|
|
710
|
+
// surface project-local skills relevant to the playbook being dispatched.
|
|
711
|
+
//
|
|
712
|
+
// PR-82 hard-scoped this to type === 'review'. This wiring generalizes it:
|
|
713
|
+
// 1. Discovery walks every project skill / command / documented slash-
|
|
714
|
+
// command (engine/discover-project-skills.js).
|
|
715
|
+
// 2. engine/playbook-intents.js maps playbook → intent set.
|
|
716
|
+
// 3. Discovered entries are filtered to the playbook's intent set and
|
|
717
|
+
// rendered into `project_skills_block`. Empty intent set or zero
|
|
718
|
+
// matches → empty string (no stray header).
|
|
719
|
+
//
|
|
720
|
+
// Header copy: review dispatches keep PR-82's "## Project review skills"
|
|
721
|
+
// header (existing agents/tests grep for it). All other playbooks use the
|
|
722
|
+
// generalized "## Project skills" header.
|
|
723
|
+
//
|
|
724
|
+
// Backward-compat: `project_review_skills_block` and
|
|
725
|
+
// `vars.skip_project_review_skills` remain wired so external consumers of
|
|
726
|
+
// the PR-82 names keep working. They alias to the generic var/flag.
|
|
727
|
+
const skipProjectSkills = !!(vars.skip_project_skills || vars.skip_project_review_skills);
|
|
728
|
+
let projectSkillsBlock = '';
|
|
729
|
+
if (!skipProjectSkills) {
|
|
706
730
|
try {
|
|
707
|
-
const discover = require('./discover-
|
|
731
|
+
const discover = require('./discover-project-skills');
|
|
732
|
+
const { intentsForPlaybook } = require('./playbook-intents');
|
|
708
733
|
const projectPath = matchedProject?.localPath || '';
|
|
709
734
|
if (projectPath) {
|
|
710
|
-
|
|
711
|
-
vars.
|
|
712
|
-
|
|
713
|
-
|
|
735
|
+
// Inherit intent set from parent dispatch type when the engine
|
|
736
|
+
// passes vars.parent_dispatch_type (followup-dispatch flow).
|
|
737
|
+
const effectiveType = vars.parent_dispatch_type || type;
|
|
738
|
+
const intents = intentsForPlaybook(effectiveType);
|
|
739
|
+
if (intents.length > 0) {
|
|
740
|
+
const entries = discover.discoverProjectSkills({ projectPath });
|
|
741
|
+
const filtered = discover.filterByIntents(entries, intents);
|
|
742
|
+
// type === 'review' keeps PR-82 header copy + meta.review.* outcome
|
|
743
|
+
// guidance; everything else uses the generic header that points at
|
|
744
|
+
// meta.skill.* in the completion report.
|
|
745
|
+
projectSkillsBlock = (type === 'review')
|
|
746
|
+
? discover.renderReviewSkillsBlock(filtered)
|
|
747
|
+
: discover.renderProjectSkillsBlock(filtered);
|
|
748
|
+
}
|
|
714
749
|
}
|
|
715
750
|
} catch (e) {
|
|
716
|
-
log('warn', `discover-
|
|
717
|
-
vars.project_review_skills_block = '';
|
|
751
|
+
log('warn', `discover-project-skills failed: ${e.message}`);
|
|
718
752
|
}
|
|
719
|
-
} else {
|
|
720
|
-
vars.project_review_skills_block = '';
|
|
721
753
|
}
|
|
754
|
+
vars.project_skills_block = projectSkillsBlock;
|
|
755
|
+
// Backward-compat alias for any consumer that still references
|
|
756
|
+
// {{project_review_skills_block}} directly. Same content; cheaper than
|
|
757
|
+
// re-rendering. Non-review playbooks that don't reference the alias
|
|
758
|
+
// ignore it (it's in PLAYBOOK_OPTIONAL_VARS).
|
|
759
|
+
vars.project_review_skills_block = projectSkillsBlock;
|
|
722
760
|
|
|
723
761
|
// W-mpeiwz6k0005bf34-c — opt-in qa-validate context block. Injected only
|
|
724
762
|
// when the dispatcher set vars.qa_run_id (truthy) from the work item's
|
|
@@ -1129,10 +1167,11 @@ function buildPrDispatch(agentId, config, project, pr, type, extraVars, taskLabe
|
|
|
1129
1167
|
const vars = {
|
|
1130
1168
|
...buildBaseVars(agentId, config, project),
|
|
1131
1169
|
branch_name: extraVars?.pr_branch || '',
|
|
1132
|
-
// W-mq16xtdx001a347e — honor meta
|
|
1133
|
-
//
|
|
1134
|
-
//
|
|
1170
|
+
// W-mq16xtdx001a347e + W-mq1cczi90006b21f — honor both meta flag names
|
|
1171
|
+
// (skipProjectReviewSkills is the PR-82 alias). Default OFF: review/fix
|
|
1172
|
+
// dispatches surface the project-local skills block.
|
|
1135
1173
|
skip_project_review_skills: !!(meta && meta.skipProjectReviewSkills),
|
|
1174
|
+
skip_project_skills: !!(meta && (meta.skipProjectSkills || meta.skipProjectReviewSkills)),
|
|
1136
1175
|
...extraVars,
|
|
1137
1176
|
task_id: dispatchId,
|
|
1138
1177
|
};
|
package/engine.js
CHANGED
|
@@ -5966,6 +5966,18 @@ function renderProjectWorkItemPromptForAgent(item, workType, agentId, config, pr
|
|
|
5966
5966
|
// failure via the qa-session-draft-failed / qa-session-execute-failed
|
|
5967
5967
|
// path. (See playbooks/qa-session-draft.md → "Failure path" section.)
|
|
5968
5968
|
..._buildRunnerBriefVars(item, project),
|
|
5969
|
+
// W-mq16xtdx001a347e + W-mq1cczi90006b21f — escape hatches for dispatches
|
|
5970
|
+
// that should NOT be steered toward project-local skills (e.g. when the
|
|
5971
|
+
// diff under review IS that skill, so a meta-review needs first-principles).
|
|
5972
|
+
// Default OFF — the project skills block is the whole point of these WIs
|
|
5973
|
+
// and should surface on every applicable dispatch by default.
|
|
5974
|
+
//
|
|
5975
|
+
// Both meta names are honored:
|
|
5976
|
+
// - meta.skipProjectReviewSkills (PR-82 alias, review-only suppression)
|
|
5977
|
+
// - meta.skipProjectSkills (W-mq1cczi90006b21f, generic suppression)
|
|
5978
|
+
// Either flag suppresses both blocks on the dispatch.
|
|
5979
|
+
skip_project_review_skills: !!(item.meta && item.meta.skipProjectReviewSkills),
|
|
5980
|
+
skip_project_skills: !!(item.meta && (item.meta.skipProjectSkills || item.meta.skipProjectReviewSkills)),
|
|
5969
5981
|
};
|
|
5970
5982
|
const cpResult = buildWorkItemDispatchVars(item, vars, config, {
|
|
5971
5983
|
worktreePath: vars.worktree_path || root,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2148",
|
|
4
4
|
"description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
|
|
5
5
|
"bin": {
|
|
6
6
|
"minions": "bin/minions.js"
|
package/playbooks/fix.md
CHANGED
|
@@ -44,6 +44,10 @@ Before editing, split the feedback into:
|
|
|
44
44
|
|
|
45
45
|
## Health Check
|
|
46
46
|
|
|
47
|
+
{{#project_skills_block}}
|
|
48
|
+
{{project_skills_block}}
|
|
49
|
+
|
|
50
|
+
{{/project_skills_block}}
|
|
47
51
|
Before starting work, run `git status` and verify the worktree is clean and on the expected branch (`{{pr_branch}}`). If the worktree is dirty or on the wrong branch, report the issue and stop.
|
|
48
52
|
|
|
49
53
|
### Branch-mismatch guard (issue #2999)
|
|
@@ -45,6 +45,10 @@ Do ALL work in the worktree.
|
|
|
45
45
|
|
|
46
46
|
## Health Check
|
|
47
47
|
|
|
48
|
+
{{#project_skills_block}}
|
|
49
|
+
{{project_skills_block}}
|
|
50
|
+
|
|
51
|
+
{{/project_skills_block}}
|
|
48
52
|
Before starting work, run `git status` and verify the worktree is clean and on the expected branch (`{{branch_name}}`). If the worktree is dirty or on the wrong branch, report the issue and stop.
|
|
49
53
|
|
|
50
54
|
## Working Style
|
package/playbooks/implement.md
CHANGED
|
@@ -38,6 +38,10 @@ If this feature spans multiple projects, inspect the relevant repos, make change
|
|
|
38
38
|
|
|
39
39
|
## Health Check
|
|
40
40
|
|
|
41
|
+
{{#project_skills_block}}
|
|
42
|
+
{{project_skills_block}}
|
|
43
|
+
|
|
44
|
+
{{/project_skills_block}}
|
|
41
45
|
Before starting work, run `git status` and verify the worktree is clean and on the expected branch. If the worktree is dirty or on the wrong branch, report the issue and stop.
|
|
42
46
|
|
|
43
47
|
## Working Style
|
package/playbooks/plan-to-prd.md
CHANGED
|
@@ -17,6 +17,10 @@ A user has provided a plan. Analyze it against the codebase and produce a struct
|
|
|
17
17
|
|
|
18
18
|
## Instructions
|
|
19
19
|
|
|
20
|
+
{{#project_skills_block}}
|
|
21
|
+
{{project_skills_block}}
|
|
22
|
+
|
|
23
|
+
{{/project_skills_block}}
|
|
20
24
|
1. **Read the plan carefully** — understand the goals, scope, and requirements
|
|
21
25
|
- If the plan declares `Project: <name>` (including `**Project:** <name>`), the engine has resolved `{{project_name}}` from that declaration. Preserve `{{project_name}}` for the top-level `project`, default item `project`, filename, and implementation framing; do not let contextual mentions of another product or repository override it.
|
|
22
26
|
2. **Check for an existing PRD** — if the engine provides `existing_prd_json` below, a PRD already exists for this plan. See "Reusing an Existing PRD" section for how to preserve item IDs and done statuses. If no existing PRD is provided, this is a fresh run — all items start as `"missing"`.
|
package/playbooks/plan.md
CHANGED
|
@@ -27,6 +27,10 @@ A user has described a feature they want built. Your job is to create a detailed
|
|
|
27
27
|
- Identify the core goal, constraints, and success criteria
|
|
28
28
|
- Note any ambiguities that need to be called out
|
|
29
29
|
|
|
30
|
+
{{#project_skills_block}}
|
|
31
|
+
{{project_skills_block}}
|
|
32
|
+
|
|
33
|
+
{{/project_skills_block}}
|
|
30
34
|
### 2. Explore the Codebase
|
|
31
35
|
- Read `CLAUDE.md` at repo root and relevant directories
|
|
32
36
|
- Map the areas of code that this feature will touch
|
package/playbooks/review.md
CHANGED
|
@@ -27,10 +27,10 @@ Use subagents only for genuinely parallel, independent tasks (e.g., reviewing un
|
|
|
27
27
|
git diff {{main_branch}}...origin/{{pr_branch}}
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
{{#
|
|
31
|
-
{{
|
|
30
|
+
{{#project_skills_block}}
|
|
31
|
+
{{project_skills_block}}
|
|
32
32
|
|
|
33
|
-
{{/
|
|
33
|
+
{{/project_skills_block}}
|
|
34
34
|
2. Think about deploy risk before commenting:
|
|
35
35
|
- What user-visible behavior changed?
|
|
36
36
|
- What dependencies, callers, or tests could be affected?
|
package/bin/minions.js.rej
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
diff a/bin/minions.js b/bin/minions.js (rejected hunks)
|
|
2
|
-
@@ -852,6 +852,14 @@ if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
3
|
-
|
|
4
|
-
Dashboard:
|
|
5
|
-
minions dash Start web dashboard (default :7331)
|
|
6
|
-
+
|
|
7
|
-
+ Watchdog (out-of-process recovery):
|
|
8
|
-
+ minions watchdog install [--interval=5]
|
|
9
|
-
+ Register OS scheduler task to probe + heal every N minutes
|
|
10
|
-
+ (Windows Task Scheduler / macOS launchd / Linux systemd --user)
|
|
11
|
-
+ minions watchdog uninstall Remove the scheduled task (idempotent)
|
|
12
|
-
+ minions watchdog status Show registration + last-run details from the OS scheduler
|
|
13
|
-
+ minions watchdog tick One-shot probe + recovery (used by the scheduler; safe to run by hand)
|
|
14
|
-
${fs.existsSync(path.join(PKG_ROOT, '.git')) ? `
|
|
15
|
-
Dev mode (this checkout, contributors only):
|
|
16
|
-
minions --dev <cmd> Run against this checkout instead of ~/.minions/
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
diff a/dashboard/slim/body.html b/dashboard/slim/body.html (rejected hunks)
|
|
2
|
-
@@ -8,7 +8,8 @@
|
|
3
|
-
silently break (handler attaches before crash, button still renders,
|
|
4
|
-
click fires but no global handler). addEventListener attaches inside
|
|
5
|
-
the same scope and is observable in DevTools when wiring fails. -->
|
|
6
|
-
- <button id="slim-new-chat-btn" class="icon-btn" title="New chat (opens a new tab)">✎</button>
|
|
7
|
-
+ <button id="slim-back-classic-btn" class="topbar-back-btn" title="Return to the classic dashboard">← Classic dashboard</button>
|
|
8
|
-
+ <button id="slim-report-bug-btn" class="topbar-back-btn" title="Report a bug in Minions">Report Bug</button>
|
|
9
|
-
<button id="slim-settings-btn" class="icon-btn" title="Settings">⚙</button>
|
|
10
|
-
</div>
|
|
11
|
-
</div>
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
diff a/dashboard/slim/js/command-send.js b/dashboard/slim/js/command-send.js (rejected hunks)
|
|
2
|
-
@@ -195,8 +195,8 @@
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
// ── Wiring ──────────────────────────────────────────────────────
|
|
6
|
-
- var newChatBtn = document.getElementById('slim-new-chat-btn');
|
|
7
|
-
- if (newChatBtn) newChatBtn.addEventListener('click', function() { newTab(); });
|
|
8
|
-
+ // (The header "new chat" button was replaced by "Report Bug"; new tabs are
|
|
9
|
-
+ // created via the "+" affordance in the chat tab bar — see renderTabBar.)
|
|
10
|
-
sendBtn.addEventListener('click', sendMessage);
|
|
11
|
-
stopBtn.addEventListener('click', abortActive);
|
|
12
|
-
inputEl.addEventListener('keydown', function(ev) {
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
diff a/dashboard/slim/js/history.js b/dashboard/slim/js/history.js (rejected hunks)
|
|
2
|
-
@@ -96,7 +83,12 @@
|
|
3
|
-
chip.title = 'review';
|
|
4
|
-
} else {
|
|
5
|
-
chip.className = 'completions-card-type-chip';
|
|
6
|
-
- chip.textContent = typeEmoji(c.type);
|
|
7
|
-
+ // Known types → Fluent icon via CSS mask (data-type); unknown → "•" glyph.
|
|
8
|
-
+ if (c.type && TYPE_ICON_SET[c.type]) {
|
|
9
|
-
+ chip.setAttribute('data-type', c.type);
|
|
10
|
-
+ } else {
|
|
11
|
-
+ chip.textContent = '•';
|
|
12
|
-
+ }
|
|
13
|
-
if (c.type) chip.title = c.type;
|
|
14
|
-
}
|
|
15
|
-
railTop.appendChild(chip);
|
|
16
|
-
@@ -105,7 +97,9 @@
|
|
17
|
-
railBottom.className = 'completions-card-rail-bottom';
|
|
18
|
-
var icon = document.createElement('span');
|
|
19
|
-
icon.className = 'completions-card-status-icon ' + status;
|
|
20
|
-
- icon.textContent = status === 'active' ? '●' : (status === 'ok' ? '✓' : (status === 'warn' ? '⚠' : '✕'));
|
|
21
|
-
+ // ok/warn/fail render as Fluent icons via the status class (CSS mask in
|
|
22
|
-
+ // styles.css); the live 'active' state keeps its pulsing dot.
|
|
23
|
-
+ if (status === 'active') icon.textContent = '●';
|
|
24
|
-
icon.title = status === 'active' ? 'Running' : (status === 'ok' ? 'Success' : (status === 'warn' ? 'Partial' : 'Failure'));
|
|
25
|
-
var sep = document.createElement('span');
|
|
26
|
-
sep.className = 'completions-card-rail-sep';
|