okstra 0.20.1 → 0.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.kr.md +2 -2
  2. package/README.md +2 -2
  3. package/docs/kr/architecture.md +1 -0
  4. package/docs/kr/cli.md +1 -1
  5. package/docs/kr/performance-improvement-plan-v2.md +330 -0
  6. package/docs/kr/performance-improvement-plan.md +125 -0
  7. package/docs/project-structure-overview.md +388 -0
  8. package/docs/superpowers/plans/2026-05-14-convergence-queue-pruning.md +1568 -0
  9. package/package.json +1 -1
  10. package/runtime/BUILD.json +2 -2
  11. package/runtime/agents/SKILL.md +7 -1
  12. package/runtime/agents/workers/claude-worker.md +3 -1
  13. package/runtime/agents/workers/report-writer-worker.md +4 -0
  14. package/runtime/bin/okstra-codex-exec.sh +42 -0
  15. package/runtime/bin/okstra-gemini-exec.sh +7 -0
  16. package/runtime/bin/okstra-trace-cleanup.sh +42 -0
  17. package/runtime/prompts/profiles/final-verification.md +8 -2
  18. package/runtime/prompts/profiles/implementation-planning.md +1 -1
  19. package/runtime/prompts/profiles/release-handoff.md +26 -28
  20. package/runtime/prompts/profiles/requirements-discovery.md +1 -1
  21. package/runtime/python/okstra_ctl/render.py +78 -4
  22. package/runtime/python/okstra_ctl/run_context.py +5 -0
  23. package/runtime/python/okstra_ctl/workflow.py +8 -7
  24. package/runtime/python/okstra_ctl/worktree.py +155 -12
  25. package/runtime/skills/okstra-brief/SKILL.md +523 -0
  26. package/runtime/skills/okstra-convergence/SKILL.md +149 -37
  27. package/runtime/skills/okstra-report-writer/SKILL.md +8 -6
  28. package/runtime/templates/prd/brief.template.md +12 -0
  29. package/runtime/templates/project-docs/task-index.template.md +12 -0
  30. package/runtime/templates/reports/error-analysis-input.template.md +12 -0
  31. package/runtime/templates/reports/final-report.template.md +39 -12
  32. package/runtime/templates/reports/final-verification-input.template.md +22 -0
  33. package/runtime/templates/reports/implementation-input.template.md +12 -0
  34. package/runtime/templates/reports/implementation-planning-input.template.md +12 -0
  35. package/runtime/templates/reports/quick-input.template.md +12 -0
  36. package/runtime/templates/reports/release-handoff-input.template.md +23 -10
  37. package/runtime/templates/reports/schedule.template.md +12 -0
  38. package/runtime/templates/reports/settings.template.json +92 -30
  39. package/runtime/templates/reports/task-brief.template.md +12 -0
  40. package/src/install.mjs +1 -0
  41. package/src/uninstall.mjs +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okstra",
3
- "version": "0.20.1",
3
+ "version": "0.21.1",
4
4
  "description": "Multi-agent cross-verification orchestrator runtime + Claude Code skills.",
5
5
  "license": "MIT",
6
6
  "author": "devonshin",
@@ -1,5 +1,5 @@
1
1
  {
2
- "package": "0.20.1",
3
- "builtAt": "2026-05-14T01:49:53.274Z",
2
+ "package": "0.21.1",
3
+ "builtAt": "2026-05-14T12:50:20.728Z",
4
4
  "repoRoot": "/home/runner/work/okstra/okstra"
5
5
  }
@@ -205,7 +205,11 @@ Convergence is enabled by default. Configure via task-manifest.json:
205
205
  - `convergence.maxRounds`: 1–3 — **phase-aware default**: `1` for `requirements-discovery`, `2` for all other task types
206
206
  - `convergence.verificationMode`: `"lightweight"` | `"full-reanalysis"` (default: `"lightweight"`)
207
207
 
208
- When `task-manifest.json` does not set `convergence.maxRounds`, lead MUST resolve the effective value via the phase-aware default above before entering Phase 5.5, and record the resolved value in the convergence state artifact.
208
+ When `task-manifest.json` does not set `convergence.maxRounds`, lead MUST resolve the effective value via the phase-aware default above before entering Phase 5.5, and record the resolved value in the convergence state artifact at `config.effectiveMaxRounds`.
209
+
210
+ **Round 2 is gated, not unconditional.** Even when `effectiveMaxRounds == 2`, Round 2 runs only when (a) the verification queue is non-empty after Round 1, AND (b) at least one Round 1 reverify dispatch terminated as `completed`. Otherwise lead writes `round2SkippedReason` to the convergence state and proceeds to final classification. See [okstra-convergence](./skills/okstra-convergence/SKILL.md) "Round 2 gate" for the predicate.
211
+
212
+ **Confirmed findings are pruned from the queue.** Findings classified as `full-consensus`, `partial-consensus`, or `worker-unique` MUST NOT appear in any subsequent round's reverify prompt for any worker. `contested` is a final classification assigned only when the last executed round completes and the queue is still non-empty — it is NEVER an intermediate queue label.
209
213
 
210
214
  If any re-verification batch yields a `verification-error` terminal status, or a worker result fails the contract, Lead MUST record one event per violation via `python3 scripts/okstra-error-log.py append-observed --error-type contract-violation --agent <offending-agent> ...`. Use `agent: "claude-lead"` only when the violation is detected internally without a specific worker.
211
215
 
@@ -282,4 +286,6 @@ After persistence, reply briefly in Korean with: completion status, final report
282
286
  | Letting `convergence.maxRounds` default to 2 for `requirements-discovery` | Resolve effective default to `1` for discovery and record in convergence state artifact |
283
287
  | Issuing serial Read calls in Phase 1 | The intake files are independent — issue all Read calls in a single message (parallel) |
284
288
  | Flagging the claude-worker dispatch prompt as "incomplete" because it lacks `[Required reading]` / `[Error reporting]` blocks | Intentional asymmetry — see [okstra-team-contract](./skills/okstra-team-contract/SKILL.md) "Asymmetry between claude-worker and codex/gemini-worker prompts" |
289
+ | Re-sending confirmed findings (`full-consensus`/`partial-consensus`/`worker-unique`) to a worker in Round 2 | Queue pruning rule — see [okstra-convergence](./skills/okstra-convergence/SKILL.md) "Round 1-N: Re-verification Loop (queue-pruned)" |
290
+ | Aggregating a `timeout`/`error` reverify dispatch as `DISAGREE` | Worker failure handling — record as `verification-error` and add to `skippedWorkers[]`. See [okstra-convergence](./skills/okstra-convergence/SKILL.md) "Worker failure handling in reverify" |
285
291
  | Skipping `--substitute-final-report` in the Phase 7 collector run | Always pass the flag — see [okstra-report-writer](./skills/okstra-report-writer/SKILL.md) "Phase 7 token-usage collector" |
