okstra 0.28.0 → 0.29.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.
@@ -83,7 +83,19 @@ The four steps below MUST execute in this exact order. Reordering them is the re
83
83
  ```
84
84
 
85
85
  The 10 substituted placeholders: `{{LEAD_TOTAL_TOKENS}}`, `{{LEAD_BILLABLE_TOKENS}}`, `{{LEAD_COST_USD}}`, `{{WORKER_TOTAL_TOKENS}}`, `{{WORKER_BILLABLE_TOKENS}}`, `{{WORKER_COST_USD}}`, `{{GRAND_TOTAL_TOKENS}}`, `{{GRAND_BILLABLE_TOKENS}}`, `{{GRAND_COST_USD}}`, `{{CLI_COST_USD}}`. The final-report file MUST already exist (Phase 6 output).
86
- 3. **Phase 7 step 2Follow-up task spawner** (BLOCKING when Section 7 is non-empty). Turns the report's `## 7. Follow-up Tasks (후속 작업)` rows into `tasks/<task-group>/<new-task-id>/` stubs.
86
+ 3. **Phase 7 step 1.5Render report views** (BLOCKING). Produces two derived artifacts from the now-substituted final-report MD:
87
+
88
+ ```bash
89
+ python3 scripts/okstra-render-report-views.py \
90
+ <runDirectoryPath>/reports/final-report-<task-type>-<seq>.md
91
+ ```
92
+
93
+ Outputs (idempotent — re-running overwrites):
94
+ - `runs/<task-type>/reports/final-report-<task-type>-<seq>.slim.md` — token-saving AI-consumption copy.
95
+ - `runs/<task-type>/reports/final-report-<task-type>-<seq>.html` — single-file self-contained human view. Section 5 `C-*` clarification rows with `Status` ∈ {`open`, `answered`} embed `<textarea>` controls; 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)) that the user pastes to `runs/<task-type>/user-responses/user-response-<task-type>-<seq>.md`. The original final-report MD is **never** mutated by user input — the sidecar is the single write target.
96
+
97
+ Must run AFTER step 1 (so token placeholders are substituted in both derived views) and BEFORE step 2 (so the slim/html artifacts exist for any validator step that checks them).
98
+ 4. **Phase 7 step 2 — Follow-up task spawner** (BLOCKING when Section 7 is non-empty). Turns the report's `## 7. Follow-up Tasks (후속 작업)` rows into `tasks/<task-group>/<new-task-id>/` stubs.
87
99
 
88
100
  ```bash
89
101
  python3 scripts/okstra-spawn-followups.py \
@@ -98,7 +110,7 @@ The four steps below MUST execute in this exact order. Reordering them is the re
98
110
  - Rows with `Auto-spawn? != yes` are reported as `skipped` and never written; surface them in Section 6 if manual action is still needed.
99
111
  - An invalid `Origin`, `Suggested task-type`, missing `Title`, or missing `Reason / Why deferred` exits `1`. The report-writer MUST refuse to ship a Section 7 with such rows.
100
112
  - **Canonical spawn rule (single source of truth):** the spawner runs when `task-type` ∈ {`implementation`, `final-verification`, `release-handoff`}, OR when Section 7 is non-empty for any other task-type. For the listed task-types Section 7 must be present in the report; an empty section renders as `- 후속 작업 없음.`. Missing / empty sections are no-ops (exit `0`). All other references to this rule (including the Persistence Checklist) defer to this statement.
101
- 4. **Phase 7 step 3 — Update Section 6** after the spawner. The report-writer MUST append one row per newly spawned task-key with its entry command:
113
+ 5. **Phase 7 step 3 — Update Section 6** after the spawner. The report-writer MUST append one row per newly spawned task-key with its entry command:
102
114
 
103
115
  ```
104
116
  - 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>`
@@ -117,7 +129,8 @@ The final report follows the structure below. If `instruction-set/final-report-t
117
129
  - Date: <ISO 8601 timestamp>
