refactor: port simple mutation commands to libswamp pattern#821
Conversation
Port `model create`, `workflow create`, `vault create`, `data rename`, `auth logout`, `repo init`, `repo upgrade`, and `version` commands to the libswamp event stream architecture (batch 3 of #739). Each command follows the standard pattern: validate -> create/mutate -> save -> yield completed. CLI handlers reduced to pure orchestration. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
There was a problem hiding this comment.
Adversarial Review
Critical / High
-
HIGH —
src/libswamp/vaults/create.ts: Missing auto-resolve for extension vault types (@-prefixed)The old
vault_create.tsCLI handler had this block before checking the registry:if (!vaultTypeRegistry.has(vaultType) && vaultType.startsWith("@")) { await resolveVaultType(vaultType, getAutoResolver()); }
The new
vaultCreategenerator skips this entirely —deps.getVaultTypeInfojust callsvaultTypeRegistry.get(type)and if the type isn't already registered, it immediately yields avalidation_failederror.Breaking example:
swamp vault create @my-org/custom-vault my-vault— this worked before (auto-resolved the extension, registered the type, then created the vault). Now it fails with "Unknown vault type: @my-org/custom-vault".Suggested fix: Add a
resolveVaultTypemethod toVaultCreateDeps(similar toModelCreateDeps.resolveModelType) that callsresolveVaultType()fromextension_auto_resolver.tsbefore checking the registry. Call it at the top of the generator beforedeps.getVaultTypeInfo().
Medium
-
MEDIUM —
src/libswamp/repo/init.ts:75,165:RepoInitInput.versionandRepoUpgradeInput.versionare dead fieldsBoth
RepoInitInputandRepoUpgradeInputdeclare aversion: stringfield. The CLI passesVERSIONinto both. But neitherrepoInitnorrepoUpgradegenerators ever readinput.version— the version is baked into the deps viacreateRepoInitDeps(VERSION)/createRepoUpgradeDeps(VERSION)which constructsnew RepoService(version). The field is carried through the input for no reason.Not a behavioral bug, but it's a confusing API contract — callers must supply a
versionthat is silently ignored. Either remove the field from the input types, or use it in the generator instead of baking it into deps.
Low
-
LOW —
src/libswamp/repo/init.ts:112,203: Unsafeas AiToolcast on user-provided stringinput.tool as AiToolis a type assertion on a string that comes from CLI options. If the Cliffy enum type validation ever changes or a caller bypasses it, an invalid tool value would pass through unchecked. The old code had the same pattern, so this isn't a regression — but since the generator is now a reusable library function (not just a CLI handler), the blast radius is wider.
Verdict
FAIL — The missing vault extension auto-resolve (#1) is a behavioral regression that breaks creation of @-scoped extension vault types. Fix that and this is good to merge.
There was a problem hiding this comment.
CLI UX Review
Blocking
None.
Suggestions
-
src/presentation/renderers/workflow_create.ts— step names are now bold: The oldworkflow_create_output.tsrendered step names as plain text (${step.name}), while the new renderer uses${bold(step.name)}. This is a minor visual improvement but contradicts the PR description's claim that output is "identical". No action needed, just noting the discrepancy. -
src/presentation/renderers/repo_init.ts—LogRepoInitRendereromits details on init that upgrade shows:repo upgradelogs skills updated, instructions, settings, and gitignore status.repo initonly logs path and tool. This asymmetry predates this PR, but since both renderers are now side-by-side in the same file, it's worth aligning them someday.
Verdict
PASS — Purely internal refactor, all 7 commands maintain correct log/JSON output, error messages are clear and actionable, no regressions observed.
There was a problem hiding this comment.
Code Review
Blocking Issues
None.
DDD Assessment
The generators correctly implement the Application Service pattern — they orchestrate domain objects for use cases, own the transaction boundary via deps injection, and yield progress/completion events. All infrastructure access is properly abstracted through deps interfaces, keeping generators fully testable with fakes. Domain value objects (ModelType.create(), RepoPath.create()) and constants (RENAMED_VAULT_TYPES) are appropriately used directly as pure domain logic.
Convention Compliance
- AGPLv3 headers: All new
.tsfiles have correct headers ✅ - No
anytypes: All code is strictly typed (existingAnyOptionspattern in CLI commands uses lint-ignore, consistent with codebase) ✅ - Named exports: All files use named exports ✅
- libswamp import boundary: All renderers and CLI commands import from
src/libswamp/mod.ts— never from internal paths ✅ - Both output modes: All commands support
"log"and"json"modes via renderer factory pattern ✅ - Test coverage: All 8 generators have corresponding
_test.tsfiles with 17 unit tests covering success paths, validation errors, and edge cases ✅ - mod.ts exports: All new types, generators, and deps factories are properly exported ✅
- DDD ratchet: Updated 48 → 49 (net +1 from new
repo_initrenderer infrastructure import) ✅
Suggestions (non-blocking)
-
modelCreateredundantModelType.create()call (src/libswamp/models/create.ts:118): The generator callsModelType.create(input.typeArg)and then passesinput.typeArgtodeps.resolveModelType(), which internally also callsModelType.create(). ThemodelTypevalue from line 118 is used later forgetModelDef/createAndSave, so it's not wasted — but ifModelType.create()ever threw on invalid input, the error would be uncaught here (unlike the deps call which is behind the null-check). Minor, since the currentModelType.create()appears to normalize rather than throw. -
CI:
claude-ux-reviewjob (.github/workflows/ci.yml): Well-structured addition. The new UX review job is gated behind path filters forsrc/cli/commands/**,src/presentation/**, andsrc/domain/errors.ts, which is a sensible scope. Theauto-mergejob correctly depends on it.
Verdict
Clean refactor following established patterns consistently across all 8 generators. Good test coverage, proper DDD layering, and correct import boundaries. LGTM.
## Summary Fixes a regression introduced in #821 (batch 3 migration) where `swamp vault create @org/custom-vault my-vault` fails with "Unknown vault type" instead of auto-resolving the extension type from the registry. ## What broke The old `vault_create.ts` CLI handler had this auto-resolve step before checking the registry: ```typescript if (!vaultTypeRegistry.has(vaultType) && vaultType.startsWith("@")) { await resolveVaultType(vaultType, getAutoResolver()); } ``` When the command was migrated to the libswamp generator pattern, this step was dropped. The generator's `deps.getVaultTypeInfo()` calls `vaultTypeRegistry.get()` directly — if the `@`-prefixed extension type isn't already registered, it immediately yields a `validation_failed` error. ## Fix Added `resolveExtensionVaultType` to `VaultCreateDeps`: - The dep calls `resolveVaultType()` from `extension_auto_resolver.ts` for `@`-prefixed types not yet in the registry - The generator calls it before `deps.getVaultTypeInfo()`, restoring the original behavior - Follows the same pattern as `ModelCreateDeps.resolveModelType` which auto-resolves extension model types ## Test plan - [x] Existing vault create tests pass (4/4) - [x] `deno check` — clean - [x] `deno lint` — clean 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
Summary
Ports 7 simple mutation commands (8 generators total) to the libswamp event stream architecture, completing batch 3 of #739.
model create— validates type, checks name uniqueness, validates global args against Zod schema, creates definitionworkflow create— checks name uniqueness, creates workflow with default job/step structurevault create— validates type (with renamed-type detection), validates name format, resolves provider config, creates vaultdata rename— validates names differ, delegates to DataRenameService for backwards-compatible forwardingauth logout— loads credentials, deletes if present, yieldsloggedOut: true/falserepo init— delegates to RepoService.init(), yields init result with skills/settings/gitignore inforepo upgrade— delegates to RepoService.upgrade(), yields upgrade resultversion— trivial generator, returns version stringPattern
All commands follow the single-phase pattern: validate → mutate → save → yield completed. No confirmation prompts needed (unlike batch 5). CLI handlers are pure orchestration: create deps, create renderer,
consumeStream().Architecture decision: deps abstraction for global registries
During review, I found that the
modelCreategenerator was accessingmodelRegistry.types()directly to build an error message, bypassing the deps interface. This was fixed by addinglistAvailableTypes()toModelCreateDeps, consistent withVaultCreateDepswhich already had the same pattern.Principle: Generators must not access global singletons directly. All infrastructure and registry access goes through the deps interface, making generators fully testable with fake deps.
Domain value object constructors (
ModelType.create(),RepoPath.create()) and domain constants (RENAMED_VAULT_TYPES) are acceptable in generators since they are pure domain logic with no side effects.User impact
None. Output is identical in both
logandjsonmodes. Same log lines, same JSON shapes. This is a purely internal refactor.Notable details
vault create: Interactive vault name prompt stays in CLI layer (same pattern as batch 6 edit commands)repo init: BothrepoInitandrepoUpgradegenerators live in the same file (repo/init.ts), each with their own event/deps/input typesversion: Uses emptyVersionDepstype for consistency with the standard(ctx, deps, input)signatureauth logout: "Not authenticated" is acompletedevent withloggedOut: false, not an error — matching original behavior where logout of a non-authenticated user is informational, not a failurerepo_initrenderer imports from infrastructure/logging, net +1 after deleting old output files)Files
src/libswamp/{models,workflows,vaults,data,auth,repo}/+version.ts*_test.tsfiles (17 unit tests)src/presentation/renderers/src/cli/commands/src/libswamp/mod.tssrc/presentation/output/files + testsintegration/ddd_layer_rules_test.tsTest plan
deno check— cleandeno lint— cleandeno fmt— cleanCloses batch 3 of #739.
🤖 Generated with Claude Code