@@ -44,7 +44,9 @@ Unlike the Codex / Gemini workers, you are an in-process Claude subagent — you
44
44
  - If the parent directory does not exist yet, create it before writing.
45
45
 
46
46
  4. Anchor all file operations to the absolute `Project Root` from the lead prompt. Use absolute paths — do NOT rely on inherited cwd. Never use `cd` to change directory.
47
- - **Executor exception (implementation phase only):** when this worker is dispatched as the `Executor` and the lead prompt provides an `EXECUTOR_WORKTREE_PATH` that differs from the session's inherited cwd, cwd-sensitive Bash commands (`cargo *`, `npm *`, `pnpm *`, `bun *`, `pytest`, `make *`, `go *`, language-toolchain test/build commands) MUST be prefixed with `cd <EXECUTOR_WORKTREE_PATH> && ` in the same Bash invocation — e.g. `cd /Users/.../worktrees/foo && cargo test -p bar`. Do NOT wrap the whole thing in `bash -lc "..."` or `bash -c "..."`; pass the chained command directly to the Bash tool so the leading `cd` token remains visible to the permission layer. The `cd` is scoped to the single Bash subshell and does not mutate the session's shell state, so this does not conflict with the "never use cd" rule above (which prevents the worker from drifting the session cwd across calls). Verifier roles do NOT use this exception — they read with absolute paths only.
47
+ - **Executor exception (implementation phase only):** when this worker is dispatched as the `Executor` and the lead prompt provides an `EXECUTOR_WORKTREE_PATH` that differs from the session's inherited cwd, cwd-sensitive Bash commands (`cargo *`, `npm *`, `pnpm *`, `bun *`, `pytest`, `make *`, `go *`, language-toolchain test/build commands) MUST be prefixed with `cd <EXECUTOR_WORKTREE_PATH> && ` in the same Bash invocation — e.g. `cd /Users/.../worktrees/foo && cargo test -p bar`. Do NOT wrap the whole thing in `bash -lc "..."` or `bash -c "..."`; pass the chained command directly to the Bash tool so the leading `cd` token remains visible to the permission layer. The `cd` is scoped to the single Bash subshell and does not mutate the session's shell state, so this does not conflict with the "never use cd" rule above (which prevents the worker from drifting the session cwd across calls).
48
+ - **Verifier QA-gate exception:** verifier roles MAY use the same `cd <WORKTREE> && <cmd>` shape when executing project-declared `qaCommands` (lint / format / typecheck / test) from `project.json`, since those commands are cwd-sensitive by nature. Outside the QA gate, verifiers still read with absolute paths only — do NOT use `cd` for file inspection.
49
+ - **No extra chaining beyond `cd && cmd`:** the permission matcher only allows the exact two-segment shape `cd <PATH> && <single-command>`. Do NOT append additional pipes, semicolons, redirects, or `&&` chains — e.g. `cd ... && cargo test ... 2>&1 | tail -20; echo "exit:$?"` will trigger a permission prompt every dispatch because the trailing `| tail`, `; echo`, and `2>&1` tokens disqualify the prefix match against `Bash(cargo:*)`. Let Claude Code capture the full stdout/stderr and exit code natively — do not post-process with `tail`, `head`, or `echo "exit:$?"`. If output truncation is genuinely needed, run the command first and read the result in a separate tool call.
48
50
 
49
51
  5. **MCP usage**: The canonical list of MCP servers and tools available for this run lives in the lead prompt's `## Available MCP Servers` section (sourced from `.project-docs/okstra/project.json`'s `mcpServers` array). When the task requires inspection of an external system covered by one of those servers, call the listed tool directly by name (e.g. `mcp__<server>__<tool>`). Do NOT shell out via `claude --mcp-cli call ...` or run the tool name as a Bash command — those are not valid invocation paths. If a server you need is not listed, record `MCP not available for this run` in your worker output rather than guessing a tool name.
50
52
 
@@ -49,6 +49,7 @@ Before writing the final report, you MUST read every input file enumerated in th
49
49
  - For the carry-in `clarification-response.md` (if present), walk every row of sub-section 5.1 (`A1`, `A2`, ...) and 5.2 (`Q1`, `Q2`, ...) including blank-answer rows. The fact that the file you write has a structurally similar Section 5/6 is NOT an excuse to skim.
50
50
  - Open every analysis-worker result file under `worker-results/` end-to-end. Do not summarize them from convergence output alone — convergence captures classifications, not full evidence.
51
51
  - Before writing, state one sentence per input file confirming end-to-end reading. If you cannot truthfully say this for a file, record a `tool-failure` in the errors sidecar instead of fabricating the report.
52
+ - When the convergence-state file is present, read it fully and reproduce the `roundHistory[]` array, `round2SkippedReason`, and `finalClassificationCounts` in the final report's Section 1 Round History sub-table. Do not derive these values from worker results alone — they live in `state/convergence-<task-type>-<seq>.json`.
52
53
 
53
54
  ## Authoring Contract
54
55
 
@@ -58,6 +59,8 @@ Hard rules:
58
59
 
59
60
  - The file's `Author:` header line is `Report writer worker` (your role) — NOT `Claude lead`.
60
61
  - Include all four convergence categories (Full Consensus, Partial Consensus, Contested, Worker-Unique). Do not omit Contested or Worker-Unique findings.
62
+ - Include a Round History sub-table in Section 1 (one row per executed round) and a `round2SkippedReason` line below it. When convergence is disabled, omit both. The values are quoted verbatim from `state/convergence-<task-type>-<seq>.json` — do not recompute.
63
+ - Treat `verification-error` votes as their own verdict. They are listed in vote summaries as `verification-error`, not folded into AGREE/DISAGREE counts.
61
64
  - Include the per-agent execution status table and the token-usage summary section. All numbers come from `team-state-<task-type>-<seq>.json` (populated by `okstra-token-usage.py` at the start of Phase 7). Do not estimate or invent.
62
65
  - If only one analysis worker produced a usable result, perform a reduced-confidence write-up and say so explicitly.
63
66
  - If evidence is missing, write `I don't know` rather than fabricating confidence.
@@ -115,3 +118,4 @@ There is NO `cli-failure` category for this worker — it has no external CLI to
115
118
  - You do NOT produce independent analysis findings — your input is the analysis workers' results plus convergence output.
116
119
  - Do NOT modify analysis worker result files. They are read-only inputs to you.
117
120
  - If the analysis workers disagree and convergence ended with `Contested` items, surface them in the final report verbatim — do not silently pick a side.
121
+ - `Contested` is a final-only classification. If you see findings labeled `Contested` in the convergence state, the lead has already exhausted re-verification — do not invent a synthesizing answer; surface each worker's position verbatim.
@@ -29,6 +29,18 @@
29
29
  # if worktree-path is non-empty (the implementation-phase invariant) and
