okstra 0.47.0 → 0.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/superpowers/plans/2026-06-04-adversarial-implementation-planning.md +294 -0
- package/docs/superpowers/plans/2026-06-04-coverage-critic.md +516 -0
- package/docs/superpowers/plans/2026-06-05-acceptance-critic.md +251 -0
- package/docs/superpowers/plans/2026-06-05-compact-markdown-report-tables.md +323 -0
- package/docs/superpowers/specs/2026-06-04-adversarial-implementation-planning-design.md +90 -0
- package/docs/superpowers/specs/2026-06-04-coverage-critic-design.md +99 -0
- package/docs/superpowers/specs/2026-06-05-acceptance-critic-design.md +90 -0
- package/docs/superpowers/specs/2026-06-05-compact-markdown-report-tables-design.md +87 -0
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/SKILL.md +3 -1
- package/runtime/bin/lib/okstra/tmux-pane.sh +40 -0
- package/runtime/bin/okstra-codex-exec.sh +17 -21
- package/runtime/bin/okstra-gemini-exec.sh +12 -15
- package/runtime/bin/okstra-trace-cleanup.sh +13 -1
- package/runtime/prompts/profiles/_common-contract.md +5 -5
- package/runtime/prompts/profiles/error-analysis.md +1 -0
- package/runtime/prompts/profiles/final-verification.md +2 -0
- package/runtime/prompts/profiles/implementation-planning.md +5 -1
- package/runtime/prompts/profiles/requirements-discovery.md +1 -0
- package/runtime/prompts/wizard/prompts.ko.json +13 -0
- package/runtime/python/okstra_ctl/render.py +24 -3
- package/runtime/python/okstra_ctl/report_views.py +16 -29
- package/runtime/python/okstra_ctl/run.py +12 -0
- package/runtime/python/okstra_ctl/wizard.py +72 -1
- package/runtime/skills/okstra-convergence/SKILL.md +80 -5
- package/runtime/skills/okstra-run/SKILL.md +1 -0
- package/runtime/templates/project-docs/task-index.template.md +1 -8
- package/runtime/templates/reports/final-report.template.md +24 -28
- package/runtime/templates/reports/i18n/en.json +14 -15
- package/runtime/templates/reports/i18n/ko.json +14 -15
- package/runtime/templates/reports/schedule.template.md +3 -7
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# Acceptance devil's-advocate critic 구현 계획 (sub-project B2)
|
|
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:** B1 의 critic primitive 를 `final-verification` 으로 확장해 "받아들이면 안 되는 이유/놓친 acceptance blocker" 를 캐는 악마의 변호인 패스를 추가하고, 후보 blocker 를 confirm-or-downgrade(확인→Acceptance Blocker, 미확인→Residual Risk)로 검증해 verdict 에 반영한다.
|
|
6
|
+
|
|
7
|
+
**Architecture:** selection·`--critic`·`convergence.critic` 블록·dispatch primitive 는 B1 그대로 재사용; 적용 phase 집합(render `critic_phases` + wizard `S_CRITIC_PICK.applies`)에 `final-verification` 만 추가. critic *행동*은 convergence skill 에서 phase 로 분기 — 3 finding-phase=coverage(B1), final-verification=acceptance devil's-advocate(신규, confirm-or-downgrade, Acceptance Blockers/Residual Risk 출력). 새 enum/validator 없음(기존 verdict↔blocker validator 재사용).
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Python 3 (okstra_ctl: render/wizard, pytest), Markdown skill/prompt 문서. 빌드: `npm run build`.
|
|
10
|
+
|
|
11
|
+
**설계 근거:** [`docs/superpowers/specs/2026-06-05-acceptance-critic-design.md`](../specs/2026-06-05-acceptance-critic-design.md)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 파일 구조
|
|
16
|
+
|
|
17
|
+
| 파일 | 책임 | 작업 |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| [`scripts/okstra_ctl/render.py`](../../../scripts/okstra_ctl/render.py) | `critic_phases` 에 final-verification 추가 | Modify (928) |
|
|
20
|
+
| [`scripts/okstra_ctl/wizard.py`](../../../scripts/okstra_ctl/wizard.py) | `S_CRITIC_PICK.applies`(1979) + summary(2313) phase tuple 에 final-verification | Modify |
|
|
21
|
+
| [`tests/test_render_critic_block.py`](../../../tests/test_render_critic_block.py) | final-verification 비적용→적용 플립 | Modify |
|
|
22
|
+
| [`tests/test_wizard_critic_pick.py`](../../../tests/test_wizard_critic_pick.py) | final-verification skipped→applies 플립 | Modify |
|
|
23
|
+
| [`skills/okstra-convergence/SKILL.md`](../../../skills/okstra-convergence/SKILL.md) | "Acceptance critic pass (final-verification)" 절 + Index | Modify |
|
|
24
|
+
| [`prompts/profiles/final-verification.md`](../../../prompts/profiles/final-verification.md) | 악마의 변호인 critic opt-in 선언 + verdict 영향 | Modify |
|
|
25
|
+
| [`agents/SKILL.md`](../../../agents/SKILL.md) | Phase 5.6 critic 가 final-verification 에도 적용 | Modify |
|
|
26
|
+
| [`prompts/wizard/prompts.ko.json`](../../../prompts/wizard/prompts.ko.json) | `critic_pick` label phase-중립 일반화 | Modify |
|
|
27
|
+
| [`CHANGES.md`](../../../CHANGES.md) | 사용자 영향 항목 | Modify |
|
|
28
|
+
|
|
29
|
+
작업 순서: 적용 phase 확장(render+wizard+test) → convergence skill 모드 → 프로필+agents+label → CHANGES+검증.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
### B2-Task1: 적용 phase 에 final-verification 추가 (render + wizard + 테스트 플립)
|
|
34
|
+
|
|
35
|
+
**Files:**
|
|
36
|
+
- Modify: `scripts/okstra_ctl/render.py:928`
|
|
37
|
+
- Modify: `scripts/okstra_ctl/wizard.py` (1979, 2313)
|
|
38
|
+
- Modify: `tests/test_render_critic_block.py`
|
|
39
|
+
- Modify: `tests/test_wizard_critic_pick.py`
|
|
40
|
+
|
|
41
|
+
- [ ] **Step 1: 테스트를 새 기대값으로 갱신 (red 유발)**
|
|
42
|
+
|
|
43
|
+
(1a) `tests/test_render_critic_block.py`: `test_critic_enabled_for_applicable_phases` 의 parametrize 리스트에 `"final-verification"` 을 추가:
|
|
44
|
+
```python
|
|
45
|
+
@pytest.mark.parametrize("task_type", ["requirements-discovery", "error-analysis", "implementation-planning", "final-verification"])
|
|
46
|
+
def test_critic_enabled_for_applicable_phases(task_type):
|
|
47
|
+
```
|
|
48
|
+
그리고 `test_critic_disabled_for_non_applicable_phases` 의 리스트에서 `"final-verification"` 을 삭제(남는 것: `["implementation", "release-handoff"]`).
|
|
49
|
+
|
|
50
|
+
(1b) `tests/test_wizard_critic_pick.py`: `test_critic_step_applies_for_three_phases_when_unset` 의 parametrize 리스트에 `"final-verification"` 추가(원하면 함수명도 `_for_applicable_phases` 로 변경 — 선택). `test_critic_step_skipped_for_other_phases` 리스트에서 `"final-verification"` 삭제(남는 것: `["implementation", "release-handoff"]`).
|
|
51
|
+
|
|
52
|
+
- [ ] **Step 2: 실패 확인**
|
|
53
|
+
|
|
54
|
+
Run: `python3 -m pytest tests/test_render_critic_block.py tests/test_wizard_critic_pick.py -q`
|
|
55
|
+
Expected: FAIL — final-verification 이 아직 critic 적용 phase 가 아님.
|
|
56
|
+
|
|
57
|
+
- [ ] **Step 3: render + wizard 구현**
|
|
58
|
+
|
|
59
|
+
(3a) `scripts/okstra_ctl/render.py:928` 의 줄
|
|
60
|
+
` critic_phases = {"requirements-discovery", "error-analysis", "implementation-planning"}`
|
|
61
|
+
를:
|
|
62
|
+
` critic_phases = {"requirements-discovery", "error-analysis", "implementation-planning", "final-verification"}`
|
|
63
|
+
|
|
64
|
+
(3b) `scripts/okstra_ctl/wizard.py` 의 `S_CRITIC_PICK` Step applies(현 1979) 의 phase tuple
|
|
65
|
+
`s.task_type in ("requirements-discovery", "error-analysis", "implementation-planning")`
|
|
66
|
+
를:
|
|
67
|
+
`s.task_type in ("requirements-discovery", "error-analysis", "implementation-planning", "final-verification")`
|
|
68
|
+
|
|
69
|
+
(3c) 같은 파일 summary 조건(현 2313) 의 동일 tuple 도 같은 4-phase tuple 로 교체.
|
|
70
|
+
|
|
71
|
+
(주의: 이 phase tuple 이 wizard.py 에 두 곳 — applies, summary — 있다. 둘 다 갱신. grep `"requirements-discovery", "error-analysis", "implementation-planning"` 로 누락 확인.)
|
|
72
|
+
|
|
73
|
+
- [ ] **Step 4: 통과 확인**
|
|
74
|
+
|
|
75
|
+
Run: `python3 -m pytest tests/test_render_critic_block.py tests/test_wizard_critic_pick.py -q`
|
|
76
|
+
Expected: PASS.
|
|
77
|
+
|
|
78
|
+
- [ ] **Step 5: 커밋**
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
git add scripts/okstra_ctl/render.py scripts/okstra_ctl/wizard.py tests/test_render_critic_block.py tests/test_wizard_critic_pick.py
|
|
82
|
+
git commit -m "feat(okstra_ctl): enable coverage critic selection for final-verification"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
### B2-Task2: convergence skill — Acceptance critic pass (final-verification) 모드
|
|
88
|
+
|
|
89
|
+
**Files:**
|
|
90
|
+
- Modify: `skills/okstra-convergence/SKILL.md`
|
|
91
|
+
|
|
92
|
+
- [ ] **Step 1: 새 "## Acceptance critic pass (final-verification)" 절 추가**
|
|
93
|
+
|
|
94
|
+
기존 `## Coverage critic pass` 절이 끝나는 지점 직후(다음 `## Output` 헤딩 **앞**)에 새 절 삽입:
|
|
95
|
+
|
|
96
|
+
```markdown
|
|
97
|
+
## Acceptance critic pass (final-verification)
|
|
98
|
+
|
|
99
|
+
The `final-verification` phase reuses the SAME reused-worker dispatch as §"Coverage critic pass" (provider + `config.critic.modelExecutionValue` from the `convergence.critic` block; default off; same model-unresolved skip rule). Only the prompt, the verification semantics, and the output sink differ — final-verification's findings are defects/blockers, so the critic acts as an **acceptance devil's advocate** (find reasons NOT to accept), and its candidate blockers are NEVER dropped (that would suppress real defects).
|
|
100
|
+
|
|
101
|
+
### Prompt
|
|
102
|
+
```
|
|
103
|
+
You are the acceptance devil's advocate for <task-key>. The delivered work is about
|
|
104
|
+
to be judged for acceptance. Your ONLY job is to find reasons it should NOT be
|
|
105
|
+
accepted — surface candidate acceptance BLOCKERS the verifiers may have missed:
|
|
106
|
+
- requirements / acceptance points with no covering evidence,
|
|
107
|
+
- DB / IO / SQL changes lacking real-execution evidence,
|
|
108
|
+
- regressions or broken error paths,
|
|
109
|
+
- scope / contract violations.
|
|
110
|
+
For each, emit a candidate blocker with a one-line statement, evidence (file:line /
|
|
111
|
+
log / test output), and a severity (critical / major / minor). Do NOT restate an
|
|
112
|
+
existing Acceptance Blocker. If you find none, say so explicitly.
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Verification — confirm-or-downgrade (BLOCKING)
|
|
116
|
+
Each candidate blocker is verified by the Phase 4 analysers (excluding the critic). Do NOT use the adversarial finding classifier's "uncertain → reject" rule here.
|
|
117
|
+
- **Confirmed** (an analyser reproduces it or cites supporting evidence) → promote to a `## 4 Acceptance Blockers` row (keep severity + recommended follow-up phase).
|
|
118
|
+
- **Not confirmed** (cannot reproduce, or evidence is weak) → **downgrade to a Residual Risk row — never drop it.** Record the escalation trigger so the user can re-judge a high-severity-but-unconfirmed candidate.
|
|
119
|
+
|
|
120
|
+
### Verdict impact
|
|
121
|
+
Promoted blockers enter `## 4 Acceptance Blockers`; since `accepted` requires zero blockers, the verdict moves to `conditional-accept` / `blocked` automatically. The existing verdict↔blocker consistency validator (`validators/validate-run.py` `_validate_final_verification_consistency`) enforces this unchanged — no new enum or validator.
|
|
122
|
+
|
|
123
|
+
### State
|
|
124
|
+
Critic output lives at `runs/final-verification/worker-results/<provider>-critic-final-verification-<seq>.md`. The convergence state `config.critic` summary (see §"Coverage critic pass") records `mode: "acceptance-devils-advocate"`, `candidatesProposed`, `confirmedBlockers`, `downgradedToResidual` (optional v1.2 fields; readers treat absence as null).
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
- [ ] **Step 2: Index 항목 추가**
|
|
128
|
+
|
|
129
|
+
`## Index` 목록에서 `- [Coverage critic pass](#coverage-critic-pass)` 다음 줄에 추가:
|
|
130
|
+
```markdown
|
|
131
|
+
- [Acceptance critic pass (final-verification)](#acceptance-critic-pass-final-verification)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
- [ ] **Step 3: 빌드 + 검증**
|
|
135
|
+
|
|
136
|
+
Run: `npm run build && bash validators/validate-workflow.sh`
|
|
137
|
+
Expected: build 22/22 synced, validator PASS.
|
|
138
|
+
|
|
139
|
+
- [ ] **Step 4: 커밋**
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
git add skills/okstra-convergence/SKILL.md runtime/
|
|
143
|
+
git commit -m "feat(skills/okstra-convergence): acceptance devil's-advocate critic mode for final-verification"
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
### B2-Task3: final-verification 프로필 + agents/SKILL.md + label 일반화
|
|
149
|
+
|
|
150
|
+
**Files:**
|
|
151
|
+
- Modify: `prompts/profiles/final-verification.md`
|
|
152
|
+
- Modify: `agents/SKILL.md`
|
|
153
|
+
- Modify: `prompts/wizard/prompts.ko.json`
|
|
154
|
+
|
|
155
|
+
- [ ] **Step 1: final-verification 프로필에 critic opt-in 선언**
|
|
156
|
+
|
|
157
|
+
`prompts/profiles/final-verification.md` 의 `- Non-goals:` 줄(현 47) **앞에** 새 top-level bullet 삽입:
|
|
158
|
+
|
|
159
|
+
```markdown
|
|
160
|
+
- Cross-verification mode:
|
|
161
|
+
- **Acceptance critic (opt-in)**: when `convergence.critic.enabled=true` (chosen via the okstra-run picker or `--critic`), a reused-worker **acceptance devil's-advocate** pass runs after convergence to surface candidate acceptance blockers the verifiers may have missed. Each candidate is verified **confirm-or-downgrade**: confirmed → an `Acceptance Blockers` row (which, since `accepted` requires zero blockers, moves the verdict to `conditional-accept` / `blocked`); unconfirmed → a `Residual Risk` row (never dropped). See `skills/okstra-convergence/SKILL.md` "Acceptance critic pass (final-verification)".
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
- [ ] **Step 2: agents/SKILL.md — Phase 5.6 행이 final-verification 도 커버함을 명시**
|
|
165
|
+
|
|
166
|
+
`agents/SKILL.md` 의 `| 5.6 Coverage critic | ... |` 행을 찾아, 설명 셀을 final-verification 모드도 포함하도록 보강. 행 텍스트를:
|
|
167
|
+
```markdown
|
|
168
|
+
| 5.6 Critic pass | (opt-in) reused-worker critic pass: coverage gaps (discovery/error-analysis/impl-planning) or acceptance devil's-advocate (final-verification), each verified one round | `okstra-convergence` "Coverage critic pass" / "Acceptance critic pass" |
|
|
169
|
+
```
|
|
170
|
+
로 교체(컬럼 수 유지). PROGRESS 라인(`phase-5.6-critic ...`)은 그대로 두되, 그 설명에 final-verification 도 포함됨을 한 구절 덧붙여도 좋다(선택).
|
|
171
|
+
|
|
172
|
+
- [ ] **Step 3: prompts.ko.json critic_pick label 일반화**
|
|
173
|
+
|
|
174
|
+
`prompts/wizard/prompts.ko.json` 의 `critic_pick.label` 이 현재 coverage 중심 문구라면 phase-중립으로 일반화:
|
|
175
|
+
```json
|
|
176
|
+
"label": "추가 critic 패스를 돌릴까요? (놓친 finding/blocker 를 캐는 검증 패스 — opt-in)",
|
|
177
|
+
```
|
|
178
|
+
(label 만 변경; options/echo_template 불변.)
|
|
179
|
+
|
|
180
|
+
- [ ] **Step 4: 빌드 + 검증**
|
|
181
|
+
|
|
182
|
+
Run: `npm run build && bash validators/validate-workflow.sh`
|
|
183
|
+
Expected: build 성공, validator PASS.
|
|
184
|
+
|
|
185
|
+
- [ ] **Step 5: 커밋**
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
git add prompts/profiles/final-verification.md agents/SKILL.md prompts/wizard/prompts.ko.json runtime/
|
|
189
|
+
git commit -m "feat(profiles,agents): declare acceptance devil's-advocate critic for final-verification"
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
### B2-Task4: CHANGES + 전체 검증 + 최종 커밋
|
|
195
|
+
|
|
196
|
+
**Files:**
|
|
197
|
+
- Modify: `CHANGES.md`
|
|
198
|
+
|
|
199
|
+
- [ ] **Step 1: CHANGES.md 항목 추가**
|
|
200
|
+
|
|
201
|
+
`## 2026-06-05` 날짜 헤더가 있으면 그 다음에, 없으면 파일 맨 위 `# 변경 이력` 설명 단락 다음에 새 `## 2026-06-05` 헤더를 만들고 그 아래에 삽입:
|
|
202
|
+
|
|
203
|
+
```markdown
|
|
204
|
+
### feat(convergence): final-verification 악마의 변호인 critic — 놓친 acceptance blocker 발굴
|
|
205
|
+
|
|
206
|
+
- B1 의 opt-in critic 을 final-verification 으로 확장했다. 단, final-verification 의 finding 은 결함/blocker 이라 B1 의 적대적-drop 검증을 그대로 쓰면 "재현 못 한 진짜 결함" 을 억누른다. 그래서 여기서 critic 은 **악마의 변호인** — "받아들이면 안 되는 이유/놓친 acceptance blocker" 를 캐고 — 후보 blocker 는 **confirm-or-downgrade** 로 검증한다: analyser 가 확인하면 Acceptance Blocker 로 승격(→ `accepted` 는 blocker 0 을 요구하므로 verdict 가 conditional-accept/blocked 로 밀림), 확인 못 하면 **기각이 아니라 Residual Risk** 로 강등(추적 보존). 선택 UX·`--critic`·모델은 B1 그대로이고 적용 phase 에 final-verification 만 추가(이제 4 phase). 기본 off.
|
|
207
|
+
- 사용자 영향: 다음 release + `npx -y okstra@latest install` 후 적용. 이제 final-verification 에서도 critic 을 켜면 검증자가 놓친 수용 차단 사유를 한 번 더 캐고, 확인된 것만 verdict 를 막고 미확인은 잔여 위험으로 남는다(기본 꺼짐). 새 verdict/validator 추가 없이 기존 verdict↔blocker 규칙으로 동작한다. critic *행동*은 lead/워커 prompt 지시(LLM).
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
- [ ] **Step 2: 전체 검증**
|
|
211
|
+
|
|
212
|
+
Run:
|
|
213
|
+
```bash
|
|
214
|
+
npm run build
|
|
215
|
+
python3 -m pytest tests/ -q
|
|
216
|
+
bash validators/validate-workflow.sh
|
|
217
|
+
node bin/okstra --version
|
|
218
|
+
```
|
|
219
|
+
ALL must pass. `tests/test_okstra_worktree.py` 의 알려진 간헐 flake 는 단독 재실행으로 확인. 그 외 실패 → STOP + BLOCKED.
|
|
220
|
+
|
|
221
|
+
- [ ] **Step 3: 일관성 self-review**
|
|
222
|
+
|
|
223
|
+
Run:
|
|
224
|
+
```bash
|
|
225
|
+
grep -rn "final-verification" scripts/okstra_ctl/render.py scripts/okstra_ctl/wizard.py | grep -i critic
|
|
226
|
+
grep -rn "Acceptance critic\|acceptance-devils-advocate\|devil's-advocate\|confirm-or-downgrade" skills/okstra-convergence/SKILL.md prompts/profiles/final-verification.md CHANGES.md
|
|
227
|
+
```
|
|
228
|
+
Confirm: render/wizard 가 final-verification 을 critic 적용 phase 로 포함; skill·profile·CHANGES 가 악마의 변호인/confirm-or-downgrade 를 일관되게 기술. 댕글링 없음.
|
|
229
|
+
|
|
230
|
+
- [ ] **Step 4: 최종 커밋**
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
git add CHANGES.md
|
|
234
|
+
git commit -m "docs(changes): log acceptance devil's-advocate critic for final-verification"
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Self-Review (작성자 체크리스트)
|
|
240
|
+
|
|
241
|
+
**1. Spec coverage**
|
|
242
|
+
- §2.1 적용 phase 확장 → B2-Task1(render+wizard) + B2-Task3(profile).
|
|
243
|
+
- §2.2 악마의 변호인 모드(프롬프트·confirm-or-downgrade·verdict 연동) → B2-Task2(skill) + B2-Task3(profile 선언).
|
|
244
|
+
- §2.3 상태 기록(config.critic mode/counts) → B2-Task2 State 단락.
|
|
245
|
+
- §3 변경 파일 9종 → B2-Task1–4 전부.
|
|
246
|
+
- §4 enforcement(render/wizard 테스트 + 기존 verdict validator 재사용) → B2-Task1 테스트, B2-Task2 명시.
|
|
247
|
+
- §6 수용기준 1–5 → B2-Task1(1,2), B2-Task2(3), B2-Task3(4), B2-Task4(5).
|
|
248
|
+
|
|
249
|
+
**2. Placeholder scan:** 코드/markdown 블록 실제 내용. TBD/TODO 없음.
|
|
250
|
+
|
|
251
|
+
**3. 식별자 일관성:** `critic_phases`(render set), `S_CRITIC_PICK`(wizard, 불변), `convergence.critic`(B1, 불변), "Acceptance critic pass (final-verification)"(신규 절 제목, Task2 정의 → Task3 profile 참조 동일), `confirm-or-downgrade`/`acceptance-devils-advocate`(skill+CHANGES 동일 철자). 4 적용 phase 집합이 render(set)·wizard(applies+summary)에서 동일.
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
# Compact markdown final-report tables (option X) 구현 계획
|
|
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:** 정본 final-report `.md` 의 narrative 표를 `meta(<br> stack) + prose` 형태로 재구성해 markdown 에디터에서도 핵심 본문이 넓게 읽히게 한다. §5 Clarification 은 평면 유지.
|
|
6
|
+
|
|
7
|
+
**Architecture:** 변경은 (1) jinja 템플릿 레이아웃 + i18n 라벨 1개, (2) `report_views._inline` 의 `<br>` 보존 + 이제 불필요한 grouping 분기 제거(§5 만 유지)에 한정. `data.json`·report-writer 계약 불변.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** jinja2 템플릿, Python 3 (report_views, pytest), JSON i18n. 빌드 `npm run build`.
|
|
10
|
+
|
|
11
|
+
**설계 근거:** [`docs/superpowers/specs/2026-06-05-compact-markdown-report-tables-design.md`](../specs/2026-06-05-compact-markdown-report-tables-design.md)
|
|
12
|
+
|
|
13
|
+
**범위 메모:** spec 표 목록(§1, §1.1, §1.2, §3.1, §3.2, §4, Execution Status)에 더해 **§7 Follow-up Tasks** 도 동일 안전 md-merge 대상에 포함한다(코드가 컬럼 파싱하지 않음 — §5 와 달리 안전, 일관성 확보). §5 만 평면 예외.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 파일 구조
|
|
18
|
+
|
|
19
|
+
| 파일 | 책임 | 작업 |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| [`templates/reports/final-report.template.md`](../../../templates/reports/final-report.template.md) | §1/§1.1/§1.2/§3.1/§3.2/§4/§7/Exec 표를 meta+prose 로 | Modify |
|
|
22
|
+
| [`templates/reports/i18n/ko.json`](../../../templates/reports/i18n/ko.json) · [`en.json`](../../../templates/reports/i18n/en.json) | `columns.recordMeta` 키 추가 | Modify |
|
|
23
|
+
| [`scripts/okstra_ctl/report_views.py`](../../../scripts/okstra_ctl/report_views.py) | `_inline` `<br>` 보존; generic/Exec/§7 grouping 분기 제거(§5 유지) | Modify |
|
|
24
|
+
| [`tests/test_report_views.py`](../../../tests/test_report_views.py) | `<br>` 보존 + §5 유지 + §1/§3/§4 plain 테스트 | Modify |
|
|
25
|
+
| [`CHANGES.md`](../../../CHANGES.md) | 사용자 영향 항목 | Modify |
|
|
26
|
+
|
|
27
|
+
작업 순서: 템플릿+i18n(.md 구조) → report_views(`<br>` 보존 + 분기 정리) → CHANGES + 전체 검증 + 실제 재렌더.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
### Task 1: 템플릿 + i18n — narrative 표를 meta(`<br>`)+prose 로
|
|
32
|
+
|
|
33
|
+
**Files:**
|
|
34
|
+
- Modify: `templates/reports/i18n/ko.json`, `templates/reports/i18n/en.json`
|
|
35
|
+
- Modify: `templates/reports/final-report.template.md`
|
|
36
|
+
|
|
37
|
+
- [ ] **Step 1: i18n 에 meta 헤더 키 추가**
|
|
38
|
+
|
|
39
|
+
`templates/reports/i18n/ko.json` 의 `"columns"` 객체에 추가: `"recordMeta": "항목"`.
|
|
40
|
+
`templates/reports/i18n/en.json` 의 `"columns"` 객체에 추가: `"recordMeta": "Record"`.
|
|
41
|
+
|
|
42
|
+
- [ ] **Step 2: §1 Summary 표 교체**
|
|
43
|
+
|
|
44
|
+
`templates/reports/final-report.template.md` 의 §1 표 블록
|
|
45
|
+
```
|
|
46
|
+
| ID | Ticket ID | {{ t("columns.summary") }} | {{ t("columns.source") }} |
|
|
47
|
+
|----|-----------|------------|----------------------------|
|
|
48
|
+
{% for row in summary -%}
|
|
49
|
+
| {{ row.id }} | `{{ row.ticketId }}` | {{ row.summary }} | {{ row.source }} |
|
|
50
|
+
{% endfor %}
|
|
51
|
+
```
|
|
52
|
+
를 다음으로 교체:
|
|
53
|
+
```
|
|
54
|
+
| {{ t("columns.recordMeta") }} | {{ t("columns.summary") }} |
|
|
55
|
+
|--------|------------|
|
|
56
|
+
{% for row in summary -%}
|
|
57
|
+
| **{{ row.id }}**<br>Ticket: `{{ row.ticketId }}`<br>{{ t("columns.source") }}: {{ row.source }} | {{ row.summary }} |
|
|
58
|
+
{% endfor %}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- [ ] **Step 3: §1.1 Consensus 표 교체**
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
| ID | Ticket ID | Statement | Source items (worker:item) | Evidence (path:line / log / worker report) |
|
|
65
|
+
|----|-----------|-----------|----------------------------|---------------------------------------------|
|
|
66
|
+
{% for row in crossVerification.consensus -%}
|
|
67
|
+
| {{ row.id }} | `{{ row.ticketId }}` | {{ row.statement }} | {{ row.sourceItems | join(', ') }} | {{ row.evidence }} |
|
|
68
|
+
{% endfor %}
|
|
69
|
+
```
|
|
70
|
+
를:
|
|
71
|
+
```
|
|
72
|
+
| {{ t("columns.recordMeta") }} | Statement | Evidence (path:line / log / worker report) |
|
|
73
|
+
|--------|-----------|---------------------------------------------|
|
|
74
|
+
{% for row in crossVerification.consensus -%}
|
|
75
|
+
| **{{ row.id }}**<br>Ticket: `{{ row.ticketId }}`<br>Source items: {{ row.sourceItems | join(', ') }} | {{ row.statement }} | {{ row.evidence }} |
|
|
76
|
+
{% endfor %}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
- [ ] **Step 4: §1.2 Differences 표 교체**
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
| ID | Ticket ID | Disagreement | Workers (position + item) | Evidence |
|
|
83
|
+
|----|-----------|--------------|---------------------------|----------|
|
|
84
|
+
{% for row in crossVerification.differences -%}
|
|
85
|
+
| {{ row.id }} | `{{ row.ticketId }}` | {{ row.disagreement }} | {% for w in row.workersPosition %}{{ w.worker }}:{{ w.itemId }} ({{ w.position }}){% if not loop.last %} / {% endif %}{% endfor %} | {{ row.evidence }} |
|
|
86
|
+
{% endfor %}
|
|
87
|
+
```
|
|
88
|
+
를:
|
|
89
|
+
```
|
|
90
|
+
| {{ t("columns.recordMeta") }} | Disagreement | Evidence |
|
|
91
|
+
|--------|--------------|----------|
|
|
92
|
+
{% for row in crossVerification.differences -%}
|
|
93
|
+
| **{{ row.id }}**<br>Ticket: `{{ row.ticketId }}`<br>Workers: {% for w in row.workersPosition %}{{ w.worker }}:{{ w.itemId }} ({{ w.position }}){% if not loop.last %} / {% endif %}{% endfor %} | {{ row.disagreement }} | {{ row.evidence }} |
|
|
94
|
+
{% endfor %}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
- [ ] **Step 5: §3.1 Primary Evidence 표 교체**
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
| ID | Ticket ID | Evidence | Source items (worker:item) | Source (path:line / log) |
|
|
101
|
+
|----|-----------|----------|----------------------------|---------------------------|
|
|
102
|
+
{% for row in evidence.primary -%}
|
|
103
|
+
| {{ row.id }} | `{{ row.ticketId }}` | {{ row.evidence }} | {{ row.sourceItems | join(', ') }} | {{ row.source }} |
|
|
104
|
+
{% endfor %}
|
|
105
|
+
```
|
|
106
|
+
를:
|
|
107
|
+
```
|
|
108
|
+
| {{ t("columns.recordMeta") }} | Evidence |
|
|
109
|
+
|--------|----------|
|
|
110
|
+
{% for row in evidence.primary -%}
|
|
111
|
+
| **{{ row.id }}**<br>Ticket: `{{ row.ticketId }}`<br>Source items: {{ row.sourceItems | join(', ') }}<br>Source: {{ row.source }} | {{ row.evidence }} |
|
|
112
|
+
{% endfor %}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- [ ] **Step 6: §3.2 Secondary 표 교체**
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
| ID | Ticket ID | Hypothesis or supporting evidence | Source / confidence |
|
|
119
|
+
|----|-----------|-----------------------------------|---------------------|
|
|
120
|
+
{% for row in evidence.secondary -%}
|
|
121
|
+
| {{ row.id }} | `{{ row.ticketId }}` | {{ row.hypothesis }} | {{ row.confidence }} |
|
|
122
|
+
{% endfor %}
|
|
123
|
+
```
|
|
124
|
+
를:
|
|
125
|
+
```
|
|
126
|
+
| {{ t("columns.recordMeta") }} | Hypothesis or supporting evidence | Source / confidence |
|
|
127
|
+
|--------|-----------------------------------|---------------------|
|
|
128
|
+
{% for row in evidence.secondary -%}
|
|
129
|
+
| **{{ row.id }}**<br>Ticket: `{{ row.ticketId }}` | {{ row.hypothesis }} | {{ row.confidence }} |
|
|
130
|
+
{% endfor %}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
- [ ] **Step 7: §4 Risks 표 교체**
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
| ID | Ticket ID | Item | Risk if ignored | Mitigation Owner |
|
|
137
|
+
|----|-----------|------|-----------------|------------------|
|
|
138
|
+
{% for row in missingInformation -%}
|
|
139
|
+
| {{ row.id }} | `{{ row.ticketId }}` | {{ row.item }} | {{ row.risk }} | {{ row.owner }} |
|
|
140
|
+
```
|
|
141
|
+
의 헤더/구분/row 3줄을:
|
|
142
|
+
```
|
|
143
|
+
| {{ t("columns.recordMeta") }} | Item | Risk if ignored | Mitigation Owner |
|
|
144
|
+
|--------|------|-----------------|------------------|
|
|
145
|
+
{% for row in missingInformation -%}
|
|
146
|
+
| **{{ row.id }}**<br>Ticket: `{{ row.ticketId }}` | {{ row.item }} | {{ row.risk }} | {{ row.owner }} |
|
|
147
|
+
```
|
|
148
|
+
(`{% endfor %}` 이후는 그대로.)
|
|
149
|
+
|
|
150
|
+
- [ ] **Step 8: Execution Status 표 교체**
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
| Agent | Role | Model | Status | {{ t("columns.rawTokens") }} | {{ t("columns.billableTokens") }} | {{ t("columns.cost") }} | Duration | Summary of Key Findings |
|
|
154
|
+
|-------|------|-------|--------|-----------|-----------|------------|----------|-------------------------|
|
|
155
|
+
{% for row in executionStatus -%}
|
|
156
|
+
| {{ row.agent }} | {{ row.role }} | {{ row.model }} | {{ row.status }} | {{ row.totalTokens | format_int }}{% if row.cliTotalTokens %} (CLI: {{ row.cliTotalTokens | format_int }}){% endif %} | {{ row.billableTokens | format_int }} | {{ row.costUsd | format_usd }}{% if row.cliCostUsd %} (+ CLI {{ row.cliCostUsd | format_usd }}){% endif %} | {{ row.durationMs | format_duration_ms }} | {{ row.summary }} |
|
|
157
|
+
{% endfor %}
|
|
158
|
+
```
|
|
159
|
+
를:
|
|
160
|
+
```
|
|
161
|
+
| {{ t("columns.recordMeta") }} | Summary of Key Findings |
|
|
162
|
+
|--------|-------------------------|
|
|
163
|
+
{% for row in executionStatus -%}
|
|
164
|
+
| **{{ row.agent }}**<br>Role: {{ row.role }}<br>Model: {{ row.model }}<br>Status: {{ row.status }}<br>{{ t("columns.rawTokens") }}: {{ row.totalTokens | format_int }}{% if row.cliTotalTokens %} (CLI: {{ row.cliTotalTokens | format_int }}){% endif %}<br>{{ t("columns.billableTokens") }}: {{ row.billableTokens | format_int }}<br>{{ t("columns.cost") }}: {{ row.costUsd | format_usd }}{% if row.cliCostUsd %} (+ CLI {{ row.cliCostUsd | format_usd }}){% endif %}<br>Duration: {{ row.durationMs | format_duration_ms }} | {{ row.summary }} |
|
|
165
|
+
{% endfor %}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
- [ ] **Step 9: §7 Follow-up Tasks 표 교체**
|
|
169
|
+
|
|
170
|
+
§7 표를 찾아(헤더에 `Title` / `Scope` / `Reason`), 짧은 컬럼(ID, Ticket ID, Origin, New Task ID, Suggested task-type, Priority, Auto-spawn 등)을 meta 셀에 `<br>` stack 하고 Title/Scope/Reason 을 prose 컬럼으로 둔다. 헤더를 `| {{ t("columns.recordMeta") }} | Title | Scope (files/areas) | Reason / Why deferred |` 로, row 의 meta 셀은 `**{{ row.id }}**<br>Ticket: \`{{ row.ticketId }}\`<br>Origin: {{ row.origin }}<br>New Task ID: {{ row.newTaskId }}<br>Type: {{ row.suggestedTaskType }}<br>Priority: {{ row.priority }}<br>Auto-spawn: {{ row.autoSpawn }}` 로 구성(현 row 의 필드명을 그대로 사용 — 먼저 현 §7 row 의 jinja 필드명을 읽어 정확히 매핑할 것). 빈 상태 분기 유지.
|
|
171
|
+
|
|
172
|
+
- [ ] **Step 10: §5 는 건드리지 않음 (확인)**
|
|
173
|
+
|
|
174
|
+
§5 Clarification Items 의 8-컬럼 표(`| ID | Ticket ID | Kind | Statement | Expected form | Blocks | Status | User input |`)는 **변경하지 않는다**. grep 으로 §5 표가 8-컬럼 그대로인지 확인.
|
|
175
|
+
|
|
176
|
+
- [ ] **Step 11: 렌더 스모크 + 커밋**
|
|
177
|
+
|
|
178
|
+
기존 렌더 테스트로 회귀 확인 후, 픽스처 data.json 으로 렌더해 §1/§1.1/§1.2/§3.1/§3.2/§4/§7/Exec 가 `recordMeta` 헤더 + `<br>` 를 포함하고 §5 가 8-컬럼인지 확인:
|
|
179
|
+
```bash
|
|
180
|
+
python3 -m pytest tests/test_render_final_report.py tests/test_template_full_render_both_langs.py -q
|
|
181
|
+
```
|
|
182
|
+
Expected: PASS (템플릿 문법 OK). 실패 시 jinja 문법/필드명 교정.
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
git add templates/reports/final-report.template.md templates/reports/i18n/ko.json templates/reports/i18n/en.json
|
|
186
|
+
git commit -m "feat(report-template): compact narrative tables as meta(<br>)+prose; §5 stays flat"
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
### Task 2: report_views — `<br>` 보존 + grouping 분기 정리
|
|
192
|
+
|
|
193
|
+
**Files:**
|
|
194
|
+
- Modify: `scripts/okstra_ctl/report_views.py`
|
|
195
|
+
- Modify: `tests/test_report_views.py`
|
|
196
|
+
|
|
197
|
+
- [ ] **Step 1: 실패 테스트 작성 (`<br>` 보존 + §1 plain)**
|
|
198
|
+
|
|
199
|
+
`tests/test_report_views.py` 끝에 추가:
|
|
200
|
+
```python
|
|
201
|
+
def test_inline_preserves_br_tags():
|
|
202
|
+
from okstra_ctl.report_views import _inline # noqa: PLC0415
|
|
203
|
+
out = _inline("**C-1**<br>Ticket: `DEV-1`<br>Source items: claude:F-001")
|
|
204
|
+
assert "<br>" in out
|
|
205
|
+
assert "<br>" not in out
|
|
206
|
+
assert "<strong>C-1</strong>" in out
|
|
207
|
+
assert "<code>DEV-1</code>" in out
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def test_summary_meta_table_renders_plain_with_br():
|
|
211
|
+
# §1 Summary is now pre-merged in the .md (meta col + Summary col); the
|
|
212
|
+
# grouped-table branch must NOT fire (no separate `Ticket ID` column) and
|
|
213
|
+
# the <br> in the meta cell survives.
|
|
214
|
+
html_out = _emit(
|
|
215
|
+
"| 항목 | 한 줄 요약 |",
|
|
216
|
+
"| **P-001**<br>Ticket: `DEV-9184`<br>출처: task-brief.md:19 | " + ("on-the-fly 계산 전환 핵심 변경 " * 4) + " |",
|
|
217
|
+
section="1. Summary of the Problem or Verification Target",
|
|
218
|
+
)
|
|
219
|
+
assert 'class="grouped-table"' not in html_out
|
|
220
|
+
assert "<br>" in html_out
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
- [ ] **Step 2: 실패 확인**
|
|
224
|
+
|
|
225
|
+
Run: `python3 -m pytest tests/test_report_views.py -q -k "inline_preserves_br or summary_meta_table"`
|
|
226
|
+
Expected: FAIL — `_inline` escapes `<br>`; §1 still hits the generic grouping branch (grouped-table present).
|
|
227
|
+
|
|
228
|
+
- [ ] **Step 3: `_inline` 가 `<br>` 보존**
|
|
229
|
+
|
|
230
|
+
`scripts/okstra_ctl/report_views.py` `_inline` 의 `return out` 직전에 추가:
|
|
231
|
+
```python
|
|
232
|
+
# Preserve explicit <br> line breaks used inside compact meta cells (the
|
|
233
|
+
# markdown source intentionally stacks short fields with <br>). They are
|
|
234
|
+
# escaped to <br> by html.escape above; restore the tag.
|
|
235
|
+
out = out.replace("<br>", "<br>").replace("<br/>", "<br>").replace("<br />", "<br>")
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
- [ ] **Step 4: generic/Exec/§7 grouping 분기 제거 (§5 유지)**
|
|
239
|
+
|
|
240
|
+
`_grouped_table_spec` 에서 다음을 **삭제**:
|
|
241
|
+
- "Execution Status by Agent" 분기 (`if len(norm) >= 3 and norm[0] == "Agent" ...`).
|
|
242
|
+
- "§7 Follow-up Tasks" 분기 (`if any("Follow-up Tasks" in h ...)`).
|
|
243
|
+
- generic "Ticket ID" 분기 (`if any(h == "Ticket ID" for h in norm): ...`) + 그 헬퍼 `_is_force_meta`, `_column_is_wide`, 상수 `_WIDE_PROSE_TOKENS`, `_FORCE_META_TOKENS`, `_WIDE_CONTENT_THRESHOLD`, `_FOLLOWUP_WIDE_PREFIXES` (이제 미사용).
|
|
244
|
+
- **유지:** §5 Clarification 분기(Expected form/Statement/User input wide) + signature 의 `rows` 파라미터(호출부 호환). `rows` 가 더는 분기 로직에 안 쓰이면, 호출부 `_grouped_table_spec(header_cells, rows, section_path)` 도 `_grouped_table_spec(header_cells, section_path)` 로 되돌리고 시그니처에서 `rows` 제거(미사용 인자 정리).
|
|
245
|
+
|
|
246
|
+
docstring 을 "§5 Clarification Items 만 grouped (interactive form). 나머지 narrative 표는 템플릿에서 이미 compact 하게 렌더되므로 여기서 grouping 하지 않는다." 로 갱신.
|
|
247
|
+
|
|
248
|
+
- [ ] **Step 5: 통과 확인 + 기존 grouped 테스트 정리**
|
|
249
|
+
|
|
250
|
+
Run: `python3 -m pytest tests/test_report_views.py -q`
|
|
251
|
+
Expected: 신규 2개 PASS. **단, Task 직전(2343e30)에 추가한 §1/§3/§4 grouped 테스트**(`test_summary_table_groups_short_cols_and_widens_prose`, `test_risks_table_widens_item_risk_mitigation`)는 이제 의도가 바뀌었으므로 제거하거나 plain+`<br>` 기대로 갱신한다. `test_clarification_expected_form_is_wide_not_meta` 는 **유지**(§5 grouping 살아있음). 모든 report_views 테스트 PASS 확인.
|
|
252
|
+
|
|
253
|
+
- [ ] **Step 6: 빌드 + 커밋**
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
npm run build && bash validators/validate-workflow.sh
|
|
257
|
+
git add scripts/okstra_ctl/report_views.py tests/test_report_views.py runtime/
|
|
258
|
+
git commit -m "refactor(report-views): preserve <br>; group only §5 (narrative tables compact in md)"
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
### Task 3: CHANGES + 전체 검증 + 실제 재렌더
|
|
264
|
+
|
|
265
|
+
**Files:**
|
|
266
|
+
- Modify: `CHANGES.md`
|
|
267
|
+
|
|
268
|
+
- [ ] **Step 1: CHANGES 항목 추가**
|
|
269
|
+
|
|
270
|
+
`## 2026-06-05` 아래(직전 report-views 항목 근처)에 삽입:
|
|
271
|
+
```markdown
|
|
272
|
+
### feat(report-template): 정본 final-report `.md` 표를 compact 하게 (meta + prose)
|
|
273
|
+
|
|
274
|
+
- markdown 표는 컬럼 병합이 안 돼 ID·Ticket·Source 같은 짧은 코드 컬럼이 칸을 차지하면 요약·근거·이견·위험 같은 긴 본문이 좁아져 뭉개졌다(에디터에서 한 글자/줄). 이제 §1 Summary·§1.1 Consensus·§1.2 Differences·§3.1/§3.2 Evidence·§4 Risks·§7 Follow-up·Execution Status 표를 **짧은 코드 필드는 `<br>` 로 한 meta 셀에 stack + 긴 본문은 별도 컬럼**으로 렌더한다. §5 Clarification 은 carry-in 파서·validator 8-컬럼 계약 때문에 평면 유지(§5 compact 는 HTML view grouping 담당). `data.json`·report-writer 계약은 불변(템플릿 레이아웃만). HTML self-contained view 도 `_inline` 이 `<br>` 를 보존해 동일하게 compact 하게 보인다.
|
|
275
|
+
- 사용자 영향: 다음 release + `npx -y okstra@latest install` 후 적용. 이제 final-report 를 어떤 markdown 에디터로 열어도 핵심 본문이 넓게 읽힌다. `.md`↔HTML 레이아웃이 일관된다.
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
- [ ] **Step 2: 전체 검증**
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
npm run build
|
|
282
|
+
python3 -m pytest tests/ -q
|
|
283
|
+
bash validators/validate-workflow.sh
|
|
284
|
+
node bin/okstra --version
|
|
285
|
+
```
|
|
286
|
+
ALL must pass (worktree 격리 flake 예외는 단독 재실행 확인). 그 외 실패 → STOP/BLOCKED.
|
|
287
|
+
|
|
288
|
+
- [ ] **Step 3: 실제 재렌더 검증 (BLOCKING — 육안)**
|
|
289
|
+
|
|
290
|
+
기존 사용자 리포트를 /tmp 복사본으로 재렌더하고 구조 확인:
|
|
291
|
+
```bash
|
|
292
|
+
SRC="/Volumes/Workspaces/workspace/projects/FontsNinja/app/fontradar-v2-api/.okstra/tasks/calcule-des-prix-1-1/dev-9184/runs/requirements-discovery/reports/final-report-requirements-discovery-001.data.json"
|
|
293
|
+
```
|
|
294
|
+
NOTE: HTML view 는 `.md` 에서 파생되므로, **새 템플릿으로 `.md` 를 다시 렌더**해야 한다. data.json → md 렌더는 `scripts/okstra-render-final-report.py`(또는 report-writer 경로) 사용. 해당 CLI 의 인자를 `--help` 로 확인 후, /tmp 에 새 `.md` 를 렌더하고:
|
|
295
|
+
- 새 `.md` 의 §1/§1.1/§1.2/§3.1/§3.2/§4/§7/Exec 가 `**ID**<br>Ticket: …` meta 셀 + prose 컬럼인지,
|
|
296
|
+
- §5 가 여전히 `| ID | Ticket ID | Kind | Statement | Expected form | Blocks | Status | User input |` 8-컬럼인지,
|
|
297
|
+
- 그 `.md` 로 `okstra-render-report-views.py` 를 돌려 HTML 의 meta 셀이 `<br>` 줄바꿈으로 보이는지(literal `<br>` 아님),
|
|
298
|
+
- `python3 -c "import sys; sys.path.insert(0,'scripts'); from okstra_ctl.clarification_items import parse_clarification_items; print(len(parse_clarification_items(open('<new-md>').read()) or []))"` 로 §5 carry-in 파서가 여전히 행을 파싱하는지
|
|
299
|
+
확인. /tmp 산출물은 정리(사용자 프로젝트 파일은 건드리지 않음).
|
|
300
|
+
|
|
301
|
+
- [ ] **Step 4: 최종 커밋**
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
git add CHANGES.md
|
|
305
|
+
git commit -m "docs(changes): log compact markdown final-report tables"
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## Self-Review (작성자 체크리스트)
|
|
311
|
+
|
|
312
|
+
**1. Spec coverage**
|
|
313
|
+
- §2.1 대상 표 7종 + §7 → Task 1 Step 2–9.
|
|
314
|
+
- §2.1 §5 평면 유지 → Task 1 Step 10 + 미변경.
|
|
315
|
+
- §2.2 meta `<br>` 포맷 + i18n → Task 1 Step 1–9.
|
|
316
|
+
- §2.3 `_inline` `<br>` 보존 → Task 2 Step 3.
|
|
317
|
+
- §2.3 generic/Exec 분기 제거, §5 유지 → Task 2 Step 4(§7 도 제거).
|
|
318
|
+
- §2.4 계약 불변 → Task 1(템플릿만), §5 미변경.
|
|
319
|
+
- §4 검증(실제 재렌더 + §5 파서) → Task 3 Step 2–3.
|
|
320
|
+
|
|
321
|
+
**2. Placeholder scan:** §7(Step 9)은 현 row 필드명을 "먼저 읽어 매핑"하라고 명시 — 구현자가 실제 필드명 확인 후 작성(추측 금지). 그 외 모든 jinja/코드 블록은 실제 내용. TBD 없음.
|
|
322
|
+
|
|
323
|
+
**3. 식별자 일관성:** `columns.recordMeta`(i18n, Task1) ↔ 템플릿 헤더 사용 동일. `_inline`(Task2) ↔ report_views. `_grouped_table_spec` 시그니처에서 `rows` 제거 시 호출부도 함께(Task2 Step4 명시).
|