okstra 0.23.0 → 0.25.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.
@@ -24,7 +24,7 @@ taskType: "{{FM_TASK_TYPE}}"
24
24
 
25
25
  > **이 블록은 `task-type` = `implementation-planning` 결과에서만 의미를 가집니다.** 다른 task-type의 보고서에서는 이 섹션 전체를 삭제하세요.
26
26
  >
27
- > 다음 `implementation` run은 아래 체크박스가 `[x]`로 표시되어 있을 때에만 진입할 수 있습니다 (`okstra_ctl.run._validate_approved_plan` 가 이 마커를 line-anchored 정규식으로 검사하여 통과/거부합니다). 본문(`Sections 1`–`4.5`)을 끝까지 읽고, `4.5.9 Open Questions`가 비어 있거나 모두 해소된 뒤 승인해 주세요.
27
+ > 다음 `implementation` run은 아래 체크박스가 `[x]`로 표시되어 있을 때에만 진입할 수 있습니다 (`okstra_ctl.run._validate_approved_plan` 가 이 마커를 line-anchored 정규식으로 검사하여 통과/거부합니다). 본문(`Sections 1`–`4.5`)을 끝까지 읽고, `## 5. Clarification Items` 표에서 `Blocks=approval` 모든 행이 `Status` 가 `resolved` 또는 `obsolete` 인지 확인한 뒤 승인해 주세요. 한 행이라도 `open`/`answered` 로 남아 있으면 `Approved` 마커를 새기지 마십시오 — 향후 `validate-run.py` 가 이 정합성을 자동 검사할 예정입니다.
28
28
 
29
29
  - 승인 여부 (사용자가 직접 편집): `- [ ] Approved` ← 승인하려면 `[ ]` 를 `[x]` 로 변경하여 저장하세요.
30
30
  - 승인 후 다음 단계 명령어 (방법 A — 수동 편집):
@@ -34,13 +34,13 @@ taskType: "{{FM_TASK_TYPE}}"
34
34
  - Claude Code 세션 안: `/okstra-run task-key={{TASK_KEY}} task-type=implementation approved-plan=<이 보고서 경로> approve`
35
35
  - 별도 터미널: `scripts/okstra.sh --task-key {{TASK_KEY}} --task-type implementation --approved-plan <이 보고서 경로> --approve`
36
36
  - 방법 B 는 `--approve` 입력 행위 자체를 승인 의사로 모델링합니다. 런타임이 본 블록의 체크박스를 자동으로 `[x]` 로 바꾸고, 본 섹션 하단에 `승인 일시 (CLI ack): <ISO8601>` audit 라인을 한 줄 덧붙입니다.
37
- - 승인을 보류하거나 거부하려면 체크박스는 `[ ]` 로 두고 `--approve` 도 사용하지 마세요. 필요한 변경 사항은 `4.5.9 Open Questions` 또는 `Section 5 Clarification Requests` 기록한 같은 phase 를 재실행해 주세요.
37
+ - 승인을 보류하거나 거부하려면 체크박스는 `[ ]` 로 두고 `--approve` 도 사용하지 마세요. 필요한 변경 사항은 `## 5. Clarification Items` 표에 행으로 기록한 뒤 (`Blocks=approval` 표시) 같은 phase 를 재실행해 주세요.
38
38
 
39
39
  ## 0. Clarification Response Carried In From Previous Run
40
40
  - Source file: `{{CLARIFICATION_RESPONSE_RELATIVE_PATH}}`
41
41
  - If the source path is empty, write `- No prior clarification response was provided for this run.` and skip the rest of this section.
