Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
645931d
fix(triage): make Stage 1-pre subsumption check large-file safe
yiliang114 Aug 28, 2026
314a79b
Merge origin/main into fix/issue-10322-subsumption-size-ceiling
yiliang114 Aug 28, 2026
ba0c72e
fix(triage): wire 1-pre closer resolution same-repo, per-issue, fail-…
yiliang114 Aug 28, 2026
808e5f2
fix(triage): align 1-pre carve-out and close comment with frozen-patc…
yiliang114 Aug 28, 2026
e6b52b2
test(triage): pin 1-pre two-patch mechanism and contents-API prohibition
yiliang114 Aug 28, 2026
b96d667
fix(triage): dedup stage 1-pre closers and judge only fetched patches
yiliang114 Aug 28, 2026
3d3a958
test(triage): pin the closer-repo gate and the closer-side fetch guards
yiliang114 Aug 28, 2026
4533ffd
docs(triage): correct stage 1-pre blast-radius and order claims, pin …
yiliang114 Aug 28, 2026
e9e86fe
fix(triage): count occurrences in subsumption and name a fixed linked…
yiliang114 Aug 28, 2026
753ec77
test(triage): pin occurrence counts, jq wiring, closer-set population
yiliang114 Aug 28, 2026
dc2abaf
fix(triage): enumerate every remaining-delta shape subsumption admits
yiliang114 Aug 29, 2026
92b8ac2
Merge branch 'main' into fix/issue-10322-subsumption-size-ceiling
yiliang114 Aug 30, 2026
631f063
fix(triage): close structural gaps in 1-pre subsumption, route oversi…
yiliang114 Aug 30, 2026
3a64f5c
fix(triage): match covered lines hunk by hunk in 1-pre subsumption
yiliang114 Aug 31, 2026
dd60883
Merge branch 'main' into fix/issue-10322-subsumption-size-ceiling
yiliang114 Sep 1, 2026
1c87ac1
fix(triage): require 1-pre closers to have landed on the default branch
yiliang114 Sep 1, 2026
4cbac99
fix(triage): use raw contents for subsumption
yiliang114 Sep 1, 2026
dda2331
fix(triage): preserve absent-file subsumption
yiliang114 Sep 1, 2026
82ad484
Merge branch 'main' into fix/issue-10322-subsumption-size-ceiling
yiliang114 Sep 2, 2026
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
216 changes: 160 additions & 56 deletions .qwen/skills/triage/references/pr-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,13 @@ The parser is not intent-aware — prose like "resolves #123's closer" links
`ISSUES`. There is no deterministic intent check: the linkage decides WHICH
issues the branches below read, and they then act on those issues' states.
The blast radius stays bounded — the only irreversible act (close)
additionally requires this PR's diff to be fully subsumed by the default
branch, which is true only when the change is already landed, so an
accidental linkage can at worst reach a visible, reversible request-changes
review or a maintainer escalation, never a substantively wrong close.
additionally requires this PR's diff to be fully subsumed by the merged
fix, which is true only when the change is contained in that fix's own
patch — judged against the closer's frozen patch below, not the live
default branch — so an accidental linkage can at worst reach a close as
duplicate of a merged fix that contains the change (reversible via the
reopen invitation in the close comment), or a visible, reversible
request-changes review or a maintainer escalation.

```bash
# Record each linked issue's state; $N feeds the closer query below. The loop
Expand Down Expand Up @@ -229,62 +232,162 @@ proceed to 1a.
gh pr review "$PR_NUMBER" --repo "$REPO" --request-changes --body-file /tmp/stage-1pre-not-planned.md
```

