Skip to content

Releases: systeminit/swamp

swamp 20260307.224826.0-sha.df22793b

07 Mar 22:49
Immutable release. Only release title and notes can be modified.
df22793

Choose a tag to compare

What's Changed

  • fix: skip unresolvable cross-model resource expressions in globalArguments (#644)

Summary

Fixes #641. When a model's globalArguments contain CEL expressions referencing another model's resource or file data (e.g., ${{ model.ssh-key.resource.state.key.attributes.id }}), method execution crashes with No such key: resource if the referenced model has no data on disk. This prevents methods like update from running even when they don't need the unresolvable field.

What Changed

1. Expression evaluation skips unresolvable resource/file references

In ExpressionEvaluationService.evaluateDefinition(), expressions referencing model.*.resource or model.*.file are now skipped when the context proves the data doesn't exist (the referenced model was never executed or its data was cleaned up). This extends the existing pattern that already skips unresolved inputs.* references.

The raw ${{ ... }} expression is preserved in the definition — it is not silently discarded.

2. Broadened unresolved-expression detection in globalArgs validation

In DefaultMethodExecutionService.executeWorkflow(), the check for unresolved globalArgs fields now catches any remaining ${{ ... }} expression, not just inputs.* ones. This is safe because vault/env expressions are always resolved before executeWorkflow() is called (both in the CLI path via model_method_run.ts:282 and the workflow path via execution_service.ts:402).

3. Fixed pre-existing silent data corruption bug in globalArgs Proxy

The Proxy on context.globalArgs previously only threw for unresolved inputs.* expressions. If a globalArg contained an unresolved model.*.resource expression, the Proxy would silently return the raw ${{ ... }} string to the method. A model method could unknowingly send that literal string to an API. The Proxy now throws for any unresolved expression:

Unresolved expression in globalArguments.ssh_keys: ${{ model["ssh-key"].resource.state... }}

Design Rationale

Follows an established pattern. The codebase already skips unresolved inputs.* expressions in evaluateDefinition() (lines 214-229). The model.*.resource case is structurally identical — an expression references context data that doesn't exist yet. Extending the same skip logic is a natural, consistent extension.

The safety net already exists. The Proxy on context.globalArgs catches any attempt to use an unresolved expression at runtime. The flow is:

  1. Expression can't be resolved → skip it, leave the raw ${{ ... }} in the definition
  2. If the method actually needs that field → Proxy throws a clear error
  3. If the method doesn't need that field → everything works fine

buildContext() already loads data from disk. When dataRepo is provided (which it is in both CLI and workflow paths), ModelResolver.buildContext() eagerly populates model.*.resource from on-disk data. If the data exists, the expression resolves normally. The skip only triggers when data genuinely doesn't exist.

Alternative approaches are worse:

  • "Only evaluate fields the method schema needs" — requires the evaluation service to understand method schemas, a layer violation
  • "Make model.*.resource persistently available" — already happens; the problem is the referenced model has no data at all
  • "Allow data.latest() in definitions" — pushes complexity to users

User Impact

  • No breaking changes. Every expression that resolved before still resolves. The skip logic only triggers when the CEL evaluation would have crashed anyway.
  • What was broken now works. Models with cross-model resource references in globalArguments can run methods that don't need those fields (e.g., update doesn't need ssh_keys).
  • Better error messages. If a method accesses an unresolved field, the error is now Unresolved expression in globalArguments.ssh_keys: ${{ ... }} instead of the cryptic No such key: resource.
  • Fixes silent corruption. Unresolved non-input expressions can no longer silently leak through the Proxy as raw strings.

Files Changed

File Change
src/domain/expressions/expression_evaluation_service.ts Skip expressions with unresolvable model.*.resource/model.*.file deps
src/domain/models/method_execution_service.ts Broaden globalArgs unresolved detection + fix Proxy to catch all expressions
src/domain/models/method_execution_service_test.ts 3 new unit tests for Proxy and globalArgs validation behavior
integration/keeb_shell_model_test.ts Integration test: model method run with unresolvable cross-model resource ref
.claude/skills/swamp-model/references/troubleshooting.md Document new error message and updated behavior

Testing

  • All 2769 existing tests pass
  • 3 new unit tests covering Proxy throws, Proxy allows resolved fields, and executeWorkflow skips validation
  • 1 new integration test verifying standalone method run succeeds with unresolvable cross-model resource expression
  • Manually verified against reproduction case in /tmp/swamp-641-test

Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260307.224826.0-sha.df22793b/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