118
130
  - Task Key: <task-key>
119
131
  - Task Type: <task-type>
120
- - Author: `<Report writer worker if in roster, else Claude lead>`
132
+ - Report Owner: `Claude lead`
133
+ - Report Author: `<Report writer worker if in roster, else Claude lead (release-handoff or recorded-fallback only)>`
121
134
  - Lead model: `<lead-model>`
122
135
  - Preparation Method: Final report authored by Report writer worker (or lead-authored fallback — record the documented dispatch failure reason here when applicable)
123
136
  ```
@@ -208,7 +221,7 @@ The final-report template `okstra-final-report.template.md` Section 2 already en
208
221
 
209
222
  ### Release-handoff section contract (release-handoff runs only)
210
223
 
211
- When the run's `task-type` is `release-handoff`, the final report MUST include Section `## 4.6 Release Handoff Deliverables` with all seven sub-sections (`4.6.1` Source Verification Report, `4.6.2` Feature Branch & Working-Tree State, `4.6.3` User Selections, `4.6.4` Executed Commands, `4.6.5` Commit List, `4.6.6` Pull Request Outcome, `4.6.7` 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 4.6.3 populated but leave 4.6.4–4.6.6 explicitly empty per the template's empty-state lines.
224
+ When the run's `task-type` is `release-handoff`, the final report MUST include Section `## 4.6 Release Handoff Deliverables` with all eight sub-sections (`4.6.1` Source Verification Report, `4.6.2` Feature Branch & Working-Tree State, `4.6.3` User Selections, `4.6.4` Executed Commands, `4.6.5` Commit List, `4.6.6` Merge Conflict Probe, `4.6.7` Pull Request Outcome, `4.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 4.6.3 populated but leave 4.6.4–4.6.6 explicitly empty per the template's empty-state lines.
212
225
 
213
226
  **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`).
214
227
 
@@ -278,6 +291,8 @@ Persistence steps that must be performed in Phase 7:
278
291
  - [ ] 6. **Generate final status file**: `runs/<task-type>/status/final-<task-type>-<seq>.status` (if necessary)
279
292
  - [ ] 7. **Save convergence state**: `runs/<task-type>/state/convergence-<task-type>-<seq>.json` (when convergence is enabled)
280
293
  - [ ] 8. **Spawn follow-up task stubs**: run `scripts/okstra-spawn-followups.py` against the final-report per the canonical spawn rule defined in "Phase 7 follow-up task spawner" above. Do not restate the trigger condition here — that section is the single source of truth. The script is idempotent across reruns.
294
+ - [ ] 9. **Slim AI report**: `runs/<task-type>/reports/final-report-<task-type>-<seq>.slim.md` (produced by Phase 7 step 1.5 — see "Phase 6 → Phase 7 execution sequence" above)
295
+ - [ ] 10. **Human HTML report**: `runs/<task-type>/reports/final-report-<task-type>-<seq>.html` (same step 1.5; self-contained, embeds `Export user response` button)
281
296
 
282
297
  ### Response after Persistence
283
298
 
@@ -213,7 +213,7 @@ Terminal statuses that can be recorded for a worker:
213
213
 
214
214
  **Authoritative source.** If other documents (SKILL.md, worker agent definitions) disagree with this section, this section wins.
215
215
 
216
- ### Result Frontmatter (mandatory, precedes Section 0)
216
+ ### Result Frontmatter (mandatory, precedes Section 1)
217
217
 
218
218
  Every worker result file MUST begin with a YAML frontmatter block. The values are sourced from the corresponding fields of the input files' frontmatter (e.g. `analysis-material.md`, `task-brief.md`) — copy them verbatim; do NOT regenerate them. Only `workerId` and `title` are worker-specific.
219
219
 
@@ -260,11 +260,8 @@ The same frontmatter contract applies to the `Report writer worker`'s final-repo
260
260
 
261
261
  A successful worker result must include the following sections in this exact order, beneath the frontmatter block:
262
262
 
263
- 0. **Reading Confirmation** one short line per input file stating that the worker read it end-to-end. Each line takes the form `- Read <file-name> end-to-end (<line-count> lines).`. The enumerated files are audience-scoped they MUST match the recipient's row in the "Audience-scoped enumeration" table above:
264
- - **Claude / Codex / Gemini analysis workers**: `task-brief.md`, `analysis-profile.md`, `analysis-material.md` (if present), `reference-expectations.md`, `clarification-response.md` (if a carry-in was provided). Analysis workers MUST NOT include `final-report-template.md` — it is not in their `[Required reading]` block.
265
- - **Report writer worker (Phase 6)**: all of the above **plus** `final-report-template.md`.
263
+ > **Reading Confirmation lives in the audit sidecar, NOT in the main worker result.** Section 0 is intentionally absent from the main file. The worker writes Reading Confirmation to `runs/<task-type>/worker-results/<worker>-audit-<task-type>-<seq>.md` per the dispatch-prompt clause above; the validator FAILS any main worker-result file that contains a `## 0. Reading Confirmation` heading. If a file was skipped or only partially read, the worker MUST NOT produce sections 1–5 — instead it records a `tool-failure` in the errors sidecar and stops.
266
264
 