30
30
  # `worker` otherwise.
31
31
  #
32
+ # When role == `verifier`, the wrapper additionally grants the codex
33
+ # `workspace-write` sandbox write access to `~/.cargo` and `~/.rustup` (when
34
+ # they exist). Cargo's package-cache flock (`~/.cargo/.package-cache`) and
35
+ # the registry/cache trees live OUTSIDE the workspace, so without these
36
+ # extra `--add-dir` entries any `cargo build/test/clippy` invoked by the
37
+ # verifier fails with `Resource temporarily unavailable` on the global
38
+ # flock — which is the documented FU-V1 verifier-harness failure. Only the
39
+ # `verifier` role gets this extension; other roles (`worker`, `executor`,
40
+ # any custom label) keep the prior policy. Override per-role extras via the
41
+ # `OKSTRA_CODEX_VERIFIER_EXTRA_DIRS` env var (colon-separated absolute
42
+ # paths; empty disables the verifier extension entirely).
43
+ #
32
44
  # For linked worktrees (the okstra implementation default), the per-worktree
33
45
  # git metadata (index, HEAD, refs) and the shared object database live in the
34
46
  # main repository's `.git` directory — OUTSIDE the worktree-path. Without
@@ -107,6 +119,25 @@ if [[ -n "$worktree_path" ]]; then
107
119
  fi
108
120
  fi
109
121
 
122
+ # Verifier-role sandbox extension (see header comment for rationale).
123
+ if [[ "$role" == "verifier" ]]; then
124
+ if [[ -n "${OKSTRA_CODEX_VERIFIER_EXTRA_DIRS+x}" ]]; then
125
+ verifier_extra_raw="$OKSTRA_CODEX_VERIFIER_EXTRA_DIRS"
126
+ else
127
+ verifier_extra_raw="${HOME}/.cargo:${HOME}/.rustup"
128
+ fi
129
+ if [[ -n "$verifier_extra_raw" ]]; then
130
+ IFS=':' read -r -a verifier_extra_dirs <<< "$verifier_extra_raw"
131
+ for verifier_dir in "${verifier_extra_dirs[@]}"; do
132
+ [[ -z "$verifier_dir" ]] && continue
133
+ if [[ -d "$verifier_dir" ]]; then
134
+ verifier_dir_abs=$(cd "$verifier_dir" && pwd -P)
135
+ extra_args+=(--add-dir "$verifier_dir_abs")
136
+ fi
137
+ done
138
+ fi
139
+ fi
140
+
110
141
  # Derive a live-progress log path next to the prompt. The codex CLI streams
111
142
  # its progress over stdout/stderr, but the caller (codex-worker subagent)
112
143
  # only polls `BashOutput` on a 60s cadence — so without a sideband, a 10–30
@@ -141,6 +172,17 @@ if [[ -n "${TMUX:-}" ]]; then
141
172
  if [[ -n "$trace_pane" ]]; then
142
173
  tmux select-pane -t "$trace_pane" -T "codex-${role}-trace" 2>/dev/null || true
143
174
  tmux last-pane 2>/dev/null || true
175
+ # Register the spawned pane so the `SessionEnd` hook (see
176
+ # `okstra-trace-cleanup.sh`) can kill it when the caller's Claude
177
+ # session exits. Scope by caller `$TMUX_PANE` — the pane Claude itself
178
+ # is attached to — so concurrent Claude instances in the same tmux
179
+ # session do not stomp each other's trace panes.
180
+ if [[ -n "${TMUX_PANE:-}" ]]; then
181
+ registry_dir="${TMPDIR:-/tmp}/okstra-trace-panes"
182
+ mkdir -p "$registry_dir" 2>/dev/null || true
183
+ safe_pane="${TMUX_PANE//[^A-Za-z0-9]/_}"
184
+ printf '%s\n' "$trace_pane" >> "$registry_dir/${safe_pane}.list" 2>/dev/null || true
185
+ fi
144
186
  fi
145
187
  fi
146
188
 
@@ -121,6 +121,13 @@ if [[ -n "${TMUX:-}" ]]; then
121
121
  if [[ -n "$trace_pane" ]]; then
122
122
  tmux select-pane -t "$trace_pane" -T "gemini-${role}-trace" 2>/dev/null || true
123
123
  tmux last-pane 2>/dev/null || true
124
+ # See `okstra-codex-exec.sh` for the registry rationale — kept in lock-step.
125
+ if [[ -n "${TMUX_PANE:-}" ]]; then
126
+ registry_dir="${TMPDIR:-/tmp}/okstra-trace-panes"
127
+ mkdir -p "$registry_dir" 2>/dev/null || true
128
+ safe_pane="${TMUX_PANE//[^A-Za-z0-9]/_}"
129
+ printf '%s\n' "$trace_pane" >> "$registry_dir/${safe_pane}.list" 2>/dev/null || true
130
+ fi
124
131
  fi
125
132
  fi
126
133
 
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # okstra-trace-cleanup.sh — kill tmux trace panes spawned by okstra worker
4
+ # wrappers (`okstra-codex-exec.sh`, `okstra-gemini-exec.sh`) for the current
5
+ # Claude Code session.
6
+ #
7
+ # Invoked from the `SessionEnd` hook in
8
+ # `templates/reports/settings.template.json`. The wrappers register every
9
+ # pane they split into a registry file keyed by the caller's `$TMUX_PANE`
10
+ # (i.e. the pane Claude itself is attached to). On Claude `/exit`, the hook
11
+ # runs in that same pane's env, reads its own registry file, and kills each
12
+ # registered pane.
13
+ #
14
+ # Scoping by caller `$TMUX_PANE` (not by tmux session) lets multiple Claude
15
+ # instances coexist in the same tmux session without stomping each other's
16
+ # trace panes.
17
+ #
18
+ # Failures are tolerated silently — a stale pane id, missing $TMUX, or a
19
+ # locked tmux client must never prevent Claude from exiting cleanly.
20
+
21
+ set -u
22
+
23
+ # No tmux pane context → nothing to clean.
24
+ if [[ -z "${TMUX_PANE:-}" ]]; then
25
+ exit 0
26
+ fi
27
+
28
+ registry_dir="${TMPDIR:-/tmp}/okstra-trace-panes"
29
+ safe_pane="${TMUX_PANE//[^A-Za-z0-9]/_}"
30
+ registry_file="$registry_dir/${safe_pane}.list"
31
+
32
+ if [[ ! -f "$registry_file" ]]; then
33
+ exit 0
34
+ fi
35
+
36
+ while IFS= read -r pane_id; do
37
+ [[ -n "$pane_id" ]] || continue
38
+ tmux kill-pane -t "$pane_id" 2>/dev/null || true
39
+ done < "$registry_file"
40
+
41
+ rm -f "$registry_file" 2>/dev/null || true
42
+ exit 0
@@ -19,8 +19,14 @@
19
19
  - acceptance blockers
