diff --git a/.agents/skills/operational-vs-release-workflow/references/branch-protection-and-promotion.md b/.agents/skills/operational-vs-release-workflow/references/branch-protection-and-promotion.md index b0a1c1a7..597cd5f1 100644 --- a/.agents/skills/operational-vs-release-workflow/references/branch-protection-and-promotion.md +++ b/.agents/skills/operational-vs-release-workflow/references/branch-protection-and-promotion.md @@ -9,22 +9,23 @@ covers that case). ## Configuring branch protection: don't hand-build the rules Delete **all** classic branch-protection rules and stray rulesets because rulesets are the only -protection mechanism. Create **exactly two rulesets named `develop` and `main`** from the hub's -`repo-config/*.json` payloads. Run `repo-config/configure.sh apply / -release|operational` from a hub checkout at `main`. The names are load-bearing because governance -content and workflows reference them. The registry `workflowModel` selects the `develop` payload -for a registered repository. Pass the model explicitly for a repository outside the registry. -See `repo-config/README.md` "Rulesets" for the configured state. +protection mechanism. From a hub checkout at `main`, create **exactly two rulesets named `develop` +and `main`** from the hub's `repo-config/*.json` payloads. Run +`repo-config/configure.sh apply / release|operational` from that checkout. The names +are load-bearing because governance content and workflows reference them. The registry +`workflowModel` selects the `develop` payload for a registered repository. Pass the model +explicitly for a repository outside the registry. See the hub's `repo-config/README.md` +"Rulesets" for the configured state. ## Executing a `develop -> main` promotion safely Two traps, both learned the hard way: - **Never delete `develop`.** A promotion PR's head *is* `develop`, so `gh pr merge --delete-branch` - (and a repo's "Automatically delete head branches" toggle, kept off in `repo-config/settings.json` - for exactly this reason) deletes `develop` itself. Merge a promotion with a plain - `gh pr merge --merge`, no `--delete-branch`. If `develop` is ever lost this way, restore it to - the merged PR's head SHA, which is still reachable as the merge commit's second parent: + (and a repo's "Automatically delete head branches" toggle, kept off in the hub's + `repo-config/settings.json` for exactly this reason) deletes `develop` itself. Merge a promotion + with a plain `gh pr merge --merge`, no `--delete-branch`. If `develop` is ever lost this way, + restore it to the merged PR's head SHA, which is still reachable as the merge commit's second parent: `gh api -X POST "repos///git/refs" -f ref=refs/heads/develop -f sha="$(gh pr view --json headRefOid --jq .headRefOid)"`. - **Spurious EOL-only conflicts resolve by taking `develop`.** When `develop`'s `.editorconfig` line-ending default has changed (for example the fleet-wide CRLF-to-LF flip) while `main` hasn't @@ -105,5 +106,5 @@ supplies its own input-deterministic generator and wires the codegen reference w `actions/create-github-app-token` deprecated the numeric `app-id` input in v3.0.0. Use `client-id: ${{ secrets.CODEGEN_APP_CLIENT_ID }}`. When adding new App-token call sites, use the -same form, and do not reintroduce `app-id` / `CODEGEN_APP_ID`. See `repo-config/README.md` -"Secrets" for which secrets each mechanism needs. +same form, and do not reintroduce `app-id` / `CODEGEN_APP_ID`. See the hub's +`repo-config/README.md` "Secrets" for which secrets each mechanism needs. diff --git a/.claude-plugin/fleet-skills/.source-digest b/.claude-plugin/fleet-skills/.source-digest index eb5b2b09..9b29c275 100644 --- a/.claude-plugin/fleet-skills/.source-digest +++ b/.claude-plugin/fleet-skills/.source-digest @@ -1 +1 @@ -33837ae6c41ef01f +096bcb76aa0ac4ae diff --git a/.claude-plugin/fleet-skills/skills/operational-vs-release-workflow/references/branch-protection-and-promotion.md b/.claude-plugin/fleet-skills/skills/operational-vs-release-workflow/references/branch-protection-and-promotion.md index b0a1c1a7..597cd5f1 100644 --- a/.claude-plugin/fleet-skills/skills/operational-vs-release-workflow/references/branch-protection-and-promotion.md +++ b/.claude-plugin/fleet-skills/skills/operational-vs-release-workflow/references/branch-protection-and-promotion.md @@ -9,22 +9,23 @@ covers that case). ## Configuring branch protection: don't hand-build the rules Delete **all** classic branch-protection rules and stray rulesets because rulesets are the only -protection mechanism. Create **exactly two rulesets named `develop` and `main`** from the hub's -`repo-config/*.json` payloads. Run `repo-config/configure.sh apply / -release|operational` from a hub checkout at `main`. The names are load-bearing because governance -content and workflows reference them. The registry `workflowModel` selects the `develop` payload -for a registered repository. Pass the model explicitly for a repository outside the registry. -See `repo-config/README.md` "Rulesets" for the configured state. +protection mechanism. From a hub checkout at `main`, create **exactly two rulesets named `develop` +and `main`** from the hub's `repo-config/*.json` payloads. Run +`repo-config/configure.sh apply / release|operational` from that checkout. The names +are load-bearing because governance content and workflows reference them. The registry +`workflowModel` selects the `develop` payload for a registered repository. Pass the model +explicitly for a repository outside the registry. See the hub's `repo-config/README.md` +"Rulesets" for the configured state. ## Executing a `develop -> main` promotion safely Two traps, both learned the hard way: - **Never delete `develop`.** A promotion PR's head *is* `develop`, so `gh pr merge --delete-branch` - (and a repo's "Automatically delete head branches" toggle, kept off in `repo-config/settings.json` - for exactly this reason) deletes `develop` itself. Merge a promotion with a plain - `gh pr merge --merge`, no `--delete-branch`. If `develop` is ever lost this way, restore it to - the merged PR's head SHA, which is still reachable as the merge commit's second parent: + (and a repo's "Automatically delete head branches" toggle, kept off in the hub's + `repo-config/settings.json` for exactly this reason) deletes `develop` itself. Merge a promotion + with a plain `gh pr merge --merge`, no `--delete-branch`. If `develop` is ever lost this way, + restore it to the merged PR's head SHA, which is still reachable as the merge commit's second parent: `gh api -X POST "repos///git/refs" -f ref=refs/heads/develop -f sha="$(gh pr view --json headRefOid --jq .headRefOid)"`. - **Spurious EOL-only conflicts resolve by taking `develop`.** When `develop`'s `.editorconfig` line-ending default has changed (for example the fleet-wide CRLF-to-LF flip) while `main` hasn't @@ -105,5 +106,5 @@ supplies its own input-deterministic generator and wires the codegen reference w `actions/create-github-app-token` deprecated the numeric `app-id` input in v3.0.0. Use `client-id: ${{ secrets.CODEGEN_APP_CLIENT_ID }}`. When adding new App-token call sites, use the -same form, and do not reintroduce `app-id` / `CODEGEN_APP_ID`. See `repo-config/README.md` -"Secrets" for which secrets each mechanism needs. +same form, and do not reintroduce `app-id` / `CODEGEN_APP_ID`. See the hub's +`repo-config/README.md` "Secrets" for which secrets each mechanism needs. diff --git a/.github/actions/prose-gate/prose_lint.py b/.github/actions/prose-gate/prose_lint.py index 2d3272ad..fedd9f6d 100755 --- a/.github/actions/prose-gate/prose_lint.py +++ b/.github/actions/prose-gate/prose_lint.py @@ -185,7 +185,17 @@ def changed_lines(base: str, root: Path) -> dict[str, set[int]] | None: """ try: d = subprocess.run( - ["git", "-C", str(root), "diff", "--unified=0", "--no-color", base, "--"], + [ + "git", + "-C", + str(root), + "diff", + "--unified=0", + "--no-color", + "--ignore-cr-at-eol", + base, + "--", + ], capture_output=True, text=True, check=True, diff --git a/.github/skills/operational-vs-release-workflow/references/branch-protection-and-promotion.md b/.github/skills/operational-vs-release-workflow/references/branch-protection-and-promotion.md index b0a1c1a7..597cd5f1 100644 --- a/.github/skills/operational-vs-release-workflow/references/branch-protection-and-promotion.md +++ b/.github/skills/operational-vs-release-workflow/references/branch-protection-and-promotion.md @@ -9,22 +9,23 @@ covers that case). ## Configuring branch protection: don't hand-build the rules Delete **all** classic branch-protection rules and stray rulesets because rulesets are the only -protection mechanism. Create **exactly two rulesets named `develop` and `main`** from the hub's -`repo-config/*.json` payloads. Run `repo-config/configure.sh apply / -release|operational` from a hub checkout at `main`. The names are load-bearing because governance -content and workflows reference them. The registry `workflowModel` selects the `develop` payload -for a registered repository. Pass the model explicitly for a repository outside the registry. -See `repo-config/README.md` "Rulesets" for the configured state. +protection mechanism. From a hub checkout at `main`, create **exactly two rulesets named `develop` +and `main`** from the hub's `repo-config/*.json` payloads. Run +`repo-config/configure.sh apply / release|operational` from that checkout. The names +are load-bearing because governance content and workflows reference them. The registry +`workflowModel` selects the `develop` payload for a registered repository. Pass the model +explicitly for a repository outside the registry. See the hub's `repo-config/README.md` +"Rulesets" for the configured state. ## Executing a `develop -> main` promotion safely Two traps, both learned the hard way: - **Never delete `develop`.** A promotion PR's head *is* `develop`, so `gh pr merge --delete-branch` - (and a repo's "Automatically delete head branches" toggle, kept off in `repo-config/settings.json` - for exactly this reason) deletes `develop` itself. Merge a promotion with a plain - `gh pr merge --merge`, no `--delete-branch`. If `develop` is ever lost this way, restore it to - the merged PR's head SHA, which is still reachable as the merge commit's second parent: + (and a repo's "Automatically delete head branches" toggle, kept off in the hub's + `repo-config/settings.json` for exactly this reason) deletes `develop` itself. Merge a promotion + with a plain `gh pr merge --merge`, no `--delete-branch`. If `develop` is ever lost this way, + restore it to the merged PR's head SHA, which is still reachable as the merge commit's second parent: `gh api -X POST "repos///git/refs" -f ref=refs/heads/develop -f sha="$(gh pr view --json headRefOid --jq .headRefOid)"`. - **Spurious EOL-only conflicts resolve by taking `develop`.** When `develop`'s `.editorconfig` line-ending default has changed (for example the fleet-wide CRLF-to-LF flip) while `main` hasn't @@ -105,5 +106,5 @@ supplies its own input-deterministic generator and wires the codegen reference w `actions/create-github-app-token` deprecated the numeric `app-id` input in v3.0.0. Use `client-id: ${{ secrets.CODEGEN_APP_CLIENT_ID }}`. When adding new App-token call sites, use the -same form, and do not reintroduce `app-id` / `CODEGEN_APP_ID`. See `repo-config/README.md` -"Secrets" for which secrets each mechanism needs. +same form, and do not reintroduce `app-id` / `CODEGEN_APP_ID`. See the hub's +`repo-config/README.md` "Secrets" for which secrets each mechanism needs. diff --git a/scripts/tests/test_prose_lint.py b/scripts/tests/test_prose_lint.py index dc846db1..e66ef120 100755 --- a/scripts/tests/test_prose_lint.py +++ b/scripts/tests/test_prose_lint.py @@ -1732,6 +1732,29 @@ def test_a_hunk_before_its_file_header_is_not_attributed_to_the_previous_file(se """Reading a stray hunk against whichever file came last invents a scope.""" self.assertEqual({}, self.run_diff("@@ -1 +1 @@\n+orphan\n")) + def test_a_line_ending_only_change_adds_no_lines(self) -> None: + """Renormalizing CRLF to LF does not make existing prose newly authored.""" + root = Path(self.enterContext(tempfile.TemporaryDirectory())) + subprocess.run(["git", "init", "--quiet", str(root)], check=True) + subprocess.run(["git", "-C", str(root), "config", "core.autocrlf", "false"], check=True) + subprocess.run(["git", "-C", str(root), "config", "commit.gpgsign", "false"], check=True) + subprocess.run(["git", "-C", str(root), "config", "user.name", "Test"], check=True) + subprocess.run( + ["git", "-C", str(root), "config", "user.email", "test@example.invalid"], check=True + ) + bait = root / "bait.md" + bait.write_bytes(b"Existing prose.\r\n") + subprocess.run(["git", "-C", str(root), "add", "bait.md"], check=True) + subprocess.run(["git", "-C", str(root), "commit", "--quiet", "-m", "baseline"], check=True) + + bait.write_bytes(b"Existing prose.\n") + + self.assertEqual({}, prose_lint.changed_lines("HEAD", root)) + + bait.write_bytes(b"New prose.\n") + + self.assertEqual({"bait.md": {1}}, prose_lint.changed_lines("HEAD", root)) + def test_a_git_failure_is_none_rather_than_an_empty_scope(self) -> None: """An empty scope filters every file out and reports a clean run, which is a false pass.""" with mock.patch.object(