Skip to content

Commit 9e18a8a

Browse files
committed
Add lane:dev for docs/dev/ PRs (admin self-merge, no review)
Introduce a new review lane for dev docs. Detection is content-based: if every docs-touching file in the PR is under docs/dev/, the PR is auto-assigned lane:dev and moved straight to stage:ready-to-merge with review-workflow status flipped to success — same treatment as lane:editorial-fix. Branch-name detection also supports docs/dev/ as a fallback for branches whose content is mixed or ambiguous. Admins can apply lane:dev manually to any PR via the labels sidebar.
1 parent 39c0c36 commit 9e18a8a

1 file changed

Lines changed: 36 additions & 16 deletions

File tree

.github/workflows/stage-progression.yml

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
const action = context.payload.action;
3434
3535
// ── Constants ────────────────────────────────────────────
36-
const LANE_LABELS = ['lane:new-doc', 'lane:major-revision', 'lane:minor-revision', 'lane:editorial-fix'];
36+
const LANE_LABELS = ['lane:new-doc', 'lane:major-revision', 'lane:minor-revision', 'lane:editorial-fix', 'lane:dev'];
3737
const STAGE_LABELS = ['stage:needs-lane', 'stage:peer-review', 'stage:lead-civil-review', 'stage:ai-editor-review', 'stage:director-review', 'stage:ready-to-merge'];
3838
3939
const STAGE_DISPLAY = {
@@ -78,6 +78,7 @@ jobs:
7878
if (b.startsWith('docs/major/')) return 'lane:major-revision';
7979
if (b.startsWith('docs/minor/')) return 'lane:minor-revision';
8080
if (b.startsWith('docs/fix/')) return 'lane:editorial-fix';
81+
if (b.startsWith('docs/dev/')) return 'lane:dev';
8182
return null;
8283
}
8384
@@ -102,12 +103,22 @@ jobs:
102103
});
103104
}
104105
105-
async function prTouchesDocs() {
106+
// Fetches the PR's file list once and returns both whether it
107+
// touches any docs/ files and whether every docs/ file is under
108+
// docs/dev/. The dev-lane detection is content-based so that
109+
// dev docs automatically use the lightweight lane regardless of
110+
// branch naming — users don't have to remember a docs/dev/
111+
// branch prefix to get the dev-lane treatment.
112+
async function getDocsFileState() {
106113
const files = await github.paginate(github.rest.pulls.listFiles, {
107114
owner: context.repo.owner, repo: context.repo.repo,
108115
pull_number: prNumber, per_page: 100,
109116
});
110-
return files.some(f => f.filename.startsWith('docs/'));
117+
const docsFiles = files.filter(f => f.filename.startsWith('docs/'));
118+
return {
119+
touchesDocs: docsFiles.length > 0,
120+
allUnderDev: docsFiles.length > 0 && docsFiles.every(f => f.filename.startsWith('docs/dev/')),
121+
};
111122
}
112123
113124
// ── Commit status helper ─────────────────────────────────
@@ -208,10 +219,12 @@ jobs:
208219
await ensureState();
209220
const toAdd = [lane];
210221
let comment;
211-
if (lane === 'lane:editorial-fix') {
222+
if (lane === 'lane:editorial-fix' || lane === 'lane:dev') {
212223
toAdd.push('stage:ready-to-merge');
213-
await setReviewStatus('success', 'Editorial fix — admin may merge');
214-
comment = `📋 **Lane: Editorial Fix**\n\nNo formal review required.\n\n@usace-rmc/docs-admin please review, merge, and approve the deploy.`;
224+
const statusDesc = lane === 'lane:editorial-fix' ? 'Editorial fix — admin may merge' : 'Dev doc — admin may merge';
225+
const laneTitle = lane === 'lane:editorial-fix' ? 'Editorial Fix' : 'Dev Doc';
226+
await setReviewStatus('success', statusDesc);
227+
comment = `📋 **Lane: ${laneTitle}**\n\nNo formal review required.\n\n@usace-rmc/docs-admin please review, merge, and approve the deploy.`;
215228
} else {
216229
toAdd.push('stage:peer-review');
217230
await setReviewStatus('pending', STAGE_STATUS_DESC['stage:peer-review']);
@@ -231,14 +244,18 @@ jobs:
231244
232245
// ── pull_request opened/reopened ─────────────────────────
233246
if (eventName === 'pull_request' && ['opened', 'reopened'].includes(action)) {
234-
if (!(await prTouchesDocs())) return;
235-
const lane = existingLane || detectLane(branch);
247+
const { touchesDocs, allUnderDev } = await getDocsFileState();
248+
if (!touchesDocs) return;
249+
// Content-based dev detection runs before branch-name detection:
250+
// if every changed docs file lives under docs/dev/, it's a dev
251+
// doc PR regardless of what the branch is called.
252+
const lane = existingLane || (allUnderDev ? 'lane:dev' : detectLane(branch));
236253
if (!lane) {
237254
await addLabels(['stage:needs-lane']);
238255
await setReviewStatus('pending', STAGE_STATUS_DESC['stage:needs-lane']);
239256
const reason = branch.startsWith('docs/')
240-
? `Branch \`${branch}\` starts with \`docs/\` but does not match an expected sub-prefix (\`docs/new/\`, \`docs/major/\`, \`docs/minor/\`, \`docs/fix/\`).`
241-
: `Branch \`${branch}\` does not follow the \`docs/{new,major,minor,fix}/\` naming convention, but this PR modifies files under \`docs/\` and therefore requires a review lane.`;
257+
? `Branch \`${branch}\` starts with \`docs/\` but does not match an expected sub-prefix (\`docs/new/\`, \`docs/major/\`, \`docs/minor/\`, \`docs/fix/\`, \`docs/dev/\`).`
258+
: `Branch \`${branch}\` does not follow the \`docs/{new,major,minor,fix,dev}/\` naming convention, but this PR modifies files under \`docs/\` and therefore requires a review lane.`;
242259
await postComment(`⚠️ **Could not determine review lane**\n\n${reason}\n\n@usace-rmc/docs-admin please apply the correct \`lane:*\` label.`);
243260
return;
244261
}
@@ -257,14 +274,15 @@ jobs:
257274
await setReviewStatus('pending', STAGE_STATUS_DESC['stage:needs-lane']);
258275
return;
259276
}
260-
if (!(await prTouchesDocs())) return;
261-
const lane = detectLane(branch);
277+
const { touchesDocs, allUnderDev } = await getDocsFileState();
278+
if (!touchesDocs) return;
279+
const lane = allUnderDev ? 'lane:dev' : detectLane(branch);
262280
if (!lane) {
263281
await addLabels(['stage:needs-lane']);
264282
await setReviewStatus('pending', STAGE_STATUS_DESC['stage:needs-lane']);
265283
const reason = branch.startsWith('docs/')
266284
? `Branch \`${branch}\` starts with \`docs/\` but does not match an expected sub-prefix.`
267-
: `Branch \`${branch}\` does not follow \`docs/{new,major,minor,fix}/\` naming. This PR now modifies files under \`docs/\` and requires a review lane.`;
285+
: `Branch \`${branch}\` does not follow \`docs/{new,major,minor,fix,dev}/\` naming. This PR now modifies files under \`docs/\` and requires a review lane.`;
268286
await postComment(`⚠️ **Could not determine review lane**\n\n${reason}\n\n@usace-rmc/docs-admin please apply the correct \`lane:*\` label.`);
269287
return;
270288
}
@@ -355,10 +373,12 @@ jobs:
355373
if (LANE_LABELS.includes(added) && (!existingStage || existingStage === 'stage:needs-lane')) {
356374
await removeLabel('stage:needs-lane');
357375
await ensureState();
358-
if (added === 'lane:editorial-fix') {
376+
if (added === 'lane:editorial-fix' || added === 'lane:dev') {
359377
await addLabels(['stage:ready-to-merge']);
360-
await setReviewStatus('success', 'Editorial fix — admin may merge');
361-
await postComment(`📋 Lane set to **editorial fix**.\n\n@usace-rmc/docs-admin please review and merge.`);
378+
const statusDesc = added === 'lane:editorial-fix' ? 'Editorial fix — admin may merge' : 'Dev doc — admin may merge';
379+
const laneLabel = added === 'lane:editorial-fix' ? 'editorial fix' : 'dev doc';
380+
await setReviewStatus('success', statusDesc);
381+
await postComment(`📋 Lane set to **${laneLabel}**.\n\n@usace-rmc/docs-admin please review and merge.`);
362382
} else {
363383
await addLabels(['stage:peer-review']);
364384
await setReviewStatus('pending', STAGE_STATUS_DESC['stage:peer-review']);

0 commit comments

Comments
 (0)