20
20
  - residual risk
21
21
  - final release recommendations
22
+ - Pre-verification entry gate (mandatory — refuse to start if any item fails):
23
+ - the task brief MUST cite the originating `implementation` final-report path under `## Source Implementation Report`. The lead opens that file and confirms it includes `Plan link & approval evidence`, `Commit list`, `Diff summary`, `Validation evidence`, and `Routing recommendation for final-verification`.
24
+ - the task brief MUST identify the worktree / checkout under verification and the implementation base ref. If the implementation report names a task worktree, final-verification MUST inspect that same worktree rather than the caller's original checkout.
25
+ - the lead MUST capture `git rev-parse HEAD`, `git status --short`, and `git diff --stat <implementation-base>..HEAD` from the verification worktree before dispatching workers. These values are the verification target and must be cited in the final report.
26
+ - if the cited implementation report is missing, lacks commits for delivered code changes, or the current checkout does not match the implementation report's commit list / diff summary, the run MUST end with status `blocked` and route back to `implementation` or `implementation-planning` rather than verifying an ambiguous target.
22
27
  - Required deliverable shape (final report, in addition to the standard sections):
23
- - **Verdict vocabulary**: Section 2 (`Final Verdict`) MUST state exactly one of `accepted`, `conditional-accept`, or `blocked`. `conditional-accept` requires an explicit, exhaustive list of conditions; ambiguous verdicts ("looks good", "mostly ready") are not allowed.
28
+ - **Source Implementation Report**: relative path of the originating `implementation` final-report file, the quoted commit list / diff summary used as the verification target, the worktree path inspected, and the base/head SHAs captured at run start.
29
+ - **Verdict vocabulary**: Section 2 (`Final Verdict`) MUST include a `Verdict Token` field whose value is exactly one of `accepted`, `conditional-accept`, or `blocked`. `conditional-accept` requires an explicit, exhaustive list of conditions; ambiguous verdicts ("looks good", "mostly ready") are not allowed.
24
30
  - **Acceptance Blockers block** (under section 4): one row per blocker with `id`, `severity` (`critical` / `major` / `minor`), evidence (file path, log excerpt, or test output), and the recommended follow-up phase (`error-analysis` or `implementation-planning`). Empty block is acceptable and preferred — render the single line `- No acceptance blockers found.`
25
31
  - **Residual Risk block** (under section 4): risks that are not blockers but should be tracked, each with mitigation owner and a trigger that would escalate them to a blocker.
26
32
  - **Validation Evidence**: for every requirement in the originating plan or task brief, cite the artifact (commit SHA, test output, log line, MCP SELECT result) that demonstrates coverage. Paraphrased "verified" claims without an artifact are rejected.
@@ -30,7 +36,7 @@
30
36
  - Clarification request policy (phase-specific addendum — shared policy is in `_common-contract.md`):
31
37
  - populate section 5 only when a blocker hinges on information only the user can supply (deployment intent, intended target environment, business-rule interpretation)
32
38
  - Self-review pass before finalising the report (`Claude lead` runs this; do not delegate to a generic subagent):
33
- 1. **Verdict precision** — section 2 uses one of the three allowed verdict tokens; `conditional-accept` lists every condition as an actionable item.
39
+ 1. **Verdict precision** — section 2 includes `Verdict Token` with one of the three allowed verdict tokens; `conditional-accept` lists every condition as an actionable item.
34
40
  2. **Blocker traceability** — every blocker cites a concrete artifact (file:line, log excerpt, test exit code, MCP SELECT). Blockers without evidence are demoted to residual risk or removed.
35
41
  3. **Coverage check** — every requirement in the originating plan/task brief is either marked covered (with artifact) or listed as a blocker. No silent omissions.
36
42
  4. **Verifier dissent preserved** — if workers reach different verdicts, the disagreement is visible in section 1.2; synthesis hides nothing.
@@ -53,7 +53,7 @@
53
53
  - each step is one action completable in roughly 2–5 minutes (e.g. "write the failing test for X", "run it to confirm it fails", "implement minimal code", "run test to confirm pass", "commit")
54
54
  - every step names exact file paths and exact commands; for code steps, include the actual code or the diff sketch — not a description
55
55
  - prefer TDD ordering (failing test → implementation → green → commit) when the touched area has or can have tests
56
- - dependency / migration risk assessment (ordering constraints, data backfills, feature-flag prerequisites, cross-team coordination)
56
+ - dependency / migration risk assessment (ordering constraints, data backfills, feature-flag prerequisites, repo-internal sequencing)
57
57
  - validation checklist (pre / mid / post) — each item is an exact command or observable outcome
58
58
  - rollback strategy — exact revert path (commits, flags, migrations) and the signal that triggers rollback
59
59
  - explicit `User Approval Request (사용자 승인 게이트)` block placed at the **top of the report** with a single canonical checkbox marker `- [ ] Approved` (user toggles to `- [x] Approved` to authorise the next `implementation` run). Section `4.5.8` is retained only as a back-pointer to this top block for validator/key-substring compatibility — it must NOT carry an independent marker.
@@ -1,52 +1,49 @@
1
1
  # Release Handoff Profile
2
2
 
3
- - Purpose: take an `accepted` final-verification verdict and turn it into a delivered commit and/or pull request, with explicit user selection at every mutating step
4
- - **Execution model: single-lead, no worker dispatch.** This phase is a thin orchestrator over `git` / `gh`; it does NOT run team-mode, does NOT call `TeamCreate`, does NOT dispatch analysis or drafter sub-agents, and does NOT run convergence. The Claude lead performs every step inline (drafting commit/PR text, asking the user, running git, writing the final report) — see "Lead-only contract" below.
3
+ - Purpose: take an `accepted` final-verification verdict for an already-committed implementation branch and turn it into a delivered push and/or pull request, with explicit user selection at every mutating step
4
+ - **Execution model: single-lead, no worker dispatch.** This phase is a thin orchestrator over `git` / `gh`; it does NOT run team-mode, does NOT call `TeamCreate`, does NOT dispatch analysis or drafter sub-agents, and does NOT run convergence. The Claude lead performs every step inline (drafting PR text, asking the user, running git / gh, writing the final report) — see "Lead-only contract" below.
5
5
  - Required workers: *(none — this profile intentionally has no `- Required workers:` block; the run is executed entirely by the Claude lead)*
6
6
  - Lead-only contract (replaces the shared team contract for this phase):
7
7
  - The Claude lead is the sole agent for this run. No `Agent(...)` worker dispatch, no `TeamCreate`, no parallel sub-agents, no convergence loop.
