@yemi33/minions 0.1.1678 → 0.1.1679

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.1679 (2026-05-02)
4
+
5
+ ### Fixes
6
+ - preserve PR context for linked fix work items
7
+
3
8
  ## 0.1.1678 (2026-05-02)
4
9
 
5
10
  ### Fixes
package/dashboard.js CHANGED
@@ -110,6 +110,22 @@ function normalizePrMetadata(metadata) {
110
110
  };
111
111
  }
112
112
 
113
+ function getWorkItemPrRef(input) {
114
+ if (!input || typeof input !== 'object') return null;
115
+ return input.targetPr || input.pr || input.prId || input.prNumber || input.pullRequest || input.sourcePr || input.prUrl || null;
116
+ }
117
+
118
+ function copyWorkItemPrFields(item, input, pr = null) {
119
+ const prRef = getWorkItemPrRef(input);
120
+ if (!prRef && !pr) return;
121
+ const prNumber = pr ? shared.getPrNumber(pr) : shared.getPrNumber(prRef);
122
+ item.targetPr = pr?.id || prRef;
123
+ item.pr_id = pr?.id || (typeof prRef === 'string' ? prRef : '');
124
+ if (prNumber != null) item.prNumber = prNumber;
125
+ if (pr?.branch || input.prBranch) item.prBranch = pr?.branch || input.prBranch;
126
+ if (pr?.title || input.prTitle) item.prTitle = pr?.title || input.prTitle;
127
+ if (pr?.url || input.prUrl) item.prUrl = pr?.url || input.prUrl;
128
+ }
113
129
  function linkPullRequestForTracking({ url, title, project: projectName, autoObserve, context, workItemId }, config = CONFIG, options = {}) {
114
130
  if (!url) {
115
131
  const err = new Error('url required');
@@ -1395,6 +1411,8 @@ async function executeCCActions(actions) {
1395
1411
  const workType = routing.normalizeWorkType(action.workType || (action.type !== 'dispatch' ? action.type : WORK_TYPE.IMPLEMENT), WORK_TYPE.IMPLEMENT);
1396
1412
  const id = 'W-' + shared.uid();
1397
1413
  const project = action.project || '';
1414
+ const prRef = getWorkItemPrRef(action);
1415
+ let linkedPr = null;
1398
1416
 
1399
1417
  // Strict project resolution. Silent fallback to PROJECTS[0] when the model named an unknown
1400
1418
  // project caused work items to land in the wrong repo. Now: unknown name → error; ambiguous
@@ -1408,6 +1426,18 @@ async function executeCCActions(actions) {
1408
1426
  results.push({ type: action.type, error: `Project "${project}" not found. Known projects: ${known}` });
1409
1427
  break;
1410
1428
  }
1429
+ } else if (prRef) {
1430
+ const allPrs = getPullRequests().filter(p => !p._ghost);
1431
+ linkedPr = shared.findPrRecord(allPrs, prRef) || null;
1432
+ if (linkedPr?._project && linkedPr._project !== 'central') {
1433
+ targetProject = PROJECTS.find(p => p.name?.toLowerCase() === String(linkedPr._project).toLowerCase()) || null;
1434
+ }
1435
+ if (!targetProject && PROJECTS.length > 1) {
1436
+ results.push({ type: action.type, error: `project field is required when ${PROJECTS.length} projects are configured: ${PROJECTS.map(p => p.name).join(', ')}` });
1437
+ break;
1438
+ } else if (!targetProject && PROJECTS.length === 1) {
1439
+ targetProject = PROJECTS[0];
1440
+ }
1411
1441
  } else if (PROJECTS.length > 1) {
1412
1442
  results.push({ type: action.type, error: `project field is required when ${PROJECTS.length} projects are configured: ${PROJECTS.map(p => p.name).join(', ')}` });
1413
1443
  break;
@@ -1416,6 +1446,16 @@ async function executeCCActions(actions) {
1416
1446
  }
1417
1447
  // PROJECTS.length === 0 → targetProject stays null, falls back to root work-items.json (existing behavior).
1418
1448
 
1449
+ if (prRef && !linkedPr && targetProject) {
1450
+ const projectPrs = shared.safeJson(shared.projectPrPath(targetProject)) || [];
1451
+ shared.normalizePrRecords(projectPrs, targetProject);
1452
+ linkedPr = shared.findPrRecord(projectPrs, prRef, targetProject) || null;
1453
+ }
1454
+ if (prRef && (workType === WORK_TYPE.FIX || workType === WORK_TYPE.REVIEW || workType === WORK_TYPE.TEST) && !linkedPr) {
1455
+ results.push({ type: action.type, error: `PR not found: ${prRef}` });
1456
+ break;
1457
+ }
1458
+
1419
1459
  const wiPath = targetProject ? shared.projectWorkItemsPath(targetProject) : path.join(MINIONS_DIR, 'work-items.json');
1420
1460
 
1421
1461
  // Promote `agent` (singular) → `agents` (array). Models emit either shape and the prior code
@@ -1438,14 +1478,16 @@ async function executeCCActions(actions) {
1438
1478
  const isOneShot = action.oneShot === true || (action.oneShot !== false && ccOneShotTypes.has(workType));
1439
1479
  shared.mutateJsonFileLocked(wiPath, items => {
1440
1480
  if (!Array.isArray(items)) items = [];
1441
- items.push({
1481
+ const item = {
1442
1482
  id, title: action.title, type: workType,
1443
1483
  priority: action.priority || 'medium', description: action.description || '',
1444
1484
  status: WI_STATUS.PENDING, created: new Date().toISOString(),
1445
- createdBy: 'command-center', project,
1485
+ createdBy: 'command-center', project: targetProject?.name || project,
1446
1486
  ...(agentHints.length ? { preferred_agent: agentHints[0], agents: agentHints } : {}),
1447
1487
  ...(isOneShot ? { oneShot: true } : {}),
1448
- });
1488
+ };
1489
+ copyWorkItemPrFields(item, action, linkedPr);
1490
+ items.push(item);
1449
1491
  return items;
1450
1492
  }, { defaultValue: [] });
1451
1493
  results.push({ type: action.type, id, ok: true });
@@ -2740,6 +2782,7 @@ const server = http.createServer(async (req, res) => {
2740
2782
  if (body.acceptanceCriteria) item.acceptanceCriteria = body.acceptanceCriteria;
2741
2783
  if (body.skipPr) item.skipPr = true;
2742
2784
  if (body.oneShot) item.oneShot = true;
2785
+ copyWorkItemPrFields(item, body);
2743
2786
  let dupId = null;
2744
2787
  mutateJsonFileLocked(wiPath, (items) => {
2745
2788
  if (!Array.isArray(items)) items = [];
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "runtime": "copilot",
3
3
  "models": null,
4
- "cachedAt": "2026-05-02T05:22:33.108Z"
4
+ "cachedAt": "2026-05-02T05:46:43.671Z"
5
5
  }
@@ -628,9 +628,13 @@ function selectPlaybook(workType, item) {
628
628
  if (workType === WORK_TYPE.IMPLEMENT || workType === WORK_TYPE.IMPLEMENT_LARGE) {
629
629
  return 'implement';
630
630
  }
631
- if (workType === WORK_TYPE.REVIEW && !item?._pr && !item?.pr_id) {
631
+ const hasPrContext = !!(item?._pr || item?.pr_id || item?.targetPr || item?.sourcePr || item?.pr);
632
+ if (workType === WORK_TYPE.REVIEW && !hasPrContext) {
632
633
  return 'work-item';
633
634
  }
635
+ if (workType === WORK_TYPE.FIX && hasPrContext) {
636
+ return 'fix';
637
+ }
634
638
  const typeSpecificPlaybooks = ['explore', 'review', 'test', 'plan-to-prd', 'plan', 'ask', 'verify', 'decompose', 'docs', 'meeting-investigate', 'meeting-debate', 'meeting-conclude'];
635
639
  return typeSpecificPlaybooks.includes(workType) ? workType : 'work-item';
636
640
  }
package/engine.js CHANGED
@@ -2592,6 +2592,14 @@ function renderProjectWorkItemPromptForAgent(item, workType, agentId, config, pr
2592
2592
  worktree_path: path.resolve(root, config.engine?.worktreeRoot || '../worktrees', `${branchName}`),
2593
2593
  commit_message: item.commitMessage || `feat: ${item.title || item.id}`,
2594
2594
  notes_content: '',
2595
+ pr_id: item.pr_id || item._pr || item.targetPr || item.sourcePr || item.pr || '',
2596
+ pr_number: item.prNumber || item.pr_number || '',
2597
+ pr_title: item.pr_title || item.prTitle || '',
2598
+ pr_branch: item.pr_branch || item.prBranch || '',
2599
+ pr_author: item.pr_author || item.prAuthor || '',
2600
+ pr_url: item.pr_url || item.prUrl || '',
2601
+ reviewer: item.reviewer || 'Reviewer',
2602
+ review_note: item.review_note || item.reviewNote || item.description || item.title || 'See PR thread comments',
2595
2603
  };
2596
2604
  const cpResult = buildWorkItemDispatchVars(item, vars, config, {
2597
2605
  worktreePath: vars.worktree_path || root,
@@ -2612,6 +2620,38 @@ function renderProjectWorkItemPromptForAgent(item, workType, agentId, config, pr
2612
2620
  };
2613
2621
  }
2614
2622
 
2623
+ function getWorkItemPrRef(item) {
2624
+ if (!item || typeof item !== 'object') return null;
2625
+ return item.targetPr || item.pr || item.pr_id || item.sourcePr || item.pullRequest || item.prUrl || item.prNumber || null;
2626
+ }
2627
+
2628
+ function resolveWorkItemPrRecord(item, project) {
2629
+ if (!project) return null;
2630
+ const prRef = getWorkItemPrRef(item);
2631
+ if (!prRef) return null;
2632
+ const prs = safeJson(projectPrPath(project)) || [];
2633
+ shared.normalizePrRecords(prs, project);
2634
+ return shared.findPrRecord(prs, prRef, project);
2635
+ }
2636
+
2637
+ function withWorkItemPrContext(item, pr) {
2638
+ if (!pr) return item;
2639
+ const prNumber = shared.getPrNumber(pr);
2640
+ return {
2641
+ ...item,
2642
+ _pr: pr.id,
2643
+ pr_id: pr.id,
2644
+ targetPr: item.targetPr || pr.id,
2645
+ prNumber: prNumber ?? item.prNumber,
2646
+ pr_number: prNumber ?? item.pr_number,
2647
+ pr_title: pr.title || item.pr_title || item.prTitle || '',
2648
+ pr_branch: pr.branch || item.pr_branch || item.prBranch || '',
2649
+ pr_author: pr.agent || item.pr_author || item.prAuthor || '',
2650
+ pr_url: pr.url || item.pr_url || item.prUrl || '',
2651
+ reviewer: item.reviewer || 'Reviewer',
2652
+ review_note: item.review_note || item.reviewNote || item.description || item.title || 'See PR thread comments',
2653
+ };
2654
+ }
2615
2655
  function projectFromDispatchMeta(metaProject, config) {
2616
2656
  if (!metaProject) return null;
2617
2657
  const projects = getProjects(config);
@@ -2775,8 +2815,17 @@ function discoverFromWorkItems(config, project) {
2775
2815
  skipped.noAgent++; continue;
2776
2816
  }
2777
2817
 
2818
+ const linkedPr = resolveWorkItemPrRecord(item, project);
2819
+ const promptItem = linkedPr ? withWorkItemPrContext(item, linkedPr) : item;
2820
+ const prBranch = linkedPr?.branch || '';
2821
+ const isPrTargeted = !!(linkedPr && (workType === WORK_TYPE.FIX || workType === WORK_TYPE.REVIEW || workType === WORK_TYPE.TEST));
2822
+ if (!linkedPr && getWorkItemPrRef(item) && (workType === WORK_TYPE.FIX || workType === WORK_TYPE.REVIEW || workType === WORK_TYPE.TEST)) {
2823
+ if (item._pendingReason !== 'pr_not_found') { item._pendingReason = 'pr_not_found'; needsWrite = true; }
2824
+ log('warn', `Work item ${item.id} references PR ${getWorkItemPrRef(item)} but no tracked PR record was found`);
2825
+ continue;
2826
+ }
2778
2827
  const isShared = item.branchStrategy === 'shared-branch' && item.featureBranch;
2779
- const branchName = isShared ? item.featureBranch : (item.branch || `work/${item.id}`);
2828
+ const branchName = isPrTargeted && prBranch ? prBranch : (isShared ? item.featureBranch : (item.branch || `work/${item.id}`));
2780
2829
  const deferredAgentResolution = agentId === routing.ANY_AGENT;
2781
2830
 
2782
2831
  // Branch mutex: skip if target branch is locked by an active dispatch
@@ -2789,7 +2838,7 @@ function discoverFromWorkItems(config, project) {
2789
2838
  }
2790
2839
 
2791
2840
  const promptAgentId = deferredAgentResolution ? reservedAgentId : agentId;
2792
- const promptResult = renderProjectWorkItemPromptForAgent(item, workType, promptAgentId, config, project, root, branchName);
2841
+ const promptResult = renderProjectWorkItemPromptForAgent(promptItem, workType, promptAgentId, config, project, root, branchName);
2793
2842
  if (promptResult.needsReview) {
2794
2843
  log('warn', `Work item ${item.id} exceeded 3 checkpoint-resumes — marking as failed for manual intervention`);
2795
2844
  item.status = WI_STATUS.FAILED;
@@ -2827,7 +2876,7 @@ function discoverFromWorkItems(config, project) {
2827
2876
  agentRole: config.agents[agentId]?.role || tempAgents.get(agentId)?.role || 'Agent',
2828
2877
  task: `[${project?.name || 'project'}] ${item.title || item.description?.slice(0, 80) || item.id}`,
2829
2878
  prompt,
2830
- meta: { dispatchKey: key, source: 'work-item', branch: branchName, branchStrategy: item.branchStrategy || 'parallel', useExistingBranch: !!(item.branchStrategy === 'shared-branch' && item.featureBranch), item, project: { name: project?.name, localPath: project?.localPath }, deferAgentResolution: deferredAgentResolution }
2879
+ meta: { dispatchKey: key, source: 'work-item', branch: branchName, branchStrategy: item.branchStrategy || 'parallel', useExistingBranch: !!(isPrTargeted || (item.branchStrategy === 'shared-branch' && item.featureBranch)), item: promptItem, project: { name: project?.name, localPath: project?.localPath }, deferAgentResolution: deferredAgentResolution, ...(linkedPr ? { pr: linkedPr } : {}) }
2831
2880
  });
2832
2881
 
2833
2882
  setCooldown(key);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1678",
3
+ "version": "0.1.1679",
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"
@@ -80,8 +80,8 @@ I'll dispatch dallas to fix that bug.
80
80
  - `knowledge`: `title`, `content`, and `category` REQUIRED. Valid categories: architecture, conventions, project-notes, build-reports, reviews.
81
81
 
82
82
  Core action types:
83
- - **dispatch**: title (REQUIRED), workType, priority (low/medium/high), agents[] or agent (optional — both shapes accepted), project (REQUIRED when multi-project), description
84
- workTypes: `explore` (research/report only, NO PR), `ask` (answer/report, NO PR), `implement` (new code, PR REQUIRED), `fix` (bug fix, PR REQUIRED), `review` (code review, NO PR), `test` (tests, PR if new), `verify` (merge/build/maintenance, NO PR)
83
+ - **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)
84
+ 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)
85
85
  If the user wants a design/architecture artifact committed through a PR, dispatch `implement` or `docs` rather than `explore`.
86
86
  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. Use multi-agent arrays only when the user names multiple agents or asks for fan-out.
87
87
  - **build-and-test**: pr, project (optional), agent (optional) — Run the build-and-test playbook against a PR. The agent will checkout the PR branch, run the project's build/test commands, and report results. Use when the user asks to "run tests on PR X" or "build PR X" or after a fix to verify nothing regressed.