Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions .github/scripts/pr-readiness/test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,50 @@ const context = {
const blockerCodes = (readiness) => (
readiness.deterministic_blockers.map((item) => item.code)
);
const closingChildren = Array.from({ length: 31 }, (_, index) => ({
...input.linked_issues[0],
number: 101 + index,
parent_number: 100,
}));
const manyLinkedIssues = [{
...input.linked_issues[0],
number: 100,
issue_type: "Task",
body: taskBody,
sub_issue_count: closingChildren.length,
sub_issues: closingChildren.map((issue) => ({
repository: issue.repository,
number: issue.number,
state: issue.state,
})),
}, ...closingChildren];
const manyLinkedIssuesInput = {
...input,
linked_issues: manyLinkedIssues,
linked_issue_count: manyLinkedIssues.length,
};
const scriptDirectory = path.dirname(new URL(import.meta.url).pathname);
const verifySource = fs.readFileSync(
path.join(scriptDirectory, "verify.mjs"),
"utf8",
);
const workflowSource = fs.readFileSync(
path.join(scriptDirectory, "..", "..", "workflows", "codex-openai-review.yml"),
"utf8",
);
for (const source of [verifySource, workflowSource]) {
assert.match(source, /closingIssuesReferences\(first: 100\)/);
assert.doesNotMatch(source, /closingIssuesReferences\.nodes\s*\.slice\(/);
}
assert.deepEqual(context.readiness.deterministic_blockers, []);
assert.deepEqual(
analyzePullRequest(manyLinkedIssuesInput).deterministic_blockers,
[],
);
assert.ok(blockerCodes(analyzePullRequest({
...manyLinkedIssuesInput,
linked_issue_count: manyLinkedIssues.length + 1,
})).includes("too-many-closing-issues"));
assert.ok(analyzePullRequest({ ...input, title: "Bad title" })
.deterministic_blockers.some((item) => item.code === "invalid-title"));
assert.ok(analyzePullRequest({ ...input, body: "" })
Expand Down Expand Up @@ -427,6 +470,35 @@ try {
new RegExp(context.readiness.snapshot_sha256),
);

const manyFixture = structuredClone(fixture);
manyFixture.repository.pullRequest.closingIssuesReferences = {
totalCount: manyLinkedIssues.length,
nodes: manyLinkedIssues.map((issue) => ({
repository: { nameWithOwner: issue.repository },
number: issue.number,
title: issue.title,
body: issue.body,
state: issue.state,
issueType: { name: issue.issue_type },
parent: issue.parent_number == null
? null
: { number: issue.parent_number },
subIssues: {
totalCount: issue.sub_issue_count ?? 0,
nodes: (issue.sub_issues ?? []).map((subIssue) => ({
repository: { nameWithOwner: subIssue.repository },
number: subIssue.number,
state: subIssue.state,
})),
},
})),
};
fs.writeFileSync(fixtureFile, JSON.stringify(manyFixture));
const manyReadiness = analyzePullRequest(manyLinkedIssuesInput);
const manyResult = verify(manyReadiness.snapshot_sha256);
assert.equal(manyResult.status, 0, manyResult.stderr);
fs.writeFileSync(fixtureFile, JSON.stringify(fixture));

const assertStale = (mutate) => {
const staleFixture = structuredClone(fixture);
mutate(staleFixture.repository.pullRequest);
Expand Down
3 changes: 1 addition & 2 deletions .github/scripts/pr-readiness/verify.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ async function fetchPullRequest() {
body
baseRefOid
headRefOid
closingIssuesReferences(first: 20) {
closingIssuesReferences(first: 100) {
totalCount
nodes {
repository { nameWithOwner }
Expand Down Expand Up @@ -100,7 +100,6 @@ const data = await fetchPullRequest();
const pullRequest = data.repository?.pullRequest;
if (!pullRequest) throw new Error("Pull request was not found");
const linkedIssues = pullRequest.closingIssuesReferences.nodes
.slice(0, 10)
.map((issue) => ({
repository: issue.repository.nameWithOwner,
number: issue.number,
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/codex-openai-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ jobs:
pullRequest(number: $number) {
title
body
closingIssuesReferences(first: 20) {
closingIssuesReferences(first: 100) {
totalCount
nodes {
repository { nameWithOwner }
Expand Down Expand Up @@ -449,7 +449,6 @@ jobs:
linked_issue_count:
pullRequest.closingIssuesReferences.totalCount,
linked_issues: pullRequest.closingIssuesReferences.nodes
.slice(0, 10)
.map((issue) => ({
repository: issue.repository.nameWithOwner,
number: issue.number,
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ linkage requirements:
- The complete PR result follows the current Issue plan. Material deviations
must be reflected in the Issue or disclosed and resolved in the PR.

The reviewer preserves every native closing Issue returned by GitHub's maximum
100-node GraphQL page. It fails closed when `totalCount` exceeds the collected
nodes, rather than silently reviewing a truncated relationship set. Native
sub-issue snapshots use the same 100-node fail-closed bound.

Caller policy can add trusted organization-level constraints. Repository-owned
title formats, Issue Types, sections, relationships, ownership rules,
validation commands, platform requirements, and finding severity rules belong
Expand Down
Loading