Skip to content

feat: nuget package distribution + sample-based ruleset extraction (PLAN-0006)#9

Merged
ncipollina merged 14 commits into
mainfrom
feat/plan-0006-nuget-package-distribution
Jul 21, 2026
Merged

feat: nuget package distribution + sample-based ruleset extraction (PLAN-0006)#9
ncipollina merged 14 commits into
mainfrom
feat/plan-0006-nuget-package-distribution

Conversation

@ncipollina

Copy link
Copy Markdown
Contributor

Summary

Implements PLAN-0006/ADR-0006: sharp-mud now ships as a set of SharpMud.* NuGet packages (Engine, Hosting, Persistence + .Sqlite/.DynamoDb providers, Adapters.Cli/.Telnet, plus a SharpMud meta-package) rather than a single monolithic solution with one hardcoded ruleset.

  • Repository reorganized: src/SharpMud.Host split into SharpMud.Hosting (ruleset-agnostic generic-host composition helpers: WorldContext, IWorldBuilder, IPlayerFactory, SessionLoop/LoginFlow, AddSharpMud* DI extensions) and samples/SharpMud.Samples.Classic (the D&D-flavored reference consumer + composition root, merged from src/SharpMud.Ruleset.Classic + src/SharpMud.Host).
  • SharpMud.Persistence split into a provider-agnostic core plus SharpMud.Persistence.Sqlite/SharpMud.Persistence.DynamoDb provider packages.
  • Packaging metadata (Directory.Build.props, LICENSE, icon.png, NOTICE.md), the SharpMud meta-package, and publish-preview.yaml/publish-release.yaml CI workflows.
  • docsite/ — a Zensical + uv GitHub Pages skeleton with a real Getting Started walkthrough, matching dynamodb-efcore-provider's established shape.
  • Every subsystem doc, README.md, SPEC.md, and the Dockerfile updated off the old SharpMud.Host/SharpMud.Ruleset.Classic paths.

Test plan

  • dotnet build SharpMud.slnx — clean, 0 warnings, both net10.0/net11.0
  • dotnet test SharpMud.slnx — 107/107 passing
  • dotnet pack SharpMud.slnx — produces all 7 expected packages, correct meta-package nuspec dependencies, no sample/test projects packed
  • Real consumer smoke test: a throwaway project referencing the packed nupkgs (not ProjectReference) built and ran cleanly over both SharpMud.Adapters.Cli (login-free single-player) and SharpMud.Adapters.Telnet (real IAC negotiation + username/password login flow), including a clean SIGTERM shutdown-save
  • docsite/ builds clean via uv synczensical build, matching the CI workflow's own command
  • Enable GitHub Pages (Pages source: GitHub Actions) in repo settings — manual step, not done here

🤖 Generated with Claude Code

ncipollina and others added 5 commits July 20, 2026 12:13
…-0006

New SharpMud.Hosting package: SharpMudApplicationBuilder/SharpMudApplication
wrapping HostApplicationBuilder/IHost, WorldContext/IWorldBuilder/
WorldLoaderHostedService (resolves the previously-open "world-builder
registration point" question - a consumer-supplied IWorldBuilder loads a
persisted world or builds+saves a fresh one during startup, before any
transport starts accepting connections), GameLoopHostedService (wraps
GameLoop as a BackgroundService rather than giving GameLoop itself a
Hosting dependency, and auto-registers every DI-resolved ITickable),
ShutdownSaveHostedService (the previously-unowned shutdown-time whole-world
save), IPlayerFactory (lets LoginFlow/PlayerLogin stay ruleset-agnostic
without blowing the 4-parameter limit - they're now constructor-injected
services instead of static classes taking a 5th parameter), and
AddSharpMudRuleset (registers BuiltinCommands before a consumer's ruleset
callback runs).

SessionLoop/LoginFlow/PlayerLogin converted from static classes taking
World/parser/registry/repository as method parameters to
constructor-injected service classes - SessionLoop alone was already past
coding-standards.md's 4-parameter limit before this change.

