From dfb0da068f816568fb1fe4cdba619f0094becb66 Mon Sep 17 00:00:00 2001 From: Danil Pismenny Date: Tue, 4 Aug 2026 20:42:06 +0300 Subject: [PATCH 1/7] minor --- memory-bank/features/FT-017/README.md | 3 +- memory-bank/features/FT-017/brief.md | 14 +- memory-bank/features/FT-017/decision-log.md | 128 +++++++++--------- memory-bank/features/FT-017/design.md | 25 ++-- .../features/FT-017/implementation-plan.md | 24 ++-- 5 files changed, 93 insertions(+), 101 deletions(-) diff --git a/memory-bank/features/FT-017/README.md b/memory-bank/features/FT-017/README.md index 682ccb9..6f02d52 100644 --- a/memory-bank/features/FT-017/README.md +++ b/memory-bank/features/FT-017/README.md @@ -33,5 +33,4 @@ Git delivery. for live GitHub-writing verification. - [decision-log.md](decision-log.md) - Historical release-distribution decisions retained from the earlier FT-017 - migration package. + FPF decisions, evidence provenance, and the remaining live-verification gate. diff --git a/memory-bank/features/FT-017/brief.md b/memory-bank/features/FT-017/brief.md index 42c9353..6e08987 100644 --- a/memory-bank/features/FT-017/brief.md +++ b/memory-bank/features/FT-017/brief.md @@ -43,7 +43,7 @@ supported Codex CLI while closing the remaining capability-contract gap. | --- | --- | --- | --- | --- | | `MET-01` | Human-gate capability contract visibility | One implicit `workspace-write` command | Every run reports either restricted or full-delivery permissions | Dry-run/help assertions | | `MET-02` | Full Git delivery reachability | GitHub/network/Git writes are not guaranteed | An explicitly authorized mode can edit, test, commit, push, and create/update a PR | Deterministic command tests plus opt-in live E2E evidence | -| `MET-03` | Supported Codex command compatibility | Compatibility can drift with CLI option placement | Generated commands are accepted by the supported Codex CLI contract | Bats command-shape coverage and real-Codex smoke validation | +| `MET-03` | Supported Codex command compatibility | Compatibility can drift with CLI option placement | Generated commands are accepted by the supported Codex CLI contract | Go command-shape tests and real-Codex smoke validation | ### Scope @@ -86,8 +86,10 @@ supported Codex CLI while closing the remaining capability-contract gap. ### Constraints / Assumptions -- `ASM-01` The supported reference environment is Codex CLI `0.145.0`, whose - global permission options are accepted before the `exec` subcommand. +- `ASM-01` Issue #37 reproduces the rejected argument order with Codex CLI + `0.144.6`; the feature must verify the exact supported command grammar with + the approved executable before acceptance. The repository does not pin an + installed Codex version. - `ASM-02` Full delivery requires independently configured GitHub authentication and repository authorization; `start-issue` can select a launcher policy but cannot grant those external capabilities. @@ -173,8 +175,8 @@ supported Codex CLI while closing the remaining capability-contract gap. | Check ID | Covers | How to check | Expected result | Evidence path | | --- | --- | --- | --- | --- | -| `CHK-01` | `EC-01` - `EC-04`, `SC-01` - `SC-04`, `NEG-01` | `make test` | Syntax, shellcheck, memory-bank audit, and deterministic Bats coverage pass for both modes and FT-015 regressions. | Local terminal/CI test output | -| `CHK-02` | `EC-01`, `EC-05`, `SC-01`, `SC-05`, `NEG-02` | Review `--help`, `--human-gate-help`, README files, and spec alongside output assertions | All surfaces state the same default, opt-in, capability, risk, and troubleshooting contract. | Review diff and Bats output | +| `CHK-01` | `EC-01` - `EC-04`, `SC-01` - `SC-04`, `NEG-01` | `make test` | Go formatting/vet/tests, memory-bank audit, and deterministic human-gate coverage pass for both modes and FT-015 regressions. | Local terminal/CI test output | +| `CHK-02` | `EC-01`, `EC-05`, `SC-01`, `SC-05`, `NEG-02` | Review `--help`, `--human-gate-help`, README files, and spec alongside Go output assertions | All surfaces state the same default, opt-in, capability, risk, and troubleshooting contract. | Review diff and Go test output | | `CHK-03` | `EC-02`, `EC-03`, `EC-06`, `SC-02`, `SC-03`, `SC-06`, `NEG-03` | With explicit approval, run the real-Codex full-delivery E2E procedure from FT-017's plan | Supported Codex accepts the command and the isolated fixture records commit, push, PR, terminal status, and retained artifacts. | Retained E2E artifact directory and fixture PR URL | ### Test matrix @@ -198,5 +200,5 @@ supported Codex CLI while closing the remaining capability-contract gap. | Evidence ID | Artifact | Producer | Path contract | Reused by checks | | --- | --- | --- | --- | --- | | `EVID-01` | Local and CI test output | implementer / CI | Terminal output and GitHub Actions job | `CHK-01` | -| `EVID-02` | Documentation diff plus help assertions | implementer / reviewer | Changed docs and Bats output | `CHK-02` | +| `EVID-02` | Documentation diff plus help assertions | implementer / reviewer | Changed docs and Go test output | `CHK-02` | | `EVID-03` | Live-E2E log, state files, commit/PR identifiers | approved operator | Retained E2E artifact path printed by runner | `CHK-03` | diff --git a/memory-bank/features/FT-017/decision-log.md b/memory-bank/features/FT-017/decision-log.md index af7ca3f..a5d3eaf 100644 --- a/memory-bank/features/FT-017/decision-log.md +++ b/memory-bank/features/FT-017/decision-log.md @@ -5,15 +5,15 @@ doc_function: reference purpose: "Records FPF analysis and accepted local decisions for FT-017. It does not own feature scope, selected design, acceptance criteria, or execution sequence." derived_from: - brief.md - - ../../../.github/workflows/ci.yml - - ../../../.github/workflows/release.yml - - ../../../install.sh + - design.md + - implementation-plan.md + - https://github.com/openai/codex/blob/main/codex-rs/exec/src/cli.rs status: active audience: humans_and_agents must_not_define: - - ft_016_scope - - ft_016_selected_design - - ft_016_acceptance_criteria + - ft_017_scope + - ft_017_selected_design + - ft_017_acceptance_criteria - implementation_sequence --- @@ -21,88 +21,82 @@ must_not_define: ## Purpose and Ownership -This log records why `DEC-01` remains open. The canonical owner of the blocker and the verify contract is [brief.md](brief.md). A selected solution belongs in a future `design.md`, not here. +This log records FPF decisions for FT-017. `brief.md` owns problem space and +acceptance; `design.md` owns the selected solution; `implementation-plan.md` +owns execution sequencing. This file records rationale and provenance only. -## DL-01 — Multi-platform Go release distribution contract +## DL-01 — Permission boundary and default -**Status:** accepted on 2026-07-22 by feature requester. +**Status:** accepted by FPF review on 2026-08-04. -### FPF framing +### Facts -- **Bounded context:** distribution is separate from CLI-semantic parity. It owns the relationship among a compiled artifact, release assets, installer/update selection, and the platform on which a user executes the artifact. -- **Evidence boundary:** facts below come only from the current repository and issue #34. The issue requests a Go binary but defines neither supported OS/architecture targets nor asset-selection rules. -- **Decision criterion:** provide Go releases for the requester-selected operating systems with explicit platform assets, verifiable integrity, and no inferred reduction of platform support. - -### Available facts - -1. `install.sh` downloads one fixed asset named `start-issue` and one fixed checksum file named `start-issue.sha256`. -2. `.github/workflows/release.yml` builds the current sole release asset on `ubuntu-latest`. -3. `.github/workflows/ci.yml` verifies installation on both `ubuntu-latest` and `macos-latest`. -4. A Bash release artifact is portable across those CI operating systems; a Go executable is platform-specific. -5. Issue #34 requires Go to become the primary distribution artifact and requires installation/release workflows to publish it successfully, but does not state the intended OS/architecture matrix or compatibility policy. +- Issue #37 states that `workspace-write` does not provide the network and Git + metadata writes needed for normal GitHub delivery. +- Existing FT-015 behavior uses `workspace-write` and must remain compatible. +- Full delivery can create external GitHub state and must therefore be opt-in. ### Decision -| Area | Accepted contract | -| --- | --- | -| Target matrix | `linux/amd64`, `linux/arm64`, `darwin/amd64`, `darwin/arm64`, and `windows/amd64`. The operating systems are requester-selected; the architecture set follows the explicit `dapi/port-selector` release pattern. | -| Build/release | Use GoReleaser v2 with `CGO_ENABLED=0`, one statically built executable per target, `start-issue--` asset names, and a SHA-256 `checksums.txt` manifest. During the v1-to-v2 cutover, also upload a `start-issue` bridge and its `start-issue.sha256` checksum for the v1 updater. | -| POSIX install | Adapt the referenced install-script strategy: detect `uname -s`/`uname -m`, select the matching asset, download it, verify its checksum from `checksums.txt`, and install it under the public name `start-issue`. | -| Windows delivery | Publish `start-issue-windows-amd64.exe` as a first-class release asset and document manual download/PATH installation. The existing POSIX shell installer is not a Windows installer. | -| Cutover | No separate human release-approval gate. The normal tag-triggered release proceeds only after `CHK-01` through `CHK-03` are green. | - -### Resolution rationale +Keep two semantic modes: `restricted` and `full-delivery`. `restricted` is the +built-in default. Full delivery requires an explicit CLI/environment selection +and a visible warning before Codex starts. -The requester directly chose macOS, Linux, and Windows and delegated release-strategy selection to this feature. The selected GoReleaser layout and target architecture set are grounded in the referenced `dapi/port-selector` repository: its `.goreleaser.yml` uses the exact five targets, `CGO_ENABLED=0`, binary-format archives, and `checksums.txt`; its installer performs POSIX OS/architecture detection. The decision preserves explicit asset integrity while avoiding a false claim that the POSIX installer supports Windows. +### FPF rationale -### Rejected alternatives +The launcher capability boundary is a separate bounded context from task-level +approval decisions. Least privilege is the selection criterion: absent an +explicit user choice, preserve the existing restricted behavior. The semantic +names keep the public contract independent from Codex's low-level flags. -- A single cross-platform `start-issue` Go asset is rejected: compiled Go executables are platform-specific. -- A narrower target matrix is rejected: the requester selected all three operating systems and the referenced strategy supplies the matching explicit matrix. -- A release approval gate is rejected: the requester explicitly said it is unnecessary; automated evidence gates remain mandatory. +## DL-02 — Full-delivery command mapping -## DL-02 — Go toolchain and Windows update boundary +**Status:** accepted by FPF review on 2026-08-04, pending live verification. -**Status:** accepted on 2026-07-22 by feature owner under delegated release-strategy choice. +### Evidence -### FPF framing and facts - -- The toolchain is an execution-environment contract, not a user-facing CLI capability; it must be deterministic in local, CI, and release paths. -- The referenced `dapi/port-selector` release pattern pins `go 1.21` in `go.mod` and GitHub Actions. This repository currently has no Go toolchain contract. -- A POSIX process can replace its executable through the existing install/update style; Windows generally locks a running executable. The referenced release strategy documents a Windows binary download rather than a shell installer. +- The official Codex `exec` CLI source marks + `dangerously_bypass_approvals_and_sandbox` as a global option for `exec`. +- The same source marks `model`, `json`, and `output-last-message` as global + options compatible with the `exec` command. +- The local repository has no installed `codex` executable, so live parser + validation cannot be performed in this worktree. ### Decision -1. Pin Go `1.24` in `go.mod`, `mise.toml`, and CI/release setup for this migration. -2. The initial Windows contract is binary release plus manual installation and manual update: `start-issue update` on Windows must not try to overwrite its running `.exe`; it returns a clear instruction naming the matching release asset. POSIX retains verified automatic install/update behavior. +Map `full-delivery` to: + +```text +codex [--model MODEL] --dangerously-bypass-approvals-and-sandbox exec \ + --cd WORKTREE --json --output-last-message PATH - +``` -### Rationale and risk control +Keep restricted mode on the existing `codex exec --cd WORKTREE +--sandbox workspace-write --json --output-last-message PATH -` path. Do not +use `--ask-for-approval` in the generated command because issue #37 identifies +that spelling/placement as the compatibility failure under investigation. -Go 1.24 is the explicit baseline because its linker emits a Mach-O `LC_UUID`, which current macOS releases require. The Windows manual-update behavior avoids an unsafe or undeclared helper-process design. It is a documented platform-specific delivery difference, not a hidden parity exception, because the Bash baseline has no Windows runtime contract. +### Rationale and limits -## DL-03 — ID-01 dry-run worktree-path conflict handling +The bypass mapping is the only documented current CLI mechanism found that +explicitly covers both approvals and sandboxing. It is intentionally treated +as a high-risk capability switch, not as authorization for destructive or +production actions. `CHK-03` and `AG-01` remain mandatory before acceptance. -**Status:** accepted on 2026-07-24 by the feature requester. +## DL-03 — Current implementation grounding -### Case and approved expectation +**Status:** accepted by FPF review on 2026-08-04. -- **Stable case ID:** `ID-01` -- **Parity case:** `worktree-path-conflict-dry-run` in - `cmd/start-issue/parity_integration_test.go` -- **Bash baseline expectation:** accepts the supplied conflict choice during - `--dry-run` and reports `Worktree path already exists` before continuing - down the selected reuse path. -- **Go expectation:** reports `Worktree path exists; would prompt for reuse or - delete/recreate` without consuming a choice or selecting a reuse/delete - outcome. +The feature package must target the current Go implementation under +`cmd/start-issue/`, its Go tests, `test/e2e/human-gate.sh`, `Makefile`, and the +README/spec documentation. The earlier references to `scripts/lib/start_issue` +and Bats were stale artifacts from the pre-Go implementation and have been +removed from the execution plan. -### User-visible rationale and acceptance +## Open evidence gate -When a worktree path conflicts, the Go dry-run tells the user that a choice is -still required. This avoids presenting one stdin-supplied choice as the -determined outcome of a non-executing command and, importantly, avoids the -legacy path in which the delete/recreate selection can reach mutation logic -before Bash's later dry-run check. The different dry-run diagnostic is -user-visible and is intentionally accepted for `ID-01`; all other observable -records, fake-command logs, and filesystem state remain subject to `CHK-01` -parity. +The exact supported release/version matrix is not asserted locally. Before +`delivery_status: done`, the approved live Codex executable must accept both +command forms and a retained E2E artifact must prove the declared full-delivery +behavior. If that verification fails, reject `full-delivery` and keep the +restricted path as the safe fallback. diff --git a/memory-bank/features/FT-017/design.md b/memory-bank/features/FT-017/design.md index 73fba04..7350899 100644 --- a/memory-bank/features/FT-017/design.md +++ b/memory-bank/features/FT-017/design.md @@ -31,10 +31,12 @@ capability boundary around that lifecycle. The solution must keep restricted behavior safe by default while giving an operator one deliberate, visible way to authorize end-to-end Git delivery. -The local reference CLI is Codex `0.145.0`. Its approval and sandbox flags are -global options, while JSONL and last-message outputs are `exec` options. The -design therefore needs a stable semantic contract owned by `start-issue`, not -an unchecked string of arbitrary Codex arguments. +Issue #37 reproduces the obsolete `--ask-for-approval` placement with Codex +`0.144.6`. The current upstream Codex `exec` source exposes +`--dangerously-bypass-approvals-and-sandbox` as a global option and keeps the +JSONL/last-message contract on `exec`. The design therefore owns a semantic +contract rather than passing arbitrary Codex arguments; the exact approved +release remains a live-verification concern. ## C4 Applicability @@ -66,10 +68,9 @@ enforces credentials and repository authorization independently. - `SOL-01` Add one semantic configuration axis named human-gate permissions with exactly two values: `restricted` and `full-delivery`. Resolve it as CLI option → environment variable → built-in `restricted`. -- `SOL-02` Map `restricted` to Codex global options - `--ask-for-approval never --sandbox workspace-write` and map explicit - `full-delivery` to - `--ask-for-approval never --sandbox danger-full-access`. +- `SOL-02` Keep `restricted` on the existing `--sandbox workspace-write` + command and map explicit `full-delivery` to the global + `--dangerously-bypass-approvals-and-sandbox` option. - `SOL-03` Build the command in supported grammar order: `codex`, global model and permission options, `exec`, then worktree and batch-output options. - `SOL-04` Print the resolved semantic mode and a concise capability statement @@ -96,8 +97,8 @@ enforces credentials and repository authorization independently. | Trade-off ID | Decision | Benefit | Cost / Risk | | --- | --- | --- | --- | | `TRD-01` | Expose two semantic modes instead of raw Codex controls | Small, testable public contract with stable operator meaning | Advanced Codex policies are not expressible through this feature. | -| `TRD-02` | Use `danger-full-access` for explicit full delivery | Provides network and Git metadata writes required by the delivery contract | Batch commands are unsandboxed and must be treated as high risk. | -| `TRD-03` | Keep `never` approval for batch execution | Preserves unattended human-gate semantics | Capability errors cannot escalate mid-run and must be diagnosed clearly. | +| `TRD-02` | Use the explicit Codex bypass option for full delivery | Covers the approvals and sandbox boundaries implicated by issue #37 | Batch commands are unsandboxed and must be treated as high risk. | +| `TRD-03` | Use the explicit bypass mode for unattended batch execution | Preserves unattended human-gate semantics for the selected full-delivery mode | Capability errors cannot escalate mid-run and must be diagnosed clearly. | ## Accepted Local Decisions @@ -120,8 +121,8 @@ enforces credentials and repository authorization independently. | --- | --- | --- | --- | | `CTR-01` | `--human-gate-permissions restricted\|full-delivery` | CLI parser / config resolver | CLI value wins over environment; invalid or empty explicit values fail before issue fetch. | | `CTR-02` | `START_ISSUE_HUMAN_GATE_PERMISSIONS` | shell environment / config resolver | Used only when CLI input is absent; unset resolves to `restricted`. | -| `CTR-03` | Restricted Codex command | launcher / Codex | `codex [--model MODEL] --ask-for-approval never --sandbox workspace-write exec --cd WORKTREE --json --output-last-message PATH -`. | -| `CTR-04` | Full-delivery Codex command | launcher / Codex | Same shape as `CTR-03`, with `--sandbox danger-full-access`; selected only by explicit `full-delivery`. | +| `CTR-03` | Restricted Codex command | launcher / Codex | `codex [--model MODEL] exec --cd WORKTREE --sandbox workspace-write --json --output-last-message PATH -`. | +| `CTR-04` | Full-delivery Codex command | launcher / Codex | `codex [--model MODEL] --dangerously-bypass-approvals-and-sandbox exec --cd WORKTREE --json --output-last-message PATH -`; selected only by explicit `full-delivery`. | | `CTR-05` | Permission status output | launcher / operator | Reports semantic mode and capability boundary before execution and in dry-run; full delivery includes an unsandboxed-execution warning. | ## Invariants diff --git a/memory-bank/features/FT-017/implementation-plan.md b/memory-bank/features/FT-017/implementation-plan.md index 757d928..e0e798a 100644 --- a/memory-bank/features/FT-017/implementation-plan.md +++ b/memory-bank/features/FT-017/implementation-plan.md @@ -35,13 +35,9 @@ existing FT-015 batch, state, status, and resume behavior. | Path / module | Current role | Why relevant | Reuse / mirror | | --- | --- | --- | --- | -| `scripts/start-issue` | Initializes shared CLI state and sources modules | New resolved permission state needs a safe default | Follow existing agent/model state initialization | -| `scripts/lib/start_issue/cli.sh` | Parses public options and validates mode combinations | Owns the new CLI input and early rejection path | Follow `--human-gate`/`--model` value parsing patterns | -| `scripts/lib/start_issue/config.sh` | Resolves config values and sources | Owns CLI/environment/default precedence | Mirror model resolution without adding project/user persistence | -| `scripts/lib/start_issue/agent.sh` | Builds and runs Codex human-gate commands | Main permission mapping and supported grammar change surface | Keep array-based command construction and FT-015 state helpers | -| `scripts/lib/start_issue/output.sh` | Renders help, dry-run, and runtime status | Must expose mode, capabilities, and warning consistently | Extend current human-gate help and dry-run output | -| `test/helpers/fake-bin/codex` | Deterministic Codex command double | Must validate global option order and both sandbox mappings | Extend argument capture/rejection behavior | -| `test/start_issue.bats` | Public CLI and human-gate regression suite | Existing tests cover restricted command, DONE, HUMAN_GATE, and errors | Add precedence, invalid value, full-delivery, and order assertions | +| `cmd/start-issue/main.go` | Go CLI parser, config resolution, launcher, help, and human-gate state | Owns the new option, command mapping, and diagnostics | Extend existing options and array-based `exec.Cmd` construction | +| `cmd/start-issue/main_test.go` | Deterministic Go regression suite | Existing tests cover human-gate command, DONE, HUMAN_GATE, and errors | Add precedence, invalid value, full-delivery, and order assertions | +| `cmd/start-issue/parity_integration_test.go` | Go/Bash observable parity coverage | Protects unaffected legacy behavior during the Go implementation | Keep non-human-gate parity cases green | | `test/e2e/human-gate.sh` | Opt-in real-Codex smoke runner | Closest existing live verification surface | Add a separately guarded full-delivery scenario only after approval | | `README.md`, `README.ru.md`, `doc/spec.md` | Public and canonical behavior docs | Must match help and command behavior | Update together with output assertions | @@ -59,15 +55,15 @@ existing FT-015 batch, state, status, and resume behavior. | Open Question ID | Question | Why unresolved | Blocks | Default action / escalation owner | | --- | --- | --- | --- | --- | -| `OQ-01` | Which future Codex versions remain compatible after `0.145.0`? | The external CLI has no repository-owned stability guarantee. | Does not block implementation; affects future maintenance | Treat `0.145.0` as the tested baseline and update adapter/docs together on command-shape failure. | +| `OQ-01` | Which future Codex versions remain compatible after the issue baseline? | The external CLI has no repository-owned stability guarantee and no local executable is installed. | Does not block deterministic implementation; blocks final live acceptance | Treat the approved live executable as the acceptance baseline and update adapter/docs together on command-shape failure. | | `OQ-02` | Which isolated fixture repository/issue should receive the live full-delivery PR? | Live target selection is operator-owned and may change. | `STEP-06` only | Require explicit target and approval through `AG-01`; never infer from global focus or an unrelated repo. | ## Environment Contract | Area | Contract | Used by | Failure symptom | | --- | --- | --- | --- | -| setup | Bash, Git, jq, shellcheck, Bats, fake agent binaries, and the current modular source tree | `STEP-01` - `STEP-05` | `make test` dependency or fixture failure | -| supported Codex | Local reference is `codex-cli 0.145.0`; approval/sandbox options must be accepted before `exec` | `STEP-03`, `STEP-06` | Real CLI rejects command before emitting `thread.started` | +| setup | Go, Bash, Git, and the current Go source tree; deterministic tests use fakes | `STEP-01` - `STEP-05` | `make test` dependency or fixture failure | +| supported Codex | Issue baseline is `0.144.6`; the approved live executable must accept the recorded command forms | `STEP-03`, `STEP-06` | Real CLI rejects command before emitting `thread.started` | | deterministic test | `make test` is canonical and must not use network or real agent binaries | `CHK-01`, `STEP-02` - `STEP-05` | External side effects or nondeterministic test failures | | live access | Explicit opt-in, authenticated `gh`, real Codex, authorized fixture repo/issue, network, and permission to push/create a PR | `STEP-06` | Missing auth, push rejection, absent PR, or no terminal status | | secrets | Credentials remain in existing authenticated tools/environment and never enter tracked files or command output | all steps | Token-like data appears in diff, logs, or state artifacts | @@ -99,10 +95,10 @@ existing FT-015 batch, state, status, and resume behavior. | Step ID | Actor | Implements | Goal | Touchpoints | Artifact | Verifies | Evidence IDs | Check command / procedure | Blocked by | Needs approval | Escalate if | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| `STEP-01` | agent | `REQ-03`, `SOL-01`, `SD-02`, `CTR-01`, `CTR-02`, `FM-03` | Add CLI/environment/default resolution and fail-fast validation | `scripts/start-issue`, `cli.sh`, `config.sh` | Resolved mode and source | `CHK-01`, `NEG-01` | `EVID-01` | Focused Bats tests, then `make test` | `PRE-01` | none | Validation occurs after fetch or mutation | -| `STEP-02` | agent | `REQ-07`, `INV-01`, `INV-02` | Extend fake Codex and tests before changing launcher behavior | Fake Codex, Bats suite | Red/green command contract tests | `CHK-01`, `SC-01` - `SC-04` | `EVID-01` | `bats test/start_issue.bats` | `STEP-01` | none | Fake cannot distinguish global and exec arguments | -| `STEP-03` | agent | `REQ-01`, `REQ-02`, `REQ-04`, `SOL-02`, `SOL-03`, `CTR-03`, `CTR-04`, `INV-04` | Build validated restricted/full-delivery commands in supported order | `agent.sh` | Array-based Codex command mapping | `CHK-01`, `SC-02`, `SC-03` | `EVID-01` | Focused Bats tests; inspect `--dry-run` command | `STEP-02`, `PRE-02` | none | Supported Codex rejects generated grammar | -| `STEP-04` | agent | `REQ-01`, `REQ-06`, `SOL-04`, `CTR-05`, `FM-02`, `FM-04` | Add capability output, warning, dedicated help, and public docs | `output.sh`, README files, spec | Consistent operator contract | `CHK-02`, `SC-05`, `NEG-02` | `EVID-02` | Help assertions and documentation review | `STEP-03` | none | Docs imply permission equals credentials or product authorization | +| `STEP-01` | agent | `REQ-03`, `SOL-01`, `SD-02`, `CTR-01`, `CTR-02`, `FM-03` | Add CLI/environment/default resolution and fail-fast validation | `cmd/start-issue/main.go`, `main_test.go` | Resolved mode and source | `CHK-01`, `NEG-01` | `EVID-01` | Focused Go tests, then `make test` | `PRE-01` | none | Validation occurs after fetch or mutation | +| `STEP-02` | agent | `REQ-07`, `INV-01`, `INV-02` | Extend the fake Codex process and Go tests before changing launcher behavior | `cmd/start-issue/main_test.go` | Red/green command contract tests | `CHK-01`, `SC-01` - `SC-04` | `EVID-01` | `go test ./cmd/start-issue` | `STEP-01` | none | Fake cannot distinguish global and exec arguments | +| `STEP-03` | agent | `REQ-01`, `REQ-02`, `REQ-04`, `SOL-02`, `SOL-03`, `CTR-03`, `CTR-04`, `INV-04` | Build validated restricted/full-delivery commands in supported order | `cmd/start-issue/main.go` | Array-based Codex command mapping | `CHK-01`, `SC-02`, `SC-03` | `EVID-01` | Focused Go tests; inspect `--dry-run` command | `STEP-02`, `PRE-02` | none | Supported Codex rejects generated grammar | +| `STEP-04` | agent | `REQ-01`, `REQ-06`, `SOL-04`, `CTR-05`, `FM-02`, `FM-04` | Add capability output, warning, dedicated help, and public docs | `cmd/start-issue/main.go`, README files, spec | Consistent operator contract | `CHK-02`, `SC-05`, `NEG-02` | `EVID-02` | Help assertions and documentation review | `STEP-03` | none | Docs imply permission equals credentials or product authorization | | `STEP-05` | agent | `REQ-05`, `REQ-07`, `SOL-05`, `INV-05`, `RB-01`, `RB-02` | Run full deterministic regression and simplify review | All changed runtime/tests/docs | Green local suite and complexity verdict | `CHK-01`, `CHK-02`, `SC-04` | `EVID-01`, `EVID-02`, `EVID-09` | `make test`; inspect diff for unnecessary branches/abstractions | `STEP-01` - `STEP-04` | none | FT-015 state/resume behavior changes | | `STEP-06` | human + agent | `REQ-08`, `SOL-06`, `SD-04`, `SC-06`, `NEG-03`, `RB-03` | Extend/run isolated live full-delivery verification and retain evidence | E2E runner and approved fixture repo/issue | E2E log, state artifacts, commit and PR URL | `CHK-03`, `EC-06` | `EVID-03` | Follow canonical cmux caller-tab procedure and poll to terminal PASS/failure | `STEP-05`, `PRE-03`, `OQ-02` | `AG-01` | Target/auth/caller context is missing, or any unexpected external scope appears | From 5b09380be60efdbfad5de12816630f72449a6b0b Mon Sep 17 00:00:00 2001 From: Danil Pismenny Date: Tue, 4 Aug 2026 21:07:30 +0300 Subject: [PATCH 2/7] Add Codex human-gate delivery permissions --- README.md | 48 +++++- README.ru.md | 44 ++++- cmd/start-issue/main.go | 86 +++++++++- cmd/start-issue/main_test.go | 160 +++++++++++++++++- doc/spec.md | 41 +++-- memory-bank/features/FT-017/brief.md | 8 +- memory-bank/features/FT-017/decision-log.md | 15 +- .../features/FT-017/implementation-plan.md | 9 +- test/e2e/human-gate.sh | 60 ++++++- 9 files changed, 422 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 573c702..23242e4 100644 --- a/README.md +++ b/README.md @@ -159,6 +159,7 @@ second runtime implementation. | `--prompt-file PATH` | Prompt template file for the selected agent. With `init`, the file content to write. Mutually exclusive with `--prompt`. | | `--improve-prompt` | Ask the selected agent to generate a reviewable improved prompt template proposal, then exit before creating a worktree. | | `--human-gate` | Codex-only batch mode for issue work. Runs `codex exec`, exits on `STATUS: DONE`, and resumes the same session on `STATUS: HUMAN_GATE`. | +| `--human-gate-permissions restricted\|full-delivery` | Select the human-gate capability contract. CLI overrides `START_ISSUE_HUMAN_GATE_PERMISSIONS`; default is `restricted`. | | `--human-gate-help` | Show dedicated help for the Codex human-gate workflow, including prompt contract, exit codes, and state files. | | `--prompt-output-file PATH` | Proposal output path for `--improve-prompt`. | | `--no-init` | Do not run `init.sh` even if it exists in the created worktree. | @@ -190,6 +191,7 @@ Related Claude Code marketplace workflows: | `START_ISSUE_PROMPT` | Inline prompt template used when no CLI prompt is provided. It overrides project and user prompt files. Mutually exclusive with `START_ISSUE_PROMPT_FILE` when no CLI prompt is provided. | | `START_ISSUE_PROMPT_FILE` | Prompt template file used when no CLI prompt is provided. It overrides project and user prompt files. Mutually exclusive with `START_ISSUE_PROMPT` when no CLI prompt is provided. | | `START_ISSUE_WORKTREE_DIR` | Default parent directory for created worktrees when `--worktree-dir` is not provided. Built-in default: `~/worktrees`. | +| `START_ISSUE_HUMAN_GATE_PERMISSIONS` | Human-gate capability contract when the CLI option is absent: `restricted` or `full-delivery`. Built-in default: `restricted`. | | `START_ISSUE_DUMP_PROMPT` | When set to `1`, dry-run output includes the full rendered prompt instead of only summary information. | ## Configuration Files @@ -238,6 +240,35 @@ The batch flow: This mode is intentionally Codex-only. `--human-gate` with any other agent fails clearly instead of being ignored. +Human-gate permissions are explicit: + +- `restricted` is the default. It uses `--sandbox workspace-write` and supports + working-tree edits, but network access, Git metadata writes, push, and PR + delivery are not guaranteed. +- `full-delivery` is an explicit opt-in. It runs Codex with + `--dangerously-bypass-approvals-and-sandbox`, allowing the normal issue + workflow to read GitHub context, edit, test, commit, push, and create or + update a PR when the current `gh` session and repository permissions allow + it. This is unsandboxed execution. + +Select the mode with the CLI (highest precedence), the environment, or the +safe built-in default: + +```bash +start-issue 123 --agent codex --human-gate \ + --human-gate-permissions full-delivery + +START_ISSUE_HUMAN_GATE_PERMISSIONS=full-delivery \ + start-issue 123 --agent codex --human-gate +``` + +Full delivery changes launcher capability only. It does not authorize +destructive Git operations, production/security changes, or product decisions; +the prompt must still return `STATUS: HUMAN_GATE` for those. Before using it, +verify `gh auth status`, the selected account, the remote, and repository write +access. A restricted capability failure should be handled by manual delivery or +an explicit full-delivery rerun, not reported as a task-level product decision. + When the workflow is about to block for a branch/worktree decision, it prints `Waiting for input: ...`. Before handing control to an interactive agent or Codex batch run, it prints `Handing off to in `. A non-zero @@ -291,18 +322,29 @@ test/e2e/human-gate.sh --scenario human-gate Exit the resumed Codex session to let the script verify the artifacts. +To validate actual commit, push, and PR creation in the private fixture, use +the separately authorized unsandboxed scenario. It creates and retains a unique +remote branch and PR as evidence: + +```bash +START_ISSUE_E2E=1 START_ISSUE_E2E_FULL_DELIVERY=1 \ + test/e2e/human-gate.sh --scenario full-delivery +``` + #### Scenarios and checks | Scenario | Command | What it verifies | | --- | --- | --- | | `done` | `START_ISSUE_E2E=1 make e2e-human-gate` | A real Codex batch run emits `thread.started`, saves `thread-id`, `events.jsonl`, and `last-message.txt`, ends with `STATUS: DONE`, and leaves no fixture change other than `.start-issue` state. | | `human-gate` | `START_ISSUE_E2E=1 test/e2e/human-gate.sh --scenario human-gate` | The same artifact and clean-worktree checks, plus the reported explicit `codex resume --include-non-interactive ` handoff. The operator exits the resumed interactive session before the script can finish. | +| `full-delivery` | `START_ISSUE_E2E=1 START_ISSUE_E2E_FULL_DELIVERY=1 test/e2e/human-gate.sh --scenario full-delivery` | The current Codex accepts the global full-delivery option and completes a unique fixture commit, push, and PR; the runner prints the retained PR URL. | -Both scenarios verify authenticated `gh`, a real rather than fake Codex binary, +All scenarios verify authenticated `gh`, a real rather than fake Codex binary, and the required `codex exec` help interface (`--output-last-message`, without the obsolete `--ask-for-approval` flag). The selected Codex executable is -printed in the test output. They do not prove application behavior beyond this -human-gate protocol and are intentionally excluded from CI. +printed in the test output. The `done` and `human-gate` scenarios do not prove +application behavior beyond this protocol; `full-delivery` additionally proves +the explicitly authorized fixture delivery path. All are excluded from CI. ### CI sandbox E2E diff --git a/README.ru.md b/README.ru.md index 93e158a..76723c7 100644 --- a/README.ru.md +++ b/README.ru.md @@ -81,6 +81,31 @@ Batch flow: Режим намеренно поддерживается только для Codex. `--human-gate` с любым другим agent завершается явной ошибкой. +Права human-gate задаются явно: + +- `restricted` — безопасный default. Он использует `--sandbox workspace-write` + и разрешает редактирование worktree, но не гарантирует network, запись Git + metadata, push и доставку PR. +- `full-delivery` — явный opt-in. Codex запускается с + `--dangerously-bypass-approvals-and-sandbox`, поэтому при корректной `gh` + session и правах репозитория workflow может прочитать GitHub context, + изменить и проверить код, сделать commit/push и создать или обновить PR. + Это unsandboxed execution. + +```bash +start-issue 123 --agent codex --human-gate \ + --human-gate-permissions full-delivery + +START_ISSUE_HUMAN_GATE_PERMISSIONS=full-delivery \ + start-issue 123 --agent codex --human-gate +``` + +Приоритет: CLI, environment, затем `restricted`. Full delivery расширяет только +технические возможности launcher и не разрешает destructive Git operations, +production/security changes или product decisions: для них prompt по-прежнему +обязан вернуть `STATUS: HUMAN_GATE`. Перед запуском проверьте `gh auth status`, +выбранный account, remote и write access к репозиторию. + Перед ожиданием решения по конфликту branch/worktree команда печатает `Waiting for input: ...`, а перед передачей управления agent — `Handing off to in `. Ненулевой код `codex exec` считается @@ -134,18 +159,29 @@ test/e2e/human-gate.sh --scenario human-gate Выйдите из возобновлённой Codex-сессии, после чего скрипт проверит артефакты. +Для реальной проверки commit, push и создания PR в private fixture используется +отдельно подтверждаемый unsandboxed scenario. Он сохраняет уникальные remote +branch и PR как evidence: + +```bash +START_ISSUE_E2E=1 START_ISSUE_E2E_FULL_DELIVERY=1 \ + test/e2e/human-gate.sh --scenario full-delivery +``` + #### Сценарии и проверки | Сценарий | Команда | Что проверяется | | --- | --- | --- | | `done` | `START_ISSUE_E2E=1 make e2e-human-gate` | Реальный Codex batch run выдаёт `thread.started`, сохраняет `thread-id`, `events.jsonl` и `last-message.txt`, заканчивается `STATUS: DONE` и не меняет fixture worktree за пределами `.start-issue` state. | | `human-gate` | `START_ISSUE_E2E=1 test/e2e/human-gate.sh --scenario human-gate` | Те же проверки артефактов и чистоты worktree, а также явный handoff `codex resume --include-non-interactive `. Перед завершением скрипта оператор выходит из возобновлённой interactive session. | +| `full-delivery` | `START_ISSUE_E2E=1 START_ISSUE_E2E_FULL_DELIVERY=1 test/e2e/human-gate.sh --scenario full-delivery` | Текущий Codex принимает global full-delivery option и выполняет уникальные fixture commit, push и PR; runner печатает сохранённый PR URL. | -Оба сценария проверяют авторизованный `gh`, реальный, а не fake Codex binary, и +Все сценарии проверяют авторизованный `gh`, реальный, а не fake Codex binary, и обязательный интерфейс справки `codex exec` (`--output-last-message`, без устаревшего флага `--ask-for-approval`). Выбранный Codex executable печатается -в test output. Они не доказывают поведение приложения за пределами human-gate -protocol и намеренно не входят в CI. +в test output. `done` и `human-gate` не доказывают поведение приложения за +пределами protocol; `full-delivery` дополнительно проверяет явно разрешённую +доставку в fixture. Все сценарии намеренно не входят в CI. ### CI sandbox E2E @@ -260,6 +296,7 @@ boundaries. Новые возможности должны сохранять э | `--prompt-file PATH` | Файл prompt template для выбранного агента. С `init` - содержимое файла, которое нужно записать. Нельзя использовать вместе с `--prompt`. | | `--improve-prompt` | Попросить выбранного агента сгенерировать reviewable proposal улучшенного prompt template и выйти до создания worktree. | | `--human-gate` | Codex-only batch mode для issue workflow. Запускает `codex exec`, выходит на `STATUS: DONE` и резюмирует ту же сессию на `STATUS: HUMAN_GATE`. | +| `--human-gate-permissions restricted\|full-delivery` | Выбрать capability contract human-gate. CLI имеет приоритет над `START_ISSUE_HUMAN_GATE_PERMISSIONS`; default — `restricted`. | | `--human-gate-help` | Показать отдельную справку по Codex human-gate workflow: prompt contract, exit codes и state files. | | `--prompt-output-file PATH` | Путь для proposal-файла в режиме `--improve-prompt`. | | `--no-init` | Не запускать `init.sh`, даже если он есть в созданном worktree. | @@ -291,6 +328,7 @@ boundaries. Новые возможности должны сохранять э | `START_ISSUE_PROMPT` | Inline prompt template, который используется, если prompt не задан через CLI. Перебивает project и user prompt files. Нельзя использовать вместе с `START_ISSUE_PROMPT_FILE`, когда prompt не задан через CLI. | | `START_ISSUE_PROMPT_FILE` | Файл prompt template, который используется, если prompt не задан через CLI. Перебивает project и user prompt files. Нельзя использовать вместе с `START_ISSUE_PROMPT`, когда prompt не задан через CLI. | | `START_ISSUE_WORKTREE_DIR` | Родительская директория по умолчанию для создаваемых worktree, если `--worktree-dir` не передан. Встроенное значение по умолчанию: `~/worktrees`. | +| `START_ISSUE_HUMAN_GATE_PERMISSIONS` | Capability contract human-gate при отсутствии CLI option: `restricted` или `full-delivery`. Built-in default: `restricted`. | | `START_ISSUE_DUMP_PROMPT` | Если задана в `1`, dry-run выводит полный rendered prompt вместо краткой информации. | ## Файлы конфигурации diff --git a/cmd/start-issue/main.go b/cmd/start-issue/main.go index eb0d6bd..4ecff4e 100644 --- a/cmd/start-issue/main.go +++ b/cmd/start-issue/main.go @@ -59,6 +59,7 @@ func versionFromBuildInfo(info *debug.BuildInfo, fallback ...string) string { type options struct { repo, base, worktreeDir, agent, model, promptFile, prompt, command string promptOutput, worktreeDirSource string + humanGatePermissions, humanGatePermissionsSource string issue string dryRun, noInit, flat, ai, improvePrompt, humanGate, project, user, force bool mode string @@ -138,10 +139,18 @@ func main() { } func parse(args []string) (options, error) { - o := options{worktreeDir: os.Getenv("START_ISSUE_WORKTREE_DIR")} + o := options{ + worktreeDir: os.Getenv("START_ISSUE_WORKTREE_DIR"), + humanGatePermissions: "restricted", + humanGatePermissionsSource: "built-in default", + } if o.worktreeDir != "" { o.worktreeDirSource = "START_ISSUE_WORKTREE_DIR" } + if permissions := os.Getenv("START_ISSUE_HUMAN_GATE_PERMISSIONS"); permissions != "" { + o.humanGatePermissions = permissions + o.humanGatePermissionsSource = "START_ISSUE_HUMAN_GATE_PERMISSIONS" + } var err error for len(args) > 0 { a := args[0] @@ -199,6 +208,11 @@ func parse(args []string) (options, error) { o.promptOutput, err = value() case "--human-gate": o.humanGate = true + case "--human-gate-permissions": + o.humanGatePermissions, err = value() + if err == nil { + o.humanGatePermissionsSource = "CLI" + } case "--project": o.project = true case "--user": @@ -239,6 +253,9 @@ func parse(args []string) (options, error) { if o.mode != "" && o.issue != "" { return o, fmt.Errorf("Use either %s or , not both.", o.mode) } + if !validHumanGatePermissions(o.humanGatePermissions) { + return o, fmt.Errorf("Invalid human-gate permissions %q. Use restricted or full-delivery.", o.humanGatePermissions) + } if o.worktreeDir == "" && o.mode == "" { home, err := userHomeDir() if err != nil { @@ -250,6 +267,10 @@ func parse(args []string) (options, error) { return o, nil } +func validHumanGatePermissions(value string) bool { + return value == "restricted" || value == "full-delivery" +} + func userHomeDir() (string, error) { home, err := os.UserHomeDir() if err != nil { @@ -425,7 +446,7 @@ func runWithReader(o options, reader *bufio.Reader) error { if o.dryRun { fmt.Printf(" [DRY-RUN] Would run: git worktree add -b %s %s %s\n", branch, worktree, o.base) if o.humanGate { - return humanGate(model, worktree, rendered, true) + return humanGate(model, worktree, rendered, o.humanGatePermissions, o.humanGatePermissionsSource, true) } return launchSelected(options{dryRun: true}, agent, model, worktree, rendered) } @@ -1743,7 +1764,7 @@ func canonicalPath(path string) string { func launchSelected(o options, agent, model, worktree, prompt string) error { if o.dryRun { if o.humanGate { - return humanGate(model, worktree, prompt, true) + return humanGate(model, worktree, prompt, o.humanGatePermissions, o.humanGatePermissionsSource, true) } if agent == "none" { printManualNextSteps(model, worktree) @@ -1756,7 +1777,7 @@ func launchSelected(o options, agent, model, worktree, prompt string) error { if !o.dryRun { printAgentHandoff(agent, worktree) } - return humanGate(model, worktree, prompt, false) + return humanGate(model, worktree, prompt, o.humanGatePermissions, o.humanGatePermissionsSource, false) } if !o.dryRun && agent != "none" { printAgentHandoff(agent, worktree) @@ -1824,16 +1845,21 @@ func normalizePromptProposal(result string) string { } return strings.TrimSpace(strings.Join(lines, "\n")) } -func humanGate(model, worktree, prompt string, dryRun bool) error { +func humanGate(model, worktree, prompt, permissions, permissionsSource string, dryRun bool) error { runID := os.Getenv("START_ISSUE_RUN_ID") if runID == "" { runID = time.Now().Format("20060102-150405") } dir := filepath.Join(worktree, ".start-issue", "runs", runID) events, last := filepath.Join(dir, "events.jsonl"), filepath.Join(dir, "last-message.txt") - args := []string{"exec", "--cd", worktree, "--sandbox", "workspace-write", "--json", "--output-last-message", last, "-"} - if model != "" { - args = append([]string{"exec", "--model", model}, args[1:]...) + args := humanGateArgs(model, worktree, last, permissions) + fmt.Printf(" State dir: %s\n", dir) + fmt.Printf(" Human-gate permissions: %s (%s)\n", permissions, permissionsSource) + if permissions == "full-delivery" { + fmt.Println(" WARNING: Codex will run without approvals or sandboxing for GitHub and Git delivery.") + fmt.Println(" Requires authenticated GitHub access and repository write permission; destructive or production actions still require HUMAN_GATE.") + } else { + fmt.Println(" Restricted mode: working-tree edits only; network, Git metadata writes, push, and PR delivery are not guaranteed.") } if dryRun { threadID := filepath.Join(dir, "thread-id") @@ -1888,6 +1914,21 @@ func humanGate(model, worktree, prompt string, dryRun bool) error { return fmt.Errorf("No recognized final status found. Inspect: %s", last) } +func humanGateArgs(model, worktree, lastMessage, permissions string) []string { + args := []string{} + if model != "" { + args = append(args, "--model", model) + } + if permissions == "full-delivery" { + args = append(args, "--dangerously-bypass-approvals-and-sandbox") + } + args = append(args, "exec", "--cd", worktree) + if permissions == "restricted" { + args = append(args, "--sandbox", "workspace-write") + } + return append(args, "--json", "--output-last-message", lastMessage, "-") +} + func captureThreadID(events string) (string, error) { eventsBody, err := os.ReadFile(events) if err != nil { @@ -2159,6 +2200,9 @@ Options: --improve-prompt Ask the selected agent to improve the selected prompt template and write a reviewable proposal --human-gate Codex-only batch mode that resumes on HUMAN_GATE + --human-gate-permissions + Permission contract for --human-gate + Default: START_ISSUE_HUMAN_GATE_PERMISSIONS or restricted --human-gate-help Show detailed help for the human-gate mode --prompt-output-file Output path for --improve-prompt proposal @@ -2215,6 +2259,7 @@ Environment variables: START_ISSUE_PROMPT START_ISSUE_PROMPT_FILE START_ISSUE_WORKTREE_DIR + START_ISSUE_HUMAN_GATE_PERMISSIONS START_ISSUE_DUMP_PROMPT Examples: @@ -2224,6 +2269,7 @@ Examples: start-issue 123 --agent codex start-issue 123 --agent codex --model gpt-5.2 start-issue 123 --agent codex --human-gate + start-issue 123 --agent codex --human-gate --human-gate-permissions full-delivery start-issue 123 --agent claude --model sonnet start-issue 123 --agent kimi --prompt-file .start-issue/prompt.md start-issue 123 --no-agent # Only create worktree @@ -2254,8 +2300,28 @@ func humanGateHelp() { Usage: start-issue --agent codex --human-gate + start-issue --agent codex --human-gate \ + --human-gate-permissions full-delivery start-issue --human-gate-help +Permission modes: + restricted (default) + Uses Codex workspace-write sandboxing. Working-tree edits are supported, + but network access, Git metadata writes, push, and PR delivery are not + guaranteed. Select with START_ISSUE_HUMAN_GATE_PERMISSIONS=restricted or + --human-gate-permissions restricted. + + full-delivery (explicit opt-in) + Runs Codex with --dangerously-bypass-approvals-and-sandbox so a normal + issue workflow can read GitHub context, edit, test, commit, push, and + create or update a PR. This is unsandboxed execution. It requires an + authenticated gh session and repository write permission. It does not + authorize destructive, production, security, or product decisions; those + still require STATUS: HUMAN_GATE. + +Precedence: + --human-gate-permissions, START_ISSUE_HUMAN_GATE_PERMISSIONS, restricted. + Flow: The normal issue workflow creates or reuses the worktree, renders the prompt, and runs Codex in batch mode. The final message must contain one @@ -2280,6 +2346,10 @@ Final status examples: Troubleshooting: Inspect events.jsonl and last-message.txt when batch parsing fails. The explicit thread id is saved before status handling when available. + If restricted mode cannot read GitHub or write Git metadata, either finish + delivery manually or explicitly select full-delivery after reviewing its risk. + If full delivery cannot push or create a PR, verify gh auth status, the + selected GitHub account, remote URL, and repository permissions. If automatic resume fails, run: codex resume --include-non-interactive `) } diff --git a/cmd/start-issue/main_test.go b/cmd/start-issue/main_test.go index cace48e..0e64277 100644 --- a/cmd/start-issue/main_test.go +++ b/cmd/start-issue/main_test.go @@ -58,6 +58,44 @@ func TestParseTracksWorktreeDirectorySource(t *testing.T) { } } +func TestParseHumanGatePermissionsPrecedenceAndValidation(t *testing.T) { + t.Setenv("HOME", t.TempDir()) + t.Setenv("START_ISSUE_HUMAN_GATE_PERMISSIONS", "") + + o, err := parse([]string{"1"}) + if err != nil { + t.Fatal(err) + } + if o.humanGatePermissions != "restricted" || o.humanGatePermissionsSource != "built-in default" { + t.Fatalf("default permissions = %q (%s)", o.humanGatePermissions, o.humanGatePermissionsSource) + } + + t.Setenv("START_ISSUE_HUMAN_GATE_PERMISSIONS", "full-delivery") + o, err = parse([]string{"1"}) + if err != nil { + t.Fatal(err) + } + if o.humanGatePermissions != "full-delivery" || o.humanGatePermissionsSource != "START_ISSUE_HUMAN_GATE_PERMISSIONS" { + t.Fatalf("environment permissions = %q (%s)", o.humanGatePermissions, o.humanGatePermissionsSource) + } + + o, err = parse([]string{"1", "--human-gate-permissions", "restricted"}) + if err != nil { + t.Fatal(err) + } + if o.humanGatePermissions != "restricted" || o.humanGatePermissionsSource != "CLI" { + t.Fatalf("CLI permissions = %q (%s)", o.humanGatePermissions, o.humanGatePermissionsSource) + } + + if _, err := parse([]string{"1", "--human-gate-permissions", "unlimited"}); err == nil || !strings.Contains(err.Error(), "Use restricted or full-delivery") { + t.Fatalf("invalid CLI permissions error = %v", err) + } + t.Setenv("START_ISSUE_HUMAN_GATE_PERMISSIONS", "unlimited") + if _, err := parse([]string{"1"}); err == nil || !strings.Contains(err.Error(), "Use restricted or full-delivery") { + t.Fatalf("invalid environment permissions error = %v", err) + } +} + func TestUserHomeDirRejectsUnavailableOrRelativeHome(t *testing.T) { t.Setenv("HOME", "") if runtime.GOOS != "windows" { @@ -1117,6 +1155,8 @@ func TestUsageListsCompatibilityEntryPoints(t *testing.T) { "--update", "--install", "--human-gate-help", + "--human-gate-permissions ", + "START_ISSUE_HUMAN_GATE_PERMISSIONS", "Agent selection precedence:", ".start-issue/agent in the git root", "Prompt template precedence:", @@ -1129,6 +1169,24 @@ func TestUsageListsCompatibilityEntryPoints(t *testing.T) { } } +func TestHumanGateHelpExplainsPermissionContract(t *testing.T) { + output := captureStdout(t, humanGateHelp) + for _, want := range []string{ + "restricted (default)", + "full-delivery (explicit opt-in)", + "--dangerously-bypass-approvals-and-sandbox", + "authenticated gh session", + "repository write permission", + "destructive, production, security, or product decisions", + "START_ISSUE_HUMAN_GATE_PERMISSIONS", + "gh auth status", + } { + if !strings.Contains(output, want) { + t.Fatalf("human-gate help missing %q:\n%s", want, output) + } + } +} + func TestAIBranchPromptPreservesTransliterationAndTagConstraints(t *testing.T) { bin, log := t.TempDir(), filepath.Join(t.TempDir(), "prompt") writeExecutable(t, filepath.Join(bin, "pi"), "#!/bin/sh\nlast=''\nfor arg do last=$arg; done\nprintf '%s' \"$last\" > '"+log+"'\nprintf '%s\\n' feature/issue-34-ispravit-tsap\n") @@ -1772,7 +1830,7 @@ func TestHumanGateSavesThreadIDBeforeDone(t *testing.T) { t.Setenv("CODEX_LAST", "STATUS: DONE") t.Setenv("START_ISSUE_FAKE_CODEX_REJECT_ASK_FOR_APPROVAL", "1") - if err := humanGate("", worktree, "prompt", false); err != nil { + if err := humanGate("", worktree, "prompt", "restricted", "built-in default", false); err != nil { t.Fatal(err) } threadID, err := os.ReadFile(filepath.Join(worktree, ".start-issue", "runs", "done", "thread-id")) @@ -1789,7 +1847,7 @@ func TestHumanGateSavesThreadIDWhenFinalMessageIsMissing(t *testing.T) { t.Setenv("CODEX_EVENTS", `{"type":"thread.started","thread_id":"thread-recovery"}`) t.Setenv("CODEX_SKIP_LAST", "1") - err := humanGate("", worktree, "prompt", false) + err := humanGate("", worktree, "prompt", "restricted", "built-in default", false) if err == nil || !strings.Contains(err.Error(), "No recognized final status found") { t.Fatalf("humanGate error = %v, want missing final-status error", err) } @@ -1808,7 +1866,7 @@ func TestHumanGateExecFailureReturnsExitCodeOne(t *testing.T) { t.Setenv("CODEX_LAST", "STATUS: DONE") t.Setenv("CODEX_EXEC_EXIT", "42") - err := humanGate("", worktree, "prompt", false) + err := humanGate("", worktree, "prompt", "restricted", "built-in default", false) var exit exitError if !errors.As(err, &exit) || exit.code != 1 { t.Fatalf("got %T %v, want human-gate exit code 1", err, err) @@ -1836,11 +1894,13 @@ func TestHumanGateDryRunShowsAllStateArtifacts(t *testing.T) { t.Setenv("START_ISSUE_RUN_ID", "plan") dir := filepath.Join(worktree, ".start-issue", "runs", "plan") output := captureStdout(t, func() { - if err := humanGate("", worktree, "prompt", true); err != nil { + if err := humanGate("", worktree, "prompt", "restricted", "built-in default", true); err != nil { t.Fatal(err) } }) for _, want := range []string{ + "Human-gate permissions: restricted (built-in default)", + "Restricted mode: working-tree edits only", "--output-last-message " + filepath.Join(dir, "last-message.txt"), "> " + filepath.Join(dir, "events.jsonl"), "Would write captured thread ID: " + filepath.Join(dir, "thread-id"), @@ -1857,6 +1917,73 @@ func TestHumanGateDryRunShowsAllStateArtifacts(t *testing.T) { } } +func TestHumanGateArgsMapPermissionModesInSupportedOrder(t *testing.T) { + worktree := "/tmp/worktree" + last := "/tmp/last-message.txt" + restricted := humanGateArgs("gpt-test", worktree, last, "restricted") + if got, want := fmt.Sprint(restricted), "[--model gpt-test exec --cd /tmp/worktree --sandbox workspace-write --json --output-last-message /tmp/last-message.txt -]"; got != want { + t.Fatalf("restricted args = %s, want %s", got, want) + } + fullDelivery := humanGateArgs("gpt-test", worktree, last, "full-delivery") + if got, want := fmt.Sprint(fullDelivery), "[--model gpt-test --dangerously-bypass-approvals-and-sandbox exec --cd /tmp/worktree --json --output-last-message /tmp/last-message.txt -]"; got != want { + t.Fatalf("full-delivery args = %s, want %s", got, want) + } +} + +func TestHumanGateFullDeliveryDryRunShowsResolvedModeAndCommand(t *testing.T) { + worktree := t.TempDir() + t.Setenv("START_ISSUE_RUN_ID", "full-delivery-plan") + output := captureStdout(t, func() { + err := launchSelected(options{ + dryRun: true, + humanGate: true, + humanGatePermissions: "full-delivery", + humanGatePermissionsSource: "CLI", + }, "codex", "gpt-test", worktree, "prompt") + if err != nil { + t.Fatal(err) + } + }) + wantCommand := "codex --model gpt-test --dangerously-bypass-approvals-and-sandbox exec --cd " + for _, want := range []string{ + "Human-gate permissions: full-delivery (CLI)", + wantCommand, + "WARNING: Codex will run without approvals or sandboxing", + } { + if !strings.Contains(output, want) { + t.Fatalf("full-delivery dry-run missing %q:\n%s", want, output) + } + } + if strings.Contains(output, "--sandbox workspace-write") { + t.Fatalf("full-delivery dry-run retained restricted sandbox:\n%s", output) + } +} + +func TestHumanGateFullDeliveryReportsWarningAndCompletes(t *testing.T) { + worktree, bin := t.TempDir(), t.TempDir() + writeFakeCodex(t, bin) + t.Setenv("PATH", bin+string(os.PathListSeparator)+os.Getenv("PATH")) + t.Setenv("START_ISSUE_RUN_ID", "full-delivery") + t.Setenv("CODEX_EVENTS", `{"type":"thread.started","thread_id":"thread-full-delivery"}`) + t.Setenv("CODEX_LAST", "STATUS: DONE") + + output := captureStdout(t, func() { + if err := humanGate("gpt-test", worktree, "prompt", "full-delivery", "CLI", false); err != nil { + t.Fatal(err) + } + }) + for _, want := range []string{ + "Human-gate permissions: full-delivery (CLI)", + "WARNING: Codex will run without approvals or sandboxing", + "destructive or production actions still require HUMAN_GATE", + "STATUS: DONE", + } { + if !strings.Contains(output, want) { + t.Fatalf("full-delivery output missing %q:\n%s", want, output) + } + } +} + func TestHumanGatePreservesCallerWorkingDirectory(t *testing.T) { worktree, bin, log := t.TempDir(), t.TempDir(), filepath.Join(t.TempDir(), "cwd") writeFakeCodex(t, bin) @@ -1869,7 +1996,7 @@ func TestHumanGatePreservesCallerWorkingDirectory(t *testing.T) { if err != nil { t.Fatal(err) } - if err := humanGate("", worktree, "prompt", false); err != nil { + if err := humanGate("", worktree, "prompt", "restricted", "built-in default", false); err != nil { t.Fatal(err) } got, err := os.ReadFile(log) @@ -1889,7 +2016,7 @@ func TestHumanGateRejectsDoneWithoutThreadID(t *testing.T) { t.Setenv("CODEX_EVENTS", `{"type":"item.completed"}`) t.Setenv("CODEX_LAST", "STATUS: DONE") - err := humanGate("", worktree, "prompt", false) + err := humanGate("", worktree, "prompt", "restricted", "built-in default", false) if err == nil || !strings.Contains(err.Error(), "did not capture thread_id") { t.Fatalf("got %v", err) } @@ -1904,7 +2031,7 @@ func TestHumanGateResumeFailureReturnsExitCodeTwo(t *testing.T) { t.Setenv("CODEX_LAST", "STATUS: HUMAN_GATE") t.Setenv("CODEX_RESUME_EXIT", "1") - err := humanGate("", worktree, "prompt", false) + err := humanGate("", worktree, "prompt", "restricted", "built-in default", false) var exit exitError if !errors.As(err, &exit) || exit.code != 2 { t.Fatalf("got %T %v", err, err) @@ -1928,9 +2055,28 @@ fi if [ -n "$START_ISSUE_CWD_LOG" ]; then pwd > "$START_ISSUE_CWD_LOG" fi +while [ "$#" -gt 0 ] && [ "$1" != "exec" ] && [ "$1" != "resume" ]; do + case "$1" in + --model) + shift 2 + ;; + --dangerously-bypass-approvals-and-sandbox) + shift + ;; + *) + printf '%s\n' "unexpected global option: $1" >&2 + exit 1 + ;; + esac +done if [ "$1" = "exec" ]; then + shift last="" while [ "$#" -gt 0 ]; do + if [ "$1" = "--dangerously-bypass-approvals-and-sandbox" ]; then + printf '%s\n' "full-delivery option must precede exec" >&2 + exit 1 + fi if [ "$1" = "--output-last-message" ]; then last="$2" shift 2 diff --git a/doc/spec.md b/doc/spec.md index 20ad2d7..c56381b 100644 --- a/doc/spec.md +++ b/doc/spec.md @@ -72,6 +72,7 @@ Agent-specific behavior должен быть централизован за е | `--prompt-file` | Файл prompt template | См. приоритет prompt | | `--improve-prompt` | Сгенерировать reviewable proposal улучшенного prompt template и выйти до создания worktree | false | | `--human-gate` | Codex-only batch mode для issue workflow с resume на `STATUS: HUMAN_GATE` | false | +| `--human-gate-permissions ` | Capability contract для human-gate; CLI имеет приоритет над `START_ISSUE_HUMAN_GATE_PERMISSIONS` | `restricted` | | `--human-gate-help` | Показать отдельную справку по human-gate mode | false | | `--prompt-output-file` | Путь proposal-файла для `--improve-prompt` | Для `.md`: рядом с source как `*.improved.md`; для остальных файлов: `.improved`; иначе `.start-issue/prompt.improved.md` | | `--no-init` | Пропустить запуск `init.sh` | false | @@ -202,11 +203,14 @@ git rev-parse --show-toplevel 1. Режим валиден только для `agent=codex`; для остальных agent он завершается явной ошибкой. 2. До agent launch workflow остается обычным: parse input, resolve config, fetch issue, plan branch, create/reuse worktree, run optional `init.sh`, render prompt. -3. Вместо интерактивного Codex launch выполняется: +3. Permission mode разрешается в порядке CLI + `--human-gate-permissions`, `START_ISSUE_HUMAN_GATE_PERMISSIONS`, built-in + `restricted`. Другие значения отклоняются до issue fetch и worktree mutation. +4. В restricted mode вместо интерактивного Codex launch выполняется: ```bash -codex exec \ - [--model "$MODEL"] \ +codex [--model "$MODEL"] \ + exec \ --cd "$WORKTREE_PATH" \ --sandbox workspace-write \ --json \ @@ -214,21 +218,36 @@ codex exec \ - ``` -4. Rendered prompt передается в `codex exec` через stdin. -5. Из JSONL event stream извлекается `thread_id` из события `thread.started`. -6. Saved `last-message.txt` является единственным источником final status. -7. Поддерживаются только два terminal status: +5. В explicit full-delivery mode выполняется: + +```bash +codex [--model "$MODEL"] \ + --dangerously-bypass-approvals-and-sandbox \ + exec \ + --cd "$WORKTREE_PATH" \ + --json \ + --output-last-message "$STATE_DIR/last-message.txt" \ + - +``` + +6. Full delivery требует authenticated `gh`, корректный remote и repository + write permission. Это unsandboxed execution, но оно не авторизует destructive, + production/security или product decisions: они остаются `HUMAN_GATE`. +7. Rendered prompt передается в `codex exec` через stdin. +8. Из JSONL event stream извлекается `thread_id` из события `thread.started`. +9. Saved `last-message.txt` является единственным источником final status. +10. Поддерживаются только два terminal status: - `STATUS: DONE` - `STATUS: HUMAN_GATE` -8. На `STATUS: DONE` команда завершается с кодом `0`, не открывая Codex TUI. -9. На `STATUS: HUMAN_GATE` выполняется: +11. На `STATUS: DONE` команда завершается с кодом `0`, не открывая Codex TUI. +12. На `STATUS: HUMAN_GATE` выполняется: ```bash codex resume --include-non-interactive "$thread_id" ``` -10. `codex resume --last` не используется как primary mechanism. -11. `codex exec --ephemeral` не используется, потому что session должна быть resumable. +13. `codex resume --last` не используется как primary mechanism. +14. `codex exec --ephemeral` не используется, потому что session должна быть resumable. Dedicated help доступен через: diff --git a/memory-bank/features/FT-017/brief.md b/memory-bank/features/FT-017/brief.md index 6e08987..339ed3c 100644 --- a/memory-bank/features/FT-017/brief.md +++ b/memory-bank/features/FT-017/brief.md @@ -10,7 +10,7 @@ derived_from: - ../FT-015/feature.md - https://github.com/dapi/start-issue/issues/37 status: active -delivery_status: planned +delivery_status: in_progress audience: humans_and_agents must_not_define: - implementation_sequence @@ -87,9 +87,9 @@ supported Codex CLI while closing the remaining capability-contract gap. ### Constraints / Assumptions - `ASM-01` Issue #37 reproduces the rejected argument order with Codex CLI - `0.144.6`; the feature must verify the exact supported command grammar with - the approved executable before acceptance. The repository does not pin an - installed Codex version. + `0.144.6`. Local parser validation confirms both selected command forms on + Codex CLI `0.145.0`; live full-delivery behavior still requires the explicit + `CHK-03` approval gate. The repository does not pin an installed version. - `ASM-02` Full delivery requires independently configured GitHub authentication and repository authorization; `start-issue` can select a launcher policy but cannot grant those external capabilities. diff --git a/memory-bank/features/FT-017/decision-log.md b/memory-bank/features/FT-017/decision-log.md index a5d3eaf..66bf206 100644 --- a/memory-bank/features/FT-017/decision-log.md +++ b/memory-bank/features/FT-017/decision-log.md @@ -59,8 +59,9 @@ names keep the public contract independent from Codex's low-level flags. `dangerously_bypass_approvals_and_sandbox` as a global option for `exec`. - The same source marks `model`, `json`, and `output-last-message` as global options compatible with the `exec` command. -- The local repository has no installed `codex` executable, so live parser - validation cannot be performed in this worktree. +- The local Codex CLI `0.145.0` accepts both selected command forms through its + parser/help path. No agent session or external GitHub write was performed by + this parser validation. ### Decision @@ -95,8 +96,8 @@ removed from the execution plan. ## Open evidence gate -The exact supported release/version matrix is not asserted locally. Before -`delivery_status: done`, the approved live Codex executable must accept both -command forms and a retained E2E artifact must prove the declared full-delivery -behavior. If that verification fails, reject `full-delivery` and keep the -restricted path as the safe fallback. +The exact future release/version matrix is not asserted locally. Before +`delivery_status: done`, a retained approved E2E artifact must prove the +declared full-delivery behavior on the selected Codex executable. If that +verification fails, reject `full-delivery` and keep the restricted path as the +safe fallback. diff --git a/memory-bank/features/FT-017/implementation-plan.md b/memory-bank/features/FT-017/implementation-plan.md index e0e798a..750e031 100644 --- a/memory-bank/features/FT-017/implementation-plan.md +++ b/memory-bank/features/FT-017/implementation-plan.md @@ -22,6 +22,11 @@ must_not_define: Implement the accepted FT-017 permission-mode contract while preserving the existing FT-015 batch, state, status, and resume behavior. +Deterministic implementation, documentation, command-shape coverage, and local +Codex CLI `0.145.0` parser validation are complete. `STEP-06` remains pending +because the real full-delivery run requires explicit `AG-01` authorization and +creates retained fixture GitHub state. + ## Grounding / Support References | Document | Role in this plan | Facts reused | Conflict action | @@ -55,7 +60,7 @@ existing FT-015 batch, state, status, and resume behavior. | Open Question ID | Question | Why unresolved | Blocks | Default action / escalation owner | | --- | --- | --- | --- | --- | -| `OQ-01` | Which future Codex versions remain compatible after the issue baseline? | The external CLI has no repository-owned stability guarantee and no local executable is installed. | Does not block deterministic implementation; blocks final live acceptance | Treat the approved live executable as the acceptance baseline and update adapter/docs together on command-shape failure. | +| `OQ-01` | Which future Codex versions remain compatible after the issue baseline? | The external CLI has no repository-owned stability guarantee; local parser validation covers `0.145.0` only. | Does not block deterministic implementation; blocks claims about future versions | Treat the approved live executable as the acceptance baseline and update adapter/docs together on command-shape failure. | | `OQ-02` | Which isolated fixture repository/issue should receive the live full-delivery PR? | Live target selection is operator-owned and may change. | `STEP-06` only | Require explicit target and approval through `AG-01`; never infer from global focus or an unrelated repo. | ## Environment Contract @@ -63,7 +68,7 @@ existing FT-015 batch, state, status, and resume behavior. | Area | Contract | Used by | Failure symptom | | --- | --- | --- | --- | | setup | Go, Bash, Git, and the current Go source tree; deterministic tests use fakes | `STEP-01` - `STEP-05` | `make test` dependency or fixture failure | -| supported Codex | Issue baseline is `0.144.6`; the approved live executable must accept the recorded command forms | `STEP-03`, `STEP-06` | Real CLI rejects command before emitting `thread.started` | +| supported Codex | Issue failure baseline is `0.144.6`; local parser validation covers `0.145.0`; the approved live executable must complete the recorded full-delivery flow | `STEP-03`, `STEP-06` | Parser rejection or no `thread.started` event | | deterministic test | `make test` is canonical and must not use network or real agent binaries | `CHK-01`, `STEP-02` - `STEP-05` | External side effects or nondeterministic test failures | | live access | Explicit opt-in, authenticated `gh`, real Codex, authorized fixture repo/issue, network, and permission to push/create a PR | `STEP-06` | Missing auth, push rejection, absent PR, or no terminal status | | secrets | Credentials remain in existing authenticated tools/environment and never enter tracked files or command output | all steps | Token-like data appears in diff, logs, or state artifacts | diff --git a/test/e2e/human-gate.sh b/test/e2e/human-gate.sh index 9f1efa2..780b507 100755 --- a/test/e2e/human-gate.sh +++ b/test/e2e/human-gate.sh @@ -10,12 +10,15 @@ scenario="done" usage() { cat <<'EOF' -Usage: START_ISSUE_E2E=1 test/e2e/human-gate.sh [--scenario done|human-gate] +Usage: START_ISSUE_E2E=1 test/e2e/human-gate.sh [--scenario done|human-gate|full-delivery] Runs start-issue against a real Codex CLI using the private fixture repository dapi/start-issue-e2e-fixture and its control issue #1. It deletes the temporary clone after a successful run; set START_ISSUE_E2E_KEEP=1 to retain it. The HUMAN_GATE scenario opens Codex resume interactively; exit it to continue. +FULL_DELIVERY also requires START_ISSUE_E2E_FULL_DELIVERY=1. It authorizes an +unsandboxed Codex run that creates a unique fixture commit, remote branch, and +pull request. Those remote artifacts are retained as evidence. EOF } @@ -40,8 +43,13 @@ while [[ $# -gt 0 ]]; do esac done -[[ "$scenario" == "done" || "$scenario" == "human-gate" ]] || fail "scenario must be done or human-gate" +[[ "$scenario" == "done" || "$scenario" == "human-gate" || "$scenario" == "full-delivery" ]] || \ + fail "scenario must be done, human-gate, or full-delivery" [[ "${START_ISSUE_E2E:-}" == "1" ]] || fail "set START_ISSUE_E2E=1 to authorize a real Codex session" +if [[ "$scenario" == "full-delivery" ]]; then + [[ "${START_ISSUE_E2E_FULL_DELIVERY:-}" == "1" ]] || \ + fail "set START_ISSUE_E2E_FULL_DELIVERY=1 to authorize unsandboxed GitHub delivery" +fi start_issue_bin="${START_ISSUE_E2E_BINARY:-$repo_root/.build/start-issue}" [[ -x "$start_issue_bin" ]] || fail "start-issue executable not found: $start_issue_bin" @@ -59,23 +67,53 @@ printf '%s' "$codex_exec_help" | grep -Fq -- '--output-last-message' || \ if printf '%s' "$codex_exec_help" | grep -q -- '--ask-for-approval'; then fail "installed codex exec still advertises --ask-for-approval; use a current Codex CLI" fi +if [[ "$scenario" == "full-delivery" ]]; then + codex_help="$(codex --help 2>&1)" || fail "codex --help failed" + printf '%s' "$codex_help" | grep -Fq -- '--dangerously-bypass-approvals-and-sandbox' || \ + fail "resolved codex does not support the full-delivery permission option" +fi fixture_root="$(mktemp -d "${TMPDIR:-/tmp}/start-issue-human-gate.XXXXXX")" fixture_dir="$fixture_root/fixture" worktree_parent="$fixture_root/worktrees" log_path="$fixture_root/e2e.log" expected_status="DONE" +permission_args=() if [[ "$scenario" == "human-gate" ]]; then expected_status="HUMAN_GATE" fi -prompt=$(cat </dev/null || \ + fail "delivery commit does not contain the expected marker" + git -C "$worktree_path" ls-remote --exit-code --heads origin "$delivery_branch" >/dev/null || \ + fail "remote delivery branch is missing: $delivery_branch" + pr_url="$(gh pr list --repo "$fixture_repo" --state open --head "$delivery_branch" --json url --jq '.[0].url // empty')" + [[ -n "$pr_url" ]] || fail "full-delivery pull request is missing for $delivery_branch" + printf 'Full-delivery PR: %s\n' "$pr_url" +fi + unexpected_changes="$(git -C "$worktree_path" status --porcelain | awk '$0 !~ /^\?\? \.start-issue\// { print }')" [[ -z "$unexpected_changes" ]] || fail "fixture worktree has unexpected changes: $unexpected_changes" From e896c7a83d9790f0e6f0df55f8ecc30b7ab68765 Mon Sep 17 00:00:00 2001 From: Danil Pismenny Date: Tue, 4 Aug 2026 21:19:50 +0300 Subject: [PATCH 3/7] Address human-gate permission review --- README.md | 6 +++--- README.ru.md | 6 +++--- cmd/start-issue/main.go | 6 +++++- cmd/start-issue/main_test.go | 7 +++++-- doc/spec.md | 2 +- memory-bank/features/FT-017/design.md | 2 +- memory-bank/features/FT-017/implementation-plan.md | 2 +- test/e2e/human-gate.sh | 5 +++-- 8 files changed, 22 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 23242e4..3e844c7 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,7 @@ second runtime implementation. | `--prompt-file PATH` | Prompt template file for the selected agent. With `init`, the file content to write. Mutually exclusive with `--prompt`. | | `--improve-prompt` | Ask the selected agent to generate a reviewable improved prompt template proposal, then exit before creating a worktree. | | `--human-gate` | Codex-only batch mode for issue work. Runs `codex exec`, exits on `STATUS: DONE`, and resumes the same session on `STATUS: HUMAN_GATE`. | -| `--human-gate-permissions restricted\|full-delivery` | Select the human-gate capability contract. CLI overrides `START_ISSUE_HUMAN_GATE_PERMISSIONS`; default is `restricted`. | +| `--human-gate-permissions restricted\|full-delivery` | Select the human-gate capability contract. Requires `--human-gate`; CLI overrides `START_ISSUE_HUMAN_GATE_PERMISSIONS`; default is `restricted`. | | `--human-gate-help` | Show dedicated help for the Codex human-gate workflow, including prompt contract, exit codes, and state files. | | `--prompt-output-file PATH` | Proposal output path for `--improve-prompt`. | | `--no-init` | Do not run `init.sh` even if it exists in the created worktree. | @@ -324,7 +324,7 @@ Exit the resumed Codex session to let the script verify the artifacts. To validate actual commit, push, and PR creation in the private fixture, use the separately authorized unsandboxed scenario. It creates and retains a unique -remote branch and PR as evidence: +remote branch, PR, and local diagnostic fixture as evidence: ```bash START_ISSUE_E2E=1 START_ISSUE_E2E_FULL_DELIVERY=1 \ @@ -337,7 +337,7 @@ START_ISSUE_E2E=1 START_ISSUE_E2E_FULL_DELIVERY=1 \ | --- | --- | --- | | `done` | `START_ISSUE_E2E=1 make e2e-human-gate` | A real Codex batch run emits `thread.started`, saves `thread-id`, `events.jsonl`, and `last-message.txt`, ends with `STATUS: DONE`, and leaves no fixture change other than `.start-issue` state. | | `human-gate` | `START_ISSUE_E2E=1 test/e2e/human-gate.sh --scenario human-gate` | The same artifact and clean-worktree checks, plus the reported explicit `codex resume --include-non-interactive ` handoff. The operator exits the resumed interactive session before the script can finish. | -| `full-delivery` | `START_ISSUE_E2E=1 START_ISSUE_E2E_FULL_DELIVERY=1 test/e2e/human-gate.sh --scenario full-delivery` | The current Codex accepts the global full-delivery option and completes a unique fixture commit, push, and PR; the runner prints the retained PR URL. | +| `full-delivery` | `START_ISSUE_E2E=1 START_ISSUE_E2E_FULL_DELIVERY=1 test/e2e/human-gate.sh --scenario full-delivery` | The current Codex accepts the global full-delivery option and completes a unique fixture commit, push, and PR; the runner prints the retained PR URL and local artifact path. | All scenarios verify authenticated `gh`, a real rather than fake Codex binary, and the required `codex exec` help interface (`--output-last-message`, without diff --git a/README.ru.md b/README.ru.md index 76723c7..c581080 100644 --- a/README.ru.md +++ b/README.ru.md @@ -161,7 +161,7 @@ test/e2e/human-gate.sh --scenario human-gate Для реальной проверки commit, push и создания PR в private fixture используется отдельно подтверждаемый unsandboxed scenario. Он сохраняет уникальные remote -branch и PR как evidence: +branch, PR и локальный diagnostic fixture как evidence: ```bash START_ISSUE_E2E=1 START_ISSUE_E2E_FULL_DELIVERY=1 \ @@ -174,7 +174,7 @@ START_ISSUE_E2E=1 START_ISSUE_E2E_FULL_DELIVERY=1 \ | --- | --- | --- | | `done` | `START_ISSUE_E2E=1 make e2e-human-gate` | Реальный Codex batch run выдаёт `thread.started`, сохраняет `thread-id`, `events.jsonl` и `last-message.txt`, заканчивается `STATUS: DONE` и не меняет fixture worktree за пределами `.start-issue` state. | | `human-gate` | `START_ISSUE_E2E=1 test/e2e/human-gate.sh --scenario human-gate` | Те же проверки артефактов и чистоты worktree, а также явный handoff `codex resume --include-non-interactive `. Перед завершением скрипта оператор выходит из возобновлённой interactive session. | -| `full-delivery` | `START_ISSUE_E2E=1 START_ISSUE_E2E_FULL_DELIVERY=1 test/e2e/human-gate.sh --scenario full-delivery` | Текущий Codex принимает global full-delivery option и выполняет уникальные fixture commit, push и PR; runner печатает сохранённый PR URL. | +| `full-delivery` | `START_ISSUE_E2E=1 START_ISSUE_E2E_FULL_DELIVERY=1 test/e2e/human-gate.sh --scenario full-delivery` | Текущий Codex принимает global full-delivery option и выполняет уникальные fixture commit, push и PR; runner печатает сохранённые PR URL и local artifact path. | Все сценарии проверяют авторизованный `gh`, реальный, а не fake Codex binary, и обязательный интерфейс справки `codex exec` (`--output-last-message`, без @@ -296,7 +296,7 @@ boundaries. Новые возможности должны сохранять э | `--prompt-file PATH` | Файл prompt template для выбранного агента. С `init` - содержимое файла, которое нужно записать. Нельзя использовать вместе с `--prompt`. | | `--improve-prompt` | Попросить выбранного агента сгенерировать reviewable proposal улучшенного prompt template и выйти до создания worktree. | | `--human-gate` | Codex-only batch mode для issue workflow. Запускает `codex exec`, выходит на `STATUS: DONE` и резюмирует ту же сессию на `STATUS: HUMAN_GATE`. | -| `--human-gate-permissions restricted\|full-delivery` | Выбрать capability contract human-gate. CLI имеет приоритет над `START_ISSUE_HUMAN_GATE_PERMISSIONS`; default — `restricted`. | +| `--human-gate-permissions restricted\|full-delivery` | Выбрать capability contract human-gate. Требует `--human-gate`; CLI имеет приоритет над `START_ISSUE_HUMAN_GATE_PERMISSIONS`; default — `restricted`. | | `--human-gate-help` | Показать отдельную справку по Codex human-gate workflow: prompt contract, exit codes и state files. | | `--prompt-output-file PATH` | Путь для proposal-файла в режиме `--improve-prompt`. | | `--no-init` | Не запускать `init.sh`, даже если он есть в созданном worktree. | diff --git a/cmd/start-issue/main.go b/cmd/start-issue/main.go index 4ecff4e..0b92737 100644 --- a/cmd/start-issue/main.go +++ b/cmd/start-issue/main.go @@ -253,6 +253,9 @@ func parse(args []string) (options, error) { if o.mode != "" && o.issue != "" { return o, fmt.Errorf("Use either %s or , not both.", o.mode) } + if o.humanGatePermissionsSource == "CLI" && !o.humanGate { + return o, errors.New("--human-gate-permissions requires --human-gate.") + } if !validHumanGatePermissions(o.humanGatePermissions) { return o, fmt.Errorf("Invalid human-gate permissions %q. Use restricted or full-delivery.", o.humanGatePermissions) } @@ -2201,7 +2204,7 @@ Options: prompt template and write a reviewable proposal --human-gate Codex-only batch mode that resumes on HUMAN_GATE --human-gate-permissions - Permission contract for --human-gate + Requires --human-gate; permission contract for it Default: START_ISSUE_HUMAN_GATE_PERMISSIONS or restricted --human-gate-help Show detailed help for the human-gate mode --prompt-output-file @@ -2321,6 +2324,7 @@ Permission modes: Precedence: --human-gate-permissions, START_ISSUE_HUMAN_GATE_PERMISSIONS, restricted. + --human-gate-permissions requires --human-gate. Flow: The normal issue workflow creates or reuses the worktree, renders the diff --git a/cmd/start-issue/main_test.go b/cmd/start-issue/main_test.go index 0e64277..174f7c6 100644 --- a/cmd/start-issue/main_test.go +++ b/cmd/start-issue/main_test.go @@ -79,7 +79,7 @@ func TestParseHumanGatePermissionsPrecedenceAndValidation(t *testing.T) { t.Fatalf("environment permissions = %q (%s)", o.humanGatePermissions, o.humanGatePermissionsSource) } - o, err = parse([]string{"1", "--human-gate-permissions", "restricted"}) + o, err = parse([]string{"1", "--human-gate", "--human-gate-permissions", "restricted"}) if err != nil { t.Fatal(err) } @@ -87,7 +87,10 @@ func TestParseHumanGatePermissionsPrecedenceAndValidation(t *testing.T) { t.Fatalf("CLI permissions = %q (%s)", o.humanGatePermissions, o.humanGatePermissionsSource) } - if _, err := parse([]string{"1", "--human-gate-permissions", "unlimited"}); err == nil || !strings.Contains(err.Error(), "Use restricted or full-delivery") { + if _, err := parse([]string{"1", "--human-gate-permissions", "restricted"}); err == nil || !strings.Contains(err.Error(), "requires --human-gate") { + t.Fatalf("permission flag without human-gate error = %v", err) + } + if _, err := parse([]string{"1", "--human-gate", "--human-gate-permissions", "unlimited"}); err == nil || !strings.Contains(err.Error(), "Use restricted or full-delivery") { t.Fatalf("invalid CLI permissions error = %v", err) } t.Setenv("START_ISSUE_HUMAN_GATE_PERMISSIONS", "unlimited") diff --git a/doc/spec.md b/doc/spec.md index c56381b..0c76298 100644 --- a/doc/spec.md +++ b/doc/spec.md @@ -72,7 +72,7 @@ Agent-specific behavior должен быть централизован за е | `--prompt-file` | Файл prompt template | См. приоритет prompt | | `--improve-prompt` | Сгенерировать reviewable proposal улучшенного prompt template и выйти до создания worktree | false | | `--human-gate` | Codex-only batch mode для issue workflow с resume на `STATUS: HUMAN_GATE` | false | -| `--human-gate-permissions ` | Capability contract для human-gate; CLI имеет приоритет над `START_ISSUE_HUMAN_GATE_PERMISSIONS` | `restricted` | +| `--human-gate-permissions ` | Capability contract для human-gate; требует `--human-gate`; CLI имеет приоритет над `START_ISSUE_HUMAN_GATE_PERMISSIONS` | `restricted` | | `--human-gate-help` | Показать отдельную справку по human-gate mode | false | | `--prompt-output-file` | Путь proposal-файла для `--improve-prompt` | Для `.md`: рядом с source как `*.improved.md`; для остальных файлов: `.improved`; иначе `.start-issue/prompt.improved.md` | | `--no-init` | Пропустить запуск `init.sh` | false | diff --git a/memory-bank/features/FT-017/design.md b/memory-bank/features/FT-017/design.md index 7350899..9f97571 100644 --- a/memory-bank/features/FT-017/design.md +++ b/memory-bank/features/FT-017/design.md @@ -119,7 +119,7 @@ enforces credentials and repository authorization independently. | Contract ID | Input / Output | Producer / Consumer | Semantics / Constraints | | --- | --- | --- | --- | -| `CTR-01` | `--human-gate-permissions restricted\|full-delivery` | CLI parser / config resolver | CLI value wins over environment; invalid or empty explicit values fail before issue fetch. | +| `CTR-01` | `--human-gate-permissions restricted\|full-delivery` | CLI parser / config resolver | CLI value wins over environment; it requires `--human-gate`; invalid or empty explicit values fail before issue fetch. | | `CTR-02` | `START_ISSUE_HUMAN_GATE_PERMISSIONS` | shell environment / config resolver | Used only when CLI input is absent; unset resolves to `restricted`. | | `CTR-03` | Restricted Codex command | launcher / Codex | `codex [--model MODEL] exec --cd WORKTREE --sandbox workspace-write --json --output-last-message PATH -`. | | `CTR-04` | Full-delivery Codex command | launcher / Codex | `codex [--model MODEL] --dangerously-bypass-approvals-and-sandbox exec --cd WORKTREE --json --output-last-message PATH -`; selected only by explicit `full-delivery`. | diff --git a/memory-bank/features/FT-017/implementation-plan.md b/memory-bank/features/FT-017/implementation-plan.md index 750e031..d3156b7 100644 --- a/memory-bank/features/FT-017/implementation-plan.md +++ b/memory-bank/features/FT-017/implementation-plan.md @@ -94,7 +94,7 @@ creates retained fixture GitHub state. | Approval Gate ID | Trigger | Applies to | Why approval is required | Approver / evidence | | --- | --- | --- | --- | --- | -| `AG-01` | Running a real full-delivery session that can commit, push, and create/update a PR | `STEP-06`, `WS-4`, `CHK-03` | The run is unsandboxed and creates external GitHub state | User names/approves the fixture target; retained log and PR URL record approval context | +| `AG-01` | Running a real full-delivery session that can commit, push, and create/update a PR | `STEP-06`, `WS-4`, `CHK-03` | The run is unsandboxed and creates external GitHub state | User names/approves the fixture target; retained fixture directory, log, state artifacts, and PR URL record approval context | ## Work Order diff --git a/test/e2e/human-gate.sh b/test/e2e/human-gate.sh index 780b507..47c89ea 100755 --- a/test/e2e/human-gate.sh +++ b/test/e2e/human-gate.sh @@ -18,7 +18,8 @@ clone after a successful run; set START_ISSUE_E2E_KEEP=1 to retain it. The HUMAN_GATE scenario opens Codex resume interactively; exit it to continue. FULL_DELIVERY also requires START_ISSUE_E2E_FULL_DELIVERY=1. It authorizes an unsandboxed Codex run that creates a unique fixture commit, remote branch, and -pull request. Those remote artifacts are retained as evidence. +pull request. Its temporary fixture and diagnostic artifacts are retained as +evidence automatically. EOF } @@ -176,7 +177,7 @@ unexpected_changes="$(git -C "$worktree_path" status --porcelain | awk '$0 !~ /^ [[ -z "$unexpected_changes" ]] || fail "fixture worktree has unexpected changes: $unexpected_changes" printf 'PASS: real Codex human-gate %s scenario. State: %s\n' "$scenario" "$state_dir" -if [[ "${START_ISSUE_E2E_KEEP:-}" == "1" ]]; then +if [[ "$scenario" == "full-delivery" || "${START_ISSUE_E2E_KEEP:-}" == "1" ]]; then printf 'The temporary fixture is preserved at: %s\n' "$fixture_root" else git -C "$fixture_dir" worktree remove --force "$worktree_path" From cb28e2ed00febf654a6a98f3d398e0f7ce3e3fd2 Mon Sep 17 00:00:00 2001 From: Danil Pismenny Date: Mon, 24 Aug 2026 22:22:01 +0500 Subject: [PATCH 4/7] Document human-gate permission usage --- README.md | 3 + README.ru.md | 3 + doc/human-gate-permissions.md | 124 +++++++++++++++++ doc/human-gate-permissions.ru.md | 126 ++++++++++++++++++ doc/spec.md | 5 + memory-bank/features/FT-017/brief.md | 2 +- .../features/FT-017/implementation-plan.md | 4 +- 7 files changed, 264 insertions(+), 3 deletions(-) create mode 100644 doc/human-gate-permissions.md create mode 100644 doc/human-gate-permissions.ru.md diff --git a/README.md b/README.md index 3e844c7..34050cb 100644 --- a/README.md +++ b/README.md @@ -281,6 +281,9 @@ Dedicated help: start-issue --human-gate-help ``` +For a copy-ready preflight and end-to-end example, see +[Codex human-gate permissions](doc/human-gate-permissions.md). + Prompt contract: - The final message must contain exactly one terminal status line: `STATUS: DONE` or `STATUS: HUMAN_GATE`. diff --git a/README.ru.md b/README.ru.md index c581080..7ae21e5 100644 --- a/README.ru.md +++ b/README.ru.md @@ -118,6 +118,9 @@ production/security changes или product decisions: для них prompt по- start-issue --human-gate-help ``` +Пошаговая проверка и готовый end-to-end пример находятся в +[инструкции по правам Codex human-gate](doc/human-gate-permissions.ru.md). + Контракт prompt: - Финальное сообщение должно содержать ровно одну terminal status line: `STATUS: DONE` или `STATUS: HUMAN_GATE`. diff --git a/doc/human-gate-permissions.md b/doc/human-gate-permissions.md new file mode 100644 index 0000000..4b59256 --- /dev/null +++ b/doc/human-gate-permissions.md @@ -0,0 +1,124 @@ +# Codex human-gate permissions + +This guide explains how to choose the capability boundary for a Codex +human-gate run. For the complete CLI contract, see [spec.md](spec.md). + +## Choose a mode + +Use `restricted` for normal working-tree implementation: + +```bash +start-issue 123 --agent codex --human-gate +``` + +`restricted` is the default. Codex runs with the `workspace-write` sandbox. It +can edit and test files in the prepared worktree, but network access, Git +metadata writes, push, and pull-request delivery are not guaranteed. + +Use `full-delivery` only when the run must also read GitHub context, commit, +push, and create or update a pull request: + +```bash +start-issue 123 --agent codex --human-gate \ + --human-gate-permissions full-delivery +``` + +This is an explicit opt-in to unsandboxed Codex execution. It grants technical +capability, not permission for destructive Git operations, production or +security changes, or unresolved product decisions. Codex must still return +`STATUS: HUMAN_GATE` when one of those decisions requires the operator. + +## Full-delivery preflight + +Before using `full-delivery`, check the selected GitHub account, remote, and +repository access: + +```bash +gh auth status +git remote get-url origin +gh repo view --json nameWithOwner,viewerPermission +``` + +Run a dry-run to verify the resolved mode and launch command without creating +a worktree or starting Codex: + +```bash +start-issue 123 --agent codex --human-gate \ + --human-gate-permissions full-delivery \ + --dry-run +``` + +The output should report: + +```text +Human-gate permissions: full-delivery (CLI) +``` + +## Example: implement an issue and deliver its PR + +Assume issue `123` is in the current repository and the authenticated GitHub +account has write access. + +1. Inspect the planned run: + + ```bash + start-issue 123 --agent codex --human-gate \ + --human-gate-permissions full-delivery \ + --dry-run + ``` + +2. Start the real run after reviewing the unsandboxed-execution warning: + + ```bash + start-issue 123 --agent codex --human-gate \ + --human-gate-permissions full-delivery + ``` + +3. Codex can now implement and test the issue, commit the result, push the + issue branch, and create or update its pull request when the task and + repository state allow it. + +4. A final `STATUS: DONE` exits successfully. A final + `STATUS: HUMAN_GATE` opens the exact saved Codex thread for the operator. + +The CLI keeps diagnostic state under: + +```text +/.start-issue/runs//events.jsonl +/.start-issue/runs//last-message.txt +/.start-issue/runs//thread-id +``` + +## One-command environment override + +The environment variable is useful for one command or a controlled automation +wrapper: + +```bash +START_ISSUE_HUMAN_GATE_PERMISSIONS=full-delivery \ + start-issue 123 --agent codex --human-gate +``` + +The CLI option has higher precedence than the environment variable. Avoid +putting `full-delivery` in a global shell profile: keeping the opt-in visible at +the command site makes the unsandboxed boundary easier to review. + +## Troubleshooting + +- If `restricted` cannot access GitHub or write Git metadata, finish delivery + manually or rerun with an explicitly reviewed `full-delivery` selection. +- If full delivery cannot push or create a PR, recheck `gh auth status`, the + selected account, `origin`, and `viewerPermission`. +- If status parsing fails, inspect `events.jsonl` and `last-message.txt` in the + printed state directory. +- If automatic resume fails, use the saved thread id: + + ```bash + codex resume --include-non-interactive + ``` + +For the built-in reference, run: + +```bash +start-issue --human-gate-help +``` diff --git a/doc/human-gate-permissions.ru.md b/doc/human-gate-permissions.ru.md new file mode 100644 index 0000000..2a3b602 --- /dev/null +++ b/doc/human-gate-permissions.ru.md @@ -0,0 +1,126 @@ +# Права Codex human-gate + +Эта инструкция объясняет, как выбрать границу возможностей для Codex +human-gate run. Полный контракт CLI описан в [spec.md](spec.md). + +## Выбор режима + +Для обычной реализации в worktree используйте `restricted`: + +```bash +start-issue 123 --agent codex --human-gate +``` + +`restricted` — режим по умолчанию. Codex работает в sandbox +`workspace-write`: он может изменять и тестировать файлы подготовленного +worktree, но network access, запись Git metadata, push и доставка PR не +гарантированы. + +Если run должен также читать GitHub context, делать commit и push, создавать +или обновлять pull request, явно включите `full-delivery`: + +```bash +start-issue 123 --agent codex --human-gate \ + --human-gate-permissions full-delivery +``` + +Это явное согласие на запуск Codex без sandbox и approvals. Режим даёт +техническую возможность, но не разрешает destructive Git operations, +production/security changes или нерешённые product decisions. В таких случаях +Codex по-прежнему обязан вернуть `STATUS: HUMAN_GATE` и передать решение +оператору. + +## Проверка перед full delivery + +Проверьте выбранный GitHub account, remote и права на репозиторий: + +```bash +gh auth status +git remote get-url origin +gh repo view --json nameWithOwner,viewerPermission +``` + +Сначала выполните dry-run: он покажет выбранный режим и launch command, но не +создаст worktree и не запустит Codex. + +```bash +start-issue 123 --agent codex --human-gate \ + --human-gate-permissions full-delivery \ + --dry-run +``` + +В output должна быть строка: + +```text +Human-gate permissions: full-delivery (CLI) +``` + +## Пример: реализовать issue и доставить PR + +Предположим, что issue `123` находится в текущем репозитории, а выбранный +GitHub account имеет write access. + +1. Просмотрите план запуска: + + ```bash + start-issue 123 --agent codex --human-gate \ + --human-gate-permissions full-delivery \ + --dry-run + ``` + +2. После проверки warning об unsandboxed execution запустите реальный run: + + ```bash + start-issue 123 --agent codex --human-gate \ + --human-gate-permissions full-delivery + ``` + +3. Codex сможет реализовать и протестировать issue, сделать commit, push + issue branch и создать или обновить pull request, если это допускают задача + и состояние репозитория. + +4. Финальный `STATUS: DONE` успешно завершит команду. Финальный + `STATUS: HUMAN_GATE` откроет оператору точный сохранённый Codex thread. + +CLI сохраняет диагностические данные в: + +```text +/.start-issue/runs//events.jsonl +/.start-issue/runs//last-message.txt +/.start-issue/runs//thread-id +``` + +## Одноразовое переключение через environment + +Environment variable удобно использовать для одной команды или в +контролируемой automation wrapper: + +```bash +START_ISSUE_HUMAN_GATE_PERMISSIONS=full-delivery \ + start-issue 123 --agent codex --human-gate +``` + +CLI option имеет приоритет над environment variable. Не стоит добавлять +`full-delivery` в глобальный shell profile: видимый opt-in рядом с командой +упрощает проверку границы unsandboxed execution. + +## Диагностика + +- Если `restricted` не может обратиться к GitHub или записать Git metadata, + завершите доставку вручную либо повторите run с явно проверенным + `full-delivery`. +- Если full delivery не может сделать push или создать PR, повторно проверьте + `gh auth status`, выбранный account, `origin` и `viewerPermission`. +- При ошибке status parsing проверьте `events.jsonl` и `last-message.txt` в + напечатанном state directory. +- Если automatic resume не сработал, используйте сохранённый thread id: + + ```bash + codex resume --include-non-interactive + ``` + +Встроенная справка: + +```bash +start-issue --human-gate-help +``` diff --git a/doc/spec.md b/doc/spec.md index 0c76298..41cd5dd 100644 --- a/doc/spec.md +++ b/doc/spec.md @@ -255,6 +255,11 @@ Dedicated help доступен через: start-issue --human-gate-help ``` +Практические инструкции и пример полного запуска: + +- [English](human-gate-permissions.md) +- [Русский](human-gate-permissions.ru.md) + Там документируются: - полный flow; diff --git a/memory-bank/features/FT-017/brief.md b/memory-bank/features/FT-017/brief.md index 339ed3c..37f699f 100644 --- a/memory-bank/features/FT-017/brief.md +++ b/memory-bank/features/FT-017/brief.md @@ -176,7 +176,7 @@ supported Codex CLI while closing the remaining capability-contract gap. | Check ID | Covers | How to check | Expected result | Evidence path | | --- | --- | --- | --- | --- | | `CHK-01` | `EC-01` - `EC-04`, `SC-01` - `SC-04`, `NEG-01` | `make test` | Go formatting/vet/tests, memory-bank audit, and deterministic human-gate coverage pass for both modes and FT-015 regressions. | Local terminal/CI test output | -| `CHK-02` | `EC-01`, `EC-05`, `SC-01`, `SC-05`, `NEG-02` | Review `--help`, `--human-gate-help`, README files, and spec alongside Go output assertions | All surfaces state the same default, opt-in, capability, risk, and troubleshooting contract. | Review diff and Go test output | +| `CHK-02` | `EC-01`, `EC-05`, `SC-01`, `SC-05`, `NEG-02` | Review `--help`, `--human-gate-help`, README files, practical guides, and spec alongside Go output assertions | All surfaces state the same default, opt-in, capability, risk, and troubleshooting contract. | Review diff and Go test output | | `CHK-03` | `EC-02`, `EC-03`, `EC-06`, `SC-02`, `SC-03`, `SC-06`, `NEG-03` | With explicit approval, run the real-Codex full-delivery E2E procedure from FT-017's plan | Supported Codex accepts the command and the isolated fixture records commit, push, PR, terminal status, and retained artifacts. | Retained E2E artifact directory and fixture PR URL | ### Test matrix diff --git a/memory-bank/features/FT-017/implementation-plan.md b/memory-bank/features/FT-017/implementation-plan.md index d3156b7..f458933 100644 --- a/memory-bank/features/FT-017/implementation-plan.md +++ b/memory-bank/features/FT-017/implementation-plan.md @@ -44,7 +44,7 @@ creates retained fixture GitHub state. | `cmd/start-issue/main_test.go` | Deterministic Go regression suite | Existing tests cover human-gate command, DONE, HUMAN_GATE, and errors | Add precedence, invalid value, full-delivery, and order assertions | | `cmd/start-issue/parity_integration_test.go` | Go/Bash observable parity coverage | Protects unaffected legacy behavior during the Go implementation | Keep non-human-gate parity cases green | | `test/e2e/human-gate.sh` | Opt-in real-Codex smoke runner | Closest existing live verification surface | Add a separately guarded full-delivery scenario only after approval | -| `README.md`, `README.ru.md`, `doc/spec.md` | Public and canonical behavior docs | Must match help and command behavior | Update together with output assertions | +| `README.md`, `README.ru.md`, `doc/human-gate-permissions*.md`, `doc/spec.md` | Public guides and canonical behavior docs | Must match help and command behavior | Update together with output assertions | ## Test Strategy @@ -103,7 +103,7 @@ creates retained fixture GitHub state. | `STEP-01` | agent | `REQ-03`, `SOL-01`, `SD-02`, `CTR-01`, `CTR-02`, `FM-03` | Add CLI/environment/default resolution and fail-fast validation | `cmd/start-issue/main.go`, `main_test.go` | Resolved mode and source | `CHK-01`, `NEG-01` | `EVID-01` | Focused Go tests, then `make test` | `PRE-01` | none | Validation occurs after fetch or mutation | | `STEP-02` | agent | `REQ-07`, `INV-01`, `INV-02` | Extend the fake Codex process and Go tests before changing launcher behavior | `cmd/start-issue/main_test.go` | Red/green command contract tests | `CHK-01`, `SC-01` - `SC-04` | `EVID-01` | `go test ./cmd/start-issue` | `STEP-01` | none | Fake cannot distinguish global and exec arguments | | `STEP-03` | agent | `REQ-01`, `REQ-02`, `REQ-04`, `SOL-02`, `SOL-03`, `CTR-03`, `CTR-04`, `INV-04` | Build validated restricted/full-delivery commands in supported order | `cmd/start-issue/main.go` | Array-based Codex command mapping | `CHK-01`, `SC-02`, `SC-03` | `EVID-01` | Focused Go tests; inspect `--dry-run` command | `STEP-02`, `PRE-02` | none | Supported Codex rejects generated grammar | -| `STEP-04` | agent | `REQ-01`, `REQ-06`, `SOL-04`, `CTR-05`, `FM-02`, `FM-04` | Add capability output, warning, dedicated help, and public docs | `cmd/start-issue/main.go`, README files, spec | Consistent operator contract | `CHK-02`, `SC-05`, `NEG-02` | `EVID-02` | Help assertions and documentation review | `STEP-03` | none | Docs imply permission equals credentials or product authorization | +| `STEP-04` | agent | `REQ-01`, `REQ-06`, `SOL-04`, `CTR-05`, `FM-02`, `FM-04` | Add capability output, warning, dedicated help, and public docs | `cmd/start-issue/main.go`, README files, practical guides, spec | Consistent operator contract | `CHK-02`, `SC-05`, `NEG-02` | `EVID-02` | Help assertions and documentation review | `STEP-03` | none | Docs imply permission equals credentials or product authorization | | `STEP-05` | agent | `REQ-05`, `REQ-07`, `SOL-05`, `INV-05`, `RB-01`, `RB-02` | Run full deterministic regression and simplify review | All changed runtime/tests/docs | Green local suite and complexity verdict | `CHK-01`, `CHK-02`, `SC-04` | `EVID-01`, `EVID-02`, `EVID-09` | `make test`; inspect diff for unnecessary branches/abstractions | `STEP-01` - `STEP-04` | none | FT-015 state/resume behavior changes | | `STEP-06` | human + agent | `REQ-08`, `SOL-06`, `SD-04`, `SC-06`, `NEG-03`, `RB-03` | Extend/run isolated live full-delivery verification and retain evidence | E2E runner and approved fixture repo/issue | E2E log, state artifacts, commit and PR URL | `CHK-03`, `EC-06` | `EVID-03` | Follow canonical cmux caller-tab procedure and poll to terminal PASS/failure | `STEP-05`, `PRE-03`, `OQ-02` | `AG-01` | Target/auth/caller context is missing, or any unexpected external scope appears | From f35c04b207baf149cfadb98c4df885de7f06feeb Mon Sep 17 00:00:00 2001 From: Danil Pismenny Date: Mon, 24 Aug 2026 22:25:57 +0500 Subject: [PATCH 5/7] Clarify why human-gate permissions are needed --- doc/human-gate-permissions.md | 40 ++++++++++++++++++++++++++------ doc/human-gate-permissions.ru.md | 39 +++++++++++++++++++++++++------ 2 files changed, 65 insertions(+), 14 deletions(-) diff --git a/doc/human-gate-permissions.md b/doc/human-gate-permissions.md index 4b59256..060bc96 100644 --- a/doc/human-gate-permissions.md +++ b/doc/human-gate-permissions.md @@ -1,14 +1,40 @@ -# Codex human-gate permissions +# Codex human-gate: purpose and permission modes This guide explains how to choose the capability boundary for a Codex human-gate run. For the complete CLI contract, see [spec.md](spec.md). +The examples assume that `codex` is already selected by project or user +configuration. + +## Why use human-gate + +A normal `start-issue` run hands control to an interactive agent session. +Human-gate instead runs Codex in batch mode so it can work through the issue +without the operator staying in the interactive UI. Codex then ends in one of +two explicit states: + +- `STATUS: DONE` — the requested work is complete; +- `STATUS: HUMAN_GATE` — a real decision or missing permission requires the + operator, so `start-issue` resumes the exact saved Codex thread. + +This is useful for semi-autonomous issue work: implementation and tests can run +unattended, while destructive actions, missing credentials, and product +decisions still stop at a visible human gate. + +Permission modes are needed because editing a worktree and delivering a pull +request cross different trust boundaries. A sandboxed Codex run can usually +change and test code, but may be unable to access GitHub, write Git metadata, +push, or create a PR. Giving every run unrestricted access would solve that +technical limitation by silently broadening privileges. `start-issue` therefore +keeps the safe boundary by default and requires a visible opt-in when the agent +must deliver the change end to end. + ## Choose a mode Use `restricted` for normal working-tree implementation: ```bash -start-issue 123 --agent codex --human-gate +start-issue 123 --human-gate ``` `restricted` is the default. Codex runs with the `workspace-write` sandbox. It @@ -19,7 +45,7 @@ Use `full-delivery` only when the run must also read GitHub context, commit, push, and create or update a pull request: ```bash -start-issue 123 --agent codex --human-gate \ +start-issue 123 --human-gate \ --human-gate-permissions full-delivery ``` @@ -43,7 +69,7 @@ Run a dry-run to verify the resolved mode and launch command without creating a worktree or starting Codex: ```bash -start-issue 123 --agent codex --human-gate \ +start-issue 123 --human-gate \ --human-gate-permissions full-delivery \ --dry-run ``` @@ -62,7 +88,7 @@ account has write access. 1. Inspect the planned run: ```bash - start-issue 123 --agent codex --human-gate \ + start-issue 123 --human-gate \ --human-gate-permissions full-delivery \ --dry-run ``` @@ -70,7 +96,7 @@ account has write access. 2. Start the real run after reviewing the unsandboxed-execution warning: ```bash - start-issue 123 --agent codex --human-gate \ + start-issue 123 --human-gate \ --human-gate-permissions full-delivery ``` @@ -96,7 +122,7 @@ wrapper: ```bash START_ISSUE_HUMAN_GATE_PERMISSIONS=full-delivery \ - start-issue 123 --agent codex --human-gate + start-issue 123 --human-gate ``` The CLI option has higher precedence than the environment variable. Avoid diff --git a/doc/human-gate-permissions.ru.md b/doc/human-gate-permissions.ru.md index 2a3b602..5db2b56 100644 --- a/doc/human-gate-permissions.ru.md +++ b/doc/human-gate-permissions.ru.md @@ -1,14 +1,39 @@ -# Права Codex human-gate +# Codex human-gate: зачем он нужен и как выбрать права Эта инструкция объясняет, как выбрать границу возможностей для Codex human-gate run. Полный контракт CLI описан в [spec.md](spec.md). +В примерах предполагается, что `codex` уже выбран в project или user config. + +## Зачем нужен human-gate + +Обычный запуск `start-issue` передаёт управление интерактивной agent session. +Human-gate вместо этого запускает Codex в batch mode: агент может выполнить +issue без постоянного присутствия оператора в интерактивном UI, а в конце +обязан вернуть одно из двух явных состояний: + +- `STATUS: DONE` — запрошенная работа завершена; +- `STATUS: HUMAN_GATE` — требуется реальное решение или недостающее разрешение, + поэтому `start-issue` открывает оператору точный сохранённый Codex thread. + +Такой режим нужен для полуавтономной работы над issue: реализация и тесты могут +выполняться без участия человека, но destructive actions, missing credentials +и product decisions по-прежнему останавливаются на видимом human gate. + +Отдельные permission modes нужны потому, что изменение worktree и доставка PR +пересекают разные границы доверия. Codex в sandbox обычно может изменить и +проверить код, но может не получить доступ к GitHub, записи Git metadata, push +или созданию PR. Если сразу дать каждому run полный доступ, техническая проблема +исчезнет ценой незаметного расширения привилегий. Поэтому `start-issue` +сохраняет безопасную границу по умолчанию и требует явный opt-in, когда агент +должен самостоятельно доставить изменение до PR. + ## Выбор режима Для обычной реализации в worktree используйте `restricted`: ```bash -start-issue 123 --agent codex --human-gate +start-issue 123 --human-gate ``` `restricted` — режим по умолчанию. Codex работает в sandbox @@ -20,7 +45,7 @@ worktree, но network access, запись Git metadata, push и доставк или обновлять pull request, явно включите `full-delivery`: ```bash -start-issue 123 --agent codex --human-gate \ +start-issue 123 --human-gate \ --human-gate-permissions full-delivery ``` @@ -44,7 +69,7 @@ gh repo view --json nameWithOwner,viewerPermission создаст worktree и не запустит Codex. ```bash -start-issue 123 --agent codex --human-gate \ +start-issue 123 --human-gate \ --human-gate-permissions full-delivery \ --dry-run ``` @@ -63,7 +88,7 @@ GitHub account имеет write access. 1. Просмотрите план запуска: ```bash - start-issue 123 --agent codex --human-gate \ + start-issue 123 --human-gate \ --human-gate-permissions full-delivery \ --dry-run ``` @@ -71,7 +96,7 @@ GitHub account имеет write access. 2. После проверки warning об unsandboxed execution запустите реальный run: ```bash - start-issue 123 --agent codex --human-gate \ + start-issue 123 --human-gate \ --human-gate-permissions full-delivery ``` @@ -97,7 +122,7 @@ Environment variable удобно использовать для одной к ```bash START_ISSUE_HUMAN_GATE_PERMISSIONS=full-delivery \ - start-issue 123 --agent codex --human-gate + start-issue 123 --human-gate ``` CLI option имеет приоритет над environment variable. Не стоит добавлять From 7c7737e4c3e0d9959ba0d721b2324212260cf22a Mon Sep 17 00:00:00 2001 From: Danil Pismenny Date: Mon, 24 Aug 2026 22:52:09 +0500 Subject: [PATCH 6/7] Rename Codex human-gate mode to batch --- CHANGELOG.md | 10 + Makefile | 8 +- README.md | 52 +++-- README.ru.md | 52 +++-- cmd/start-issue/main.go | 132 ++++++----- cmd/start-issue/main_test.go | 110 +++++---- doc/batch-mode.md | 195 ++++++++++++++++ doc/batch-mode.ru.md | 215 ++++++++++++++++++ doc/human-gate-permissions.md | 150 ------------ doc/human-gate-permissions.ru.md | 151 ------------ doc/spec.md | 36 +-- memory-bank/README.md | 2 +- memory-bank/domain/README.md | 2 +- memory-bank/domain/context-map.md | 2 +- memory-bank/domain/events.md | 4 +- memory-bank/domain/glossary.md | 7 +- memory-bank/domain/model.md | 2 +- memory-bank/domain/rules.md | 2 +- memory-bank/domain/states.md | 6 +- memory-bank/engineering/architecture.md | 6 +- .../engineering/autonomy-boundaries.md | 2 +- memory-bank/engineering/frontend.md | 4 +- memory-bank/features/FT-017/README.md | 6 +- memory-bank/features/FT-017/brief.md | 36 ++- memory-bank/features/FT-017/decision-log.md | 2 +- memory-bank/features/FT-017/design.md | 24 +- .../features/FT-017/implementation-plan.md | 16 +- .../features/FT-019/implementation-plan.md | 2 +- memory-bank/features/README.md | 4 +- memory-bank/ops/development.md | 4 +- memory-bank/ops/stages.md | 2 +- memory-bank/product/context.md | 2 +- memory-bank/product/roadmap.md | 2 +- memory-bank/product/vision.md | 2 +- test/e2e/batch.sh | 186 +++++++++++++++ test/e2e/human-gate.sh | 186 +-------------- 36 files changed, 909 insertions(+), 715 deletions(-) create mode 100644 doc/batch-mode.md create mode 100644 doc/batch-mode.ru.md delete mode 100644 doc/human-gate-permissions.md delete mode 100644 doc/human-gate-permissions.ru.md create mode 100755 test/e2e/batch.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a1abd6..77676a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ This project follows Semantic Versioning. ## [Unreleased] +### Added + +- Added `--batch` and `--batch-help` as the primary names for autonomous Codex + execution while keeping `--human-gate` and `--human-gate-help` as compatible + aliases. +- Added explicit `restricted` and `full-delivery` batch permissions through + `--batch-permissions` and `START_ISSUE_BATCH_PERMISSIONS`. +- Added English and Russian batch-mode guides with preflight, examples, human + handoff semantics, and troubleshooting. + ## [2.0.2] - 2026-08-04 ### Changed diff --git a/Makefile b/Makefile index 06e8916..fef8b33 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build install uninstall test e2e-sandbox e2e-human-gate print-version bump-patch bump-minor bump-major release-patch release-minor release-major +.PHONY: build install uninstall test e2e-sandbox e2e-batch e2e-human-gate print-version bump-patch bump-minor bump-major release-patch release-minor release-major PREFIX ?= $(HOME)/.local BINDIR ?= $(PREFIX)/bin @@ -31,8 +31,10 @@ test: python3 scripts/check_memory_bank_index.py --max-depth 4 git diff --check -e2e-human-gate: build - @START_ISSUE_E2E_BINARY="$(abspath $(BUILD_OUTPUT))" bash test/e2e/human-gate.sh +e2e-batch: build + @START_ISSUE_E2E_BINARY="$(abspath $(BUILD_OUTPUT))" bash test/e2e/batch.sh + +e2e-human-gate: e2e-batch e2e-sandbox: build @START_ISSUE_SANDBOX_BINARY="$(abspath $(BUILD_OUTPUT))" bash test/e2e/sandbox.sh diff --git a/README.md b/README.md index 34050cb..1854ab2 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ start-issue https://github.com/owner/repo/issues/123 start-issue 123 --repo owner/repo --base develop start-issue 123 --agent codex start-issue 123 --agent codex --model gpt-5.2 -start-issue 123 --agent codex --human-gate +start-issue 123 --batch start-issue 123 --agent kimi --prompt-file .start-issue/prompt.md start-issue 123 --no-agent start-issue 123 --dry-run @@ -76,7 +76,7 @@ start-issue update start-issue --update start-issue install start-issue --install -start-issue --human-gate-help +start-issue --batch-help ``` Running `start-issue` without an issue prints the normal help plus the currently @@ -120,7 +120,7 @@ explicit external process boundaries. - Repository/worktree helpers fetch issue metadata, plan reuse safely, and run the optional `init.sh` hook found in a prepared worktree. - Agent helpers validate adapters, build launch commands, generate AI branch - names, and run Codex human-gate mode. + names, and run Codex batch mode. - Release helpers select platform assets, verify checksums and staged `--version` output, and atomically install updates. @@ -158,9 +158,11 @@ second runtime implementation. | `--prompt TEXT` | Inline prompt template for the selected agent. With `init`, the prompt template to write. Mutually exclusive with `--prompt-file`. | | `--prompt-file PATH` | Prompt template file for the selected agent. With `init`, the file content to write. Mutually exclusive with `--prompt`. | | `--improve-prompt` | Ask the selected agent to generate a reviewable improved prompt template proposal, then exit before creating a worktree. | -| `--human-gate` | Codex-only batch mode for issue work. Runs `codex exec`, exits on `STATUS: DONE`, and resumes the same session on `STATUS: HUMAN_GATE`. | -| `--human-gate-permissions restricted\|full-delivery` | Select the human-gate capability contract. Requires `--human-gate`; CLI overrides `START_ISSUE_HUMAN_GATE_PERMISSIONS`; default is `restricted`. | -| `--human-gate-help` | Show dedicated help for the Codex human-gate workflow, including prompt contract, exit codes, and state files. | +| `--batch` | Codex-only batch mode for issue work. Runs `codex exec`, exits on `STATUS: DONE`, and resumes the same session on `STATUS: HUMAN_GATE`. | +| `--human-gate` | Compatibility alias for `--batch`. | +| `--batch-permissions restricted\|full-delivery` | Select the batch capability contract. Requires `--batch` or its legacy alias; CLI overrides `START_ISSUE_BATCH_PERMISSIONS`; default is `restricted`. | +| `--batch-help` | Show dedicated help for Codex batch mode, including the `HUMAN_GATE` handoff, exit codes, and state files. | +| `--human-gate-help` | Compatibility alias for `--batch-help`. | | `--prompt-output-file PATH` | Proposal output path for `--improve-prompt`. | | `--no-init` | Do not run `init.sh` even if it exists in the created worktree. | | `--command COMMAND`, `-c COMMAND` | Claude command prefix used by the default Claude prompt. Default: `/task-router:route-task`. | @@ -191,7 +193,7 @@ Related Claude Code marketplace workflows: | `START_ISSUE_PROMPT` | Inline prompt template used when no CLI prompt is provided. It overrides project and user prompt files. Mutually exclusive with `START_ISSUE_PROMPT_FILE` when no CLI prompt is provided. | | `START_ISSUE_PROMPT_FILE` | Prompt template file used when no CLI prompt is provided. It overrides project and user prompt files. Mutually exclusive with `START_ISSUE_PROMPT` when no CLI prompt is provided. | | `START_ISSUE_WORKTREE_DIR` | Default parent directory for created worktrees when `--worktree-dir` is not provided. Built-in default: `~/worktrees`. | -| `START_ISSUE_HUMAN_GATE_PERMISSIONS` | Human-gate capability contract when the CLI option is absent: `restricted` or `full-delivery`. Built-in default: `restricted`. | +| `START_ISSUE_BATCH_PERMISSIONS` | Batch capability contract when the CLI option is absent: `restricted` or `full-delivery`. Built-in default: `restricted`. | | `START_ISSUE_DUMP_PROMPT` | When set to `1`, dry-run output includes the full rendered prompt instead of only summary information. | ## Configuration Files @@ -227,9 +229,9 @@ The update workflow works outside a git repository and requires only `gh`. The Go binary parses release metadata, downloads assets, and verifies checksums internally. -## Codex Human-Gate +## Codex Batch Mode -`start-issue 123 --agent codex --human-gate` keeps the normal issue-start workflow through worktree creation, optional `init.sh`, and prompt rendering, but replaces the final interactive Codex launch with a resumable batch run. +`start-issue 123 --batch` keeps the normal issue-start workflow through worktree creation, optional `init.sh`, and prompt rendering, but replaces the final interactive Codex launch with a resumable batch run. The batch flow: @@ -238,9 +240,11 @@ The batch flow: 3. exits `0` on `STATUS: DONE`; 4. opens `codex resume --include-non-interactive ` on `STATUS: HUMAN_GATE`. -This mode is intentionally Codex-only. `--human-gate` with any other agent fails clearly instead of being ignored. +This mode is intentionally Codex-only. `--batch` with any other resolved agent +fails clearly instead of being ignored. The released `--human-gate` flag remains +available as a compatibility alias for `--batch`. -Human-gate permissions are explicit: +Batch permissions are explicit: - `restricted` is the default. It uses `--sandbox workspace-write` and supports working-tree edits, but network access, Git metadata writes, push, and PR @@ -255,11 +259,11 @@ Select the mode with the CLI (highest precedence), the environment, or the safe built-in default: ```bash -start-issue 123 --agent codex --human-gate \ - --human-gate-permissions full-delivery +start-issue 123 --batch \ + --batch-permissions full-delivery -START_ISSUE_HUMAN_GATE_PERMISSIONS=full-delivery \ - start-issue 123 --agent codex --human-gate +START_ISSUE_BATCH_PERMISSIONS=full-delivery \ + start-issue 123 --batch ``` Full delivery changes launcher capability only. It does not authorize @@ -272,17 +276,17 @@ an explicit full-delivery rerun, not reported as a task-level product decision. When the workflow is about to block for a branch/worktree decision, it prints `Waiting for input: ...`. Before handing control to an interactive agent or Codex batch run, it prints `Handing off to in `. A non-zero -exit from `codex exec` is reported as a failed human-gate run with exit code 1; +exit from `codex exec` is reported as a failed batch run with exit code 1; the captured events and thread id remain available for diagnosis. Dedicated help: ```bash -start-issue --human-gate-help +start-issue --batch-help ``` For a copy-ready preflight and end-to-end example, see -[Codex human-gate permissions](doc/human-gate-permissions.md). +[Codex batch mode: autonomous work with a human gate](doc/batch-mode.md). Prompt contract: @@ -309,7 +313,7 @@ The normal automated test suite uses a fake Codex CLI. To exercise the real loca CLI, run this opt-in test from a `start-issue` checkout: ```bash -START_ISSUE_E2E=1 make e2e-human-gate +START_ISSUE_E2E=1 make e2e-batch ``` The script uses the private `dapi/start-issue-e2e-fixture` repository and its @@ -320,7 +324,7 @@ fixture worktree change other than its `.start-issue` state. To test interactive ```bash START_ISSUE_E2E=1 \ -test/e2e/human-gate.sh --scenario human-gate +test/e2e/batch.sh --scenario human-gate ``` Exit the resumed Codex session to let the script verify the artifacts. @@ -331,16 +335,16 @@ remote branch, PR, and local diagnostic fixture as evidence: ```bash START_ISSUE_E2E=1 START_ISSUE_E2E_FULL_DELIVERY=1 \ - test/e2e/human-gate.sh --scenario full-delivery + test/e2e/batch.sh --scenario full-delivery ``` #### Scenarios and checks | Scenario | Command | What it verifies | | --- | --- | --- | -| `done` | `START_ISSUE_E2E=1 make e2e-human-gate` | A real Codex batch run emits `thread.started`, saves `thread-id`, `events.jsonl`, and `last-message.txt`, ends with `STATUS: DONE`, and leaves no fixture change other than `.start-issue` state. | -| `human-gate` | `START_ISSUE_E2E=1 test/e2e/human-gate.sh --scenario human-gate` | The same artifact and clean-worktree checks, plus the reported explicit `codex resume --include-non-interactive ` handoff. The operator exits the resumed interactive session before the script can finish. | -| `full-delivery` | `START_ISSUE_E2E=1 START_ISSUE_E2E_FULL_DELIVERY=1 test/e2e/human-gate.sh --scenario full-delivery` | The current Codex accepts the global full-delivery option and completes a unique fixture commit, push, and PR; the runner prints the retained PR URL and local artifact path. | +| `done` | `START_ISSUE_E2E=1 make e2e-batch` | A real Codex batch run emits `thread.started`, saves `thread-id`, `events.jsonl`, and `last-message.txt`, ends with `STATUS: DONE`, and leaves no fixture change other than `.start-issue` state. | +| `human-gate` | `START_ISSUE_E2E=1 test/e2e/batch.sh --scenario human-gate` | The same artifact and clean-worktree checks, plus the reported explicit `codex resume --include-non-interactive ` handoff. The operator exits the resumed interactive session before the script can finish. | +| `full-delivery` | `START_ISSUE_E2E=1 START_ISSUE_E2E_FULL_DELIVERY=1 test/e2e/batch.sh --scenario full-delivery` | The current Codex accepts the global full-delivery option and completes a unique fixture commit, push, and PR; the runner prints the retained PR URL and local artifact path. | All scenarios verify authenticated `gh`, a real rather than fake Codex binary, and the required `codex exec` help interface (`--output-last-message`, without diff --git a/README.ru.md b/README.ru.md index 7ae21e5..020af56 100644 --- a/README.ru.md +++ b/README.ru.md @@ -68,9 +68,9 @@ Workflow обновления определяет последний GitHub Rel релиз, обновляет тот же путь executable. Если установленная версия уже актуальна, команда успешно завершается и печатает понятный no-op статус. -## Codex Human-Gate +## Codex Batch Mode -`start-issue 123 --agent codex --human-gate` сохраняет обычный issue-start workflow до создания worktree, опционального `init.sh` и рендера prompt, но заменяет финальный интерактивный запуск Codex на resumable batch run. +`start-issue 123 --batch` сохраняет обычный issue-start workflow до создания worktree, опционального `init.sh` и рендера prompt, но заменяет финальный интерактивный запуск Codex на resumable batch run. Batch flow: @@ -79,9 +79,11 @@ Batch flow: 3. завершает команду с кодом `0` на `STATUS: DONE`; 4. открывает `codex resume --include-non-interactive ` на `STATUS: HUMAN_GATE`. -Режим намеренно поддерживается только для Codex. `--human-gate` с любым другим agent завершается явной ошибкой. +Режим намеренно поддерживается только для Codex. `--batch` с любым другим +выбранным agent завершается явной ошибкой. Ранее выпущенный `--human-gate` +продолжает работать как совместимый alias для `--batch`. -Права human-gate задаются явно: +Права batch mode задаются явно: - `restricted` — безопасный default. Он использует `--sandbox workspace-write` и разрешает редактирование worktree, но не гарантирует network, запись Git @@ -93,11 +95,11 @@ Batch flow: Это unsandboxed execution. ```bash -start-issue 123 --agent codex --human-gate \ - --human-gate-permissions full-delivery +start-issue 123 --batch \ + --batch-permissions full-delivery -START_ISSUE_HUMAN_GATE_PERMISSIONS=full-delivery \ - start-issue 123 --agent codex --human-gate +START_ISSUE_BATCH_PERMISSIONS=full-delivery \ + start-issue 123 --batch ``` Приоритет: CLI, environment, затем `restricted`. Full delivery расширяет только @@ -109,17 +111,17 @@ production/security changes или product decisions: для них prompt по- Перед ожиданием решения по конфликту branch/worktree команда печатает `Waiting for input: ...`, а перед передачей управления agent — `Handing off to in `. Ненулевой код `codex exec` считается -ошибкой human-gate и возвращается как код 1; сохраненные events и thread id +ошибкой batch run и возвращается как код 1; сохраненные events и thread id остаются доступными для диагностики. Отдельная справка: ```bash -start-issue --human-gate-help +start-issue --batch-help ``` Пошаговая проверка и готовый end-to-end пример находятся в -[инструкции по правам Codex human-gate](doc/human-gate-permissions.ru.md). +[инструкции по Codex batch mode](doc/batch-mode.ru.md). Контракт prompt: @@ -146,7 +148,7 @@ State files: Codex из checkout `start-issue` выполните opt-in команду: ```bash -START_ISSUE_E2E=1 make e2e-human-gate +START_ISSUE_E2E=1 make e2e-batch ``` Скрипт использует приватный репозиторий `dapi/start-issue-e2e-fixture` и его @@ -157,7 +159,7 @@ control issue, требует авторизованный `gh`, не допус ```bash START_ISSUE_E2E=1 \ -test/e2e/human-gate.sh --scenario human-gate +test/e2e/batch.sh --scenario human-gate ``` Выйдите из возобновлённой Codex-сессии, после чего скрипт проверит артефакты. @@ -168,16 +170,16 @@ branch, PR и локальный diagnostic fixture как evidence: ```bash START_ISSUE_E2E=1 START_ISSUE_E2E_FULL_DELIVERY=1 \ - test/e2e/human-gate.sh --scenario full-delivery + test/e2e/batch.sh --scenario full-delivery ``` #### Сценарии и проверки | Сценарий | Команда | Что проверяется | | --- | --- | --- | -| `done` | `START_ISSUE_E2E=1 make e2e-human-gate` | Реальный Codex batch run выдаёт `thread.started`, сохраняет `thread-id`, `events.jsonl` и `last-message.txt`, заканчивается `STATUS: DONE` и не меняет fixture worktree за пределами `.start-issue` state. | -| `human-gate` | `START_ISSUE_E2E=1 test/e2e/human-gate.sh --scenario human-gate` | Те же проверки артефактов и чистоты worktree, а также явный handoff `codex resume --include-non-interactive `. Перед завершением скрипта оператор выходит из возобновлённой interactive session. | -| `full-delivery` | `START_ISSUE_E2E=1 START_ISSUE_E2E_FULL_DELIVERY=1 test/e2e/human-gate.sh --scenario full-delivery` | Текущий Codex принимает global full-delivery option и выполняет уникальные fixture commit, push и PR; runner печатает сохранённые PR URL и local artifact path. | +| `done` | `START_ISSUE_E2E=1 make e2e-batch` | Реальный Codex batch run выдаёт `thread.started`, сохраняет `thread-id`, `events.jsonl` и `last-message.txt`, заканчивается `STATUS: DONE` и не меняет fixture worktree за пределами `.start-issue` state. | +| `human-gate` | `START_ISSUE_E2E=1 test/e2e/batch.sh --scenario human-gate` | Те же проверки артефактов и чистоты worktree, а также явный handoff `codex resume --include-non-interactive `. Перед завершением скрипта оператор выходит из возобновлённой interactive session. | +| `full-delivery` | `START_ISSUE_E2E=1 START_ISSUE_E2E_FULL_DELIVERY=1 test/e2e/batch.sh --scenario full-delivery` | Текущий Codex принимает global full-delivery option и выполняет уникальные fixture commit, push и PR; runner печатает сохранённые PR URL и local artifact path. | Все сценарии проверяют авторизованный `gh`, реальный, а не fake Codex binary, и обязательный интерфейс справки `codex exec` (`--output-last-message`, без @@ -207,7 +209,7 @@ start-issue https://github.com/owner/repo/issues/123 start-issue 123 --repo owner/repo --base develop start-issue 123 --agent codex start-issue 123 --agent codex --model gpt-5.2 -start-issue 123 --agent codex --human-gate +start-issue 123 --batch start-issue 123 --agent kimi --prompt-file .start-issue/prompt.md start-issue 123 --no-agent start-issue 123 --dry-run @@ -219,7 +221,7 @@ start-issue update start-issue --update start-issue install start-issue --install -start-issue --human-gate-help +start-issue --batch-help ``` Запуск `start-issue` без issue печатает обычную справку, а также текущий @@ -260,7 +262,7 @@ CLI entrypoint — `cmd/start-issue`; runtime, build и тесты реализ - Helpers repository/worktree получают metadata issue, безопасно планируют reuse и запускают optional hook `init.sh` внутри подготовленной worktree. - Helpers agent adapter валидируют agent, строят launch commands, генерируют AI - branch names и выполняют Codex human-gate mode. + branch names и выполняют Codex batch mode. - Helpers release выбирают platform assets, проверяют checksum и staged `--version`, затем атомарно устанавливают update. @@ -298,9 +300,11 @@ boundaries. Новые возможности должны сохранять э | `--prompt TEXT` | Inline prompt template для выбранного агента. С `init` - prompt template, который нужно записать. Нельзя использовать вместе с `--prompt-file`. | | `--prompt-file PATH` | Файл prompt template для выбранного агента. С `init` - содержимое файла, которое нужно записать. Нельзя использовать вместе с `--prompt`. | | `--improve-prompt` | Попросить выбранного агента сгенерировать reviewable proposal улучшенного prompt template и выйти до создания worktree. | -| `--human-gate` | Codex-only batch mode для issue workflow. Запускает `codex exec`, выходит на `STATUS: DONE` и резюмирует ту же сессию на `STATUS: HUMAN_GATE`. | -| `--human-gate-permissions restricted\|full-delivery` | Выбрать capability contract human-gate. Требует `--human-gate`; CLI имеет приоритет над `START_ISSUE_HUMAN_GATE_PERMISSIONS`; default — `restricted`. | -| `--human-gate-help` | Показать отдельную справку по Codex human-gate workflow: prompt contract, exit codes и state files. | +| `--batch` | Codex-only batch mode для issue workflow. Запускает `codex exec`, выходит на `STATUS: DONE` и резюмирует ту же сессию на `STATUS: HUMAN_GATE`. | +| `--human-gate` | Совместимый alias для `--batch`. | +| `--batch-permissions restricted\|full-delivery` | Выбрать capability contract batch mode. Требует `--batch` или его legacy alias; CLI имеет приоритет над `START_ISSUE_BATCH_PERMISSIONS`; default — `restricted`. | +| `--batch-help` | Показать отдельную справку по Codex batch mode: `HUMAN_GATE`, exit codes и state files. | +| `--human-gate-help` | Совместимый alias для `--batch-help`. | | `--prompt-output-file PATH` | Путь для proposal-файла в режиме `--improve-prompt`. | | `--no-init` | Не запускать `init.sh`, даже если он есть в созданном worktree. | | `--command COMMAND`, `-c COMMAND` | Префикс Claude command для стандартного Claude prompt. Значение по умолчанию: `/task-router:route-task`. | @@ -331,7 +335,7 @@ boundaries. Новые возможности должны сохранять э | `START_ISSUE_PROMPT` | Inline prompt template, который используется, если prompt не задан через CLI. Перебивает project и user prompt files. Нельзя использовать вместе с `START_ISSUE_PROMPT_FILE`, когда prompt не задан через CLI. | | `START_ISSUE_PROMPT_FILE` | Файл prompt template, который используется, если prompt не задан через CLI. Перебивает project и user prompt files. Нельзя использовать вместе с `START_ISSUE_PROMPT`, когда prompt не задан через CLI. | | `START_ISSUE_WORKTREE_DIR` | Родительская директория по умолчанию для создаваемых worktree, если `--worktree-dir` не передан. Встроенное значение по умолчанию: `~/worktrees`. | -| `START_ISSUE_HUMAN_GATE_PERMISSIONS` | Capability contract human-gate при отсутствии CLI option: `restricted` или `full-delivery`. Built-in default: `restricted`. | +| `START_ISSUE_BATCH_PERMISSIONS` | Capability contract batch mode при отсутствии CLI option: `restricted` или `full-delivery`. Built-in default: `restricted`. | | `START_ISSUE_DUMP_PROMPT` | Если задана в `1`, dry-run выводит полный rendered prompt вместо краткой информации. | ## Файлы конфигурации diff --git a/cmd/start-issue/main.go b/cmd/start-issue/main.go index 0b92737..ab9257c 100644 --- a/cmd/start-issue/main.go +++ b/cmd/start-issue/main.go @@ -57,12 +57,12 @@ func versionFromBuildInfo(info *debug.BuildInfo, fallback ...string) string { } type options struct { - repo, base, worktreeDir, agent, model, promptFile, prompt, command string - promptOutput, worktreeDirSource string - humanGatePermissions, humanGatePermissionsSource string - issue string - dryRun, noInit, flat, ai, improvePrompt, humanGate, project, user, force bool - mode string + repo, base, worktreeDir, agent, model, promptFile, prompt, command string + promptOutput, worktreeDirSource string + batchPermissions, batchPermissionsSource string + issue string + dryRun, noInit, flat, ai, improvePrompt, batch, project, user, force bool + mode string } type issue struct { @@ -140,16 +140,16 @@ func main() { func parse(args []string) (options, error) { o := options{ - worktreeDir: os.Getenv("START_ISSUE_WORKTREE_DIR"), - humanGatePermissions: "restricted", - humanGatePermissionsSource: "built-in default", + worktreeDir: os.Getenv("START_ISSUE_WORKTREE_DIR"), + batchPermissions: "restricted", + batchPermissionsSource: "built-in default", } if o.worktreeDir != "" { o.worktreeDirSource = "START_ISSUE_WORKTREE_DIR" } - if permissions := os.Getenv("START_ISSUE_HUMAN_GATE_PERMISSIONS"); permissions != "" { - o.humanGatePermissions = permissions - o.humanGatePermissionsSource = "START_ISSUE_HUMAN_GATE_PERMISSIONS" + if permissions := os.Getenv("START_ISSUE_BATCH_PERMISSIONS"); permissions != "" { + o.batchPermissions = permissions + o.batchPermissionsSource = "START_ISSUE_BATCH_PERMISSIONS" } var err error for len(args) > 0 { @@ -206,12 +206,12 @@ func parse(args []string) (options, error) { o.improvePrompt = true case "--prompt-output-file": o.promptOutput, err = value() - case "--human-gate": - o.humanGate = true - case "--human-gate-permissions": - o.humanGatePermissions, err = value() + case "--batch", "--human-gate": + o.batch = true + case "--batch-permissions": + o.batchPermissions, err = value() if err == nil { - o.humanGatePermissionsSource = "CLI" + o.batchPermissionsSource = "CLI" } case "--project": o.project = true @@ -225,8 +225,8 @@ func parse(args []string) (options, error) { return o, fmt.Errorf("Use only one command mode; got %s and %s.", o.mode, mode) } o.mode = mode - case "--human-gate-help": - humanGateHelp() + case "--batch-help", "--human-gate-help": + batchHelp() os.Exit(0) default: if strings.HasPrefix(a, "-") { @@ -253,11 +253,11 @@ func parse(args []string) (options, error) { if o.mode != "" && o.issue != "" { return o, fmt.Errorf("Use either %s or , not both.", o.mode) } - if o.humanGatePermissionsSource == "CLI" && !o.humanGate { - return o, errors.New("--human-gate-permissions requires --human-gate.") + if o.batchPermissionsSource == "CLI" && !o.batch { + return o, errors.New("--batch-permissions requires --batch.") } - if !validHumanGatePermissions(o.humanGatePermissions) { - return o, fmt.Errorf("Invalid human-gate permissions %q. Use restricted or full-delivery.", o.humanGatePermissions) + if !validBatchPermissions(o.batchPermissions) { + return o, fmt.Errorf("Invalid batch permissions %q. Use restricted or full-delivery.", o.batchPermissions) } if o.worktreeDir == "" && o.mode == "" { home, err := userHomeDir() @@ -270,7 +270,7 @@ func parse(args []string) (options, error) { return o, nil } -func validHumanGatePermissions(value string) bool { +func validBatchPermissions(value string) bool { return value == "restricted" || value == "full-delivery" } @@ -306,8 +306,8 @@ func runWithReader(o options, reader *bufio.Reader) error { if err != nil { return err } - if o.humanGate && agent != "codex" { - return fmt.Errorf("--human-gate requires agent 'codex'. Current agent: %s.", agent) + if o.batch && agent != "codex" { + return fmt.Errorf("--batch requires agent 'codex'. Current agent: %s.", agent) } if o.improvePrompt && agent == "none" { return errors.New("--improve-prompt requires an agent. Use --agent claude, codex, kimi, or pi.") @@ -448,8 +448,8 @@ func runWithReader(o options, reader *bufio.Reader) error { rendered := renderIssuePrompt(prompt, issueURL, number, in, labels, repo, branch, worktree, o.base) if o.dryRun { fmt.Printf(" [DRY-RUN] Would run: git worktree add -b %s %s %s\n", branch, worktree, o.base) - if o.humanGate { - return humanGate(model, worktree, rendered, o.humanGatePermissions, o.humanGatePermissionsSource, true) + if o.batch { + return runBatch(model, worktree, rendered, o.batchPermissions, o.batchPermissionsSource, true) } return launchSelected(options{dryRun: true}, agent, model, worktree, rendered) } @@ -1766,8 +1766,8 @@ func canonicalPath(path string) string { } func launchSelected(o options, agent, model, worktree, prompt string) error { if o.dryRun { - if o.humanGate { - return humanGate(model, worktree, prompt, o.humanGatePermissions, o.humanGatePermissionsSource, true) + if o.batch { + return runBatch(model, worktree, prompt, o.batchPermissions, o.batchPermissionsSource, true) } if agent == "none" { printManualNextSteps(model, worktree) @@ -1776,11 +1776,11 @@ func launchSelected(o options, agent, model, worktree, prompt string) error { printLaunch(agent, model, worktree, prompt) return nil } - if o.humanGate { + if o.batch { if !o.dryRun { printAgentHandoff(agent, worktree) } - return humanGate(model, worktree, prompt, o.humanGatePermissions, o.humanGatePermissionsSource, false) + return runBatch(model, worktree, prompt, o.batchPermissions, o.batchPermissionsSource, false) } if !o.dryRun && agent != "none" { printAgentHandoff(agent, worktree) @@ -1848,16 +1848,16 @@ func normalizePromptProposal(result string) string { } return strings.TrimSpace(strings.Join(lines, "\n")) } -func humanGate(model, worktree, prompt, permissions, permissionsSource string, dryRun bool) error { +func runBatch(model, worktree, prompt, permissions, permissionsSource string, dryRun bool) error { runID := os.Getenv("START_ISSUE_RUN_ID") if runID == "" { runID = time.Now().Format("20060102-150405") } dir := filepath.Join(worktree, ".start-issue", "runs", runID) events, last := filepath.Join(dir, "events.jsonl"), filepath.Join(dir, "last-message.txt") - args := humanGateArgs(model, worktree, last, permissions) + args := batchArgs(model, worktree, last, permissions) fmt.Printf(" State dir: %s\n", dir) - fmt.Printf(" Human-gate permissions: %s (%s)\n", permissions, permissionsSource) + fmt.Printf(" Batch permissions: %s (%s)\n", permissions, permissionsSource) if permissions == "full-delivery" { fmt.Println(" WARNING: Codex will run without approvals or sandboxing for GitHub and Git delivery.") fmt.Println(" Requires authenticated GitHub access and repository write permission; destructive or production actions still require HUMAN_GATE.") @@ -1917,7 +1917,7 @@ func humanGate(model, worktree, prompt, permissions, permissionsSource string, d return fmt.Errorf("No recognized final status found. Inspect: %s", last) } -func humanGateArgs(model, worktree, lastMessage, permissions string) []string { +func batchArgs(model, worktree, lastMessage, permissions string) []string { args := []string{} if model != "" { args = append(args, "--model", model) @@ -1946,7 +1946,7 @@ func captureThreadID(events string) (string, error) { return event.ThreadID, nil } } - return "", fmt.Errorf("Codex human-gate run did not capture thread_id. Inspect: %s", events) + return "", fmt.Errorf("Codex batch run did not capture thread_id. Inspect: %s", events) } func finalStatus(body string) string { @@ -2202,11 +2202,13 @@ Options: --prompt-file Prompt template file for the launched agent --improve-prompt Ask the selected agent to improve the selected prompt template and write a reviewable proposal - --human-gate Codex-only batch mode that resumes on HUMAN_GATE - --human-gate-permissions - Requires --human-gate; permission contract for it - Default: START_ISSUE_HUMAN_GATE_PERMISSIONS or restricted - --human-gate-help Show detailed help for the human-gate mode + --batch Run Codex autonomously until DONE or HUMAN_GATE + --human-gate Compatibility alias for --batch + --batch-permissions + Requires --batch; capability contract for the run + Default: START_ISSUE_BATCH_PERMISSIONS or restricted + --batch-help Show detailed help for Codex batch mode + --human-gate-help Compatibility alias for --batch-help --prompt-output-file Output path for --improve-prompt proposal --no-init Skip init.sh execution @@ -2262,7 +2264,7 @@ Environment variables: START_ISSUE_PROMPT START_ISSUE_PROMPT_FILE START_ISSUE_WORKTREE_DIR - START_ISSUE_HUMAN_GATE_PERMISSIONS + START_ISSUE_BATCH_PERMISSIONS START_ISSUE_DUMP_PROMPT Examples: @@ -2271,8 +2273,8 @@ Examples: start-issue 123 --repo owner/repo --base develop start-issue 123 --agent codex start-issue 123 --agent codex --model gpt-5.2 - start-issue 123 --agent codex --human-gate - start-issue 123 --agent codex --human-gate --human-gate-permissions full-delivery + start-issue 123 --batch + start-issue 123 --batch --batch-permissions full-delivery start-issue 123 --agent claude --model sonnet start-issue 123 --agent kimi --prompt-file .start-issue/prompt.md start-issue 123 --no-agent # Only create worktree @@ -2289,7 +2291,7 @@ Examples: start-issue --update start-issue install start-issue --install - start-issue --human-gate-help + start-issue --batch-help `, runningVersion()) } @@ -2297,22 +2299,35 @@ func printBanner() { fmt.Printf("start-issue v%s\n\n", runningVersion()) } -func humanGateHelp() { +func batchHelp() { printBanner() - fmt.Println(`Codex human-gate mode + fmt.Println(`Codex batch mode + +What it is: + --batch runs Codex autonomously through codex exec instead of opening the + interactive UI immediately. Codex works until its final message reports + STATUS: DONE or STATUS: HUMAN_GATE. HUMAN_GATE resumes the exact saved + thread so the operator can make the required decision. + + Batch mode requires the resolved agent to be codex. The examples below + assume codex is already selected in project or user configuration. Usage: - start-issue --agent codex --human-gate - start-issue --agent codex --human-gate \ - --human-gate-permissions full-delivery - start-issue --human-gate-help + start-issue --batch + start-issue --batch \ + --batch-permissions full-delivery + start-issue --batch-help + +Compatibility aliases: + --human-gate Same behavior as --batch. + --human-gate-help Same behavior as --batch-help. Permission modes: restricted (default) Uses Codex workspace-write sandboxing. Working-tree edits are supported, but network access, Git metadata writes, push, and PR delivery are not - guaranteed. Select with START_ISSUE_HUMAN_GATE_PERMISSIONS=restricted or - --human-gate-permissions restricted. + guaranteed. Select with START_ISSUE_BATCH_PERMISSIONS=restricted or + --batch-permissions restricted. full-delivery (explicit opt-in) Runs Codex with --dangerously-bypass-approvals-and-sandbox so a normal @@ -2323,13 +2338,14 @@ Permission modes: still require STATUS: HUMAN_GATE. Precedence: - --human-gate-permissions, START_ISSUE_HUMAN_GATE_PERMISSIONS, restricted. - --human-gate-permissions requires --human-gate. + --batch-permissions, START_ISSUE_BATCH_PERMISSIONS, restricted. + --batch-permissions requires --batch or its --human-gate alias. Flow: The normal issue workflow creates or reuses the worktree, renders the - prompt, and runs Codex in batch mode. The final message must contain one - terminal status line: STATUS: DONE or STATUS: HUMAN_GATE. + prompt, and runs Codex in batch mode. start-issue saves the event stream, + last message, and thread id. The final message must contain one terminal + status line: STATUS: DONE or STATUS: HUMAN_GATE. Exit codes: 0 Codex returned STATUS: DONE. diff --git a/cmd/start-issue/main_test.go b/cmd/start-issue/main_test.go index 174f7c6..de4f9ba 100644 --- a/cmd/start-issue/main_test.go +++ b/cmd/start-issue/main_test.go @@ -58,42 +58,47 @@ func TestParseTracksWorktreeDirectorySource(t *testing.T) { } } -func TestParseHumanGatePermissionsPrecedenceAndValidation(t *testing.T) { +func TestParseBatchPermissionsPrecedenceAndValidation(t *testing.T) { t.Setenv("HOME", t.TempDir()) - t.Setenv("START_ISSUE_HUMAN_GATE_PERMISSIONS", "") + t.Setenv("START_ISSUE_BATCH_PERMISSIONS", "") o, err := parse([]string{"1"}) if err != nil { t.Fatal(err) } - if o.humanGatePermissions != "restricted" || o.humanGatePermissionsSource != "built-in default" { - t.Fatalf("default permissions = %q (%s)", o.humanGatePermissions, o.humanGatePermissionsSource) + if o.batchPermissions != "restricted" || o.batchPermissionsSource != "built-in default" { + t.Fatalf("default permissions = %q (%s)", o.batchPermissions, o.batchPermissionsSource) } - t.Setenv("START_ISSUE_HUMAN_GATE_PERMISSIONS", "full-delivery") + t.Setenv("START_ISSUE_BATCH_PERMISSIONS", "full-delivery") o, err = parse([]string{"1"}) if err != nil { t.Fatal(err) } - if o.humanGatePermissions != "full-delivery" || o.humanGatePermissionsSource != "START_ISSUE_HUMAN_GATE_PERMISSIONS" { - t.Fatalf("environment permissions = %q (%s)", o.humanGatePermissions, o.humanGatePermissionsSource) + if o.batchPermissions != "full-delivery" || o.batchPermissionsSource != "START_ISSUE_BATCH_PERMISSIONS" { + t.Fatalf("environment permissions = %q (%s)", o.batchPermissions, o.batchPermissionsSource) } - o, err = parse([]string{"1", "--human-gate", "--human-gate-permissions", "restricted"}) + o, err = parse([]string{"1", "--batch", "--batch-permissions", "restricted"}) if err != nil { t.Fatal(err) } - if o.humanGatePermissions != "restricted" || o.humanGatePermissionsSource != "CLI" { - t.Fatalf("CLI permissions = %q (%s)", o.humanGatePermissions, o.humanGatePermissionsSource) + if !o.batch || o.batchPermissions != "restricted" || o.batchPermissionsSource != "CLI" { + t.Fatalf("CLI permissions = %q (%s)", o.batchPermissions, o.batchPermissionsSource) } - if _, err := parse([]string{"1", "--human-gate-permissions", "restricted"}); err == nil || !strings.Contains(err.Error(), "requires --human-gate") { - t.Fatalf("permission flag without human-gate error = %v", err) + legacy, err := parse([]string{"1", "--human-gate"}) + if err != nil || !legacy.batch { + t.Fatalf("legacy --human-gate alias = %+v, %v", legacy, err) } - if _, err := parse([]string{"1", "--human-gate", "--human-gate-permissions", "unlimited"}); err == nil || !strings.Contains(err.Error(), "Use restricted or full-delivery") { + + if _, err := parse([]string{"1", "--batch-permissions", "restricted"}); err == nil || !strings.Contains(err.Error(), "requires --batch") { + t.Fatalf("permission flag without batch error = %v", err) + } + if _, err := parse([]string{"1", "--batch", "--batch-permissions", "unlimited"}); err == nil || !strings.Contains(err.Error(), "Use restricted or full-delivery") { t.Fatalf("invalid CLI permissions error = %v", err) } - t.Setenv("START_ISSUE_HUMAN_GATE_PERMISSIONS", "unlimited") + t.Setenv("START_ISSUE_BATCH_PERMISSIONS", "unlimited") if _, err := parse([]string{"1"}); err == nil || !strings.Contains(err.Error(), "Use restricted or full-delivery") { t.Fatalf("invalid environment permissions error = %v", err) } @@ -1157,9 +1162,12 @@ func TestUsageListsCompatibilityEntryPoints(t *testing.T) { "--setup", "--update", "--install", + "--batch", + "--human-gate Compatibility alias for --batch", + "--batch-help", "--human-gate-help", - "--human-gate-permissions ", - "START_ISSUE_HUMAN_GATE_PERMISSIONS", + "--batch-permissions ", + "START_ISSUE_BATCH_PERMISSIONS", "Agent selection precedence:", ".start-issue/agent in the git root", "Prompt template precedence:", @@ -1172,8 +1180,8 @@ func TestUsageListsCompatibilityEntryPoints(t *testing.T) { } } -func TestHumanGateHelpExplainsPermissionContract(t *testing.T) { - output := captureStdout(t, humanGateHelp) +func TestBatchHelpExplainsPermissionContract(t *testing.T) { + output := captureStdout(t, batchHelp) for _, want := range []string{ "restricted (default)", "full-delivery (explicit opt-in)", @@ -1181,11 +1189,13 @@ func TestHumanGateHelpExplainsPermissionContract(t *testing.T) { "authenticated gh session", "repository write permission", "destructive, production, security, or product decisions", - "START_ISSUE_HUMAN_GATE_PERMISSIONS", + "START_ISSUE_BATCH_PERMISSIONS", + "--human-gate Same behavior as --batch", + "STATUS: DONE or STATUS: HUMAN_GATE", "gh auth status", } { if !strings.Contains(output, want) { - t.Fatalf("human-gate help missing %q:\n%s", want, output) + t.Fatalf("batch help missing %q:\n%s", want, output) } } } @@ -1824,7 +1834,7 @@ func TestHelperArgsAreNonInteractive(t *testing.T) { } } -func TestHumanGateSavesThreadIDBeforeDone(t *testing.T) { +func TestBatchSavesThreadIDBeforeDone(t *testing.T) { worktree, bin := t.TempDir(), t.TempDir() writeFakeCodex(t, bin) t.Setenv("PATH", bin+string(os.PathListSeparator)+os.Getenv("PATH")) @@ -1833,7 +1843,7 @@ func TestHumanGateSavesThreadIDBeforeDone(t *testing.T) { t.Setenv("CODEX_LAST", "STATUS: DONE") t.Setenv("START_ISSUE_FAKE_CODEX_REJECT_ASK_FOR_APPROVAL", "1") - if err := humanGate("", worktree, "prompt", "restricted", "built-in default", false); err != nil { + if err := runBatch("", worktree, "prompt", "restricted", "built-in default", false); err != nil { t.Fatal(err) } threadID, err := os.ReadFile(filepath.Join(worktree, ".start-issue", "runs", "done", "thread-id")) @@ -1842,7 +1852,7 @@ func TestHumanGateSavesThreadIDBeforeDone(t *testing.T) { } } -func TestHumanGateSavesThreadIDWhenFinalMessageIsMissing(t *testing.T) { +func TestBatchSavesThreadIDWhenFinalMessageIsMissing(t *testing.T) { worktree, bin := t.TempDir(), t.TempDir() writeFakeCodex(t, bin) t.Setenv("PATH", bin+string(os.PathListSeparator)+os.Getenv("PATH")) @@ -1850,9 +1860,9 @@ func TestHumanGateSavesThreadIDWhenFinalMessageIsMissing(t *testing.T) { t.Setenv("CODEX_EVENTS", `{"type":"thread.started","thread_id":"thread-recovery"}`) t.Setenv("CODEX_SKIP_LAST", "1") - err := humanGate("", worktree, "prompt", "restricted", "built-in default", false) + err := runBatch("", worktree, "prompt", "restricted", "built-in default", false) if err == nil || !strings.Contains(err.Error(), "No recognized final status found") { - t.Fatalf("humanGate error = %v, want missing final-status error", err) + t.Fatalf("runBatch error = %v, want missing final-status error", err) } threadID, readErr := os.ReadFile(filepath.Join(worktree, ".start-issue", "runs", "missing-last-message", "thread-id")) if readErr != nil || string(threadID) != "thread-recovery\n" { @@ -1860,7 +1870,7 @@ func TestHumanGateSavesThreadIDWhenFinalMessageIsMissing(t *testing.T) { } } -func TestHumanGateExecFailureReturnsExitCodeOne(t *testing.T) { +func TestBatchExecFailureReturnsExitCodeOne(t *testing.T) { worktree, bin := t.TempDir(), t.TempDir() writeFakeCodex(t, bin) t.Setenv("PATH", bin+string(os.PathListSeparator)+os.Getenv("PATH")) @@ -1869,10 +1879,10 @@ func TestHumanGateExecFailureReturnsExitCodeOne(t *testing.T) { t.Setenv("CODEX_LAST", "STATUS: DONE") t.Setenv("CODEX_EXEC_EXIT", "42") - err := humanGate("", worktree, "prompt", "restricted", "built-in default", false) + err := runBatch("", worktree, "prompt", "restricted", "built-in default", false) var exit exitError if !errors.As(err, &exit) || exit.code != 1 { - t.Fatalf("got %T %v, want human-gate exit code 1", err, err) + t.Fatalf("got %T %v, want batch exit code 1", err, err) } if !strings.Contains(err.Error(), "Codex batch run failed") { t.Fatalf("error = %v, want batch failure diagnostic", err) @@ -1892,17 +1902,17 @@ func TestRunChecksForGitBeforeRepositoryValidation(t *testing.T) { } } -func TestHumanGateDryRunShowsAllStateArtifacts(t *testing.T) { +func TestBatchDryRunShowsAllStateArtifacts(t *testing.T) { worktree := t.TempDir() t.Setenv("START_ISSUE_RUN_ID", "plan") dir := filepath.Join(worktree, ".start-issue", "runs", "plan") output := captureStdout(t, func() { - if err := humanGate("", worktree, "prompt", "restricted", "built-in default", true); err != nil { + if err := runBatch("", worktree, "prompt", "restricted", "built-in default", true); err != nil { t.Fatal(err) } }) for _, want := range []string{ - "Human-gate permissions: restricted (built-in default)", + "Batch permissions: restricted (built-in default)", "Restricted mode: working-tree edits only", "--output-last-message " + filepath.Join(dir, "last-message.txt"), "> " + filepath.Join(dir, "events.jsonl"), @@ -1916,32 +1926,32 @@ func TestHumanGateDryRunShowsAllStateArtifacts(t *testing.T) { t.Fatalf("dry-run includes obsolete --ask-for-approval argument:\n%s", output) } if _, err := os.Stat(dir); !os.IsNotExist(err) { - t.Fatalf("human-gate dry-run created state directory: %v", err) + t.Fatalf("batch dry-run created state directory: %v", err) } } -func TestHumanGateArgsMapPermissionModesInSupportedOrder(t *testing.T) { +func TestBatchArgsMapPermissionModesInSupportedOrder(t *testing.T) { worktree := "/tmp/worktree" last := "/tmp/last-message.txt" - restricted := humanGateArgs("gpt-test", worktree, last, "restricted") + restricted := batchArgs("gpt-test", worktree, last, "restricted") if got, want := fmt.Sprint(restricted), "[--model gpt-test exec --cd /tmp/worktree --sandbox workspace-write --json --output-last-message /tmp/last-message.txt -]"; got != want { t.Fatalf("restricted args = %s, want %s", got, want) } - fullDelivery := humanGateArgs("gpt-test", worktree, last, "full-delivery") + fullDelivery := batchArgs("gpt-test", worktree, last, "full-delivery") if got, want := fmt.Sprint(fullDelivery), "[--model gpt-test --dangerously-bypass-approvals-and-sandbox exec --cd /tmp/worktree --json --output-last-message /tmp/last-message.txt -]"; got != want { t.Fatalf("full-delivery args = %s, want %s", got, want) } } -func TestHumanGateFullDeliveryDryRunShowsResolvedModeAndCommand(t *testing.T) { +func TestBatchFullDeliveryDryRunShowsResolvedModeAndCommand(t *testing.T) { worktree := t.TempDir() t.Setenv("START_ISSUE_RUN_ID", "full-delivery-plan") output := captureStdout(t, func() { err := launchSelected(options{ - dryRun: true, - humanGate: true, - humanGatePermissions: "full-delivery", - humanGatePermissionsSource: "CLI", + dryRun: true, + batch: true, + batchPermissions: "full-delivery", + batchPermissionsSource: "CLI", }, "codex", "gpt-test", worktree, "prompt") if err != nil { t.Fatal(err) @@ -1949,7 +1959,7 @@ func TestHumanGateFullDeliveryDryRunShowsResolvedModeAndCommand(t *testing.T) { }) wantCommand := "codex --model gpt-test --dangerously-bypass-approvals-and-sandbox exec --cd " for _, want := range []string{ - "Human-gate permissions: full-delivery (CLI)", + "Batch permissions: full-delivery (CLI)", wantCommand, "WARNING: Codex will run without approvals or sandboxing", } { @@ -1962,7 +1972,7 @@ func TestHumanGateFullDeliveryDryRunShowsResolvedModeAndCommand(t *testing.T) { } } -func TestHumanGateFullDeliveryReportsWarningAndCompletes(t *testing.T) { +func TestBatchFullDeliveryReportsWarningAndCompletes(t *testing.T) { worktree, bin := t.TempDir(), t.TempDir() writeFakeCodex(t, bin) t.Setenv("PATH", bin+string(os.PathListSeparator)+os.Getenv("PATH")) @@ -1971,12 +1981,12 @@ func TestHumanGateFullDeliveryReportsWarningAndCompletes(t *testing.T) { t.Setenv("CODEX_LAST", "STATUS: DONE") output := captureStdout(t, func() { - if err := humanGate("gpt-test", worktree, "prompt", "full-delivery", "CLI", false); err != nil { + if err := runBatch("gpt-test", worktree, "prompt", "full-delivery", "CLI", false); err != nil { t.Fatal(err) } }) for _, want := range []string{ - "Human-gate permissions: full-delivery (CLI)", + "Batch permissions: full-delivery (CLI)", "WARNING: Codex will run without approvals or sandboxing", "destructive or production actions still require HUMAN_GATE", "STATUS: DONE", @@ -1987,7 +1997,7 @@ func TestHumanGateFullDeliveryReportsWarningAndCompletes(t *testing.T) { } } -func TestHumanGatePreservesCallerWorkingDirectory(t *testing.T) { +func TestBatchPreservesCallerWorkingDirectory(t *testing.T) { worktree, bin, log := t.TempDir(), t.TempDir(), filepath.Join(t.TempDir(), "cwd") writeFakeCodex(t, bin) t.Setenv("PATH", bin+string(os.PathListSeparator)+os.Getenv("PATH")) @@ -1999,7 +2009,7 @@ func TestHumanGatePreservesCallerWorkingDirectory(t *testing.T) { if err != nil { t.Fatal(err) } - if err := humanGate("", worktree, "prompt", "restricted", "built-in default", false); err != nil { + if err := runBatch("", worktree, "prompt", "restricted", "built-in default", false); err != nil { t.Fatal(err) } got, err := os.ReadFile(log) @@ -2011,7 +2021,7 @@ func TestHumanGatePreservesCallerWorkingDirectory(t *testing.T) { } } -func TestHumanGateRejectsDoneWithoutThreadID(t *testing.T) { +func TestBatchRejectsDoneWithoutThreadID(t *testing.T) { worktree, bin := t.TempDir(), t.TempDir() writeFakeCodex(t, bin) t.Setenv("PATH", bin+string(os.PathListSeparator)+os.Getenv("PATH")) @@ -2019,13 +2029,13 @@ func TestHumanGateRejectsDoneWithoutThreadID(t *testing.T) { t.Setenv("CODEX_EVENTS", `{"type":"item.completed"}`) t.Setenv("CODEX_LAST", "STATUS: DONE") - err := humanGate("", worktree, "prompt", "restricted", "built-in default", false) + err := runBatch("", worktree, "prompt", "restricted", "built-in default", false) if err == nil || !strings.Contains(err.Error(), "did not capture thread_id") { t.Fatalf("got %v", err) } } -func TestHumanGateResumeFailureReturnsExitCodeTwo(t *testing.T) { +func TestBatchResumeFailureReturnsExitCodeTwo(t *testing.T) { worktree, bin := t.TempDir(), t.TempDir() writeFakeCodex(t, bin) t.Setenv("PATH", bin+string(os.PathListSeparator)+os.Getenv("PATH")) @@ -2034,7 +2044,7 @@ func TestHumanGateResumeFailureReturnsExitCodeTwo(t *testing.T) { t.Setenv("CODEX_LAST", "STATUS: HUMAN_GATE") t.Setenv("CODEX_RESUME_EXIT", "1") - err := humanGate("", worktree, "prompt", "restricted", "built-in default", false) + err := runBatch("", worktree, "prompt", "restricted", "built-in default", false) var exit exitError if !errors.As(err, &exit) || exit.code != 2 { t.Fatalf("got %T %v", err, err) diff --git a/doc/batch-mode.md b/doc/batch-mode.md new file mode 100644 index 0000000..5ee3daf --- /dev/null +++ b/doc/batch-mode.md @@ -0,0 +1,195 @@ +# Codex batch mode: autonomous work with a human gate + +This guide is for someone seeing `start-issue --batch` for the first time. It +explains what batch mode does, why a run may stop at `HUMAN_GATE`, how the +operator continues the same Codex session, and when the agent needs restricted +or full-delivery permissions. For the complete CLI contract, see +[spec.md](spec.md). + +The examples assume that `codex` is already selected by project or user +configuration. The released `--human-gate` flag remains available as a +compatibility alias for `--batch`. + +## What human-gate is + +Human-gate is a checkpoint where autonomous Codex work is either declared +complete or handed to a person for a decision. + +A normal `start-issue` run opens an interactive Codex session. With `--batch`, +Codex runs in batch mode and works through the issue without the +operator remaining in the interactive UI. Its final response must contain one +explicit result: + +- `STATUS: DONE` — the task is complete and no human action is needed; +- `STATUS: HUMAN_GATE` — continuing requires a human decision. + +Human-gate is not an approval prompt before every command and it is not a +sandbox mode. It defines how an autonomous run ends and when control returns to +the operator. + +## Why use it + +Without this protocol, an exit code alone cannot reliably distinguish finished +work from a run that stopped because of a question, missing access, or an +unresolved failure. Human-gate lets Codex implement and test unattended, report +unambiguous completion, stop before decisions it must not make, and resume the +same saved thread instead of starting over with a new agent. + +## How it works + +1. `start-issue` resolves the issue, creates or reuses the worktree, and renders + the prompt as usual. +2. It runs `codex exec` in batch mode instead of opening an interactive session. +3. It saves the event stream, final message, and `thread_id`. +4. Codex performs the task and ends with exactly one status line: + `STATUS: DONE` or `STATUS: HUMAN_GATE`. +5. `DONE` exits successfully. `HUMAN_GATE` opens the saved session with + `codex resume --include-non-interactive `, preserving all context + for the operator. +6. A Codex error or missing status produces an error while preserving the + diagnostic files. + +Codex decides whether to stop according to the prompt rules; `start-issue` +does not inspect every command. It reads the final status and performs the +corresponding handoff. + +## When Codex should stop + +`STATUS: HUMAN_GATE` is appropriate for destructive operations, missing +credentials, incompatible product choices, unresolved failures that cannot be +safely fixed within the issue, and production or security decisions. Routine +technical choices and recoverable errors should be handled by Codex without +stopping the run. + +## How permissions relate to human-gate + +These are separate controls: + +- `--batch` enables the batch run and `DONE` / `HUMAN_GATE` handoff; +- `--batch-permissions` controls which technical operations Codex can + perform during that run. + +Editing a worktree and delivering a PR cross different trust boundaries. A +sandboxed run can usually change and test code but may not be able to access +GitHub, write Git metadata, push, or create a PR. The safe boundary therefore +remains the default, while end-to-end delivery requires a visible opt-in. + +## Choose a mode + +Use `restricted` for normal working-tree implementation: + +```bash +start-issue 123 --batch +``` + +`restricted` is the default. Codex runs with the `workspace-write` sandbox. It +can edit and test files in the prepared worktree, but network access, Git +metadata writes, push, and pull-request delivery are not guaranteed. + +Use `full-delivery` only when the run must also read GitHub context, commit, +push, and create or update a pull request: + +```bash +start-issue 123 --batch \ + --batch-permissions full-delivery +``` + +This is an explicit opt-in to unsandboxed Codex execution. It grants technical +capability, not permission for destructive Git operations, production or +security changes, or unresolved product decisions. Codex must still return +`STATUS: HUMAN_GATE` when one of those decisions requires the operator. + +## Full-delivery preflight + +Before using `full-delivery`, check the selected GitHub account, remote, and +repository access: + +```bash +gh auth status +git remote get-url origin +gh repo view --json nameWithOwner,viewerPermission +``` + +Run a dry-run to verify the resolved mode and launch command without creating +a worktree or starting Codex: + +```bash +start-issue 123 --batch \ + --batch-permissions full-delivery \ + --dry-run +``` + +The output should report: + +```text +Batch permissions: full-delivery (CLI) +``` + +## Example: implement an issue and deliver its PR + +Assume issue `123` is in the current repository and the authenticated GitHub +account has write access. + +1. Inspect the planned run: + + ```bash + start-issue 123 --batch \ + --batch-permissions full-delivery \ + --dry-run + ``` + +2. Start the real run after reviewing the unsandboxed-execution warning: + + ```bash + start-issue 123 --batch \ + --batch-permissions full-delivery + ``` + +3. Codex can now implement and test the issue, commit the result, push the + issue branch, and create or update its pull request when the task and + repository state allow it. + +4. A final `STATUS: DONE` exits successfully. A final + `STATUS: HUMAN_GATE` opens the exact saved Codex thread for the operator. + +The CLI keeps diagnostic state under: + +```text +/.start-issue/runs//events.jsonl +/.start-issue/runs//last-message.txt +/.start-issue/runs//thread-id +``` + +## One-command environment override + +The environment variable is useful for one command or a controlled automation +wrapper: + +```bash +START_ISSUE_BATCH_PERMISSIONS=full-delivery \ + start-issue 123 --batch +``` + +The CLI option has higher precedence than the environment variable. Avoid +putting `full-delivery` in a global shell profile: keeping the opt-in visible at +the command site makes the unsandboxed boundary easier to review. + +## Troubleshooting + +- If `restricted` cannot access GitHub or write Git metadata, finish delivery + manually or rerun with an explicitly reviewed `full-delivery` selection. +- If full delivery cannot push or create a PR, recheck `gh auth status`, the + selected account, `origin`, and `viewerPermission`. +- If status parsing fails, inspect `events.jsonl` and `last-message.txt` in the + printed state directory. +- If automatic resume fails, use the saved thread id: + + ```bash + codex resume --include-non-interactive + ``` + +For the built-in reference, run: + +```bash +start-issue --batch-help +``` diff --git a/doc/batch-mode.ru.md b/doc/batch-mode.ru.md new file mode 100644 index 0000000..f80067f --- /dev/null +++ b/doc/batch-mode.ru.md @@ -0,0 +1,215 @@ +# Batch mode: автономная работа Codex с остановкой для человека + +Эта инструкция написана для человека, который впервые видит флаг +`start-issue --batch` и хочет понять, что он делает. Здесь объясняется, чем +batch mode отличается от обычного запуска, зачем Codex может остановиться со +статусом `HUMAN_GATE`, как после этого продолжить ту же session и какие права +нужны агенту для работы с кодом или полной доставки pull request. + +В примерах предполагается, что `codex` уже выбран в project или user config. +Ранее выпущенный флаг `--human-gate` продолжает работать как совместимый alias +для `--batch`. Полное техническое описание находится в [spec.md](spec.md). + +## Что такое human-gate + +Human-gate — это контрольная точка, в которой автономная работа Codex либо +считается завершённой, либо передаётся человеку для принятия решения. + +При обычном запуске `start-issue` открывает интерактивную Codex session, и +пользователь остаётся внутри неё. С флагом `--batch` Codex запускается в +batch mode и самостоятельно работает над issue. Пользователю не нужно следить +за каждым шагом в интерактивном UI. + +В конце Codex обязан явно сообщить один из двух результатов: + +- `STATUS: DONE` — задача выполнена и вмешательство человека не требуется; +- `STATUS: HUMAN_GATE` — продолжать без решения человека нельзя. + +Human-gate — это не запрос подтверждения перед каждой командой и не название +sandbox. Это договорённость о том, как автономный запуск завершается и когда +управление возвращается человеку. + +## Зачем он нужен + +Без human-gate трудно надёжно отличить завершённую задачу от запуска, который +остановился из-за вопроса, ошибки или нехватки доступа. Одного exit code Codex +для этого недостаточно. + +Human-gate позволяет: + +- отдать Codex реализацию issue и выполнение тестов без постоянного наблюдения; +- получить однозначный результат `DONE`, когда работа действительно закончена; +- остановиться на решении, которое агент не должен принимать самостоятельно; +- продолжить тот же Codex thread со всей историей, а не объяснять задачу новому + агенту заново. + +## Как он работает + +1. `start-issue` получает issue, создаёт или переиспользует worktree и готовит + prompt так же, как при обычном запуске. +2. Вместо интерактивной session запускается `codex exec` в batch mode. +3. `start-issue` сохраняет события запуска, финальное сообщение и `thread_id`. +4. Codex выполняет задачу и заканчивает ответ ровно одной status line: + `STATUS: DONE` или `STATUS: HUMAN_GATE`. +5. При `DONE` команда завершается с кодом `0`. +6. При `HUMAN_GATE` команда открывает сохранённую session через + `codex resume --include-non-interactive `. Пользователь видит всю + историю, отвечает на вопрос и продолжает работу в том же thread. +7. Если Codex завершился с ошибкой или не вернул понятный status, + `start-issue` сообщает об ошибке и оставляет диагностические файлы. + +Human-gate срабатывает не по ключевым словам в issue и не после каждой команды. +Решение остановиться принимает Codex по правилам prompt, а `start-issue` читает +status line из его финального сообщения и выполняет нужный переход. + +## Когда Codex должен остановиться + +`STATUS: HUMAN_GATE` нужен, когда без человека нельзя безопасно выбрать +следующий шаг. Например: + +- требуется destructive operation, удаление данных или переписывание Git + history; +- отсутствуют credentials или доступ, который должен предоставить человек; +- есть несколько несовместимых product-вариантов без зафиксированного решения; +- тесты или конфликт нельзя безопасно исправить в рамках issue; +- действие затрагивает production или security и требует отдельного согласия. + +Обычные технические вопросы, исправимые ошибки и выбор реализации внутри +согласованного scope не должны останавливать run: Codex должен решить их сам. + +## Как permission mode связан с human-gate + +Это две разные настройки: + +- `--batch` включает автономный batch run и протокол + `DONE` / `HUMAN_GATE`; +- `--batch-permissions` определяет, какие технические действия доступны + Codex во время этого run. + +Разделение нужно потому, что правка кода и доставка PR требуют разных прав. +Sandbox обычно позволяет менять и тестировать файлы worktree, но может не дать +доступ к GitHub, записи Git metadata, push или созданию PR. Поэтому безопасный +режим остаётся default, а полный доступ для end-to-end доставки включается +явно. + +## Выбор режима + +Для обычной реализации в worktree используйте `restricted`: + +```bash +start-issue 123 --batch +``` + +`restricted` — режим по умолчанию. Codex работает в sandbox +`workspace-write`: он может изменять и тестировать файлы подготовленного +worktree, но network access, запись Git metadata, push и доставка PR не +гарантированы. + +Если run должен также читать GitHub context, делать commit и push, создавать +или обновлять pull request, явно включите `full-delivery`: + +```bash +start-issue 123 --batch \ + --batch-permissions full-delivery +``` + +Это явное согласие на запуск Codex без sandbox и approvals. Режим даёт +техническую возможность, но не разрешает destructive Git operations, +production/security changes или нерешённые product decisions. В таких случаях +Codex по-прежнему обязан вернуть `STATUS: HUMAN_GATE` и передать решение +оператору. + +## Проверка перед full delivery + +Проверьте выбранный GitHub account, remote и права на репозиторий: + +```bash +gh auth status +git remote get-url origin +gh repo view --json nameWithOwner,viewerPermission +``` + +Сначала выполните dry-run: он покажет выбранный режим и launch command, но не +создаст worktree и не запустит Codex. + +```bash +start-issue 123 --batch \ + --batch-permissions full-delivery \ + --dry-run +``` + +В output должна быть строка: + +```text +Batch permissions: full-delivery (CLI) +``` + +## Пример: реализовать issue и доставить PR + +Предположим, что issue `123` находится в текущем репозитории, а выбранный +GitHub account имеет write access. + +1. Просмотрите план запуска: + + ```bash + start-issue 123 --batch \ + --batch-permissions full-delivery \ + --dry-run + ``` + +2. После проверки warning об unsandboxed execution запустите реальный run: + + ```bash + start-issue 123 --batch \ + --batch-permissions full-delivery + ``` + +3. Codex сможет реализовать и протестировать issue, сделать commit, push + issue branch и создать или обновить pull request, если это допускают задача + и состояние репозитория. + +4. Финальный `STATUS: DONE` успешно завершит команду. Финальный + `STATUS: HUMAN_GATE` откроет оператору точный сохранённый Codex thread. + +CLI сохраняет диагностические данные в: + +```text +/.start-issue/runs//events.jsonl +/.start-issue/runs//last-message.txt +/.start-issue/runs//thread-id +``` + +## Одноразовое переключение через environment + +Environment variable удобно использовать для одной команды или в +контролируемой automation wrapper: + +```bash +START_ISSUE_BATCH_PERMISSIONS=full-delivery \ + start-issue 123 --batch +``` + +CLI option имеет приоритет над environment variable. Не стоит добавлять +`full-delivery` в глобальный shell profile: видимый opt-in рядом с командой +упрощает проверку границы unsandboxed execution. + +## Диагностика + +- Если `restricted` не может обратиться к GitHub или записать Git metadata, + завершите доставку вручную либо повторите run с явно проверенным + `full-delivery`. +- Если full delivery не может сделать push или создать PR, повторно проверьте + `gh auth status`, выбранный account, `origin` и `viewerPermission`. +- При ошибке status parsing проверьте `events.jsonl` и `last-message.txt` в + напечатанном state directory. +- Если automatic resume не сработал, используйте сохранённый thread id: + + ```bash + codex resume --include-non-interactive + ``` + +Встроенная справка: + +```bash +start-issue --batch-help +``` diff --git a/doc/human-gate-permissions.md b/doc/human-gate-permissions.md deleted file mode 100644 index 060bc96..0000000 --- a/doc/human-gate-permissions.md +++ /dev/null @@ -1,150 +0,0 @@ -# Codex human-gate: purpose and permission modes - -This guide explains how to choose the capability boundary for a Codex -human-gate run. For the complete CLI contract, see [spec.md](spec.md). - -The examples assume that `codex` is already selected by project or user -configuration. - -## Why use human-gate - -A normal `start-issue` run hands control to an interactive agent session. -Human-gate instead runs Codex in batch mode so it can work through the issue -without the operator staying in the interactive UI. Codex then ends in one of -two explicit states: - -- `STATUS: DONE` — the requested work is complete; -- `STATUS: HUMAN_GATE` — a real decision or missing permission requires the - operator, so `start-issue` resumes the exact saved Codex thread. - -This is useful for semi-autonomous issue work: implementation and tests can run -unattended, while destructive actions, missing credentials, and product -decisions still stop at a visible human gate. - -Permission modes are needed because editing a worktree and delivering a pull -request cross different trust boundaries. A sandboxed Codex run can usually -change and test code, but may be unable to access GitHub, write Git metadata, -push, or create a PR. Giving every run unrestricted access would solve that -technical limitation by silently broadening privileges. `start-issue` therefore -keeps the safe boundary by default and requires a visible opt-in when the agent -must deliver the change end to end. - -## Choose a mode - -Use `restricted` for normal working-tree implementation: - -```bash -start-issue 123 --human-gate -``` - -`restricted` is the default. Codex runs with the `workspace-write` sandbox. It -can edit and test files in the prepared worktree, but network access, Git -metadata writes, push, and pull-request delivery are not guaranteed. - -Use `full-delivery` only when the run must also read GitHub context, commit, -push, and create or update a pull request: - -```bash -start-issue 123 --human-gate \ - --human-gate-permissions full-delivery -``` - -This is an explicit opt-in to unsandboxed Codex execution. It grants technical -capability, not permission for destructive Git operations, production or -security changes, or unresolved product decisions. Codex must still return -`STATUS: HUMAN_GATE` when one of those decisions requires the operator. - -## Full-delivery preflight - -Before using `full-delivery`, check the selected GitHub account, remote, and -repository access: - -```bash -gh auth status -git remote get-url origin -gh repo view --json nameWithOwner,viewerPermission -``` - -Run a dry-run to verify the resolved mode and launch command without creating -a worktree or starting Codex: - -```bash -start-issue 123 --human-gate \ - --human-gate-permissions full-delivery \ - --dry-run -``` - -The output should report: - -```text -Human-gate permissions: full-delivery (CLI) -``` - -## Example: implement an issue and deliver its PR - -Assume issue `123` is in the current repository and the authenticated GitHub -account has write access. - -1. Inspect the planned run: - - ```bash - start-issue 123 --human-gate \ - --human-gate-permissions full-delivery \ - --dry-run - ``` - -2. Start the real run after reviewing the unsandboxed-execution warning: - - ```bash - start-issue 123 --human-gate \ - --human-gate-permissions full-delivery - ``` - -3. Codex can now implement and test the issue, commit the result, push the - issue branch, and create or update its pull request when the task and - repository state allow it. - -4. A final `STATUS: DONE` exits successfully. A final - `STATUS: HUMAN_GATE` opens the exact saved Codex thread for the operator. - -The CLI keeps diagnostic state under: - -```text -/.start-issue/runs//events.jsonl -/.start-issue/runs//last-message.txt -/.start-issue/runs//thread-id -``` - -## One-command environment override - -The environment variable is useful for one command or a controlled automation -wrapper: - -```bash -START_ISSUE_HUMAN_GATE_PERMISSIONS=full-delivery \ - start-issue 123 --human-gate -``` - -The CLI option has higher precedence than the environment variable. Avoid -putting `full-delivery` in a global shell profile: keeping the opt-in visible at -the command site makes the unsandboxed boundary easier to review. - -## Troubleshooting - -- If `restricted` cannot access GitHub or write Git metadata, finish delivery - manually or rerun with an explicitly reviewed `full-delivery` selection. -- If full delivery cannot push or create a PR, recheck `gh auth status`, the - selected account, `origin`, and `viewerPermission`. -- If status parsing fails, inspect `events.jsonl` and `last-message.txt` in the - printed state directory. -- If automatic resume fails, use the saved thread id: - - ```bash - codex resume --include-non-interactive - ``` - -For the built-in reference, run: - -```bash -start-issue --human-gate-help -``` diff --git a/doc/human-gate-permissions.ru.md b/doc/human-gate-permissions.ru.md deleted file mode 100644 index 5db2b56..0000000 --- a/doc/human-gate-permissions.ru.md +++ /dev/null @@ -1,151 +0,0 @@ -# Codex human-gate: зачем он нужен и как выбрать права - -Эта инструкция объясняет, как выбрать границу возможностей для Codex -human-gate run. Полный контракт CLI описан в [spec.md](spec.md). - -В примерах предполагается, что `codex` уже выбран в project или user config. - -## Зачем нужен human-gate - -Обычный запуск `start-issue` передаёт управление интерактивной agent session. -Human-gate вместо этого запускает Codex в batch mode: агент может выполнить -issue без постоянного присутствия оператора в интерактивном UI, а в конце -обязан вернуть одно из двух явных состояний: - -- `STATUS: DONE` — запрошенная работа завершена; -- `STATUS: HUMAN_GATE` — требуется реальное решение или недостающее разрешение, - поэтому `start-issue` открывает оператору точный сохранённый Codex thread. - -Такой режим нужен для полуавтономной работы над issue: реализация и тесты могут -выполняться без участия человека, но destructive actions, missing credentials -и product decisions по-прежнему останавливаются на видимом human gate. - -Отдельные permission modes нужны потому, что изменение worktree и доставка PR -пересекают разные границы доверия. Codex в sandbox обычно может изменить и -проверить код, но может не получить доступ к GitHub, записи Git metadata, push -или созданию PR. Если сразу дать каждому run полный доступ, техническая проблема -исчезнет ценой незаметного расширения привилегий. Поэтому `start-issue` -сохраняет безопасную границу по умолчанию и требует явный opt-in, когда агент -должен самостоятельно доставить изменение до PR. - -## Выбор режима - -Для обычной реализации в worktree используйте `restricted`: - -```bash -start-issue 123 --human-gate -``` - -`restricted` — режим по умолчанию. Codex работает в sandbox -`workspace-write`: он может изменять и тестировать файлы подготовленного -worktree, но network access, запись Git metadata, push и доставка PR не -гарантированы. - -Если run должен также читать GitHub context, делать commit и push, создавать -или обновлять pull request, явно включите `full-delivery`: - -```bash -start-issue 123 --human-gate \ - --human-gate-permissions full-delivery -``` - -Это явное согласие на запуск Codex без sandbox и approvals. Режим даёт -техническую возможность, но не разрешает destructive Git operations, -production/security changes или нерешённые product decisions. В таких случаях -Codex по-прежнему обязан вернуть `STATUS: HUMAN_GATE` и передать решение -оператору. - -## Проверка перед full delivery - -Проверьте выбранный GitHub account, remote и права на репозиторий: - -```bash -gh auth status -git remote get-url origin -gh repo view --json nameWithOwner,viewerPermission -``` - -Сначала выполните dry-run: он покажет выбранный режим и launch command, но не -создаст worktree и не запустит Codex. - -```bash -start-issue 123 --human-gate \ - --human-gate-permissions full-delivery \ - --dry-run -``` - -В output должна быть строка: - -```text -Human-gate permissions: full-delivery (CLI) -``` - -## Пример: реализовать issue и доставить PR - -Предположим, что issue `123` находится в текущем репозитории, а выбранный -GitHub account имеет write access. - -1. Просмотрите план запуска: - - ```bash - start-issue 123 --human-gate \ - --human-gate-permissions full-delivery \ - --dry-run - ``` - -2. После проверки warning об unsandboxed execution запустите реальный run: - - ```bash - start-issue 123 --human-gate \ - --human-gate-permissions full-delivery - ``` - -3. Codex сможет реализовать и протестировать issue, сделать commit, push - issue branch и создать или обновить pull request, если это допускают задача - и состояние репозитория. - -4. Финальный `STATUS: DONE` успешно завершит команду. Финальный - `STATUS: HUMAN_GATE` откроет оператору точный сохранённый Codex thread. - -CLI сохраняет диагностические данные в: - -```text -/.start-issue/runs//events.jsonl -/.start-issue/runs//last-message.txt -/.start-issue/runs//thread-id -``` - -## Одноразовое переключение через environment - -Environment variable удобно использовать для одной команды или в -контролируемой automation wrapper: - -```bash -START_ISSUE_HUMAN_GATE_PERMISSIONS=full-delivery \ - start-issue 123 --human-gate -``` - -CLI option имеет приоритет над environment variable. Не стоит добавлять -`full-delivery` в глобальный shell profile: видимый opt-in рядом с командой -упрощает проверку границы unsandboxed execution. - -## Диагностика - -- Если `restricted` не может обратиться к GitHub или записать Git metadata, - завершите доставку вручную либо повторите run с явно проверенным - `full-delivery`. -- Если full delivery не может сделать push или создать PR, повторно проверьте - `gh auth status`, выбранный account, `origin` и `viewerPermission`. -- При ошибке status parsing проверьте `events.jsonl` и `last-message.txt` в - напечатанном state directory. -- Если automatic resume не сработал, используйте сохранённый thread id: - - ```bash - codex resume --include-non-interactive - ``` - -Встроенная справка: - -```bash -start-issue --human-gate-help -``` diff --git a/doc/spec.md b/doc/spec.md index 41cd5dd..55ceb34 100644 --- a/doc/spec.md +++ b/doc/spec.md @@ -71,9 +71,11 @@ Agent-specific behavior должен быть централизован за е | `--prompt` | Inline prompt template | См. приоритет prompt | | `--prompt-file` | Файл prompt template | См. приоритет prompt | | `--improve-prompt` | Сгенерировать reviewable proposal улучшенного prompt template и выйти до создания worktree | false | -| `--human-gate` | Codex-only batch mode для issue workflow с resume на `STATUS: HUMAN_GATE` | false | -| `--human-gate-permissions ` | Capability contract для human-gate; требует `--human-gate`; CLI имеет приоритет над `START_ISSUE_HUMAN_GATE_PERMISSIONS` | `restricted` | -| `--human-gate-help` | Показать отдельную справку по human-gate mode | false | +| `--batch` | Codex-only batch mode для issue workflow с resume на `STATUS: HUMAN_GATE` | false | +| `--human-gate` | Совместимый alias для `--batch` | false | +| `--batch-permissions ` | Capability contract для batch mode; требует `--batch` или его legacy alias; CLI имеет приоритет над `START_ISSUE_BATCH_PERMISSIONS` | `restricted` | +| `--batch-help` | Показать отдельную справку по batch mode и `HUMAN_GATE` handoff | false | +| `--human-gate-help` | Совместимый alias для `--batch-help` | false | | `--prompt-output-file` | Путь proposal-файла для `--improve-prompt` | Для `.md`: рядом с source как `*.improved.md`; для остальных файлов: `.improved`; иначе `.start-issue/prompt.improved.md` | | `--no-init` | Пропустить запуск `init.sh` | false | | `--command` / `-c` | Совместимый Claude command для дефолтного Claude prompt | `/task-router:route-task` | @@ -195,16 +197,18 @@ git rev-parse --show-toplevel - `gh` CLI с авторизованной GitHub session -## Codex human-gate mode +## Codex batch mode -`--human-gate` вводит отдельный Codex-only launch path для issue workflow. +`--batch` вводит отдельный Codex-only launch path для issue workflow. +Ранее выпущенный `--human-gate` является совместимым alias и включает тот же +внутренний mode без отдельной ветки поведения. Контракт режима: 1. Режим валиден только для `agent=codex`; для остальных agent он завершается явной ошибкой. 2. До agent launch workflow остается обычным: parse input, resolve config, fetch issue, plan branch, create/reuse worktree, run optional `init.sh`, render prompt. 3. Permission mode разрешается в порядке CLI - `--human-gate-permissions`, `START_ISSUE_HUMAN_GATE_PERMISSIONS`, built-in + `--batch-permissions`, `START_ISSUE_BATCH_PERMISSIONS`, built-in `restricted`. Другие значения отклоняются до issue fetch и worktree mutation. 4. В restricted mode вместо интерактивного Codex launch выполняется: @@ -252,13 +256,15 @@ codex resume --include-non-interactive "$thread_id" Dedicated help доступен через: ```bash -start-issue --human-gate-help +start-issue --batch-help ``` +`start-issue --human-gate-help` сохраняется как совместимый alias. + Практические инструкции и пример полного запуска: -- [English](human-gate-permissions.md) -- [Русский](human-gate-permissions.ru.md) +- [English](batch-mode.md) +- [Русский](batch-mode.ru.md) Там документируются: @@ -390,7 +396,7 @@ Templating правила: 11. Если включен `--improve-prompt`, сгенерировать proposal улучшенного prompt template и завершить workflow до worktree/agent launch. 12. Если это ordinary non-setup launch и `~/.config/start-issue` отсутствует, выполнить first-run onboarding gate перед оставшимся workflow. -Если включен `--human-gate`: +Если включен `--batch`: 1. После `render_prompt_template` проверить, что resolved agent равен `codex`. 2. Создать `STATE_DIR=/.start-issue/runs/`. @@ -591,7 +597,7 @@ start-issue 123 --repo owner/repo start-issue 123 --base develop start-issue 123 --agent codex start-issue 123 --agent codex --model gpt-5.2 -start-issue 123 --agent codex --human-gate +start-issue 123 --batch start-issue 123 --agent claude --model sonnet start-issue 123 --agent kimi --prompt-file .start-issue/prompt.md start-issue 123 --agent codex --improve-prompt @@ -608,7 +614,7 @@ start-issue init --user --force START_ISSUE_AGENT=codex start-issue 123 START_ISSUE_MODEL=sonnet start-issue 123 START_ISSUE_WORKTREE_DIR=~/projects/worktrees start-issue 123 -start-issue --human-gate-help +start-issue --batch-help ``` ## Зависимости @@ -630,7 +636,8 @@ start-issue --human-gate-help - [x] `start-issue 123` по умолчанию выбирает `claude`. - [x] `start-issue 123 --agent codex` создает worktree и запускает Codex в этом worktree. -- [x] `start-issue 123 --agent codex --human-gate` запускает Codex через `codex exec`, а не через обычный интерактивный launch. +- [x] `start-issue 123 --batch` запускает Codex через `codex exec`, а не через обычный интерактивный launch. +- [x] `start-issue 123 --human-gate` включает тот же batch path как совместимый alias. - [x] `start-issue 123 --agent kimi` запускает Kimi в этом worktree. - [x] `start-issue 123 --agent pi` запускает Pi в этом worktree. - [x] `start-issue 123 --no-agent` только готовит worktree и печатает следующие шаги. @@ -648,7 +655,8 @@ start-issue --human-gate-help - [x] `STATUS: DONE` завершает workflow без открытия Codex TUI. - [x] `STATUS: HUMAN_GATE` резюмирует ту же Codex session по explicit `thread_id`. - [x] Missing final status или missing `thread_id` завершаются явной ошибкой с указанием diagnostic artifact. -- [x] `start-issue --human-gate-help` документирует flow, prompt contract, exit codes и state files. +- [x] `start-issue --batch-help` документирует flow, prompt contract, exit codes и state files. +- [x] `start-issue --human-gate-help` сохраняет совместимость как alias для `--batch-help`. - [x] `--improve-prompt` создает reviewable proposal улучшенного prompt template и не перезаписывает active prompt. - [x] Claude-specific aliases сохранены, help text описывает agent-neutral поведение. - [x] `--dry-run` печатает selected agent, selected model, prompt source и launch command. diff --git a/memory-bank/README.md b/memory-bank/README.md index 6d4b3ff..ead8fc1 100644 --- a/memory-bank/README.md +++ b/memory-bank/README.md @@ -75,4 +75,4 @@ flows and templates remain upstream-derived process references. - [features/FT-016/README.md](features/FT-016/README.md) Opt-in real Codex human-gate E2E suite. - [features/FT-017/README.md](features/FT-017/README.md) - Codex human-gate delivery permission modes. + Codex batch delivery permission modes. diff --git a/memory-bank/domain/README.md b/memory-bank/domain/README.md index 5634e9c..d103ec2 100644 --- a/memory-bank/domain/README.md +++ b/memory-bank/domain/README.md @@ -14,7 +14,7 @@ audience: humans_and_agents `memory-bank/domain/` defines the stable language and workflow model for `start-issue`: issue input, repository context, configuration, prompt templates, -worktrees, agent adapters, release assets, human-gate runs, and feature +worktrees, agent adapters, release assets, batch runs, human gates, and feature packages. Domain documents do not own product positioning, shell implementation sequence, diff --git a/memory-bank/domain/context-map.md b/memory-bank/domain/context-map.md index 9f15025..ce74397 100644 --- a/memory-bank/domain/context-map.md +++ b/memory-bank/domain/context-map.md @@ -61,4 +61,4 @@ canonical_for: - `OQ-01` How future lifecycle commands should be decomposed into focused Go helpers without weakening the existing ownership boundaries. - `OQ-02` Whether non-Codex agents will expose enough resumable batch semantics - to generalize human-gate mode. + to generalize batch mode. diff --git a/memory-bank/domain/events.md b/memory-bank/domain/events.md index 392d385..b28fd2e 100644 --- a/memory-bank/domain/events.md +++ b/memory-bank/domain/events.md @@ -25,7 +25,7 @@ canonical_for: | `DE-03` | `WorktreeReady` | It is safe to run init/prompt/agent inside the target worktree | Worktree lifecycle | Pipeline, agent launch | branch name and path | | `DE-04` | `PromptProposalWritten` | A reviewable improved prompt proposal exists | Prompt improvement workflow | User/maintainer | output path and source prompt | | `DE-05` | `ReleaseUpdateInstalled` | Running executable has been replaced by verified release asset | Update workflow | CLI user | old version, new version, executable path | -| `DE-06` | `HumanGateReached` | Codex batch run requires a human decision | Human-gate workflow | CLI user, resume command | thread id, state directory, final message | +| `DE-06` | `HumanGateReached` | Codex batch run requires a human decision | Batch workflow | CLI user, resume command | thread id, state directory, final message | | `DE-07` | `MemoryBankAuditPassed` | Governed docs are reachable and links are valid | `check_memory_bank_index.py` | Maintainer/agent | scope, entrypoint, max depth | ## Event Rules @@ -39,5 +39,5 @@ canonical_for: This project does not currently publish runtime domain events. The events above are conceptual workflow facts used for documentation, tests, and feature design. -For Codex human-gate, JSONL `thread.started` is an external technical event +For Codex batch mode, JSONL `thread.started` is an external technical event observed by the workflow, while `HumanGateReached` is the local workflow verdict. diff --git a/memory-bank/domain/glossary.md b/memory-bank/domain/glossary.md index f7dae8c..e946754 100644 --- a/memory-bank/domain/glossary.md +++ b/memory-bank/domain/glossary.md @@ -39,7 +39,8 @@ canonical_for: | `prompt proposal` | Reviewable improved prompt file written by `--improve-prompt` | Prompt improvement | Active prompt template | | `release asset` | Built single-file `start-issue` binary uploaded to GitHub Releases | Install and update | Source modules | | `running executable` | The exact `start-issue` path invoked by the user | Self-update | Repository source script | -| `human-gate run` | Codex batch execution with persisted state and final status parsing | `--human-gate` | Normal interactive Codex launch | +| `batch run` | Autonomous Codex execution with persisted state and final status parsing | `--batch`; legacy alias `--human-gate` | Normal interactive Codex launch | +| `human gate` | Terminal batch outcome that resumes the saved Codex thread for a human decision | `STATUS: HUMAN_GATE` | The batch mode itself or a permission mode | | `memory-bank` | Governed project documentation and process layer adapted from `dapi/memory-bank` | Agent/project context | Runtime state under `.start-issue` | ## Naming Rules @@ -50,8 +51,8 @@ canonical_for: config initialization. - Use `worktree` for git worktrees only; use `workspace` only in prose when the branch/worktree/agent setup is meant together. -- Use `human-gate` only for the Codex batch/resume workflow currently grounded - in `codex exec` and `codex resume`. +- Use `batch` for the Codex execution mode grounded in `codex exec`; use + `human gate` only for the `STATUS: HUMAN_GATE` outcome and saved-thread resume. ## Ambiguous Terms diff --git a/memory-bank/domain/model.md b/memory-bank/domain/model.md index 34f5109..1f138fa 100644 --- a/memory-bank/domain/model.md +++ b/memory-bank/domain/model.md @@ -30,7 +30,7 @@ canonical_for: | `AgentAdapter` | policy/boundary | Agent-specific validation and command construction | Uses `Configuration`, `RenderedPrompt`, worktree path | Owns Codex/Claude/Kimi/Pi differences | | `ReleaseMetadata` | value object | Latest release tag and asset URLs | Used by self-update | Comes from GitHub Releases | | `InstalledExecutable` | entity | Running binary path and current version | Updated in place by self-update | Not necessarily repo checkout | -| `HumanGateRun` | entity | Codex batch state directory, events, last message, thread id | Belongs to one worktree run | Final status comes from `last-message.txt` | +| `BatchRun` | entity | Codex batch state directory, events, last message, thread id | Belongs to one worktree run | Final status comes from `last-message.txt`; `HUMAN_GATE` is one outcome | | `FeaturePackage` | documentation aggregate | Governed docs for one delivery unit | Uses memory-bank flows and stable IDs | Existing packages may use legacy layout | ## Relationship Map diff --git a/memory-bank/domain/rules.md b/memory-bank/domain/rules.md index dda387d..5006ddd 100644 --- a/memory-bank/domain/rules.md +++ b/memory-bank/domain/rules.md @@ -26,7 +26,7 @@ canonical_for: | `DR-04` | Existing worktree reuse requires exact branch/path validation before side effects | `WorktreePlan` | Avoid corrupting unrelated local work | FT-008 | | `DR-05` | `setup` writes only user config, while project config belongs to `init --project` | `setup`, `init` | Keep onboarding safe outside git repos | FT-014 | | `DR-06` | `update` uses latest GitHub Release and updates the running executable path | `InstalledExecutable` | Avoid updating the wrong binary | FT-013 | -| `DR-07` | `--human-gate` is Codex-only until another adapter has an equivalent grounded resume contract | `HumanGateRun` | Avoid false support claims | FT-015 | +| `DR-07` | `--batch` is Codex-only until another adapter has an equivalent grounded resume contract; `--human-gate` is a compatibility alias | `BatchRun` | Avoid false support claims without breaking released commands | FT-015, FT-017 | | `DR-08` | Agent-specific behavior must stay behind the adapter boundary | `AgentAdapter` | Prevent scattered per-agent branching | FT-009 / spec | ## Policies diff --git a/memory-bank/domain/states.md b/memory-bank/domain/states.md index f454b46..c44c2a4 100644 --- a/memory-bank/domain/states.md +++ b/memory-bank/domain/states.md @@ -24,7 +24,7 @@ canonical_for: | `SM-02` | Worktree lifecycle | `worktree.sh` | Plan before side effects | | `SM-03` | Configuration setup | `init.sh` / `config.sh` | `setup` and `init` have distinct scopes | | `SM-04` | Self-update workflow | `update.sh` | Independent from git repo and issue workflow | -| `SM-05` | Codex human-gate run | `agent.sh` / `pipeline.sh` | Uses persisted run state | +| `SM-05` | Codex batch run | Go launcher | Uses persisted run state and may end at a human gate | ## States @@ -50,7 +50,7 @@ canonical_for: | `TR-02` | `config_resolved` | `issue_fetched` | Issue workflow continues | Issue input is present and dependencies are available | Mode is `setup`, `init`, or `update` | | `TR-03` | `issue_fetched` | `worktree_planned` | Worktree planner runs | Branch name can be generated | Branch/path state is unreadable | | `TR-04` | `worktree_planned` | `worktree_ready` | User or planner selects create/reuse/delete path | Path validation passes | Existing path belongs to another branch | -| `TR-05` | `worktree_ready` | `agent_launched` | Agent launch | Agent is supported and not `none` | `--human-gate` with non-Codex | +| `TR-05` | `worktree_ready` | `agent_launched` | Agent launch | Agent is supported and not `none` | `--batch` with non-Codex | | `TR-06` | `worktree_ready` | `manual_next_steps` | No-agent mode | Agent is `none` | none | | `TR-07` | `config_resolved` | `update_noop` / `update_installed` | Update mode | Release metadata and running executable version known | Checksum/download/install fails | | `TR-08` | `worktree_ready` | `human_gate_done` / `human_gate_resume` | Codex batch final status parsed | Agent is Codex and thread id/status are valid | Missing or unknown final status | @@ -60,7 +60,7 @@ canonical_for: - `SI-01` Side effects that assume a valid worktree cannot run before `worktree_ready`. - `SI-02` Update states do not require git repository context. -- `SI-03` Human-gate terminal verdict comes only from saved `last-message.txt`. +- `SI-03` Batch terminal verdict comes only from saved `last-message.txt`. ## Implementation Notes diff --git a/memory-bank/engineering/architecture.md b/memory-bank/engineering/architecture.md index 11a0740..197f76a 100644 --- a/memory-bank/engineering/architecture.md +++ b/memory-bank/engineering/architecture.md @@ -36,7 +36,7 @@ Agent-specific behavior must stay centralized in Go adapter helpers: - model argument handling; - AI branch-name helper commands; - prompt-improvement helper commands; -- Codex-specific human-gate batch/resume command construction. +- Codex-specific batch/resume command construction. Do not add ad hoc `case "$AGENT"` branches in unrelated modules unless the change is only routing to the adapter boundary. @@ -58,10 +58,10 @@ The owner is the Go configuration layer; user-facing descriptions must stay alig ## Failure Handling - Fail fast for invalid user intent: unknown agents, prompt source conflicts, - unsupported human-gate agent, missing required update dependencies. + unsupported batch agent, missing required update dependencies. - Treat optional integrations as warnings when documented as optional: `zellij-tab-status` and non-zero `init.sh` do not abort the normal workflow. -- Preserve diagnostic artifacts for Codex human-gate failures under the run state +- Preserve diagnostic artifacts for Codex batch failures under the run state directory. - Never continue after a worktree safety validation failure. diff --git a/memory-bank/engineering/autonomy-boundaries.md b/memory-bank/engineering/autonomy-boundaries.md index 01dee16..28f979b 100644 --- a/memory-bank/engineering/autonomy-boundaries.md +++ b/memory-bank/engineering/autonomy-boundaries.md @@ -46,7 +46,7 @@ Stop and ask before: - deleting user worktrees or local branches outside an explicit tested workflow; - changing release tags, publishing releases, or pushing to `master`; - adding a new runtime language/core rewrite; -- generalizing Codex human-gate behavior to other agents without evidence; +- generalizing Codex batch behavior to other agents without evidence; - changing security/sandbox approval defaults for agent launch; - resolving contradictory product requirements by guessing. diff --git a/memory-bank/engineering/frontend.md b/memory-bank/engineering/frontend.md index 019adec..4128dcb 100644 --- a/memory-bank/engineering/frontend.md +++ b/memory-bank/engineering/frontend.md @@ -21,7 +21,7 @@ the CLI: flags, prompts, help text, dry-run output, and agent launch commands. - `--dry-run` output. - `setup` and first-run onboarding prompts. - `init` prompts and planned writes. -- `--human-gate-help`. +- `--batch-help` (`--human-gate-help` remains a compatibility alias). - Release/update status messages. ## CLI UX Rules @@ -29,7 +29,7 @@ the CLI: flags, prompts, help text, dry-run output, and agent launch commands. - Keep output explicit about effective config and sources. - Do not hide destructive choices behind defaults. - Keep normal help concise; use dedicated help for complex modes such as - `--human-gate-help`. + `--batch-help`. - When adding interactive prompts, support non-interactive test coverage through Go test input simulation. diff --git a/memory-bank/features/FT-017/README.md b/memory-bank/features/FT-017/README.md index 6f02d52..24f904b 100644 --- a/memory-bank/features/FT-017/README.md +++ b/memory-bank/features/FT-017/README.md @@ -1,8 +1,8 @@ --- -title: "FT-017: Codex human-gate delivery permissions" +title: "FT-017: Codex batch delivery permissions" doc_kind: feature doc_function: index -purpose: "Navigation for the Codex human-gate permission and full-delivery contract feature package." +purpose: "Navigation for the Codex batch permission and full-delivery contract feature package." derived_from: - ../../dna/governance.md - brief.md @@ -12,7 +12,7 @@ status: active audience: humans_and_agents --- -# FT-017: Codex human-gate delivery permissions +# FT-017: Codex batch delivery permissions ## About diff --git a/memory-bank/features/FT-017/brief.md b/memory-bank/features/FT-017/brief.md index 37f699f..4e3a43c 100644 --- a/memory-bank/features/FT-017/brief.md +++ b/memory-bank/features/FT-017/brief.md @@ -1,8 +1,8 @@ --- -title: "FT-017: Codex human-gate delivery permissions" +title: "FT-017: Codex batch delivery permissions" doc_kind: feature doc_function: canonical -purpose: "Canonical brief for making Codex human-gate capabilities explicit and supporting opt-in end-to-end Git delivery." +purpose: "Canonical brief for making Codex batch capabilities explicit and supporting opt-in end-to-end Git delivery." derived_from: - ../../flows/feature-flow.md - ../../product/context.md @@ -17,7 +17,7 @@ must_not_define: - solution_space --- -# FT-017: Codex human-gate delivery permissions +# FT-017: Codex batch delivery permissions ## What @@ -41,19 +41,19 @@ supported Codex CLI while closing the remaining capability-contract gap. | Metric ID | Metric | Baseline | Target | Measurement method | | --- | --- | --- | --- | --- | -| `MET-01` | Human-gate capability contract visibility | One implicit `workspace-write` command | Every run reports either restricted or full-delivery permissions | Dry-run/help assertions | +| `MET-01` | Batch capability contract visibility | One implicit `workspace-write` command | Every run reports either restricted or full-delivery permissions | Dry-run/help assertions | | `MET-02` | Full Git delivery reachability | GitHub/network/Git writes are not guaranteed | An explicitly authorized mode can edit, test, commit, push, and create/update a PR | Deterministic command tests plus opt-in live E2E evidence | | `MET-03` | Supported Codex command compatibility | Compatibility can drift with CLI option placement | Generated commands are accepted by the supported Codex CLI contract | Go command-shape tests and real-Codex smoke validation | ### Scope -- `REQ-01` Preserve a restricted human-gate mode for working-tree-only +- `REQ-01` Preserve a restricted batch mode for working-tree-only automation and make that restricted completion boundary explicit in output and help. - `REQ-02` Add an explicit opt-in full-delivery mode whose declared contract includes GitHub reads, network operations, Git metadata writes, push, and pull-request creation/update. -- `REQ-03` Resolve the human-gate permission mode from a documented CLI option, +- `REQ-03` Resolve the batch permission mode from a documented CLI option, environment variable, and safe built-in default, and show the winning value in dry-run output. - `REQ-04` Generate a Codex command compatible with the supported CLI syntax, @@ -64,16 +64,19 @@ supported Codex CLI while closing the remaining capability-contract gap. occurs before the batch run. - `REQ-06` Explain authentication, network access, Git write access, the risk of full-delivery mode, restricted-mode limitations, and troubleshooting in - dedicated human-gate help and project documentation. + dedicated batch help and project documentation. - `REQ-07` Add deterministic automated coverage for permission precedence, validation, command construction, dry-run visibility, default compatibility, and existing `DONE`/`HUMAN_GATE` behavior. - `REQ-08` Provide an explicit opt-in real-Codex E2E procedure for validating full delivery without adding live GitHub writes to `make test` or CI. +- `REQ-09` Present `--batch` and `--batch-help` as the primary public names + while preserving the released `--human-gate` and `--human-gate-help` forms + as behaviorally equivalent compatibility aliases. ### Non-Scope -- `NS-01` Do not generalize human-gate mode to Claude, Kimi, Pi, or `agent=none`. +- `NS-01` Do not generalize batch mode to Claude, Kimi, Pi, or `agent=none`. - `NS-02` Do not provision GitHub credentials, modify Codex user configuration, or store secrets in project files. - `NS-03` Do not authorize production changes, destructive Git operations, or @@ -124,6 +127,8 @@ supported Codex CLI while closing the remaining capability-contract gap. security risk, mode selection, and troubleshooting consistently. - `EC-06` An explicitly approved live E2E can demonstrate end-to-end Git delivery and preserve auditable artifacts without becoming a CI dependency. +- `EC-07` New users can discover the workflow as batch mode, while existing + `--human-gate` invocations continue to work without behavior changes. ### Traceability matrix @@ -137,13 +142,14 @@ supported Codex CLI while closing the remaining capability-contract gap. | `REQ-06` | `ASM-02`, `CON-03` | `EC-05`, `SC-05` | `CHK-02` | `EVID-02` | | `REQ-07` | `CON-01` | `EC-01` - `EC-04`, `SC-01` - `SC-04`, `NEG-01` | `CHK-01` | `EVID-01` | | `REQ-08` | `CON-04` | `EC-06`, `SC-06` | `CHK-03` | `EVID-03` | +| `REQ-09` | `PCON-01` | `EC-07`, `SC-07` | `CHK-01`, `CHK-02` | `EVID-01`, `EVID-02` | ### Acceptance Scenarios - `SC-01` Given no permission override, when a user inspects or runs - human-gate mode, then restricted mode is selected and its working-tree-only + batch mode, then restricted mode is selected and its working-tree-only completion boundary is visible. -- `SC-02` Given explicit full-delivery selection, when the human-gate command is +- `SC-02` Given explicit full-delivery selection, when the batch command is built, then the mode is visibly reported and the command permits the declared GitHub/network/Git delivery workflow. - `SC-03` Given the supported Codex CLI, when either permission mode builds the @@ -158,6 +164,9 @@ supported Codex CLI while closing the remaining capability-contract gap. - `SC-06` Given explicit operator authorization, isolated fixture resources, and valid credentials, when the full-delivery E2E runs, then it records successful commit/push/PR delivery and retained diagnostic artifacts. +- `SC-07` Given either primary `--batch` or legacy `--human-gate`, when the + same issue workflow runs, then command construction, state, status, resume, + and exit behavior are identical; help presents the legacy form as an alias. ### Negative / Edge Scenarios @@ -170,13 +179,16 @@ supported Codex CLI while closing the remaining capability-contract gap. - `NEG-03` Given full-delivery mode without valid GitHub credentials or remote authorization, when the live workflow reaches delivery, then it fails with a capability diagnostic and does not relabel the failure as a product decision. +- `NEG-04` Given `--batch-permissions` without either batch entrypoint, when + arguments are parsed, then the CLI rejects the option before issue fetch or + worktree mutation and names `--batch` as the required primary mode. ### Checks | Check ID | Covers | How to check | Expected result | Evidence path | | --- | --- | --- | --- | --- | -| `CHK-01` | `EC-01` - `EC-04`, `SC-01` - `SC-04`, `NEG-01` | `make test` | Go formatting/vet/tests, memory-bank audit, and deterministic human-gate coverage pass for both modes and FT-015 regressions. | Local terminal/CI test output | -| `CHK-02` | `EC-01`, `EC-05`, `SC-01`, `SC-05`, `NEG-02` | Review `--help`, `--human-gate-help`, README files, practical guides, and spec alongside Go output assertions | All surfaces state the same default, opt-in, capability, risk, and troubleshooting contract. | Review diff and Go test output | +| `CHK-01` | `EC-01` - `EC-04`, `EC-07`, `SC-01` - `SC-04`, `SC-07`, `NEG-01`, `NEG-04` | `make test` | Go formatting/vet/tests, memory-bank audit, and deterministic batch coverage pass for both permission modes, primary and legacy entrypoints, and FT-015 regressions. | Local terminal/CI test output | +| `CHK-02` | `EC-01`, `EC-05`, `EC-07`, `SC-01`, `SC-05`, `SC-07`, `NEG-02` | Review `--help`, `--batch-help`, README files, practical guides, and spec alongside Go output assertions | All surfaces present batch as primary, label legacy aliases, and state the same default, opt-in, capability, risk, and troubleshooting contract. | Review diff and Go test output | | `CHK-03` | `EC-02`, `EC-03`, `EC-06`, `SC-02`, `SC-03`, `SC-06`, `NEG-03` | With explicit approval, run the real-Codex full-delivery E2E procedure from FT-017's plan | Supported Codex accepts the command and the isolated fixture records commit, push, PR, terminal status, and retained artifacts. | Retained E2E artifact directory and fixture PR URL | ### Test matrix diff --git a/memory-bank/features/FT-017/decision-log.md b/memory-bank/features/FT-017/decision-log.md index 66bf206..08e3a86 100644 --- a/memory-bank/features/FT-017/decision-log.md +++ b/memory-bank/features/FT-017/decision-log.md @@ -89,7 +89,7 @@ production actions. `CHK-03` and `AG-01` remain mandatory before acceptance. **Status:** accepted by FPF review on 2026-08-04. The feature package must target the current Go implementation under -`cmd/start-issue/`, its Go tests, `test/e2e/human-gate.sh`, `Makefile`, and the +`cmd/start-issue/`, its Go tests, `test/e2e/batch.sh`, `Makefile`, and the README/spec documentation. The earlier references to `scripts/lib/start_issue` and Bats were stale artifacts from the pre-Go implementation and have been removed from the execution plan. diff --git a/memory-bank/features/FT-017/design.md b/memory-bank/features/FT-017/design.md index 9f97571..d156ee1 100644 --- a/memory-bank/features/FT-017/design.md +++ b/memory-bank/features/FT-017/design.md @@ -2,7 +2,7 @@ title: "FT-017: Design" doc_kind: feature doc_function: canonical -purpose: "Solution-space document for explicit restricted and full-delivery Codex human-gate permission modes." +purpose: "Solution-space document for explicit restricted and full-delivery Codex batch permission modes." derived_from: - brief.md - ../FT-015/solution.md @@ -65,7 +65,7 @@ enforces credentials and repository authorization independently. ## Selected Solution -- `SOL-01` Add one semantic configuration axis named human-gate permissions +- `SOL-01` Add one semantic configuration axis named batch permissions with exactly two values: `restricted` and `full-delivery`. Resolve it as CLI option → environment variable → built-in `restricted`. - `SOL-02` Keep `restricted` on the existing `--sandbox workspace-write` @@ -82,6 +82,9 @@ enforces credentials and repository authorization independently. - `SOL-06` Extend the opt-in real-Codex E2E runner with a separately authorized full-delivery scenario that uses isolated fixture resources and retains delivery evidence. +- `SOL-07` Expose the existing workflow primarily as `--batch` and + `--batch-help`; keep the released `--human-gate` and `--human-gate-help` + spellings as compatibility aliases for the same internal mode. ## Alternatives Considered @@ -105,8 +108,8 @@ enforces credentials and repository authorization independently. - `SD-01` Name the public values by user outcome (`restricted`, `full-delivery`) rather than Codex implementation names so help and future adapters can describe capability without leaking every low-level flag. -- `SD-02` Use `--human-gate-permissions VALUE` and - `START_ISSUE_HUMAN_GATE_PERMISSIONS` as the two explicit inputs. Project/user +- `SD-02` Use `--batch-permissions VALUE` and + `START_ISSUE_BATCH_PERMISSIONS` as the two explicit inputs. Project/user persistence is deferred; the dangerous mode must not become an unnoticed repository default in this feature. - `SD-03` A full-delivery selection is itself explicit authorization to launch @@ -114,16 +117,20 @@ enforces credentials and repository authorization independently. production actions excluded by `NS-03`. - `SD-04` Live full-delivery verification remains a manual approval gate and is never folded into `make test` or CI. +- `SD-05` Name the execution mode after what the user starts (`batch`), while + keeping `HUMAN_GATE` as one terminal outcome of that mode rather than the + mode's primary public name. ## Contracts | Contract ID | Input / Output | Producer / Consumer | Semantics / Constraints | | --- | --- | --- | --- | -| `CTR-01` | `--human-gate-permissions restricted\|full-delivery` | CLI parser / config resolver | CLI value wins over environment; it requires `--human-gate`; invalid or empty explicit values fail before issue fetch. | -| `CTR-02` | `START_ISSUE_HUMAN_GATE_PERMISSIONS` | shell environment / config resolver | Used only when CLI input is absent; unset resolves to `restricted`. | +| `CTR-01` | `--batch-permissions restricted\|full-delivery` | CLI parser / config resolver | CLI value wins over environment; it requires batch mode through `--batch` or its legacy alias; invalid or empty explicit values fail before issue fetch. | +| `CTR-02` | `START_ISSUE_BATCH_PERMISSIONS` | shell environment / config resolver | Used only when CLI input is absent; unset resolves to `restricted`. | | `CTR-03` | Restricted Codex command | launcher / Codex | `codex [--model MODEL] exec --cd WORKTREE --sandbox workspace-write --json --output-last-message PATH -`. | | `CTR-04` | Full-delivery Codex command | launcher / Codex | `codex [--model MODEL] --dangerously-bypass-approvals-and-sandbox exec --cd WORKTREE --json --output-last-message PATH -`; selected only by explicit `full-delivery`. | | `CTR-05` | Permission status output | launcher / operator | Reports semantic mode and capability boundary before execution and in dry-run; full delivery includes an unsandboxed-execution warning. | +| `CTR-06` | `--batch`, `--batch-help`; legacy `--human-gate`, `--human-gate-help` | CLI parser / operator | Batch names are primary in help/docs; each legacy form maps to exactly the same runtime or help behavior without a second implementation path. | ## Invariants @@ -134,6 +141,8 @@ enforces credentials and repository authorization independently. - `INV-04` Batch commands remain arrays and are never evaluated through `eval`. - `INV-05` FT-015's exact thread-id resume and state-artifact paths remain stable across permission modes. +- `INV-06` Primary and legacy batch entrypoints resolve to one internal boolean + mode and cannot diverge in command, state, resume, or exit semantics. ## Failure Modes @@ -148,6 +157,8 @@ enforces credentials and repository authorization independently. - `FM-04` A full-delivery prompt proposes destructive or production work: prompt policy still requires `STATUS: HUMAN_GATE`; permission mode does not broaden product authorization. +- `FM-05` A legacy alias diverges from `--batch`: shared parser-state tests fail + and the release is blocked until both entrypoints use the same internal path. ## Rollout / Backout @@ -175,3 +186,4 @@ contract changes. | `REQ-06` | `SOL-04`, `TRD-02`, `SD-03` | `CTR-05`, `INV-03` | `FM-02`, `FM-04`, `RB-02` | | `REQ-07` | `SOL-01` - `SOL-05` | `CTR-01` - `CTR-05`, `INV-01` - `INV-05` | `FM-01` - `FM-04`, `RB-01`, `RB-02` | | `REQ-08` | `SOL-06`, `SD-04` | `INV-03`, `INV-05` | `FM-02`, `RB-03` | +| `REQ-09` | `SOL-07`, `SD-05` | `CTR-06`, `INV-06` | `FM-05`, `RB-01` | diff --git a/memory-bank/features/FT-017/implementation-plan.md b/memory-bank/features/FT-017/implementation-plan.md index f458933..34717ec 100644 --- a/memory-bank/features/FT-017/implementation-plan.md +++ b/memory-bank/features/FT-017/implementation-plan.md @@ -2,7 +2,7 @@ title: "FT-017: Implementation Plan" doc_kind: feature doc_function: derived -purpose: "Execution plan for explicit restricted and full-delivery Codex human-gate permission modes." +purpose: "Execution plan for explicit restricted and full-delivery Codex batch permission modes." derived_from: - brief.md - design.md @@ -40,11 +40,11 @@ creates retained fixture GitHub state. | Path / module | Current role | Why relevant | Reuse / mirror | | --- | --- | --- | --- | -| `cmd/start-issue/main.go` | Go CLI parser, config resolution, launcher, help, and human-gate state | Owns the new option, command mapping, and diagnostics | Extend existing options and array-based `exec.Cmd` construction | -| `cmd/start-issue/main_test.go` | Deterministic Go regression suite | Existing tests cover human-gate command, DONE, HUMAN_GATE, and errors | Add precedence, invalid value, full-delivery, and order assertions | +| `cmd/start-issue/main.go` | Go CLI parser, config resolution, launcher, help, and batch state | Owns the primary names, compatibility aliases, permission mapping, and diagnostics | Extend existing options and array-based `exec.Cmd` construction | +| `cmd/start-issue/main_test.go` | Deterministic Go regression suite | Existing tests cover batch command, DONE, HUMAN_GATE, and errors | Add precedence, invalid value, full-delivery, and order assertions | | `cmd/start-issue/parity_integration_test.go` | Go/Bash observable parity coverage | Protects unaffected legacy behavior during the Go implementation | Keep non-human-gate parity cases green | -| `test/e2e/human-gate.sh` | Opt-in real-Codex smoke runner | Closest existing live verification surface | Add a separately guarded full-delivery scenario only after approval | -| `README.md`, `README.ru.md`, `doc/human-gate-permissions*.md`, `doc/spec.md` | Public guides and canonical behavior docs | Must match help and command behavior | Update together with output assertions | +| `test/e2e/batch.sh` | Opt-in real-Codex smoke runner | Closest existing live verification surface | Add a separately guarded full-delivery scenario only after approval | +| `README.md`, `README.ru.md`, `doc/batch-mode*.md`, `doc/spec.md` | Public guides and canonical behavior docs | Must match help and command behavior | Update together with output assertions | ## Test Strategy @@ -53,7 +53,7 @@ creates retained fixture GitHub state. | Permission resolution and validation | `REQ-03`, `SC-01`, `SC-02`, `NEG-01`, `CTR-01`, `CTR-02` | None | CLI beats env; env beats default; invalid value fails before fetch/mutation | `make test` | Existing test job | none | none | | Codex command construction | `REQ-01`, `REQ-02`, `REQ-04`, `SC-03`, `CTR-03`, `CTR-04`, `INV-02`, `INV-04` | Restricted dry-run and fake Codex execution | Assert semantic mode mapping, global-before-`exec` order, model coexistence, and no raw interpolation | `make test` | Existing test job | Real installed-Codex parser behavior is external | `AG-01` for live run | | FT-015 state/resume regression | `REQ-05`, `SC-04`, `INV-05` | DONE, HUMAN_GATE, missing status/thread | Run existing scenarios under default restricted and one full-delivery fake path | `make test` | Existing test job | none | none | -| Help/docs contract | `REQ-01`, `REQ-06`, `SC-05`, `NEG-02`, `CTR-05` | Dedicated human-gate help assertions | Assert default, full-delivery warning, prerequisites, limitations, and troubleshooting | `make test`; documentation review | Existing test job | Prose consistency review is manual | reviewer approval in PR | +| Help/docs contract | `REQ-01`, `REQ-06`, `REQ-09`, `SC-05`, `SC-07`, `NEG-02`, `CTR-05`, `CTR-06` | Dedicated batch help assertions | Assert primary and legacy names, default, full-delivery warning, prerequisites, limitations, and troubleshooting | `make test`; documentation review | Existing test job | Prose consistency review is manual | reviewer approval in PR | | End-to-end Git delivery | `REQ-02`, `REQ-08`, `SC-06`, `NEG-03`, `SOL-06`, `RB-03` | Real Codex terminal-state smoke only | Keep syntax/static coverage automated; add guarded scenario entrypoint | `make test` plus explicitly approved E2E | Excluded from CI | Requires real Codex, credentials, network, Git writes, push, and PR creation | `AG-01` | ## Open Questions / Ambiguities @@ -87,7 +87,7 @@ creates retained fixture GitHub state. | --- | --- | --- | --- | --- | | `WS-1` | `REQ-03`, `SOL-01`, `SD-02`, `CTR-01`, `CTR-02` | Validated permission resolution and early errors | agent | `PRE-01` | | `WS-2` | `REQ-01`, `REQ-02`, `REQ-04`, `SOL-02` - `SOL-05`, `CTR-03` - `CTR-05` | Correct commands and visible capability status | agent | `WS-1`, `PRE-02` | -| `WS-3` | `REQ-06`, `REQ-07`, `SC-01` - `SC-05` | Automated regression coverage and aligned docs | agent | `WS-1`, `WS-2` | +| `WS-3` | `REQ-06`, `REQ-07`, `REQ-09`, `SC-01` - `SC-05`, `SC-07` | Automated regression coverage, compatibility aliases, and aligned docs | agent | `WS-1`, `WS-2` | | `WS-4` | `REQ-08`, `SOL-06`, `SC-06`, `RB-03` | Guarded full-delivery E2E evidence | human + agent | `WS-2`, `WS-3`, `PRE-03`, `AG-01` | ## Approval Gates @@ -100,7 +100,7 @@ creates retained fixture GitHub state. | Step ID | Actor | Implements | Goal | Touchpoints | Artifact | Verifies | Evidence IDs | Check command / procedure | Blocked by | Needs approval | Escalate if | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| `STEP-01` | agent | `REQ-03`, `SOL-01`, `SD-02`, `CTR-01`, `CTR-02`, `FM-03` | Add CLI/environment/default resolution and fail-fast validation | `cmd/start-issue/main.go`, `main_test.go` | Resolved mode and source | `CHK-01`, `NEG-01` | `EVID-01` | Focused Go tests, then `make test` | `PRE-01` | none | Validation occurs after fetch or mutation | +| `STEP-01` | agent | `REQ-03`, `REQ-09`, `SOL-01`, `SOL-07`, `SD-02`, `SD-05`, `CTR-01`, `CTR-02`, `CTR-06`, `FM-03`, `FM-05` | Add primary batch names, compatibility aliases, permission resolution, and fail-fast validation | `cmd/start-issue/main.go`, `main_test.go` | One internal batch mode with resolved permissions and source | `CHK-01`, `SC-07`, `NEG-01`, `NEG-04` | `EVID-01` | Focused Go tests, then `make test` | `PRE-01` | none | Alias behavior diverges or validation occurs after fetch/mutation | | `STEP-02` | agent | `REQ-07`, `INV-01`, `INV-02` | Extend the fake Codex process and Go tests before changing launcher behavior | `cmd/start-issue/main_test.go` | Red/green command contract tests | `CHK-01`, `SC-01` - `SC-04` | `EVID-01` | `go test ./cmd/start-issue` | `STEP-01` | none | Fake cannot distinguish global and exec arguments | | `STEP-03` | agent | `REQ-01`, `REQ-02`, `REQ-04`, `SOL-02`, `SOL-03`, `CTR-03`, `CTR-04`, `INV-04` | Build validated restricted/full-delivery commands in supported order | `cmd/start-issue/main.go` | Array-based Codex command mapping | `CHK-01`, `SC-02`, `SC-03` | `EVID-01` | Focused Go tests; inspect `--dry-run` command | `STEP-02`, `PRE-02` | none | Supported Codex rejects generated grammar | | `STEP-04` | agent | `REQ-01`, `REQ-06`, `SOL-04`, `CTR-05`, `FM-02`, `FM-04` | Add capability output, warning, dedicated help, and public docs | `cmd/start-issue/main.go`, README files, practical guides, spec | Consistent operator contract | `CHK-02`, `SC-05`, `NEG-02` | `EVID-02` | Help assertions and documentation review | `STEP-03` | none | Docs imply permission equals credentials or product authorization | diff --git a/memory-bank/features/FT-019/implementation-plan.md b/memory-bank/features/FT-019/implementation-plan.md index 615e16d..3d3e0ed 100644 --- a/memory-bank/features/FT-019/implementation-plan.md +++ b/memory-bank/features/FT-019/implementation-plan.md @@ -21,7 +21,7 @@ must_not_define: | Path / module | Current role | Why relevant | Reuse / mirror | | --- | --- | --- | --- | -| `test/e2e/human-gate.sh` | Existing real-agent E2E | Establishes E2E script conventions and opt-in boundary | Keep separate because it needs secrets/interactive Codex | +| `test/e2e/batch.sh` | Existing real-agent E2E | Establishes E2E script conventions and opt-in boundary | Keep separate because it needs secrets/interactive Codex | | `Makefile` | Build/test entrypoint | Owns the local and CI target | Add sandbox target beside human-gate target | | `.github/workflows/ci.yml` | CI checks | Runs Go build and tests | Add network-free sandbox job | | `cmd/start-issue/main.go` | Product executable | Must be exercised as a built subprocess | Do not add test-only product hooks | diff --git a/memory-bank/features/README.md b/memory-bank/features/README.md index 340f0d0..d8ee66e 100644 --- a/memory-bank/features/README.md +++ b/memory-bank/features/README.md @@ -39,9 +39,9 @@ audience: humans_and_agents - [FT-016: Real Codex human-gate E2E suite](FT-016/README.md) Opt-in local smoke suite that validates the real Codex human-gate path. -- [FT-017: Codex human-gate delivery permissions](FT-017/README.md) +- [FT-017: Codex batch delivery permissions](FT-017/README.md) Explicit restricted/default and opt-in full-delivery capability contract for - Codex human-gate runs. + Codex batch runs. - [FT-018: Agent CLI launch compatibility](FT-018/README.md) Issue #34 follow-up package for current Kimi Code CLI command and cwd compatibility. diff --git a/memory-bank/ops/development.md b/memory-bank/ops/development.md index 650c75e..46608bc 100644 --- a/memory-bank/ops/development.md +++ b/memory-bank/ops/development.md @@ -39,11 +39,11 @@ Install locally from source: make install ``` -Run the opt-in real-Codex human-gate E2E smoke test with a usable issue in the +Run the opt-in real-Codex batch E2E smoke test with a usable issue in the current repository: ```bash -START_ISSUE_E2E=1 make e2e-human-gate +START_ISSUE_E2E=1 make e2e-batch ``` Run the deterministic, network-free built-binary E2E used in CI: diff --git a/memory-bank/ops/stages.md b/memory-bank/ops/stages.md index 2cd1add..7f76229 100644 --- a/memory-bank/ops/stages.md +++ b/memory-bank/ops/stages.md @@ -55,7 +55,7 @@ There is no centralized runtime observability. Diagnostics come from: - command output; - Go test logs; - GitHub Actions logs; -- human-gate state files under +- batch state files under `/.start-issue/runs//`. ## Test Data And Smoke Targets diff --git a/memory-bank/product/context.md b/memory-bank/product/context.md index 22a8055..f9fc9a1 100644 --- a/memory-bank/product/context.md +++ b/memory-bank/product/context.md @@ -58,7 +58,7 @@ tracker, or the developer's review and merge process. prompt silently. - `WF-04` Update the installed CLI: compare the running executable version with the latest GitHub Release, verify checksum, and install into the same path. -- `WF-05` Run Codex human-gate mode: execute Codex in batch mode, persist run +- `WF-05` Run Codex batch mode: execute Codex autonomously, persist run state, exit on `STATUS: DONE`, and resume interactively on `STATUS: HUMAN_GATE`. diff --git a/memory-bank/product/roadmap.md b/memory-bank/product/roadmap.md index 04ce786..5ec5828 100644 --- a/memory-bank/product/roadmap.md +++ b/memory-bank/product/roadmap.md @@ -42,7 +42,7 @@ into feature packages. ## Open Bets -- `BET-01` Whether Codex human-gate patterns should remain Codex-only or become +- `BET-01` Whether Codex batch/human-gate patterns should remain Codex-only or become a generic agent capability after other CLIs expose equivalent contracts. - `BET-02` Whether future lifecycle complexity warrants extracting Go helper packages from the current command package. diff --git a/memory-bank/product/vision.md b/memory-bank/product/vision.md index 6e398e7..7640add 100644 --- a/memory-bank/product/vision.md +++ b/memory-bank/product/vision.md @@ -32,7 +32,7 @@ behavior behind implicit magic. | --- | --- | --- | --- | --- | | `BET-01` | Agent-neutral start workflow | Developers switch between Claude, Codex, Kimi, Pi, and manual mode | Current adapter support in README/spec | Revisit when adding an agent | | `BET-02` | Reviewable prompt/config evolution | Prompt templates materially affect agent output | `--improve-prompt`, setup/init flows | Revisit when prompt placeholders change | -| `BET-03` | Resumable batch work for Codex | Some issue work can run unattended until a real human gate | `--human-gate` feature | Revisit when other agents expose equivalent resume contracts | +| `BET-03` | Resumable batch work for Codex | Some issue work can run unattended until a real human gate | `--batch` feature | Revisit when other agents expose equivalent resume contracts | ## Experience Principles diff --git a/test/e2e/batch.sh b/test/e2e/batch.sh new file mode 100755 index 0000000..408af9c --- /dev/null +++ b/test/e2e/batch.sh @@ -0,0 +1,186 @@ +#!/usr/bin/env bash + +# Opt-in smoke test for a real local Codex batch session. +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +fixture_repo="dapi/start-issue-e2e-fixture" +fixture_issue="1" +scenario="done" + +usage() { + cat <<'EOF' +Usage: START_ISSUE_E2E=1 test/e2e/batch.sh [--scenario done|human-gate|full-delivery] + +Runs start-issue against a real Codex CLI using the private fixture repository +dapi/start-issue-e2e-fixture and its control issue #1. It deletes the temporary +clone after a successful run; set START_ISSUE_E2E_KEEP=1 to retain it. The +HUMAN_GATE scenario opens Codex resume interactively; exit it to continue. +FULL_DELIVERY also requires START_ISSUE_E2E_FULL_DELIVERY=1. It authorizes an +unsandboxed Codex run that creates a unique fixture commit, remote branch, and +pull request. Its temporary fixture and diagnostic artifacts are retained as +evidence automatically. +EOF +} + +fail() { + printf 'E2E batch: %s\n' "$*" >&2 + exit 1 +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --scenario) + scenario="${2:-}" + shift 2 + ;; + --help|-h) + usage + exit 0 + ;; + *) + fail "unknown argument: $1" + ;; + esac +done + +[[ "$scenario" == "done" || "$scenario" == "human-gate" || "$scenario" == "full-delivery" ]] || \ + fail "scenario must be done, human-gate, or full-delivery" +[[ "${START_ISSUE_E2E:-}" == "1" ]] || fail "set START_ISSUE_E2E=1 to authorize a real Codex session" +if [[ "$scenario" == "full-delivery" ]]; then + [[ "${START_ISSUE_E2E_FULL_DELIVERY:-}" == "1" ]] || \ + fail "set START_ISSUE_E2E_FULL_DELIVERY=1 to authorize unsandboxed GitHub delivery" +fi +start_issue_bin="${START_ISSUE_E2E_BINARY:-$repo_root/.build/start-issue}" + +[[ -x "$start_issue_bin" ]] || fail "start-issue executable not found: $start_issue_bin" + +codex_path="$(command -v codex || true)" +[[ -n "$codex_path" ]] || fail "codex is not on PATH" +[[ "$codex_path" != "$repo_root/test/helpers/fake-bin/codex" ]] || fail "PATH resolves codex to the test fake" +gh auth status >/dev/null || fail "gh is not authenticated" + +codex_exec_help="$(codex exec --help 2>&1)" || fail "codex exec --help failed" +printf '%s' "$codex_exec_help" | grep -Fq 'Run Codex non-interactively' || \ + fail "resolved codex does not expose the real Codex exec interface" +printf '%s' "$codex_exec_help" | grep -Fq -- '--output-last-message' || \ + fail "resolved codex does not support --output-last-message" +if printf '%s' "$codex_exec_help" | grep -q -- '--ask-for-approval'; then + fail "installed codex exec still advertises --ask-for-approval; use a current Codex CLI" +fi +if [[ "$scenario" == "full-delivery" ]]; then + codex_help="$(codex --help 2>&1)" || fail "codex --help failed" + printf '%s' "$codex_help" | grep -Fq -- '--dangerously-bypass-approvals-and-sandbox' || \ + fail "resolved codex does not support the full-delivery permission option" +fi + +fixture_root="$(mktemp -d "${TMPDIR:-/tmp}/start-issue-batch.XXXXXX")" +fixture_dir="$fixture_root/fixture" +worktree_parent="$fixture_root/worktrees" +log_path="$fixture_root/e2e.log" +expected_status="DONE" +permission_args=() +if [[ "$scenario" == "human-gate" ]]; then + expected_status="HUMAN_GATE" +fi + +if [[ "$scenario" == "full-delivery" ]]; then + permission_args=(--batch-permissions full-delivery) + delivery_id="$(date -u +%Y%m%d%H%M%S)-$$" + delivery_branch="e2e/batch-full-delivery-$delivery_id" + delivery_file="full-delivery-$delivery_id.txt" + fixture_base="$(gh repo view "$fixture_repo" --json defaultBranchRef --jq '.defaultBranchRef.name')" + [[ -n "$fixture_base" ]] || fail "could not resolve fixture default branch" +fi + +if [[ "$scenario" == "full-delivery" ]]; then + prompt=$(cat <&1 | tee "$log_path" +command_status=${PIPESTATUS[0]} +set -e + +[[ $command_status -eq 0 ]] || fail "start-issue exited $command_status; inspect $log_path" + +state_dir="$(awk '/^[[:space:]]*State dir: / { sub(/^[[:space:]]*State dir: /, ""); print; exit }' "$log_path")" +[[ -n "$state_dir" ]] || fail "state directory was not reported; inspect $log_path" +last_message_path="$state_dir/last-message.txt" +worktree_path="$(dirname "$(dirname "$(dirname "$state_dir")")")" + +[[ -f "$state_dir/events.jsonl" ]] || fail "events.jsonl is missing: $state_dir/events.jsonl" +[[ -f "$last_message_path" ]] || fail "last-message.txt is missing: $last_message_path" +[[ -f "$state_dir/thread-id" ]] || fail "thread-id is missing: $state_dir/thread-id" +jq -e 'select(.type == "thread.started") | .thread_id | strings' "$state_dir/events.jsonl" >/dev/null || \ + fail "thread.started event is missing from $state_dir/events.jsonl" +grep -Fx "STATUS: $expected_status" "$last_message_path" >/dev/null || \ + fail "expected STATUS: $expected_status in $last_message_path" + +if [[ "$scenario" == "human-gate" ]]; then + grep -F 'Resume command: codex resume --include-non-interactive ' "$log_path" >/dev/null || \ + fail "resume command was not reported; inspect $log_path" +fi + +if [[ "$scenario" == "full-delivery" ]]; then + current_branch="$(git -C "$worktree_path" branch --show-current)" + [[ "$current_branch" == "$delivery_branch" ]] || \ + fail "full-delivery branch is $current_branch, want $delivery_branch" + git -C "$worktree_path" show "HEAD:$delivery_file" | grep -Fx "full-delivery $delivery_id" >/dev/null || \ + fail "delivery commit does not contain the expected marker" + git -C "$worktree_path" ls-remote --exit-code --heads origin "$delivery_branch" >/dev/null || \ + fail "remote delivery branch is missing: $delivery_branch" + pr_url="$(gh pr list --repo "$fixture_repo" --state open --head "$delivery_branch" --json url --jq '.[0].url // empty')" + [[ -n "$pr_url" ]] || fail "full-delivery pull request is missing for $delivery_branch" + printf 'Full-delivery PR: %s\n' "$pr_url" +fi + +unexpected_changes="$(git -C "$worktree_path" status --porcelain | awk '$0 !~ /^\?\? \.start-issue\// { print }')" +[[ -z "$unexpected_changes" ]] || fail "fixture worktree has unexpected changes: $unexpected_changes" + +printf 'PASS: real Codex batch %s scenario. State: %s\n' "$scenario" "$state_dir" +if [[ "$scenario" == "full-delivery" || "${START_ISSUE_E2E_KEEP:-}" == "1" ]]; then + printf 'The temporary fixture is preserved at: %s\n' "$fixture_root" +else + git -C "$fixture_dir" worktree remove --force "$worktree_path" + rm -rf -- "$fixture_root" + printf 'Temporary fixture clone and worktree removed. Set START_ISSUE_E2E_KEEP=1 to preserve them.\n' +fi diff --git a/test/e2e/human-gate.sh b/test/e2e/human-gate.sh index 47c89ea..f9ddb4c 100755 --- a/test/e2e/human-gate.sh +++ b/test/e2e/human-gate.sh @@ -1,186 +1,6 @@ #!/usr/bin/env bash - -# Opt-in smoke test for a real local Codex human-gate session. set -euo pipefail -repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -fixture_repo="dapi/start-issue-e2e-fixture" -fixture_issue="1" -scenario="done" - -usage() { - cat <<'EOF' -Usage: START_ISSUE_E2E=1 test/e2e/human-gate.sh [--scenario done|human-gate|full-delivery] - -Runs start-issue against a real Codex CLI using the private fixture repository -dapi/start-issue-e2e-fixture and its control issue #1. It deletes the temporary -clone after a successful run; set START_ISSUE_E2E_KEEP=1 to retain it. The -HUMAN_GATE scenario opens Codex resume interactively; exit it to continue. -FULL_DELIVERY also requires START_ISSUE_E2E_FULL_DELIVERY=1. It authorizes an -unsandboxed Codex run that creates a unique fixture commit, remote branch, and -pull request. Its temporary fixture and diagnostic artifacts are retained as -evidence automatically. -EOF -} - -fail() { - printf 'E2E human-gate: %s\n' "$*" >&2 - exit 1 -} - -while [[ $# -gt 0 ]]; do - case "$1" in - --scenario) - scenario="${2:-}" - shift 2 - ;; - --help|-h) - usage - exit 0 - ;; - *) - fail "unknown argument: $1" - ;; - esac -done - -[[ "$scenario" == "done" || "$scenario" == "human-gate" || "$scenario" == "full-delivery" ]] || \ - fail "scenario must be done, human-gate, or full-delivery" -[[ "${START_ISSUE_E2E:-}" == "1" ]] || fail "set START_ISSUE_E2E=1 to authorize a real Codex session" -if [[ "$scenario" == "full-delivery" ]]; then - [[ "${START_ISSUE_E2E_FULL_DELIVERY:-}" == "1" ]] || \ - fail "set START_ISSUE_E2E_FULL_DELIVERY=1 to authorize unsandboxed GitHub delivery" -fi -start_issue_bin="${START_ISSUE_E2E_BINARY:-$repo_root/.build/start-issue}" - -[[ -x "$start_issue_bin" ]] || fail "start-issue executable not found: $start_issue_bin" - -codex_path="$(command -v codex || true)" -[[ -n "$codex_path" ]] || fail "codex is not on PATH" -[[ "$codex_path" != "$repo_root/test/helpers/fake-bin/codex" ]] || fail "PATH resolves codex to the test fake" -gh auth status >/dev/null || fail "gh is not authenticated" - -codex_exec_help="$(codex exec --help 2>&1)" || fail "codex exec --help failed" -printf '%s' "$codex_exec_help" | grep -Fq 'Run Codex non-interactively' || \ - fail "resolved codex does not expose the real Codex exec interface" -printf '%s' "$codex_exec_help" | grep -Fq -- '--output-last-message' || \ - fail "resolved codex does not support --output-last-message" -if printf '%s' "$codex_exec_help" | grep -q -- '--ask-for-approval'; then - fail "installed codex exec still advertises --ask-for-approval; use a current Codex CLI" -fi -if [[ "$scenario" == "full-delivery" ]]; then - codex_help="$(codex --help 2>&1)" || fail "codex --help failed" - printf '%s' "$codex_help" | grep -Fq -- '--dangerously-bypass-approvals-and-sandbox' || \ - fail "resolved codex does not support the full-delivery permission option" -fi - -fixture_root="$(mktemp -d "${TMPDIR:-/tmp}/start-issue-human-gate.XXXXXX")" -fixture_dir="$fixture_root/fixture" -worktree_parent="$fixture_root/worktrees" -log_path="$fixture_root/e2e.log" -expected_status="DONE" -permission_args=() -if [[ "$scenario" == "human-gate" ]]; then - expected_status="HUMAN_GATE" -fi - -if [[ "$scenario" == "full-delivery" ]]; then - permission_args=(--human-gate-permissions full-delivery) - delivery_id="$(date -u +%Y%m%d%H%M%S)-$$" - delivery_branch="e2e/human-gate-full-delivery-$delivery_id" - delivery_file="full-delivery-$delivery_id.txt" - fixture_base="$(gh repo view "$fixture_repo" --json defaultBranchRef --jq '.defaultBranchRef.name')" - [[ -n "$fixture_base" ]] || fail "could not resolve fixture default branch" -fi - -if [[ "$scenario" == "full-delivery" ]]; then - prompt=$(cat <&1 | tee "$log_path" -command_status=${PIPESTATUS[0]} -set -e - -[[ $command_status -eq 0 ]] || fail "start-issue exited $command_status; inspect $log_path" - -state_dir="$(awk '/^[[:space:]]*State dir: / { sub(/^[[:space:]]*State dir: /, ""); print; exit }' "$log_path")" -[[ -n "$state_dir" ]] || fail "state directory was not reported; inspect $log_path" -last_message_path="$state_dir/last-message.txt" -worktree_path="$(dirname "$(dirname "$(dirname "$state_dir")")")" - -[[ -f "$state_dir/events.jsonl" ]] || fail "events.jsonl is missing: $state_dir/events.jsonl" -[[ -f "$last_message_path" ]] || fail "last-message.txt is missing: $last_message_path" -[[ -f "$state_dir/thread-id" ]] || fail "thread-id is missing: $state_dir/thread-id" -jq -e 'select(.type == "thread.started") | .thread_id | strings' "$state_dir/events.jsonl" >/dev/null || \ - fail "thread.started event is missing from $state_dir/events.jsonl" -grep -Fx "STATUS: $expected_status" "$last_message_path" >/dev/null || \ - fail "expected STATUS: $expected_status in $last_message_path" - -if [[ "$scenario" == "human-gate" ]]; then - grep -F 'Resume command: codex resume --include-non-interactive ' "$log_path" >/dev/null || \ - fail "resume command was not reported; inspect $log_path" -fi - -if [[ "$scenario" == "full-delivery" ]]; then - current_branch="$(git -C "$worktree_path" branch --show-current)" - [[ "$current_branch" == "$delivery_branch" ]] || \ - fail "full-delivery branch is $current_branch, want $delivery_branch" - git -C "$worktree_path" show "HEAD:$delivery_file" | grep -Fx "full-delivery $delivery_id" >/dev/null || \ - fail "delivery commit does not contain the expected marker" - git -C "$worktree_path" ls-remote --exit-code --heads origin "$delivery_branch" >/dev/null || \ - fail "remote delivery branch is missing: $delivery_branch" - pr_url="$(gh pr list --repo "$fixture_repo" --state open --head "$delivery_branch" --json url --jq '.[0].url // empty')" - [[ -n "$pr_url" ]] || fail "full-delivery pull request is missing for $delivery_branch" - printf 'Full-delivery PR: %s\n' "$pr_url" -fi - -unexpected_changes="$(git -C "$worktree_path" status --porcelain | awk '$0 !~ /^\?\? \.start-issue\// { print }')" -[[ -z "$unexpected_changes" ]] || fail "fixture worktree has unexpected changes: $unexpected_changes" - -printf 'PASS: real Codex human-gate %s scenario. State: %s\n' "$scenario" "$state_dir" -if [[ "$scenario" == "full-delivery" || "${START_ISSUE_E2E_KEEP:-}" == "1" ]]; then - printf 'The temporary fixture is preserved at: %s\n' "$fixture_root" -else - git -C "$fixture_dir" worktree remove --force "$worktree_path" - rm -rf -- "$fixture_root" - printf 'Temporary fixture clone and worktree removed. Set START_ISSUE_E2E_KEEP=1 to preserve them.\n' -fi +# Compatibility wrapper. New documentation and automation should use batch.sh. +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +exec "$script_dir/batch.sh" "$@" From 8c2ecf99dd74c42d078e0a36c98b09b5a2caf251 Mon Sep 17 00:00:00 2001 From: Danil Pismenny Date: Mon, 24 Aug 2026 22:59:29 +0500 Subject: [PATCH 7/7] Rewrite Russian batch guide in plain language --- doc/batch-mode.ru.md | 222 +++++++++++++++---------------------------- 1 file changed, 78 insertions(+), 144 deletions(-) diff --git a/doc/batch-mode.ru.md b/doc/batch-mode.ru.md index f80067f..52cf0cc 100644 --- a/doc/batch-mode.ru.md +++ b/doc/batch-mode.ru.md @@ -1,127 +1,86 @@ -# Batch mode: автономная работа Codex с остановкой для человека +# Batch-режим Codex -Эта инструкция написана для человека, который впервые видит флаг -`start-issue --batch` и хочет понять, что он делает. Здесь объясняется, чем -batch mode отличается от обычного запуска, зачем Codex может остановиться со -статусом `HUMAN_GATE`, как после этого продолжить ту же session и какие права -нужны агенту для работы с кодом или полной доставки pull request. +Флаг `--batch` запускает Codex без интерактивного интерфейса. `start-issue` +готовит рабочее дерево, формирует задание, запускает `codex exec` и ждёт +результат. -В примерах предполагается, что `codex` уже выбран в project или user config. -Ранее выпущенный флаг `--human-gate` продолжает работать как совместимый alias -для `--batch`. Полное техническое описание находится в [spec.md](spec.md). +Такой запуск удобен, когда не нужно наблюдать за каждым шагом агента. Codex +может сам изменить код и запустить тесты, а человек подключается после +завершения работы или когда без его решения продолжать нельзя. -## Что такое human-gate +Команды ниже предполагают, что `codex` уже выбран в конфигурации проекта или +пользователя. Старый флаг `--human-gate` продолжает работать как alias для +`--batch`. -Human-gate — это контрольная точка, в которой автономная работа Codex либо -считается завершённой, либо передаётся человеку для принятия решения. +## Как проходит запуск -При обычном запуске `start-issue` открывает интерактивную Codex session, и -пользователь остаётся внутри неё. С флагом `--batch` Codex запускается в -batch mode и самостоятельно работает над issue. Пользователю не нужно следить -за каждым шагом в интерактивном UI. - -В конце Codex обязан явно сообщить один из двух результатов: - -- `STATUS: DONE` — задача выполнена и вмешательство человека не требуется; -- `STATUS: HUMAN_GATE` — продолжать без решения человека нельзя. - -Human-gate — это не запрос подтверждения перед каждой командой и не название -sandbox. Это договорённость о том, как автономный запуск завершается и когда -управление возвращается человеку. - -## Зачем он нужен - -Без human-gate трудно надёжно отличить завершённую задачу от запуска, который -остановился из-за вопроса, ошибки или нехватки доступа. Одного exit code Codex -для этого недостаточно. - -Human-gate позволяет: - -- отдать Codex реализацию issue и выполнение тестов без постоянного наблюдения; -- получить однозначный результат `DONE`, когда работа действительно закончена; -- остановиться на решении, которое агент не должен принимать самостоятельно; -- продолжить тот же Codex thread со всей историей, а не объяснять задачу новому - агенту заново. - -## Как он работает - -1. `start-issue` получает issue, создаёт или переиспользует worktree и готовит - prompt так же, как при обычном запуске. -2. Вместо интерактивной session запускается `codex exec` в batch mode. -3. `start-issue` сохраняет события запуска, финальное сообщение и `thread_id`. -4. Codex выполняет задачу и заканчивает ответ ровно одной status line: +1. `start-issue` получает issue, создаёт или переиспользует рабочее дерево и + формирует задание. +2. Вместо обычной интерактивной сессии запускается `codex exec`. +3. Codex работает над задачей и в конце возвращает один из двух статусов: `STATUS: DONE` или `STATUS: HUMAN_GATE`. +4. `start-issue` сохраняет журнал событий, последнее сообщение и `thread_id`. 5. При `DONE` команда завершается с кодом `0`. -6. При `HUMAN_GATE` команда открывает сохранённую session через - `codex resume --include-non-interactive `. Пользователь видит всю - историю, отвечает на вопрос и продолжает работу в том же thread. -7. Если Codex завершился с ошибкой или не вернул понятный status, - `start-issue` сообщает об ошибке и оставляет диагностические файлы. +6. При `HUMAN_GATE` открывается та же сессия через + `codex resume --include-non-interactive `. -Human-gate срабатывает не по ключевым словам в issue и не после каждой команды. -Решение остановиться принимает Codex по правилам prompt, а `start-issue` читает -status line из его финального сообщения и выполняет нужный переход. +Если Codex завершился с ошибкой или не вернул статус, `start-issue` сообщает об +ошибке и сохраняет диагностические файлы. -## Когда Codex должен остановиться +## Что означает HUMAN_GATE -`STATUS: HUMAN_GATE` нужен, когда без человека нельзя безопасно выбрать -следующий шаг. Например: +`HUMAN_GATE` означает, что агент дошёл до решения, которое нельзя безопасно +принять самостоятельно. Например: -- требуется destructive operation, удаление данных или переписывание Git - history; -- отсутствуют credentials или доступ, который должен предоставить человек; -- есть несколько несовместимых product-вариантов без зафиксированного решения; -- тесты или конфликт нельзя безопасно исправить в рамках issue; -- действие затрагивает production или security и требует отдельного согласия. +- нужно удалить данные или переписать историю Git; +- не хватает учётных данных или доступа; +- есть несколько несовместимых вариантов поведения продукта; +- действие затрагивает продакшен или безопасность; +- тест или конфликт нельзя исправить в рамках текущей задачи. -Обычные технические вопросы, исправимые ошибки и выбор реализации внутри -согласованного scope не должны останавливать run: Codex должен решить их сам. +Обычные технические вопросы и исправимые ошибки не должны приводить к +`HUMAN_GATE`: Codex должен разобраться с ними сам. -## Как permission mode связан с human-gate +Финальное сообщение Codex должно содержать ровно одну строку со статусом. При +`HUMAN_GATE` в сообщении также должен быть конкретный вопрос к человеку. -Это две разные настройки: +`start-issue` не анализирует действия агента и сам не решает, когда нужна +остановка. Он читает статус из последнего сообщения Codex и либо завершает +команду, либо открывает сохранённую сессию. -- `--batch` включает автономный batch run и протокол - `DONE` / `HUMAN_GATE`; -- `--batch-permissions` определяет, какие технические действия доступны - Codex во время этого run. +## Права batch-режима -Разделение нужно потому, что правка кода и доставка PR требуют разных прав. -Sandbox обычно позволяет менять и тестировать файлы worktree, но может не дать -доступ к GitHub, записи Git metadata, push или созданию PR. Поэтому безопасный -режим остаётся default, а полный доступ для end-to-end доставки включается -явно. +`--batch` определяет способ запуска Codex. `--batch-permissions` определяет, +что Codex может делать во время этого запуска. -## Выбор режима +### restricted -Для обычной реализации в worktree используйте `restricted`: +Режим по умолчанию: ```bash start-issue 123 --batch ``` -`restricted` — режим по умолчанию. Codex работает в sandbox -`workspace-write`: он может изменять и тестировать файлы подготовленного -worktree, но network access, запись Git metadata, push и доставка PR не -гарантированы. +Codex запускается в песочнице `workspace-write`. Он может изменять файлы +рабочего дерева и запускать тесты. Доступ к GitHub, запись метаданных Git, push +и создание PR не гарантируются. -Если run должен также читать GitHub context, делать commit и push, создавать -или обновлять pull request, явно включите `full-delivery`: +Этот режим подходит, если Codex должен подготовить изменения, а commit, push и +PR будут сделаны вручную. + +### full-delivery ```bash start-issue 123 --batch \ --batch-permissions full-delivery ``` -Это явное согласие на запуск Codex без sandbox и approvals. Режим даёт -техническую возможность, но не разрешает destructive Git operations, -production/security changes или нерешённые product decisions. В таких случаях -Codex по-прежнему обязан вернуть `STATUS: HUMAN_GATE` и передать решение -оператору. - -## Проверка перед full delivery +Codex запускается без песочницы и подтверждений. Он может получить контекст из +GitHub, изменить и проверить код, сделать commit и push, создать или обновить +pull request. -Проверьте выбранный GitHub account, remote и права на репозиторий: +Для этого нужны авторизованный `gh`, правильный `origin` и право записи в +репозиторий: ```bash gh auth status @@ -129,8 +88,14 @@ git remote get-url origin gh repo view --json nameWithOwner,viewerPermission ``` -Сначала выполните dry-run: он покажет выбранный режим и launch command, но не -создаст worktree и не запустит Codex. +`full-delivery` даёт Codex технический доступ, но не разрешает удаление данных, +изменения продакшена, решения, связанные с безопасностью, и другие опасные +действия. Для них агент всё равно должен вернуть `STATUS: HUMAN_GATE`. + +## Проверка перед запуском + +Через `--dry-run` можно проверить выбранный режим и команду Codex без создания +рабочего дерева и запуска агента: ```bash start-issue 123 --batch \ @@ -138,40 +103,28 @@ start-issue 123 --batch \ --dry-run ``` -В output должна быть строка: +В выводе должно быть: ```text Batch permissions: full-delivery (CLI) ``` -## Пример: реализовать issue и доставить PR - -Предположим, что issue `123` находится в текущем репозитории, а выбранный -GitHub account имеет write access. +После проверки запустите ту же команду без `--dry-run`. -1. Просмотрите план запуска: +## Переключение через переменную окружения - ```bash - start-issue 123 --batch \ - --batch-permissions full-delivery \ - --dry-run - ``` - -2. После проверки warning об unsandboxed execution запустите реальный run: - - ```bash - start-issue 123 --batch \ - --batch-permissions full-delivery - ``` +```bash +START_ISSUE_BATCH_PERMISSIONS=full-delivery \ + start-issue 123 --batch +``` -3. Codex сможет реализовать и протестировать issue, сделать commit, push - issue branch и создать или обновить pull request, если это допускают задача - и состояние репозитория. +Параметр командной строки имеет приоритет над переменной окружения. Не стоит +добавлять `full-delivery` в глобальный профиль оболочки: безопаснее включать +его явно для конкретного запуска. -4. Финальный `STATUS: DONE` успешно завершит команду. Финальный - `STATUS: HUMAN_GATE` откроет оператору точный сохранённый Codex thread. +## Диагностика -CLI сохраняет диагностические данные в: +Файлы запуска сохраняются в рабочем дереве: ```text /.start-issue/runs//events.jsonl @@ -179,36 +132,17 @@ CLI сохраняет диагностические данные в: /.start-issue/runs//thread-id ``` -## Одноразовое переключение через environment +- `events.jsonl` содержит события `codex exec`; +- `last-message.txt` содержит сообщение со статусом; +- `thread-id` содержит идентификатор сессии для продолжения работы. -Environment variable удобно использовать для одной команды или в -контролируемой automation wrapper: +Если автоматическое продолжение сессии не сработало: ```bash -START_ISSUE_BATCH_PERMISSIONS=full-delivery \ - start-issue 123 --batch +codex resume --include-non-interactive ``` -CLI option имеет приоритет над environment variable. Не стоит добавлять -`full-delivery` в глобальный shell profile: видимый opt-in рядом с командой -упрощает проверку границы unsandboxed execution. - -## Диагностика - -- Если `restricted` не может обратиться к GitHub или записать Git metadata, - завершите доставку вручную либо повторите run с явно проверенным - `full-delivery`. -- Если full delivery не может сделать push или создать PR, повторно проверьте - `gh auth status`, выбранный account, `origin` и `viewerPermission`. -- При ошибке status parsing проверьте `events.jsonl` и `last-message.txt` в - напечатанном state directory. -- Если automatic resume не сработал, используйте сохранённый thread id: - - ```bash - codex resume --include-non-interactive - ``` - -Встроенная справка: +Полная встроенная справка: ```bash start-issue --batch-help