Skip to content

Releases: systeminit/swamp

swamp 20260304.001341.0-sha.5a407e45

04 Mar 00:15
Immutable release. Only release title and notes can be modified.
5a407e4

Choose a tag to compare

What's Changed

  • fix: allow @swamp and @si namespaces for local extension models (#593)

Summary

  • Local extension models using @swamp/* or @si/* namespaces were incorrectly rejected during loading with "uses a reserved namespace" errors
  • Removed the reserved namespace check from the user model loader — users should be free to use any @-prefixed namespace locally since we don't enforce namespace ownership on publish
  • The extension manifest validation (for extension push) still blocks publishing under @swamp or @si

Test plan

  • Updated 4 tests: swamp/* and si/* (no @) now check for "must use '@' prefix" error; @swamp/* and @si/* now expect successful loading
  • All 39 user model loader tests pass
  • All 80 extension tests pass (manifest still enforces reserved namespaces for publishing)
  • deno fmt --check passes
  • deno lint passes
  • deno run compile produces working binary

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260304.001341.0-sha.5a407e45/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/v20260304.001341.0-sha.5a407e45/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/v20260304.001341.0-sha.5a407e45/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/v20260304.001341.0-sha.5a407e45/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260303.215804.0-sha.809428ec

03 Mar 21:59
Immutable release. Only release title and notes can be modified.
809428e

Choose a tag to compare

What's Changed

  • feat: show model types and vault metadata in extension push display (#592)

Summary

Fixes the extension push resolved display to show model type strings
(e.g., @adam/cfgmgmt/link) instead of raw filenames (e.g.,
cfgmgmt/link.ts). Also adds Global Arguments to the model display and
enriches vault entries with type, name, and config field metadata.

Additionally, the ExtensionContentMetadata system now extracts vault
metadata from extensions/vaults/ source files, closing the gap where
models and workflows were indexed but vaults were not.

Closes #589

What changed

Modified files (6)

File Changes
src/domain/extensions/extension_content.ts Added globalArguments to ExtractedModel; added ExtractedVault interface; added vaults to ExtensionContentMetadata
src/domain/extensions/extension_content_extractor.ts Added extractGlobalArguments() and extractVaultFromSource() functions; added vaultFiles/vaultsDir params to extractContentMetadata()
src/domain/extensions/extension_content_extractor_test.ts Updated 13 existing tests for new return shape; added 6 new tests (globalArguments inline, globalArguments named ref, vault extraction, vault configSchema, skip non-vault, skip vault without type)
src/cli/commands/extension_push.ts Moved content metadata extraction before the resolved display; maps extracted metadata to presentation DTOs with fallback to file paths
src/presentation/output/extension_push_output.ts Replaced modelFiles/vaultFiles string arrays with rich ResolvedModelEntry/ResolvedVaultEntry types; display shows type strings, global arguments, vault names, and config fields
src/presentation/output/extension_push_output_test.ts Updated resolved display test to use new models/vaults fields

Design decisions

Content metadata extraction moved earlier in push flow

Previously, extractContentMetadata() ran after the resolved display
(step 12b) and was only used for the registry push. Now it runs before
the display (step 9b) so the same extracted data enriches both the local
display and the registry payload — no double extraction.

Map-based lookup instead of endsWith matching

The resolved display maps file paths to extracted metadata using a Map
keyed by the relative path from models/vaults dir. This avoids false
matches when files share a suffix (e.g., models/instance.ts and
models/aws/instance.ts both ending with instance.ts).

Vault extraction uses createProvider as discriminator

Following the same approach as the registry (swamp-club #184), vault
files are identified by the presence of createProvider in the source.
This cleanly distinguishes vault files from utility modules that may
also live in the vaults directory.

hasConfigSchema boolean aligns with registry expectations

The ExtractedVault type includes both hasConfigSchema (boolean for
the registry's indexed shape) and configFields (detailed field array
for local display). The registry stores whatever is present but only
requires the boolean.

Default parameters preserve backward compatibility

extractContentMetadata() uses default parameter values (vaultFiles = [], vaultsDir = "") so all existing callers continue to work without
modification.

Tradeoffs

Workflow inputs not extracted

Workflow YAML files can define an inputs schema (JSON Schema with
properties, required fields, defaults) but the extractor does not
capture this. Adding it is straightforward but was out of scope for this
issue. This means the registry doesn't know what parameters a workflow
accepts — tracked as a follow-up.

Regex-based extraction, not AST parsing

All extraction (models, vaults, globalArguments) uses regex with
balanced-brace matching rather than a TypeScript AST parser. This is
consistent with the existing extractor design — fast, zero-dependency,
and sufficient for the canonical patterns. The tradeoff is that unusual
formatting or dynamic expressions won't be captured, but extraction is
best-effort by design (failures are silently skipped).

No display enrichment for workflows

Workflows still display as raw file paths. Enriching them with extracted
name/description would be a natural follow-up but was not part of the
issue scope.

User impact

Push display now shows model types instead of filenames

Before:

Models (2):
  extensions/models/cfgmgmt/link.ts
  extensions/models/cfgmgmt/apply.ts

After:

Models (2):
  @adam/cfgmgmt/link (extensions/models/cfgmgmt/link.ts)
    Global Arguments:
      region: string
      profile: string (optional)
  @adam/cfgmgmt/apply (extensions/models/cfgmgmt/apply.ts)

Push display now shows vault metadata instead of filenames

Before:

Vaults (1):
  extensions/vaults/hashicorp.ts

After:

Vaults (1):
  @hashicorp/vault - HashiCorp Vault (extensions/vaults/hashicorp.ts)
    Config Fields:
      address: string
      token_env: string (optional)

Registry receives vault content metadata

The contentMetadata payload sent during extension push confirm phase
now includes a vaults array alongside the existing models and
workflows arrays, enabling the registry to index vault types without
re-parsing the archive.

Graceful fallback

If metadata extraction fails for any file, the display falls back to
showing file paths (the previous behavior). Push is never blocked by
extraction failures.

Test plan

  • 21 extractor tests pass (13 existing updated + 6 new vault/globalArguments + 2 assertion additions)
  • 7 push output tests pass (1 updated for new shape)
  • All 2658 tests pass
  • deno check passes
  • deno lint passes
  • deno fmt --check passes
  • deno run compile produces working binary

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260303.215804.0-sha.809428ec/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/v20260303.215804.0-sha.809428ec/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/v20260303.215804.0-sha.809428ec/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/v20260303.215804.0-sha.809428ec/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260303.195617.0-sha.44d28105

03 Mar 19:57
Immutable release. Only release title and notes can be modified.
44d2810

Choose a tag to compare

What's Changed

  • feat: add extension fmt command with vault support and quality gate on push (#591)

Summary

Adds swamp extension fmt — a new command that formats and lints extension TypeScript files before publishing. Push now gates on quality checks, blocking uploads when formatting or lint issues are detected. This addresses the problem described in #586 where published extensions contained formatting inconsistencies and lint errors, forcing every consumer to fix them manually before their CI would pass.

Closes #586

What changed

New files (7)

File Purpose
src/cli/commands/extension_fmt.ts New swamp extension fmt command with auto-fix and --check modes
src/cli/resolve_extension_files.ts Shared manifest file resolver (models, vaults, workflows, additional files)
src/cli/resolve_extension_files_test.ts 8 unit tests for the resolver (including 3 vault-specific tests)
src/domain/extensions/extension_quality_checker.ts Domain service: runs deno fmt --check + deno lint, returns structured results
src/domain/extensions/extension_quality_checker_test.ts Unit tests for the quality checker
src/presentation/output/extension_fmt_output.ts Output rendering for fmt (log + json modes)
integration/extension_fmt_test.ts 6 integration tests (model formatting, vault formatting, check mode, help)

Modified files (8)

File Changes
src/cli/commands/extension.ts Registers the new fmt subcommand
src/cli/commands/extension_push.ts Refactored to use shared resolveExtensionFiles; adds quality gate before bundling
src/presentation/output/extension_push_output.ts Adds renderExtensionPushQualityErrors for quality gate failures
integration/extension_push_test.ts 2 new tests (push blocked by formatting issues, push blocked by lint issues); fixes existing test model files to pass quality checks
integration/ddd_layer_rules_test.ts Bumps presentation-infra violation ratchet (35 → 36) for new output file
.claude/skills/swamp-extension-model/SKILL.md Adds fmt entries to Quick Reference table
.claude/skills/swamp-extension-model/references/publishing.md New "Extension Formatting" section with commands, process, and push relationship
.claude/skills/swamp-extension-model/references/troubleshooting.md New "Extension has formatting or lint issues" troubleshooting entry

Design decisions

Shared file resolver (resolve_extension_files.ts)

Both extension fmt and extension push need to resolve manifest files — models, vaults, workflows, additional files, and their local imports. Previously, push had ~130 lines of inline resolution logic. PR #578 added another ~25 lines for vault resolution inline in push.

Rather than duplicating all of this in fmt, we extracted a shared resolveExtensionFiles() function. Both commands now call it, eliminating duplication and ensuring vault files (added in #578) are automatically included in formatting and quality checks.

Vault-aware from day one

The resolver handles manifest.vaults entries alongside models — resolving paths relative to the vaults directory (resolveVaultsDir), auto-discovering local imports via resolveLocalImports, and including them in the file list. This means extension fmt formats vault TypeScript files just like model files, and push's quality gate checks them too.

Quality checker as a domain service

extension_quality_checker.ts is a pure function that takes a file list and deno path, runs both checks, and returns structured { passed, issues } results. This keeps the domain logic testable and reusable — fmt uses it for re-checking after auto-fix, push uses it as a gate.

--no-config for consistency

Both deno fmt and deno lint are invoked with --no-config to ensure consistent behavior regardless of the extension author's local deno configuration. Extensions should meet a universal baseline, not vary by environment.

Push gates rather than warns

Push blocks on quality check failure rather than showing a skippable warning. This matches the philosophy from #586 — enforcing quality at the push boundary ensures every published extension is clean, which is especially valuable for LLM-authored extensions that may not run local checks.

User impact

New command: swamp extension fmt

# Auto-fix formatting and lint issues in extension files
swamp extension fmt manifest.yaml

# Check-only mode (CI-friendly, exits non-zero on issues)
swamp extension fmt manifest.yaml --check

# With custom repo directory
swamp extension fmt manifest.yaml --repo-dir /path/to/repo

The command:

  1. Parses the manifest and resolves all TypeScript files (models, vaults, local imports)
  2. Runs deno fmt to fix formatting
  3. Runs deno lint --fix to auto-fix lint issues
  4. Re-checks for any remaining unfixable issues and reports them

Push now enforces quality

swamp extension push automatically runs the equivalent of --check before uploading. If issues are found:

ERR Quality checks failed (push blocked):
ERR   Formatting issues:
ERR     ...
ERR Run 'swamp extension fmt <manifest-path>' to fix these issues.

error: Extension has formatting or lint issues. Run 'swamp extension fmt <manifest-path>' to fix.

This means extensions that were previously publishable with lint errors or bad formatting will now be blocked until the author runs swamp extension fmt to fix them.

Vault extensions are covered

Extensions that include vault implementations (manifest.vaults) get the same formatting and lint treatment as model files. No extra flags needed — the command resolves vault files from the manifest automatically.

Test plan

  • 8 unit tests for resolve_extension_files (model resolution, vault resolution, missing file errors, empty arrays)
  • Unit tests for extension_quality_checker (pass/fail scenarios)
  • 6 integration tests for extension fmt (auto-fix formatting, auto-fix lint, check mode, vault formatting, vault check mode, help output)
  • 2 integration tests for push quality gate (blocked by formatting, blocked by lint)
  • All 2650 existing tests pass
  • deno check passes
  • deno lint passes
  • deno fmt --check passes
  • deno run compile produces working binary

🤖 Generated with Claude Code

Co-authored-by: Blake Irvin blakeirvin@me.com


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260303.195617.0-sha.44d28105/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/v20260303.195617.0-sha.44d28105/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/v20260303.195617.0-sha.44d28105/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/v20260303.195617.0-sha.44d28105/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260303.194651.0-sha.57e888d5

03 Mar 19:47
Immutable release. Only release title and notes can be modified.
57e888d

Choose a tag to compare

What's Changed

  • fix: validate user-defined vault providers and YAML configs at load time (#590)

Summary

Addresses two findings from the adversarial code review on the user-defined vault implementations PR (#578):

  • Runtime VaultProvider validation: createProvider() return values are now checked for required methods (get, put, list, getName) before being registered. A broken user vault extension now fails immediately with a clear error listing the missing methods, instead of silently registering and crashing at runtime on first use.

  • YAML vault config schema validation: Vault config files loaded from .swamp/vault/ are now validated against a Zod schema (VaultConfigDataSchema) instead of being cast with as VaultConfigData. Malformed or corrupted YAML files (missing id, name, type, or createdAt) produce descriptive errors at load time. The config field defaults to {} if omitted.

User Impact

Improved error messages for vault extension authors:
Previously, a user-defined vault with a broken createProvider (e.g., returning null or an object missing required methods) would silently register and only fail when a secret operation was attempted. Now it fails at registration time with a message like:

createProvider for vault type '@myorg/broken' (vault 'my-vault') returned an invalid provider:
missing methods: put, list. A VaultProvider must implement get, put, list, and getName.

Improved resilience for corrupted vault configs:
Previously, a corrupted .swamp/vault/ YAML file (e.g., missing the id field) would cause an opaque runtime error deep in the call stack. Now it produces:

Invalid vault config in .swamp/vault/mock/bad.yaml: ...

No breaking changes — all existing vault configs and user-defined vault implementations continue to work identically. The config field now defaults to {} if omitted from YAML, which is more permissive than the previous behavior.

Changes

File Change
src/domain/vaults/vault_service.ts Added assertVaultProvider() validation after createProvider call
src/domain/vaults/vault_config.ts Added VaultConfigDataSchema Zod schema for YAML validation
src/infrastructure/persistence/yaml_vault_config_repository.ts Replaced 4 raw as VaultConfigData casts with parseVaultConfig() schema validation
src/domain/vaults/vault_service_test.ts 3 new tests: empty object, null, partial provider
src/infrastructure/persistence/yaml_vault_config_repository_test.ts 4 new tests: missing id, missing name, wrong structure, config defaulting

Test plan

  • 2630 tests pass (7 new tests added)
  • deno check passes
  • deno lint passes
  • deno fmt passes
  • deno run compile produces working binary
  • CI passes

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260303.194651.0-sha.57e888d5/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/v20260303.194651.0-sha.57e888d5/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/v20260303.194651.0-sha.57e888d5/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/v20260303.194651.0-sha.57e888d5/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260303.192612.0-sha.6d952d90

03 Mar 19:27
Immutable release. Only release title and notes can be modified.
6d952d9

Choose a tag to compare

What's Changed

  • feat: add user-defined vault implementations (#578)

Summary

Enables users to create custom vault providers via TypeScript extension files, following the same pattern as extension models. Users export a vault object from .ts files in extensions/vaults/ that defines the vault type, Zod config schema, and provider factory function. These are automatically discovered, bundled, and registered at CLI startup.

Closes #101

Design Decisions

Why a VaultTypeRegistry instead of a static array

The previous VAULT_TYPES array in vault_types.ts was a static, hardcoded list. A registry pattern (VaultTypeRegistry) was chosen because:

  • Dynamic registration: User-defined vaults register at runtime during CLI startup, alongside built-in types
  • Singleton coordination: Both vault_types.ts (built-in registration) and UserVaultLoader (user vault registration) write to the same registry without coupling to each other
  • Case-insensitive lookup: Registry normalizes type identifiers to lowercase, matching existing getVaultType() behavior
  • Duplicate prevention: Registry rejects duplicate type registrations, preventing user types from shadowing built-in types

Why mirror the UserModelLoader pattern

UserVaultLoader follows the exact same architecture as UserModelLoader:

  • Bundle with cache: Uses bundleExtension() (esbuild) to compile TypeScript to JavaScript, cached in .swamp/vault-bundles/ with mtime-based invalidation. This is required because compiled swamp binaries cannot directly import TypeScript
  • File URL / data URL fallback: Import via file:// URL first, falling back to data: URL for environments where file imports are restricted
  • Reserved namespaces: @swamp/ and @si/ are blocked for user vault types to prevent namespace collisions with future built-in types
  • Graceful failure: Individual vault load failures log warnings but don't block CLI startup

This was chosen over a novel approach because the model loader pattern is battle-tested and maintains consistency across the extension system.

Why @namespace/name scoped types

User vault types use scoped identifiers (e.g., @hashicorp/vault) to:

  • Prevent collisions: Multiple extension authors can create vault types without name conflicts
  • Match npm conventions: Familiar scoping pattern for TypeScript/JavaScript developers
  • Distinguish from built-in: Built-in types use simple names (aws-sm, 1password), user types use scoped names

Why no CalVer for vault bundles

Unlike model definitions, vault providers have no persisted schema that changes over time. The VaultProvider interface (get, put, list, getName) is stable, so there's no need for versioned schemas or migration logic.

Why walk() in YamlVaultConfigRepository

The existing repository used two-level readDir scanning (type directory → YAML files), which broke for scoped types like @hashicorp/vault where the directory structure is three levels deep (.swamp/vault/@hashicorp/vault/<id>.yaml). Switched to recursive walk() from @std/fs to handle arbitrary nesting depth. The definition repository solved this same problem with manual recursion — walk() is simpler and achieves the same result.

Why --config JSON flag for user-defined types

Built-in vault types use specific CLI flags (--region, --vault-url, etc.) for configuration. User-defined types have arbitrary config schemas, so a generic --config <json> flag was added. The JSON is validated against the vault type's Zod configSchema at creation time, giving users immediate feedback on invalid configuration.

Extension push/pull integration

Vault files are treated as a parallel track alongside models and workflows:

  • Push: Vault source files go into vaults/ in the archive, bundles into vault-bundles/
  • Pull: Source extracted to the resolved vaults directory, bundles to .swamp/vault-bundles/
  • Safety analysis: Vault .ts files go through the same security analysis as model files
  • Conflict detection: Checks for existing vault files before overwriting

What Changed

New files (6)

File Purpose
src/domain/vaults/vault_type_registry.ts Map-backed singleton registry for vault types
src/domain/vaults/vault_type_registry_test.ts 7 tests: registration, lookup, case-insensitivity, duplicates
src/domain/vaults/user_vault_loader.ts Discovers, bundles, imports, validates, and registers user vault extensions
src/domain/vaults/user_vault_loader_test.ts 6 tests: loading, reserved namespaces, skip non-vault files
src/cli/resolve_vaults_dir.ts Resolves vault extension directory (env var → config → default)
src/cli/resolve_vaults_dir_test.ts 3 tests: default, marker config, env var priority

Modified files (22)

File Changes
src/domain/vaults/vault_types.ts Registers built-ins with registry; delegates getVaultTypes()/getVaultType() to registry
src/domain/vaults/vault_service.ts Checks registry for user types with createProvider; validates config against configSchema
src/cli/mod.ts Adds loadUserVaults() called after loadUserModels() in runCli()
src/cli/commands/vault_create.ts Adds --config flag; uses registry instead of getVaultType(); validates user type configs
src/domain/extensions/extension_manifest.ts Adds vaults field to manifest schema
src/cli/commands/extension_push.ts Collects, bundles, and archives vault files
src/cli/commands/extension_pull.ts Extracts vault source and bundles; safety-analyzes vault files
src/cli/commands/extension_update.ts Passes vaultsDir through install context
src/cli/commands/extension_search.ts Passes vaultsDir through pull context
src/infrastructure/persistence/paths.ts Adds vaultBundles to SWAMP_SUBDIRS
src/infrastructure/persistence/repo_marker_repository.ts Adds vaultsDir to RepoMarkerData
src/infrastructure/persistence/yaml_vault_config_repository.ts Uses recursive walk() for namespaced vault type discovery
src/domain/repo/repo_service.ts Creates vault-bundles dir; adds to .gitignore
src/presentation/output/extension_push_output.ts Adds vault files/count to push output

User Impact

Creating a custom vault provider

Users can now create vault implementations by adding a TypeScript file to extensions/vaults/:

// extensions/vaults/hashicorp_vault.ts
import { z } from "npm:zod";

class HashiCorpVaultProvider {
  // ... implements get(), put(), list(), getName()
}

export const vault = {
  type: "@hashicorp/vault",
  name: "HashiCorp Vault",
  description: "Store secrets using HashiCorp Vault KV v2",
  configSchema: z.object({
    address: z.string().url(),
    token: z.string().optional(),
    mount: z.string().default("secret"),
  }),
  createProvider: (name, config) => {
    const parsed = configSchema.parse(config);
    return new HashiCorpVaultProvider(name, parsed);
  },
};

Using custom vault types

# Custom vault types appear in type search
swamp vault type search
# → aws-sm, azure-kv, 1password, local_encryption, @hashicorp/vault

# Create vault instance with JSON config
swamp vault create @hashicorp/vault my-vault \
  --config '{"address":"https://vault.example.com:8200","token":"s.xxx"}'

# Use like any built-in vault
swamp vault put my-vault api-key "sk-12345"
swamp vault list-keys my-vault
# ${{ vault.my-vault.api-key }} in workflows

Sharing vault implementations

Vault extensions integrate with the existing extension push/pull system:

# extension.yaml
name: hashicorp-vault
version: "1.0.0"
vaults:
  - hashicorp_vault.ts
swamp extension push
swamp extension pull hashicorp-vault  # on another repo

Backward compatibility

  • All existing built-in vault types work unchanged
  • Existing vault configurations are unaffected
  • The --config flag is only required for user-defined types
  • .swamp.yaml supports optional vaultsDir override (defaults to extensions/vaults)

Test plan

  • All 2608 tests pass (4 new test files, updated assertions in 4 existing test files)
  • deno check passes
  • deno lint passes
  • deno fmt passes
  • deno run compile produces working binary
  • End-to-end: compiled binary loads user vault from extensions/vaults/, shows in vault type search, creates vault instance with --config, persists correctly, retrievable via vault get and vault search
  • CI passes

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260303.192612.0-sha.6d952d90/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/v20260303.192612.0-sha.6d952d90/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/v20260303.192612.0-sha.6d952d90/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/v20260303.192612.0-sha.6d952d90/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260303.164829.0-sha.8c8e8eac

03 Mar 16:49
Immutable release. Only release title and notes can be modified.
8c8e8ea

Choose a tag to compare

What's Changed

  • feat: add adversarial code review to CI pipeline (#588)

Summary

Adds a second Claude-powered code review step to the CI pipeline that acts as a devil's advocate — systematically attempting to break the code rather than just assessing its quality.

The existing "Claude Code Review" is a constructive reviewer that checks style, DDD, tests, and general quality. The new "Adversarial Code Review" is a skeptic whose job is to find problems that the author and a standard reviewer would miss.

How it differs from the existing review

Aspect Existing Review Adversarial Review
Mindset Constructive colleague Skeptic trying to break the code
Scope Broad (style, DDD, tests, quality) Deep (logic, security, failure modes)
Style concerns Yes Explicitly excluded
DDD/architecture Yes No — focuses on correctness
Blocking bar Bugs, type errors, missing tests Only CRITICAL/HIGH severity (security, data loss, logic errors)
Pass action --approve (counts toward merge) --comment (doesn't count as approval)
Output format Free-form with categories Structured severity levels + verdict

The adversarial reviewer systematically attacks the code across 7 dimensions:

  1. Logic & Correctness — off-by-one, wrong operators, edge case inputs
  2. Error Handling & Failure Modes — swallowed errors, inconsistent state after failures
  3. Security — command injection, path traversal, data exposure, TOCTOU
  4. Concurrency & State — race conditions, async ordering, shared state corruption
  5. Data Integrity — silent truncation, unexpected mutations, cache staleness
  6. Resource Management — leaked handles, unbounded loops, memory leaks
  7. API Contract Violations — breaking changes, signature mismatches

Key design choices

  1. Runs in parallel with the existing review (both needs: [test, deps-audit]) — no added wall-clock time to the pipeline
  2. Uses --comment when passing instead of --approve — only the standard review grants approval, the adversarial reviewer never rubber-stamps
  3. Only blocks on CRITICAL/HIGH — security vulnerabilities, data corruption, logic errors in production paths. Medium/Low findings are advisory warnings
  4. Demands concrete examples — the prompt requires "name the exact input that breaks it" rather than vague warnings like "this could have edge cases"
  5. Explicit "don't invent problems" instruction — if the code is solid, the reviewer must say so rather than manufacturing findings to justify its existence
  6. Auto-merge now requires both reviews to pass before merging

Test plan

  • Open a PR with a deliberate bug (e.g., off-by-one error) and verify the adversarial review catches it
  • Open a clean PR and verify the adversarial review passes with --comment (not --approve)
  • Verify both reviews run in parallel (not sequentially)
  • Verify auto-merge waits for both reviews to complete

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260303.164829.0-sha.8c8e8eac/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/v20260303.164829.0-sha.8c8e8eac/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/v20260303.164829.0-sha.8c8e8eac/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/v20260303.164829.0-sha.8c8e8eac/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260303.155423.0-sha.99728850

03 Mar 15:55
Immutable release. Only release title and notes can be modified.
9972885

Choose a tag to compare

What's Changed

  • fix: false update notification when current version is newer than cache (#587)

Summary

  • Fixed false "update available" notification that appeared on every command even when the user was already on the latest (or newer) version

Root cause

The UpdateNotificationService.getNotification() method compared the cached latest version with the current running version using !== (not-equal). This meant any difference between the two triggered the notification — including when the user's version was newer than the cached value.

Concrete scenario: The background update check cached latestVersion: "20260302.220424.0-sha.cc1fb239". The user then updated to "20260303.151256.0-sha.471cac05" (a newer build). On every subsequent command, the notification fired because "20260302..." !== "20260303...", even though the user was ahead.

Fix

Changed the comparison from !== (different) to > (lexicographically greater). Since CalVer versions (YYYYMMDD.HHMMSS.patch-sha.hash) are zero-padded and naturally sortable, the notification now only triggers when the cached version is genuinely newer than the running binary.

Added a test covering the "current version newer than cache" scenario.

Test plan

  • deno check — passes
  • deno lint — passes
  • deno fmt — passes
  • deno run test — all 79 update-related tests pass (1 new)
  • Verified: cached version 20260302... < current version 20260303... → no notification

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260303.155423.0-sha.99728850/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/v20260303.155423.0-sha.99728850/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/v20260303.155423.0-sha.99728850/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/v20260303.155423.0-sha.99728850/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260303.151256.0-sha.471cac05

03 Mar 15:13
Immutable release. Only release title and notes can be modified.
471cac0

Choose a tag to compare

What's Changed

  • fix: correct Kiro hook format and add USER_PROMPT env var support (#584)

Summary

Fixes #581 — Kiro IDE does not recognize the swamp audit hook due to three issues, all addressed in this PR.

1. Hook file format

  • Wrong extension: Kiro expects .kiro.hook, not .json — renamed swamp-audit.jsonswamp-audit.kiro.hook
  • Missing toolTypes: Kiro requires toolTypes: ["*"] in the when property — added
  • No timeout: Added timeout: 5 to the then property to prevent hangs as a safety net

2. Input mechanism — USER_PROMPT env var

Kiro IDE passes postToolUse data via the USER_PROMPT environment variable, not stdin. The audit record command was blocking on readStdin() which caused timeouts. The fix:

  • For --tool kiro: check USER_PROMPT env var first, fall back to stdin (preserving kiro-cli compatibility)
  • The Kiro IDE format uses camelCase keys (toolName, toolArgs, toolResult, toolSuccess) which differs from the kiro-cli snake_case format

3. Dual-format normalizer

Updated normalizeKiro() to handle both:

  • kiro-cli (stdin, snake_case): tool_name, tool_input, tool_response
  • Kiro IDE (USER_PROMPT, camelCase): toolName, toolArgs, toolResult, toolSuccess

4. Upgrade cleanup

swamp repo upgrade --tool kiro now removes the old swamp-audit.json file so stale hooks don't linger.

Open question for @danmcclain

The USER_PROMPT env var format is based on Kiro's own description of its postToolUse data. Two things we'd appreciate verification on:

  1. Is USER_PROMPT valid JSON? We parse it with JSON.parse() — if it's a different format, the hook will silently no-op (safe but won't record).
  2. Does toolArgs contain { command: "..." } for execute_bash tools? You noted it "appears to be empty {}" in your examples — if that's always the case, we won't be able to extract the command. Could you verify with your test hook on an execute_bash invocation?

Files changed

File Change
src/domain/repo/repo_service.ts .kiro.hook extension, toolTypes, timeout, old file cleanup
src/cli/commands/audit.ts readHookInput() with USER_PROMPT env var fallback
src/domain/audit/hook_input.ts Dual-format normalizeKiro()
src/domain/audit/hook_input_test.ts 4 new tests for Kiro IDE camelCase format
src/domain/repo/repo_service_test.ts Updated hook format tests + old file cleanup test

Test plan

  • deno check passes
  • deno lint passes
  • deno fmt passes
  • deno run test — 2605 tests pass (4 new)
  • deno run compile — binary builds
  • Manual test: swamp repo init --tool kiro creates swamp-audit.kiro.hook with correct format
  • @danmcclain to verify with Kiro IDE that the hook appears and USER_PROMPT format works

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260303.151256.0-sha.471cac05/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/v20260303.151256.0-sha.471cac05/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/v20260303.151256.0-sha.471cac05/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/v20260303.151256.0-sha.471cac05/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260303.145336.0-sha.fcbb0957

03 Mar 14:54
Immutable release. Only release title and notes can be modified.
fcbb095

Choose a tag to compare

What's Changed

  • fix: improve update/upgrade output and init error message (#585)

Summary

  • Enrich swamp update log output with "updated globally" and "run swamp repo upgrade" hints after a successful update
  • Enrich swamp repo upgrade log output to show what changed: skills, instructions, settings, and .gitignore status
  • Improve swamp repo init error when already initialized to show current tool and suggest swamp repo upgrade -t <tool>

Fixes #583

Output examples

swamp repo init (already initialized):

14:32:20 FTL error Error: 'Repository already initialized at /private/tmp/swamp-output-test (tool: "claude"). To switch tools, run: swamp repo upgrade -t <tool>. To reinitialize from scratch, run: swamp repo --force -t <tool>'

swamp repo upgrade -t claude (same tool, nothing changed):

14:32:21 INF repo Upgraded swamp repository: "20260206.200442.0" → "20260206.200442.0" (tool: "claude")
14:32:21 INF repo   Skills updated: swamp-data, swamp-model, swamp-repo, swamp-workflow, swamp-extension-model, swamp-vault, swamp-issue, swamp-troubleshooting
14:32:21 INF repo   Instructions: unchanged
14:32:21 INF repo   Settings: unchanged
14:32:21 INF repo   .gitignore: unchanged

swamp repo upgrade -t cursor (switching tools):

14:32:22 INF repo Upgraded swamp repository: "20260206.200442.0" → "20260206.200442.0" (tool: "cursor")
14:32:22 INF repo   Skills updated: swamp-data, swamp-model, swamp-repo, swamp-workflow, swamp-extension-model, swamp-vault, swamp-issue, swamp-troubleshooting
14:32:22 INF repo   Instructions: updated
14:32:22 INF repo   Settings: updated
14:32:22 INF repo   .gitignore: updated

swamp update (updated case — from test output):

14:33:14 INF update swamp updated successfully!
14:33:14 INF update "20260207..." → "20260208..."
14:33:14 INF update SHA-256 integrity check passed
14:33:14 INF update The swamp binary has been updated globally.
14:33:14 INF update Run `swamp repo upgrade` in your repositories to update skills and settings.

Test plan

  • deno check — type checking passes
  • deno lint — no lint errors
  • deno fmt — formatting correct
  • deno run test — all 260 tests pass
  • deno run compile — binary recompiles
  • Tested compiled binary output in /tmp directory for all three scenarios

🤖 Generated with Claude Code

Co-authored-by: Walter Heck walterheck@helixiora.com


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260303.145336.0-sha.fcbb0957/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/v20260303.145336.0-sha.fcbb0957/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/v20260303.145336.0-sha.fcbb0957/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/v20260303.145336.0-sha.fcbb0957/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260303.045025.0-sha.aab4626d

03 Mar 04:51
Immutable release. Only release title and notes can be modified.
aab4626

Choose a tag to compare

What's Changed

  • fix: switch API token auth from Bearer to x-api-key header (#582)

Summary

  • Switch all API key authentication from Authorization: Bearer to x-api-key header, reserving Bearer exclusively for the createApiKey call during login (session token auth)
  • Reorder login flow to create the API key first, then verify identity with whoami using the new API key
  • Update ExtensionApiClient, HttpTelemetrySender, and all related tests

Fixes #577

Test Plan

  • All 2586 existing tests pass
  • swamp_club_client_test.ts: Verifies whoami sends x-api-key header
  • extension_api_client_test.ts: Verifies yank sends x-api-key header
  • http_telemetry_sender_test.ts: Verifies telemetry sends x-api-key header
  • deno check, deno lint, deno fmt --check all pass
  • deno run compile succeeds

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260303.045025.0-sha.aab4626d/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/v20260303.045025.0-sha.aab4626d/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/v20260303.045025.0-sha.aab4626d/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/v20260303.045025.0-sha.aab4626d/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/