42
- - If the source path is set, walk through both halves of the prior section 5 material requests (`A1`, `A2`, ...) from sub-section 5.1 and user questions (`Q1`, `Q2`, ...) from sub-section 5.2 and summarize how each one was used or invalidated by this run's evidence. Keep the two prefixes separate; do not collapse them into a single list.
43
- - Cite the entry id (`A*` or `Q*`) and the new evidence (file path, line number, log excerpt, worker finding, etc.) that resolves or refutes the prior answer or material.
42
+ - If the source path is set, walk every row of the prior report's `## 5. Clarification Items` table (`C-001`, `C-002`, ...). For each row, summarize how its `User input` (or its absence) was used by this run's evidence, cite the new evidence (file path, line number, log excerpt, worker finding) that resolves or refutes the prior answer, and update the row's `Status` to `resolved` or `obsolete` when carrying it into this run's section 5.
43
+ - Transitional rule (legacy carry-in only): if the prior report uses the deprecated `4.5.9 Open Questions` / `5.1 Additional Materials` / `5.2 Questions for the User` layout with `OQ-*` / `A*` / `Q*` IDs, walk every row of all three blocks. Map each legacy row into a single `C-*` row in this run's new `## 5. Clarification Items` table assign `Kind = material` for prior `A*`, `Kind = decision` for prior `Q*` and prior `OQ*`, and `Blocks = approval` only for items the prior report flagged as gating the implementation-planning approval (default: `next-phase`). Keep the legacy ID in the new row's Statement column for traceability (e.g. `"[legacy OQ-003] ..."`).
44
44
 
45
45
  ## Summary of the Problem or Verification Target
46
46
 
@@ -223,14 +223,7 @@ revert 경로와 롤백 트리거 신호를 표로 정리합니다. 추상적
223
223
  ### 4.5.8 User Approval Request (사용자 승인 요청 — 본 보고서 상단 참조)
224
224
  - 실제 승인 게이트는 본 보고서 **상단 `User Approval Request (사용자 승인 게이트)` 블록**에 있습니다. 이 하위 섹션은 validator가 요구하는 영문 키워드(`User Approval Request`)와 본문 구조 일관성을 위해 남겨 둡니다.
225
225
  - 본 섹션에는 승인 결정에 영향을 주는 *플랜 측 보충 메모*만 적습니다(예: 위험을 줄이기 위한 사전 작업, 승인 전 사용자가 확인해 두어야 할 사항). 승인 마커는 본 섹션이 아니라 상단 블록의 체크박스로만 부여합니다.
226
-
227
- ### 4.5.9 Open Questions
228
-
229
- pre-planning에서 발견된 모호점을 표로 남깁니다. 사용자가 승인 전에 해소해야 할 질문 목록입니다. 없을 시: `- Open question 없음.` 한 줄.
230
-
231
- | ID | Ticket ID | 질문 | Blocking (예/아니오) | 기대 답 형태 | Status |
232
- |----|-----------|------|----------------------|--------------|--------|
233
- | OQ-001 | `<TICKET-or-fallback>` | <질문 본문> | 예 | <예/아니오 / 선택 / 자유서술> | open |
226
+ - 사용자가 답하거나 자료를 첨부해야만 승인이 가능한 항목은 **이 섹션에 적지 않습니다** — `## 5. Clarification Items` 표에 한 행으로 등록하고 `Blocks=approval` 로 표시하세요. 같은 항목을 두 위치에 적으면 sync 가 깨집니다.
234
227
 
235
228
  ## 4.6 Release Handoff Deliverables (release-handoff runs only)
236
229
 
@@ -287,49 +280,56 @@ H1 이 `skip` 이거나 H3 가 `cancel` 인 경우, 본 섹션 다음의 4.6.4 ~
287
280
  - 단, H1=`skip` 또는 H3=`cancel` 로 종료된 경우 사용자가 추후 재진입해 다시 release-handoff 를 실행할 수 있는지 한 줄로 명시합니다.
288
281
  - 형식 예시: `- Routing: done. PR <url> opened against <base>; no follow-up required.`
289
282
 
290
- ## 5. Clarification Requests for the Next Run
283
+ ## 5. Clarification Items
284
+
285
+ 이 섹션은 다음 run 으로 넘어가기 전에 사용자가 답하거나 자료를 첨부해야 하는 항목을 **한 표 안에서** 추적합니다. `task-type`이 `error-analysis` 또는 `requirements-discovery` 이고 지금까지의 증거만으로 확신 있는 최종 판단이 어려울 때는 반드시 채웁니다. 그 외의 `task-type`에서는 lead 가 필요하다고 판단할 때만 채우고, 그렇지 않다면 `- 추가 정보 요청 없음. Section 2의 최종 판단이 그대로 유효합니다.` 한 줄만 남깁니다.
291
286
 
292
- 섹션은 다음 run으로 넘어가기 전에 사용자에게 받아야 입력을 정리하는 자리입니다. `task-type`이 `error-analysis` 또는 `requirements-discovery`이고 지금까지의 증거만으로는 확신 있는 최종 판단이 어려울 반드시 채웁니다. 외의 `task-type`에서는 lead가 추가 run이 필요하다고 판단했을 때만 채우고, 그렇지 않다면 `- 추가 정보 요청 없음. Section 2의 최종 판단이 그대로 유효합니다.` 라고만 한 줄 적은 뒤 Section 6으로 넘어갑니다.
287
+ 표가 도입되기 전에는 `4.5.9 Open Questions` / `5.1 추가 자료 요청` / `5.2 사용자 확인 질문` 자리에 같은 항목이 중복으로 적히는 문제가 있었습니다. 위치는 이상 사용하지 않으며, 모든 항목은 표의 행으로 표현합니다. 외부에 같은 항목을 다시 적지 마세요 (sync 깨집니다).
293
288
 
294
289
  작성 원칙:
295
290
 
296
291
  - 개발자가 아닌 사람도 한 번 읽고 무엇을 어디서 가져와야 하는지 이해할 수 있게, 줄임말과 내부 용어 대신 풀어 쓴 문장으로 작성합니다. (예: "QPS" 대신 "초당 평균 요청 수", "AC 미정" 대신 "합격 기준이 아직 정의되지 않았습니다")
297
- - 한 항목은 "왜 묻는지", "구체적으로 무엇을 묻는지", "어떤 형태의 답을 기대하는지"가 모두 분명히 드러나야 합니다. 한 줄짜리 단답형 질문은 피하고, 필요한 배경을 1~2문장으로 함께 적습니다.
298
- - 사용자에게 추가로 부탁드리는 자료(로그, 스크린샷, 설정 파일 사본, 재현 절차 등)와, 사용자에게 답을 받아야 하는 질문은 **반드시 개의 별도 하위 섹션으로 나누어 적습니다.** 같은 표나 같은 항목 안에 섞지 않습니다.
299
- - run 사이에 일관된 추적이 가능하도록 항목별 ID(`A1`, `A2` / `Q1`, `Q2`)는 한 번 부여한 뒤 다음 run에서도 유지합니다. 이미 답변된 항목은 다음 run에서도 삭제하지 말고 `Status`만 `resolved` 또는 `obsolete`로 갱신합니다.
300
- - Status 값의 의미는 다음과 같습니다.
301
- - `open`: 요청했지만 아직 받지 못했습니다.
302
- - `answered`: 사용자가 아래 답변 칸을 채워 두었습니다(다음 run의 lead가 검증해야 합니다).
303
- - `resolved`: 다음 run에서 lead가 답변을 받아 검증을 마쳤습니다.
304
- - `obsolete`: 이후 분석 결과로 더 이상 필요 없어진 항목입니다.
305
-
306
- 이 보고서에 답을 채우신 뒤에는 한 줄로 같은 phase를 다시 실행하실 수 있습니다(자동으로 `$EDITOR`가 이 파일을 열고, 저장하면 같은 phase가 `--clarification-response`로 carry-in 되어 재실행됩니다).
292
+ - 한 행은 "왜 필요한지", "무엇을 묻거나 받아야 하는지", "어떤 형태의 답을 기대하는지"가 모두 분명히 드러나야 합니다. 한 줄짜리 단답형 질문은 피하고, 필요한 배경을 1~2 문장으로 함께 적습니다.
293
+ - run 사이에 일관된 추적이 가능하도록 항목별 ID (`C-001`, `C-002`, ...) 부여한 다음 run 에서도 유지합니다. 이미 답변된 항목은 다음 run 에서도 삭제하지 말고 `Status`만 `resolved` 또는 `obsolete` 로 갱신합니다.
294
+
295
+ 이 보고서에 답을 채우신 뒤에는 한 줄로 같은 phase 를 다시 실행하실 수 있습니다 (자동으로 `$EDITOR` 가 이 파일을 열고, 저장하면 같은 phase 가 `--clarification-response` carry-in 되어 재실행됩니다).
307
296
  - Claude Code 세션 안: `/okstra-run resume-clarification task-key={{TASK_KEY}}`
308
297
  - 별도 터미널: `scripts/okstra.sh --resume-clarification --task-key {{TASK_KEY}}`
309
298
 
310
- 스크립트로 자동화하실 때는 셸 형식 `scripts/okstra.sh --task-key {{TASK_KEY}} --task-type {{TASK_TYPE}} --clarification-response <이 파일 경로>`도 그대로 사용하실 수 있습니다. Node `okstra` admin CLI 는 `--task-key`/`--task-type`/`--resume-clarification` 을 받지 않으므로 위 두 진입점 중 하나를 사용하세요.
311
-
312
- ### 5.1 추가 자료 요청 (Additional Materials Requested)
313
-
314
- 하위 섹션에는 **사용자가 다음 run 전에 첨부 또는 공유해 주셨으면 하는 자료**만 적습니다. 질문이 아니라 자료 요청입니다. 자료가 어디에 있는지(파일 경로, 페이지 URL, 데이터베이스 쿼리 결과 등), 어떤 시점/조건의 데이터를 받아야 하는지, 받은 자료를 어디에 두면 되는지(예: `<project-root>/.project-docs/<task-group>/<task-id>/` 아래) 함께 적습니다. 받을 자료가 없다면 `- 추가로 요청드릴 자료가 없습니다.` 줄만 남깁니다.
315
-
316
- | 자료 ID | Ticket ID | 무엇을 / 필요한지 (문장으로 서술) | 어디에서 가져올 있는지 (파일 경로, 시스템 이름, URL 등) | 어디에 두면 되는지 / 어떻게 전달해 주실지 | Status | 사용자가 첨부한 위치 또는 메모 (다음 run 전에 사용자가 채움) |
317
- |---------|-----------|--------------------------------------|------------------------------------------------------------|--------------------------------------------|--------|---------------------------------------------------------------|
318
- | A1 | `<TICKET-or-fallback>` | <어떤 자료를 받아야 하는지를 1~2문장으로. 예: "오류가 처음 보고된 시각 전후 30분 동안의 worker 로그가 필요합니다. 어떤 task가 실패했고 그때 큐에 무엇이 쌓여 있었는지를 함께 확인해야 root cause 가설을 좁힐 수 있기 때문입니다." > | <예: "Datadog 로그에서 `service:worker env:prod` 필터, 시각 범위 2026-04-30 09:30~10:30 KST" / 또는 정확한 파일 경로> | <예: "`.project-docs/tasks/8852/logs/` 아래에 `worker-2026-04-30.log` 라는 이름으로 저장해 주시면 됩니다."> | open | <빈칸으로 두고 다음 run 전에 사용자가 채움> |
319
-
320
- ### 5.2 사용자 확인 질문 (Questions for the User)
321
-
322
- 하위 섹션에는 **사용자가 직접 결정해 주시거나 확인해 주셔야만 다음 단계로 진행할 수 있는 질문**만 적습니다. 자료 요청은 위 5.1에 두고 여기에는 의사결정/확인 질문만 둡니다. 질문이 없다면 `- 추가로 확인이 필요한 질문이 없습니다.` 한 줄만 남깁니다.
323
-
324
- 질문은 다음을 분명히 보여주어야 합니다.
325
-
326
- - **왜 답이 필요한가**: 답에 따라 무엇이 어떻게 달라지는지 문장으로.
327
- - **무엇을 묻는가**: 약어 없이 풀어 완전한 문장의 질문.
328
- - **어떤 형태의 답을 기대하는가**: 예/아니오인지, 둘 중 하나를 고르는 선택인지, 숫자/날짜/파일경로인지, 아니면 짧은 자유서술인지. 가능하면 보기를 함께 제시합니다.
329
-
330
- | 질문 ID | Ticket ID | 이 답이 필요한 이유 (왜) | 질문 본문 (무엇을 묻는지, 풀어 쓴 문장) | 기대하는 답의 형태 / 보기 | Blocking (예 / 아니오) | Status | 사용자 답변 (다음 run 전에 사용자가 채움) |
331
- |---------|-----------|--------------------------|-----------------------------------------|----------------------------|------------------------|--------|--------------------------------------------|
332
- | Q1 | `<TICKET-or-fallback>` | <예: "이 결정에 따라 `bugfix`로 이어갈지 `feature`로 이어갈지가 갈리며, 다음 run에서 사용할 task-type이 달라집니다."> | <예: "지난 주 새벽에 보고된 결제 실패가 일회성 사고였는지, 아니면 같은 증상이 다시 발생한 적이 있는지 알려주실 수 있을까요? 같은 증상이 다시 발생했다면 가장 최근 발생 시각과 영향 받은 사용자 수도 함께 부탁드립니다."> | <예: "다음 중 한 줄로 답해 주시면 됩니다 — (a) 일회성으로 그 후 재발 없음, (b) 재발 있음, 가장 최근 시각: YYYY-MM-DD HH:MM, 영향 사용자 수 약 N명"> | 예 | open | <빈칸으로 두고 다음 run 전에 사용자가 채움> |
299
+ 스크립트로 자동화하실 때는 셸 형식 `scripts/okstra.sh --task-key {{TASK_KEY}} --task-type {{TASK_TYPE}} --clarification-response <이 파일 경로>` 그대로 사용하실 수 있습니다. Node `okstra` admin CLI 는 `--task-key`/`--task-type`/`--resume-clarification` 을 받지 않으므로 위 두 진입점 중 하나를 사용하세요.
300
+
301
+ ### 5.1 Clarification Items table
302
+
303
+ | ID | Ticket ID | Kind | Statement (무엇이 필요한지 + ) | Expected form (답·자료의 모양) | Blocks | Status | User input (사용자가 다음 run 전에 채움) |
304
+ |----|-----------|------|----------------------------------|-------------------------------|--------|--------|------------------------------------------|
305
+ | C-001 | `<TICKET-or-fallback>` | `decision` | <예: "지난 새벽에 보고된 결제 실패가 일회성 사고였는지, 아니면 같은 증상이 다시 발생한 적이 있는지 알려주십시오. 결정에 따라 다음 run 의 task-type 이 `error-analysis` 로 갈지 `requirements-discovery` 로 갈지가 갈립니다."> | <예: "(a) 일회성·재발 없음, (b) 재발 있음 — 가장 최근 시각 YYYY-MM-DD HH:MM, 영향 사용자 수 약 N명"> | `next-phase` | open | <빈칸으로 두고 다음 run 전에 사용자가 채움> |
306
+ | C-002 | `<TICKET-or-fallback>` | `material` | <예: "오류가 처음 보고된 시각 전후 30분 동안의 worker 로그가 필요합니다. 어떤 task 가 실패했고 그때 큐에 무엇이 쌓여 있었는지를 함께 확인해야 root cause 가설을 좁힐 수 있기 때문입니다. Datadog `service:worker env:prod` 필터, 시각 범위 2026-04-30 09:30~10:30 KST. `.project-docs/tasks/8852/logs/worker-2026-04-30.log` 로 저장해 주십시오."> | <예: "위 경로의 .log 파일 (gzip 압축 가능)"> | `next-phase` | open | |
307
+ | C-003 | `<TICKET-or-fallback>` | `data-point` | <예: "본 배치 시작 직전 `common.FontManualClassifiers` `prediction=0` / `prediction=1` 수가 필요합니다. acceptance #5 동일성 검증 ground truth 입니다."> | <예: " 정수 (prediction=0 count, prediction=1 count) 줄로"> | `approval` | open | |
308
+
309
+ ### 5.2 컬럼 가이드
310
+
311
+ - **`Kind`** `{material, decision, data-point}`:
312
+ - `material` — 파일 / 스냅샷 / 로그 / 스크린샷 등 사용자가 따로 **첨부 또는 경로 공유** 해야 하는 자료.
313
+ - `decision` 사용자가 선택지 중 하나를 고르거나 (a/b/c 보기, yes/no) 짧게 자유서술로 결정해 주셔야 진행 가능한 의사결정.
314
+ - `data-point` — 숫자 / ID / 날짜 / 짧은 문자열처럼 사용자가 명령 한 번으로 뽑아 **inline 으로** 답해 주실 수 있는 사실 확인.
315
+ - **`Statement`**: 무엇을 묻거나 받아야 하는지 + 필요한지 + (material 경우) 어디서 가져오는지·어디에 두면 되는지를 1~3 문장으로. 자료 출처가 길면 본 셀에 풀어 적고 `Expected form` 셀은 짧게 유지합니다.
316
+ - **`Expected form`**: / 자료의 모양 예/아니오, 보기 a/b/c, 정수, 날짜, 파일 경로, URL 등. 가능하면 정확한 보기 또는 예시 한 줄.
317
+ - **`Blocks`** `{approval, next-phase, none}`:
318
+ - `approval` — `implementation-planning` 의 User Approval Request 통과를 막는 항목. 모두 `resolved`/`obsolete` 가 되어야 상단 체크박스를 `[x]` 로 바꿀 수 있습니다.
319
+ - `next-phase` 승인 게이트가 아니더라도 다음 run 진입에 답이 필요한 항목.
320
+ - `none` — informational. 답이 없어도 다음 phase 는 진행 가능하지만 audit 기록을 위해 남깁니다.
321
+ - **`Status`** `{open, answered, resolved, obsolete}`:
322
+ - `open` — 요청했지만 아직 받지 못함.
323
+ - `answered` — 사용자가 `User input` 칸을 채워 두었음 (다음 run 의 lead 가 검증해야 함).
324
+ - `resolved` — 다음 run 에서 lead 가 답변·자료를 받아 검증을 마침.
325
+ - `obsolete` — 이후 분석 결과로 더 이상 필요 없어진 항목.
326
+ - **`User input`**: 사용자가 직접 채우는 칸. `material` 이면 첨부 파일 경로 / URL / 메모, `decision` 이면 선택 또는 짧은 서술, `data-point` 이면 inline 값.
327
+
328
+ 작성 안티패턴 (자동 거절):
329
+
330
+ - 같은 항목을 `## 4.5.8 User Approval Request` 보충 메모와 본 표에 동시 등록 — `Blocks=approval` 행 하나로 충분합니다.
331
+ - 한 행을 두 개로 나눠 "자료 요청 행" 과 "관련 질문 행" 으로 쪼개기 — 자료 + yes/no 가 묶인 항목은 `Kind=material`, `Expected form` 에 "파일 경로 + yes/no" 라고 적은 한 행으로 처리합니다.
332
+ - 사용자가 답하지 않아도 진행 가능한 항목을 `Blocks=approval` 로 표시 — `Blocks=none` 또는 `next-phase` 로 정확히 표시하세요. 잘못 표시하면 사용자가 불필요하게 게이트에 걸립니다.
333
333
 
334
334
  ## 6. Recommended Next Steps
335
335
 
@@ -346,7 +346,7 @@ When concrete actions exist, list them as a numbered list using the rules below.
346
346
  - 별도 터미널: `scripts/okstra.sh --task-key {{TASK_KEY}} --task-type <next-phase>`
347
347
  2. **Additional verification needed before implementation or release.** List read-only checks (test commands, log queries, dashboard URLs) that the user should run before moving to the next phase. No state-mutating commands here.
348
348
  3. **Follow-up tasks or related tasks if needed.** Reference them by `task-key` when they already exist; otherwise describe the new brief to draft.
349
- 4. **If section 5 has any `open` rows**, the highest-priority next step MUST be the clarification turn-around. Show both forms:
349
+ 4. **If section 5 has any rows with `Status` in `{open, answered}`**, the highest-priority next step MUST be the clarification turn-around. Show both forms:
350
350
  - Preferred (interactive) — opens this file in `$EDITOR`, then auto-reruns the same phase with `--clarification-response` carry-in:
351
351
  - Claude Code 세션 안: `/okstra-run resume-clarification task-key={{TASK_KEY}}`
352
352
  - 별도 터미널: `scripts/okstra.sh --resume-clarification --task-key {{TASK_KEY}}`
@@ -370,7 +370,7 @@ Empty-state placeholder, copy verbatim when nothing else applies:
370
370
  - `out-of-plan` — 구현 중 발견됐으나 본 plan의 file list / step 범위를 벗어나 처리하지 못한 항목 (Out-of-plan edits 블록에 기록되지 않고 미처리로 남은 것).
371
371
  - `verifier-concern` — verifier가 PASS는 줬으나 후속 개선 권고로 남긴 항목.
372
372
  - `scope-boundary` — `implementation-planning`의 `4.5.5 Dependency / Migration Risk` 또는 task-brief `Out of Scope` 에서 의도적으로 제외했으나, 본 run 결과에 비추어 별도 ticket이 필요한 항목.
373
- - `open-question` — `4.5.9 Open Questions` / `Section 5 Clarification Requests` 에서 분리한 후속 작업.
373
+ - `open-question` — `## 5. Clarification Items` 에서 분리한 후속 작업 (사용자 답을 그대로 닫지 않고 별도 task 로 진행해야 하는 경우).
374
374
  - `manual` — lead가 추가로 식별한 follow-up.
375
375
 
376
376
  규칙:
package/src/wizard.mjs ADDED
@@ -0,0 +1,105 @@
1
+ import { runPythonModule } from "./_python-helper.mjs";
2
+ import { resolvePaths } from "./paths.mjs";
3
+
4
+ const USAGE = `okstra wizard — interactive okstra-run input collector
5
+
6
+ Used by the okstra-run skill to drive the per-step prompt loop. Each
7
+ subcommand round-trips a JSON state file held by the skill.
8
+
9
+ Subcommands:
10
+ init seed a fresh wizard state and emit the first prompt
11
+ step submit an answer (or fetch the current prompt) and emit next
12
+ render-args emit the final --flag/value map for 'okstra render-bundle'
13
+ confirmation emit the multi-line confirmation echo block
14
+
15
+ Usage:
16
+ okstra wizard init --state-file <path> --project-root <p> --project-id <id>
17
+ okstra wizard step --state-file <path> [--answer <value>]
18
+ okstra wizard render-args --state-file <path>
19
+ okstra wizard confirmation --state-file <path>
20
+
21
+ 'init' auto-fills --workspace-root from 'okstra paths --field workspace',
22
+ so callers do not pass it.
23
+
24
+ All subcommands emit a single JSON object on stdout. On validation failure
25
+ 'step' returns {ok:false, error, current} so the skill can re-prompt.
26
+ `;
27
+
28
+ function parseFlags(args) {
29
+ const out = {};
30
+ for (let i = 0; i < args.length; i += 1) {
31
+ const a = args[i];
32
+ if (!a.startsWith("--")) {
33
+ throw new Error(`unexpected positional argument: ${a}`);
34
+ }
35
+ const key = a.slice(2);
36
+ const next = args[i + 1];
37
+ if (next === undefined || next.startsWith("--")) {
38
+ throw new Error(`flag --${key} requires a value`);
39
+ }
40
+ out[key] = next;
41
+ i += 1;
42
+ }
43
+ return out;
44
+ }
45
+
46
+ export async function run(args) {
47
+ if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
48
+ process.stdout.write(USAGE);
49
+ return args.length === 0 ? 2 : 0;
50
+ }
51
+
52
+ const [sub, ...rest] = args;
53
+ if (!["init", "step", "render-args", "confirmation"].includes(sub)) {
54
+ process.stderr.write(`error: unknown wizard subcommand '${sub}'\n\n${USAGE}`);
55
+ return 2;
56
+ }
57
+
58
+ let flags;
59
+ try {
60
+ flags = parseFlags(rest);
61
+ } catch (err) {
62
+ process.stderr.write(`error: ${err.message}\n\n${USAGE}`);
63
+ return 2;
64
+ }
65
+
66
+ if (!flags["state-file"]) {
67
+ process.stderr.write("error: --state-file is required\n");
68
+ return 2;
69
+ }
70
+
71
+ // build python argv
72
+ const pyArgs = [sub, "--state-file", flags["state-file"]];
73
+ if (sub === "init") {
74
+ if (!flags["project-root"] || !flags["project-id"]) {
75
+ process.stderr.write("error: init requires --project-root and --project-id\n");
76
+ return 2;
77
+ }
78
+ const paths = await resolvePaths();
79
+ pyArgs.push("--workspace-root", paths.workspace);
80
+ pyArgs.push("--project-root", flags["project-root"]);
81
+ pyArgs.push("--project-id", flags["project-id"]);
82
+ } else if (sub === "step" && flags.answer !== undefined) {
83
+ pyArgs.push("--answer", flags.answer);
84
+ }
85
+
86
+ const result = await runPythonModule({
87
+ module: "okstra_ctl.wizard",
88
+ args: pyArgs,
89
+ stdio: "capture",
90
+ });
91
+
92
+ if (result.code !== 0 && !result.stdout.trim()) {
93
+ process.stdout.write(
94
+ JSON.stringify(
95
+ { ok: false, stage: "python", reason: result.stderr.trim() || "no output" },
96
+ null,
97
+ 2,
98
+ ) + "\n",
99
+ );
100
+ return 1;
101
+ }
102
+
103
+ process.stdout.write(result.stdout);
104
+ return result.code === 0 ? 0 : result.code;
105
+ }