diff --git a/.github/workflows/validate-task.yml b/.github/workflows/validate-task.yml
index 151cbfb..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
- run: docker run --rm -v "$PWD":/check --workdir /check mstruebing/editorconfig-checker@sha256:67b9e9b16a674e36f7c05919da789f03a01d343ca8423eb8797179399af07c00 # editorconfig-checker v3.4.0
+ - name: Check EditorConfig step
+ 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..5eee7e9 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -132,6 +132,63 @@
"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: EditorConfig",
+ "type": "process",
+ "command": "docker",
+ "args": [ "run", "--rm", "--pull=always", "-v", "${workspaceFolder}:/check", "-w", "/check", "mstruebing/editorconfig-checker:latest" ],
+ "problemMatcher": [],
+ "presentation": {
+ "showReuseMessage": false,
+ "clear": false
+ }
+ },
+ {
+ "label": "Lint: Workflows",
+ "type": "process",
+ "command": "docker",
+ "args": [ "run", "--rm", "--pull=always", "-v", "${workspaceFolder}:/repo", "-w", "/repo", "rhysd/actionlint:latest", "-color" ],
+ "problemMatcher": [],
+ "presentation": {
+ "showReuseMessage": false,
+ "clear": false
+ }
+ },
+ {
+ "label": "Lint: Markdown",
+ "type": "process",
+ "command": "docker",
+ "args": [ "run", "--rm", "--pull=always", "-v", "${workspaceFolder}:/workdir", "-w", "/workdir", "davidanson/markdownlint-cli2:latest", "**/*.md" ],
+ "problemMatcher": [],
+ "presentation": {
+ "showReuseMessage": false,
+ "clear": false
+ }
+ },
+ {
+ "label": "Lint: Spelling",
+ "type": "process",
+ "command": "docker",
+ "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,
+ "clear": false
+ }
+ },
+ {
+ "label": "Lint: All",
+ "dependsOrder": "sequence",
+ "dependsOn": [
+ "Lint: EditorConfig",
+ "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
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