@yemi33/minions 0.1.1821 → 0.1.1823

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/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1823 (2026-05-09)
4
+
5
+ ### Other
6
+ - docs(copilot): document test runner specifics for agents
7
+
3
8
  ## 0.1.1821 (2026-05-09)
4
9
 
5
10
  ### Features
@@ -20,11 +20,11 @@ try { localStorage.removeItem('cc-sending'); } catch {}
20
20
  function _ccStripActionBlockFromText(value) {
21
21
  var text = value || '';
22
22
  if (!text) return text;
23
- // Tier 1 — strict: 3 leading + 0-3 trailing equals on its own line.
24
- var full = /(?:^|\r?\n)===ACTIONS={0,3}[ \t]*(?=\r?\n|$)/m.exec(text);
23
+ // Tier 1 — strict: exact canonical delimiter on its own line.
24
+ var full = /(?:^|\r?\n)===ACTIONS===[ \t]*(?=\r?\n|$)/m.exec(text);
25
25
  if (full) return text.slice(0, full.index + full[0].indexOf('===ACTIONS')).trim();
26
- // Tier 2 — loose: ===ACTIONS followed by punctuation/extra-equals to EOL.
27
- var block = /(?:^|\r?\n)===ACTIONS\b[^\r\n]*(?=\r?\n|$)/m.exec(text);
26
+ // Tier 2 — loose: sentinel-looking malformed delimiters; not prose.
27
+ var block = /(?:^|\r?\n)===ACTIONS(?:[ \t]*(?:[-=]>?|={1,}|$)|[^A-Za-z0-9_\s\r\n][^\r\n]*)(?=\r?\n|$)/m.exec(text);
28
28
  if (block) return text.slice(0, block.index + block[0].indexOf('===ACTIONS')).trim();
29
29
  // Tier 3 — very loose: 2+ leading equals + ACTIONS keyword + 0+ trailing
30
30
  // equals, case-insensitive. Catches ====ACTIONS===, ===actions===, etc.
package/dashboard.js CHANGED
@@ -233,9 +233,30 @@ function normalizePrMetadata(metadata) {
233
233
  };
234
234
  }
235
235
 
