okstra 0.31.0 → 0.32.1

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 (52) hide show
  1. package/package.json +1 -1
  2. package/runtime/BUILD.json +2 -2
  3. package/runtime/agents/SKILL.md +3 -3
  4. package/runtime/agents/workers/report-writer-worker.md +45 -67
  5. package/runtime/bin/okstra-render-final-report.py +101 -0
  6. package/runtime/bin/okstra-render-report-views.py +17 -10
  7. package/runtime/bin/okstra-token-usage.py +3 -1
  8. package/runtime/python/okstra_ctl/final_report_schema.py +253 -0
  9. package/runtime/python/okstra_ctl/render_final_report.py +201 -0
  10. package/runtime/python/okstra_ctl/report_views.py +108 -305
  11. package/runtime/python/okstra_ctl/wizard.py +16 -5
  12. package/runtime/python/okstra_token_usage/__init__.py +5 -1
  13. package/runtime/python/okstra_token_usage/cli.py +66 -36
  14. package/runtime/python/okstra_token_usage/report.py +148 -65
  15. package/runtime/python/okstra_vendor/__init__.py +37 -0
  16. package/runtime/python/okstra_vendor/jinja2/__init__.py +38 -0
  17. package/runtime/python/okstra_vendor/jinja2/_identifier.py +6 -0
  18. package/runtime/python/okstra_vendor/jinja2/async_utils.py +99 -0
  19. package/runtime/python/okstra_vendor/jinja2/bccache.py +408 -0
  20. package/runtime/python/okstra_vendor/jinja2/compiler.py +1998 -0
  21. package/runtime/python/okstra_vendor/jinja2/constants.py +20 -0
  22. package/runtime/python/okstra_vendor/jinja2/debug.py +191 -0
  23. package/runtime/python/okstra_vendor/jinja2/defaults.py +48 -0
  24. package/runtime/python/okstra_vendor/jinja2/environment.py +1672 -0
  25. package/runtime/python/okstra_vendor/jinja2/exceptions.py +166 -0
  26. package/runtime/python/okstra_vendor/jinja2/ext.py +870 -0
  27. package/runtime/python/okstra_vendor/jinja2/filters.py +1873 -0
  28. package/runtime/python/okstra_vendor/jinja2/idtracking.py +318 -0
  29. package/runtime/python/okstra_vendor/jinja2/lexer.py +868 -0
  30. package/runtime/python/okstra_vendor/jinja2/loaders.py +693 -0
  31. package/runtime/python/okstra_vendor/jinja2/meta.py +112 -0
  32. package/runtime/python/okstra_vendor/jinja2/nativetypes.py +130 -0
  33. package/runtime/python/okstra_vendor/jinja2/nodes.py +1206 -0
  34. package/runtime/python/okstra_vendor/jinja2/optimizer.py +48 -0
  35. package/runtime/python/okstra_vendor/jinja2/parser.py +1049 -0
  36. package/runtime/python/okstra_vendor/jinja2/py.typed +0 -0
  37. package/runtime/python/okstra_vendor/jinja2/runtime.py +1062 -0
  38. package/runtime/python/okstra_vendor/jinja2/sandbox.py +436 -0
  39. package/runtime/python/okstra_vendor/jinja2/tests.py +256 -0
  40. package/runtime/python/okstra_vendor/jinja2/utils.py +766 -0
  41. package/runtime/python/okstra_vendor/jinja2/visitor.py +92 -0
  42. package/runtime/python/okstra_vendor/markupsafe/__init__.py +396 -0
  43. package/runtime/python/okstra_vendor/markupsafe/_native.py +8 -0
  44. package/runtime/python/okstra_vendor/markupsafe/py.typed +0 -0
  45. package/runtime/schemas/final-report-v1.0.schema.json +1391 -0
  46. package/runtime/skills/okstra-report-writer/SKILL.md +29 -28
  47. package/runtime/templates/reports/final-report.template.md +370 -411
  48. package/runtime/templates/reports/report.css +12 -6
  49. package/runtime/validators/lib/fixtures.sh +7 -7
  50. package/runtime/validators/validate-report-views.py +24 -153
  51. package/runtime/validators/validate-run.py +102 -19
  52. package/src/install.mjs +20 -1
