From bb6f262dba464ea96dfdd6e378275d093bfc2ebb Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Fri, 10 Jul 2026 21:22:55 -0700 Subject: [PATCH 1/3] conformance: per-surface lint architecture (VS Code lint tasks, editorconfig docker :latest) (#401) Converges Utilities onto the fleet lint architecture: editorconfig-checker via docker :latest (its action is install-only), VS Code Lint group added, husky language-only with the if guard, cspell scoped to README+HISTORY, AGENTS.md lint guidance synced. --- .github/workflows/validate-task.yml | 2 +- .husky/pre-commit | 7 +++- .vscode/tasks.json | 53 +++++++++++++++++++++++++++++ AGENTS.md | 2 +- 4 files changed, 61 insertions(+), 3 deletions(-) diff --git a/.github/workflows/validate-task.yml b/.github/workflows/validate-task.yml index 151cbfb..21e6fb4 100644 --- a/.github/workflows/validate-task.yml +++ b/.github/workflows/validate-task.yml @@ -82,4 +82,4 @@ jobs: uses: raven-actions/actionlint@3d39aea434753780c3b3d4a1a31c854b4dbf49d7 # v2.2.0 - name: Check line endings step - run: docker run --rm -v "$PWD":/check --workdir /check mstruebing/editorconfig-checker@sha256:67b9e9b16a674e36f7c05919da789f03a01d343ca8423eb8797179399af07c00 # editorconfig-checker v3.4.0 + run: docker run --rm -v "$PWD":/check --workdir /check mstruebing/editorconfig-checker:latest diff --git a/.husky/pre-commit b/.husky/pre-commit index 818853f..e9a75d5 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,9 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -dotnet husky run +# Local pre-commit: language formatting and style only (no Docker). Full lint runs in CI and the VS Code Lint tasks. + +# .NET: CSharpier + dotnet format style via Husky.Net. A Python repo runs ruff here instead. +if command -v dotnet >/dev/null 2>&1; then + dotnet husky run +fi diff --git a/.vscode/tasks.json b/.vscode/tasks.json index d1ffb6b..d3ec0b3 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -132,6 +132,59 @@ "showReuseMessage": false, "clear": false } + }, + // Lint group - the local full doc-lint surface (Docker at :latest), matching the CI lint set. + // Run on demand. The pre-commit hook does language formatting only. Every repo carries these. + { + "label": "Lint: Line Endings", + "type": "shell", + "command": "docker run --rm -v \"${workspaceFolder}:/check\" -w /check mstruebing/editorconfig-checker:latest", + "problemMatcher": [], + "presentation": { + "showReuseMessage": false, + "clear": false + } + }, + { + "label": "Lint: Workflows", + "type": "shell", + "command": "docker run --rm -v \"${workspaceFolder}:/repo\" -w /repo rhysd/actionlint:latest -color", + "problemMatcher": [], + "presentation": { + "showReuseMessage": false, + "clear": false + } + }, + { + "label": "Lint: Markdown", + "type": "shell", + "command": "docker run --rm -v \"${workspaceFolder}:/workdir\" -w /workdir davidanson/markdownlint-cli2:latest \"**/*.md\"", + "problemMatcher": [], + "presentation": { + "showReuseMessage": false, + "clear": false + } + }, + { + "label": "Lint: Spelling", + "type": "shell", + "command": "docker run --rm -v \"${workspaceFolder}:/workdir\" -w /workdir ghcr.io/streetsidesoftware/cspell:latest --no-progress README.md HISTORY.md", + "problemMatcher": [], + "presentation": { + "showReuseMessage": false, + "clear": false + } + }, + { + "label": "Lint: All", + "dependsOrder": "sequence", + "dependsOn": [ + "Lint: Line Endings", + "Lint: Workflows", + "Lint: Markdown", + "Lint: Spelling" + ], + "problemMatcher": [] } ] } diff --git a/AGENTS.md b/AGENTS.md index dfab58b..4507001 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -159,7 +159,7 @@ Anti-pattern: don't keep flipping the code on the same style point. Flip the rul - **Config files.** [`.editorconfig`](./.editorconfig) (per-file-type EOL plus the C# / ReSharper style block), [`.gitattributes`](./.gitattributes), [`.markdownlint-cli2.jsonc`](./.markdownlint-cli2.jsonc), [`CODESTYLE.md`](./CODESTYLE.md) (C# code style), and [`.github/copilot-instructions.md`](./.github/copilot-instructions.md) (the Copilot review runbook) hold the repo's formatting, linting, and review-mechanics rules. `CODESTYLE.md` sits at the repo root because `AGENTS.md` and `copilot-instructions.md` link it by relative path. Keep `copilot-instructions.md` narrow (Copilot/VS Code mechanics plus the commit/PR-title summary); project-specific conventions and the public-API contract live in this file, not there. - **Clean-compile gate.** Husky.Net pre-commit git hooks run the C# clean-compile checks (CSharpier format, then `dotnet format style --verify-no-changes`), installed with `dotnet tool restore` + `dotnet husky install`. The [`.vscode/tasks.json`](./.vscode/tasks.json) tasks `.NET Build`, `CSharpier Format`, and `.NET Format` are the canonical task names (owned by the `CODESTYLE.md` ".NET" section); do not loosen them. CI is the authoritative backstop: the `lint` job ([`WORKFLOW.md`](./WORKFLOW.md) D1.3) enforces CSharpier, `dotnet format style`, `markdownlint`, scoped `cspell`, and `actionlint` from the same config files, because a local hook can be bypassed. Keep the editor task, the hook, and CI in sync (CODESTYLE "Clean-Compile Verification"). -- **Linting tools.** CI is the authoritative lint run; a local run is only for fast feedback. The `dotnet` checks need only the .NET SDK: `dotnet format style` is built into the SDK, and CSharpier is restored by `dotnet tool restore` against [`.config/dotnet-tools.json`](./.config/dotnet-tools.json). The markdown, spelling, and workflow linters have no committed manifest; run each from its official Docker image, the portable path that avoids a local Node or Go install, mounting the repo as the working directory: `cspell` from `ghcr.io/streetsidesoftware/cspell`, `markdownlint-cli2` from `davidanson/markdownlint-cli2`, and `actionlint` (which bundles `shellcheck`) from `rhysd/actionlint`, pinned to the versions [`validate-task.yml`](./.github/workflows/validate-task.yml) uses. Each takes the file globs directly, for example `docker run --rm -v "$PWD":/work -w /work ghcr.io/streetsidesoftware/cspell cspell README.md HISTORY.md` or `... davidanson/markdownlint-cli2 '**/*.md'`. The cspell accepted-word list and the path exclusions both live in [`cspell.json`](./cspell.json), the single source: the Code Spell Checker extension reads `cspell.json` ahead of the workspace `cSpell` settings (so GUI "Add to dictionary" lands words there), and the CLI and CI read the same file. Do not keep a parallel word list in the `.code-workspace` file. A local cspell or markdownlint result that reports zero files checked scanned nothing; ignore it. There is intentionally no wrapper script; the editor, these Docker images, and CI are the supported runners. +- **Linting tools.** CI is the authoritative lint run; a local run is only for fast feedback. The `dotnet` checks need only the .NET SDK: `dotnet format style` is built into the SDK, and CSharpier is restored by `dotnet tool restore` against [`.config/dotnet-tools.json`](./.config/dotnet-tools.json). The markdown, spelling, and workflow linters have no committed manifest; run each from its official Docker image, the portable path that avoids a local Node or Go install, mounting the repo as the working directory: `cspell` from `ghcr.io/streetsidesoftware/cspell`, `markdownlint-cli2` from `davidanson/markdownlint-cli2`, and `actionlint` (which bundles `shellcheck`) from `rhysd/actionlint`, at `:latest`. CI runs these three as pinned action wrappers (Dependabot bumps them) and editorconfig-checker via Docker `:latest`; local Docker runs and the VS Code **Lint** tasks track `:latest`. Each takes the file globs directly, for example `docker run --rm -v "$PWD":/work -w /work ghcr.io/streetsidesoftware/cspell README.md HISTORY.md` or `... davidanson/markdownlint-cli2 '**/*.md'`. The cspell accepted-word list and the path exclusions both live in [`cspell.json`](./cspell.json), the single source: the Code Spell Checker extension reads `cspell.json` ahead of the workspace `cSpell` settings (so GUI "Add to dictionary" lands words there), and the CLI and CI read the same file. Do not keep a parallel word list in the `.code-workspace` file. A local cspell or markdownlint result that reports zero files checked scanned nothing; ignore it. There is intentionally no wrapper script; the editor, these Docker images, and CI are the supported runners. - **Release notes.** Keep a short summary in [`README.md`](./README.md) and the full history in [`HISTORY.md`](./HISTORY.md); update both when cutting a release. ## Workflow YAML Conventions From f04d27624d8ba8921d119811d32619cbbd08cf38 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sat, 11 Jul 2026 07:45:24 -0700 Subject: [PATCH 2/3] lint: EditorConfig rename + process-type VS Code Lint tasks (#402) Fleet lint conventions: EditorConfig rename (step + VS Code task) and process-type VS Code Lint tasks. Canonical: ProjectTemplate #280. --- .github/workflows/validate-task.yml | 2 +- .vscode/tasks.json | 24 ++++++++++++++---------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/.github/workflows/validate-task.yml b/.github/workflows/validate-task.yml index 21e6fb4..34bc502 100644 --- a/.github/workflows/validate-task.yml +++ b/.github/workflows/validate-task.yml @@ -81,5 +81,5 @@ jobs: - name: Lint workflows step uses: raven-actions/actionlint@3d39aea434753780c3b3d4a1a31c854b4dbf49d7 # v2.2.0 - - name: Check line endings step + - name: Check EditorConfig step run: docker run --rm -v "$PWD":/check --workdir /check mstruebing/editorconfig-checker:latest diff --git a/.vscode/tasks.json b/.vscode/tasks.json index d3ec0b3..3b233ea 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -136,9 +136,10 @@ // Lint group - the local full doc-lint surface (Docker at :latest), matching the CI lint set. // Run on demand. The pre-commit hook does language formatting only. Every repo carries these. { - "label": "Lint: Line Endings", - "type": "shell", - "command": "docker run --rm -v \"${workspaceFolder}:/check\" -w /check mstruebing/editorconfig-checker:latest", + "label": "Lint: EditorConfig", + "type": "process", + "command": "docker", + "args": [ "run", "--rm", "-v", "${workspaceFolder}:/check", "-w", "/check", "mstruebing/editorconfig-checker:latest" ], "problemMatcher": [], "presentation": { "showReuseMessage": false, @@ -147,8 +148,9 @@ }, { "label": "Lint: Workflows", - "type": "shell", - "command": "docker run --rm -v \"${workspaceFolder}:/repo\" -w /repo rhysd/actionlint:latest -color", + "type": "process", + "command": "docker", + "args": [ "run", "--rm", "-v", "${workspaceFolder}:/repo", "-w", "/repo", "rhysd/actionlint:latest", "-color" ], "problemMatcher": [], "presentation": { "showReuseMessage": false, @@ -157,8 +159,9 @@ }, { "label": "Lint: Markdown", - "type": "shell", - "command": "docker run --rm -v \"${workspaceFolder}:/workdir\" -w /workdir davidanson/markdownlint-cli2:latest \"**/*.md\"", + "type": "process", + "command": "docker", + "args": [ "run", "--rm", "-v", "${workspaceFolder}:/workdir", "-w", "/workdir", "davidanson/markdownlint-cli2:latest", "**/*.md" ], "problemMatcher": [], "presentation": { "showReuseMessage": false, @@ -167,8 +170,9 @@ }, { "label": "Lint: Spelling", - "type": "shell", - "command": "docker run --rm -v \"${workspaceFolder}:/workdir\" -w /workdir ghcr.io/streetsidesoftware/cspell:latest --no-progress README.md HISTORY.md", + "type": "process", + "command": "docker", + "args": [ "run", "--rm", "-v", "${workspaceFolder}:/workdir", "-w", "/workdir", "ghcr.io/streetsidesoftware/cspell:latest", "--no-progress", "README.md", "HISTORY.md" ], "problemMatcher": [], "presentation": { "showReuseMessage": false, @@ -179,7 +183,7 @@ "label": "Lint: All", "dependsOrder": "sequence", "dependsOn": [ - "Lint: Line Endings", + "Lint: EditorConfig", "Lint: Workflows", "Lint: Markdown", "Lint: Spelling" From 11dd6899de89283102dc40a10e34308964989fc0 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sat, 11 Jul 2026 20:41:42 -0700 Subject: [PATCH 3/3] conformance: migrate tests to xUnit v3 + AwesomeAssertions; --pull=always (#403) Completes #380 (xUnit v3 test migration; ILoggerFactory seam already on develop) and #387 (config hygiene). Adds --pull=always to local Lint tasks. --- .vscode/tasks.json | 8 ++++---- Directory.Packages.props | 2 +- UtilitiesTests/.editorconfig | 3 --- UtilitiesTests/UtilitiesTests.csproj | 20 ++------------------ 4 files changed, 7 insertions(+), 26 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 3b233ea..5eee7e9 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -139,7 +139,7 @@ "label": "Lint: EditorConfig", "type": "process", "command": "docker", - "args": [ "run", "--rm", "-v", "${workspaceFolder}:/check", "-w", "/check", "mstruebing/editorconfig-checker:latest" ], + "args": [ "run", "--rm", "--pull=always", "-v", "${workspaceFolder}:/check", "-w", "/check", "mstruebing/editorconfig-checker:latest" ], "problemMatcher": [], "presentation": { "showReuseMessage": false, @@ -150,7 +150,7 @@ "label": "Lint: Workflows", "type": "process", "command": "docker", - "args": [ "run", "--rm", "-v", "${workspaceFolder}:/repo", "-w", "/repo", "rhysd/actionlint:latest", "-color" ], + "args": [ "run", "--rm", "--pull=always", "-v", "${workspaceFolder}:/repo", "-w", "/repo", "rhysd/actionlint:latest", "-color" ], "problemMatcher": [], "presentation": { "showReuseMessage": false, @@ -161,7 +161,7 @@ "label": "Lint: Markdown", "type": "process", "command": "docker", - "args": [ "run", "--rm", "-v", "${workspaceFolder}:/workdir", "-w", "/workdir", "davidanson/markdownlint-cli2:latest", "**/*.md" ], + "args": [ "run", "--rm", "--pull=always", "-v", "${workspaceFolder}:/workdir", "-w", "/workdir", "davidanson/markdownlint-cli2:latest", "**/*.md" ], "problemMatcher": [], "presentation": { "showReuseMessage": false, @@ -172,7 +172,7 @@ "label": "Lint: Spelling", "type": "process", "command": "docker", - "args": [ "run", "--rm", "-v", "${workspaceFolder}:/workdir", "-w", "/workdir", "ghcr.io/streetsidesoftware/cspell:latest", "--no-progress", "README.md", "HISTORY.md" ], + "args": [ "run", "--rm", "--pull=always", "-v", "${workspaceFolder}:/workdir", "-w", "/workdir", "ghcr.io/streetsidesoftware/cspell:latest", "--no-progress", "README.md", "HISTORY.md" ], "problemMatcher": [], "presentation": { "showReuseMessage": false, diff --git a/Directory.Packages.props b/Directory.Packages.props index de12f18..fdfbc51 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -9,8 +9,8 @@ - + diff --git a/UtilitiesTests/.editorconfig b/UtilitiesTests/.editorconfig index f443e5e..1bb70bb 100644 --- a/UtilitiesTests/.editorconfig +++ b/UtilitiesTests/.editorconfig @@ -8,6 +8,3 @@ dotnet_diagnostic.CA1707.severity = none # CA1515: xUnit discovers only public test classes, so they cannot be internal. dotnet_diagnostic.CA1515.severity = none - -# CA2007: xUnit forbids ConfigureAwait in test methods (xUnit1030), so CA2007 cannot be satisfied in test code. -dotnet_diagnostic.CA2007.severity = none diff --git a/UtilitiesTests/UtilitiesTests.csproj b/UtilitiesTests/UtilitiesTests.csproj index ab15043..6d87d47 100644 --- a/UtilitiesTests/UtilitiesTests.csproj +++ b/UtilitiesTests/UtilitiesTests.csproj @@ -1,28 +1,12 @@ - false - Pieter Viljoen - Pieter Viljoen - Pieter Viljoen - Generally useful utility classes - 1.1.0.1 - 1.1.0.1 - 1.1.0.0 - ptr727.Utilities.Tests - UtilitiesTests + true ptr727.Utilities.Tests - en - true - true - true - snupkg - - - + all runtime; build; native; contentfiles; analyzers; buildtransitive