236
- function getWorkItemPrRef(input) {
237
- if (!input || typeof input !== 'object') return null;
238
- return input.targetPr || input.pr || input.prId || input.prNumber || input.pullRequest || input.sourcePr || input.prUrl || null;
236
+ function normalizePrTitleMatchText(value) {
237
+ return String(value || '').trim().toLowerCase().replace(/\s+/g, ' ');
238
+ }
239
+
240
+ function findPrRecordReferencedByText(prs, text, project = null) {
241
+ if (!Array.isArray(prs) || !String(text || '').trim()) return null;
242
+ const explicitRef = extractPrRefFromText(text);
243
+ if (explicitRef) return shared.findPrRecord(prs, explicitRef, project);
244
+ const normalizedText = normalizePrTitleMatchText(text);
245
+ if (!normalizedText) return null;
246
+ const matches = prs.filter(pr => {
247
+ const title = normalizePrTitleMatchText(pr?.title);
248
+ return title.length >= 8 && normalizedText.includes(title);
249
+ });
250
+ return matches.length === 1 ? matches[0] : null;
251
+ }
252
+
253
+ function inferActionPrRecord(action, prs, project = null) {
254
+ const text = [
255
+ action?.title,
256
+ action?.description,
257
+ action?.reason,
258
+ ].filter(value => typeof value === 'string' && value.trim()).join('\n');
259
+ return findPrRecordReferencedByText(prs, text, project);
239
260
  }
240
261
 
241
262
  function copyWorkItemPrFields(item, input, pr = null) {
@@ -1901,11 +1922,11 @@ function findCCActionsDelimiter(text) {
1901
1922
  // but are not parsed (they shouldn't reach the user as actions).
1902
1923
  function findCCActionsHeader(text) {
1903
1924
  if (!text) return null;
1904
- // Tier 1 — strict, parseable: 3 leading + 0-3 trailing equals, well-formed line.
1905
- const strict = /(?:^|\r?\n)===ACTIONS={0,3}[ \t]*(?=\r?\n|$)/m.exec(text);
1925
+ // Tier 1 — strict, parseable: the exact canonical delimiter on its own line.
1926
+ const strict = /(?:^|\r?\n)===ACTIONS===[ \t]*(?=\r?\n|$)/m.exec(text);
1906
1927
  if (strict) {
1907
1928
  const headerStart = strict.index + strict[0].indexOf('===ACTIONS');
1908
- const headerMatch = text.slice(headerStart).match(/^===ACTIONS={0,3}[ \t]*/);
1929
+ const headerMatch = text.slice(headerStart).match(/^===ACTIONS===[ \t]*/);
1909
1930
  return {
1910
1931
  index: headerStart,
1911
1932
  headerLength: headerMatch ? headerMatch[0].length : '===ACTIONS==='.length,
@@ -2020,6 +2041,32 @@ function _extractActionsJson(segment) {
2020
2041
  const CC_DISPATCH_ACTION_ALIASES = new Set(['fix', 'explore', 'review', 'test', 'implement', 'implement:large', 'ask', 'verify']);
2021
2042
  const CC_ACTION_INTENT_WORK_TYPES = new Set(['fix', 'implement', 'implement:large', 'explore', 'review', 'test', 'ask', 'verify']);
2022
2043
 
2044
+ function getWorkItemPrRef(input) {
2045
+ if (!input || typeof input !== 'object') return null;
2046
+ return input.targetPr || input.pr || input.prId || input.prNumber || input.pullRequest || input.sourcePr || input.prUrl || null;
2047
+ }
2048
+
2049
+ function isPrTargetedWorkType(workType) {
2050
+ return ['fix', 'review', 'test'].includes(String(workType || '').toLowerCase());
2051
+ }
2052
+
2053
+ function trimTrailingPrRefPunctuation(value) {
2054
+ return String(value || '').replace(/[),.;:]+$/g, '');
2055
+ }
2056
+
2057
+ function extractPrRefFromText(value) {
2058
+ const text = String(value || '');
2059
+ if (!text.trim()) return null;
2060
+ const urlMatch = text.match(/https?:\/\/[^\s<>()]+(?:\/pull\/\d+|\/pullrequest\/\d+)[^\s<>()]*/i);
2061
+ if (urlMatch) return trimTrailingPrRefPunctuation(urlMatch[0]);
2062
+ const canonicalMatch = text.match(/\b(?:github|ado):[^\s#]+#\d+\b/i);
2063
+ if (canonicalMatch) return canonicalMatch[0];
2064
+ const legacyMatch = text.match(/\bPR-(\d+)\b/i);
2065
+ if (legacyMatch) return legacyMatch[1];
2066
+ const numberMatch = text.match(/\b(?:pr|pull\s+request|pullrequest)\s*#?\s*(\d+)\b/i);
2067
+ return numberMatch ? numberMatch[1] : null;
2068
+ }
2069
+
2023
2070
  function normalizeCCAction(action) {
2024
2071
  if (!action || typeof action !== 'object') return action;
2025
2072
  if (typeof action.type !== 'string') return action;
@@ -2051,7 +2098,7 @@ function _ccCleanIntentString(value, max = 500) {
2051
2098
  function _ccNormalizeIntentMetadata(meta) {
2052
2099
  if (!meta || typeof meta !== 'object' || Array.isArray(meta)) return {};
2053
2100
  const out = {};
2054
- for (const key of ['intent', 'type', 'title', 'description', 'priority', 'project', 'branchStrategy', 'branch_strategy']) {
2101
+ for (const key of ['intent', 'type', 'title', 'description', 'priority', 'project', 'branchStrategy', 'branch_strategy', 'pr', 'targetPr', 'prId', 'prNumber', 'pullRequest', 'sourcePr', 'prUrl']) {
2055
2102
  const value = _ccCleanIntentString(meta[key], key === 'description' ? 2000 : 300);
2056
2103
  if (value) out[key] = value;
2057
2104
  }
@@ -2165,7 +2212,7 @@ function _actionsWithIntentFallback(actions, opts = {}) {
2165
2212
  const { message = '', intentMetadata = null, source = 'command-center', filePath = null, title: docTitle = null, answerText = '', toolUses = [] } = opts;
2166
2213
  const existing = (Array.isArray(actions) ? actions.map(normalizeCCAction) : [])
2167
2214
  .filter(action => !_ccShouldSuppressAnsweredAskDispatch(action, { message, source, answerText, toolUses }));
2168
- if (_messageRequestsDirectHandling(message)) return existing.filter(a => normalizeCCAction(a)?.type !== 'dispatch');
2215
+ if (_messageRequestsDirectHandling(message, { answerText, toolUses })) return existing.filter(a => normalizeCCAction(a)?.type !== 'dispatch');
2169
2216
  if (existing.some(a => normalizeCCAction(a)?.type === 'dispatch')) return existing;
2170
2217
  if (existing.length > 0) return existing;
2171
2218
  const meta = _ccNormalizeIntentMetadata(intentMetadata);
@@ -2199,6 +2246,8 @@ function _actionsWithIntentFallback(actions, opts = {}) {
2199
2246
  workType: intent.workType,
2200
2247
  ...common,
2201
2248
  };
2249
+ const prRef = getWorkItemPrRef(meta) || extractPrRefFromText([message, meta.title, meta.description].filter(Boolean).join('\n'));
2250
+ if (prRef && isPrTargetedWorkType(intent.workType)) action.pr = prRef;
2202
2251
  if (!hasTarget) action._intentFallbackError = _ccFallbackMissingTargetError(intent);
2203
2252
  if (_ccShouldSuppressAnsweredAskDispatch(action, { message, source, answerText, toolUses })) return existing;
2204
2253
  return [action];
@@ -2246,7 +2295,11 @@ function parseCCActions(text) {
2246
2295
  if (jsonStr) {
2247
2296
  try {
2248
2297
  const parsed = JSON.parse(jsonStr);
2249
- actions = Array.isArray(parsed) ? parsed : [parsed];
2298
+ if (Array.isArray(parsed)) {
2299
+ actions = parsed;
2300
+ } else {
2301
+ parseError = 'actions JSON must be an array after ===ACTIONS=== delimiter';
2302
+ }
2250
2303
  } catch (e) {
2251
2304
  parseError = e.message || 'invalid JSON';
2252
2305
  }
@@ -2261,17 +2314,6 @@ function parseCCActions(text) {
2261
2314
  parseError = 'Malformed ===ACTIONS=== delimiter (extra equals, lowercase, or trailing punctuation). Actions silently discarded — fix the model output.';
2262
2315
  }
2263
2316
  }
2264
- if (actions.length === 0) {
2265
- const actionRegex = /`{3,}\s*action\s*\r?\n([\s\S]*?)`{3,}/g;
2266
- let match;
2267
- while ((match = actionRegex.exec(displayText)) !== null) {
2268
- try { actions.push(JSON.parse(match[1].trim())); } catch {}
2269
- }
2270
- if (actions.length > 0) {
2271
- displayText = displayText.replace(/`{3,}\s*action\s*\r?\n[\s\S]*?`{3,}\n?/g, '').trim();
2272
- parseError = null; // legacy fallback recovered actions
2273
- }
2274
- }
2275
2317
  actions = actions.map(normalizeCCAction);
2276
2318
  const result = { text: displayText, actions };
2277
2319
  if (parseError && actions.length === 0) {
@@ -2455,15 +2497,33 @@ function _messageExplicitlyRequestsDelegation(message) {
2455
2497
  return false;
2456
2498
  }
2457
2499
 
2458
- function _messageRequestsDirectHandling(message) {
2500
+ function _messageRequestsDirectHandling(message, opts = {}) {
2459
2501
  const normalized = _normalizeIntentText(message);
2460
2502
  if (!normalized.trim()) return false;
2503
+ if (_messageRequestsInformationOnly(message) && !_unansweredInvestigationSignal(opts)) return true;
2461
2504
  if (_intentHasAnyPhrase(normalized, DIRECT_HANDLING_PHRASES)) return true;
2462
2505
  if (_intentHasAnyToken(normalized, DIRECT_QUICK_TERMS) && _intentHasAnyToken(normalized, DIRECT_QUICK_OBJECTS)) return true;
2463
2506
  if (_intentHasVerbObject(normalized, DIRECT_REPLY_TERMS, DIRECT_REPLY_TARGETS)) return true;
2464
2507
  return _intentHasVerbObject(normalized, DIRECT_SELF_TERMS, DIRECT_SELF_TARGETS);
2465
2508
  }
2466
2509
 
2510
+ function _unansweredInvestigationSignal({ answerText = '', toolUses = [] } = {}) {
2511
+ const answerLen = String(answerText || '').trim().length;
2512
+ const toolCount = Array.isArray(toolUses) ? toolUses.length : 0;
2513
+ return toolCount >= 4 && answerLen < ANSWERED_ASK_MIN_CHARS;
2514
+ }
2515
+
2516
+ function _messageRequestsInformationOnly(message) {
2517
+ const normalized = _normalizeIntentText(message);
2518
+ const tokens = _intentTokens(normalized);
2519
+ if (!tokens.length) return false;
2520
+ if (['what', 'which', 'who', 'when', 'where', 'why', 'how'].includes(tokens[0])) return true;
2521
+ if (['show', 'list'].includes(tokens[0])) return true;
2522
+ if (tokens[0] === 'tell' && tokens[1] === 'me') return true;
2523
+ if (String(message || '').trim().endsWith('?') && !_intentHasAnyToken(normalized, DELEGATION_ACTION_TERMS)) return true;
2524
+ return false;
2525
+ }
2526
+
2467
2527
  function _messageIsSmallDocOnlyRequest(normalized) {
2468
2528
  if (_intentHasAnyToken(normalized, DOC_CHAT_DIRECT_DOC_TERMS)) return true;
2469
2529
  return _intentHasVerbObject(normalized, DOC_CHAT_DOC_ACTION_TERMS, DOC_CHAT_DOC_OBJECTS);
@@ -2575,7 +2635,7 @@ function _priorityFromDelegationMessage(message) {
2575
2635
  }
2576
2636
 
2577
2637
  function _inferDelegationActionFromMessage(message, { source = 'command-center', filePath = null, title = null, answerText = '', toolUses = [] } = {}) {
2578
- if (_messageRequestsDirectHandling(message)) return null;
2638
+ if (_messageRequestsDirectHandling(message, { answerText, toolUses })) return null;
2579
2639
  const explicit = _messageHasDelegationIntent(message);
2580
2640
  const mediumLarge = _messageHasMediumLargeWorkIntent(message, { source });
2581
2641
  const toolCount = Array.isArray(toolUses) ? toolUses.length : 0;
@@ -2606,7 +2666,7 @@ function _inferDelegationActionFromMessage(message, { source = 'command-center',
2606
2666
  function _ensureDelegationForIntent(actions, opts = {}) {
2607
2667
  const list = (Array.isArray(actions) ? actions.map(normalizeCCAction) : [])
2608
2668
  .filter(action => !_ccShouldSuppressAnsweredAskDispatch(action, opts));
2609
- if (_messageRequestsDirectHandling(opts.message)) return list.filter(a => normalizeCCAction(a)?.type !== 'dispatch');
2669
+ if (_messageRequestsDirectHandling(opts.message, opts)) return list.filter(a => normalizeCCAction(a)?.type !== 'dispatch');
2610
2670
  if (list.some(a => normalizeCCAction(a)?.type === 'dispatch')) return list;
2611
2671
  if (list.length > 0) return list;
2612
2672
  const inferred = _inferDelegationActionFromMessage(opts.message, opts);
@@ -3361,8 +3421,15 @@ async function executeCCActions(actions, { source = 'command-center', inferredPr
3361
3421
  const workType = routing.normalizeWorkType(action.workType || (action.type !== 'dispatch' ? action.type : WORK_TYPE.IMPLEMENT), WORK_TYPE.IMPLEMENT);
3362
3422
  const id = 'W-' + shared.uid();
3363
3423
  const project = action.project || '';
3364
- const prRef = getWorkItemPrRef(action);
3424
+ const prTargetedWorkType = isPrTargetedWorkType(workType);
3425
+ let prRef = getWorkItemPrRef(action);
3365
3426
  let linkedPr = null;
3427
+ let allPrsForAction = null;
3428
+ if (!prRef && prTargetedWorkType) {
3429
+ allPrsForAction = getPullRequests().filter(p => !p._ghost);
3430
+ linkedPr = inferActionPrRecord(action, allPrsForAction, null);
3431
+ if (linkedPr) prRef = linkedPr.id || linkedPr.url || linkedPr.prNumber;
3432
+ }
3366
3433
 
3367
3434
  // Strict project resolution. Silent fallback to PROJECTS[0] when the model named an unknown
3368
3435
  // project caused work items to land in the wrong repo. Now: unknown name → error; ambiguous
@@ -3377,8 +3444,8 @@ async function executeCCActions(actions, { source = 'command-center', inferredPr
3377
3444
  break;
3378
3445
  }
3379
3446
  } else if (prRef) {
3380
- const allPrs = getPullRequests().filter(p => !p._ghost);
3381
- linkedPr = shared.findPrRecord(allPrs, prRef) || null;
3447
+ const allPrs = allPrsForAction || getPullRequests().filter(p => !p._ghost);
3448
+ if (!linkedPr) linkedPr = shared.findPrRecord(allPrs, prRef) || null;
3382
3449
  if (linkedPr?._project && linkedPr._project !== 'central') {
3383
3450
  targetProject = resolveProjectSourceTarget(linkedPr._project, PROJECTS, { allowCentral: false }).project || null;
3384
3451
  }
@@ -3396,12 +3463,17 @@ async function executeCCActions(actions, { source = 'command-center', inferredPr
3396
3463
  }
3397
3464
  // PROJECTS.length === 0 → targetProject stays null, falls back to root work-items.json (existing behavior).
3398
3465
 
3399
- if (prRef && !linkedPr && targetProject) {
3466
+ if (targetProject && (!linkedPr || !prRef)) {
3400
3467
  const projectPrs = shared.safeJson(shared.projectPrPath(targetProject)) || [];
3401
3468
  shared.normalizePrRecords(projectPrs, targetProject);
3402
- linkedPr = shared.findPrRecord(projectPrs, prRef, targetProject) || null;
3469
+ if (!prRef && prTargetedWorkType) {
3470
+ linkedPr = inferActionPrRecord(action, projectPrs, targetProject);
3471
+ if (linkedPr) prRef = linkedPr.id || linkedPr.url || linkedPr.prNumber;
3472
+ } else if (prRef && !linkedPr) {
3473
+ linkedPr = shared.findPrRecord(projectPrs, prRef, targetProject) || null;
3474
+ }
3403
3475
  }
3404
- if (prRef && (workType === WORK_TYPE.FIX || workType === WORK_TYPE.REVIEW || workType === WORK_TYPE.TEST) && !linkedPr) {
3476
+ if (prRef && prTargetedWorkType && !linkedPr) {
3405
3477
  results.push({ type: action.type, error: `PR not found: ${prRef}` });
3406
3478
  break;
3407
3479
  }
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "runtime": "copilot",
3
3
  "models": null,
4
- "cachedAt": "2026-05-09T18:10:43.397Z"
4
+ "cachedAt": "2026-05-09T18:16:37.444Z"
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1821",
3
+ "version": "0.1.1823",
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
@@ -11,6 +11,8 @@ Repo: {{repo_name}} | Org: {{ado_org}} | Project: {{ado_project}}
11
11
  Fix issues found by {{reviewer}} on **{{pr_id}}**: {{pr_title}}
12
12
  Branch: `{{pr_branch}}`
13
13
 
14
+ When `{{pr_id}}`/`{{pr_branch}}` identify an existing pull request, this is a fix-on-existing-PR task. Work in the provided PR branch, push the fix commit to that branch, and do not create a replacement branch or a new PR.
15
+
14
16
  {{checkpoint_context}}
15
17
 
16
18
  ## Review Findings to Address
@@ -97,7 +97,8 @@ I'll dispatch dallas to fix that bug.
97
97
 
98
98
  Core action types:
99
99
  - **dispatch**: title (REQUIRED), workType, priority (low/medium/high), agents[] or agent (optional — both shapes accepted), project (REQUIRED when multi-project unless `pr` resolves to a tracked PR), description, pr (optional PR number/id/url for work that targets an existing PR), scope (`"fan-out"` only when the user explicitly asks to fan out to all agents). Do not emit `type:"fix"` or `type:"implement"`; emit `type:"dispatch"` with `workType:"fix"` or emit `type:"dispatch"` with `workType:"implement"`.
100
- workTypes: `explore` (research/report only, NO PR), `ask` (answer/report, NO PR), `implement` (new code, PR REQUIRED), `fix` (standalone bug fix creates a PR; include `pr` when fixing review comments/build failures on an existing PR), `review` (code review, NO PR), `test` (tests, PR if new), `verify` (merge/build/maintenance, NO PR)
100
+ workTypes: `explore` (research/report only, NO PR), `ask` (answer/report, NO PR), `implement` (new code, PR REQUIRED), `fix` (standalone bug fix creates a PR; include `pr` whenever the user references an existing PR by number, URL, or title, including "fix/bypass this policy on this PR", review comments, or build failures), `review` (code review, NO PR), `test` (tests, PR if new), `verify` (merge/build/maintenance, NO PR)
101
+ For fix-style requests that name or contextually refer to an existing PR, preserving `pr` is mandatory: the agent must check out that PR's branch and push a fix commit there, not create a new branch or new PR.
101
102
  Do not dispatch an `ask` work item for a question you already answered inline in the same turn, especially after using tools. If the answer needs deeper investigation, give a brief handoff note and dispatch; do not both provide a complete answer and queue an agent to repeat it.
102
103
  If the user wants a design/architecture artifact committed through a PR, dispatch `implement` or `docs` rather than `explore`.
103
104
  When the user names a specific agent ("assign this to lambert"), put exactly that one name in `agents` (e.g. `"agents": ["lambert"]`). A single-agent assignment is hard-pinned by the server — it will queue for that agent only and skip the routing table. If the user explicitly asks for fan-out/all agents, set `scope: "fan-out"`.