okstra 0.20.0 → 0.21.0

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 (39) hide show
  1. package/docs/kr/architecture.md +1 -1
  2. package/docs/kr/performance-improvement-plan-v2.md +330 -0
  3. package/docs/kr/performance-improvement-plan.md +125 -0
  4. package/docs/project-structure-overview.md +386 -0
  5. package/docs/superpowers/plans/2026-05-14-convergence-queue-pruning.md +1568 -0
  6. package/package.json +1 -1
  7. package/runtime/BUILD.json +2 -2
  8. package/runtime/agents/SKILL.md +7 -1
  9. package/runtime/agents/workers/codex-worker.md +6 -4
  10. package/runtime/agents/workers/gemini-worker.md +6 -4
  11. package/runtime/agents/workers/report-writer-worker.md +4 -0
  12. package/runtime/bin/okstra-codex-exec.sh +36 -6
  13. package/runtime/bin/okstra-gemini-exec.sh +6 -8
  14. package/runtime/prompts/profiles/final-verification.md +8 -2
  15. package/runtime/prompts/profiles/implementation-planning.md +1 -1
  16. package/runtime/prompts/profiles/release-handoff.md +26 -28
  17. package/runtime/prompts/profiles/requirements-discovery.md +1 -1
  18. package/runtime/python/okstra_ctl/render.py +78 -4
  19. package/runtime/python/okstra_ctl/run.py +0 -6
  20. package/runtime/python/okstra_ctl/run_context.py +5 -0
  21. package/runtime/python/okstra_ctl/workflow.py +8 -7
  22. package/runtime/python/okstra_ctl/worktree.py +155 -15
  23. package/runtime/python/okstra_token_usage/blocks.py +0 -2
  24. package/runtime/python/okstra_token_usage/claude.py +0 -2
  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 +83 -30
  39. package/runtime/templates/reports/task-brief.template.md +12 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okstra",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
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.0",
3
- "builtAt": "2026-05-13T15:25:34.223Z",
2
+ "package": "0.21.0",
3
+ "builtAt": "2026-05-14T11:48:06.992Z",
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" |
@@ -27,9 +27,11 @@ You are a Codex worker agent. Your job is to execute the OpenAI Codex CLI and re
27
27
 
28
28
  **Required form (uses the okstra wrapper to avoid redirect-triggered permission prompts):**
29
29
  ```bash
30
- $HOME/.okstra/bin/okstra-codex-exec.sh "<absolute-project-root>" "<assigned-model-execution-value>" "<absolute-prompt-history-path>" [<absolute-worktree-path>]
30
+ $HOME/.okstra/bin/okstra-codex-exec.sh "<absolute-project-root>" "<assigned-model-execution-value>" "<absolute-prompt-history-path>" [<absolute-worktree-path>] [<role>]
31
31
  ```
32
32
 
33
+ The fifth argument `<role>` is the trace-pane label suffix (`codex-<role>-trace`); pass the literal string `worker` for every dispatch from this subagent. The wrapper defaults to `worker` when the argument is omitted, but pass it explicitly so the dispatch is self-describing.
34
+
33
35
  The fourth argument is **mandatory for implementation phase** and optional otherwise. It must be the literal `EXECUTOR_WORKTREE_PATH` recorded in the run context; the wrapper forwards it to codex as `--add-dir`, which grants the codex sandbox write access to the worktree (where all implementation-phase mutations occur). Without it, codex's `workspace-write` sandbox is anchored only at `<project-root>` and rejects every Edit/Write that targets the worktree (EPERM), which is the failure pattern that originally motivated this argument.
34
36
 
35
37
  The wrapper internally runs:
@@ -73,7 +75,7 @@ The wrapper exists because Claude Code's Bash permission matcher rejects simple-
73
75
 
74
76
  **Dispatch (background, no foreground timeout):**
