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.
- package/README.kr.md +4 -0
- package/README.md +4 -0
- package/bin/okstra +1 -0
- package/docs/kr/architecture.md +74 -13
- package/docs/kr/cli.md +6 -1
- package/docs/superpowers/plans/2026-05-17-dual-format-final-report.md +167 -0
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/claude-worker.md +2 -2
- package/runtime/agents/workers/codex-worker.md +1 -1
- package/runtime/agents/workers/gemini-worker.md +1 -1
- package/runtime/agents/workers/report-writer-worker.md +3 -1
- package/runtime/bin/okstra-render-report-views.py +129 -0
- package/runtime/prompts/profiles/implementation-planning.md +1 -1
- package/runtime/python/okstra_ctl/report_views.py +701 -0
- package/runtime/python/okstra_ctl/wizard.py +220 -14
- package/runtime/skills/okstra-brief/SKILL.md +73 -35
- package/runtime/skills/okstra-report-writer/SKILL.md +19 -4
- package/runtime/skills/okstra-team-contract/SKILL.md +2 -5
- package/runtime/templates/reports/final-report.template.md +169 -2
- package/runtime/templates/reports/report.css +151 -0
- package/runtime/templates/reports/report.js +163 -0
- package/runtime/templates/reports/user-response.template.md +69 -0
- package/runtime/validators/lib/fixtures.sh +40 -3
- package/runtime/validators/validate-report-views.py +283 -0
- package/runtime/validators/validate-run.py +251 -3
- package/runtime/validators/validate-workflow.sh +4 -0
- package/src/install.mjs +1 -0
- package/src/render-views.mjs +67 -0
|
@@ -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).
|
|
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.
|