Imported from swamp-club/swamp (
AGENTS.md). Install upstream withnpx skills add swamp-club/swamp. Copyright stays with the author.
Project: swamp
Deno based CLI for doing AI Native Automation.
Planning
When planning new features, always use the ddd skill to inform the
architecture.
Workflows
In this repository the word "workflow" — including "create/run/execute/validate/
debug workflow", "automate", "orchestrate", and "automated/nightly job" — refers
to a swamp workflow: a declarative YAML DAG of model-method steps authored via
swamp workflow create. This is swamp's own first-class concept, implemented in
this codebase, and it is the default meaning here. Load and follow the swamp
skill for these requests. Do NOT interpret workflow requests as agent
orchestration tasks. Only use your agent harness's native task or orchestration
tools when the user explicitly names that mechanism or explicitly asks you to do
the work yourself step by step rather than author a swamp workflow.
Code Style
-
TypeScript strict mode, no
anytypes -
Use named exports, not default exports
-
Comprehensive unit test coverage
-
All
.tsand.tsxfiles must include the AGPLv3 copyright header fromFILE-LICENSE-TEMPLATE.mdat the top of the file (as//comments). Rundeno run license-headersto add headers to any new files. -
No fire-and-forget promises. Every promise must be awaited or explicitly handled — unhandled promises race with
Deno.exitand silently lose data. For outbound network calls, pass anAbortSignalwith a timeout so the caller controls cancellation. -
Interpolate values bare in LogTape tagged templates — let the formatter handle quoting. Strings passed as
${value}render as"value"in log output; wrapping them in literal quotes ("${value}") doubles the quotes to""value"". Numbers and other primitives render unquoted, so a bare${count}is correct in all cases. -
The
organizationsarray fromGET /api/whoamiis an authorization contract, not a general-purpose payload.getCollectives()insrc/infrastructure/http/swamp_club_client.tsreduces it to slugs, and extension push and pull authorize namespace ownership from that list. Add new server-supplied data as its own optional top-level field joined onslug(ascollectiveEntitlementsdoes) rather than as extra keys on the organization entries, so changes to unrelated concerns can never reach the publish path.
Changes should only touch what's necessary — don't refactor adjacent code that isn't part of the task. Keep the blast radius small.
Commands
Use deno run to get a complete list of custom tasks. deno run dev runs the
CLI. swamp help <command> outputs the full CLI schema as structured JSON — use
it to verify exact flags and arguments before running any swamp command.
Verification
The Deno version is pinned in .tool-versions at the repo root — the single
source of truth for CI, the Docker image, and the runtime embedded in released
binaries. Change the version there and in the Dockerfile, never in an
individual workflow; integration/toolchain_pins_rules_test.ts fails on drift.
deno must resolve on a plain, non-login shell's PATH or the verification
workflows exit 127.
During development, use these commands for quick feedback:
deno check- Type checkingdeno lint- Lintingdeno fmt- Formattingdeno run test- Tests (ordeno run test src/path/to_test.tsfor a single file)
Before opening a PR, run the verification workflow in the container sandbox
instead of these commands individually — it runs all checks (lint, fmt, test,
compile, deps audit, agent reviews) as a DAG and produces an attestation. See
agent-constraints/verification-conventions.md for the docker command. Do not
run deno check, deno lint, deno fmt, deno run test, or
deno run compile as a pre-PR gate — the verification workflow covers all of
them.
Source Control & Pull Requests
- Use the
github-prskill to create commit messages and pull requests. - PRs are auto-merged after passing CI security gates and attestation
validation. The CI merge gate requires:
validate-attestation(always runs),claude-adversarial-review(runs on core source changes),claude-ci-security-review(runs on workflow changes), andclaude-review-integrity(runs on trust-root changes). All other checks (lint, test, compile, code review, UX review, skill review) run locally via the verification workflow and are validated through the attestation. To prevent auto-merge, add theholdlabel to the PR. - When a PR fixes a GitHub issue filed by an external contributor (not a repo
collaborator), add them as a co-author to the commit. Check with
gh api /repos/swamp-club/swamp/collaborators --jq '.[].login'to determine if the issue author is a team member. If they are not, addCo-authored-by: Name <email>to the commit. Usegh api /users/<username>to look up their name, and use<username>@users.noreply.github.comas the email unless a public email is available from the API response.
Architecture
- Follows domain driven design principles. Use the
dddskill when designing or reviewing code. - Uses Cliffy for the command line
- Uses Ink for interactive terminal UIs (search, TUI dashboard)
- Uses LogTape for logging and non-interactive output (
"log"mode) - Uses JSON for structured output (
"json"mode via--json) - Every command must support both
"log"and"json"output modes - Start at
design/README.md(the six primitives and the index) anddesign/architecture.mdto understand the design
IMPORTANT: CLI commands and presentation renderers must import libswamp types
and functions from src/libswamp/mod.ts — never from internal module paths like
src/libswamp/data/get.ts. Only libswamp-internal code (other generators, tests
in src/libswamp/) may import from internal paths.
Testing
What tests belong in this repo
- Unit tests (
src/**/foo_test.ts, next to the source): in-process only. Never spawn subprocesses, bind sockets, or mutate process-global state (PATH,HOME,Deno.envraces across parallel test files — mock instead:withMockedCommand/withMockedFetchfrom@swamp-club/swamp-testing). The one exception is infrastructure adapter tests, which may run a localhost mock server onport: 0. - Integration tests (
integration/): wire real components together in-process — repositories on a real temp filesystem, services + event buses, port-0 mock servers. Must NOT spawn the CLI as a subprocess. - Architecture fitness tests (
integration/*_rules_test.ts,arch_fitness_helpers.ts): static rules over the source tree (layer boundaries, libswamp encapsulation, json-mode conformance, license headers). - Property tests (
*_property_test.ts, fast-check): invariants and round-trips for parsers, serialization, and data lifecycle. - Contract/conformance tests (
packages/testing/suites): run first-party providers against the same contracts extension authors are held to.
When to add beyond unit tests
The ddd skill maps building blocks to required test types (property tests,
conformance suites, integration tests). The triggers below cover structural
changes that aren't tied to a single building block:
-
Architectural fitness test — add or update a
*_rules_test.tsinintegration/when you introduce a new module boundary, allow a new cross-layer import, add a new libswamp public export, or change the json-mode/license-header conventions. Use the pinned-ratchet pattern fromarch_fitness_helpers.ts. -
Conformance suite — add a suite to
packages/testing/when you define a new provider interface that extension authors will implement. Follow thedatastore_conformance.tspattern. -
Integration test — add to
integration/when you change cross-component contracts (shared constants, event schemas, repository interfaces) or wire new components together for the first time. -
Acceptance/UAT tests do NOT live here. Anything that spawns the swamp CLI and asserts user-facing behavior (stdout, exit codes, flags, journeys) belongs in the
swamp-uatrepo, which runs against the compiled binary. Do not add newrunCliCommand-style subprocess tests tointegration/.
Timing and flakiness rules
- Never wait with a fixed
setTimeoutsleep for async work to finish — poll the condition withwaitForfrom@swamp-club/swamp-testing. - Never assert on measured wall-clock durations (upper bounds, or comparing two elapsed times); assert on work done (call counts, events) instead.
- Never sleep to advance a file's mtime — set it explicitly with
Deno.utime. - Generate unique test IDs with
crypto.randomUUID(), notDate.now(). - Restore env vars with
if (original !== undefined)— truthiness checks delete vars that were set to the empty string.
Conventions
- Unit tests live next to source files:
foo.ts→foo_test.ts - Integration tests live in
integration/directory (sibling tosrc/) - Use
@std/assertfor assertions (assertEquals,assertStringIncludes,assertThrows, etc.) - Use
ink-testing-libraryfor testing Ink components - Test private functions indirectly through public APIs
- Name tests as
Deno.test("functionName: describes behavior", ...)— seesrc/domain/data/composite_name_test.tsfor a canonical example - Run all tests with
deno run test - Run a single test file:
deno run test src/cli/repo_context_test.ts(do not use--before the file path) - Refactorings that change shared constants, paths, or cross-component contracts must include integration tests to verify components still work together
- Tests must run on Linux, macOS, and Windows. Use
assertPathEqualsfromsrc/infrastructure/persistence/path_test_helpers.tsfor path-string comparisons —assertEqualsagainst forward-slash literals fails on Windows. - Use
@std/path(dirname,basename,join,fromFileUrl,SEPARATOR) for all path operations. Never hand-roll withlastIndexOf("/"),split("/").pop(),URL.pathname, or"/"-prefixed concatenation. Deno.symlinkrequires{ type: "file" | "dir" }— Windows refuses symlinks whose target doesn't exist at link-creation time without it.- Test fixtures that initialize a repo (
repoInit/RepoService.init) passtools: []unless the test is about tool scaffolding. The default tool list (["claude"]) installs bundled skills into~/.claude/skills, outside the temp directory. withTempDircleanup uses an inline Windows-only.catch(() => {})to absorb EBUSY when V8 hasn't GC'd native handles — copy from any existing test file.
IMPORTANT: CLI command tests require logging initialization and model barrel
imports before they can run. See src/cli/commands/data_get_test.ts for the
pattern (await initializeLogging({}) and
import "../../domain/models/models.ts").
Session Learnings
If you hit a non-obvious problem during a session — something that wasted time, caused a wrong approach, or revealed a convention not documented here — propose an update to AGENTS.md or the relevant skill before finishing. Only capture things that would trip up future sessions, not one-off issues. Frame learnings as positive conventions (what to do) rather than reactive rules (what not to do).