src/SharpMud.Host deleted. HostOptions trimmed to DbPath only (transport
selection moves to the consumer's own Program.cs, not yet updated in this
commit). HostRunner.cs moved into SharpMud.Adapters.Telnet as a starting
point for AddSharpMudTelnetTransport (not yet rewritten). Ruleset.Classic +
Program.cs/appsettings.json/HubWorldBuilder.cs moved into
samples/SharpMud.Samples.Classic (csproj/Program.cs rewrite not yet done -
next commit).

Test coverage: SessionLoopTests/LoginFlowTests/HostOptionsTests/
PasswordHashingTests moved and updated for the new constructor-injected
shape, plus new PlayerLoginTests. 16/16 passing.

Per PLAN-0006 (docs/adr/0006-nuget-package-distribution.md).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…, finish sample rewrite

SharpMud.Persistence core drops its SQLite PackageReferences - genuinely
provider-agnostic now, GameDbContext already used DbContextOptions<T>
generically with no hardcoded provider. New SharpMud.Persistence.Sqlite
(AddSharpMudSqlitePersistence) and SharpMud.Persistence.DynamoDb
(AddSharpMudDynamoDbPersistence, EntityFrameworkCore.DynamoDb 10.0.0).

Resolved the TPH/discriminator open question from PLAN-0006 by reading the
DynamoDB provider's own docs directly: TPH is the supported (only
supported) inheritance strategy, discriminators auto-configured by
default - BehaviorConfiguration's HasDiscriminator<string>("BehaviorType")
usage is compatible in principle, though the exact fluent-API interop
isn't verified against a live table.

Multi-targeted every packaged src project to net10.0;net11.0 (verified
clean - no net11.0-only API usage anywhere), since Persistence.DynamoDb
needed net10.0 support to even reference Engine/Persistence, and the ADR
already called for this. Persistence.DynamoDb itself stays net10.0-only,
matching the provider's own target.

Added a new IStorageInitializer hook in SharpMud.Hosting and wired it into
WorldLoaderHostedService - SQLite's EnsureCreatedAsync schema step needs
to run before world loading, but hosted-service StartAsync ordering across
independently-registered Hosting/Persistence packages would be fragile to
rely on directly.

Found and fixed a real startup-ordering bug: the generic host constructs
every registered IHostedService (resolving IEnumerable<IHostedService>)
before calling any of their StartAsync methods - so GameLoopHostedService/
the transport BackgroundServices constructor-injecting ITickable/
LoginFlow/SessionLoop directly would eagerly resolve WorldContext-dependent
services before WorldLoaderHostedService.StartAsync had populated it.
Fixed by deferring resolution to ExecuteAsync via IServiceProvider - the
legitimate service-locator exception in coding-standards.md.

Renamed HostOptions to SharpMudHostOptions - collided with the real
Microsoft.Extensions.Hosting.HostOptions BCL type every consumer of this
package would also have in scope.

Finished samples/SharpMud.Samples.Classic/Program.cs against
SharpMud.Hosting's builder: ClassicWorldBuilder/ClassicPlayerFactory
implement Hosting's extension points, CombatManager registered as both
ICombatManager and ITickable, SHARPMUD_MODE/--telnet parsing preserved as
the sample's own composition-root decision. csproj gained OutputType=Exe
and the references/packages Program.cs needs.

Full solution builds clean (net10.0 + net11.0) and all 107 tests pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Manual verification caught a real behavior regression: under the old
procedural Program.cs, the CLI branch's SessionLoop.RunAsync returning
meant the whole process fell through to shutdown. Under IHost,
CliTransportBackgroundService.ExecuteAsync returning doesn't stop
anything else - the host just kept running GameLoop forever, waiting for
a SIGINT/SIGTERM a local CLI user has no reason to send.

CliTransportBackgroundService now calls IHostApplicationLifetime
.StopApplication() once its single session ends. Telnet's
BackgroundService deliberately does not do this - many concurrent
sessions, one ending must not stop the host.

Verified end-to-end: CLI mode (look/move/quit, persists across runs,
exits cleanly on quit), Telnet mode (real TCP connection - IAC
negotiation, username/password character creation, look/quit, host stays
alive after the client disconnects), and graceful SIGTERM shutdown
(process exits promptly, world data actually lands on disk - confirmed
via direct sqlite3 query).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…nd subsystem doc corrections for PLAN-0006

