okstra 0.55.0 → 0.56.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/bin/okstra +24 -7
  2. package/docs/project-structure-overview.md +0 -1
  3. package/docs/superpowers/plans/2026-05-25-okstra-project-root-rename.md +0 -1
  4. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase2.md +275 -0
  5. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase3.md +282 -0
  6. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4a.md +147 -0
  7. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4b.md +262 -0
  8. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4c.md +184 -0
  9. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4d.md +88 -0
  10. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4e.md +250 -0
  11. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa.md +409 -0
  12. package/docs/superpowers/specs/2026-06-07-stage-conformance-qa-design.md +169 -0
  13. package/package.json +1 -1
  14. package/runtime/BUILD.json +2 -2
  15. package/runtime/bin/lib/okstra/cli.sh +5 -1
  16. package/runtime/bin/lib/okstra/usage.sh +5 -0
  17. package/runtime/bin/okstra.sh +1 -0
  18. package/runtime/prompts/profiles/_implementation-verifier.md +23 -2
  19. package/runtime/prompts/profiles/final-verification.md +1 -0
  20. package/runtime/prompts/profiles/implementation-planning.md +4 -0
  21. package/runtime/python/okstra_ctl/conformance.py +270 -0
  22. package/runtime/python/okstra_ctl/paths.py +2 -0
  23. package/runtime/python/okstra_ctl/run.py +29 -0
  24. package/runtime/skills/okstra-run/SKILL.md +12 -0
  25. package/runtime/skills/okstra-setup/SKILL.md +35 -0
  26. package/runtime/validators/validate-implementation-plan-stages.py +28 -3
  27. package/runtime/validators/validate-run.py +96 -0
  28. package/src/okstra-dirs.mjs +1 -1
  29. package/src/migrate.mjs +0 -146
