Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# AGENTS.md — stackctl

## Runtime

This is a **Deno 2.x** project. There is no Node.js, no npm, no `package.json`. All dependencies come from JSR. Do not use `npm install`, `node`, or Node-ecosystem tools. Do not add `node_modules` or a `package.json`.

Entrypoint: `src/main.ts`
Package name on JSR: `@anitrend/stackctl`

## Dev commands

| Task | Command |
|------|---------|
| Cache deps | `deno task cache` |
| Type-check | `deno task check` |
| Format | `deno task fmt` |
| Check format (CI) | `deno task fmt:check` |
| Lint | `deno task lint` |
| Run all tests | `deno task test` |
| Coverage report | `deno task coverage` (run after test with `--coverage=.coverage`) |
| Build binary | `deno task build` |
| Cross-compile | `deno task build:linux:x64`, `:linux:arm64`, `:darwin:x64`, `:darwin:arm64` |

**CI-mandated order**: `deno task cache` -> `fmt:check` -> `lint` -> `check` (type-check) -> `test` -> `coverage`

Tests require permissions. The `test` task bakes them in, but if calling `deno test` directly you need:
```
deno test --allow-read --allow-write --allow-env --allow-run --allow-sys
```

## Formatting and linting (do not guess)

- **Line width**: 100 chars
- **Indent**: 2 spaces, no tabs
- **Quotes**: double quotes, not single
- **Prose wrap**: always
- **Lint rules**: `recommended` tag, with `no-unused-vars` **excluded**
- **Compiler**: `strict: true`, `noUnusedLocals: true`, `noUnusedParameters: true`

These are enforced by CI. Match the repo style; the formatter is the authority.

## Source layout

```
src/
cli/mod.ts — All CLI commands (one large file, ~1600 lines). Uses @cliffy/command.
config/ — Config loading, merging, validation, init. Exports from mod.ts.
compose/ — Docker Compose discovery, generation, merging, transform, reload, sync, plan.
render/ — ${VAR} interpolation in compose YAML.
docker/ — Docker / Swarm CLI wrappers (stack deploy, rm, ps, services, swarm status, etc.).
secrets/ — SOPS + age integration for encrypting .env files.
env/ — .env file scaffolding (list, create, diff, materialize from profile presets, audit).
process/ — ProcessRunner interface + RealProcessRunner. All external tool calls go through this.
testing/ — FakeProcessRunner for unit tests, plus helper factories.
main.ts — Bootstraps CLI.
version.ts — VERSION constant. Updated by CI on release.
```

## Config resolution order (later wins)

1. Built-in defaults (`src/config/defaults.ts`)
2. `.stackctl` (base)
3. `.stackctl.<profile>` (profile overlay)
4. `.stackctl.local` (local overrides, gitignored)
5. `.stackctl.local.<profile>` (local profile overlay)

Explicit `--override` files are applied after all config layers but before rendering.
Config merges use: scalars replaced, maps deep-merged, sequences appended.

## Exit codes

| Code | Meaning |
|------|---------|
| 0 | Success |
| 1 | Drift or validation failure |
| 2 | User config error |
| 3 | Missing dependency (Docker, sops, age) |
| 4 | Unexpected / internal error |

## Testing conventions

- Test files are co-located: `foo_test.ts` next to `foo.ts`.
- Use `FakeProcessRunner` from `src/testing/fakes.ts` instead of calling real tools. Pre-program responses with `FakeProcessRunnerBuilder`. The fake throws on unknown commands — every test must configure all commands its code path will invoke.
- Use `@std/assert` and `@std/testing` (from JSR) for assertions.
- Coverage threshold: **minimum 80% line coverage** for `src/`. Check with `deno task coverage`.
- The CI workflow (`ci.yml`) runs on pushes/PRs to `main` and `dev` branches.

## Build and release

- Binary compiled via `deno compile` with permissions baked in: `--allow-read --allow-write --allow-env --allow-sys --allow-run=git,docker,sops,age,age-keygen,shred,rm`
- Release workflow triggers on `v*` tags. Cross-compiles for 4 targets (linux/macOS, x64/arm64), packages tarballs, generates SHA256 checksums.
- **Version bump**: after a release, `version-bump.yml` updates both `deno.json` (`"version"` field) and `src/version.ts` (`VERSION` constant). If changing version manually, update both.
- `release-drafter-config.yml` auto-labels PRs by branch prefix.

## Branch, commit, and PR conventions

See `CONTRIBUTING.md` for full details. Quick reference:

- **Branch**: `<type>/<issue-number>-<short-description>` (e.g., `feat/1208-implement-override-merging`)
- **Commit**: `<type>(<scope>): <summary>` (e.g., `feat(config): add profile overlay discovery`)
- **PR title**: same format as commits, linked to the relevant issue
- **Valid types**: `feat`, `fix`, `chore`, `docs`, `refactor`, `test`, `build`, `ci`, `revert`
- **Valid scopes**: `config`, `generate`, `render`, `cli`, `docker`, `secrets`, `project`, `env`, `compose`, `process`

## Documentation requirements

- `AGENTS.md` (this file) and `docs/` must be kept up to date with code changes. If you add, remove, or change behavior, update them in the same PR.
- `docs/migration.md` documents the migration path from the legacy `stackctl.sh` script to the standalone `stackctl` binary. Keep it in sync with CLI changes that affect documented commands or config fields.

## General constraints

- This project is in **early development**. All commands are listed as "Planned" in `README.md`; check actual implementation status in `src/cli/mod.ts` before assuming a command exists.
- Permissions for the compiled binary are defined in `src/main.ts` shebang and the CI build step. Do not change permissions without updating both.
- Git-ignored files: `.stackctl.local`, `.stackctl.local.*`, `*.env` (except `.env.example`), `*.env.enc`, `age-key.txt`, `age.key`, `dist/`, `.rendered/`, `.coverage/`, `cov/`.
Loading