From adca0d54442015293a957f3ee216c3b231b50789 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 21 Jul 2026 21:04:58 +0000 Subject: [PATCH 1/4] docs(core): correct bundled analyzer count in package README The nuget.org package README advertised "16 Roslyn analyzers (FCE001-FCE016)", but the package ships 22 (FCE001-FCE022). FCE017-FCE022 were added since the claim was last touched, and the analyzer reference under doc/ already lists all 22. The count is the flagship anti-drift claim on the page every evaluator reads first, so a stale number there is especially corrosive. Refs: #230 Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01773Snh91KveG8SCwPQ9mtB --- FirstClassErrors/README.nuget.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirstClassErrors/README.nuget.md b/FirstClassErrors/README.nuget.md index 8cec5349..9156e6ce 100644 --- a/FirstClassErrors/README.nuget.md +++ b/FirstClassErrors/README.nuget.md @@ -27,7 +27,7 @@ checked at build time and can be extracted into a living error catalog. opt-in per project, multi-language) with the companion `fce` tool. Run it in CI so the catalog is published as a build artifact and never drifts from the deployed system. See *Generate the catalog in CI* below. -- **16 Roslyn analyzers in the box (`FCE001`–`FCE016`).** Bundled in the package — no +- **22 Roslyn analyzers in the box (`FCE001`–`FCE022`).** Bundled in the package — no separate install. They catch, at build time, what would otherwise surface late: duplicate error codes, unresolved `[DocumentedBy]` references, documented errors missing from the catalog, an unused `ToException()` result, and more. From 96f93cbce49cb093c7d164c2a90ad2ec0e83a2a2 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 21 Jul 2026 21:04:58 +0000 Subject: [PATCH 2/4] fix(cli): align CLI docs and messages with the real command tree The CLI package README and two command strings named commands that do not exist as written: - The renderer commands live under config (fce config renderer add|remove|list), not fce renderer ...; fix the README list and the RendererReferenceSettings summary. - config show prints the raw configuration file verbatim, not a "resolved" view; reword accordingly. - The catalog contract commands (fce catalog update|diff) were absent from the README; add them with a pointer to the versioning guides. - RendererRemoveCommand told users to run the nonexistent fce init; point it at fce config init. Refs: #230 Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01773Snh91KveG8SCwPQ9mtB --- FirstClassErrors.Cli/README.nuget.md | 13 +++++++++++-- FirstClassErrors.Cli/RendererAddCommand.cs | 2 +- FirstClassErrors.Cli/RendererRemoveCommand.cs | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/FirstClassErrors.Cli/README.nuget.md b/FirstClassErrors.Cli/README.nuget.md index 9a86794c..c71a862d 100644 --- a/FirstClassErrors.Cli/README.nuget.md +++ b/FirstClassErrors.Cli/README.nuget.md @@ -33,9 +33,18 @@ format does not need it. Other commands: +- `fce catalog update` — create or refresh the committed catalog baseline (deliberately accept the current contract) +- `fce catalog diff` — compare the catalog against the baseline and report the changes - `fce config init` — create an `fce.json` so options need not be repeated on every run -- `fce config show` — print the resolved configuration -- `fce renderer add|list|remove` — manage custom renderer libraries +- `fce config show` — print the raw configuration file (no defaults merged in) +- `fce config renderer add|remove|list` — manage custom renderer libraries + +`fce catalog update`/`diff` track your error catalog as a versioned contract, so a breaking +change to your errors is caught in review rather than in production — see the +catalog-versioning guides: +[overview](https://github.com/Reefact/first-class-errors/blob/main/doc/handwritten/for-users/CatalogVersioning.en.md), +[command reference](https://github.com/Reefact/first-class-errors/blob/main/doc/handwritten/for-users/CatalogVersioningReference.en.md), +[CI/CD integration](https://github.com/Reefact/first-class-errors/blob/main/doc/handwritten/for-users/CatalogVersioningCI.en.md) Run `fce --help` or `fce generate --help` for the full option list. diff --git a/FirstClassErrors.Cli/RendererAddCommand.cs b/FirstClassErrors.Cli/RendererAddCommand.cs index eec95c21..b0631ddd 100644 --- a/FirstClassErrors.Cli/RendererAddCommand.cs +++ b/FirstClassErrors.Cli/RendererAddCommand.cs @@ -12,7 +12,7 @@ namespace FirstClassErrors.Cli; -/// Settings for fce renderer add <path>. +/// Settings for fce config renderer add <path>. internal sealed class RendererReferenceSettings : ConfigScopedSettings { [CommandArgument(0, "")] diff --git a/FirstClassErrors.Cli/RendererRemoveCommand.cs b/FirstClassErrors.Cli/RendererRemoveCommand.cs index 9f20408a..850c2f8e 100644 --- a/FirstClassErrors.Cli/RendererRemoveCommand.cs +++ b/FirstClassErrors.Cli/RendererRemoveCommand.cs @@ -16,7 +16,7 @@ protected override int Execute(CommandContext context, RendererReferenceSettings string path = ConfigurationStore.Resolve(settings.ConfigPath); if (!ConfigurationStore.Exists(path)) { - Console.Error.WriteLine($"error: no configuration at '{path}'. Run 'fce init' first."); + Console.Error.WriteLine($"error: no configuration at '{path}'. Run 'fce config init' first."); return 1; } From 0d1089188d620e84a5b89def094f5fe36ed17505 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 21 Jul 2026 21:36:17 +0000 Subject: [PATCH 3/4] docs(binder,core): mark not-yet-published install commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FirstClassErrors.RequestBinder and FirstClassErrors.Cli are not on nuget.org yet (only the core library and Testing shipped 0.1.0-preview.1), so 'dotnet add package ...RequestBinder' and 'dotnet tool install ...Cli' fail for anyone following the docs. Add a short "not yet on nuget.org" note next to every such command on the surfaces visible today — the repo README, the getting-started, request-binder and operational-integration guides, and the core package README — keeping the French translations in sync. The CLI's own package README is left unchanged: it is only visible on nuget.org once the CLI is published, at which point its install command is already valid. Removing these notes is a step for the release that ships these packages. Refs: #230 Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01773Snh91KveG8SCwPQ9mtB --- FirstClassErrors/README.nuget.md | 2 ++ README.md | 4 ++++ doc/handwritten/for-users/GettingStarted.en.md | 2 ++ doc/handwritten/for-users/GettingStarted.fr.md | 2 ++ doc/handwritten/for-users/OperationalIntegration.en.md | 2 ++ doc/handwritten/for-users/OperationalIntegration.fr.md | 2 ++ doc/handwritten/for-users/README.fr.md | 4 ++++ doc/handwritten/for-users/RequestBinder.en.md | 2 ++ doc/handwritten/for-users/RequestBinder.fr.md | 2 ++ 9 files changed, 22 insertions(+) diff --git a/FirstClassErrors/README.nuget.md b/FirstClassErrors/README.nuget.md index 9156e6ce..ad75f4f5 100644 --- a/FirstClassErrors/README.nuget.md +++ b/FirstClassErrors/README.nuget.md @@ -73,6 +73,8 @@ The documentation generator ships as a .NET tool, [`FirstClassErrors.Cli`](https://www.nuget.org/packages/FirstClassErrors.Cli), so a pipeline can produce the error catalog as a build artifact: +> **Not yet on nuget.org.** `FirstClassErrors.Cli` is not published yet — the commands below will work once it ships. + dotnet tool install --global FirstClassErrors.Cli dotnet build MyApp.sln -c Release fce generate --solution MyApp.sln --no-build \ diff --git a/README.md b/README.md index e6c1ba7a..073f6cf3 100644 --- a/README.md +++ b/README.md @@ -118,12 +118,16 @@ To bind incoming requests into typed commands or queries at the primary-adapter dotnet add package FirstClassErrors.RequestBinder ``` +> **Not yet on nuget.org.** `FirstClassErrors.RequestBinder` is not published yet — the command above will work once it ships. + To generate documentation, install the CLI: ```bash dotnet tool install --global FirstClassErrors.Cli ``` +> **Not yet on nuget.org.** `FirstClassErrors.Cli` is not published yet — the command above will work once it ships. + Then follow the [Getting Started guide](doc/handwritten/for-users/GettingStarted.en.md) to create and generate your first documented error. ## 🎯 When it is a good fit diff --git a/doc/handwritten/for-users/GettingStarted.en.md b/doc/handwritten/for-users/GettingStarted.en.md index 02c54670..6f776cfc 100644 --- a/doc/handwritten/for-users/GettingStarted.en.md +++ b/doc/handwritten/for-users/GettingStarted.en.md @@ -27,6 +27,8 @@ Install the documentation CLI once on your machine: dotnet tool install --global FirstClassErrors.Cli ``` +> **Not yet on nuget.org.** `FirstClassErrors.Cli` is not published yet — the command above will work once it ships. + ## 2. Opt the project into documentation generation Add this property directly to the project file that contains your errors: diff --git a/doc/handwritten/for-users/GettingStarted.fr.md b/doc/handwritten/for-users/GettingStarted.fr.md index 2af2c6c2..75b06d58 100644 --- a/doc/handwritten/for-users/GettingStarted.fr.md +++ b/doc/handwritten/for-users/GettingStarted.fr.md @@ -27,6 +27,8 @@ Installez une fois le CLI de documentation sur votre machine : dotnet tool install --global FirstClassErrors.Cli ``` +> **Pas encore sur nuget.org.** `FirstClassErrors.Cli` n'est pas encore publié — la commande ci-dessus fonctionnera une fois qu'il sera disponible. + ## 2. Activer la génération pour le projet Ajoutez cette propriété directement dans le fichier du projet qui contient vos erreurs : diff --git a/doc/handwritten/for-users/OperationalIntegration.en.md b/doc/handwritten/for-users/OperationalIntegration.en.md index fc756252..25b89bad 100644 --- a/doc/handwritten/for-users/OperationalIntegration.en.md +++ b/doc/handwritten/for-users/OperationalIntegration.en.md @@ -44,6 +44,8 @@ For ambiguous declarations, project discovery, and worker behavior, see [Archite Install the CLI, build, then generate from the existing binaries: +> **Not yet on nuget.org.** The `fce` CLI (`FirstClassErrors.Cli`) is not published yet — the install commands on this page (here and in the CI example below) will work once it ships. + ```bash dotnet tool install --global FirstClassErrors.Cli dotnet build MyApp.sln -c Release diff --git a/doc/handwritten/for-users/OperationalIntegration.fr.md b/doc/handwritten/for-users/OperationalIntegration.fr.md index 4e00a13b..9457dae8 100644 --- a/doc/handwritten/for-users/OperationalIntegration.fr.md +++ b/doc/handwritten/for-users/OperationalIntegration.fr.md @@ -44,6 +44,8 @@ Pour les déclarations ambiguës, la découverte de projets et le fonctionnement Installez le CLI, compilez, puis générez depuis les binaires existants : +> **Pas encore sur nuget.org.** Le CLI `fce` (`FirstClassErrors.Cli`) n'est pas encore publié — les commandes d'installation de cette page (ici et dans l'exemple CI ci-dessous) fonctionneront une fois qu'il sera disponible. + ```bash dotnet tool install --global FirstClassErrors.Cli dotnet build MyApp.sln -c Release diff --git a/doc/handwritten/for-users/README.fr.md b/doc/handwritten/for-users/README.fr.md index 1f016da6..d89309ca 100644 --- a/doc/handwritten/for-users/README.fr.md +++ b/doc/handwritten/for-users/README.fr.md @@ -118,12 +118,16 @@ Pour lier les requêtes entrantes en commandes ou requêtes typées à la fronti dotnet add package FirstClassErrors.RequestBinder ``` +> **Pas encore sur nuget.org.** `FirstClassErrors.RequestBinder` n'est pas encore publié — la commande ci-dessus fonctionnera une fois qu'il sera disponible. + Pour générer la documentation, installez le CLI : ```bash dotnet tool install --global FirstClassErrors.Cli ``` +> **Pas encore sur nuget.org.** `FirstClassErrors.Cli` n'est pas encore publié — la commande ci-dessus fonctionnera une fois qu'il sera disponible. + Suivez ensuite le guide [Premiers pas](GettingStarted.fr.md) pour créer et générer votre première erreur documentée. ## 🎯 Quand la bibliothèque est adaptée diff --git a/doc/handwritten/for-users/RequestBinder.en.md b/doc/handwritten/for-users/RequestBinder.en.md index ca954173..04331288 100644 --- a/doc/handwritten/for-users/RequestBinder.en.md +++ b/doc/handwritten/for-users/RequestBinder.en.md @@ -42,6 +42,8 @@ single envelope error carrying all of them. dotnet add package FirstClassErrors.RequestBinder ``` +> **Not yet on nuget.org.** `FirstClassErrors.RequestBinder` is not published yet — the command above will work once it ships. + It targets **.NET Standard 2.0** and ships on the same release train as `FirstClassErrors`, at the same version — so the two always resolve together. diff --git a/doc/handwritten/for-users/RequestBinder.fr.md b/doc/handwritten/for-users/RequestBinder.fr.md index ae494417..a736fe63 100644 --- a/doc/handwritten/for-users/RequestBinder.fr.md +++ b/doc/handwritten/for-users/RequestBinder.fr.md @@ -44,6 +44,8 @@ unique qui les porte toutes. dotnet add package FirstClassErrors.RequestBinder ``` +> **Pas encore sur nuget.org.** `FirstClassErrors.RequestBinder` n'est pas encore publié — la commande ci-dessus fonctionnera une fois qu'il sera disponible. + Il cible **.NET Standard 2.0** et voyage sur le même train de release que `FirstClassErrors`, à la même version — les deux se résolvent donc toujours ensemble. From e8cdb19fc8291d4c16bc8831fc23656c20dc0a1c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 21 Jul 2026 21:41:34 +0000 Subject: [PATCH 4/4] ci(analyzers): guard the advertised analyzer count MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The package README's flagship bullet ("N Roslyn analyzers in the box (FCE001-FCENNN)") is hand-maintained and drifted: it sat at 16 (FCE001-FCE016) long after the code reached 22 (FCE001-FCE022). A count a human retypes on every rule change is a count that rots. Add tools/analyzer-count-check/check-analyzer-count.sh: it derives the count and the low/high ids from FirstClassErrors.Analyzers/DiagnosticIds.cs (the source of truth) and fails if the README's number or either range endpoint disagrees. Numbering is a stable handle, not a contiguous contract, so the count is the number of declared ids and the range is their numeric min-max — a retired id in the middle keeps the check correct. Pure POSIX shell over two tracked files, no toolchain. Wire it as a third job in the analyzers workflow so the guard runs on every push and pull request touching main. Refs: #230 Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01773Snh91KveG8SCwPQ9mtB --- .github/workflows/analyzers.yml | 16 ++++ .../check-analyzer-count.sh | 76 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100755 tools/analyzer-count-check/check-analyzer-count.sh diff --git a/.github/workflows/analyzers.yml b/.github/workflows/analyzers.yml index f624671c..303d4102 100644 --- a/.github/workflows/analyzers.yml +++ b/.github/workflows/analyzers.yml @@ -106,3 +106,19 @@ jobs: # does not depend on the trailing diagnostic-id list, which is a Roslyn-version formatting detail. working-directory: tools/floor-check run: grep -qE 'FirstClassErrors\.Analyzers\.[A-Za-z]+Analyzer' build.log + + readme-count: + name: Guard the advertised analyzer count + runs-on: ubuntu-latest + # Reads two tracked files with a POSIX shell; seconds. Cap a stuck runner. + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + + # Derive the count and FCExxx range from DiagnosticIds.cs (the source of + # truth) and fail if the package README's flagship "N Roslyn analyzers in + # the box (FCE001-FCENNN)" bullet disagrees — the claim that drifted to a + # stale 16 while the code shipped 22. + - name: Check the package README count against DiagnosticIds.cs + run: tools/analyzer-count-check/check-analyzer-count.sh diff --git a/tools/analyzer-count-check/check-analyzer-count.sh b/tools/analyzer-count-check/check-analyzer-count.sh new file mode 100755 index 00000000..01da5364 --- /dev/null +++ b/tools/analyzer-count-check/check-analyzer-count.sh @@ -0,0 +1,76 @@ +#!/bin/sh +# Guard the analyzer count advertised in the package README against the real set +# of diagnostics, so the storefront claim can never silently drift again (it did: +# the bullet sat at "16 (FCE001-FCE016)" long after the code reached FCE022). +# +# Source of truth: FirstClassErrors.Analyzers/DiagnosticIds.cs — every FCExxx +# constant it declares is one shipped diagnostic. The flagship bullet in +# FirstClassErrors/README.nuget.md advertises both a count and a range: +# +# **22 Roslyn analyzers in the box (`FCE001`-`FCE022`).** +# +# This check derives the count and the low/high ids from DiagnosticIds.cs and +# fails if the README's number or either range endpoint disagrees. Numbering is +# a stable handle, not a contiguous contract (see the DiagnosticIds.cs summary), +# so the count is the number of declared ids and the range is their numeric +# min-max — a retired id in the middle keeps this correct. +# +# Usage: tools/analyzer-count-check/check-analyzer-count.sh +# Exit: 0 = README matches the code, 1 = drift found, 2 = usage/parse error. +# +# Runs in the CI check (.github/workflows/analyzers.yml). It reads only two +# tracked files and needs no toolchain, so it runs anywhere a POSIX shell does. + +set -u + +# --- locate the repo files, independent of the caller's CWD ------------------- +script_dir=$(cd "$(dirname "$0")" && pwd) +root=$(cd "$script_dir/../.." && pwd) +ids_file="$root/FirstClassErrors.Analyzers/DiagnosticIds.cs" +readme="$root/FirstClassErrors/README.nuget.md" + +fail() { printf 'analyzer-count-check: %s\n' "$1" >&2; exit "${2:-1}"; } + +[ -f "$ids_file" ] || fail "diagnostic ids not found: $ids_file" 2 +[ -f "$readme" ] || fail "package README not found: $readme" 2 + +# --- source of truth: the declared FCExxx ids -------------------------------- +ids=$(grep -oE '"FCE[0-9]+"' "$ids_file" | tr -d '"' | sort -u) +[ -n "$ids" ] || fail "no FCExxx constants found in $ids_file" 2 + +count=$(printf '%s\n' "$ids" | grep -c .) +low_num=$(printf '%s\n' "$ids" | sed -E 's/^FCE0*//' | sort -n | head -1) +high_num=$(printf '%s\n' "$ids" | sed -E 's/^FCE0*//' | sort -n | tail -1) +low=$(printf 'FCE%03d' "$low_num") +high=$(printf 'FCE%03d' "$high_num") + +# --- the claim advertised in the README -------------------------------------- +claim=$(grep -E 'Roslyn analyzers in the box' "$readme" | head -1) +[ -n "$claim" ] || fail "no 'N Roslyn analyzers in the box (...)' claim found in $readme" 2 + +claimed_count=$(printf '%s\n' "$claim" | grep -oE '[0-9]+ Roslyn analyzers in the box' | grep -oE '^[0-9]+') +claimed_codes=$(printf '%s\n' "$claim" | grep -oE 'FCE[0-9]+') +claimed_low=$(printf '%s\n' "$claimed_codes" | head -1) +claimed_high=$(printf '%s\n' "$claimed_codes" | tail -1) + +# --- compare ----------------------------------------------------------------- +status=0 +if [ "${claimed_count:-}" != "$count" ]; then + printf 'analyzer-count-check: README says %s analyzers, DiagnosticIds.cs declares %s\n' \ + "${claimed_count:-?}" "$count" >&2 + status=1 +fi +if [ "${claimed_low:-}" != "$low" ] || [ "${claimed_high:-}" != "$high" ]; then + printf 'analyzer-count-check: README range (%s-%s) does not match the code range (%s-%s)\n' \ + "${claimed_low:-?}" "${claimed_high:-?}" "$low" "$high" >&2 + status=1 +fi + +if [ "$status" -ne 0 ]; then + printf '\nUpdate the bullet in FirstClassErrors/README.nuget.md to read:\n' >&2 + printf ' **%s Roslyn analyzers in the box (`%s`-`%s`).**\n' "$count" "$low" "$high" >&2 + exit 1 +fi + +printf 'analyzer-count-check: OK - %s analyzers (%s-%s), README matches DiagnosticIds.cs\n' \ + "$count" "$low" "$high"