8
- - The lead drafts the commit message and PR body **inline** by reading the run brief, the cited final-verification report, and `git diff <base>..HEAD --stat`. No drafter worker is dispatched.
8
+ - The lead drafts the PR title and PR body **inline** by reading the run brief, the cited final-verification report, `git log --oneline <base>..HEAD`, and `git diff <base>..HEAD --stat`. No drafter worker is dispatched.
9
9
  - The lead authors the final-report file directly (no `Report writer worker` dispatch). The report still conforms to the standard `okstra-final-report.template.md` structure, including the `## 4.6 Release Handoff Deliverables` section.
10
10
  - The shared anti-escalation rule from the common contract still applies: do not start any other lifecycle phase from inside this run.
11
11
  - The shared "authority & permissions assumption" rule from the common contract still applies: assume the user holds every permission needed; do not block on hypothetical approvals.
12
12
  - The shared "MCP read-only" rule still applies if the brief lists MCP servers, though most release-handoff runs do not use MCP.
13
13
  - Pre-handoff entry gate (mandatory — refuse to start if any item fails):
14
- - the task brief MUST cite the originating `final-verification` final-report path under `## Source Verification Report`. The lead opens that file and confirms section `## 2. Final Verdict` contains exactly the token `accepted`.
15
- - if the verdict is `conditional-accept`, `blocked`, or any other token (including ambiguous phrasing like "looks good"), the run MUST end immediately with status `blocked` and a routing recommendation back to `error-analysis` or `implementation-planning`. Do NOT prompt the user; do NOT run any git command.
16
- - the lead MUST capture `git status --short` and confirm the working tree is clean OR contains only the files listed in the prior `implementation` run's `Out-of-plan edits` block. Unexpected dirty state aborts the run.
14
+ - the task brief MUST cite the originating `final-verification` final-report path under `## Source Verification Report`. The lead opens that file and confirms section `## 2. Final Verdict` contains a `Verdict Token` field whose value is exactly `accepted`.
15
+ - if the verdict is `conditional-accept`, `blocked`, or any other token (including ambiguous phrasing like "looks good"), the run MUST end immediately with status `blocked` and a routing recommendation back to `error-analysis` or `implementation-planning`. Do NOT prompt the user; Do NOT run any git command.
16
+ - the lead MUST capture `git status --short` and confirm the working tree is clean. Dirty state aborts the run; release-handoff packages the commits produced by `implementation`, it does not stage or commit changes.
17
17
  - the lead MUST capture `git rev-parse --abbrev-ref HEAD` and record it as the **feature branch**. If the current branch is itself `main`, `master`, `prod`, `preprod`, `staging`, or `dev`, the run MUST end immediately — release-handoff never operates on a base branch.
18
+ - the lead MUST confirm `git log --oneline <base>..HEAD` contains at least one implementation commit. If it is empty, the run MUST end with status `blocked` and route back to `implementation`.
18
19
  - User interaction protocol (Claude lead — performed in order, using `AskUserQuestion` or the equivalent interactive prompt):
19
20
  1. **Action selection** — present three choices and capture exactly one:
20
- - `commit only` — stage and commit the working-tree changes locally; no push, no PR.
21
- - `commit + PR` — commit, push the feature branch, then open a pull request.
21
+ - `local only` — record the accepted, already-committed branch and end without push or PR.
22
+ - `push + PR` — push the feature branch, then open or reuse a pull request.
22
23
  - `skip` — record the verified state and end the run without any git command.
23
24
  If the user picks `skip`, route directly to the final-report self-review pass.
24
- 2. **PR base branch** (only when the user picked `commit + PR`) — present six options and capture exactly one:
25
+ 2. **PR base branch** (only when the user picked `push + PR`) — present six options and capture exactly one:
25
26
  - `staging`
26
27
  - `preprod`
27
- - `prod`
28
28
  - `main`
29
- - `dev`
30
29
  - `직접 입력` (free-form branch name; lead validates the name exists on origin via `git ls-remote --heads origin <name>` and re-asks on failure)
31
30
  The chosen base MUST NOT equal the feature branch. If it does, re-ask.
32
- 3. **Commit message + PR body confirmation** — show the lead's inline draft verbatim and capture one of:
31
+ 3. **PR title + PR body confirmation** — show the lead's inline draft verbatim and capture one of:
33
32
  - `use as-is` — proceed with the drafted text.
34
33
  - `edit then proceed` — accept inline edits from the user, then proceed with the edited text.
35
- - `cancel` — end the run without executing any git command; record the cancellation in the final report.
34
+ - `cancel` — end the run without executing push or PR commands; record the cancellation in the final report.
36
35
  - Inline drafting rules (Claude lead):
37
- - read the run brief, the cited final-verification report, and `git diff <base>..HEAD --stat` to ground the drafted text in actual changes.
36
+ - read the run brief, the cited final-verification report, `git log --oneline <base>..HEAD`, and `git diff <base>..HEAD --stat` to ground the drafted text in actual committed changes.
38
37
  - produce **two artifacts** before showing them to the user:
39
- 1. **Commit message** — a single message in Conventional Commits style (`<type>(<scope>): <subject>` + optional body + optional footer). When `commit + PR` will be opened against a `release-please`-managed repo, the type MUST match a configured changelog section (`feat` / `fix` / `perf` / `revert` / `deps` / `docs` / `refactor` / `build` / `ci` / `chore` / `test`).
38
+ 1. **PR title** — by default the subject of the most recent implementation commit, or a concise Conventional Commits-style summary of the committed range.
40
39
  2. **PR body** — markdown with sections `## Summary`, `## Changes`, `## Test plan`, `## Linked issues` (omit a section only if it is genuinely empty).
41
- - if the diff is empty or no commit can be produced (working tree already matches the base), record "no staged changes; commit skipped" in the final report and skip `git commit` while still proceeding to the PR step if requested.
42
40
  - Allowed actions during the run (Claude lead only):
43
41
  - read-only inspection: `git status`, `git status --short`, `git diff`, `git log`, `git rev-parse`, `git ls-remote --heads origin <name>`, `gh pr list --head <branch>`, `gh pr view <url>`.
44
- - local commit: `git add -- <path>...` (prefer explicit file paths over `git add -A` / `git add .`), `git commit -m "<message>"`. Re-use the user-confirmed message exactly.
45
- - feature-branch push (only when the user picked `commit + PR`): `git push -u origin <current-branch>`. The pushed ref MUST be the feature branch never the chosen base branch.
46
- - PR creation (only when the user picked `commit + PR` AND no PR with the same head already exists on origin): `gh pr create --base <chosen-base> --head <current-branch> --title "<title>" --body "<body>"`. The title is the commit message subject by default; the body is the user-confirmed PR body.
42
+ - feature-branch push (only when the user picked `push + PR`): `git push -u origin <current-branch>`. The pushed ref MUST be the feature branch — never the chosen base branch.
43
+ - PR creation (only when the user picked `push + PR` AND no PR with the same head already exists on origin): `gh pr create --base <chosen-base> --head <current-branch> --title "<title>" --body "<body>"`. The title and body are the user-confirmed PR draft.
47
44
  - PR reuse: if `gh pr list --head <branch> --state open --json url --jq '.[0].url'` returns a URL, treat that PR as already existing — record the URL in the final report and SKIP `gh pr create`.
