@yemi33/minions 0.1.1001 → 0.1.1002

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,6 +1,6 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.1001 (2026-04-15)
3
+ ## 0.1.1002 (2026-04-15)
4
4
 
5
5
  ### Features
6
6
  - fix stopAfter=0 watch expiry — run forever for all conditions (#1117)
@@ -25,6 +25,7 @@
25
25
  - gate auto-fix conflict dispatch behind autoFixConflicts flag
26
26
 
27
27
  ### Fixes
28
+ - extend auto-link fallback to ADO projects
28
29
  - auto-link existing GitHub PR when agent completes without creating one
29
30
  - preserve ordered lists across blank lines in markdown renderer
30
31
  - restore CC rendering and tab bar
@@ -44,7 +45,6 @@
44
45
  - address review feedback — move writeToInbox outside lock, add absolute condition auto-expire
45
46
  - fix watches feature gaps — human notifications, branch stub, status-change init, unique keys
46
47
  - skip isAlreadyDispatched in needsReReview to allow re-review within 1hr
47
- - skip SessionStart hook settings test on CI
48
48
 
49
49
  ### Other
50
50
  - docs: update CLAUDE.md with recent Minions architecture changes
package/engine/ado.js CHANGED
@@ -890,6 +890,29 @@ const isAdoThrottled = () => _adoThrottle.isThrottled();
890
890
  /** Returns a snapshot of the current throttle state. Calls isAdoThrottled() for a fresh value. */
891
891
  const getAdoThrottleState = () => _adoThrottle.getState();
892
892
 
893
+ /**
894
+ * Query ADO for an open PR on a specific branch.
895
+ * Used as a last-resort fallback when an agent completes without logging a PR URL
896
+ * but a PR may already exist from a prior orphaned dispatch.
897
+ * @param {object} project — project config with adoOrg, adoProject, repositoryId, prUrlBase
898
+ * @param {string} branch — source branch name (without refs/heads/ prefix)
899
+ * @returns {{ prNumber: number, url: string }|null}
900
+ */
901
+ async function findOpenPrOnBranch(project, branch) {
902
+ if (!project.adoOrg || !project.adoProject || !project.repositoryId || !branch) return null;
903
+ const token = await getAdoToken();
904
+ if (!token) return null;
905
+ const orgBase = shared.getAdoOrgBase(project);
906
+ const sourceRef = encodeURIComponent(`refs/heads/${branch}`);
907
+ const url = `${orgBase}/${project.adoProject}/_apis/git/repositories/${project.repositoryId}/pullrequests?searchCriteria.status=active&searchCriteria.sourceRefName=${sourceRef}&api-version=7.1`;
908
+ const data = await adoFetch(url, token);
909
+ const pr = (data.value || [])[0];
910
+ if (!pr) return null;
911
+ const prNumber = pr.pullRequestId;
912
+ const prUrl = project.prUrlBase ? `${project.prUrlBase}${prNumber}` : `https://dev.azure.com/${project.adoOrg}/${project.adoProject}/_git/${project.repositoryId}/pullrequest/${prNumber}`;
913
+ return { prNumber, url: prUrl };
914
+ }
915
+
893
916
  /** Reset throttle state — exported for testing only. */
894
917
  function _resetAdoThrottle() {
895
918
  _adoThrottle._reset();
@@ -913,6 +936,7 @@ module.exports = {
913
936
  getAdoThrottleState,
914
937
  fetchAdoPrMetadata,
915
938
  fetchSinglePrBuildStatus,
939
+ findOpenPrOnBranch,
916
940
  _resetAdoThrottle, // exported for testing
917
941
  _setAdoThrottleForTest, // exported for testing
918
942
  };
@@ -1792,37 +1792,44 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
1792
1792
  }
1793
1793
  }
1794
1794
  }