Completes PLAN-0006: Directory.Build.props/LICENSE/icon/meta-package/
publish-preview+release workflows for the SharpMud.* NuGet packages;
docsite/ (Zensical+uv) GitHub Pages skeleton with a real Getting Started
walkthrough; every subsystem doc updated off the old SharpMud.Host/
Ruleset.Classic paths. Verified via dotnet pack + a real consumer project
built against the packed nupkgs over both CLI and Telnet transports,
which caught and fixed a doc bug (CLI is login-free, not the login flow
the doc originally claimed).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@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: 7a3814f77e

ℹ️ 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 samples/SharpMud.Samples.Classic/Program.cs Outdated
Comment thread docsite/docs/getting-started.md Outdated
@ncipollina ncipollina changed the title PLAN-0006: NuGet package distribution + sample-based ruleset extraction feat: NuGet package distribution + sample-based ruleset extraction (PLAN-0006) Jul 20, 2026
@github-actions github-actions Bot added the type: feat New feature label Jul 20, 2026
…, fix Codex-flagged getting-started sample

- Sample no longer explicitly references/loads Microsoft.Extensions.Configuration.Json/
  .EnvironmentVariables - Microsoft.Extensions.Hosting already pulls these in and
  Host.CreateApplicationBuilder wires them up by default.
- Bump global.json, Directory.Packages.props' net11.0 group, Dockerfile, and
  docs/deployment.md from preview.5.26302.115 to preview.6.26359.118 (matching the
  installed SDK); bump Microsoft.SourceLink.GitHub to the matching preview.6 release.
  Left EntityFrameworkCore.DynamoDb on the stable 10.0.0 line - its only net11.0
  build still depends on EF Core preview.5, one behind ours - and updated the
  comment to record why.
- Fix Codex review feedback on PR #9: docsite/docs/getting-started.md's sample
  IWorldBuilder used ThingId.New() for its root id, which changes every process
  start and defeats reload-from-persistence; switch to a fixed Guid like the real
  HubWorldBuilder does.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ncipollina ncipollina changed the title feat: NuGet package distribution + sample-based ruleset extraction (PLAN-0006) feat: nuget package distribution + sample-based ruleset extraction (PLAN-0006) Jul 20, 2026
@ncipollina

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. What shall we delve into next?

Reviewed commit: ad3213c1d3

ℹ️ 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".

…cs site

No SemVer compatibility guarantees yet and only prerelease packages are
published so far - make that explicit before someone builds against it
expecting stability. Also fixes getting-started.md's install commands,
which were missing --prerelease (no stable release exists to resolve
without it).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment thread src/SharpMud.Hosting/ServiceCollectionExtensions.cs
Comment thread Directory.Build.props
Comment thread Directory.Packages.props
Comment thread docs/persistence.md Outdated
Comment thread docsite/docs/getting-started.md
…riptions, warning-clean build, DB path CLI arg, narrow meta-package scope

- Fix shutdown-save stop-order race: GameLoopHostedService now registered
  after ShutdownSaveHostedService, so the generic host's reverse-order stop
  sequence quiesces the tick loop before the snapshot save runs, not
  concurrently with it. New regression test verifies this ordering directly
  and was confirmed to fail against the old ordering.
- Add a real Description to every SharpMud.* package's csproj (only the
  meta-package had one before).
- Pin NSubstitute back to 5.3.0 (AutoFixture.AutoNSubstitute 4.18.1's <6.0.0
  constraint - only unreleased AutoFixture previews support 6.x), which also
  resolved two nullable warnings NSubstitute 6.0's newly-annotated CallInfo
  indexer introduced in SessionLoopTests.cs. Suppress the pre-existing
  xUnit1051 false-positive in TelnetListenerTests.cs via pragma - the token
  genuinely derives from TestContext.Current.CancellationToken through a
  linked source the analyzer can't trace. Solution now builds with 0
  warnings.
- Add --db-path CLI arg support (wins over SHARPMUD_DB_PATH, same
  precedence as --telnet), resolved in the sample's own Program.cs matching
  the transport-selection precedent, rather than just downgrading
  docs/persistence.md to describe a CLI-arg gap that didn't need to exist.
