@naxodev/apnea 0.2.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 (44) hide show
  1. package/README.md +18 -1
  2. package/SECURITY.md +32 -0
  3. package/briefs/orchestrator.md +4 -3
  4. package/dist/cli.js +8283 -15058
  5. package/docs/protocol/artifacts.md +18 -2
  6. package/docs/protocol/config.md +15 -3
  7. package/docs/protocol/manual-gate.md +8 -8
  8. package/docs/protocol/overview.md +17 -4
  9. package/extension/adapters/commit.ts +5 -1
  10. package/extension/adapters/dispatch.ts +9 -1
  11. package/extension/adapters/setup.ts +15 -1
  12. package/extension/adapters/start.ts +5 -1
  13. package/extension/adapters/status.ts +17 -2
  14. package/extension/adapters/wait.ts +6 -1
  15. package/extension/api.ts +7 -1
  16. package/extension/cli/main.ts +67 -7
  17. package/extension/cli/parse.ts +172 -5
  18. package/extension/domain/paths.ts +2 -11
  19. package/extension/domain/timeouts.ts +4 -0
  20. package/extension/domain/types.ts +65 -3
  21. package/extension/errors.ts +51 -16
  22. package/extension/operation-hooks.ts +6 -0
  23. package/extension/registry.ts +29 -15
  24. package/extension/run-tool.ts +19 -2
  25. package/extension/schema/config.ts +58 -16
  26. package/extension/schema/frontmatter.ts +57 -0
  27. package/extension/schema/state.ts +210 -13
  28. package/extension/services/app-live.ts +2 -1
  29. package/extension/services/config.ts +6 -4
  30. package/extension/services/file-system.ts +346 -75
  31. package/extension/services/herdr.ts +389 -242
  32. package/extension/services/operation-lock.ts +418 -0
  33. package/extension/services/process.ts +477 -0
  34. package/extension/services/run-store.ts +38 -16
  35. package/extension/services/vcs.ts +1258 -328
  36. package/extension/workflows/commit.ts +214 -13
  37. package/extension/workflows/dispatch.ts +274 -57
  38. package/extension/workflows/setup.ts +59 -32
  39. package/extension/workflows/start.ts +6 -4
  40. package/extension/workflows/status.ts +2 -2
  41. package/extension/workflows/wait.ts +62 -77
  42. package/package.json +2 -2
  43. package/schemas/config.schema.json +5 -1
  44. package/schemas/state.schema.json +165 -11
package/README.md CHANGED
@@ -86,7 +86,7 @@ of them went stale; a reader could not tell which.
86
86
  | `start <goal>` | `workflow_start` | `[--allow-dirty] [--slug=name]` _(CLI only)_ | start a run |
87
87
  | `resume` / `abandon` | `workflow_start` | | resume or abandon |
88
88
  | `status` | `workflow_status` | | read-only snapshot |
89
- | `dispatch <kind>` | `dispatch_role` | `[--rework]` | launch a role |
89
+ | `dispatch <kind>` | `dispatch_role` | `[--rework] [--redeliver]` | launch or explicitly redeliver a role |
90
90
  | `wait` | `workflow_wait` | `[--poll=<ms>] [--budget=<ms>]` _(CLI only)_ | wait for the pending artifact |
91
91
  | `commit [message]` | `workflow_commit_phase` | `[--done]` | verify + commit phase |
92
92
  | `reset-rounds <gate>` | — | `[--i-am-human]` _(CLI only)_ | **human only** |
@@ -94,6 +94,18 @@ of them went stale; a reader could not tell which.
94
94
  Prefix with `/` inside Pi (`/apnea status`), or run it as a shell command (`apnea status`).
95
95
  `/apnea-start` and `/apnea-status` are short aliases.
96
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
+
97
109
  `reset-rounds` is not a Pi tool. It exists only as `apnea reset-rounds` and `/apnea reset-rounds`.
98
110
  Only the CLI gates it — it refuses unless stdin/stdout are a terminal and a human retypes the gate
99
111
  key, or passes `--i-am-human`. The slash command has no such gate: `/apnea` is already a human at
@@ -109,9 +121,14 @@ See [`docs/protocol/config.md`](docs/protocol/config.md) for the budget-floor ar
109
121
  ```text
110
122
  /apnea setup # ~/.config/apnea/config.json from PATH
111
123
  /apnea setup --project # also .apnea/config.json role→profile only
124
+ /apnea setup --force # replace profiles; required to replace malformed global JSON
112
125
  /apnea setup --agents-md # also write/refresh an AGENTS.md loop primer at the repo root
113
126
  ```
114
127
 
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.
131
+
115
132
  After upgrading from a version with floating panes, old copied
116
133
  `~/.config/apnea/herdr-plugin` files are inert. Apnea does not unlink or delete them; remove that
117
134
  directory manually if you no longer need it.
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)