Skip to content

Publish fce as a NuGet tool and split lib/cli release trains#107

Merged
Reefact merged 9 commits into
mainfrom
claude/first-class-errors-nuget-sr1aiq
Jul 13, 2026
Merged

Publish fce as a NuGet tool and split lib/cli release trains#107
Reefact merged 9 commits into
mainfrom
claude/first-class-errors-nuget-sr1aiq

Conversation

@Reefact

@Reefact Reefact commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Summary

Restructure the release process so the library (FirstClassErrors + FirstClassErrors.Testing) and the CLI tool (FirstClassErrors.Cli) version and release independently — each with its own cadence and version, separate Git tags (lib-v*.*.* / cli-v*.*.*), train-scoped release notes, and selective packaging — and refresh the NuGet presentation.

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Refactoring
  • Analyzer / diagnostic change
  • Tests
  • Documentation
  • Build / CI / tooling

Changes

  • FirstClassErrors.Cli.csproj — packaged as a .NET tool (PackAsTool, ToolCommandName fce, PackageId, description, tags, links, icon, README, embedded SBOM). The fce documentation generator is now a publishable package; the GenDoc worker it spawns travels bundled inside the tool.
  • release.yml — accepts a component input and parses train-prefixed tags (lib-v*.*.* / cli-v*.*.*), resolves the train and version, and packs only that train (combined with the existing pre-release-flag handling).
  • release-dryrun.yml — full-history checkout (fetch-depth: 0); rehearses both trains' packaging and note generation so neither path first runs in production.
  • tools/packaging/pack.sh — takes a lib/cli scope and packs only that train's projects.
  • tools/packaging/release-notes.sh (new) — train-scoped release notes filtered by Conventional Commit scope (core,analyzers,testing for lib; cli,gendoc for cli), so one train's notes never list the other's commits.
  • FirstClassErrors/README.nuget.md — rewritten to surface concrete features (errors as values or exceptions, the two-audience message model, structured diagnostics, living documentation, the 16 bundled analyzers, zero runtime dependencies).
  • FirstClassErrors.Cli/README.nuget.md (new) — NuGet page for the fce tool: install, usage, CI integration, and a compatibility note.
  • DocumentationContractVersionAttribute.cs (+ test) — internal assembly marker stamping the documentation-contract version (1), independent of the library's SemVer, so a future fce can detect an unsupported contract; an unmarked assembly reads as version 1.
  • doc/OperationalIntegration.en.md / .fr.md — document installing and running fce in CI; French kept in sync.

Testing

Run locally on this branch (Release), and re-run by CI on this PR:

  • dotnet build FirstClassErrors.sln — 0 warnings, 0 errors
  • dotnet test FirstClassErrors.sln — 609 passed, 0 failed
  • Analyzer tests pass (FirstClassErrors.Analyzers.UnitTests) — 51 passed

Also verified: pack.sh lib / pack.sh cli produce their packages with the SBOM embedded; train-scoped release-notes isolation; and the packaged fce tool, installed from a local feed, generates an error catalog end to end.

Documentation

  • Public API / error documentation updated
  • README / doc/ updated
  • French translation (doc/README.fr.md) updated if user-facing behavior changed
  • No documentation change required

DocumentationContractVersionAttribute is internal — no public API change. French was kept in sync where it applies (doc/OperationalIntegration.fr.md, alongside its English counterpart); doc/README.fr.md and the root README.md were not changed — only the NuGet-package READMEs, which have no French variant.

Related issues

None — this arose from a working session, not a tracked issue.

https://claude.ai/code/session_01JejR9K9ABeQ8LqB5fwGeM2

claude added 6 commits July 12, 2026 13:41
Make FirstClassErrors.Cli publishable as a .NET tool (PackAsTool,
ToolCommandName "fce", PackageId FirstClassErrors.Cli) so the error-documentation
generator can be installed with `dotnet tool install` and run in CI, instead of
requiring consumers to build it from source.

The GenDoc worker the tool spawns as a separate process is bundled inside the
tool package (tools/<tfm>/any) by the existing copy targets and resolved at
runtime from AppContext.BaseDirectory; a packed tool was installed from a local
feed and `fce generate` produced a catalog end to end. The package embeds the
same SPDX SBOM as the two library packages, so pack.sh packs three projects now
and its SBOM-present assertion covers the tool too; the release.yml comment is
updated to match. No symbol package is produced for the tool (IncludeSymbols
overridden): a .snupkg serves referenced libraries, not an executed tool.