- Any linked issue **closed as completed** → find what closed it (GraphQL —
the REST timeline's `closed` event carries no reliable closer reference):
- Any linked issue **closed as completed** → resolve one closer candidate
per closed-as-completed linked issue, in the deterministic `$ISSUES` order:

```bash
gh api graphql -f query='
query($owner: String!, $name: String!, $n: Int!) {
repository(owner: $owner, name: $name) {
issue(number: $n) {
timelineItems(last: 20, itemTypes: [CLOSED_EVENT]) {
nodes {
... on ClosedEvent {
closer {
... on PullRequest { number state merged }
... on Commit { oid }
```bash
CLOSER=$(gh api graphql -f query='
query($owner: String!, $name: String!, $n: Int!) {
repository(owner: $owner, name: $name) {
issue(number: $n) {
timelineItems(last: 20, itemTypes: [CLOSED_EVENT]) {
nodes {
... on ClosedEvent {
closer {
... on PullRequest {
number
state
merged
repository { nameWithOwner }
}
... on Commit { oid }
}
}
}
}
}
}
}
}' -f owner="${REPO%%/*}" -f name="${REPO##*/}" -F n="$N" \
--jq '.data.repository.issue.timelineItems.nodes // [] | last | .closer | select(. != null and .number != null) | "\(.number) \(.merged)"'
```

Only the LAST (most recent) close event counts — earlier closes belong to
reopen cycles and their closers are stale. If the query fails or emits
nothing (the number is a PR, not an issue; the issue does not exist; the
latest close was manual), treat the closer as unresolved.

- Closed by a **merged PR** → compare this PR's production diff (exclude
test/generated files per the Stage 0 size rules) against the default
branch (`$DEFAULT_BRANCH` — this PR's base, per the scope check above):
- **Fully subsumed** — applying this PR's ENTIRE diff to the default
branch would change nothing: every production line this PR adds already
exists there, AND every production line this PR deletes is already
absent there (check per file via
`gh api "repos/$REPO/contents/<path>?ref=$DEFAULT_BRANCH"`). A diff
with NO production changes (e.g. tests-only) is never fully subsumed —
any file it adds outside the production set is itself a remaining
delta. → post the terminal comment below, then close the PR. This is
the ONLY place triage closes a PR.
- **Any remaining delta** — everything else: an added production line
that is missing there, a deleted production line that still exists
there, or any non-production addition → submit exactly one
`CHANGES_REQUESTED` review: name the merged PR, name the remaining
delta, ask the author to rebase onto the default branch and reduce the
PR to that delta (bilingual body whose first line is the
`<!-- qwen-triage stage=1-pre -->` marker, @mention the author). Stop:
}' -f owner="${REPO%%/*}" -f name="${REPO##*/}" -F n="$N" \
--jq '.data.repository.issue.timelineItems.nodes // [] | last | .closer
| select(. != null and .number != null)
| "\(.number) \(.merged) \(.repository.nameWithOwner)"')
read -r MERGED_PR MERGED_FLAG MERGED_REPO <<< "$CLOSER"
if [ "$MERGED_FLAG" = "true" ] && [ "$MERGED_REPO" = "$REPO" ]; then
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
# One closer can close several linked issues — keep only its first
# entry: a duplicate iteration re-fetches, and its `>` truncates the
# already-fetched patch before gh retries
case " $MERGED_PRS " in
*" $MERGED_PR "*) ;;
*) MERGED_PRS="$MERGED_PRS $MERGED_PR" ;;
esac
fi
```

```bash
gh pr review "$PR_NUMBER" --repo "$REPO" --request-changes --body-file /tmp/stage-1pre-remaining-delta.md
```
One query run resolves one issue's closer — run it once per issue
with that issue's `$N`, never once for all issues: bash leaves `$N`
bound to the loop's last element, so a single out-of-loop query
resolves only the last issue's closer. GraphQL is the closer source —
the REST timeline's `closed` event carries no reliable closer
reference. Only the LAST (most recent) close event counts — earlier
closes belong to reopen cycles and their closers are stale. If the
query fails or emits
nothing (the number is a PR, not an issue; the issue does not exist;
the latest close was manual), treat that issue's closer as unresolved.
The closer must also be a merged PR of `$REPO` itself (`MERGED_FLAG`
true and `MERGED_REPO` equal to `$REPO`): a closing keyword can close
an issue from a merged PR in a DIFFERENT repository, and PR numbers
restart at 1 in every repository, so a foreign closer's number
collides within `$REPO` — `gh pr diff` would fetch this repo's
unrelated same-number PR. The gate can only fetch and judge `$REPO`'s
patches: a foreign-repo closer cannot be verified and is treated as
unresolved — never resolved to a colliding number. `$MERGED_PRS`
lists the resolved closers in the deterministic `$ISSUES` order, each
closer number once even when one closer closed several linked issues.
Comment thread
yiliang114 marked this conversation as resolved.
Resolved

- Closed by one or more **merged PRs** of this repo (`$MERGED_PRS`
non-empty) → compare this PR's production diff (exclude test/generated
files per the Stage 0 size rules) against each resolved closer's
production diff. Read each patch once; two calls per closer cover any
number of files. Every fetch carries a failure guard: a failed or
empty fetch never reaches the judgment as an empty patch — an
unfetchable closer is dropped as unverifiable (if no resolved closer
remains, the unresolved-closer branch below applies), and an
unfetchable PR patch aborts the run fail-closed:

```bash
gh pr diff "$PR_NUMBER" --repo "$REPO" > /tmp/stage-1pre-pr.patch || exit 1
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
[ -s /tmp/stage-1pre-pr.patch ] || exit 1
FETCHED_PRS=""
for MERGED_PR in $MERGED_PRS; do
# failed or empty: this closer is unverifiable — drop it, never judge
# an empty patch
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
gh pr diff "$MERGED_PR" --repo "$REPO" \
> "/tmp/stage-1pre-closer-$MERGED_PR.patch" || continue
[ -s "/tmp/stage-1pre-closer-$MERGED_PR.patch" ] || continue
FETCHED_PRS="$FETCHED_PRS $MERGED_PR"
done
# judge only the fetchable set — if every closer fetch failed this
# leaves $MERGED_PRS empty and the unresolved-closer branch below
# escalates instead of judging nothing as verified
MERGED_PRS="$FETCHED_PRS"
```

Do NOT download the default branch's files one by one via
`gh api "repos/$REPO/contents/<path>?ref=$DEFAULT_BRANCH"`: above ~1 MiB
that endpoint silently returns HTTP 200 with empty content
(`content_len: 0`, `encoding: "none"`, no error), so a per-file download
can never confirm subsumption for large files — the check would fail
silently exactly where a duplicate is most expensive. It would also
spend one REST call per file against the shared CI PAT's rate limit and
transit every full blob through the agent's context. Two frozen patches
per closer cost two calls for any file count and keep the verdict atomic
with respect to base-branch movement during the check. Judge this PR's
patch against each closer's patch in `$MERGED_PRS` order, comparing
within the same file:
- **Fully subsumed** — file by file: every production file section in
this PR's patch has a counterpart section in the closer's patch, and
within each matched pair, every production line this PR adds also
appears among the lines the closer's patch adds to that file at
least as many times as this PR adds it, and, symmetrically for
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
deletions, every production line this PR deletes appears among the
lines the closer's patch deletes from it at least as many times as
this PR deletes it. The quantifiers count occurrences, not set membership:
identical line text occurs many times (blank lines make
this commonplace), so one occurrence in the closer's patch covers
exactly one occurrence in this PR's patch — a closer that deletes
one blank line never covers this PR deleting three. A section with
no line-level
representation (a pure rename, a binary, a mode change, an empty
file) is covered only by an equivalent change to the same path in
the closer's patch. A line the closer's patch both adds and deletes
in that file cancels out and covers neither direction, and the
quantifiers range over matched sections only, so they never hold
vacuously — a rename-only diff closes only when its rename section
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
has that same-path counterpart. The closer's patch is frozen against
ITS merge base, so a line later edited or reverted on the default
branch still counts as covered here — the close comment below invites
reopening for exactly that case. Subsumption ranges over the ENTIRE diff,
not the production sections alone: a diff with NO production changes
(e.g. tests-only) is never fully subsumed, and a diff whose production
lines are fully covered but which ALSO changes tests or docs is not
subsumed either — anything it adds outside the covered production set
is itself a remaining delta. If more than one
closer fully subsumes this PR, the FIRST one in `$MERGED_PRS` order
is the duplicate target, and the close comment names the FIRST linked
issue the chosen closer closed, in `$ISSUES` order: `$MERGED_PRS`
records closer numbers only, so this rule fixes which #N the singular
template names when one closer closed several linked issues. → post
the terminal comment below, then close the PR. This is the ONLY place
triage closes a PR.
- **Any remaining delta** — no closer fully subsumes this PR: for each
closer, an added production line ITS patch does not add, a deleted
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
production line ITS patch does not delete, or any non-production
addition → submit exactly one `CHANGES_REQUESTED` review: name the
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
resolved closers,
name the remaining delta, ask the author to rebase onto the default
branch and reduce the PR to that delta (bilingual body whose first
line is the `<!-- qwen-triage stage=1-pre -->` marker, @mention the
author). Stop:

