okstra 0.87.1 → 0.88.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okstra",
3
- "version": "0.87.1",
3
+ "version": "0.88.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.87.1",
3
- "builtAt": "2026-06-16T19:16:54.628Z",
2
+ "package": "0.88.0",
3
+ "builtAt": "2026-06-16T20:19:47.032Z",
4
4
  "repoRoot": "/home/runner/work/okstra/okstra"
5
5
  }
@@ -206,12 +206,13 @@ These phases are governed by [team-contract](./team-contract.md). It is the cano
206
206
 
207
207
  `TeamCreate` MUST be the first Agent-related tool call after Phase 2 prompt preparation. Do not call `Agent(... team_name: ...)` for any worker until this phase has executed — the Agent tool rejects `team_name` for non-existent teams with `"team을 먼저 생성하거나 team_name 없이 호출해야 합니다"` / `"team must be created first or call without team_name"`, and silently stripping `team_name` to retry is NOT a valid recovery (it loses the Teams split-pane behavior and is indistinguishable from never having attempted Teams mode).
208
208
 
209
+ 0. **Surface the Teams tools first (deferred-tool environments) — BLOCKING.** `TeamCreate` / `TeamDelete` / `SendMessage` / `TaskList` are commonly **deferred tools**: present by name but not callable until their schemas are loaded via ToolSearch. Before step 1, load them with the explicit selector form `ToolSearch("select:TeamCreate,TeamDelete,SendMessage,TaskList")` — NOT a keyword query. Keyword-search ranking is non-deterministic while MCP servers are still (re)connecting (this is why the failure surfaces on `implementation` entry, after a context compaction re-injects the deferred-tool list mid-reconnect, far more than on earlier phases), so a keyword miss is NOT evidence the environment lacks Agent Teams. If the `select:` result comes back without `TeamCreate`, retry the same `select:` call ONCE. A non-empty result means the tool exists — proceed to step 1 and let the actual `TeamCreate` return value decide ok/error. Only when the explicit `select:` for `TeamCreate` is empty on BOTH attempts may you treat the environment as lacking Agent Teams support and go to step 5.
209
210
  1. Call `TeamCreate(team_name: ..., description: "Lead-plus-worker okstra run for <task-key>")`. The team name comes verbatim from the launch prompt's Team Creation Gate block — `okstra-<task-key>`, and implementation stage runs append `-s<N>` (stage isolation: a leftover team from another stage of the same task must not collide).
210
211
  2. Record the `TeamCreate` outcome in team-state under `teamCreate: { attempted: true, status: "ok"|"error", error?: <message> }` AND the exact name as `teamName` before any dispatch. This is the audit trail that justifies a later no-`team_name` fallback, and `teamName` is the SSOT every later consumer (teardown, reconcile, token collector) reads.
211
212
  2-1. If `TeamCreate` fails with "team already exists" (stale leftover from an earlier attempt): call `TeamList`; if the team is listed in this session, `TeamDelete` it and retry step 1 once. If it is NOT listed, do NOT remove `~/.claude/teams/...` / `~/.claude/tasks/...` with shell commands on your own initiative — that is destructive harness-internal state and `rm -rf` is commonly denied by user permission rules. Ask the user via AskUserQuestion (recommended option: quarantine); on approval, move both dirs into `~/.okstra/trash/<UTC-timestamp>/` with `mv` (reversible), then retry step 1 once.
212
213
  3. Verify `team-state.lead.sessionId` is populated. The `okstra.sh` exec path fills it automatically (`generate_claude_session_id` → `claude --session-id ...`). The render-only / in-session takeover path (`okstra-run` skill) auto-detects the live session's jsonl via `resolve_inproc_lead_session_id`, but the detector is best-effort and may return empty if `~/.claude/projects/<encoded-cwd>/` is unreadable or has no jsonl yet. If `lead.sessionId` is empty at this point, write the running session's id into team-state before proceeding — Phase 7 token-usage collection depends on it and will fail with `lead jsonl not found (sessionId=)` otherwise.
213
214
  4. If `TeamCreate` succeeds, proceed to Phase 4 (dispatch with `team_name`).
214
- 5. If `TeamCreate` fails (tool unavailable, permission denied, environment lacks Agent Teams support), proceed to Phase 5 fallback (dispatch with `run_in_background: true` and no `team_name`).
215
+ 5. If `TeamCreate` fails permission denied, OR the step-0 explicit `select:` returned empty for `TeamCreate` on both attempts (environment genuinely lacks Agent Teams support) proceed to Phase 5 fallback (dispatch with `run_in_background: true` and no `team_name`). A keyword-`ToolSearch` miss, a "deferred tool not yet loaded" state, or any signal short of an attempted `TeamCreate` call / empty step-0 `select:` is NOT a failure: surface the tool (step 0) and call it before deciding. Recording `teamCreate.status: "error"` without either an attempted `TeamCreate` or a doubly-empty step-0 `select:` is a contract violation (it silently degrades the run to background dispatch and loses the Teams split-pane behavior).
215
216
 
