okstra 0.28.0 → 0.30.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.
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env python3
2
+ """CLI entrypoint for Phase 7 step 1.5 — render two derived views of
3
+ an okstra final-report markdown.
4
+
5
+ Usage:
6
+ okstra-render-report-views.py <path-to-final-report.md>
7
+ [--task-key <task-group/task-id>]
8
+ [--task-type <profile>]
9
+ [--seq <NNN>]
10
+ [--source-report <relative-path>]
11
+
12
+ When the optional flags are omitted, the script infers what it can from
13
+ the report path (``runs/<task-type>/reports/final-report-<task-type>-<seq>.md``)
14
+ and the report's frontmatter / ``- Task Key:`` / ``- Task Type:`` lines.
15
+
16
+ Outputs (idempotent — overwrites):
17
+ - <stem>.slim.md — token-saving copy for the next-phase lead prompt
18
+ - <stem>.html — single-file self-contained HTML view
19
+
20
+ This script is the canonical single-reference-point. The Node CLI
21
+ (``bin/okstra render-views``) is a thin wrapper that spawns it.
22
+ """
23
+ from __future__ import annotations
24
+
25
+ import argparse
26
+ import os
27
+ import re
28
+ import sys
29
+ from pathlib import Path
30
+
31
+ REPO_ROOT = Path(__file__).resolve().parents[1]
32
+ SCRIPTS_DIR = REPO_ROOT / "scripts"
33
+ HOME_LIB = Path(os.environ.get("OKSTRA_HOME", str(Path.home() / ".okstra"))) / "lib" / "python"
34
+
35
+ # Prefer dev sources when present, fall back to install. Without this
36
+ # order, a stale install (~/.okstra/lib/python/okstra_ctl) without the
37
+ # report_views module shadows the in-repo copy during development.
38
+ if (SCRIPTS_DIR / "okstra_ctl" / "report_views.py").is_file():
39
+ if str(SCRIPTS_DIR) in sys.path:
40
+ sys.path.remove(str(SCRIPTS_DIR))
41
+ sys.path.insert(0, str(SCRIPTS_DIR))
42
+ if HOME_LIB.is_dir() and str(HOME_LIB) not in sys.path:
43
+ sys.path.append(str(HOME_LIB))
44
+ elif HOME_LIB.is_dir() and str(HOME_LIB) not in sys.path:
45
+ sys.path.insert(0, str(HOME_LIB))
46
+
47
+ from okstra_ctl.report_views import RunMeta, render_both_views # noqa: E402
48
+
49
+
50
+ _TEMPLATES_DIRS = (
51
+ REPO_ROOT / "templates" / "reports",
52
+ Path.home() / ".okstra" / "lib" / "templates" / "reports",
53
+ )
54
+
55
+ # task-type itself can contain hyphens (``implementation-planning``,
56
+ # ``final-verification``, ``release-handoff``), so the filename segment
57
+ # between ``final-report-`` and ``-<seq>.md`` is matched greedily; the
58
+ # trailing ``-(?P<seq>\d+)\.md$`` anchor pins seq to the last numeric tail.
59
+ _PATH_RE = re.compile(
60
+ r"runs/(?P<task_type>[^/]+)/reports/final-report-.+-(?P<seq>\d+)\.md$"
61
+ )
62
+
63
+
64
+ def _load_assets() -> tuple[str, str]:
65
+ css_text: str | None = None
66
+ js_text: str | None = None
67
+ for d in _TEMPLATES_DIRS:
68
+ css = d / "report.css"
69
+ js = d / "report.js"
70
+ if css.is_file() and js.is_file():
71
+ css_text = css.read_text(encoding="utf-8")
72
+ js_text = js.read_text(encoding="utf-8")
73
+ break
74
+ if css_text is None or js_text is None:
75
+ raise SystemExit(
76
+ "report.css / report.js not found. Looked under: "
77
+ + ", ".join(str(d) for d in _TEMPLATES_DIRS)
78
+ )
79
+ return css_text, js_text
80
+
81
+
82
+ def _infer_from_path(path: Path) -> dict[str, str]:
83
+ posix = path.as_posix()
84
+ m = _PATH_RE.search(posix)
85
+ if m:
86
+ return {"task_type": m.group("task_type"), "seq": m.group("seq")}
87
+ return {}
88
+
89
+
90
+ def _infer_from_body(text: str) -> dict[str, str]:
91
+ found: dict[str, str] = {}
92
+ for label, key in (("Task Key", "task_key"), ("Task Type", "task_type")):
93
+ m = re.search(rf"^- {label}:\s*(\S.*?)\s*$", text, re.MULTILINE)
94
+ if m:
95
+ found[key] = m.group(1)
96
+ return found
97
+
98
+
99
+ def main(argv: list[str] | None = None) -> int:
100
+ parser = argparse.ArgumentParser(
101
+ description="Render slim AI + self-contained HTML views of an okstra final-report."
102
+ )
103
+ parser.add_argument("report_path", type=Path)
104
+ parser.add_argument("--task-key", default=None)
105
+ parser.add_argument("--task-type", default=None)
106
+ parser.add_argument("--seq", default=None)
107
+ parser.add_argument("--source-report", default=None)
108
+ args = parser.parse_args(argv)
109
+
110
+ report_path: Path = args.report_path.resolve()
111
+ if not report_path.is_file():
112
+ parser.error(f"final-report not found: {report_path}")
113
+
114
+ inferred = {**_infer_from_path(report_path), **_infer_from_body(report_path.read_text(encoding="utf-8"))}
115
+ task_key = args.task_key or inferred.get("task_key") or "unknown"
116
+ task_type = args.task_type or inferred.get("task_type") or "unknown"
117
+ seq = args.seq or inferred.get("seq") or "000"
118
+ source_report = args.source_report or report_path.name
119
+
120
+ css, js = _load_assets()
121
+ meta = RunMeta(task_key=task_key, task_type=task_type, seq=seq, source_report=source_report)
122
+ slim_path, html_path = render_both_views(report_path, run_meta=meta, css=css, js=js)
123
+ print(f"slim: {slim_path}")
124
+ print(f"html: {html_path}")
125
+ return 0
126
+
127
+
128
+ if __name__ == "__main__":
129
+ sys.exit(main())
@@ -69,7 +69,7 @@
69
69
  - dependency / migration risk assessment (ordering constraints, data backfills, feature-flag prerequisites, repo-internal sequencing)
