@@ -13,16 +13,13 @@ name: companion-pr-check
1313# surfaces the declared link but reports "couldn't verify".
1414
1515on :
16+ # PR-driven only: runs on the one PR being opened/edited/synced — no periodic
17+ # bulk scan. We assume companions are declared on BOTH sides, so the per-PR
18+ # trigger keeps each side's status fresh; to refresh after a companion merges,
19+ # re-edit the PR (or run this workflow manually via the Actions tab).
1620 pull_request :
1721 types : [opened, edited, reopened, synchronize]
1822 branches : [staging, main]
19- schedule :
20- # Refresh open staging/main PRs in case the companion merges AFTER this PR was
21- # opened. CAVEAT: GitHub runs scheduled workflows ONLY from the DEFAULT branch's
22- # copy of this file — so this auto-refresh activates once the workflow lands on
23- # the default branch (via the normal promotion), not before. The pull_request
24- # triggers below always work; re-editing the PR re-runs the check meanwhile.
25- - cron : ' */30 * * * *'
2623 workflow_dispatch : {}
2724
2825permissions :
4946 const TRAILER = /Companion:\s*(?:https?:\/\/github\.com\/)?([\w.-]+)\/([\w.-]+)(?:\/pull\/|#)(\d+)/gi;
5047 const REF = /(?:https?:\/\/github\.com\/)?([\w.-]+)\/([\w.-]+)(?:\/pull\/|#)(\d+)/g;
5148 const { owner, repo } = context.repo;
49+ // Directional label: copilot/mothership PRs get "requires-sim-merge",
50+ // sim PRs get "requires-mothership-merge". Applied whenever the PR
51+ // declares a companion; removed when it declares none.
52+ const otherSide = repo === 'sim' ? 'mothership/copilot' : 'sim';
53+ const LABEL = repo === 'sim' ? 'requires-mothership-merge' : 'requires-sim-merge';
54+ const LABEL_DESC = `Has a companion PR on the ${otherSide} side — merge in lockstep`;
5255 const crossToken = process.env.CROSS_REPO_TOKEN;
5356 // Read the OTHER repo's PR via a plain REST fetch with the PAT in the
5457 // header — keeps the PAT strictly READ-ONLY and avoids re-instantiating
@@ -108,19 +111,16 @@ jobs:
108111 const ex = await findSticky(prNumber);
109112 if (ex) await github.rest.issues.deleteComment({ owner, repo, comment_id: ex.id });
110113 // Drop the label too, so a PR edited to remove all companions doesn't
111- // keep a stale has-companion badge. 404 if not present → ignore.
112- try { await github.rest.issues.removeLabel({ owner, repo, issue_number: prNumber, name: 'has-companion' }); } catch {}
114+ // keep a stale badge. 404 (not present) is expected; surface anything else.
115+ try { await github.rest.issues.removeLabel({ owner, repo, issue_number: prNumber, name: LABEL }); }
116+ catch (e) { if (e.status !== 404) core.warning(`companion: removeLabel ${LABEL} on #${prNumber} failed (${e.status || e.message})`); }
113117 }
114118 async function ensureLabel() {
115- try { await github.rest.issues.getLabel({ owner, repo, name: 'has-companion' }); }
116- catch {
117- try {
118- await github.rest.issues.createLabel({
119- owner, repo, name: 'has-companion', color: '5319e7',
120- description: 'Has a cross-repo companion PR (see companion-pr-check)',
121- });
122- } catch {}
123- }
119+ try { await github.rest.issues.getLabel({ owner, repo, name: LABEL }); return; }
120+ catch (e) { if (e.status && e.status !== 404) { core.warning(`companion: getLabel ${LABEL} failed (${e.status})`); return; } }
121+ // 404 → label doesn't exist yet, create it. 422 = another run beat us (fine).
122+ try { await github.rest.issues.createLabel({ owner, repo, name: LABEL, color: 'd93f0b', description: LABEL_DESC }); }
123+ catch (e) { if (e.status !== 422) core.warning(`companion: createLabel ${LABEL} failed (${e.status || e.message})`); }
124124 }
125125
126126 // staging PRs are a single feature → just this PR's body ("the one").
@@ -162,7 +162,8 @@ jobs:
162162 const companions = await collectCompanions(pr);
163163 if (companions.length === 0) { await clear(pr.number); return; }
164164 await ensureLabel();
165- try { await github.rest.issues.addLabels({ owner, repo, issue_number: pr.number, labels: ['has-companion'] }); } catch {}
165+ try { await github.rest.issues.addLabels({ owner, repo, issue_number: pr.number, labels: [LABEL] }); }
166+ catch (e) { core.warning(`companion: addLabels ${LABEL} on #${pr.number} failed (${e.status || e.message})`); }
166167
167168 const base = pr.base.ref;
168169 const lines = [];
@@ -200,6 +201,7 @@ jobs:
200201 if (context.eventName === 'pull_request') {
201202 await checkPR(context.payload.pull_request);
202203 } else {
204+ // workflow_dispatch only: manual full re-scan of open staging/main PRs.
203205 for (const b of ['staging', 'main']) {
204206 const prs = await github.paginate(github.rest.pulls.list, { owner, repo, base: b, state: 'open', per_page: 100 });
205207 for (const pr of prs) await checkPR(pr);
0 commit comments