okstra 0.102.2 → 0.102.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/README.kr.md +5 -5
  2. package/README.md +5 -5
  3. package/docs/kr/architecture/storage-model.md +1 -1
  4. package/docs/kr/architecture.md +7 -11
  5. package/docs/kr/cli.md +4 -4
  6. package/docs/kr/container.md +1 -1
  7. package/docs/kr/performance-improvement-plan-v2.md +2 -2
  8. package/docs/project-structure-overview.md +2 -2
  9. package/package.json +7 -2
  10. package/runtime/BUILD.json +2 -2
  11. package/runtime/bin/okstra-antigravity-exec.sh +1 -2
  12. package/runtime/bin/okstra-codex-exec.sh +1 -2
  13. package/runtime/bin/okstra-render-final-report.py +2 -9
  14. package/runtime/bin/okstra-spawn-followups.py +4 -6
  15. package/runtime/bin/okstra-trace-cleanup.sh +24 -4
  16. package/runtime/prompts/lead/okstra-lead-contract.md +2 -2
  17. package/runtime/prompts/lead/report-writer.md +1 -1
  18. package/runtime/prompts/profiles/_common-contract.md +9 -11
  19. package/runtime/python/okstra_ctl/codex_dispatch.py +4 -12
  20. package/runtime/python/okstra_ctl/consumers.py +35 -0
  21. package/runtime/python/okstra_ctl/container.py +4 -6
  22. package/runtime/python/okstra_ctl/dispatch_core.py +4 -12
  23. package/runtime/python/okstra_ctl/final_report_paths.py +24 -0
  24. package/runtime/python/okstra_ctl/handoff.py +2 -1
  25. package/runtime/python/okstra_ctl/implementation_stage.py +7 -11
  26. package/runtime/python/okstra_ctl/report_view_artifacts.py +14 -0
  27. package/runtime/python/okstra_ctl/report_views.py +7 -4
  28. package/runtime/python/okstra_ctl/run.py +3 -10
  29. package/runtime/python/okstra_ctl/stage_targets.py +18 -0
  30. package/runtime/python/okstra_ctl/wizard.py +24 -25
  31. package/runtime/python/okstra_ctl/wizard_stage_intent.py +54 -0
  32. package/runtime/python/okstra_token_usage/report.py +2 -5
  33. package/runtime/validators/validate-report-views.py +2 -1
  34. package/runtime/validators/validate-run.py +1 -11
  35. package/src/commands/lifecycle/install.mjs +5 -5
  36. package/src/commands/lifecycle/uninstall.mjs +4 -4
  37. package/docs/superpowers/plans/2026-05-12-ticket-id-in-reports.md +0 -638
  38. package/docs/superpowers/plans/2026-05-14-convergence-queue-pruning.md +0 -1568
  39. package/docs/superpowers/plans/2026-05-17-dual-format-final-report.md +0 -167
  40. package/docs/superpowers/plans/2026-05-20-final-report-language.md +0 -1501
  41. package/docs/superpowers/plans/2026-05-20-implementation-planning-multi-stage.md +0 -1267
  42. package/docs/superpowers/plans/2026-05-20-okstra-run-prompt-sot-b1.md +0 -1007
  43. package/docs/superpowers/plans/2026-05-20-wizard-messages-json-sot.md +0 -720
  44. package/docs/superpowers/plans/2026-05-20-wizard-prompt-json-sot-a1.md +0 -681
  45. package/docs/superpowers/plans/2026-05-21-improvement-discovery-task-type.md +0 -1691
  46. package/docs/superpowers/plans/2026-05-24-implementation-lead-context-slimming.md +0 -1700
  47. package/docs/superpowers/plans/2026-05-25-okstra-project-root-rename.md +0 -158
  48. package/docs/superpowers/plans/2026-05-26-wizard-3-option-picker.md +0 -860
  49. package/docs/superpowers/plans/2026-06-02-final-verification-protocol-hardening.md +0 -326
  50. package/docs/superpowers/plans/2026-06-02-okstra-run-branch-confirm-step.md +0 -337
  51. package/docs/superpowers/plans/2026-06-02-okstra-run-phase-pane-cleanup.md +0 -410
  52. package/docs/superpowers/plans/2026-06-02-requirements-discovery-fanout.md +0 -728
  53. package/docs/superpowers/plans/2026-06-04-adversarial-implementation-planning.md +0 -294
  54. package/docs/superpowers/plans/2026-06-04-adversarial-verification.md +0 -570
  55. package/docs/superpowers/plans/2026-06-04-coverage-critic.md +0 -516
  56. package/docs/superpowers/plans/2026-06-04-stage-cohesion-planner.md +0 -351
  57. package/docs/superpowers/plans/2026-06-04-stage-run-batching.md +0 -457
  58. package/docs/superpowers/plans/2026-06-05-acceptance-critic.md +0 -251
  59. package/docs/superpowers/plans/2026-06-05-compact-markdown-report-tables.md +0 -323
  60. package/docs/superpowers/plans/2026-06-05-wizard-batch-prompts.md +0 -559
  61. package/docs/superpowers/plans/2026-06-06-final-verification-whole-task-gate.md +0 -993
  62. package/docs/superpowers/plans/2026-06-06-stage-parallel-and-pending-fixes.md +0 -93
  63. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p1.md +0 -447
  64. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p2.md +0 -289
  65. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p3.md +0 -774
  66. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p4.md +0 -303
  67. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p5-multidep-base.md +0 -387
  68. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase2.md +0 -275
  69. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase3.md +0 -282
  70. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4a.md +0 -147
  71. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4b.md +0 -262
  72. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4c.md +0 -184
  73. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4d.md +0 -88
  74. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4e.md +0 -250
  75. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa.md +0 -409
  76. package/docs/superpowers/plans/2026-06-09-implementation-run-artifact-stage-isolation.md +0 -320
  77. package/docs/superpowers/plans/2026-06-10-concurrent-run-team-guard.md +0 -456
  78. package/docs/superpowers/plans/2026-06-10-git-reconcile-stale-sha-recovery.md +0 -1408
  79. package/docs/superpowers/plans/2026-06-10-lead-worker-completion-polling-PROBE.md +0 -42
  80. package/docs/superpowers/plans/2026-06-10-lead-worker-completion-polling.md +0 -337
  81. package/docs/superpowers/plans/2026-06-10-p6-token-usage-incremental.md +0 -1029
  82. package/docs/superpowers/plans/2026-06-10-stage-group-handoff.md +0 -1572
  83. package/docs/superpowers/plans/2026-06-11-fix-cycle.md +0 -1290
  84. package/docs/superpowers/plans/2026-06-11-wizard-whole-task-final-verification.md +0 -526
  85. package/docs/superpowers/plans/2026-06-12-html-plan-approval.md +0 -1000
  86. package/docs/superpowers/plans/2026-06-13-repo-risk-hardening.md +0 -493
  87. package/docs/superpowers/plans/2026-06-14-host-runtime-auto-execution.md +0 -1775
  88. package/docs/superpowers/plans/2026-06-15-coding-preflight-pack-dispatch-path.md +0 -504
  89. package/docs/superpowers/plans/2026-06-15-internal-skill-migration-final-fixups.md +0 -342
  90. package/docs/superpowers/plans/2026-06-15-internal-skill-migration-fixups.md +0 -258
  91. package/docs/superpowers/plans/2026-06-15-internal-skill-migration-remaining-fixups.md +0 -387
  92. package/docs/superpowers/plans/2026-06-15-internal-skill-resource-migration.md +0 -749
  93. package/docs/superpowers/plans/2026-06-15-worker-prompt-anchor-final-fixups.md +0 -828
  94. package/docs/superpowers/plans/2026-06-15-worker-prompt-header-error-contract.md +0 -490
  95. package/docs/superpowers/plans/2026-06-17-okstra-error-report.md +0 -724
  96. package/docs/superpowers/plans/2026-06-18-subagent-pane-reclaim.md +0 -483
  97. package/docs/superpowers/plans/2026-06-19-agent-operating-standard.md +0 -476
  98. package/docs/superpowers/plans/2026-06-19-okstra-inspect-recap-facet.md +0 -647
  99. package/docs/superpowers/plans/2026-06-20-error-feedback-loop.md +0 -1019
  100. package/docs/superpowers/plans/2026-06-20-run-index-row-normalization.md +0 -583
  101. package/docs/superpowers/plans/2026-06-20-stage-auto-integrate-teardown.md +0 -819
  102. package/docs/superpowers/plans/2026-06-21-okstra-container-local-user-test.md +0 -714
  103. package/docs/superpowers/plans/2026-06-21-release-handoff-local-checkout.md +0 -348
  104. package/docs/superpowers/plans/2026-06-23-implementation-stage-multiselect-chain.md +0 -630
  105. package/docs/superpowers/plans/2026-06-23-implementation-step-commit-granularity.md +0 -274
  106. package/docs/superpowers/plans/2026-06-23-phase-batch-cleanup-enforcement.md +0 -409
  107. package/docs/superpowers/plans/2026-06-24-cross-project-precondition.md +0 -542
  108. package/docs/superpowers/plans/2026-06-24-decision-drafts-render-gap.md +0 -568
  109. package/docs/superpowers/plans/2026-06-24-manual-user-test-section.md +0 -203
  110. package/docs/superpowers/plans/2026-06-24-okstra-container-build-rename.md +0 -525
  111. package/docs/superpowers/specs/2026-05-12-ticket-id-in-reports-design.md +0 -131
  112. package/docs/superpowers/specs/2026-05-15-implementation-plan-verification-design.md +0 -254
  113. package/docs/superpowers/specs/2026-05-20-final-report-language-design.md +0 -383
  114. package/docs/superpowers/specs/2026-05-20-implementation-planning-multi-stage-design.md +0 -322
  115. package/docs/superpowers/specs/2026-05-20-okstra-run-prompt-sot-design.md +0 -299
  116. package/docs/superpowers/specs/2026-05-21-improvement-discovery-task-type-design.md +0 -335
  117. package/docs/superpowers/specs/2026-06-02-okstra-run-branch-confirm-step-design.md +0 -113
  118. package/docs/superpowers/specs/2026-06-02-okstra-run-phase-pane-cleanup-design.md +0 -173
  119. package/docs/superpowers/specs/2026-06-02-requirements-discovery-fanout-design.md +0 -154
  120. package/docs/superpowers/specs/2026-06-04-adversarial-implementation-planning-design.md +0 -90
  121. package/docs/superpowers/specs/2026-06-04-adversarial-verification-design.md +0 -176
  122. package/docs/superpowers/specs/2026-06-04-coverage-critic-design.md +0 -99
  123. package/docs/superpowers/specs/2026-06-04-stage-splitting-cost-aware-design.md +0 -98
  124. package/docs/superpowers/specs/2026-06-05-acceptance-critic-design.md +0 -90
  125. package/docs/superpowers/specs/2026-06-05-compact-markdown-report-tables-design.md +0 -87
  126. package/docs/superpowers/specs/2026-06-05-wizard-batch-prompts-design.md +0 -121
  127. package/docs/superpowers/specs/2026-06-06-final-verification-whole-task-gate-design.md +0 -126
  128. package/docs/superpowers/specs/2026-06-06-stage-worktree-isolation-design.md +0 -180
  129. package/docs/superpowers/specs/2026-06-06-vertical-slice-tdd-planning-design.md +0 -179
  130. package/docs/superpowers/specs/2026-06-07-stage-conformance-qa-design.md +0 -169
  131. package/docs/superpowers/specs/2026-06-09-executor-model-custom-id-cascade-design.md +0 -66
  132. package/docs/superpowers/specs/2026-06-09-implementation-run-artifact-stage-isolation-design.md +0 -87
  133. package/docs/superpowers/specs/2026-06-10-blocking-contract-posthoc-conformance-design.md +0 -168
  134. package/docs/superpowers/specs/2026-06-10-concurrent-run-team-guard-design.md +0 -107
  135. package/docs/superpowers/specs/2026-06-10-git-reconcile-stale-sha-recovery-design.md +0 -105
  136. package/docs/superpowers/specs/2026-06-10-lead-worker-completion-polling-design.md +0 -113
  137. package/docs/superpowers/specs/2026-06-10-stage-group-handoff-design.md +0 -156
  138. package/docs/superpowers/specs/2026-06-11-brief-entry-only-handoff-stage-entry-design.md +0 -158
  139. package/docs/superpowers/specs/2026-06-11-fix-cycle-design.md +0 -94
  140. package/docs/superpowers/specs/2026-06-11-wizard-whole-task-final-verification-design.md +0 -89
  141. package/docs/superpowers/specs/2026-06-12-codex-lead-adapter-design.md +0 -358
  142. package/docs/superpowers/specs/2026-06-12-html-plan-approval-design.md +0 -85
  143. package/docs/superpowers/specs/2026-06-12-stage-discipline-rule-design.md +0 -108
  144. package/docs/superpowers/specs/2026-06-13-forbidden-actions-ssot-design.md +0 -134
  145. package/docs/superpowers/specs/2026-06-13-neutral-tmux-lead-adapter-design.md +0 -284
  146. package/docs/superpowers/specs/2026-06-14-host-runtime-auto-execution-design.md +0 -404
  147. package/docs/superpowers/specs/2026-06-17-okstra-error-report-design.md +0 -123
  148. package/docs/superpowers/specs/2026-06-18-subagent-pane-reclaim-design.md +0 -150
  149. package/docs/superpowers/specs/2026-06-19-agent-operating-standard-design.md +0 -120
  150. package/docs/superpowers/specs/2026-06-19-okstra-inspect-recap-facet-design.md +0 -96
  151. package/docs/superpowers/specs/2026-06-20-error-feedback-loop-design.md +0 -171
  152. package/docs/superpowers/specs/2026-06-20-run-index-row-normalization-design.md +0 -112
  153. package/docs/superpowers/specs/2026-06-20-stage-auto-integrate-teardown-design.md +0 -145
  154. package/docs/superpowers/specs/2026-06-21-okstra-container-local-user-test-design.md +0 -125
  155. package/docs/superpowers/specs/2026-06-21-release-handoff-local-checkout-design.md +0 -113
  156. package/docs/superpowers/specs/2026-06-23-implementation-stage-multiselect-chain-design.md +0 -138
  157. package/docs/superpowers/specs/2026-06-23-implementation-step-commit-granularity-design.md +0 -120
  158. package/docs/superpowers/specs/2026-06-24-cross-project-precondition-design.md +0 -129
  159. package/docs/superpowers/specs/2026-06-24-decision-drafts-render-gap-design.md +0 -185
  160. package/docs/superpowers/specs/2026-06-24-manual-user-test-section-design.md +0 -121
  161. package/docs/superpowers/specs/2026-06-24-okstra-container-build-rename-design.md +0 -110
