okstra 0.49.0 → 0.51.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 +8 -7
- package/README.md +8 -7
- package/bin/okstra +2 -0
- package/docs/kr/architecture.md +23 -24
- package/docs/kr/cli.md +6 -6
- package/docs/project-structure-overview.md +13 -9
- package/docs/superpowers/plans/2026-06-05-wizard-batch-prompts.md +559 -0
- package/docs/superpowers/specs/2026-06-05-wizard-batch-prompts-design.md +121 -0
- package/docs/task-process/error-analysis.md +1 -1
- package/docs/task-process/final-verification.md +1 -1
- package/docs/task-process/release-handoff.md +1 -1
- package/docs/task-process/requirements-discovery.md +1 -1
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/SKILL.md +18 -14
- package/runtime/agents/workers/claude-worker.md +4 -4
- package/runtime/agents/workers/codex-worker.md +3 -3
- package/runtime/agents/workers/gemini-worker.md +3 -3
- package/runtime/agents/workers/report-writer-worker.md +3 -3
- package/runtime/bin/lib/okstra/cli.sh +8 -1
- package/runtime/bin/lib/okstra/globals.sh +3 -0
- package/runtime/bin/lib/okstra/interactive.sh +14 -12
- package/runtime/bin/lib/okstra/usage.sh +6 -0
- package/runtime/bin/okstra-render-report-views.py +1 -1
- package/runtime/bin/okstra-team-reconcile.sh +28 -0
- package/runtime/bin/okstra.sh +2 -0
- package/runtime/prompts/launch.template.md +4 -2
- package/runtime/prompts/profiles/_common-contract.md +15 -15
- package/runtime/prompts/profiles/_implementation-deliverable.md +1 -1
- package/runtime/prompts/profiles/_implementation-executor.md +3 -3
- package/runtime/prompts/profiles/_implementation-verifier.md +2 -2
- package/runtime/prompts/profiles/error-analysis.md +1 -1
- package/runtime/prompts/profiles/final-verification.md +2 -2
- package/runtime/prompts/profiles/implementation-planning.md +10 -9
- package/runtime/prompts/profiles/implementation.md +1 -1
- package/runtime/prompts/profiles/improvement-discovery.md +5 -5
- package/runtime/prompts/profiles/release-handoff.md +2 -2
- package/runtime/prompts/profiles/requirements-discovery.md +2 -2
- package/runtime/python/okstra_ctl/analysis_packet.py +259 -0
- package/runtime/python/okstra_ctl/clarification_items.py +11 -11
- package/runtime/python/okstra_ctl/context_cost.py +308 -0
- package/runtime/python/okstra_ctl/migrate.py +2 -12
- package/runtime/python/okstra_ctl/paths.py +22 -0
- package/runtime/python/okstra_ctl/render.py +285 -126
- package/runtime/python/okstra_ctl/render_final_report.py +32 -1
- package/runtime/python/okstra_ctl/report_views.py +12 -12
- package/runtime/python/okstra_ctl/run.py +510 -248
- package/runtime/python/okstra_ctl/sequence.py +2 -5
- package/runtime/python/okstra_ctl/team_reconcile.py +131 -0
- package/runtime/python/okstra_ctl/wizard.py +219 -136
- package/runtime/python/okstra_ctl/workflow.py +1 -1
- package/runtime/python/okstra_ctl/worktree.py +13 -5
- package/runtime/schemas/final-report-v1.0.schema.json +4 -0
- package/runtime/skills/okstra-brief/SKILL.md +1 -1
- package/runtime/skills/okstra-coding-preflight/SKILL.md +69 -0
- package/runtime/skills/okstra-coding-preflight/architecture/hexagonal.md +116 -0
- package/runtime/skills/okstra-coding-preflight/clean-code.md +254 -0
- package/runtime/skills/okstra-coding-preflight/languages/java.md +64 -0
- package/runtime/skills/okstra-coding-preflight/languages/javascript-typescript.md +87 -0
- package/runtime/skills/okstra-coding-preflight/languages/kotlin.md +69 -0
- package/runtime/skills/okstra-coding-preflight/languages/nodejs.md +66 -0
- package/runtime/skills/okstra-coding-preflight/languages/python.md +179 -0
- package/runtime/skills/okstra-coding-preflight/languages/rust.md +105 -0
- package/runtime/skills/okstra-coding-preflight/languages/sql.md +68 -0
- package/runtime/skills/okstra-context-loader/SKILL.md +12 -6
- package/runtime/skills/okstra-convergence/SKILL.md +8 -8
- package/runtime/skills/okstra-inspect/SKILL.md +100 -1
- package/runtime/skills/okstra-report-writer/SKILL.md +27 -23
- package/runtime/skills/okstra-run/SKILL.md +3 -1
- package/runtime/skills/okstra-team-contract/SKILL.md +8 -5
- package/runtime/templates/reports/final-report.template.md +188 -187
- package/runtime/templates/reports/i18n/en.json +4 -4
- package/runtime/templates/reports/i18n/ko.json +4 -4
- package/runtime/templates/reports/implementation-planning-input.template.md +1 -1
- package/runtime/templates/reports/release-handoff-input.template.md +1 -1
- package/runtime/templates/reports/user-response.template.md +1 -1
- package/runtime/templates/worker-prompt-preamble.md +4 -4
- package/runtime/validators/lib/fixtures.sh +2 -2
- package/runtime/validators/validate-implementation-plan-stages.py +9 -9
- package/runtime/validators/validate-report-views.py +10 -10
- package/runtime/validators/validate-run.py +36 -36
- package/runtime/validators/validate_improvement_report.py +8 -8
- package/src/_python-helper.mjs +3 -3
- package/src/context-cost.mjs +27 -0
- package/src/install.mjs +1 -0
- package/src/uninstall.mjs +1 -0
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"declinedFixRecommendations": "- None.",
|
|
16
16
|
"discrepancy": "- None.",
|
|
17
17
|
"lingeringRisks": "- No tracked lingering risks.",
|
|
18
|
-
"noClarification": "- No additional information requested. The Section
|
|
19
|
-
"noFollowUp": "- No follow-up tasks. The next phase for this run is in §
|
|
18
|
+
"noClarification": "- No additional information requested. The Section 7 verdict stands as-is.",
|
|
19
|
+
"noFollowUp": "- No follow-up tasks. The next phase for this run is in §3 (Recommended Next Steps)."
|
|
20
20
|
},
|
|
21
21
|
"columns": {
|
|
22
22
|
"recordMeta": "Record",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"stepwiseExecutionOrder": "Stepwise Execution Order"
|
|
40
40
|
},
|
|
41
41
|
"sectionIntro": {
|
|
42
|
-
"verdictCard": "At-a-glance verdict card — a summary that mirrors the values in `##
|
|
42
|
+
"verdictCard": "At-a-glance verdict card — a summary that mirrors the values in `## 7. Final Verdict` and `## 3. Recommended Next Steps`.",
|
|
43
43
|
"clarificationCarryIn": "Unresolved `Clarification Items` from the prior report, re-examined against new evidence and carried in with updated status.",
|
|
44
44
|
"ticketCoverage": "Summary of the core problems, requirements, and verification targets this run covered.",
|
|
45
45
|
"executionStatus": "At-a-glance table of each worker's status, assigned model, and key findings.",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"intro": "Final conclusion and recommended direction. `Direction` values: `continue-investigation / begin-implementation / approve / reject / hold`. When `task-type` is `final-verification`, `Verdict Token` is one of `accepted / conditional-accept / blocked` and serves as the `release-handoff` entry gate. For all other task-types: `not-applicable`."
|
|
72
72
|
},
|
|
73
73
|
"evidence": {
|
|
74
|
-
"sourceItemsColumnNote": "The `Source items` column is described in §
|
|
74
|
+
"sourceItemsColumnNote": "The `Source items` column is described in §6.1."
|
|
75
75
|
},
|
|
76
76
|
"roundHistory": {
|
|
77
77
|
"round2SkippedReasonNote": "(one of: `queue-empty`, `max-rounds-1`, `all-reverify-non-result`, `not-skipped`, `convergence-disabled`, `single-analyser-only`)",
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"declinedFixRecommendations": "- 없음.",
|
|
16
16
|
"discrepancy": "- 없음.",
|
|
17
17
|
"lingeringRisks": "- 추적 대상 잔존 위험 없음.",
|
|
18
|
-
"noClarification": "- 추가 정보 요청 없음. Section
|
|
19
|
-
"noFollowUp": "- 후속 작업 없음. 본 run 의 다음 phase 는 §
|
|
18
|
+
"noClarification": "- 추가 정보 요청 없음. Section 7 의 최종 판단이 그대로 유효합니다.",
|
|
19
|
+
"noFollowUp": "- 후속 작업 없음. 본 run 의 다음 phase 는 §3 (Recommended Next Steps) 참고."
|
|
20
20
|
},
|
|
21
21
|
"columns": {
|
|
22
22
|
"recordMeta": "항목",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"stepwiseExecutionOrder": "단계별 실행 순서"
|
|
40
40
|
},
|
|
41
41
|
"sectionIntro": {
|
|
42
|
-
"verdictCard": "한눈에 보는 결과 카드 — `##
|
|
42
|
+
"verdictCard": "한눈에 보는 결과 카드 — `## 7. Final Verdict` 와 `## 3. Recommended Next Steps` 의 값을 그대로 옮긴 요약입니다.",
|
|
43
43
|
"clarificationCarryIn": "이전 보고서의 미해결 `Clarification Items` 를 새 증거로 재검토해 해소·갱신한 뒤 이어받은 항목입니다.",
|
|
44
44
|
"ticketCoverage": "이 run 이 다룬 핵심 문제·요구사항·검증 대상 요약입니다.",
|
|
45
45
|
"executionStatus": "각 worker 의 상태·배정 모델·핵심 finding 을 한눈에 보는 표입니다.",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"intro": "최종 결론과 권장 방향입니다. `Direction` 값: `continue-investigation / begin-implementation / approve / reject / hold`. `task-type` 이 `final-verification` 일 때 `Verdict Token` 은 `accepted / conditional-accept / blocked` 중 하나이며 `release-handoff` 진입 게이트로 쓰입니다. 그 외 task-type 에서는 `not-applicable`."
|
|
72
72
|
},
|
|
73
73
|
"evidence": {
|
|
74
|
-
"sourceItemsColumnNote": "`Source items` 열 설명은 §
|
|
74
|
+
"sourceItemsColumnNote": "`Source items` 열 설명은 §6.1 과 동일합니다."
|
|
75
75
|
},
|
|
76
76
|
"roundHistory": {
|
|
77
77
|
"round2SkippedReasonNote": "(허용 값: `queue-empty`, `max-rounds-1`, `all-reverify-non-result`, `not-skipped`, `convergence-disabled`, `single-analyser-only`)",
|
|
@@ -108,7 +108,7 @@ The final report of an `implementation-planning` run MUST contain every section
|
|
|
108
108
|
|
|
109
109
|
## Stage Output Shape (reference)
|
|
110
110
|
|
|
111
|
-
This run's final report MUST emit `##
|
|
111
|
+
This run's final report MUST emit `## 5.5 Stage Map` and `## 5.5.<i> Stage <i>` sections per the implementation-planning profile §"Required deliverable shape". Two illustrative shapes:
|
|
112
112
|
|
|
113
113
|
### Shape A — single stage (small work)
|
|
114
114
|
| stage | title | depends-on | step-count | exit-contract-summary |
|
|
@@ -26,7 +26,7 @@ taskType: "{{FM_TASK_TYPE}}"
|
|
|
26
26
|
## Source Verification Report
|
|
27
27
|
|
|
28
28
|
- Path (project-relative) to the `final-verification` final-report whose verdict authorises this handoff:
|
|
29
|
-
- Verbatim quoted `Verdict Token` row from that report's `##
|
|
29
|
+
- Verbatim quoted `Verdict Token` row from that report's `## 7. Final Verdict` table (MUST have value `accepted`):
|
|
30
30
|
- Run timestamp of that final-verification run:
|
|
31
31
|
|
|
32
32
|
> If this section is empty or cites a `Verdict Token` value other than `accepted`, the lead MUST end the run immediately and route back to `final-verification`. Release-handoff never operates on `conditional-accept` or `blocked` outcomes.
|
|
@@ -18,7 +18,7 @@ created-at: <ISO 8601 UTC timestamp>
|
|
|
18
18
|
|
|
19
19
|
## Body 스키마
|
|
20
20
|
|
|
21
|
-
본문은 `# User Response` 단일 H1 헤딩 아래, 각 응답을 `## <Response ID>` 헤딩으로 구분합니다. Response ID 는 final-report `##
|
|
21
|
+
본문은 `# User Response` 단일 H1 헤딩 아래, 각 응답을 `## <Response ID>` 헤딩으로 구분합니다. Response ID 는 final-report `## 1. Clarification Items` 표의 `ID` 컬럼(`C-NNN`)을 그대로 인용합니다.
|
|
22
22
|
|
|
23
23
|
각 응답 블록의 schema:
|
|
24
24
|
|
|
@@ -8,7 +8,7 @@ It replaces the previous practice of inlining ~80 lines of identical boilerplate
|
|
|
8
8
|
|
|
9
9
|
## Required reading (analysis workers + report-writer worker)
|
|
10
10
|
|
|
11
|
-
You are required to read every input file enumerated by the dispatcher (the lead's prompt lists them under `[Required reading]`) from the very first character to the very last character before you produce any analysis output. Skimming, partial reads, jumping to a single section, or relying on prior knowledge of a similar file's structure is not acceptable.
|
|
11
|
+
You are required to read every primary input file enumerated by the dispatcher (the lead's prompt lists them under `[Required reading]`) from the very first character to the very last character before you produce any analysis output. Skimming, partial reads, jumping to a single section, or relying on prior knowledge of a similar file's structure is not acceptable. Source files listed as fallback/evidence paths are read on demand when you need to verify a citation, resolve ambiguity, or inspect material the packet says it omitted.
|
|
12
12
|
|
|
13
13
|
### Audience-scoped enumeration (BLOCKING — performance optimization)
|
|
14
14
|
|
|
@@ -16,16 +16,16 @@ Different recipients need different files. Do NOT include `final-report-template
|
|
|
16
16
|
|
|
17
17
|
| Recipient | Files included in `[Required reading]` |
|
|
18
18
|
|---|---|
|
|
19
|
-
| Claude / Codex / Gemini analysis workers | task-brief, analysis-profile, analysis-material
|
|
19
|
+
| Claude / Codex / Gemini analysis workers | analysis-packet.md as the primary compact input; task-brief, analysis-profile, analysis-material, reference-expectations, and clarification-response remain source/fallback paths, not automatic first-read files |
|
|
20
20
|
| Report writer worker (Phase 6) | all of the above **plus** the instruction-set-local `final-report-template.md` (phase-stripped) and `final-report-schema.json` (per-task-type excerpt) — NOT the full `templates/reports/...` / `schemas/...` sources |
|
|
21
21
|
| Reverify dispatches (Phase 5.5, lightweight mode) | **do NOT inject `[Required reading]` at all** — see [okstra-convergence](../skills/okstra-convergence/SKILL.md) "Reverify prompt: required-reading suppression". |
|
|
22
22
|
|
|
23
23
|
### Reading rules
|
|
24
24
|
|
|
25
25
|
- Use a single `Read` tool call per file with no `offset` and no `limit`. If a file is genuinely too large for one read, page through with explicit `offset` / `limit` covering the entire file, and state the page boundaries in your Findings.
|
|
26
|
-
- For the carry-in clarification response, walk every row of `##
|
|
26
|
+
- For the carry-in clarification response, walk every row of `## 1. Clarification Items` (`C-001`, `C-002`, ...) in full, including rows whose `User input` cell is blank — a blank `User input` with `Status=open` is itself a signal you must surface. The structural similarity between the prior final report and the upcoming output is NOT a license to skim.
|
|
27
27
|
- Write the Reading Confirmation block to your **audit sidecar** at `runs/<task-type>/worker-results/<worker>-audit-<task-type>-<seq>.md` (sibling to the main worker-results file). One short line per input file confirming end-to-end reading. Do NOT include a `## 0. Reading Confirmation` heading in the main worker-results file — the validator fails worker-results that contain one. If you cannot truthfully confirm a file end-to-end, record a `tool-failure` in the errors sidecar instead of fabricating Findings.
|
|
28
|
-
-
|
|
28
|
+
- Treat `analysis-packet.md` as the canonical primary analysis input. It preserves the source files' frontmatter and extracts the task-specific brief, phase focus, reference expectations, and carry-in clarification rows. If the packet appears incomplete or a finding depends on a source citation, open the corresponding source file and cite it directly.
|
|
29
29
|
|
|
30
30
|
---
|
|
31
31
|
|
|
@@ -350,13 +350,13 @@ report_lines.extend(
|
|
|
350
350
|
"| **전체 합계** | **`2`** | **`2`** | **`$0.02`** |",
|
|
351
351
|
"| Codex/Gemini CLI 추가 비용 | | | `$0.00` |",
|
|
352
352
|
"",
|
|
353
|
-
"##
|
|
353
|
+
"## 7. Final Verdict",
|
|
354
354
|
"",
|
|
355
355
|
"| 항목 | 값 |",
|
|
356
356
|
"|------|----|",
|
|
357
357
|
"| Verdict Token | `accepted` |",
|
|
358
358
|
"",
|
|
359
|
-
"##
|
|
359
|
+
"## 5.8 Final Verification Deliverables",
|
|
360
360
|
"",
|
|
361
361
|
"Source Implementation Report / Acceptance Blockers / Residual Risk / "
|
|
362
362
|
"Validation Evidence / Read-only Command Log / Conditional Acceptance "
|
|
@@ -12,9 +12,9 @@ from dataclasses import dataclass
|
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
from typing import List, Tuple
|
|
14
14
|
|
|
15
|
-
STAGE_MAP_HEADING = re.compile(r"^##\s+
|
|
15
|
+
STAGE_MAP_HEADING = re.compile(r"^##\s+5\.5\s+Stage\s+Map\b", re.M)
|
|
16
16
|
STAGE_SECTION = re.compile(
|
|
17
|
-
r"^##\s+
|
|
17
|
+
r"^##\s+5\.5\.(\d+)\s+Stage\s+\1\s*:\s*(.+)$", re.M
|
|
18
18
|
)
|
|
19
19
|
REQUIRED_SUBSECTIONS = (
|
|
20
20
|
"Carry-In",
|
|
@@ -48,7 +48,7 @@ class ValidationError:
|
|
|
48
48
|
def _check_stage_map_present(text: str) -> List[ValidationError]:
|
|
49
49
|
if not STAGE_MAP_HEADING.search(text):
|
|
50
50
|
return [ValidationError("S1", 0,
|
|
51
|
-
"section '##
|
|
51
|
+
"section '## 5.5 Stage Map' is missing")]
|
|
52
52
|
return []
|
|
53
53
|
|
|
54
54
|
|
|
@@ -91,15 +91,15 @@ def _parse_stage_map(text: str) -> Tuple[List[StageMeta], List[ValidationError]]
|
|
|
91
91
|
|
|
92
92
|
|
|
93
93
|
def _slice_stage_section(text: str, stage_number: int) -> str:
|
|
94
|
-
"""Return the body of `##
|
|
94
|
+
"""Return the body of `## 5.5.<n> Stage <n>:` up to the next stage heading."""
|
|
95
95
|
start_m = re.search(
|
|
96
|
-
rf"^##\s+
|
|
96
|
+
rf"^##\s+5\.5\.{stage_number}\s+Stage\s+{stage_number}\s*:", text, re.M
|
|
97
97
|
)
|
|
98
98
|
if not start_m:
|
|
99
99
|
return ""
|
|
100
100
|
start = start_m.end()
|
|
101
101
|
nxt = re.search(
|
|
102
|
-
rf"^##\s+
|
|
102
|
+
rf"^##\s+5\.5\.{stage_number + 1}\s+Stage\s+", text[start:], re.M
|
|
103
103
|
)
|
|
104
104
|
return text[start: start + nxt.start()] if nxt else text[start:]
|
|
105
105
|
|
|
@@ -134,10 +134,10 @@ def _check_each_stage_section(text: str, stages: List[StageMeta]) -> List[Valida
|
|
|
134
134
|
errs: List[ValidationError] = []
|
|
135
135
|
for s in stages:
|
|
136
136
|
if not re.search(
|
|
137
|
-
rf"^##\s+
|
|
137
|
+
rf"^##\s+5\.5\.{s.stage_number}\s+Stage\s+{s.stage_number}\s*:", text, re.M
|
|
138
138
|
):
|
|
139
139
|
errs.append(ValidationError("S3", s.stage_number,
|
|
140
|
-
f"stage section '##
|
|
140
|
+
f"stage section '## 5.5.{s.stage_number} Stage {s.stage_number}:' missing"))
|
|
141
141
|
continue
|
|
142
142
|
section = _slice_stage_section(text, s.stage_number)
|
|
143
143
|
|
|
@@ -231,7 +231,7 @@ def _check_parallel_safety(text: str, stages: List[StageMeta]) -> List[Validatio
|
|
|
231
231
|
def collect_validation_errors(text: str) -> List[ValidationError]:
|
|
232
232
|
"""All S1–S9 checks against the report text; empty list means valid.
|
|
233
233
|
|
|
234
|
-
S1 (missing `##
|
|
234
|
+
S1 (missing `## 5.5 Stage Map` heading) makes the rest unparseable, so it
|
|
235
235
|
short-circuits. Shared by `main()` (CLI / implementation entry) and the
|
|
236
236
|
implementation-planning boundary check in validate-run.py — one validator,
|
|
237
237
|
one reference point, enforced at both produce-time and consume-time."""
|
|
@@ -6,12 +6,12 @@ Checks, for a given final-report MD path:
|
|
|
6
6
|
1. ``*.html`` sibling exists.
|
|
7
7
|
2. HTML's ``source-sha256`` in run-meta matches the current MD body —
|
|
8
8
|
stale html detection.
|
|
9
|
-
3. HTML's §
|
|
9
|
+
3. HTML's §5.6 / §5.7 / §5.8 deliverable regions contain no
|
|
10
10
|
``<textarea>`` / ``<input>`` / ``<select>`` (form-attach is
|
|
11
|
-
restricted to §
|
|
11
|
+
restricted to §1 clarification rows).
|
|
12
12
|
4. HTML has no external URLs in ``<script src=>`` / ``<link href=>`` /
|
|
13
13
|
``<img src=>`` — self-contained guarantee.
|
|
14
|
-
5. Every Response ID in HTML matches the §
|
|
14
|
+
5. Every Response ID in HTML matches the §1 Clarification Items table
|
|
15
15
|
of the source MD (1:1).
|
|
16
16
|
|
|
17
17
|
Exit codes: 0 on success, 1 on any failure. Failures are printed one
|
|
@@ -54,7 +54,7 @@ def _main_body(html_text: str) -> str:
|
|
|
54
54
|
|
|
55
55
|
def _no_form_sections(html_body: str) -> list[str]:
|
|
56
56
|
"""Return a list of strings, each being the rendered chunk of a
|
|
57
|
-
no-form section (
|
|
57
|
+
no-form section (5.6 / 5.7 / 5.8) up to the next h2/h3. Used to
|
|
58
58
|
check that no form controls live inside them.
|
|
59
59
|
"""
|
|
60
60
|
chunks: list[str] = []
|
|
@@ -65,7 +65,7 @@ def _no_form_sections(html_body: str) -> list[str]:
|
|
|
65
65
|
]
|
|
66
66
|
for i, m in enumerate(headings):
|
|
67
67
|
text = m.group(2).strip()
|
|
68
|
-
if not text.startswith(("
|
|
68
|
+
if not text.startswith(("5.6", "5.7", "5.8")):
|
|
69
69
|
continue
|
|
70
70
|
start = m.end()
|
|
71
71
|
end = headings[i + 1].start() if i + 1 < len(headings) else len(html_body)
|
|
@@ -82,9 +82,9 @@ def validate(report_path: Path) -> list[str]:
|
|
|
82
82
|
html_path = report_path.with_name(report_path.stem + ".html")
|
|
83
83
|
md_ids = _md_response_ids(md)
|
|
84
84
|
|
|
85
|
-
# (1) sibling artifact exists — conditional on §
|
|
85
|
+
# (1) sibling artifact exists — conditional on §1 clarification rows.
|
|
86
86
|
# The html view's sole value over the MD is its embedded form widgets
|
|
87
|
-
# for §
|
|
87
|
+
# for §1 C-* rows, so a clarification-free report intentionally has no
|
|
88
88
|
# html sibling (see report_views.report_has_clarification_items). When
|
|
89
89
|
# there are no C-* rows and no html, that is the expected skip state.
|
|
90
90
|
if not html_path.is_file():
|
|
@@ -115,7 +115,7 @@ def validate(report_path: Path) -> list[str]:
|
|
|
115
115
|
for chunk in _no_form_sections(html_body):
|
|
116
116
|
if "<textarea" in chunk or "<input" in chunk or "<select" in chunk:
|
|
117
117
|
failures.append(
|
|
118
|
-
"html §
|
|
118
|
+
"html §5.6/§5.7/§5.8 deliverable section contains a form control"
|
|
119
119
|
)
|
|
120
120
|
break
|
|
121
121
|
|
|
@@ -123,14 +123,14 @@ def validate(report_path: Path) -> list[str]:
|
|
|
123
123
|
if _EXTERNAL_URL_RE.search(html_text):
|
|
124
124
|
failures.append("html contains external URL in script/link/img — must be self-contained")
|
|
125
125
|
|
|
126
|
-
# (5) Response ID parity: HTML form rows ↔ §
|
|
126
|
+
# (5) Response ID parity: HTML form rows ↔ §1 C-* rows in MD.
|
|
127
127
|
# Bidirectional — catches both "MD has C-* the HTML lost" AND
|
|
128
128
|
# "HTML has stale C-* that the current MD no longer declares".
|
|
129
129
|
# (md_ids computed once at the top.)
|
|
130
130
|
html_ids = sorted(set(_RESPONSE_ID_ATTR_RE.findall(html_text)))
|
|
131
131
|
if md_ids != html_ids:
|
|
132
132
|
failures.append(
|
|
133
|
-
f"Response ID mismatch: MD §
|
|
133
|
+
f"Response ID mismatch: MD §1 has {md_ids}, HTML has {html_ids}"
|
|
134
134
|
)
|
|
135
135
|
|
|
136
136
|
return failures
|
|
@@ -644,32 +644,32 @@ _DEPRECATED_FINAL_REPORT_PATTERNS: tuple[tuple[re.Pattern, str], ...] = (
|
|
|
644
644
|
"the YAML frontmatter `approved: true|false` field. Delete the body section.",
|
|
645
645
|
),
|
|
646
646
|
(
|
|
647
|
-
re.compile(r"^###[ \t]+
|
|
648
|
-
"deprecated `###
|
|
649
|
-
"to the YAML frontmatter `approved: true|false` field. Delete the §
|
|
647
|
+
re.compile(r"^###[ \t]+5\.5\.8[ \t]+User Approval Request\b", re.MULTILINE),
|
|
648
|
+
"deprecated `### 5.5.8 User Approval Request` stub — approval gate moved "
|
|
649
|
+
"to the YAML frontmatter `approved: true|false` field. Delete the §5.5.8 heading + body.",
|
|
650
650
|
),
|
|
651
651
|
(
|
|
652
|
-
re.compile(r"^###[ \t]+
|
|
653
|
-
"deprecated `###
|
|
654
|
-
"`##
|
|
652
|
+
re.compile(r"^###[ \t]+5\.5\.9[ \t]+Open Questions\b", re.MULTILINE),
|
|
653
|
+
"deprecated `### 5.5.9 Open Questions` block — promote each row into "
|
|
654
|
+
"`## 1. Clarification Items` with `Kind=decision` (and `Blocks=approval` "
|
|
655
655
|
"if it gates the frontmatter approval flag).",
|
|
656
656
|
),
|
|
657
657
|
(
|
|
658
658
|
re.compile(
|
|
659
|
-
r"^###[ \t]+
|
|
659
|
+
r"^###[ \t]+1\.1[ \t]+(?:추가 자료 요청|Additional Materials)\b",
|
|
660
660
|
re.MULTILINE,
|
|
661
661
|
),
|
|
662
|
-
"deprecated `###
|
|
663
|
-
"every clarification item lives as one row of the unified `##
|
|
662
|
+
"deprecated `### 1.1 추가 자료 요청` / `Additional Materials` sub-section — "
|
|
663
|
+
"every clarification item lives as one row of the unified `## 1. "
|
|
664
664
|
"Clarification Items` 8-column table (`Kind=material`).",
|
|
665
665
|
),
|
|
666
666
|
(
|
|
667
667
|
re.compile(
|
|
668
|
-
r"^###[ \t]+
|
|
668
|
+
r"^###[ \t]+1\.2[ \t]+(?:사용자 확인 질문|Questions for the User)\b",
|
|
669
669
|
re.MULTILINE,
|
|
670
670
|
),
|
|
671
|
-
"deprecated `###
|
|
672
|
-
"sub-section — collapse into the unified `##
|
|
671
|
+
"deprecated `### 1.2 사용자 확인 질문` / `Questions for the User` "
|
|
672
|
+
"sub-section — collapse into the unified `## 1. Clarification Items` "
|
|
673
673
|
"8-column table (`Kind=decision`).",
|
|
674
674
|
),
|
|
675
675
|
)
|
|
@@ -708,7 +708,7 @@ def validate_report(
|
|
|
708
708
|
"final report is missing the top-of-report `## Verdict Card` block — "
|
|
709
709
|
"render it between the report header and the (conditional) Approval "
|
|
710
710
|
"block. Its Verdict Token / Direction / Next Step cells must byte-match "
|
|
711
|
-
"the corresponding cells in `##
|
|
711
|
+
"the corresponding cells in `## 7. Final Verdict` and `## 3.` first item."
|
|
712
712
|
)
|
|
713
713
|
|
|
714
714
|
# Reading Confirmation belongs in the worker audit sidecar, not the
|
|
@@ -860,7 +860,7 @@ PLANNING_REQUIRED_SECTIONS = (
|
|
|
860
860
|
"Plan Body Verification",
|
|
861
861
|
)
|
|
862
862
|
|
|
863
|
-
# §
|
|
863
|
+
# §5.7 implementation deliverables — substring scan against report body.
|
|
864
864
|
IMPLEMENTATION_REQUIRED_SECTIONS = (
|
|
865
865
|
"Approved Plan Reference",
|
|
866
866
|
"Commit List",
|
|
@@ -872,7 +872,7 @@ IMPLEMENTATION_REQUIRED_SECTIONS = (
|
|
|
872
872
|
"Routing Recommendation",
|
|
873
873
|
)
|
|
874
874
|
|
|
875
|
-
# §
|
|
875
|
+
# §5.8 final-verification deliverables — substring scan against report body.
|
|
876
876
|
FINAL_VERIFICATION_REQUIRED_SECTIONS = (
|
|
877
877
|
"Source Implementation Report",
|
|
878
878
|
"Acceptance Blockers",
|
|
@@ -892,7 +892,7 @@ FINAL_VERIFICATION_VERDICT_TOKENS = (
|
|
|
892
892
|
"blocked",
|
|
893
893
|
)
|
|
894
894
|
|
|
895
|
-
# `##
|
|
895
|
+
# `## 7. Final Verdict` Verdict Token cell — captures the value between
|
|
896
896
|
# backticks on the `Verdict Token` row. Tolerant to extra column whitespace
|
|
897
897
|
# and to leading bold/italic markers in the label cell.
|
|
898
898
|
_FINAL_VERDICT_TOKEN_RE = re.compile(
|
|
@@ -909,20 +909,20 @@ _VERDICT_CARD_BLOCK_RE = re.compile(
|
|
|
909
909
|
re.DOTALL | re.MULTILINE,
|
|
910
910
|
)
|
|
911
911
|
|
|
912
|
-
# `##
|
|
912
|
+
# `## 7. Final Verdict` block scope — used to scope the Verdict Token
|
|
913
913
|
# regex so that we don't accidentally match a Verdict Token row that
|
|
914
914
|
# lives in the Verdict Card or anywhere else.
|
|
915
915
|
_FINAL_VERDICT_BLOCK_RE = re.compile(
|
|
916
|
-
r"^##[ \t]+
|
|
916
|
+
r"^##[ \t]+7\.[ \t]+Final Verdict[ \t]*$\n(?P<body>.*?)(?=^##[ \t]|\Z)",
|
|
917
917
|
re.DOTALL | re.MULTILINE,
|
|
918
918
|
)
|
|
919
919
|
|
|
920
|
-
# `##
|
|
920
|
+
# `## 5.6 Release Handoff Deliverables` and `## 5.6.6 Merge Conflict
|
|
921
921
|
# Probe` are required when task-type == release-handoff. The probe sub-
|
|
922
922
|
# section was retro-added to the template; old runs that predate it ship
|
|
923
923
|
# without it, but new runs must include it.
|
|
924
924
|
_MERGE_CONFLICT_PROBE_HEADING_RE = re.compile(
|
|
925
|
-
r"^###[ \t]+
|
|
925
|
+
r"^###[ \t]+5\.6\.6[ \t]+Merge Conflict Probe\b", re.MULTILINE
|
|
926
926
|
)
|
|
927
927
|
|
|
928
928
|
PLAN_VERIFY_GATE_VALUES = (
|
|
@@ -932,7 +932,7 @@ PLAN_VERIFY_GATE_VALUES = (
|
|
|
932
932
|
"aborted-non-result",
|
|
933
933
|
)
|
|
934
934
|
|
|
935
|
-
# `Gate result:` line in §
|
|
935
|
+
# `Gate result:` line in §5.5.9 of the final report — captures the value
|
|
936
936
|
# token that follows. Tolerates arbitrary markdown formatting between the
|
|
937
937
|
# label and the value (backticks for inline code, double-asterisks for
|
|
938
938
|
# bold, colons, hyphens, whitespace). The captured value is then
|
|
@@ -954,8 +954,8 @@ _FRONTMATTER_BLOCK_RE = re.compile(r"\A---\n(.*?)\n---\n", re.DOTALL)
|
|
|
954
954
|
|
|
955
955
|
|
|
956
956
|
def _extract_final_verdict_token(content: str) -> str | None:
|
|
957
|
-
"""Return the `Verdict Token` cell value from the `##
|
|
958
|
-
block, or None when the row is absent. Scoped to §
|
|
957
|
+
"""Return the `Verdict Token` cell value from the `## 7. Final Verdict`
|
|
958
|
+
block, or None when the row is absent. Scoped to §7 so the Verdict
|
|
959
959
|
Card row (which has the same shape) does not shadow the authoritative
|
|
960
960
|
value.
|
|
961
961
|
"""
|
|
@@ -980,7 +980,7 @@ def _extract_verdict_card_token(content: str) -> str | None:
|
|
|
980
980
|
|
|
981
981
|
|
|
982
982
|
def _validate_verdict_card_consistency(content: str, failures: list[str]) -> None:
|
|
983
|
-
"""Verdict Card is a non-authoritative index of §
|
|
983
|
+
"""Verdict Card is a non-authoritative index of §7. If both blocks
|
|
984
984
|
carry a Verdict Token row, the values MUST byte-match (modulo case
|
|
985
985
|
and surrounding whitespace) — divergence is a contract violation per
|
|
986
986
|
`okstra-report-writer` SKILL.md "Authoring Contract".
|
|
@@ -988,15 +988,15 @@ def _validate_verdict_card_consistency(content: str, failures: list[str]) -> Non
|
|
|
988
988
|
card_value = _extract_verdict_card_token(content)
|
|
989
989
|
final_value = _extract_final_verdict_token(content)
|
|
990
990
|
if card_value is None or final_value is None:
|
|
991
|
-
# Missing-Card and missing-§
|
|
991
|
+
# Missing-Card and missing-§7 are surfaced by other checks; this
|
|
992
992
|
# function only enforces the consistency contract between the two.
|
|
993
993
|
return
|
|
994
994
|
if card_value.strip().lower() != final_value.strip().lower():
|
|
995
995
|
failures.append(
|
|
996
996
|
"Verdict Card `Verdict Token` value "
|
|
997
|
-
f"`{card_value}` does not match `##
|
|
997
|
+
f"`{card_value}` does not match `## 7. Final Verdict` value "
|
|
998
998
|
f"`{final_value}` — the Card is a non-authoritative index and "
|
|
999
|
-
"MUST byte-match §
|
|
999
|
+
"MUST byte-match §7. Either fix the Card or update §7; do not "
|
|
1000
1000
|
"ship divergent values."
|
|
1001
1001
|
)
|
|
1002
1002
|
|
|
@@ -1161,7 +1161,7 @@ def _load_stage_validator():
|
|
|
1161
1161
|
|
|
1162
1162
|
def _append_stage_structure_failures(content: str, failures: list[str]) -> None:
|
|
1163
1163
|
"""Enforce the Stage Map structural contract at the implementation-planning
|
|
1164
|
-
boundary. Without this, a plan missing `##
|
|
1164
|
+
boundary. Without this, a plan missing `## 5.5 Stage Map` passes the
|
|
1165
1165
|
planning gate, gets approved, and only fails later at the `implementation`
|
|
1166
1166
|
entry (validators/validate-implementation-plan-stages.py via
|
|
1167
1167
|
prepare_task_bundle). Running the same validator here moves the failure to
|
|
@@ -1189,7 +1189,7 @@ def validate_phase_boundary(
|
|
|
1189
1189
|
skipped its core outputs (or an implementation run that ran under the
|
|
1190
1190
|
wrong task type).
|
|
1191
1191
|
|
|
1192
|
-
Additionally enforces the Plan Body Verification gate (§
|
|
1192
|
+
Additionally enforces the Plan Body Verification gate (§5.5.9):
|
|
1193
1193
|
- gate ∈ {passed, passed-with-dissent} → top-level Approval checkbox
|
|
1194
1194
|
MUST be present.
|
|
1195
1195
|
- gate ∈ {blocked-by-disagreement, aborted-non-result} → checkbox
|
|
@@ -1200,7 +1200,7 @@ def validate_phase_boundary(
|
|
|
1200
1200
|
return
|
|
1201
1201
|
content = report_path.read_text()
|
|
1202
1202
|
|
|
1203
|
-
# Verdict Card vs §
|
|
1203
|
+
# Verdict Card vs §7. Final Verdict Verdict Token consistency. The Card
|
|
1204
1204
|
# is a non-authoritative index; divergence is a contract violation.
|
|
1205
1205
|
_validate_verdict_card_consistency(content, failures)
|
|
1206
1206
|
|
|
@@ -1208,7 +1208,7 @@ def validate_phase_boundary(
|
|
|
1208
1208
|
for needle in IMPLEMENTATION_REQUIRED_SECTIONS:
|
|
1209
1209
|
if needle not in content:
|
|
1210
1210
|
failures.append(
|
|
1211
|
-
"implementation report is missing required §
|
|
1211
|
+
"implementation report is missing required §5.7 "
|
|
1212
1212
|
f"deliverable section: `{needle}`"
|
|
1213
1213
|
)
|
|
1214
1214
|
|
|
@@ -1216,13 +1216,13 @@ def validate_phase_boundary(
|
|
|
1216
1216
|
for needle in FINAL_VERIFICATION_REQUIRED_SECTIONS:
|
|
1217
1217
|
if needle not in content:
|
|
1218
1218
|
failures.append(
|
|
1219
|
-
"final-verification report is missing required §
|
|
1219
|
+
"final-verification report is missing required §5.8 "
|
|
1220
1220
|
f"deliverable section: `{needle}`"
|
|
1221
1221
|
)
|
|
1222
1222
|
token_value = _extract_final_verdict_token(content)
|
|
1223
1223
|
if token_value is None:
|
|
1224
1224
|
failures.append(
|
|
1225
|
-
"final-verification report `##
|
|
1225
|
+
"final-verification report `## 7. Final Verdict` table is "
|
|
1226
1226
|
"missing the `Verdict Token` row — required by the release-"
|
|
1227
1227
|
"handoff entry gate."
|
|
1228
1228
|
)
|
|
@@ -1236,7 +1236,7 @@ def validate_phase_boundary(
|
|
|
1236
1236
|
if task_type == "release-handoff":
|
|
1237
1237
|
if _MERGE_CONFLICT_PROBE_HEADING_RE.search(content) is None:
|
|
1238
1238
|
failures.append(
|
|
1239
|
-
"release-handoff report is missing `###
|
|
1239
|
+
"release-handoff report is missing `### 5.6.6 Merge Conflict "
|
|
1240
1240
|
"Probe` sub-section — required by the release-handoff profile "
|
|
1241
1241
|
"(self-review 6, merge-conflict probe audit). When the run is "
|
|
1242
1242
|
"`local only` / `skip`, record the single line `- Not run "
|
|
@@ -1260,7 +1260,7 @@ def validate_phase_boundary(
|
|
|
1260
1260
|
if "Plan Body Verification" in content:
|
|
1261
1261
|
failures.append(
|
|
1262
1262
|
"implementation-planning report has `Plan Body Verification` "
|
|
1263
|
-
"section but no `Gate result:` line — required by §
|
|
1263
|
+
"section but no `Gate result:` line — required by §5.5.9."
|
|
1264
1264
|
)
|
|
1265
1265
|
return
|
|
1266
1266
|
gate_value = gate_match.group("value").strip().lower()
|
|
@@ -1655,7 +1655,7 @@ def _rerender_report_views_after_autofix(report_path: Path) -> str:
|
|
|
1655
1655
|
except Exception as exc: # noqa: BLE001
|
|
1656
1656
|
return f"report-views re-render failed: {exc}"
|
|
1657
1657
|
if rendered is None:
|
|
1658
|
-
return "report-views skipped (no §
|
|
1658
|
+
return "report-views skipped (no §1 clarification rows)"
|
|
1659
1659
|
return "report-views re-rendered"
|
|
1660
1660
|
|
|
1661
1661
|
|
|
@@ -205,9 +205,9 @@ def _check_candidates_table(
|
|
|
205
205
|
body: str, brief_frontmatter: dict, errors: list[str]
|
|
206
206
|
) -> None:
|
|
207
207
|
"""Items 1–7 coordinator — parse table, check header, delegate cap and rows."""
|
|
208
|
-
rows = _read_section_table(body, "
|
|
208
|
+
rows = _read_section_table(body, "5.9 Improvement Candidates")
|
|
209
209
|
if not rows:
|
|
210
|
-
errors.append("missing or empty `##
|
|
210
|
+
errors.append("missing or empty `## 5.9 Improvement Candidates` table")
|
|
211
211
|
return
|
|
212
212
|
header, *data = rows
|
|
213
213
|
expected_columns = [
|
|
@@ -216,7 +216,7 @@ def _check_candidates_table(
|
|
|
216
216
|
]
|
|
217
217
|
if header != expected_columns:
|
|
218
218
|
errors.append(
|
|
219
|
-
f"`##
|
|
219
|
+
f"`## 5.9 Improvement Candidates` header must be {expected_columns}, "
|
|
220
220
|
f"got {header}"
|
|
221
221
|
)
|
|
222
222
|
_check_candidates_cap(len(data), brief_frontmatter, errors)
|
|
@@ -228,18 +228,18 @@ def _check_candidates_table(
|
|
|
228
228
|
def _check_final_verdict(
|
|
229
229
|
body: str, errors: list[str]
|
|
230
230
|
) -> list[list[str]]:
|
|
231
|
-
"""Item 8 — ##
|
|
231
|
+
"""Item 8 — ## 7. Final Verdict verdict token is in enum.
|
|
232
232
|
|
|
233
233
|
Returns the parsed final_verdict_rows (empty list when absent).
|
|
234
234
|
"""
|
|
235
|
-
final_verdict_rows = _read_section_table(body, "
|
|
235
|
+
final_verdict_rows = _read_section_table(body, "7. Final Verdict")
|
|
236
236
|
if not final_verdict_rows:
|
|
237
|
-
errors.append("missing `##
|
|
237
|
+
errors.append("missing `## 7. Final Verdict` block")
|
|
238
238
|
return []
|
|
239
239
|
token_cell = final_verdict_rows[-1][0] if final_verdict_rows[-1] else ""
|
|
240
240
|
if token_cell not in _VERDICT_TOKENS:
|
|
241
241
|
errors.append(
|
|
242
|
-
f"`##
|
|
242
|
+
f"`## 7. Final Verdict` Verdict Token '{token_cell}' must be one of {_VERDICT_TOKENS}"
|
|
243
243
|
)
|
|
244
244
|
return final_verdict_rows
|
|
245
245
|
|
|
@@ -253,7 +253,7 @@ def _check_verdict_card(
|
|
|
253
253
|
errors.append("missing `## Verdict Card` block")
|
|
254
254
|
return
|
|
255
255
|
if final_verdict_rows and verdict_card_rows[-1] != final_verdict_rows[-1]:
|
|
256
|
-
errors.append("Verdict Card row must byte-match `##
|
|
256
|
+
errors.append("Verdict Card row must byte-match `## 7. Final Verdict` row")
|
|
257
257
|
|
|
258
258
|
|
|
259
259
|
def validate_improvement_report(
|
package/src/_python-helper.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { resolvePaths } from "./paths.mjs";
|
|
2
|
+
import { buildPythonpath, resolvePaths } from "./paths.mjs";
|
|
3
3
|
|
|
4
4
|
export async function runPythonSnippet({ script, args = [], extraEnv = {} }) {
|
|
5
5
|
const paths = await resolvePaths();
|
|
6
6
|
return new Promise((resolve) => {
|
|
7
7
|
const child = spawn("python3", ["-c", script, ...args], {
|
|
8
8
|
stdio: ["ignore", "pipe", "pipe"],
|
|
9
|
-
env: { ...process.env, PYTHONPATH: paths
|
|
9
|
+
env: { ...process.env, PYTHONPATH: buildPythonpath(paths), ...extraEnv },
|
|
10
10
|
});
|
|
11
11
|
let stdout = "";
|
|
12
12
|
let stderr = "";
|
|
@@ -22,7 +22,7 @@ export async function runPythonModule({ module, args = [], extraEnv = {}, stdio
|
|
|
22
22
|
return new Promise((resolve) => {
|
|
23
23
|
const child = spawn("python3", ["-m", module, ...args], {
|
|
24
24
|
stdio: stdio === "capture" ? ["ignore", "pipe", "pipe"] : ["ignore", "inherit", "inherit"],
|
|
25
|
-
env: { ...process.env, PYTHONPATH: paths
|
|
25
|
+
env: { ...process.env, PYTHONPATH: buildPythonpath(paths), ...extraEnv },
|
|
26
26
|
});
|
|
27
27
|
let stdout = "";
|
|
28
28
|
let stderr = "";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { runPythonModule } from "./_python-helper.mjs";
|
|
2
|
+
|
|
3
|
+
const USAGE = `okstra context-cost — estimate context/read cost for a task bundle
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
okstra context-cost <task-root>
|
|
7
|
+
okstra context-cost <task-key> --project-root <dir>
|
|
8
|
+
okstra context-cost <task-key> --cwd <dir>
|
|
9
|
+
|
|
10
|
+
Output: JSON with task file counts, current-run counts, lead Phase 1 read
|
|
11
|
+
surface, analysis-worker initial read surface, and report-writer synthesis
|
|
12
|
+
surface. This is read-only; it never mutates task artifacts.
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
export async function run(args) {
|
|
16
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
17
|
+
process.stdout.write(USAGE);
|
|
18
|
+
return 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const result = await runPythonModule({
|
|
22
|
+
module: "okstra_ctl.context_cost",
|
|
23
|
+
args,
|
|
24
|
+
stdio: "inherit-stdout",
|
|
25
|
+
});
|
|
26
|
+
return result.code ?? 0;
|
|
27
|
+
}
|
package/src/install.mjs
CHANGED