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.
- package/docs/kr/architecture.md +1 -1
- package/docs/kr/performance-improvement-plan-v2.md +330 -0
- package/docs/kr/performance-improvement-plan.md +125 -0
- package/docs/project-structure-overview.md +386 -0
- package/docs/superpowers/plans/2026-05-14-convergence-queue-pruning.md +1568 -0
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/SKILL.md +7 -1
- package/runtime/agents/workers/codex-worker.md +6 -4
- package/runtime/agents/workers/gemini-worker.md +6 -4
- package/runtime/agents/workers/report-writer-worker.md +4 -0
- package/runtime/bin/okstra-codex-exec.sh +36 -6
- package/runtime/bin/okstra-gemini-exec.sh +6 -8
- package/runtime/prompts/profiles/final-verification.md +8 -2
- package/runtime/prompts/profiles/implementation-planning.md +1 -1
- package/runtime/prompts/profiles/release-handoff.md +26 -28
- package/runtime/prompts/profiles/requirements-discovery.md +1 -1
- package/runtime/python/okstra_ctl/render.py +78 -4
- package/runtime/python/okstra_ctl/run.py +0 -6
- package/runtime/python/okstra_ctl/run_context.py +5 -0
- package/runtime/python/okstra_ctl/workflow.py +8 -7
- package/runtime/python/okstra_ctl/worktree.py +155 -15
- package/runtime/python/okstra_token_usage/blocks.py +0 -2
- package/runtime/python/okstra_token_usage/claude.py +0 -2
- package/runtime/skills/okstra-brief/SKILL.md +523 -0
- package/runtime/skills/okstra-convergence/SKILL.md +149 -37
- package/runtime/skills/okstra-report-writer/SKILL.md +8 -6
- package/runtime/templates/prd/brief.template.md +12 -0
- package/runtime/templates/project-docs/task-index.template.md +12 -0
- package/runtime/templates/reports/error-analysis-input.template.md +12 -0
- package/runtime/templates/reports/final-report.template.md +39 -12
- package/runtime/templates/reports/final-verification-input.template.md +22 -0
- package/runtime/templates/reports/implementation-input.template.md +12 -0
- package/runtime/templates/reports/implementation-planning-input.template.md +12 -0
- package/runtime/templates/reports/quick-input.template.md +12 -0
- package/runtime/templates/reports/release-handoff-input.template.md +23 -10
- package/runtime/templates/reports/schedule.template.md +12 -0
- package/runtime/templates/reports/settings.template.json +83 -30
- package/runtime/templates/reports/task-brief.template.md +12 -0
package/package.json
CHANGED
package/runtime/BUILD.json
CHANGED
package/runtime/agents/SKILL.md
CHANGED
|
@@ -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`
|
|
131
|
-
#
|
|
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`
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
# the silent-degrade failure
|
|
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
|
-
- **
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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;
|
|
16
|
-
- the lead MUST capture `git status --short` and confirm the working tree is clean
|
|
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
|
-
- `
|
|
21
|
-
- `
|
|
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 `
|
|
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. **
|
|
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
|
|
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. **
|
|
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
|
-
-
|
|
45
|
-
-
|
|
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
|
|
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 `
|
|
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
|
|
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: `
|
|
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
|
|
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
|
|
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 `
|
|
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 `
|
|
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
|
|
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"])
|