okstra 0.49.0 → 0.50.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/kr/architecture.md +8 -8
- package/docs/kr/cli.md +2 -2
- package/docs/project-structure-overview.md +3 -3
- package/docs/superpowers/plans/2026-06-05-wizard-batch-prompts.md +559 -0
- package/docs/superpowers/specs/2026-06-05-wizard-batch-prompts-design.md +121 -0
- package/docs/task-process/error-analysis.md +1 -1
- package/docs/task-process/final-verification.md +1 -1
- package/docs/task-process/release-handoff.md +1 -1
- package/docs/task-process/requirements-discovery.md +1 -1
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/SKILL.md +3 -3
- package/runtime/agents/workers/claude-worker.md +1 -1
- package/runtime/agents/workers/codex-worker.md +1 -1
- package/runtime/agents/workers/gemini-worker.md +1 -1
- package/runtime/agents/workers/report-writer-worker.md +3 -3
- package/runtime/bin/okstra-render-report-views.py +1 -1
- package/runtime/prompts/launch.template.md +1 -1
- package/runtime/prompts/profiles/_common-contract.md +11 -11
- package/runtime/prompts/profiles/_implementation-deliverable.md +1 -1
- package/runtime/prompts/profiles/_implementation-executor.md +1 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +1 -1
- package/runtime/prompts/profiles/error-analysis.md +1 -1
- package/runtime/prompts/profiles/final-verification.md +2 -2
- package/runtime/prompts/profiles/implementation-planning.md +9 -9
- package/runtime/prompts/profiles/improvement-discovery.md +5 -5
- package/runtime/prompts/profiles/release-handoff.md +2 -2
- package/runtime/prompts/profiles/requirements-discovery.md +2 -2
- package/runtime/python/okstra_ctl/clarification_items.py +11 -11
- package/runtime/python/okstra_ctl/render.py +1 -1
- package/runtime/python/okstra_ctl/render_final_report.py +1 -1
- package/runtime/python/okstra_ctl/report_views.py +12 -12
- package/runtime/python/okstra_ctl/run.py +3 -3
- package/runtime/python/okstra_ctl/wizard.py +90 -3
- package/runtime/python/okstra_ctl/workflow.py +1 -1
- package/runtime/skills/okstra-brief/SKILL.md +1 -1
- package/runtime/skills/okstra-convergence/SKILL.md +8 -8
- package/runtime/skills/okstra-report-writer/SKILL.md +22 -22
- package/runtime/skills/okstra-run/SKILL.md +2 -0
- package/runtime/skills/okstra-team-contract/SKILL.md +1 -1
- package/runtime/templates/reports/final-report.template.md +187 -187
- package/runtime/templates/reports/i18n/en.json +4 -4
- package/runtime/templates/reports/i18n/ko.json +4 -4
- package/runtime/templates/reports/implementation-planning-input.template.md +1 -1
- package/runtime/templates/reports/release-handoff-input.template.md +1 -1
- package/runtime/templates/reports/user-response.template.md +1 -1
- package/runtime/templates/worker-prompt-preamble.md +1 -1
- package/runtime/validators/lib/fixtures.sh +2 -2
- package/runtime/validators/validate-implementation-plan-stages.py +9 -9
- package/runtime/validators/validate-report-views.py +10 -10
- package/runtime/validators/validate-run.py +36 -36
- package/runtime/validators/validate_improvement_report.py +8 -8
|
@@ -39,14 +39,14 @@
|
|
|
39
39
|
- When the brief's `Desired Outcome`, classification, or routing target depends on a chain of decisions, walk that chain one branch at a time. Each branch is one `Clarification Items` row, not a free-form interview.
|
|
40
40
|
- For every clarification row, put the single best answer and one-line rationale in `Expected form` as `Recommended: ...`. Put other options and one-sentence consequences in the same cell as `Alternatives: ...`.
|
|
41
41
|
- **Codebase-first rule**: if a branch can be resolved by `Read` / `Grep` / file inspection, resolve it that way and record `Evidence checked: <path:line>` in the `Statement` cell. Do NOT escalate to the user.
|
|
42
|
-
- Budget: the unified `##
|
|
42
|
+
- Budget: the unified `## 1. Clarification Items` table caps at the smaller of (a) one row per unresolved decision branch, (b) 8 rows total. Beyond the cap, fold remaining ambiguity into the routing recommendation's risk notes.
|
|
43
43
|
- Expected output emphasis:
|
|
44
44
|
- evidence-backed routing decision
|
|
45
45
|
- uncertainty boundaries and missing inputs
|
|
46
46
|
- next recommended phase and safe resume guidance
|
|
47
47
|
- canonical-term resolution for every `terminology:*` brief item, written as a one-line `<term> = <definition>` line in a new `Domain Alignment` subsection of the final report; alongside each, propose whether `<PROJECT_ROOT>/.okstra/glossary.md` should be updated (proposal only — actual writes happen via `okstra-brief` Step 4.5 on a subsequent run)
|
|
48
48
|
- Clarification request policy (phase-specific addenda — shared policy is in `_common-contract.md`):
|
|
49
|
-
- if any blocking input is missing at the time of writing the final report, populate `##
|
|
49
|
+
- if any blocking input is missing at the time of writing the final report, populate `## 1. Clarification Items` in `final-report-template.md` (a single unified table; `Blocks=next-phase` for items the next run cannot start without)
|
|
50
50
|
- prefer concrete questions whose answers map directly to a routing decision (`bugfix` vs `feature`, `error-analysis` vs `implementation-planning`, etc.). State each option in plain language with one sentence describing what choosing it would mean for the next phase.
|
|
51
51
|
- every clarification row carries a recommended answer + one-line rationale inside the `Expected form` cell; rows that lack a recommendation are rejected as half-formed.
|
|
52
52
|
- **Codebase-first ambiguity resolution (defect rule)**: any ambiguity that can be answered by `Read` / `Grep` / file inspection MUST be resolved that way and recorded with file:line evidence. Writing a clarification row for something the codebase already answers is a defect of this phase.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
"""Parse the ``##
|
|
1
|
+
"""Parse the ``## 1. Clarification Items`` table from a final-report markdown.
|
|
2
2
|
|
|
3
|
-
The unified §
|
|
3
|
+
The unified §1 table (introduced when §4.5.9 / §5.1 / §5.2 collapsed into a
|
|
4
4
|
single section) is the canonical home for every clarification an
|
|
5
5
|
implementation-planning run owes the user — decisions, file attachments,
|
|
6
6
|
single data points. Each row carries a ``Blocks`` column whose value picks
|
|
@@ -12,7 +12,7 @@ This module exposes one read function for that gate so both
|
|
|
12
12
|
``_validate_approved_plan`` (pre-implementation run-prep) and any later
|
|
13
13
|
validator can share the same parsing logic.
|
|
14
14
|
|
|
15
|
-
Legacy compatibility: reports written before the §
|
|
15
|
+
Legacy compatibility: reports written before the §1 unification used
|
|
16
16
|
``4.5.9 Open Questions`` + ``5.1 Additional Materials`` + ``5.2 Questions
|
|
17
17
|
for the User`` and lacked a ``Blocks`` column. Those reports cannot be
|
|
18
18
|
gate-checked by Blocks; the parser returns ``None`` to signal "schema
|
|
@@ -26,13 +26,13 @@ from pathlib import Path
|
|
|
26
26
|
from typing import Optional
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
SECTION_HEADING_PATTERN = re.compile(r"^##\s+
|
|
30
|
-
NEXT_TOP_LEVEL_HEADING_PATTERN = re.compile(r"^##\s+(?!
|
|
29
|
+
SECTION_HEADING_PATTERN = re.compile(r"^##\s+1\.\s+Clarification Items\s*$", re.MULTILINE)
|
|
30
|
+
NEXT_TOP_LEVEL_HEADING_PATTERN = re.compile(r"^##\s+(?!1\.)", re.MULTILINE)
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
@dataclass(frozen=True)
|
|
34
34
|
class ClarificationItem:
|
|
35
|
-
"""One row of the §
|
|
35
|
+
"""One row of the §1 table.
|
|
36
36
|
|
|
37
37
|
``raw_*`` fields preserve the exact cell text (after backtick stripping)
|
|
38
38
|
for diagnostics; canonical lowercased versions live in ``blocks`` /
|
|
@@ -77,9 +77,9 @@ def _is_separator_row(line: str) -> bool:
|
|
|
77
77
|
return True
|
|
78
78
|
|
|
79
79
|
|
|
80
|
-
def
|
|
81
|
-
"""Return the substring spanning the §
|
|
82
|
-
next ``##`` heading), or None if §
|
|
80
|
+
def _section_1_slice(report_text: str) -> Optional[str]:
|
|
81
|
+
"""Return the substring spanning the §1 section (heading exclusive of the
|
|
82
|
+
next ``##`` heading), or None if §1 is absent."""
|
|
83
83
|
start_match = SECTION_HEADING_PATTERN.search(report_text)
|
|
84
84
|
if not start_match:
|
|
85
85
|
return None
|
|
@@ -89,7 +89,7 @@ def _section_5_slice(report_text: str) -> Optional[str]:
|
|
|
89
89
|
|
|
90
90
|
|
|
91
91
|
def parse_clarification_items(report_text: str) -> Optional[list[ClarificationItem]]:
|
|
92
|
-
"""Return the list of §
|
|
92
|
+
"""Return the list of §1 rows. ``None`` means "no unified §1 table
|
|
93
93
|
detected" (legacy report or missing section) — caller must NOT treat
|
|
94
94
|
that as "table is empty".
|
|
95
95
|
|
|
@@ -97,7 +97,7 @@ def parse_clarification_items(report_text: str) -> Optional[list[ClarificationIt
|
|
|
97
97
|
just the ``- 추가 정보 요청 없음.`` placeholder); that IS a confident
|
|
98
98
|
"no approval-blocking items".
|
|
99
99
|
"""
|
|
100
|
-
section =
|
|
100
|
+
section = _section_1_slice(report_text)
|
|
101
101
|
if section is None:
|
|
102
102
|
return None
|
|
103
103
|
|
|
@@ -75,7 +75,7 @@ def _strip_phase_blocks(text: str, current_phase: str) -> str:
|
|
|
75
75
|
entirely. When *current_phase* is empty or not one of the four
|
|
76
76
|
block-targetable phases (e.g. `requirements-discovery`,
|
|
77
77
|
`error-analysis`), every block is dropped — correct because none of
|
|
78
|
-
the `##
|
|
78
|
+
the `## 5.5` / `5.6` / `5.7` / `5.8` deliverable sections apply
|
|
79
79
|
there.
|
|
80
80
|
|
|
81
81
|
Observed (fontsninja-classifier-v2 RD run): the raw final-report
|
|
@@ -9,7 +9,7 @@ the canonical user-facing markdown.
|
|
|
9
9
|
|
|
10
10
|
Why this exists: prior to v0.32, report-writer-worker wrote the markdown
|
|
11
11
|
directly. Free-form authoring led to silent contract violations — missing
|
|
12
|
-
columns in the Execution Status table, omitted §
|
|
12
|
+
columns in the Execution Status table, omitted §4 phase-continuation
|
|
13
13
|
rows, invented ``## Index`` sections. Routing everything through one
|
|
14
14
|
template + schema cuts those failure modes to zero.
|
|
15
15
|
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
Single product, single source of truth:
|
|
4
4
|
|
|
5
5
|
* ``render_html(src_md, *, run_meta)`` — deterministic self-contained
|
|
6
|
-
HTML renderer for human readers. Sections §
|
|
7
|
-
rows (those reachable from §
|
|
8
|
-
controls. §
|
|
6
|
+
HTML renderer for human readers. Sections §1/§3/§4 user-actionable
|
|
7
|
+
rows (those reachable from §1 ``C-*`` IDs) get embedded ``<form>``
|
|
8
|
+
controls. §5.6 / §5.7 / §5.8 deliverable sub-sections are explicitly
|
|
9
9
|
excluded from form attachment — they are read-only deliverables.
|
|
10
10
|
|
|
11
11
|
User responses are NEVER merged back into the original report. The HTML
|
|
@@ -57,7 +57,7 @@ def _strip_leading_frontmatter(text: str) -> str:
|
|
|
57
57
|
|
|
58
58
|
from .clarification_items import (
|
|
59
59
|
_is_separator_row,
|
|
60
|
-
|
|
60
|
+
_section_1_slice,
|
|
61
61
|
_split_pipe_row,
|
|
62
62
|
parse_clarification_items,
|
|
63
63
|
)
|
|
@@ -79,7 +79,7 @@ _LINK_PATTERN = re.compile(r"\[([^\]]+)\]\(([^)]+)\)")
|
|
|
79
79
|
|
|
80
80
|
# Sections whose Response-ID-bearing rows must NOT get form attachment
|
|
81
81
|
# (read-only deliverables — see plan §1.4).
|
|
82
|
-
_NO_FORM_SECTION_PREFIXES = ("##
|
|
82
|
+
_NO_FORM_SECTION_PREFIXES = ("## 5.6", "### 5.6", "## 5.7", "### 5.7", "## 5.8", "### 5.8")
|
|
83
83
|
|
|
84
84
|
|
|
85
85
|
@dataclass(frozen=True)
|
|
@@ -161,7 +161,7 @@ def _markdown_to_html(
|
|
|
161
161
|
headings: list[tuple[int, str, str]] = []
|
|
162
162
|
i = 0
|
|
163
163
|
n = len(lines)
|
|
164
|
-
current_section_path: list[str] = [] # ['##
|
|
164
|
+
current_section_path: list[str] = [] # ['## 1. ...', '### 1.1 ...'] etc.
|
|
165
165
|
|
|
166
166
|
while i < n:
|
|
167
167
|
line = lines[i]
|
|
@@ -394,7 +394,7 @@ class _GroupedSpec:
|
|
|
394
394
|
value`` metadata cell led by ``headline_col``; the long columns
|
|
395
395
|
(``wide_cols``) each keep their own min-width column.
|
|
396
396
|
|
|
397
|
-
``kind == "clarification"`` additionally re-attaches the §
|
|
397
|
+
``kind == "clarification"`` additionally re-attaches the §1 form
|
|
398
398
|
widget to the ``user_input_col`` cell and the ``data-*`` row attrs."""
|
|
399
399
|
headline_col: int
|
|
400
400
|
group_cols: tuple[int, ...]
|
|
@@ -410,7 +410,7 @@ class _GroupedSpec:
|
|
|
410
410
|
def _grouped_table_spec(
|
|
411
411
|
header_cells: list[str], section_path: list[str]
|
|
412
412
|
) -> Optional[_GroupedSpec]:
|
|
413
|
-
"""Only §
|
|
413
|
+
"""Only §1 Clarification Items is grouped in the HTML view (it keeps the
|
|
414
414
|
interactive form and stays flat in the .md). All other narrative tables are
|
|
415
415
|
already rendered compactly by the template, so no grouping is applied here."""
|
|
416
416
|
norm = [h.strip() for h in header_cells]
|
|
@@ -420,7 +420,7 @@ def _grouped_table_spec(
|
|
|
420
420
|
group = tuple(c for c in range(len(norm)) if c != headline and c not in wide_set)
|
|
421
421
|
return _GroupedSpec(headline_col=headline, group_cols=group, wide_cols=wide, **kw)
|
|
422
422
|
|
|
423
|
-
# §
|
|
423
|
+
# §1 Clarification Items — keep the interactive form, and widen the three
|
|
424
424
|
# long-prose columns (Expected form is prose too, not a code column).
|
|
425
425
|
if (
|
|
426
426
|
any("Clarification Items" in h for h in section_path)
|
|
@@ -474,7 +474,7 @@ def _grouped_meta_cell(
|
|
|
474
474
|
def _grouped_clarification_row(
|
|
475
475
|
row: list[str], spec: _GroupedSpec
|
|
476
476
|
) -> tuple[str, str]:
|
|
477
|
-
"""Return ``(tr_attrs, wide_cells_html)`` for one §
|
|
477
|
+
"""Return ``(tr_attrs, wide_cells_html)`` for one §1 row, re-attaching
|
|
478
478
|
the form widget + ``data-*`` attrs to ``C-\\d+`` rows exactly as the
|
|
479
479
|
non-grouped path does."""
|
|
480
480
|
rid = row[spec.id_col] if 0 <= spec.id_col < len(row) else ""
|
|
@@ -822,7 +822,7 @@ def serialize_user_response(
|
|
|
822
822
|
# --------------------------------------------------------------------------- #
|
|
823
823
|
|
|
824
824
|
def report_has_clarification_items(src_md: str) -> bool:
|
|
825
|
-
"""True when the final-report MD has at least one §
|
|
825
|
+
"""True when the final-report MD has at least one §1``C-*``
|
|
826
826
|
clarification row. This is the single predicate that gates HTML-view
|
|
827
827
|
generation: the self-contained html's only value over the markdown is
|
|
828
828
|
the embedded ``<form>`` widgets for those rows, so a clarification-free
|
|
@@ -844,7 +844,7 @@ def render_html_view(
|
|
|
844
844
|
) -> Path | None:
|
|
845
845
|
"""Write ``<stem>.html`` next to ``src_md_path`` and return its path,
|
|
846
846
|
or return ``None`` when generation is skipped because the report has
|
|
847
|
-
no §
|
|
847
|
+
no §1 clarification rows (see ``report_has_clarification_items``).
|
|
848
848
|
Idempotent — overwrites an existing html sibling, and removes a stale
|
|
849
849
|
one when a previously-clarification-bearing report no longer has rows."""
|
|
850
850
|
src_text = src_md_path.read_text(encoding="utf-8")
|
|
@@ -175,14 +175,14 @@ def _validate_approved_plan(path: str) -> None:
|
|
|
175
175
|
f"approved plan is not yet approved (frontmatter `approved: {m.group(1)}`): {path}\n"
|
|
176
176
|
" open the report and change the frontmatter line to `approved: true`, "
|
|
177
177
|
"or re-run okstra with `--approve` to flip it from the CLI.\n"
|
|
178
|
-
" resolve any `Blocks=approval` rows in `##
|
|
178
|
+
" resolve any `Blocks=approval` rows in `## 1. Clarification Items` first."
|
|
179
179
|
)
|
|
180
|
-
# frontmatter approved == true 상태. §
|
|
180
|
+
# frontmatter approved == true 상태. §1 Clarification Items 의
|
|
181
181
|
# Blocks=approval 행이 아직 open/answered 면 승인을 무효화한다.
|
|
182
182
|
blockers = unresolved_approval_blockers(body)
|
|
183
183
|
if blockers:
|
|
184
184
|
lines = [
|
|
185
|
-
f"approved plan frontmatter has `approved: true` but §
|
|
185
|
+
f"approved plan frontmatter has `approved: true` but §1 has {len(blockers)} "
|
|
186
186
|
f"unresolved `Blocks=approval` row(s); resolve them or mark them obsolete first:",
|
|
187
187
|
]
|
|
188
188
|
for b in blockers:
|
|
@@ -205,6 +205,28 @@ S_CONFIRM = "confirm"
|
|
|
205
205
|
S_EDIT_TARGET = "edit_target"
|
|
206
206
|
S_DONE = "done"
|
|
207
207
|
|
|
208
|
+
# ---- 멀티탭 배치 프롬프트 그룹 (방출 계층 전용) ----
|
|
209
|
+
# 그룹 id 는 S_* 가 아니므로 prompts JSON SOT / step-id 동기화 검사 대상이 아니다.
|
|
210
|
+
GROUP_MODELS = "models"
|
|
211
|
+
GROUP_OPTIONS = "options"
|
|
212
|
+
GROUP_MAX_TABS = 4 # AskUserQuestion 의 질문(탭) 수 한도
|
|
213
|
+
|
|
214
|
+
# 멤버는 모두 서로 의존이 없는 단일선택 픽 step 이어야 한다.
|
|
215
|
+
# *_TEXT 후속 / workers_override / pr_template_scope 는 의존성 때문에 개별 유지.
|
|
216
|
+
PROMPT_GROUPS: dict[str, tuple[str, ...]] = {
|
|
217
|
+
GROUP_MODELS: (S_LEAD_MODEL, S_EXECUTOR_MODEL, S_CLAUDE_MODEL,
|
|
218
|
+
S_CODEX_MODEL, S_GEMINI_MODEL, S_REPORT_WRITER_MODEL),
|
|
219
|
+
GROUP_OPTIONS: (S_DIRECTIVE_PICK, S_RELATED_TASKS_PICK,
|
|
220
|
+
S_CLARIFICATION_PICK, S_PR_TEMPLATE_PICK),
|
|
221
|
+
}
|
|
222
|
+
GROUP_LABELS: dict[str, str] = {
|
|
223
|
+
GROUP_MODELS: "모델 선택 (탭별로 선택)",
|
|
224
|
+
GROUP_OPTIONS: "추가 옵션 (탭별로 선택)",
|
|
225
|
+
}
|
|
226
|
+
_STEP_TO_GROUP: dict[str, str] = {
|
|
227
|
+
sid: gid for gid, ids in PROMPT_GROUPS.items() for sid in ids
|
|
228
|
+
}
|
|
229
|
+
|
|
208
230
|
|
|
209
231
|
# ---- Data types ----------------------------------------------------------
|
|
210
232
|
|
|
@@ -305,9 +327,11 @@ class Prompt:
|
|
|
305
327
|
help: str = ""
|
|
306
328
|
echo_template: str = "" # e.g. "task-group: {value}"
|
|
307
329
|
multi: bool = False # only meaningful when kind == "pick"
|
|
330
|
+
# only meaningful when kind == "pick_group": one entry per AskUserQuestion tab
|
|
331
|
+
questions: list["Prompt"] = field(default_factory=list)
|
|
308
332
|
|
|
309
333
|
def to_json(self) -> dict[str, Any]:
|
|
310
|
-
|
|
334
|
+
out = {
|
|
311
335
|
"step": self.step,
|
|
312
336
|
"kind": self.kind,
|
|
313
337
|
"label": self.label,
|
|
@@ -316,6 +340,14 @@ class Prompt:
|
|
|
316
340
|
"echoTemplate": self.echo_template,
|
|
317
341
|
"multi": self.multi,
|
|
318
342
|
}
|
|
343
|
+
if self.kind == "pick_group":
|
|
344
|
+
out["questions"] = [
|
|
345
|
+
{"step": q.step, "label": q.label,
|
|
346
|
+
"options": [asdict(o) for o in q.options],
|
|
347
|
+
"multi": q.multi}
|
|
348
|
+
for q in self.questions
|
|
349
|
+
]
|
|
350
|
+
return out
|
|
319
351
|
|
|
320
352
|
|
|
321
353
|
class WizardError(Exception):
|
|
@@ -373,12 +405,12 @@ def _validate_approved_plan(path_str: str, project_root: Path) -> Path:
|
|
|
373
405
|
" edit the report and change the line to `approved: true`, or re-run "
|
|
374
406
|
"okstra with `--approve` to flip it from the CLI."
|
|
375
407
|
)
|
|
376
|
-
# frontmatter approved == true 라도 §
|
|
408
|
+
# frontmatter approved == true 라도 §1 의 Blocks=approval 행이 미해결이면
|
|
377
409
|
# 승인이 무효 — prepare_task_bundle 의 _validate_approved_plan 과 동일 규약.
|
|
378
410
|
blockers = unresolved_approval_blockers(body)
|
|
379
411
|
if blockers:
|
|
380
412
|
lines = [
|
|
381
|
-
f"approved plan frontmatter has `approved: true` but §
|
|
413
|
+
f"approved plan frontmatter has `approved: true` but §1 has {len(blockers)} "
|
|
382
414
|
f"unresolved `Blocks=approval` row(s); resolve them or mark them obsolete first:",
|
|
383
415
|
]
|
|
384
416
|
for b in blockers:
|
|
@@ -2218,6 +2250,30 @@ def init_state(
|
|
|
2218
2250
|
)
|
|
2219
2251
|
|
|
2220
2252
|
|
|
2253
|
+
def _build_group_prompt(state: WizardState, group_id: str) -> Prompt:
|
|
2254
|
+
"""그룹의 적용가능·미답변 픽 멤버를 최대 GROUP_MAX_TABS 개 모은다.
|
|
2255
|
+
|
|
2256
|
+
멤버가 1개뿐이면 멀티탭 UI가 불필요하므로 그 멤버의 평범한 픽을 반환한다.
|
|
2257
|
+
호출부(next_prompt)는 적용 가능한 멤버가 최소 1개일 때만 진입하므로 빈 그룹은
|
|
2258
|
+
도달 불가다.
|
|
2259
|
+
"""
|
|
2260
|
+
members: list[Prompt] = []
|
|
2261
|
+
for sid in PROMPT_GROUPS[group_id]:
|
|
2262
|
+
if sid in state.answered:
|
|
2263
|
+
continue
|
|
2264
|
+
step = STEP_BY_ID[sid]
|
|
2265
|
+
if not step.applies(state):
|
|
2266
|
+
continue
|
|
2267
|
+
members.append(step.build(state))
|
|
2268
|
+
if len(members) >= GROUP_MAX_TABS:
|
|
2269
|
+
break
|
|
2270
|
+
assert members, f"group {group_id!r} reached with no applicable members"
|
|
2271
|
+
if len(members) == 1:
|
|
2272
|
+
return members[0]
|
|
2273
|
+
return Prompt(step=group_id, kind="pick_group",
|
|
2274
|
+
label=GROUP_LABELS[group_id], questions=members)
|
|
2275
|
+
|
|
2276
|
+
|
|
2221
2277
|
def next_prompt(state: WizardState) -> Prompt:
|
|
2222
2278
|
if state.confirmed:
|
|
2223
2279
|
return Prompt(step=S_DONE, kind="done")
|
|
@@ -2225,10 +2281,39 @@ def next_prompt(state: WizardState) -> Prompt:
|
|
|
2225
2281
|
if step.id in state.answered:
|
|
2226
2282
|
continue
|
|
2227
2283
|
if step.applies(state):
|
|
2284
|
+
group_id = _STEP_TO_GROUP.get(step.id)
|
|
2285
|
+
if group_id is not None:
|
|
2286
|
+
return _build_group_prompt(state, group_id)
|
|
2228
2287
|
return step.build(state)
|
|
2229
2288
|
return Prompt(step=S_DONE, kind="done")
|
|
2230
2289
|
|
|
2231
2290
|
|
|
2291
|
+
def _submit_group(state: WizardState, prompt: Prompt, value: str) -> dict[str, Any]:
|
|
2292
|
+
"""pick_group 답(JSON 객체)을 각 멤버 submit() 으로 라우팅한다.
|
|
2293
|
+
|
|
2294
|
+
멤버 submit 이 WizardError 를 던지면 그대로 전파되어 같은 그룹을 재-프롬프트한다.
|
|
2295
|
+
answered 마킹은 모든 멤버 submit 이 통과한 뒤에만 일괄 수행한다(answered 단위의
|
|
2296
|
+
전부-아니면-전무). 개별 멤버가 변경한 state 필드는 롤백하지 않지만, 재-프롬프트 시
|
|
2297
|
+
같은 그룹이 다시 나와 사용자 입력으로 덮어쓰므로 무해하다.
|
|
2298
|
+
"""
|
|
2299
|
+
try:
|
|
2300
|
+
answers = json.loads(value or "{}")
|
|
2301
|
+
except json.JSONDecodeError as exc:
|
|
2302
|
+
raise WizardError(f"pick_group answer must be a JSON object: {exc}")
|
|
2303
|
+
if not isinstance(answers, dict):
|
|
2304
|
+
raise WizardError("pick_group answer must be a JSON object")
|
|
2305
|
+
echoes: list[str] = []
|
|
2306
|
+
for q in prompt.questions:
|
|
2307
|
+
echo = STEP_BY_ID[q.step].submit(state, str(answers.get(q.step, "") or ""))
|
|
2308
|
+
if echo:
|
|
2309
|
+
echoes.append(echo)
|
|
2310
|
+
for q in prompt.questions:
|
|
2311
|
+
if q.step not in state.answered:
|
|
2312
|
+
state.answered.append(q.step)
|
|
2313
|
+
nxt = next_prompt(state)
|
|
2314
|
+
return {"echo": "; ".join(echoes), "next": nxt.to_json()}
|
|
2315
|
+
|
|
2316
|
+
|
|
2232
2317
|
def submit(state: WizardState, value: str) -> dict[str, Any]:
|
|
2233
2318
|
"""Validate the answer for the *currently active* step and advance.
|
|
2234
2319
|
|
|
@@ -2238,6 +2323,8 @@ def submit(state: WizardState, value: str) -> dict[str, Any]:
|
|
|
2238
2323
|
prompt = next_prompt(state)
|
|
2239
2324
|
if prompt.kind == "done":
|
|
2240
2325
|
return {"echo": "", "next": prompt.to_json()}
|
|
2326
|
+
if prompt.kind == "pick_group":
|
|
2327
|
+
return _submit_group(state, prompt, value)
|
|
2241
2328
|
step = STEP_BY_ID[prompt.step]
|
|
2242
2329
|
echo = step.submit(state, value or "")
|
|
2243
2330
|
if prompt.step not in state.answered:
|
|
@@ -87,7 +87,7 @@ PHASE_RULES: dict[str, dict[str, str]] = {
|
|
|
87
87
|
" - trade-off matrix across options (complexity, risk, reversibility, test cost, rollout cost) and recommended option with rationale tied to isolation / single-responsibility / YAGNI principles\n"
|
|
88
88
|
" - bite-sized stepwise execution order for the recommended option (each step ~2-5 min, exact file paths and commands, TDD ordering when applicable, no placeholders)\n"
|
|
89
89
|
" - dependency / migration risk assessment, validation checklist (pre / mid / post with exact commands), rollback strategy with revert path and trigger signal\n"
|
|
90
|
-
" - every unresolved ambiguity registered as a `Blocks=approval` row in the `##
|
|
90
|
+
" - every unresolved ambiguity registered as a `Blocks=approval` row in the `## 1. Clarification Items` table (do NOT create a separate `Open Questions` block under `5.5.x` — the unified table is the single home)\n"
|
|
91
91
|
" - YAML frontmatter line `approved: false` awaiting human flip to `true`\n"
|
|
92
92
|
" - self-review confirmation (spec coverage, placeholder scan, internal consistency, ambiguity, scope)"
|
|
93
93
|
),
|
|
@@ -760,7 +760,7 @@ If the list is non-empty, run **one** `AskUserQuestion`:
|
|
|
760
760
|
1. `Yes — collect now (Recommended)` — proceed to 6.5c.
|
|
761
761
|
2. `No — leave for the downstream phase` — set
|
|
762
762
|
`reporter-confirmations: skipped`. The phase will promote each
|
|
763
|
-
pending row into its own `##
|
|
763
|
+
pending row into its own `## 1. Clarification Items` as
|
|
764
764
|
`Blocks=next-phase` (`Blocks=approval` only in
|
|
765
765
|
`implementation-planning`); see each phase profile's "Brief
|
|
766
766
|
consumption" addendum.
|
|
@@ -77,7 +77,7 @@ Read the worker result files generated in Phase 4/5 and extract individual findi
|
|
|
77
77
|
- For bullet/numbered findings, parse `[TICKETID: <id>]` from the item title.
|
|
78
78
|
- Items with multiple tickets (e.g. `TICKET-123, TICKET-456`) expand to a set of ticket keys.
|
|
79
79
|
- Items tagged `unknown` keep the literal `unknown` as their ticket key.
|
|
80
|
-
2. For each finding, record the summary, evidence (file path, line number, basis), the worker who identified it, **the worker-internal item ID assigned by that worker** (e.g. `F-001`, `1.1`, `F-3` — see `prompts/profiles/_common-contract.md` "Cross-worker traceability" SSOT), and the parsed ticket set. The item ID is persisted on the finding record as `findings[].discoveredBy.<worker>.itemId` and on each cross-worker confirmation as `findings[].sourceItems[]` (one entry per contributing `<worker>:<item-id>` pair). The final-report's `##
|
|
80
|
+
2. For each finding, record the summary, evidence (file path, line number, basis), the worker who identified it, **the worker-internal item ID assigned by that worker** (e.g. `F-001`, `1.1`, `F-3` — see `prompts/profiles/_common-contract.md` "Cross-worker traceability" SSOT), and the parsed ticket set. The item ID is persisted on the finding record as `findings[].discoveredBy.<worker>.itemId` and on each cross-worker confirmation as `findings[].sourceItems[]` (one entry per contributing `<worker>:<item-id>` pair). The final-report's `## 6.1 Consensus` / `## 6.2 Differences` / `## 2.1 Primary Evidence` tables read this list verbatim into their `Source items` columns — without this, the synthesised `C-NNN` row has no traceable link back to the original worker wording.
|
|
81
81
|
3. Claude Lead groups findings based on semantic similarity AND ticket-set equality:
|
|
82
82
|
- Same semantics + same ticket set across 2+ workers → immediately reach `full consensus`.
|
|
83
83
|
- Same semantics but disjoint ticket sets → keep as separate groups (do NOT over-merge across tickets).
|
|
@@ -561,12 +561,12 @@ existing Acceptance Blocker. If you find none, say so explicitly.
|
|
|
561
561
|
### Verification — confirm-or-downgrade (BLOCKING)
|
|
562
562
|
|
|
563
563
|
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.
|
|
564
|
-
- **Confirmed** (an analyser reproduces it or cites supporting evidence) → promote to a `##
|
|
564
|
+
- **Confirmed** (an analyser reproduces it or cites supporting evidence) → promote to a `## 5.8 Acceptance Blockers` row (keep severity + recommended follow-up phase).
|
|
565
565
|
- **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.
|
|
566
566
|
|
|
567
567
|
### Verdict impact
|
|
568
568
|
|
|
569
|
-
Promoted blockers enter `##
|
|
569
|
+
Promoted blockers enter `## 5.8 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.
|
|
570
570
|
|
|
571
571
|
### State
|
|
572
572
|
|
|
@@ -630,7 +630,7 @@ Default values are emitted into the manifest by `scripts/okstra_ctl/render.py` (
|
|
|
630
630
|
|
|
631
631
|
### Plan-item extraction (Round 0 equivalent)
|
|
632
632
|
|
|
633
|
-
From the report-writer's draft of `##
|
|
633
|
+
From the report-writer's draft of `## 5.5 Implementation Plan Deliverables`, lead extracts plan items with the following prefixes (see also `templates/reports/final-report.template.md` §5.5.9):
|
|
634
634
|
|
|
635
635
|
| Prefix | Source sub-section | One row per |
|
|
636
636
|
|--------|--------------------|-------------|
|
|
@@ -689,13 +689,13 @@ Plan-body verification stays **lightweight** even under this posture — the `ve
|
|
|
689
689
|
- all dispatches non-result → `aborted-non-result`
|
|
690
690
|
- any `partial-consensus` / `dissent-isolated` present, no `majority-disagree` → `passed-with-dissent`
|
|
691
691
|
- all items `full-consensus` → `passed`
|
|
692
|
-
6. Lead writes `runs/<task-type>/state/plan-body-verification-<task-type>-<seq>.json` (schema below) and populates `###
|
|
693
|
-
7. For every `majority-disagree` item, lead adds a row to `##
|
|
692
|
+
6. Lead writes `runs/<task-type>/state/plan-body-verification-<task-type>-<seq>.json` (schema below) and populates `### 5.5.9 Plan Body Verification` in the final report (template at `templates/reports/final-report.template.md`). The §5.5.9 body uses a single `#### Verdict details` table (`Plan item / Worker / Verdict / Breakage kind / Note` — one row per plan-item × worker pair). The older wide `| Plan item | <worker1> | <worker2> | … | Classification |` matrix and the former narrow `#### Verdict summary` card are both removed — the matrix scaled horizontally with the worker count, and the summary only restated per-item classifications already derivable from the details table. The validator's `Plan Body Verification` + `Gate result:` substring checks gate this section.
|
|
693
|
+
7. For every `majority-disagree` item, lead adds a row to `## 1. Clarification Items` with:
|
|
694
694
|
- new `C-<N>` ID (numbering continues from any existing rows)
|
|
695
695
|
- `Statement` summarising the disagreement and the worker breakage `<kind>`
|
|
696
696
|
- `Kind` chosen per the standard policy (usually `decision` for option-level conflicts, `data-point` for path/symbol mismatches)
|
|
697
697
|
- `Blocks=approval`
|
|
698
|
-
- the §
|
|
698
|
+
- the §5.5.9 verdict table's `Classification` column for that row reads `majority-disagree → C-<N>` (1:1 ID match — orphan on either side is a contract violation per `prompts/profiles/implementation-planning.md` self-review step 6).
|
|
699
699
|
8. The top-of-report `- [ ] Approved` marker line is rendered if and only if the Gate result is `passed` or `passed-with-dissent`. `validators/validate-run.py` `validate_phase_boundary` enforces this correspondence; manually adding the marker line when the gate did not pass is a contract violation.
|
|
700
700
|
|
|
701
701
|
### `plan-body-verification-<task-type>-<seq>.json` schema
|
|
@@ -802,4 +802,4 @@ Mirrors finding convergence (§"Worker failure handling in reverify"). Concretel
|
|
|
802
802
|
|
|
803
803
|
- A dispatch that returns terminal non-result MUST NOT be aggregated as `DISAGREE`.
|
|
804
804
|
- If at least one dispatch was issued AND **all** plan-body dispatches return non-result, the Gate result is `aborted-non-result`. Record one `contract-violation` event per non-result dispatch.
|
|
805
|
-
- When the gate is `aborted-non-result`, report-writer MUST keep the frontmatter `approved: false` (publishing `approved: true` under this gate result is a validator failure). A single row is added to `##
|
|
805
|
+
- When the gate is `aborted-non-result`, report-writer MUST keep the frontmatter `approved: false` (publishing `approved: true` under this gate result is a validator failure). A single row is added to `## 1. Clarification Items` with `Statement="plan-body verification could not run — all workers returned non-result"`, `Kind=decision`, `Blocks=approval`, allowing the user to either retry the phase or override by manually flipping the frontmatter to `approved: true` (or running `--approve` on the resume command).
|
|
@@ -48,9 +48,9 @@ The prompt MUST include, in this order at the top:
|
|
|
48
48
|
6. `**Model:** Report writer worker, <modelExecutionValue>` (resolved per Phase 5.5 anchor-header rules)
|
|
49
49
|
7. The full `[Required reading]` clause (see [okstra-team-contract](../okstra-team-contract/SKILL.md)) — for Phase 6 it adds two **per-task-type, instruction-set-local** read-only files, both scoped to this run's task-type by `okstra-ctl` at prep time:
|
|
50
50
|
- `<instruction-set>/final-report-schema.json` — a task-type excerpt of the data.json schema (the other task-types' deliverable blocks and their unreachable `$defs` are stripped; ~38% of the full schema is `$defs` alone). This is your authoring contract for the data.json shape. Do **NOT** pull the full `schemas/final-report-v1.0.schema.json` — it carries all task-types and its `schemas/...` path is not part of the task bundle. (Validation still runs against the full schema post-hoc via the renderer, so the excerpt never relaxes the contract.)
|
|
51
|
-
- `<instruction-set>/final-report-template.md` — the **phase-stripped** template (every other task-type's §
|
|
51
|
+
- `<instruction-set>/final-report-template.md` — the **phase-stripped** template (every other task-type's §5.x deliverable block removed by `render.py`'s `_strip_phase_blocks`, leaving only your run's §5.x). Do **NOT** also pull the full `templates/reports/final-report.template.md` source (it re-adds ~330 lines of other phases' deliverables and is not in the task bundle).
|
|
52
52
|
8. A one-line MCP pointer instead of the verbatim block — `**MCP servers:** follow the task brief's "## Available MCP Servers" section (already in your Required reading).` The brief is already in the report-writer's Required reading (item 7), so the verbatim block is redundant.
|
|
53
|
-
9. The convergence classifications (Full/Partial/Contested/Worker-Unique), the round history data (`roundHistory[]`), the `round2SkippedReason` value, and pointers to all worker result files under `worker-results/`. The report-writer worker populates `crossVerification.roundHistory` in the data.json so Section
|
|
53
|
+
9. The convergence classifications (Full/Partial/Contested/Worker-Unique), the round history data (`roundHistory[]`), the `round2SkippedReason` value, and pointers to all worker result files under `worker-results/`. The report-writer worker populates `crossVerification.roundHistory` in the data.json so Section 6 can show which rounds executed, queue sizes, and why Round 2 was (or was not) skipped. The renderer prints the full per-round table only when more than one round ran; single-round or zero-round histories are auto-collapsed to a one-line summary.
|
|
54
54
|
10. `**Report Language:** <en|ko>` — must be either `en` or `ko`; `auto`
|
|
55
55
|
has been resolved by the lead from project.json / global config
|
|
56
56
|
before the dispatch is constructed. The worker copies this verbatim
|
|
@@ -78,9 +78,9 @@ Speculative reasons such as "session resume constraint", "team object no longer
|
|
|
78
78
|
|
|
79
79
|
## Phase 6 → Phase 7 execution sequence (BLOCKING order)
|
|
80
80
|
|
|
81
|
-
The four steps below MUST execute in this exact order. Reordering them is the recurring root cause of reports shipping with `--` token cells (Phase 7 not run yet), Section
|
|
81
|
+
The four steps below MUST execute in this exact order. Reordering them is the recurring root cause of reports shipping with `--` token cells (Phase 7 not run yet), Section 3 missing follow-up entries, or Section 4 rows never spawning.
|
|
82
82
|
|
|
83
|
-
1. **Phase 6 — Report writer worker drafts the final-report data.json** at `runs/<task-type>/reports/final-report-<task-type>-<seq>.data.json`, then invokes `scripts/okstra-render-final-report.py` to produce the sibling markdown. Token Usage cells in the data.json are `null` at this point (renderer emits `--` for nulls); Section
|
|
83
|
+
1. **Phase 6 — Report writer worker drafts the final-report data.json** at `runs/<task-type>/reports/final-report-<task-type>-<seq>.data.json`, then invokes `scripts/okstra-render-final-report.py` to produce the sibling markdown. Token Usage cells in the data.json are `null` at this point (renderer emits `--` for nulls); Section 3 lists prioritized actions but does NOT yet include auto-spawned follow-ups (they don't exist yet).
|
|
84
84
|
2. **Phase 7 step 1 — Token-usage collector with `--substitute-data`** (BLOCKING). One invocation aggregates `leadUsage` / `workers[].usage` / `usageSummary` into team-state AND populates `tokenUsage` + `executionStatus[].totalTokens` etc. in the data.json AND re-invokes the renderer so the sibling markdown carries the real numbers. Skipping the flag ships a markdown full of `--` cells.
|
|
85
85
|
|
|
86
86
|
```bash
|
|
@@ -103,7 +103,7 @@ The four steps below MUST execute in this exact order. Reordering them is the re
|
|
|
103
103
|
- When the report has **no** `C-*` clarification rows, the html carries no interactive forms (it would only duplicate the MD), so the renderer prints `html: skipped (...)` and writes nothing. This is the expected state for clarification-free runs — `validators/validate-report-views.py` treats "no C-* rows + no html" as a pass, not a missing artifact.
|
|
104
104
|
|
|
105
105
|
Must run AFTER step 1 (so token placeholders are substituted in any rendered html) and BEFORE step 2 (so the html artifact, when generated, exists for the validator step that checks it).
|
|
106
|
-
4. **Phase 7 step 2 — Follow-up task spawner** (BLOCKING when Section
|
|
106
|
+
4. **Phase 7 step 2 — Follow-up task spawner** (BLOCKING when Section 4 is non-empty). Turns the report's `## 4. Follow-up Tasks (후속 작업)` rows into `tasks/<task-group>/<new-task-id>/` stubs.
|
|
107
107
|
|
|
108
108
|
```bash
|
|
109
109
|
python3 scripts/okstra-spawn-followups.py \
|
|
@@ -115,11 +115,11 @@ The four steps below MUST execute in this exact order. Reordering them is the re
|
|
|
115
115
|
|
|
116
116
|
Behaviour contract:
|
|
117
117
|
- Idempotent: rows whose target dir exists are reported as `existing` and skipped. Reruns of the same parent task are safe.
|
|
118
|
-
- Rows with `autoSpawn != "yes"` are reported as `skipped` and never written; surface them in Section
|
|
118
|
+
- Rows with `autoSpawn != "yes"` are reported as `skipped` and never written; surface them in Section 3 if manual action is still needed.
|
|
119
119
|
- Rows whose `origin` is `phase-continuation` are reported as `skipped (no new task dir)` and never spawn — they advance the same task-key via `/okstra-run` instead.
|
|
120
120
|
- An invalid `origin`, `suggestedTaskType`, missing `title`, missing `reason`, or missing `newTaskId` exits `1`. (Schema validation in Phase 6 catches most of these before the spawner runs.)
|
|
121
121
|
- **Canonical spawn rule (single source of truth):** the spawner runs when `task-type` ∈ {`implementation`, `final-verification`, `release-handoff`}, OR when `followUpTasks` is non-empty for any other task-type. For the listed task-types `followUpTasks` must be present (schema enforces the phase-continuation row for non-terminal task-types); an empty array is permitted only for `release-handoff`. Missing arrays are no-ops (exit `0`). All other references to this rule (including the Persistence Checklist) defer to this statement.
|
|
122
|
-
5. **Phase 7 step 3 — Update Section
|
|
122
|
+
5. **Phase 7 step 3 — Update Section 3** after the spawner. The report-writer MUST append one row per newly spawned task-key with its entry command:
|
|
123
123
|
|
|
124
124
|
```
|
|
125
125
|
- Follow-up: `<task-group>/<new-task-id>` — Claude Code 세션 안 `/okstra-run task-key=<task-group>/<new-task-id> task-type=<suggested>` / 별도 터미널 `scripts/okstra.sh --task-key <task-group>/<new-task-id> --task-type <suggested>`
|
|
@@ -215,16 +215,16 @@ When the run's `task-type` is `implementation-planning`, the final report MUST c
|
|
|
215
215
|
| 5 | `Dependency` | `### Dependency / Migration Risk (의존성·마이그레이션 위험)` |
|
|
216
216
|
| 6 | `Validation Checklist` | `### Validation Checklist (검증 체크리스트)` |
|
|
217
217
|
| 7 | `Rollback` | `### Rollback Strategy (롤백 전략)` |
|
|
218
|
-
| 8 | `User Approval Request` | Satisfied by the top-of-report `## User Approval Request (사용자 승인 게이트)` block. Do NOT recreate a `###
|
|
219
|
-
| 9 | `Plan Body Verification` + `Gate result:` | `### Plan Body Verification (계획 본문 검증)` containing a `Gate result:` line — copy `templates/reports/final-report.template.md §
|
|
218
|
+
| 8 | `User Approval Request` | Satisfied by the top-of-report `## User Approval Request (사용자 승인 게이트)` block. Do NOT recreate a `### 5.5.8 User Approval Request` body stub — the validator now fails reports that contain one. |
|
|
219
|
+
| 9 | `Plan Body Verification` + `Gate result:` | `### Plan Body Verification (계획 본문 검증)` containing a `Gate result:` line — copy `templates/reports/final-report.template.md §5.5.9` verbatim. Validator checks both substrings. |
|
|
220
220
|
|
|
221
221
|
The Korean translation in parentheses is optional but the English keyword is mandatory. The body of each section is written in the Report Language per the writing rules below. For non-`implementation-planning` runs, omit this entire block — these headings are NOT validator-checked for other task-types.
|
|
222
222
|
|
|
223
|
-
The final-report template `templates/reports/final-report.template.md` Section
|
|
223
|
+
The final-report template `templates/reports/final-report.template.md` Section 5.5 already encodes this contract — copy that block verbatim and fill in.
|
|
224
224
|
|
|
225
225
|
### Final-verification verdict token contract (BLOCKING)
|
|
226
226
|
|
|
227
|
-
When the run's `task-type` is `final-verification`, the report's `##
|
|
227
|
+
When the run's `task-type` is `final-verification`, the report's `## 7. Final Verdict` table MUST contain a `Verdict Token` row whose value is **exactly one of** the literal strings below. The `release-handoff` profile reads this row as its entry gate; any other value blocks the next phase.
|
|
228
228
|
|
|
229
229
|
| # | Required substring | Meaning |
|
|
230
230
|
|---|--------------------|---------|
|
|
@@ -234,15 +234,15 @@ When the run's `task-type` is `final-verification`, the report's `## 2. Final Ve
|
|
|
234
234
|
|
|
235
235
|
For every other task-type, set the `Verdict Token` cell to `not-applicable`. Do NOT omit the row — the template renders it for all task-types and downstream tooling expects the field to exist.
|
|
236
236
|
|
|
237
|
-
The final-report template `templates/reports/final-report.template.md` Section
|
|
237
|
+
The final-report template `templates/reports/final-report.template.md` Section 7 already encodes this contract — copy that block verbatim and fill in.
|
|
238
238
|
|
|
239
239
|
### Release-handoff section contract (release-handoff runs only)
|
|
240
240
|
|
|
241
|
-
When the run's `task-type` is `release-handoff`, the final report MUST include Section `##
|
|
241
|
+
When the run's `task-type` is `release-handoff`, the final report MUST include Section `## 5.6 Release Handoff Deliverables` with all eight sub-sections (`5.6.1` Source Verification Report, `5.6.2` Feature Branch & Working-Tree State, `5.6.3` User Selections, `5.6.4` Executed Commands, `5.6.5` Commit List, `5.6.6` Merge Conflict Probe, `5.6.7` Pull Request Outcome, `5.6.8` Routing Recommendation). Every entry is dictated by the lead's recorded git/gh command log and the user's verbatim answers to the H1/H2/H3 menu prompts. H1 choices are `local only`, `push + PR`, or `skip`; release-handoff records existing implementation commits and MUST NOT create new commits. If the user picked `skip` (H1) or `cancel` (H3), keep 5.6.3 populated but leave 5.6.4–5.6.6 explicitly empty per the template's empty-state lines.
|
|
242
242
|
|
|
243
243
|
**Single-lead authorship (release-handoff only):** release-handoff has no worker roster (no `Report writer worker`, no `Claude worker` drafter). The Claude lead authors the final-report file directly — there is no `Report writer worker` dispatch to perform in Phase 6, no resume-safe dispatch concern, and no mandatory worker-results file for a report-writer role. The rest of this skill's dispatch / resume / fallback machinery applies ONLY when `Report writer worker` is in the roster (i.e. every task-type other than `release-handoff`).
|
|
244
244
|
|
|
245
|
-
The final-report template `templates/reports/final-report.template.md` Section
|
|
245
|
+
The final-report template `templates/reports/final-report.template.md` Section 5.6 already encodes this contract — copy that block verbatim and fill in. For non-`release-handoff` runs, omit Section 5.6 entirely.
|
|
246
246
|
|
|
247
247
|
### Mandatory worker-results file (BLOCKING)
|
|
248
248
|
|
|
@@ -260,16 +260,16 @@ Skipping this file because "the real report is in `reports/`" is wrong. Both fil
|
|
|
260
260
|
|
|
261
261
|
### Main Body Section
|
|
262
262
|
|
|
263
|
-
Section numbering follows `templates/reports/final-report.template.md` exactly — that file is the documentation SSOT for section names and ordering. For full body structure at authoring time, consult your run's **phase-stripped** `final-report-template.md` (the instruction-set copy of the same template, with other task-types' §
|
|
263
|
+
Section numbering follows `templates/reports/final-report.template.md` exactly — that file is the documentation SSOT for section names and ordering. For full body structure at authoring time, consult your run's **phase-stripped** `final-report-template.md` (the instruction-set copy of the same template, with other task-types' §5.x deliverable blocks removed); the "copy that block verbatim" references below mean the §-block as it appears in that stripped copy, not a re-read of the full source.
|
|
264
264
|
|
|
265
|
-
**Verdict Card (top-of-report, mandatory).** Render `## Verdict Card` between the report header and the (conditional) Approval block. Its `Verdict Token` / `Direction` / `Next Step` cells MUST byte-match the corresponding cells in `##
|
|
265
|
+
**Verdict Card (top-of-report, mandatory).** Render `## Verdict Card` between the report header and the (conditional) Approval block. Its `Verdict Token` / `Direction` / `Next Step` cells MUST byte-match the corresponding cells in `## 7. Final Verdict` and the first item of `## 6.`. Divergence is `contract-violated`.
|
|
266
266
|
|
|
267
|
-
0. **Clarification Response Carried In** — render this `## 0.` heading ONLY when `{{CLARIFICATION_RESPONSE_RELATIVE_PATH}}` is non-empty. Walk every `C-*` row of the prior report's `##
|
|
267
|
+
0. **Clarification Response Carried In** — render this `## 0.` heading ONLY when `{{CLARIFICATION_RESPONSE_RELATIVE_PATH}}` is non-empty. Walk every `C-*` row of the prior report's `## 1. Clarification Items` table, reconcile against new evidence, and record the outcome (`resolved` / `obsolete`) with citation before drafting the verdict. When no carry-in path was provided, OMIT the `## 0.` heading entirely — the validator fails an empty Section 0 stub.
|
|
268
268
|
1. **Cross Verification Results** — 4 categories (Full / Partial / Contested / Worker-Unique) when convergence is enabled, per `okstra-convergence`. Prepend the Round History sub-table (columns: `Round | inputQueueSize | resolvedCount | carriedForwardCount | dispatches | skippedWorkers`) plus a `round2SkippedReason: <value>` note, pulled verbatim from `convergence-<task-type>-<seq>.json`. Empty contested list renders as `- 합의 미달 항목 없음.`. Convergence-disabled runs use the legacy Consensus/Differences format and omit the round table.
|
|
269
269
|
2. **Final Verdict** — `Direction` ∈ `continue-investigation` / `begin-implementation` / `approve` / `reject` / `hold`. **Verdict Token** is `not-applicable` for every task-type except `final-verification` — see "Final-verification verdict token contract" below for that case.
|
|
270
270
|
3. **Evidence and Detailed Analysis** — primary evidence rows (file path, line, snippet); secondary evidence / alternate interpretations. If `reference-expectations.md` lists explicit expected values, record match/gap per row.
|
|
271
|
-
4. **Missing Information and Risks** — uncertain / "I don't know" items. `implementation-planning` adds §
|
|
272
|
-
5. **Clarification Items** — single unified `C-*` table; column schema, ID convention, and rerun behaviour are owned by `_common-contract.md §Clarification request policy` (8-column SSOT). The deprecated `
|
|
271
|
+
4. **Missing Information and Risks** — uncertain / "I don't know" items. `implementation-planning` adds §5.5 (see heading contract below); `release-handoff` adds §5.6.
|
|
272
|
+
5. **Clarification Items** — single unified `C-*` table; column schema, ID convention, and rerun behaviour are owned by `_common-contract.md §Clarification request policy` (8-column SSOT). The deprecated `5.5.9 Open Questions` / `1.1 추가 자료 요청` / `1.2 사용자 확인 질문` sub-sections are removed; the validator fails reports that reintroduce them.
|
|
273
273
|
6. **Recommended Next Steps** — prioritized actions. After Phase 7's follow-up spawner runs, append a row per newly created task-key (see "Phase 6 → Phase 7 execution sequence" above).
|
|
274
274
|
7. **Follow-up Tasks** — auto-spawn-eligible table. Each row drives `okstra-spawn-followups.py`; see template §7 for the row schema.
|
|
275
275
|
|
|
@@ -281,8 +281,8 @@ Section numbering follows `templates/reports/final-report.template.md` exactly
|
|
|
281
281
|
empty-states, token summary, column headers, release-handoff labels)
|
|
282
282
|
are i18n-rendered by `okstra-render-final-report.py` from
|
|
283
283
|
`templates/reports/i18n/<lang>.json`; do not translate those — focus
|
|
284
|
-
on the prose you author (Section
|
|
285
|
-
narratives, Section
|
|
284
|
+
on the prose you author (Section 6 categories, Section 2 evidence
|
|
285
|
+
narratives, Section 5 risks, Section 3 recommendations, etc.).
|
|
286
286
|
Code identifiers, file paths, model names, status tokens, and the
|
|
287
287
|
validator-checked English substrings (`Option Candidates`,
|
|
288
288
|
`Verdict Token`, `accepted`/`conditional-accept`/`blocked`, etc.)
|
|
@@ -296,7 +296,7 @@ Section numbering follows `templates/reports/final-report.template.md` exactly
|
|
|
296
296
|
- Write the actual analysis text instead of a meta-description
|
|
297
297
|
- Do not make unfounded assertions
|
|
298
298
|
- Include findings from all four categories. Do not omit "contested" or "worker-unique" findings
|
|
299
|
-
- Include the convergence round history sub-table (Section
|
|
299
|
+
- Include the convergence round history sub-table (Section 6) so the reader can audit which rounds executed and what `round2SkippedReason` indicates (e.g. `"not-skipped"` when Round 2 ran, or one of the three skip reasons). Pull values verbatim from `convergence-<task-type>-<seq>.json`; do NOT recompute.
|
|
300
300
|
- For each finding, include a brief summary of votes per worker across executed rounds. `verification-error` votes are listed as such — never as `DISAGREE`.
|
|
301
301
|
- The report writer worker does not participate in the re-verification vote. It is responsible only for drafting the final report
|
|
302
302
|
|
|
@@ -42,6 +42,7 @@ The wizard tells you *which UI to use* via `kind` (and the optional `multi` flag
|
|
|
42
42
|
|
|
43
43
|
- `kind: "pick"` + `multi: false` (default) → render `AskUserQuestion` with `label`, `options[].label`, and `multiSelect: false`. Use the chosen `options[].value` (single string) as the answer.
|
|
44
44
|
- `kind: "pick"` + `multi: true` → render `AskUserQuestion` with `label`, `options[].label`, and `multiSelect: true`. Join the chosen `options[].value` entries with `,` into a single CSV string and submit that as `--answer "csv,values"`. If the user selects nothing, still submit `--answer ""` — the wizard will reply `ok: false` and re-prompt the same step (do not skip the call).
|
|
45
|
+
- `kind: "pick_group"` → render a SINGLE `AskUserQuestion` whose questions array maps 1:1 to the wizard's `questions[]`. For each entry use `questions[].label`, `questions[].options[].label`, and `multiSelect: questions[].multi`. Collect the user's chosen `options[].value` per tab, build a JSON object keyed by each `questions[].step`, and submit it as a single literal `--answer '{"lead_model":"opus","claude_model":"default",...}'`. A tab the user leaves at its default still gets its `"default"`/`""` value in the JSON. Never split a `pick_group` into multiple `AskUserQuestion` calls — the wizard already capped it at 4 tabs and emits any remainder as the next prompt.
|
|
45
46
|
- `kind: "text"` → write `label` as a plain text message and consume the user's NEXT message as the answer.
|
|
46
47
|
- `kind: "done"` → input collection finished; move to Step 5.
|
|
47
48
|
|
|
@@ -96,6 +97,7 @@ Repeat until `next.kind == "done"`:
|
|
|
96
97
|
1. **Render** the prompt according to `kind` (and `multi` for pick):
|
|
97
98
|
- `pick` + `multi: false` → `AskUserQuestion` with `multiSelect: false`, `label`, and `options`. The user's chosen option's `value` is the answer string.
|
|
98
99
|
- `pick` + `multi: true` → `AskUserQuestion` with `multiSelect: true`, `label`, and `options`. Join the selected `value`s with `,` into a single literal CSV string (e.g. `"claude,codex,gemini"`) and submit it as a single `--answer "claude,codex,gemini"`. Empty selection submits `--answer ""` and the wizard re-prompts.
|
|
100
|
+
- `pick_group` → one `AskUserQuestion` with one question per `questions[]` entry (tab). Map each tab's selected `value` back by `questions[].step`, assemble a JSON object, and submit it as a single literal `--answer '<json>'`.
|
|
99
101
|
- `text` → plain text message containing `label`. Consume the user's next reply verbatim as the answer string (empty reply = empty string).
|
|
100
102
|
2. **Submit** the answer — call `okstra wizard step` with the literal state-file path from Step 2 and the literal user answer (no shell variables, no `$(...)`):
|
|
101
103
|
```bash
|