216
217
  Use agent and subagent names that map cleanly to the selected worker roles. Do not create ambiguous role names that differ from `Claude worker`, `Codex worker`, `Antigravity worker`, or `Report writer worker`.
217
218
 
@@ -32,10 +32,11 @@ profile document.
32
32
  - Run-start pane recording (shared — runs ONCE at run start, before the FIRST worker dispatch):
33
33
  - The codex/antigravity wrappers now self-anchor their trace pane by walking their own ancestor PIDs against tmux `pane_pid`s (see `lib/okstra/tmux-pane.sh`), so they no longer depend on this file. The lead still records its own pane id here for the cleanup steps below (which-pane-to-never-kill) and as the "am I in tmux" gate. A bare `tmux display-message -p '#{pane_id}'` is NOT reliable for this — Claude Code's Bash tool strips `$TMUX`/`$TMUX_PANE`, so that command returns the most-recently-active *client's* pane (often a different session, or a foreign pane when the lead is launched outside tmux entirely). The lead therefore records via the same ancestry resolver.
34
34
  - The lead MUST run once, at run start: `mkdir -p "<RUN_DIR>/state" && { . "$HOME/.okstra/bin/lib/okstra/tmux-pane.sh" 2>/dev/null && okstra_resolve_caller_pane; } > "<RUN_DIR>/state/lead-pane.id" 2>/dev/null || true` (substitute the run's absolute `RUN_DIR`). When the lead is not inside a tmux pane (e.g. Claude launched from the GUI app) no ancestor matches a pane, the file is empty, and every pane step below silently no-ops — that empty/absent file is the single signal that the lead is not in tmux.
35
+ - This recording is **silent internal setup**: the lead MUST emit NOTHING to the user about it — no pane id, no `%NNN`, no `lead-pane.id` path, no announcement that the phase-start-reset / wrap-up-disposition steps "activate". Just record the file and proceed.
35
36
  - Phase-start pane reset (shared — runs BEFORE dispatching each new worker batch):
36
37
  - okstra creates two kinds of tmux pane per run: (a) **worker-agent panes** the harness gives to dispatched subagents (titled `claude-worker` / `codex-worker` / `antigravity-worker` / `report-writer-worker`), and (b) **trace panes** the codex/antigravity wrappers spawn (`<cli>-<role>-<pid>-tail`). Both accumulate across internal phases because each new phase dispatches a fresh worker batch and the prior panes are never reclaimed.
37
38
  - When `<RUN_DIR>/state/lead-pane.id` is non-empty (the lead is in tmux), the lead MUST run `$HOME/.okstra/bin/okstra-trace-cleanup.sh --run-dir "<RUN_DIR>"` **immediately before** dispatching the next phase's workers — i.e. just before emitting each `PROGRESS: phase-5.5-convergence round=<N>` marker and just before `PROGRESS: phase-6-synthesis dispatching report-writer-worker`. This closes every prior-phase okstra pane (worker-agent + trace) for this run, while NEVER killing the lead's own pane.
38
- - This is **automatic and silent** — NO user prompt. Report it in one short line (e.g. `이전 phase okstra pane 3개 정리`) and proceed. It is silent-skipped when the lead is not in tmux; the lead MUST NOT fabricate a synthetic pane list in that case.
39
+ - This is **automatic and silent** — NO user prompt. Report it in one short, plain-language line (e.g. `이전 phase okstra pane 3개 정리`) and proceed — never expose internal identifiers (`%NNN`, `lead-pane.id`, step names like "phase-start pane reset"). It is silent-skipped when the lead is not in tmux; the lead MUST NOT fabricate a synthetic pane list in that case.
39
40
  - Phase wrap-up — okstra pane disposition (shared, runs AFTER Phase 7 persistence/token collection and BEFORE teammate teardown):
40
41
  - At run end the only residual okstra panes are the LAST phase's (e.g. the `report-writer-worker` agent pane and any codex/antigravity trace pane). `okstra-trace-cleanup.sh --list --run-dir "<RUN_DIR>"` returns one tab-separated `<pane_id>\t<pane_title>` line per residual okstra pane (worker-agent + trace) for this run.
41
42
  - When `<RUN_DIR>/state/lead-pane.id` is non-empty, after the final-report file has been written and the routing recommendation has been issued, the lead MUST run `$HOME/.okstra/bin/okstra-trace-cleanup.sh --list --run-dir "<RUN_DIR>"` exactly once. The output lists every residual okstra pane (worker-agent + trace) for this run, never the lead's own pane.
@@ -57,7 +57,7 @@
57
57
  - The final report MUST include section headings containing each of the following exact strings: `Option Candidates`, `Trade-off`, `Recommended Option`, `Stage Map`, `Stage Exit Contract`, `Stage Validation`, `Dependency`, `Validation Checklist`, `Rollback`, `Requirement Coverage`. (Approval is no longer a body section — it is the YAML frontmatter `approved` field.)
58
58
  - Korean translations are allowed in parentheses (e.g. `### Recommended Option (권장 옵션)`), but the English keyword must be present verbatim in the heading line.
59
59
  - The shape and ordering follow `final-report-template.md` sections 5.4 (`Implementation Plan Deliverables`) + 5.5 (`Stage Map`). Do NOT translate the heading keywords — `validators/validate-run.py` does substring matching on the raw report text and missing English strings are a real, repeatedly observed failure mode (root cause: writer translated the headings to Korean).
60
- - Beyond substring matching, when the Plan Body Verification gate result is `passed` / `passed-with-dissent`, `validators/validate-run.py` runs the **structural** Stage Map validator (`validators/validate-implementation-plan-stages.py`) at the planning boundary — the exact `## 5.5 Stage Map` heading, each `## 5.5.<i> Stage <i>:` section with its four required subsections, the per-stage effective step count (≤6), the `depends-on` DAG, and the per-stage vertical-slice contract (S10) are all enforced here, not deferred to the `implementation` entry gate. S10 scans for the literal in-section strings `Slice value:`, `Acceptance:`, and the Stepwise `action`-cell prefixes `RED:` / `GREEN:` (or a `TDD exemption:` line) — keep these tokens verbatim for the same reason as the heading keywords above.
60
+ - Beyond substring matching, when the Plan Body Verification gate result is `passed` / `passed-with-dissent`, `validators/validate-run.py` runs the **structural** Stage Map validator (`validators/validate-implementation-plan-stages.py`) at the planning boundary — the exact `## 5.5 Stage Map` heading, each `## 5.5.<i> Stage <i>:` section with its four required subsections, the per-stage effective step count (≤6), the `depends-on` DAG, and the per-stage vertical-slice contract (S10) are all enforced here, not deferred to the `implementation` entry gate. S10 scans for the literal in-section strings `Slice value:`, `Acceptance:`, the three `Test case (success):` / `Test case (boundary):` / `Test case (failure):` lines (S10d), and the Stepwise `action`-cell prefixes `RED:` / `GREEN:` (or a `TDD exemption:` line, which waives both the test-case lines and the RED/GREEN check) — keep these tokens verbatim for the same reason as the heading keywords above.
61
61
  - Required deliverable shape (final report, in addition to the standard sections):
62
62
  - at least two implementation options. **Each option must include**:
63
63
  - **File Structure**: an explicit list of files to create / modify / delete with each file's responsibility (one-line each). Use the form `Create: path — responsibility` / `Modify: path:line-range — change summary` / `Delete: path — reason`.
@@ -66,12 +66,17 @@
66
66
  - trade-off matrix across options (rows = options, columns at minimum: complexity, risk, reversibility, test coverage cost, rollout cost)
67
67
  - recommended option with rationale tied to the design principles above
68
68
  - **Stage Map (mandatory — always emitted, even when N=1):** a table of all stages with `stage | title | depends-on | step-count | exit-contract-summary`. `depends-on` is `(none)` or a comma-separated stage number list. Stages with `depends-on (none)` can be implemented in parallel by two simultaneous `implementation` runs.
69
- - **Per-stage slice declaration (mandatory two lines, directly under the `## 5.5.<i> Stage <i>:` heading, before `### Carry-In`):**
69
+ - **Per-stage slice declaration (mandatory lines, directly under the `## 5.5.<i> Stage <i>:` heading, before `### Carry-In`):**
70
70
  - `Slice value: <the one user-observable increment this stage delivers, end-to-end>` — describe WHAT starts working from the consumer's view (e.g. "X 를 조회하면 Y 가 반환된다"), NOT a layer name ("repository 추가"). Validator S10a rejects a missing/empty value.
71
71
  - `Acceptance: <the observable pass condition or the exact command>` — the signal that proves the slice is done; normally the same test command that the `RED:` step below flips to PASS. Validator S10b rejects a missing/empty value.
72
+ - **Test-case design (mandatory three lines unless the stage carries a `TDD exemption:`):** the plan, not the executor, decides which cases the stage's tests must cover, so a stage cannot ship with only one happy-path assertion. Declare all three:
73
+ - `Test case (success): <input → expected, the command/test name>` — the happy path: a valid input that proves the slice works end-to-end.
74
+ - `Test case (boundary): <edge input → expected, the command/test name>` — an edge/boundary input (empty, min/max, missing, first/last, off-by-one). When the slice genuinely has no boundary, write `N/A — <reason>` so the value is non-empty and the omission is a conscious, reviewable decision — never leave it blank.
75
+ - `Test case (failure): <invalid input → error/rejection, the command/test name>` — a negative/failure path (invalid input, unauthorized, error propagation).
76
+ Validator S10d rejects a missing/empty line in any of the three categories (skipped only when a `TDD exemption:` line is present). The `RED:` step below must encode these cases, not a single assertion.
72
77
  - **Per-stage subsections** (`## 5.5.<i> Stage <i>: <title>` for each `i`), each containing the four required subsections:
73
78
  - `### Carry-In` — for `depends-on (none)`: task-brief only. Otherwise: each depended-on stage's static exit contract + runtime sidecar path `runs/<impl-key>/carry/stage-<i>.json` placeholder.
74
- - `### Stepwise Execution Order` — bite-sized table with `step | action | files | command | expected`. **Effective row count ≤ 6** (excluding header / divider / blank). Each step is one action completable in 2–5 minutes; for code steps include actual code or diff sketch. **TDD ordering is MUST, not a preference:** the **first** effective step's `action` cell MUST start with the literal `RED:` and describe the failing test that captures this stage's `Acceptance` (`expected` = FAIL); at least one later `action` cell MUST start with the literal `GREEN:` and describe the minimal implementation that makes it pass (`expected` = PASS); an optional refactor step starts with `REFACTOR:`. **Exemption:** doc-only / config-only / pure-rename stages with no observable runtime behaviour may omit RED/GREEN by declaring one line `TDD exemption: <reason>` in the stage section (mirrors the executor's per-step exemption in `_implementation-executor.md`). Validator S10c enforces RED-first + GREEN, or the exemption line.
79
+ - `### Stepwise Execution Order` — bite-sized table with `step | action | files | command | expected`. **Effective row count ≤ 6** (excluding header / divider / blank). Each step is one action completable in 2–5 minutes; for code steps include actual code or diff sketch. **TDD ordering is MUST, not a preference:** the **first** effective step's `action` cell MUST start with the literal `RED:` and describe the failing test(s) that capture this stage's `Acceptance` **and the three declared `Test case (success|boundary|failure)` lines** (`expected` = FAIL) — the RED step encodes the case set, not a single happy-path assertion; at least one later `action` cell MUST start with the literal `GREEN:` and describe the minimal implementation that makes it pass (`expected` = PASS); an optional refactor step starts with `REFACTOR:`. **Exemption:** doc-only / config-only / pure-rename stages with no observable runtime behaviour may omit RED/GREEN by declaring one line `TDD exemption: <reason>` in the stage section (mirrors the executor's per-step exemption in `_implementation-executor.md`). Validator S10c enforces RED-first + GREEN, or the exemption line.
75
80
  - **Per-stage conformance declaration (mandatory one line, in the stage section — same placement freedom as `TDD exemption:`):** the stage MUST carry exactly one of:
76
81
  - `Conformance tests: stage-<N> — <task_root>/qa/scripts/stage-<N>.<ext> (requires=[db|io|http|external,...])` — a Tier3 verification script that proves this stage's upstream requirements (brief / requirements-discovery / error-analysis / improvement-discovery → this stage's `Acceptance`) hold against **real** DB rows, real endpoints, or the real external API — NOT mocks. When you emit this line you MUST also (a) write the script to `<task_root>/qa/scripts/stage-<N>.<ext>` and (b) add a matching entry to `<task_root>/qa/conformance-manifest.json` with fields `stageKey` (= `<task-id>-stage-<N>`), `script`, `runCommand`, `requirementIds`, `requires` (subset of `{db, io, http, external}`), `passContract`, `exemption: null`, `waiver: null`. The script's standard interface: a `main` that exits `0`=PASS / non-zero=FAIL, and whose stdout ends with `QA-RESULT: PASS|FAIL` followed by one `REQ <id>: PASS|FAIL: <근거>` line per requirement. When the verification body is a test spec, author it with the project's own test framework (devDependency) invoked via a discovery override at `<task_root>/qa/scripts/` (jest: `--config <project config> --roots <task_root>/qa/scripts`) — never hand-roll `describe`/`expect` and never widen the project's own test config; for TypeScript specs also write `<task_root>/qa/scripts/tsconfig.json` extending the project tsconfig with the runner's `types` entry so editors resolve the file.
77
82
  - `Conformance exemption: <reason>` — only for stages that touch no db/io/http/external surface, or where unit tests fully cover the increment. (If the eventual `implementation` diff actually touches one of those surfaces, `validate-run.py`'s diff-surface cross-check is BLOCKING — an exemption cannot hide a real db/io/http/external change.)
@@ -111,4 +116,4 @@
111
116
  5. **Scope check** — if the recommended plan now spans multiple independent subsystems, recommend splitting into separate planning runs rather than shipping an oversized plan.
112
117
  6. **Review-rule preflight check** — if a project review rule pack exists, map each relevant rule to the recommended option. Reject the draft if it knowingly creates a violation that the later PR reviewer would flag, unless the plan records a specific rationale and follow-up. In particular, scan for repeated helper stacks across planned files, tests that assert delegation to the same calculator/helper they exercise, public names that hide side effects, domain rules placed in repositories/adapters, and APIs made dead by this change.
113
118
  7. **Plan-body verification reconciliation (BLOCKING for implementation-planning).** Inspect the `### 5.5.9 Plan Body Verification` verdict table. For every plan-item row classified as `majority-disagree → C-<N>`, the corresponding `C-<N>` row MUST exist in `## 1. Clarification Items` with `Kind` chosen per the standard policy and `Blocks=approval`. Do NOT create a parallel `Open Questions` block under the implementation plan body — the unified table is the single home. Conversely, the `Classification` column's `C-<N>` reference and the `## 1. Clarification Items` `ID` column MUST match 1:1; an orphan on either side is a contract violation. For `partial-consensus` and `worker-unique` plan-items, the dissenting opinion lives in §5.5.9 `Dissent log` and is NOT promoted to §5.
114
- 8. **Stage Map self-check** — for every stage, count the effective rows of its `Stepwise Execution Order` table by hand; reject the draft if any stage exceeds 6. Confirm each stage declares a non-empty `Slice value:` and `Acceptance:` line, and that its first step `action` starts with `RED:` with a later `GREEN:` (or carries a `TDD exemption:` line) this is what validator S10 enforces. Walk the `depends-on` graph and confirm it is a DAG (no cycle, no self-reference). For each `depends-on` link, confirm it encodes a real data/contract dependency — do NOT add links to serialise unrelated work, and do NOT split a stage merely to create more parallel stages. **Parallel-safety:** for every pair of `depends-on (none)` stages, confirm their `Stage Exit Contract` predicted file sets are disjoint; if they share a file, merge them or add a `depends-on` link (validator S9 rejects overlap).
119
+ 8. **Stage Map self-check** — for every stage, count the effective rows of its `Stepwise Execution Order` table by hand; reject the draft if any stage exceeds 6. Confirm each stage declares a non-empty `Slice value:` and `Acceptance:` line, the three `Test case (success|boundary|failure):` lines (or carries a `TDD exemption:` line), and that its first step `action` starts with `RED:` with a later `GREEN:` this is what validator S10 enforces, including S10d on the test-case lines. Read each stage's three test-case lines as a reviewer: reject any that restates the happy path in all three slots, leaves `boundary` blank, or writes `N/A` where a real edge input exists. Walk the `depends-on` graph and confirm it is a DAG (no cycle, no self-reference). For each `depends-on` link, confirm it encodes a real data/contract dependency — do NOT add links to serialise unrelated work, and do NOT split a stage merely to create more parallel stages. **Parallel-safety:** for every pair of `depends-on (none)` stages, confirm their `Stage Exit Contract` predicted file sets are disjoint; if they share a file, merge them or add a `depends-on` link (validator S9 rejects overlap).
@@ -7,7 +7,7 @@
7
7
  - codex
8
8
  - report-writer
9
9
  - Optional workers (opt-in via `--workers`):
10
- - antigravity — when added to the roster it joins the verifier set; when omitted only the Claude+Codex verifiers participate (`--executor antigravity` is therefore not selectable without explicitly listing `antigravity` in `--workers`)
10
+ - antigravity — when added to the roster it joins the verifier set; when omitted only the Claude+Codex verifiers participate. `--executor antigravity` requires `antigravity` in the roster: the direct CLI demands it explicitly in `--workers`, while the wizard adds it automatically when you pick antigravity as the executor (the executor pick *is* the explicit listing).
11
11
  - **Executor binding (resolved at run-prep time, fixed for this run):**
12
12
  - Executor display name: `{{EXECUTOR_DISPLAY_NAME}}`
13
13
  - Executor provider: `{{EXECUTOR_PROVIDER}}` (one of: `claude` | `codex` | `antigravity`; chosen via `--executor` or `OKSTRA_DEFAULT_EXECUTOR`, default `claude`)
@@ -219,7 +219,7 @@
219
219
  "approve_plan_confirm": {
220
220
  "label": "이 플랜으로 구현을 진행할까요?\n {path}\n· 예 — 이 plan 대로 구현을 실행합니다. 플랜이 아직 승인 전이면 지금 data.json(정본) + 리포트를 함께 approved 로 처리한 뒤 진행합니다. (markdown 만 손으로 고치면 일관성 검증에서 거부되므로 이 경로로 승인하세요.)\n· 아니오 — 진행하지 않습니다.",
221
221
  "label_final_verification": "이 plan 을 검증 기준으로 삼아 진행할까요?\n {path}\n· 예 — 선택한 stage 의 구현 결과를 위 plan 기준으로 검증합니다. (plan 이 아직 승인 전이면 data.json(정본) + 리포트를 함께 approved 로 처리한 뒤 검증합니다.)\n· 아니오 — 진행하지 않습니다.",
222
- "html_approval_note": "\nHTML 승인 기록 발견: {sidecar}\n 선택 옵션: {option}\n· 예(기록 적용) — 승인과 함께 위 옵션을 frontmatter 에 기록합니다.",
222
+ "html_approval_note": "\n보고서에서 내보낸 승인 기록을 발견했습니다: {sidecar}\n 선택한 구현 옵션: {option}\n· 예(기록 적용) — 승인과 함께 위 옵션을 frontmatter 에 기록합니다.",
223
223
  "html_approval_note_default_option": "(추천 옵션 그대로)",
224
224
  "echo_template": "approve-plan: {value}",
225
225
  "options": {
@@ -231,8 +231,8 @@
231
231
  "no": "아니오 — 진행하지 않음"
232
232
  },
233
233
  "options_html_approval": {
234
- "yes_apply": "예 — HTML 기록대로 승인 + 옵션 적용 (추천)",
235
- "yes": "예 — 승인만 (HTML 기록 무시)",
234
+ "yes_apply": "예 — 내보낸 기록대로 승인 + 옵션 적용 (추천)",
235
+ "yes": "예 — 승인만 (내보낸 기록 무시)",
236
236
  "no": "아니오 — 진행하지 않음"
237
237
  },
238
238
  "echo_variants": {
@@ -245,7 +245,7 @@
245
245
  "errors": {
246
246
  "declined": "진행을 선택하지 않으면 다음 단계로 넘어갈 수 없습니다. 진행(예)하거나 위저드를 종료하세요.",
247
247
  "still_unapproved": "approve-plan: 승인 처리 후에도 승인 상태가 아닙니다 (data.json/markdown 불일치): {path}",
248
- "unknown_option": "HTML 승인 기록의 옵션 `{option}` 이 plan 의 Option Candidates 에 없습니다. 유효 후보: {candidates}. 보고서 HTML 에서 다시 Export 하거나 '예 — 승인만' 을 선택하세요."
248
+ "unknown_option": "보고서에서 내보낸 승인 기록의 옵션 `{option}` 이 plan 의 Option Candidates 에 없습니다. 유효 후보: {candidates}. 보고서에서 다시 내보내거나 '예 — 승인만' 을 선택하세요."
249
249
  }
250
250
  },
251
251
  "stage_pick": {
@@ -846,7 +846,13 @@ def _resolved_roster(state: WizardState) -> list[str]:
846
846
  """Effective worker list AFTER override. Implementation: profile default
847
847
  (caller never asks for override). Others: override or profile default."""
848
848
  if state.task_type == "implementation":
849
- return list(state.profile_workers)
849
+ roster = list(state.profile_workers)
850
+ # implementation 은 roster override 단계를 건너뛰므로, executor 로 명시
851
+ # 선택한 워커(예: antigravity)가 프로필 기본 roster 에 없으면 여기서
852
+ # 합류시켜야 render-bundle 의 executor∈roster 가드를 통과한다.
853
+ if state.executor and state.executor not in roster:
854
+ roster.append(state.executor)
855
+ return roster
850
856
  if state.workers_override.strip():
851
857
  return normalize_workers(state.workers_override)
852
858
  return list(state.profile_workers)
@@ -1153,11 +1153,30 @@
1153
1153
  "not": { "required": ["conformanceTests"] }
1154
1154
  }
1155
1155
  ],
1156
+ "if": { "not": { "required": ["tddExemption"] } },
1157
+ "then": {
1158
+ "required": ["testCaseSuccess", "testCaseBoundary", "testCaseFailure"]
1159
+ },
1156
1160
  "properties": {
1157
1161
  "stage": { "type": "integer", "minimum": 1 },
1158
1162
  "title": { "type": "string", "minLength": 1 },
1159
1163
  "sliceValue": { "type": "string", "minLength": 1 },
1160
1164
  "acceptance": { "type": "string", "minLength": 1 },
1165
+ "testCaseSuccess": {
1166
+ "type": "string",
1167
+ "minLength": 1,
1168
+ "description": "Happy-path test case proving the slice works for valid input. Text after the `Test case (success):` prefix."
1169
+ },
1170
+ "testCaseBoundary": {
1171
+ "type": "string",
1172
+ "minLength": 1,
1173
+ "description": "Boundary/edge test case (empty, min/max, missing). Text after the `Test case (boundary):` prefix. Use `N/A — <reason>` only when no boundary genuinely exists."
1174
+ },
1175
+ "testCaseFailure": {
1176
+ "type": "string",
1177
+ "minLength": 1,
1178
+ "description": "Failure/negative test case (invalid input, error path). Text after the `Test case (failure):` prefix."
1179
+ },
1161
1180
  "conformanceTests": {
1162
1181
  "type": "string",
1163
1182
  "minLength": 1,
@@ -121,7 +121,7 @@ That is the entire interactive flow. The wizard handles:
121
121
  - task-type pick (추천 3개 — `nextRecommendedPhase` recommended / 현재 phase 재실행 / 라이프사이클 다음 단계 — + 직접 입력; 직접 입력은 후속 `text` 단계에서 전체 task-type 화이트리스트로 검증),
122
122
  - brief path — **entry task-type(requirements-discovery / error-analysis / improvement-discovery)에서만 질문** (same-group `.okstra/briefs/<task-group>/**/*.md` candidates first, direct input last; `유지 / 변경` for existing entry tasks). downstream task-type 은 manifest 의 brief 를 자동 carry-in 하고, 등록 brief 가 없으면 `brief_carry` 3-옵션(entry 전환 추천 / 직접 입력 / 중단)이 뜬다. `release-handoff` 는 brief 단계가 아예 없다 — prepare 가 검증 보고서 인용 input 문서를 생성한다,
123
123
  - base-ref pick + git rev-parse validation (skipped when reusing an active worktree),
124
- - `implementation`-only sub-flow: approved-plan path (frontmatter `approved: true` check) + stage pick (`auto` = 의존성 충족된 가장 빠른 미완료 stage, 또는 특정 stage 번호) + executor pick. approved-plan 선택 시 그 run 의 sibling `user-responses/` 에서 plan 과 source-report·seq 가 일치하는 HTML `## APPROVAL` sidecar 를 감지하면 approve-confirm 단계가 3-옵션(`yes_apply` HTML 기록대로 승인+옵션 적용 추천 / `yes` 승인만 / `no` 중단)으로 확장된다 — `yes_apply` 는 옵션을 plan 의 `optionCandidates` 에 대해 검증한 뒤 기존 승인·옵션 경로로 적용한다,
124
+ - `implementation`-only sub-flow: approved-plan path (frontmatter `approved: true` check) + stage pick (`auto` = 의존성 충족된 가장 빠른 미완료 stage, 또는 특정 stage 번호) + executor pick. approved-plan 선택 시 그 run 의 sibling `user-responses/` 에서 plan 과 source-report·seq 가 일치하는, 보고서에서 내보낸 `## APPROVAL` sidecar 를 감지하면 approve-confirm 단계가 3-옵션(`yes_apply` 내보낸 기록대로 승인+옵션 적용 추천 / `yes` 승인만 / `no` 중단)으로 확장된다 — `yes_apply` 는 옵션을 plan 의 `optionCandidates` 에 대해 검증한 뒤 기존 승인·옵션 경로로 적용한다,
125
125
  - `release-handoff`-only sub-flow: approved plan 자동 해소 후 `handoff_stage_pick` 멀티선택 — eligible stage 묶음(stage-group) 또는 전체 task(accepted whole-task 검증 보고서 존재 시) 선택; 결과는 render-args 의 `stages` 키(csv, whole-task 면 빈 값)로 나간다,
126
126
  - `Use defaults / Customize` branch with profile-aware worker/model questions,
127
127
  - `release-handoff` PR template override + persist scope,
@@ -242,6 +242,11 @@ implementation-option: {{ frontmatter.implementationOption | yaml_scalar }}
242
242
 
243
243
  - **Slice value:** {{ stage.sliceValue }}
244
244
  - **Acceptance:** {{ stage.acceptance }}
245
+ {% if not stage.tddExemption %}
246
+ - **Test case (success):** {{ stage.testCaseSuccess }}
247
+ - **Test case (boundary):** {{ stage.testCaseBoundary }}
248
+ - **Test case (failure):** {{ stage.testCaseFailure }}
249
+ {% endif %}
245
250
  {% if stage.conformanceTests %}
246
251
  - **Conformance tests:** stage-{{ stage.stage }} — {{ stage.conformanceTests }}
247
252
  {% else %}
@@ -89,7 +89,7 @@ The final report of an `implementation-planning` run MUST contain every section
89
89
  1. **Option Candidates** — at least two viable options, each with the exact list of files to create or modify and the principal change per file.
90
90
  2. **Trade-off Matrix** — comparison of the candidates across complexity, risk, reversibility, performance impact, scope, and required test surface.
91
91
  3. **Recommended Option** — selected option with explicit rationale referencing the trade-off matrix.
92
- 4. **Stage Map** — `## 5.5 Stage Map` plus one `## 5.5.<i> Stage <i>:` section per stage. Each stage is a thin vertical slice with `Slice value:`, `Acceptance:`, `Conformance tests:` or `Conformance exemption:`, the four required subsections, and a RED/GREEN step order unless a `TDD exemption:` line applies.
92
+ 4. **Stage Map** — `## 5.5 Stage Map` plus one `## 5.5.<i> Stage <i>:` section per stage. Each stage is a thin vertical slice with `Slice value:`, `Acceptance:`, the three `Test case (success|boundary|failure):` lines, `Conformance tests:` or `Conformance exemption:`, the four required subsections, and a RED/GREEN step order unless a `TDD exemption:` line applies (which also waives the test-case lines).
93
93
  5. **Dependency and Migration Risk** — schema, data, ordering, feature-flag, and cross-service risks that the recommended option introduces.
94
94
  6. **Validation Checklist** — pre-execution, mid-execution, and post-execution checks (commands, expected outputs, observability points).
95
95
  7. **Rollback Strategy** — exact reverse procedure or compensating action for each significant step.
@@ -177,6 +177,13 @@ _LABEL_PREFIX = r"^\s*(?:[-*]\s+)?(?:\*\*)?"
177
177
  SLICE_VALUE = re.compile(_LABEL_PREFIX + r"Slice value\s*:\s*(?:\*\*)?\s*(.+?)\s*$", re.M)
178
178
  ACCEPTANCE = re.compile(_LABEL_PREFIX + r"Acceptance\s*:\s*(?:\*\*)?\s*(.+?)\s*$", re.M)
179
179
  TDD_EXEMPTION = re.compile(_LABEL_PREFIX + r"TDD exemption\s*:\s*(?:\*\*)?\s*\S", re.M)
180
+ TEST_CASE_CATEGORIES = ("success", "boundary", "failure")
181
+ TEST_CASE = {
182
+ cat: re.compile(
183
+ _LABEL_PREFIX + rf"Test case\s*\({cat}\)\s*:\s*(?:\*\*)?\s*\S", re.M
184
+ )
185
+ for cat in TEST_CASE_CATEGORIES
186
+ }
180
187
  CONFORMANCE_TESTS = re.compile(_LABEL_PREFIX + r"Conformance tests\s*:\s*(?:\*\*)?\s*\S", re.M)
181
188
  CONFORMANCE_EXEMPTION = re.compile(_LABEL_PREFIX + r"Conformance exemption\s*:\s*(?:\*\*)?\s*\S", re.M)
182
189
 
@@ -188,6 +195,10 @@ def _check_slice_tdd(text: str, stages: List[StageMeta]) -> List[ValidationError
188
195
  S10b — `Acceptance:` line with a non-empty value.
189
196
  S10c — first effective Stepwise step's action starts with `RED:` AND some
190
197
  action starts with `GREEN:`, OR a `TDD exemption:` line is present.
198
+ S10d — stage declares all three `Test case (success|boundary|failure):`
199
+ lines with non-empty values, OR a `TDD exemption:` line is present.
200
+ Forces the plan to address the happy path, edge/boundary inputs, and
201
+ failure/negative inputs instead of a single acceptance assertion.
191
202
  """
192
203
  errs: List[ValidationError] = []
193
204
  for s in stages:
@@ -204,6 +215,16 @@ def _check_slice_tdd(text: str, stages: List[StageMeta]) -> List[ValidationError
204
215
 
205
216
  if TDD_EXEMPTION.search(section):
206
217
  continue
218
+
219
+ missing_cases = [
220
+ cat for cat in TEST_CASE_CATEGORIES if not TEST_CASE[cat].search(section)
221
+ ]
222
+ if missing_cases:
223
+ errs.append(ValidationError("S10", s.stage_number,
224
+ "S10d: missing 'Test case (" + "|".join(missing_cases) + "):' "
225
+ "line(s) — declare a non-empty success, boundary, and failure "
226
+ "case each, or add a 'TDD exemption:' line"))
227
+
207
228
  rows = _effective_step_rows(section)
208
229
  actions = [r[1] for r in rows if len(r) > 1]
209
230
  first_is_red = bool(actions) and actions[0].startswith("RED:")