okstra 0.102.2 → 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 (161) hide show
  1. package/README.kr.md +5 -5
  2. package/README.md +5 -5
  3. package/docs/kr/architecture/storage-model.md +1 -1
  4. package/docs/kr/architecture.md +7 -11
  5. package/docs/kr/cli.md +4 -4
  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/commands/lifecycle/install.mjs +5 -5
  36. package/src/commands/lifecycle/uninstall.mjs +4 -4
  37. package/docs/superpowers/plans/2026-05-12-ticket-id-in-reports.md +0 -638
  38. package/docs/superpowers/plans/2026-05-14-convergence-queue-pruning.md +0 -1568
  39. package/docs/superpowers/plans/2026-05-17-dual-format-final-report.md +0 -167
  40. package/docs/superpowers/plans/2026-05-20-final-report-language.md +0 -1501
  41. package/docs/superpowers/plans/2026-05-20-implementation-planning-multi-stage.md +0 -1267
  42. package/docs/superpowers/plans/2026-05-20-okstra-run-prompt-sot-b1.md +0 -1007
  43. package/docs/superpowers/plans/2026-05-20-wizard-messages-json-sot.md +0 -720
  44. package/docs/superpowers/plans/2026-05-20-wizard-prompt-json-sot-a1.md +0 -681
  45. package/docs/superpowers/plans/2026-05-21-improvement-discovery-task-type.md +0 -1691
  46. package/docs/superpowers/plans/2026-05-24-implementation-lead-context-slimming.md +0 -1700
  47. package/docs/superpowers/plans/2026-05-25-okstra-project-root-rename.md +0 -158
  48. package/docs/superpowers/plans/2026-05-26-wizard-3-option-picker.md +0 -860
  49. package/docs/superpowers/plans/2026-06-02-final-verification-protocol-hardening.md +0 -326
  50. package/docs/superpowers/plans/2026-06-02-okstra-run-branch-confirm-step.md +0 -337
  51. package/docs/superpowers/plans/2026-06-02-okstra-run-phase-pane-cleanup.md +0 -410
  52. package/docs/superpowers/plans/2026-06-02-requirements-discovery-fanout.md +0 -728
  53. package/docs/superpowers/plans/2026-06-04-adversarial-implementation-planning.md +0 -294
  54. package/docs/superpowers/plans/2026-06-04-adversarial-verification.md +0 -570
  55. package/docs/superpowers/plans/2026-06-04-coverage-critic.md +0 -516
  56. package/docs/superpowers/plans/2026-06-04-stage-cohesion-planner.md +0 -351
  57. package/docs/superpowers/plans/2026-06-04-stage-run-batching.md +0 -457
  58. package/docs/superpowers/plans/2026-06-05-acceptance-critic.md +0 -251
  59. package/docs/superpowers/plans/2026-06-05-compact-markdown-report-tables.md +0 -323
  60. package/docs/superpowers/plans/2026-06-05-wizard-batch-prompts.md +0 -559
  61. package/docs/superpowers/plans/2026-06-06-final-verification-whole-task-gate.md +0 -993
  62. package/docs/superpowers/plans/2026-06-06-stage-parallel-and-pending-fixes.md +0 -93
  63. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p1.md +0 -447
  64. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p2.md +0 -289
  65. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p3.md +0 -774
  66. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p4.md +0 -303
  67. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p5-multidep-base.md +0 -387
  68. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase2.md +0 -275
  69. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase3.md +0 -282
  70. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4a.md +0 -147
  71. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4b.md +0 -262
  72. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4c.md +0 -184
  73. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4d.md +0 -88
  74. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4e.md +0 -250
  75. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa.md +0 -409
  76. package/docs/superpowers/plans/2026-06-09-implementation-run-artifact-stage-isolation.md +0 -320
  77. package/docs/superpowers/plans/2026-06-10-concurrent-run-team-guard.md +0 -456
  78. package/docs/superpowers/plans/2026-06-10-git-reconcile-stale-sha-recovery.md +0 -1408
  79. package/docs/superpowers/plans/2026-06-10-lead-worker-completion-polling-PROBE.md +0 -42
  80. package/docs/superpowers/plans/2026-06-10-lead-worker-completion-polling.md +0 -337
  81. package/docs/superpowers/plans/2026-06-10-p6-token-usage-incremental.md +0 -1029
  82. package/docs/superpowers/plans/2026-06-10-stage-group-handoff.md +0 -1572
  83. package/docs/superpowers/plans/2026-06-11-fix-cycle.md +0 -1290
  84. package/docs/superpowers/plans/2026-06-11-wizard-whole-task-final-verification.md +0 -526
  85. package/docs/superpowers/plans/2026-06-12-html-plan-approval.md +0 -1000
  86. package/docs/superpowers/plans/2026-06-13-repo-risk-hardening.md +0 -493
  87. package/docs/superpowers/plans/2026-06-14-host-runtime-auto-execution.md +0 -1775
  88. package/docs/superpowers/plans/2026-06-15-coding-preflight-pack-dispatch-path.md +0 -504
  89. package/docs/superpowers/plans/2026-06-15-internal-skill-migration-final-fixups.md +0 -342
  90. package/docs/superpowers/plans/2026-06-15-internal-skill-migration-fixups.md +0 -258
  91. package/docs/superpowers/plans/2026-06-15-internal-skill-migration-remaining-fixups.md +0 -387
  92. package/docs/superpowers/plans/2026-06-15-internal-skill-resource-migration.md +0 -749
  93. package/docs/superpowers/plans/2026-06-15-worker-prompt-anchor-final-fixups.md +0 -828
  94. package/docs/superpowers/plans/2026-06-15-worker-prompt-header-error-contract.md +0 -490
  95. package/docs/superpowers/plans/2026-06-17-okstra-error-report.md +0 -724
  96. package/docs/superpowers/plans/2026-06-18-subagent-pane-reclaim.md +0 -483
  97. package/docs/superpowers/plans/2026-06-19-agent-operating-standard.md +0 -476
  98. package/docs/superpowers/plans/2026-06-19-okstra-inspect-recap-facet.md +0 -647
  99. package/docs/superpowers/plans/2026-06-20-error-feedback-loop.md +0 -1019
  100. package/docs/superpowers/plans/2026-06-20-run-index-row-normalization.md +0 -583
  101. package/docs/superpowers/plans/2026-06-20-stage-auto-integrate-teardown.md +0 -819
  102. package/docs/superpowers/plans/2026-06-21-okstra-container-local-user-test.md +0 -714
  103. package/docs/superpowers/plans/2026-06-21-release-handoff-local-checkout.md +0 -348
  104. package/docs/superpowers/plans/2026-06-23-implementation-stage-multiselect-chain.md +0 -630
  105. package/docs/superpowers/plans/2026-06-23-implementation-step-commit-granularity.md +0 -274
  106. package/docs/superpowers/plans/2026-06-23-phase-batch-cleanup-enforcement.md +0 -409
  107. package/docs/superpowers/plans/2026-06-24-cross-project-precondition.md +0 -542
  108. package/docs/superpowers/plans/2026-06-24-decision-drafts-render-gap.md +0 -568
  109. package/docs/superpowers/plans/2026-06-24-manual-user-test-section.md +0 -203
  110. package/docs/superpowers/plans/2026-06-24-okstra-container-build-rename.md +0 -525
  111. package/docs/superpowers/specs/2026-05-12-ticket-id-in-reports-design.md +0 -131
  112. package/docs/superpowers/specs/2026-05-15-implementation-plan-verification-design.md +0 -254
  113. package/docs/superpowers/specs/2026-05-20-final-report-language-design.md +0 -383
  114. package/docs/superpowers/specs/2026-05-20-implementation-planning-multi-stage-design.md +0 -322
  115. package/docs/superpowers/specs/2026-05-20-okstra-run-prompt-sot-design.md +0 -299
  116. package/docs/superpowers/specs/2026-05-21-improvement-discovery-task-type-design.md +0 -335
  117. package/docs/superpowers/specs/2026-06-02-okstra-run-branch-confirm-step-design.md +0 -113
  118. package/docs/superpowers/specs/2026-06-02-okstra-run-phase-pane-cleanup-design.md +0 -173
  119. package/docs/superpowers/specs/2026-06-02-requirements-discovery-fanout-design.md +0 -154
  120. package/docs/superpowers/specs/2026-06-04-adversarial-implementation-planning-design.md +0 -90
  121. package/docs/superpowers/specs/2026-06-04-adversarial-verification-design.md +0 -176
  122. package/docs/superpowers/specs/2026-06-04-coverage-critic-design.md +0 -99
  123. package/docs/superpowers/specs/2026-06-04-stage-splitting-cost-aware-design.md +0 -98
  124. package/docs/superpowers/specs/2026-06-05-acceptance-critic-design.md +0 -90
  125. package/docs/superpowers/specs/2026-06-05-compact-markdown-report-tables-design.md +0 -87
  126. package/docs/superpowers/specs/2026-06-05-wizard-batch-prompts-design.md +0 -121
  127. package/docs/superpowers/specs/2026-06-06-final-verification-whole-task-gate-design.md +0 -126
  128. package/docs/superpowers/specs/2026-06-06-stage-worktree-isolation-design.md +0 -180
  129. package/docs/superpowers/specs/2026-06-06-vertical-slice-tdd-planning-design.md +0 -179
  130. package/docs/superpowers/specs/2026-06-07-stage-conformance-qa-design.md +0 -169
  131. package/docs/superpowers/specs/2026-06-09-executor-model-custom-id-cascade-design.md +0 -66
  132. package/docs/superpowers/specs/2026-06-09-implementation-run-artifact-stage-isolation-design.md +0 -87
  133. package/docs/superpowers/specs/2026-06-10-blocking-contract-posthoc-conformance-design.md +0 -168
  134. package/docs/superpowers/specs/2026-06-10-concurrent-run-team-guard-design.md +0 -107
  135. package/docs/superpowers/specs/2026-06-10-git-reconcile-stale-sha-recovery-design.md +0 -105
  136. package/docs/superpowers/specs/2026-06-10-lead-worker-completion-polling-design.md +0 -113
  137. package/docs/superpowers/specs/2026-06-10-stage-group-handoff-design.md +0 -156
  138. package/docs/superpowers/specs/2026-06-11-brief-entry-only-handoff-stage-entry-design.md +0 -158
  139. package/docs/superpowers/specs/2026-06-11-fix-cycle-design.md +0 -94
  140. package/docs/superpowers/specs/2026-06-11-wizard-whole-task-final-verification-design.md +0 -89
  141. package/docs/superpowers/specs/2026-06-12-codex-lead-adapter-design.md +0 -358
  142. package/docs/superpowers/specs/2026-06-12-html-plan-approval-design.md +0 -85
  143. package/docs/superpowers/specs/2026-06-12-stage-discipline-rule-design.md +0 -108
  144. package/docs/superpowers/specs/2026-06-13-forbidden-actions-ssot-design.md +0 -134
  145. package/docs/superpowers/specs/2026-06-13-neutral-tmux-lead-adapter-design.md +0 -284
  146. package/docs/superpowers/specs/2026-06-14-host-runtime-auto-execution-design.md +0 -404
  147. package/docs/superpowers/specs/2026-06-17-okstra-error-report-design.md +0 -123
  148. package/docs/superpowers/specs/2026-06-18-subagent-pane-reclaim-design.md +0 -150
  149. package/docs/superpowers/specs/2026-06-19-agent-operating-standard-design.md +0 -120
  150. package/docs/superpowers/specs/2026-06-19-okstra-inspect-recap-facet-design.md +0 -96
  151. package/docs/superpowers/specs/2026-06-20-error-feedback-loop-design.md +0 -171
  152. package/docs/superpowers/specs/2026-06-20-run-index-row-normalization-design.md +0 -112
  153. package/docs/superpowers/specs/2026-06-20-stage-auto-integrate-teardown-design.md +0 -145
  154. package/docs/superpowers/specs/2026-06-21-okstra-container-local-user-test-design.md +0 -125
  155. package/docs/superpowers/specs/2026-06-21-release-handoff-local-checkout-design.md +0 -113
  156. package/docs/superpowers/specs/2026-06-23-implementation-stage-multiselect-chain-design.md +0 -138
  157. package/docs/superpowers/specs/2026-06-23-implementation-step-commit-granularity-design.md +0 -120
  158. package/docs/superpowers/specs/2026-06-24-cross-project-precondition-design.md +0 -129
  159. package/docs/superpowers/specs/2026-06-24-decision-drafts-render-gap-design.md +0 -185
  160. package/docs/superpowers/specs/2026-06-24-manual-user-test-section-design.md +0 -121
  161. 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`)을 작성한다.