@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.
@@ -1,279 +1,25 @@
1
1
  /**
2
- * engine/discover-review-skills.js — W-mq16xtdx001a347e
2
+ * engine/discover-review-skills.js — backward-compat shim for PR #82
3
+ * (W-mq16xtdx001a347e).
3
4
  *
4
- * Cheap, bounded filesystem walk that surfaces project-local review tooling
5
- * (skills, slash-commands, copilot-instructions slash-command mentions) so
6
- * Minions review dispatches reliably steer agents toward the right skill
7
- * instead of reviewing every PR from first principles.
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
- * Walk surfaces:
10
- * - .claude/skills/*\/SKILL.md — name/description frontmatter
11
- * - .claude/commands/*.md — filename + first heading
12
- * - .github/copilot-instructions.md — slash-command mentions (top N bytes)
13
- * - CLAUDE.md — slash-command mentions (top N bytes)
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 fs = require('fs');
26
- const path = require('path');
27
-
28
- // Heuristic match — "review", "swarm", or the explicit constellation-review id.
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
+ };
@@ -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 review dispatches: discover project-local review
699
- // tooling (.claude/skills, .claude/commands, copilot-instructions slash-
700
- // command mentions) and render a "Project review skills" block near the top
701
- // of the playbook. Default-ON for `review` type; suppressed when the
702
- // dispatcher passes `meta.skipProjectReviewSkills` (threaded as
703
- // vars.skip_project_review_skills). Renders empty string when discovery
704
- // returns no matches so first-principles reviewers see no stray header.
705
- if (type === 'review' && !vars.skip_project_review_skills) {
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-review-skills');
731
+ const discover = require('./discover-project-skills');
732
+ const { intentsForPlaybook } = require('./playbook-intents');
708
733
  const projectPath = matchedProject?.localPath || '';
709
734
  if (projectPath) {
710
- const entries = discover.discoverReviewSkills({ projectPath });
711
- vars.project_review_skills_block = discover.renderReviewSkillsBlock(entries);
712
- } else {
713
- vars.project_review_skills_block = '';
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-review-skills failed: ${e.message}`);
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.skipProjectReviewSkills on PR-built
1133
- // dispatches (auto re-reviews, conflict-fix follow-ups, etc.). Default
1134
- // OFF: review dispatches surface the project-local review skills block.
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.2147",
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
@@ -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
@@ -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
@@ -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
- {{#project_review_skills_block}}
31
- {{project_review_skills_block}}
30
+ {{#project_skills_block}}
31
+ {{project_skills_block}}
32
32
 
33
- {{/project_review_skills_block}}
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?
@@ -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)">&#x270E;</button>
7
- + <button id="slim-back-classic-btn" class="topbar-back-btn" title="Return to the classic dashboard">&#8592; 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">&#9881;</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';