Publishing requires a nuget.org trusted-publishing policy for the new package id
FirstClassErrors.Cli, as already required per published package.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JejR9K9ABeQ8LqB5fwGeM2
The NuGet README led with the concept but understated what the package
concretely delivers. Surface, honestly and without hype, the features that
sell it: errors as values or exceptions, the two-audience message model that
cannot leak internals (mapped onto RFC 9457), the 16 bundled Roslyn analyzers,
zero runtime dependencies on .NET Standard 2.0, and the companion Testing
package.

Give the CI/CD documentation generation its own section now that the generator
ships as the installable `fce` tool, with a concrete install-build-generate
snippet so the "documentation as a build artifact" story is turnkey rather than
a single passing mention.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JejR9K9ABeQ8LqB5fwGeM2
The CI/CD guide told readers to run `fce generate` without saying how to obtain
the generator. Now that it ships as the FirstClassErrors.Cli tool, add the
`dotnet tool install` step and a concrete install-build-generate snippet, so the
pipeline it describes is reproducible. Mirrored in the French translation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JejR9K9ABeQ8LqB5fwGeM2
Version and release the fce CLI independently of the library. release.yml now
triggers on two tag families — lib-v* (FirstClassErrors + FirstClassErrors.Testing,
in lockstep) and cli-v* (FirstClassErrors.Cli) — resolves the train and version
from the tag prefix (or the workflow_dispatch component input) and packs only that
train, so a lib release never republishes the CLI and vice versa.

pack.sh takes a lib|cli scope selecting which projects to pack. Release notes are
generated per train from Conventional Commit scopes (release-notes.sh: core,
analyzers, testing for lib; cli, gendoc for cli), replacing --generate-notes so
one train's notes never carry the other's commits; unscoped infrastructure commits
are excluded. Checkout fetches full history and tags for that lookup, and the
dry-run rehearses both trains' packing and note generation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JejR9K9ABeQ8LqB5fwGeM2
fce is versioned independently of the library, so its NuGet page needs to state the
relationship: it reads the FirstClassErrors documentation contract, a minor fce release
adds support for a new contract while a major one changes fce's own surface, and at run
time it binds to the target's own FirstClassErrors version. Set expectations for the 0.x
preview (keep fce and the library on the same preview line) and pre-announce the 1.0
behaviour where fce declares the contract versions it supports.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JejR9K9ABeQ8LqB5fwGeM2
Stamp the library assembly with an internal DocumentationContractVersion (starting at 1):
the version of the shape fce reads by reflection — the [ProvidesErrorsFor] / [DocumentedBy]
attributes, the DescribeError DSL, and the documentation model. It is bumped only when a
change breaks that contract, independently of the library's SemVer.

Every library published from now on carries the marker, so a future fce can identify the
contract a target was built against, and treat an unmarked assembly as version 1, without
relying on the library's package version — which the CLI's decoupled build does not
preserve on its bundled copy. The attribute is internal diagnostic metadata for the
tooling, not public API, and inert until the generator's compatibility gate reads it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JejR9K9ABeQ8LqB5fwGeM2

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 47203b9d06

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/release.yml Outdated
claude added 2 commits July 13, 2026 08:19
…rors-nuget-sr1aiq

# Conflicts:
#	.github/workflows/release.yml
release-notes.sh ran `git log` against the tag being released. On a workflow_dispatch
publish the tag does not exist yet — gh release create makes it after the notes are
built — so git log failed with "ambiguous argument" and the manual release path never
reached release creation. A tag push was unaffected: the tag already exists.

Take an optional end-ref (the commit) as the range's upper bound, defaulting to the tag
so the dry-run's two-argument calls keep working; release.yml now passes $GITHUB_SHA. The
tag name still serves only to exclude the tag being created from the previous-same-train
tag lookup, so it need not resolve as a ref.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JejR9K9ABeQ8LqB5fwGeM2
@Reefact Reefact changed the title feat(cli,gendoc): introduce independent versioning and release trains for lib and cli Publish fce as a NuGet tool and split lib/cli release trains Jul 13, 2026
@Reefact Reefact enabled auto-merge July 13, 2026 08:39
@Reefact Reefact merged commit f0f4da2 into main Jul 13, 2026
11 checks passed
@Reefact Reefact deleted the claude/first-class-errors-nuget-sr1aiq branch July 13, 2026 08:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants