okstra 0.102.1 → 0.102.3

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 (162) hide show
  1. package/README.kr.md +5 -6
  2. package/README.md +5 -6
  3. package/docs/kr/architecture/storage-model.md +1 -1
  4. package/docs/kr/architecture.md +8 -12
  5. package/docs/kr/cli.md +6 -6
  6. package/docs/kr/container.md +1 -1
  7. package/docs/kr/performance-improvement-plan-v2.md +2 -2
  8. package/docs/project-structure-overview.md +2 -2
  9. package/package.json +7 -2
  10. package/runtime/BUILD.json +2 -2
  11. package/runtime/bin/okstra-antigravity-exec.sh +1 -2
  12. package/runtime/bin/okstra-codex-exec.sh +1 -2
  13. package/runtime/bin/okstra-render-final-report.py +2 -9
  14. package/runtime/bin/okstra-spawn-followups.py +4 -6
  15. package/runtime/bin/okstra-trace-cleanup.sh +24 -4
  16. package/runtime/prompts/lead/okstra-lead-contract.md +2 -2
  17. package/runtime/prompts/lead/report-writer.md +1 -1
  18. package/runtime/prompts/profiles/_common-contract.md +9 -11
  19. package/runtime/python/okstra_ctl/codex_dispatch.py +4 -12
  20. package/runtime/python/okstra_ctl/consumers.py +35 -0
  21. package/runtime/python/okstra_ctl/container.py +4 -6
  22. package/runtime/python/okstra_ctl/dispatch_core.py +4 -12
  23. package/runtime/python/okstra_ctl/final_report_paths.py +24 -0
  24. package/runtime/python/okstra_ctl/handoff.py +2 -1
  25. package/runtime/python/okstra_ctl/implementation_stage.py +7 -11
  26. package/runtime/python/okstra_ctl/report_view_artifacts.py +14 -0
  27. package/runtime/python/okstra_ctl/report_views.py +7 -4
  28. package/runtime/python/okstra_ctl/run.py +3 -10
  29. package/runtime/python/okstra_ctl/stage_targets.py +18 -0
  30. package/runtime/python/okstra_ctl/wizard.py +24 -25
  31. package/runtime/python/okstra_ctl/wizard_stage_intent.py +54 -0
  32. package/runtime/python/okstra_token_usage/report.py +2 -5
  33. package/runtime/validators/validate-report-views.py +2 -1
  34. package/runtime/validators/validate-run.py +1 -11
  35. package/src/cli-registry.mjs +1 -1
  36. package/src/commands/lifecycle/install.mjs +10 -20
  37. package/src/commands/lifecycle/uninstall.mjs +7 -10
  38. package/docs/superpowers/plans/2026-05-12-ticket-id-in-reports.md +0 -638
  39. package/docs/superpowers/plans/2026-05-14-convergence-queue-pruning.md +0 -1568
  40. package/docs/superpowers/plans/2026-05-17-dual-format-final-report.md +0 -167
  41. package/docs/superpowers/plans/2026-05-20-final-report-language.md +0 -1501
  42. package/docs/superpowers/plans/2026-05-20-implementation-planning-multi-stage.md +0 -1267
  43. package/docs/superpowers/plans/2026-05-20-okstra-run-prompt-sot-b1.md +0 -1007
  44. package/docs/superpowers/plans/2026-05-20-wizard-messages-json-sot.md +0 -720
  45. package/docs/superpowers/plans/2026-05-20-wizard-prompt-json-sot-a1.md +0 -681
  46. package/docs/superpowers/plans/2026-05-21-improvement-discovery-task-type.md +0 -1691
  47. package/docs/superpowers/plans/2026-05-24-implementation-lead-context-slimming.md +0 -1700
  48. package/docs/superpowers/plans/2026-05-25-okstra-project-root-rename.md +0 -158
  49. package/docs/superpowers/plans/2026-05-26-wizard-3-option-picker.md +0 -860
  50. package/docs/superpowers/plans/2026-06-02-final-verification-protocol-hardening.md +0 -326
  51. package/docs/superpowers/plans/2026-06-02-okstra-run-branch-confirm-step.md +0 -337
  52. package/docs/superpowers/plans/2026-06-02-okstra-run-phase-pane-cleanup.md +0 -410
  53. package/docs/superpowers/plans/2026-06-02-requirements-discovery-fanout.md +0 -728
  54. package/docs/superpowers/plans/2026-06-04-adversarial-implementation-planning.md +0 -294
  55. package/docs/superpowers/plans/2026-06-04-adversarial-verification.md +0 -570
  56. package/docs/superpowers/plans/2026-06-04-coverage-critic.md +0 -516
  57. package/docs/superpowers/plans/2026-06-04-stage-cohesion-planner.md +0 -351
  58. package/docs/superpowers/plans/2026-06-04-stage-run-batching.md +0 -457
  59. package/docs/superpowers/plans/2026-06-05-acceptance-critic.md +0 -251
  60. package/docs/superpowers/plans/2026-06-05-compact-markdown-report-tables.md +0 -323
  61. package/docs/superpowers/plans/2026-06-05-wizard-batch-prompts.md +0 -559
  62. package/docs/superpowers/plans/2026-06-06-final-verification-whole-task-gate.md +0 -993
  63. package/docs/superpowers/plans/2026-06-06-stage-parallel-and-pending-fixes.md +0 -93
  64. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p1.md +0 -447
  65. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p2.md +0 -289
  66. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p3.md +0 -774
  67. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p4.md +0 -303
  68. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p5-multidep-base.md +0 -387
  69. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase2.md +0 -275
  70. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase3.md +0 -282
  71. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4a.md +0 -147
  72. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4b.md +0 -262
  73. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4c.md +0 -184
  74. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4d.md +0 -88
  75. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4e.md +0 -250
  76. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa.md +0 -409
  77. package/docs/superpowers/plans/2026-06-09-implementation-run-artifact-stage-isolation.md +0 -320
  78. package/docs/superpowers/plans/2026-06-10-concurrent-run-team-guard.md +0 -456
  79. package/docs/superpowers/plans/2026-06-10-git-reconcile-stale-sha-recovery.md +0 -1408
  80. package/docs/superpowers/plans/2026-06-10-lead-worker-completion-polling-PROBE.md +0 -42
  81. package/docs/superpowers/plans/2026-06-10-lead-worker-completion-polling.md +0 -337
  82. package/docs/superpowers/plans/2026-06-10-p6-token-usage-incremental.md +0 -1029
  83. package/docs/superpowers/plans/2026-06-10-stage-group-handoff.md +0 -1572
  84. package/docs/superpowers/plans/2026-06-11-fix-cycle.md +0 -1290
  85. package/docs/superpowers/plans/2026-06-11-wizard-whole-task-final-verification.md +0 -526
  86. package/docs/superpowers/plans/2026-06-12-html-plan-approval.md +0 -1000
  87. package/docs/superpowers/plans/2026-06-13-repo-risk-hardening.md +0 -493
  88. package/docs/superpowers/plans/2026-06-14-host-runtime-auto-execution.md +0 -1775
  89. package/docs/superpowers/plans/2026-06-15-coding-preflight-pack-dispatch-path.md +0 -504
  90. package/docs/superpowers/plans/2026-06-15-internal-skill-migration-final-fixups.md +0 -342
  91. package/docs/superpowers/plans/2026-06-15-internal-skill-migration-fixups.md +0 -258
  92. package/docs/superpowers/plans/2026-06-15-internal-skill-migration-remaining-fixups.md +0 -387
  93. package/docs/superpowers/plans/2026-06-15-internal-skill-resource-migration.md +0 -749
  94. package/docs/superpowers/plans/2026-06-15-worker-prompt-anchor-final-fixups.md +0 -828
  95. package/docs/superpowers/plans/2026-06-15-worker-prompt-header-error-contract.md +0 -490
  96. package/docs/superpowers/plans/2026-06-17-okstra-error-report.md +0 -724
  97. package/docs/superpowers/plans/2026-06-18-subagent-pane-reclaim.md +0 -483
  98. package/docs/superpowers/plans/2026-06-19-agent-operating-standard.md +0 -476
  99. package/docs/superpowers/plans/2026-06-19-okstra-inspect-recap-facet.md +0 -647
  100. package/docs/superpowers/plans/2026-06-20-error-feedback-loop.md +0 -1019
  101. package/docs/superpowers/plans/2026-06-20-run-index-row-normalization.md +0 -583
  102. package/docs/superpowers/plans/2026-06-20-stage-auto-integrate-teardown.md +0 -819
  103. package/docs/superpowers/plans/2026-06-21-okstra-container-local-user-test.md +0 -714
  104. package/docs/superpowers/plans/2026-06-21-release-handoff-local-checkout.md +0 -348
  105. package/docs/superpowers/plans/2026-06-23-implementation-stage-multiselect-chain.md +0 -630
  106. package/docs/superpowers/plans/2026-06-23-implementation-step-commit-granularity.md +0 -274
  107. package/docs/superpowers/plans/2026-06-23-phase-batch-cleanup-enforcement.md +0 -409
  108. package/docs/superpowers/plans/2026-06-24-cross-project-precondition.md +0 -542
  109. package/docs/superpowers/plans/2026-06-24-decision-drafts-render-gap.md +0 -568
  110. package/docs/superpowers/plans/2026-06-24-manual-user-test-section.md +0 -203
  111. package/docs/superpowers/plans/2026-06-24-okstra-container-build-rename.md +0 -525
  112. package/docs/superpowers/specs/2026-05-12-ticket-id-in-reports-design.md +0 -131
  113. package/docs/superpowers/specs/2026-05-15-implementation-plan-verification-design.md +0 -254
  114. package/docs/superpowers/specs/2026-05-20-final-report-language-design.md +0 -383
  115. package/docs/superpowers/specs/2026-05-20-implementation-planning-multi-stage-design.md +0 -322
  116. package/docs/superpowers/specs/2026-05-20-okstra-run-prompt-sot-design.md +0 -299
  117. package/docs/superpowers/specs/2026-05-21-improvement-discovery-task-type-design.md +0 -335
  118. package/docs/superpowers/specs/2026-06-02-okstra-run-branch-confirm-step-design.md +0 -113
  119. package/docs/superpowers/specs/2026-06-02-okstra-run-phase-pane-cleanup-design.md +0 -173
  120. package/docs/superpowers/specs/2026-06-02-requirements-discovery-fanout-design.md +0 -154
  121. package/docs/superpowers/specs/2026-06-04-adversarial-implementation-planning-design.md +0 -90
  122. package/docs/superpowers/specs/2026-06-04-adversarial-verification-design.md +0 -176
  123. package/docs/superpowers/specs/2026-06-04-coverage-critic-design.md +0 -99
  124. package/docs/superpowers/specs/2026-06-04-stage-splitting-cost-aware-design.md +0 -98
  125. package/docs/superpowers/specs/2026-06-05-acceptance-critic-design.md +0 -90
  126. package/docs/superpowers/specs/2026-06-05-compact-markdown-report-tables-design.md +0 -87
  127. package/docs/superpowers/specs/2026-06-05-wizard-batch-prompts-design.md +0 -121
  128. package/docs/superpowers/specs/2026-06-06-final-verification-whole-task-gate-design.md +0 -126
  129. package/docs/superpowers/specs/2026-06-06-stage-worktree-isolation-design.md +0 -180
  130. package/docs/superpowers/specs/2026-06-06-vertical-slice-tdd-planning-design.md +0 -179
  131. package/docs/superpowers/specs/2026-06-07-stage-conformance-qa-design.md +0 -169
  132. package/docs/superpowers/specs/2026-06-09-executor-model-custom-id-cascade-design.md +0 -66
  133. package/docs/superpowers/specs/2026-06-09-implementation-run-artifact-stage-isolation-design.md +0 -87
  134. package/docs/superpowers/specs/2026-06-10-blocking-contract-posthoc-conformance-design.md +0 -168
  135. package/docs/superpowers/specs/2026-06-10-concurrent-run-team-guard-design.md +0 -107
  136. package/docs/superpowers/specs/2026-06-10-git-reconcile-stale-sha-recovery-design.md +0 -105
  137. package/docs/superpowers/specs/2026-06-10-lead-worker-completion-polling-design.md +0 -113
  138. package/docs/superpowers/specs/2026-06-10-stage-group-handoff-design.md +0 -156
  139. package/docs/superpowers/specs/2026-06-11-brief-entry-only-handoff-stage-entry-design.md +0 -158
  140. package/docs/superpowers/specs/2026-06-11-fix-cycle-design.md +0 -94
  141. package/docs/superpowers/specs/2026-06-11-wizard-whole-task-final-verification-design.md +0 -89
  142. package/docs/superpowers/specs/2026-06-12-codex-lead-adapter-design.md +0 -358
  143. package/docs/superpowers/specs/2026-06-12-html-plan-approval-design.md +0 -85
  144. package/docs/superpowers/specs/2026-06-12-stage-discipline-rule-design.md +0 -108
  145. package/docs/superpowers/specs/2026-06-13-forbidden-actions-ssot-design.md +0 -134
  146. package/docs/superpowers/specs/2026-06-13-neutral-tmux-lead-adapter-design.md +0 -284
  147. package/docs/superpowers/specs/2026-06-14-host-runtime-auto-execution-design.md +0 -404
  148. package/docs/superpowers/specs/2026-06-17-okstra-error-report-design.md +0 -123
  149. package/docs/superpowers/specs/2026-06-18-subagent-pane-reclaim-design.md +0 -150
  150. package/docs/superpowers/specs/2026-06-19-agent-operating-standard-design.md +0 -120
  151. package/docs/superpowers/specs/2026-06-19-okstra-inspect-recap-facet-design.md +0 -96
  152. package/docs/superpowers/specs/2026-06-20-error-feedback-loop-design.md +0 -171
  153. package/docs/superpowers/specs/2026-06-20-run-index-row-normalization-design.md +0 -112
  154. package/docs/superpowers/specs/2026-06-20-stage-auto-integrate-teardown-design.md +0 -145
  155. package/docs/superpowers/specs/2026-06-21-okstra-container-local-user-test-design.md +0 -125
  156. package/docs/superpowers/specs/2026-06-21-release-handoff-local-checkout-design.md +0 -113
  157. package/docs/superpowers/specs/2026-06-23-implementation-stage-multiselect-chain-design.md +0 -138
  158. package/docs/superpowers/specs/2026-06-23-implementation-step-commit-granularity-design.md +0 -120
  159. package/docs/superpowers/specs/2026-06-24-cross-project-precondition-design.md +0 -129
  160. package/docs/superpowers/specs/2026-06-24-decision-drafts-render-gap-design.md +0 -185
  161. package/docs/superpowers/specs/2026-06-24-manual-user-test-section-design.md +0 -121
  162. package/docs/superpowers/specs/2026-06-24-okstra-container-build-rename-design.md +0 -110
@@ -1,250 +0,0 @@
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` 에 `사용자 영향:` 엔트리 추가 + 전체 회귀 최종 확인.
@@ -1,409 +0,0 @@
1
- # Stage Conformance QA (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:** implementation/final-verification 의 각 stage 가 상위 요구사항에 부합하는지 실DB·엔드포인트·외부 API 수준에서 검증하는 Tier 3 conformance 게이트를 추가하고, 미검증을 silent pass 가 아닌 BLOCKING 으로 만들되 명시적 우회(선언형 면제 / 사용자 확인형 waiver)를 감사한다.
6
-
7
- **Architecture:** 모든 산출물이 의존하는 결정론적 코어(매니페스트 검증 + `QA-RESULT` 파서)를 먼저 만들고(Phase 1), 그 위에 강제(Phase 2) → 생성/실행 wiring(Phase 3) → 우회 UX/env(Phase 4)를 얹는다. 설계 SSOT: [`docs/superpowers/specs/2026-06-07-stage-conformance-qa-design.md`](../specs/2026-06-07-stage-conformance-qa-design.md).
8
-
9
- **Tech Stack:** Python 3 (stdlib + pytest), markdown prompt profiles, Node CLI (`bin/`, `scripts/lib/okstra/`), project.json.
10
-
11
- ---
12
-
13
- ## 단계 분해 (Phase Roadmap)
14
-
15
- 이 기능은 분리 가능한 4개 슬라이스다. **본 문서는 Phase 1 을 bite-sized TDD 로 완전히 기술**하고, Phase 2~4 는 각자 독립 계획으로 확장한다(Phase 1 이 데이터 계약을 락인하므로 그 뒤가 안정적). 각 phase 는 그 자체로 테스트 가능하다.
16
-
17
- | Phase | 슬라이스 | 핵심 산출물 | 의존 |
18
- |---|---|---|---|
19
- | 1 | 계약 & 검증 코어 | `scripts/okstra_ctl/conformance.py` + 단위테스트 | 없음 |
20
- | 2 | 강제(load-bearing) | `validators/validate-run.py` Tier3 BLOCKING + `run.py` 진입 게이트 | 1 |
21
- | 3 | 생성·실행 wiring | `implementation-planning.md`/`_implementation-verifier.md`/`final-verification.md` prompt | 1,2 |
22
- | 4 | 우회 UX & env | `--qa-waiver` CLI + wizard + `project.json.qaEnv` | 1,2 |
23
-
24
- Phase 2~4 요약(각자 별도 계획으로 작성):
25
-
26
- - **Phase 2** — `validators/validate-run.py` 에 conformance 게이트 추가: implementation/final-verification diff 가 `requires`(db/io/http/external) 표면을 건드리는데 매니페스트 entry 가 없거나 결과가 FAIL/MISSING 이고 면제·waiver 도 없으면 BLOCKING. 선언형 면제가 실제 surface 와 불일치하면 BLOCKING. `run.py` 의 implementation 진입 게이트(현 [`run.py:1110`](../../../scripts/okstra_ctl/run.py:1110) qaCommands 패턴 모방)에서 `validate_conformance_manifest` 호출. surface 추정은 implementation report 의 Diff Summary(파일 경로/확장자/SQL 키워드) 기반 휴리스틱 + 명시 `requires` 우선.
27
- - **Phase 3** — `prompts/profiles/implementation-planning.md` 에 stage 별 `### 5.5.x Stage Conformance Tests` 산출 + `.okstra/.../qa/` 스크립트·매니페스트 기록 지시(`Acceptance:` 의 실행형). `_implementation-verifier.md` 의 two-tier lookup 에 **Tier 3** 추가(스크립트 실행 → `parse_qa_result` 결과를 Read-only command log 에 기록). `final-verification.md` 는 전 stage 합집합 실행.
28
- - **Phase 4** — `scripts/okstra.sh`/`scripts/lib/okstra/cli.sh` 에 `--qa-waiver <stageKey>:"<reason>"` 패스스루(→ `run.py` 가 매니페스트 entry 의 `waiver` 채움), `okstra-run` wizard 에 사용자 확인 picker, `project.json.qaEnv`(replicaDbDsn/appBaseUrl/envFile) 문서화(resolver 는 비-identity 필드를 이미 보존하므로 코드 변경 불필요 — [`resolver.py:120`](../../../scripts/okstra_project/resolver.py:120) 확인).
29
-
30
- ---
31
-
32
- ## Phase 1 — 계약 & 검증 코어
33
-
34
- 새 모듈 [`scripts/okstra_ctl/qa_commands.py`](../../../scripts/okstra_ctl/qa_commands.py) 와 동형의 sibling `conformance.py` 를 만든다. 런타임 검증/파싱 전용(순수 함수, 부수효과 없음).
35
-
36
- ### Task 1: 매니페스트 구조 검증 (`validate_conformance_manifest`)
37
-
38
- **Files:**
39
- - Create: `scripts/okstra_ctl/conformance.py`
40
- - Test: `tests/test_okstra_ctl_conformance.py`
41
-
42
- - [ ] **Step 1: 실패 테스트 작성**
43
-
44
- ```python
45
- # tests/test_okstra_ctl_conformance.py
46
- from __future__ import annotations
47
-
48
- import sys
49
- from pathlib import Path
50
-
51
- _REPO_ROOT = Path(__file__).resolve().parent.parent
52
- sys.path.insert(0, str(_REPO_ROOT / "scripts"))
53
-
54
- from okstra_ctl.conformance import ( # noqa: E402
55
- CAPABILITY_WHITELIST,
56
- validate_conformance_manifest,
57
- )
58
-
59
-
60
- def _entry(**over):
61
- base = {
62
- "stageKey": "dev-9184-stage-3",
63
- "script": "qa/stage-3.ts",
64
- "runCommand": "npx ts-node .okstra/qa/stage-3.ts",
65
- "requirementIds": ["R-001"],
66
- "requires": ["db", "http"],
67
- "passContract": "exit 0 = PASS",
68
- "exemption": None,
69
- "waiver": None,
70
- }
71
- base.update(over)
72
- return base
73
-
74
-
75
- def test_valid_manifest_has_no_errors():
76
- manifest = {"entries": [_entry()]}
77
- assert validate_conformance_manifest(manifest) == []
78
-
79
-
80
- def test_absent_manifest_is_legal():
81
- assert validate_conformance_manifest(None) == []
82
-
83
-
84
- def test_non_object_manifest_rejected():
85
- assert validate_conformance_manifest([]) != []
86
-
87
-
88
- def test_entries_must_be_array():
89
- assert any("entries" in e for e in validate_conformance_manifest({"entries": {}}))
90
-
91
-
92
- def test_missing_required_string_fields_reported():
93
- errs = validate_conformance_manifest({"entries": [_entry(stageKey="")]})
94
- assert any("stageKey" in e for e in errs)
95
-
96
-
97
- def test_requirement_ids_must_be_non_empty_list():
98
- errs = validate_conformance_manifest({"entries": [_entry(requirementIds=[])]})
99
- assert any("requirementIds" in e for e in errs)
100
-
101
-
102
- def test_unknown_capability_rejected():
103
- errs = validate_conformance_manifest({"entries": [_entry(requires=["db", "gpu"])]})
104
- assert any("gpu" in e for e in errs)
105
-
106
-
107
- def test_capability_whitelist_value():
108
- assert CAPABILITY_WHITELIST == ("db", "io", "http", "external")
109
- ```
110
-
111
- - [ ] **Step 2: 실패 확인**
112
-
113
- Run: `python3 -m pytest tests/test_okstra_ctl_conformance.py -q`
114
- Expected: FAIL — `ModuleNotFoundError: No module named 'okstra_ctl.conformance'`
115
-
116
- - [ ] **Step 3: 최소 구현**
117
-
118
- ```python
119
- # scripts/okstra_ctl/conformance.py
120
- """Stage conformance(Tier 3) 매니페스트 검증 + `QA-RESULT` 파서.
121
-
122
- implementation/final-verification 의 verifier 는 stage 별 conformance 스크립트를
123
- 실행해 상위 요구사항 부합을 검증한다. 본 모듈은 그 검증/파싱의 결정론적 코어다.
124
-
125
- 1. `conformance-manifest.json` 구조 검증 (`validate_conformance_manifest`).
126
- 2. 스크립트 stdout 의 `QA-RESULT` 마커 파싱 (`parse_qa_result`).
127
-
128
- 스크립트 실행/게이트 강제는 verifier prompt 와 validators/validate-run.py 가 담당한다.
129
- """
130
- from __future__ import annotations
131
-
132
- import re
133
- from dataclasses import dataclass
134
-
135
- # diff 가 건드린 표면과 대조할 capability 태그 화이트리스트.
136
- CAPABILITY_WHITELIST: tuple[str, ...] = ("db", "io", "http", "external")
137
-
138
-
139
- class ConformanceError(ValueError):
140
- """conformance 매니페스트가 계약을 어긴 경우."""
141
-
142
-
143
- def _check_nonempty_str(value: object, path: str, errors: list[str]) -> bool:
144
- if not isinstance(value, str) or not value.strip():
145
- errors.append(f"{path} must be a non-empty string")
146
- return False
147
- return True
148
-
149
-
150
- def _check_capabilities(value: object, path: str, errors: list[str]) -> None:
151
- if not isinstance(value, list):
152
- errors.append(f"{path} must be an array")
153
- return
154
- for cap in value:
155
- if cap not in CAPABILITY_WHITELIST:
156
- errors.append(
157
- f"{path}: unknown capability {cap!r} "
158
- f"(allowed: {', '.join(CAPABILITY_WHITELIST)})"
159
- )
160
-
161
-
162
- def _check_entry(entry: object, idx: int, errors: list[str]) -> None:
163
- path = f"entries[{idx}]"
164
- if not isinstance(entry, dict):
165
- errors.append(f"{path} must be an object")
166
- return
167
- _check_nonempty_str(entry.get("stageKey"), f"{path}.stageKey", errors)
168
- _check_nonempty_str(entry.get("script"), f"{path}.script", errors)
169
- _check_nonempty_str(entry.get("runCommand"), f"{path}.runCommand", errors)
170
- _check_nonempty_str(entry.get("passContract"), f"{path}.passContract", errors)
171
- req_ids = entry.get("requirementIds")
172
- if (
173
- not isinstance(req_ids, list)
174
- or not req_ids
175
- or not all(isinstance(r, str) and r.strip() for r in req_ids)
176
- ):
177
- errors.append(f"{path}.requirementIds must be a non-empty array of strings")
178
- _check_capabilities(entry.get("requires", []), f"{path}.requires", errors)
179
-
180
-
181
- def validate_conformance_manifest(manifest: object) -> list[str]:
182
- """conformance-manifest 전체 검증. 위반 메시지 리스트 반환(비면 안전).
183
-
184
- 매니페스트 부재(None)는 합법 — 스크립트 없는 task 가 있을 수 있고, 게이트
185
- 강제(diff surface 대조)는 validators/validate-run.py 가 판정한다.
186
- """
187
- if manifest is None:
188
- return []
189
- if not isinstance(manifest, dict):
190
- return [f"conformance manifest must be an object, got {type(manifest).__name__}"]
191
- entries = manifest.get("entries")
192
- if not isinstance(entries, list):
193
- return ["conformance manifest .entries must be an array"]
194
- errors: list[str] = []
195
- for idx, entry in enumerate(entries):
196
- _check_entry(entry, idx, errors)
197
- return errors
198
- ```
199
-
200
- - [ ] **Step 4: 통과 확인**
201
-
202
- Run: `python3 -m pytest tests/test_okstra_ctl_conformance.py -q`
203
- Expected: PASS (8 passed)
204
-
205
- - [ ] **Step 5: 커밋**
206
-
207
- ```bash
208
- git add scripts/okstra_ctl/conformance.py tests/test_okstra_ctl_conformance.py
209
- git commit -m "feat(okstra_ctl/conformance): conformance 매니페스트 구조 검증"
210
- ```
211
-
212
- ### Task 2: 면제/waiver 형태 검증 + stageKey 중복
213
-
214
- **Files:**
215
- - Modify: `scripts/okstra_ctl/conformance.py`
216
- - Test: `tests/test_okstra_ctl_conformance.py`
217
-
218
- - [ ] **Step 1: 실패 테스트 추가**
219
-
220
- ```python
221
- def test_exemption_requires_reason_and_declared_at():
222
- bad = _entry(exemption={"reason": ""})
223
- errs = validate_conformance_manifest({"entries": [bad]})
224
- assert any("exemption.reason" in e for e in errs)
225
- assert any("exemption.declaredAt" in e for e in errs)
226
-
227
-
228
- def test_valid_exemption_passes():
229
- ok = _entry(exemption={"reason": "doc-only stage", "declaredAt": "2026-06-07"})
230
- assert validate_conformance_manifest({"entries": [ok]}) == []
231
-
232
-
233
- def test_waiver_requires_fields_and_scope_whitelist():
234
- bad = _entry(waiver={"acknowledgedBy": "user", "reason": "env down",
235
- "at": "2026-06-07", "scope": ["db", "gpu"]})
236
- errs = validate_conformance_manifest({"entries": [bad]})
237
- assert any("waiver.scope" in e and "gpu" in e for e in errs)
238
-
239
-
240
- def test_valid_waiver_passes():
241
- ok = _entry(waiver={"acknowledgedBy": "user", "reason": "replica down — skip db",
242
- "at": "2026-06-07T10:00:00Z", "scope": ["db"]})
243
- assert validate_conformance_manifest({"entries": [ok]}) == []
244
-
245
-
246
- def test_duplicate_stage_key_reported():
247
- errs = validate_conformance_manifest({"entries": [_entry(), _entry()]})
248
- assert any("duplicate" in e for e in errs)
249
- ```
250
-
251
- - [ ] **Step 2: 실패 확인**
252
-
253
- Run: `python3 -m pytest tests/test_okstra_ctl_conformance.py -q`
254
- Expected: FAIL — 면제/waiver 미검증, 중복 미검출
255
-
256
- - [ ] **Step 3: 구현 추가**
257
-
258
- `conformance.py` 의 `_check_entry` 끝(`_check_capabilities(... "requires" ...)` 다음 줄)에 추가:
259
-
260
- ```python
261
- _check_exemption(entry.get("exemption"), f"{path}.exemption", errors)
262
- _check_waiver(entry.get("waiver"), f"{path}.waiver", errors)
263
- ```
264
-
265
- `_check_capabilities` 정의 다음에 두 헬퍼 추가:
266
-
267
- ```python
268
- def _check_exemption(value: object, path: str, errors: list[str]) -> None:
269
- if value is None:
270
- return
271
- if not isinstance(value, dict):
272
- errors.append(f"{path} must be an object or null")
273
- return
274
- _check_nonempty_str(value.get("reason"), f"{path}.reason", errors)
275
- _check_nonempty_str(value.get("declaredAt"), f"{path}.declaredAt", errors)
276
-
277
-
278
- def _check_waiver(value: object, path: str, errors: list[str]) -> None:
279
- if value is None:
280
- return
281
- if not isinstance(value, dict):
282
- errors.append(f"{path} must be an object or null")
283
- return
284
- _check_nonempty_str(value.get("acknowledgedBy"), f"{path}.acknowledgedBy", errors)
285
- _check_nonempty_str(value.get("reason"), f"{path}.reason", errors)
286
- _check_nonempty_str(value.get("at"), f"{path}.at", errors)
287
- _check_capabilities(value.get("scope", []), f"{path}.scope", errors)
288
- ```
289
-
290
- `validate_conformance_manifest` 의 루프를 중복 검출로 교체:
291
-
292
- ```python
293
- errors: list[str] = []
294
- seen: set[str] = set()
295
- for idx, entry in enumerate(entries):
296
- _check_entry(entry, idx, errors)
297
- key = entry.get("stageKey") if isinstance(entry, dict) else None
298
- if isinstance(key, str) and key:
299
- if key in seen:
300
- errors.append(f"entries[{idx}].stageKey duplicate: {key!r}")
301
- seen.add(key)
302
- return errors
303
- ```
304
-
305
- - [ ] **Step 4: 통과 확인**
306
-
307
- Run: `python3 -m pytest tests/test_okstra_ctl_conformance.py -q`
308
- Expected: PASS (13 passed)
309
-
310
- - [ ] **Step 5: 커밋**
311
-
312
- ```bash
313
- git add scripts/okstra_ctl/conformance.py tests/test_okstra_ctl_conformance.py
314
- git commit -m "feat(okstra_ctl/conformance): 면제/waiver 형태 + stageKey 중복 검증"
315
- ```
316
-
317
- ### Task 3: `QA-RESULT` stdout 파서 (`parse_qa_result`)
318
-
319
- **Files:**
320
- - Modify: `scripts/okstra_ctl/conformance.py`
321
- - Test: `tests/test_okstra_ctl_conformance.py`
322
-
323
- - [ ] **Step 1: 실패 테스트 추가**
324
-
325
- ```python
326
- from okstra_ctl.conformance import parse_qa_result # noqa: E402 (파일 상단 import 에 합쳐도 됨)
327
-
328
-
329
- def test_parse_pass_with_requirements():
330
- out = "running...\nREQ R-001: PASS: row count 3 > 0\nQA-RESULT: PASS\n"
331
- res = parse_qa_result(out)
332
- assert res.overall == "PASS"
333
- assert res.requirements["R-001"]["status"] == "PASS"
334
- assert "row count" in res.requirements["R-001"]["reason"]
335
-
336
-
337
- def test_parse_fail():
338
- res = parse_qa_result("REQ R-001: FAIL: parity mismatch\nQA-RESULT: FAIL\n")
339
- assert res.overall == "FAIL"
340
- assert res.requirements["R-001"]["status"] == "FAIL"
341
-
342
-
343
- def test_missing_marker_is_missing():
344
- assert parse_qa_result("no marker here").overall == "MISSING"
345
- assert parse_qa_result("").overall == "MISSING"
346
-
347
-
348
- def test_last_marker_wins():
349
- assert parse_qa_result("QA-RESULT: PASS\nQA-RESULT: FAIL\n").overall == "FAIL"
350
- ```
351
-
352
- - [ ] **Step 2: 실패 확인**
353
-
354
- Run: `python3 -m pytest tests/test_okstra_ctl_conformance.py -q`
355
- Expected: FAIL — `cannot import name 'parse_qa_result'`
356
-
357
- - [ ] **Step 3: 구현 추가**
358
-
359
- `conformance.py` 끝에 추가:
360
-
361
- ```python
362
- _QA_RESULT_RE = re.compile(r"^QA-RESULT:\s*(PASS|FAIL)\s*$", re.MULTILINE)
363
- _REQ_LINE_RE = re.compile(r"^REQ\s+(\S+):\s*(PASS|FAIL):\s*(.*)$", re.MULTILINE)
364
-
365
-
366
- @dataclass
367
- class QaResult:
368
- overall: str # "PASS" | "FAIL" | "MISSING"
369
- requirements: dict # id -> {"status": "PASS"|"FAIL", "reason": str}
370
-
371
-
372
- def parse_qa_result(stdout: str) -> QaResult:
373
- """스크립트 stdout 에서 `QA-RESULT` 마커 + `REQ` 줄 파싱.
374
-
375
- 마커가 없으면 overall='MISSING' — 스크립트가 계약을 안 지킨 것이므로 게이트는
376
- FAIL 로 취급한다. 마커가 여럿이면 마지막 것을 채택한다.
377
- """
378
- text = stdout or ""
379
- markers = _QA_RESULT_RE.findall(text)
380
- overall = markers[-1] if markers else "MISSING"
381
- requirements: dict = {}
382
- for rid, status, reason in _REQ_LINE_RE.findall(text):
383
- requirements[rid] = {"status": status, "reason": reason.strip()}
384
- return QaResult(overall=overall, requirements=requirements)
385
- ```
386
-
387
- - [ ] **Step 4: 통과 확인**
388
-
389
- Run: `python3 -m pytest tests/test_okstra_ctl_conformance.py -q`
390
- Expected: PASS (17 passed)
391
-
392
- - [ ] **Step 5: 전체 스위트 회귀 확인 + 커밋**
393
-
394
- ```bash
395
- python3 -m pytest tests/ -q
396
- git add scripts/okstra_ctl/conformance.py tests/test_okstra_ctl_conformance.py
397
- git commit -m "feat(okstra_ctl/conformance): QA-RESULT stdout 파서"
398
- ```
399
-
400
- ---
401
-
402
- ## Phase 1 Self-Review 체크
403
-
404
- - [ ] spec §3(데이터 모델) 의 모든 entry 필드(stageKey/script/runCommand/requirementIds/requires/passContract/exemption/waiver)가 `validate_conformance_manifest` 로 커버되는가?
405
- - [ ] spec §4(스크립트 계약)의 `QA-RESULT`/`REQ` 마커가 `parse_qa_result` 로 커버되는가?
406
- - [ ] capability 화이트리스트가 spec §3 의 `["db","io","http","external"]` 와 일치하는가?
407
- - [ ] Phase 2 가 의존하는 공개 API(`validate_conformance_manifest`, `parse_qa_result`, `CAPABILITY_WHITELIST`, `QaResult`)가 안정적으로 노출되는가?
408
-
409
- Phase 1 완료 후 Phase 2 계획(`docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase2.md`)을 작성한다.