fix(gates): one --gates rule in every command, run through the shim, and no executor pause the executor lacks

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-10-22 15:38:20 +02:00
commit af36578f12
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
11 changed files with 190 additions and 52 deletions

View file

@ -4,17 +4,9 @@ Imported from `CLAUDE.md` via pointer.
## Autonomy mode (`--gates`, v3.4.0)
All four pipeline commands accept a boolean `--gates {true|false}` flag. Presence (`--gates true`, or bare `--gates`) turns gating **on** — the run pauses at autonomy boundaries for operator confirmation. Absence (or `--gates false`, the default) runs phases continuously without pausing.
All four pipeline commands accept `--gates [true|false|closed|open]`. A bare `--gates`, `--gates true` or `--gates closed` turns gating **on**; `--gates false`, `--gates open` or no flag (the default) leaves it off. The next token is the flag's value only when it is one of those four; any other token stays the command's own argument, so `/trekplan --gates --project <dir>` and `/trekexecute --gates plan.md` are a bare `--gates`. Each command maps the value once through `lib/util/autonomy-gate.mjs`; a shim that fails falls back to the same mapping in prose, and the run goes on. With gating on, `/trekbrief` pauses before its auto chain, `/trekresearch` after each topic, and `/trekplan` after exploration, synthesis and adversarial review. `/trekexecute` records `gates_mode` and pauses on nothing: its one stop is the merge into main, which always asks (below).
`/trekexecute` additionally refines *how strict* the gating is via a `gates_mode` policy derived from the brief's effort signal (an explicit operator `--gates` flag takes precedence over the brief signal — see `commands/trekexecute.md` § High-effort behavior (v5.1.1)):
| `gates_mode` | Derived from (effort) | Behavior |
|--------------|-----------------------|----------|
| `adaptive` | standard / absent (default) | Stop only at meaningful boundaries (manifest-audit FAIL, plan-critic BLOCKER, main-merge gate) |
| `closed` | high | Stop at every autonomy boundary; operator confirms each transition |
| `open` | low | Skip optional checkpoints; trust manifests + verify gates only |
Under the hood: `lib/util/autonomy-gate.mjs` runs the state machine `idle → gates_on | auto_running → paused_for_gate → completed`. `start` routes to `gates_on` when `--gates true`, else `auto_running`; a `phase_boundary` from `gates_on` pauses at `paused_for_gate` (awaiting `resume`); `finish` reaches the terminal `completed`. The module is pure data with no I/O. Separately, `lib/stats/event-emit.mjs` records named lifecycle events (`brief-approved`, `main-merge-gate`, `main-merge-approved`, `main-merge-declined`, `gates_mode_flag_override`, `user_input`) to `trekexecute-stats.jsonl` in the plugin data directory (`CLAUDE_PLUGIN_DATA`, else `~/.claude/plugins/data/voyage-ktg-plugin-marketplace/`, the directory `scripts/yardstick.mjs` reads); a write that fails is reported, never skipped. The main-merge gate is the final autonomy boundary before HEAD lands on `main`.
Under the hood: `lib/util/autonomy-gate.mjs` runs the state machine `idle → gates_on | auto_running → paused_for_gate → completed`. `start` routes to `gates_on` when `--gates` is bare, `true` or `closed`, else `auto_running`; a `phase_boundary` from `gates_on` pauses at `paused_for_gate` (awaiting `resume`); `finish` reaches the terminal `completed`. The module is pure data with no I/O; the commands run only its `start` transition, to map the flag. Separately, `lib/stats/event-emit.mjs` records named lifecycle events (`brief-approved`, `main-merge-gate`, `main-merge-approved`, `main-merge-declined`, `user_input`) to `trekexecute-stats.jsonl` in the plugin data directory (`CLAUDE_PLUGIN_DATA`, else `~/.claude/plugins/data/voyage-ktg-plugin-marketplace/`, the directory `scripts/yardstick.mjs` reads); a write that fails is reported, never skipped. The main-merge gate is the final autonomy boundary before HEAD lands on `main`.
**The main-merge gate is a lock, and the guard asks the one question** (operator decision 2026-10-08). The gate opens only when the run is `completed` and the review's verdict gate (`lib/review/verdict-gate.mjs`, K7) exits 0; BLOCK (1) and "could not decide" (2) keep it closed. When it opens, `/trekexecute` runs the merge into main itself and `pre-bash-executor` answers "ask" before it runs, in auto mode too, so that question is the only one. In the permission mode where Claude Code shows no prompts, the guard refuses the merge instead, and every other call it would ask about (push, tag or publish, destructive git), since no one can be asked (operator decisions 2026-10-09). The executor no longer asks "ready to merge?" first, and `--gates` no longer adds a pause before the wave merge-back: the guard asks there as well, once per session, because the push and the merge go in one call. Before this, one merge cost two questions in three places: the `--gates` pause before the wave merge-back, the gate's own question after the waves were merged, and the gate's question on the single-session path followed by the guard's at the merge itself.