48
- - Idempotency: if `git diff --cached` and `git diff` are both empty (nothing to commit), record "no staged changes; commit skipped" in the final report and skip `git commit` while still proceeding to the PR step if requested.
49
45
  - Forbidden actions (any occurrence → terminal status `contract-violated`):
46
+ - local commit commands of any kind (`git add`, `git commit`, `git restore --staged`, `git stash`). Commits belong to the prior `implementation` phase.
50
47
  - any of the following git push variants, regardless of intent or whether the user said "force it":
51
48
  - `git push --force`
52
49
  - `git push --force-with-lease`
@@ -54,36 +51,37 @@
54
51
  - `git push +<refspec>`
55
52
  - any other invocation that rewrites remote history
56
53
  - pushing directly to a base branch — i.e. `git push origin <branch>` where `<branch>` is `main`, `master`, `prod`, `preprod`, `staging`, `dev`, or the branch the user chose as the PR base in this run. The only permitted push target is the current feature branch.
57
- - bypassing repo safeguards: `--no-verify` / `-n` on `git commit` or `git push`, disabling GPG signing via `-c commit.gpgsign=false` / `--no-gpg-sign`, or any equivalent flag-based hook bypass.
54
+ - bypassing repo safeguards: `--no-verify` / `-n` on `git push`, disabling safeguards via equivalent flags, or any hook bypass.
58
55
  - release-publishing commands: `gh release create`, `gh release edit`, `npm publish`, `cargo publish`, `pip publish`, `twine upload`, `docker push`, `terraform apply`, `kubectl apply` against any non-local cluster.
59
56
  - source-code edits, refactors, or any modification to files outside the run's own artifact directories (`reports/`, `prompts/`, `state/`, `manifests/`, `worker-results/`, `status/`, `sessions/`). The diff being shipped MUST be exactly what the prior `implementation` run produced; release-handoff packages it, it does not re-author it.
60
- - executing any mutating command the user did NOT select. Examples: opening a PR when the user picked `commit only`; running `git commit` when the user picked `skip`; switching the PR base branch silently after the user already chose one.
57
+ - executing any mutating command the user did NOT select. Examples: opening a PR when the user picked `local only`; pushing when the user picked `skip`; switching the PR base branch silently after the user already chose one.
61
58
  - retrying a failed git / gh command with weaker safety flags. If `git push` fails with non-fast-forward, the lead MUST stop, explain the failure to the user, and ask for instructions — it MUST NOT add `--force`.
62
59
  - `TeamCreate`, `Agent(...)` dispatch of any kind, or any other parallel sub-agent fan-out. This phase runs entirely under the Claude lead.
63
60
  - silently treating an unrecognised user reply as one of the menu options. If the user's answer does not match a presented choice, re-ask the question verbatim.
64
61
  - Required deliverable shape (final report, in addition to the standard sections):
65
- - **Source Verification Report**: relative path of the originating `final-verification` final-report file plus the literal quoted `## 2. Final Verdict` line that read `accepted`.
62
+ - **Source Verification Report**: relative path of the originating `final-verification` final-report file plus the literal quoted `Verdict Token` row whose value is `accepted`.
66
63
  - **Feature Branch & Working-Tree State**: branch name from `git rev-parse --abbrev-ref HEAD`, output of `git status --short` at run start.
67
64
  - **User Selections**: a block recording each prompt and the user's verbatim answer.
68
- - Q1 action: `commit only` | `commit + PR` | `skip`.
65
+ - Q1 action: `local only` | `push + PR` | `skip`.
69
66
  - Q2 PR base (if applicable): the chosen branch and how it was selected (menu pick vs free-form input).