1795
- // Last resort: query GitHub directly for an open PR on this branch.
1795
+ // Last resort: query the platform directly for an open PR on this branch.
1796
1796
  // Handles the case where a prior orphaned dispatch created a PR but the engine
1797
- // never processed its output — so the PR exists on GitHub but not in pull-requests.json.
1797
+ // never processed its output — so the PR exists on the platform but not in pull-requests.json.
1798
1798
  if (!existingPrFound && meta?.branch) {
1799
1799
  const projectObj = shared.getProjects(config).find(p => p.name === meta?.project?.name);
1800
- const ghSlug = projectObj?.prUrlBase?.match(/github\.com\/([^/]+\/[^/]+)\/pull/)?.[1];
1801
- if (projectObj?.repoHost === 'github' && ghSlug) {
1800
+ if (projectObj) {
1802
1801
  try {
1803
- const raw = await execAsync(`gh pr list --head "${meta.branch}" --repo ${ghSlug} --json number,url,state --limit 1`, { timeout: 15000, windowsHide: true });
1804
- const found = JSON.parse(raw || '[]');
1805
- if (found.length > 0 && found[0].state === 'OPEN') {
1806
- const prNum = found[0].number;
1807
- const fullId = `PR-${prNum}`;
1802
+ let found = null;
1803
+ if (projectObj.repoHost === 'github') {
1804
+ const ghSlug = projectObj.prUrlBase?.match(/github\.com\/([^/]+\/[^/]+)\/pull/)?.[1];
1805
+ if (ghSlug) {
1806
+ const raw = await execAsync(`gh pr list --head "${meta.branch}" --repo ${ghSlug} --json number,url,state --limit 1`, { timeout: 15000, windowsHide: true });
1807
+ const hits = JSON.parse(raw || '[]');
1808
+ if (hits.length > 0 && hits[0].state === 'OPEN') found = { prNumber: hits[0].number, url: hits[0].url };
1809
+ }
1810
+ } else {
1811
+ found = await require('./ado').findOpenPrOnBranch(projectObj, meta.branch);
1812
+ }
1813
+ if (found) {
1814
+ const fullId = `PR-${found.prNumber}`;
1808
1815
  const prPath = shared.projectPrPath(projectObj);
1809
1816
  mutateJsonFileLocked(prPath, prs => {
1810
1817
  if (!Array.isArray(prs)) prs = [];
1811
1818
  if (prs.some(p => p.id === fullId)) return prs;
1812
1819
  prs.push({
1813
- id: fullId, prNumber: prNum, title: meta.item?.title || '',
1820
+ id: fullId, prNumber: found.prNumber, title: meta.item?.title || '',
1814
1821
  agent: agentId, branch: meta.branch, reviewStatus: 'pending',
1815
- status: PR_STATUS.ACTIVE, created: ts(), url: found[0].url,
1822
+ status: PR_STATUS.ACTIVE, created: ts(), url: found.url,
1816
1823
  prdItems: meta.item?.id ? [meta.item.id] : [],
1817
1824
  sourcePlan: meta.item?.sourcePlan || '', itemType: meta.item?.itemType || '',
1818
1825
  });
1819
1826
  return prs;
1820
1827
  });
1821
1828
  if (meta.item?.id) addPrLink(fullId, meta.item.id);
1822
- log('info', `Auto-linked existing GH PR ${fullId} on branch ${meta.branch} for ${meta.item?.id}`);
1829
+ log('info', `Auto-linked existing PR ${fullId} on branch ${meta.branch} for ${meta.item?.id}`);
1823
1830
  existingPrFound = true;
1824
1831
  }
1825
- } catch (e) { log('warn', `GH PR lookup for branch ${meta.branch}: ${e.message}`); }
1832
+ } catch (e) { log('warn', `PR lookup for branch ${meta.branch}: ${e.message}`); }
1826
1833
  }
1827
1834
  }
1828
1835
  if (!existingPrFound) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1001",
3
+ "version": "0.1.1002",
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"