okstra 0.72.0 → 0.74.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 +2 -1
- package/docs/kr/cli.md +1 -1
- package/docs/kr/performance-improvement-plan-v2.md +27 -7
- package/docs/project-structure-overview.md +1 -0
- package/docs/superpowers/plans/2026-06-12-html-plan-approval.md +1000 -0
- package/docs/superpowers/specs/2026-06-12-html-plan-approval-design.md +85 -0
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/SKILL.md +3 -3
- package/runtime/agents/workers/codex-worker.md +5 -5
- package/runtime/agents/workers/gemini-worker.md +5 -5
- package/runtime/prompts/profiles/_implementation-executor.md +1 -0
- package/runtime/prompts/profiles/_implementation-verifier.md +1 -0
- package/runtime/prompts/profiles/error-analysis.md +1 -1
- package/runtime/prompts/profiles/final-verification.md +1 -1
- package/runtime/prompts/profiles/implementation-planning.md +1 -1
- package/runtime/prompts/profiles/requirements-discovery.md +1 -1
- package/runtime/prompts/wizard/prompts.ko.json +11 -2
- package/runtime/python/okstra_ctl/clarification_items.py +32 -22
- package/runtime/python/okstra_ctl/context_cost.py +32 -5
- package/runtime/python/okstra_ctl/md_table.py +56 -0
- package/runtime/python/okstra_ctl/render_final_report.py +8 -1
- package/runtime/python/okstra_ctl/report_views.py +218 -26
- package/runtime/python/okstra_ctl/run.py +4 -4
- package/runtime/python/okstra_ctl/user_response.py +55 -0
- package/runtime/python/okstra_ctl/wizard.py +116 -11
- package/runtime/python/okstra_token_usage/claude.py +22 -0
- package/runtime/python/okstra_token_usage/collect.py +51 -3
- package/runtime/python/okstra_token_usage/cursor.py +3 -1
- package/runtime/schemas/final-report-v1.0.schema.json +2 -1
- package/runtime/skills/okstra-convergence/SKILL.md +8 -4
- package/runtime/skills/okstra-inspect/SKILL.md +20 -0
- package/runtime/skills/okstra-report-writer/SKILL.md +4 -3
- package/runtime/skills/okstra-run/SKILL.md +1 -1
- package/runtime/skills/okstra-team-contract/SKILL.md +1 -1
- package/runtime/templates/reports/final-report.template.md +57 -52
- package/runtime/templates/reports/report.css +21 -7
- package/runtime/templates/reports/report.js +56 -8
- package/runtime/templates/reports/user-response.template.md +15 -0
- package/runtime/validators/validate-implementation-plan-stages.py +9 -2
- package/runtime/validators/validate-report-views.py +2 -1
- package/runtime/validators/validate-run.py +6 -17
- package/runtime/validators/validate-schedule.py +9 -2
- package/runtime/validators/validate_improvement_report.py +2 -1
|
@@ -2,12 +2,18 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import json
|
|
5
|
+
import re
|
|
5
6
|
from datetime import datetime
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
|
|
8
9
|
from .cursor import MAX_NEEDLES, fresh_cache, load_cache, save_cache
|
|
9
10
|
from .paths import claude_project_dir, ts_in_window
|
|
10
11
|
|
|
12
|
+
# lead 의 단계 체크포인트 라인(agents/SKILL.md "Progress reporting") — phase id 가
|
|
13
|
+
# `phase-<digit>` 로 시작하는 라인만 마커로 인정해 일반 대화의 오탐을 줄인다.
|
|
14
|
+
_PROGRESS_RE = re.compile(r"^PROGRESS:\s+(phase-\d\S*(?:[ \t].*)?)$", re.MULTILINE)
|
|
15
|
+
_PROGRESS_MARKER_MAX_CHARS = 160
|
|
16
|
+
|
|
11
17
|
|
|
12
18
|
def _event_from_record(rec: dict) -> dict | None:
|
|
13
19
|
"""jsonl 레코드 1개 → 압축 이벤트. 집계에 기여하지 않으면 None.
|
|
@@ -48,6 +54,18 @@ def _event_from_record(rec: dict) -> dict | None:
|
|
|
48
54
|
if isinstance(b, dict) and b.get("type") == "tool_use")
|
|
49
55
|
if tools:
|
|
50
56
|
ev["u"] = tools
|
|
57
|
+
markers = [
|
|
58
|
+
m.group(1).strip()[:_PROGRESS_MARKER_MAX_CHARS]
|
|
59
|
+
for b in (msg.get("content") or [])
|
|
60
|
+
if isinstance(b, dict) and b.get("type") == "text"
|
|
61
|
+
for m in _PROGRESS_RE.finditer(b.get("text") or "")
|
|
62
|
+
]
|
|
63
|
+
# 서로 다른 phase id 가 한 메시지에 섞이면 계약 인용/회고 요약이라 그
|
|
64
|
+
# 시각은 단계 경계가 아니다(관측: dev-9186 run 의 막판 요약 1건이 전
|
|
65
|
+
# phase 의 lastAt 을 오염). 같은 phase id 의 반복(병렬 dispatch 3줄,
|
|
66
|
+
# intake reading+complete)은 라이브 체크포인트로 인정한다.
|
|
67
|
+
if markers and len({m.split(None, 1)[0] for m in markers}) == 1:
|
|
68
|
+
ev["p"] = markers
|
|
51
69
|
ts = rec.get("timestamp") or msg.get("timestamp")
|
|
52
70
|
if ts:
|
|
53
71
|
ev["t"] = ts
|
|
@@ -133,6 +151,7 @@ def _totals_from_events(events: list[dict], agent_name: str | None,
|
|
|
133
151
|
input_t = output_t = cache_create_t = cache_read_t = 0
|
|
134
152
|
cache_create_5m_t = cache_create_1h_t = 0
|
|
135
153
|
tool_uses = 0
|
|
154
|
+
progress_markers: list[dict] = []
|
|
136
155
|
first_ts: str | None = None
|
|
137
156
|
last_ts: str | None = None
|
|
138
157
|
for ev in events:
|
|
@@ -146,6 +165,8 @@ def _totals_from_events(events: list[dict], agent_name: str | None,
|
|
|
146
165
|
cache_create_1h_t += ev.get("c1", 0)
|
|
147
166
|
cache_read_t += ev.get("r", 0)
|
|
148
167
|
tool_uses += ev.get("u", 0)
|
|
168
|
+
for marker in ev.get("p", ()):
|
|
169
|
+
progress_markers.append({"at": ts, "marker": marker})
|
|
149
170
|
if ts:
|
|
150
171
|
if first_ts is None or ts < first_ts:
|
|
151
172
|
first_ts = ts
|
|
@@ -179,6 +200,7 @@ def _totals_from_events(events: list[dict], agent_name: str | None,
|
|
|
179
200
|
"model": model,
|
|
180
201
|
"startedAt": first_ts,
|
|
181
202
|
"endedAt": last_ts,
|
|
203
|
+
"progressMarkers": progress_markers,
|
|
182
204
|
}
|
|
183
205
|
|
|
184
206
|
|
|
@@ -26,9 +26,11 @@ def match_prefixes(worker_id: str) -> list[str]:
|
|
|
26
26
|
`-reverify-r1`, `-impl`, `-2`) when it dispatches the same role multiple
|
|
27
27
|
times or in different sub-flows. We treat every `agentName` matching one of
|
|
28
28
|
these prefixes — either exactly or as `<prefix>-<suffix>` — as belonging
|
|
29
|
-
to this worker so its tokens get aggregated. For implementation
|
|
30
|
-
|
|
31
|
-
matching provider
|
|
29
|
+
to this worker so its tokens get aggregated. For implementation /
|
|
30
|
+
final-verification runs the role variants `<provider>-executor` and
|
|
31
|
+
`<provider>-verifier` are also attributed back to the matching provider
|
|
32
|
+
worker (the bare `<provider>` prefix's `<prefix>-<suffix>` match covers
|
|
33
|
+
any role suffix Lead assigns).
|
|
32
34
|
"""
|
|
33
35
|
if not worker_id:
|
|
34
36
|
return []
|
|
@@ -141,6 +143,51 @@ def resolve_run_window(team_state_path: Path, state: dict) -> tuple[str | None,
|
|
|
141
143
|
return since, until
|
|
142
144
|
|
|
143
145
|
|
|
146
|
+
def _wall_ms(start_iso: str | None, end_iso: str | None) -> int | None:
|
|
147
|
+
if not start_iso or not end_iso:
|
|
148
|
+
return None
|
|
149
|
+
try:
|
|
150
|
+
a = datetime.fromisoformat(start_iso.replace("Z", "+00:00"))
|
|
151
|
+
b = datetime.fromisoformat(end_iso.replace("Z", "+00:00"))
|
|
152
|
+
except ValueError:
|
|
153
|
+
return None
|
|
154
|
+
return max(0, int((b - a).total_seconds() * 1000))
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def phase_timeline(markers: list[dict]) -> dict:
|
|
158
|
+
"""lead 의 PROGRESS 체크포인트(agents/SKILL.md "Progress reporting")로
|
|
159
|
+
run 내부 단계 경계 wall-clock 을 복원한다 (perf plan v2 P0 계측).
|
|
160
|
+
|
|
161
|
+
phase id = marker 의 첫 토큰(`phase-5.5-convergence` 등). 같은 phase 의
|
|
162
|
+
반복 마커(poll/collect/dispatch)는 first/last 로 접는다. `wallMsToNext` 는
|
|
163
|
+
다음 phase 의 firstAt 까지 — 마지막 phase 는 None(run 종료 신호가 마커에
|
|
164
|
+
없으므로 추정하지 않는다). 마커가 하나도 없으면 phases 가 빈 채로 남아
|
|
165
|
+
"이 run 은 측정 불가"를 명시적으로 표현한다.
|
|
166
|
+
"""
|
|
167
|
+
phases: list[dict] = []
|
|
168
|
+
by_id: dict[str, dict] = {}
|
|
169
|
+
for m in markers:
|
|
170
|
+
at = m.get("at")
|
|
171
|
+
phase_id = (m.get("marker") or "").split(None, 1)[0]
|
|
172
|
+
if not phase_id:
|
|
173
|
+
continue
|
|
174
|
+
entry = by_id.get(phase_id)
|
|
175
|
+
if entry is None:
|
|
176
|
+
entry = {"phase": phase_id, "firstAt": at, "lastAt": at,
|
|
177
|
+
"markerCount": 0, "wallMsToNext": None}
|
|
178
|
+
by_id[phase_id] = entry
|
|
179
|
+
phases.append(entry)
|
|
180
|
+
entry["markerCount"] += 1
|
|
181
|
+
if at:
|
|
182
|
+
if entry["firstAt"] is None or at < entry["firstAt"]:
|
|
183
|
+
entry["firstAt"] = at
|
|
184
|
+
if entry["lastAt"] is None or at > entry["lastAt"]:
|
|
185
|
+
entry["lastAt"] = at
|
|
186
|
+
for current, nxt in zip(phases, phases[1:]):
|
|
187
|
+
current["wallMsToNext"] = _wall_ms(current["firstAt"], nxt["firstAt"])
|
|
188
|
+
return {"source": "lead-progress-markers", "phases": phases}
|
|
189
|
+
|
|
190
|
+
|
|
144
191
|
def resolve_team_name(state: dict) -> str:
|
|
145
192
|
"""team-state 에서 이 run 의 team name 을 해소한다.
|
|
146
193
|
|
|
@@ -232,6 +279,7 @@ def collect(team_state_path: Path, project_root: Path | None = None, *,
|
|
|
232
279
|
incremental=incremental)
|
|
233
280
|
state["leadUsage"] = usage_block(totals, source="claude-jsonl")
|
|
234
281
|
state["leadUsage"]["sessionId"] = lead_sid
|
|
282
|
+
state["phaseTimeline"] = phase_timeline(totals.get("progressMarkers") or [])
|
|
235
283
|
else:
|
|
236
284
|
state["leadUsage"] = na_block(
|
|
237
285
|
f"lead session jsonl not found under {claude_project_dir(cwd)} (sessionId={lead_sid})"
|
|
@@ -18,7 +18,9 @@ from pathlib import Path
|
|
|
18
18
|
|
|
19
19
|
from okstra_project.dirs import okstra_home
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
# v2: usage 이벤트에 `p`(lead PROGRESS 마커) 키 추가 — v1 캐시는 이미 스캔한
|
|
22
|
+
# 구간의 마커가 없으므로 폐기하고 전체 재스캔한다(fail-open).
|
|
23
|
+
CACHE_SCHEMA_VERSION = 2
|
|
22
24
|
IDENTITY_PREFIX_BYTES = 256
|
|
23
25
|
MAX_NEEDLES = 16
|
|
24
26
|
|
|
@@ -1242,9 +1242,10 @@
|
|
|
1242
1242
|
|
|
1243
1243
|
"ValidationCheckRow": {
|
|
1244
1244
|
"type": "object",
|
|
1245
|
-
"required": ["phase", "ticketId", "check", "commandOrObservation", "expectedOutcome"],
|
|
1245
|
+
"required": ["id", "phase", "ticketId", "check", "commandOrObservation", "expectedOutcome"],
|
|
1246
1246
|
"additionalProperties": false,
|
|
1247
1247
|
"properties": {
|
|
1248
|
+
"id": { "type": "string", "pattern": "^VC-\\d{3,}$" },
|
|
1248
1249
|
"phase": { "enum": ["pre", "mid", "post"] },
|
|
1249
1250
|
"ticketId": { "$ref": "#/$defs/TicketId" },
|
|
1250
1251
|
"check": { "type": "string", "minLength": 1 },
|
|
@@ -519,14 +519,18 @@ Schema rules:
|
|
|
519
519
|
Runs only when `convergence.critic.enabled == true` (set by `--critic <provider>` or the okstra-run `critic_pick` step; default off). Applies to the three finding-producing phases (`requirements-discovery`, `error-analysis`, `implementation-planning`); for `final-verification` the critic runs in a different mode — see §"Acceptance critic pass (final-verification)". This pass targets **coverage** (missed findings), distinct from convergence which targets **agreement quality**.
|
|
520
520
|
|
|
521
521
|
### When
|
|
522
|
-
|
|
522
|
+
|
|
523
|
+
The critic input is the Round 0 consolidated finding list. Reverify rounds only classify findings — they never add or remove them (in-round queue insertions are forbidden, see §"Convergence State Artifact" `carriedForwardCount`) — so the critic dispatch MUST NOT wait for classification to finish:
|
|
524
|
+
|
|
525
|
+
- **Dispatch**: immediately after Round 0 grouping, CONCURRENTLY with the first reverify round's dispatches. When the verification queue is empty after Round 0 (no reverify round runs), dispatch right after grouping. Concurrent dispatch to the same provider is safe — the critic result path (`<provider>-critic-...`) never collides with a reverify result path.
|
|
526
|
+
- **Gap verification + merge**: only after BOTH the finding-convergence loop has exited AND the critic result is collected, and BEFORE the Phase 6 report-writer dispatch. If the loop exited `aborted-non-result`, do NOT dispatch a gap-verification round — surface the gaps as unverified `clarification` items per §"Gap verification" and record that fact.
|
|
523
527
|
|
|
524
528
|
### Dispatch (reused worker)
|
|
525
529
|
Dispatch ONE pass to the `config.critic.provider`'s existing subagent (`claude-worker` / `codex-worker` / `gemini-worker`) with `model = config.critic.modelExecutionValue` — no new agent type. If `config.critic.modelExecutionValue` is null/empty (model could not be resolved), skip the critic pass and record `critic-skipped: model-unresolved` in the convergence state rather than dispatching with no model. Result path: `runs/<task-type>/worker-results/<provider>-critic-<task-type>-<seq>.md`. The critic prompt seeds the consolidated findings and asks ONLY for coverage gaps:
|
|
526
530
|
|
|
527
531
|
```
|
|
528
|
-
You are the coverage critic for <task-key>. Below are the findings
|
|
529
|
-
|
|
532
|
+
You are the coverage critic for <task-key>. Below are the consolidated findings
|
|
533
|
+
the workers produced. Your ONLY job is to name what is MISSING:
|
|
530
534
|
- files / directories / execution paths nobody inspected,
|
|
531
535
|
- requirements or acceptance points with zero findings,
|
|
532
536
|
- claims raised but never verified.
|
|
@@ -543,7 +547,7 @@ Each critic gap enters the verification queue as a finding with `originWorker =
|
|
|
543
547
|
|
|
544
548
|
## Acceptance critic pass (final-verification)
|
|
545
549
|
|
|
546
|
-
The `final-verification` phase reuses the SAME reused-worker dispatch as §"Coverage critic pass" (provider + `config.critic.modelExecutionValue` from the `convergence.critic` block; default off; same model-unresolved skip rule). Only the prompt, the verification semantics, and the output sink differ — final-verification's findings are defects/blockers, so the critic acts as an **acceptance devil's advocate** (find reasons NOT to accept), and its candidate blockers are NEVER dropped (that would suppress real defects).
|
|
550
|
+
The `final-verification` phase reuses the SAME reused-worker dispatch AND the same dispatch timing as §"Coverage critic pass" §"When" (provider + `config.critic.modelExecutionValue` from the `convergence.critic` block; default off; same model-unresolved skip rule) — the delivered work the critic inspects is likewise fixed before the reverify round starts. Only the prompt, the verification semantics, and the output sink differ — final-verification's findings are defects/blockers, so the critic acts as an **acceptance devil's advocate** (find reasons NOT to accept), and its candidate blockers are NEVER dropped (that would suppress real defects).
|
|
547
551
|
|
|
548
552
|
### Prompt
|
|
549
553
|
|
|
@@ -425,6 +425,15 @@ Display rule for `workerId` vs `agent`:
|
|
|
425
425
|
|
|
426
426
|
Never write `claude (claude)` — the parenthesized agent is shown only when it adds information.
|
|
427
427
|
|
|
428
|
+
**C. Per-run phase breakdown (when `phaseTimeline` is present):**
|
|
429
|
+
|
|
430
|
+
team-state written by collector ≥ v0.73 carries a `phaseTimeline` block — the Phase 7 collector reconstructs lead operating-phase boundaries from the lead session's `PROGRESS: phase-*` checkpoint lines (`{source: "lead-progress-markers", phases: [{phase, firstAt, lastAt, markerCount, wallMsToNext}]}`).
|
|
431
|
+
|
|
432
|
+
- When the user asks for a per-phase / per-run breakdown ("단계별", "phase별", "어느 단계가 오래"), render one table per requested run: `| Phase | Start | Wall to next |` using `firstAt` and `wallMsToNext`.
|
|
433
|
+
- `wallMsToNext` is `null` on the last phase (the run-end signal is not a marker) — render `--`.
|
|
434
|
+
- `phases: []` means the lead emitted no markers (run predates the contract, or markers were skipped) — say "phase timeline 측정 불가(마커 없음)" rather than estimating from other fields.
|
|
435
|
+
- Older team-state files have no `phaseTimeline` key at all — omit the section silently.
|
|
436
|
+
|
|
428
437
|
**Timestamp parsing:** for `startedAt` / `endedAt`, normalize ISO-8601: replace trailing `Z` with `+00:00`, accept explicit offsets as-is, parse via `datetime.fromisoformat(s.replace("Z", "+00:00"))`. Strings without an offset are assumed UTC. Mixed-form comparisons must be done as `datetime` objects, never raw strings.
|
|
429
438
|
|
|
430
439
|
### time.4 — Format output
|
|
@@ -458,6 +467,17 @@ Never write `claude (claude)` — the parenthesized agent is shown only when it
|
|
|
458
467
|
| report-writer | 2 | 00:01:00 | 00:00:30 |
|
|
459
468
|
|
|
460
469
|
> Unavailable: 1 run (implementation / 2026-04-30_03-03-48) — team-state has no durationMs (Phase 7 not reached)
|
|
470
|
+
|
|
471
|
+
### Phase breakdown — requirements-discovery (run 002)
|
|
472
|
+
|
|
473
|
+
| Phase | Start (UTC) | Wall to next |
|
|
474
|
+
|------------------------|------------------|--------------|
|
|
475
|
+
| phase-1-intake | 2026-06-11 09:00 | 00:04:00 |
|
|
476
|
+
| phase-2-prompts | 2026-06-11 09:04 | 00:02:10 |
|
|
477
|
+
| phase-4-dispatch | 2026-06-11 09:06 | 00:18:30 |
|
|
478
|
+
| phase-5.5-convergence | 2026-06-11 09:25 | 00:21:00 |
|
|
479
|
+
| phase-6-synthesis | 2026-06-11 09:46 | 00:15:00 |
|
|
480
|
+
| phase-7-persist | 2026-06-11 10:01 | -- |
|
|
461
481
|
```
|
|
462
482
|
|
|
463
483
|
**Rules:**
|
|
@@ -105,8 +105,9 @@ The four steps below MUST execute in this exact order. Reordering them is the re
|
|
|
105
105
|
```
|
|
106
106
|
|
|
107
107
|
Output (idempotent — re-running overwrites):
|
|
108
|
-
- `runs/<task-type>/reports/final-report-<task-type>-<seq>.html` — single-file self-contained human view, **generated
|
|
109
|
-
-
|
|
108
|
+
- `runs/<task-type>/reports/final-report-<task-type>-<seq>.html` — single-file self-contained human view, **generated when the report has at least one §1 `C-*` clarification row OR an implementation-planning Plan Approval widget target** (a sibling `final-report-*.data.json` carrying `implementationPlanning.optionCandidates`). Clarification rows with `Status` ∈ {`open`, `answered`} embed form widgets (`<select>` for enum-style decisions, `<input>` for material / data-point kinds, `<textarea>` fallback); an `Export user response` button serialises form values to a markdown sidecar (schema in [`templates/reports/user-response.template.md`](../../templates/reports/user-response.template.md)) and downloads it as `user-response-<task-type>-<seq>.md`; the user saves it to `runs/<task-type>/user-responses/` (the renderer pre-creates that directory), and `--resume-clarification` auto-appends every sidecar found there to the next run's `clarification-response.md` (`clarification_items.clarification_response_with_sidecars`). The original final-report MD is **never** mutated by user input — the sidecar is the single write target.
|
|
109
|
+
- implementation-planning 보고서에는 본문 끝에 **Plan Approval** 섹션(구현 옵션 `<select>` + 승인 체크박스)이 렌더된다 — §1 `Blocks: approval` 행이 미해소면 disabled. 승인을 체크하고 Export 하면 sidecar 본문에 `## APPROVAL` 블록이 실리고, implementation 시작 마법사의 approve-confirm 단계가 그것을 감지해 사용자 확인 후 기존 `--approve` / `--implementation-option` 경로로 적용한다.
|
|
110
|
+
- When the report has **no** `C-*` clarification rows and is **not** a Plan Approval widget target, 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 such runs — `validators/validate-report-views.py` treats "no C-* rows + no approval target + no html" as a pass, not a missing artifact.
|
|
110
111
|
|
|
111
112
|
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).
|
|
112
113
|
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.
|
|
@@ -276,7 +277,7 @@ Section numbering follows `templates/reports/final-report.template.md` exactly
|
|
|
276
277
|
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.
|
|
277
278
|
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.
|
|
278
279
|
5. **Clarification Items** — single unified `C-*` table; column schema (4 columns with the short fields stacked in one record-meta cell), ID convention, and rerun behaviour are owned by `_common-contract.md §Clarification request policy` (SSOT). The deprecated `5.5.9 Open Questions` / `1.1 추가 자료 요청` / `1.2 사용자 확인 질문` sub-sections are removed; the validator fails reports that reintroduce them.
|
|
279
|
-
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).
|
|
280
|
+
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). **Approval-gate consistency:** when §1 carries any `Blocks: approval` row with `Status` ∈ {open, answered}, the Verdict Card `Next Step` and the first recommended step MUST point to the clarification rerun (`resume-clarification` of the SAME task-type) — never to "frontmatter `approved: true` 플립 → `implementation` 직행". Run-prep enforces this gate (`run.py _validate_approved_plan` fail-closes on those rows and on a blocking data.json `gateResult`), so a direct-implementation next-step is an instruction the reader cannot actually follow.
|
|
280
281
|
7. **Follow-up Tasks** — auto-spawn-eligible table. Each row drives `okstra-spawn-followups.py`; see template §7 for the row schema.
|
|
281
282
|
|
|
282
283
|
**§5.10 Fix History (data-presence gated).** When the run-manifest carries a `fixCycleId`, fill the data.json `fixCycle` block (`cycle` / `targetReport` / `symptom` / `runs`). Read the values from the task root's `history/fix-cycles.jsonl`: `cycle` MUST equal `fixCycleId`, `targetReport` / `symptom` come from that cycle's `opened` row, and `runs` lists its attached `run` rows (`taskType` / `runSeq` / `runManifest`). The validator (`validators/validate-run.py` → `_validate_fix_cycle`) fails the run when the block is missing or `fixCycle.cycle` does not match `fixCycleId`. When the run-manifest has no `fixCycleId`, OMIT the `fixCycle` block entirely — the renderer omits §5.10.
|
|
@@ -123,7 +123,7 @@ That is the entire interactive flow. The wizard handles:
|
|
|
123
123
|
- task-type pick (추천 3개 — `nextRecommendedPhase` recommended / 현재 phase 재실행 / 라이프사이클 다음 단계 — + 직접 입력; 직접 입력은 후속 `text` 단계에서 전체 task-type 화이트리스트로 검증),
|
|
124
124
|
- brief path — **entry task-type(requirements-discovery / error-analysis / improvement-discovery)에서만 질문** (same-group `.okstra/briefs/<task-group>/**/*.md` candidates first, direct input last; `유지 / 변경` for existing entry tasks). downstream task-type 은 manifest 의 brief 를 자동 carry-in 하고, 등록 brief 가 없으면 `brief_carry` 3-옵션(entry 전환 추천 / 직접 입력 / 중단)이 뜬다. `release-handoff` 는 brief 단계가 아예 없다 — prepare 가 검증 보고서 인용 input 문서를 생성한다,
|
|
125
125
|
- base-ref pick + git rev-parse validation (skipped when reusing an active worktree),
|
|
126
|
-
- `implementation`-only sub-flow: approved-plan path (frontmatter `approved: true` check) + stage pick (`auto` = 의존성 충족된 가장 빠른 미완료 stage, 또는 특정 stage 번호) + executor pick
|
|
126
|
+
- `implementation`-only sub-flow: approved-plan path (frontmatter `approved: true` check) + stage pick (`auto` = 의존성 충족된 가장 빠른 미완료 stage, 또는 특정 stage 번호) + executor pick. approved-plan 선택 시 그 run 의 sibling `user-responses/` 에서 plan 과 source-report·seq 가 일치하는 HTML `## APPROVAL` sidecar 를 감지하면 approve-confirm 단계가 3-옵션(`yes_apply` HTML 기록대로 승인+옵션 적용 추천 / `yes` 승인만 / `no` 중단)으로 확장된다 — `yes_apply` 는 옵션을 plan 의 `optionCandidates` 에 대해 검증한 뒤 기존 승인·옵션 경로로 적용한다,
|
|
127
127
|
- `release-handoff`-only sub-flow: approved plan 자동 해소 후 `handoff_stage_pick` 멀티선택 — eligible stage 묶음(stage-group) 또는 전체 task(accepted whole-task 검증 보고서 존재 시) 선택; 결과는 render-args 의 `stages` 키(csv, whole-task 면 빈 값)로 나간다,
|
|
128
128
|
- `Use defaults / Customize` branch with profile-aware worker/model questions,
|
|
129
129
|
- `release-handoff` PR template override + persist scope,
|
|
@@ -323,7 +323,7 @@ without proceeding — this is the contractual replacement for the previous
|
|
|
323
323
|
empty run-level error logs in production.
|
|
324
324
|
|
|
325
325
|
- `cli-failure` events are recorded by the wrapper subagent itself (Codex / Gemini), but **directly to the run-level error log** via `okstra error-log append-observed --error-type cli-failure ...` — NOT via the sidecar. The sidecar is an in-process tool-failure channel only.
|
|
326
|
-
- **Wrapper invocation arity.** Both `okstra-codex-exec.sh` and `okstra-gemini-exec.sh` accept four required positional arguments plus an optional fifth `<role>`: `<project-root> <model> <prompt-path> <worktree-path> [<role>]`. The fourth (worktree) argument is **mandatory for implementation phase** and optional otherwise. For codex it becomes `--add-dir <worktree>` (sandbox write access); for gemini it is appended to `--include-directories`. Omitting it during implementation causes the codex sandbox to reject every Edit/Write targeting the worktree with EPERM. Workers extract the path from the `**Worktree:**` / `EXECUTOR_WORKTREE_PATH` / `cwd for every mutating command:` line in the lead prompt. The optional fifth `<role>` is folded into both the caller (worker) pane title `<cli>-<role>-<pid>` and the sibling trace-pane title `<cli>-<role>-<pid>-
|
|
326
|
+
- **Wrapper invocation arity.** Both `okstra-codex-exec.sh` and `okstra-gemini-exec.sh` accept four required positional arguments plus an optional fifth `<role>`: `<project-root> <model> <prompt-path> <worktree-path> [<role>]`. The fourth (worktree) argument is **mandatory for implementation phase** and optional otherwise. For codex it becomes `--add-dir <worktree>` (sandbox write access); for gemini it is appended to `--include-directories`. Omitting it during implementation causes the codex sandbox to reject every Edit/Write targeting the worktree with EPERM. Workers extract the path from the `**Worktree:**` / `EXECUTOR_WORKTREE_PATH` / `cwd for every mutating command:` line in the lead prompt. The optional fifth `<role>` is folded into both the caller (worker) pane title `<cli>-<role>-<pid>` and the sibling trace-pane title `<cli>-<role>-<pid>-tail` (e.g. `codex-executor-93421` ↔ `codex-executor-93421-tail`). `<pid>` is the wrapper's own PID and disambiguates concurrent dispatches of the same role. The role value comes from the dispatch prompt's `**Pane role:**` line: `executor` on an `implementation` Executor dispatch, `verifier` on an `implementation` / `final-verification` verifier dispatch, so the trace pane names the actual job rather than a generic `worker`. When no `**Pane role:**` line is present (analysis phases), pass the literal `worker` (the wrapper also defaults to `worker` if the argument is omitted).
|
|
327
327
|
- **Background dispatch + polling contract (Codex / Gemini wrappers).** Both wrapper subagents MUST dispatch `okstra-codex-exec.sh` / `okstra-gemini-exec.sh` via `Bash(run_in_background: true)` and poll with `BashOutput(bash_id)` until the shell reports `status == "completed"`, capped at 30 minutes (1800s) of wall-clock elapsed time. `BashOutput` itself is the wait primitive — call it back-to-back; do NOT insert a standalone `sleep` between polls. The Claude Code harness blocks `sleep` calls of 5 seconds or longer as a circumvention vector and explicitly forbids chaining shorter sleeps inside until-loops to work around the block. Workers that hit the contract bug must NOT self-recover with `until ...; do sleep 2; done` wrappers — that path violates the harness anti-circumvention rule, even though it superficially "works". The legacy "single foreground `Bash` with 120000ms timeout" rule, and the subsequent "60-second cadence with `sleep 60` between polls" rule, are both retired. The current rule applies in **every phase** (analysis runs typically complete in 1–2 `BashOutput` calls, so there is no regression for short jobs). Recording responsibilities:
|
|
328
328
|
- Successful completion: return the wrapper's accumulated stdout from the final `BashOutput`. No log entry.
|
|
329
329
|
- Non-zero `exit_code` reported by `BashOutput`: record a `cli-failure` to the run-level error log with the real `exit_code` and observed `duration-ms`.
|