Skip to content

feat: add-component seeds C# and Python component repos once - #180

Merged
clabonte merged 2 commits into
mainfrom
feat/add-component
Oct 28, 2026
Merged

clabonte merged 2 commits into
mainfrom
feat/add-component

Conversation

@clabonte

Copy link
Copy Markdown
Collaborator

Closes #177.

specfuse authoring add-component <csharp|python> <dir> runs from the spec repo. It creates ../<dir>, renders templates/components/<lang>/ into it and runs git init. It also appends the component's groups to the project file. If the project has scripts/specfuse/generate.sh (from #176), it runs that for the new groups; otherwise it prints the commands. Options: --dry-run, --group-prefix, --namespace, --project, --no-generate. Docs are in docs/add-component.md.

Seeded once, then project-owned. The files are not in the scaffold manifest, upgrade never touches them, and the generator never writes them, as decided in #177. The command refuses a directory that exists and isn't empty, and group names the project file already uses.

What gets seeded

C#:

  • .slnx, and Directory.Build.props compiling src/ and gen-src/
  • Domain, Infrastructure and Api .csproj files. Package references come from the generator's own csharp-compile-fixture.
  • Program.cs, wiring six generated registration methods across four namespaces
  • Utils/UserHelper.cs: the generated controllers call it by name, and the generator never emits it
  • Filters/ExceptionFilter.cs: without it, a generated NotFoundException comes back as a 500
  • README, CLAUDE.md and .gitignore

Python:

  • a uv workspace over libs/contracts and libs/core
  • package roots that follow the generator's own derivation (HelloOrders → ho_contracts / ho_core)
  • the dependencies the generated code actually imports, including sqlalchemy[asyncio]

The artifact set was found by building, not guessed

I prototyped against generator 0.19.0 until the seed compiled and served. Missing snapshot and snapshotContext give CS0234; missing authorizationRuntime gives RoleRequired not found; leaving out EntityFrameworkCore.InMemory breaks ConfigureServices; and so on.

Generator bugs found along the way (filed)

  • clabonte/generator#2882: a kebab-case project name, which init requires, gives the invalid Python root hello-orders_contracts. add-component python refuses such names and points to the workaround (a PascalCase name).
  • clabonte/generator#2883: service always emits IEventCollector, and the event runtime requires tenancy, so single-tenant C# can't compile. add-component csharp requires a tenancy block and always registers the event artifacts.
  • clabonte/generator#2884: RestoManager names leak into everyone's output: "RestoManagerTestDb" as a default database name, and from rm_llm.models import ModelConfig. The seed's Program.cs passes an explicit database name.

When #2882 and #2883 are fixed, the two refusals and their CI checks go away.

Drift guard

  • example-regen.yml (pinned jar):
    • C#: seeds from hello-orders (plus a tenancy block in the copy), generates, runs dotnet build, then starts the API and checks list 200, missing 404, bad body 400.
    • Python: seeds (with a PascalCase name in the copy), generates, runs uv sync --all-packages, imports every generated module, and runs pytest.
  • ci.yml (offline):
    • both refusals, and that a refused seed leaves no directory behind
    • a dry run writes nothing
    • the C# seed's file list and git init, and no unrendered {{ tokens
    • the groups are added to the project file
    • an existing directory is never overwritten, and clashing group names are refused
    • the Python distribution name follows the derived root
    • seeded files never enter the scaffold manifest

Run locally: both example-regen steps and the ci.yml step pass against this branch, with the C# API answering 200/404/400 and Python showing every module importing and 3 tests passing. Also checked: add-component driving generate.sh from #176's branch. Not run locally: building the wheel, which was denied here; CI's smoke job builds it and runs the offline step through the installed CLI.

Base: main, not stacked. It runs generate.sh only when present, so it doesn't depend on #176 or #175.

🤖 Generated with Claude Code

`specfuse authoring add-component <csharp|python> <dir>`, run from the
spec repo, creates ../<dir>, renders templates/components/<lang> into
it, git-inits it, appends the component's groups to the project file,
and runs generate.sh for them when the project has it.

The seeded files are project-owned from then on: not in the scaffold
manifest, never touched by upgrade, never emitted by the generator.

C#: .slnx, Directory.Build.props (src/ + gen-src/), Domain /
Infrastructure / Api projects with the package references the pinned
generator's output compiles against (taken from the generator's own
compile fixture), a Program.cs wiring the generated registrations, and
the two seams generated code calls by name or needs to be usable:
UserHelper and an ExceptionFilter mapping generated domain exceptions
to HTTP. Requires a tenancy block (clabonte/generator#2883).

Python: a uv workspace with contracts and core libraries whose package
roots follow the generator's derivation; kebab-case project names are
refused until clabonte/generator#2882 is fixed.

Drift guard: example-regen seeds both from hello-orders, generates with
the pinned jar, then builds and serves the C# API (200/404/400) and
syncs, imports and tests the Python workspace. ci.yml covers the
offline refusals and the seed shape.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ifest (#185)

Both seeded .gitignore files ignored *.generated-paths.json as "rewritten on
every regen". The generator documents the opposite: the manifest is committed
in the component, its capturedAt comes from the spec repo's
<stateDir>/<group>.paths.json baseline rather than the clock, so a regen that
moved nothing leaves it byte-identical (clabonte/generator#2055). Ignoring it
discards the old-to-new path mapping a layout-changing release hands over.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@clabonte
clabonte merged commit 05c5ea4 into main Oct 28, 2026
8 checks passed
@clabonte
clabonte deleted the feat/add-component branch October 28, 2026 15:17
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.

add-component: seed C# and Python component repos once (project-owned host files)

1 participant