70
70
  - validation checklist (pre / mid / post) — each item is an exact command or observable outcome
71
71
  - rollback strategy — exact revert path (commits, flags, migrations) and the signal that triggers rollback
72
- - explicit `User Approval Request (사용자 승인 게이트)` block placed at the **top of the report** with a single canonical checkbox marker `- [ ] Approved` (user toggles to `- [x] Approved` to authorise the next `implementation` run). Section `4.5.8` is retained only as a back-pointer to this top block for validator/key-substring compatibility it must NOT carry an independent marker.
72
+ - explicit `User Approval Request (사용자 승인 게이트)` block placed at the **top of the report** with a single canonical checkbox marker `- [ ] Approved` (user toggles to `- [x] Approved` to authorise the next `implementation` run). The `User Approval Request` validator key-substring is satisfied by this top block do NOT recreate a `### 4.5.8 User Approval Request` body stub (the validator now fails reports that contain one, see `validators/validate-run.py` and `templates/reports/final-report.template.md` §4.5.8).
73
73
  - **the marker line is rendered only when the plan-body verification gate (§4.5.9) returns `passed` or `passed-with-dissent`.** When the gate returns `blocked-by-disagreement` or `aborted-non-result`, the top-of-report Approval block is rendered **without** the canonical `- [ ] Approved` bullet (the rest of the block — title, summary, audit lines — stays). The `validators/validate-run.py` `validate_phase_boundary` function enforces this exact correspondence between gate result and marker line presence.
74
74
  - every ambiguity flagged during pre-planning that the user must resolve before approval registered as a `Blocks=approval` row in the `## 5. Clarification Items` table (do NOT create a separate `Open Questions` block under `4.5.x` — the unified table is the single home)
75
75
  - **§4.5.9 Plan Body Verification (BLOCKING).** After report-writer finishes the draft, the lead MUST run a worker peer-review round on the consolidated plan body (sections 4.5.1 – 4.5.7) and populate `### 4.5.9 Plan Body Verification` in the final report. The round protocol, plan-item ID scheme (`P-Opt-*` / `P-Step-*` / `P-Dep-*` / `P-Val-*` / `P-Rb-*`), verdict semantics, gate-result classification, and dissent log format are defined in `skills/okstra-convergence/SKILL.md` "Plan-body verification mode". The four gate-result values are `passed`, `passed-with-dissent`, `blocked-by-disagreement`, `aborted-non-result`. When the gate would have been `blocked-by-disagreement` or `aborted-non-result`, the lead MUST NOT silently flip it to one of the passing values to "unblock" the run — that is a contract violation.