okstra 0.52.0 → 0.54.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 -1
- package/README.md +1 -1
- package/docs/kr/architecture.md +1 -0
- package/docs/kr/cli.md +2 -1
- package/docs/superpowers/plans/2026-06-06-final-verification-whole-task-gate.md +993 -0
- package/docs/superpowers/plans/2026-06-06-stage-parallel-and-pending-fixes.md +93 -0
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p1.md +447 -0
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p2.md +289 -0
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p3.md +774 -0
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p4.md +303 -0
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p5-multidep-base.md +387 -0
- package/docs/superpowers/specs/2026-06-06-final-verification-whole-task-gate-design.md +126 -0
- package/docs/superpowers/specs/2026-06-06-stage-worktree-isolation-design.md +180 -0
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/report-writer-worker.md +1 -0
- package/runtime/bin/lib/okstra/cli.sh +5 -1
- package/runtime/bin/okstra.sh +1 -0
- package/runtime/prompts/launch.template.md +1 -0
- package/runtime/prompts/profiles/_implementation-deliverable.md +1 -1
- package/runtime/prompts/profiles/_implementation-executor.md +16 -9
- package/runtime/prompts/profiles/_implementation-verifier.md +4 -1
- package/runtime/prompts/profiles/final-verification.md +8 -7
- package/runtime/prompts/profiles/implementation-planning.md +8 -4
- package/runtime/prompts/wizard/prompts.ko.json +3 -2
- package/runtime/python/okstra_ctl/analysis_packet.py +14 -2
- package/runtime/python/okstra_ctl/render.py +3 -0
- package/runtime/python/okstra_ctl/run.py +541 -41
- package/runtime/python/okstra_ctl/wizard.py +25 -7
- package/runtime/python/okstra_ctl/worktree.py +126 -9
- package/runtime/python/okstra_ctl/worktree_registry.py +88 -17
- package/runtime/schemas/final-report-v1.0.schema.json +36 -0
- package/runtime/skills/okstra-convergence/SKILL.md +14 -3
- package/runtime/skills/okstra-run/SKILL.md +1 -1
- package/runtime/templates/reports/final-report.template.md +12 -0
- package/runtime/templates/reports/final-verification-input.template.md +8 -5
- package/runtime/templates/reports/i18n/en.json +3 -1
- package/runtime/templates/reports/i18n/ko.json +3 -1
- package/runtime/validators/validate-run.py +143 -1
- package/runtime/validators/validate-workflow.sh +6 -1
|
@@ -313,6 +313,18 @@ def extract_contract(
|
|
|
313
313
|
}
|
|
314
314
|
|
|
315
315
|
|
|
316
|
+
def effective_run_task_type(run_manifest: dict, task_manifest: dict) -> str:
|
|
317
|
+
"""Return the task type for the specific run being validated.
|
|
318
|
+
|
|
319
|
+
`task-manifest.json` is mutable lifecycle state and may point at a later
|
|
320
|
+
phase after the user has continued the task. A final-report belongs to the
|
|
321
|
+
immutable run-manifest, so run-manifest wins here.
|
|
322
|
+
"""
|
|
323
|
+
return str(
|
|
324
|
+
run_manifest.get("taskType") or task_manifest.get("taskType") or ""
|
|
325
|
+
).strip()
|
|
326
|
+
|
|
327
|
+
|
|
316
328
|
def validate_team_state(
|
|
317
329
|
team_state: dict, project_root: Path, contract: dict, failures: list[str]
|
|
318
330
|
) -> None:
|
|
@@ -857,6 +869,7 @@ PLANNING_REQUIRED_SECTIONS = (
|
|
|
857
869
|
"Dependency",
|
|
858
870
|
"Validation Checklist",
|
|
859
871
|
"Rollback",
|
|
872
|
+
"Requirement Coverage",
|
|
860
873
|
"Plan Body Verification",
|
|
861
874
|
)
|
|
862
875
|
|
|
@@ -951,6 +964,11 @@ _APPROVED_FRONTMATTER_RE = re.compile(
|
|
|
951
964
|
re.IGNORECASE | re.MULTILINE,
|
|
952
965
|
)
|
|
953
966
|
_FRONTMATTER_BLOCK_RE = re.compile(r"\A---\n(.*?)\n---\n", re.DOTALL)
|
|
967
|
+
_REQUIREMENT_COVERAGE_HEADING_RE = re.compile(
|
|
968
|
+
r"^###[ \t]+(?:5\.5\.8[ \t]+)?Requirement Coverage\b",
|
|
969
|
+
re.IGNORECASE | re.MULTILINE,
|
|
970
|
+
)
|
|
971
|
+
_NEXT_THIRD_LEVEL_HEADING_RE = re.compile(r"^###[ \t]+", re.MULTILINE)
|
|
954
972
|
|
|
955
973
|
|
|
956
974
|
def _extract_final_verdict_token(content: str) -> str | None:
|
|
@@ -968,6 +986,115 @@ def _extract_final_verdict_token(content: str) -> str | None:
|
|
|
968
986
|
return match.group("value")
|
|
969
987
|
|
|
970
988
|
|
|
989
|
+
def _split_markdown_row(line: str) -> list[str]:
|
|
990
|
+
stripped = line.strip()
|
|
991
|
+
if stripped.startswith("|"):
|
|
992
|
+
stripped = stripped[1:]
|
|
993
|
+
if stripped.endswith("|"):
|
|
994
|
+
stripped = stripped[:-1]
|
|
995
|
+
return [cell.strip().strip("`").strip() for cell in stripped.split("|")]
|
|
996
|
+
|
|
997
|
+
|
|
998
|
+
def _is_markdown_separator(line: str) -> bool:
|
|
999
|
+
stripped = line.strip()
|
|
1000
|
+
if not stripped.startswith("|"):
|
|
1001
|
+
return False
|
|
1002
|
+
for cell in stripped.strip("|").split("|"):
|
|
1003
|
+
if not re.fullmatch(r"\s*:?-{2,}:?\s*", cell):
|
|
1004
|
+
return False
|
|
1005
|
+
return True
|
|
1006
|
+
|
|
1007
|
+
|
|
1008
|
+
def _append_requirement_coverage_failures(
|
|
1009
|
+
content: str,
|
|
1010
|
+
gate_value: str,
|
|
1011
|
+
failures: list[str],
|
|
1012
|
+
) -> None:
|
|
1013
|
+
"""Validate implementation-planning §5.5.8 requirement coverage.
|
|
1014
|
+
|
|
1015
|
+
The table is intentionally lightweight: it cannot prove semantic truth, but
|
|
1016
|
+
it makes requirement-to-plan mapping explicit and blocks publishable plans
|
|
1017
|
+
that admit uncovered requirements.
|
|
1018
|
+
"""
|
|
1019
|
+
heading = _REQUIREMENT_COVERAGE_HEADING_RE.search(content)
|
|
1020
|
+
if heading is None:
|
|
1021
|
+
failures.append(
|
|
1022
|
+
"implementation-planning report is missing `Requirement Coverage` "
|
|
1023
|
+
"section — every task-brief requirement must map to option/stage/step."
|
|
1024
|
+
)
|
|
1025
|
+
return
|
|
1026
|
+
|
|
1027
|
+
rest = content[heading.end():]
|
|
1028
|
+
next_heading = _NEXT_THIRD_LEVEL_HEADING_RE.search(rest)
|
|
1029
|
+
section = rest[: next_heading.start()] if next_heading else rest
|
|
1030
|
+
lines = section.splitlines()
|
|
1031
|
+
|
|
1032
|
+
header_idx = -1
|
|
1033
|
+
headers: list[str] = []
|
|
1034
|
+
for idx, line in enumerate(lines):
|
|
1035
|
+
if not line.lstrip().startswith("|"):
|
|
1036
|
+
continue
|
|
1037
|
+
cells = [c.lower() for c in _split_markdown_row(line)]
|
|
1038
|
+
if "id" in cells and "requirement" in cells and "status" in cells:
|
|
1039
|
+
header_idx = idx
|
|
1040
|
+
headers = cells
|
|
1041
|
+
break
|
|
1042
|
+
if header_idx < 0:
|
|
1043
|
+
failures.append(
|
|
1044
|
+
"implementation-planning Requirement Coverage section has no table "
|
|
1045
|
+
"with `ID`, `Requirement`, and `Status` columns."
|
|
1046
|
+
)
|
|
1047
|
+
return
|
|
1048
|
+
|
|
1049
|
+
id_col = headers.index("id")
|
|
1050
|
+
status_col = headers.index("status")
|
|
1051
|
+
rows: list[tuple[str, str]] = []
|
|
1052
|
+
body_started = False
|
|
1053
|
+
for line in lines[header_idx + 1:]:
|
|
1054
|
+
if not line.lstrip().startswith("|"):
|
|
1055
|
+
if body_started:
|
|
1056
|
+
break
|
|
1057
|
+
continue
|
|
1058
|
+
if _is_markdown_separator(line):
|
|
1059
|
+
body_started = True
|
|
1060
|
+
continue
|
|
1061
|
+
if not body_started:
|
|
1062
|
+
continue
|
|
1063
|
+
cells = _split_markdown_row(line)
|
|
1064
|
+
if max(id_col, status_col) >= len(cells):
|
|
1065
|
+
failures.append(
|
|
1066
|
+
"implementation-planning Requirement Coverage table has a "
|
|
1067
|
+
f"malformed row: `{line.strip()}`"
|
|
1068
|
+
)
|
|
1069
|
+
continue
|
|
1070
|
+
rows.append((cells[id_col], cells[status_col].lower()))
|
|
1071
|
+
|
|
1072
|
+
if not rows:
|
|
1073
|
+
failures.append(
|
|
1074
|
+
"implementation-planning Requirement Coverage table has no data rows."
|
|
1075
|
+
)
|
|
1076
|
+
return
|
|
1077
|
+
|
|
1078
|
+
for row_id, status in rows:
|
|
1079
|
+
if not re.fullmatch(r"covered|gap|blocked C-\d{3,}", status):
|
|
1080
|
+
failures.append(
|
|
1081
|
+
"implementation-planning Requirement Coverage row "
|
|
1082
|
+
f"`{row_id}` has invalid Status `{status}`; expected "
|
|
1083
|
+
"`covered`, `gap`, or `blocked C-NNN`."
|
|
1084
|
+
)
|
|
1085
|
+
|
|
1086
|
+
if gate_value in ("passed", "passed-with-dissent"):
|
|
1087
|
+
uncovered = [
|
|
1088
|
+
f"{row_id} ({status})"
|
|
1089
|
+
for row_id, status in rows
|
|
1090
|
+
if status != "covered"
|
|
1091
|
+
]
|
|
1092
|
+
if uncovered:
|
|
1093
|
+
failures.append(
|
|
1094
|
+
"implementation-planning Gate result is publishable but "
|
|
1095
|
+
"Requirement Coverage has uncovered row(s): "
|
|
1096
|
+
+ ", ".join(uncovered)
|
|
1097
|
+
)
|
|
971
1098
|
def _extract_verdict_card_token(content: str) -> str | None:
|
|
972
1099
|
"""Return the `Verdict Token` cell from the Verdict Card block."""
|
|
973
1100
|
block = _VERDICT_CARD_BLOCK_RE.search(content)
|
|
@@ -1101,6 +1228,19 @@ def _validate_final_verification_consistency(data: dict, failures: list[str]) ->
|
|
|
1101
1228
|
"when the verdict is `accepted`."
|
|
1102
1229
|
)
|
|
1103
1230
|
|
|
1231
|
+
scope = data.get("verificationScope", "whole-task")
|
|
1232
|
+
if scope not in ("whole-task", "single-stage"):
|
|
1233
|
+
failures.append(
|
|
1234
|
+
f"final-verification: verificationScope must be `whole-task` or "
|
|
1235
|
+
f"`single-stage`, got {scope!r}."
|
|
1236
|
+
)
|
|
1237
|
+
if scope == "single-stage" and "release-handoff" in routing:
|
|
1238
|
+
failures.append(
|
|
1239
|
+
"final-verification: verificationScope `single-stage` cannot recommend "
|
|
1240
|
+
"release-handoff routing — single-stage is a partial verification and "
|
|
1241
|
+
"release-handoff requires whole-task verification."
|
|
1242
|
+
)
|
|
1243
|
+
|
|
1104
1244
|
|
|
1105
1245
|
def validate_report_views(report_path: Path, failures: list[str]) -> None:
|
|
1106
1246
|
"""Enforce Phase 7 step 1.5 (BLOCKING) — the self-contained HTML
|
|
@@ -1292,6 +1432,8 @@ def validate_phase_boundary(
|
|
|
1292
1432
|
"must NOT publish a pre-approved plan when verification did not pass."
|
|
1293
1433
|
)
|
|
1294
1434
|
|
|
1435
|
+
_append_requirement_coverage_failures(content, gate_value, failures)
|
|
1436
|
+
|
|
1295
1437
|
# Only a publishable plan (gate passed) can be flipped to `approved: true`
|
|
1296
1438
|
# and reach the `implementation` entry, so the Stage Map structure is
|
|
1297
1439
|
# enforced only here — a blocked/aborted plan may legitimately be incomplete.
|
|
@@ -1732,7 +1874,7 @@ def main() -> int:
|
|
|
1732
1874
|
validate_report(report_path, contract["required_agent_status_entries"], failures)
|
|
1733
1875
|
validate_team_state_usage(team_state, failures)
|
|
1734
1876
|
|
|
1735
|
-
task_type =
|
|
1877
|
+
task_type = effective_run_task_type(run_manifest, task_manifest)
|
|
1736
1878
|
validate_phase_boundary(task_type, report_path, failures)
|
|
1737
1879
|
if task_type:
|
|
1738
1880
|
validate_worker_results_audit(report_path, task_type, failures)
|
|
@@ -17,7 +17,12 @@ WORKSPACE_APP_PATH="$PROJECT_ROOT"
|
|
|
17
17
|
OKSTRA_SCRIPT="$WORKSPACE_ROOT/scripts/okstra.sh"
|
|
18
18
|
RUN_VALIDATOR_PATH="$WORKSPACE_ROOT/validators/validate-run.py"
|
|
19
19
|
SOURCE_ASSET_ROOT="$WORKSPACE_ROOT/agents"
|
|
20
|
-
|
|
20
|
+
# Arbitrary sample task-type used only to exercise the bundle-prep /
|
|
21
|
+
# discovery-pointer / task-catalog / asset-seeding machinery via render-only
|
|
22
|
+
# run_okstra. Must NOT require --approved-plan (excludes implementation and
|
|
23
|
+
# final-verification) and must have a tests/fixtures/final-report-data/
|
|
24
|
+
# <task-type>-001.data.json sample (used by prepare_run_validator_fixture).
|
|
25
|
+
TASK_TYPE="requirements-discovery"
|
|
21
26
|
PRIMARY_TASK_GROUP="validation"
|
|
22
27
|
PRIMARY_TASK_ID="asset-refresh-and-reference-expectations"
|
|
23
28
|
PRIMARY_BRIEF_FILENAME="validation-brief-primary.md"
|