You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Proposal: opt-in framework packages, .agents scaffolding, and fork-local templates for the fsh CLI
Hi 👋 — I've been using the starter kit to spin up several products and hit three
friction points in the fsh CLI. I've implemented all three on a fork and would like
your read before opening PRs upstream.
The headline, first: none of this changes the default. fsh new MyApp produces
exactly what it produces today. Every new behaviour sits behind a template symbol that
defaults to false and a CLI flag that is off. src/Directory.Build.props states the
source-ownership model plainly, and backend.yml records that per-module packs were
removed once for contradicting it — I'm not asking you to reverse that, only to allow an
opt-in path beside it.
What's needed, and why
1. There's no way to scaffold from your own fork or branch
NewCommand.EnsureTemplateInstalledAsync accepts any installed FSH template and never
upgrades it, and when nothing is installed it runs dotnet new install FullStackHero.NET.StarterKit with a bare package id — no ::version, no --add-source.
There is no --template-path / --template-version / --source option anywhere in NewCommand.Settings.
Consequences: a contributor can't scaffold from the branch they're working on, and
everyone else silently keeps whatever template they installed months ago. fsh info
will show the drift; fsh new never checks it.
2. .agents/ never reaches a generated project — but AGENTS.md does
.agents/ is excluded twice: the git ls-files pathspec in templates/FullStackHero.NET.StarterKit.csproj and .agents/** in template.json. AGENTS.md is in neither exclude list, so it ships alone — leaving every scaffolded
project with a guide whose entire "Rules index" table points at files that were never
copied. AI tools then start with a broken map instead of real context.
3. Every generated project carries its own copy of src/BuildingBlocks
Eleven projects, wired in by 33 relative ProjectReference lines. For one product that's
exactly right and I wouldn't change it. Running N products off one kernel means N copies
drifting apart, with every fix applied by hand N times.
Benefits
Today (unchanged, still the default)
With the opt-in flag
Kernel lives in
every project
one starter-kit clone
Fixing a kernel bug
edit N projects
pack once, bump N versions
Editing kernel code in-project
yes
no — you edit it in the kit
Step-into debugging
trivially
yes, via embedded PDBs
Best for
one product
a platform team, several products
Concretely, for maintainers of the kit itself: --template-path makes it possible to test
template changes locally before publishing, and the .agents fix removes a dangling
reference that exists in shipped output today.
How it's implemented
fsh new MyApp --template-path ~/dev/dotnet-starter-kit # or --template-version / --template-source
fsh new MyApp --agents # ships .agents + AGENTS.md/CLAUDE.md/GEMINI.md
fsh new MyApp --framework-packages # consume FSH.Framework.* instead of source
fsh framework pack --push --clear-cache # build the 11 packages into a feed
fsh framework swap --to source|packages # convert an existing project either way
fsh framework list | clean-cache
fsh self install # build the CLI from source as the global `fsh`
Three design decisions are the ones worth your scrutiny:
No churn in the 33 project files. A new src/Directory.Build.targets rewrites any ProjectReference pointing into BuildingBlocks into the matching PackageReference
(..\..\BuildingBlocks\Core\Core.csproj → FSH.Framework.Core; %(Filename) maps 1:1
onto the ids, including the dotted Eventing.Abstractions). The csproj files are
byte-identical in both modes, and new modules get it for free rather than having to
remember a pattern. It keys off src/BuildingBlocks being absent — which is exactly the
shape of a package-mode scaffold — so it can't drift from how the project was created. -p:UseFrameworkPackages=true|false forces either way.
Embedded PDBs with embedded sources, not .snupkg. A local folder feed doesn't serve
symbol packages — only the NuGet.org symbol server does — so a .snupkg cannot give
step-into debugging locally. Embedding puts the PDB and the sources inside the DLL, so
F11 lands in real framework source with no symbol server and no source checkout. A --profile public keeps the conventional .snupkg + SourceLink for real feeds.
swap --to source regenerates the kernel from the template rather than copying it.
The template rewrites tokens inside BuildingBlocks, and not all of them are cosmetic — MultitenancyConstants.Issuer is derived from the project name. A raw copy out of a kit
clone would silently install the starter kit's own JWT issuer into someone else's project.
The command scaffolds a throwaway copy under the target project's own name and lifts the
kernel from that.
Versions are stamped 10.0.0-local.<timestamp>, unique per pack, because NuGet caches by
id + version and would otherwise serve stale bits after a rebuild.
Pre-existing bugs found along the way
These are independent of the feature and worth fixing regardless:
FullStackHero.CLI has never shipped a .snupkg.Directory.Build.props declares
"Reproducible, debuggable packages for the projects that DO ship (CLI)" behind Condition="'$(IsPackable)' == 'true'" — but that file is imported before the csproj
sets IsPackable, and MSBuild evaluates properties in document order, so IncludeSymbols stayed empty and SymbolPackageFormat fell back to the SDK default.
Moving the block to Directory.Build.targets, where the value is final, fixes it.
ProcessRunner.CaptureAsync redirects stderr but never drains it — a latent deadlock
for any child process that fills the pipe buffer.
fsh doctor fails any SDK not starting with "10.", despite promising ".NET 10+".
The update check compares against AssemblyVersion (pinned 10.0.0.0 in the csproj)
while --version uses the informational version, so a 10.0.1 build nags about itself.
fsh new printed "Scaffolding failed" while discarding the dotnet new output that
explained why, and unknown options were silently dropped rather than rejected — a
mistyped flag quietly produced a different project than the one you asked for.
Verification
Generated project (no BuildingBlocks, packages from a local feed): dotnet build -warnaserror → 0 warnings, 0 errors
Default scaffold unchanged and builds clean; a new template-smoke.yml job covers the --framework-packages --agents combination alongside the existing two
swap round-trip verified on three projects, each direction building clean
Repo builds; 55 architecture + 301 unit tests pass
Not done: per golden rule 10 this is user-facing, so the docs repo and a src/content/docs/changelog/ entry still need updating. README-CLI.md in the branch is
written to port straight into the Astro site.
The feature commit on its own — exactly these 37 files, nothing else — 294c765
Diff against upstream main — what a PR here would look like — main...maxiar:dotnet-starter-kit:feat/cli-framework-packages
(this range also carries a small, already-merged Testcontainers bump from my fork; the
commit link above is the clean view)
37 files, +2483 / −65.
Questions for you
Is the opt-in framing acceptable? I've kept source ownership as the default and the
existing smoke tests untouched, but item 3 is the one that touches your stated model and
I'd rather hear "no" now than after a PR.
Naming.FSH.Framework.* matches the existing AssemblyNames. Say the word if you'd
prefer a different package id prefix, since that's hard to change later.
Should the CLI symbol-package fix go in on its own? It's unrelated to this feature
and affects the published tool today.
Happy to rebase onto main, split it up, or drop any part of it. Thanks for the kit — it's
saved me a lot of time.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Proposal: opt-in framework packages,
.agentsscaffolding, and fork-local templates for thefshCLIHi 👋 — I've been using the starter kit to spin up several products and hit three
friction points in the
fshCLI. I've implemented all three on a fork and would likeyour read before opening PRs upstream.
The headline, first: none of this changes the default.
fsh new MyAppproducesexactly what it produces today. Every new behaviour sits behind a template symbol that
defaults to
falseand a CLI flag that is off.src/Directory.Build.propsstates thesource-ownership model plainly, and
backend.ymlrecords that per-module packs wereremoved once for contradicting it — I'm not asking you to reverse that, only to allow an
opt-in path beside it.
What's needed, and why
1. There's no way to scaffold from your own fork or branch
NewCommand.EnsureTemplateInstalledAsyncaccepts any installed FSH template and neverupgrades it, and when nothing is installed it runs
dotnet new install FullStackHero.NET.StarterKitwith a bare package id — no::version, no--add-source.There is no
--template-path/--template-version/--sourceoption anywhere inNewCommand.Settings.Consequences: a contributor can't scaffold from the branch they're working on, and
everyone else silently keeps whatever template they installed months ago.
fsh infowill show the drift;
fsh newnever checks it.2.
.agents/never reaches a generated project — butAGENTS.mddoes.agents/is excluded twice: thegit ls-filespathspec intemplates/FullStackHero.NET.StarterKit.csprojand.agents/**intemplate.json.AGENTS.mdis in neither exclude list, so it ships alone — leaving every scaffoldedproject with a guide whose entire "Rules index" table points at files that were never
copied. AI tools then start with a broken map instead of real context.
3. Every generated project carries its own copy of
src/BuildingBlocksEleven projects, wired in by 33 relative
ProjectReferencelines. For one product that'sexactly right and I wouldn't change it. Running N products off one kernel means N copies
drifting apart, with every fix applied by hand N times.
Benefits
Concretely, for maintainers of the kit itself:
--template-pathmakes it possible to testtemplate changes locally before publishing, and the
.agentsfix removes a danglingreference that exists in shipped output today.
How it's implemented
Three design decisions are the ones worth your scrutiny:
No churn in the 33 project files. A new
src/Directory.Build.targetsrewrites anyProjectReferencepointing intoBuildingBlocksinto the matchingPackageReference(
..\..\BuildingBlocks\Core\Core.csproj→FSH.Framework.Core;%(Filename)maps 1:1onto the ids, including the dotted
Eventing.Abstractions). The csproj files arebyte-identical in both modes, and new modules get it for free rather than having to
remember a pattern. It keys off
src/BuildingBlocksbeing absent — which is exactly theshape of a package-mode scaffold — so it can't drift from how the project was created.
-p:UseFrameworkPackages=true|falseforces either way.Embedded PDBs with embedded sources, not
.snupkg. A local folder feed doesn't servesymbol packages — only the NuGet.org symbol server does — so a
.snupkgcannot givestep-into debugging locally. Embedding puts the PDB and the sources inside the DLL, so
F11 lands in real framework source with no symbol server and no source checkout. A
--profile publickeeps the conventional.snupkg+ SourceLink for real feeds.swap --to sourceregenerates the kernel from the template rather than copying it.The template rewrites tokens inside BuildingBlocks, and not all of them are cosmetic —
MultitenancyConstants.Issueris derived from the project name. A raw copy out of a kitclone would silently install the starter kit's own JWT issuer into someone else's project.
The command scaffolds a throwaway copy under the target project's own name and lifts the
kernel from that.
Versions are stamped
10.0.0-local.<timestamp>, unique per pack, because NuGet caches byid + version and would otherwise serve stale bits after a rebuild.
Pre-existing bugs found along the way
These are independent of the feature and worth fixing regardless:
FullStackHero.CLIhas never shipped a.snupkg.Directory.Build.propsdeclares"Reproducible, debuggable packages for the projects that DO ship (CLI)" behind
Condition="'$(IsPackable)' == 'true'"— but that file is imported before the csprojsets
IsPackable, and MSBuild evaluates properties in document order, soIncludeSymbolsstayed empty andSymbolPackageFormatfell back to the SDK default.Moving the block to
Directory.Build.targets, where the value is final, fixes it.ProcessRunner.CaptureAsyncredirects stderr but never drains it — a latent deadlockfor any child process that fills the pipe buffer.
fsh doctorfails any SDK not starting with"10.", despite promising ".NET 10+".AssemblyVersion(pinned10.0.0.0in the csproj)while
--versionuses the informational version, so a10.0.1build nags about itself.fsh newprinted "Scaffolding failed" while discarding thedotnet newoutput thatexplained why, and unknown options were silently dropped rather than rejected — a
mistyped flag quietly produced a different project than the one you asked for.
Verification
BuildingBlocks, packages from a local feed):dotnet build -warnaserror→ 0 warnings, 0 errorstemplate-smoke.ymljob covers the--framework-packages --agentscombination alongside the existing twoswapround-trip verified on three projects, each direction building cleanNot done: per golden rule 10 this is user-facing, so the docs repo and a
src/content/docs/changelog/entry still need updating.README-CLI.mdin the branch iswritten to port straight into the Astro site.
See the changes
294c765
main— what a PR here would look like —main...maxiar:dotnet-starter-kit:feat/cli-framework-packages
(this range also carries a small, already-merged Testcontainers bump from my fork; the
commit link above is the clean view)
37 files, +2483 / −65.
Questions for you
existing smoke tests untouched, but item 3 is the one that touches your stated model and
I'd rather hear "no" now than after a PR.
FSH.Framework.*matches the existingAssemblyNames. Say the word if you'dprefer a different package id prefix, since that's hard to change later.
and affects the published tool today.
Happy to rebase onto
main, split it up, or drop any part of it. Thanks for the kit — it'ssaved me a lot of time.
All reactions