```bash
gh pr review "$PR_NUMBER" --repo "$REPO" --request-changes --body-file /tmp/stage-1pre-remaining-delta.md
```

- Closed manually (no close commit) or the closer cannot be resolved →
never close on ambiguity: flag it in the Stage 1 comment and escalate to
the maintainer.
- No resolved and fetchable closer remains (`$MERGED_PRS` empty — every
close was manual or by a commit, or every closer is unmerged or from
a foreign repository — or every closer fetch failed) → the gate must
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
never close on ambiguity: flag it in the Stage 1 comment and escalate
to the maintainer.

**Reopen guard.** The close below is the gate's only irreversible act, and an
explicit `@qwen-code /triage` re-run executes every stage again — including
Expand All @@ -311,14 +414,15 @@ cat > /tmp/stage-1pre-duplicate.md <<'EOF'
<!-- qwen-triage stage=1-pre -->

The linked issue #N was already fixed by #M, and every production change in
this PR is already on the default branch — closing as a duplicate of #M. If
something here is NOT covered by #M, say so and this can be reopened.
this PR is covered by #M's merged fix — closing as a duplicate of #M. If
something here is NOT covered by #M, or #M's fix was later edited or
reverted on the default branch, say so and this can be reopened.

<details>
<summary>中文说明</summary>

关联 issue #N 已由 #M 修复,本 PR 的生产代码改动均已存在于默认分支,
现作为 #M 的重复 PR 关闭。如本 PR 有 #M 未覆盖的内容,请说明,可以重新打开。
关联 issue #N 已由 #M 修复,本 PR 的生产代码改动均已被 #M 的合并修复覆盖,
现作为 #M 的重复 PR 关闭。如本 PR 有 #M 未覆盖的内容,或 #M 的修复后续在默认分支上被修改或回退,请说明,可以重新打开。

</details>

Expand Down
102 changes: 102 additions & 0 deletions scripts/tests/qwen-triage-workflow.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6992,13 +6992,115 @@ describe('stage 1-pre duplicate gate', () => {
expect(section).toContain('every production line this PR deletes');
});

it('pins the anti-vacuity clauses of the subsumption definition', () => {
// Without the cancellation clause, a line the closer both adds and
// deletes counts as covered; without the matched-sections clause, a
// rename-only diff closes vacuously against any closer; without the
// occurrence-count clause, one deleted occurrence in the closer's
// patch covers any number of identical deletions here (blank lines
// make that commonplace).
expect(section).toContain('cancels out and covers neither direction');
expect(section).toContain('quantifiers range over matched sections only');
expect(section).toContain(
'covered only by an equivalent change to the same path',
);
expect(section).toContain('at least as many times');
expect(section).toContain('count occurrences, not set membership');
});

it('never closes a diff with no production changes', () => {
// Stage 0 exclusions empty the comparison set for tests-only PRs; such a
// diff must be a remaining delta, never "Fully subsumed".
expect(section).toContain('NO production changes');
expect(section).toContain('never fully subsumed');
});