- ADR-0007: narrow the SharpMud meta-package to Engine+Hosting+Persistence
  only, dropping the persistence-provider/adapter ProjectReferences ADR-0006
  originally included. Every consumer was getting an unused DynamoDB SDK
  dependency and Telnet listener by default, and it made
  getting-started.md's "you still pick a provider/transport explicitly"
  framing false. Verified via a real dotnet pack - the meta-package's
  nuspec now only depends on SharpMud.Engine/.Hosting/.Persistence.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ncipollina

Copy link
Copy Markdown
Contributor Author

@codex review

Comment thread samples/SharpMud.Samples.Classic/Program.cs Outdated
Comment thread docsite/docs/index.md Outdated
Comment thread docs/architecture.md Outdated
Comment thread docs/plans/0006-nuget-package-distribution.md
Comment thread docs/networking.md

@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: 604d73bb65

ℹ️ 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 samples/SharpMud.Samples.Classic/Program.cs Outdated
…s, stale docs, IStorageInitializer dependency direction

- Fix --telnet/--db-path CLI arg parsing: both were positional patterns
  requiring their own flag to be args[0], so combining them silently
  dropped whichever came second. Switched both to Array.IndexOf-based
  lookups so they work in any order/combination. Verified live with both
  orderings over real Telnet.
- Move IStorageInitializer from SharpMud.Hosting to SharpMud.Engine.Core,
  alongside IThingRepository - it's a pure persistence-lifecycle
  abstraction with nothing Hosting-specific about it. This removes
  SharpMud.Persistence.Sqlite's reference to SharpMud.Hosting entirely,
  making the "Persistence.* -> Persistence -> Engine" dependency-direction
  rule in docs/architecture.md actually true instead of a documented
  exception.
- Update docsite/docs/index.md's meta-package description (still said "all
  of the above", stale since ADR-0007 narrowed it last round).
- Update docs/plans/0006-nuget-package-distribution.md's Open Questions
  section - 4 entries were still listed as unresolved (DynamoDB TPH
  support, IWorldBuilder registration shape, GameLoop hosting shape,
  docsite/ naming) despite all being decided/implemented.
- Update docs/networking.md - still described Telnet's old "Name:"-prompt
  placeholder instead of the real LoginFlow username/password flow that's
  been live since earlier in this PR.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ncipollina

Copy link
Copy Markdown
Contributor Author

@codex review

@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: c5a10bd090

ℹ️ 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 README.md Outdated
…architecture.md

Same class of gap as last round's docsite/docs/index.md fix - both
Solution layout ASCII trees still said the meta-package "pulls in every
SharpMud.* package", stale since ADR-0007 narrowed it to Engine+Hosting+
Persistence. Grepped the whole repo for the same phrasing to make sure
these were the last two.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment thread docs/deployment.md Outdated
Comment thread SPEC.md
Comment thread docs/plans/0006-nuget-package-distribution.md Outdated
Comment thread docs/architecture.md
Comment thread docs/architecture.md Outdated
ncipollina and others added 2 commits July 20, 2026 19:58
…ad of pinning below it

AutoFixture.AutoNSubstitute 4.18.1 (latest stable, Nov 2023) caps
NSubstitute below 6.0.0 - NuGet correctly flags NU1608 since we're outside
that stated range, but the integration itself has been validated against
6.0.0 by this repo's own test suite (108/108). Suppress the warning
directly rather than staying pinned to 5.3.0 - documented in
Directory.Build.props with the reasoning and a removal condition (once
AutoFixture publishes an updated dependency range).

Also fixes the two real nullable warnings NSubstitute 6.0's newly-annotated
CallInfo indexer surfaces in SessionLoopTests.cs - switched the manual
(Thing)x[0] cast to x.ArgAt<Thing>(0), NSubstitute's own null-safe typed
accessor, rather than suppressing those too.

The NU1608 suppression had to go in Directory.Build.props's unconditional
top-level PropertyGroup, not the existing IsTestProject-gated one - NU1608
is evaluated during restore, before IsTestProject (set by an
early-imported test-package .props file) is available, the same
restore-vs-project-body ordering gap already noted for IsPackable.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…LAN-0006, and architecture.md

- deployment.md: config table still described --telnet/port as positional
  args, stale since the order-independent Array.IndexOf fix last round.
