perf(469): format gate uses dotnet format whitespace --folder (~480s → ~0.5s) #471

Merged
timothy merged 2 commits from ci/469-format-speed into main 2026-07-19 19:59:34 +02:00
Owner

Fixes #469.

What

The blocking format CI job (and the matching Husky pre-commit hook) now verify changed .cs files with dotnet format whitespace . --folder --verify-no-changes --include <files> instead of the full dotnet format ErsatzTV.sln --no-restore --verify-no-changes --include <files>. The job's NuGet-cache + Restore steps are removed (folder mode needs no restore).

Why

--include only narrows which files are checked — never what gets loaded. The old recipe loaded the entire ~10-project MSBuild workspace and built a Roslyn compilation per project before checking a single line, a fixed cost regardless of how few files changed. --folder treats the tree as a plain folder of files and skips MSBuild/Roslyn entirely.

Measurements (local, this Mac, warm restore)

Command Time
Before dotnet format ErsatzTV.sln --no-restore --verify-no-changes --include <file> (clean) ~480s
After dotnet format whitespace . --folder --verify-no-changes --include <file> (clean) ~0.5s

Plus the old path required a dotnet restore step; the new one does not. True CI job wall-clock will show on this PR's format run.

Coverage — unchanged, proven

  • Non-vacuous: folder mode exits non-zero with error WHITESPACE on an injected trailing-whitespace line and error CHARSET on a prepended UTF-8 BOM; exits 0 on a clean file. Verified through the exact CI path (mapfile + array --include, one clean + one BOM'd file → fails and pinpoints the file).
  • No style/analyzer coverage lost: the full gate never enforced the style pass either — a probe injecting a warning-severity naming violation (local_constants not ALL_UPPER) passed the full solution format (exit 0). The only .editorconfig rule above :suggestion/:none is that one naming rule, and dotnet format's default severity ignores it. The analyzers that must block (NU1904, S3981) are enforced at compile via WarningsAsErrors in Directory.Build.props, not by this job.

Fix command for a violation

dotnet format whitespace . --folder --include <files> (the full dotnet format ErsatzTV.sln --include <files> is a superset and still works).

Docs

  • docs/ci-cd.md → Formatting section rewritten; the obsolete #406 "env vars don't shrink format's 3.95 GiB Roslyn heap" note updated (moot — no Roslyn heap now).
  • docs/decisions.md → append-only entry.

Scope notes

  • Lane left on ubuntu-latest — the job is now seconds-long/low-memory and could move to a lighter lane, but that re-touches the per-lane memory-cap accounting (#406/#604) and is a server-management capacity call.
  • The #311 BOM/whitespace fix-as-you-touch convention is unchanged; this is purely about the speed of enforcing it.

🤖 Generated with Claude Code

Fixes #469. ## What The blocking `format` CI job (and the matching Husky pre-commit hook) now verify changed `.cs` files with **`dotnet format whitespace . --folder --verify-no-changes --include <files>`** instead of the full `dotnet format ErsatzTV.sln --no-restore --verify-no-changes --include <files>`. The job's NuGet-cache + Restore steps are removed (folder mode needs no restore). ## Why `--include` only narrows *which* files are checked — never what gets loaded. The old recipe loaded the entire ~10-project MSBuild workspace and built a Roslyn compilation per project before checking a single line, a fixed cost regardless of how few files changed. `--folder` treats the tree as a plain folder of files and skips MSBuild/Roslyn entirely. ## Measurements (local, this Mac, warm restore) | | Command | Time | |---|---|---| | Before | `dotnet format ErsatzTV.sln --no-restore --verify-no-changes --include <file>` (clean) | **~480s** | | After | `dotnet format whitespace . --folder --verify-no-changes --include <file>` (clean) | **~0.5s** | Plus the old path required a `dotnet restore` step; the new one does not. True CI job wall-clock will show on this PR's `format` run. ## Coverage — unchanged, proven - **Non-vacuous:** folder mode exits non-zero with `error WHITESPACE` on an injected trailing-whitespace line and `error CHARSET` on a prepended UTF-8 BOM; exits 0 on a clean file. Verified through the exact CI path (`mapfile` + array `--include`, one clean + one BOM'd file → fails and pinpoints the file). - **No style/analyzer coverage lost:** the *full* gate never enforced the style pass either — a probe injecting a `warning`-severity naming violation (`local_constants` not `ALL_UPPER`) **passed** the full solution format (exit 0). The only `.editorconfig` rule above `:suggestion`/`:none` is that one naming rule, and `dotnet format`'s default severity ignores it. The analyzers that must block (`NU1904`, `S3981`) are enforced at compile via `WarningsAsErrors` in `Directory.Build.props`, not by this job. ## Fix command for a violation `dotnet format whitespace . --folder --include <files>` (the full `dotnet format ErsatzTV.sln --include <files>` is a superset and still works). ## Docs - `docs/ci-cd.md` → Formatting section rewritten; the obsolete #406 "env vars don't shrink `format`'s 3.95 GiB Roslyn heap" note updated (moot — no Roslyn heap now). - `docs/decisions.md` → append-only entry. ## Scope notes - Lane left on `ubuntu-latest` — the job is now seconds-long/low-memory and *could* move to a lighter lane, but that re-touches the per-lane memory-cap accounting (#406/#604) and is a server-management capacity call. - The #311 BOM/whitespace fix-as-you-touch convention is unchanged; this is purely about the *speed* of enforcing it. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
timothy added 1 commit 2026-07-19 19:28:29 +02:00
perf(469): format gate uses dotnet format whitespace --folder (~480s → ~0.5s)
Build ErsatzTV Image / decisions.md append-only (pull_request) Waiting to run
Build ErsatzTV Image / CI image pin matches docker/ci (pull_request) Successful in 6s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 17s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 1m15s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 1m28s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Has been cancelled
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been cancelled
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Has been cancelled
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Has been cancelled
eafb2e39e2
The blocking `format` CI job and the Husky pre-commit hook verified changed .cs
files with `dotnet format ErsatzTV.sln --no-restore --verify-no-changes
--include <files>`. `--include` only narrows *which* files are checked, never
what gets loaded: the full recipe loaded the ~10-project MSBuild workspace and
built a Roslyn compilation per project before checking a single line (~480s
locally, whole-solution). Switch both to `dotnet format whitespace . --folder
--verify-no-changes --include <files>`, which treats the tree as a plain folder
of files, skips MSBuild/Roslyn entirely (~0.5s), and needs no `dotnet restore`
(NuGet-cache + Restore steps removed).

Coverage is unchanged: folder mode reads .editorconfig and enforces exactly the
gate's purpose — whitespace + charset (BOM). Proven non-vacuous (error
WHITESPACE on a trailing-space line, error CHARSET on a prepended BOM, exit 0
clean). The full gate never enforced the style/analyzer pass either — a
warning-severity naming violation passes the full solution format (exit 0) — and
the analyzers that must block (NU1904, S3981) are enforced at compile via
WarningsAsErrors, not by this job.

Docs: ci-cd.md Formatting section + the obsolete #406 memory note; decisions.md.

fixes #469

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
timothy added 1 commit 2026-07-19 19:34:47 +02:00
docs(469): sweep remaining stale format-job cost/memory claims (review)
Build ErsatzTV Image / CI image pin matches docker/ci (pull_request) Successful in 8s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 9s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 23s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 20s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 5m49s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 18m28s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 18m33s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 5s
33657b4753
Cold adversarial review (MERGEABLE) flagged two spots still describing the
pre-#469 heavy `format` job as current, plus a wording imprecision:
- ci-cd.md CI-lane table row: annotate `format` runtime 37s → ~0.5s (#469).
- ci-cd.md #406 memory narrative: note the 3.95 GiB Roslyn heap is now moot
  (folder mode loads no workspace); api-docs remains the lane's real consumer.
- Precise the coverage mechanism in ci-cd.md + decisions.md: the naming rule
  passes the full gate because naming violations have no `dotnet format` batch
  code-fixer (so `--verify-no-changes` sees no change), not merely a severity
  floor.

Docs-only; no workflow/hook logic change from the reviewed commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author
Owner

Cold adversarial review complete — MERGEABLE, no functional blocker. Detection proven non-vacuous (WHITESPACE / CHARSET-BOM / FINALNEWLINE all exit-2; clean exit-0; repo-relative --include matches workspace .; nested .editorconfig honored). Dropped restore/NuGet-cache confirmed safe (folder mode loads no MSBuild workspace). No merge-gating coverage lost — the full gate never enforced the style pass either (naming rule has no batch code-fixer), and NU1904/S3981 block at compile via WarningsAsErrors.

Review's one Medium (stale format-cost claims at ci-cd.md:106/:119) + the wording nit are fixed in follow-up commit 33657b47. That commit is docs-only — no workflow/hook logic changed from the reviewed commit eafb2e39, so no fresh code review is warranted.

Review-verdict: MERGEABLE @ 33657b4753

Cold adversarial review complete — **MERGEABLE**, no functional blocker. Detection proven non-vacuous (WHITESPACE / CHARSET-BOM / FINALNEWLINE all exit-2; clean exit-0; repo-relative `--include` matches workspace `.`; nested `.editorconfig` honored). Dropped `restore`/NuGet-cache confirmed safe (folder mode loads no MSBuild workspace). No merge-gating coverage lost — the full gate never enforced the style pass either (naming rule has no batch code-fixer), and NU1904/S3981 block at compile via `WarningsAsErrors`. Review's one Medium (stale `format`-cost claims at ci-cd.md:106/:119) + the wording nit are fixed in follow-up commit 33657b47. That commit is **docs-only** — no workflow/hook logic changed from the reviewed commit eafb2e39, so no fresh code review is warranted. Review-verdict: MERGEABLE @ 33657b4753ab29a18793d4e0ec206c2ac5b94f40
Author
Owner

CI (head 33657b47): all jobs green — Build & test, EF migration integrity, Functional E2E, API docs in sync, Formatting, CI image pin, Docs update reminder . Image-push skipped (main/tag only).

Sole red = decisions.md append-only — known flake. Verified my decisions.md diff vs origin/main is purely additive (one new ## block at EOF, zero deletions/modifications), so it does not violate append-only. Awaiting a selective rerun of just that job (no push, sha stays 33657b47).

All four ## Done-when boxes on #469 are now ticked. Once decisions.md append-only is green, the H6/H10 gate is satisfied for merge.

**CI (head 33657b47):** all jobs green — `Build & test`, `EF migration integrity`, `Functional E2E`, `API docs in sync`, **`Formatting`**, `CI image pin`, `Docs update reminder` ✅. Image-push skipped (main/tag only). **Sole red = `decisions.md append-only` — known flake.** Verified my decisions.md diff vs `origin/main` is *purely additive* (one new `##` block at EOF, zero deletions/modifications), so it does not violate append-only. Awaiting a selective rerun of just that job (no push, sha stays 33657b47). All four `## Done-when` boxes on #469 are now ticked. Once `decisions.md append-only` is green, the H6/H10 gate is satisfied for merge.
timothy merged commit 48d632d50a into main 2026-07-19 19:59:34 +02:00
timothy deleted branch ci/469-format-speed 2026-07-19 19:59:34 +02:00
Sign in to join this conversation.