75
77
  ```bash
76
- $HOME/.okstra/bin/okstra-codex-exec.sh "<absolute-project-root>" "<assigned-model-execution-value>" "<absolute-prompt-history-path>" "<absolute-worktree-path>"
78
+ $HOME/.okstra/bin/okstra-codex-exec.sh "<absolute-project-root>" "<assigned-model-execution-value>" "<absolute-prompt-history-path>" "<absolute-worktree-path>" "worker"
77
79
  ```
78
80
  Call `Bash` with `run_in_background: true`. Capture the returned `bash_id` (a.k.a. `shell_id`). Pass the positional arguments verbatim — do NOT use environment variables, `cd`, `&&` chains, or pipes from `cat`. Substitute the literal extracted Project Root, model execution value, prompt-history path, and worktree path. The fourth argument is **mandatory for implementation phase** (extract from `EXECUTOR_WORKTREE_PATH` in the lead prompt's run context or the `**Worktree:**` / `cwd for every mutating command:` line) and **may be omitted only for non-implementation analysis phases** that do not mutate the worktree. Omitting it during implementation will cause every Edit/Write to fail with EPERM. The wrapper handles `-C`, `--add-dir`, `--model`, `--sandbox workspace-write`, the stdin redirect from the prompt file, and stderr suppression internally. Calling `codex exec` directly (without the wrapper) is an error in this skill: the redirect tokens disqualify the prefix match against `Bash(codex exec:*)` and produce a permission prompt every dispatch.
79
81
 
@@ -114,7 +116,7 @@ This wrapper does NOT invoke MCP tools directly. MCP availability inside the Cod
114
116
  - Include context (code, diff, file paths) if provided.
115
117
  - For long prompts, the wrapper script reads from the saved project-local prompt history file via stdin redirect internally. The caller invokes the wrapper with three required positional args + the worktree path for implementation phase:
116
118
  ```bash
117
- $HOME/.okstra/bin/okstra-codex-exec.sh "<literal-project-root>" "<assigned-model-execution-value>" "<literal-prompt-history-path>" "<literal-worktree-path>"
119
+ $HOME/.okstra/bin/okstra-codex-exec.sh "<literal-project-root>" "<assigned-model-execution-value>" "<literal-prompt-history-path>" "<literal-worktree-path>" "worker"
118
120
  ```
119
121
  - If the parent directory does not exist yet, create it before writing the prompt file.
120
122
 
@@ -184,7 +186,7 @@ and the run-level error log staying empty.
184
186
  --agent codex-worker --agent-role worker \
185
187
  --model "<assigned-model-execution-value>" \
186
188
  --error-type cli-failure \
187
- --command "$HOME/.okstra/bin/okstra-codex-exec.sh <project-root> <m> <prompt-path> <worktree-path>" \
189
+ --command "$HOME/.okstra/bin/okstra-codex-exec.sh <project-root> <m> <prompt-path> <worktree-path> worker" \
188
190
  --command-kind cli-invoke \
189
191
  --exit-code <N> --duration-ms <ms> \
190
192
  --message "<one-line summary>" \
@@ -27,9 +27,11 @@ You are a Gemini worker agent. Your job is to execute the Google Gemini CLI and
27
27
 
28
28
  **Required form (uses the okstra wrapper to avoid redirect-triggered permission prompts):**
29
29
  ```bash
30
- $HOME/.okstra/bin/okstra-gemini-exec.sh "<absolute-project-root>" "<assigned-model-execution-value>" "<absolute-prompt-history-path>" [<absolute-worktree-path>]
30
+ $HOME/.okstra/bin/okstra-gemini-exec.sh "<absolute-project-root>" "<assigned-model-execution-value>" "<absolute-prompt-history-path>" [<absolute-worktree-path>] [<role>]
31
31
  ```
32
32
 
33
+ The fifth argument `<role>` is the trace-pane label suffix (`gemini-<role>-trace`); pass the literal string `worker` for every dispatch from this subagent. The wrapper defaults to `worker` when the argument is omitted, but pass it explicitly so the dispatch is self-describing.
34
+
33
35
  The fourth argument is **mandatory for implementation phase** and optional otherwise. It must be the literal `EXECUTOR_WORKTREE_PATH` recorded in the run context; the wrapper appends it to gemini's `--include-directories` list so the model can both read and operate on the worktree alongside project-root.
34
36
 
35
37
  The wrapper internally runs:
@@ -73,7 +75,7 @@ The wrapper exists because Claude Code's Bash permission matcher rejects simple-
73
75
 
74
76
  **Dispatch (background, no foreground timeout):**
75
77
  ```bash
76
- $HOME/.okstra/bin/okstra-gemini-exec.sh "<absolute-project-root>" "<assigned-model-execution-value>" "<absolute-prompt-history-path>" "<absolute-worktree-path>"
78
+ $HOME/.okstra/bin/okstra-gemini-exec.sh "<absolute-project-root>" "<assigned-model-execution-value>" "<absolute-prompt-history-path>" "<absolute-worktree-path>" "worker"
77
79
  ```
78
80
  Call `Bash` with `run_in_background: true`. Capture the returned `bash_id` (a.k.a. `shell_id`). Pass the positional arguments verbatim — do NOT use environment variables, `cd`, `&&` chains, or pipes from `cat`. Substitute the literal extracted Project Root, model execution value, prompt-history path, and worktree path. The fourth argument is **mandatory for implementation phase** (extract from `EXECUTOR_WORKTREE_PATH` in the lead prompt's run context or the `**Worktree:**` / `cwd for every mutating command:` line) and **may be omitted only for non-implementation analysis phases** that do not mutate the worktree. The wrapper handles `-p -`, `-m`, `-o text`, `--include-directories`, the stdin redirect from the prompt file, and stderr suppression internally. Calling `gemini` directly (without the wrapper) is an error in this skill: the redirect tokens disqualify the prefix match against `Bash(gemini:*)` and produce a permission prompt every dispatch.
79
81
 
@@ -114,7 +116,7 @@ This wrapper does NOT invoke MCP tools directly. MCP availability inside the Gem
114
116
  - Include context (code, diff, file paths) if provided.
115
117
  - For long prompts, dispatch through the wrapper with literal absolute paths (plus the worktree path for implementation phase):
116
118
  ```bash
117
- $HOME/.okstra/bin/okstra-gemini-exec.sh "<literal-project-root>" "<assigned-model-execution-value>" "<literal-prompt-history-path>" "<literal-worktree-path>"
119
+ $HOME/.okstra/bin/okstra-gemini-exec.sh "<literal-project-root>" "<assigned-model-execution-value>" "<literal-prompt-history-path>" "<literal-worktree-path>" "worker"
118
120
  ```
119
121
  - If the parent directory does not exist yet, create it before writing the prompt file.
120
122
 
@@ -184,7 +186,7 @@ and the run-level error log staying empty.
184
186
  --agent gemini-worker --agent-role worker \
185
187
  --model "<assigned-model-execution-value>" \
186
188
  --error-type cli-failure \
187
- --command "$HOME/.okstra/bin/okstra-gemini-exec.sh <project-root> <m> <prompt-path> <worktree-path>" \
189
+ --command "$HOME/.okstra/bin/okstra-gemini-exec.sh <project-root> <m> <prompt-path> <worktree-path> worker" \
188
190
  --command-kind cli-invoke \
189
191
  --exit-code <N> --duration-ms <ms> \
190
192
  --message "<one-line summary>" \
@@ -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
@@ -53,10 +65,7 @@ project_root="$1"
53
65
  model="$2"
54
66
  prompt_path="$3"
55
67
  worktree_path="${4-}"
56
- role="${5-}"
57
- if [[ -z "$role" ]]; then
58
- if [[ -n "$worktree_path" ]]; then role="executor"; else role="worker"; fi
59
- fi
68
+ role="${5:-worker}"
60
69
 
61
70
  if [[ -z "$project_root" || ! -d "$project_root" ]]; then
62
71
  printf 'okstra-codex-exec: project-root is missing or not a directory: %q\n' "$project_root" >&2
@@ -110,6 +119,25 @@ if [[ -n "$worktree_path" ]]; then
110
119
  fi
111
120
  fi
112
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
+
113
141
  # Derive a live-progress log path next to the prompt. The codex CLI streams
114
142
  # its progress over stdout/stderr, but the caller (codex-worker subagent)
115
143
  # only polls `BashOutput` on a 60s cadence — so without a sideband, a 10–30
@@ -127,8 +155,10 @@ log_path="${prompt_path%.md}.log"
127
155
  # for the wrapper to exit. This fires in every phase the wrapper is invoked
128
156
  # from (analysis, error-analysis, implementation-planning, implementation,
129
157
  # …) — long-running codex dispatches are not implementation-specific. The
130
- # new pane carries the title `codex-<role>-trace` (e.g. `codex-worker-trace`
131
- # in analysis, `codex-executor-trace` in implementation) and uses `tail -F`
158
+ # new pane carries the title `codex-<role>-trace` `role` is the optional
159
+ # 5th positional arg (defaults to `worker`); callers that dispatch a
160
+ # different role (e.g. `executor`) must pass it explicitly. The pane uses
161
+ # `tail -F`
132
162
  # (follow-by-name) so it survives any truncation a re-dispatch performs on
133
163
  # the same log path. Failures are tolerated silently: missing $TMUX, a tmux
134
164
  # that refuses to split (size constraints, locked client), or a stale socket
@@ -45,10 +45,7 @@ project_root="$1"
45
45
  model="$2"
46
46
  prompt_path="$3"
47
47
  worktree_path="${4-}"
48
- role="${5-}"
49
- if [[ -z "$role" ]]; then
50
- if [[ -n "$worktree_path" ]]; then role="executor"; else role="worker"; fi
51
- fi
48
+ role="${5:-worker}"
52
49
 
53
50
  if [[ -z "$project_root" || ! -d "$project_root" ]]; then
54
51
  printf 'okstra-gemini-exec: project-root is missing or not a directory: %q\n' "$project_root" >&2
@@ -112,10 +109,11 @@ log_path="${prompt_path%.md}.log"
112
109
  # When a tmux session is reachable, split a sibling pane tailing the log so
113
110
  # the operator can watch progress live. This fires in every phase the
114
111
  # wrapper is invoked from — long-running gemini dispatches are not
115
- # implementation-specific. Title `gemini-<role>-trace` (e.g.
116
- # `gemini-worker-trace` in analysis, `gemini-executor-trace` in
117
- # implementation). See the codex wrapper for the full design rationale and
118
- # the silent-degrade failure model.
112
+ # implementation-specific. Title `gemini-<role>-trace` — `role` is the
113
+ # optional 5th positional arg (defaults to `worker`); callers that
114
+ # dispatch a different role must pass it explicitly. See the codex
115
+ # wrapper for the full design rationale and the silent-degrade failure
116
+ # model.
119
117
  if [[ -n "${TMUX:-}" ]]; then
120
118
  trace_pane=$(tmux split-window -h -P -F '#{pane_id}' \
121
119
  -c "$(dirname "$log_path")" \
@@ -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)
@@ -19,7 +19,6 @@ import json
19
19
  import os
20
20
  import re
21
21
  import shutil
22
- import subprocess
23
22
  from dataclasses import dataclass, field
24
23
  from datetime import datetime, timezone
25
24
  from pathlib import Path
@@ -819,11 +818,6 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
819
818
  )
820
819
 
821
820
 
822
- def claude_is_available() -> bool:
823
- """`claude` CLI 가 PATH 에 있는지 확인."""
824
- return shutil.which("claude") is not None
825
-
826
-
827
821
  def main(argv: list[str]) -> int:
828
822
  """CLI dispatcher for bash thin-wrapper use. Parses a flat list of argv
829
823
  (the same flags `okstra.sh` accepts), runs prepare_task_bundle, prints
@@ -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"])