okstra 0.25.1 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.kr.md +1 -0
- package/README.md +1 -0
- package/docs/kr/architecture.md +1 -1
- package/docs/kr/cli.md +10 -1
- package/docs/superpowers/specs/2026-05-15-implementation-plan-verification-design.md +254 -0
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/SKILL.md +30 -2
- package/runtime/bin/okstra.sh +1 -0
- package/runtime/prompts/profiles/_common-contract.md +5 -0
- package/runtime/prompts/profiles/implementation-planning.md +3 -0
- package/runtime/python/lib/okstra/cli.sh +8 -1
- package/runtime/python/lib/okstra/globals.sh +3 -0
- package/runtime/python/lib/okstra/usage.sh +8 -1
- package/runtime/python/okstra_ctl/render.py +32 -0
- package/runtime/python/okstra_ctl/run.py +27 -0
- package/runtime/python/okstra_ctl/wizard.py +234 -8
- package/runtime/skills/okstra-convergence/SKILL.md +203 -0
- package/runtime/skills/okstra-run/SKILL.md +27 -14
- package/runtime/templates/reports/final-report.template.md +34 -0
- package/runtime/validators/validate-run.py +71 -0
- package/src/wizard.mjs +21 -5
|
@@ -225,6 +225,40 @@ revert 경로와 롤백 트리거 신호를 표로 정리합니다. 추상적
|
|
|
225
225
|
- 본 섹션에는 승인 결정에 영향을 주는 *플랜 측 보충 메모*만 적습니다(예: 위험을 줄이기 위한 사전 작업, 승인 전 사용자가 확인해 두어야 할 사항). 승인 마커는 본 섹션이 아니라 상단 블록의 체크박스로만 부여합니다.
|
|
226
226
|
- 사용자가 답하거나 자료를 첨부해야만 승인이 가능한 항목은 **이 섹션에 적지 않습니다** — `## 5. Clarification Items` 표에 한 행으로 등록하고 `Blocks=approval` 로 표시하세요. 같은 항목을 두 위치에 적으면 sync 가 깨집니다.
|
|
227
227
|
|
|
228
|
+
### 4.5.9 Plan Body Verification (워커 사후 검증)
|
|
229
|
+
|
|
230
|
+
> **이 sub-section 은 `task-type` = `implementation-planning` 실행 결과에만 포함하세요.** Phase 6 에서 report-writer 가 합성한 4.5 본문(Option Candidates / Stepwise Execution Order / Dependency / Validation Checklist / Rollback)을 lead 가 plan-item 단위로 쪼개 워커들에게 다시 던지고 `AGREE / DISAGREE / SUPPLEMENT` 평결을 수집한 결과입니다. 자세한 라운드 프로토콜은 `skills/okstra-convergence/SKILL.md` "Plan-body verification mode" 섹션을 참고하세요.
|
|
231
|
+
|
|
232
|
+
- **Round count**: `<N>` (default: 1)
|
|
233
|
+
- **Gate result**: `<passed | passed-with-dissent | blocked-by-disagreement | aborted-non-result>`
|
|
234
|
+
- `passed` → 본 보고서 상단 `User Approval Request` 체크박스가 렌더됩니다.
|
|
235
|
+
- `passed-with-dissent` → 상단 체크박스가 렌더되되, 반대 의견은 아래 `Dissent log` 에 기록.
|
|
236
|
+
- `blocked-by-disagreement` → 상단 체크박스 라인 자체를 **렌더하지 않습니다**. majority DISAGREE 인 plan item 마다 `## 5. Clarification Items` 에 `Blocks=approval` row 가 추가되며, 사용자가 응답해야 다음 phase 로 넘어갈 수 있습니다.
|
|
237
|
+
- `aborted-non-result` → 워커 dispatch 가 모두 non-result (timeout / error). 상단 체크박스 라인 렌더하지 않음 + `## 5. Clarification Items` 에 "plan-body verification could not run" row 가 추가됩니다.
|
|
238
|
+
|
|
239
|
+
#### Verdict table
|
|
240
|
+
|
|
241
|
+
각 plan item 1 행. `Plan item` 열은 `P-Opt-<N>` / `P-Step-<N>` / `P-Dep-<N>` / `P-Val-<N>` / `P-Rb-<N>` prefix 사용. `Section` 열은 4.5 내부 sub-section 번호 (예: `4.5.4`).
|
|
242
|
+
|
|
243
|
+
| Plan item | Ticket ID | Section | <worker1> | <worker2> | ... | Classification |
|
|
244
|
+
|-----------|-----------|---------|-----------|-----------|-----|----------------|
|
|
245
|
+
| P-Opt-1 | `<id>` | 4.5.1 | AGREE | AGREE | ... | full-consensus |
|
|
246
|
+
| P-Step-3 | `<id>` | 4.5.4 | DISAGREE(a) | DISAGREE(a) | ... | majority-disagree → C-7 |
|
|
247
|
+
|
|
248
|
+
- DISAGREE 셀에는 spec 의 `(a|b|c|d|e)` breakage kind 를 함께 표기 (예: `DISAGREE(a)` = 참조 file path / symbol 불일치).
|
|
249
|
+
- 마지막 열 `Classification` ∈ `{full-consensus, partial-consensus, worker-unique, majority-disagree → C-<N>}`. `majority-disagree → C-<N>` 의 `C-<N>` 은 본 보고서 `## 5. Clarification Items` 표에서 해당 변환 row 의 ID 와 일치해야 합니다.
|
|
250
|
+
|
|
251
|
+
#### Dissent log
|
|
252
|
+
|
|
253
|
+
`partial-consensus` 와 `worker-unique` 로 분류된 plan item 의 반대 의견을 plan item 별로 묶어 적습니다. `majority-disagree` 항목의 반대 의견은 본 섹션 대신 `## 5. Clarification Items` 의 해당 row 의 `Statement` 컬럼에 옮겨 적습니다.
|
|
254
|
+
|
|
255
|
+
- **P-XXX-N**: `<worker-role>` — `<반대 의견 본문 2-3 sentences>`
|
|
256
|
+
|
|
257
|
+
#### Notes
|
|
258
|
+
|
|
259
|
+
- 본 sub-section 이 누락된 채로 task-type = implementation-planning final report 가 생성되면 validator 가 `contract-violated` 로 종료합니다.
|
|
260
|
+
- `Gate result` 가 `blocked-by-disagreement` / `aborted-non-result` 인데 상단 `User Approval Request` 체크박스 라인이 존재하면 동일하게 contract violation 입니다.
|
|
261
|
+
|
|
228
262
|
## 4.6 Release Handoff Deliverables (release-handoff runs only)
|
|
229
263
|
|
|
230
264
|
> **이 섹션은 `task-type` = `release-handoff` 실행 결과에만 포함하세요. 다른 task-type에서는 섹션 전체를 삭제하고 5번 섹션으로 넘어갑니다.**
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import argparse
|
|
6
6
|
import json
|
|
7
7
|
import os
|
|
8
|
+
import re
|
|
8
9
|
import sys
|
|
9
10
|
from datetime import datetime, timezone
|
|
10
11
|
from pathlib import Path
|
|
@@ -527,6 +528,35 @@ PLANNING_REQUIRED_SECTIONS = (
|
|
|
527
528
|
"Validation Checklist",
|
|
528
529
|
"Rollback",
|
|
529
530
|
"User Approval Request",
|
|
531
|
+
"Plan Body Verification",
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
PLAN_VERIFY_GATE_VALUES = (
|
|
535
|
+
"passed",
|
|
536
|
+
"passed-with-dissent",
|
|
537
|
+
"blocked-by-disagreement",
|
|
538
|
+
"aborted-non-result",
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
# `Gate result:` line in §4.5.9 of the final report — captures the value
|
|
542
|
+
# token that follows. Tolerates arbitrary markdown formatting between the
|
|
543
|
+
# label and the value (backticks for inline code, double-asterisks for
|
|
544
|
+
# bold, colons, hyphens, whitespace). The captured value is then
|
|
545
|
+
# validated against `PLAN_VERIFY_GATE_VALUES` below so typo'd or unknown
|
|
546
|
+
# values surface as their own failure rather than silently no-matching.
|
|
547
|
+
_GATE_RESULT_RE = re.compile(
|
|
548
|
+
r"Gate result[^A-Za-z\n]+(?P<value>[a-z][a-z\-]+)",
|
|
549
|
+
re.IGNORECASE,
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
# Approval marker line — the bullet that toggles to grant approval. Both
|
|
553
|
+
# unchecked (`- [ ] Approved`) and checked (`- [x] Approved`) forms count
|
|
554
|
+
# as "checkbox present" for this gate. Line-anchored to avoid false
|
|
555
|
+
# positives from inline-code prose examples elsewhere in the template
|
|
556
|
+
# (mirrors `APPROVED_PLAN_PATTERN` in scripts/okstra_ctl/run.py:79).
|
|
557
|
+
_APPROVAL_CHECKBOX_RE = re.compile(
|
|
558
|
+
r"^[ \t]*(?:[-*+][ \t]+)?`?\[[ xX]\][ \t]*Approved`?[ \t]*$",
|
|
559
|
+
re.MULTILINE,
|
|
530
560
|
)
|
|
531
561
|
|
|
532
562
|
|
|
@@ -541,6 +571,13 @@ def validate_phase_boundary(
|
|
|
541
571
|
required deliverable sections; absence indicates a planning run that
|
|
542
572
|
skipped its core outputs (or an implementation run that ran under the
|
|
543
573
|
wrong task type).
|
|
574
|
+
|
|
575
|
+
Additionally enforces the Plan Body Verification gate (§4.5.9):
|
|
576
|
+
- gate ∈ {passed, passed-with-dissent} → top-level Approval checkbox
|
|
577
|
+
MUST be present.
|
|
578
|
+
- gate ∈ {blocked-by-disagreement, aborted-non-result} → checkbox
|
|
579
|
+
MUST be absent (lead converted findings into Clarification rows
|
|
580
|
+
instead of opening the gate).
|
|
544
581
|
"""
|
|
545
582
|
if task_type != "implementation-planning":
|
|
546
583
|
return
|
|
@@ -554,6 +591,40 @@ def validate_phase_boundary(
|
|
|
554
591
|
f"`{needle}`"
|
|
555
592
|
)
|
|
556
593
|
|
|
594
|
+
gate_match = _GATE_RESULT_RE.search(content)
|
|
595
|
+
if gate_match is None:
|
|
596
|
+
# The `Plan Body Verification` heading check above already covers
|
|
597
|
+
# the wholly-missing case; a heading present without a `Gate result`
|
|
598
|
+
# line is its own contract violation.
|
|
599
|
+
if "Plan Body Verification" in content:
|
|
600
|
+
failures.append(
|
|
601
|
+
"implementation-planning report has `Plan Body Verification` "
|
|
602
|
+
"section but no `Gate result:` line — required by §4.5.9."
|
|
603
|
+
)
|
|
604
|
+
return
|
|
605
|
+
gate_value = gate_match.group("value").strip().lower()
|
|
606
|
+
if gate_value not in PLAN_VERIFY_GATE_VALUES:
|
|
607
|
+
failures.append(
|
|
608
|
+
"implementation-planning report `Gate result` value "
|
|
609
|
+
f"`{gate_value}` is not one of "
|
|
610
|
+
f"{', '.join(PLAN_VERIFY_GATE_VALUES)}."
|
|
611
|
+
)
|
|
612
|
+
return
|
|
613
|
+
checkbox_present = _APPROVAL_CHECKBOX_RE.search(content) is not None
|
|
614
|
+
if gate_value in ("passed", "passed-with-dissent") and not checkbox_present:
|
|
615
|
+
failures.append(
|
|
616
|
+
"implementation-planning report Gate result is "
|
|
617
|
+
f"`{gate_value}` but the top-level `User Approval Request` "
|
|
618
|
+
"checkbox line (`- [ ] Approved` / `- [x] Approved`) is missing."
|
|
619
|
+
)
|
|
620
|
+
if gate_value in ("blocked-by-disagreement", "aborted-non-result") and checkbox_present:
|
|
621
|
+
failures.append(
|
|
622
|
+
"implementation-planning report Gate result is "
|
|
623
|
+
f"`{gate_value}` but a top-level `User Approval Request` "
|
|
624
|
+
"checkbox line is present — gate must NOT render the checkbox "
|
|
625
|
+
"for this gate result."
|
|
626
|
+
)
|
|
627
|
+
|
|
557
628
|
|
|
558
629
|
def _refresh_task_catalog(project_root: Path, task_manifest: dict) -> tuple[bool, str]:
|
|
559
630
|
"""Regenerate `discovery/task-catalog.json` so it stops trailing the
|
package/src/wizard.mjs
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { mkdtempSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
1
5
|
import { runPythonModule } from "./_python-helper.mjs";
|
|
2
6
|
import { resolvePaths } from "./paths.mjs";
|
|
3
7
|
|
|
@@ -7,12 +11,14 @@ Used by the okstra-run skill to drive the per-step prompt loop. Each
|
|
|
7
11
|
subcommand round-trips a JSON state file held by the skill.
|
|
8
12
|
|
|
9
13
|
Subcommands:
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
new-state-file print a fresh absolute state-file path on stdout (no I/O on the file itself)
|
|
15
|
+
init seed a fresh wizard state and emit the first prompt
|
|
16
|
+
step submit an answer (or fetch the current prompt) and emit next
|
|
17
|
+
render-args emit the final --flag/value map for 'okstra render-bundle'
|
|
18
|
+
confirmation emit the multi-line confirmation echo block
|
|
14
19
|
|
|
15
20
|
Usage:
|
|
21
|
+
okstra wizard new-state-file
|
|
16
22
|
okstra wizard init --state-file <path> --project-root <p> --project-id <id>
|
|
17
23
|
okstra wizard step --state-file <path> [--answer <value>]
|
|
18
24
|
okstra wizard render-args --state-file <path>
|
|
@@ -50,11 +56,21 @@ export async function run(args) {
|
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
const [sub, ...rest] = args;
|
|
53
|
-
if (!["init", "step", "render-args", "confirmation"].includes(sub)) {
|
|
59
|
+
if (!["new-state-file", "init", "step", "render-args", "confirmation"].includes(sub)) {
|
|
54
60
|
process.stderr.write(`error: unknown wizard subcommand '${sub}'\n\n${USAGE}`);
|
|
55
61
|
return 2;
|
|
56
62
|
}
|
|
57
63
|
|
|
64
|
+
if (sub === "new-state-file") {
|
|
65
|
+
if (rest.length > 0) {
|
|
66
|
+
process.stderr.write("error: new-state-file takes no arguments\n");
|
|
67
|
+
return 2;
|
|
68
|
+
}
|
|
69
|
+
const dir = mkdtempSync(join(tmpdir(), "okstra-wizard-"));
|
|
70
|
+
process.stdout.write(join(dir, "state.json") + "\n");
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
58
74
|
let flags;
|
|
59
75
|
try {
|
|
60
76
|
flags = parseFlags(rest);
|