@@ -1,630 +0,0 @@
1
- # implementation stage 다중선택 + 무인 연쇄 구현 계획
2
-
3
- > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
-
5
- **Goal:** okstra-run 의 implementation stage picker 에 진행 상태를 표시하고, 여러 stage 를 체크박스로 골라 현재 세션이 의존성 순서대로 무인 연쇄 실행하게 한다.
6
-
7
- **Architecture:** wizard picker(입력 수집)와 SKILL.md(오케스트레이션)만 확장한다. stage 상태·ready·의존성 판정은 prepare 측 SSOT(`stage_targets.py`/`consumers.py`/`worktree_registry.py`)를 재사용하고, "1 run = 1 stage" 격리(worktree·registry 예약·base commit)는 불변으로 둔다. 연쇄는 단일-stage run 을 N 회 순차 실행하는 것이다.
8
-
9
- **Tech Stack:** Python 3 (`scripts/okstra_ctl/`), pytest(`tests/`), Markdown 스킬(`skills/okstra-run/SKILL.md`), JSON 프롬프트(`prompts/wizard/prompts.ko.json`), 빌드(`tools/build.mjs` → `npm run build`).
10
-
11
- ## Global Constraints
12
-
13
- - `runtime/` 은 빌드 산출물 — 직접 편집 금지. 소스 변경 후 `npm run build` 로 동기화.
14
- - "1 run = 1 stage" 격리(stage worktree·registry stage-key 예약·base commit 계산)는 변경 금지.
15
- - 비대화형 CLI `--stage <auto|N>` 단일-stage 의미 보존. 다중선택은 대화형 wizard picker 에만 추가.
16
- - stage 상태/의존성 판정은 prepare SSOT 재사용 — wizard 가 자체 재구현 금지.
17
- - pre-1.0: 호환 shim 없이 단일선택 동작을 다중선택으로 교체(단일 stage 1개 선택 = 기존 동작 동치).
18
- - 문서/주석 산문은 한국어, 코드 식별자·경로·CLI 인자는 영어.
19
- - 커밋: Conventional Commits(`type(scope): 한 문장`), AI attribution trailer 금지. 동시 세션 환경이므로 amend 금지 — 항상 새 커밋.
20
- - 완료 판정 CI: `npm run check`(exit 0).
21
-
22
- ---
23
-
24
- ## Task 1: stage 상태 집합 헬퍼 (started / reserved)
25
-
26
- 기능 A·B 가 공유할, "현재 started / reserved 인 stage 번호 집합" 읽기 헬퍼를 추가한다. done 은 기존 [`_done_stage_numbers`](../../scripts/okstra_ctl/wizard.py:961)가 이미 제공한다.
27
-
28
- **Files:**
29
- - Modify: `scripts/okstra_ctl/wizard.py` (`_done_stage_numbers` 바로 아래, [wizard.py:976](../../scripts/okstra_ctl/wizard.py:976) 부근)
30
- - Test: `tests/wizard/test_wizard_stage_pick.py`
31
-
32
- **Interfaces:**
33
- - Consumes: `read_consumers(plan_run_root)` ([consumers.py:24](../../scripts/okstra_ctl/consumers.py:24)), `worktree_registry.list_active_stage_numbers(project_id, task_group, task_id)` ([worktree_registry.py:278](../../scripts/okstra_ctl/worktree_registry.py:278)).
34
- - Produces: `_started_stage_numbers(state: WizardState) -> set[int]`, `_reserved_stage_numbers(state: WizardState) -> set[int]`. Task 2·4 가 호출한다.
35
-
36
- - [ ] **Step 1: Write the failing test**
37
-
38
- `tests/wizard/test_wizard_stage_pick.py` 에 추가. 기존 테스트의 state/plan 픽스처 헬퍼를 재사용한다(파일 상단의 기존 헬퍼 확인). consumers.jsonl 에 started 행을 쓰고, started 집합에 잡히는지 본다.
39
-
40
- ```python
41
- def test_started_stage_numbers_reads_started_rows(tmp_path, monkeypatch):
42
- state = _impl_state_with_plan(tmp_path) # 기존 헬퍼 패턴 재사용
43
- run_root = Path(state.approved_plan_path).resolve().parents[1]
44
- from okstra_ctl.consumers import append_consumer
45
- append_consumer(run_root, impl_task_key="k#stage-2", stage=2, status="started")
46
- from okstra_ctl.wizard import _started_stage_numbers
47
- assert _started_stage_numbers(state) == {2}
48
- ```
49
-
50
- - [ ] **Step 2: Run test to verify it fails**
51
-
52
- Run: `python3 -m pytest tests/wizard/test_wizard_stage_pick.py::test_started_stage_numbers_reads_started_rows -v`
53
- Expected: FAIL — `ImportError: cannot import name '_started_stage_numbers'`
54
-
55
- - [ ] **Step 3: Write minimal implementation**
56
-
57
- `scripts/okstra_ctl/wizard.py`, `_done_stage_numbers` 아래에 추가. `_done_stage_numbers` 의 순수-읽기 규약(carry backfill 금지)을 동일하게 따른다.
58
-
59
- ```python
60
- def _started_stage_numbers(state: WizardState) -> set:
61
- """approved plan 을 소비한 run 들의 consumers.jsonl 에서 status:started 인
62
- stage 번호 집합. _done_stage_numbers 와 같은 순수 읽기 — carry backfill 금지."""
63
- if not state.approved_plan_path:
64
- return set()
65
- from .consumers import read_consumers
66
- plan_run_root = Path(state.approved_plan_path).resolve().parents[1]
67
- rows = read_consumers(plan_run_root)
68
- return {r["stage"] for r in rows
69
- if r.get("status") == "started" and isinstance(r.get("stage"), int)}
70
-
71
-
72
- def _reserved_stage_numbers(state: WizardState) -> set:
73
- """worktree-registry 가 active 로 잡고 있는 stage 번호 집합(점유 SSOT).
74
- implementation prepare 와 동일 인자로 list_active_stage_numbers 를 호출한다."""
75
- if not (state.project_id and state.task_group and state.task_id):
76
- return set()
77
- from .worktree_registry import list_active_stage_numbers
78
- return list_active_stage_numbers(
79
- state.project_id, state.task_group, state.task_id)
80
- ```
81
-
82
- - [ ] **Step 4: Run test to verify it passes**
83
-
84
- Run: `python3 -m pytest tests/wizard/test_wizard_stage_pick.py::test_started_stage_numbers_reads_started_rows -v`
85
- Expected: PASS
86
-
87
- - [ ] **Step 5: Commit**
88
-
89
- ```bash
90
- git add scripts/okstra_ctl/wizard.py tests/wizard/test_wizard_stage_pick.py
91
- git commit -m "feat(wizard): add started/reserved stage-number helpers for stage picker"
92
- ```
93
-
94
- ---
95
-
96
- ## Task 2: 기능 A — implementation picker 진행 상태 마커
97
-
98
- implementation picker 의 각 stage 옵션 뒤에 `[완료]/[진행중]/[준비됨]/[대기]` 마커를 붙인다. final-verification 의 `[done]/[미완]` 은 변경하지 않는다.
99
-
100
- **Files:**
101
- - Modify: `prompts/wizard/prompts.ko.json` ([prompts.ko.json:240](../../prompts/wizard/prompts.ko.json:240) `stage_pick.options`)
102
- - Modify: `scripts/okstra_ctl/wizard.py` (`_build_stage_pick` [wizard.py:1812](../../scripts/okstra_ctl/wizard.py:1812))
103
- - Test: `tests/wizard/test_wizard_stage_pick.py`
104
-
105
- **Interfaces:**
106
- - Consumes: `_done_stage_numbers` ([wizard.py:961](../../scripts/okstra_ctl/wizard.py:961)), `_started_stage_numbers`/`_reserved_stage_numbers` (Task 1), ready 판정식(`all(d in done for d in s.depends_on)`, [stage_targets.py:83](../../scripts/okstra_ctl/stage_targets.py:83)).
107
- - Produces: implementation 분기에서 마커가 붙은 옵션 라벨. Task 4 의 multi picker 가 같은 build 를 확장한다.
108
-
109
- - [ ] **Step 1: Write the failing test**
110
-
111
- ```python
112
- def test_impl_stage_pick_shows_status_markers(tmp_path):
113
- # stage 1 done, stage 2 ready(deps=[1] 충족), stage 3 대기(deps=[2] 미충족)
114
- state = _impl_state_with_plan(tmp_path, stages=[(1, []), (2, [1]), (3, [2])])
115
- run_root = Path(state.approved_plan_path).resolve().parents[1]
116
- from okstra_ctl.consumers import append_consumer
117
- append_consumer(run_root, impl_task_key="k#stage-1", stage=1, status="done",
118
- head_commit="abc")
119
- from okstra_ctl.wizard import _build_stage_pick
120
- labels = {o.value: o.label for o in _build_stage_pick(state).options}
121
- assert "[완료]" in labels["1"]
122
- assert "[준비됨]" in labels["2"]
123
- assert "[대기]" in labels["3"]
124
- ```
125
-
126
- - [ ] **Step 2: Run test to verify it fails**
127
-
128
- Run: `python3 -m pytest tests/wizard/test_wizard_stage_pick.py::test_impl_stage_pick_shows_status_markers -v`
129
- Expected: FAIL — 라벨에 마커 없음(AssertionError)
130
-
131
- - [ ] **Step 3: Add marker strings to prompts.ko.json**
132
-
133
- `prompts/wizard/prompts.ko.json` 의 `stage_pick.options` 에 4종 추가(기존 `done_mark`/`undone_mark` 유지):
134
-
135
- ```json
136
- "options": {
137
- "auto": "auto (다음 미완료 stage)",
138
- "whole_task": "전체 task 검증 (모든 stage)",
139
- "all_stages": "전체 (남은 미완료 stage 모두)",
140
- "done_mark": "[done]",
141
- "undone_mark": "[미완]",
142
- "mark_done": "[완료]",
143
- "mark_active": "[진행중]",
144
- "mark_ready": "[준비됨]",
145
- "mark_blocked": "[대기]"
146
- }
147
- ```
148
-
149
- - [ ] **Step 4: Implement markers in `_build_stage_pick`**
150
-
151
- `scripts/okstra_ctl/wizard.py` 의 `_build_stage_pick` 에서, implementation 일 때 상태 집합을 구해 suffix 를 결정한다. 기존 `is_fv` 분기는 그대로 둔다.
152
-
153
- ```python
154
- def _build_stage_pick(state: WizardState) -> Prompt:
155
- """Parse the Stage Map from the approved plan and build the stage picker."""
156
- t = _p(state.workspace_root, "stage_pick")
157
- stages = _parse_stage_objects(state)
158
- is_fv = state.task_type == "final-verification"
159
- is_impl = state.task_type == "implementation"
160
- label = (t["label_final_verification"] or t["label"]) if is_fv else t["label"]
161
- done = _done_stage_numbers(state)
162
- started = _started_stage_numbers(state) if is_impl else set()
163
- reserved = _reserved_stage_numbers(state) if is_impl else set()
164
- options = []
165
- if _whole_task_allowed(state, stages=stages, done=done):
166
- options.append(_opt(WHOLE_TASK_STAGE, t["options"]["whole_task"]))
167
- if is_impl:
168
- options.append(_opt(ALL_STAGES, t["options"]["all_stages"]))
169
- for s in stages:
170
- depends = ",".join(map(str, s.depends_on)) or "(none)"
171
- suffix = ""
172
- if is_fv:
173
- suffix = " " + (t["options"]["done_mark"]
174
- if s.stage_number in done
175
- else t["options"]["undone_mark"])
176
- elif is_impl:
177
- suffix = " " + _impl_stage_marker(t, s, done, started, reserved)
178
- options.append(_opt(
179
- str(s.stage_number),
180
- f"{s.stage_number}: {s.title} "
181
- f"[depends-on: {depends} | steps: {s.step_count}]{suffix}",
182
- ))
183
- return Prompt(
184
- step=S_STAGE_PICK, kind="pick", multi=is_impl,
185
- label=label,
186
- options=options,
187
- echo_template=t["echo_template"],
188
- )
189
-
190
-
191
- def _impl_stage_marker(t, stage, done, started, reserved) -> str:
192
- n = stage.stage_number
193
- if n in done:
194
- return t["options"]["mark_done"]
195
- if n in started or n in reserved:
196
- return t["options"]["mark_active"]
197
- if all(d in done for d in stage.depends_on):
198
- return t["options"]["mark_ready"]
199
- return t["options"]["mark_blocked"]
200
- ```
201
-
202
- 참고: `_stage_auto_allowed` 기반 auto 옵션 추가([wizard.py:1820-1821](../../scripts/okstra_ctl/wizard.py:1820))는 이 교체로 제거된다(Task 4 의 "auto 제거" 결정과 일치). `multi=is_impl` 도 여기서 함께 켜진다(Task 4 가 submit 으로 완성). `ALL_STAGES` 상수는 Task 4 Step 3 에서 정의하므로, 이 task 만 단독 실행 시 `ALL_STAGES` 정의를 [wizard.py:1636](../../scripts/okstra_ctl/wizard.py:1636) `WHOLE_TASK_STAGE` 옆에 먼저 추가한다: `ALL_STAGES = "__all_stages__"`.
203
-
204
- - [ ] **Step 5: Update the auto-asserting test, then run**
205
-
206
- 기존 `test_stage_pick_option_list_includes_auto_and_all_stages`([tests/wizard/test_wizard_stage_pick.py:53](../../tests/wizard/test_wizard_stage_pick.py:53))는 implementation picker 에 `auto` 가 있다고 단언한다(이름과 달리 실제로는 auto+stage 번호만 검증). 이 task 가 auto 를 제거하므로 갱신한다: `assert "auto" not in values`, `assert wizard.ALL_STAGES in values`, `assert {"1","2","3"} <= set(values)` (테스트명도 `..._excludes_auto_includes_all` 로). final-verification 테스트([:72](../../tests/wizard/test_wizard_stage_pick.py:72))는 불변.
207
-
208
- Run: `python3 -m pytest tests/wizard/test_wizard_stage_pick.py -v`
209
- Expected: 마커 테스트 + 갱신한 옵션 테스트 PASS. submit 계열 테스트(`stores_auto`/`stores_numeric_stage`/`rejects_empty`/corrupt-envelope)는 이 시점에 깨질 수 있고 Task 4 에서 갱신된다(같은 PR 연속 task) — 그대로 두고 진행.
210
-
211
- - [ ] **Step 6: Commit**
212
-
213
- ```bash
214
- git add scripts/okstra_ctl/wizard.py prompts/wizard/prompts.ko.json tests/wizard/test_wizard_stage_pick.py
215
- git commit -m "feat(wizard): show stage progress markers in implementation picker"
216
- ```
217
-
218
- ---
219
-
220
- ## Task 3: 의존성 closure + 위상정렬 헬퍼 (stage_targets.py)
221
-
222
- 선택한 stage 집합에 미완료 의존성을 자동 포함하고 의존성 순서로 정렬하는 순수 함수를 SSOT 모듈에 추가한다.
223
-
224
- **Files:**
225
- - Modify: `scripts/okstra_ctl/stage_targets.py` (모듈 함수, [stage_targets.py:97](../../scripts/okstra_ctl/stage_targets.py:97) 아래)
226
- - Test: `tests/run/test_stage_targets_closure.py` (없으면 생성; 기존 `tests/run/` 도메인 사용)
227
-
228
- **Interfaces:**
229
- - Produces: `order_stage_closure(stages, selected, done) -> list[int]` — `stages` 는 `(stage_number:int, depends_on:list[int])` 시퀀스, `selected`/`done` 은 `set[int]`. 반환: selected ∪ (미완료 의존성 closure) 를 위상순서(번호 오름차순 안정)로 정렬한 리스트. Task 4 가 wizard 에서 호출한다.
230
-
231
- - [ ] **Step 1: Write the failing test**
232
-
233
- ```python
234
- def test_order_stage_closure_pulls_unmet_deps_in_order():
235
- from okstra_ctl.stage_targets import order_stage_closure
236
- stages = [(1, []), (2, [1]), (3, [2]), (4, [2])]
237
- # 4 를 골랐고 2 는 미완료, 1 은 done → closure 는 {2,4}, 위상순서 [2,4]
238
- assert order_stage_closure(stages, selected={4}, done={1}) == [2, 4]
239
-
240
-
241
- def test_order_stage_closure_drops_done_deps():
242
- from okstra_ctl.stage_targets import order_stage_closure
243
- stages = [(1, []), (2, [1])]
244
- # 2 선택, 1 done → 1 은 closure 에 안 들어감
245
- assert order_stage_closure(stages, selected={2}, done={1}) == [2]
246
- ```
247
-
248
- - [ ] **Step 2: Run test to verify it fails**
249
-
250
- Run: `python3 -m pytest tests/run/test_stage_targets_closure.py -v`
251
- Expected: FAIL — `ImportError: cannot import name 'order_stage_closure'`
252
-
253
- - [ ] **Step 3: Write minimal implementation**
254
-
255
- `scripts/okstra_ctl/stage_targets.py` 의 `resolve_effective_stages` 아래에 추가. Stage Map 번호가 1..N 단조임이 validator 에서 이미 보장되므로 순환은 없다 — 단순 closure + 번호 정렬로 위상순서를 만든다(작은 번호가 큰 번호에만 의존).
256
-
257
- ```python
258
- def order_stage_closure(
259
- stages, selected: set[int], done: set[int],
260
- ) -> list[int]:
261
- """selected ∪ (미완료 depends-on closure) 를 의존성 순서로 정렬해 반환.
262
-
263
- Stage Map 번호는 validator 가 1..N 단조로 강제하므로 순환이 없다.
264
- 번호 오름차순이 곧 위상순서다(낮은 번호가 선행)."""
265
- deps = {n: list(d) for n, d in stages}
266
- want = set(selected)
267
- frontier = list(selected)
268
- while frontier:
269
- n = frontier.pop()
270
- for d in deps.get(n, []):
271
- if d not in done and d not in want:
272
- want.add(d)
273
- frontier.append(d)
274
- return sorted(want)
275
- ```
276
-
277
- - [ ] **Step 4: Run test to verify it passes**
278
-
279
- Run: `python3 -m pytest tests/run/test_stage_targets_closure.py -v`
280
- Expected: PASS
281
-
282
- - [ ] **Step 5: Commit**
283
-
284
- ```bash
285
- git add scripts/okstra_ctl/stage_targets.py tests/run/test_stage_targets_closure.py
286
- git commit -m "feat(stage-targets): add dependency-closure topological ordering helper"
287
- ```
288
-
289
- ---
290
-
291
- ## Task 4: 기능 B — picker 다중선택 + submit 정규화
292
-
293
- implementation picker 를 체크박스로 만들고, submit 에서 점유 stage 거부 → closure → 위상정렬 → `selected_stages` 저장.
294
-
295
- **Files:**
296
- - Modify: `scripts/okstra_ctl/wizard.py` — `WizardState` ([wizard.py:367](../../scripts/okstra_ctl/wizard.py:367)), `WHOLE_TASK_STAGE` 옆 상수([wizard.py:1636](../../scripts/okstra_ctl/wizard.py:1636)), `_submit_stage_pick` ([wizard.py:1845](../../scripts/okstra_ctl/wizard.py:1845)), Step `owns` ([wizard.py:2936](../../scripts/okstra_ctl/wizard.py:2936))
297
- - Modify: `prompts/wizard/prompts.ko.json` — `stage_pick` 에 errors/echo 추가
298
- - Test: `tests/wizard/test_wizard_stage_pick.py`
299
-
300
- **Interfaces:**
301
- - Consumes: `order_stage_closure` (Task 3), `_done/_started/_reserved_stage_numbers` (Task 1·2), `_parse_stage_objects` ([wizard.py:937](../../scripts/okstra_ctl/wizard.py:937)).
302
- - Produces: `state.selected_stages: str` (위상정렬 CSV). Task 5 의 render-args 가 읽는다.
303
-
304
- - [ ] **Step 1: Write the failing tests**
305
-
306
- ```python
307
- def test_submit_stage_pick_multi_closure_orders(tmp_path):
308
- state = _impl_state_with_plan(tmp_path, stages=[(1, []), (2, [1]), (3, [2])])
309
- from okstra_ctl.wizard import _submit_stage_pick
310
- _submit_stage_pick(state, "3") # 3 선택, 1·2 미완료 → closure [1,2,3]
311
- assert state.selected_stages == "1,2,3"
312
-
313
-
314
- def test_submit_stage_pick_rejects_occupied(tmp_path):
315
- state = _impl_state_with_plan(tmp_path, stages=[(1, []), (2, [1])])
316
- run_root = Path(state.approved_plan_path).resolve().parents[1]
317
- from okstra_ctl.consumers import append_consumer
318
- append_consumer(run_root, impl_task_key="k#stage-1", stage=1, status="done",
319
- head_commit="abc")
320
- from okstra_ctl.wizard import _submit_stage_pick, WizardError
321
- import pytest
322
- with pytest.raises(WizardError):
323
- _submit_stage_pick(state, "1") # done stage 선택 거부
324
-
325
-
326
- def test_submit_stage_pick_all_expands_to_unoccupied(tmp_path):
327
- state = _impl_state_with_plan(tmp_path, stages=[(1, []), (2, [1])])
328
- from okstra_ctl.wizard import _submit_stage_pick, ALL_STAGES
329
- _submit_stage_pick(state, ALL_STAGES)
330
- assert state.selected_stages == "1,2"
331
- ```
332
-
333
- - [ ] **Step 2: Run tests to verify they fail**
334
-
335
- Run: `python3 -m pytest tests/wizard/test_wizard_stage_pick.py -k "multi_closure or rejects_occupied or all_expands" -v`
336
- Expected: FAIL — `selected_stages` 미정의 / 단일선택 submit 이라 closure 없음
337
-
338
- - [ ] **Step 3: Add state field and constant**
339
-
340
- `WizardState` 에 필드 추가([wizard.py:367](../../scripts/okstra_ctl/wizard.py:367) `selected_stage` 아래):
341
-
342
- ```python
343
- selected_stage: str = "auto"
344
- selected_stages: str = "" # implementation 다중선택 위상정렬 CSV
345
- ```
346
-
347
- `WHOLE_TASK_STAGE` 옆([wizard.py:1636](../../scripts/okstra_ctl/wizard.py:1636))에(Task 2 에서 이미 추가했다면 생략):
348
-
349
- ```python
350
- WHOLE_TASK_STAGE = "__whole_task__"
351
- ALL_STAGES = "__all_stages__"
352
- ```
353
-
354
- `new_state_file` 의 직렬화 디폴트 dict([wizard.py:3189](../../scripts/okstra_ctl/wizard.py:3189) `"selected_stage": "auto"` 부근)에 `"selected_stages": ""` 를 추가한다(필드 누락 시 역직렬화 깨짐 방지 — 기존 패턴 확인).
355
-
356
- - [ ] **Step 4: Rewrite `_submit_stage_pick`**
357
-
358
- final-verification 단일선택 로직은 보존하고, implementation 분기를 다중선택으로 교체한다.
359
-
360
- ```python
361
- def _submit_stage_pick(state: WizardState, answer: str) -> Optional[str]:
362
- if state.task_type == "implementation":
363
- return _submit_impl_stage_pick(state, answer)
364
- # final-verification: 단일선택 유지 (whole-task 또는 단일 정수; auto 불가)
365
- if not answer:
366
- raise WizardError("value required")
367
- if answer == WHOLE_TASK_STAGE:
368
- if not _whole_task_allowed(state):
369
- raise WizardError(
370
- "whole-task verification requires final-verification "
371
- "with all stages done")
372
- else:
373
- try:
374
- int(answer)
375
- except ValueError:
376
- raise WizardError(
377
- f"answer must be whole-task or a stage number, got {answer!r}")
378
- state.selected_stage = answer
379
- return f"stage: {answer}"
380
-
381
-
382
- def _submit_impl_stage_pick(state: WizardState, answer: str) -> Optional[str]:
383
- t = _p(state.workspace_root, "stage_pick")
384
- picks = [v.strip() for v in (answer or "").split(",") if v.strip()]
385
- if not picks:
386
- raise WizardError(t["errors"]["none_selected"])
387
- stages = _parse_stage_objects(state)
388
- all_nums = {s.stage_number for s in stages}
389
- done = _done_stage_numbers(state)
390
- occupied = done | _started_stage_numbers(state) | _reserved_stage_numbers(state)
391
- if ALL_STAGES in picks:
392
- if len(picks) > 1:
393
- raise WizardError(t["errors"]["all_exclusive"])
394
- chosen = {n for n in all_nums if n not in occupied}
395
- if not chosen:
396
- raise WizardError(t["errors"]["nothing_selectable"])
397
- else:
398
- try:
399
- nums = {int(p) for p in picks}
400
- except ValueError:
401
- raise WizardError(t["errors"]["bad_number"].format(answer=answer))
402
- unknown = sorted(nums - all_nums)
403
- if unknown:
404
- raise WizardError(t["errors"]["unknown_stage"].format(
405
- bad=", ".join(map(str, unknown))))
406
- bad = sorted(nums & occupied)
407
- if bad:
408
- raise WizardError(t["errors"]["occupied"].format(
409
- bad=", ".join(map(str, bad))))
410
- chosen = nums
411
- ordered = order_stage_closure(
412
- [(s.stage_number, s.depends_on) for s in stages], chosen, done)
413
- state.selected_stages = ",".join(map(str, ordered))
414
- state.selected_stage = str(ordered[0]) if ordered else "auto"
415
- added = [n for n in ordered if n not in chosen]
416
- if added:
417
- return t["echo_variants"]["with_closure"].format(
418
- stages=state.selected_stages, added=", ".join(map(str, added)))
419
- return t["echo_variants"]["plain"].format(stages=state.selected_stages)
420
- ```
421
-
422
- `prompts/wizard/prompts.ko.json` 의 `stage_pick` 에 `errors`·`echo_variants` 추가:
423
-
424
- ```json
425
- "stage_pick": {
426
- "label": "...",
427
- "label_final_verification": "...",
428
- "echo_template": "stage: {value}",
429
- "options": { "...": "..." },
430
- "errors": {
431
- "none_selected": "stage 를 하나 이상 선택하세요.",
432
- "all_exclusive": "'전체' 는 단독으로만 선택할 수 있습니다.",
433
- "nothing_selectable": "선택 가능한 stage 가 없습니다 (모두 완료/진행중).",
434
- "bad_number": "stage 번호가 올바르지 않습니다: {answer}",
435
- "unknown_stage": "Stage Map 에 없는 stage 입니다: {bad}",
436
- "occupied": "이미 완료/진행중인 stage 는 선택할 수 없습니다: {bad}"
437
- },
438
- "echo_variants": {
439
- "plain": "stages: {stages}",
440
- "with_closure": "stages: {stages} (의존성 자동 포함: {added})"
441
- }
442
- }
443
- ```
444
-
445
- Step `owns` 갱신([wizard.py:2936](../../scripts/okstra_ctl/wizard.py:2936)):
446
-
447
- ```python
448
- owns=("selected_stage", "selected_stages")),
449
- ```
450
-
451
- - [ ] **Step 5: Update existing submit tests to multi-select semantics, then run**
452
-
453
- 테스트 픽스처는 `_plan_with_approval(tmp_path)` + 직접 `WizardState(...)` 구성 패턴을 따른다(이 파일에 `_impl_state_with_plan` 헬퍼는 없다). consumers 행을 읽는 테스트는 plan 을 `tmp_path/runs/implementation/reports/final-report.md` 에 두고 `append_consumer(tmp_path/"runs"/"implementation", ...)` 로 써야 `_done/_started_stage_numbers` 의 `parents[1]` 경로와 맞는다(Task 1 테스트와 동일 구조).
454
-
455
- 기존 submit 테스트 갱신:
456
- - `test_submit_stage_pick_stores_auto`([:89](../../tests/wizard/test_wizard_stage_pick.py:89)): implementation 은 auto 미수용 → 삭제(Task 4 신규 multi/all 테스트가 대체).
457
- - `test_submit_stage_pick_stores_numeric_stage`([:121](../../tests/wizard/test_wizard_stage_pick.py:121)): 이제 `state.selected_stages == "2"` 단언 + echo 가 `stages: 2`(`selected_stage == "2"` 도 유지).
458
- - `test_submit_stage_pick_rejects_empty`([:155](../../tests/wizard/test_wizard_stage_pick.py:155)): 메시지가 `value required` → `none_selected`("stage 를 하나 이상 선택하세요") 로 바뀌므로 단언 문자열 갱신.
459
- - `test_submit_stage_pick_rejects_invalid`([:137](../../tests/wizard/test_wizard_stage_pick.py:137)): `bogus` 거부 유지(통과).
460
- - `test_step_cli_corrupt_stage_map_returns_clean_envelope`([:214](../../tests/wizard/test_wizard_stage_pick.py:214)): `_submit_impl_stage_pick` 가 `_parse_stage_objects` 를 먼저 호출하므로 손상 Stage Map 에서 여전히 WizardError('Stage Map') → `current=None` 으로 통과해야 한다. 통과하면 그대로, 깨지면 answer `"auto"` 를 `"1"` 로 바꾼다.
461
- final-verification submit 테스트([:105](../../tests/wizard/test_wizard_stage_pick.py:105))는 불변.
462
-
463
- Run: `python3 -m pytest tests/wizard/test_wizard_stage_pick.py -v`
464
- Expected: 신규 3개 + 갱신한 기존 테스트 모두 PASS.
465
-
466
- - [ ] **Step 6: Commit**
467
-
468
- ```bash
469
- git add scripts/okstra_ctl/wizard.py prompts/wizard/prompts.ko.json tests/wizard/test_wizard_stage_pick.py
470
- git commit -m "feat(wizard): make implementation stage picker multi-select with dependency closure"
471
- ```
472
-
473
- ---
474
-
475
- ## Task 5: render-args 에 chain-stages 출력
476
-
477
- implementation 의 render-args 가 위상정렬된 stage CSV 를 내보내, SKILL.md 연쇄 루프가 소비할 수 있게 한다.
478
-
479
- **Files:**
480
- - Modify: `scripts/okstra_ctl/wizard.py` — `render_args` ([wizard.py:3412](../../scripts/okstra_ctl/wizard.py:3412), [wizard.py:3440](../../scripts/okstra_ctl/wizard.py:3440))
481
- - Test: `tests/wizard/test_wizard_stage_pick.py` (또는 기존 render-args 테스트 파일)
482
-
483
- **Interfaces:**
484
- - Consumes: `state.selected_stages` (Task 4).
485
- - Produces: render-args dict 의 `chain-stages` 키(위상정렬 CSV). 단수 `stage` 는 `ordered[0]`(첫 stage)로 유지해 기존 render-bundle 단일-stage 경로와 호환.
486
-
487
- - [ ] **Step 1: Write the failing test**
488
-
489
- ```python
490
- def test_render_args_emits_chain_stages(tmp_path):
491
- state = _impl_state_ready_for_render(tmp_path) # 기존 render-args 테스트 헬퍼
492
- state.selected_stages = "1,2,3"
493
- state.selected_stage = "1"
494
- from okstra_ctl.wizard import render_args
495
- args = render_args(state)
496
- assert args["chain-stages"] == "1,2,3"
497
- assert args["stage"] == "1"
498
- ```
499
-
500
- - [ ] **Step 2: Run test to verify it fails**
501
-
502
- Run: `python3 -m pytest tests/wizard/test_wizard_stage_pick.py::test_render_args_emits_chain_stages -v`
503
- Expected: FAIL — `KeyError: 'chain-stages'`
504
-
505
- - [ ] **Step 3: Implement in `render_args`**
506
-
507
- implementation 분기([wizard.py:3412-3413](../../scripts/okstra_ctl/wizard.py:3412))와 반환 dict([wizard.py:3440](../../scripts/okstra_ctl/wizard.py:3440))을 수정:
508
-
509
- ```python
510
- chain_stages = ""
511
- if state.task_type == "implementation":
512
- chain_stages = state.selected_stages or (state.selected_stage or "auto")
513
- stage = state.selected_stage or "auto"
514
- elif state.task_type == "final-verification":
515
- ...기존 그대로...
516
- else:
517
- stage = ""
518
- ```
519
-
520
- 반환 dict 에 키 추가(기존 `"stage": stage`, `"stages": state.handoff_stages` 옆):
521
-
522
- ```python
523
- "stage": stage,
524
- "stages": state.handoff_stages,
525
- "chain-stages": chain_stages,
526
- ```
527
-
528
- - [ ] **Step 4: Run test to verify it passes**
529
-
530
- Run: `python3 -m pytest tests/wizard/test_wizard_stage_pick.py::test_render_args_emits_chain_stages -v`
531
- Expected: PASS
532
-
533
- - [ ] **Step 5: Run the full wizard suite**
534
-
535
- Run: `python3 -m pytest tests/wizard/ -v`
536
- Expected: PASS (회귀 없음)
537
-
538
- - [ ] **Step 6: Commit**
539
-
540
- ```bash
541
- git add scripts/okstra_ctl/wizard.py tests/wizard/test_wizard_stage_pick.py
542
- git commit -m "feat(wizard): emit chain-stages render-arg for implementation"
543
- ```
544
-
545
- ---
546
-
547
- ## Task 6: SKILL.md 무인 연쇄 루프 + 빌드
548
-
549
- 현재 세션이 오케스트레이터가 되어, `chain-stages` 큐를 의존성 순서로 무인 실행한다. 예외 게이트에서는 멈춰 사용자에게 인계한다.
550
-
551
- **Files:**
552
- - Modify: `skills/okstra-run/SKILL.md` (Step 5 render-bundle 호출 [SKILL.md:146-194](../../skills/okstra-run/SKILL.md:146), Step 6 [SKILL.md:247-254](../../skills/okstra-run/SKILL.md:247), 종료 [SKILL.md:293](../../skills/okstra-run/SKILL.md:293))
553
- - Modify: `CHANGES.md` (사용자 영향 항목)
554
- - Build: `npm run build`
555
-
556
- **Interfaces:**
557
- - Consumes: render-args 의 `chain-stages` CSV (Task 5), Phase 6 의 `done` append([_implementation-deliverable.md:50](../../prompts/profiles/_implementation-deliverable.md:50)).
558
-
559
- - [ ] **Step 1: SKILL.md 에 연쇄 루프 절 추가**
560
-
561
- Step 6 뒤(또는 종료 규칙 [SKILL.md:293](../../skills/okstra-run/SKILL.md:293) 직전)에 새 절을 추가한다. 내용(한국어 산문):
562
-
563
- ```markdown
564
- ## Step 7: implementation 무인 연쇄 (chain-stages)
565
-
566
- `task-type == implementation` 이고 render-args 의 `chain-stages` CSV 가 원소 2개
567
- 이상이면, 현재 세션이 오케스트레이터로서 stage 를 의존성 순서대로 무인 연쇄
568
- 실행한다(단일 원소면 기존 단일 run 과 동일, 이 절은 건너뛴다).
569
-
570
- 큐 = `chain-stages` 를 `,` 로 분해한 위상정렬 stage 리스트. 각 stage `N` 에 대해
571
- 순서대로:
572
-
573
- 1. `render-bundle` 을 Step 5 와 동일 인자로, 단 `--stage N` 으로 호출한다
574
- (base commit 은 prepare 가 predecessor done commit 으로 자동 계산).
575
- 2. Step 6 대로 Claude lead 가 되어 그 stage 의 Phase 1~7 을 인라인 실행하고,
576
- Phase 6 에서 `done` 행을 append 한다(implementation 프로파일 지시).
577
- 3. `done` 이 기록됐는지(또는 carry 사이드카로 backfill 되는지) 확인한 뒤 다음
578
- stage 로 넘어간다. 매 stage 경계에서 컨텍스트를 정리한다.
579
- 4. 각 stage 시작/완료마다 한 줄 보고: `stage N/<총개수> 시작` / `stage N done → 다음 K`.
580
-
581
- 큐를 모두 소화하거나, 다음 stage 의 render-bundle 이 ready 아님으로 거부되면
582
- 종료하고 남은 큐를 사용자에게 보고한다.
583
-
584
- ### 연쇄 중 예외 게이트
585
- render-bundle 이 동시-run 충돌 감지(`Step 5` 의 concurrency 분기)나 git stale-SHA
586
- 재조정(같은 곳의 reconcile 분기)을 띄우면, **그 stage 에서 연쇄를 멈추고** 게이트를
587
- 사용자에게 그대로 제시한다. 사용자가 게이트를 해소하면 그 자리에서 연쇄를
588
- 재개한다(남은 큐 이어서 처리). 데이터 손상·동시 점유 충돌은 사람이 확인한다 —
589
- 이것이 무인 연쇄의 안전 경계다.
590
- ```
591
-
592
- 종료 규칙([SKILL.md:293](../../skills/okstra-run/SKILL.md:293))은 단일 run 기준이므로, 연쇄 시 "큐가 빌 때까지 Step 7 을 반복한 뒤 종료"로 읽히게 한 줄 보강한다.
593
-
594
- - [ ] **Step 2: CHANGES.md 항목 추가**
595
-
596
- `CHANGES.md` 최상단에 사용자 영향 항목(한국어):
597
-
598
- ```markdown
599
- - implementation stage picker 가 각 stage 의 진행 상태([완료]/[진행중]/[준비됨]/[대기])를
600
- 표시하고, 여러 stage 를 체크박스로 골라 의존성 순서대로 무인 연쇄 실행할 수 있다.
601
- '전체' 를 고르면 남은 미완료 stage 가 모두 큐에 들어간다.
602
- 사용자 영향: implementation 을 여러 stage 에 걸쳐 한 번의 선택으로 진행할 수 있고,
603
- 점유/완료된 stage 를 고르는 실수를 picker 단계에서 미리 막는다.
604
- ```
605
-
606
- - [ ] **Step 3: Build runtime**
607
-
608
- Run: `npm run build`
609
- Expected: exit 0, `runtime/skills/okstra-run/SKILL.md` 등에 변경 반영.
610
-
611
- - [ ] **Step 4: Full check**
612
-
613
- Run: `npm run check`
614
- Expected: exit 0 (JS + Python 스위트 통과).
615
-
616
- - [ ] **Step 5: Commit**
617
-
618
- ```bash
619
- git add skills/okstra-run/SKILL.md CHANGES.md runtime/
620
- git commit -m "feat(okstra-run): chain selected implementation stages unattended in dependency order"
621
- ```
622
-
623
- ---
624
-
625
- ## Self-Review
626
-
627
- - **Spec coverage:** 기능 A(상태 표시) = Task 1·2. 기능 B picker multi = Task 4, closure = Task 3, render-args = Task 5, 무인 연쇄 루프 = Task 6, 예외 게이트 = Task 6 Step 1. "전체" 정의(점유 제외) = Task 4 Step 4 `chosen = {n ... not in occupied}`. auto 제거 = Task 2 Step 4 주석 + Task 4(implementation submit 이 auto 미수용). 비대화형 CLI auto 보존 = render-args 만 변경하고 `resolve_effective_stages` 불변 → 충족.
628
- - **Placeholder scan:** 모든 step 에 실제 코드/명령/예상 출력 존재. TBD 없음.
629
- - **Type consistency:** `order_stage_closure(stages, selected, done)` 시그니처가 Task 3 정의와 Task 4 호출(`order_stage_closure([(s.stage_number, s.depends_on)...], chosen, done)`)에서 일치. `selected_stages: str` 가 Task 4(저장)·Task 5(읽기)에서 일치. `_started_stage_numbers`/`_reserved_stage_numbers` 가 Task 1 정의·Task 2·4 사용에서 일치. `ALL_STAGES` 상수가 Task 2/4 에서 일치.
630
- - **주의:** Task 2 와 Task 4 가 `_build_stage_pick`/`_submit_stage_pick` 를 연속 수정하므로 두 task 는 같은 PR 에서 순차 실행한다. Task 2 단독 완료 시점에 일부 기존 stage_pick 테스트가 깨질 수 있으며 Task 4 에서 정리된다.