From 6d50d5145466c91a9776ca0bf2efa5cc1244863e Mon Sep 17 00:00:00 2001 From: jan-kubica Date: Sun, 20 Sep 2026 20:13:28 +0200 Subject: [PATCH 1/4] chore: remove Claude instruction aliases --- .agents/skills/finish-pr/SKILL.md | 15 +++++++++---- .agents/skills/open-pr/SKILL.md | 13 +++++++---- .agents/skills/rabbit-round/SKILL.md | 33 +++++++++++++++++++--------- .ai/generated-agent-files.txt | 1 - .ai/shared | 2 +- .claude/skills/finish-pr/SKILL.md | 15 +++++++++---- .claude/skills/open-pr/SKILL.md | 13 +++++++---- .claude/skills/rabbit-round/SKILL.md | 33 +++++++++++++++++++--------- .coderabbit.yaml | 1 - CLAUDE.md | 6 ----- package.json | 2 +- 11 files changed, 88 insertions(+), 46 deletions(-) delete mode 100644 CLAUDE.md diff --git a/.agents/skills/finish-pr/SKILL.md b/.agents/skills/finish-pr/SKILL.md index 9858b30..d84cd79 100644 --- a/.agents/skills/finish-pr/SKILL.md +++ b/.agents/skills/finish-pr/SKILL.md @@ -46,9 +46,15 @@ Never request an automated review (`@coderabbitai review`, `@codex review`, or a timed re-request after a rate limit); reviews arrive on their own. Budget the loop: at most two review rounds after the first green head. When actionable findings keep arriving past that, keep the green head and triage the remaining findings yourself -with a concrete accept or push-back. Open one stacked follow-up PR for the accepted -fixes, then reply to each deferred finding with that PR's URL and resolve it: the -named follow-up is the disposition, not a promise. +with a concrete accept, push-back, or defer. Open one stacked follow-up PR for the +accepted fixes, then reply to each deferred finding with that PR's URL, and resolve +the thread where the finding has one: the named follow-up is the disposition, not a +promise. A deferred top-level comment stays open; the reply is its terminal state. + +The budget never defers a release-blocking defect. A finding that names a +security, authorization, data-loss, or data-corruption defect, and survives +verification, is fixed on this head however late it arrives: shipping a known +defect to keep a round count is the outcome the budget exists to avoid. ## 4. Stop at a Real Terminal State @@ -58,7 +64,8 @@ The latest pushed head has converged only when: - automated reviewers are terminal, not pending - no actionable automated finding remains in a review thread or a top-level comment: each is implemented, already addressed, pushed back with evidence, - or deferred to a named follow-up PR + or deferred to a named follow-up PR, and no verified release-blocking defect + was deferred - no unresolved human request for changes remains - no blocking review or merge conflict remains diff --git a/.agents/skills/open-pr/SKILL.md b/.agents/skills/open-pr/SKILL.md index 886e3cc..0af0aff 100644 --- a/.agents/skills/open-pr/SKILL.md +++ b/.agents/skills/open-pr/SKILL.md @@ -19,12 +19,17 @@ defaults to the fork and would miss an upstream PR: git branch --show-current git status --short gh pr list --repo "$BASE_REPO" --head "$(git branch --show-current)" \ - --state all --json number,state,isDraft,headRefName,baseRefName,url + --state all \ + --json number,state,isDraft,headRefName,headRepositoryOwner,baseRefName,url ``` -An empty PR list means no PR exists. Authentication, network, or repository -errors must remain visible and stop the workflow before history changes or -publication. +An empty PR list means no PR exists. `--head` filters by branch name alone, so +in a fork workflow the list can hold another contributor's PR from a branch of +the same name: treat a result as this checkout's PR only when its +`headRepositoryOwner` is the owner your head remote pushes to. + +Authentication, network, or repository errors must remain visible and stop the +workflow before history changes or publication. Never prepare a PR in a dirty shared checkout. If the checkout is on the default branch, detached, has unrelated changes, or spans repositories or diff --git a/.agents/skills/rabbit-round/SKILL.md b/.agents/skills/rabbit-round/SKILL.md index 7815f75..5561af6 100644 --- a/.agents/skills/rabbit-round/SKILL.md +++ b/.agents/skills/rabbit-round/SKILL.md @@ -18,9 +18,13 @@ identified. Fetch paginated review threads through GitHub GraphQL so unresolved state and thread replies are preserved. Fetch top-level issue comments separately. Record every participant and reply author in a thread, which comments apply to the -current head, and which are stale. Treat a thread as bot-authored only when every -participant is a confirmed allowed bot; a mixed or uncertain thread follows the -human-thread rules. +current head, and which are stale. Classify participants from a fresh fetch. +Record a receipt for every workflow reply with its returned reply-node ID and +exact content, and retain those receipts across resume or handoff. On later +fetches, exclude only replies matched to an exact receipt; never infer an +exclusion from the requester account or an attribution footer. All remaining +participants must be confirmed allowed bots; a human, mixed, or uncertain thread +follows the human-thread rules. Do not rely only on the REST review-comments list: it does not represent thread resolution or the complete conversation reliably. @@ -37,7 +41,10 @@ comment: - **Push back**: incorrect, stale, speculative, or contrary to documented constraints. - **Defer**: accepted, but landing in a named follow-up PR because the enclosing - workflow's review budget is spent. Only with the follow-up PR's URL. + workflow's review budget is spent. Only with the follow-up PR's URL, and every + defer in one run names the same PR. Never for a verified release-blocking + defect (security, authorization, data loss, corruption): those are fixed on the + current head. Read the cited code and applicable instructions before deciding. Treat security, authorization, data loss, and compatibility claims as hypotheses to verify, not @@ -69,11 +76,14 @@ finding. Keep responses short and factual: Follow repository attribution rules for GitHub comments. Do not claim a check passed unless it ran successfully on the reported head. -After replying, resolve only review threads whose every participant is a -confirmed allowed bot and that are implemented, already addressed, answered -with a supported pushback, or deferred to a named follow-up PR. Leave human, mixed-participant, and uncertain threads -open. Top-level comments have no thread-resolution state; do not minimize bot -summaries by default. +After replying, refetch each candidate thread before resolving it. Exclude only +exact workflow reply receipts, then require every remaining participant to be a +confirmed allowed bot. Triage any new bot finding before resolving; any human, +unknown, mixed-participant, or uncertain arrival leaves the thread open. Resolve +only when the finding is implemented, already addressed, answered with supported +pushback, or deferred to a named follow-up PR. Top-level comments have no +thread-resolution state: a reply naming the follow-up PR is the whole disposition +there. Do not minimize bot summaries by default. ## 5. Recheck the Current Head @@ -81,7 +91,10 @@ Refresh the PR after the push and report one status: - `clean`: all current-head automated reviewers are terminal, required checks are green, and no actionable automated finding remains in a review thread or - top-level comment + top-level comment. A top-level finding answered with a defer reply carrying the + follow-up PR's URL is no longer actionable on later rounds, unless it names a + verified release-blocking defect: no defer makes one of those non-actionable, + and the status stays `needs_changes` until it is fixed on the current head - `pending_bots`: this round pushed the current head, or a current-head automated review or required check is still running - `needs_changes`: actionable automated feedback remains diff --git a/.ai/generated-agent-files.txt b/.ai/generated-agent-files.txt index 270e9ba..05b275f 100644 --- a/.ai/generated-agent-files.txt +++ b/.ai/generated-agent-files.txt @@ -1,3 +1,2 @@ AGENTS.md -CLAUDE.md GEMINI.md diff --git a/.ai/shared b/.ai/shared index 2c81187..ff61c10 160000 --- a/.ai/shared +++ b/.ai/shared @@ -1 +1 @@ -Subproject commit 2c81187ed1c93d60f340e266b26b79d2c7f6d952 +Subproject commit ff61c10c2578d1145669c103c6fafa1b3e10b186 diff --git a/.claude/skills/finish-pr/SKILL.md b/.claude/skills/finish-pr/SKILL.md index 9858b30..d84cd79 100644 --- a/.claude/skills/finish-pr/SKILL.md +++ b/.claude/skills/finish-pr/SKILL.md @@ -46,9 +46,15 @@ Never request an automated review (`@coderabbitai review`, `@codex review`, or a timed re-request after a rate limit); reviews arrive on their own. Budget the loop: at most two review rounds after the first green head. When actionable findings keep arriving past that, keep the green head and triage the remaining findings yourself -with a concrete accept or push-back. Open one stacked follow-up PR for the accepted -fixes, then reply to each deferred finding with that PR's URL and resolve it: the -named follow-up is the disposition, not a promise. +with a concrete accept, push-back, or defer. Open one stacked follow-up PR for the +accepted fixes, then reply to each deferred finding with that PR's URL, and resolve +the thread where the finding has one: the named follow-up is the disposition, not a +promise. A deferred top-level comment stays open; the reply is its terminal state. + +The budget never defers a release-blocking defect. A finding that names a +security, authorization, data-loss, or data-corruption defect, and survives +verification, is fixed on this head however late it arrives: shipping a known +defect to keep a round count is the outcome the budget exists to avoid. ## 4. Stop at a Real Terminal State @@ -58,7 +64,8 @@ The latest pushed head has converged only when: - automated reviewers are terminal, not pending - no actionable automated finding remains in a review thread or a top-level comment: each is implemented, already addressed, pushed back with evidence, - or deferred to a named follow-up PR + or deferred to a named follow-up PR, and no verified release-blocking defect + was deferred - no unresolved human request for changes remains - no blocking review or merge conflict remains diff --git a/.claude/skills/open-pr/SKILL.md b/.claude/skills/open-pr/SKILL.md index 886e3cc..0af0aff 100644 --- a/.claude/skills/open-pr/SKILL.md +++ b/.claude/skills/open-pr/SKILL.md @@ -19,12 +19,17 @@ defaults to the fork and would miss an upstream PR: git branch --show-current git status --short gh pr list --repo "$BASE_REPO" --head "$(git branch --show-current)" \ - --state all --json number,state,isDraft,headRefName,baseRefName,url + --state all \ + --json number,state,isDraft,headRefName,headRepositoryOwner,baseRefName,url ``` -An empty PR list means no PR exists. Authentication, network, or repository -errors must remain visible and stop the workflow before history changes or -publication. +An empty PR list means no PR exists. `--head` filters by branch name alone, so +in a fork workflow the list can hold another contributor's PR from a branch of +the same name: treat a result as this checkout's PR only when its +`headRepositoryOwner` is the owner your head remote pushes to. + +Authentication, network, or repository errors must remain visible and stop the +workflow before history changes or publication. Never prepare a PR in a dirty shared checkout. If the checkout is on the default branch, detached, has unrelated changes, or spans repositories or diff --git a/.claude/skills/rabbit-round/SKILL.md b/.claude/skills/rabbit-round/SKILL.md index 7815f75..5561af6 100644 --- a/.claude/skills/rabbit-round/SKILL.md +++ b/.claude/skills/rabbit-round/SKILL.md @@ -18,9 +18,13 @@ identified. Fetch paginated review threads through GitHub GraphQL so unresolved state and thread replies are preserved. Fetch top-level issue comments separately. Record every participant and reply author in a thread, which comments apply to the -current head, and which are stale. Treat a thread as bot-authored only when every -participant is a confirmed allowed bot; a mixed or uncertain thread follows the -human-thread rules. +current head, and which are stale. Classify participants from a fresh fetch. +Record a receipt for every workflow reply with its returned reply-node ID and +exact content, and retain those receipts across resume or handoff. On later +fetches, exclude only replies matched to an exact receipt; never infer an +exclusion from the requester account or an attribution footer. All remaining +participants must be confirmed allowed bots; a human, mixed, or uncertain thread +follows the human-thread rules. Do not rely only on the REST review-comments list: it does not represent thread resolution or the complete conversation reliably. @@ -37,7 +41,10 @@ comment: - **Push back**: incorrect, stale, speculative, or contrary to documented constraints. - **Defer**: accepted, but landing in a named follow-up PR because the enclosing - workflow's review budget is spent. Only with the follow-up PR's URL. + workflow's review budget is spent. Only with the follow-up PR's URL, and every + defer in one run names the same PR. Never for a verified release-blocking + defect (security, authorization, data loss, corruption): those are fixed on the + current head. Read the cited code and applicable instructions before deciding. Treat security, authorization, data loss, and compatibility claims as hypotheses to verify, not @@ -69,11 +76,14 @@ finding. Keep responses short and factual: Follow repository attribution rules for GitHub comments. Do not claim a check passed unless it ran successfully on the reported head. -After replying, resolve only review threads whose every participant is a -confirmed allowed bot and that are implemented, already addressed, answered -with a supported pushback, or deferred to a named follow-up PR. Leave human, mixed-participant, and uncertain threads -open. Top-level comments have no thread-resolution state; do not minimize bot -summaries by default. +After replying, refetch each candidate thread before resolving it. Exclude only +exact workflow reply receipts, then require every remaining participant to be a +confirmed allowed bot. Triage any new bot finding before resolving; any human, +unknown, mixed-participant, or uncertain arrival leaves the thread open. Resolve +only when the finding is implemented, already addressed, answered with supported +pushback, or deferred to a named follow-up PR. Top-level comments have no +thread-resolution state: a reply naming the follow-up PR is the whole disposition +there. Do not minimize bot summaries by default. ## 5. Recheck the Current Head @@ -81,7 +91,10 @@ Refresh the PR after the push and report one status: - `clean`: all current-head automated reviewers are terminal, required checks are green, and no actionable automated finding remains in a review thread or - top-level comment + top-level comment. A top-level finding answered with a defer reply carrying the + follow-up PR's URL is no longer actionable on later rounds, unless it names a + verified release-blocking defect: no defer makes one of those non-actionable, + and the status stays `needs_changes` until it is fixed on the current head - `pending_bots`: this round pushed the current head, or a current-head automated review or required check is still running - `needs_changes`: actionable automated feedback remains diff --git a/.coderabbit.yaml b/.coderabbit.yaml index 589a2cb..85ec56b 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -54,7 +54,6 @@ knowledge_base: enabled: true filePatterns: - "AGENTS.md" - - "CLAUDE.md" - "GEMINI.md" - "CONTRIBUTING.md" - "SECURITY.md" diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index dd0070d..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,6 +0,0 @@ -@AGENTS.md - -## Claude Code - -Use the project skills in `.claude/skills/` when a task matches one of those -workflows. diff --git a/package.json b/package.json index 292f75b..078e37c 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "test:rust-lints": "cd rust-lints/stella_lints && cargo test", "lint": "bun run build && bun --bun oxlint -c oxlint.config.ts --report-unused-disable-directives-severity=error --deny-warnings --type-aware .", "lint:fix": "bun --bun oxlint -c oxlint.config.ts --type-aware --fix .", - "format": "oxfmt . \"!.ai/**\" \"!.agents/**\" \"!.claude/**\" \"!AGENTS.md\" \"!CLAUDE.md\" \"!GEMINI.md\"", + "format": "oxfmt . \"!.ai/**\" \"!.agents/**\" \"!.claude/**\" \"!AGENTS.md\" \"!GEMINI.md\"", "publint": "publint --pack npm packages/typescript-config && publint --pack npm packages/oxlint-config && publint --pack npm packages/oxlint-plugin", "pack:dry-run": "bun run build && (cd packages/typescript-config && npm pack --dry-run) && (cd packages/oxlint-config && npm pack --dry-run) && (cd packages/oxlint-plugin && npm pack --dry-run)", "prepack": "bun run build && bun run typecheck", From e55177db29ee32363ee41c488da2cb15008605b3 Mon Sep 17 00:00:00 2001 From: jan-kubica Date: Sun, 20 Sep 2026 20:24:26 +0200 Subject: [PATCH 2/4] fix: match pull request head repository exactly --- .agents/skills/open-pr/SKILL.md | 9 ++++++--- .ai/shared | 2 +- .claude/skills/open-pr/SKILL.md | 9 ++++++--- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.agents/skills/open-pr/SKILL.md b/.agents/skills/open-pr/SKILL.md index 0af0aff..e146133 100644 --- a/.agents/skills/open-pr/SKILL.md +++ b/.agents/skills/open-pr/SKILL.md @@ -20,13 +20,16 @@ git branch --show-current git status --short gh pr list --repo "$BASE_REPO" --head "$(git branch --show-current)" \ --state all \ - --json number,state,isDraft,headRefName,headRepositoryOwner,baseRefName,url + --json number,state,isDraft,headRefName,headRepository,baseRefName,url ``` An empty PR list means no PR exists. `--head` filters by branch name alone, so in a fork workflow the list can hold another contributor's PR from a branch of -the same name: treat a result as this checkout's PR only when its -`headRepositoryOwner` is the owner your head remote pushes to. +the same name. Resolve the head push remote's complete `owner/name` repository +identity, then treat a result as this checkout's PR only when its +`headRepository.nameWithOwner` matches exactly. Matching only the owner is +insufficient because an organization can own multiple repositories in one fork +network. Authentication, network, or repository errors must remain visible and stop the workflow before history changes or publication. diff --git a/.ai/shared b/.ai/shared index ff61c10..361abd6 160000 --- a/.ai/shared +++ b/.ai/shared @@ -1 +1 @@ -Subproject commit ff61c10c2578d1145669c103c6fafa1b3e10b186 +Subproject commit 361abd6a913afdcf340008a8b906a1ff89498c85 diff --git a/.claude/skills/open-pr/SKILL.md b/.claude/skills/open-pr/SKILL.md index 0af0aff..e146133 100644 --- a/.claude/skills/open-pr/SKILL.md +++ b/.claude/skills/open-pr/SKILL.md @@ -20,13 +20,16 @@ git branch --show-current git status --short gh pr list --repo "$BASE_REPO" --head "$(git branch --show-current)" \ --state all \ - --json number,state,isDraft,headRefName,headRepositoryOwner,baseRefName,url + --json number,state,isDraft,headRefName,headRepository,baseRefName,url ``` An empty PR list means no PR exists. `--head` filters by branch name alone, so in a fork workflow the list can hold another contributor's PR from a branch of -the same name: treat a result as this checkout's PR only when its -`headRepositoryOwner` is the owner your head remote pushes to. +the same name. Resolve the head push remote's complete `owner/name` repository +identity, then treat a result as this checkout's PR only when its +`headRepository.nameWithOwner` matches exactly. Matching only the owner is +insufficient because an organization can own multiple repositories in one fork +network. Authentication, network, or repository errors must remain visible and stop the workflow before history changes or publication. From d5b7153231d1c5a8c83156ada0c2f6f0e002c34a Mon Sep 17 00:00:00 2001 From: jan-kubica Date: Sun, 20 Sep 2026 20:35:44 +0200 Subject: [PATCH 3/4] fix: sync pull request workflow safeguards --- .agents/skills/open-pr/SKILL.md | 31 ++++++++++++++--------- .agents/skills/rabbit-round/SKILL.md | 34 +++++++++++++++----------- .agents/skills/security-audit/SKILL.md | 14 ++++++++--- .ai/shared | 2 +- .claude/skills/open-pr/SKILL.md | 31 ++++++++++++++--------- .claude/skills/rabbit-round/SKILL.md | 34 +++++++++++++++----------- .claude/skills/security-audit/SKILL.md | 14 ++++++++--- 7 files changed, 101 insertions(+), 59 deletions(-) diff --git a/.agents/skills/open-pr/SKILL.md b/.agents/skills/open-pr/SKILL.md index e146133..d9d4ba8 100644 --- a/.agents/skills/open-pr/SKILL.md +++ b/.agents/skills/open-pr/SKILL.md @@ -16,20 +16,29 @@ repository first (step 3) and query it explicitly; in a fork checkout, `gh` defaults to the fork and would miss an upstream PR: ```bash -git branch --show-current +HEAD_BRANCH="$(git symbolic-ref --quiet --short HEAD)" || { + echo "detached HEAD; cannot identify a pull request branch" >&2 + exit 1 +} +HEAD_SHA="$(git rev-parse --verify HEAD)" git status --short -gh pr list --repo "$BASE_REPO" --head "$(git branch --show-current)" \ - --state all \ - --json number,state,isDraft,headRefName,headRepository,baseRefName,url +: "${BASE_REPO:?set BASE_REPO to the resolved owner/name base repository}" +: "${HEAD_REPO:?set HEAD_REPO to the resolved owner/name head repository}" +PR_CANDIDATES="$(gh pr list --repo "$BASE_REPO" --head "$HEAD_BRANCH" \ + --state open \ + --json number,state,isDraft,headRefName,headRefOid,headRepository,baseRefName,url)" ``` -An empty PR list means no PR exists. `--head` filters by branch name alone, so -in a fork workflow the list can hold another contributor's PR from a branch of -the same name. Resolve the head push remote's complete `owner/name` repository -identity, then treat a result as this checkout's PR only when its -`headRepository.nameWithOwner` matches exactly. Matching only the owner is -insufficient because an organization can own multiple repositories in one fork -network. +Resolve `BASE_REPO` before the query and resolve `HEAD_REPO` from the branch's +configured push remote. Do not guess either identity from an account name. Filter +`PR_CANDIDATES` to entries whose `headRepository.nameWithOwner` equals `HEAD_REPO` +and whose `headRefOid` equals `HEAD_SHA`, then accept exactly one match. An empty +candidate list means no open PR exists. A missing repository identity or head SHA, +more than one exact match, or a detached or otherwise ambiguous local branch must +stop the workflow before any candidate's base is used. A non-empty list with no +exact match belongs to another head and is not this checkout's PR. `--head` filters +by branch name alone, so matching only the owner is insufficient: an organization +can own multiple repositories in one fork network. Authentication, network, or repository errors must remain visible and stop the workflow before history changes or publication. diff --git a/.agents/skills/rabbit-round/SKILL.md b/.agents/skills/rabbit-round/SKILL.md index 5561af6..329ed0d 100644 --- a/.agents/skills/rabbit-round/SKILL.md +++ b/.agents/skills/rabbit-round/SKILL.md @@ -19,12 +19,14 @@ Fetch paginated review threads through GitHub GraphQL so unresolved state and thread replies are preserved. Fetch top-level issue comments separately. Record every participant and reply author in a thread, which comments apply to the current head, and which are stale. Classify participants from a fresh fetch. -Record a receipt for every workflow reply with its returned reply-node ID and -exact content, and retain those receipts across resume or handoff. On later -fetches, exclude only replies matched to an exact receipt; never infer an -exclusion from the requester account or an attribution footer. All remaining -participants must be confirmed allowed bots; a human, mixed, or uncertain thread -follows the human-thread rules. +Record a receipt for every workflow reply and retain those receipts across resume +or handoff. A review-thread reply receipt contains the returned review-comment +node ID and exact content; a top-level reply receipt contains the returned +issue-comment node ID and exact content. On later fetches, exclude a reply only +when its surface, node ID, and content exactly match the corresponding receipt; +never infer an exclusion from the requester account or an attribution footer. All +remaining participants must be confirmed allowed bots; a human, mixed, or +uncertain thread follows the human-thread rules. Do not rely only on the REST review-comments list: it does not represent thread resolution or the complete conversation reliably. @@ -58,9 +60,9 @@ CI-equivalent verification before publication when practical. Commit and push the implementation before saying it is fixed. Push a new branch normally; use `--force-with-lease` only after intentionally rebasing a published -branch. Capture the resulting head SHA. If this round pushes a new head, its -final status is `pending_bots` even when GitHub has not registered checks or -reviewers yet; a newly published head cannot be clean in the same pass. +branch. Capture the resulting head SHA. A newly published head cannot be `clean` +in the same pass, even when GitHub has not registered checks or reviewers yet; +classify it using the Section 5 precedence. ## 4. Reply With Verifiable Evidence @@ -87,18 +89,22 @@ there. Do not minimize bot summaries by default. ## 5. Recheck the Current Head -Refresh the PR after the push and report one status: +Refresh the PR after the push and report one status. Apply this precedence: +`failing_ci` > `needs_changes` > `pending_bots` > `clean`. +- `failing_ci`: a current-head required check is known to have failed, regardless + of pending reviewers, actionable feedback, or a push in this round +- `needs_changes`: no required check is known to have failed, but actionable + automated feedback remains +- `pending_bots`: no required check is known to have failed and no actionable + automated feedback remains, but this round pushed the current head or a + current-head automated review or required check is still running - `clean`: all current-head automated reviewers are terminal, required checks are green, and no actionable automated finding remains in a review thread or top-level comment. A top-level finding answered with a defer reply carrying the follow-up PR's URL is no longer actionable on later rounds, unless it names a verified release-blocking defect: no defer makes one of those non-actionable, and the status stays `needs_changes` until it is fixed on the current head -- `pending_bots`: this round pushed the current head, or a current-head - automated review or required check is still running -- `needs_changes`: actionable automated feedback remains -- `failing_ci`: a current-head required check failed Preserve the PR's explicit draft state. This skill performs one pass; it does not schedule polling, merge, deploy, or bypass protections. diff --git a/.agents/skills/security-audit/SKILL.md b/.agents/skills/security-audit/SKILL.md index adcfff1..4202fd7 100644 --- a/.agents/skills/security-audit/SKILL.md +++ b/.agents/skills/security-audit/SKILL.md @@ -25,10 +25,16 @@ candidate until validation establishes a reachable security failure. validate reachability and check counterevidence before reporting. - Do not claim a surface passed when it was not reviewed. Record exclusions, deferred work, and proof gaps. -- Do not publish unresolved vulnerability details. In a public repository, keep - unresolved findings, exploitation steps, private architecture, and operational - controls out of issues, commits, pull requests, and repository files unless the - user explicitly approves disclosure. +- Do not publish unresolved vulnerability details. Keep unresolved findings, + exploitation steps, private architecture, and operational controls out of public + issues, commits, pull requests, and repository files. Never open a public GitHub + issue for a suspected vulnerability. Returning findings privately to the requester + is not public disclosure. +- When the user explicitly requests an external vulnerability report, follow the + repository's private reporting channel. For Stella repositories, route it to + `security@stellaworkspace.com`. Check the latest published release before + reporting when possible, and include the affected version, impact, reproduction + steps, and known mitigations. Never include personal data or repository secrets. ## Workflow diff --git a/.ai/shared b/.ai/shared index 361abd6..51ba601 160000 --- a/.ai/shared +++ b/.ai/shared @@ -1 +1 @@ -Subproject commit 361abd6a913afdcf340008a8b906a1ff89498c85 +Subproject commit 51ba601136cbfef4381377498e393931877d8cc4 diff --git a/.claude/skills/open-pr/SKILL.md b/.claude/skills/open-pr/SKILL.md index e146133..d9d4ba8 100644 --- a/.claude/skills/open-pr/SKILL.md +++ b/.claude/skills/open-pr/SKILL.md @@ -16,20 +16,29 @@ repository first (step 3) and query it explicitly; in a fork checkout, `gh` defaults to the fork and would miss an upstream PR: ```bash -git branch --show-current +HEAD_BRANCH="$(git symbolic-ref --quiet --short HEAD)" || { + echo "detached HEAD; cannot identify a pull request branch" >&2 + exit 1 +} +HEAD_SHA="$(git rev-parse --verify HEAD)" git status --short -gh pr list --repo "$BASE_REPO" --head "$(git branch --show-current)" \ - --state all \ - --json number,state,isDraft,headRefName,headRepository,baseRefName,url +: "${BASE_REPO:?set BASE_REPO to the resolved owner/name base repository}" +: "${HEAD_REPO:?set HEAD_REPO to the resolved owner/name head repository}" +PR_CANDIDATES="$(gh pr list --repo "$BASE_REPO" --head "$HEAD_BRANCH" \ + --state open \ + --json number,state,isDraft,headRefName,headRefOid,headRepository,baseRefName,url)" ``` -An empty PR list means no PR exists. `--head` filters by branch name alone, so -in a fork workflow the list can hold another contributor's PR from a branch of -the same name. Resolve the head push remote's complete `owner/name` repository -identity, then treat a result as this checkout's PR only when its -`headRepository.nameWithOwner` matches exactly. Matching only the owner is -insufficient because an organization can own multiple repositories in one fork -network. +Resolve `BASE_REPO` before the query and resolve `HEAD_REPO` from the branch's +configured push remote. Do not guess either identity from an account name. Filter +`PR_CANDIDATES` to entries whose `headRepository.nameWithOwner` equals `HEAD_REPO` +and whose `headRefOid` equals `HEAD_SHA`, then accept exactly one match. An empty +candidate list means no open PR exists. A missing repository identity or head SHA, +more than one exact match, or a detached or otherwise ambiguous local branch must +stop the workflow before any candidate's base is used. A non-empty list with no +exact match belongs to another head and is not this checkout's PR. `--head` filters +by branch name alone, so matching only the owner is insufficient: an organization +can own multiple repositories in one fork network. Authentication, network, or repository errors must remain visible and stop the workflow before history changes or publication. diff --git a/.claude/skills/rabbit-round/SKILL.md b/.claude/skills/rabbit-round/SKILL.md index 5561af6..329ed0d 100644 --- a/.claude/skills/rabbit-round/SKILL.md +++ b/.claude/skills/rabbit-round/SKILL.md @@ -19,12 +19,14 @@ Fetch paginated review threads through GitHub GraphQL so unresolved state and thread replies are preserved. Fetch top-level issue comments separately. Record every participant and reply author in a thread, which comments apply to the current head, and which are stale. Classify participants from a fresh fetch. -Record a receipt for every workflow reply with its returned reply-node ID and -exact content, and retain those receipts across resume or handoff. On later -fetches, exclude only replies matched to an exact receipt; never infer an -exclusion from the requester account or an attribution footer. All remaining -participants must be confirmed allowed bots; a human, mixed, or uncertain thread -follows the human-thread rules. +Record a receipt for every workflow reply and retain those receipts across resume +or handoff. A review-thread reply receipt contains the returned review-comment +node ID and exact content; a top-level reply receipt contains the returned +issue-comment node ID and exact content. On later fetches, exclude a reply only +when its surface, node ID, and content exactly match the corresponding receipt; +never infer an exclusion from the requester account or an attribution footer. All +remaining participants must be confirmed allowed bots; a human, mixed, or +uncertain thread follows the human-thread rules. Do not rely only on the REST review-comments list: it does not represent thread resolution or the complete conversation reliably. @@ -58,9 +60,9 @@ CI-equivalent verification before publication when practical. Commit and push the implementation before saying it is fixed. Push a new branch normally; use `--force-with-lease` only after intentionally rebasing a published -branch. Capture the resulting head SHA. If this round pushes a new head, its -final status is `pending_bots` even when GitHub has not registered checks or -reviewers yet; a newly published head cannot be clean in the same pass. +branch. Capture the resulting head SHA. A newly published head cannot be `clean` +in the same pass, even when GitHub has not registered checks or reviewers yet; +classify it using the Section 5 precedence. ## 4. Reply With Verifiable Evidence @@ -87,18 +89,22 @@ there. Do not minimize bot summaries by default. ## 5. Recheck the Current Head -Refresh the PR after the push and report one status: +Refresh the PR after the push and report one status. Apply this precedence: +`failing_ci` > `needs_changes` > `pending_bots` > `clean`. +- `failing_ci`: a current-head required check is known to have failed, regardless + of pending reviewers, actionable feedback, or a push in this round +- `needs_changes`: no required check is known to have failed, but actionable + automated feedback remains +- `pending_bots`: no required check is known to have failed and no actionable + automated feedback remains, but this round pushed the current head or a + current-head automated review or required check is still running - `clean`: all current-head automated reviewers are terminal, required checks are green, and no actionable automated finding remains in a review thread or top-level comment. A top-level finding answered with a defer reply carrying the follow-up PR's URL is no longer actionable on later rounds, unless it names a verified release-blocking defect: no defer makes one of those non-actionable, and the status stays `needs_changes` until it is fixed on the current head -- `pending_bots`: this round pushed the current head, or a current-head - automated review or required check is still running -- `needs_changes`: actionable automated feedback remains -- `failing_ci`: a current-head required check failed Preserve the PR's explicit draft state. This skill performs one pass; it does not schedule polling, merge, deploy, or bypass protections. diff --git a/.claude/skills/security-audit/SKILL.md b/.claude/skills/security-audit/SKILL.md index adcfff1..4202fd7 100644 --- a/.claude/skills/security-audit/SKILL.md +++ b/.claude/skills/security-audit/SKILL.md @@ -25,10 +25,16 @@ candidate until validation establishes a reachable security failure. validate reachability and check counterevidence before reporting. - Do not claim a surface passed when it was not reviewed. Record exclusions, deferred work, and proof gaps. -- Do not publish unresolved vulnerability details. In a public repository, keep - unresolved findings, exploitation steps, private architecture, and operational - controls out of issues, commits, pull requests, and repository files unless the - user explicitly approves disclosure. +- Do not publish unresolved vulnerability details. Keep unresolved findings, + exploitation steps, private architecture, and operational controls out of public + issues, commits, pull requests, and repository files. Never open a public GitHub + issue for a suspected vulnerability. Returning findings privately to the requester + is not public disclosure. +- When the user explicitly requests an external vulnerability report, follow the + repository's private reporting channel. For Stella repositories, route it to + `security@stellaworkspace.com`. Check the latest published release before + reporting when possible, and include the affected version, impact, reproduction + steps, and known mitigations. Never include personal data or repository secrets. ## Workflow From 87d59fddf90e0a1881e33046338edaa8db71d8f0 Mon Sep 17 00:00:00 2001 From: jan-kubica Date: Sun, 20 Sep 2026 20:41:53 +0200 Subject: [PATCH 4/4] fix: pin pull request workflow identity --- .agents/skills/finish-pr/SKILL.md | 15 +++++++--- .agents/skills/open-pr/SKILL.md | 44 ++++++++++++++++++++-------- .agents/skills/rabbit-round/SKILL.md | 7 +++++ .ai/shared | 2 +- .claude/skills/finish-pr/SKILL.md | 15 +++++++--- .claude/skills/open-pr/SKILL.md | 44 ++++++++++++++++++++-------- .claude/skills/rabbit-round/SKILL.md | 7 +++++ 7 files changed, 101 insertions(+), 33 deletions(-) diff --git a/.agents/skills/finish-pr/SKILL.md b/.agents/skills/finish-pr/SKILL.md index d84cd79..2fa97db 100644 --- a/.agents/skills/finish-pr/SKILL.md +++ b/.agents/skills/finish-pr/SKILL.md @@ -15,6 +15,11 @@ Resolve the PR, repository, branch, current head SHA, base branch, draft state, required checks, review state, and applicable repository instructions. Work from a clean isolated checkout of the PR head. Preserve unrelated changes. +Pin every GitHub query and mutation to the resolved full `owner/name` repository +and PR number. Require every refreshed PR to retain that identity, and evaluate +checks, reviews, replies, resolutions, and merge eligibility only for its exact +current head SHA. Stop and restart the evaluation when the head changes. + Rebase or restack according to repository policy before trusting results. Review conflict resolutions and push safely. Preserve an explicit draft state unless the user asks to mark the PR ready. @@ -46,10 +51,12 @@ Never request an automated review (`@coderabbitai review`, `@codex review`, or a timed re-request after a rate limit); reviews arrive on their own. Budget the loop: at most two review rounds after the first green head. When actionable findings keep arriving past that, keep the green head and triage the remaining findings yourself -with a concrete accept, push-back, or defer. Open one stacked follow-up PR for the -accepted fixes, then reply to each deferred finding with that PR's URL, and resolve -the thread where the finding has one: the named follow-up is the disposition, not a -promise. A deferred top-level comment stays open; the reply is its terminal state. +with a concrete accept, push-back, or defer. Open one named follow-up PR for the +accepted fixes; make it stacked only when repository policy or the existing branch +stack requires that. Then reply to each deferred finding with that PR's URL, and +resolve the thread where the finding has one: the named follow-up is the +disposition, not a promise. A deferred top-level comment stays open; the reply is +its terminal state. The budget never defers a release-blocking defect. A finding that names a security, authorization, data-loss, or data-corruption defect, and survives diff --git a/.agents/skills/open-pr/SKILL.md b/.agents/skills/open-pr/SKILL.md index d9d4ba8..ab286df 100644 --- a/.agents/skills/open-pr/SKILL.md +++ b/.agents/skills/open-pr/SKILL.md @@ -32,13 +32,14 @@ PR_CANDIDATES="$(gh pr list --repo "$BASE_REPO" --head "$HEAD_BRANCH" \ Resolve `BASE_REPO` before the query and resolve `HEAD_REPO` from the branch's configured push remote. Do not guess either identity from an account name. Filter `PR_CANDIDATES` to entries whose `headRepository.nameWithOwner` equals `HEAD_REPO` -and whose `headRefOid` equals `HEAD_SHA`, then accept exactly one match. An empty -candidate list means no open PR exists. A missing repository identity or head SHA, -more than one exact match, or a detached or otherwise ambiguous local branch must -stop the workflow before any candidate's base is used. A non-empty list with no -exact match belongs to another head and is not this checkout's PR. `--head` filters -by branch name alone, so matching only the owner is insufficient: an organization -can own multiple repositories in one fork network. +and whose `headRefOid` equals `HEAD_SHA`, then accept exactly one match and assign +its `number` to `PR_NUMBER`. An empty candidate list means no open PR exists. A +missing repository identity or head SHA, more than one exact match, or a detached +or otherwise ambiguous local branch must stop the workflow before any candidate's +base is used. A non-empty list with no exact match belongs to another head and is +not this checkout's PR. `--head` filters by branch name alone, so matching only the +owner is insufficient: an organization can own multiple repositories in one fork +network. Authentication, network, or repository errors must remain visible and stop the workflow before history changes or publication. @@ -71,11 +72,19 @@ If it is installed and `gh stack view` identifies a stack, use absent or the branch is not stacked, use ordinary Git; do not install an optional extension merely to prepare a normal PR. -For an existing PR, read its `baseRefName` and base repository with `gh pr -view`. Match that repository to a configured Git remote, fetch the PR base from -that remote, and rebase onto the fetched base. If no configured remote matches, -fetch the base repository URL directly and rebase onto `FETCH_HEAD`; do not add -or rewrite remotes silently. +For an existing PR, read its metadata from the exact match rather than the +checkout's implicit repository context: + +```bash +PR_METADATA="$(gh pr view "$PR_NUMBER" --repo "$BASE_REPO" --json number,baseRefName,headRefOid,headRepository,url)" +``` + +Treat the explicit `BASE_REPO` as the base repository and reject a response whose +PR number, head repository, or head SHA no longer matches the identity established +in step 1. Match `BASE_REPO` to a configured Git remote, fetch the PR base from that +remote, and rebase onto the fetched base. If no configured remote matches, fetch +the base repository URL directly and rebase onto `FETCH_HEAD`; do not add or +rewrite remotes silently. For a branch without a PR, prefer its configured upstream remote and that remote's default branch. Fall back to `origin` only when no upstream is @@ -129,6 +138,11 @@ normally. Use `--force-with-lease`, never plain force, only after intentionally rebasing a published branch. For a stack, submit every layer and verify each PR targets its parent. +Refresh `HEAD_SHA` from the final local commit immediately before pushing. Push +the explicit local branch to its resolved `HEAD_REPO` destination, then verify the +remote branch resolves to that SHA; do not let an implicit push target select the +repository or branch. + ## 8. Open or Update Review State - An explicit draft request creates or preserves a draft. @@ -140,6 +154,12 @@ Write a concise title and body describing only the visible implementation. Follow repository rules for attribution and public context. Do not add a test plan unless requested. +For an existing PR, update only `PR_NUMBER` in `BASE_REPO`; never rely on the +checkout's implicit repository or branch selection. For a new PR, pass the resolved +base repository, base branch, and head repository and branch explicitly. After any +create or update, refetch that exact PR and require its repository identity and +`headRefOid` to equal `BASE_REPO` and the pushed `HEAD_SHA` before reporting it. + Report the URL, readiness, checks run or skipped, and any blocker. Do not begin bot monitoring, merge, or deployment unless the user requested that broader workflow. diff --git a/.agents/skills/rabbit-round/SKILL.md b/.agents/skills/rabbit-round/SKILL.md index 329ed0d..8a02a51 100644 --- a/.agents/skills/rabbit-round/SKILL.md +++ b/.agents/skills/rabbit-round/SKILL.md @@ -15,6 +15,13 @@ Resolve the repository, PR, current head SHA, requester identity, draft state, and applicable comment-attribution rules. Fail visibly if the PR cannot be identified. +Pin every GitHub query and mutation to the resolved full `owner/name` repository +and PR number; never rely on the checkout's implicit repository or branch. Require +each fetched PR's repository identity, number, and `headRefOid` to match the +captured identity. Before replying to or resolving feedback, refetch that exact PR +and stop if its head changed; results from one head never authorize a mutation on +another. + Fetch paginated review threads through GitHub GraphQL so unresolved state and thread replies are preserved. Fetch top-level issue comments separately. Record every participant and reply author in a thread, which comments apply to the diff --git a/.ai/shared b/.ai/shared index 51ba601..f070572 160000 --- a/.ai/shared +++ b/.ai/shared @@ -1 +1 @@ -Subproject commit 51ba601136cbfef4381377498e393931877d8cc4 +Subproject commit f07057218876be4ea69848218f488243db1e18b9 diff --git a/.claude/skills/finish-pr/SKILL.md b/.claude/skills/finish-pr/SKILL.md index d84cd79..2fa97db 100644 --- a/.claude/skills/finish-pr/SKILL.md +++ b/.claude/skills/finish-pr/SKILL.md @@ -15,6 +15,11 @@ Resolve the PR, repository, branch, current head SHA, base branch, draft state, required checks, review state, and applicable repository instructions. Work from a clean isolated checkout of the PR head. Preserve unrelated changes. +Pin every GitHub query and mutation to the resolved full `owner/name` repository +and PR number. Require every refreshed PR to retain that identity, and evaluate +checks, reviews, replies, resolutions, and merge eligibility only for its exact +current head SHA. Stop and restart the evaluation when the head changes. + Rebase or restack according to repository policy before trusting results. Review conflict resolutions and push safely. Preserve an explicit draft state unless the user asks to mark the PR ready. @@ -46,10 +51,12 @@ Never request an automated review (`@coderabbitai review`, `@codex review`, or a timed re-request after a rate limit); reviews arrive on their own. Budget the loop: at most two review rounds after the first green head. When actionable findings keep arriving past that, keep the green head and triage the remaining findings yourself -with a concrete accept, push-back, or defer. Open one stacked follow-up PR for the -accepted fixes, then reply to each deferred finding with that PR's URL, and resolve -the thread where the finding has one: the named follow-up is the disposition, not a -promise. A deferred top-level comment stays open; the reply is its terminal state. +with a concrete accept, push-back, or defer. Open one named follow-up PR for the +accepted fixes; make it stacked only when repository policy or the existing branch +stack requires that. Then reply to each deferred finding with that PR's URL, and +resolve the thread where the finding has one: the named follow-up is the +disposition, not a promise. A deferred top-level comment stays open; the reply is +its terminal state. The budget never defers a release-blocking defect. A finding that names a security, authorization, data-loss, or data-corruption defect, and survives diff --git a/.claude/skills/open-pr/SKILL.md b/.claude/skills/open-pr/SKILL.md index d9d4ba8..ab286df 100644 --- a/.claude/skills/open-pr/SKILL.md +++ b/.claude/skills/open-pr/SKILL.md @@ -32,13 +32,14 @@ PR_CANDIDATES="$(gh pr list --repo "$BASE_REPO" --head "$HEAD_BRANCH" \ Resolve `BASE_REPO` before the query and resolve `HEAD_REPO` from the branch's configured push remote. Do not guess either identity from an account name. Filter `PR_CANDIDATES` to entries whose `headRepository.nameWithOwner` equals `HEAD_REPO` -and whose `headRefOid` equals `HEAD_SHA`, then accept exactly one match. An empty -candidate list means no open PR exists. A missing repository identity or head SHA, -more than one exact match, or a detached or otherwise ambiguous local branch must -stop the workflow before any candidate's base is used. A non-empty list with no -exact match belongs to another head and is not this checkout's PR. `--head` filters -by branch name alone, so matching only the owner is insufficient: an organization -can own multiple repositories in one fork network. +and whose `headRefOid` equals `HEAD_SHA`, then accept exactly one match and assign +its `number` to `PR_NUMBER`. An empty candidate list means no open PR exists. A +missing repository identity or head SHA, more than one exact match, or a detached +or otherwise ambiguous local branch must stop the workflow before any candidate's +base is used. A non-empty list with no exact match belongs to another head and is +not this checkout's PR. `--head` filters by branch name alone, so matching only the +owner is insufficient: an organization can own multiple repositories in one fork +network. Authentication, network, or repository errors must remain visible and stop the workflow before history changes or publication. @@ -71,11 +72,19 @@ If it is installed and `gh stack view` identifies a stack, use absent or the branch is not stacked, use ordinary Git; do not install an optional extension merely to prepare a normal PR. -For an existing PR, read its `baseRefName` and base repository with `gh pr -view`. Match that repository to a configured Git remote, fetch the PR base from -that remote, and rebase onto the fetched base. If no configured remote matches, -fetch the base repository URL directly and rebase onto `FETCH_HEAD`; do not add -or rewrite remotes silently. +For an existing PR, read its metadata from the exact match rather than the +checkout's implicit repository context: + +```bash +PR_METADATA="$(gh pr view "$PR_NUMBER" --repo "$BASE_REPO" --json number,baseRefName,headRefOid,headRepository,url)" +``` + +Treat the explicit `BASE_REPO` as the base repository and reject a response whose +PR number, head repository, or head SHA no longer matches the identity established +in step 1. Match `BASE_REPO` to a configured Git remote, fetch the PR base from that +remote, and rebase onto the fetched base. If no configured remote matches, fetch +the base repository URL directly and rebase onto `FETCH_HEAD`; do not add or +rewrite remotes silently. For a branch without a PR, prefer its configured upstream remote and that remote's default branch. Fall back to `origin` only when no upstream is @@ -129,6 +138,11 @@ normally. Use `--force-with-lease`, never plain force, only after intentionally rebasing a published branch. For a stack, submit every layer and verify each PR targets its parent. +Refresh `HEAD_SHA` from the final local commit immediately before pushing. Push +the explicit local branch to its resolved `HEAD_REPO` destination, then verify the +remote branch resolves to that SHA; do not let an implicit push target select the +repository or branch. + ## 8. Open or Update Review State - An explicit draft request creates or preserves a draft. @@ -140,6 +154,12 @@ Write a concise title and body describing only the visible implementation. Follow repository rules for attribution and public context. Do not add a test plan unless requested. +For an existing PR, update only `PR_NUMBER` in `BASE_REPO`; never rely on the +checkout's implicit repository or branch selection. For a new PR, pass the resolved +base repository, base branch, and head repository and branch explicitly. After any +create or update, refetch that exact PR and require its repository identity and +`headRefOid` to equal `BASE_REPO` and the pushed `HEAD_SHA` before reporting it. + Report the URL, readiness, checks run or skipped, and any blocker. Do not begin bot monitoring, merge, or deployment unless the user requested that broader workflow. diff --git a/.claude/skills/rabbit-round/SKILL.md b/.claude/skills/rabbit-round/SKILL.md index 329ed0d..8a02a51 100644 --- a/.claude/skills/rabbit-round/SKILL.md +++ b/.claude/skills/rabbit-round/SKILL.md @@ -15,6 +15,13 @@ Resolve the repository, PR, current head SHA, requester identity, draft state, and applicable comment-attribution rules. Fail visibly if the PR cannot be identified. +Pin every GitHub query and mutation to the resolved full `owner/name` repository +and PR number; never rely on the checkout's implicit repository or branch. Require +each fetched PR's repository identity, number, and `headRefOid` to match the +captured identity. Before replying to or resolving feedback, refetch that exact PR +and stop if its head changed; results from one head never authorize a mutation on +another. + Fetch paginated review threads through GitHub GraphQL so unresolved state and thread replies are preserved. Fetch top-level issue comments separately. Record every participant and reply author in a thread, which comments apply to the