# @naxodev/apnea

Apnea drives a multi-role development loop — plan, review, code, review, verify, commit — where
each role runs in its own real terminal pane and hands off work through files on disk instead of
a hidden subagent. Any harness that can run a shell command can hold the orchestrator seat.

## The loop

```text
plan → plan review → phase package → code → code review → verify+commit → …
→ pr-description
```

Roles can be different harnesses (Pi, Claude, Codex, …) via **global profiles**. Project config
only rebinds roles to profile names that already exist.

## Requirements

- **bun `>=1.3.7`.** Needed to **run** the tool, not just to build it: the installed `bin` is
  `dist/cli.js` with a `#!/usr/bin/env bun` shebang, and npm does not enforce the `engines.bun`
  key. If you install `@naxodev/apnea` globally with only `node` on `PATH`, the `apnea` command
  will fail on first invocation — this is the most likely first-run failure for a new user.
- **herdr**, for reusable interactive pane dispatch.
- **jj or git.** Per [ADR 0007](docs/adr/0007-jj-first-commits.md), if neither is present
  auto-commit is refused.
- **At least one agent CLI** — `pi`, `claude`, or `codex`.

## Install

```bash
bun install -g @naxodev/apnea
# or
npm install -g @naxodev/apnea
```

For Pi, install the separate adapter:

```bash
pi install npm:@naxodev/pi-apnea
```

From source, for contributors:

```bash
cd packages/apnea
bun install
bun run build
./dist/cli.js help
```

Optionally put it on `PATH`, e.g. `ln -s "$(pwd)/dist/cli.js" ~/.local/bin/apnea`.

## Sixty-second quickstart

1. `apnea setup` — writes global profiles to `~/.config/apnea/config.json`.
2. `apnea start "<goal>"` — starts a run against your working copy.
3. `apnea status` — a read-only snapshot of where the run stands and what to call next.

`apnea status` with no run in progress looks like this:

```console
$ apnea status
OK: no active run
next: apnea start
{
  "has_state": false
}
```

## CLI reference

Any harness that can run a shell command can hold the orchestrator seat — the CLI and the Pi
tools share one definition in `extension/registry.ts`, so they cannot drift apart (see
[ADR 0009](docs/adr/0009-cli-driver-split.md)).

### Operations

One row per operation. The CLI verb and the `/apnea` subcommand are the same word — they share a
single definition in `extension/registry.ts`, so they cannot drift apart (see
[ADR 0009](docs/adr/0009-cli-driver-split.md)). The two were listed as separate tables until one
of them went stale; a reader could not tell which.

| Operation             | Pi tool                 | Flags                                                                         | Purpose                                                        |
| --------------------- | ----------------------- | ----------------------------------------------------------------------------- | -------------------------------------------------------------- |
| `setup`               | —                       | `[--project] [--force] [--agents-md]`                                         | global profiles, optional project bindings, `AGENTS.md` primer |
| `start <goal>`        | `workflow_start`        | `[--allow-dirty] [--slug=name]` _(CLI only)_                                  | start a run                                                    |
| `resume`              | `workflow_start`        |                                                                               | resume an existing run                                         |
| `abandon`             | —                       | `[--confirm=<token>] [--stop-panes] [--stopped-work] [--acknowledge-corrupt]` | human-only stop and archive                                    |
| `status`              | `workflow_status`       |                                                                               | read-only snapshot                                             |
| `dispatch <kind>`     | `dispatch_role`         | `[--rework] [--redeliver]`                                                    | launch or explicitly redeliver a role                          |
| `wait`                | `workflow_wait`         | `[--poll=<ms>] [--budget=<ms>]` _(CLI only)_                                  | wait for the pending artifact                                  |
| `commit [message]`    | `workflow_commit_phase` | `[--done]`                                                                    | verify + commit phase                                          |
| `reset-rounds <gate>` | —                       | `[--i-am-human]` _(CLI only)_                                                 | **human only**                                                 |

Prefix with `/` inside Pi (`/apnea status`), or run it as a shell command (`apnea status`).
`/apnea-start` and `/apnea-status` are short aliases.

`--rework` remains through 0.2.x as a deprecated assertion. Persisted review state selects and
authorizes rework even when callers omit the flag. Caller input grants authority only for
ambiguous version-1 plan or code migration. `--redeliver` reuses matching pending ownership without
advancing the round. Use it only after proving the prior pane is dead; a manual dispatch with no
pane requires the operator to request redelivery explicitly. Apnea persists whether pending work
crossed a manual or interactive boundary. Interactive ownership without a saved pane id refuses as
ambiguous. Legacy null-pane ownership has no safe discriminator and also refuses redelivery.
Before any redelivery checks or mutations, Apnea reads the pending artifact. A complete artifact
refuses redelivery and directs the caller to `workflow_wait`; review artifacts count as complete only
with a valid verdict and legal, schema-valid rework metadata. Malformed or incomplete artifacts
continue through normal liveness validation.

