@naxodev/apnea 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +25 -10
  2. package/SECURITY.md +32 -0
  3. package/briefs/orchestrator.md +4 -3
  4. package/dist/cli.js +8571 -15324
  5. package/docs/adr/0005-harness-profiles.md +1 -1
  6. package/docs/adr/0010-package-split.md +1 -1
  7. package/docs/protocol/artifacts.md +18 -2
  8. package/docs/protocol/config.md +22 -25
  9. package/docs/protocol/manual-gate.md +8 -8
  10. package/docs/protocol/overview.md +18 -5
  11. package/extension/adapters/commit.ts +5 -1
  12. package/extension/adapters/dispatch.ts +9 -1
  13. package/extension/adapters/setup.ts +15 -1
  14. package/extension/adapters/start.ts +5 -1
  15. package/extension/adapters/status.ts +17 -2
  16. package/extension/adapters/wait.ts +6 -1
  17. package/extension/api.ts +7 -1
  18. package/extension/cli/main.ts +67 -7
  19. package/extension/cli/parse.ts +172 -5
  20. package/extension/domain/herdr.ts +0 -86
  21. package/extension/domain/paths.ts +3 -13
  22. package/extension/domain/setup.ts +0 -20
  23. package/extension/domain/timeouts.ts +4 -0
  24. package/extension/domain/types.ts +64 -11
  25. package/extension/domain/verify-commands.ts +200 -108
  26. package/extension/errors.ts +51 -16
  27. package/extension/operation-hooks.ts +6 -0
  28. package/extension/registry.ts +29 -15
  29. package/extension/run-tool.ts +19 -2
  30. package/extension/schema/config.ts +86 -30
  31. package/extension/schema/frontmatter.ts +57 -0
  32. package/extension/schema/state.ts +226 -16
  33. package/extension/services/app-live.ts +2 -1
  34. package/extension/services/config.ts +6 -4
  35. package/extension/services/file-system.ts +346 -75
  36. package/extension/services/herdr.ts +393 -402
  37. package/extension/services/operation-lock.ts +418 -0
  38. package/extension/services/process.ts +477 -0
  39. package/extension/services/run-store.ts +38 -16
  40. package/extension/services/vcs.ts +1388 -86
  41. package/extension/workflows/commit.ts +222 -18
  42. package/extension/workflows/dispatch.ts +320 -220
  43. package/extension/workflows/setup.ts +61 -141
  44. package/extension/workflows/start.ts +6 -5
  45. package/extension/workflows/status.ts +2 -2
  46. package/extension/workflows/wait.ts +63 -134
  47. package/package.json +2 -3
  48. package/schemas/config.schema.json +11 -7
  49. package/schemas/state.schema.json +170 -12
  50. package/herdr-plugin/herdr-plugin.toml +0 -15
  51. package/herdr-plugin/scripts/run-task.sh +0 -8
package/README.md CHANGED
@@ -20,8 +20,7 @@ only rebinds roles to profile names that already exist.
20
20
  `dist/cli.js` with a `#!/usr/bin/env bun` shebang, and npm does not enforce the `engines.bun`
21
21
  key. If you install `@naxodev/apnea` globally with only `node` on `PATH`, the `apnea` command
22
22
  will fail on first invocation — this is the most likely first-run failure for a new user.
23
- - **herdr**, for pane-based dispatch. The optional `floating` pane style additionally needs
24
- **herdr ≥ 0.7.4** plus the linked plugin (see [`docs/protocol/config.md`](docs/protocol/config.md)).
23
+ - **herdr**, for reusable interactive pane dispatch.
25
24
  - **jj or git.** Per [ADR 0007](docs/adr/0007-jj-first-commits.md), if neither is present
26
25
  auto-commit is refused.
27
26
  - **At least one agent CLI** — `pi`, `claude`, or `codex`.