it('resolves the closer repository and rejects colliding foreign numbers', () => {
// A closing keyword can close an issue from a merged PR in another
// repository, and PR numbers restart at 1 everywhere, so a foreign
// closer number would fetch this repo's unrelated same-number PR
// unless the closer's repository is selected and matched. The capture
// is pinned too — every stage-local variable is bound in a snippet.
expect(section).toContain('repository { nameWithOwner }');
expect(section).toContain('read -r MERGED_PR MERGED_FLAG MERGED_REPO');
// The jq output interpolation wires the selection set to the binding:
// reverted to the pre-PR two-field form, MERGED_REPO stays empty for
// every closer, the gate rejects them all, and the duplicate gate is
// dead while every other pin stays green.
expect(section).toContain('\\(.merged) \\(.repository.nameWithOwner)');
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
expect(section).toContain('[ "$MERGED_REPO" = "$REPO" ]');
expect(section).toContain('never resolved to a colliding number');
});

it('compares against every resolved closer in one fixed order', () => {
// Multiple closed-as-completed issues each resolve a closer; the query
// runs once per issue — never once outside the per-issue iteration,
// where bash leaves $N bound to the last element — and the first
// subsuming closer in the fixed order wins, so multiple closers have
// exactly one outcome.
expect(section).toContain('never once for all issues');
expect(section).toContain('bound to the loop');
expect(section).toContain('FIRST one');
expect(section).toContain('the deterministic `$ISSUES` order');
// $MERGED_PRS drops which issue each closer came from; when the
// chosen closer closed several linked issues, the singular close
// comment needs a rule for which #N it names.
expect(section).toContain('names the FIRST linked');
});

it('enumerates the remaining delta per closer, never as a union', () => {
// Union quantification ("a line no closer's patch adds") is empty when
// a fix is split across two closers (A adds L1, B adds L2, this PR adds
// both): the branch would demand a delta it simultaneously denies.
// Per-closer quantification matches the "Fully subsumed" check it
// negates.
expect(section).toContain('ITS patch does not add');
expect(section).toContain('ITS patch does not delete');
});

it('guards every patch fetch fail-closed', () => {
// '>' truncates the target before gh runs; a failed or empty fetch
// must never be judged as an empty patch — an unfetchable closer is
// unverifiable and routes to the unresolved-closer escalation.
expect(section).toContain('|| exit 1');
expect(section).toContain('[ -s /tmp/stage-1pre-pr.patch ]');
expect(section).toContain(
'> "/tmp/stage-1pre-closer-$MERGED_PR.patch" || continue',
);
expect(section).toContain(
'[ -s "/tmp/stage-1pre-closer-$MERGED_PR.patch" ] || continue',
);
expect(section).toContain('carries a failure guard');
});

it('judges only the deduped, fetchable closer set', () => {
// One closer can close several linked issues, and the duplicate
// iteration's '>' truncates the already-fetched patch before gh
// retries. A dropped closer must also leave $MERGED_PRS, or "every
// closer fetch failed" is unreachable and an empty patch reaches
// judgment as verified.
expect(section).toContain('*" $MERGED_PR "*) ;;');
// The else arm is the only line that ever populates the closer set:
// deleted, $MERGED_PRS stays empty, the judgment branch becomes
// unreachable, and every completed-issue PR escalates instead.
expect(section).toContain('*) MERGED_PRS="$MERGED_PRS $MERGED_PR" ;;');
expect(section).toContain('FETCHED_PRS="$FETCHED_PRS $MERGED_PR"');
expect(section).toContain('MERGED_PRS="$FETCHED_PRS"');
});

it('decides subsumption from two frozen gh pr diff patches, never per-file downloads', () => {
// The check compares this PR's patch against each closer's patch —
// two `gh pr diff` fetches cover any file count. Downloading
// default-branch files one by one via the contents API silently
// returns HTTP 200 with empty content above ~1 MiB, re-introducing
// the silent large-file failure the linked issue describes;
// deleting the patch prescription or the prohibition must make this
// red.
expect(section).toContain('gh pr diff "$PR_NUMBER"');
expect(section).toContain('gh pr diff "$MERGED_PR"');
expect(section).toContain('Do NOT download');
});

it('scopes linkage extraction to same-repo closing references', () => {
// A bare `.number` extraction drops the repository qualifier, so a
// cross-repo closing reference resolves against this repo's
Expand Down
Loading