`reset-rounds` is not a Pi tool. It exists only as `apnea reset-rounds` and `/apnea reset-rounds`.
Only the CLI gates it — it refuses unless stdin/stdout are a terminal and a human retypes the gate
key, or passes `--i-am-human`. The slash command has no such gate: `/apnea` is already a human at
a terminal. See [ADR 0002](docs/adr/0002-orchestrator-authority.md).

`apnea wait` is resumable: exit `3` means the call's budget ran out but the role hasn't timed out,
so call `apnea wait` again. Exit codes: `0` ok, `1` refused/error, `2` usage, `3` still waiting.
See [`docs/protocol/config.md`](docs/protocol/config.md) for the budget-floor arithmetic behind
`--poll` and `--budget`.

### Abandon confirmation

`apnea abandon` previews the pending role, pane, artifact, delivery mode, acquired panes, and full commit transaction.
It returns a confirmation token bound to the repository and raw state bytes. Pi's `/apnea abandon` uses the same flags and checks.
The model-facing `workflow_start` rejects `action=abandon`.

`--confirm=<token> --stop-panes` requests closure of known panes through bounded Herdr calls.
It records intent before closure and results afterward. It always retains active state: a successful pane close cannot prove descendant exit.
The invoking pane is never closed. Unknown invoking identity, query failures, and missing panes refuse closure.
Legacy pane history can be incomplete, and manual workers have no pane identity.

After stopping all run workers and descendants, use `--confirm=<token> --stopped-work` to attest that termination is complete.
This explicit attestation applies equally to CLI and Pi; it is not an OS-enforced process guarantee.
Changed state requires another preview. An unresolved `pending_commit` always refuses abandonment; recover it with `apnea commit` first.

Corrupt state additionally requires `--acknowledge-corrupt`, acknowledging unknown worker ownership and commit recovery risks.
The escape preserves exact bytes, including invalid UTF-8. Raw fingerprinting is bounded to 64 MiB; larger files refuse unchanged.
Archival writes audit evidence first, publishes an exclusive state archive, and removes active state last.
Failures retain active state; an interrupted archival attempt may also leave an archive or audit file.

Tasks and artifacts remain at their original paths. New runs persist a fresh UUID and use `.apnea/runs/<run-id>/` namespaces.
Active legacy runs retain their original paths. A late write to an old run's artifact path cannot satisfy the new run's pending artifact.
Workers still share repository access; namespaces do not sandbox arbitrary writes.

### Setup flags

```text
/apnea setup              # ~/.config/apnea/config.json from PATH
/apnea setup --project    # also .apnea/config.json role→profile only
/apnea setup --force      # replace profiles; required to replace malformed global JSON
/apnea setup --agents-md  # also write/refresh an AGENTS.md loop primer at the repo root
```

Setup never silently destroys malformed JSON. Without `--force`, malformed existing global JSON
is left byte-for-byte unchanged and setup refuses. `--force` atomically replaces it and reports
the replacement. Malformed existing project config always fails closed.

After upgrading from a version with floating panes, old copied
`~/.config/apnea/herdr-plugin` files are inert. Apnea does not unlink or delete them; remove that
directory manually if you no longer need it.

The `@naxodev/pi-apnea` adapter applies Pi-specific role launch behavior. The core package remains
host-neutral.

Fallback: skill `apnea-setup` or prompt `/apnea-init` (both point at the same rules). **No config
UI in v1.**

## Maturity status

**What you can rely on:** the loop, the artifact contract, and the CLI are implemented, and the
extension suite is green — CI runs it on every pull request.

**What may still change:** the command surface and config shape may move before `1.0`. Breaking
changes will land in minor bumps, not patches.

**Non-goals for v1:** worktrees, parallel coders, push/PR automation, memory store, native
CLAUDE.md injection, force-approve, config UI.

## Docs

| Doc                                                            | Purpose                |
| -------------------------------------------------------------- | ---------------------- |
| [`CONTEXT.md`](CONTEXT.md)                                     | Glossary               |
| [`docs/protocol/overview.md`](docs/protocol/overview.md)       | Loop, steps, tools     |
| [`docs/protocol/artifacts.md`](docs/protocol/artifacts.md)     | Paths + front-matter   |
| [`docs/protocol/config.md`](docs/protocol/config.md)           | Profiles + trust model |
| [`docs/protocol/manual-gate.md`](docs/protocol/manual-gate.md) | Bootstrap acceptance   |
| [`docs/adr/`](docs/adr/)                                       | Decisions              |
| [`briefs/`](briefs/)                                           | Role briefs            |

## Contributing

Build, test, typecheck commands, version-control conventions, and what CI enforces are in
[`CONTRIBUTING.md`](CONTRIBUTING.md).

## Security

Apnea runs repo-controlled text through agent CLIs by design — read
[`SECURITY.md`](SECURITY.md) for the trust model before pointing it at a repository you don't
trust, and for how to report a vulnerability.

## License

MIT — see [`LICENSE`](LICENSE).
