Skip to content

[Application] show hazard exposure results #308

[Application] show hazard exposure results

[Application] show hazard exposure results #308

Workflow file for this run

name: PR Policy
on:
pull_request:
types:
- opened
- edited
- synchronize
- reopened
- ready_for_review
permissions:
contents: read
pull-requests: read
jobs:
validate-pr:
name: Validate PR
runs-on: ubuntu-latest
steps:
- name: Check title and body
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
const title = pr.title || "";
const body = pr.body || "";
const errors = [];
const titlePattern = /^\[(Engine|Domain|Application|Docs|Build|Analysis|Chore)\]\s.+$/;
if (!titlePattern.test(title)) {
errors.push("PR title must match `[Area] short summary`.");
}
const requiredHeadings = [
"## Summary",
"## Related Issue",
"## Area",
"## Architecture Check",
"## Verification",
];
for (const heading of requiredHeadings) {
if (!body.includes(heading)) {
errors.push(`Missing section: ${heading}`);
}
}
const section = (heading) => {
const escaped = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const pattern = new RegExp(`${escaped}\\s*([\\s\\S]*?)(?=\\n## |$)`, "i");
const match = body.match(pattern);
return match ? match[1] : "";
};
const relatedIssueSection = section("## Related Issue");
const areaSection = section("## Area");
const verificationSection = section("## Verification");
const selectedAreas = [...areaSection.matchAll(/- \[[xX]\] (.+)/g)].map((match) => match[1].trim());
const changedFiles = await github.paginate(github.rest.pulls.listFiles, {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
per_page: 100,
});
const docsPolicyOnlyPatterns = [
/^docs\//,
/^uml\//,
/^AGENTS\.md$/,
/^\.github\/ISSUE_TEMPLATE\//,
/^\.github\/PULL_REQUEST_TEMPLATE\.md$/,
/^\.github\/workflows\/pr-policy\.yml$/,
/^CONTRIBUTING\.md$/,
];
const isDocsPolicyOnlyPr =
changedFiles.length > 0 &&
changedFiles.every((file) =>
docsPolicyOnlyPatterns.some((pattern) => pattern.test(file.filename))
);
const applicationOnlyPatterns = [
/^src\/application\//,
/^CMakeLists\.txt$/,
];
const isApplicationOnlyPr =
changedFiles.length > 0 &&
changedFiles.every((file) =>
applicationOnlyPatterns.some((pattern) => pattern.test(file.filename))
);
const issuePattern = /\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?|refs?)\s+#\d+\b/i;
const hasIssueReference = issuePattern.test(relatedIssueSection);
const usesDocsOnlyException = /\bnone\s*\(docs\/policy-only PR\)/i.test(relatedIssueSection);
const usesApplicationOnlyException = /\bnone\s*\(application-only PR\)/i.test(relatedIssueSection);
if (!hasIssueReference) {
if (!((usesDocsOnlyException && isDocsPolicyOnlyPr) || (usesApplicationOnlyException && isApplicationOnlyPr))) {
errors.push("`## Related Issue` must include an issue reference such as `Closes #12`, unless changed files are limited to docs/policy paths with `None (docs/policy-only PR)` or application-only paths with `None (application-only PR)`.");
}
}
if (selectedAreas.length === 0) {
errors.push("Select at least one checkbox in `## Area`.");
}
if (!/- \[[xX]\] /.test(verificationSection)) {
errors.push("Select at least one checkbox in `## Verification`.");
}
if (errors.length > 0) {
core.setFailed(errors.join("\n"));
} else {
core.info("PR policy checks passed.");
}