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/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;
}
diff --git a/FirstClassErrors/README.nuget.md b/FirstClassErrors/README.nuget.md
index 8cec5349..ad75f4f5 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.
@@ -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.
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"