okstra 0.46.0 → 0.48.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.
@@ -0,0 +1,516 @@
1
+ # Coverage critic 구현 계획 (sub-project B1)
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** requirements-discovery / error-analysis / implementation-planning 에 opt-in coverage critic pass 를 추가한다 — Phase 5.5 직후 기존 worker 를 critic 프롬프트로 재사용 dispatch 하고, gap 은 1회 적대적 reverify 후 병합. 사용 여부·provider 는 okstra-run select box(또는 `--critic` 플래그)로 고른다.
6
+
7
+ **Architecture:** 백엔드는 `convergence.critic {enabled,provider,modelExecutionValue}` manifest 블록 — `render._build_convergence_block` 가 ctx `CRITIC_CHOICE` + 기존 `*_MODEL_EXECUTION_VALUE` 로 해석. 선택은 `--critic` CLI 플래그 + okstra-run wizard `S_CRITIC_PICK` step(플래그 미지정 시에만 표시). critic *행동*(gap 탐색·1회 적대 reverify·병합)은 convergence skill + lead/profile 의 prompt 지시(런타임 강제 아님).
8
+
9
+ **Tech Stack:** Python 3 (okstra_ctl: run/render/wizard, pytest), wizard prompts JSON, Markdown skill/prompt 문서. 빌드: `npm run build`.
10
+
11
+ **설계 근거:** [`docs/superpowers/specs/2026-06-04-coverage-critic-design.md`](../specs/2026-06-04-coverage-critic-design.md)
12
+
13
+ ---
14
+
15
+ ## 파일 구조
16
+
17
+ | 파일 | 책임 | 작업 |
18
+ |---|---|---|
19
+ | [`scripts/okstra_ctl/run.py`](../../../scripts/okstra_ctl/run.py) | `--critic` 플래그 + `PrepareInputs.critic` + ctx `CRITIC_CHOICE` + canonical_argv + 값 검증 | Modify |
20
+ | [`scripts/okstra_ctl/render.py`](../../../scripts/okstra_ctl/render.py) | `_build_convergence_block` 에 `critic` 블록 (phase-gated) | Modify (899–) |
21
+ | `tests/test_render_critic_block.py` | critic 블록 resolve 단위 테스트 | Create |
22
+ | [`scripts/okstra_ctl/wizard.py`](../../../scripts/okstra_ctl/wizard.py) | `S_CRITIC_PICK` step (state/constant/build/submit/Step/finalize/summary) | Modify |
23
+ | [`prompts/wizard/prompts.ko.json`](../../../prompts/wizard/prompts.ko.json) | `critic_pick` step label/options | Modify |
24
+ | `tests/test_wizard_critic_pick.py` | wizard step build/submit/applies 테스트 | Create |
25
+ | [`skills/okstra-convergence/SKILL.md`](../../../skills/okstra-convergence/SKILL.md) | "Coverage critic pass" 절 | Modify |
26
+ | [`agents/SKILL.md`](../../../agents/SKILL.md) | Phase 5.5→6 critic pass + PROGRESS 라인 | Modify |
27
+ | [`prompts/profiles/requirements-discovery.md`](../../../prompts/profiles/requirements-discovery.md) / [`error-analysis.md`](../../../prompts/profiles/error-analysis.md) / [`implementation-planning.md`](../../../prompts/profiles/implementation-planning.md) | coverage critic opt-in 선언 | Modify |
28
+ | [`CHANGES.md`](../../../CHANGES.md) | 사용자 영향 항목 | Modify |
29
+
30
+ 작업 순서: 백엔드(run+render+test) → wizard(step+json+test) → convergence skill → agents+profiles → CHANGES+검증.
31
+
32
+ ---
33
+
34
+ ### B1-Task1: 백엔드 — `--critic` 플래그 + manifest `convergence.critic` 블록
35
+
36
+ **Files:**
37
+ - Modify: `scripts/okstra_ctl/run.py`
38
+ - Modify: `scripts/okstra_ctl/render.py` (`_build_convergence_block`)
39
+ - Create: `tests/test_render_critic_block.py`
40
+
41
+ - [ ] **Step 1: 실패하는 render 테스트 작성**
42
+
43
+ Create `tests/test_render_critic_block.py`:
44
+
45
+ ```python
46
+ """_build_convergence_block — coverage critic 블록 phase-gated resolve.
47
+
48
+ critic 은 requirements-discovery / error-analysis / implementation-planning 에서만
49
+ enabled 가능. provider 는 ctx CRITIC_CHOICE, 모델은 해당 provider 의
50
+ *_MODEL_EXECUTION_VALUE 에서. 미지정/off/비적용 phase 면 disabled.
51
+ """
52
+ from __future__ import annotations
53
+
54
+ import sys
55
+ from pathlib import Path
56
+
57
+ import pytest
58
+
59
+ _REPO_ROOT = Path(__file__).resolve().parents[1]
60
+ sys.path.insert(0, str(_REPO_ROOT / "scripts"))
61
+
62
+ from okstra_ctl.render import _build_convergence_block # noqa: E402
63
+
64
+ _MODEL_CTX = {
65
+ "CLAUDE_WORKER_MODEL_EXECUTION_VALUE": "opus",
66
+ "CODEX_WORKER_MODEL_EXECUTION_VALUE": "gpt-5.5",
67
+ "GEMINI_WORKER_MODEL_EXECUTION_VALUE": "auto",
68
+ }
69
+
70
+
71
+ def _ctx(task_type, critic_choice):
72
+ return {"TASK_TYPE": task_type, "CRITIC_CHOICE": critic_choice, **_MODEL_CTX}
73
+
74
+
75
+ def test_critic_disabled_when_unset():
76
+ block = _build_convergence_block(_ctx("error-analysis", ""))
77
+ assert block["critic"] == {"enabled": False, "provider": None, "modelExecutionValue": None}
78
+
79
+
80
+ def test_critic_disabled_when_off():
81
+ block = _build_convergence_block(_ctx("error-analysis", "off"))
82
+ assert block["critic"]["enabled"] is False
83
+
84
+
85
+ @pytest.mark.parametrize("task_type", ["requirements-discovery", "error-analysis", "implementation-planning"])
86
+ def test_critic_enabled_for_applicable_phases(task_type):
87
+ block = _build_convergence_block(_ctx(task_type, "claude"))
88
+ assert block["critic"] == {"enabled": True, "provider": "claude", "modelExecutionValue": "opus"}
89
+
90
+
91
+ def test_critic_model_follows_provider():
92
+ block = _build_convergence_block(_ctx("error-analysis", "codex"))
93
+ assert block["critic"]["provider"] == "codex"
94
+ assert block["critic"]["modelExecutionValue"] == "gpt-5.5"
95
+
96
+
97
+ @pytest.mark.parametrize("task_type", ["implementation", "final-verification", "release-handoff"])
98
+ def test_critic_disabled_for_non_applicable_phases(task_type):
99
+ block = _build_convergence_block(_ctx(task_type, "claude"))
100
+ assert block["critic"]["enabled"] is False
101
+ ```
102
+
103
+ - [ ] **Step 2: 실패 확인**
104
+
105
+ Run: `python3 -m pytest tests/test_render_critic_block.py -q`
106
+ Expected: FAIL — `block` 에 `"critic"` 키 없음 (`KeyError`).
107
+
108
+ - [ ] **Step 3: render `_build_convergence_block` 에 critic 블록 추가**
109
+
110
+ `scripts/okstra_ctl/render.py` `_build_convergence_block` 의 `return { ... }` 직전(현재 `plan_verify_enabled = ...` 다음)에 삽입:
111
+
112
+ ```python
113
+ critic_choice = (ctx.get("CRITIC_CHOICE", "") or "").strip().lower()
114
+ critic_phases = {"requirements-discovery", "error-analysis", "implementation-planning"}
115
+ critic_exec_key = {
116
+ "claude": "CLAUDE_WORKER_MODEL_EXECUTION_VALUE",
117
+ "codex": "CODEX_WORKER_MODEL_EXECUTION_VALUE",
118
+ "gemini": "GEMINI_WORKER_MODEL_EXECUTION_VALUE",
119
+ }
120
+ critic_enabled = critic_choice in critic_exec_key and task_type in critic_phases
121
+ critic_block = {
122
+ "enabled": critic_enabled,
123
+ "provider": critic_choice if critic_enabled else None,
124
+ "modelExecutionValue": (ctx.get(critic_exec_key[critic_choice]) or None) if critic_enabled else None,
125
+ }
126
+ ```
127
+
128
+ 그리고 같은 함수의 반환 dict 에 `"planBodyVerification": {...}` 항목 **앞에** `"critic": critic_block,` 한 줄 추가. docstring 의 ctx knobs 목록에 한 줄 추가:
129
+
130
+ ```python
131
+ - `CRITIC_CHOICE`: "" | "off" | "claude" | "codex" | "gemini" — coverage critic
132
+ backing provider (enabled only for requirements-discovery / error-analysis /
133
+ implementation-planning); model taken from that provider's execution value.
134
+ ```
135
+
136
+ - [ ] **Step 4: render 테스트 통과 확인**
137
+
138
+ Run: `python3 -m pytest tests/test_render_critic_block.py -q`
139
+ Expected: PASS.
140
+
141
+ - [ ] **Step 5: run.py — `--critic` 플래그 + PrepareInputs + ctx + canonical_argv + 검증**
142
+
143
+ (5a) `PrepareInputs` dataclass 의 `executor: str = ""` 줄 다음에 추가:
144
+ ```python
145
+ critic: str = ""
146
+ ```
147
+
148
+ (5b) argparse 의 `p.add_argument("--executor", default="")` 다음 줄에 추가:
149
+ ```python
150
+ p.add_argument("--critic", default="")
151
+ ```
152
+ 그리고 argparse 결과를 `PrepareInputs(...)` 로 묶는 생성부에 `critic=args.critic,` 를 (executor 인자 옆에) 추가. (생성부는 argparse 직후 `PrepareInputs(` 호출 — `executor=args.executor` 패턴을 그대로 따라 한 줄 추가.)
153
+
154
+ (5c) `_canonical_argv` 의 pairs 리스트에서 `("--executor", ...)` 다음에 추가:
155
+ ```python
156
+ ("--critic", inp.critic or ctx.get("CRITIC_CHOICE", "")),
157
+ ```
158
+
159
+ (5d) 모델 메타데이터 resolve 직후(executor binding 블록 시작 `# ---- executor binding` 줄 **앞**)에 critic 검증 + ctx 변수 삽입:
160
+ ```python
161
+ # ---- coverage critic choice (validated; phase-gating happens in render) ----
162
+ critic_choice = (inp.critic or "").strip().lower()
163
+ if critic_choice not in ("", "off", "claude", "codex", "gemini"):
164
+ raise PrepareError(
165
+ f"--critic must be one of: off, claude, codex, gemini (got: {critic_choice!r})"
166
+ )
167
+ ```
168
+ (use whatever error type the file already raises for `--executor` validation — match the executor validator's exception class, seen near the `--executor must be one of` raise.)
169
+
170
+ (5e) ctx dict 빌드부(`"RENDER_ONLY": ...` 등이 있는 dict)에 한 줄 추가:
171
+ ```python
172
+ "CRITIC_CHOICE": critic_choice,
173
+ ```
174
+
175
+ - [ ] **Step 6: run.py 검증 동작 테스트 (수동)**
176
+
177
+ Run: `python3 -c "import sys; sys.path.insert(0,'scripts'); from okstra_ctl.run import build_arg_parser" 2>/dev/null; python3 -m pytest tests/ -q -k "convergence or critic or plan_body"`
178
+ Expected: PASS (critic 블록 테스트 + 기존 convergence/plan-body 테스트 무회귀).
179
+
180
+ - [ ] **Step 7: 전체 빠른 회귀 + 커밋**
181
+
182
+ Run: `python3 -m pytest tests/ -q`
183
+ Expected: PASS (전체).
184
+
185
+ ```bash
186
+ git add scripts/okstra_ctl/run.py scripts/okstra_ctl/render.py tests/test_render_critic_block.py
187
+ git commit -m "feat(okstra_ctl): --critic flag + convergence.critic manifest block (phase-gated)"
188
+ ```
189
+
190
+ ---
191
+
192
+ ### B1-Task2: okstra-run wizard `S_CRITIC_PICK` step
193
+
194
+ **Files:**
195
+ - Modify: `scripts/okstra_ctl/wizard.py`
196
+ - Modify: `prompts/wizard/prompts.ko.json`
197
+ - Create: `tests/test_wizard_critic_pick.py`
198
+
199
+ - [ ] **Step 1: 실패하는 wizard 테스트 작성**
200
+
201
+ Create `tests/test_wizard_critic_pick.py`:
202
+
203
+ ```python
204
+ """S_CRITIC_PICK wizard step — coverage critic 선택.
205
+
206
+ 3 적용 phase 에서만 표시, 미선택 시. provider/off 를 state.critic 에 기록.
207
+ """
208
+ from __future__ import annotations
209
+
210
+ import sys
211
+ from pathlib import Path
212
+
213
+ import pytest
214
+
215
+ _REPO_ROOT = Path(__file__).resolve().parents[1]
216
+ sys.path.insert(0, str(_REPO_ROOT / "scripts"))
217
+
218
+ from okstra_ctl import wizard as W # noqa: E402
219
+
220
+
221
+ def _state(task_type="error-analysis", critic=""):
222
+ s = W.WizardState(workspace_root=str(_REPO_ROOT))
223
+ s.task_type = task_type
224
+ s.critic = critic
225
+ return s
226
+
227
+
228
+ def test_critic_pick_build_offers_off_and_provider_and_custom():
229
+ p = W._build_critic_pick(_state())
230
+ values = [o["value"] if isinstance(o, dict) else o.value for o in p.options]
231
+ assert "off" in values
232
+ assert "claude" in values
233
+
234
+
235
+ def test_critic_pick_submit_records_provider():
236
+ s = _state()
237
+ W._submit_critic_pick(s, "codex")
238
+ assert s.critic == "codex"
239
+
240
+
241
+ def test_critic_pick_submit_records_off():
242
+ s = _state()
243
+ W._submit_critic_pick(s, "off")
244
+ assert s.critic == "off"
245
+
246
+
247
+ def test_critic_pick_submit_rejects_unknown():
248
+ s = _state()
249
+ with pytest.raises(W.WizardError):
250
+ W._submit_critic_pick(s, "bogus")
251
+
252
+
253
+ @pytest.mark.parametrize("task_type", ["requirements-discovery", "error-analysis", "implementation-planning"])
254
+ def test_critic_step_applies_for_three_phases_when_unset(task_type):
255
+ step = next(st for st in W.STEPS if st.id == W.S_CRITIC_PICK)
256
+ assert step.applies(_state(task_type=task_type, critic="")) is True
257
+
258
+
259
+ @pytest.mark.parametrize("task_type", ["implementation", "final-verification", "release-handoff"])
260
+ def test_critic_step_skipped_for_other_phases(task_type):
261
+ step = next(st for st in W.STEPS if st.id == W.S_CRITIC_PICK)
262
+ assert step.applies(_state(task_type=task_type, critic="")) is False
263
+
264
+
265
+ def test_critic_step_skipped_when_preseeded():
266
+ step = next(st for st in W.STEPS if st.id == W.S_CRITIC_PICK)
267
+ assert step.applies(_state(task_type="error-analysis", critic="claude")) is False
268
+ ```
269
+
270
+ NOTE (confirmed against wizard.py): the step list is the module-level `STEPS: list[Step]` (≈line 1806) and the `Step` dataclass field is `id` (≈line 617: `id`, `applies`, `build`, `submit`, `owns`). `Prompt` is constructed with `step=` (see `_build_executor`). `_opt(value, label)` returns `Option(value, label, description)`. The test above is already written to these confirmed names.
271
+
272
+ - [ ] **Step 2: 실패 확인**
273
+
274
+ Run: `python3 -m pytest tests/test_wizard_critic_pick.py -q`
275
+ Expected: FAIL — `_build_critic_pick` / `S_CRITIC_PICK` 미정의 (`AttributeError`).
276
+
277
+ - [ ] **Step 3: wizard 구현**
278
+
279
+ (3a) `S_EXECUTOR = "executor"` 류 상수 근처에 추가:
280
+ ```python
281
+ S_CRITIC_PICK = "critic_pick"
282
+ ```
283
+
284
+ (3b) `WizardState` 의 `executor: str = ""` 줄 다음에 추가:
285
+ ```python
286
+ critic: str = ""
287
+ ```
288
+
289
+ (3c) `_build_executor` / `_submit_executor` 패턴을 그대로 따라 새 함수 추가(파일 내 다른 `_build_*`/`_submit_*` 모음 근처):
290
+ ```python
291
+ CRITIC_CHOICES = ["off", "claude", "codex", "gemini"]
292
+
293
+
294
+ def _build_critic_pick(state: WizardState) -> Prompt:
295
+ t = _p(state.workspace_root, "critic_pick")
296
+ return Prompt(
297
+ step=S_CRITIC_PICK, kind="pick",
298
+ label=t["label"],
299
+ options=[_opt(k, v) for k, v in t["options"].items() if not k.startswith("_")],
300
+ echo_template=t["echo_template"],
301
+ )
302
+
303
+
304
+ def _submit_critic_pick(state: WizardState, value: str) -> Optional[str]:
305
+ if value not in CRITIC_CHOICES:
306
+ raise WizardError(f"critic must be one of {CRITIC_CHOICES}, got: {value!r}")
307
+ state.critic = value
308
+ return f"critic: {value}"
309
+ ```
310
+ (If `Prompt`/`_opt`/`_p` signatures differ from what `_build_executor` uses, mirror `_build_executor` exactly — it is the canonical static-pick precedent.)
311
+
312
+ (3d) `Step(...)` 리스트(STEPS)에 `S_DEFAULTS_OR_CUSTOM` Step **앞에** 추가:
313
+ ```python
314
+ Step(S_CRITIC_PICK,
315
+ applies=lambda s: (s.task_type in ("requirements-discovery", "error-analysis", "implementation-planning")
316
+ and not s.critic
317
+ and S_CRITIC_PICK not in s.answered),
318
+ build=_build_critic_pick, submit=_submit_critic_pick,
319
+ owns=("critic",)),
320
+ ```
321
+
322
+ (3e) finalize dict(반환 dict, `"executor": state.executor,` 가 있는 곳)에 추가:
323
+ ```python
324
+ "critic": state.critic,
325
+ ```
326
+
327
+ (3f) summary 출력부(`executor` summary line 근처)에, 3 적용 phase 일 때 critic 줄 추가:
328
+ ```python
329
+ if state.task_type in ("requirements-discovery", "error-analysis", "implementation-planning"):
330
+ lines.append(f" critic : {state.critic or '(off)'}")
331
+ ```
332
+
333
+ (3g) wizard `_cli` 의 argparse(pre-seed 용)에 `--critic` 추가하고 초기 state 에 반영 — `executor` 가 pre-seed 되는 방식을 그대로 따라 `--critic` 를 받아 `state.critic` 에 넣는다(없으면 빈 문자열). 이로써 플래그가 넘어오면 `not s.critic` 가 거짓이 되어 step 이 건너뛰어진다. (executor 의 pre-seed 경로를 grep 해 동일하게 배선.)
334
+
335
+ - [ ] **Step 4: prompts.ko.json `critic_pick` 항목 추가**
336
+
337
+ `prompts/wizard/prompts.ko.json` 에 새 키 추가(다른 step 항목과 같은 형식):
338
+ ```json
339
+ "critic_pick": {
340
+ "label": "coverage critic 를 추가로 돌릴까요? (놓친 finding·각도를 캐는 검증 패스 — opt-in)",
341
+ "echo_template": "critic: {value}",
342
+ "options": {
343
+ "off": "사용 안 함 (기본·추천)",
344
+ "claude": "claude critic (추천)",
345
+ "직접 입력": "직접 입력 (codex / gemini 등 provider 직접 지정)"
346
+ }
347
+ }
348
+ ```
349
+ NOTE — "직접 입력" custom row (REQUIRED by the user's standing picker convention: 추천 1~2개 + 마지막 "직접 입력"). The codebase's custom-input mechanism is `PICK_TYPE_CUSTOM = "__free_input__"` (≈line 87) plus a per-field `*_pending_text` flag, used by pickers like `directive` / `task_group`. The fixed-choice `executor` step does NOT use it. So model the critic picker on a custom-input picker (grep `pending_text` and `PICK_TYPE_CUSTOM` and read e.g. the `directive` build/submit pair): the "직접 입력" row emits `PICK_TYPE_CUSTOM`, the wizard then prompts free text, and `_submit_critic_pick` receives that text (a provider string `codex`/`gemini`) and validates it against `CRITIC_CHOICES`. If wiring a full pending-text round is disproportionate for a closed 4-value enum, the acceptable fallback is to list all four values (`off`/`claude`/`codex`/`gemini`) as explicit options with "직접 입력" still present as the trailing custom row — but do NOT silently drop the custom row. Confirm the chosen approach in the implementer report.
350
+
351
+ - [ ] **Step 5: wizard 테스트 통과 확인**
352
+
353
+ Run: `python3 -m pytest tests/test_wizard_critic_pick.py -q`
354
+ Expected: PASS.
355
+
356
+ - [ ] **Step 6: 커밋**
357
+
358
+ ```bash
359
+ git add scripts/okstra_ctl/wizard.py prompts/wizard/prompts.ko.json tests/test_wizard_critic_pick.py
360
+ git commit -m "feat(okstra_ctl/wizard): S_CRITIC_PICK step for coverage critic selection"
361
+ ```
362
+
363
+ ---
364
+
365
+ ### B1-Task3: convergence SKILL.md — "Coverage critic pass" 절
366
+
367
+ **Files:**
368
+ - Modify: `skills/okstra-convergence/SKILL.md`
369
+
370
+ - [ ] **Step 1: "Coverage critic pass" 절 추가**
371
+
372
+ `## Output` 절(Phase 6 로 넘기는 정보) **앞에**(또는 §"Plan-body verification mode" 앞 적절한 위치에) 새 절 삽입:
373
+
374
+ ```markdown
375
+ ## Coverage critic pass
376
+
377
+ Runs only when `convergence.critic.enabled == true` (set by `--critic <provider>` or the okstra-run `critic_pick` step; default off). Applies to `requirements-discovery`, `error-analysis`, and `implementation-planning`. This pass targets **coverage** (missed findings), distinct from convergence which targets **agreement quality**.
378
+
379
+ ### When
380
+ After Phase 5.5 finding convergence completes (findings classified) and BEFORE the Phase 6 report-writer dispatch.
381
+
382
+ ### Dispatch (reused worker)
383
+ Dispatch ONE pass to the `config.critic.provider`'s existing subagent (`claude-worker` / `codex-worker` / `gemini-worker`) with `model = config.critic.modelExecutionValue` — no new agent type. Result path: `runs/<task-type>/worker-results/<provider>-critic-<task-type>-<seq>.md`. The critic prompt seeds the consolidated findings and asks ONLY for coverage gaps:
384
+
385
+ ```
386
+ You are the coverage critic for <task-key>. Below are the findings the workers
387
+ already agreed on. Your ONLY job is to name what is MISSING:
388
+ - files / directories / execution paths nobody inspected,
389
+ - requirements or acceptance points with zero findings,
390
+ - claims raised but never verified.
391
+ For each gap, emit a NEW finding with evidence (file:line or the requirement quote).
392
+ Do NOT restate an existing finding. If nothing is missing, say so explicitly.
393
+ ```
394
+
395
+ ### Gap verification (1 adversarial reverify round)
396
+ Each critic gap enters the verification queue as a finding with `originWorker = "<provider>-critic"` and `source = "critic"`. The lead runs ONE adversarial reverify round (§"Adversarial Verification Mode" classifier) with the Phase 4 analysers (excluding the critic itself) as voters. Only gaps classified `full-consensus` / `partial-consensus` merge into the final report findings; `contested` / `worker-unique` gaps are treated as hallucinations and dropped (recorded in the convergence state, not promoted). If fewer than one non-critic analyser is available to vote, the gaps are surfaced as unverified `clarification` items rather than merged, and that fact is recorded.
397
+
398
+ ### State
399
+ - `convergence.critic` manifest block: `{ enabled, provider, modelExecutionValue }`.
400
+ - Convergence state artifact: critic gaps appear in `findings[]` with `source: "critic"`. Add a `config.critic` summary `{ provider, modelExecutionValue, gapsProposed, gapsMerged }`. `source` and `config.critic` are optional v1.2 fields (readers treat absence as null); no enum changes.
401
+ ```
402
+
403
+ - [ ] **Step 2: 빌드 + 검증**
404
+
405
+ Run: `npm run build && bash validators/validate-workflow.sh`
406
+ Expected: build 22/22 synced, validator PASS.
407
+
408
+ - [ ] **Step 3: 커밋**
409
+
410
+ ```bash
411
+ git add skills/okstra-convergence/SKILL.md runtime/
412
+ git commit -m "feat(skills/okstra-convergence): define coverage critic pass"
413
+ ```
414
+
415
+ ---
416
+
417
+ ### B1-Task4: agents/SKILL.md 흐름 + 3 프로필 opt-in 선언
418
+
419
+ **Files:**
420
+ - Modify: `agents/SKILL.md`
421
+ - Modify: `prompts/profiles/requirements-discovery.md`, `error-analysis.md`, `implementation-planning.md`
422
+
423
+ - [ ] **Step 1: agents/SKILL.md — Phase 5.5→6 critic pass + PROGRESS**
424
+
425
+ (1a) lead phase 표/Phase 6 설명에서 Phase 5.5 와 Phase 6 사이에 한 줄 추가(Phase 5.5 행 설명 끝 또는 Phase 6 행 앞):
426
+ ```markdown
427
+ | 5.6 Coverage critic | (opt-in) reused-worker critic pass for missed findings, 1 adversarial reverify round | `okstra-convergence` "Coverage critic pass" |
428
+ ```
429
+ (표 컬럼 수에 맞춰 기존 행 형식대로.)
430
+
431
+ (1b) Progress reporting 체크포인트 목록에서 `PROGRESS: phase-6-synthesis ...` 줄 **앞에** 추가:
432
+ ```markdown
433
+ - `PROGRESS: phase-5.6-critic provider=<provider> gaps=<n>` — when the coverage critic pass runs (Phase 5.6, opt-in). Omitted when `convergence.critic.enabled == false`.
434
+ ```
435
+
436
+ - [ ] **Step 2: 3 프로필에 coverage critic opt-in 선언**
437
+
438
+ 세 파일 각각의 `- Cross-verification mode:` bullet(requirements-discovery / error-analysis 는 직전 sub-project 가 추가한 bullet; implementation-planning 도 동일 bullet 존재) 아래에 sub-bullet 추가. requirements-discovery 예:
439
+ ```markdown
440
+ - **Coverage critic (opt-in)**: when `convergence.critic.enabled=true` (chosen via `--critic` or the okstra-run picker), a reused-worker critic pass runs after convergence to surface missed findings; its gaps are merged only after a 1-round adversarial reverify. See `skills/okstra-convergence/SKILL.md` "Coverage critic pass".
441
+ ```
442
+ error-analysis / implementation-planning 도 동일 문장(phase 명만 자연스럽게). requirements-discovery / error-analysis 가 `- Cross-verification mode:` bullet 을 아직 안 가졌다면(직전 sub-project 가 error-analysis 에 추가했는지 확인 — 안 했으면 새 `- Cross-verification mode:` top-level bullet 을 `- Non-goals:` 앞에 만들고 그 안에 이 sub-bullet 을 넣는다).
443
+
444
+ - [ ] **Step 3: 빌드 + 검증**
445
+
446
+ Run: `npm run build && bash validators/validate-workflow.sh`
447
+ Expected: build 성공, validator PASS.
448
+
449
+ - [ ] **Step 4: 커밋**
450
+
451
+ ```bash
452
+ git add agents/SKILL.md prompts/profiles/requirements-discovery.md prompts/profiles/error-analysis.md prompts/profiles/implementation-planning.md runtime/
453
+ git commit -m "feat(agents,prompts/profiles): declare opt-in coverage critic pass"
454
+ ```
455
+
456
+ ---
457
+
458
+ ### B1-Task5: CHANGES + 전체 검증 + 최종 커밋
459
+
460
+ **Files:**
461
+ - Modify: `CHANGES.md`
462
+
463
+ - [ ] **Step 1: CHANGES.md 항목 추가**
464
+
465
+ `## 2026-06-04` 헤더 바로 다음, 기존 첫 `###` 블록 앞에 삽입:
466
+
467
+ ```markdown
468
+ ### feat(convergence): opt-in coverage critic — 놓친 finding 을 캐는 검증 패스
469
+
470
+ - 워커들은 설계상 같은 질문을 triangulate 하므로 "아무도 안 본 각도" 는 구조적으로 비어 있다(커버리지 갭). requirements-discovery / error-analysis / implementation-planning 에 **opt-in coverage critic** 을 추가 — Phase 5.5 직후, 선택한 provider 의 기존 worker 를 critic 프롬프트로 재사용 dispatch 해 "빠진 파일/각도/요구사항" 을 새 finding 으로 내게 하고, 그 gap 은 **1회 적대적 reverify** 를 거쳐 살아남은 것만 최종 리포트에 병합한다(환각 gap 기각). 사용 여부·provider 는 okstra-run 초기 select box(추천 + 직접 입력) 또는 `--critic <off|claude|codex|gemini>` 로 고른다 — 플래그를 넘기면 선택 단계는 건너뛴다. 모델은 선택한 provider 의 모델을 따른다. **기본 off** 라 미선택 run 의 비용은 0.
471
+ - 사용자 영향: 다음 release + `npx -y okstra@latest install` 후 적용. 이제 세 phase 에서 교차검증이 끝난 뒤 원하면 critic 패스를 켜 놓친 finding 을 한 번 더 캘 수 있다(기본 꺼짐). critic gap 은 적대적 검증을 통과해야 리포트에 들어가므로 근거 약한 gap 은 걸러진다. critic *행동*은 lead/워커 prompt 지시(LLM)이며, 강제되는 건 `--critic` 해석·`convergence.critic` manifest 형태·wizard step 뿐이다.
472
+ ```
473
+
474
+ - [ ] **Step 2: 전체 검증**
475
+
476
+ Run:
477
+ ```bash
478
+ npm run build
479
+ python3 -m pytest tests/ -q
480
+ bash validators/validate-workflow.sh
481
+ node bin/okstra --version
482
+ ```
483
+ ALL must pass. pytest 실패가 1건이라도 있으면(단, `tests/test_okstra_worktree.py` 의 알려진 간헐 격리 flake 는 단독 재실행으로 확인) STOP + BLOCKED 보고.
484
+
485
+ - [ ] **Step 3: 일관성 self-review**
486
+
487
+ Run:
488
+ ```bash
489
+ grep -rn "convergence.critic\|CRITIC_CHOICE\|critic_pick\|S_CRITIC_PICK\|Coverage critic" scripts/okstra_ctl/ skills/okstra-convergence/SKILL.md agents/SKILL.md prompts/ tests/ CHANGES.md
490
+ ```
491
+ Confirm: `--critic`/CRITIC_CHOICE(run+render), critic_pick/S_CRITIC_PICK(wizard+json+test), convergence.critic(render+skill+profiles), Coverage critic(skill+agents+profiles+CHANGES) 가 정의/참조 일관. 댕글링 토큰 없음.
492
+
493
+ - [ ] **Step 4: 최종 커밋**
494
+
495
+ ```bash
496
+ git add CHANGES.md
497
+ git commit -m "docs(changes): log opt-in coverage critic pass"
498
+ ```
499
+
500
+ ---
501
+
502
+ ## Self-Review (작성자 체크리스트)
503
+
504
+ **1. Spec coverage**
505
+ - §2.1 critic primitive(dispatch·1회 적대 reverify·병합·모델) → Task3(skill 정의) + Task1(모델/블록).
506
+ - §2.2 wizard select + CLI bypass → Task2(S_CRITIC_PICK, pre-seed bypass) + Task1(`--critic`).
507
+ - §2.3 manifest critic 블록 + render resolve → Task1.
508
+ - §2.4 3 적용 phase → Task1(render phase-gate) + Task4(프로필 선언).
509
+ - §3 데이터(manifest/state source/config.critic) → Task1 + Task3.
510
+ - §4 변경 파일 → Task1–5 전부.
511
+ - §5 enforcement(테스트 강제 vs prompt-only) → Task1/Task2 테스트, Task3/4 prompt 선언.
512
+ - §7 수용기준 1–5 → Task2(wizard), Task1(manifest), Task3(skill), Task4(profile), Task5(검증).
513
+
514
+ **2. Placeholder scan:** 코드 블록은 실제 내용. wizard 의 일부 anchor(STEPS 리스트명·Prompt 필드명·custom-input 인코딩)는 "read-before-edit + mirror executor step" 으로 명시 — 추측 금지 지시. TBD/TODO 없음.
515
+
516
+ **3. 식별자 일관성:** `critic`(PrepareInputs/WizardState field), `CRITIC_CHOICE`(ctx), `S_CRITIC_PICK`/`critic_pick`(wizard step/json), `convergence.critic`(manifest), `source:"critic"`(state) — Task 간 동일 철자 확인.