- SPEC.md: roadmap still named SharpMud.Ruleset.Classic, deleted in this
  PR (renamed to SharpMud.Samples.Classic) - left alone earlier as
  "historical roadmap text", but unlike an ADR, SPEC.md isn't an immutable
  record, and this pointed at a project name that no longer exists at all.
- PLAN-0006: meta-package task entry still described the pre-ADR-0007
  "every SharpMud.* project" scope.
- architecture.md: solution layout was missing
  SharpMud.Adapters.Cli.Tests/.Telnet.Tests; dependency-direction rule
  implied Adapters.*/Persistence.* only reach Engine transitively through
  Hosting/Persistence, but both directly reference Engine too.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ncipollina

Copy link
Copy Markdown
Contributor Author

@codex review

Comment thread Directory.Build.props Outdated
Comment thread src/SharpMud.Persistence.DynamoDb/ServiceCollectionExtensions.cs Outdated
@ncipollina

Copy link
Copy Markdown
Contributor Author

⚠️ The PR body/test plan is now stale: it still says dotnet test is 107/107 and dotnet pack produces “all 7 expected packages,” but current validation is 108/108 and the package split produces 8 nupkgs including the SharpMud meta-package. Can you update the PR description so reviewers and the merge record match the current state?

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

Reviewed commit: b71f2afaf8

ℹ️ 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".

@ncipollina

Copy link
Copy Markdown
Contributor Author

⚠️ docs/plans/0006-nuget-package-distribution.md appears to overstate the implemented test coverage. The Test plan says there are unit tests for SharpMudApplicationBuilder/SharpMudApplication wiring and builtin command/IEnumerable<ITickable> auto-registration, but I only found the hosting-service-order regression test in SharpMud.Hosting.Tests. Can we either add that coverage or adjust the plan text so it reflects what is actually covered?

ncipollina and others added 2 commits July 20, 2026 20:26
…e, rename test project off the deleted Ruleset.Classic namespace

- Move the NU1608 (NSubstitute/AutoFixture.AutoNSubstitute) suppression out
  of the repo-root Directory.Build.props into a new tests/Directory.Build.props,
  per review: the repo-wide suppression would have hidden future
  package-range conflicts in src/ packages too. Directory-based discovery
  scopes this to tests/ without needing an IsTestProject condition at all -
  sidesteps the restore-vs-project-body evaluation-order gap that made the
  previous IsTestProject-gated attempt not actually apply at restore time.
  Verified empirically both ways before landing on this.
- Drop SharpMud.Persistence.DynamoDb's stale "no reference to SharpMud.Hosting"
  rationale for not registering IStorageInitializer - stale since that
  interface moved to SharpMud.Engine last round, which this project already
  references. The real reason (doesn't create/migrate tables) stands alone.
- Rename tests/SharpMud.Samples.Classic.Tests off the deleted
  SharpMud.Ruleset.Classic.Tests namespace (RootNamespace, global usings,
  and all 4 file namespace declarations) - a leftover from the original
  project move that never got renamed. Required adding explicit `using
  SharpMud.Ruleset.Classic;` to CombatManagerTests.cs/CombatResolverTests.cs,
  since they'd been implicitly resolving CombatManager/ICombatResolver/
  CombatantBehavior via C#'s enclosing-namespace lookup (the old test
  namespace was nested under the production one) rather than an explicit
  using - swept the rest of the repo for the same pattern and confirmed no
  other spots need it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…eset.Classic to SharpMud.Samples.Classic

The project/folder was renamed to SharpMud.Samples.Classic back in PLAN-0006's
implementation, but the C# namespace was deliberately left as
SharpMud.Ruleset.Classic at the time (no drive-by rename). Now renamed for
real across every file in samples/SharpMud.Samples.Classic and its
Persistence.Tests/Samples.Classic.Tests consumers, closing that gap.

Also fixes 7 comments in src/SharpMud.Engine and src/SharpMud.Persistence
that referenced the sample by its old namespace name as an example
consumer, stale now that the rename is real.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ncipollina
ncipollina merged commit e37ce16 into main Jul 21, 2026
6 checks passed
@ncipollina
ncipollina deleted the feat/plan-0006-nuget-package-distribution branch July 21, 2026 00:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: feat New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant