okstra 0.102.2 → 0.102.4
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/README.kr.md +5 -5
- package/README.md +5 -5
- package/docs/kr/architecture/storage-model.md +8 -3
- package/docs/kr/architecture.md +12 -13
- package/docs/kr/cli.md +4 -4
- package/docs/kr/container.md +1 -1
- package/docs/kr/performance-improvement-plan-v2.md +2 -2
- package/docs/project-structure-overview.md +2 -2
- package/package.json +7 -2
- package/runtime/BUILD.json +2 -2
- package/runtime/bin/lib/okstra/interactive.sh +12 -0
- package/runtime/bin/okstra-antigravity-exec.sh +1 -2
- package/runtime/bin/okstra-codex-exec.sh +1 -2
- package/runtime/bin/okstra-render-final-report.py +2 -9
- package/runtime/bin/okstra-spawn-followups.py +4 -6
- package/runtime/bin/okstra-trace-cleanup.sh +24 -4
- package/runtime/prompts/lead/okstra-lead-contract.md +2 -2
- package/runtime/prompts/lead/report-writer.md +1 -1
- package/runtime/prompts/profiles/_common-contract.md +9 -11
- package/runtime/python/okstra_ctl/codex_dispatch.py +6 -13
- package/runtime/python/okstra_ctl/consumers.py +35 -0
- package/runtime/python/okstra_ctl/container.py +4 -6
- package/runtime/python/okstra_ctl/dispatch_core.py +8 -13
- package/runtime/python/okstra_ctl/final_report_paths.py +24 -0
- package/runtime/python/okstra_ctl/handoff.py +2 -1
- package/runtime/python/okstra_ctl/implementation_outcome.py +271 -0
- package/runtime/python/okstra_ctl/implementation_stage.py +7 -11
- package/runtime/python/okstra_ctl/path_hints.py +665 -0
- package/runtime/python/okstra_ctl/render.py +4 -3
- package/runtime/python/okstra_ctl/report_view_artifacts.py +14 -0
- package/runtime/python/okstra_ctl/report_views.py +7 -4
- package/runtime/python/okstra_ctl/run.py +3 -10
- package/runtime/python/okstra_ctl/run_context.py +4 -2
- package/runtime/python/okstra_ctl/stage_targets.py +18 -0
- package/runtime/python/okstra_ctl/wizard.py +37 -26
- package/runtime/python/okstra_ctl/wizard_stage_intent.py +54 -0
- package/runtime/python/okstra_project/state.py +40 -2
- package/runtime/python/okstra_token_usage/report.py +2 -5
- package/runtime/validators/validate-report-views.py +2 -1
- package/runtime/validators/validate-run.py +1 -11
- package/src/commands/lifecycle/install.mjs +5 -5
- package/src/commands/lifecycle/uninstall.mjs +4 -4
- package/docs/superpowers/plans/2026-05-12-ticket-id-in-reports.md +0 -638
- package/docs/superpowers/plans/2026-05-14-convergence-queue-pruning.md +0 -1568
- package/docs/superpowers/plans/2026-05-17-dual-format-final-report.md +0 -167
- package/docs/superpowers/plans/2026-05-20-final-report-language.md +0 -1501
- package/docs/superpowers/plans/2026-05-20-implementation-planning-multi-stage.md +0 -1267
- package/docs/superpowers/plans/2026-05-20-okstra-run-prompt-sot-b1.md +0 -1007
- package/docs/superpowers/plans/2026-05-20-wizard-messages-json-sot.md +0 -720
- package/docs/superpowers/plans/2026-05-20-wizard-prompt-json-sot-a1.md +0 -681
- package/docs/superpowers/plans/2026-05-21-improvement-discovery-task-type.md +0 -1691
- package/docs/superpowers/plans/2026-05-24-implementation-lead-context-slimming.md +0 -1700
- package/docs/superpowers/plans/2026-05-25-okstra-project-root-rename.md +0 -158
- package/docs/superpowers/plans/2026-05-26-wizard-3-option-picker.md +0 -860
- package/docs/superpowers/plans/2026-06-02-final-verification-protocol-hardening.md +0 -326
- package/docs/superpowers/plans/2026-06-02-okstra-run-branch-confirm-step.md +0 -337
- package/docs/superpowers/plans/2026-06-02-okstra-run-phase-pane-cleanup.md +0 -410
- package/docs/superpowers/plans/2026-06-02-requirements-discovery-fanout.md +0 -728
- package/docs/superpowers/plans/2026-06-04-adversarial-implementation-planning.md +0 -294
- package/docs/superpowers/plans/2026-06-04-adversarial-verification.md +0 -570
- package/docs/superpowers/plans/2026-06-04-coverage-critic.md +0 -516
- package/docs/superpowers/plans/2026-06-04-stage-cohesion-planner.md +0 -351
- package/docs/superpowers/plans/2026-06-04-stage-run-batching.md +0 -457
- package/docs/superpowers/plans/2026-06-05-acceptance-critic.md +0 -251
- package/docs/superpowers/plans/2026-06-05-compact-markdown-report-tables.md +0 -323
- package/docs/superpowers/plans/2026-06-05-wizard-batch-prompts.md +0 -559
- package/docs/superpowers/plans/2026-06-06-final-verification-whole-task-gate.md +0 -993
- package/docs/superpowers/plans/2026-06-06-stage-parallel-and-pending-fixes.md +0 -93
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p1.md +0 -447
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p2.md +0 -289
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p3.md +0 -774
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p4.md +0 -303
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p5-multidep-base.md +0 -387
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase2.md +0 -275
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase3.md +0 -282
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4a.md +0 -147
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4b.md +0 -262
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4c.md +0 -184
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4d.md +0 -88
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4e.md +0 -250
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa.md +0 -409
- package/docs/superpowers/plans/2026-06-09-implementation-run-artifact-stage-isolation.md +0 -320
- package/docs/superpowers/plans/2026-06-10-concurrent-run-team-guard.md +0 -456
- package/docs/superpowers/plans/2026-06-10-git-reconcile-stale-sha-recovery.md +0 -1408
- package/docs/superpowers/plans/2026-06-10-lead-worker-completion-polling-PROBE.md +0 -42
- package/docs/superpowers/plans/2026-06-10-lead-worker-completion-polling.md +0 -337
- package/docs/superpowers/plans/2026-06-10-p6-token-usage-incremental.md +0 -1029
- package/docs/superpowers/plans/2026-06-10-stage-group-handoff.md +0 -1572
- package/docs/superpowers/plans/2026-06-11-fix-cycle.md +0 -1290
- package/docs/superpowers/plans/2026-06-11-wizard-whole-task-final-verification.md +0 -526
- package/docs/superpowers/plans/2026-06-12-html-plan-approval.md +0 -1000
- package/docs/superpowers/plans/2026-06-13-repo-risk-hardening.md +0 -493
- package/docs/superpowers/plans/2026-06-14-host-runtime-auto-execution.md +0 -1775
- package/docs/superpowers/plans/2026-06-15-coding-preflight-pack-dispatch-path.md +0 -504
- package/docs/superpowers/plans/2026-06-15-internal-skill-migration-final-fixups.md +0 -342
- package/docs/superpowers/plans/2026-06-15-internal-skill-migration-fixups.md +0 -258
- package/docs/superpowers/plans/2026-06-15-internal-skill-migration-remaining-fixups.md +0 -387
- package/docs/superpowers/plans/2026-06-15-internal-skill-resource-migration.md +0 -749
- package/docs/superpowers/plans/2026-06-15-worker-prompt-anchor-final-fixups.md +0 -828
- package/docs/superpowers/plans/2026-06-15-worker-prompt-header-error-contract.md +0 -490
- package/docs/superpowers/plans/2026-06-17-okstra-error-report.md +0 -724
- package/docs/superpowers/plans/2026-06-18-subagent-pane-reclaim.md +0 -483
- package/docs/superpowers/plans/2026-06-19-agent-operating-standard.md +0 -476
- package/docs/superpowers/plans/2026-06-19-okstra-inspect-recap-facet.md +0 -647
- package/docs/superpowers/plans/2026-06-20-error-feedback-loop.md +0 -1019
- package/docs/superpowers/plans/2026-06-20-run-index-row-normalization.md +0 -583
- package/docs/superpowers/plans/2026-06-20-stage-auto-integrate-teardown.md +0 -819
- package/docs/superpowers/plans/2026-06-21-okstra-container-local-user-test.md +0 -714
- package/docs/superpowers/plans/2026-06-21-release-handoff-local-checkout.md +0 -348
- package/docs/superpowers/plans/2026-06-23-implementation-stage-multiselect-chain.md +0 -630
- package/docs/superpowers/plans/2026-06-23-implementation-step-commit-granularity.md +0 -274
- package/docs/superpowers/plans/2026-06-23-phase-batch-cleanup-enforcement.md +0 -409
- package/docs/superpowers/plans/2026-06-24-cross-project-precondition.md +0 -542
- package/docs/superpowers/plans/2026-06-24-decision-drafts-render-gap.md +0 -568
- package/docs/superpowers/plans/2026-06-24-manual-user-test-section.md +0 -203
- package/docs/superpowers/plans/2026-06-24-okstra-container-build-rename.md +0 -525
- package/docs/superpowers/specs/2026-05-12-ticket-id-in-reports-design.md +0 -131
- package/docs/superpowers/specs/2026-05-15-implementation-plan-verification-design.md +0 -254
- package/docs/superpowers/specs/2026-05-20-final-report-language-design.md +0 -383
- package/docs/superpowers/specs/2026-05-20-implementation-planning-multi-stage-design.md +0 -322
- package/docs/superpowers/specs/2026-05-20-okstra-run-prompt-sot-design.md +0 -299
- package/docs/superpowers/specs/2026-05-21-improvement-discovery-task-type-design.md +0 -335
- package/docs/superpowers/specs/2026-06-02-okstra-run-branch-confirm-step-design.md +0 -113
- package/docs/superpowers/specs/2026-06-02-okstra-run-phase-pane-cleanup-design.md +0 -173
- package/docs/superpowers/specs/2026-06-02-requirements-discovery-fanout-design.md +0 -154
- package/docs/superpowers/specs/2026-06-04-adversarial-implementation-planning-design.md +0 -90
- package/docs/superpowers/specs/2026-06-04-adversarial-verification-design.md +0 -176
- package/docs/superpowers/specs/2026-06-04-coverage-critic-design.md +0 -99
- package/docs/superpowers/specs/2026-06-04-stage-splitting-cost-aware-design.md +0 -98
- package/docs/superpowers/specs/2026-06-05-acceptance-critic-design.md +0 -90
- package/docs/superpowers/specs/2026-06-05-compact-markdown-report-tables-design.md +0 -87
- package/docs/superpowers/specs/2026-06-05-wizard-batch-prompts-design.md +0 -121
- package/docs/superpowers/specs/2026-06-06-final-verification-whole-task-gate-design.md +0 -126
- package/docs/superpowers/specs/2026-06-06-stage-worktree-isolation-design.md +0 -180
- package/docs/superpowers/specs/2026-06-06-vertical-slice-tdd-planning-design.md +0 -179
- package/docs/superpowers/specs/2026-06-07-stage-conformance-qa-design.md +0 -169
- package/docs/superpowers/specs/2026-06-09-executor-model-custom-id-cascade-design.md +0 -66
- package/docs/superpowers/specs/2026-06-09-implementation-run-artifact-stage-isolation-design.md +0 -87
- package/docs/superpowers/specs/2026-06-10-blocking-contract-posthoc-conformance-design.md +0 -168
- package/docs/superpowers/specs/2026-06-10-concurrent-run-team-guard-design.md +0 -107
- package/docs/superpowers/specs/2026-06-10-git-reconcile-stale-sha-recovery-design.md +0 -105
- package/docs/superpowers/specs/2026-06-10-lead-worker-completion-polling-design.md +0 -113
- package/docs/superpowers/specs/2026-06-10-stage-group-handoff-design.md +0 -156
- package/docs/superpowers/specs/2026-06-11-brief-entry-only-handoff-stage-entry-design.md +0 -158
- package/docs/superpowers/specs/2026-06-11-fix-cycle-design.md +0 -94
- package/docs/superpowers/specs/2026-06-11-wizard-whole-task-final-verification-design.md +0 -89
- package/docs/superpowers/specs/2026-06-12-codex-lead-adapter-design.md +0 -358
- package/docs/superpowers/specs/2026-06-12-html-plan-approval-design.md +0 -85
- package/docs/superpowers/specs/2026-06-12-stage-discipline-rule-design.md +0 -108
- package/docs/superpowers/specs/2026-06-13-forbidden-actions-ssot-design.md +0 -134
- package/docs/superpowers/specs/2026-06-13-neutral-tmux-lead-adapter-design.md +0 -284
- package/docs/superpowers/specs/2026-06-14-host-runtime-auto-execution-design.md +0 -404
- package/docs/superpowers/specs/2026-06-17-okstra-error-report-design.md +0 -123
- package/docs/superpowers/specs/2026-06-18-subagent-pane-reclaim-design.md +0 -150
- package/docs/superpowers/specs/2026-06-19-agent-operating-standard-design.md +0 -120
- package/docs/superpowers/specs/2026-06-19-okstra-inspect-recap-facet-design.md +0 -96
- package/docs/superpowers/specs/2026-06-20-error-feedback-loop-design.md +0 -171
- package/docs/superpowers/specs/2026-06-20-run-index-row-normalization-design.md +0 -112
- package/docs/superpowers/specs/2026-06-20-stage-auto-integrate-teardown-design.md +0 -145
- package/docs/superpowers/specs/2026-06-21-okstra-container-local-user-test-design.md +0 -125
- package/docs/superpowers/specs/2026-06-21-release-handoff-local-checkout-design.md +0 -113
- package/docs/superpowers/specs/2026-06-23-implementation-stage-multiselect-chain-design.md +0 -138
- package/docs/superpowers/specs/2026-06-23-implementation-step-commit-granularity-design.md +0 -120
- package/docs/superpowers/specs/2026-06-24-cross-project-precondition-design.md +0 -129
- package/docs/superpowers/specs/2026-06-24-decision-drafts-render-gap-design.md +0 -185
- package/docs/superpowers/specs/2026-06-24-manual-user-test-section-design.md +0 -121
- 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 에서 정리된다.
|