Releases: systeminit/swamp
swamp 20260304.001341.0-sha.5a407e45
What's Changed
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@swampor@si
Test plan
- Updated 4 tests:
swamp/*andsi/*(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 --checkpasses -
deno lintpasses -
deno run compileproduces 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
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 checkpasses -
deno lintpasses -
deno fmt --checkpasses -
deno run compileproduces 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
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/repoThe command:
- Parses the manifest and resolves all TypeScript files (models, vaults, local imports)
- Runs
deno fmtto fix formatting - Runs
deno lint --fixto auto-fix lint issues - 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 checkpasses -
deno lintpasses -
deno fmt --checkpasses -
deno run compileproduces 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
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 withas VaultConfigData. Malformed or corrupted YAML files (missingid,name,type, orcreatedAt) produce descriptive errors at load time. Theconfigfield 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 checkpasses -
deno lintpasses -
deno fmtpasses -
deno run compileproduces 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
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) andUserVaultLoader(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 todata: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 intovault-bundles/ - Pull: Source extracted to the resolved vaults directory, bundles to
.swamp/vault-bundles/ - Safety analysis: Vault
.tsfiles 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 workflowsSharing vault implementations
Vault extensions integrate with the existing extension push/pull system:
# extension.yaml
name: hashicorp-vault
version: "1.0.0"
vaults:
- hashicorp_vault.tsswamp extension push
swamp extension pull hashicorp-vault # on another repoBackward compatibility
- All existing built-in vault types work unchanged
- Existing vault configurations are unaffected
- The
--configflag is only required for user-defined types .swamp.yamlsupports optionalvaultsDiroverride (defaults toextensions/vaults)
Test plan
- All 2608 tests pass (4 new test files, updated assertions in 4 existing test files)
-
deno checkpasses -
deno lintpasses -
deno fmtpasses -
deno run compileproduces working binary - End-to-end: compiled binary loads user vault from
extensions/vaults/, shows invault type search, creates vault instance with--config, persists correctly, retrievable viavault getandvault 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
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:
- Logic & Correctness — off-by-one, wrong operators, edge case inputs
- Error Handling & Failure Modes — swallowed errors, inconsistent state after failures
- Security — command injection, path traversal, data exposure, TOCTOU
- Concurrency & State — race conditions, async ordering, shared state corruption
- Data Integrity — silent truncation, unexpected mutations, cache staleness
- Resource Management — leaked handles, unbounded loops, memory leaks
- API Contract Violations — breaking changes, signature mismatches
Key design choices
- Runs in parallel with the existing review (both
needs: [test, deps-audit]) — no added wall-clock time to the pipeline - Uses
--commentwhen passing instead of--approve— only the standard review grants approval, the adversarial reviewer never rubber-stamps - Only blocks on CRITICAL/HIGH — security vulnerabilities, data corruption, logic errors in production paths. Medium/Low findings are advisory warnings
- Demands concrete examples — the prompt requires "name the exact input that breaks it" rather than vague warnings like "this could have edge cases"
- Explicit "don't invent problems" instruction — if the code is solid, the reviewer must say so rather than manufacturing findings to justify its existence
- 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
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 version20260303...→ 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
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— renamedswamp-audit.json→swamp-audit.kiro.hook - Missing
toolTypes: Kiro requirestoolTypes: ["*"]in thewhenproperty — added - No timeout: Added
timeout: 5to thethenproperty 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: checkUSER_PROMPTenv 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:
- Is
USER_PROMPTvalid JSON? We parse it withJSON.parse()— if it's a different format, the hook will silently no-op (safe but won't record). - Does
toolArgscontain{ command: "..." }forexecute_bashtools? 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 anexecute_bashinvocation?
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 checkpasses -
deno lintpasses -
deno fmtpasses -
deno run test— 2605 tests pass (4 new) -
deno run compile— binary builds - Manual test:
swamp repo init --tool kirocreatesswamp-audit.kiro.hookwith correct format - @danmcclain to verify with Kiro IDE that the hook appears and
USER_PROMPTformat 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
What's Changed
- fix: improve update/upgrade output and init error message (#585)
Summary
- Enrich
swamp updatelog output with "updated globally" and "runswamp repo upgrade" hints after a successful update - Enrich
swamp repo upgradelog output to show what changed: skills, instructions, settings, and .gitignore status - Improve
swamp repo initerror when already initialized to show current tool and suggestswamp 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
What's Changed
- fix: switch API token auth from Bearer to x-api-key header (#582)
Summary
- Switch all API key authentication from
Authorization: Bearertox-api-keyheader, reserving Bearer exclusively for thecreateApiKeycall during login (session token auth) - Reorder login flow to create the API key first, then verify identity with
whoamiusing 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: Verifieswhoamisendsx-api-keyheaderextension_api_client_test.ts: Verifies yank sendsx-api-keyheaderhttp_telemetry_sender_test.ts: Verifies telemetry sendsx-api-keyheaderdeno check,deno lint,deno fmt --checkall passdeno run compilesucceeds
🤖 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/