@@ -0,0 +1,88 @@
1
+ # Stage Conformance QA — Phase 4d (verifier/final-verification Tier 3 실행) Implementation Plan
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:** verifier 가 stage conformance 스크립트를 실제로 실행하고 결과 사이드카(`<task_root>/qa/result-<stageKey>.json`)를 작성하도록 prompt 계약을 추가한다(Tier 3). final-verification 은 전 stage 합집합을 통합 state 에 대해 실행한다. 이로써 Phase 3 게이트가 읽을 결과가 생산돼 BLOCKING 강제가 활성화된다.
6
+
7
+ **Architecture:** 순수 prompt 계약 변경(2개 markdown) + `npm run build`. 코드 변경 없음 — 사이드카 포맷/파서/게이트는 Phase 1~4c 에서 이미 고정됨. 검증은 build 성공 + workflow validator + 전체 pytest 회귀 없음 + grep 토큰 일관성. SSOT: [`docs/superpowers/specs/2026-06-07-stage-conformance-qa-design.md`](../specs/2026-06-07-stage-conformance-qa-design.md) §12.3, §12.6.
8
+
9
+ **Tech Stack:** markdown prompt profiles, `tools/build.mjs`.
10
+
11
+ **전제:** Phase 1~4c 완료. `result-<stageKey>.json` 포맷 `{stageKey, overall, requirements}` (코드 고정: `qa_result_from_dict` / validate-run 로더). 매니페스트 위치 `<task_root>/qa/`(토큰 `TASK_QA_PATH`). 스크립트 인터페이스 `main → exit 0=PASS/≠0=FAIL` + stdout `QA-RESULT: PASS|FAIL` + `REQ <id>: PASS|FAIL: <근거>`.
12
+
13
+ **검증 토큰(발명 금지 — 실제 코드와 일치 확인):** `TASK_QA_PATH`([paths.py](../../../scripts/okstra_ctl/paths.py)), `result-<stageKey>.json`([validate-run.py `_load_conformance_results`](../../../validators/validate-run.py)), `QA-RESULT`/`REQ`([conformance.py `parse_qa_result`](../../../scripts/okstra_ctl/conformance.py)), 필드 `overall`/`requirements`.
14
+
15
+ ---
16
+
17
+ ## Task 1: verifier 에 Tier 3 추가
18
+
19
+ **Files:**
20
+ - Modify: `prompts/profiles/_implementation-verifier.md`
21
+
22
+ - [ ] **Step 1: Tier 3 서브섹션 추가**
23
+
24
+ `### Two-tier command lookup (NO auto-detection)` 의 Tier 2 schema 블록 + `### Execution rule` 다음(또는 그 사이 적절한 위치)에 새 서브섹션을 추가한다. 제목을 `### Tier 3 — stage conformance scripts (요구사항 부합 검증)` 로 하고 다음 계약을 명문화:
25
+
26
+ - 소스: 이 run 의 stage 에 해당하는 `<task_root>/qa/conformance-manifest.json`(토큰 `TASK_QA_PATH`) entry. 이 run 의 stageKey 는 `<task-id>-stage-<N>`(주입된 Stage 번호). 매니페스트에서 그 stageKey 의 entry 를 찾는다.
27
+ - entry 에 `exemption` 또는 `waiver` 가 있으면 **실행하지 않고** 그 사실(사유)을 Read-only command log + 결과 사이드카에 기록한다(게이트는 통과; waiver 는 conditional). 실행 안 함.
28
+ - 그 외에는 entry 의 `runCommand` 를 **worktree cwd** 에서 실행한다. env 는 `<PROJECT_ROOT>/.okstra/project.json` 의 `qaEnv`(replicaDbDsn / appBaseUrl / envFile — Phase 4e)에서 주입한다. **replica/test env 전용** — shared/staging/prod 금지(기존 DB real-execution 게이트와 동일 원칙).
29
+ - 표준 인터페이스 해석: 프로세스 exit code + stdout 의 `QA-RESULT: PASS|FAIL`(여럿이면 마지막) + `REQ <id>: PASS|FAIL: <근거>` 줄들. 마커가 없으면 `MISSING`(=게이트 BLOCKING).
30
+ - **결과 사이드카 작성(BLOCKING 산출물)**: `<task_root>/qa/result-<stageKey>.json` 을 `{"stageKey": "<…>", "overall": "PASS|FAIL|MISSING", "ranAt": "<UTC ISO8601>", "requirements": { "<id>": {"status": "...", "reason": "..."} }}` 로 쓴다. 이 파일이 validate-run Tier3 게이트의 입력이다 — 누락 시 게이트가 "never ran"으로 BLOCKING.
31
+ - Read-only command log 에 `runCommand` exact line + exit code 기록. conformance 스크립트는 **replica datastore 를 mutate 해도 됨**(통합 검증의 본질) — 단 qaEnv replica 대상에 한함. `runCommand` 자체에 대한 source/lockfile mutation deny-list(`--fix`/`npm install` 등)는 동일 적용.
32
+ - 매니페스트가 없거나 이 stage 의 entry 가 없으면 verifier 는 `conformance: no manifest entry for <stageKey>` 를 기록한다(선언 강제는 planning S11 + validate-run diff-surface 교차검증의 몫).
33
+
34
+ - [ ] **Step 2: Execution rule / Read-only log 연계 한 줄**
35
+
36
+ `### Execution rule` 단락에 Tier 3 가 Tier 1·2 다음에 실행된다는 한 줄을 추가하고, `### Read-only command log` 가 Tier 3 conformance 실행도 포함함을 명시.
37
+
38
+ - [ ] **Step 3: 빌드 + 검증**
39
+
40
+ Run: `npm run build`
41
+ Run: `bash validators/validate-workflow.sh` → PASS
42
+ Run: `python3 -m pytest tests/ -q` → 회귀 없음
43
+ Run(토큰 일관성): `grep -n "TASK_QA_PATH\|result-.*stageKey\|QA-RESULT\|conformance-manifest" prompts/profiles/_implementation-verifier.md` — 참조가 실제 코드 토큰과 일치하는지 육안 확인.
44
+
45
+ - [ ] **Step 4: 커밋**
46
+
47
+ ```bash
48
+ git add prompts/profiles/_implementation-verifier.md
49
+ git commit -m "feat(prompts/implementation-verifier): Tier3 conformance 스크립트 실행 + 결과 사이드카"
50
+ ```
51
+
52
+ ## Task 2: final-verification 에 Tier 3 추가
53
+
54
+ **Files:**
55
+ - Modify: `prompts/profiles/final-verification.md`
56
+
57
+ - [ ] **Step 1: Two-tier → Tier 3 확장**
58
+
59
+ `Two-tier command lookup (shared with implementation)` 단락(현 line 38 부근)에, whole-task 재검증 시 **Tier 3 conformance** 도 실행한다는 계약을 추가:
60
+
61
+ - task-level `<task_root>/qa/conformance-manifest.json` 의 **전 stage entry** 에 대해, 통합(merged) state 에 대해 각 `runCommand` 를 실행하고 `<task_root>/qa/result-<stageKey>.json` 을 갱신한다. (single-stage scope 면 해당 stage 만.)
62
+ - exemption/waiver entry 는 실행하지 않고 기록(waiver → conditional-accept 조건으로 §2 Verdict 에 노출).
63
+ - 결과가 PASS 가 아닌(또는 미실행) entry 는 Acceptance Blocker(`major`+) — 기존 DB real-execution 게이트(line 17)와 동일하게 `accepted` 를 막아 `conditional-accept`/`blocked` 로 만든다. 이는 validate-run Tier3 게이트와 일치한다.
64
+ - env/deny-list 규칙은 verifier 와 동일(replica 전용, source mutation 금지, self-check).
65
+
66
+ - [ ] **Step 2: 빌드 + 검증**
67
+
68
+ Run: `npm run build`
69
+ Run: `bash validators/validate-workflow.sh` → PASS
70
+ Run: `python3 -m pytest tests/ -q` → 회귀 없음
71
+
72
+ - [ ] **Step 3: 커밋**
73
+
74
+ ```bash
75
+ git add prompts/profiles/final-verification.md
76
+ git commit -m "feat(prompts/final-verification): Tier3 conformance 합집합 실행 + acceptance blocker 연계"
77
+ ```
78
+
79
+ ---
80
+
81
+ ## Phase 4d Self-Review 체크
82
+
83
+ - [ ] verifier 가 stageKey entry 를 실행 → `result-<stageKey>.json` 작성(포맷 정확)? exemption/waiver 시 미실행+기록?
84
+ - [ ] final-verification 이 task-level qa 전 stage 합집합 실행 + acceptance blocker 연계?
85
+ - [ ] 참조 토큰(TASK_QA_PATH / result-<stageKey>.json / QA-RESULT / overall/requirements)이 실제 코드와 일치(발명 없음)?
86
+ - [ ] `npm run build` 동기화 + workflow validator + 전체 pytest 통과?
87
+
88
+ Phase 4d 완료 후 Phase 4e(`qaEnv` 스키마/setup + `--qa-waiver` CLI + wizard)로 conformance QA 기능을 완성한다.
@@ -0,0 +1,250 @@
1
+ # Stage Conformance QA — Phase 4e (env + 우회 UX = 완성) Implementation Plan
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:** conformance QA 기능을 완성한다 — (a) `--qa-waiver <stageKey>:"<reason>"` 로 사용자 확인형 우회를 매니페스트 entry 의 `waiver` 에 기록, (b) `project.json.qaEnv.surfacePatterns` 를 diff-surface 교차검증에 반영, (c) `qaEnv`(replicaDbDsn/appBaseUrl/envFile/surfacePatterns) 를 setup 문서/wizard 에 명문화.
6
+
7
+ **Architecture:** 순수 헬퍼(`conformance.py`) + run.py prepare-time I/O + CLI 패스스루(bash) + validate-run surfacePatterns 배선 + 문서. SSOT: [`docs/superpowers/specs/2026-06-07-stage-conformance-qa-design.md`](../specs/2026-06-07-stage-conformance-qa-design.md) §7.2, §8, §12.5, §12.7.
8
+
9
+ **Tech Stack:** Python 3 (pytest), Bash, markdown.
10
+
11
+ **전제:** Phase 1~4d 완료. `detect_surfaces(file_paths, patterns)` 이미 override param 보유. `task_dir(project_root, task_group, task_id)` 존재([paths.py:39](../../../scripts/okstra_ctl/paths.py)).
12
+
13
+ ---
14
+
15
+ ## Task 1: waiver 헬퍼(순수)
16
+
17
+ **Files:** Modify `scripts/okstra_ctl/conformance.py`; Test `tests/test_okstra_ctl_conformance.py`
18
+
19
+ - [ ] **Step 1: 실패 테스트 추가**
20
+
21
+ ```python
22
+ from okstra_ctl.conformance import apply_qa_waiver, parse_qa_waiver_arg # noqa: E402
23
+
24
+
25
+ def test_parse_qa_waiver_arg_splits_on_first_colon():
26
+ assert parse_qa_waiver_arg('t-stage-1:replica down: skip') == ("t-stage-1", "replica down: skip")
27
+ assert parse_qa_waiver_arg("nope") is None
28
+ assert parse_qa_waiver_arg("") is None
29
+
30
+
31
+ def test_apply_qa_waiver_sets_entry_waiver():
32
+ manifest = {"entries": [{"stageKey": "t-stage-1", "waiver": None}]}
33
+ assert apply_qa_waiver(manifest, "t-stage-1", "replica down", at="2026-06-07T00:00:00Z") is True
34
+ w = manifest["entries"][0]["waiver"]
35
+ assert w["acknowledgedBy"] == "user" and w["reason"] == "replica down"
36
+ assert w["scope"] == [] and w["at"] == "2026-06-07T00:00:00Z"
37
+
38
+
39
+ def test_apply_qa_waiver_unknown_stage_returns_false():
40
+ manifest = {"entries": [{"stageKey": "t-stage-1"}]}
41
+ assert apply_qa_waiver(manifest, "t-stage-9", "x", at="t") is False
42
+ ```
43
+
44
+ - [ ] **Step 2: 실패 확인** — `python3 -m pytest tests/test_okstra_ctl_conformance.py -q` → FAIL(import 없음)
45
+
46
+ - [ ] **Step 3: 구현 추가** (`conformance.py` 끝)
47
+
48
+ ```python
49
+ def parse_qa_waiver_arg(arg: object) -> tuple[str, str] | None:
50
+ """`--qa-waiver` 값 `<stageKey>:<reason>` 를 (stageKey, reason) 로 분해.
51
+ 형식이 아니거나 비면 None."""
52
+ if not isinstance(arg, str) or ":" not in arg:
53
+ return None
54
+ key, reason = arg.split(":", 1)
55
+ key, reason = key.strip(), reason.strip()
56
+ if not key or not reason:
57
+ return None
58
+ return key, reason
59
+
60
+
61
+ def apply_qa_waiver(manifest: object, stage_key: str, reason: str, *, at: str,
62
+ acknowledged_by: str = "user") -> bool:
63
+ """매니페스트에서 stage_key entry 의 `waiver` 를 채운다(in place). 찾으면 True.
64
+ 사용자 확인형 우회(spec §7.2) — reason 은 사용자 지시 원문."""
65
+ entries = manifest.get("entries") if isinstance(manifest, dict) else None
66
+ if not isinstance(entries, list):
67
+ return False
68
+ for entry in entries:
69
+ if isinstance(entry, dict) and entry.get("stageKey") == stage_key:
70
+ entry["waiver"] = {"acknowledgedBy": acknowledged_by, "reason": reason,
71
+ "scope": [], "at": at}
72
+ return True
73
+ return False
74
+ ```
75
+
76
+ - [ ] **Step 4: 통과** — `python3 -m pytest tests/test_okstra_ctl_conformance.py -q` → PASS
77
+ - [ ] **Step 5: 커밋** — `git commit -m "feat(okstra_ctl/conformance): qa waiver 파싱/적용 헬퍼"`
78
+
79
+ ## Task 2: run.py — `--qa-waiver` prepare-time 적용
80
+
81
+ **Files:** Modify `scripts/okstra_ctl/run.py`; Test `tests/test_okstra_run_qa_waiver.py` (신규)
82
+
83
+ - [ ] **Step 1: argparse + PrepareInputs 필드 추가**
84
+
85
+ `scripts/okstra_ctl/run.py` 의 argparse 에 `--approved-plan` 근처(line 1862 부근)에:
86
+
87
+ ```python
88
+ p.add_argument("--qa-waiver", default="", dest="qa_waiver",
89
+ help='Stage conformance 우회: "<stageKey>:<reason>" (사용자 확인형, 매니페스트 entry.waiver 기록)')
90
+ ```
91
+
92
+ `PrepareInputs`(line 258, `approved_plan_path` 근처)에 `qa_waiver: str = ""` 추가. argv→PrepareInputs 매핑 지점에도 `qa_waiver=args.qa_waiver` 전달.
93
+
94
+ - [ ] **Step 2: implementation prepare gate 에 waiver 적용**
95
+
96
+ `run.py` 의 implementation 진입 게이트(qaCommands 검증 블록, `if inp.task_type == "implementation":` 부근 line 1112)에 이어, waiver 적용 헬퍼를 추가하고 호출:
97
+
98
+ ```python
99
+ def _apply_qa_waiver_if_requested(inp: "PrepareInputs", project_root: Path) -> None:
100
+ """`--qa-waiver` 가 있으면 task-level 매니페스트 entry 의 waiver 를 채운다."""
101
+ if not inp.qa_waiver:
102
+ return
103
+ from .conformance import apply_qa_waiver, parse_qa_waiver_arg
104
+ from .paths import task_dir
105
+ parsed = parse_qa_waiver_arg(inp.qa_waiver)
106
+ if parsed is None:
107
+ raise PrepareError(
108
+ f'--qa-waiver must be "<stageKey>:<reason>", got {inp.qa_waiver!r}'
109
+ )
110
+ stage_key, reason = parsed
111
+ manifest_path = task_dir(project_root, inp.task_group, inp.task_id) / "qa" / "conformance-manifest.json"
112
+ if not manifest_path.is_file():
113
+ raise PrepareError(f"--qa-waiver: conformance manifest not found at {manifest_path}")
114
+ manifest = json.loads(manifest_path.read_text())
115
+ when = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
116
+ if not apply_qa_waiver(manifest, stage_key, reason, at=when):
117
+ raise PrepareError(f"--qa-waiver: stageKey {stage_key!r} not in manifest {manifest_path}")
118
+ manifest_path.write_text(json.dumps(manifest, indent=2, ensure_ascii=False) + "\n")
119
+ ```
120
+
121
+ 게이트 블록에서 `if inp.task_type == "implementation":` 안, qaCommands 검증 다음에 `_apply_qa_waiver_if_requested(inp, project_root)` 호출. (`datetime`/`timezone` import 가 없으면 상단에 `from datetime import datetime, timezone` 추가.)
122
+
123
+ - [ ] **Step 3: 테스트(신규 파일)**
124
+
125
+ ```python
126
+ # tests/test_okstra_run_qa_waiver.py
127
+ import json, sys
128
+ from pathlib import Path
129
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "scripts"))
130
+ from okstra_ctl.run import _apply_qa_waiver_if_requested, PrepareInputs, PrepareError
131
+ from okstra_ctl.paths import task_dir
132
+ import pytest
133
+
134
+
135
+ def _inp(**o):
136
+ base = dict(task_type="implementation", task_group="g", task_id="t", qa_waiver="")
137
+ base.update(o)
138
+ return PrepareInputs(**{k: v for k, v in base.items() if k in PrepareInputs.__dataclass_fields__})
139
+
140
+
141
+ def test_waiver_fills_manifest(tmp_path):
142
+ qa = task_dir(tmp_path, "g", "t") / "qa"; qa.mkdir(parents=True)
143
+ (qa / "conformance-manifest.json").write_text(json.dumps(
144
+ {"entries": [{"stageKey": "t-stage-1", "waiver": None}]}))
145
+ _apply_qa_waiver_if_requested(_inp(qa_waiver="t-stage-1:replica down"), tmp_path)
146
+ m = json.loads((qa / "conformance-manifest.json").read_text())
147
+ assert m["entries"][0]["waiver"]["reason"] == "replica down"
148
+
149
+
150
+ def test_waiver_unknown_stage_raises(tmp_path):
151
+ qa = task_dir(tmp_path, "g", "t") / "qa"; qa.mkdir(parents=True)
152
+ (qa / "conformance-manifest.json").write_text(json.dumps({"entries": []}))
153
+ with pytest.raises(PrepareError):
154
+ _apply_qa_waiver_if_requested(_inp(qa_waiver="t-stage-1:x"), tmp_path)
155
+
156
+
157
+ def test_no_waiver_noop(tmp_path):
158
+ _apply_qa_waiver_if_requested(_inp(qa_waiver=""), tmp_path) # no manifest, no error
159
+ ```
160
+
161
+ (주의: `PrepareInputs` 필수 필드가 더 있으면 `_inp` 헬퍼를 실제 필드에 맞춰 보강 — 구현자가 dataclass 확인.)
162
+
163
+ - [ ] **Step 4: 통과 + 전체** — `python3 -m pytest tests/test_okstra_run_qa_waiver.py tests/ -q`
164
+ - [ ] **Step 5: 커밋** — `git commit -m "feat(okstra_ctl/run): --qa-waiver 로 매니페스트 entry waiver 기록"`
165
+
166
+ ## Task 3: CLI 패스스루(bash)
167
+
168
+ **Files:** Modify `scripts/okstra.sh`, `scripts/lib/okstra/cli.sh`; Test `tests/test_okstra_sh_qa_waiver_passthrough.py`
169
+
170
+ - [ ] **Step 1: cli.sh 옵션 파싱** — `--stage)` 케이스(line 98) 근처에 추가:
171
+
172
+ ```bash
173
+ --qa-waiver)
174
+ QA_WAIVER="$(require_option_value --qa-waiver "${2-}")"
175
+ shift 2
176
+ ;;
177
+ ```
178
+
179
+ help 문자열(line 192)의 옵션 목록에 `--qa-waiver` 추가.
180
+
181
+ - [ ] **Step 2: okstra.sh 전달** — line 125(`--stage`) 근처에:
182
+
183
+ ```bash
184
+ [[ -n "${QA_WAIVER-}" ]] && PY_ARGS+=(--qa-waiver "$QA_WAIVER")
185
+ ```
186
+
187
+ - [ ] **Step 3: 테스트** — `tests/test_okstra_sh_stage_passthrough.py` 를 모델로 `--qa-waiver foo:bar` 가 `python -m okstra_ctl.run` 인자까지 전달되는지 검증(스테이지 테스트의 인자-캡처 패턴 재사용).
188
+
189
+ - [ ] **Step 4: 통과 + 커밋** — `git commit -m "feat(cli): --qa-waiver 패스스루(okstra.sh/cli.sh)"`
190
+
191
+ ## Task 4: surfacePatterns 배선(validate-run)
192
+
193
+ **Files:** Modify `validators/validate-run.py`; Test `tests/test_validate_run_conformance.py`
194
+
195
+ - [ ] **Step 1: `_validate_conformance` 에 surface_patterns 주입**
196
+
197
+ `_validate_conformance(report_path, failures)` 시그니처에 `surface_patterns: object = None` 추가. 내부 diff-surface 호출을 `detect_surfaces(changed_files, surface_patterns)` 로 바꾼다. 메인 dispatch 에서 호출 시 project.json 의 `qaEnv.surfacePatterns` 를 읽어 전달:
198
+
199
+ ```python
200
+ if task_type in ("implementation", "final-verification"):
201
+ _sp = None
202
+ _pj = project_json_path(project_root)
203
+ if _pj.is_file():
204
+ try:
205
+ _sp = (json.loads(_pj.read_text()).get("qaEnv") or {}).get("surfacePatterns")
206
+ except (OSError, json.JSONDecodeError):
207
+ _sp = None
208
+ _validate_conformance(report_path, failures, surface_patterns=_sp)
209
+ ```
210
+
211
+ `project_json_path` import 추가(`from okstra_project import project_json_path` 또는 기존 import 라인 확장 — 실제 export 확인).
212
+
213
+ - [ ] **Step 2: 테스트 추가** — `tests/test_validate_run_conformance.py` 에: 커스텀 패턴으로 `.xyz` 를 db 로 매핑해 미선언 시 BLOCKING 되는지(직접 `_validate_conformance(report, failures, surface_patterns={"db": ["*.xyz"]})` 호출).
214
+
215
+ - [ ] **Step 3: 통과 + 전체 + workflow** — `python3 -m pytest tests/ -q` + `bash validators/validate-workflow.sh`
216
+ - [ ] **Step 4: 커밋** — `git commit -m "feat(validators): qaEnv.surfacePatterns 를 diff-surface 교차검증에 반영"`
217
+
218
+ ## Task 5: qaEnv 문서 + wizard picker(prescriptive)
219
+
220
+ **Files:** Modify `skills/okstra-setup/SKILL.md`, `skills/okstra-run/SKILL.md` (wizard); 그리고 surfacePatterns 기본 over-broad 주의 노트
221
+
222
+ - [ ] **Step 1: setup 문서** — `skills/okstra-setup/SKILL.md` 의 `qaCommands` 설명 근처에 `qaEnv` 블록 문서 추가:
223
+
224
+ ```json
225
+ "qaEnv": {
226
+ "replicaDbDsn": "<replica/test DB DSN — never shared/staging/prod>",
227
+ "appBaseUrl": "http://localhost:3000",
228
+ "envFile": ".okstra/qa.env",
229
+ "surfacePatterns": { "db": ["*.sql", "*repository*"], "http": ["*controller*"] }
230
+ }
231
+ ```
232
+
233
+ 설명: verifier Tier3 conformance 스크립트가 replica DB·endpoint 에 닿는 통로(replica 전용). `surfacePatterns` 는 diff-surface 교차검증의 프로젝트별 override(기본 `*router*`/`*storage*` 패턴은 프론트엔드 파일을 넓게 잡으므로 프론트 중심 repo 는 override 권장 — Phase 4b 리뷰 노트).
234
+
235
+ - [ ] **Step 2: wizard picker** — `skills/okstra-run/SKILL.md` 의 implementation 입력 흐름에, conformance BLOCKING 이 예상되고 사용자가 우회를 원할 때 `--qa-waiver <stageKey>:"<reason>"` 를 안내하는 추천형 picker 한 단계 추가(추천 + "직접 입력", 마지막 옵션 "직접 입력"). lead/worker 자체 면제 금지 — 사용자 입력만.
236
+
237
+ - [ ] **Step 3: build + 검증** — `npm run build` + `bash validators/validate-workflow.sh` + `python3 -m pytest tests/ -q`
238
+ - [ ] **Step 4: 커밋** — `git commit -m "docs(skills): qaEnv(replica/surfacePatterns) 문서 + qa-waiver wizard 안내"`
239
+
240
+ ---
241
+
242
+ ## Phase 4e Self-Review 체크
243
+
244
+ - [ ] `--qa-waiver` 가 매니페스트 entry.waiver 를 채우고(원문 reason), 미지 stageKey/형식오류는 PrepareError?
245
+ - [ ] CLI 패스스루(okstra.sh→run)가 동작?
246
+ - [ ] qaEnv.surfacePatterns 가 detect_surfaces 에 반영?
247
+ - [ ] setup 문서/wizard 에 qaEnv·waiver 명문화?
248
+ - [ ] 전체 pytest + workflow validator + build 통과?
249
+
250
+ Phase 4e 완료 = conformance QA 기능 전체 완성. 완료 후 `CHANGES.md` 에 `사용자 영향:` 엔트리 추가 + 전체 회귀 최종 확인.