@yemi33/minions 0.1.2146 → 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.
@@ -301,6 +301,12 @@ const PLAYBOOK_OPTIONAL_VARS = new Set([
301
301
  // PR-context vars on non-PR tasks (implement/explore/etc.)
302
302
  'pr_id', 'pr_number', 'pr_title', 'pr_branch', 'pr_author', 'pr_url',
303
303
  'reviewer',
304
+ // W-mq16xtdx001a347e — review dispatches inject this block (or empty string
305
+ // when discovery finds no project-local review skills). Optional because
306
+ // non-review playbooks never reference it and review-against-skill-less
307
+ // projects legitimately resolve it to ''.
308
+ 'project_review_skills_block',
309
+ 'skip_project_review_skills',
304
310
  // P-e6b3c2d8 — QA Session template vars. session_id / target_kind /
305
311
  // flows_raw / managed_spawn_name are required (declared in
306
312
  // PLAYBOOK_REQUIRED_VARS['qa-session-setup']); these target_* sub-fields
@@ -333,6 +339,17 @@ const PLAYBOOK_OPTIONAL_VARS = new Set([
333
339
  'role', // 'primary' | 'co-service'
334
340
  'primary_project', // canonical primary project name; empty on single-project sessions
335
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',
336
353
  ]);
337
354
 
338
355
  const PLAYBOOK_REQUIRED_VARS = {
@@ -689,6 +706,58 @@ function renderPlaybook(type, vars) {
689
706
  } catch (e) { log('warn', `managed-spawn live-processes inject failed: ${e.message}`); }
690
707
  }
691
708
 
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) {
730
+ try {
731
+ const discover = require('./discover-project-skills');
732
+ const { intentsForPlaybook } = require('./playbook-intents');
733
+ const projectPath = matchedProject?.localPath || '';
734
+ if (projectPath) {
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
+ }
749
+ }
750
+ } catch (e) {
751
+ log('warn', `discover-project-skills failed: ${e.message}`);
752
+ }
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;
760
+
692
761
  // W-mpeiwz6k0005bf34-c — opt-in qa-validate context block. Injected only
693
762
  // when the dispatcher set vars.qa_run_id (truthy) from the work item's
694
763
  // `meta.qaRunId`. Mirrors the managed_spawn hint pattern: the playbook is
@@ -1098,6 +1167,11 @@ function buildPrDispatch(agentId, config, project, pr, type, extraVars, taskLabe
1098
1167
  const vars = {
1099
1168
  ...buildBaseVars(agentId, config, project),
1100
1169
  branch_name: extraVars?.pr_branch || '',
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.
1173
+ skip_project_review_skills: !!(meta && meta.skipProjectReviewSkills),
1174
+ skip_project_skills: !!(meta && (meta.skipProjectSkills || meta.skipProjectReviewSkills)),
1101
1175
  ...extraVars,
1102
1176
  task_id: dispatchId,
1103
1177
  };
package/engine/shared.js CHANGED
@@ -5919,6 +5919,97 @@ function migratePrGateFlags(projectsRoot) {
5919
5919
  return summary;
5920
5920
  }
5921
5921
 
5922
+ // ─── PR Reference → URL Derivation ───────────────────────────────────────────
5923
+ //
5924
+ // W-mq5wfh1v000e0da9 — Given a PR ref (URL, canonical `host:scope#N` id, or
5925
+ // bare number / legacy `PR-N`), derive a usable URL. Used by the fix-WI auto-
5926
+ // enrollment helper below so untracked PRs can be linked without forcing the
5927
+ // caller to construct a URL themselves.
5928
+ //
5929
+ // Returns null when no URL can be derived (e.g. bare number with no
5930
+ // project.prUrlBase to anchor against).
5931
+ function deriveUrlForPrRef(prRef, project) {
5932
+ const ref = String(prRef || '').trim();
5933
+ if (!ref) return null;
5934
+ if (/^https?:\/\//i.test(ref)) return ref;
5935
+ const canonical = parseCanonicalPrId(ref);
5936
+ if (canonical) {
5937
+ if (canonical.scope.startsWith('github:')) {
5938
+ const slug = canonical.scope.slice('github:'.length);
5939
+ return `https://github.com/${slug}/pull/${canonical.prNumber}`;
5940
+ }
5941
+ if (canonical.scope.startsWith('ado:')) {
5942
+ const parts = canonical.scope.slice('ado:'.length).split('/');
5943
+ if (parts.length >= 3) {
5944
+ return `https://dev.azure.com/${parts[0]}/${parts[1]}/_git/${parts[2]}/pullrequest/${canonical.prNumber}`;
5945
+ }
5946
+ }
5947
+ }
5948
+ // Bare number / PR-N — derive from project.prUrlBase if available.
5949
+ const numMatch = ref.match(/^(?:PR-)?(\d+)$/i);
5950
+ if (numMatch && project && project.prUrlBase) {
5951
+ return `${project.prUrlBase}${numMatch[1]}`;
5952
+ }
5953
+ return null;
5954
+ }
5955
+
5956
+ // W-mq5wfh1v000e0da9 — Auto-enroll a PR into pull-requests.json when a
5957
+ // `type: fix` work item is created with a structured PR pointer. Without
5958
+ // this, fix WIs against untracked PRs bypass the polling / review / build
5959
+ // pipelines (the engine `pr_not_found` gate blocks dispatch entirely, and
5960
+ // human PR comments / build failures never surface) until an operator hits
5961
+ // POST /api/pull-requests/link manually.
5962
+ //
5963
+ // Idempotent: no-ops if the PR is already enrolled. Concurrent calls are
5964
+ // safe because the underlying upsertPullRequestRecord serializes via
5965
+ // mutatePullRequests.
5966
+ //
5967
+ // Only inspects STRUCTURED refs (targetPr / pr_id / prId / sourcePr /
5968
+ // pullRequest / prUrl / prNumber / references[*].url / meta.pr_followup
5969
+ // .parent_pr_url). Description-only PR mentions are INTENTIONALLY skipped
5970
+ // per the "structured-vs-loose split" — enrollment must be intentional.
5971
+ //
5972
+ // Returns:
5973
+ // { skipped: true, reason } — not a fix / no ref / no URL / upsert error
5974
+ // { alreadyEnrolled: true, id } — PR already in pull-requests.json
5975
+ // { enrolled: true, id, prPath } — newly enrolled
5976
+ function autoEnrollPrFromFixWorkItem(item, project, minionsDir) {
5977
+ if (!item || item.type !== WORK_TYPE.FIX) return { skipped: true, reason: 'not-fix' };
5978
+ const prRef = extractStructuredWorkItemPrRef(item);
5979
+ if (!prRef) return { skipped: true, reason: 'no-structured-ref' };
5980
+ const url = deriveUrlForPrRef(prRef, project);
5981
+ if (!url) return { skipped: true, reason: 'no-url' };
5982
+ const prPath = project ? projectPrPath(project) : centralPullRequestsPath(minionsDir);
5983
+ const existing = safeJsonArr(prPath);
5984
+ if (findPrRecord(existing, prRef, project)) {
5985
+ return { alreadyEnrolled: true, id: getCanonicalPrId(project, prRef, url) };
5986
+ }
5987
+ const parsedUrl = parsePrUrl(url);
5988
+ const prNum = parsedUrl ? parsedUrl.prNumber : null;
5989
+ const prId = getCanonicalPrId(project, prRef, url);
5990
+ try {
5991
+ const result = upsertPullRequestRecord(prPath, {
5992
+ id: prId,
5993
+ prNumber: prNum,
5994
+ title: `PR #${prNum != null ? prNum : '?'} (polling...)`,
5995
+ description: '',
5996
+ agent: 'human',
5997
+ branch: '',
5998
+ reviewStatus: 'pending',
5999
+ status: 'active',
6000
+ created: new Date().toISOString(),
6001
+ url,
6002
+ contextOnly: false,
6003
+ }, { project, itemId: item.id });
6004
+ return result.created
6005
+ ? { enrolled: true, id: result.id, prPath }
6006
+ : { alreadyEnrolled: true, id: result.id };
6007
+ } catch (e) {
6008
+ log('warn', `autoEnrollPrFromFixWorkItem ${item.id}: ${e.message}`);
6009
+ return { skipped: true, reason: 'upsert-error', error: e.message };
6010
+ }
6011
+ }
6012
+
5922
6013
  // ─── Cross-Platform Process Kill Helpers ─────────────────────────────────────
5923
6014
 
5924
6015
  function normalizeKillPid(proc) {
@@ -7091,6 +7182,8 @@ module.exports = {
7091
7182
  upsertPullRequestRecord,
7092
7183
  isAutoManagedPrRecord, // W-mq5s5ttx000j7ab8-a — exported for engine + watch-plugin gate consolidation
7093
7184
  migratePrGateFlags, // W-mq5s5ttx000j7ab8-a — boot migration wired from engine/cli.js
7185
+ autoEnrollPrFromFixWorkItem,
7186
+ deriveUrlForPrRef, // exported for testing
7094
7187
  nextWorkItemId,
7095
7188
  getProjectOrg,
7096
7189
  getAdoOrgBase,
package/engine.js CHANGED
@@ -5885,6 +5885,12 @@ function renderProjectWorkItemPromptForAgent(item, workType, agentId, config, pr
5885
5885
  qa_artifacts_dir: item.meta && item.meta.qaRunId
5886
5886
  ? path.posix.join('engine', 'qa-artifacts', String(item.meta.qaRunId))
5887
5887
  : '',
5888
+ // W-mq16xtdx001a347e — escape hatch for review dispatches that should NOT
5889
+ // be steered toward project-local review skills (e.g. when the diff
5890
+ // under review IS that skill, so a meta-review needs first-principles).
5891
+ // Default OFF — the new "Project review skills" block is the whole point
5892
+ // of W-mq16xtdx and should surface on every review by default.
5893
+ skip_project_review_skills: !!(item.meta && item.meta.skipProjectReviewSkills),
5888
5894
  // P-e6b3c2d8 — QA Session template vars. The qa-sessions chain helpers
5889
5895
  // (engine/qa-sessions.js#_baseWorkItem) stamp meta.sessionId,
5890
5896
  // meta.sessionPhase, and meta.qaSession.{target,flowsRaw,mode,capture,runner}
@@ -5960,6 +5966,18 @@ function renderProjectWorkItemPromptForAgent(item, workType, agentId, config, pr
5960
5966
  // failure via the qa-session-draft-failed / qa-session-execute-failed
5961
5967
  // path. (See playbooks/qa-session-draft.md → "Failure path" section.)
5962
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)),
5963
5981
  };
5964
5982
  const cpResult = buildWorkItemDispatchVars(item, vars, config, {
5965
5983
  worktreePath: vars.worktree_path || root,
@@ -6319,6 +6337,18 @@ function discoverFromWorkItems(config, project) {
6319
6337
  skipped.noAgent++; continue;
6320
6338
  }
6321
6339
 
6340
+ // W-mq5wfh1v000e0da9 — defense-in-depth: auto-enroll the PR for fix WIs
6341
+ // carrying a structured PR pointer. Primary enrollment runs in the
6342
+ // dashboard `POST /api/work-items` handler, but WIs created via CLI / restored
6343
+ // from disk / older code paths might land here without the PR record.
6344
+ // No-op if the PR is already tracked. Failures swallowed — the gate below
6345
+ // still trips on missing PR records and surfaces `pr_not_found`.
6346
+ try {
6347
+ if (item.type === WORK_TYPE.FIX) {
6348
+ shared.autoEnrollPrFromFixWorkItem(item, project, MINIONS_DIR);
6349
+ }
6350
+ } catch (e) { log('warn', `auto-enroll PR for ${item.id}: ${e.message}`); }
6351
+
6322
6352
  const linkedPr = resolveWorkItemPrRecord(item, project);
6323
6353
  const promptItem = linkedPr ? withWorkItemPrContext(item, linkedPr) : item;
6324
6354
  const prBranch = linkedPr?.branch || '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2146",
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,6 +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_skills_block}}
31
+ {{project_skills_block}}
32
+
33
+ {{/project_skills_block}}
30
34
  2. Think about deploy risk before commenting:
31
35
  - What user-visible behavior changed?
32
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';