@@ -53,8 +52,7 @@ Optionally put it on `PATH`, e.g. `ln -s "$(pwd)/dist/cli.js" ~/.local/bin/apnea
53
52
 
54
53
  ## Sixty-second quickstart
55
54
 
56
- 1. `apnea setup` — writes global profiles to `~/.config/apnea/config.json` (and the herdr
57
- `apnea` plugin, if herdr is present).
55
+ 1. `apnea setup` — writes global profiles to `~/.config/apnea/config.json`.
58
56
  2. `apnea start "<goal>"` — starts a run against your working copy.
59
57
  3. `apnea status` — a read-only snapshot of where the run stands and what to call next.
60
58
 
@@ -88,7 +86,7 @@ of them went stale; a reader could not tell which.
88
86
  | `start <goal>` | `workflow_start` | `[--allow-dirty] [--slug=name]` _(CLI only)_ | start a run |
89
87
  | `resume` / `abandon` | `workflow_start` | | resume or abandon |
90
88
  | `status` | `workflow_status` | | read-only snapshot |
91
- | `dispatch <kind>` | `dispatch_role` | `[--rework]` | launch a role |
89
+ | `dispatch <kind>` | `dispatch_role` | `[--rework] [--redeliver]` | launch or explicitly redeliver a role |
92
90
  | `wait` | `workflow_wait` | `[--poll=<ms>] [--budget=<ms>]` _(CLI only)_ | wait for the pending artifact |
93
91
  | `commit [message]` | `workflow_commit_phase` | `[--done]` | verify + commit phase |
94
92
  | `reset-rounds <gate>` | — | `[--i-am-human]` _(CLI only)_ | **human only** |
@@ -96,6 +94,18 @@ of them went stale; a reader could not tell which.
96
94
  Prefix with `/` inside Pi (`/apnea status`), or run it as a shell command (`apnea status`).
97
95
  `/apnea-start` and `/apnea-status` are short aliases.
98
96
 
97
+ `--rework` remains through 0.2.x as a deprecated assertion. Persisted review state selects and
98
+ authorizes rework even when callers omit the flag. Caller input grants authority only for
99
+ ambiguous version-1 plan or code migration. `--redeliver` reuses matching pending ownership without
100
+ advancing the round. Use it only after proving the prior pane is dead; a manual dispatch with no
101
+ pane requires the operator to request redelivery explicitly. Apnea persists whether pending work
102
+ crossed a manual or interactive boundary. Interactive ownership without a saved pane id refuses as
103
+ ambiguous. Legacy null-pane ownership has no safe discriminator and also refuses redelivery.
104
+ Before any redelivery checks or mutations, Apnea reads the pending artifact. A complete artifact
105
+ refuses redelivery and directs the caller to `workflow_wait`; review artifacts count as complete only
106
+ with a valid verdict and legal, schema-valid rework metadata. Malformed or incomplete artifacts
107
+ continue through normal liveness validation.
108
+
99
109
  `reset-rounds` is not a Pi tool. It exists only as `apnea reset-rounds` and `/apnea reset-rounds`.
100
110
  Only the CLI gates it — it refuses unless stdin/stdout are a terminal and a human retypes the gate
101
111
  key, or passes `--i-am-human`. The slash command has no such gate: `/apnea` is already a human at
@@ -109,14 +119,19 @@ See [`docs/protocol/config.md`](docs/protocol/config.md) for the budget-floor ar
109
119
  ### Setup flags
110
120
 
111
121
  ```text
112
- /apnea setup # ~/.config/apnea/config.json from PATH (+ herdr apnea plugin when herdr is present)
122
+ /apnea setup # ~/.config/apnea/config.json from PATH
113
123
  /apnea setup --project # also .apnea/config.json role→profile only
124
+ /apnea setup --force # replace profiles; required to replace malformed global JSON
114
125
  /apnea setup --agents-md # also write/refresh an AGENTS.md loop primer at the repo root
