okstra 0.52.0 → 0.53.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.
Files changed (40) hide show
  1. package/README.kr.md +1 -1
  2. package/README.md +1 -1
  3. package/docs/kr/architecture.md +1 -0
  4. package/docs/kr/cli.md +2 -1
  5. package/docs/superpowers/plans/2026-06-06-final-verification-whole-task-gate.md +993 -0
  6. package/docs/superpowers/plans/2026-06-06-stage-parallel-and-pending-fixes.md +93 -0
  7. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p1.md +447 -0
  8. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p2.md +289 -0
  9. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p3.md +774 -0
  10. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p4.md +303 -0
  11. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p5-multidep-base.md +387 -0
  12. package/docs/superpowers/specs/2026-06-06-final-verification-whole-task-gate-design.md +126 -0
  13. package/docs/superpowers/specs/2026-06-06-stage-worktree-isolation-design.md +180 -0
  14. package/package.json +1 -1
  15. package/runtime/BUILD.json +2 -2
  16. package/runtime/agents/workers/report-writer-worker.md +1 -0
  17. package/runtime/bin/lib/okstra/cli.sh +5 -1
  18. package/runtime/bin/okstra.sh +1 -0
  19. package/runtime/prompts/launch.template.md +1 -0
  20. package/runtime/prompts/profiles/_implementation-deliverable.md +1 -1
  21. package/runtime/prompts/profiles/_implementation-executor.md +16 -9
  22. package/runtime/prompts/profiles/_implementation-verifier.md +4 -1
  23. package/runtime/prompts/profiles/final-verification.md +7 -7
  24. package/runtime/prompts/profiles/implementation-planning.md +8 -4
  25. package/runtime/prompts/wizard/prompts.ko.json +3 -2
  26. package/runtime/python/okstra_ctl/analysis_packet.py +14 -2
  27. package/runtime/python/okstra_ctl/render.py +3 -0
  28. package/runtime/python/okstra_ctl/run.py +541 -41
  29. package/runtime/python/okstra_ctl/wizard.py +25 -7
  30. package/runtime/python/okstra_ctl/worktree.py +126 -9
  31. package/runtime/python/okstra_ctl/worktree_registry.py +88 -17
  32. package/runtime/schemas/final-report-v1.0.schema.json +36 -0
  33. package/runtime/skills/okstra-convergence/SKILL.md +14 -3
  34. package/runtime/skills/okstra-run/SKILL.md +1 -1
  35. package/runtime/templates/reports/final-report.template.md +12 -0
  36. package/runtime/templates/reports/final-verification-input.template.md +8 -5
  37. package/runtime/templates/reports/i18n/en.json +3 -1
  38. package/runtime/templates/reports/i18n/ko.json +3 -1
  39. package/runtime/validators/validate-run.py +143 -1
  40. 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 = str(task_manifest.get("taskType") or run_manifest.get("taskType") or "").strip()
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
- TASK_TYPE="final-verification"
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"