267
- If a file was skipped or only partially read, the worker MUST NOT produce sections 1–5; instead it records a `tool-failure` in the errors sidecar and stops. This section exists specifically to counteract the common failure mode where workers skim long inputs because they share structure with the file the run will eventually write into.
268
265
  1. Findings
269
266
  2. Missing Information or Assumptions
270
267
  3. Safe or Reasonable Areas
@@ -33,6 +33,7 @@ Template authoring notes (NOT rendered to readers — HTML comments).
33
33
  - Task Key: {{TASK_KEY}}
34
34
  - Task Type: {{TASK_TYPE}}
35
35
  - Report Owner: `Claude lead`
36
+ - Report Author: `<Report writer worker | Claude lead (release-handoff or recorded-fallback only)>`
36
37
  - Lead Model: `{{LEAD_MODEL}}`
37
38
  - Okstra Version: `{{OKSTRA_VERSION}}`
38
39
 
@@ -360,7 +361,17 @@ H1 = `skip` 이거나 H3 = `cancel` 인 경우 4.6.4 ~ 4.6.6 은 빈 결과로
360
361
 
361
362
  commit 범위가 비어 있으면 release-handoff 는 실행되면 안 됩니다 → `- No implementation commits found; release-handoff is blocked.` + routing 을 `implementation` 으로 되돌림.
362
363
 
363
- ### 4.6.6 Pull Request Outcome (PR 결과)
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 결과)
364
375
 
365
376
  다음 네 가지 중 정확히 하나의 형식으로 한 줄:
366
377
 
@@ -369,7 +380,7 @@ commit 범위가 비어 있으면 release-handoff 는 실행되면 안 됩니다
369
380
  - `- PR reused: <url>` (run 시작 시 같은 head 의 open PR 이 이미 존재해 `gh pr create` 생략)
370
381
  - `- PR creation skipped: <reason>` (H3 = `cancel`, 또는 push/PR 도중 사용자 중단)
371
382
 
372
- ### 4.6.7 Routing Recommendation (마지막 phase 라우팅)
383
+ ### 4.6.8 Routing Recommendation (마지막 phase 라우팅)
373
384
 
374
385
  `release-handoff` 는 lifecycle 종착 phase 이므로 일반적으로 `done` 으로 라우팅합니다. H1 = `skip` 또는 H3 = `cancel` 로 종료된 경우 재진입 가능 여부를 한 줄로 명시합니다.
375
386
 
@@ -377,6 +388,162 @@ commit 범위가 비어 있으면 release-handoff 는 실행되면 안 됩니다
377
388
 
