Skip to content

refactor: port simple mutation commands to libswamp pattern#821

Merged
stack72 merged 1 commit intomainfrom
refactor/batch-3-simple-mutations
Mar 22, 2026
Merged

refactor: port simple mutation commands to libswamp pattern#821
stack72 merged 1 commit intomainfrom
refactor/batch-3-simple-mutations

Conversation

@stack72
Copy link
Copy Markdown
Contributor

@stack72 stack72 commented Mar 22, 2026

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 definition
  • workflow create — checks name uniqueness, creates workflow with default job/step structure
  • vault create — validates type (with renamed-type detection), validates name format, resolves provider config, creates vault
  • data rename — validates names differ, delegates to DataRenameService for backwards-compatible forwarding
  • auth logout — loads credentials, deletes if present, yields loggedOut: true/false
  • repo init — delegates to RepoService.init(), yields init result with skills/settings/gitignore info
  • repo upgrade — delegates to RepoService.upgrade(), yields upgrade result
  • version — trivial generator, returns version string

Pattern

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 modelCreate generator was accessing modelRegistry.types() directly to build an error message, bypassing the deps interface. This was fixed by adding listAvailableTypes() to ModelCreateDeps, consistent with VaultCreateDeps which 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 log and json modes. 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: Both repoInit and repoUpgrade generators live in the same file (repo/init.ts), each with their own event/deps/input types
  • version: Uses empty VersionDeps type for consistency with the standard (ctx, deps, input) signature
  • auth logout: "Not authenticated" is a completed event with loggedOut: false, not an error — matching original behavior where logout of a non-authenticated user is informational, not a failure
  • DDD layer ratchet: Updated from 48 → 49 (new repo_init renderer imports from infrastructure/logging, net +1 after deleting old output files)

Files

Layer Files Purpose
Generators 7 files in src/libswamp/{models,workflows,vaults,data,auth,repo}/ + version.ts 8 generators with deps injection
Tests 7 *_test.ts files (17 unit tests) Cover: success, validation errors, uniqueness checks, type resolution
Renderers 7 files in src/presentation/renderers/ Log + JSON renderers matching original output
CLI 7 files in src/cli/commands/ Pure orchestration: wire deps → renderer → consumeStream
Exports src/libswamp/mod.ts All new types, generators, and deps factories
Deleted 7 old src/presentation/output/ files + tests Replaced by renderers
Updated integration/ddd_layer_rules_test.ts Ratchet 48 → 49

Test plan

  • 17 new generator unit tests
  • Full test suite: 3506 passed, 0 failed
  • deno check — clean
  • deno lint — clean
  • deno fmt — clean

Closes batch 3 of #739.

🤖 Generated with Claude Code

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]>
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Adversarial Review

Critical / High

  1. HIGH — src/libswamp/vaults/create.ts: Missing auto-resolve for extension vault types (@-prefixed)

    The old vault_create.ts CLI handler had this block before checking the registry:

    if (!vaultTypeRegistry.has(vaultType) && vaultType.startsWith("@")) {
      await resolveVaultType(vaultType, getAutoResolver());
    }

    The new vaultCreate generator skips this entirely — deps.getVaultTypeInfo just calls vaultTypeRegistry.get(type) and if the type isn't already registered, it immediately yields a validation_failed error.

    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 resolveVaultType method to VaultCreateDeps (similar to ModelCreateDeps.resolveModelType) that calls resolveVaultType() from extension_auto_resolver.ts before checking the registry. Call it at the top of the generator before deps.getVaultTypeInfo().

Medium

  1. MEDIUM — src/libswamp/repo/init.ts:75,165: RepoInitInput.version and RepoUpgradeInput.version are dead fields

    Both RepoInitInput and RepoUpgradeInput declare a version: string field. The CLI passes VERSION into both. But neither repoInit nor repoUpgrade generators ever read input.version — the version is baked into the deps via createRepoInitDeps(VERSION) / createRepoUpgradeDeps(VERSION) which constructs new 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 version that is silently ignored. Either remove the field from the input types, or use it in the generator instead of baking it into deps.

Low

  1. LOW — src/libswamp/repo/init.ts:112,203: Unsafe as AiTool cast on user-provided string

    input.tool as AiTool is 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.

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

CLI UX Review

Blocking

None.

Suggestions

  1. src/presentation/renderers/workflow_create.ts — step names are now bold: The old workflow_create_output.ts rendered 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.

  2. src/presentation/renderers/repo_init.tsLogRepoInitRenderer omits details on init that upgrade shows: repo upgrade logs skills updated, instructions, settings, and gitignore status. repo init only 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.

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

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 .ts files have correct headers ✅
  • No any types: All code is strictly typed (existing AnyOptions pattern 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.ts files 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_init renderer infrastructure import) ✅

Suggestions (non-blocking)

  1. modelCreate redundant ModelType.create() call (src/libswamp/models/create.ts:118): The generator calls ModelType.create(input.typeArg) and then passes input.typeArg to deps.resolveModelType(), which internally also calls ModelType.create(). The modelType value from line 118 is used later for getModelDef/createAndSave, so it's not wasted — but if ModelType.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 current ModelType.create() appears to normalize rather than throw.

  2. CI: claude-ux-review job (.github/workflows/ci.yml): Well-structured addition. The new UX review job is gated behind path filters for src/cli/commands/**, src/presentation/**, and src/domain/errors.ts, which is a sensible scope. The auto-merge job 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.

@stack72 stack72 merged commit 90818f2 into main Mar 22, 2026
11 checks passed
@stack72 stack72 deleted the refactor/batch-3-simple-mutations branch March 22, 2026 14:39
stack72 added a commit that referenced this pull request Mar 22, 2026
## 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]>
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.

1 participant