Skip to content

feat: add a reader fold-state source (R816) #1219

feat: add a reader fold-state source (R816)

feat: add a reader fold-state source (R816) #1219

Workflow file for this run

name: validate-pr
on:
pull_request:
types:
- opened
- edited
- reopened
- synchronize
- ready_for_review
permissions:
contents: read
pull-requests: read
jobs:
changelog-placement:
name: changelog-placement
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check changelog entries land under Unreleased
run: |
git fetch --no-tags origin "$GITHUB_BASE_REF"
python3 scripts/check_changelog_placement.py --base "origin/$GITHUB_BASE_REF"
validate-pr:
name: validate-pr
runs-on: ubuntu-24.04
steps:
- name: Validate PR title and body
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
const errors = [];
const title = pr.title || "";
const body = pr.body || "";
const conventionalTitle =
/^(feat|fix|docs|chore|refactor|test|build|ci|perf|revert)(\([^)]+\))?:\s.+\(R\d+\)$/i;
if (!conventionalTitle.test(title)) {
errors.push("PR title must follow Conventional Commits and include ticket ID (e.g. `fix: short summary (R123)`).");
}
if (/\b(wip|dnm|do not merge)\b/i.test(title)) {
errors.push("PR title cannot contain WIP/DNM markers.");
}
const normalizedBody = body
.replace(/<!--[\s\S]*?-->/g, "")
.trim();
if (normalizedBody.length < 50) {
errors.push("PR description is too short. Please use the template.");
}
const requiredSections = [
"## Objective",
"## Scope",
"## Verification",
"## Risk",
];
for (const section of requiredSections) {
if (!normalizedBody.includes(section)) {
errors.push(`PR description is missing required section: ${section}`);
}
}
const closesIssuePattern =
/\b(close[sd]?|fix(e[sd])?|resolve[sd]?)\s+((?:[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)?#\d+|https?:\/\/github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]\/issues\/\d+)\b/i;
if (!closesIssuePattern.test(normalizedBody)) {
errors.push(
"PR description must include an issue auto-close reference (for example: `Closes #123`).",
);
}
const highRiskPattern = /\b(Priority:\s*P0|Risk\s*Tier:\s*T3)\b/i;
if (highRiskPattern.test(normalizedBody)) {
const rollbackSection = normalizedBody.split("## Rollback")[1] || "";
const rollbackContent = rollbackSection.split("##")[0].trim();
if (
rollbackContent.length < 20 ||
rollbackContent.includes("How to safely disable or revert")
) {
errors.push(
"High-risk PRs (P0 or T3) must include detailed rollback notes in the ## Rollback section.",
);
}
}
if (errors.length > 0) {
core.setFailed(errors.map((e) => `- ${e}`).join("\n"));
} else {
core.info("PR policy checks passed.");
}