macOS (Intel):

curl -L https://github.com/systeminit/swamp/releases/download/v20260307.224826.0-sha.df22793b/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (x86_64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260307.224826.0-sha.df22793b/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (aarch64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260307.224826.0-sha.df22793b/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260307.222421.0-sha.8ee10347

07 Mar 22:25
Immutable release. Only release title and notes can be modified.
8ee1034

Choose a tag to compare

What's Changed

  • fix: add --json to all non-interactive CLI examples in skill files (#643)

Summary

  • Added --json flag to all non-interactive CLI command examples across 11 skill files
  • Skill files are the primary way the agent interacts with the CLI — commands missing --json cause the agent to receive human-readable log output instead of structured JSON, making parsing unreliable
  • Interactive commands (edit, bare issue bug/feature) are intentionally left unchanged since the agent cannot use them

Files changed: swamp-data, swamp-repo, swamp-extension-model, swamp-issue, swamp-vault, and swamp-model skill files and their references.

Test Plan

  • Verified all non-interactive commands now include --json
  • Verified no --json was added to interactive edit commands
  • Ran deno fmt to ensure formatting is correct

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260307.222421.0-sha.8ee10347/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

macOS (Intel):

curl -L https://github.com/systeminit/swamp/releases/download/v20260307.222421.0-sha.8ee10347/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (x86_64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260307.222421.0-sha.8ee10347/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (aarch64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260307.222421.0-sha.8ee10347/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260307.015433.0-sha.94e4b27d

07 Mar 01:55
Immutable release. Only release title and notes can be modified.
94e4b27

Choose a tag to compare

What's Changed

  • fix: allow reserved collective members to push extensions (#642)

fix: allow reserved collective members to push extensions

Summary

  • Moves the reserved collective (@swamp, @si) check from manifest schema validation to the push command's authorization flow
  • Legitimate members of the swamp or si collectives (verified via swamp auth whoami) can now push extensions scoped to those collectives
  • Non-members are still rejected with a clear error message listing their available collectives

Problem

Running swamp extension push manifest.yaml with a @swamp/ or @si/ scoped extension was unconditionally rejected during Zod schema parsing — before the user's collective membership was ever checked. This made it impossible for actual collective members to publish official extensions.

What changed

Manifest validation (extension_manifest.ts)

  • Removed the .refine() that rejected reserved collectives at parse time
  • Removed the now-unused ModelType import
  • The manifest parser now only validates structure (format, required fields), not authorization

Push command (extension_push.ts)

  • Added ModelType.isReservedCollective() check in the existing collective membership validation
  • If the extension uses a reserved collective but the server can't be reached to verify membership, the push is rejected (no username fallback for reserved collectives)
  • If the server confirms membership, the push proceeds normally

Tests (extension_manifest_test.ts)

  • Replaced the two rejection tests with acceptance tests verifying @swamp/ and @si/ names are valid at the manifest level

Security boundaries preserved

  1. Server-side membership verification is required for reserved collectives — the username fallback path is explicitly blocked
  2. Non-members are still rejected — the existing isAllowed check validates collective membership via the API
  3. Network failures are safe — if the server can't be reached for a reserved collective, the push fails closed with a clear error
  4. All other validation unchanged — scoped name format, CalVer version, content collective matching, safety analysis, and quality checks remain in place

Test plan

  • deno check passes
  • deno lint passes
  • deno fmt passes
  • All 2733 tests pass (deno run test)
  • Manual: swamp extension push with @swamp/ extension as a collective member succeeds
  • Manual: swamp extension push with @swamp/ extension as a non-member is rejected

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260307.015433.0-sha.94e4b27d/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

macOS (Intel):

curl -L https://github.com/systeminit/swamp/releases/download/v20260307.015433.0-sha.94e4b27d/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (x86_64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260307.015433.0-sha.94e4b27d/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (aarch64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260307.015433.0-sha.94e4b27d/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260306.211430.0-sha.49d4f8dd

06 Mar 21:15
Immutable release. Only release title and notes can be modified.
49d4f8d

Choose a tag to compare

What's Changed

  • fix: track resource deletion in local data model (#637) (#640)

Problem

When a user runs a delete method on a model (e.g., swamp model method run my-widget delete), the cloud resource is destroyed but swamp's local data store retains no record of the deletion. This creates two problems:

  1. Doomed API calls: Subsequent get or update methods happily execute against a resource that no longer exists, resulting in raw cloud provider errors (HTTP 404, "resource not found", etc.) that are confusing and unhelpful to the user.

  2. No deletion history: There is no way to distinguish "this resource was never created" from "this resource existed but was deleted" — the data store simply shows the last active version with no indication that the resource is gone.

User Impact

Before this fix, a typical user experience looks like:

$ swamp model method run my-widget create    # ✅ Creates the resource
$ swamp model method run my-widget delete    # ✅ Deletes the resource
$ swamp model method run my-widget get       # ❌ Raw 404 error from cloud API
$ swamp model method run my-widget update    # ❌ Raw 404 error from cloud API

After this fix:

$ swamp model method run my-widget create    # ✅ Creates the resource
$ swamp model method run my-widget delete    # ✅ Deletes + writes deletion marker
$ swamp model method run my-widget get       # ❌ Clear error: "Resource 'widget' was deleted at 2026-03-06T19:00:00Z — run a 'create' method to re-create it first"
$ swamp model method run my-widget update    # ❌ Same clear error
$ swamp model method run my-widget create    # ✅ Re-creates, clears deletion state
$ swamp model method run my-widget get       # ✅ Works again

Solution

1. Method Kind Classification (MethodKind + inferMethodKind())

Added a MethodKind type ("create" | "read" | "update" | "delete" | "list" | "action") and an inferMethodKind() utility that:

  • Returns the explicit kind if set on the method definition
  • Otherwise infers from conventional names: create→create, get/read/describe/show→read, update/patch→update, delete/destroy/remove→delete, list/search/find→list
  • Returns undefined for unrecognized names

Extension models can also set kind explicitly on their method definitions for non-conventional names.

2. Data Lifecycle Tracking (DataLifecycle)

Added a lifecycle field to DataMetadata with values "active" (default) or "deleted". This is backward-compatible — existing data without a lifecycle field is treated as active.

3. Deletion Markers (Tombstones)

After a delete method succeeds, executeWorkflow() writes a new version of each resource with lifecycle: "deleted" and JSON content containing { deletedAt, deletedByMethod }. This acts as a tombstone that records when and how the resource was deleted.

4. Fast-Fail on Deleted Resources

Before executing read or update methods, executeWorkflow() checks for deletion markers. If any resource has lifecycle: "deleted", it throws a UserError with a clear message including the deletion timestamp and instructions to re-create.

5. Create Clears Deletion State

When a create method writes new resource data, Data.create() defaults lifecycle to "active", naturally superseding the deletion marker. The latest symlink points to the new active version.

6. GC Interaction

Deletion markers (tombstones) are permanent — findExpiredData() in the data lifecycle service skips data with lifecycle: "deleted" so they are never auto-expired.

Files Changed

File Change
src/domain/models/model.ts MethodKind type, kind field on MethodDefinition, inferMethodKind() utility
src/domain/models/user_model_loader.ts Propagate kind through extension model loader
src/domain/data/data_metadata.ts DataLifecycleSchema, lifecycle field in metadata
src/domain/data/data.ts lifecycle in Data class, isDeleted accessor, withDeletionMarker() factory
src/domain/data/mod.ts Export new types
src/domain/models/method_execution_service.ts Pre-check for deleted resources, post-delete marker writing
src/domain/data/data_lifecycle_service.ts Skip deletion markers in GC

Tests

Unit Tests (33 new tests)

model_test.ts (8 tests)

  • inferMethodKind returns explicit kind from definition
  • Infers create, read, update, delete, list from conventional names
  • Returns undefined for unrecognized method names
  • Explicit kind overrides conventional name inference

data_metadata_test.ts (4 tests)

  • Schema accepts "active" and "deleted" lifecycle values
  • Schema rejects invalid lifecycle values
  • Schema accepts undefined (backward compatibility)

data_test.ts (12 tests)

  • Defaults lifecycle to "active" on create
  • Creates data with "deleted" lifecycle
  • isDeleted returns true/false correctly
  • withDeletionMarker() creates correct deletion marker
  • toData() includes lifecycle only when "deleted" (saves disk)
  • fromData() defaults to "active" when lifecycle missing
  • Roundtrip preserves "deleted" lifecycle
  • withNewVersion() preserves lifecycle

method_execution_service_test.ts (6 tests)

  • Delete method writes deletion markers to all resources
  • Read after delete throws UserError with timestamp
  • Update after delete throws UserError
  • Create after delete succeeds (clears deletion state)
  • Delete skips already-deleted resources
  • Explicit kind override prevents deletion marker writing

data_lifecycle_service_test.ts (2 tests)

  • Skips deletion markers in findExpiredData()
  • Returns expired active data alongside deletion markers

user_model_loader_test.ts (1 test)

  • kind propagated through model definition conversion

E2E Verification

Manually verified the full lifecycle with a test extension model:

  1. Create → version 1 (active) ✅
  2. Get → succeeds ✅
  3. Delete → writes version 2 with lifecycle: deleted
  4. Get after delete → fails with UserError
  5. Update after delete → fails with UserError
  6. Create after delete → writes version 3 (active) ✅
  7. Get after re-create → succeeds ✅

Test plan

  • deno check — Type checking passes
  • deno lint — No lint errors
  • deno fmt — Formatted
  • deno run test — All 2765 tests pass
  • deno run compile — Binary compiles
  • E2E verification of full create → delete → fail → re-create lifecycle

Closes #637

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260306.211430.0-sha.49d4f8dd/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

macOS (Intel):

curl -L https://github.com/systeminit/swamp/releases/download/v20260306.211430.0-sha.49d4f8dd/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (x86_64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260306.211430.0-sha.49d4f8dd/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (aarch64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260306.211430.0-sha.49d4f8dd/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260306.195336.0-sha.4172137b

06 Mar 19:54
Immutable release. Only release title and notes can be modified.
4172137

Choose a tag to compare

What's Changed

  • refactor: rename "namespace" to "collective" across extensions, models, and vaults (#639)

Summary

Standardizes terminology across the codebase to use "collective" instead of "namespace" when referring to the swamp organizational unit (@org/name scoped names). "Collective" is the correct ubiquitous language — a user is a member of one or more collectives, and extensions are published to a collective.

Scope rule

Only renames "namespace" → "collective" when it refers to swamp's organizational unit. External concepts (HashiCorp Vault enterprise namespaces, CEL expression namespaces) are left unchanged.

Changes

Domain layer

  • extension_namespace_validator.tsextension_collective_validator.ts — renamed file, types (NamespaceMismatchCollectiveMismatch, NamespaceValidationResultCollectiveValidationResult), and function (validateContentNamespaces()validateContentCollectives())
  • extension_manifest.ts — error messages and comments: @namespace/name@collective/name, reserved namespacereserved collective
  • model_type.tsRESERVED_NAMESPACESRESERVED_COLLECTIVES, isReservedNamespace()isReservedCollective()
  • user_model_loader.tsvalidateUserNamespace()validateUserCollective()
  • user_vault_loader.ts — comments and error messages updated

CLI commands

  • extension search--namespace flag → --collective flag
  • extension push — now validates the manifest collective against the user's actual collectives from the whoami API (via organizations response), with username fallback when the server is unreachable. Blocks the push with a clear error if the collective doesn't match (no more prompt to continue)
  • extension pull/yank/rm — error message format: @namespace/name@collective/name
  • auth whoami — now displays collectives (extracted from organizations) in both JSON and log output

Infrastructure

  • swamp_club_client.ts — added WhoamiOrganization interface, organizations field on WhoamiResponse, and getCollectives() helper that extracts org slugs
  • extension_api_client.ts — search param namespacecollective

Presentation

  • extension_push_output.tsrenderExtensionPushNamespaceErrors()renderExtensionPushCollectiveErrors(), JSON key namespaceErrorscollectiveErrors

Documentation & skills

  • design/extension.md — all organizational "namespace" → "collective"
  • .claude/skills/swamp-extension-model/ — SKILL.md, publishing.md, troubleshooting.md updated
  • .claude/skills/swamp-vault/ — SKILL.md, troubleshooting.md, user-defined-vaults.md updated (only swamp org references, not HashiCorp Vault namespace refs)

User-facing changes

  1. swamp extension search: The --namespace flag is now --collective

    # Before
    swamp extension search --namespace stack72
    # After
    swamp extension search --collective stack72
  2. swamp extension push: Collective validation now checks against your actual collectives from the server (not just username). If the manifest collective isn't one of yours, the push is blocked with a clear error listing your available collectives:

    Extension collective "@swampadmin" is not one of your collectives (@swamp, @system-initiative, @stack72).
    Use one of: @swamp, @system-initiative, @stack72
    
  3. swamp auth whoami: Now shows collectives in both output modes:

    stack72 (stack72@example.com) on https://club.swamp.sh
    Collectives: swamp, system-initiative, stack72
    
  4. Error messages: All error messages now use "collective" instead of "namespace" (e.g., reserved collective instead of reserved namespace)

Test plan

  • All 2733 tests pass
  • deno check — type checking passes
  • deno lint — linting passes
  • deno fmt — formatting passes
  • deno run compile — binary compiles
  • Manual testing of swamp extension push with collective validation

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260306.195336.0-sha.4172137b/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

macOS (Intel):

curl -L https://github.com/systeminit/swamp/releases/download/v20260306.195336.0-sha.4172137b/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (x86_64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260306.195336.0-sha.4172137b/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (aarch64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260306.195336.0-sha.4172137b/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260306.023804.0-sha.0f6f4d7e

06 Mar 02:38
Immutable release. Only release title and notes can be modified.
0f6f4d7

Choose a tag to compare

What's Changed

  • fix: make input validation method-aware for extension models (#626) (#638)

Summary

Fixes #626. Input validation previously enforced all required inputs unconditionally before method execution. This caused methods like delete to fail on extension models with CRUD patterns because create-time inputs (e.g., dropletName, region) weren't provided — even though delete doesn't use them.

Problem

Extension models define inputs in globalArguments with ${{ inputs.* }} expressions. The InputValidationService checked ALL required inputs before any method could run, making it impossible to run methods that don't need those inputs.

For example, a @test/droplet extension model with:

  • inputs.required: [dropletName, region, tagName, vpcName]
  • globalArguments referencing all four inputs
  • create method using context.globalArgs (needs all inputs)
  • delete method not accessing context.globalArgs (needs no inputs)

Running swamp model method run web-droplet delete would fail with input validation errors for dropletName, region, etc.

Approach: Three-Layer Fix

The fix required changes at three different layers because inputs flow through multiple stages before reaching method code:

Layer 1: CLI Validation (src/cli/commands/model_method_run.ts)

  • Scan the method's YAML arguments for ${{ inputs.* }} expression references
  • Filter the required array to only include inputs the method references
  • Type/enum validation still runs for any inputs the user provides
  • Defaults are still applied from the original schema

Layer 2: CEL Expression Evaluation (src/domain/expressions/expression_evaluation_service.ts)

  • Skip evaluating CEL expressions that reference inputs not provided by the user
  • Without this, the CEL evaluator would crash with No such key: apiToken when trying to resolve ${{ inputs.apiToken }} for a method that doesn't need it
  • Follows the same pattern as the existing vault/env runtime expression skipping

Layer 3: Runtime Guard (src/domain/models/method_execution_service.ts)

  • Wrap context.globalArgs in a Proxy that throws a clear error if a method accesses a field containing an unresolved ${{ inputs.* }} expression
  • Strip unresolved fields from globalArguments before Zod schema validation (otherwise validation fails on raw expression strings)
  • This catches the case where extension model TypeScript code tries to use context.globalArgs.dropletName but the user didn't provide that input

Supporting Code (src/domain/expressions/expression_parser.ts)

  • New extractInputReferencesFromCel() — extracts input field names from a single CEL expression string
  • New extractInputReferences() — scans a data structure for ${{ }} expressions and returns the set of inputs.* fields referenced
  • Handles both dot notation (inputs.fieldName) and bracket notation (inputs["field-name"])
  • Uses negative lookbehind to exclude cross-model references (model.foo.input.bar)

Why all three layers?

A single-layer fix was insufficient:

  1. Layer 1 alone — Doesn't help extension models where inputs flow through globalArguments → TypeScript code, not YAML method arguments
  2. Layer 1 + Layer 2 alone — Validation passes and CEL evaluation succeeds, but Zod schema validation in executeWorkflow() fails on raw ${{ inputs.* }} strings, and if that's bypassed, create silently produces data with unresolved expression strings instead of failing
  3. All three layers — Validation is method-aware, CEL gracefully skips missing inputs, and the runtime Proxy catches actual access to unresolved fields with a clear error message

Binary Testing

After compiling, tested the binary against a reproduction repo (/tmp/swamp-626-test/) with a @test/droplet extension model matching the exact scenario from issue #626:

Scenario Expected Result
swamp model method run web-droplet delete (no inputs) Exit 0 — delete doesn't use globalArgs Exit 0
swamp model method run web-droplet create (no inputs) Exit 1 — clear error about missing inputs Exit 1: Missing required input(s): dropletName (needed by globalArguments.dropletName)
swamp model method run web-droplet create --input dropletName=test --input region=nyc3 --input tagName=demo --input vpcName=my-vpc Exit 0 — data persisted correctly Exit 0, data written to .swamp/data/

Test plan

  • 7 new unit tests for extractInputReferences() in expression_parser_test.ts — dot/bracket notation, deduplication, cross-model exclusion, nested structures
  • 3 new integration tests in inputs_test.ts — unreferenced required inputs succeed, referenced required inputs still validated, globalArguments inputs don't block unrelated methods
  • Full test suite: 2733/2733 passed
  • deno check — passed
  • deno lint — passed
  • deno fmt — passed
  • deno run compile — passed
  • Compiled binary tested against reproduction repo (3 scenarios above)

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260306.023804.0-sha.0f6f4d7e/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

macOS (Intel):

curl -L https://github.com/systeminit/swamp/releases/download/v20260306.023804.0-sha.0f6f4d7e/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (x86_64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260306.023804.0-sha.0f6f4d7e/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (aarch64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260306.023804.0-sha.0f6f4d7e/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260306.001640.0-sha.c50eb0d8

06 Mar 00:17
Immutable release. Only release title and notes can be modified.
c50eb0d

Choose a tag to compare

What's Changed

  • fix: handle multi-line comments and template literals in import check (#635)

Summary

Fixes the critical review findings from #632 — the stripCommentsAndStrings function processed each line independently, causing:

  • False positives: import() inside multi-line block comments/JSDoc was flagged as a dynamic import
  • False negatives: import() inside template literal ${} expressions was missed

Replace the per-line approach with a whole-file tokenizer that:

  • Tracks block comment state across line boundaries
  • Uses a stack to handle nested template literals and ${} expressions
  • Preserves code inside ${} expressions (so real dynamic imports are caught)
  • Blanks template literal text (so mentions in text aren't flagged)
  • Preserves newlines so line numbers remain stable for error messages

Test plan

  • deno check passes
  • deno lint passes
  • deno fmt passes
  • deno run test — 2723 tests pass (25 quality checker tests)
  • deno run compile succeeds

New test cases:

  • Multi-line block comment containing import() — not flagged
  • Multi-line template literal text containing import() — not flagged
  • import() inside template ${} expression — correctly flagged
  • Nested template literal handling

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260306.001640.0-sha.c50eb0d8/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

macOS (Intel):

curl -L https://github.com/systeminit/swamp/releases/download/v20260306.001640.0-sha.c50eb0d8/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (x86_64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260306.001640.0-sha.c50eb0d8/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (aarch64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260306.001640.0-sha.c50eb0d8/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260305.235414.0-sha.65f82635

05 Mar 23:55
Immutable release. Only release title and notes can be modified.
65f8263

Choose a tag to compare

What's Changed

  • fix: inline non-zod npm packages in extension bundler (#632)

Summary

  • Only externalize zod in the extension bundler (--external npm:zod@4 --external npm:zod) instead of all npm packages (--external npm:*). All other npm packages are now inlined into the bundle, which fixes extensions failing in the compiled binary.
  • Ban dynamic import() calls in extensions — the quality checker now rejects them during extension push with a clear error message. The checker strips comments and string literals before scanning to avoid false positives.
  • Update docs (SKILL.md, examples.md, design/extension.md) to reflect the new bundling behavior and static import requirement.

Why

The --external npm:* flag (added in #611 to work around #609) left all npm packages as runtime npm: specifiers. This works in development with Deno's npm resolver, but breaks in the compiled binary which can only resolve packages from its own embedded dependency graph. Only zod needs to be externalized (to share instances with swamp for instanceof checks).

Dynamic import() was the pattern that triggered #609's CJS/ESM interop breakage. Static top-level imports are handled correctly by deno bundle inlining.

Breaking change for extension authors

Dynamic import() calls are no longer allowed in extensions. The quality checker will reject them during extension push. Extension authors must use static top-level imports instead:

// Before (no longer allowed)
const mod = await import("npm:@aws-sdk/client-s3@3");

// After (use static imports)
import { S3Client } from "npm:@aws-sdk/client-s3@3.750.0";

This only affects extension push — local extensions in extensions/models/ are not checked.

Test plan

  • deno check passes
  • deno lint passes
  • deno fmt passes
  • deno run test — 2704 tests pass
  • deno run compile succeeds
  • Test compiled binary with an extension that uses non-zod npm packages

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260305.235414.0-sha.65f82635/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

macOS (Intel):

curl -L https://github.com/systeminit/swamp/releases/download/v20260305.235414.0-sha.65f82635/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (x86_64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260305.235414.0-sha.65f82635/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (aarch64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260305.235414.0-sha.65f82635/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260305.234552.0-sha.d276cc8b

05 Mar 23:46
Immutable release. Only release title and notes can be modified.
d276cc8

Choose a tag to compare

What's Changed

  • fix: handle instruction section edge cases from adversarial review (#634)

Summary

Fixes three issues found by the adversarial review on #630:

  • Orphaned BEGIN marker creates duplicate markers: When the END marker is
    accidentally deleted, the file falls through to legacy migration (which
    matches because the body contains the legacy signature). This prepends a new
    section with its own BEGIN marker, creating duplicates that break subsequent
    upgrades. Fixed by stripping orphaned BEGIN markers before legacy/prepend
    logic runs.

  • Legacy migration silently deletes user content: When migrating legacy
    content and the end boundary text (Use \swamp --help` to see available
    commands.`) was edited by the user, the fallback discarded everything after
    the legacy start pattern. Fixed by prepending the new managed section and
    preserving all existing content. Some template text duplication is acceptable;
    silent data loss is not.

  • Duplicate END markers not detected: Only duplicate BEGIN markers were
    caught by replaceManagedSection. A bad merge leaving BEGIN...END...END
    would silently leave an orphaned END marker. Fixed by adding a matching check
    for duplicate END markers.

Also strengthened test assertions:

  • Missing-END-marker test now asserts exactly one BEGIN and one END marker
    (not just presence via assertStringIncludes)
  • Partial-template-match test now asserts user content is preserved after
    migration
  • Added new test for duplicate END marker detection

Test plan

  • deno run test src/domain/repo/repo_service_test.ts — all 77 tests pass
  • deno check — type checking passes
  • deno lint — linting passes
  • deno fmt --check — formatting passes

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260305.234552.0-sha.d276cc8b/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

macOS (Intel):

curl -L https://github.com/systeminit/swamp/releases/download/v20260305.234552.0-sha.d276cc8b/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (x86_64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260305.234552.0-sha.d276cc8b/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (aarch64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260305.234552.0-sha.d276cc8b/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260305.232409.0-sha.a4fd834a

05 Mar 23:25
Immutable release. Only release title and notes can be modified.
a4fd834

Choose a tag to compare

What's Changed

  • fix: swamp repo upgrade merges instructions instead of overwriting (#630)

Summary

Fixes #628swamp repo upgrade was fully overwriting CLAUDE.md/AGENTS.md with the swamp template, destroying any project-specific content users had added.

  • Adopt the same section-marker pattern already used for .gitignore management
  • Shared instruction files (CLAUDE.md, AGENTS.md) now use HTML comment markers to delimit the swamp-managed section
  • Tool-specific files (.cursor/rules/swamp.mdc, .kiro/steering/swamp-rules.md) continue to be fully overwritten since they are not shared with user content
  • On init: if a CLAUDE.md already exists, the managed section is merged in rather than skipping the file
  • On upgrade, four cases are handled: no file, file with markers, file with legacy content, file with no swamp content

Test plan

  • deno check — passes
  • deno lint — passes
  • deno fmt — passes
  • deno run test — 2700/2700 passed
  • deno run compile — binary compiles
  • Manual: swamp repo init in empty dir creates CLAUDE.md with markers
  • Manual: swamp repo init in dir with existing CLAUDE.md merges managed section
  • Manual: Add user content to CLAUDE.md, swamp repo upgrade preserves it

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260305.232409.0-sha.a4fd834a/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

macOS (Intel):

curl -L https://github.com/systeminit/swamp/releases/download/v20260305.232409.0-sha.a4fd834a/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (x86_64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260305.232409.0-sha.a4fd834a/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (aarch64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260305.232409.0-sha.a4fd834a/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/