115
126
  ```
116
127
 
117
- `pane_style` (`regular` default, `floating` opt-in) lives in config; floating needs herdr ≥ 0.7.4
128
+ Setup never silently destroys malformed JSON. Without `--force`, malformed existing global JSON
129
+ is left byte-for-byte unchanged and setup refuses. `--force` atomically replaces it and reports
130
+ the replacement. Malformed existing project config always fails closed.
118
131
 
119
- - the linked plugin see [`docs/protocol/config.md`](docs/protocol/config.md).
132
+ After upgrading from a version with floating panes, old copied
133
+ `~/.config/apnea/herdr-plugin` files are inert. Apnea does not unlink or delete them; remove that
134
+ directory manually if you no longer need it.
120
135
 
121
136
  The `@naxodev/pi-apnea` adapter applies Pi-specific role launch behavior. The core package remains
122
137
  host-neutral.
@@ -129,8 +144,8 @@ UI in v1.**
129
144
  **What you can rely on:** the loop, the artifact contract, and the CLI are implemented, and the
130
145
  extension suite is green — CI runs it on every pull request.
131
146
 
132
- **What may still change:** the command surface and config shape may move before `1.0`. This is
133
- `0.1.0` — breaking changes will land in minor bumps, not patches.
147
+ **What may still change:** the command surface and config shape may move before `1.0`. Breaking
148
+ changes will land in minor bumps, not patches.
134
149
 
135
150
  **Non-goals for v1:** worktrees, parallel coders, push/PR automation, memory store, native
136
151
  CLAUDE.md injection, force-approve, config UI.
package/SECURITY.md CHANGED
@@ -17,6 +17,38 @@ this tool at a repository.
17
17
  - **Planner-authored verify commands execute at the commit gate**, in the project working
18
18
  directory, in the same trust domain as the coder writing source. Their output goes to
19
19
  `verify.log`.
20
+ - **Git phase commits bypass hooks by design.** Apnea stages into an isolated index, writes and
21
+ validates the exact tree, creates it with `commit-tree`, then compare-and-swaps the branch ref.
22
+ Running hooks after validation would let them change the committed tree. Apnea preserves
23
+ `commit.gpgsign=true` by asking `commit-tree` to sign, but it does not run commit-msg,
24
+ pre-commit, or post-commit policy hooks. The validated tree is written to the real index before
25
+ the ref moves, so index failure cannot advance the branch. An unrelated external Git process can
26
+ still race that real-index replacement; the repository lock coordinates Apnea processes, not
27
+ arbitrary Git clients.
28
+ - **Repository locks identify owners by PID plus a random token.** The token prevents one owner
29
+ from deleting a replacement lock. Apnea never reclaims dead or malformed locks automatically:
30
+ it reports the validated lock path and requires manual cleanup after the user verifies no owner
31
+ remains. PID reuse can keep a dead lock looking live because there is no portable
32
+ process-creation identity across supported platforms; this also fails closed. Global setup waits
33
+ only when the recorded PID is currently live; it never retries stale or malformed ownership.
34
+ - **Setup serializes account-global configuration and role resources.** Every setup holds one
35
+ same-user lock keyed by the canonical account home for its full read/merge/write and role-agent
36
+ materialization. Setup takes this global lock before an optional repository lock. Global config
37
+ replacement is atomic, rejects symlink components, and fsyncs the file and parent directory
38
+ where the platform supports it. Malformed global JSON remains byte-for-byte unchanged unless a
39
+ human passes `--force`; the result explicitly reports a forced replacement. Malformed project
40
+ config always fails closed.
41
+ - **Persisted inputs are bounded regular UTF-8 files.** Config, state, setup `AGENTS.md`, and
42
+ workflow artifacts are opened once and read through the descriptor. Apnea rejects non-regular
43
+ files, invalid UTF-8, and inputs over 1 MiB before parsing. Descriptor checks avoid a separate
44
+ stat/read race. Existing project and account-home symlink confinement still applies.
45
+ - **State-owned artifact paths cannot escape the repository.** `pending_artifact`,
46
+ `current_phase_package`, and `current_code_review` must be relative paths below `.apnea/`.
47
+ Absolute paths, NULs, backslashes, empty components, and `.` or `..` components make state
48
+ corrupt.
49
+ - **Project-path checks reject existing symlink components but cannot provide `openat` semantics.**
50
+ Node does not expose a portable directory-descriptor traversal API, so a hostile same-user
51
+ process can still race a checked parent component before the final filesystem operation.
20
52
 
21
53
  See [`docs/protocol/config.md`](docs/protocol/config.md) (Trust model section) and
22
54
  [`docs/adr/0006-config-trust-model.md`](docs/adr/0006-config-trust-model.md) for the full
@@ -13,11 +13,11 @@ You schedule an Apnea **Run**. You do not implement product code, edit app sourc
13
13
  1. Start run (clean tree unless allow-dirty / resume). **Start only writes state — do not stop here.**
14
14
  2. Immediately dispatch **planner** (`kind=plan`) → wait for plan artifact.
15
15
  3. Dispatch **reviewer** (plan review) → wait for verdict.
16
- 4. On `CHANGES_REQUIRED`, re-dispatch planner then reviewer (new round).
16
+ 4. On `CHANGES_REQUIRED`, dispatch planner then reviewer. Persisted state advances the round.
17
17
  5. On `APPROVED`, dispatch planner for **phase package**.
18
18
  6. Dispatch **coder** with phase package path.
19
19
  7. Dispatch **reviewer** (code review) → wait.
20
- 8. On `CHANGES_REQUIRED` with `rework: code` or no rework field, re-dispatch coder with `rework=true`, then reviewer.
20
+ 8. On `CHANGES_REQUIRED` with `rework: code` or no rework field, dispatch coder, then reviewer. Persisted state advances the round.
21
21
  9. On `CHANGES_REQUIRED` with `rework: phase_package`, dispatch planner for the revised phase package, then coder and reviewer. The package dispatch advances the review round automatically.
22
22
  10. On `APPROVED`, commit phase (run verify commands first).
23
23
  11. Repeat from phase packaging until planner reports no remaining phases.
@@ -26,6 +26,7 @@ You schedule an Apnea **Run**. You do not implement product code, edit app sourc
26
26
  ## Rules
27
27
 
28
28
  - One outstanding Dispatch at a time.
29
+ - Never infer or authorize rework from caller input, except an explicit `rework=true` assertion for ambiguous version-1 plan or code state. `workflow_wait` persists every new required target, and `dispatch_role` consumes it.
29
30
  - Read verdicts only from artifact front-matter.
30
31
  - Never push remotes or open PRs.
31
32
 
@@ -40,7 +41,7 @@ don't, the orchestrator still owns recovery.
40
41
  - Prompt sitting in the input / INSERT / "Pasted text" → `herdr pane send-keys <id> Enter`.
41
42
  - Agent idle, no artifact → `herdr pane run <id>` with a short nudge naming the exact artifact path.
42
43
  - Agent working / API retrying → `workflow_wait` again (do not re-dispatch yet).
43
- - Pane missing / harness exited to bare shell → `dispatch_role` same kind (no rework flag).
44
+ - Pane missing / harness exited to bare shell → `dispatch_role` same kind with `redeliver=true` (no rework flag).
44
45
  3. Only escalate after recovery failed twice, or on: round cap, dirty reviewer tree, illegal step, VCS confusion.
45
46
 
46
47
  ## Escalate (after recovery fails)