70
- - Q3 message/body: `use as-is` | `edit then proceed` (with a diff between the lead's draft and the final text) | `cancel`.
67
+ - Q3 title/body: `use as-is` | `edit then proceed` (with a diff between the lead's draft and the final text) | `cancel`.
71
68
  - **Executed Commands**: every git / gh command the lead actually ran, with its exit code and a one-line stdout/stderr summary. Read-only inspection commands MAY be summarised; mutating commands MUST be listed verbatim.
72
- - **Commit List**: each commit SHA (short and full), its subject line, and the files it touched. If no commit was produced (idempotent no-op), state `- No commit was produced (working tree had no staged changes).`
69
+ - **Commit List**: each existing implementation commit in `git log <base>..HEAD`, with short/full SHA, subject line, and touched files. Release-handoff MUST NOT create new commits.
73
70
  - **Pull Request Outcome**: one of
74
- - `- No PR action requested.` (user picked `commit only` or `skip`)
71
+ - `- No PR action requested.` (user picked `local only` or `skip`)
75
72
  - `- PR created: <url>` with title and base branch
76
73
  - `- PR reused: <url>` when an existing PR was found via `gh pr list`
77
74
  - `- PR creation skipped: <reason>` for any user-driven cancellation
78
75
  - **Routing recommendation**: explicit `done` token, since release-handoff is the terminal lifecycle phase. If the run ended in `skip` or `cancel`, the recommendation MUST also state whether re-entry into release-handoff is appropriate.
79
76
  - Self-review pass before finalising the report (`Claude lead` runs this):
80
- 1. **Entry-gate audit** — section 2 cites the originating final-verification report path and the literal `accepted` verdict line. If either is missing, the run is invalid and MUST be re-routed to `final-verification`.
77
+ 1. **Entry-gate audit** — section 2 cites the originating final-verification report path and the literal `Verdict Token` row with value `accepted`. If either is missing, the run is invalid and MUST be re-routed to `final-verification`.
81
78
  2. **User-selection traceability** — every executed mutating command maps to a user selection captured in the report. Any mutating command without a corresponding user answer is a contract violation.
82
79
  3. **Forbidden-action audit** — scan the run's session transcripts (`git`, `gh` invocations) for every entry in the Forbidden actions list above. Any occurrence means the run has crossed into unsafe territory and MUST be flagged as `contract-violated`.
83
80
  4. **Push-target audit** — for every `git push` recorded, confirm the refspec resolves to the feature branch, not the base branch.
84
81
  5. **Idempotency check** — if a PR with the same head already existed at run start, confirm the report records `PR reused` rather than a fresh `gh pr create` invocation.
85
82
  - Non-goals:
86
83
  - re-litigating the final-verification verdict — release-handoff trusts the cited `accepted` verdict and does not reopen acceptance checks.
84
+ - creating, amending, squashing, or rewriting commits. Commit production belongs to `implementation`.
87
85
  - opening additional PRs, releases, or deployments beyond the single PR the user chose to create.
88
86
  - merging the PR. Merging is a separate, manual step performed by the user (or by repo automation) after release-handoff ends; the lead MUST NOT call `gh pr merge`.
89
87
  - escalating beyond the menu choices on user phrasing — every mutating action requires an explicit menu selection.
@@ -10,7 +10,7 @@
10
10
  {{INCLUDE:_common-contract.md}}
11
11
  - Primary focus areas:
12
12
  - classify the work as bugfix, feature, improvement, refactor, or ops-change
13
- - determine whether `error-analysis`, `implementation-planning`, or a direct implementation handoff is the next safe step
13
+ - determine whether `error-analysis` or `implementation-planning` is the next safe step; direct `implementation` handoff is not a valid routing target because implementation requires an approved `implementation-planning` report
14
14
  - identify missing materials that block reliable routing
15
15
  - define task continuity expectations for long-running work under the same task key
16
16
  - capture approval or confirmation points before the next phase starts
@@ -47,6 +47,74 @@ def _write_json(path: Path, payload: dict) -> None:
47
47
  _write_text(path, json.dumps(payload, indent=2, ensure_ascii=False) + "\n")
48
48
 
49
49
 
50
+ _FM_DEFAULT = "no-classification"
51
+
52
+ _FM_TAGS_BASE = ["obsidian", "okstra"]
53
+
54
+ _FM_TAGS_CATALOG: dict[str, list[str]] = {
55
+ "task-brief": ["task-brief"],
56
+ "task-index": ["task-index"],
57
+ "error-analysis-input": ["error-analysis", "input"],
58
+ "implementation-input": ["implementation", "input"],
59
+ "implementation-planning-input": ["implementation-planning", "input"],
60
+ "final-verification-input": ["final-verification", "input"],
61
+ "release-handoff-input": ["release-handoff", "input"],
62
+ "quick-input": ["quick", "input"],
63
+ "final-report": ["final-report"],
64
+ "schedule": ["schedule"],
65
+ "prd-brief": ["prd", "brief"],
66
+ }
67
+
68
+
69
+ def _fm_scalar(value: str, default: str = _FM_DEFAULT) -> str:
70
+ v = (value or "").strip()
71
+ return v if v else default
72
+
73
+
74
+ def _fm_array(values: list[str], extras: list[str] | None = None) -> str:
75
+ items = [str(v).strip() for v in values if v and str(v).strip()]
76
+ if extras:
77
+ items.extend(str(e).strip() for e in extras if str(e).strip())
78
+ if not items:
79
+ return "[]"
80
+ return "[" + ", ".join(f'"{v}"' for v in items) + "]"
81
+
82
+
83
+ def _fm_tags(doc_type: str) -> str:
84
+ extra = _FM_TAGS_CATALOG.get((doc_type or "").strip(), [])
85
+ return _fm_array(_FM_TAGS_BASE + list(extra))
86
+
87
+
88
+ def _doc_type_from_template_path(template_path: str) -> str:
89
+ name = Path(template_path).name
90
+ if name.endswith(".template.md"):
91
+ stem = name[: -len(".template.md")]
92
+ else:
93
+ stem = Path(name).stem
94
+ if stem == "brief" and "prd" in Path(template_path).parts:
95
+ return "prd-brief"
96
+ return stem
97
+
98
+
99
+ def _frontmatter_mapping(ctx: dict) -> dict:
100
+ task_id = (ctx.get("TASK_ID") or "").strip()
101
+ project_id = (ctx.get("PROJECT_ID") or "").strip()
102
+ task_group = (ctx.get("TASK_GROUP") or "").strip()
103
+ task_key = (ctx.get("TASK_KEY") or "").strip()
104
+ task_date = (ctx.get("TASK_DATE") or "").strip()
105
+ doc_type = (ctx.get("DOC_TYPE") or "").strip()
106
+ return {
107
+ "{{TASK_KEY}}": _fm_scalar(task_key),
108
+ "{{TASK_ID}}": _fm_scalar(task_id),
109
+ "{{PROJECT_ID}}": _fm_scalar(project_id),
110
+ "{{TASK_GROUP}}": _fm_scalar(task_group),
111
+ "{{TASK_DATE}}": _fm_scalar(task_date),
112
+ "{{FM_ID}}": _fm_array([task_id, project_id, task_group]),
113
+ "{{FM_ALIASES}}": _fm_array([task_id, project_id, task_group]),
114
+ "{{FM_TAGS}}": _fm_tags(doc_type),
115
+ }
116
+
117
+
50
118
  def _resolve_workers(ctx: dict) -> list[str]:
51
119
  return [w.strip() for w in ctx.get("SELECTED_REVIEWERS", "").split(",") if w.strip()]
52
120
 
@@ -830,6 +898,10 @@ def render_task_index(template_path: str, output_path: str, ctx: dict) -> None:
830
898
  mapping = {
831
899
  "{{TASK_KEY}}": task_manifest.get("taskKey", ctx.get("TASK_KEY", "")),
832
900
  "{{TASK_TYPE}}": task_manifest.get("taskType", ctx.get("ANALYSIS_TYPE", "")),
901
+ "{{TASK_DATE}}": ctx.get("TASK_DATE", ""),
902
+ "{{PROJECT_ID}}": ctx.get("PROJECT_ID", ""),
903
+ "{{TASK_GROUP}}": ctx.get("TASK_GROUP", ""),
904
+ "{{TASK_ID}}": ctx.get("TASK_ID", ""),
833
905
  "{{CURRENT_TASK_STATUS}}": task_manifest.get("currentStatus", ctx.get("CURRENT_TASK_STATUS", "")),
834
906
  "{{CURRENT_RUN_STATUS}}": task_manifest.get("latestRunStatus", ctx.get("CURRENT_RUN_STATUS", "")),
835
907
  "{{RELATED_TASKS_INLINE}}": ctx.get("RELATED_TASKS_INLINE", "None"),
@@ -870,6 +942,9 @@ def render_task_index(template_path: str, output_path: str, ctx: dict) -> None:
870
942
  "{{WORKFLOW_PHASE_STATE_LINES}}": "\n".join(phase_state_lines),
871
943
  "{{WORKFLOW_LAST_SAFE_CHECKPOINT_LINES}}": "\n".join(checkpoint_lines),
872
944
  }
945
+ fm_ctx = dict(ctx)
946
+ fm_ctx.setdefault("DOC_TYPE", _doc_type_from_template_path(template_path))
947
+ mapping.update(_frontmatter_mapping(fm_ctx))
873
948
  rendered = template
874
949
  for k, v in mapping.items():
875
950
  rendered = rendered.replace(k, v)
@@ -1035,10 +1110,6 @@ def render_template_file(template_path: str, output_path: str, ctx: dict) -> Non
1035
1110
 
1036
1111
  mapping = {
1037
1112
  "{{TEAM_CREATION_GATE}}": team_creation_gate_block,
1038
- "{{PROJECT_ID}}": ctx.get("PROJECT_ID", ""),
1039
- "{{TASK_GROUP}}": ctx.get("TASK_GROUP", ""),
1040
- "{{TASK_ID}}": ctx.get("TASK_ID", ""),
1041
- "{{TASK_KEY}}": ctx.get("TASK_KEY", ""),
1042
1113
  "{{TASK_TYPE}}": ctx.get("ANALYSIS_TYPE", ""),
1043
1114
  "{{ANALYSIS_PROFILE}}": ctx.get("REVIEW_PROFILE", ""),
1044
1115
  "{{ANALYSIS_TYPE}}": ctx.get("ANALYSIS_TYPE", ""),
@@ -1143,6 +1214,9 @@ def render_template_file(template_path: str, output_path: str, ctx: dict) -> Non
1143
1214
  "{{EXECUTOR_WORKTREE_STATUS}}": ctx.get("EXECUTOR_WORKTREE_STATUS", ""),
1144
1215
  "{{EXECUTOR_WORKTREE_NOTE}}": ctx.get("EXECUTOR_WORKTREE_NOTE", ""),
1145
1216
  }
1217
+ fm_ctx = dict(ctx)
1218
+ fm_ctx.setdefault("DOC_TYPE", _doc_type_from_template_path(template_path))
1219
+ mapping.update(_frontmatter_mapping(fm_ctx))
1146
1220
  rendered = template
1147
1221
  for k, v in mapping.items():
1148
1222
  rendered = rendered.replace(k, v)
@@ -30,6 +30,10 @@ def _now_iso() -> str:
30
30
  return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
31
31
 
32
32
 
33
+ def _now_task_date() -> str:
34
+ return datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
35
+
36
+
33
37
  def _okstra_home() -> Path:
34
38
  home = os.environ.get("OKSTRA_HOME")
35
39
  if home:
@@ -109,6 +113,7 @@ def compute_and_write_run_context(
109
113
  run_seq_override=run_seq_override,
110
114
  )
111
115
  ctx["RUN_TIMESTAMP_ISO"] = _now_iso()
116
+ ctx["TASK_DATE"] = _now_task_date()
112
117
  run_manifests_dir = Path(ctx["RUN_MANIFESTS_DIR"])
113
118
  ctx_path = run_manifests_dir / _run_context_filename(
114
119
  ctx["TASK_TYPE_SEGMENT"], ctx["RUN_MANIFESTS_SEQ"])
@@ -124,23 +124,24 @@ PHASE_RULES: dict[str, dict[str, str]] = {
124
124
  },
125
125
  "release-handoff": {
126
126
  "allowed": (
127
- " - entering this phase only when the cited final-verification report's verdict is exactly `accepted`\n"
128
- " - asking the user (via `AskUserQuestion` / interactive prompt) which delivery action to take: `commit only`, `commit + PR`, or `skip` (end the run)\n"
127
+ " - entering this phase only when the cited final-verification report's `Verdict Token` is exactly `accepted`\n"
128
+ " - asking the user (via `AskUserQuestion` / interactive prompt) which delivery action to take: `local only`, `push + PR`, or `skip` (end the run)\n"
129
129
  " - asking the user to pick a PR base branch from `staging` | `preprod` | `prod` | `main` | `dev` | a user-supplied branch name\n"
130
- " - drafting commit message(s) and PR body **inline as the Claude lead** (no drafter worker, no `Agent` dispatch); the lead reviews its own draft with the user before any git command runs\n"
131
- " - local git operations: `git status`, `git diff`, `git log`, `git add`, `git commit -m`\n"
130
+ " - drafting PR title and PR body **inline as the Claude lead** (no drafter worker, no `Agent` dispatch); the lead reviews its own draft with the user before any mutating git / gh command runs\n"
131
+ " - read-only git inspection: `git status`, `git diff`, `git log`, `git rev-parse`\n"
132
132
  " - pushing the current feature branch to its origin remote via `git push -u origin <current-branch>` (the feature branch only — NEVER the base branch)\n"
133
133
  " - creating a pull request via `gh pr create --base <chosen-base> --head <current-branch>`; if a PR with the same head already exists, surface its URL and skip creation\n"
134
134
  " - the lead writes the final report directly (no `Report writer worker` dispatch); the report still conforms to the standard final-report template"
135
135
  ),
136
136
  "forbidden": (
137
- " - entering this phase when the cited final-verification verdict is `conditional-accept` or `blocked`, or when no final-verification report is cited\n"
138
- " - any source-code edit, refactor, or scope expansion beyond what is strictly needed to author commit messages / PR descriptions (the changes themselves are inherited from prior `implementation` runs)\n"
137
+ " - entering this phase when the cited final-verification `Verdict Token` is `conditional-accept` or `blocked`, or when no final-verification report is cited\n"
138
+ " - any source-code edit, refactor, or scope expansion beyond what is strictly needed to author PR descriptions (the changes themselves are inherited from prior `implementation` runs)\n"
139
+ " - local commit commands (`git add`, `git commit`, `git stash`, `git restore --staged`); commits are produced by `implementation`, not release-handoff\n"
139
140
  " - `git push --force`, `git push --force-with-lease`, or any rewriting of remote history\n"
140
141
  " - pushing directly to a base branch (`main`, `master`, `prod`, `preprod`, `staging`, `dev`, or any branch the user named as the PR base)\n"
141
142
  " - bypassing git hooks (`--no-verify`, `-n`), bypassing GPG signing, or otherwise disabling repo-configured safeguards\n"
142
143
  " - release-publishing commands: `gh release`, `npm publish`, `cargo publish`, `pip publish`, `docker push`, `terraform apply`, `kubectl apply` against non-local clusters\n"
143
- " - executing any command the user did NOT select (e.g. if the user picked `commit only`, opening a PR is forbidden; if the user picked `skip`, the run ends without git commands)\n"
144
+ " - executing any command the user did NOT select (e.g. if the user picked `local only`, opening a PR is forbidden; if the user picked `skip`, the run ends without git commands)\n"
144
145
  " - `TeamCreate`, `Agent(...)` worker dispatch, parallel sub-agent fan-out, or any team-mode orchestration — this phase runs single-lead\n"
145
146
  " - silently retrying a failed git/gh command with weaker flags (e.g. retrying `git push` with `--force` after a non-fast-forward rejection)"
146
147
  ),