From c1ea35be350acf01b7d660bb9e92b97c3b869039 Mon Sep 17 00:00:00 2001 From: rombirli Date: Fri, 9 Oct 2026 13:27:29 +0200 Subject: [PATCH 01/11] Add mandatory delivery workflow to new-rule skill --- .claude/skills/new-rule/SKILL.md | 106 ++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) diff --git a/.claude/skills/new-rule/SKILL.md b/.claude/skills/new-rule/SKILL.md index 6334afdf79f..543fb438e80 100644 --- a/.claude/skills/new-rule/SKILL.md +++ b/.claude/skills/new-rule/SKILL.md @@ -1,12 +1,116 @@ --- name: new-rule -description: Guidelines for implementing new rules in sonar-java +description: >- + Implement Java analyzer rules using the mandatory delivery workflow: + metadata → failing reproducer → implementation → PR babysit → TP/FP > 90%. + Use when creating or shipping any new/changed rule. --- # New Rule Implementation Skill This skill provides sonar-java-specific guidelines for implementing new rules. +**Non-negotiable:** every rule PR must follow the [Mandatory delivery workflow](#mandatory-delivery-workflow). Do not open or merge a PR that is not fully green, has open review-bot comments, messy history, or a TP ratio of 90% or below. + +## Mandatory delivery workflow + +Execute **in order**. Never skip. Never combine steps into one commit. If precision fails, loop again. + +### Step 0 — Pre-flight (once, before any rule work) + +**Run once** before Step 1. When implementing multiple rules in parallel (subagents), collect answers **here only**, then start each rule with the answers already known — do not re-ask per rule. + +Verify / ask as needed: + +- [ ] Access to the RSpec repository and a local `rule-api` jar (see [Metadata Files](#metadata-files)) +- [ ] Rule id(s) to implement +- [ ] RSpec status per rule: merged on `master` **or** branch name — look up branch/PR **only** in https://github.com/SonarSource/rspec (never in this repo) +- [ ] RSpec Jira ticket → its **epic parent** key (from the RSpec PR title/branch) — used in the PR body as `Part of ` (Step 4); **not** reused as the implementation ticket +- [ ] Rule title (for the PR title format in Step 4) +- [ ] Any path/env overrides needed + +Stop and resolve gaps before Step 1. + +### Commit rules (all steps) + +- **Exactly one commit per step** below (after history cleanup: still one commit per step — restructure/rebase, do **not** squash everything into a single commit). +- Message: single line, few words, clear. **No rule id** (it is in the branch). **No "java"** (it is the repo). **No Jira key** (never in commits). +- Examples: `Add rule metadata`, `Add failing reproducer`, `Implement rule`, `Fix review comments`, `Update ruling expectations` + +### Step 1 — Metadata → commit + +1. Generate metadata with `rule-api` (see [Metadata Files](#metadata-files)). Never hand-write `.html`/`.json`. +2. Stage **only** rule-relevant files: `SXXXX.html`, `SXXXX.json`, and the profile files under `sonar-java-plugin/src/main/resources/profiles/`. +3. Commit. + +### Step 2 — Failing reproducer → commit + +1. Write the test sample (`java-checks-test-sources/default/src/main/java/checks/{RuleId}CheckSample.java`) covering **every** edge case, noncompliant and compliant, with precise location markers (see the test conventions in `AGENTS.md`). +2. Write `{RuleId}CheckTest.java`, with both a semantic and a `withoutSemantic()` test. +3. Add an **empty** check class (`@Rule(key = "SXXXX")`, minimal `nodesToVisit`/`visitNode`). +4. Run the test — it **must fail**. +5. Commit (reproducer + empty check + test). + +### Step 3 — Implementation → commit + +1. Implement the check until the unit test **passes**. +2. Commit. + +### Step 4 — Push & PR + +Push the branch, named with your GitHub username convention (e.g. `/short-kebab-description`), and open the PR. + +**Separate Jira tickets (mandatory):** RSpec and rule implementation use **different** SONARJAVA tickets. Do **not** put a Jira key in the initial PR title or in commits. + +1. Open the analyzer PR with title exactly: `Implement rule SXXXX - ` (rule id and title from RSpec; no `SONARJAVA-…` prefix). +2. In the **PR body**, include a line `Part of `, the **epic parent** of the RSpec ticket. Jira automation creates the implementation ticket in that epic. +3. After the ticket exists: change its issue type from the automation default (**Maintenance**) to **Feature** (new rules are features). Do not invent an epic or ticket key. + +Do not stop here. + +### Step 5 — Babysit until merge-ready + +Loop until **all** are true: + +- Every CI check is green +- **No** unresolved or open review-bot comments (address or validly refute each one) +- No merge blockers + +While babysitting: + +- **Rebase:** automatically rebase on `master` and fix conflicts (rebase, not merge). +- **Comments:** only unresolved threads; read body and location only, not huge JSON dumps. Treat Bugbot carefully — fix only if valid; explain when you disagree. + +If **ruling** fails (likely for a new rule), a PR updating the expectations is created automatically on CI failure. Review it, merge it into the branch if the new findings are legitimate, otherwise fix the implementation and the unit tests. Push and re-babysit. + +### Step 6 — TP/FP analysis (only when Step 5 is fully green) + +1. Collect the **new** findings from the ruling expectation diff: `its/ruling/src/test/resources/expected//java-.json`, where each entry is `"group:artifact:path/to/File.java": [line, …]`. Sources are under `its/sources//`. +2. **Sample** new findings before classifying (do not analyze unbounded volumes): + - Let `N` be the sample size (default **100**). + - If total new findings ≤ `N` → analyze **all**. + - Else sample up to `N` across projects (water-filling / largest-remainder): + 1. Give every project with findings its fair equal share of the remaining budget among projects that still have unsampled issues. + 2. Projects with fewer findings than their share contribute **all** of theirs; leftover budget is redistributed evenly to projects that still have unsampled issues. + 3. Repeat until the budget is exhausted. + - Examples: A=1000, B=10_000_000 → about 50 each of 100; A=1, B=100, C=3 → take 1/96/3. +3. For **every** sampled finding: classify **TP** (correct) or **FP** (incorrect). +4. Compute `TP% = 100 * TPs / (TPs + FPs)` on the sample. +5. **Must be > 90%.** If 90% or below: extend the reproducer with cases for those FPs, then **restart from Step 1** and repeat the full workflow (new commits per step; clean history again at the end). + +### Step 7 — Clean history + +Before considering the PR done: history must show **exactly one commit per workflow step** (metadata, reproducer, implementation, plus any later babysit/ruling/precision fix steps still as clear step commits). Rewrite history as needed (`rebase`, not merge), force-push only if appropriate for the branch, then confirm Steps 5 and 6 still hold. + +### Definition of done + +The PR is done **only** when: + +1. History is clean (one commit per step) +2. All checks are green +3. No open review-bot comments +4. TP/FP analysis is done and **TP% > 90%** + ## What to Do ### Metadata Files From d2231d4ff59b3b2656865fa08923f51af342cdfa Mon Sep 17 00:00:00 2001 From: rombirli Date: Fri, 9 Oct 2026 14:33:03 +0200 Subject: [PATCH 02/11] Add cursor instructions --- .cursor/rules/rules-implementation.mdc | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .cursor/rules/rules-implementation.mdc diff --git a/.cursor/rules/rules-implementation.mdc b/.cursor/rules/rules-implementation.mdc new file mode 100644 index 00000000000..cc18755097b --- /dev/null +++ b/.cursor/rules/rules-implementation.mdc @@ -0,0 +1,12 @@ +--- +description: Guide for implementing Java analyzer rules (checks). Delegates to the rule-implementation Claude skill — the single source of truth. +globs: **/*checks*/**/*.java +alwaysApply: false +--- +# Rule implementation + +Do **not** follow outdated guidance that may appear elsewhere. The single source of truth is: + +`.claude/skills/new-rule/SKILL.md` + +Read that skill and follow it whenever implementing, placing, or wiring a new or existing Java check. From 26f90d18d57986562a587fffa1c554b73fd050d8 Mon Sep 17 00:00:00 2001 From: rombirli Date: Fri, 9 Oct 2026 14:33:46 +0200 Subject: [PATCH 03/11] Address gitar's comment: Non-negotiable rule forbids opening the PR that Step 4 requires --- .claude/skills/new-rule/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.claude/skills/new-rule/SKILL.md b/.claude/skills/new-rule/SKILL.md index 543fb438e80..69e83a41c09 100644 --- a/.claude/skills/new-rule/SKILL.md +++ b/.claude/skills/new-rule/SKILL.md @@ -10,7 +10,7 @@ description: >- This skill provides sonar-java-specific guidelines for implementing new rules. -**Non-negotiable:** every rule PR must follow the [Mandatory delivery workflow](#mandatory-delivery-workflow). Do not open or merge a PR that is not fully green, has open review-bot comments, messy history, or a TP ratio of 90% or below. +**Non-negotiable:** every rule PR must follow the [Mandatory delivery workflow](#mandatory-delivery-workflow). Do not end before a PR that is not fully green, has open review-bot comments, messy history, or a TP ratio of 90% or below. ## Mandatory delivery workflow From a253ea062f5ec9d936cf9d422407bded476faf0b Mon Sep 17 00:00:00 2001 From: rombirli Date: Fri, 9 Oct 2026 14:35:39 +0200 Subject: [PATCH 04/11] Address Romain's comment: do not reask per rule --- .claude/skills/new-rule/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.claude/skills/new-rule/SKILL.md b/.claude/skills/new-rule/SKILL.md index 69e83a41c09..31caa1a600e 100644 --- a/.claude/skills/new-rule/SKILL.md +++ b/.claude/skills/new-rule/SKILL.md @@ -18,7 +18,7 @@ Execute **in order**. Never skip. Never combine steps into one commit. If precis ### Step 0 — Pre-flight (once, before any rule work) -**Run once** before Step 1. When implementing multiple rules in parallel (subagents), collect answers **here only**, then start each rule with the answers already known — do not re-ask per rule. +**Run once** before Step 1. When implementing multiple rules in parallel (subagents), collect answers **here only** once at the beginning, then start each rule with the answers already known — do not re-ask later. Verify / ask as needed: From 27c2fcd54f94c5e17e4a5c216a39c44ce3375ec0 Mon Sep 17 00:00:00 2001 From: rombirli Date: Fri, 9 Oct 2026 14:36:46 +0200 Subject: [PATCH 05/11] Address Romain's comment: bugbot -> gitar --- .claude/skills/new-rule/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.claude/skills/new-rule/SKILL.md b/.claude/skills/new-rule/SKILL.md index 31caa1a600e..ff2f5c335a7 100644 --- a/.claude/skills/new-rule/SKILL.md +++ b/.claude/skills/new-rule/SKILL.md @@ -79,7 +79,7 @@ Loop until **all** are true: While babysitting: - **Rebase:** automatically rebase on `master` and fix conflicts (rebase, not merge). -- **Comments:** only unresolved threads; read body and location only, not huge JSON dumps. Treat Bugbot carefully — fix only if valid; explain when you disagree. +- **Comments:** only unresolved threads; read body and location only, not huge JSON dumps. Treat gitar review bot carefully — fix only if valid; explain when you disagree. If **ruling** fails (likely for a new rule), a PR updating the expectations is created automatically on CI failure. Review it, merge it into the branch if the new findings are legitimate, otherwise fix the implementation and the unit tests. Push and re-babysit. From 55dc5c7d3508934c2fd9de6fde039fe7fb4051b2 Mon Sep 17 00:00:00 2001 From: Gitar Date: Fri, 9 Oct 2026 12:38:08 +0000 Subject: [PATCH 06/11] fix: clarify ruling sample allocation Co-authored-by: rombirli <56340680+rombirli@users.noreply.github.com> --- .claude/skills/new-rule/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.claude/skills/new-rule/SKILL.md b/.claude/skills/new-rule/SKILL.md index ff2f5c335a7..06e5e514ebe 100644 --- a/.claude/skills/new-rule/SKILL.md +++ b/.claude/skills/new-rule/SKILL.md @@ -89,7 +89,7 @@ If **ruling** fails (likely for a new rule), a PR updating the expectations is c 2. **Sample** new findings before classifying (do not analyze unbounded volumes): - Let `N` be the sample size (default **100**). - If total new findings ≤ `N` → analyze **all**. - - Else sample up to `N` across projects (water-filling / largest-remainder): + - Else sample up to `N` across projects (water-filling, i.e. equal shares capped by each project's count): 1. Give every project with findings its fair equal share of the remaining budget among projects that still have unsampled issues. 2. Projects with fewer findings than their share contribute **all** of theirs; leftover budget is redistributed evenly to projects that still have unsampled issues. 3. Repeat until the budget is exhausted. From b5fd95e790e0a74ed4e3d568e1009cd30c2698a6 Mon Sep 17 00:00:00 2001 From: Gitar Date: Fri, 9 Oct 2026 12:46:01 +0000 Subject: [PATCH 07/11] docs: remove ruling Tests section redundant with babysit step Co-authored-by: rombirli <56340680+rombirli@users.noreply.github.com> --- .claude/skills/new-rule/SKILL.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.claude/skills/new-rule/SKILL.md b/.claude/skills/new-rule/SKILL.md index 06e5e514ebe..3f0b9ae13db 100644 --- a/.claude/skills/new-rule/SKILL.md +++ b/.claude/skills/new-rule/SKILL.md @@ -124,10 +124,6 @@ The PR is done **only** when: - `sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S{RULE_ID}.json` - `sonar-java-plugin/src/main/resources/profiles/{Sonar_way|Sonar_agentic_AI}/S{RULE_ID}` -### Tests -Ruling test expectation files, located in `its/ruling/src/test/resources`, are updated by merging an automatically -generated PR into the branch when CI checks fail. - ### MethodMatchers Use `MethodMatchers` to match method calls by type, name, and signature: From d120e3ec1520b92eab907e4b9a1cdb16a288b6c6 Mon Sep 17 00:00:00 2001 From: rombirli Date: Fri, 9 Oct 2026 15:05:47 +0200 Subject: [PATCH 08/11] Reuse rspec jira ticket in PR title --- .claude/skills/new-rule/SKILL.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.claude/skills/new-rule/SKILL.md b/.claude/skills/new-rule/SKILL.md index 3f0b9ae13db..470020d53e2 100644 --- a/.claude/skills/new-rule/SKILL.md +++ b/.claude/skills/new-rule/SKILL.md @@ -25,7 +25,7 @@ Verify / ask as needed: - [ ] Access to the RSpec repository and a local `rule-api` jar (see [Metadata Files](#metadata-files)) - [ ] Rule id(s) to implement - [ ] RSpec status per rule: merged on `master` **or** branch name — look up branch/PR **only** in https://github.com/SonarSource/rspec (never in this repo) -- [ ] RSpec Jira ticket → its **epic parent** key (from the RSpec PR title/branch) — used in the PR body as `Part of ` (Step 4); **not** reused as the implementation ticket +- [ ] RSpec Jira ticket key (from the RSpec PR title/branch) — reused as the implementation ticket and as the PR title prefix (Step 4) - [ ] Rule title (for the PR title format in Step 4) - [ ] Any path/env overrides needed @@ -60,11 +60,10 @@ Stop and resolve gaps before Step 1. Push the branch, named with your GitHub username convention (e.g. `/short-kebab-description`), and open the PR. -**Separate Jira tickets (mandatory):** RSpec and rule implementation use **different** SONARJAVA tickets. Do **not** put a Jira key in the initial PR title or in commits. +**Single Jira ticket (mandatory):** the RSpec and the rule implementation share the **same** SONARJAVA ticket. Do **not** create a second ticket, and do **not** put the Jira key in commits. -1. Open the analyzer PR with title exactly: `Implement rule SXXXX - ` (rule id and title from RSpec; no `SONARJAVA-…` prefix). -2. In the **PR body**, include a line `Part of `, the **epic parent** of the RSpec ticket. Jira automation creates the implementation ticket in that epic. -3. After the ticket exists: change its issue type from the automation default (**Maintenance**) to **Feature** (new rules are features). Do not invent an epic or ticket key. +1. Open the analyzer PR with title exactly: `SONARJAVA-XXXX Implement rule SXXXX - `, reusing the RSpec ticket key as prefix (rule id and title from RSpec). +2. Do not invent a ticket key — take it from the RSpec PR or branch. Do not stop here. From a06cb9667d6247b94d8c92e454d6ceea0fe116b9 Mon Sep 17 00:00:00 2001 From: rombirli Date: Mon, 12 Oct 2026 08:06:20 +0200 Subject: [PATCH 09/11] Clarify false positive retry loop --- .claude/skills/new-rule/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.claude/skills/new-rule/SKILL.md b/.claude/skills/new-rule/SKILL.md index 470020d53e2..2d0071441f3 100644 --- a/.claude/skills/new-rule/SKILL.md +++ b/.claude/skills/new-rule/SKILL.md @@ -95,7 +95,7 @@ If **ruling** fails (likely for a new rule), a PR updating the expectations is c - Examples: A=1000, B=10_000_000 → about 50 each of 100; A=1, B=100, C=3 → take 1/96/3. 3. For **every** sampled finding: classify **TP** (correct) or **FP** (incorrect). 4. Compute `TP% = 100 * TPs / (TPs + FPs)` on the sample. -5. **Must be > 90%.** If 90% or below: extend the reproducer with cases for those FPs, then **restart from Step 1** and repeat the full workflow (new commits per step; clean history again at the end). +5. **Must be > 90%.** If 90% or below: add reproducers for the FPs as new test cases and confirm the test fails (Step 2 commit), then introduce heuristics in the implementation to reduce the noise until it passes (Step 3 commit), and re-run Steps 5 and 6. Do not restart from Step 1: metadata and the check class already exist. ### Step 7 — Clean history From 23e6c2ebea7f44b765375dfd33584b6c7d39e12d Mon Sep 17 00:00:00 2001 From: rombirli Date: Mon, 12 Oct 2026 08:10:42 +0200 Subject: [PATCH 10/11] Move agent skills to .agents and symlink .claude --- {.claude => .agents}/commands/migrate-test-samples.md | 0 {.claude => .agents}/commands/project-level-rules.md | 0 {.claude => .agents}/commands/quickfix-tests.md | 0 {.claude => .agents}/skills/new-rule/SKILL.md | 0 .claude | 1 + .cursor/rules/rules-implementation.mdc | 2 +- AGENTS.md | 2 +- 7 files changed, 3 insertions(+), 2 deletions(-) rename {.claude => .agents}/commands/migrate-test-samples.md (100%) rename {.claude => .agents}/commands/project-level-rules.md (100%) rename {.claude => .agents}/commands/quickfix-tests.md (100%) rename {.claude => .agents}/skills/new-rule/SKILL.md (100%) create mode 120000 .claude diff --git a/.claude/commands/migrate-test-samples.md b/.agents/commands/migrate-test-samples.md similarity index 100% rename from .claude/commands/migrate-test-samples.md rename to .agents/commands/migrate-test-samples.md diff --git a/.claude/commands/project-level-rules.md b/.agents/commands/project-level-rules.md similarity index 100% rename from .claude/commands/project-level-rules.md rename to .agents/commands/project-level-rules.md diff --git a/.claude/commands/quickfix-tests.md b/.agents/commands/quickfix-tests.md similarity index 100% rename from .claude/commands/quickfix-tests.md rename to .agents/commands/quickfix-tests.md diff --git a/.claude/skills/new-rule/SKILL.md b/.agents/skills/new-rule/SKILL.md similarity index 100% rename from .claude/skills/new-rule/SKILL.md rename to .agents/skills/new-rule/SKILL.md diff --git a/.claude b/.claude new file mode 120000 index 00000000000..c0ca4685663 --- /dev/null +++ b/.claude @@ -0,0 +1 @@ +.agents \ No newline at end of file diff --git a/.cursor/rules/rules-implementation.mdc b/.cursor/rules/rules-implementation.mdc index cc18755097b..c88de105b38 100644 --- a/.cursor/rules/rules-implementation.mdc +++ b/.cursor/rules/rules-implementation.mdc @@ -7,6 +7,6 @@ alwaysApply: false Do **not** follow outdated guidance that may appear elsewhere. The single source of truth is: -`.claude/skills/new-rule/SKILL.md` +`.agents/skills/new-rule/SKILL.md` Read that skill and follow it whenever implementing, placing, or wiring a new or existing Java check. diff --git a/AGENTS.md b/AGENTS.md index 27916a372ab..bf2706f26eb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,7 @@ SonarJava is a static code analyzer for Java. The rules are implemented in **Java** using the SonarJava API. Test resources are **Java** files annotated with special comment markers. -For step-by-step guidance on implementing a new rule (including metadata generation), see `.claude/skills/new-rule/SKILL.md`. +For step-by-step guidance on implementing a new rule (including metadata generation), see `.agents/skills/new-rule/SKILL.md`. --- From 475334e1eb94e3afbd0a6f9a6e0e872de637467c Mon Sep 17 00:00:00 2001 From: rombirli Date: Mon, 12 Oct 2026 08:10:42 +0200 Subject: [PATCH 11/11] Clarify commit history rules --- .agents/skills/new-rule/SKILL.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.agents/skills/new-rule/SKILL.md b/.agents/skills/new-rule/SKILL.md index 2d0071441f3..6ed42e4a115 100644 --- a/.agents/skills/new-rule/SKILL.md +++ b/.agents/skills/new-rule/SKILL.md @@ -33,7 +33,8 @@ Stop and resolve gaps before Step 1. ### Commit rules (all steps) -- **Exactly one commit per step** below (after history cleanup: still one commit per step — restructure/rebase, do **not** squash everything into a single commit). +- Aim for **one commit per step** below. While working, extra commits are fine: fixes, retries and ruling updates will be folded back into one commit per step during [Step 7](#step-7--clean-history). The rule is an invariant on the **final** history, not a constraint on how you work. +- Never squash everything into a single commit either: the final history keeps one commit per step. - Message: single line, few words, clear. **No rule id** (it is in the branch). **No "java"** (it is the repo). **No Jira key** (never in commits). - Examples: `Add rule metadata`, `Add failing reproducer`, `Implement rule`, `Fix review comments`, `Update ruling expectations` @@ -80,7 +81,7 @@ While babysitting: - **Rebase:** automatically rebase on `master` and fix conflicts (rebase, not merge). - **Comments:** only unresolved threads; read body and location only, not huge JSON dumps. Treat gitar review bot carefully — fix only if valid; explain when you disagree. -If **ruling** fails (likely for a new rule), a PR updating the expectations is created automatically on CI failure. Review it, merge it into the branch if the new findings are legitimate, otherwise fix the implementation and the unit tests. Push and re-babysit. +If **ruling** fails (likely for a new rule), a PR updating the expectations is created automatically on CI failure. Review it, merge it into the branch if the new findings are legitimate, otherwise fix the implementation and the unit tests. Push and re-babysit. The resulting merge commit is acceptable while babysitting; Step 7 folds the expectation update into a single `Update ruling expectations` commit. ### Step 6 — TP/FP analysis (only when Step 5 is fully green) @@ -99,7 +100,14 @@ If **ruling** fails (likely for a new rule), a PR updating the expectations is c ### Step 7 — Clean history -Before considering the PR done: history must show **exactly one commit per workflow step** (metadata, reproducer, implementation, plus any later babysit/ruling/precision fix steps still as clear step commits). Rewrite history as needed (`rebase`, not merge), force-push only if appropriate for the branch, then confirm Steps 5 and 6 still hold. +This is where the one-commit-per-step rule is enforced. Whatever the branch looks like after babysitting and precision loops, the final history must show **exactly one commit per workflow step**: + +- one `Add rule metadata` commit +- one `Add failing reproducer` commit, including the FP cases added in Step 6 +- one `Implement rule` commit, including the heuristics added in Step 6 +- one `Update ruling expectations` commit, folding in the auto-generated ruling PR and dropping its merge commit + +Rewrite history with interactive `rebase` (never a merge), then force-push and confirm Steps 5 and 6 still hold. ### Definition of done