378
389
  <!-- /RENDER_IF (task-type == release-handoff) -->
379
390
 
391
+ <!-- RENDER_IF: task-type == implementation
392
+ Delete the entire `## 4.7` block + sub-sections otherwise. -->
393
+
394
+ ## 4.7 Implementation Deliverables
395
+
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`) 의 등장 여부를 검사합니다.
397
+
398
+ ### 4.7.1 Approved Plan Reference (승인된 계획 참조)
399
+
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)
407
+
408
+ | # | Short SHA | Full SHA | Plan Step | Subject | Files |
409
+ |---|-----------|----------|-----------|---------|-------|
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.` 한 줄.
413
+
414
+ ### 4.7.3 Diff Summary (변경 요약)
415
+
416
+ ```
417
+ <git diff --stat <base>..HEAD 의 raw 출력>
418
+ ```
419
+
420
+ 다음으로 file-by-file 한 줄 요약 표:
421
+
422
+ | File | Action | Lines (+/-) | Plan step / Out-of-plan |
423
+ |------|--------|-------------|--------------------------|
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 카테고리.
462
+
463
+ | Category | Rollback command | Verification | Result |
464
+ |----------|-------------------|---------------|--------|
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)>.`
478
+
479
+ <!-- /RENDER_IF (task-type == implementation) -->
480
+
481
+ <!-- RENDER_IF: task-type == final-verification
482
+ Delete the entire `## 4.8` block + sub-sections otherwise. -->
483
+
484
+ ## 4.8 Final Verification Deliverables
485
+
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` 중 하나인지 검사합니다.
487
+
488
+ ### 4.8.1 Source Implementation Report (선행 implementation 인용)
489
+
490
+ - Path (project-relative): `<runs/implementation/.../reports/final-report-implementation-<seq>.md>`
491
+ - 인용된 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>`
495
+ - `git status --short` (run 시작 시점):
496
+ ```
497
+ <원문 그대로>
498
+ ```
499
+
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` |
507
+
508
+ ### 4.8.3 Residual Risk (잔존 위험)
509
+
510
+ blocker 는 아니지만 추적해야 할 위험. 각 항목에 mitigation owner 와 blocker 로 격상되는 trigger 를 명시.
511
+
512
+ | ID | Item | Mitigation owner | Escalation trigger |
513
+ |----|------|------------------|---------------------|
514
+ | RR-001 | <한 줄> | <역할 / 팀> | <어떤 조건이면 AB-? 로 격상> |
515
+
516
+ 비어 있으면: `- 추적 대상 잔존 위험 없음.`
517
+
518
+ ### 4.8.4 Validation Evidence (요구사항 커버리지)
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 |
525
+
526
+ ### 4.8.5 Read-only Command Log (실행 명령 로그)
527
+
528
+ 본 run 에서 실행한 모든 명령 (Tier 1 plan validation + Tier 2 `project.json.qaCommands`) 을 실행 순서대로 한 row 씩 기록. mutating 명령은 등장하면 안 됨.
529
+
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
+
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) -->
546
+
380
547
  ## 5. Clarification Items
381
548
 
382
549
  다음 run 으로 넘어가기 전에 사용자가 답하거나 자료를 첨부해야 하는 항목을 **한 표 안에서** 추적합니다. `task-type` 이 `error-analysis` / `requirements-discovery` 이고 지금까지의 증거만으로 확신 있는 최종 판단이 어려울 때는 반드시 채웁니다. 그 외 task-type 에서는 lead 가 필요하다고 판단할 때만 채우고, 그렇지 않다면 `- 추가 정보 요청 없음. Section 2 의 최종 판단이 그대로 유효합니다.` 한 줄만 남깁니다.
@@ -0,0 +1,151 @@
1
+ /* Single self-contained stylesheet for the okstra final-report HTML view.
2
+ * Inlined verbatim by scripts/okstra_ctl/report_views.py render_html.
3
+ * No external @import, no url() references. System colors only so dark
4
+ * mode follows the user's OS without a media query toggle.
5
+ */
6
+
7
+ * { box-sizing: border-box; }
8
+
9
+ html { color-scheme: light dark; }
10
+
11
+ body {
12
+ margin: 0;
13
+ padding: 0;
14
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
15
+ font-size: 15px;
16
+ line-height: 1.55;
17
+ background: Canvas;
18
+ color: CanvasText;
19
+ }
20
+
21
+ .report-header,
22
+ .report-footer {
23
+ position: sticky;
24
+ display: flex;
25
+ align-items: center;
26
+ gap: 0.75rem;
27
+ padding: 0.6rem 1rem;
28
+ background: Canvas;
29
+ border-bottom: 1px solid GrayText;
30
+ z-index: 10;
31
+ }
32
+
33
+ .report-header { top: 0; }
34
+ .report-footer {
35
+ bottom: 0;
36
+ border-top: 1px solid GrayText;
37
+ border-bottom: none;
38
+ flex-wrap: wrap;
39
+ }
40
+
41
+ .report-header > div { flex: 1; font-weight: 600; }
42
+
43
+ main {
44
+ max-width: 80ch;
45
+ margin: 1.5rem auto;
46
+ padding: 0 1rem 4rem;
47
+ }
48
+
49
+ h1, h2, h3, h4, h5, h6 { line-height: 1.25; margin-top: 1.6em; margin-bottom: 0.4em; }
50
+ h1 { font-size: 1.7rem; }
51
+ h2 { font-size: 1.35rem; border-bottom: 1px solid GrayText; padding-bottom: 0.2em; }
52
+ h3 { font-size: 1.12rem; }
53
+ h4 { font-size: 1rem; }
54
+
55
+ p, ul, ol, blockquote, pre, table { margin: 0.6em 0; }
56
+
57
+ ul, ol { padding-left: 1.4em; }
58
+
59
+ blockquote {
60
+ border-left: 3px solid GrayText;
61
+ padding: 0.2em 0.8em;
62
+ color: GrayText;
63
+ }
64
+
65
+ code {
66
+ font-family: "SFMono-Regular", Menlo, Consolas, monospace;
67
+ font-size: 0.92em;
68
+ padding: 0.1em 0.35em;
69
+ background: color-mix(in srgb, CanvasText 8%, transparent);
70
+ border-radius: 3px;
71
+ }
72
+
73
+ pre {
74
+ overflow-x: auto;
75
+ padding: 0.8em 1em;
76
+ background: color-mix(in srgb, CanvasText 6%, transparent);
77
+ border-radius: 4px;
78
+ }
79
+ pre code { background: transparent; padding: 0; }
80
+
81
+ table {
82
+ width: 100%;
83
+ border-collapse: collapse;
84
+ font-size: 0.92rem;
85
+ }
86
+ th, td {
87
+ border: 1px solid color-mix(in srgb, GrayText 50%, transparent);
88
+ padding: 0.45em 0.6em;
89
+ vertical-align: top;
90
+ text-align: left;
91
+ }
92
+ thead th {
93
+ position: sticky;
94
+ top: 3rem;
95
+ background: Canvas;
96
+ z-index: 5;
97
+ }
98
+
99
+ tr[data-response-id] {
100
+ background: color-mix(in srgb, Highlight 6%, transparent);
101
+ }
102
+ tr[data-response-id][data-status="resolved"],
103
+ tr[data-response-id][data-status="obsolete"] {
104
+ background: transparent;
105
+ opacity: 0.65;
106
+ }
107
+
108
+ textarea {
109
+ width: 100%;
110
+ min-height: 2.2em;
111
+ font: inherit;
112
+ padding: 0.3em 0.4em;
113
+ border: 1px solid GrayText;
114
+ border-radius: 3px;
115
+ background: Canvas;
116
+ color: CanvasText;
117
+ resize: vertical;
118
+ }
119
+ textarea[disabled] { opacity: 0.55; }
120
+
121
+ button[data-action] {
122
+ font: inherit;
123
+ padding: 0.4em 0.9em;
124
+ border: 1px solid GrayText;
125
+ border-radius: 4px;
126
+ background: ButtonFace;
127
+ color: ButtonText;
128
+ cursor: pointer;
129
+ }
130
+ button[data-action]:hover { background: color-mix(in srgb, Highlight 20%, ButtonFace); }
131
+
132
+ #user-response-output {
133
+ flex-basis: 100%;
134
+ max-height: 14em;
135
+ overflow: auto;
136
+ margin: 0.6em 0 0;
137
+ padding: 0.6em 0.8em;
138
+ background: color-mix(in srgb, CanvasText 6%, transparent);
139
+ border-radius: 4px;
140
+ white-space: pre-wrap;
141
+ font-family: "SFMono-Regular", Menlo, Consolas, monospace;
142
+ font-size: 0.85rem;
143
+ }
144
+ #user-response-output:empty { display: none; }
145
+
146
+ @media print {
147
+ .report-header, .report-footer { position: static; }
148
+ thead th { position: static; }
149
+ button[data-action] { display: none; }
150
+ #user-response-output { display: none; }
151
+ }
@@ -0,0 +1,163 @@
1
+ /* Client-side glue for the okstra final-report HTML view.
2
+ *
3
+ * Responsibilities:
4
+ * 1. Collect <textarea> values for every <tr data-response-id> whose
5
+ * Status is open/answered (disabled rows are skipped automatically).
6
+ * 2. Serialise them into markdown whose bytes are IDENTICAL to
7
+ * scripts/okstra_ctl/report_views.py serialize_user_response.
8
+ * 3. Write the result to <pre id="user-response-output"> and offer a
9
+ * [Copy] button.
10
+ *
11
+ * The byte-identity contract is enforced by tests/test_report_views.py
12
+ * which spawns Node to execute buildUserResponseMarkdown and diffs the
13
+ * output against the Python function. If you edit the format here you
14
+ * MUST edit serialize_user_response too. The template at
15
+ * templates/reports/user-response.template.md documents the schema.
16
+ */
17
+
18
+ (function () {
19
+ "use strict";
20
+
21
+ function readRunMeta() {
22
+ var el = document.getElementById("run-meta");
23
+ if (!el) return {};
24
+ try {
25
+ return JSON.parse(el.textContent || "{}");
26
+ } catch (e) {
27
+ return {};
28
+ }
29
+ }
30
+
31
+ function isoNowUtc() {
32
+ return new Date().toISOString().replace(/\.\d+Z$/, "Z");
33
+ }
34
+
35
+ function trimMultiline(s) {
36
+ return String(s == null ? "" : s).replace(/^\s+|\s+$/g, "");
37
+ }
38
+
39
+ function collectEntries() {
40
+ var entries = [];
41
+ var rows = document.querySelectorAll("tr[data-response-id]");
42
+ for (var i = 0; i < rows.length; i++) {
43
+ var row = rows[i];
44
+ var ta = row.querySelector("textarea[data-response-id]");
45
+ if (!ta || ta.disabled) continue;
46
+ var value = trimMultiline(ta.value);
47
+ if (!value) continue;
48
+ entries.push({
49
+ responseId: row.getAttribute("data-response-id") || "",
50
+ kind: row.getAttribute("data-kind") || "",
51
+ value: value,
52
+ rationale: null,
53
+ });
54
+ }
55
+ return entries;
56
+ }
57
+
58
+ function buildUserResponseMarkdown(runMeta, entries, createdAt) {
59
+ var head =
60
+ "---\n" +
61
+ "task-key: " + (runMeta["task-key"] || "") + "\n" +
62
+ "task-type: " + (runMeta["task-type"] || "") + "\n" +
63
+ "seq: " + (runMeta["seq"] || "") + "\n" +
64
+ "source-report: " + (runMeta["source-report"] || "") + "\n" +
65
+ "created-by: user\n" +
66
+ "created-at: " + createdAt + "\n" +
67
+ "---\n" +
68
+ "\n" +
69
+ "# User Response\n";
70
+
71
+ if (!entries || entries.length === 0) {
72
+ return head + "\n_(No user responses recorded.)_\n";
73
+ }
74
+
75
+ var chunks = "";
76
+ for (var i = 0; i < entries.length; i++) {
77
+ var e = entries[i];
78
+ var chunk =
79
+ "\n## " + e.responseId + "\n" +
80
+ "- Kind: " + e.kind + "\n" +
81
+ "- Value:\n" +
82
+ " > " + trimMultiline(e.value) + "\n";
83
+ if (e.rationale) {
84
+ chunk += "- Rationale: " + trimMultiline(e.rationale) + "\n";
85
+ }
86
+ chunks += chunk;
87
+ }
88
+ return head + chunks;
89
+ }
90
+
91
+ function exportUserResponse() {
92
+ var runMeta = readRunMeta();
93
+ var entries = collectEntries();
94
+ var md = buildUserResponseMarkdown(runMeta, entries, isoNowUtc());
95
+ var out = document.getElementById("user-response-output");
96
+ if (out) out.textContent = md;
97
+ return md;
98
+ }
99
+
100
+ function copyUserResponse() {
101
+ var out = document.getElementById("user-response-output");
102
+ if (!out || !out.textContent) return;
103
+ var text = out.textContent;
104
+ if (navigator.clipboard && navigator.clipboard.writeText) {
105
+ navigator.clipboard.writeText(text).catch(function () {
106
+ fallbackCopy(text);
107
+ });
108
+ } else {
109
+ fallbackCopy(text);
110
+ }
111
+ }
112
+
113
+ function fallbackCopy(text) {
114
+ var ta = document.createElement("textarea");
115
+ ta.value = text;
116
+ ta.style.position = "fixed";
117
+ ta.style.opacity = "0";
118
+ document.body.appendChild(ta);
119
+ ta.select();
120
+ try { document.execCommand("copy"); } catch (e) {}
121
+ document.body.removeChild(ta);
122
+ }
123
+
124
+ function bind() {
125
+ var clickables = document.querySelectorAll("button[data-action]");
126
+ for (var i = 0; i < clickables.length; i++) {
127
+ var btn = clickables[i];
128
+ var action = btn.getAttribute("data-action");
129
+ if (action === "export-user-response") {
130
+ btn.addEventListener("click", exportUserResponse);
131
+ } else if (action === "copy-user-response") {
132
+ btn.addEventListener("click", copyUserResponse);
133
+ }
134
+ }
135
+ }
136
+
137
+ if (typeof window !== "undefined") {
138
+ if (document.readyState === "loading") {
139
+ document.addEventListener("DOMContentLoaded", bind);
140
+ } else {
141
+ bind();
142
+ }
143
+ // Expose for tests / debug.
144
+ window.okstraReportView = {
145
+ buildUserResponseMarkdown: buildUserResponseMarkdown,
146
+ collectEntries: collectEntries,
147
+ exportUserResponse: exportUserResponse,
148
+ };
149
+ }
150
+
151
+ // Node export for cross-impl byte-identity test. We expose on both
152
+ // CommonJS module.exports (works in `require()` callers) and
153
+ // globalThis (works under ESM where the parent uses `vm.runInThisContext`
154
+ // — see tests/test_report_views.py for the byte-identity harness).
155
+ if (typeof module !== "undefined" && module.exports) {
156
+ module.exports = { buildUserResponseMarkdown: buildUserResponseMarkdown };
157
+ }
158
+ if (typeof globalThis !== "undefined") {
159
+ globalThis.__okstraReportViewExports__ = {
160
+ buildUserResponseMarkdown: buildUserResponseMarkdown,
161
+ };
162
+ }
163
+ })();
@@ -0,0 +1,69 @@
1
+ <!-- single source of truth: scripts/okstra_ctl/report_views.py serialize_user_response -->
2
+ <!-- byte-identical client implementation: templates/reports/report.js buildUserResponseMarkdown -->
3
+
4
+ # User-Response Sidecar Template
5
+
6
+ 이 파일은 final-report HTML 의 **Export user response** 버튼이 만들어내는 markdown 의 표준 포맷을 정의합니다. 산출물은 `runs/<task-type>/user-responses/user-response-<task-type>-<seq>.md` 경로에 저장됩니다. 원본 final-report MD 는 어떤 경우에도 머지하지 않습니다.
7
+
8
+ ## Frontmatter 스키마
9
+
10
+ ```yaml
11
+ task-key: <task-group>/<task-id>
12
+ task-type: <requirements-discovery | error-analysis | implementation-planning | implementation | final-verification | release-handoff>
13
+ seq: <3-digit zero-padded run sequence>
14
+ source-report: <project-relative path to the final-report .md the HTML was derived from>
15
+ created-by: user
16
+ created-at: <ISO 8601 UTC timestamp>
17
+ ```
18
+
19
+ ## Body 스키마
20
+
21
+ 본문은 `# User Response` 단일 H1 헤딩 아래, 각 응답을 `## <Response ID>` 헤딩으로 구분합니다. Response ID 는 final-report `## 5. Clarification Items` 표의 `ID` 컬럼(`C-NNN`)을 그대로 인용합니다.
22
+
23
+ 각 응답 블록의 schema:
24
+
25
+ ```markdown
26
+ ## <Response ID>
27
+ - Kind: <material | decision | data-point>
28
+ - Value:
29
+ > <multi-line value, trimmed>
30
+ - Rationale: <optional one-line rationale>
31
+ ```
32
+
33
+ 빈 응답 집합(사용자가 어떤 행도 채우지 않고 Export 를 누른 경우)은 다음 한 줄을 본문에 출력합니다:
34
+
35
+ ```markdown
36
+ _(No user responses recorded.)_
37
+ ```
38
+
39
+ ## Example
40
+
41
+ ```markdown
42
+ ---
43
+ task-key: demo/T-1
44
+ task-type: implementation-planning
45
+ seq: 003
46
+ source-report: runs/implementation-planning/reports/final-report-implementation-planning-003.md
47
+ created-by: user
48
+ created-at: 2026-05-17T10:00:00Z
49
+ ---
50
+
51
+ # User Response
52
+
53
+ ## C-001
54
+ - Kind: decision
55
+ - Value:
56
+ > (a) 일회성. 재발 없음.
57
+ - Rationale: 결제 로그 재확인 결과 동일 패턴 미관측.
58
+
59
+ ## C-003
60
+ - Kind: data-point
61
+ - Value:
62
+ > (prediction=0: 1,204) (prediction=1: 38)
63
+ ```
64
+
65
+ ## 호환성 규칙
66
+
67
+ - `Kind` 가 알 수 없는 값이면 폼은 `<textarea>` fallback 으로 렌더되며, 직렬화 시 받은 `Kind` 문자열을 그대로 보존합니다.
68
+ - `Status` 가 `resolved` 또는 `obsolete` 인 행은 HTML 폼이 `disabled` 로 렌더되어 Export 결과에서 자동 제외됩니다. 그 행을 다시 열려면 final-report 의 `Status` 를 `open` / `answered` 로 되돌리고 보고서를 재생성해야 합니다.
69
+ - Python (`scripts/okstra_ctl/report_views.py` `serialize_user_response`) 과 JavaScript (`templates/reports/report.js` `buildUserResponseMarkdown`) 의 출력은 **byte-identical** 이어야 합니다. `tests/test_report_views.py` 가 두 구현의 동일성을 검증합니다.