@yemi33/minions 0.1.1638 → 0.1.1639

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,8 +1,9 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.1638 (2026-04-30)
3
+ ## 0.1.1639 (2026-04-30)
4
4
 
5
5
  ### Fixes
6
+ - auto-link agent-created PRs to work items (#1904)
6
7
  - Playbook 'fix' / 'review' gates items forever when pr_branch is unresolved (closes #1899) (#1901)
7
8
 
8
9
  ## 0.1.1637 (2026-04-30)
package/dashboard.js CHANGED
@@ -5838,9 +5838,9 @@ What would you like to discuss or change? When you're happy, say "approve" and I
5838
5838
  // /api/prd/regenerate removed — use /api/plans/approve which does diff-aware update
5839
5839
 
5840
5840
  // Agents
5841
- { method: 'POST', path: '/api/pull-requests/link', desc: 'Manually link an external PR for tracking', params: 'url, title?, project?, autoObserve?, context?', handler: async (req, res) => {
5841
+ { method: 'POST', path: '/api/pull-requests/link', desc: 'Manually link an external PR for tracking', params: 'url, title?, project?, autoObserve?, context?, workItemId?', handler: async (req, res) => {
5842
5842
  const body = await readBody(req);
5843
- const { url, title, project: projectName, autoObserve, context } = body;
5843
+ const { url, title, project: projectName, autoObserve, context, workItemId } = body;
5844
5844
  if (!url) return jsonReply(res, 400, { error: 'url required' });
5845
5845
 
5846
5846
  // Determine project
@@ -5855,6 +5855,14 @@ What would you like to discuss or change? When you're happy, say "approve" and I
5855
5855
  const prId = shared.getCanonicalPrId(targetProject, prNum, url);
5856
5856
  const contextText = typeof context === 'string' ? context : (context == null ? '' : JSON.stringify(context));
5857
5857
 
5858
+ // Resolve a work-item association from either the top-level workItemId
5859
+ // field (preferred) or context.workItemId (legacy CC payload shape).
5860
+ // Without this, manually-linked PRs end up with prdItems=[] and the
5861
+ // Work Items page renders no PR even though _context records the ID.
5862
+ const linkedItemId = (typeof workItemId === 'string' && workItemId.trim())
5863
+ || (context && typeof context === 'object' && typeof context.workItemId === 'string' && context.workItemId.trim())
5864
+ || '';
5865
+
5858
5866
  // Atomic check-and-insert to prevent duplicates and races with polling loops
5859
5867
  let duplicate = false;
5860
5868
  mutateJsonFileLocked(prPath, (prs) => {
@@ -5871,7 +5879,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
5871
5879
  status: 'active',
5872
5880
  created: new Date().toISOString(),
5873
5881
  url,
5874
- prdItems: [],
5882
+ prdItems: linkedItemId ? [linkedItemId] : [],
5875
5883
  _manual: true,
5876
5884
  _contextOnly: !autoObserve,
5877
5885
  _autoObserve: !!autoObserve,
@@ -5880,6 +5888,16 @@ What would you like to discuss or change? When you're happy, say "approve" and I
5880
5888
  return prs;
5881
5889
  }, { defaultValue: [] });
5882
5890
  if (duplicate) return jsonReply(res, 400, { error: 'PR already tracked' });
5891
+ // Persist the work-item ↔ PR association in pr-links.json so
5892
+ // queries.getWorkItems() can render item._pr / item._prUrl. addPrLink
5893
+ // is idempotent and handles the central / project-scoped split.
5894
+ if (linkedItemId) {
5895
+ try {
5896
+ shared.addPrLink(prId, linkedItemId, { project: targetProject, prNumber: parseInt(prNum, 10) || null, url });
5897
+ } catch (e) {
5898
+ shared.log('warn', `PR link addPrLink failed for ${prId} → ${linkedItemId}: ${e.message}`);
5899
+ }
5900
+ }
5883
5901
  invalidateStatusCache();
5884
5902
  jsonReply(res, 200, { ok: true, id: prId });
5885
5903
 
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "runtime": "copilot",
3
3
  "models": null,
4
- "cachedAt": "2026-04-30T16:34:18.069Z"
4
+ "cachedAt": "2026-04-30T16:35:01.979Z"
5
5
  }
@@ -744,13 +744,33 @@ function syncPrsFromOutput(output, agentId, meta, config) {
744
744
  }
745
745
  } catch {}
746
746
 
747
+ // prId → URL captured from inbox notes. Populated alongside prMatches so
748
+ // extractPrUrl below has a fallback when the agent's stdout doesn't contain
749
+ // the URL (the W-moljyu60wuzr / #1902 case — gh pr create ran in a sibling
750
+ // dispatch and only the inbox note carries the link).
751
+ const inboxUrls = new Map();
747
752
  const today = dateStamp();
748
753
  const inboxFiles = getInboxFiles().filter(f => f.includes(agentId) && f.includes(today));
749
754
  for (const f of inboxFiles) {
750
755
  const content = safeRead(path.join(INBOX_DIR, f));
751
756
  if (!content) continue;
752
- const prHeaderPattern = /\*\*PR[:\*]*\*?\s*[#-]*\s*(?:(?:visualstudio\.com|dev\.azure\.com)[^\s"]*?pullrequest\/(\d+)|github\.com\/[^\s"]*?\/pull\/(\d+))/gi;
753
- while ((match = prHeaderPattern.exec(content)) !== null) prMatches.add(match[1] || match[2]);
757
+ // Match a PR declaration line in the agent's findings note: optional bold,
758
+ // optional "Pull Request" spelling, line-anchored so "see PR https://..."
759
+ // mid-paragraph mentions don't trigger a false-positive. The protocol
760
+ // and host prefix is optional so "PR: https://github.com/..." ,
761
+ // "**PR:** github.com/...", etc. all match.
762
+ const prHeaderPattern = /(?:^|\n)\s*\*{0,2}(?:PR|Pull\s+Request)[:\*]*\*?\s*[#-]*\s*(?:https?:\/\/)?[^\s"]*?(?:(?:visualstudio\.com|dev\.azure\.com)[^\s"]*?pullrequest\/(\d+)|github\.com\/[^\s"]*?\/pull\/(\d+))/gi;
763
+ while ((match = prHeaderPattern.exec(content)) !== null) {
764
+ const prId = match[1] || match[2];
765
+ prMatches.add(prId);
766
+ // Pull the URL substring out of the matched chunk so we can hand it to
767
+ // extractPrUrl as a fallback. Prefer the first inbox URL we see for a
768
+ // given prId — later notes don't override the canonical record.
769
+ if (!inboxUrls.has(prId)) {
770
+ const urlMatch = match[0].match(/https?:\/\/[^\s"\\)]+/);
771
+ if (urlMatch) inboxUrls.set(prId, urlMatch[0].replace(/[.,;:]+$/, ''));
772
+ }
773
+ }
754
774
  }
755
775
 
756
776
  if (prMatches.size === 0) return 0;
@@ -773,7 +793,10 @@ function syncPrsFromOutput(output, agentId, meta, config) {
773
793
  return defaultProject;
774
794
  }
775
795
 
776
- // Extract PR URL directly from agent output — no manual construction
796
+ // Extract PR URL directly from agent output — no manual construction.
797
+ // Falls back to the URL captured from the inbox note when the agent stdout
798
+ // doesn't contain the link (gh pr create may have run in a sibling dispatch
799
+ // whose stdout was rotated; the inbox note is the durable artifact).
777
800
  function extractPrUrl(prId) {
778
801
  // Stop at backslash in addition to whitespace/quotes — raw JSONL encodes newlines as \n (literal
779
802
  // backslash-n), so without this the regex would capture e.g. "pull/1804\n/usr/bin/bash".
@@ -781,7 +804,7 @@ function syncPrsFromOutput(output, agentId, meta, config) {
781
804
  if (ghMatch) return ghMatch[0].replace(/[.,;:]+$/, '');
782
805
  const adoMatch = output.match(new RegExp(`https?://(?:dev\\.azure\\.com|[^/]+\\.visualstudio\\.com)[^\\s"'\\)\\]\\\\]*?pullrequest/${prId}(?:[^\\s"'\\)\\]\\\\]*)`, 'i'));
783
806
  if (adoMatch) return adoMatch[0].replace(/[.,;:]+$/, '');
784
- return '';
807
+ return inboxUrls.get(prId) || '';
785
808
  }
786
809
 
787
810
  const agentName = config.agents?.[agentId]?.name || agentId;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1638",
3
+ "version": "0.1.1639",
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"