# 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 `any` types
- Use named exports, not default exports
- Comprehensive unit test coverage
- All `.ts` and `.tsx` files must include the AGPLv3 copyright header from
  `FILE-LICENSE-TEMPLATE.md` at the top of the file (as `//` comments). Run
  `deno run license-headers` to add headers to any new files.
- No fire-and-forget promises. Every promise must be awaited or explicitly
  handled — unhandled promises race with `Deno.exit` and silently lose data. For
  outbound network calls, pass an `AbortSignal` with 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 `organizations` array from `GET /api/whoami` is an authorization contract,
  not a general-purpose payload. `getCollectives()` in
  `src/infrastructure/http/swamp_club_client.ts` reduces 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 on `slug` (as
  `collectiveEntitlements` does) 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:

1. `deno check` - Type checking
2. `deno lint` - Linting
3. `deno fmt` - Formatting
4. `deno run test` - Tests (or `deno run test src/path/to_test.ts` for a single
   file)

Before opening a PR, run the verification workflows instead of these commands
individually. They run on the host, each in a fresh `git worktree` at the
verified commit: `verify-build` (lint, fmt, type check, test, deps audit,
compile), `verify-reviews` (agent reviews), and `verify-skills` (skill review
and trigger evals). Their results produce the attestation. See
`agent-constraints/verification-conventions.md` for the commands. Do not run
`deno check`, `deno lint`, `deno fmt`, `deno run test`, or `deno run compile` as
a pre-PR gate — the verification workflows cover all of them.

## Source Control & Pull Requests

- Use the `github-pr` skill 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), and
  `claude-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 the `hold` label 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, add
  `Co-authored-by: Name <email>` to the commit. Use `gh api /users/<username>`
  to look up their name, and use `<username>@users.noreply.github.com` as the
  email unless a public email is available from the API response.

## Architecture

- Follows domain driven design principles. Use the `ddd` skill 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) and
  `design/architecture.md` to understand the design

IMPORTANT: `src/libswamp/mod.ts` lists libswamp's public surface — it is not an
import path. Import each name from the libswamp file that defines it (e.g.
`dataGet` from `src/libswamp/data/get.ts`), never from `mod.ts`: an import of
the barrel depends on all of libswamp and hides what a file really uses from
`deno info`. CLI commands, presentation renderers and `src/serve/` may import a
name from a libswamp file only if `mod.ts` exports that name from that file — to
make a name public, add it to `mod.ts`. Only libswamp-internal code (other
generators, tests in `src/libswamp/`) may import names `mod.ts` does not list.
`deno run unbarrel` rewrites any barrel import into direct ones.

## 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.env` races across parallel test files — mock instead:
  `withMockedCommand` / `withMockedFetch` from `@swamp-club/swamp-testing`). The
  one exception is infrastructure adapter tests, which may run a localhost mock
  server on `port: 0`.
- Supply env values to any test — unit or integration — with `withMockedEnv`
  from `src/infrastructure/persistence/path_test_helpers.ts`. It answers
  `Deno.env.get`, `has` and `toObject` for the current test file only; map a
  variable to `undefined` to read it as unset. Child processes never see it, so
  pass them an explicit `env`. `integration/test_env_mutation_rules_test.ts`
  fails on any `Deno.env.set` / `Deno.env.delete` in a test file.
- A test that points `SWAMP_HOME` at a fresh directory and runs a real extension
  bundle calls `linkSharedSwampDenoCache(home)` from
  `integration/test_helpers.ts` first. The bundler's `DENO_DIR` lives under
  `SWAMP_HOME`, so an empty one makes `deno bundle` download esbuild from npm on
  every run — a network dependency `withMockedFetch` cannot stub.
- **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.ts` in
  `integration/` 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 from
  `arch_fitness_helpers.ts`.
- **Conformance suite** — add a suite to `packages/testing/` when you define a
  new provider interface that extension authors will implement. Follow the
  `datastore_conformance.ts` pattern.
- **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-uat` repo, which runs against the compiled binary. Do not add
  new `runCliCommand`-style subprocess tests to `integration/`.

### Timing and flakiness rules

- Never wait with a fixed `setTimeout` sleep for async work to finish — poll the
  condition with `waitFor` from `@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()`, not `Date.now()`.
- Keep tests correct under `deno test --repeats`, which the scheduled flaky-test
  job runs: each repeat must exercise the code path again, not pass against
  state an earlier repeat left behind. A test that registers a type in a
  process-global registry (`modelRegistry`, `vaultTypeRegistry`,
  `datastoreTypeRegistry`) either uses a per-run type name built with
  `crypto.randomUUID()`, or registers unconditionally and calls `invalidateType`
  in a `finally`. Guard with `has()` only when the registered provider captures
  nothing from the test body.
- 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 to `src/`)
- Use `@std/assert` for assertions (`assertEquals`, `assertStringIncludes`,
  `assertThrows`, etc.)
- Use `ink-testing-library` for testing Ink components
- Test private functions indirectly through public APIs
- Name tests as `Deno.test("functionName: describes behavior", ...)` — see
  `src/domain/data/composite_name_test.ts` for 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 `assertPathEquals` from
  `src/infrastructure/persistence/path_test_helpers.ts` for path-string
  comparisons — `assertEquals` against forward-slash literals fails on Windows.
- Use `@std/path` (`dirname`, `basename`, `join`, `fromFileUrl`, `SEPARATOR`)
  for all path operations. Never hand-roll with `lastIndexOf("/")`,
  `split("/").pop()`, `URL.pathname`, or `"/"`-prefixed concatenation.
- `Deno.symlink` requires `{ 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`) pass
  `tools: []` unless the test is about tool scaffolding. The default tool list
  (`["claude"]`) installs bundled skills into `~/.claude/skills`, outside the
  temp directory.
- `withTempDir` cleanup 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).