@@ -1,162 +1,160 @@
1
+ {# OKSTRA Final Report — Jinja2 template (schemas/final-report-v1.0).
2
+ Consumed by scripts/okstra-render-final-report.py against the JSON
3
+ SSOT at runs/<task-type>/reports/final-report-<task-type>-<seq>.data.json.
4
+ Custom filters: format_int, format_usd, format_duration_ms,
5
+ yaml_scalar, yaml_inline_list (defined in render_final_report.py).
6
+ Token Usage cells render as `--` while tokenUsage.*.totalTokens is
7
+ null in Phase 6; Phase 7 mutates the data.json then re-renders. #}
1
8
  ---
2
- title: OKSTRA Final Report - {{TASK_KEY}}
3
- id: {{FM_ID}}
4
- tags: {{FM_TAGS}}
5
- status: in-progress
6
- aliases: {{FM_ALIASES}}
7
- date: {{TASK_DATE}}
8
- task-id: "{{TASK_ID}}"
9
- task-group: "{{TASK_GROUP}}"
10
- project-id: "{{PROJECT_ID}}"
11
- taskType: "{{FM_TASK_TYPE}}"
9
+ title: {{ frontmatter.title | yaml_scalar }}
10
+ id: {{ frontmatter.id | yaml_scalar }}
11
+ tags: {{ frontmatter.tags | yaml_inline_list }}
12
+ status: {{ frontmatter.status }}
13
+ aliases: {{ frontmatter.aliases | yaml_inline_list }}
14
+ date: {{ frontmatter.date }}
15
+ task-id: {{ frontmatter.taskId | yaml_scalar }}
16
+ task-group: {{ frontmatter.taskGroup | yaml_scalar }}
17
+ project-id: {{ frontmatter.projectId | yaml_scalar }}
18
+ taskType: {{ frontmatter.taskType | yaml_scalar }}
19
+ workerId: {{ frontmatter.workerId | yaml_scalar }}
12
20
  ---
13
21
 
14
- <!--
15
- Template authoring notes (NOT rendered to readers — HTML comments).
16
-
17
- - All `<!-- ... -->` blocks in this file are author-side guidance. Do NOT
18
- convert them into Markdown quote blocks (`> …`) in the produced report.
19
- - "Conditional block" guards: where a section heading is wrapped in a
20
- `<!-- RENDER_IF: <condition> -->` … `<!-- /RENDER_IF -->` pair, the
21
- report-writer MUST delete the entire heading + body when the condition is
22
- false. Leaving a section heading with placeholder body ("No prior X" /
23
- "Not applicable") is a contract violation that the validator will catch.
24
- - Section ordering rule: Verdict Card → Approval Block (planning only) →
25
- Carry-in (only if non-empty) → Summary/Ticket/Status → Token table →
26
- §1–§7. The reader's first 30 lines must answer "what was decided" and
27
- "what do I do next"; deep evidence lives further down.
28
- -->
29
-
30
- # {{TASK_KEY}} - Multi-Agent Cross Verification Final Report
31
-
32
- - Created at: {{RUN_TIMESTAMP_ISO}}
33
- - Task Key: {{TASK_KEY}}
34
- - Task Type: {{TASK_TYPE}}
35
- - Report Owner: `Claude lead`
36
- - Report Author: `<Report writer worker | Claude lead (release-handoff or recorded-fallback only)>`
37
- - Lead Model: `{{LEAD_MODEL}}`
38
- - Okstra Version: `{{OKSTRA_VERSION}}`
22
+ # {{ header.taskKey }} - Multi-Agent Cross Verification Final Report
23
+
24
+ - Created at: {{ header.createdAt }}
25
+ - Task Key: {{ header.taskKey }}
26
+ - Task Type: {{ header.taskType }}
27
+ - Report Owner: `{{ header.reportOwner }}`
28
+ - Report Author: `{{ header.reportAuthor }}`
29
+ - Lead Model: `{{ header.leadModel }}`
30
+ - Okstra Version: `{{ header.okstraVersion }}`
39
31
 
40
32
  ## Verdict Card
41
33
 
42
- 한눈에 보는 결과 카드. 본 표의 모든 값은 `## 2. Final Verdict` 및 `## 6. Recommended Next Steps` 의 권위 있는 값과 정확히 일치해야 합니다. 두 곳의 값이 다르면 본 카드를 깨진 인덱스로 간주합니다.
34
+ 한눈에 보는 결과 카드. 본 표의 모든 값은 `## 2. Final Verdict` 및 `## 6. Recommended Next Steps` 의 권위 있는 값과 정확히 일치해야 합니다.
43
35
 
44
36
  | 항목 | 값 |
45
37
  |------|----|
46
- | Final Conclusion | <한 결론 — `## 2.` 의 Final Conclusion 셀과 동일> |
47
- | Verdict Token | `<accepted / conditional-accept / blocked / not-applicable>` |
48
- | Direction | `<continue-investigation / begin-implementation / approve / reject / hold>` |
49
- | Approval Required? | `<yes — User Approval Request 블록 참조 / no>` |
50
- | Next Step | <한 `## 6.` 첫 항목과 동일> |
51
-
52
- <!-- RENDER_IF: task-type == implementation-planning -->
38
+ | Final Conclusion | {{ verdictCard.finalConclusion }} |
39
+ | Verdict Token | `{{ verdictCard.verdictToken }}` |
40
+ | Direction | `{{ verdictCard.direction }}` |
41
+ | Approval Required? | `{% if verdictCard.approvalRequired %}yes — User Approval Request 블록 참조{% else %}no{% endif %}` |
42
+ | Next Step | {{ verdictCard.nextStep }} |
53
43
 
44
+ {% if header.taskType == 'implementation-planning' %}
54
45
  ## User Approval Request (사용자 승인 게이트)
55
46
 
56
47
  다음 `implementation` run 은 아래 체크박스가 `[x]` 일 때에만 진입할 수 있습니다 (`okstra_ctl.run._validate_approved_plan` 가 line-anchored 정규식으로 검사). `## 5. Clarification Items` 표에서 `Blocks=approval` 인 모든 행이 `resolved` / `obsolete` 가 된 뒤 승인해 주세요.
57
48
 
58
- - 승인 체크박스 (사용자가 직접 편집): `- [ ] Approved`
49
+ - 승인 체크박스 (사용자가 직접 편집): `- [{% if approvalBlock.approved %}x{% else %} {% endif %}] Approved`
59
50
  - 승인 후 다음 단계 명령 (수동 편집 방식):
60
- - Claude Code: `/okstra-run task-key={{TASK_KEY}} task-type=implementation approved-plan=<이 보고서 경로>`
61
- - 별도 터미널: `scripts/okstra.sh --task-key {{TASK_KEY}} --task-type implementation --approved-plan <이 보고서 경로>`
51
+ - Claude Code: `/okstra-run task-key={{ header.taskKey }} task-type=implementation approved-plan=<이 보고서 경로>`
52
+ - 별도 터미널: `scripts/okstra.sh --task-key {{ header.taskKey }} --task-type implementation --approved-plan <이 보고서 경로>`
62
53
  - 승인 + 실행 한 번에 (진입 명령 자체를 승인 의사로):
63
- - Claude Code: `/okstra-run task-key={{TASK_KEY}} task-type=implementation approved-plan=<이 보고서 경로> approve`
64
- - 별도 터미널: `scripts/okstra.sh --task-key {{TASK_KEY}} --task-type implementation --approved-plan <이 보고서 경로> --approve`
54
+ - Claude Code: `/okstra-run task-key={{ header.taskKey }} task-type=implementation approved-plan=<이 보고서 경로> approve`
55
+ - 별도 터미널: `scripts/okstra.sh --task-key {{ header.taskKey }} --task-type implementation --approved-plan <이 보고서 경로> --approve`
65
56
  - 보류·거부: 체크박스를 `[ ]` 로 두고 `--approve` 를 사용하지 마세요. 필요 변경은 `## 5.` 표에 `Blocks=approval` 행으로 등록한 뒤 같은 phase 를 재실행합니다.
66
57
 
67
- <!-- /RENDER_IF -->
68
-
69
- <!-- RENDER_IF: CLARIFICATION_RESPONSE_RELATIVE_PATH is non-empty
70
- When empty, DELETE this entire `## 0.` heading and body. The validator
71
- fails an empty Section 0 stub ("No prior clarification was provided"). -->
72
-
58
+ {% endif %}
59
+ {% if clarificationCarryIn and clarificationCarryIn.sourceFile %}
73
60
  ## 0. Clarification Response Carried In From Previous Run
74
61
 
75
- - Source file: `{{CLARIFICATION_RESPONSE_RELATIVE_PATH}}`
62
+ - Source file: `{{ clarificationCarryIn.sourceFile }}`
76
63
  - 이전 보고서의 `## 5. Clarification Items` 표 매 행(`C-001`, `C-002`, …) 을 새 증거에 비추어 검토하고, 각 행의 `Status` 를 `resolved` 또는 `obsolete` 로 갱신한 뒤 본 run 의 `## 5.` 표에 carry-in 합니다. 해소 근거(파일:라인 / 로그 / 워커 결과) 를 함께 인용합니다.
77
64
 
78
- <!-- /RENDER_IF -->
79
-
65
+ {% endif %}
80
66
  ## Summary of the Problem or Verification Target
81
67
 
82
68
  3~5 개 row 로 핵심 문제·요구사항·검증 대상을 표로 정리합니다. brief, 소스 자료, worker 결과를 근거로 작성합니다.
83
69
 
84
70
  | ID | Ticket ID | 한 줄 요약 | 출처 (brief/source/worker) |
85
71
  |----|-----------|------------|----------------------------|
86
- | P-001 | `<TICKET-or-fallback>` | <핵심 항목 한 줄> | <출처> |
72
+ {% for row in summary -%}
73
+ | {{ row.id }} | `{{ row.ticketId }}` | {{ row.summary }} | {{ row.source }} |
74
+ {% endfor %}
87
75
 
76
+ {% if ticketCoverage.omit %}
77
+ {# Ticket Coverage omitted entirely — release-handoff / final-verification #}
78
+ {%- else %}
88
79
  ## Ticket Coverage
89
80
 
90
81
  본 run 이 다룬 ticket 의 역방향 인덱스. 본문 항목들은 모두 `Ticket ID` 컬럼 또는 `[TICKETID: <id>]` 태그로 ticket 과 묶여 있습니다.
91
82
 
92
- - 단일 ticket run 인 경우: 표 대신 다음 한 줄 — `- Single ticket run: <TICKET-or-task-fallback>`
93
- - 다중 ticket 이거나 `Issue / Ticket` 이 비어 `Task ID` 폴백이 섞여 있는 경우: 표를 채웁니다.
94
-
83
+ {% if ticketCoverage.singleTicket -%}
84
+ - Single ticket run: {{ ticketCoverage.singleTicket }}
85
+ {%- else %}
95
86
  | Ticket ID | 등장 섹션 | 관련 항목 IDs |
96
87
  |-----------|-----------|---------------|
97
- | `<TICKET-123>` | `1.1, 3.1, 4.5.4` | `F-001, F-003, A1` |
88
+ {% for row in ticketCoverage.rows -%}
89
+ | `{{ row.ticketId }}` | `{{ row.sections }}` | `{{ row.relatedIds }}` |
90
+ {% endfor %}
91
+ {%- endif %}
98
92
 
99
- 규칙: `Ticket ID` 는 본문에서 등장한 ticket 키와 정확히 동일 문자열. `Issue / Ticket` 이 비어 폴백된 경우 `Task ID` 값을 prefix 없이 그대로 (예: `8852`). 식별 불가는 `unknown`. `관련 항목 IDs` 는 `F-001`, `M-002`, `A1`, `Q1` 같은 row ID 를 콤마로 나열하고, row ID 가 없는 경우 섹션 번호 (예: `4.5.3`) 를 적습니다.
93
+ 규칙: `Ticket ID` 는 본문에서 등장한 ticket 키와 정확히 동일 문자열. `Issue / Ticket` 이 비어 폴백된 경우 `Task ID` 값을 prefix 없이 그대로 (예: `8852`). 식별 불가는 `unknown`.
100
94
 
95
+ {% endif %}
101
96
  ## Execution Status by Agent
102
97
 
103
98
  각 worker 의 status, 배정 모델, key finding 을 한 표에 모읍니다. worker 산출물을 근거 없는 주장으로 대체하지 않습니다.
104
99
 
105
- {{EXECUTION_STATUS_TABLE_ROWS}}
100
+ | Agent | Role | Model | Status | 처리 토큰 | 환산 토큰 | 비용 (USD) | Duration | Summary of Key Findings |
101
+ |-------|------|-------|--------|-----------|-----------|------------|----------|-------------------------|
102
+ {% for row in executionStatus -%}
103
+ | {{ 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 }} |
104
+ {% endfor %}
106
105
 
107
106
  ## Token Usage Summary
108
107
 
109
108
  | 항목 | 처리 토큰 | 환산 토큰 (input 기준) | 비용 (USD) |
110
109
  |------|-----------|------------------------|------------|
111
- | Lead | `{{LEAD_TOTAL_TOKENS}}` | `{{LEAD_BILLABLE_TOKENS}}` | `{{LEAD_COST_USD}}` |
112
- | Worker 합계 | `{{WORKER_TOTAL_TOKENS}}` | `{{WORKER_BILLABLE_TOKENS}}` | `{{WORKER_COST_USD}}` |
113
- | **전체 합계** | **`{{GRAND_TOTAL_TOKENS}}`** | **`{{GRAND_BILLABLE_TOKENS}}`** | **`{{GRAND_COST_USD}}`** |
114
- | Codex/Gemini CLI 추가 비용 | | | `{{CLI_COST_USD}}` |
115
-
116
- <!--
117
- 환산식 설명·"읽는 법"·캐시 가중치 등은 `docs/kr/architecture.md#token-accounting`
118
- 및 audit 사이드카에서 참조하세요. 본문 가독성 회복을 위해 인용구는 제거했습니다.
119
-
120
- placeholders 정책:
121
- - Phase 6 시점에는 10 개 `{{...}}` 토큰을 verbatim 으로 둡니다.
122
- - Phase 7 의 `okstra-token-usage.py --substitute-final-report` 가 치환합니다.
123
- - 임의 값 (`0` / `N/A` / `--` / `not-collected` / `pending`) 으로 대체 금지.
124
- Validator 가 치환 실패와 임의 값 모두 fail 합니다.
125
- -->
110
+ | Lead | `{{ tokenUsage.lead.totalTokens | format_int }}` | `{{ tokenUsage.lead.billableTokens | format_int }}` | `{{ tokenUsage.lead.costUsd | format_usd }}` |
111
+ | Worker 합계 | `{{ tokenUsage.worker.totalTokens | format_int }}` | `{{ tokenUsage.worker.billableTokens | format_int }}` | `{{ tokenUsage.worker.costUsd | format_usd }}` |
112
+ | **전체 합계** | **`{{ tokenUsage.grand.totalTokens | format_int }}`** | **`{{ tokenUsage.grand.billableTokens | format_int }}`** | **`{{ tokenUsage.grand.costUsd | format_usd }}`** |
113
+ | Codex/Gemini CLI 추가 비용 | | | `{{ tokenUsage.cli.costUsd | format_usd }}` |
126
114
 
127
- ## 1. Cross Verification Results
115
+ {# Phase 6 시점에는 numeric cells 가 null → 모두 `--` 로 렌더링.
116
+ Phase 7 의 okstra-token-usage.py 가 data.json 의 tokenUsage 를 채우고
117
+ 이 renderer 를 재호출하여 최종 markdown 을 다시 생성합니다. #}
128
118
 
129
- ### 1.0 Round History (convergence-enabled runs only)
119
+ ## 1. Cross Verification Results
130
120
 
131
- `state/convergence-<task-type>-<seq>.json` 값을 그대로 옮깁니다. convergence 가 비활성화된 run 에서는 이 sub-section 전체를 삭제합니다.
121
+ {% if crossVerification.roundHistory and not crossVerification.roundHistory.disabled -%}
122
+ ### 1.0 Round History
132
123
 
133
124
  | Round | inputQueueSize | resolvedCount | carriedForwardCount | dispatches (worker:status:durationMs) | skippedWorkers (worker:reason) |
134
125
  |-------|----------------|---------------|----------------------|----------------------------------------|---------------------------------|
135
- | 1 | 3 | 2 | 1 | codex-worker:completed:184221, gemini-worker:completed:201337 | claude-worker:no-items |
136
- | 2 | 1 | 1 | 0 | claude-worker:completed:92110 | -- |
126
+ {% for row in crossVerification.roundHistory.rounds -%}
127
+ | {{ row.round }} | {{ row.inputQueueSize }} | {{ row.resolvedCount }} | {{ row.carriedForwardCount }} | {{ row.dispatches }} | {{ row.skippedWorkers }} |
128
+ {% endfor %}
137
129
 
138
- - `round2SkippedReason`: `not-skipped` ← 값은 `queue-empty | max-rounds-1 | all-reverify-non-result | not-skipped` 중 하나.
139
- - 실행된 round 수가 0 (Round 0 에서 모든 finding 이 곧장 full-consensus) 이면 표 대신 한 줄 — `- Round 0 grouping 에서 모든 finding 이 합의되어 재검증 라운드가 실행되지 않았습니다.`
130
+ - `round2SkippedReason`: `{{ crossVerification.roundHistory.round2SkippedReason }}` ← 값은 `queue-empty | max-rounds-1 | all-reverify-non-result | not-skipped | convergence-disabled | single-analyser-only` 중 하나.
140
131
 
132
+ {% endif %}
141
133
  ### 1.1 Consensus
142
134
 
135
+ {% if crossVerification.consensus | length == 0 -%}
136
+ - 합의 항목 없음.
137
+ {%- else %}
143
138
  | ID | Ticket ID | Statement | Source items (worker:item) | Evidence (path:line / log / worker report) |
144
139
  |----|-----------|-----------|----------------------------|---------------------------------------------|
145
- | C-001 | `<TICKET-or-fallback>` | <합의된 결론> | claude:F-001, codex:1.1, gemini:F-3 | <증거 위치> |
146
-
147
- 우선 (bullet 으로 degrade 금지). 합의된 결론을 row 마다 기록합니다.
140
+ {% for row in crossVerification.consensus -%}
141
+ | {{ row.id }} | `{{ row.ticketId }}` | {{ row.statement }} | {{ row.sourceItems | join(', ') }} | {{ row.evidence }} |
142
+ {% endfor %}
143
+ {%- endif %}
148
144
 
149
- `Source items` 규칙: 본 합의 row 가 어느 워커의 어느 항목들에서 합성됐는지를 `<worker>:<item-id>` 페어 콤마-리스트로 적습니다. 워커 항목 ID 는 워커 자체가 부여한 leading-column ID 입니다 (claude 는 `F-NNN`, codex 는 `1.1` 같은 hierarchical, gemini 는 자유). 바로 이 컬럼이 합성된 `C-NNN` 을 원본 worker-results 파일로 역추적하는 단 하나의 다리입니다 — 단순 워커 이름만 (`claude, codex, gemini`) 적으면 추적성이 끊깁니다. 자세한 정책은 `prompts/profiles/_common-contract.md` "Cross-worker traceability" SSOT.
145
+ `Source items` 규칙: 본 합의 row 가 어느 워커의 어느 항목들에서 합성됐는지를 `<worker>:<item-id>` 페어 콤마-리스트로 적습니다. 자세한 정책은 `prompts/profiles/_common-contract.md` "Cross-worker traceability" SSOT.
150
146
 
151
147
  ### 1.2 Differences
152
148
 
149
+ {% if crossVerification.differences | length == 0 -%}
150
+ - 유의미한 차이 없음. 1.1 Consensus 가 그대로 유효합니다.
151
+ {%- else %}
153
152
  | ID | Ticket ID | Disagreement | Workers (position + item) | Evidence |
154
153
  |----|-----------|--------------|---------------------------|----------|
155
- | D-001 | `<TICKET-or-fallback>` | <차이 요약> | claude:F-005 (A) / codex:1.3 (B) / gemini:-- | <증거 위치> |
156
-
157
- 유의미한 차이 없음: 표 대신 — `- 유의미한 차이 없음. 1.1 Consensus 가 그대로 유효합니다.`
158
-
159
- `Workers (position + item)` 규칙: 1.1 의 `Source items` 와 동일한 `<worker>:<item-id>` 형식이되 워커마다 `(<position>)` 라벨 (`A` / `B` / `-` 등) 을 함께 적어 어느 워커가 어느 입장을 취했는지 표시합니다. 해당 plan item 에 의견을 내지 않은 워커는 `<worker>:--` 로 기록합니다.
154
+ {% for row in crossVerification.differences -%}
155
+ | {{ 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 }} |
156
+ {% endfor %}
157
+ {%- endif %}
160
158
 
161
159
  ## 2. Final Verdict
162
160
 
@@ -164,469 +162,430 @@ placeholders 정책:
164
162
 
165
163
  | 항목 | 값 |
166
164
  |------|----|
167
- | Final Conclusion | <한 결론> |
168
- | Verdict Token | `<accepted / conditional-accept / blocked / not-applicable>` |
169
- | Direction | `<continue-investigation / begin-implementation / approve / reject / hold>` |
170
- | 근거 요약 | <`1.1`, `3.1` 보고서 ID 를 콤마로> |
171
- | 다음 단계 | <Section 6 또는 7 중 어디로 이어지는지> |
165
+ | Final Conclusion | {{ finalVerdict.finalConclusion }} |
166
+ | Verdict Token | `{{ finalVerdict.verdictToken }}` |
167
+ | Direction | `{{ finalVerdict.direction }}` |
168
+ | 근거 요약 | {{ finalVerdict.rationaleRowIds | join(', ') }} |
169
+ | 다음 단계 | {{ finalVerdict.nextStep }} |
172
170
 
173
171
  ## 3. Evidence and Detailed Analysis
174
172
 
175
173
  ### 3.1 Primary Evidence
176
174
 
175
+ {% if evidence.primary | length == 0 -%}
176
+ - 주 증거 없음.
177
+ {%- else %}
177
178
  | ID | Ticket ID | Evidence | Source items (worker:item) | Source (path:line / log) |
178
179
  |----|-----------|----------|----------------------------|---------------------------|
179
- | E-001 | `<TICKET-or-fallback>` | <증거 한 줄 요약> | claude:F-002, codex:1.4 | `<path>:<line>` 또는 로그 인용 |
180
+ {% for row in evidence.primary -%}
181
+ | {{ row.id }} | `{{ row.ticketId }}` | {{ row.evidence }} | {{ row.sourceItems | join(', ') }} | {{ row.source }} |
182
+ {% endfor %}
183
+ {%- endif %}
180
184
 
181
- `Source items` 컬럼 규칙은 §1.1 과 동일 — `<worker>:<item-id>` 페어 콤마-리스트로 어느 워커 항목들에서 본 증거가 도출됐는지 기록합니다. 워커 결과 인용 없이 lead 가 독립적으로 (예: MCP 직접 조회로) 얻은 증거는 `lead:mcp-<순번>` 형식으로 적습니다 (예: `lead:mcp-1`). `Source (path:line / log)` 컬럼은 코드/로그 위치 자체를 기록합니다.
185
+ `Source items` 컬럼 규칙은 §1.1 과 동일.
182
186
 
183
187
  ### 3.2 Secondary Evidence or Alternate Interpretations
184
188
 
189
+ {% if not evidence.secondary or evidence.secondary | length == 0 -%}
190
+ - 보조 증거 또는 대안 해석 없음.
191
+ {%- else %}
185
192
  | ID | Ticket ID | Hypothesis or supporting evidence | Source / confidence |
186
193
  |----|-----------|-----------------------------------|---------------------|
187
- | S-001 | `<TICKET-or-fallback>` | <보조 증거 또는 대안 해석> | <출처 + low/mid> |
188
-
189
- 해당 없음 시: `- 보조 증거 또는 대안 해석 없음.`
194
+ {% for row in evidence.secondary -%}
195
+ | {{ row.id }} | `{{ row.ticketId }}` | {{ row.hypothesis }} | {{ row.confidence }} |
196
+ {% endfor %}
197
+ {%- endif %}
190
198
 
191
199
  ## 4. Missing Information and Risks
192
200
 
201
+ {% if missingInformation | length == 0 -%}
202
+ - 누락된 정보·위험 없음.
203
+ {%- else %}
193
204
  | ID | Ticket ID | Item | Risk if ignored | Mitigation Owner |
194
205
  |----|-----------|------|-----------------|------------------|
195
- | R-001 | `<TICKET-or-fallback>` | <누락 정보 또는 불확실 항목> | <무시할 때의 위험> | <역할 / 팀> |
196
-
197
- `I don't know` / `uncertain` 항목은 모두 표 안에 두고 본문 산문으로 흩지 않습니다.
198
-
199
- <!-- RENDER_IF: task-type == implementation-planning
200
- Delete the entire `## 4.5` block + sub-sections otherwise.
201
-
202
- Validator (validators/validate-run.py PLANNING_REQUIRED_SECTIONS) does
203
- substring matching for these 9 English keywords in order:
204
- Option Candidates, Trade-off, Recommended Option,
205
- Stepwise Execution Order, Dependency, Validation Checklist, Rollback,
206
- User Approval Request, Plan Body Verification
207
- The first 7 + 9th live below; "User Approval Request" is satisfied by
208
- the top-of-report Approval block heading. Do NOT recreate a §4.5.8 stub
209
- in body — validator now fails reports that contain one. -->
206
+ {% for row in missingInformation -%}
207
+ | {{ row.id }} | `{{ row.ticketId }}` | {{ row.item }} | {{ row.risk }} | {{ row.owner }} |
208
+ {% endfor %}
209
+ {%- endif %}
210
210
 
211
+ {% if header.taskType == 'implementation-planning' %}
211
212
  ## 4.5 Implementation Plan Deliverables
212
213
 
213
214
  ### 4.5.1 Option Candidates (옵션 후보)
214
215
 
215
- 최소 개의 구현 옵션. 각 옵션마다:
216
+ {% for opt in implementationPlanning.optionCandidates %}
217
+ **{{ opt.name }}**
216
218
 
217
- - **File Structure** — 파일 단위 책임을 한 줄씩 (`Ticket ID` 채움):
219
+ - File Structure:
218
220
 
219
221
  | ID | Ticket ID | Action | Path (and line-range) | Change summary |
220
222
  |----|-----------|--------|------------------------|----------------|
221
- | OF-001 | `<TICKET-or-fallback>` | Create / Modify / Delete | `<path>[:<line-range>]` | <한 줄 요약> |
223
+ {% for fs in opt.fileStructure -%}
224
+ | {{ fs.id }} | `{{ fs.ticketId }}` | {{ fs.action }} | `{{ fs.path }}` | {{ fs.summary }} |
225
+ {% endfor %}
226
+
227
+ - 영향 인터페이스 / 공개 계약 / 다운스트림 소비자: {{ opt.interfaces }}
228
+ - 폭발 반경 추정: {{ opt.blastRadius }}
222
229
 
223
- - 영향을 받는 인터페이스 / 공개 계약 / 다운스트림 소비자.
224
- - 폭발 반경 추정 (units, configs, deployment manifests, data migrations).
230
+ {% endfor %}
225
231
 
226
232
  ### 4.5.2 Trade-off Matrix (트레이드오프 매트릭스)
227
233
 
228
234
  | Option | Complexity | Risk | Reversibility | Test Coverage Cost | Rollout Cost |
229
235
  |--------|-----------|------|---------------|--------------------|--------------|
230
- | <Option A> | <…> | <…> | <…> | <…> | <…> |
236
+ {% for row in implementationPlanning.tradeoffMatrix -%}
237
+ | {{ row.option }} | {{ row.complexity }} | {{ row.risk }} | {{ row.reversibility }} | {{ row.testCoverageCost }} | {{ row.rolloutCost }} |
238
+ {% endfor %}
231
239
 
232
240
  ### 4.5.3 Recommended Option (권장 옵션)
233
241
 
234
242
  | 항목 | 값 |
235
243
  |------|----|
236
- | Recommended Option | <옵션 이름> |
237
- | 핵심 이유 | <한 줄> |
238
- | 근거 (Trade-off 행 / 원칙) | <4.5.2 + 적용 디자인 원칙: isolation / files-that-change-together / follow-established-patterns / YAGNI> |
239
- | 채택되지 않은 옵션 요약 | <짧게: 어떤 비용 때문에 탈락> |
244
+ | Recommended Option | {{ implementationPlanning.recommendedOption.name }} |
245
+ | 핵심 이유 | {{ implementationPlanning.recommendedOption.coreReason }} |
246
+ | 근거 (Trade-off 행 / 원칙) | {{ implementationPlanning.recommendedOption.rationale }} |
247
+ | 채택되지 않은 옵션 요약 | {{ implementationPlanning.recommendedOption.rejectedSummary }} |
240
248
 
241
249
  ### 4.5.4 Stepwise Execution Order (단계별 실행 순서)
242
250
 
243
251
  | Step | Ticket ID | Action (≤ 5min) | Files | Command / Test | Expected outcome |
244
252
  |------|-----------|------------------|-------|----------------|-------------------|
245
- | 1 | `<TICKET-or-fallback>` | <예: 실패하는 테스트 작성> | `tests/foo_test.py` | `pytest tests/foo_test.py::test_x -v` | FAIL with <reason> |
253
+ {% for row in implementationPlanning.stepwiseExecution -%}
254
+ | {{ row.step }} | `{{ row.ticketId }}` | {{ row.action }} | `{{ row.files }}` | `{{ row.commandOrTest }}` | {{ row.expectedOutcome }} |
255
+ {% endfor %}
246
256
 
247
- 규칙: 한 step 은 약 2~5 분. 모든 step 은 정확한 파일 경로와 명령어 포함. TDD 가능 영역은 failing test → implementation → green → commit 순서.
257
+ 규칙: 한 step 은 약 2~5 분. 모든 step 은 정확한 파일 경로와 명령어 포함.
248
258
 
249
259
  ### 4.5.5 Dependency / Migration Risk (의존성·마이그레이션 위험)
250
260
 
251
- 순서 제약, 데이터 백필, feature-flag 선행 조건, repo-internal sequencing. 외부 승인·권한·vendor 조율은 위험/일정 항목으로 등록하지 않습니다. 해당 없음: `- 의존성·마이그레이션 위험 없음.`
252
-
253
- | ID | Kind (order / backfill / flag-precondition / repo-sequencing / other) | Item | 영향 | 완화 / 선행 작업 |
254
- |----|------------------------------------------------------------------------|------|------|------------------|
255
- | DM-001 | <kind> | <한 줄 요약> | <영향 범위> | <대응 방안> |
261
+ {% if implementationPlanning.dependencyMigrationRisk | length == 0 -%}
262
+ - 의존성·마이그레이션 위험 없음.
263
+ {%- else %}
264
+ | ID | Kind | Item | 영향 | 완화 / 선행 작업 |
265
+ |----|------|------|------|------------------|
266
+ {% for row in implementationPlanning.dependencyMigrationRisk -%}
267
+ | {{ row.id }} | {{ row.kind }} | {{ row.item }} | {{ row.impact }} | {{ row.mitigation }} |
268
+ {% endfor %}
269
+ {%- endif %}
256
270
 
257
271
  ### 4.5.6 Validation Checklist (검증 체크리스트)
258
272
 
259
273
  | Phase | Ticket ID | Check | Command / Observation | Expected outcome |
260
274
  |-------|-----------|-------|------------------------|-------------------|
261
- | pre | `<TICKET-or-fallback>` | <체크 이름> | `<정확한 명령어 또는 관찰 지점>` | <기대 출력 / 상태> |
262
- | mid | `<TICKET-or-fallback>` | <체크 이름> | `<...>` | <...> |
263
- | post | `<TICKET-or-fallback>` | <체크 이름> | `<...>` | <...> |
264
-
265
- 추상적 서술 금지. 모든 row 는 명령어 또는 관찰 가능한 결과를 포함.
275
+ {% for row in implementationPlanning.validationChecklist -%}
276
+ | {{ row.phase }} | `{{ row.ticketId }}` | {{ row.check }} | `{{ row.commandOrObservation }}` | {{ row.expectedOutcome }} |
277
+ {% endfor %}
266
278
 
267
279
  ### 4.5.7 Rollback Strategy (롤백 전략)
268
280
 
269
- | ID | Step | Action (revert command / flag toggle / migration down) | Trigger signal (error rate / latency / user report 등) | 확인 방법 |
270
- |----|------|---------------------------------------------------------|--------------------------------------------------------|-----------|
271
- | RB-001 | 1 | `<예: git revert <SHA>>` | <예: 에러율 > 1% 5분 지속> | `<관찰 명령 / 대시보드>` |
281
+ | ID | Step | Action | Trigger signal | 확인 방법 |
282
+ |----|------|--------|----------------|-----------|
283
+ {% for row in implementationPlanning.rollbackStrategy -%}
284
+ | {{ row.id }} | {{ row.step }} | `{{ row.action }}` | {{ row.triggerSignal }} | `{{ row.verificationMethod }}` |
285
+ {% endfor %}
272
286
 
273
287
  ### 4.5.9 Plan Body Verification (계획 본문 검증)
274
288
 
275
- Phase 6 에서 report-writer 가 합성한 4.5 본문을 lead 가 plan-item 단위로 워커들에게 다시 던지고 `AGREE / DISAGREE / SUPPLEMENT` 평결을 수집한 결과. 라운드 프로토콜은 `skills/okstra-convergence/SKILL.md` "Plan-body verification mode" 참조.
289
+ Phase 6 에서 report-writer 가 합성한 4.5 본문을 lead 가 plan-item 단위로 워커들에게 다시 던지고 평결을 수집한 결과.
276
290
 
277
- - **Round count**: `<N>` (default: 1)
278
- - **Gate result**: `<passed | passed-with-dissent | blocked-by-disagreement | aborted-non-result>`
279
- - `passed` → 상단 `User Approval Request` 체크박스 라인 렌더.
280
- - `passed-with-dissent` → 체크박스 렌더 + 반대 의견은 아래 `Dissent log` 에 기록.
281
- - `blocked-by-disagreement` → 체크박스 라인 **렌더하지 않음**. majority DISAGREE 인 plan item 마다 `## 5.` 에 `Blocks=approval` row 추가.
282
- - `aborted-non-result` → 워커 dispatch 가 모두 non-result. 체크박스 라인 렌더하지 않음 + `## 5.` 에 "plan-body verification could not run" row.
291
+ - **Round count**: `{{ implementationPlanning.planBodyVerification.roundCount }}`
292
+ - **Gate result**: `{{ implementationPlanning.planBodyVerification.gateResult }}`
283
293
 
284
294
  #### Verdict summary
285
295
 
286
- 각 plan item 1 행. `Plan item` 열은 `P-Opt-<N>` / `P-Step-<N>` / `P-Dep-<N>` / `P-Val-<N>` / `P-Rb-<N>` prefix. `Section` 열은 4.5 내부 sub-section 번호. 워커별 평결은 아래 `Verdict details` 표로 분리합니다 (워커 수에 비례해 가로 폭이 폭발하던 단일 wide 표를 두 개로 쪼개 시인성을 회복).
287
-
288
296
  | Plan item | Ticket ID | Section | Classification |
289
297
  |-----------|-----------|---------|----------------|
290
- | P-Opt-1 | `<id>` | 4.5.1 | full-consensus |
291
- | P-Step-3 | `<id>` | 4.5.4 | majority-disagree C-7 |
292
-
293
- - `Classification` ∈ `{full-consensus, partial-consensus, worker-unique, majority-disagree → C-<N>}`.
294
- - `majority-disagree → C-<N>` 의 `C-<N>` 은 `## 5. Clarification Items` 의 row ID 와 1:1 일치해야 합니다.
298
+ {% for row in implementationPlanning.planBodyVerification.verdictSummary -%}
299
+ | {{ row.planItem }} | `{{ row.ticketId }}` | {{ row.section }} | {{ row.classification }} |
300
+ {% endfor %}
295
301
 
296
302
  #### Verdict details
297
303
 
298
- 워커별 평결을 plan item × worker 쌍으로 1 행씩 기록합니다. 워커 수가 늘어나도 가로 폭이 늘지 않고 행 수만 늘어납니다. `Verdict` `{AGREE, DISAGREE, SUPPLEMENT, verification-error}`. `Breakage kind` `DISAGREE` 일 때만 채우며 spec 의 `(a|b|c|d|e)` 중 하나 (a=참조 file path / symbol 불일치, b=… 자세한 정의는 `skills/okstra-convergence/SKILL.md`).
299
-
300
- | Plan item | Worker | Verdict | Breakage kind | Note (선택) |
301
- |-----------|--------|---------|---------------|-------------|
302
- | P-Opt-1 | claude-worker | AGREE | -- | |
303
- | P-Opt-1 | codex-worker | AGREE | -- | |
304
- | P-Step-3 | claude-worker | DISAGREE | a | 참조한 `crates/.../mod.rs:120` 가 없음 |
305
- | P-Step-3 | codex-worker | DISAGREE | a | 동일 — 파일 path 미존재 |
306
- | P-Step-3 | gemini-worker | SUPPLEMENT | -- | 대체 step 제안 (Dissent log 참조) |
304
+ | Plan item | Worker | Verdict | Breakage kind | Note |
305
+ |-----------|--------|---------|---------------|------|
306
+ {% for row in implementationPlanning.planBodyVerification.verdictDetails -%}
307
+ | {{ row.planItem }} | {{ row.worker }} | {{ row.verdict }} | {{ row.breakageKind or '--' }} | {{ row.note or '' }} |
308
+ {% endfor %}
307
309
 
308
310
  #### Dissent log
309
311
 
310
- `partial-consensus` `worker-unique` 반대 의견을 plan item 별로 묶어 적습니다. `majority-disagree` 항목의 반대 의견은 본 섹션 대신 `## 5.` 의 해당 row `Statement` 컬럼에 옮겨 적습니다.
311
-
312
- - **P-XXX-N**: `<worker-role>` — `<반대 의견 본문 2-3 sentences>`
313
-
314
- <!-- /RENDER_IF (task-type == implementation-planning) -->
315
-
316
- <!-- RENDER_IF: task-type == release-handoff
317
- Delete the entire `## 4.6` block + sub-sections otherwise. -->
312
+ {% if implementationPlanning.planBodyVerification.dissentLog | length == 0 -%}
313
+ - 반대 의견 없음.
314
+ {%- else %}
315
+ {% for row in implementationPlanning.planBodyVerification.dissentLog -%}
316
+ - **{{ row.planItem }}**: `{{ row.workerRole }}` — {{ row.body }}
317
+ {% endfor %}
318
+ {%- endif %}
318
319
 
320
+ {% endif %}
321
+ {% if header.taskType == 'release-handoff' %}
319
322
  ## 4.6 Release Handoff Deliverables
320
323
 
321
- git/gh mutating 명령이 실행된 phase 의 감사 기록. 모든 mutating command 는 반드시 아래 `User Selections` 표의 사용자 응답과 1:1 대응되어야 하며, 대응되지 않는 mutating command 가 있으면 self-review 가 `contract-violated` 로 종료해야 합니다.
324
+ git/gh mutating 명령이 실행된 phase 의 감사 기록.
322
325
 
323
- ### 4.6.1 Source Verification Report (선행 final-verification 인용)
326
+ ### 4.6.1 Source Verification Report
324
327
 
325
- - Path (project-relative): `<runs/final-verification/.../reports/final-report-final-verification-<seq>.md>`
326
- - Quoted `Verdict Token` row from that report's `## 2.` table (값이 정확히 `accepted` 여야 함):
327
- > <원문 인용>
328
- - 원본 `Verdict Token` 값이 `accepted` 가 아니면 본 run 은 실행되지 않아야 했습니다. self-review 에서 contract-violated 처리하고 routing 을 `final-verification` 으로 되돌립니다.
328
+ - Path (project-relative): `{{ releaseHandoff.sourceVerificationReport.path }}`
329
+ - Quoted `Verdict Token` row from that report's `## 2.` table:
330
+ > {{ releaseHandoff.sourceVerificationReport.verdictTokenQuote }}
329
331
 
330
332
  ### 4.6.2 Feature Branch & Working-Tree State (run 시작 시점)
331
333
 
332
- - Feature branch (`git rev-parse --abbrev-ref HEAD`): `<branch-name>`
334
+ - Feature branch: `{{ releaseHandoff.featureBranchState.branchName }}`
333
335
  - `git status --short` 출력:
334
336
  ```
335
- <원문 그대로>
337
+ {{ releaseHandoff.featureBranchState.gitStatusShort }}
336
338
  ```
337
- - 기존 PR 존재 여부 (`gh pr list --head <branch> --state open --json url --jq '.[0].url'`): `<URL or empty>`
339
+ - 기존 PR 존재 여부: `{{ releaseHandoff.featureBranchState.existingPrUrl or '(none)' }}`
338
340
 
339
341
  ### 4.6.3 User Selections (메뉴 응답 기록)
340
342
 
341
343
  | 질문 ID | 질문 본문 | 사용자 응답 (원문) | 응답이 가능한 보기 |
342
344
  |---------|-----------|--------------------|--------------------|
343
- | H1 | 어떤 작업을 실행할까요? | <`local only` / `push + PR` / `skip`> | `local only` / `push + PR` / `skip` |
344
- | H2 | PR base 브랜치를 골라주세요. (H1=`push + PR` 인 경우에만 묻습니다) | <`staging` / `preprod` / `prod` / `main` / `dev` / 사용자가 입력한 브랜치명> | 위 + 직접 입력 |
345
- | H3 | lead 가 작성한 PR title / PR body 초안을 어떻게 처리할까요? | <`use as-is` / `edit then proceed` / `cancel`> | `use as-is` / `edit then proceed` / `cancel` |
346
-
347
- H1 = `skip` 이거나 H3 = `cancel` 인 경우 4.6.4 ~ 4.6.6 은 빈 결과로 채우고 (mutating 명령 미실행) 4.6.7 routing 만 채웁니다.
345
+ | H1 | 어떤 작업을 실행할까요? | `{{ releaseHandoff.userSelections.h1 }}` | `local only` / `push + PR` / `skip` |
346
+ | H2 | PR base 브랜치 (H1=`push + PR` 인 경우) | `{{ releaseHandoff.userSelections.h2 or '(n/a)' }}` | staging / preprod / prod / main / dev / 사용자 입력 |
347
+ | H3 | PR title/body 초안 처리 | `{{ releaseHandoff.userSelections.h3 }}` | `use as-is` / `edit then proceed` / `cancel` |
348
348
 
349
- ### 4.6.4 Executed Commands (실행한 git / gh 명령 로그)
350
-
351
- - Mutating commands: 정확한 명령행 / exit code / 한 줄 stdout 요약을 한 행씩 모두 기록 (누락은 contract-violated).
352
- - Read-only inspection commands: 요약 또는 명령행 목록만으로 충분.
349
+ ### 4.6.4 Executed Commands
353
350
 
351
+ {% if releaseHandoff.executedCommands | length == 0 -%}
352
+ - (mutating 명령 미실행 — H1=`skip` 또는 H3=`cancel`)
353
+ {%- else %}
354
354
  | # | command (verbatim) | exit code | stdout/stderr 요약 |
355
355
  |---|---------------------|-----------|--------------------|
356
- | 1 | `<예: git add path/to/file.py>` | `0` | `<요약>` |
357
-
358
- ### 4.6.5 Commit List (생성된 commit)
359
-
360
- `implementation` phase 에서 이미 생성된 commit 범위 (`git log <base>..HEAD`) 를 기록. release-handoff 는 새 commit 을 만들지 않습니다. 각 commit 의 short SHA / full SHA / subject / 영향 파일 목록.
361
-
362
- commit 범위가 비어 있으면 release-handoff 는 실행되면 안 됩니다 → `- No implementation commits found; release-handoff is blocked.` + routing 을 `implementation` 으로 되돌림.
363
-
364
- ### 4.6.6 Merge Conflict Probe (사전 머지 충돌 점검)
365
-
366
- `push + PR` 경로에서만 실행. 다음 정확히 하나의 형식으로 한 줄을 기록합니다 (read-only — `git fetch origin <base>` + `git merge-tree --merge-base origin/<base> HEAD origin/<base>` 만 허용; mutating git 명령 금지):
367
-
368
- - `- Not run (user picked local only or skip).`
369
- - `- Clean no conflicts against <base> at <origin/base SHA>.`
370
- - `- Conflicts detected against <base> at <origin/base SHA>; user chose <proceed anyway | change base branch | cancel>. Conflicting paths: <list>.`
371
-
372
- `push + PR` 인데 본 항목이 없거나 위 셋 외의 자유 서술이면 self-review 가 `contract-violated` 로 종료합니다 (`release-handoff` profile self-review 6번 — merge-conflict probe audit).
373
-
374
- ### 4.6.7 Pull Request Outcome (PR 결과)
375
-
376
- 다음 가지 정확히 하나의 형식으로 줄:
377
-
378
- - `- No PR action requested.` (H1 = `local only` 또는 `skip`)
379
- - `- PR created: <url>` + 타이틀 + base 브랜치
380
- - `- PR reused: <url>` (run 시작 같은 head open PR 이미 존재해 `gh pr create` 생략)
381
- - `- PR creation skipped: <reason>` (H3 = `cancel`, 또는 push/PR 도중 사용자 중단)
382
-
383
- ### 4.6.8 Routing Recommendation (마지막 phase 라우팅)
384
-
385
- `release-handoff` lifecycle 종착 phase 이므로 일반적으로 `done` 으로 라우팅합니다. H1 = `skip` 또는 H3 = `cancel` 로 종료된 경우 재진입 가능 여부를 한 줄로 명시합니다.
386
-
387
- 형식 예시: `- Routing: done. PR <url> opened against <base>; no follow-up required.`
388
-
389
- <!-- /RENDER_IF (task-type == release-handoff) -->
390
-
391
- <!-- RENDER_IF: task-type == implementation
392
- Delete the entire `## 4.7` block + sub-sections otherwise. -->
393
-
356
+ {% for row in releaseHandoff.executedCommands -%}
357
+ | {{ row.number }} | `{{ row.command }}` | `{{ row.exitCode }}` | {{ row.outputSummary }} |
358
+ {% endfor %}
359
+ {%- endif %}
360
+
361
+ ### 4.6.5 Commit List
362
+
363
+ {% if releaseHandoff.commitList.empty -%}
364
+ - No implementation commits found; release-handoff is blocked.
365
+ {%- else %}
366
+ | Short SHA | Full SHA | Subject | Files |
367
+ |-----------|----------|---------|-------|
368
+ {% for row in releaseHandoff.commitList -%}
369
+ | `{{ row.shortSha }}` | `{{ row.fullSha }}` | {{ row.subject }} | {{ row.files }} |
370
+ {% endfor %}
371
+ {%- endif %}
372
+
373
+ ### 4.6.6 Merge Conflict Probe
374
+
375
+ {% if releaseHandoff.mergeConflictProbe.kind == 'not-run' -%}
376
+ - Not run (user picked local only or skip).
377
+ {%- elif releaseHandoff.mergeConflictProbe.kind == 'clean' -%}
378
+ - Clean no conflicts against {{ releaseHandoff.mergeConflictProbe.baseBranch }} at {{ releaseHandoff.mergeConflictProbe.baseSha }}.
379
+ {%- else -%}
380
+ - Conflicts detected against {{ releaseHandoff.mergeConflictProbe.baseBranch }} at {{ releaseHandoff.mergeConflictProbe.baseSha }}; user chose {{ releaseHandoff.mergeConflictProbe.userChoice }}. Conflicting paths: {{ releaseHandoff.mergeConflictProbe.conflictingPaths | join(', ') }}.
381
+ {%- endif %}
382
+
383
+ ### 4.6.7 Pull Request Outcome
384
+
385
+ {% if releaseHandoff.pullRequestOutcome.kind == 'no-action' -%}
386
+ - No PR action requested.
387
+ {%- elif releaseHandoff.pullRequestOutcome.kind == 'created' -%}
388
+ - PR created: {{ releaseHandoff.pullRequestOutcome.url }} — title: {{ releaseHandoff.pullRequestOutcome.title }} (base: {{ releaseHandoff.pullRequestOutcome.baseBranch }})
389
+ {%- elif releaseHandoff.pullRequestOutcome.kind == 'reused' -%}
390
+ - PR reused: {{ releaseHandoff.pullRequestOutcome.url }}
391
+ {%- else -%}
392
+ - PR creation skipped: {{ releaseHandoff.pullRequestOutcome.reason }}
393
+ {%- endif %}
394
+
395
+ ### 4.6.8 Routing Recommendation
396
+
397
+ {{ releaseHandoff.routingRecommendation }}
398
+
399
+ {% endif %}
400
+ {% if header.taskType == 'implementation' %}
394
401
  ## 4.7 Implementation Deliverables
395
402
 
396
- `implementation` profile 의 "Required deliverable shape" 를 보고서 본문 구조로 옮겨 적습니다. 모든 sub-section 은 필수이며, 비어 있는 경우에도 헤딩은 유지하고 본문에 명시적 빈 상태 한 줄을 적습니다. validator 는 8 개 substring (`Approved Plan Reference`, `Commit List`, `Diff Summary`, `Out-of-plan Edits`, `Validation Evidence`, `Verifier Results`, `Rollback Verification`, `Routing Recommendation`) 의 등장 여부를 검사합니다.
403
+ ### 4.7.1 Approved Plan Reference
397
404
 
398
- ### 4.7.1 Approved Plan Reference (승인된 계획 참조)
405
+ - Plan file (project-relative): `{{ implementation.approvedPlanReference.planFile }}`
406
+ - Approval evidence:
407
+ > {{ implementation.approvedPlanReference.approvalEvidence }}
408
+ - 본 run 의 `EXECUTOR_WORKTREE_PATH`: `{{ implementation.approvedPlanReference.executorWorktreePath }}`
409
+ - 본 run 의 base ref: `{{ implementation.approvedPlanReference.baseRefSha }}`
399
410
 
400
- - Plan file (project-relative): `<runs/implementation-planning/.../reports/final-report-implementation-planning-<seq>.md>`
401
- - Approval evidence (해당 plan 의 정확한 인용 — `- [x] Approved` 마커 + Section 4.5.3 Recommended Option 한 줄):
402
- > <원문 인용>
403
- - 본 run 의 `EXECUTOR_WORKTREE_PATH`: `<absolute path>`
404
- - 본 run 의 base ref (`{{EXECUTOR_WORKTREE_BASE_REF}}`): `<commit SHA>`
405
-
406
- ### 4.7.2 Commit List (생성된 commit)
411
+ ### 4.7.2 Commit List
407
412
 
413
+ {% if implementation.commitList | length == 0 -%}
414
+ - No implementation commits produced; routing recommendation must be back to implementation-planning.
415
+ {%- else %}
408
416
  | # | Short SHA | Full SHA | Plan Step | Subject | Files |
409
417
  |---|-----------|----------|-----------|---------|-------|
410
- | 1 | `<abc1234>` | `<full-sha>` | Step 1 | `<exact commit subject>` | `<file paths>` |
411
-
412
- 규칙: commit = 한 plan step (또는 cohesive sub-step). `Subject` 는 git log 에 적힌 원문 그대로 — 재해석·요약 금지. commit 이 없으면 본 run 은 실행되지 않아야 했음 → `- No implementation commits produced; routing recommendation must be back to implementation-planning.` 한 줄.
418
+ {% for row in implementation.commitList -%}
419
+ | {{ row.number }} | `{{ row.shortSha }}` | `{{ row.fullSha }}` | {{ row.planStep }} | {{ row.subject }} | {{ row.files }} |
420
+ {% endfor %}
421
+ {%- endif %}
413
422
 
414
- ### 4.7.3 Diff Summary (변경 요약)
423
+ ### 4.7.3 Diff Summary
415
424
 
416
425
  ```
417
- <git diff --stat <base>..HEAD 의 raw 출력>
426
+ {{ implementation.diffSummary.rawStat }}
418
427
  ```
419
428
 
420
- 다음으로 file-by-file 한 줄 요약 표:
421
-
422
429
  | File | Action | Lines (+/-) | Plan step / Out-of-plan |
423
430
  |------|--------|-------------|--------------------------|
424
- | `<path>` | created / modified / deleted | `+12 / -3` | Step 2 또는 `Out-of-plan` |
425
-
426
- ### 4.7.4 Out-of-plan Edits (계획 외 편집)
427
-
428
- 승인된 plan 의 file list 에 없는 파일을 편집한 경우 row 로 기록. 없으면 `- 계획 외 편집 없음.` 한 줄.
429
-
430
- | ID | File | Rationale | Trigger (어떤 step 수행 중 발견) |
431
- |----|------|-----------|--------------------------------|
432
- | OOP-001 | `<path>` | <한 두 문장> | Step `<N>` |
433
-
434
- ### 4.7.5 Validation Evidence (검증 증거)
435
-
436
- plan `4.5.6 Validation Checklist` pre / mid / post 각 row 대해 실제 명령과 출력 / exit code 를 모두 기록합니다. 요약·"tests pass" 같은 단어 금지 — 명령 line 과 exit code 는 원문 그대로.
437
-
438
- | Phase | Command | Exit code | Output tail (≤10 lines) | TDD evidence (failing→passing SHAs) |
439
- |-------|---------|-----------|--------------------------|--------------------------------------|
440
- | pre | `<cmd>` | `0` | <인용> | -- |
441
- | mid | `<cmd>` | `0` | <인용> | `<failing SHA>` → `<passing SHA>` |
442
- | post | `<cmd>` | `0` | <인용> | -- |
443
-
444
- ### 4.7.6 Verifier Results (verifier 별 결과)
445
-
446
- verifier role 마다 한 sub-block 으로 정리합니다 (`Claude verifier`, `Codex verifier`, opt-in 시 `Gemini verifier`). 각 verifier 의 read-only 명령 로그, 독립 재실행 결과, lint/format/typecheck 결과, 그리고 Discrepancy 라인을 모두 보존합니다. lead 는 합의 verdict 를 합성하되 의견을 collapse 하지 않습니다.
447
-
448
- - **Claude verifier** — Verdict: `<PASS | CONCERNS | FAIL>`
449
- - Read-only command log: <verifier 의 worker result 에서 원문 인용>
450
- - Independent validation re-run: <plan validation command 별 exit code + tail>
451
- - Style / lint / type-check: <도구·exit code·새 위반 수>
452
- - Declined fix recommendations: <한 줄씩 — 없으면 `- 없음.`>
453
- - Discrepancy (vs executor): `- 없음.` 또는 `- <plan step / command>: executor=<result>, verifier=<result>`
454
- - **Codex verifier** Verdict: ...
455
- - **Gemini verifier** (opt-in 시) Verdict: ...
456
-
457
- 합의 verdict (lead 합성): `<PASS | CONCERNS | FAIL>` — 한 verifier 라도 `FAIL` 이면 합의는 `FAIL` (override 시 lead 가 구체적 재현 시점 이유 인용 필수).
458
-
459
- ### 4.7.7 Rollback Verification (롤백 검증)
460
-
461
- 변경 카테고리별로 다른 강도의 확인. 표 1 행 = 1 카테고리.
431
+ {% for row in implementation.diffSummary.files -%}
432
+ | `{{ row.file }}` | {{ row.action }} | `{{ row.lines }}` | {{ row.planStep }} |
433
+ {% endfor %}
434
+
435
+ ### 4.7.4 Out-of-plan Edits
436
+
437
+ {% if implementation.outOfPlanEdits | length == 0 -%}
438
+ - 계획 외 편집 없음.
439
+ {%- else %}
440
+ | ID | File | Rationale | Trigger |
441
+ |----|------|-----------|---------|
442
+ {% for row in implementation.outOfPlanEdits -%}
443
+ | {{ row.id }} | `{{ row.file }}` | {{ row.rationale }} | {{ row.trigger }} |
444
+ {% endfor %}
445
+ {%- endif %}
446
+
447
+ ### 4.7.5 Validation Evidence
448
+
449
+ | Phase | Command | Exit code | Output tail | TDD evidence |
450
+ |-------|---------|-----------|-------------|--------------|
451
+ {% for row in implementation.validationEvidence -%}
452
+ | {{ row.phase }} | `{{ row.command }}` | `{{ row.exitCode }}` | {{ row.outputTail }} | {{ row.tddEvidence or '--' }} |
453
+ {% endfor %}
454
+
455
+ ### 4.7.6 Verifier Results
456
+
457
+ {% for block in implementation.verifierResults %}
458
+ - **{{ block.verifier }}** Verdict: `{{ block.verdict }}`
459
+ - Read-only command log: {{ block.readOnlyCommandLog }}
460
+ - Independent validation re-run: {{ block.independentValidationRerun }}
461
+ - Style / lint / type-check: {{ block.styleLintTypecheck or '--' }}
462
+ - Declined fix recommendations: {{ block.declinedFixRecommendations or '- 없음.' }}
463
+ - Discrepancy: {{ block.discrepancy or '- 없음.' }}
464
+ {% endfor %}
465
+
466
+ ### 4.7.7 Rollback Verification
462
467
 
463
468
  | Category | Rollback command | Verification | Result |
464
469
  |----------|-------------------|---------------|--------|
465
- | Pure code | `git revert <SHA>` | `git rev-parse <SHA>` 가 resolve 됨 | `ok` |
466
- | Feature-flag-gated | flag off 상태 validation run | 4.7.5 해당 명령 인용 | `ok` |
467
- | Schema/config/stateful | rollback step `<cmd>` dry-run | exit code + stdout 인용 | `ok` 또는 `unable — route back to planning` |
468
-
469
- dry-run 모드가 없는 stateful 변경은 본 항목을 `unable` 로 적고 `## 6.` 의 첫 항목을 `implementation-planning` 재진입으로 권장해야 합니다.
470
-
471
- ### 4.7.8 Routing Recommendation (다음 phase 라우팅)
472
-
473
- 다음 셋 중 하나의 형식으로 한 줄:
474
-
475
- - `- Routing: final-verification. All plan steps satisfied; rollback verified; verifier consensus <PASS|CONCERNS>.`
476
- - `- Routing: error-analysis. <한 줄 — 어떤 결함이 추가 분석을 요구하는지>.`
477
- - `- Routing: implementation-planning. <한 줄 — 왜 새 plan 이 필요한지 (drift / scope-mismatch / stateful-rollback-gap)>.`
470
+ {% for row in implementation.rollbackVerification -%}
471
+ | {{ row.category }} | `{{ row.rollbackCommand }}` | {{ row.verification }} | `{{ row.result }}` |
472
+ {% endfor %}
478
473
 
479
- <!-- /RENDER_IF (task-type == implementation) -->
474
+ ### 4.7.8 Routing Recommendation
480
475
 
481
- <!-- RENDER_IF: task-type == final-verification
482
- Delete the entire `## 4.8` block + sub-sections otherwise. -->
476
+ {{ implementation.routingRecommendation }}
483
477
 
478
+ {% endif %}
479
+ {% if header.taskType == 'final-verification' %}
484
480
  ## 4.8 Final Verification Deliverables
485
481
 
486
- `final-verification` profile 의 "Required deliverable shape" 를 본문 구조로 옮겨 적습니다. 모든 sub-section 필수. validator 는 6 개 substring (`Source Implementation Report`, `Acceptance Blockers`, `Residual Risk`, `Validation Evidence`, `Read-only Command Log`, `Routing Recommendation`) 등장과 `## 2. Final Verdict` 의 `Verdict Token` 값이 `accepted` / `conditional-accept` / `blocked` 중 하나인지 검사합니다.
482
+ ### 4.8.1 Source Implementation Report
487
483
 
488
- ### 4.8.1 Source Implementation Report (선행 implementation 인용)
489
-
490
- - Path (project-relative): `<runs/implementation/.../reports/final-report-implementation-<seq>.md>`
484
+ - Path (project-relative): `{{ finalVerification.sourceImplementationReport.path }}`
491
485
  - 인용된 commit list / diff summary 요약:
492
- > <원문 인용 — `## 4.7.2` / `## 4.7.3` 에서>
493
- - 검증 대상 worktree path: `<absolute path>`
494
- - run 시작 시 capture 한 head/base SHA: `<base SHA> .. <head SHA>`
486
+ > {{ finalVerification.sourceImplementationReport.commitListQuote }}
487
+ - 검증 대상 worktree path: `{{ finalVerification.sourceImplementationReport.worktreePath }}`
488
+ - run 시작 시 capture 한 head/base SHA: `{{ finalVerification.sourceImplementationReport.baseHeadSha }}`
495
489
  - `git status --short` (run 시작 시점):
496
490
  ```
497
- <원문 그대로>
491
+ {{ finalVerification.sourceImplementationReport.gitStatusShort }}
498
492
  ```
499
493
 
500
- ### 4.8.2 Acceptance Blockers (수락 차단 항목)
501
-
502
- 비어 있으면 표 대신 `- No acceptance blockers found.` 한 줄. 모든 blocker 는 구체적 artifact (file:line, log, exit code, MCP SELECT 결과) 인용 필수 — 증거 없는 blocker 는 4.8.3 residual risk 로 강등.
503
-
504
- | ID | Severity | Statement | Evidence (path:line / log / exit code) | Recommended follow-up phase |
505
- |----|----------|-----------|-----------------------------------------|------------------------------|
506
- | AB-001 | `critical / major / minor` | <한 줄 결함 요약> | `<file>:<line>` 또는 로그 인용 | `error-analysis` / `implementation-planning` |
494
+ ### 4.8.2 Acceptance Blockers
507
495
 
508
- ### 4.8.3 Residual Risk (잔존 위험)
496
+ {% if finalVerification.acceptanceBlockers | length == 0 -%}
497
+ - No acceptance blockers found.
498
+ {%- else %}
499
+ | ID | Severity | Statement | Evidence | Recommended follow-up phase |
500
+ |----|----------|-----------|----------|------------------------------|
501
+ {% for row in finalVerification.acceptanceBlockers -%}
502
+ | {{ row.id }} | `{{ row.severity }}` | {{ row.statement }} | {{ row.evidence }} | `{{ row.followUpPhase }}` |
503
+ {% endfor %}
504
+ {%- endif %}
509
505
 
510
- blocker 아니지만 추적해야 할 위험. 각 항목에 mitigation owner 와 blocker 로 격상되는 trigger 를 명시.
506
+ ### 4.8.3 Residual Risk
511
507
 
508
+ {% if finalVerification.residualRisk | length == 0 -%}
509
+ - 추적 대상 잔존 위험 없음.
510
+ {%- else %}
512
511
  | ID | Item | Mitigation owner | Escalation trigger |
513
512
  |----|------|------------------|---------------------|
514
- | RR-001 | <한 줄> | <역할 / 팀> | <어떤 조건이면 AB-? 로 격상> |
515
-
516
- 비어 있으면: `- 추적 대상 잔존 위험 없음.`
513
+ {% for row in finalVerification.residualRisk -%}
514
+ | {{ row.id }} | {{ row.item }} | {{ row.owner }} | {{ row.escalationTrigger }} |
515
+ {% endfor %}
516
+ {%- endif %}
517
517
 
518
518
  ### 4.8.4 Validation Evidence (요구사항 커버리지)
519
519
 
520
- 선행 plan / task brief 모든 요구사항에 대해 cover 한 artifact 를 cite. 패러프레이즈 ("verified") 금지.
521
-
522
- | ID | Requirement (plan/brief 인용) | Artifact (commit SHA / test output / log line / MCP SELECT) | Status (covered / blocker AB-? / gap) |
523
- |----|--------------------------------|--------------------------------------------------------------|----------------------------------------|
524
- | VE-001 | <한 줄> | `<artifact>` | covered |
520
+ | ID | Requirement (plan/brief 인용) | Artifact | Status |
521
+ |----|--------------------------------|----------|--------|
522
+ {% for row in finalVerification.validationEvidence -%}
523
+ | {{ row.id }} | {{ row.requirement }} | `{{ row.artifact }}` | {{ row.status }} |
524
+ {% endfor %}
525
525
 
526
- ### 4.8.5 Read-only Command Log (실행 명령 로그)
526
+ ### 4.8.5 Read-only Command Log
527
527
 
528
- run 에서 실행한 모든 명령 (Tier 1 plan validation + Tier 2 `project.json.qaCommands`) 실행 순서대로 row 기록. mutating 명령은 등장하면 안 됨.
528
+ | # | Tier | Command (verbatim) | Exit code | Output tail |
529
+ |---|------|---------------------|-----------|-------------|
530
+ {% for row in finalVerification.readonlyCommandLog -%}
531
+ | {{ row.number }} | {{ row.tier }} | `{{ row.command }}` | `{{ row.exitCode }}` | {{ row.outputTail }} |
532
+ {% endfor %}
529
533
 
530
- | # | Tier | Command (verbatim) | Exit code | Output tail (≤5 lines) |
531
- |---|------|---------------------|-----------|-------------------------|
532
- | 1 | 1 | `<plan validation cmd>` | `0` | <인용> |
533
- | 2 | 2 | `cargo clippy --all-targets -- -D warnings` | `0` | <인용> |
534
+ ### 4.8.6 Routing Recommendation
534
535
 
535
- `project.json.qaCommands` 의 category 가 비어 있으면 `qa-command not configured: <lint/format/typecheck/test>` 한 줄. deny-list (`--fix`, `--write`, ` -w`, ` -u`, `--snapshot-update`, `INSTA_UPDATE=<not-no>`, `cargo update`, `npm install` without `ci` 등) 토큰을 포함한 cmd 는 `qa-command rejected (denied token: <token>): <label>` 한 줄로 기록 후 실행하지 않음.
536
-
537
- ### 4.8.6 Routing Recommendation (다음 phase 라우팅)
538
-
539
- `## 2. Final Verdict` 의 `Verdict Token` 과 1:1 대응. 다음 셋 중 하나:
540
-
541
- - `- Routing: release-handoff. Verdict Token = accepted; PR-ready.`
542
- - `- Routing: release-handoff with conditions. Verdict Token = conditional-accept; conditions listed in 4.8.2 / 4.8.3 must be resolved before push.`
543
- - `- Routing: error-analysis (or implementation-planning). Verdict Token = blocked; <blocker AB-?> requires <re-analysis | replan>.`
544
-
545
- <!-- /RENDER_IF (task-type == final-verification) -->
536
+ {{ finalVerification.routingRecommendation }}
546
537
 
538
+ {% endif %}
547
539
  ## 5. Clarification Items
548
540
 
549
- 다음 run 으로 넘어가기 전에 사용자가 답하거나 자료를 첨부해야 하는 항목을 **한 표 안에서** 추적합니다. `task-type` 이 `error-analysis` / `requirements-discovery` 이고 지금까지의 증거만으로 확신 있는 최종 판단이 어려울 때는 반드시 채웁니다. 그 외 task-type 에서는 lead 가 필요하다고 판단할 때만 채우고, 그렇지 않다면 `- 추가 정보 요청 없음. Section 2 의 최종 판단이 그대로 유효합니다.` 한 줄만 남깁니다.
550
-
551
- 작성 원칙:
552
-
553
- - 개발자가 아닌 사람도 한 번 읽고 무엇을 어디서 가져와야 하는지 이해할 수 있게, 줄임말과 내부 용어 대신 풀어 쓴 문장.
554
- - 한 행은 "왜 필요한지", "무엇을 묻거나 받아야 하는지", "어떤 형태의 답을 기대하는지" 가 모두 드러나야 함.
555
- - 항목별 ID (`C-001`, `C-002`, …) 는 run 간 유지. 답변된 항목은 다음 run 에서도 삭제하지 말고 `Status` 만 `resolved` / `obsolete` 로 갱신.
541
+ 다음 run 으로 넘어가기 전에 사용자가 답하거나 자료를 첨부해야 하는 항목을 **한 표 안에서** 추적합니다.
556
542
 
543
+ {% if clarificationItems | length == 0 -%}
544
+ - 추가 정보 요청 없음. Section 2 의 최종 판단이 그대로 유효합니다.
545
+ {%- else %}
557
546
  답을 채우신 뒤 같은 phase 를 다시 실행:
558
547
 
559
- - Claude Code: `/okstra-run resume-clarification task-key={{TASK_KEY}}`
560
- - 별도 터미널: `scripts/okstra.sh --resume-clarification --task-key {{TASK_KEY}}`
548
+ - Claude Code: `/okstra-run resume-clarification task-key={{ header.taskKey }}`
549
+ - 별도 터미널: `scripts/okstra.sh --resume-clarification --task-key {{ header.taskKey }}`
561
550
 
562
- | ID | Ticket ID | Kind | Statement (무엇이 필요한지 + 왜) | Expected form (답·자료의 모양) | Blocks | Status | User input |
563
- |----|-----------|------|----------------------------------|-------------------------------|--------|--------|-----------|
564
- | C-001 | `<TICKET-or-fallback>` | `decision` | <예: "지난 주 새벽 결제 실패가 일회성이었는지 재발했는지 알려주세요. 다음 run 의 task-type 이 `error-analysis` 로 갈지 `requirements-discovery` 로 갈지가 갈립니다."> | <예: "(a) 일회성, (b) 재발 — 최근 시각 YYYY-MM-DD HH:MM, 영향 사용자 수 약 N명"> | `next-phase` | open | <빈칸으로 두고 다음 run 전에 사용자가 채움> |
565
- | C-002 | `<TICKET-or-fallback>` | `material` | <예: "오류 시각 전후 30분 worker 로그가 필요합니다. Datadog `service:worker env:prod` 필터, 시각 2026-04-30 09:30~10:30 KST. `.project-docs/okstra/tasks/8852/logs/worker-2026-04-30.log` 로 저장해 주세요."> | <예: "위 경로의 .log 파일 (gzip 가능)"> | `next-phase` | open | |
566
- | C-003 | `<TICKET-or-fallback>` | `data-point` | <예: "본 배치 시작 직전 `common.FontManualClassifiers` 의 `prediction=0` / `prediction=1` 행 수가 필요합니다. acceptance #5 의 동일성 검증 ground truth."> | <예: "두 정수 (prediction=0 count, prediction=1 count) 한 줄로"> | `approval` | open | |
551
+ | ID | Ticket ID | Kind | Statement | Expected form | Blocks | Status | User input |
552
+ |----|-----------|------|-----------|---------------|--------|--------|-----------|
553
+ {% for row in clarificationItems -%}
554
+ | {{ row.id }} | `{{ row.ticketId }}` | `{{ row.kind }}` | {{ row.statement }} | {{ row.expectedForm }} | `{{ row.blocks }}` | {{ row.status }} | {{ row.userInput or '' }} |
555
+ {% endfor %}
567
556
 
568
557
  컬럼 가이드 (전체 정의는 `prompts/profiles/_common-contract.md §Clarification request policy` SSOT 참조):
569
558
 
570
559
  - **`Kind`** ∈ `{material, decision, data-point}`.
571
- - **`Blocks`** ∈ `{approval, next-phase, none}` — `approval` 은 implementation-planning 의 승인 게이트 차단.
560
+ - **`Blocks`** ∈ `{approval, next-phase, none}`.
572
561
  - **`Status`** ∈ `{open, answered, resolved, obsolete}`.
573
-
574
- 안티패턴 (validator 가 fail 처리):
575
-
576
- - 같은 항목을 별도 sub-section (`5.1 추가 자료 요청` / `5.2 사용자 확인 질문` / `4.5.9 Open Questions`) 으로 분리하기 — 본 8-열 표 한 곳으로만 표현합니다.
577
- - 한 행을 두 개로 쪼개기 — 자료 + yes/no 가 묶인 항목은 `Kind=material`, `Expected form` 에 "파일 경로 + yes/no" 라고 적은 한 행.
578
- - 사용자가 답하지 않아도 진행 가능한 항목을 `Blocks=approval` 로 표시 — `Blocks=none` / `next-phase` 로 정확히 표시.
562
+ {%- endif %}
579
563
 
580
564
  ## 6. Recommended Next Steps
581
565
 
582
- This section is **always present** never omit the heading. If there are no concrete actions, write the single line `- No further action required. Final verdict in section 2 stands.` and stop.
583
-
584
- When concrete actions exist, list them as a numbered list. Each item includes the exact copy-pasteable command(s). Show **both** the in-session form (`/okstra-run …`) and the external-terminal form (`scripts/okstra.sh …`) — the Node `okstra` admin CLI does NOT accept `--task-key` / `--task-type` / `--resume-clarification`.
585
-
586
- 1. **Highest-priority next action.** State what + why in one sentence, then the command.
587
- - Same phase rerun:
588
- - Claude Code: `/okstra-run task-key={{TASK_KEY}} task-type={{TASK_TYPE}}`
589
- - 별도 터미널: `scripts/okstra.sh --task-key {{TASK_KEY}} --task-type {{TASK_TYPE}}`
590
- - Next phase (omit `task-type` to use the manifest's `workflow.nextRecommendedPhase` when concrete):
591
- - Claude Code: `/okstra-run task-key={{TASK_KEY}} task-type=<next-phase>`
592
- - 별도 터미널: `scripts/okstra.sh --task-key {{TASK_KEY}} --task-type <next-phase>`
593
- 2. **Additional read-only verification** before moving to the next phase (test commands, log queries, dashboard URLs). No state-mutating commands here.
594
- 3. **Follow-up tasks** if needed. Reference by `task-key` when they exist; otherwise describe the new brief to draft.
595
- 4. **If `## 5.` has any row with `Status` in `{open, answered}`**, the highest-priority next step MUST be the clarification turn-around:
596
- - Preferred: `/okstra-run resume-clarification task-key={{TASK_KEY}}` / `scripts/okstra.sh --resume-clarification --task-key {{TASK_KEY}}`.
597
-
598
- Empty-state placeholder (copy verbatim when nothing else applies): `- No further action required. Final verdict in section 2 stands.`
566
+ {% if recommendedNextSteps | length == 0 -%}
567
+ - No further action required. Final verdict in section 2 stands.
568
+ {%- else %}
569
+ {% for step in recommendedNextSteps -%}
570
+ {{ loop.index }}. {{ step.text }}
571
+ {% if step.commands -%}
572
+ {% for cmd in step.commands %}
573
+ - {{ cmd.label }}:
574
+ - Claude Code: `{{ cmd.claudeCode }}`
575
+ - 별도 터미널: `{{ cmd.terminal }}`
576
+ {% endfor -%}
577
+ {%- endif %}
578
+ {% endfor %}
579
+ {%- endif %}
599
580
 
600
581
  ## 7. Follow-up Tasks (후속 작업)
601
582
 
602
- run 끝난 사용자가 이어갈 모든 task 를 한 표에 정리. 두 종류가 동거합니다:
603
-
604
- 1. **phase-continuation row (필수)** — 본 task 의 다음 phase. `Suggested task-type` 은 `## 2. Final Verdict` 의 다음 phase 와 byte-identical. `Auto-spawn? = no` 로 고정 (다음 phase 는 사용자가 `/okstra-run` 으로 직접 진입하며, spawn 스크립트가 task 디렉토리를 자동 생성하지 않음).
605
- 2. **scope-boundary row (선택)** — 본 run 의 구현·검증 범위를 **넘어서는** 작업. lead 가 필요하다고 판단할 때만.
606
-
607
- phase-continuation row 의무 적용 범위:
608
-
609
- - `task-type` ∈ {`requirements-discovery`, `implementation-planning`, `error-analysis`, `implementation`, `final-verification`}: **필수**. 다음 phase 가 자명하므로 phase-continuation row 한 개를 반드시 채움.
610
- - `release-handoff`: 다음 phase 없음 → phase-continuation 생략. scope-boundary row 만 (있다면) 채움.
611
-
612
- 후속 항목 출처(`Origin` 컬럼): `phase-continuation` / `out-of-plan` / `verifier-concern` / `scope-boundary` / `open-question` / `manual` 중 하나.
613
-
614
- 규칙: `Auto-spawn? = yes` 인 row 는 Phase 7 의 `scripts/okstra-spawn-followups.py` 가 자동으로 `tasks/<TASK_GROUP>/<New Task ID>/` 디렉터리 + `task-manifest.json` (status: `todo`) + stub task-brief 를 생성. `no` 는 사람이 처리. `phase-continuation` row 는 항상 `no` — 같은 task-key 를 재사용하여 다음 phase 로 진입하므로 새 task 디렉터리를 만들면 안 됨. `New Task ID` 는 task-group 내 유일한 알파숫자·하이픈 slug(phase-continuation row 의 경우 현재 task-id 를 그대로 사용). 동일 follow-up 이 여러 run 에 등장하면 `New Task ID` 를 동일하게 유지하여 중복 spawn 방지.
615
-
583
+ {% if followUpTasks | length == 0 -%}
584
+ - 후속 작업 없음. 본 run 의 다음 phase 는 §6 (Recommended Next Steps) 참고.
585
+ {%- else %}
616
586
  | ID | Ticket ID | Origin | New Task ID | Title | Suggested task-type | Scope (files/areas) | Reason / Why deferred | Priority (P0/P1/P2) | Auto-spawn? |
617
587
  |----|-----------|--------|-------------|-------|---------------------|---------------------|------------------------|---------------------|-------------|
618
- | FU-001 | `<TICKET-or-fallback>` | `phase-continuation` | `<current-task-id>` | <다음 phase 진입 요약> | `<next-task-type>` | `<scope summary>` | <다음 phase 가 자동으로 추천되는 사유 한 줄> | `P0` | `no` |
619
- | FU-002 | `<TICKET-or-fallback>` | `<out-of-plan / verifier-concern / scope-boundary / open-question / manual>` | `<new-task-id-slug>` | <한 제목> | `<task-type>` | `<files / areas>` | <한 문장 사유> | `P1` | `yes` |
620
-
621
- 상태(phase-continuation 의무가 없는 task-type 이고 scope-boundary 도 없을 때): `- 후속 작업 없음. 본 run 의 다음 phase 는 §6 (Recommended Next Steps) 참고.`
622
-
623
- 본 섹션이 채워진 경우 Section 6 의 "Follow-up tasks" 항목에 진입 명령(phase-continuation 은 `/okstra-run` 형태, auto-spawn 된 row 는 새 task-key)을 함께 적어 사용자가 즉시 이어갈 수 있게 합니다.
624
-
625
- ## Writing Rules
626
-
627
- - Markdown. 본문은 한국어. 기술 식별자 (파일 경로, 코드 심볼, 모델명, status 값) 는 원형 유지.
628
- - 섹션 구조는 brief 가 명시적으로 다른 구조를 요구하지 않는 한 보존.
629
- - 증거 기반 (speculation 금지). 메타 코멘트 (저장소·세션·이전 메시지 언급) 로 본문 대체 금지.
630
- - 본 문서는 `Claude lead` 의 최종 합성이지 raw worker dump 가 아닙니다.
631
- - 표 우선. 같은 모양의 항목을 여러 개 나열할 때 bullet 으로 degrade 금지.
632
- - Reading Confirmation / 워커 raw output / 환산식 설명 등 audit 자료는 본문이 아닌 사이드카에 둡니다 (`runs/<task-type>/worker-results/<worker>-audit-<task-type>-<seq>.md`).
588
+ {% for row in followUpTasks -%}
589
+ | {{ row.id }} | `{{ row.ticketId }}` | `{{ row.origin }}` | `{{ row.newTaskId }}` | {{ row.title }} | `{{ row.suggestedTaskType }}` | `{{ row.scope }}` | {{ row.reason }} | `{{ row.priority }}` | `{{ row.autoSpawn }}` |
590
+ {% endfor %}
591
+ {%- endif %}